mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 05:10:45 +00:00
Update to show user-facing process errors; add support for fields linked to records in other tables (adornment);
This commit is contained in:
@ -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",
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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));
|
||||
|
||||
/////////////////////
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
|
Reference in New Issue
Block a user