From e678c340a89f8a7d086e022e429df20e1424f570 Mon Sep 17 00:00:00 2001 From: Austin Akers Date: Thu, 21 Nov 2024 16:53:34 -0800 Subject: [PATCH 1/2] re-enabling feature after testing --- .../styles/components/_react-table.scss | 7 +++-- .../instance/browse/BrowseDatatable.js | 7 +++-- src/components/instance/browse/index.js | 30 ++++++++++++------- src/components/shared/DataTable.js | 11 ++++++- src/functions/instance/getTableData.js | 22 +++++++++++++- 5 files changed, 60 insertions(+), 17 deletions(-) diff --git a/src/assets/styles/components/_react-table.scss b/src/assets/styles/components/_react-table.scss index 18e504389..aa2726630 100755 --- a/src/assets/styles/components/_react-table.scss +++ b/src/assets/styles/components/_react-table.scss @@ -18,6 +18,11 @@ min-width: 100px; white-space: nowrap; + &.disabled-column { + background-color: $lighter-grey-overlay !important; + cursor: not-allowed; + } + &.sorted { &.desc { box-shadow: inset 0 -2px 0 0 $lighter-grey-overlay; @@ -26,7 +31,6 @@ &.asc { box-shadow: inset 0 2px 0 0 $lighter-grey-overlay; } - } } } @@ -129,7 +133,6 @@ } } - .paginator { align-items: center; display: flex; diff --git a/src/components/instance/browse/BrowseDatatable.js b/src/components/instance/browse/BrowseDatatable.js index 30e6f5d67..96875b1d6 100644 --- a/src/components/instance/browse/BrowseDatatable.js +++ b/src/components/instance/browse/BrowseDatatable.js @@ -17,7 +17,7 @@ let controller; let controller2; let controller3; -function BrowseDatatable({ tableState, setTableState, activeTable }) { +function BrowseDatatable({ tableState, setTableState, activeTable, tableDescriptionAttributes }) { const navigate = useNavigate(); const { compute_stack_id, schema, table, customer_id } = useParams(); const auth = useStoreState(instanceState, (s) => s.auth); @@ -65,7 +65,7 @@ function BrowseDatatable({ tableState, setTableState, activeTable }) { } controller = new AbortController(); controller2 = new AbortController(); - const { newData, newTotalRecords, newTotalPages, newEntityAttributes, hashAttribute, dataTableColumns, error } = await getTableData({ + const { newData, newTotalRecords, newTotalPages, newEntityAttributes, hashAttribute, dynamicAttributesFromDataTable, dataTableColumns, error } = await getTableData({ schema, table, filtered: tableState.filtered, @@ -103,6 +103,7 @@ function BrowseDatatable({ tableState, setTableState, activeTable }) { newEntityAttributes, hashAttribute, dataTableColumns, + dynamicAttributesFromDataTable, error, }); } @@ -148,6 +149,8 @@ function BrowseDatatable({ tableState, setTableState, activeTable }) { columns={tableState.dataTableColumns || []} data={tableState.tableData || []} error={tableState.error} + tableDescriptionAttributes={tableDescriptionAttributes} + dynamicAttributesFromDataTable={tableState.dynamicAttributesFromDataTable} currentPage={tableState.page} pageSize={tableState.pageSize} totalPages={totalPages || 0} diff --git a/src/components/instance/browse/index.js b/src/components/instance/browse/index.js index 34188dd32..c49e4ded4 100644 --- a/src/components/instance/browse/index.js +++ b/src/components/instance/browse/index.js @@ -64,13 +64,13 @@ function BrowseIndex() { const structure = useStoreState(instanceState, (s) => s.structure); const [entities, setEntities] = useState({ schemas: [], tables: [], activeTable: false }); const [tableState, setTableState] = useState(defaultTableState); + const [tableDescription, setTableDescription] = useState(null); const baseUrl = `/o/${customer_id}/i/${compute_stack_id}/browse`; const showForm = instanceAuths[compute_stack_id]?.super || instanceAuths[compute_stack_id]?.structure === true; const showTableForm = showForm || (instanceAuths[compute_stack_id]?.structure && instanceAuths[compute_stack_id]?.structure?.includes(schema)); const emptyPromptMessage = showForm - ? `Please ${(schema && entities.tables && !entities.tables.length) || !entities.schemas.length ? 'create' : 'choose'} a ${ - schema ? 'table' : `${versionAsFloat >= 4.2 ? 'database' : 'schema'}` - }` + ? `Please ${(schema && entities.tables && !entities.tables.length) || !entities.schemas.length ? 'create' : 'choose'} a ${schema ? 'table' : `${versionAsFloat >= 4.2 ? 'database' : 'schema'}` + }` : "This user has not been granted access to any tables. A super-user must update this user's role."; const [hasHashAttr, setHasHashAttr] = useState(true); @@ -78,18 +78,26 @@ function BrowseIndex() { buildInstanceStructure({ auth, url }); }; - const checkForHashAttribute = () => { - async function check() { - if (table) { + const fetchDescribeTable = async () => { + if (table) { + try { const result = await describeTable({ auth, url, schema, table }); - setHasHashAttr(Boolean(result.hash_attribute)); + setTableDescription(result); + } catch (e) { + addError(e); } } - - check(); }; - useEffect(checkForHashAttribute, [auth, url, schema, table]); + useEffect(() => { + fetchDescribeTable(); + }, [auth, url, schema, table]); + + useEffect(() => { + if (tableDescription) { + setHasHashAttr(Boolean(tableDescription.hash_attribute)); + } + }, [tableDescription]); const validate = () => { if (structure) { @@ -153,7 +161,7 @@ function BrowseIndex() { ) : schema && table && action && entities.activeTable ? ( ) : schema && table && entities.activeTable ? ( - + ) : schema && table && !hasHashAttr ? ( ) : ( diff --git a/src/components/shared/DataTable.js b/src/components/shared/DataTable.js index b3b3c4515..aeb235227 100644 --- a/src/components/shared/DataTable.js +++ b/src/components/shared/DataTable.js @@ -71,6 +71,8 @@ function DataTable({ onRowClick, sorted, loading, + dynamicAttributesFromDataTable, + tableDescriptionAttributes, manual = false, }) { const { headerGroups, page, rows, prepareRow, state, setAllFilters, canPreviousPage, canNextPage, pageOptions, pageCount, gotoPage, nextPage, previousPage, setPageSize } = @@ -111,7 +113,14 @@ function DataTable({ return ( addError({ error: { message: err.message, componentStack } })} FallbackComponent={ErrorFallback}>
- + {loading || localLoading ? (
diff --git a/src/functions/instance/getTableData.js b/src/functions/instance/getTableData.js index 4adeba9f1..6a989c1b5 100644 --- a/src/functions/instance/getTableData.js +++ b/src/functions/instance/getTableData.js @@ -2,6 +2,21 @@ import describeTable from '../api/instance/describeTable'; import searchByValue from '../api/instance/searchByValue'; import searchByConditions from '../api/instance/searchByConditions'; +const getAttributesFromTableData = (tableData, existingAttributes) => { + if (existingAttributes.length >= 8) return []; + const existing = new Map(existingAttributes.map((value, index) => [value, index])); + const extra = new Map(); + for (const dataRow of tableData) { + for (const key of Object.keys(dataRow)) { + if (!existing.has(key)) { + const count = extra.get(key) || 0; + extra.set(key, count + 1); + } + } + } + return Array.from(extra).sort(([, a], [, b]) => b - a).map(([key]) => key).slice(0, 8 - existingAttributes.length); +} + export default async ({ schema, table, filtered, pageSize, onlyCached, sorted, page, auth, url, signal, signal2 }) => { let fetchError = false; let newTotalRecords = 0; @@ -10,6 +25,7 @@ export default async ({ schema, table, filtered, pageSize, onlyCached, sorted, p let allAttributes = false; let hashAttribute = false; let get_attributes = ['*']; + let dynamicAttributesFromDataTable = []; const offset = page * pageSize; try { @@ -76,9 +92,12 @@ export default async ({ schema, table, filtered, pageSize, onlyCached, sorted, p } } + + dynamicAttributesFromDataTable = getAttributesFromTableData(newData, allAttributes) + allAttributes.push(...dynamicAttributesFromDataTable); // sort columns, but keep primary key / hash attribute first, and created and updated last. // NOTE: __created__ and __updated__ might not exist in the schema, only include if they exist. - const orderedColumns = allAttributes.filter((a) => ![hashAttribute, '__createdtime__', '__updatedtime__'].includes(a)).sort(); + const orderedColumns = allAttributes.filter((a) => ![hashAttribute, '__createdtime__', '__updatedtime__'].includes(a)) const newEntityAttributes = orderedColumns.reduce((ac, a) => ({ ...ac, [a]: null }), {}); if (allAttributes.includes('__createdtime__')) orderedColumns.push('__createdtime__'); @@ -97,5 +116,6 @@ export default async ({ schema, table, filtered, pageSize, onlyCached, sorted, p hashAttribute, dataTableColumns, error: fetchError === 'table' ? `You are not authorized to view ${schema}:${table}` : fetchError, + dynamicAttributesFromDataTable, }; }; From 536140f4fd6f3f5ca9908c29bff626be3e879cec Mon Sep 17 00:00:00 2001 From: Austin Akers Date: Thu, 21 Nov 2024 16:54:15 -0800 Subject: [PATCH 2/2] re-enabling feature after testing --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f486ac4eb..f927f09c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "harperdb-studio", - "version": "4.8.1", + "version": "4.8.2", "description": "A UI for HarperDB", "deploymentUrl": "studio.harperdb.io", "private": true,