mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 05:10:45 +00:00
Adding test mode to assocaited record screens; code editor as an adornment type for forms
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.33",
|
"@kingsrook/qqq-frontend-core": "1.0.34",
|
||||||
"@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",
|
||||||
|
@ -158,6 +158,7 @@ function QDynamicForm(props: Props): JSX.Element
|
|||||||
bulkEditMode={bulkEditMode}
|
bulkEditMode={bulkEditMode}
|
||||||
bulkEditSwitchChangeHandler={bulkEditSwitchChanged}
|
bulkEditSwitchChangeHandler={bulkEditSwitchChanged}
|
||||||
success={`${values[fieldName]}` !== "" && !errors[fieldName] && touched[fieldName]}
|
success={`${values[fieldName]}` !== "" && !errors[fieldName] && touched[fieldName]}
|
||||||
|
formFieldObject={field}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
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 * as Yup from "yup";
|
import * as Yup from "yup";
|
||||||
@ -73,6 +74,17 @@ class DynamicFormUtils
|
|||||||
fieldType = "text";
|
fieldType = "text";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let more: any = {};
|
||||||
|
if (field.hasAdornment(AdornmentType.CODE_EDITOR))
|
||||||
|
{
|
||||||
|
fieldType = "ace";
|
||||||
|
const values = field.getAdornment(AdornmentType.CODE_EDITOR).values;
|
||||||
|
if (values.has("languageMode"))
|
||||||
|
{
|
||||||
|
more.languageMode = values.get("languageMode");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let label = field.label ? field.label : field.name;
|
let label = field.label ? field.label : field.name;
|
||||||
label += field.isRequired ? " *" : "";
|
label += field.isRequired ? " *" : "";
|
||||||
|
|
||||||
@ -84,6 +96,7 @@ class DynamicFormUtils
|
|||||||
type: fieldType,
|
type: fieldType,
|
||||||
displayFormat: field.displayFormat,
|
displayFormat: field.displayFormat,
|
||||||
// todo invalidMsg: "Zipcode is not valid (e.g. 70000).",
|
// todo invalidMsg: "Zipcode is not valid (e.g. 70000).",
|
||||||
|
...more
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,8 +22,9 @@
|
|||||||
import {InputAdornment, InputLabel} from "@mui/material";
|
import {InputAdornment, InputLabel} from "@mui/material";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Switch from "@mui/material/Switch";
|
import Switch from "@mui/material/Switch";
|
||||||
import {ErrorMessage, Field, useFormikContext} from "formik";
|
import {ErrorMessage, Field, FieldProps, useFormikContext} from "formik";
|
||||||
import React, {SyntheticEvent, useState} from "react";
|
import React, {useState} from "react";
|
||||||
|
import AceEditor from "react-ace";
|
||||||
import QBooleanFieldSwitch from "qqq/components/QDynamicFormField/QBooleanFieldSwitch";
|
import QBooleanFieldSwitch from "qqq/components/QDynamicFormField/QBooleanFieldSwitch";
|
||||||
import MDBox from "qqq/components/Temporary/MDBox";
|
import MDBox from "qqq/components/Temporary/MDBox";
|
||||||
import MDInput from "qqq/components/Temporary/MDInput";
|
import MDInput from "qqq/components/Temporary/MDInput";
|
||||||
@ -43,14 +44,15 @@ interface Props
|
|||||||
|
|
||||||
bulkEditMode?: boolean;
|
bulkEditMode?: boolean;
|
||||||
bulkEditSwitchChangeHandler?: any;
|
bulkEditSwitchChangeHandler?: any;
|
||||||
|
formFieldObject: any; // is the type returned by DynamicFormUtils.getDynamicField
|
||||||
}
|
}
|
||||||
|
|
||||||
function QDynamicFormField({
|
function QDynamicFormField({
|
||||||
label, name, displayFormat, value, bulkEditMode, bulkEditSwitchChangeHandler, type, isEditable, ...rest
|
label, name, displayFormat, value, bulkEditMode, bulkEditSwitchChangeHandler, type, isEditable, formFieldObject, ...rest
|
||||||
}: Props): JSX.Element
|
}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const [ switchChecked, setSwitchChecked ] = useState(false);
|
const [switchChecked, setSwitchChecked] = useState(false);
|
||||||
const [ isDisabled, setIsDisabled ] = useState(!isEditable || bulkEditMode);
|
const [isDisabled, setIsDisabled] = useState(!isEditable || bulkEditMode);
|
||||||
|
|
||||||
const {setFieldValue} = useFormikContext();
|
const {setFieldValue} = useFormikContext();
|
||||||
|
|
||||||
@ -82,15 +84,50 @@ function QDynamicFormField({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let field;
|
||||||
|
let getsBulkEditHtmlLabel = true;
|
||||||
|
if (type === "checkbox")
|
||||||
|
{
|
||||||
|
getsBulkEditHtmlLabel = false;
|
||||||
|
field = (<QBooleanFieldSwitch name={name} label={label} value={value} isDisabled={isDisabled} />);
|
||||||
|
}
|
||||||
|
else if (type === "ace")
|
||||||
|
{
|
||||||
|
let mode = "text";
|
||||||
|
if(formFieldObject && formFieldObject.languageMode)
|
||||||
|
{
|
||||||
|
mode = formFieldObject.languageMode;
|
||||||
|
}
|
||||||
|
|
||||||
const field = () =>
|
getsBulkEditHtmlLabel = false;
|
||||||
(type == "checkbox" ?
|
field = (
|
||||||
<QBooleanFieldSwitch name={name} label={label} value={value} isDisabled={isDisabled} /> :
|
<>
|
||||||
|
<InputLabel shrink={true}>{label}</InputLabel>
|
||||||
|
<AceEditor
|
||||||
|
mode={mode}
|
||||||
|
theme="github"
|
||||||
|
name="editor"
|
||||||
|
editorProps={{$blockScrolling: true}}
|
||||||
|
onChange={(value: string, event: any) =>
|
||||||
|
{
|
||||||
|
setFieldValue(name, value, false);
|
||||||
|
}}
|
||||||
|
width="100%"
|
||||||
|
height="300px"
|
||||||
|
value={value}
|
||||||
|
style={{border: "1px solid gray"}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
field = (
|
||||||
<>
|
<>
|
||||||
<Field {...rest} onWheel={handleOnWheel} name={name} type={type} as={MDInput} variant="standard" label={label} InputLabelProps={inputLabelProps} InputProps={inputProps} fullWidth disabled={isDisabled}
|
<Field {...rest} onWheel={handleOnWheel} name={name} type={type} as={MDInput} variant="standard" label={label} InputLabelProps={inputLabelProps} InputProps={inputProps} fullWidth disabled={isDisabled}
|
||||||
onKeyPress={(e: any) =>
|
onKeyPress={(e: any) =>
|
||||||
{
|
{
|
||||||
if(e.key === "Enter")
|
if (e.key === "Enter")
|
||||||
{
|
{
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
@ -103,10 +140,16 @@ function QDynamicFormField({
|
|||||||
</MDBox>
|
</MDBox>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const bulkEditSwitchChanged = () =>
|
const bulkEditSwitchChanged = () =>
|
||||||
{
|
{
|
||||||
const newSwitchValue = !switchChecked;
|
setBulkEditSwitch(!switchChecked);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setBulkEditSwitch = (value: boolean) =>
|
||||||
|
{
|
||||||
|
const newSwitchValue = value;
|
||||||
setSwitchChecked(newSwitchValue);
|
setSwitchChecked(newSwitchValue);
|
||||||
setIsDisabled(!newSwitchValue);
|
setIsDisabled(!newSwitchValue);
|
||||||
bulkEditSwitchChangeHandler(name, newSwitchValue);
|
bulkEditSwitchChangeHandler(name, newSwitchValue);
|
||||||
@ -124,13 +167,13 @@ function QDynamicFormField({
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box width="100%" sx={{background: (type == "checkbox" && isDisabled) ? "#f0f2f5!important" : "initial"}}>
|
<Box width="100%" sx={{background: (type == "checkbox" && isDisabled) ? "#f0f2f5!important" : "initial"}}>
|
||||||
{/* for checkboxes, if we put the whole thing in a label, we get bad overly aggressive toggling of the outer switch... */}
|
{
|
||||||
{(type == "checkbox" ?
|
getsBulkEditHtmlLabel
|
||||||
field() :
|
? (<label htmlFor={`bulkEditSwitch-${name}`}>
|
||||||
<label htmlFor={`bulkEditSwitch-${name}`}>
|
{field}
|
||||||
{field()}
|
</label>)
|
||||||
</label>
|
: <div onClick={() => setBulkEditSwitch(true)}>{field}</div>
|
||||||
)}
|
}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@ -139,7 +182,7 @@ function QDynamicFormField({
|
|||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
<MDBox mb={1.5}>
|
<MDBox mb={1.5}>
|
||||||
{field()}
|
{field}
|
||||||
</MDBox>
|
</MDBox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -847,7 +847,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element
|
|||||||
// construct the url for the export //
|
// construct the url for the export //
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
const d = new Date();
|
const d = new Date();
|
||||||
const dateString = `${d.getFullYear()}-${zp(d.getMonth())}-${zp(d.getDate())} ${zp(d.getHours())}${zp(d.getMinutes())}`;
|
const dateString = `${d.getFullYear()}-${zp(d.getMonth()+1)}-${zp(d.getDate())} ${zp(d.getHours())}${zp(d.getMinutes())}`;
|
||||||
const filename = `${tableMetaData.label} Export ${dateString}.${format}`;
|
const filename = `${tableMetaData.label} Export ${dateString}.${format}`;
|
||||||
const url = `/data/${tableMetaData.name}/export/${filename}?filter=${encodeURIComponent(JSON.stringify(buildQFilter(filterModel)))}&fields=${visibleFields.join(",")}`;
|
const url = `/data/${tableMetaData.name}/export/${filename}?filter=${encodeURIComponent(JSON.stringify(buildQFilter(filterModel)))}&fields=${visibleFields.join(",")}`;
|
||||||
|
|
||||||
@ -870,7 +870,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element
|
|||||||
}, 1);
|
}, 1);
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>Generating file <u>${filename}</u>${totalRecords ? " with " + totalRecords.toLocaleString() + "records" : ""}...</body>
|
<body>Generating file <u>${filename}</u>${totalRecords ? " with " + totalRecords.toLocaleString() + " record" + (totalRecords == 1 ? "" : "s") : ""}...</body>
|
||||||
</html>`);
|
</html>`);
|
||||||
|
|
||||||
///////////////////////////////////////////
|
///////////////////////////////////////////
|
||||||
|
@ -20,7 +20,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import {Typography} from "@mui/material";
|
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
||||||
|
import {Label} from "@mui/icons-material";
|
||||||
|
import {ToggleButton, ToggleButtonGroup, Typography} from "@mui/material";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import Card from "@mui/material/Card";
|
import Card from "@mui/material/Card";
|
||||||
@ -29,10 +31,20 @@ import TextField from "@mui/material/TextField";
|
|||||||
import React, {useState} from "react";
|
import React, {useState} from "react";
|
||||||
import AceEditor from "react-ace";
|
import AceEditor from "react-ace";
|
||||||
import {QCancelButton, QSaveButton} from "qqq/components/QButtons";
|
import {QCancelButton, QSaveButton} from "qqq/components/QButtons";
|
||||||
|
import ScriptDocsForm from "qqq/pages/entity-view/ScriptDocsForm";
|
||||||
|
import ScriptTestForm from "qqq/pages/entity-view/ScriptTestForm";
|
||||||
import QClient from "qqq/utils/QClient";
|
import QClient from "qqq/utils/QClient";
|
||||||
|
|
||||||
|
interface AssociatedScriptDefinition
|
||||||
|
{
|
||||||
|
testInputFields: QFieldMetaData[];
|
||||||
|
testOutputFields: QFieldMetaData[];
|
||||||
|
scriptType: any;
|
||||||
|
}
|
||||||
|
|
||||||
interface Props
|
interface Props
|
||||||
{
|
{
|
||||||
|
scriptDefinition: AssociatedScriptDefinition;
|
||||||
tableName: string;
|
tableName: string;
|
||||||
primaryKey: any;
|
primaryKey: any;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
@ -46,11 +58,24 @@ interface Props
|
|||||||
|
|
||||||
const qController = QClient.getInstance();
|
const qController = QClient.getInstance();
|
||||||
|
|
||||||
function AssociatedScriptEditor({tableName, primaryKey, fieldName, titlePrefix, recordLabel, scriptName, code, closeCallback}: Props): JSX.Element
|
function AssociatedScriptEditor({scriptDefinition, tableName, primaryKey, fieldName, titlePrefix, recordLabel, scriptName, code, closeCallback}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const [closing, setClosing] = useState(false);
|
const [closing, setClosing] = useState(false);
|
||||||
const [updatedCode, setUpdatedCode] = useState(code)
|
const [updatedCode, setUpdatedCode] = useState(code)
|
||||||
const [commitMessage, setCommitMessage] = useState("")
|
const [commitMessage, setCommitMessage] = useState("")
|
||||||
|
const [openTool, setOpenTool] = useState(null);
|
||||||
|
|
||||||
|
const changeOpenTool = (event: React.MouseEvent<HTMLElement>, newValue: string | null) =>
|
||||||
|
{
|
||||||
|
setOpenTool(newValue);
|
||||||
|
|
||||||
|
// need this to make Ace recognize new height.
|
||||||
|
setTimeout(() =>
|
||||||
|
{
|
||||||
|
window.dispatchEvent(new Event("resize"))
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const saveClicked = () =>
|
const saveClicked = () =>
|
||||||
{
|
{
|
||||||
@ -80,23 +105,56 @@ function AssociatedScriptEditor({tableName, primaryKey, fieldName, titlePrefix,
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{position: "absolute", overflowY: "auto", height: "100%", width: "100%"}} p={12}>
|
<Box sx={{position: "absolute", overflowY: "auto", height: "100%", width: "100%"}} p={6}>
|
||||||
<Card sx={{height: "100%", p: 3}}>
|
<Card sx={{height: "100%", p: 3}}>
|
||||||
<Typography variant="h5" pb={1}>
|
|
||||||
{`${titlePrefix}: ${recordLabel} - ${scriptName}`}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<AceEditor
|
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||||
mode="javascript"
|
<Typography variant="h5" pb={1}>
|
||||||
theme="github"
|
{`${titlePrefix}: ${recordLabel} - ${scriptName}`}
|
||||||
name="editor"
|
</Typography>
|
||||||
editorProps={{$blockScrolling: true}}
|
|
||||||
onChange={updateCode}
|
<Box>
|
||||||
width="100%"
|
<Typography variant="body2" display="inline" pr={1}>
|
||||||
height="100%"
|
Tools:
|
||||||
value={updatedCode}
|
</Typography>
|
||||||
style={{border: "1px solid gray"}}
|
<ToggleButtonGroup
|
||||||
/>
|
value={openTool}
|
||||||
|
exclusive
|
||||||
|
onChange={changeOpenTool}
|
||||||
|
size="small"
|
||||||
|
sx={{pb: 1}}
|
||||||
|
>
|
||||||
|
<ToggleButton value="test">Test</ToggleButton>
|
||||||
|
<ToggleButton value="docs">Docs</ToggleButton>
|
||||||
|
</ToggleButtonGroup>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{height: openTool ? "45%" : "100%"}}>
|
||||||
|
<AceEditor
|
||||||
|
mode="javascript"
|
||||||
|
theme="github"
|
||||||
|
name="editor"
|
||||||
|
editorProps={{$blockScrolling: true}}
|
||||||
|
onChange={updateCode}
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
value={updatedCode}
|
||||||
|
style={{border: "1px solid gray"}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{
|
||||||
|
openTool &&
|
||||||
|
<Box sx={{height: "45%"}} pt={2}>
|
||||||
|
{
|
||||||
|
openTool == "test" && <ScriptTestForm scriptDefinition={scriptDefinition} tableName={tableName} fieldName={fieldName} recordId={primaryKey} code={updatedCode} />
|
||||||
|
}
|
||||||
|
{
|
||||||
|
openTool == "docs" && <ScriptDocsForm helpText={scriptDefinition.scriptType.values.helpText} exampleCode={scriptDefinition.scriptType.values.sampleCode} aceEditorHeight="100%" />
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
|
||||||
<Box pt={1}>
|
<Box pt={1}>
|
||||||
<Grid container alignItems="flex-end">
|
<Grid container alignItems="flex-end">
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException";
|
import {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException";
|
||||||
|
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
||||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||||
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||||
import {Alert, Chip, Icon, ListItem, ListItemAvatar, Typography} from "@mui/material";
|
import {Alert, Chip, Icon, ListItem, ListItemAvatar, Typography} from "@mui/material";
|
||||||
@ -35,24 +36,20 @@ import ListItemText from "@mui/material/ListItemText";
|
|||||||
import Modal from "@mui/material/Modal";
|
import Modal from "@mui/material/Modal";
|
||||||
import Snackbar from "@mui/material/Snackbar";
|
import Snackbar from "@mui/material/Snackbar";
|
||||||
import Tab from "@mui/material/Tab";
|
import Tab from "@mui/material/Tab";
|
||||||
import Table from "@mui/material/Table";
|
|
||||||
import TableBody from "@mui/material/TableBody";
|
|
||||||
import TableContainer from "@mui/material/TableContainer";
|
|
||||||
import TableRow from "@mui/material/TableRow";
|
|
||||||
import Tabs from "@mui/material/Tabs";
|
import Tabs from "@mui/material/Tabs";
|
||||||
import TextField from "@mui/material/TextField";
|
|
||||||
import React, {useContext, useReducer, useState} from "react";
|
import React, {useContext, useReducer, useState} from "react";
|
||||||
import AceEditor from "react-ace";
|
import AceEditor from "react-ace";
|
||||||
import {useParams} from "react-router-dom";
|
import {useParams} from "react-router-dom";
|
||||||
import QContext from "QContext";
|
import QContext from "QContext";
|
||||||
import BaseLayout from "qqq/components/BaseLayout";
|
import BaseLayout from "qqq/components/BaseLayout";
|
||||||
import CustomWidthTooltip from "qqq/components/CustomWidthTooltip/CustomWidthTooltip";
|
import CustomWidthTooltip from "qqq/components/CustomWidthTooltip/CustomWidthTooltip";
|
||||||
import DataTableBodyCell from "qqq/components/Temporary/DataTable/DataTableBodyCell";
|
|
||||||
import DataTableHeadCell from "qqq/components/Temporary/DataTable/DataTableHeadCell";
|
|
||||||
import MDBox from "qqq/components/Temporary/MDBox";
|
import MDBox from "qqq/components/Temporary/MDBox";
|
||||||
import AssociatedScriptEditor from "qqq/pages/entity-view/AssociatedScriptEditor";
|
import AssociatedScriptEditor from "qqq/pages/entity-view/AssociatedScriptEditor";
|
||||||
|
import ScriptLogsView from "qqq/pages/entity-view/ScriptLogsView";
|
||||||
|
import ScriptTestForm from "qqq/pages/entity-view/ScriptTestForm";
|
||||||
import QClient from "qqq/utils/QClient";
|
import QClient from "qqq/utils/QClient";
|
||||||
import QValueUtils from "qqq/utils/QValueUtils";
|
import QValueUtils from "qqq/utils/QValueUtils";
|
||||||
|
import ScriptDocsForm from "./ScriptDocsForm";
|
||||||
|
|
||||||
import "ace-builds/src-noconflict/mode-java";
|
import "ace-builds/src-noconflict/mode-java";
|
||||||
import "ace-builds/src-noconflict/mode-javascript";
|
import "ace-builds/src-noconflict/mode-javascript";
|
||||||
@ -60,7 +57,6 @@ import "ace-builds/src-noconflict/mode-json";
|
|||||||
import "ace-builds/src-noconflict/theme-github";
|
import "ace-builds/src-noconflict/theme-github";
|
||||||
import "ace-builds/src-noconflict/ext-language_tools";
|
import "ace-builds/src-noconflict/ext-language_tools";
|
||||||
|
|
||||||
|
|
||||||
const qController = QClient.getInstance();
|
const qController = QClient.getInstance();
|
||||||
|
|
||||||
interface TabPanelProps
|
interface TabPanelProps
|
||||||
@ -119,6 +115,8 @@ function EntityDeveloperView({table}: Props): JSX.Element
|
|||||||
const [selectedTabs, setSelectedTabs] = useState({} as any);
|
const [selectedTabs, setSelectedTabs] = useState({} as any);
|
||||||
const [viewingRevisions, setViewingRevisions] = useState({} as any);
|
const [viewingRevisions, setViewingRevisions] = useState({} as any);
|
||||||
const [scriptLogs, setScriptLogs] = useState({} as any);
|
const [scriptLogs, setScriptLogs] = useState({} as any);
|
||||||
|
const [testInputValues, setTestInputValues] = useState({} as any);
|
||||||
|
const [testOutputValues, setTestOutputValues] = useState({} as any);
|
||||||
|
|
||||||
const [editingScript, setEditingScript] = useState(null as any);
|
const [editingScript, setEditingScript] = useState(null as any);
|
||||||
const [alertText, setAlertText] = useState(null as string);
|
const [alertText, setAlertText] = useState(null as string);
|
||||||
@ -157,6 +155,23 @@ function EntityDeveloperView({table}: Props): JSX.Element
|
|||||||
|
|
||||||
setAssociatedScripts(developerModeData.associatedScripts);
|
setAssociatedScripts(developerModeData.associatedScripts);
|
||||||
|
|
||||||
|
const testInputValues = {};
|
||||||
|
const testOutputValues = {};
|
||||||
|
console.log("@dk - here");
|
||||||
|
console.log(developerModeData.associatedScripts);
|
||||||
|
developerModeData.associatedScripts.forEach((object: any) =>
|
||||||
|
{
|
||||||
|
const fieldName = object.associatedScript.fieldName;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
testInputValues[fieldName] = {};
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
testOutputValues[fieldName] = {};
|
||||||
|
});
|
||||||
|
setTestInputValues(testInputValues);
|
||||||
|
setTestOutputValues(testOutputValues);
|
||||||
|
|
||||||
const recordJSONObject = {} as any;
|
const recordJSONObject = {} as any;
|
||||||
for (let key of record.values.keys())
|
for (let key of record.values.keys())
|
||||||
{
|
{
|
||||||
@ -208,15 +223,44 @@ function EntityDeveloperView({table}: Props): JSX.Element
|
|||||||
return color;
|
return color;
|
||||||
};
|
};
|
||||||
|
|
||||||
const editScript = (fieldName: string, code: string) =>
|
const editScript = (fieldName: string, code: string, object: any) =>
|
||||||
{
|
{
|
||||||
const editingScript = {} as any;
|
const editingScript = {} as any;
|
||||||
editingScript.fieldName = fieldName;
|
editingScript.fieldName = fieldName;
|
||||||
editingScript.titlePrefix = code ? "Editing Script" : "Creating New Script";
|
editingScript.titlePrefix = code ? "Editing Script" : "Creating New Script";
|
||||||
editingScript.code = code;
|
editingScript.code = code;
|
||||||
|
editingScript.scriptDefinitionObject = object;
|
||||||
setEditingScript(editingScript);
|
setEditingScript(editingScript);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const testScript = (object: any, fieldName: string) =>
|
||||||
|
{
|
||||||
|
const viewingRevisionArray = object.scriptRevisions?.filter((rev: any) => rev?.values?.id === viewingRevisions[fieldName]);
|
||||||
|
const code = viewingRevisionArray?.length > 0 ? viewingRevisionArray[0].values.contents : "";
|
||||||
|
|
||||||
|
const inputValues = new Map<string, any>();
|
||||||
|
if (object.testInputFields)
|
||||||
|
{
|
||||||
|
object.testInputFields.forEach((field: QFieldMetaData) =>
|
||||||
|
{
|
||||||
|
console.log(`${field.name} = ${testInputValues[fieldName][field.name]}`)
|
||||||
|
inputValues.set(field.name, testInputValues[fieldName][field.name]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const newTestOutputValues = JSON.parse(JSON.stringify(testOutputValues));
|
||||||
|
newTestOutputValues[fieldName] = {};
|
||||||
|
setTestOutputValues(newTestOutputValues);
|
||||||
|
|
||||||
|
(async () =>
|
||||||
|
{
|
||||||
|
const output = await qController.testScript(tableName, id, fieldName, code, inputValues);
|
||||||
|
const newTestOutputValues = JSON.parse(JSON.stringify(testOutputValues));
|
||||||
|
newTestOutputValues[fieldName] = output.outputValues;
|
||||||
|
setTestOutputValues(newTestOutputValues);
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
|
||||||
const closeEditingScript = (event: object, reason: string, alert: string = null) =>
|
const closeEditingScript = (event: object, reason: string, alert: string = null) =>
|
||||||
{
|
{
|
||||||
if (reason === "backdropClick")
|
if (reason === "backdropClick")
|
||||||
@ -256,7 +300,7 @@ function EntityDeveloperView({table}: Props): JSX.Element
|
|||||||
scriptLogs[revisionId] = null;
|
scriptLogs[revisionId] = null;
|
||||||
setScriptLogs(scriptLogs);
|
setScriptLogs(scriptLogs);
|
||||||
|
|
||||||
loadRevisionLogs(fieldName, revisionId)
|
loadRevisionLogs(fieldName, revisionId);
|
||||||
|
|
||||||
forceUpdate();
|
forceUpdate();
|
||||||
};
|
};
|
||||||
@ -276,7 +320,7 @@ function EntityDeveloperView({table}: Props): JSX.Element
|
|||||||
setScriptLogs(scriptLogs);
|
setScriptLogs(scriptLogs);
|
||||||
forceUpdate();
|
forceUpdate();
|
||||||
})();
|
})();
|
||||||
}
|
};
|
||||||
|
|
||||||
function getRevisionsList(scriptRevisions: any, fieldName: any, currentScriptRevisionId: any)
|
function getRevisionsList(scriptRevisions: any, fieldName: any, currentScriptRevisionId: any)
|
||||||
{
|
{
|
||||||
@ -333,54 +377,7 @@ function EntityDeveloperView({table}: Props): JSX.Element
|
|||||||
return <Typography variant="body2" p={3}>No logs available for this version.</Typography>;
|
return <Typography variant="body2" p={3}>No logs available for this version.</Typography>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (<ScriptLogsView logs={logs} />);
|
||||||
<TableContainer sx={{boxShadow: "none"}}>
|
|
||||||
<Table>
|
|
||||||
<Box component="thead">
|
|
||||||
<TableRow key="header">
|
|
||||||
<DataTableHeadCell sorted={false}>Timestamp</DataTableHeadCell>
|
|
||||||
<DataTableHeadCell sorted={false} align="right">Run Time (ms)</DataTableHeadCell>
|
|
||||||
<DataTableHeadCell sorted={false}>Had Error?</DataTableHeadCell>
|
|
||||||
<DataTableHeadCell sorted={false}>Input</DataTableHeadCell>
|
|
||||||
<DataTableHeadCell sorted={false}>Output</DataTableHeadCell>
|
|
||||||
<DataTableHeadCell sorted={false}>Logs</DataTableHeadCell>
|
|
||||||
</TableRow>
|
|
||||||
</Box>
|
|
||||||
<TableBody>
|
|
||||||
{
|
|
||||||
logs.map((logRecord) =>
|
|
||||||
{
|
|
||||||
let logs = "";
|
|
||||||
if (logRecord.values.scriptLogLine)
|
|
||||||
{
|
|
||||||
for (let i = 0; i < logRecord.values.scriptLogLine.length; i++)
|
|
||||||
{
|
|
||||||
console.log(" += " + i);
|
|
||||||
logs += (logRecord.values.scriptLogLine[i].values.text + "\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableRow key={logRecord.values.id}>
|
|
||||||
<DataTableBodyCell>{QValueUtils.formatDateTime(logRecord.values.startTimestamp)}</DataTableBodyCell>
|
|
||||||
<DataTableBodyCell align="right">{logRecord.values.runTimeMillis?.toLocaleString()}</DataTableBodyCell>
|
|
||||||
<DataTableBodyCell>
|
|
||||||
<div style={{color: logRecord.values.hadError ? "red" : "auto"}}>{QValueUtils.formatBoolean(logRecord.values.hadError)}</div>
|
|
||||||
</DataTableBodyCell>
|
|
||||||
<DataTableBodyCell>{logRecord.values.input}</DataTableBodyCell>
|
|
||||||
<DataTableBodyCell>
|
|
||||||
{logRecord.values.output}
|
|
||||||
{logRecord.values.error}
|
|
||||||
</DataTableBodyCell>
|
|
||||||
<DataTableBodyCell>{logs}</DataTableBodyCell>
|
|
||||||
</TableRow>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -440,7 +437,7 @@ function EntityDeveloperView({table}: Props): JSX.Element
|
|||||||
console.log(`Defaulting revision for ${fieldName} to ${currentScriptRevisionId}`);
|
console.log(`Defaulting revision for ${fieldName} to ${currentScriptRevisionId}`);
|
||||||
viewingRevisions[fieldName] = currentScriptRevisionId;
|
viewingRevisions[fieldName] = currentScriptRevisionId;
|
||||||
|
|
||||||
if(!scriptLogs[currentScriptRevisionId])
|
if (!scriptLogs[currentScriptRevisionId])
|
||||||
{
|
{
|
||||||
loadRevisionLogs(fieldName, currentScriptRevisionId);
|
loadRevisionLogs(fieldName, currentScriptRevisionId);
|
||||||
}
|
}
|
||||||
@ -508,7 +505,7 @@ function EntityDeveloperView({table}: Props): JSX.Element
|
|||||||
</Typography>
|
</Typography>
|
||||||
}
|
}
|
||||||
<CustomWidthTooltip title={editButtonTooltip}>
|
<CustomWidthTooltip title={editButtonTooltip}>
|
||||||
<Button sx={{py: 0}} onClick={() => editScript(fieldName, code)}>
|
<Button sx={{py: 0}} onClick={() => editScript(fieldName, code, object)}>
|
||||||
{editButtonText}
|
{editButtonText}
|
||||||
</Button>
|
</Button>
|
||||||
</CustomWidthTooltip>
|
</CustomWidthTooltip>
|
||||||
@ -521,6 +518,7 @@ function EntityDeveloperView({table}: Props): JSX.Element
|
|||||||
theme="github"
|
theme="github"
|
||||||
name={`view-${fieldName}`}
|
name={`view-${fieldName}`}
|
||||||
readOnly
|
readOnly
|
||||||
|
highlightActiveLine={false}
|
||||||
editorProps={{$blockScrolling: true}}
|
editorProps={{$blockScrolling: true}}
|
||||||
width="100%"
|
width="100%"
|
||||||
height="400px"
|
height="400px"
|
||||||
@ -551,71 +549,15 @@ function EntityDeveloperView({table}: Props): JSX.Element
|
|||||||
</Grid>
|
</Grid>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel index={2} value={selectedTabs[fieldName]}>
|
<TabPanel index={2} value={selectedTabs[fieldName]}>
|
||||||
<Grid container height="440px" spacing={2}>
|
<Box sx={{height: "455px"}} px={2} pb={1}>
|
||||||
<Grid item xs={6}>
|
<ScriptTestForm scriptDefinition={object} tableName={tableName} fieldName={fieldName} recordId={id} code={code} />
|
||||||
<Box gap={2} pb={1} height="40px" px={2}>
|
</Box>
|
||||||
<Card sx={{width: "100%", height: "400px"}}>
|
|
||||||
<Box width="100%">
|
|
||||||
<Typography variant="h6" p={2}>Test Input</Typography>
|
|
||||||
<Box px={2} pb={2}>
|
|
||||||
<TextField id="testInput1" label="Ship To Zip" variant="standard" fullWidth sx={{mb: 2}} />
|
|
||||||
<TextField id="testInput1" label="No of Cartons" variant="standard" fullWidth sx={{mb: 2}} />
|
|
||||||
</Box>
|
|
||||||
<div style={{float: "right"}}>
|
|
||||||
<Button>Submit</Button>
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
</Card>
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={6}>
|
|
||||||
<Box gap={2} pb={1} height="40px">
|
|
||||||
<Card sx={{width: "100%", height: "400px"}}>
|
|
||||||
<Typography variant="h6" pl={3}>Test Output</Typography>
|
|
||||||
</Card>
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel index={3} value={selectedTabs[fieldName]}>
|
<TabPanel index={3} value={selectedTabs[fieldName]}>
|
||||||
<Grid container height="440px">
|
<Box sx={{height: "455px"}} px={2} pb={1}>
|
||||||
<Grid item xs={12}>
|
<ScriptDocsForm helpText={object.scriptType.values.helpText} exampleCode={object.scriptType.values.sampleCode} />
|
||||||
<Box gap={2} pb={1} pl={3}>
|
</Box>
|
||||||
<Box pb={1}>
|
|
||||||
<Typography variant="h6">Documentation</Typography>
|
|
||||||
</Box>
|
|
||||||
<Box sx={{overflow: "auto"}} className="devDocumentation">
|
|
||||||
<Typography variant="body2" sx={{maxWidth: "1200px", margin: "auto"}}>
|
|
||||||
<p>A <b>Deposco Order Optimization Batch Name Script</b> is called when an order is being
|
|
||||||
optimized for shipping within Deposco. It is responsible for determining the order's
|
|
||||||
<b>Batch Name</b> - in other words, an indication of what day the order should be shipped,
|
|
||||||
and whether or not the order is a line haul.</p>
|
|
||||||
|
|
||||||
<p><b>Input</b></p>
|
|
||||||
<p>The input to this type of script is an object named <code>input</code>, with the following fields:</p>
|
|
||||||
<ul>
|
|
||||||
<li><code>warehouseId</code> The id of the warehouse that the order is shipping from. See the <b>Warehouse</b> table for mappings.</li>
|
|
||||||
<li><code>shipToZipCode</code> The zip code that the order is shipping to.</li>
|
|
||||||
<li><code>estimatedNoOfCartons</code> The estimated number of cartons that the order will ship in.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p><b>Output</b></p>
|
|
||||||
<p>The script is responsible only for outputting a single value - a <code>string</code> which will be set as the order's
|
|
||||||
<b>Batch Name</b> in Deposco.</p>
|
|
||||||
|
|
||||||
<p><b>Example</b></p>
|
|
||||||
<code style={{whiteSpace: "pre-wrap"}}>
|
|
||||||
if(today.weekday == 1)
|
|
||||||
(
|
|
||||||
return "TUE-Line-Haul"
|
|
||||||
)
|
|
||||||
</code>
|
|
||||||
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
@ -629,6 +571,7 @@ function EntityDeveloperView({table}: Props): JSX.Element
|
|||||||
editingScript &&
|
editingScript &&
|
||||||
<Modal open={editingScript as boolean} onClose={(event, reason) => closeEditingScript(event, reason)}>
|
<Modal open={editingScript as boolean} onClose={(event, reason) => closeEditingScript(event, reason)}>
|
||||||
<AssociatedScriptEditor
|
<AssociatedScriptEditor
|
||||||
|
scriptDefinition={editingScript.scriptDefinitionObject}
|
||||||
tableName={tableName}
|
tableName={tableName}
|
||||||
primaryKey={id}
|
primaryKey={id}
|
||||||
fieldName={editingScript.fieldName}
|
fieldName={editingScript.fieldName}
|
||||||
|
82
src/qqq/pages/entity-view/ScriptDocsForm.tsx
Normal file
82
src/qqq/pages/entity-view/ScriptDocsForm.tsx
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
import {Typography} from "@mui/material";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import Card from "@mui/material/Card";
|
||||||
|
import Grid from "@mui/material/Grid";
|
||||||
|
import React from "react";
|
||||||
|
import AceEditor from "react-ace";
|
||||||
|
|
||||||
|
interface Props
|
||||||
|
{
|
||||||
|
helpText: string;
|
||||||
|
exampleCode: string;
|
||||||
|
aceEditorHeight: string
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptDocsForm.defaultProps = {
|
||||||
|
aceEditorHeight: "100%",
|
||||||
|
};
|
||||||
|
|
||||||
|
function ScriptDocsForm({helpText, exampleCode, aceEditorHeight}: Props): JSX.Element
|
||||||
|
{
|
||||||
|
|
||||||
|
const oneBlock = (name: string, mode: string, heading: string, code: string): JSX.Element =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<Grid item xs={6} height="100%">
|
||||||
|
<Box gap={2} pb={1} pr={2} height="100%">
|
||||||
|
<Card sx={{width: "100%", height: "100%"}}>
|
||||||
|
<Typography variant="h6" p={2} pb={1}>{heading}</Typography>
|
||||||
|
<Box className="devDocumentation" height="100%">
|
||||||
|
<Typography variant="body2" sx={{maxWidth: "1200px", margin: "auto", height: "100%"}}>
|
||||||
|
<AceEditor
|
||||||
|
mode={mode}
|
||||||
|
theme="github"
|
||||||
|
name={name}
|
||||||
|
editorProps={{$blockScrolling: true}}
|
||||||
|
value={code}
|
||||||
|
readOnly
|
||||||
|
highlightActiveLine={false}
|
||||||
|
width="100%"
|
||||||
|
showPrintMargin={false}
|
||||||
|
height="100%"
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container spacing={2} height="100%">
|
||||||
|
{oneBlock("helpText", "text", "Documentation", helpText)}
|
||||||
|
{oneBlock("exampleCode", "javascript", "ExampleCode", exampleCode)}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ScriptDocsForm;
|
||||||
|
|
96
src/qqq/pages/entity-view/ScriptLogsView.tsx
Normal file
96
src/qqq/pages/entity-view/ScriptLogsView.tsx
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import Table from "@mui/material/Table";
|
||||||
|
import TableBody from "@mui/material/TableBody";
|
||||||
|
import TableContainer from "@mui/material/TableContainer";
|
||||||
|
import TableRow from "@mui/material/TableRow";
|
||||||
|
import React from "react";
|
||||||
|
import DataTableBodyCell from "qqq/components/Temporary/DataTable/DataTableBodyCell";
|
||||||
|
import DataTableHeadCell from "qqq/components/Temporary/DataTable/DataTableHeadCell";
|
||||||
|
import QValueUtils from "qqq/utils/QValueUtils";
|
||||||
|
|
||||||
|
interface Props
|
||||||
|
{
|
||||||
|
logs: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptLogsView.defaultProps = {
|
||||||
|
logs: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
function ScriptLogsView({logs}: Props): JSX.Element
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<TableContainer sx={{boxShadow: "none"}}>
|
||||||
|
<Table>
|
||||||
|
<Box component="thead">
|
||||||
|
<TableRow key="header">
|
||||||
|
<DataTableHeadCell sorted={false}>Timestamp</DataTableHeadCell>
|
||||||
|
<DataTableHeadCell sorted={false} align="right">Run Time (ms)</DataTableHeadCell>
|
||||||
|
<DataTableHeadCell sorted={false}>Had Error?</DataTableHeadCell>
|
||||||
|
<DataTableHeadCell sorted={false}>Input</DataTableHeadCell>
|
||||||
|
<DataTableHeadCell sorted={false}>Output</DataTableHeadCell>
|
||||||
|
<DataTableHeadCell sorted={false}>Logs</DataTableHeadCell>
|
||||||
|
</TableRow>
|
||||||
|
</Box>
|
||||||
|
<TableBody>
|
||||||
|
{
|
||||||
|
logs.map((logRecord: any) =>
|
||||||
|
{
|
||||||
|
let logs = "";
|
||||||
|
if (logRecord.values.scriptLogLine)
|
||||||
|
{
|
||||||
|
for (let i = 0; i < logRecord.values.scriptLogLine.length; i++)
|
||||||
|
{
|
||||||
|
console.log(" += " + i);
|
||||||
|
logs += (logRecord.values.scriptLogLine[i].values.text + "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow key={logRecord.values.id}>
|
||||||
|
<DataTableBodyCell>{QValueUtils.formatDateTime(logRecord.values.startTimestamp)}</DataTableBodyCell>
|
||||||
|
<DataTableBodyCell align="right">{logRecord.values.runTimeMillis?.toLocaleString()}</DataTableBodyCell>
|
||||||
|
<DataTableBodyCell>
|
||||||
|
<div style={{color: logRecord.values.hadError ? "red" : "auto"}}>{QValueUtils.formatBoolean(logRecord.values.hadError)}</div>
|
||||||
|
</DataTableBodyCell>
|
||||||
|
<DataTableBodyCell>{logRecord.values.input}</DataTableBodyCell>
|
||||||
|
<DataTableBodyCell>
|
||||||
|
{logRecord.values.output}
|
||||||
|
{logRecord.values.error}
|
||||||
|
</DataTableBodyCell>
|
||||||
|
<DataTableBodyCell>{logs}</DataTableBodyCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ScriptLogsView;
|
||||||
|
|
||||||
|
|
189
src/qqq/pages/entity-view/ScriptTestForm.tsx
Normal file
189
src/qqq/pages/entity-view/ScriptTestForm.tsx
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
||||||
|
import {Typography} from "@mui/material";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import Button from "@mui/material/Button";
|
||||||
|
import Card from "@mui/material/Card";
|
||||||
|
import Grid from "@mui/material/Grid";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import React, {useEffect, useState} from "react";
|
||||||
|
import MDTypography from "components/MDTypography";
|
||||||
|
import MDBox from "qqq/components/Temporary/MDBox";
|
||||||
|
import QClient from "qqq/utils/QClient";
|
||||||
|
import QValueUtils from "qqq/utils/QValueUtils";
|
||||||
|
|
||||||
|
interface AssociatedScriptDefinition
|
||||||
|
{
|
||||||
|
testInputFields: QFieldMetaData[];
|
||||||
|
testOutputFields: QFieldMetaData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props
|
||||||
|
{
|
||||||
|
scriptDefinition: AssociatedScriptDefinition;
|
||||||
|
tableName: string;
|
||||||
|
fieldName: string;
|
||||||
|
recordId: any;
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptTestForm.defaultProps = {
|
||||||
|
// foo: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const qController = QClient.getInstance();
|
||||||
|
|
||||||
|
function ScriptTestForm({scriptDefinition, tableName, fieldName, recordId, code}: Props): JSX.Element
|
||||||
|
{
|
||||||
|
const [testInputValues, setTestInputValues] = useState({} as any);
|
||||||
|
const [testOutputValues, setTestOutputValues] = useState({} as any);
|
||||||
|
const [testException, setTestException] = useState(null as string)
|
||||||
|
const [firstRender, setFirstRender] = useState(true);
|
||||||
|
|
||||||
|
if(firstRender)
|
||||||
|
{
|
||||||
|
setFirstRender(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(firstRender)
|
||||||
|
{
|
||||||
|
scriptDefinition.testInputFields.forEach((field: QFieldMetaData) =>
|
||||||
|
{
|
||||||
|
testInputValues[field.name] = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const testScript = () =>
|
||||||
|
{
|
||||||
|
const inputValues = new Map<string, any>();
|
||||||
|
if (scriptDefinition.testInputFields)
|
||||||
|
{
|
||||||
|
scriptDefinition.testInputFields.forEach((field: QFieldMetaData) =>
|
||||||
|
{
|
||||||
|
inputValues.set(field.name, testInputValues[field.name]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setTestOutputValues({});
|
||||||
|
setTestException(null);
|
||||||
|
|
||||||
|
(async () =>
|
||||||
|
{
|
||||||
|
const output = await qController.testScript(tableName, recordId, fieldName, code, inputValues);
|
||||||
|
console.log("got output:")
|
||||||
|
console.log(output);
|
||||||
|
console.log(Object.keys(output));
|
||||||
|
setTestOutputValues(output.outputObject);
|
||||||
|
if(output.exception)
|
||||||
|
{
|
||||||
|
setTestException(output.exception.message)
|
||||||
|
console.log(`set test exception to ${output.exception.message}`);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
|
||||||
|
// console.log("Rendering vvv");
|
||||||
|
// console.log(`${testOutputValues}`);
|
||||||
|
// console.log("Rendering ^^^");
|
||||||
|
|
||||||
|
const handleInputChange = (fieldName: string, newValue: string) =>
|
||||||
|
{
|
||||||
|
testInputValues[fieldName] = newValue;
|
||||||
|
console.log(`Setting ${fieldName} = ${newValue}`);
|
||||||
|
setTestInputValues(JSON.parse(JSON.stringify(testInputValues)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log(testInputValues);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container spacing={2} height="100%">
|
||||||
|
<Grid item xs={6} height="100%">
|
||||||
|
<Box gap={2} pb={1} pr={2} height="100%">
|
||||||
|
<Card sx={{width: "100%", height: "100%", overflow: "auto"}}>
|
||||||
|
<Box width="100%">
|
||||||
|
<Typography variant="h6" p={2} pb={1}>Test Input</Typography>
|
||||||
|
<Box px={2} pb={2}>
|
||||||
|
{
|
||||||
|
scriptDefinition.testInputFields && testInputValues && scriptDefinition.testInputFields.map((field: QFieldMetaData) =>
|
||||||
|
{
|
||||||
|
return (<TextField
|
||||||
|
key={field.name}
|
||||||
|
id={field.name}
|
||||||
|
label={field.label}
|
||||||
|
value={testInputValues[field.name]}
|
||||||
|
variant="standard"
|
||||||
|
onChange={(event) =>
|
||||||
|
{
|
||||||
|
handleInputChange(field.name, event.target.value);
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
sx={{mb: 2}}
|
||||||
|
/>);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
<div style={{float: "right"}}>
|
||||||
|
<Button onClick={() => testScript()}>Submit</Button>
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} height="100%">
|
||||||
|
<Box gap={2} pb={1} height="100%">
|
||||||
|
<Card sx={{width: "100%", height: "100%", overflow: "auto"}}>
|
||||||
|
<Typography variant="h6" p={2} pl={3} pb={1}>Test Output</Typography>
|
||||||
|
<Box p={3} pt={0}>
|
||||||
|
{
|
||||||
|
testException &&
|
||||||
|
<Typography variant="body2" color="red">
|
||||||
|
{testException}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
scriptDefinition.testOutputFields && testOutputValues && scriptDefinition.testOutputFields.map((f: any) =>
|
||||||
|
{
|
||||||
|
const field = new QFieldMetaData(f);
|
||||||
|
console.log(field.name);
|
||||||
|
console.log(testOutputValues[field.name]);
|
||||||
|
return (
|
||||||
|
<MDBox key={field.name} flexDirection="row" pr={2}>
|
||||||
|
<Typography variant="button" fontWeight="bold" pr={1}>
|
||||||
|
{field.label}:
|
||||||
|
</Typography>
|
||||||
|
<MDTypography variant="button" fontWeight="regular" color="text">
|
||||||
|
{QValueUtils.getValueForDisplay(field, testOutputValues[field.name], testOutputValues[field.name], "view")}
|
||||||
|
</MDTypography>
|
||||||
|
</MDBox>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ScriptTestForm;
|
@ -144,15 +144,23 @@ class QValueUtils
|
|||||||
|
|
||||||
if (field.hasAdornment(AdornmentType.CODE_EDITOR))
|
if (field.hasAdornment(AdornmentType.CODE_EDITOR))
|
||||||
{
|
{
|
||||||
|
let mode = "text";
|
||||||
|
const adornmentValues = field.getAdornment(AdornmentType.CODE_EDITOR).values;
|
||||||
|
if (adornmentValues.has("languageMode"))
|
||||||
|
{
|
||||||
|
mode = adornmentValues.get("languageMode");
|
||||||
|
}
|
||||||
|
|
||||||
if(usage === "view")
|
if(usage === "view")
|
||||||
{
|
{
|
||||||
return (<AceEditor
|
return (<AceEditor
|
||||||
mode="javascript"
|
mode={mode}
|
||||||
theme="github"
|
theme="github"
|
||||||
name={field.name}
|
name={field.name}
|
||||||
editorProps={{$blockScrolling: true}}
|
editorProps={{$blockScrolling: true}}
|
||||||
value={rawValue}
|
value={rawValue}
|
||||||
readOnly
|
readOnly
|
||||||
|
highlightActiveLine={false}
|
||||||
width="100%"
|
width="100%"
|
||||||
showPrintMargin={false}
|
showPrintMargin={false}
|
||||||
height="200px"
|
height="200px"
|
||||||
@ -191,6 +199,10 @@ class QValueUtils
|
|||||||
{
|
{
|
||||||
return (displayValue);
|
return (displayValue);
|
||||||
}
|
}
|
||||||
|
else if (field.type === QFieldType.BOOLEAN && (typeof displayValue) === "boolean")
|
||||||
|
{
|
||||||
|
return displayValue ? "Yes" : "No";
|
||||||
|
}
|
||||||
|
|
||||||
let returnValue = displayValue;
|
let returnValue = displayValue;
|
||||||
if (displayValue === undefined && rawValue !== undefined)
|
if (displayValue === undefined && rawValue !== undefined)
|
||||||
|
Reference in New Issue
Block a user