From cbaeb3cce47cdc7793eddf05743c1534aa266829 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 4 May 2023 10:54:15 -0500 Subject: [PATCH] Fixes for column stats with joins; fix some js console warnings; --- src/qqq/components/misc/SavedFilters.tsx | 23 +++-- src/qqq/pages/records/query/ColumnStats.tsx | 19 +++- src/qqq/pages/records/query/RecordQuery.tsx | 108 +++++++++++--------- src/qqq/utils/qqq/FilterUtils.ts | 32 +++++- 4 files changed, 116 insertions(+), 66 deletions(-) diff --git a/src/qqq/components/misc/SavedFilters.tsx b/src/qqq/components/misc/SavedFilters.tsx index 39103fa..ef52bc6 100644 --- a/src/qqq/components/misc/SavedFilters.tsx +++ b/src/qqq/components/misc/SavedFilters.tsx @@ -294,13 +294,8 @@ function SavedFilters({qController, metaData, tableMetaData, currentSavedFilter, ////////////////////////////////////////////////////////////////// // we don't want this job to go async, so, pass a large timeout // ////////////////////////////////////////////////////////////////// - formData.append("_qStepTimeoutMillis", 60 * 1000); - - const formDataHeaders = { - "content-type": "multipart/form-data; boundary=--------------------------320289315924586491558366", - }; - - const processResult = await qController.processInit(processName, formData, formDataHeaders); + formData.append(QController.STEP_TIMEOUT_MILLIS_PARAM_NAME, 60 * 1000); + const processResult = await qController.processInit(processName, formData, qController.defaultMultipartFormDataHeaders()); if (processResult instanceof QJobError) { const jobError = processResult as QJobError; @@ -346,7 +341,7 @@ function SavedFilters({qController, metaData, tableMetaData, currentSavedFilter, onClose={closeSavedFiltersMenu} keepMounted > - Filter Actions + Filter Actions { hasStorePermission && handleDropdownOptionClick(SAVE_OPTION)}> @@ -382,7 +377,7 @@ function SavedFilters({qController, metaData, tableMetaData, currentSavedFilter, } - Your Filters + Your Filters { savedFilters && savedFilters.length > 0 ? ( savedFilters.map((record: QRecord, index: number) => @@ -413,7 +408,7 @@ function SavedFilters({qController, metaData, tableMetaData, currentSavedFilter, {currentSavedFilter.values.get("label")} { filterIsModified && ( - + ) @@ -430,6 +425,13 @@ function SavedFilters({qController, metaData, tableMetaData, currentSavedFilter, onClose={handleSaveFilterPopupClose} aria-labelledby="alert-dialog-title" aria-describedby="alert-dialog-description" + onKeyPress={(e) => + { + if (e.key == "Enter") + { + handleFilterDialogButtonOnClick(); + } + }} > { currentSavedFilter ? ( @@ -479,7 +481,6 @@ function SavedFilters({qController, metaData, tableMetaData, currentSavedFilter, ):( isDeleteFilter ? ( Are you sure you want to delete the filter {`'${currentSavedFilter?.values.get("label")}'`}? - ):( Are you sure you want to update the filter {`'${currentSavedFilter?.values.get("label")}'`} with the current filter criteria? ) diff --git a/src/qqq/pages/records/query/ColumnStats.tsx b/src/qqq/pages/records/query/ColumnStats.tsx index d540721..fcf8231 100644 --- a/src/qqq/pages/records/query/ColumnStats.tsx +++ b/src/qqq/pages/records/query/ColumnStats.tsx @@ -44,6 +44,7 @@ interface Props { tableMetaData: QTableMetaData; fieldMetaData: QFieldMetaData; + fieldTableName: string; filter: QQueryFilter; } @@ -52,7 +53,7 @@ ColumnStats.defaultProps = { const qController = Client.getInstance(); -function ColumnStats({tableMetaData, fieldMetaData, filter}: Props): JSX.Element +function ColumnStats({tableMetaData, fieldMetaData, fieldTableName, filter}: Props): JSX.Element { const [statusString, setStatusString] = useState("Calculating statistics..."); const [loading, setLoading] = useState(true); @@ -73,9 +74,11 @@ function ColumnStats({tableMetaData, fieldMetaData, filter}: Props): JSX.Element (async () => { + const fullFieldName = (fieldTableName == tableMetaData.name ? "" : `${fieldTableName}.`) + fieldMetaData.name; + const formData = new FormData(); formData.append("tableName", tableMetaData.name); - formData.append("fieldName", fieldMetaData.name); + formData.append("fieldName", fullFieldName); formData.append("filterJSON", JSON.stringify(filter)); if(orderBy) { @@ -115,7 +118,16 @@ function ColumnStats({tableMetaData, fieldMetaData, filter}: Props): JSX.Element const valueCounts = [] as QRecord[]; for(let i = 0; i < result.values.valueCounts.length; i++) { - valueCounts.push(new QRecord(result.values.valueCounts[i])); + let valueRecord = new QRecord(result.values.valueCounts[i]); + + //////////////////////////////////////////////////////////////////////////////////////////////// + // for a field from a join, the backend will have sent it as table.field (e.g., lineItem.sku) // + // but we'll have a "better time" in the rest of this code if we have it as just the field // + // name, so, copy it there... // + //////////////////////////////////////////////////////////////////////////////////////////////// + valueRecord.displayValues.set(fieldMetaData.name, valueRecord.displayValues.get(fullFieldName)); + valueRecord.values.set(fieldMetaData.name, valueRecord.values.get(fullFieldName)); + valueCounts.push(valueRecord); } setValueCounts(valueCounts); @@ -128,6 +140,7 @@ function ColumnStats({tableMetaData, fieldMetaData, filter}: Props): JSX.Element const rows = DataGridUtils.makeRows(valueCounts, fakeTableMetaData); const columns = DataGridUtils.setupGridColumns(fakeTableMetaData, null, null, "bySection"); + columns.forEach((c) => { c.width = 200; diff --git a/src/qqq/pages/records/query/RecordQuery.tsx b/src/qqq/pages/records/query/RecordQuery.tsx index fce926f..a28910b 100644 --- a/src/qqq/pages/records/query/RecordQuery.tsx +++ b/src/qqq/pages/records/query/RecordQuery.tsx @@ -19,14 +19,15 @@ * along with this program. If not, see . */ +import {QController} from "@kingsrook/qqq-frontend-core/lib/controllers/QController"; import {Capability} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Capability"; +import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import {QJobComplete} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobComplete"; import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError"; import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; -import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria"; import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter"; import {QueryJoin} from "@kingsrook/qqq-frontend-core/lib/model/query/QueryJoin"; import {Alert, Box, Collapse, TablePagination} from "@mui/material"; @@ -194,6 +195,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element const [launchingProcess, setLaunchingProcess] = useState(launchProcess); const [recordIdsForProcess, setRecordIdsForProcess] = useState(null as string | QQueryFilter); const [columnStatsFieldName, setColumnStatsFieldName] = useState(null as string); + const [columnStatsField, setColumnStatsField] = useState(null as QFieldMetaData); + const [columnStatsFieldTableName, setColumnStatsFieldTableName] = useState(null as string) const [filterForColumnStats, setFilterForColumnStats] = useState(null as QQueryFilter); const instance = useRef({timer: null}); @@ -280,17 +283,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element { const formData = new FormData(); formData.append("id", currentSavedFilterId); - - ////////////////////////////////////////////////////////////////// - // we don't want this job to go async, so, pass a large timeout // - ////////////////////////////////////////////////////////////////// - formData.append("_qStepTimeoutMillis", 60 * 1000); - - const formDataHeaders = { - "content-type": "multipart/form-data; boundary=--------------------------320289315924586491558366", - }; - - const processResult = await qController.processInit("querySavedFilter", formData, formDataHeaders); + formData.append(QController.STEP_TIMEOUT_MILLIS_PARAM_NAME, 60 * 1000); + const processResult = await qController.processInit("querySavedFilter", formData, qController.defaultMultipartFormDataHeaders()); if (processResult instanceof QJobError) { const jobError = processResult as QJobError; @@ -1065,6 +1059,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element } setColumnStatsFieldName(null); + setColumnStatsFieldTableName(null); + setColumnStatsField(null); }; const closeModalProcess = (event: object, reason: string) => @@ -1140,8 +1136,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element that match your query, because you have included fields from other tables which may have more than one record associated with each {tableMetaData?.label}. - let distinctPart = isJoinMany(tableMetaData, getVisibleJoinTables()) ? ( -  ({distinctRecords} distinct + let distinctPart = isJoinMany(tableMetaData, getVisibleJoinTables()) ? ( +  ({safeToLocaleString(distinctRecords)} distinct info_outlined ) @@ -1170,14 +1166,14 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element return (loading ? "Counting..." : "No rows"); } - return <> + return Showing {from.toLocaleString()} to {to.toLocaleString()} of { count == -1 ? <>more than {to.toLocaleString()} : <> {count.toLocaleString()}{distinctPart} } - ; + ; } else { @@ -1215,6 +1211,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element if (selectedSavedFilterId != null) { const qRecord = await fetchSavedFilter(selectedSavedFilterId); + setCurrentSavedFilter(qRecord); // this fixed initial load not showing filter name + const models = await FilterUtils.determineFilterAndSortModels(qController, tableMetaData, qRecord.values.get("filterJson"), null, null, null); handleFilterChange(models.filter); handleSortChange(models.sort); @@ -1233,17 +1231,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element let qRecord = null; const formData = new FormData(); formData.append("id", filterId); - - ////////////////////////////////////////////////////////////////// - // we don't want this job to go async, so, pass a large timeout // - ////////////////////////////////////////////////////////////////// - formData.append("_qStepTimeoutMillis", 60 * 1000); - - const formDataHeaders = { - "content-type": "multipart/form-data; boundary=--------------------------320289315924586491558366", - }; - - const processResult = await qController.processInit("querySavedFilter", formData, formDataHeaders); + formData.append(QController.STEP_TIMEOUT_MILLIS_PARAM_NAME, 60 * 1000); + const processResult = await qController.processInit("querySavedFilter", formData, qController.defaultMultipartFormDataHeaders()); if (processResult instanceof QJobError) { const jobError = processResult as QJobError; @@ -1293,6 +1282,25 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element { setFilterForColumnStats(buildQFilter(tableMetaData, filterModel)); setColumnStatsFieldName(column.field); + + if(column.field.indexOf(".") > -1) + { + const nameParts = column.field.split(".", 2); + for (let i = 0; i < tableMetaData?.exposedJoins?.length; i++) + { + const join = tableMetaData?.exposedJoins[i]; + if(join?.joinTable.name == nameParts[0]) + { + setColumnStatsField(join.joinTable.fields.get(nameParts[1])); + setColumnStatsFieldTableName(nameParts[0]); + } + } + } + else + { + setColumnStatsField(tableMetaData.fields.get(column.field)); + setColumnStatsFieldTableName(tableMetaData.name); + } }; const CustomColumnMenu = forwardRef( @@ -1439,6 +1447,15 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element } ); + const safeToLocaleString = (n: Number): string => + { + if(n != null && n != undefined) + { + return (n.toLocaleString()); + } + return (""); + } + function CustomToolbar() { const handleMouseDown: GridEventListener<"cellMouseDown"> = ( @@ -1473,15 +1490,6 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element const joinIsMany = isJoinMany(tableMetaData, visibleJoinTables); - const safeToLocaleString = (n: Number): string => - { - if(n != null && n != undefined) - { - return (n.toLocaleString()); - } - return (""); - } - const selectionMenuOptions: string[] = []; selectionMenuOptions.push(`This page (${safeToLocaleString(distinctRecordsOnPageCount)} ${joinIsMany ? "distinct " : ""}record${distinctRecordsOnPageCount == 1 ? "" : "s"})`); selectionMenuOptions.push(`Full query result (${joinIsMany ? safeToLocaleString(distinctRecords) + ` distinct record${distinctRecords == 1 ? "" : "s"}` : safeToLocaleString(totalRecords) + ` record${totalRecords == 1 ? "" : "s"}`})`); @@ -1552,9 +1560,11 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element Refresh - + {/* @ts-ignore */} +
- + {/* @ts-ignore */} + { hasValidFilters && ( @@ -1567,7 +1577,6 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element if (e.key == "Enter") { setShowClearFiltersWarning(false) - navigate(metaData.getTablePathByName(tableName)); handleFilterChange({items: []} as GridFilterModel); } }}> @@ -1580,7 +1589,6 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element { setShowClearFiltersWarning(false); - navigate(metaData.getTablePathByName(tableName)); handleFilterChange({items: []} as GridFilterModel); }}/> @@ -1588,8 +1596,10 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
) } - - + {/* @ts-ignore */} + + {/* @ts-ignore */} + @@ -1671,22 +1681,22 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element { if (menuItems.length > 0) { - menuItems.push(); + menuItems.push(); } }; const menuItems: JSX.Element[] = []; if (table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission) { - menuItems.push(library_addBulk Load); + menuItems.push(library_addBulk Load); } if (table.capabilities.has(Capability.TABLE_UPDATE) && table.editPermission) { - menuItems.push(editBulk Edit); + menuItems.push(editBulk Edit); } if (table.capabilities.has(Capability.TABLE_DELETE) && table.deletePermission) { - menuItems.push(deleteBulk Delete); + menuItems.push(deleteBulk Delete); } const runRecordScriptProcess = metaData?.processes.get("runRecordScript"); @@ -1696,7 +1706,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element menuItems.push( processClicked(process)}>{process.iconName ?? "arrow_forward"}{process.label}); } - menuItems.push( navigate("dev")}>codeDeveloper Mode); + menuItems.push( navigate("dev")}>codeDeveloper Mode); if (tableProcesses && tableProcesses.length) { @@ -1711,7 +1721,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element if (menuItems.length === 0) { - menuItems.push(blockNo actions available); + menuItems.push(blockNo actions available); } const renderActionsMenu = ( @@ -1898,7 +1908,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element - + closeColumnStats(null, null)} disabled={false} /> diff --git a/src/qqq/utils/qqq/FilterUtils.ts b/src/qqq/utils/qqq/FilterUtils.ts index 1c61364..7c56bf6 100644 --- a/src/qqq/utils/qqq/FilterUtils.ts +++ b/src/qqq/utils/qqq/FilterUtils.ts @@ -360,7 +360,7 @@ class FilterUtils let defaultFilter = {items: []} as GridFilterModel; let defaultSort = [] as GridSortItem[]; - if (tableMetaData.fields !== undefined) + if (tableMetaData && tableMetaData.fields !== undefined) { if (filterString != null || (searchParams && searchParams.has("filter"))) { @@ -376,7 +376,33 @@ class FilterUtils for (let i = 0; i < qQueryFilter?.criteria?.length; i++) { const criteria = qQueryFilter.criteria[i]; - const field = tableMetaData.fields.get(criteria.fieldName); + let fieldTable = tableMetaData; + let field = null; + if (criteria.fieldName.indexOf(".") > -1) + { + const nameParts = criteria.fieldName.split(".", 2); + for (let i = 0; i < tableMetaData?.exposedJoins?.length; i++) + { + const joinTable = tableMetaData.exposedJoins[i].joinTable; + if (joinTable.name == nameParts[0]) + { + fieldTable = joinTable; + field = joinTable.fields.get(nameParts[1]); + break; + } + } + } + else + { + field = tableMetaData.fields.get(criteria.fieldName); + } + + if (field == null) + { + console.log("Couldn't find field for filter: " + criteria.fieldName); + continue; + } + let values = criteria.values; if (field.possibleValueSourceName) { @@ -388,7 +414,7 @@ class FilterUtils ////////////////////////////////////////////////////////////////////////////////// if (values && values.length > 0) { - values = await qController.possibleValues(tableMetaData.name, null, field.name, "", values); + values = await qController.possibleValues(fieldTable.name, null, field.name, "", values); } ////////////////////////////////////////////