Update to show user-facing process errors; add support for fields linked to records in other tables (adornment);

This commit is contained in:
2022-09-27 19:06:32 -05:00
parent 1a0d8d9f3b
commit 3ce6a8858e
5 changed files with 119 additions and 19 deletions

View File

@ -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));

View File

@ -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));
/////////////////////

View File

@ -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}
<MDBox mt={3} display="flex" justifyContent="center">
<MDBox display="flex" flexDirection="column" alignItems="center">
<Button onClick={toggleShowErrorDetail} startIcon={<Icon>{showErrorDetail ? "expand_less" : "expand_more"}</Icon>}>
{showErrorDetail ? "Hide " : "Show "}
detailed error message
</Button>
<MDBox mt={1} style={{display: showErrorDetail ? "block" : "none"}}>
{processError}
{
isUserFacingError ? (
<MDBox mt={1}>
<b>{processError}</b>
</MDBox>
</MDBox>
</MDBox>
) : (
<MDBox mt={3} display="flex" justifyContent="center">
<MDBox display="flex" flexDirection="column" alignItems="center">
<Button onClick={toggleShowErrorDetail} startIcon={<Icon>{showErrorDetail ? "expand_less" : "expand_more"}</Icon>}>
{showErrorDetail ? "Hide " : "Show "}
detailed error message
</Button>
<MDBox mt={1} style={{display: showErrorDetail ? "block" : "none"}}>
{processError}
</MDBox>
</MDBox>
</MDBox>
)
}
</MDTypography>
</>
);
@ -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)

View File

@ -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 (<a target={adornment.getValue("target") ?? "_self"} href={rawValue} onClick={(e) =>
let href = rawValue;
const toRecordFromTable = adornment.getValue("toRecordFromTable");
if(toRecordFromTable)
{
e.stopPropagation();
}}>{rawValue}</a>)
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 (<a target={adornment.getValue("target") ?? "_self"} href={href} onClick={(e) => e.stopPropagation()}>{displayValue ?? rawValue}</a>)
}
else
{
return (<Link target={adornment.getValue("target") ?? "_self"} to={href} onClick={(e) => e.stopPropagation()}>{displayValue ?? rawValue}</Link>)
}
}
if (field.hasAdornment(AdornmentType.CHIP))