diff --git a/package.json b/package.json index bf637c9..82e295e 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "@auth0/auth0-react": "1.10.2", "@emotion/react": "11.7.1", "@emotion/styled": "11.6.0", - "@kingsrook/qqq-frontend-core": "1.0.79", + "@kingsrook/qqq-frontend-core": "1.0.80", "@mui/icons-material": "5.4.1", "@mui/material": "5.11.1", "@mui/styles": "5.11.1", diff --git a/src/qqq/components/forms/EntityForm.tsx b/src/qqq/components/forms/EntityForm.tsx index ffc4853..179655e 100644 --- a/src/qqq/components/forms/EntityForm.tsx +++ b/src/qqq/components/forms/EntityForm.tsx @@ -346,6 +346,12 @@ function EntityForm(props: Props): JSX.Element const fieldName = section.fieldNames[j]; const field = tableMetaData.fields.get(fieldName); + if(!field) + { + console.log(`Omitting un-found field ${fieldName} from form`); + continue; + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // if id !== null (and we're not copying) - means we're on the edit screen -- show all fields on the edit screen. // // || (or) we're on the insert screen in which case, only show editable fields. // diff --git a/src/qqq/pages/processes/ProcessRun.tsx b/src/qqq/pages/processes/ProcessRun.tsx index b11b64b..70063de 100644 --- a/src/qqq/pages/processes/ProcessRun.tsx +++ b/src/qqq/pages/processes/ProcessRun.tsx @@ -985,6 +985,10 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is } setQJobRunning(null); } + else + { + console.warn(`Process response was not of an expected type (need an npm clean?) ${JSON.stringify(lastProcessResponse)}`); + } } }, [lastProcessResponse]); diff --git a/src/qqq/pages/records/query/RecordQuery.tsx b/src/qqq/pages/records/query/RecordQuery.tsx index 9ebef6d..8ad2235 100644 --- a/src/qqq/pages/records/query/RecordQuery.tsx +++ b/src/qqq/pages/records/query/RecordQuery.tsx @@ -71,6 +71,7 @@ import DataGridUtils from "qqq/utils/DataGridUtils"; import Client from "qqq/utils/qqq/Client"; import FilterUtils from "qqq/utils/qqq/FilterUtils"; import ProcessUtils from "qqq/utils/qqq/ProcessUtils"; +import TableUtils from "qqq/utils/qqq/TableUtils"; import ValueUtils from "qqq/utils/qqq/ValueUtils"; const CURRENT_SAVED_FILTER_ID_LOCAL_STORAGE_KEY_ROOT = "qqq.currentSavedFilterId"; @@ -628,6 +629,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element let models = await FilterUtils.determineFilterAndSortModels(qController, tableMetaData, null, searchParams, filterLocalStorageKey, sortLocalStorageKey); setFilterModel(models.filter); setColumnSortModel(models.sort); + setWarningAlert(models.warning); + setQueryFilter(FilterUtils.buildQFilterFromGridFilter(tableMetaData, models.filter, models.sort, rowsPerPage)); return; } @@ -708,16 +711,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element if (tableMetaData?.exposedJoins) { const visibleJoinTables = getVisibleJoinTables(); - - queryJoins = []; - for (let i = 0; i < tableMetaData.exposedJoins.length; i++) - { - const join = tableMetaData.exposedJoins[i]; - if (visibleJoinTables.has(join.joinTable.name)) - { - queryJoins.push(new QueryJoin(join.joinTable.name, true, "LEFT")); - } - } + queryJoins = TableUtils.getQueryJoins(tableMetaData, visibleJoinTables); } ////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1400,6 +1394,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element const models = await FilterUtils.determineFilterAndSortModels(qController, tableMetaData, qRecord.values.get("filterJson"), null, null, null); handleFilterChange(models.filter); handleSortChange(models.sort, models.filter); + setWarningAlert(models.warning); + localStorage.setItem(currentSavedFilterLocalStorageKey, selectedSavedFilterId.toString()); } else @@ -1431,35 +1427,13 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element return (qRecord); } - const getFieldAndTable = (fieldName: string): [QFieldMetaData, QTableMetaData] => - { - if(fieldName.indexOf(".") > -1) - { - const nameParts = fieldName.split(".", 2); - for (let i = 0; i < tableMetaData?.exposedJoins?.length; i++) - { - const join = tableMetaData?.exposedJoins[i]; - if(join?.joinTable.name == nameParts[0]) - { - return ([join.joinTable.fields.get(nameParts[1]), join.joinTable]); - } - } - } - else - { - return ([tableMetaData.fields.get(fieldName), tableMetaData]); - } - - return (null); - } - const copyColumnValues = async (column: GridColDef) => { let data = ""; let counter = 0; if (latestQueryResults && latestQueryResults.length) { - let [qFieldMetaData, fieldTable] = getFieldAndTable(column.field); + let [qFieldMetaData, fieldTable] = TableUtils.getFieldAndTable(tableMetaData, column.field); for (let i = 0; i < latestQueryResults.length; i++) { let record = latestQueryResults[i] as QRecord; @@ -1489,7 +1463,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element setFilterForColumnStats(buildQFilter(tableMetaData, filterModel)); setColumnStatsFieldName(column.field); - const [field, fieldTable] = getFieldAndTable(column.field); + const [field, fieldTable] = TableUtils.getFieldAndTable(tableMetaData, column.field); setColumnStatsField(field); setColumnStatsFieldTableName(fieldTable.name); }; @@ -1962,7 +1936,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element { (warningAlert) ? ( - setWarningAlert(null)}>{warningAlert} + warning} sx={{mb: 3}} onClose={() => setWarningAlert(null)}>{warningAlert} ) : null } diff --git a/src/qqq/pages/records/view/RecordView.tsx b/src/qqq/pages/records/view/RecordView.tsx index e04fcdd..c9124e2 100644 --- a/src/qqq/pages/records/view/RecordView.tsx +++ b/src/qqq/pages/records/view/RecordView.tsx @@ -27,6 +27,7 @@ import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QT import {QTableSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableSection"; import {QTableVariant} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableVariant"; import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; +import {QueryJoin} from "@kingsrook/qqq-frontend-core/lib/model/query/QueryJoin"; import {Alert, Typography} from "@mui/material"; import Avatar from "@mui/material/Avatar"; import Box from "@mui/material/Box"; @@ -103,7 +104,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element const [allTableProcesses, setAllTableProcesses] = useState([] as QProcessMetaData[]); const [actionsMenu, setActionsMenu] = useState(null); const [notFoundMessage, setNotFoundMessage] = useState(null as string); - const [errorMessage, setErrorMessage] = useState(null as string) + const [errorMessage, setErrorMessage] = useState(null as string); const [successMessage, setSuccessMessage] = useState(null as string); const [warningMessage, setWarningMessage] = useState(null as string); const [activeModalProcess, setActiveModalProcess] = useState(null as QProcessMetaData); @@ -325,6 +326,31 @@ function RecordView({table, launchProcess}: Props): JSX.Element reload(); }, [location.pathname, location.hash]); + const getVisibleJoinTables = (tableMetaData: QTableMetaData): Set => + { + const visibleJoinTables = new Set(); + + for (let i = 0; i < tableMetaData?.sections.length; i++) + { + const section = tableMetaData?.sections[i]; + if (section.isHidden || !section.fieldNames || !section.fieldNames.length) + { + continue; + } + + section.fieldNames.forEach((fieldName) => + { + const [field, tableForField] = TableUtils.getFieldAndTable(tableMetaData, fieldName); + if(tableForField && tableForField.name != tableMetaData.name) + { + visibleJoinTables.add(tableForField.name); + } + }) + } + + return (visibleJoinTables); + }; + if (!asyncLoadInited) { setAsyncLoadInited(true); @@ -368,13 +394,20 @@ function RecordView({table, launchProcess}: Props): JSX.Element setActiveModalProcess(launchingProcess); } + let queryJoins: QueryJoin[] = null; + const visibleJoinTables = getVisibleJoinTables(tableMetaData); + if(visibleJoinTables.size > 0) + { + queryJoins = TableUtils.getQueryJoins(tableMetaData, visibleJoinTables); + } + ///////////////////// // load the record // ///////////////////// let record: QRecord; try { - record = await qController.get(tableName, id, tableVariant); + record = await qController.get(tableName, id, tableVariant, null, queryJoins); setRecord(record); } catch (e) @@ -465,17 +498,22 @@ function RecordView({table, launchProcess}: Props): JSX.Element const fields = ( { - section.fieldNames.map((fieldName: string) => ( - - - {tableMetaData.fields.get(fieldName).label}: -
 
-
- - {ValueUtils.getDisplayValue(tableMetaData.fields.get(fieldName), record, "view")} - -
- )) + section.fieldNames.map((fieldName: string) => + { + let [field, tableForField] = TableUtils.getFieldAndTable(tableMetaData, fieldName); + let label = field.label; + return ( + + + {label}: +
 
+
+ + {ValueUtils.getDisplayValue(field, record, "view", fieldName)} + +
+ ) + }) }
); diff --git a/src/qqq/utils/qqq/FilterUtils.ts b/src/qqq/utils/qqq/FilterUtils.ts index eeb86b9..6fbb489 100644 --- a/src/qqq/utils/qqq/FilterUtils.ts +++ b/src/qqq/utils/qqq/FilterUtils.ts @@ -31,6 +31,7 @@ import {QFilterOrderBy} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilt import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter"; import {ThisOrLastPeriodExpression} from "@kingsrook/qqq-frontend-core/lib/model/query/ThisOrLastPeriodExpression"; import {GridFilterItem, GridFilterModel, GridLinkOperator, GridSortItem} from "@mui/x-data-grid-pro"; +import TableUtils from "qqq/utils/qqq/TableUtils"; import ValueUtils from "qqq/utils/qqq/ValueUtils"; const CURRENT_SAVED_FILTER_ID_LOCAL_STORAGE_KEY_ROOT = "qqq.currentSavedFilterId"; @@ -375,10 +376,11 @@ class FilterUtils ** Get the default filter to use on the page - either from given filter string, query string param, or ** local storage, or a default (empty). *******************************************************************************/ - public static async determineFilterAndSortModels(qController: QController, tableMetaData: QTableMetaData, filterString: string, searchParams: URLSearchParams, filterLocalStorageKey: string, sortLocalStorageKey: string): Promise<{ filter: GridFilterModel, sort: GridSortItem[] }> + public static async determineFilterAndSortModels(qController: QController, tableMetaData: QTableMetaData, filterString: string, searchParams: URLSearchParams, filterLocalStorageKey: string, sortLocalStorageKey: string): Promise<{ filter: GridFilterModel, sort: GridSortItem[], warning: string }> { let defaultFilter = {items: []} as GridFilterModel; let defaultSort = [] as GridSortItem[]; + let warningParts = [] as string[]; if (tableMetaData && tableMetaData.fields !== undefined) { @@ -396,30 +398,11 @@ class FilterUtils for (let i = 0; i < qQueryFilter?.criteria?.length; i++) { const criteria = qQueryFilter.criteria[i]; - 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); - } - + let [field, fieldTable] = TableUtils.getFieldAndTable(tableMetaData, criteria.fieldName); if (field == null) { console.log("Couldn't find field for filter: " + criteria.fieldName); + warningParts.push("Your filter contained an unrecognized field name: " + criteria.fieldName) continue; } @@ -500,7 +483,7 @@ class FilterUtils localStorage.setItem(sortLocalStorageKey, JSON.stringify(defaultSort)); } - return ({filter: defaultFilter, sort: defaultSort}); + return ({filter: defaultFilter, sort: defaultSort, warning: warningParts.length > 0 ? "Warning: " + warningParts.join("; ") : ""}); } catch (e) { @@ -551,7 +534,7 @@ class FilterUtils }); } - return ({filter: defaultFilter, sort: defaultSort}); + return ({filter: defaultFilter, sort: defaultSort, warning: warningParts.length > 0 ? "Warning: " + warningParts.join("; ") : ""}); } diff --git a/src/qqq/utils/qqq/TableUtils.ts b/src/qqq/utils/qqq/TableUtils.ts index 444afa8..dd0f6eb 100644 --- a/src/qqq/utils/qqq/TableUtils.ts +++ b/src/qqq/utils/qqq/TableUtils.ts @@ -19,8 +19,10 @@ * along with this program. If not, see . */ +import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import {QTableSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableSection"; +import {QueryJoin} from "@kingsrook/qqq-frontend-core/lib/model/query/QueryJoin"; /******************************************************************************* ** Utility class for working with QQQ Tables @@ -28,7 +30,6 @@ import {QTableSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTa *******************************************************************************/ class TableUtils { - /******************************************************************************* ** *******************************************************************************/ @@ -85,6 +86,61 @@ class TableUtils })]); } } + + + /******************************************************************************* + ** + *******************************************************************************/ + public static getFieldAndTable(tableMetaData: QTableMetaData, fieldName: string): [QFieldMetaData, QTableMetaData] + { + if (fieldName.indexOf(".") > -1) + { + const nameParts = fieldName.split(".", 2); + for (let i = 0; i < tableMetaData?.exposedJoins?.length; i++) + { + const join = tableMetaData?.exposedJoins[i]; + if (join?.joinTable.name == nameParts[0]) + { + return ([join.joinTable.fields.get(nameParts[1]), join.joinTable]); + } + } + } + else + { + return ([tableMetaData.fields.get(fieldName), tableMetaData]); + } + + return (null); + } + + /******************************************************************************* + ** + *******************************************************************************/ + public static getQueryJoins(tableMetaData: QTableMetaData, visibleJoinTables: Set): QueryJoin[] + { + const queryJoins = []; + for (let i = 0; i < tableMetaData.exposedJoins.length; i++) + { + const join = tableMetaData.exposedJoins[i]; + if (visibleJoinTables.has(join.joinTable.name)) + { + let joinName = null; + if (join.joinPath && join.joinPath.length == 1 && join.joinPath[0].name) + { + joinName = join.joinPath[0].name; + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // todo - what about a join with a longer path? it would be nice to pass such joinNames through there too, // + // but what, that would actually be multiple queryJoins? needs a fair amount of thought. // + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + queryJoins.push(new QueryJoin(join.joinTable.name, true, "LEFT", null, null, joinName)); + } + } + + return queryJoins; + } + + } export default TableUtils;