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/interaction": "5.10.0",
|
||||||
"@fullcalendar/react": "5.10.0",
|
"@fullcalendar/react": "5.10.0",
|
||||||
"@fullcalendar/timegrid": "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/icons-material": "5.4.1",
|
||||||
"@mui/material": "5.4.1",
|
"@mui/material": "5.4.1",
|
||||||
"@mui/styled-engine": "5.4.1",
|
"@mui/styled-engine": "5.4.1",
|
||||||
|
@ -623,6 +623,7 @@ function EntityList({table}: Props): JSX.Element
|
|||||||
setTableState(tableName);
|
setTableState(tableName);
|
||||||
setFiltersMenu(null);
|
setFiltersMenu(null);
|
||||||
const metaData = await qController.loadMetaData();
|
const metaData = await qController.loadMetaData();
|
||||||
|
QValueUtils.qInstance = metaData;
|
||||||
|
|
||||||
setTableProcesses(QProcessUtils.getProcessesForTable(metaData, tableName));
|
setTableProcesses(QProcessUtils.getProcessesForTable(metaData, tableName));
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ import Grid from "@mui/material/Grid";
|
|||||||
import Icon from "@mui/material/Icon";
|
import Icon from "@mui/material/Icon";
|
||||||
import Menu from "@mui/material/Menu";
|
import Menu from "@mui/material/Menu";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
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 {useLocation, useNavigate, useSearchParams} from "react-router-dom";
|
||||||
import DashboardWidgets from "qqq/components/DashboardWidgets";
|
import DashboardWidgets from "qqq/components/DashboardWidgets";
|
||||||
import {QActionsMenuButton, QDeleteButton, QEditButton} from "qqq/components/QButtons";
|
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 MDBox from "qqq/components/Temporary/MDBox";
|
||||||
import MDTypography from "qqq/components/Temporary/MDTypography";
|
import MDTypography from "qqq/components/Temporary/MDTypography";
|
||||||
import QClient from "qqq/utils/QClient";
|
import QClient from "qqq/utils/QClient";
|
||||||
|
import QProcessUtils from "qqq/utils/QProcessUtils";
|
||||||
import QTableUtils from "qqq/utils/QTableUtils";
|
import QTableUtils from "qqq/utils/QTableUtils";
|
||||||
import QValueUtils from "qqq/utils/QValueUtils";
|
import QValueUtils from "qqq/utils/QValueUtils";
|
||||||
import QProcessUtils from "../../../../utils/QProcessUtils";
|
|
||||||
|
|
||||||
const qController = QClient.getInstance();
|
const qController = QClient.getInstance();
|
||||||
|
|
||||||
@ -84,6 +84,11 @@ function ViewContents({id, table}: Props): JSX.Element
|
|||||||
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
|
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
|
||||||
const closeActionsMenu = () => setActionsMenu(null);
|
const closeActionsMenu = () => setActionsMenu(null);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
setAsyncLoadInited(false);
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
if (!asyncLoadInited)
|
if (!asyncLoadInited)
|
||||||
{
|
{
|
||||||
setAsyncLoadInited(true);
|
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) //
|
// load top-level meta-data (e.g., to find processes for table) //
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
const metaData = await qController.loadMetaData();
|
const metaData = await qController.loadMetaData();
|
||||||
|
QValueUtils.qInstance = metaData;
|
||||||
setTableProcesses(QProcessUtils.getProcessesForTable(metaData, tableName));
|
setTableProcesses(QProcessUtils.getProcessesForTable(metaData, tableName));
|
||||||
|
|
||||||
/////////////////////
|
/////////////////////
|
||||||
|
@ -93,7 +93,8 @@ function ProcessRun({process, defaultProcessValues}: Props): JSX.Element
|
|||||||
const [tableMetaData, setTableMetaData] = useState(null);
|
const [tableMetaData, setTableMetaData] = useState(null);
|
||||||
const [qInstance, setQInstance] = useState(null as QInstance);
|
const [qInstance, setQInstance] = useState(null as QInstance);
|
||||||
const [processValues, setProcessValues] = useState({} as any);
|
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 [needToCheckJobStatus, setNeedToCheckJobStatus] = useState(false);
|
||||||
const [lastProcessResponse, setLastProcessResponse] = useState(
|
const [lastProcessResponse, setLastProcessResponse] = useState(
|
||||||
null as QJobStarted | QJobComplete | QJobError | QJobRunning,
|
null as QJobStarted | QJobComplete | QJobError | QJobRunning,
|
||||||
@ -101,6 +102,15 @@ function ProcessRun({process, defaultProcessValues}: Props): JSX.Element
|
|||||||
const [showErrorDetail, setShowErrorDetail] = useState(false);
|
const [showErrorDetail, setShowErrorDetail] = useState(false);
|
||||||
const [showFullHelpText, setShowFullHelpText] = 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. //
|
// 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:
|
An error occurred while running the process:
|
||||||
{" "}
|
{" "}
|
||||||
{process.label}
|
{process.label}
|
||||||
<MDBox mt={3} display="flex" justifyContent="center">
|
{
|
||||||
<MDBox display="flex" flexDirection="column" alignItems="center">
|
isUserFacingError ? (
|
||||||
<Button onClick={toggleShowErrorDetail} startIcon={<Icon>{showErrorDetail ? "expand_less" : "expand_more"}</Icon>}>
|
<MDBox mt={1}>
|
||||||
{showErrorDetail ? "Hide " : "Show "}
|
<b>{processError}</b>
|
||||||
detailed error message
|
|
||||||
</Button>
|
|
||||||
<MDBox mt={1} style={{display: showErrorDetail ? "block" : "none"}}>
|
|
||||||
{processError}
|
|
||||||
</MDBox>
|
</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>
|
</MDTypography>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -723,9 +741,16 @@ function ProcessRun({process, defaultProcessValues}: Props): JSX.Element
|
|||||||
else if (lastProcessResponse instanceof QJobError)
|
else if (lastProcessResponse instanceof QJobError)
|
||||||
{
|
{
|
||||||
const qJobError = lastProcessResponse as 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);
|
setJobUUID(null);
|
||||||
setProcessError(qJobError.error);
|
if(qJobError.userFacingError)
|
||||||
|
{
|
||||||
|
setProcessError(qJobError.userFacingError, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setProcessError(qJobError.error);
|
||||||
|
}
|
||||||
setQJobRunning(null);
|
setQJobRunning(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -815,6 +840,7 @@ function ProcessRun({process, defaultProcessValues}: Props): JSX.Element
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
const qInstance = await QClient.getInstance().loadMetaData();
|
const qInstance = await QClient.getInstance().loadMetaData();
|
||||||
|
QValueUtils.qInstance = qInstance;
|
||||||
setQInstance(qInstance);
|
setQInstance(qInstance);
|
||||||
}
|
}
|
||||||
catch (e)
|
catch (e)
|
||||||
|
@ -22,10 +22,14 @@
|
|||||||
import {AdornmentType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/AdornmentType";
|
import {AdornmentType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/AdornmentType";
|
||||||
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
||||||
import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType";
|
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 {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||||
import "datejs";
|
import "datejs";
|
||||||
import {Chip, Icon} from "@mui/material";
|
import {Chip, Icon} from "@mui/material";
|
||||||
|
import {queryByTestId} from "@testing-library/react";
|
||||||
import React, {Fragment} from "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
|
** Utility class for working with QQQ Values
|
||||||
@ -33,6 +37,31 @@ import React, {Fragment} from "react";
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
class QValueUtils
|
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
|
** 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))
|
if (field.hasAdornment(AdornmentType.LINK))
|
||||||
{
|
{
|
||||||
const adornment = field.getAdornment(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();
|
if(QValueUtils.getQInstance())
|
||||||
}}>{rawValue}</a>)
|
{
|
||||||
|
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))
|
if (field.hasAdornment(AdornmentType.CHIP))
|
||||||
|
Reference in New Issue
Block a user