From 3ce6a8858e811abaca673d463d11180a08b1fc0f Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Tue, 27 Sep 2022 19:06:32 -0500 Subject: [PATCH] Update to show user-facing process errors; add support for fields linked to records in other tables (adornment); --- package.json | 2 +- src/qqq/pages/entity-list/index.tsx | 1 + .../components/ViewContents/index.tsx | 10 ++- src/qqq/pages/process-run/index.tsx | 52 +++++++++---- src/qqq/utils/QValueUtils.tsx | 73 ++++++++++++++++++- 5 files changed, 119 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 9048327..323557a 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "@fullcalendar/interaction": "5.10.0", "@fullcalendar/react": "5.10.0", "@fullcalendar/timegrid": "5.10.0", - "@kingsrook/qqq-frontend-core": "1.0.22", + "@kingsrook/qqq-frontend-core": "1.0.23", "@mui/icons-material": "5.4.1", "@mui/material": "5.4.1", "@mui/styled-engine": "5.4.1", diff --git a/src/qqq/pages/entity-list/index.tsx b/src/qqq/pages/entity-list/index.tsx index 753941b..4ddba6e 100644 --- a/src/qqq/pages/entity-list/index.tsx +++ b/src/qqq/pages/entity-list/index.tsx @@ -623,6 +623,7 @@ function EntityList({table}: Props): JSX.Element setTableState(tableName); setFiltersMenu(null); const metaData = await qController.loadMetaData(); + QValueUtils.qInstance = metaData; setTableProcesses(QProcessUtils.getProcessesForTable(metaData, tableName)); diff --git a/src/qqq/pages/entity-view/components/ViewContents/index.tsx b/src/qqq/pages/entity-view/components/ViewContents/index.tsx index 9a172bd..aa79ce8 100644 --- a/src/qqq/pages/entity-view/components/ViewContents/index.tsx +++ b/src/qqq/pages/entity-view/components/ViewContents/index.tsx @@ -35,7 +35,7 @@ import Grid from "@mui/material/Grid"; import Icon from "@mui/material/Icon"; import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; -import React, {useReducer, useState} from "react"; +import React, {useEffect, useReducer, useState} from "react"; import {useLocation, useNavigate, useSearchParams} from "react-router-dom"; import DashboardWidgets from "qqq/components/DashboardWidgets"; import {QActionsMenuButton, QDeleteButton, QEditButton} from "qqq/components/QButtons"; @@ -45,9 +45,9 @@ import MDAlert from "qqq/components/Temporary/MDAlert"; import MDBox from "qqq/components/Temporary/MDBox"; import MDTypography from "qqq/components/Temporary/MDTypography"; import QClient from "qqq/utils/QClient"; +import QProcessUtils from "qqq/utils/QProcessUtils"; import QTableUtils from "qqq/utils/QTableUtils"; import QValueUtils from "qqq/utils/QValueUtils"; -import QProcessUtils from "../../../../utils/QProcessUtils"; const qController = QClient.getInstance(); @@ -84,6 +84,11 @@ function ViewContents({id, table}: Props): JSX.Element const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget); const closeActionsMenu = () => setActionsMenu(null); + useEffect(() => + { + setAsyncLoadInited(false); + }, [location]); + if (!asyncLoadInited) { setAsyncLoadInited(true); @@ -100,6 +105,7 @@ function ViewContents({id, table}: Props): JSX.Element // load top-level meta-data (e.g., to find processes for table) // ////////////////////////////////////////////////////////////////// const metaData = await qController.loadMetaData(); + QValueUtils.qInstance = metaData; setTableProcesses(QProcessUtils.getProcessesForTable(metaData, tableName)); ///////////////////// diff --git a/src/qqq/pages/process-run/index.tsx b/src/qqq/pages/process-run/index.tsx index 2843ca0..e2e01a8 100644 --- a/src/qqq/pages/process-run/index.tsx +++ b/src/qqq/pages/process-run/index.tsx @@ -93,7 +93,8 @@ function ProcessRun({process, defaultProcessValues}: Props): JSX.Element const [tableMetaData, setTableMetaData] = useState(null); const [qInstance, setQInstance] = useState(null as QInstance); const [processValues, setProcessValues] = useState({} as any); - const [processError, setProcessError] = useState(null as string); + const [processError, _setProcessError] = useState(null as string); + const [isUserFacingError, setIsUserFacingError] = useState(false); const [needToCheckJobStatus, setNeedToCheckJobStatus] = useState(false); const [lastProcessResponse, setLastProcessResponse] = useState( null as QJobStarted | QJobComplete | QJobError | QJobRunning, @@ -101,6 +102,15 @@ function ProcessRun({process, defaultProcessValues}: Props): JSX.Element const [showErrorDetail, setShowErrorDetail] = useState(false); const [showFullHelpText, setShowFullHelpText] = useState(false); + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // for setting the processError state - call this function, which will also set the isUserFacingError state // + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + const setProcessError = (message: string, isUserFacing: boolean = false) => + { + _setProcessError(message); + setIsUserFacingError(isUserFacing); + }; + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // the validation screen - it can change whether next is actually the final step or not... so, use this state field to track that. // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -216,17 +226,25 @@ function ProcessRun({process, defaultProcessValues}: Props): JSX.Element An error occurred while running the process: {" "} {process.label} - - - - - {processError} + { + isUserFacingError ? ( + + {processError} - - + ) : ( + + + + + {processError} + + + + ) + } ); @@ -723,9 +741,16 @@ function ProcessRun({process, defaultProcessValues}: Props): JSX.Element else if (lastProcessResponse instanceof QJobError) { const qJobError = lastProcessResponse as QJobError; - console.log(`Got an error from the backend... ${qJobError.error}`); + console.log(`Got an error from the backend... ${qJobError.error} : ${qJobError.userFacingError}`); setJobUUID(null); - setProcessError(qJobError.error); + if(qJobError.userFacingError) + { + setProcessError(qJobError.userFacingError, true); + } + else + { + setProcessError(qJobError.error); + } setQJobRunning(null); } } @@ -815,6 +840,7 @@ function ProcessRun({process, defaultProcessValues}: Props): JSX.Element try { const qInstance = await QClient.getInstance().loadMetaData(); + QValueUtils.qInstance = qInstance; setQInstance(qInstance); } catch (e) diff --git a/src/qqq/utils/QValueUtils.tsx b/src/qqq/utils/QValueUtils.tsx index e6a5432..81cc1e2 100644 --- a/src/qqq/utils/QValueUtils.tsx +++ b/src/qqq/utils/QValueUtils.tsx @@ -22,10 +22,14 @@ import {AdornmentType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/AdornmentType"; import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType"; +import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; import "datejs"; import {Chip, Icon} from "@mui/material"; +import {queryByTestId} from "@testing-library/react"; import React, {Fragment} from "react"; +import {Link} from "react-router-dom"; +import QClient from "qqq/utils/QClient"; /******************************************************************************* ** Utility class for working with QQQ Values @@ -33,6 +37,31 @@ import React, {Fragment} from "react"; *******************************************************************************/ class QValueUtils { + public static qInstance: QInstance = null; + public static loadingQInstance = false; + + private static getQInstance(): QInstance + { + if(QValueUtils.qInstance == null) + { + if(QValueUtils.loadingQInstance) + { + return (null); + } + + QValueUtils.loadingQInstance = true; + const qController = QClient.getInstance(); + (async () => + { + QValueUtils.qInstance = await qController.loadMetaData(); + })(); + + return (null); + } + + return QValueUtils.qInstance; + } + /******************************************************************************* ** When you have a field, and a record - call this method to get a string or @@ -55,10 +84,48 @@ class QValueUtils if (field.hasAdornment(AdornmentType.LINK)) { const adornment = field.getAdornment(AdornmentType.LINK); - return ( + let href = rawValue; + + const toRecordFromTable = adornment.getValue("toRecordFromTable"); + if(toRecordFromTable) { - e.stopPropagation(); - }}>{rawValue}) + if(QValueUtils.getQInstance()) + { + let tablePath = QValueUtils.getQInstance().getTablePathByName(toRecordFromTable); + if(!tablePath) + { + console.log("Couldn't find path for table: " + tablePath); + return (""); + } + + if(!tablePath.endsWith("/")) + { + tablePath += "/" + } + href = tablePath + rawValue; + } + else + { + ////////////////////////////////////////////////////////////////////////////////// + // if no instance, we can't get the table path, so we can't do a to-record link // + ////////////////////////////////////////////////////////////////////////////////// + return (QValueUtils.getUnadornedValueForDisplay(field, rawValue, displayValue)); + } + } + + if(!href) + { + return (""); + } + + if(href.startsWith("http")) + { + return ( e.stopPropagation()}>{displayValue ?? rawValue}) + } + else + { + return ( e.stopPropagation()}>{displayValue ?? rawValue}) + } } if (field.hasAdornment(AdornmentType.CHIP))