mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 05:10:45 +00:00
Initial checkin - record scripts WIP
This commit is contained in:
210
src/qqq/components/scripts/ScriptEditor.tsx
Normal file
210
src/qqq/components/scripts/ScriptEditor.tsx
Normal file
@ -0,0 +1,210 @@
|
||||
/*
|
||||
* 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 {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError";
|
||||
import {ToggleButton, ToggleButtonGroup, Typography} from "@mui/material";
|
||||
import Alert from "@mui/material/Alert";
|
||||
import Box from "@mui/material/Box";
|
||||
import Card from "@mui/material/Card";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Snackbar from "@mui/material/Snackbar";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import FormData from "form-data";
|
||||
import React, {useReducer, useState} from "react";
|
||||
import AceEditor from "react-ace";
|
||||
import {QCancelButton, QSaveButton} from "qqq/components/buttons/DefaultButtons";
|
||||
import Client from "qqq/utils/qqq/Client";
|
||||
|
||||
export interface ScriptEditorProps
|
||||
{
|
||||
title: string;
|
||||
scriptId: number;
|
||||
contents: string;
|
||||
closeCallback: any;
|
||||
}
|
||||
|
||||
|
||||
const qController = Client.getInstance();
|
||||
|
||||
function ScriptEditor({title, scriptId, contents, closeCallback}: ScriptEditorProps): JSX.Element
|
||||
{
|
||||
const [closing, setClosing] = useState(false);
|
||||
const [updatedCode, setUpdatedCode] = useState(contents)
|
||||
const [commitMessage, setCommitMessage] = useState("")
|
||||
const [openTool, setOpenTool] = useState(null);
|
||||
const [errorAlert, setErrorAlert] = useState("")
|
||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||
|
||||
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 = () =>
|
||||
{
|
||||
setClosing(true);
|
||||
|
||||
(async () =>
|
||||
{
|
||||
const formData = new FormData();
|
||||
formData.append("scriptId", scriptId);
|
||||
formData.append("contents", updatedCode);
|
||||
formData.append("commitMessage", commitMessage);
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// we don't want this job to go async, so, pass a large timeout //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
formData.append("_qStepTimeoutMillis", 60 * 1000);
|
||||
|
||||
const formDataHeaders = {
|
||||
"content-type": "multipart/form-data; boundary=--------------------------320289315924586491558366",
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
const processResult = await qController.processInit("storeScriptRevision", formData, formDataHeaders);
|
||||
console.log("process result");
|
||||
console.log(processResult);
|
||||
|
||||
if (processResult instanceof QJobError)
|
||||
{
|
||||
const jobError = processResult as QJobError
|
||||
setErrorAlert(jobError.userFacingError ?? jobError.error)
|
||||
setClosing(false);
|
||||
return;
|
||||
}
|
||||
|
||||
closeCallback(null, "saved", "Saved New Script Version");
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
// @ts-ignore
|
||||
setErrorAlert(e.message ?? "Unexpected error saving script")
|
||||
setClosing(false);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
const cancelClicked = () =>
|
||||
{
|
||||
setClosing(true);
|
||||
closeCallback(null, "cancelled");
|
||||
}
|
||||
|
||||
const updateCode = (value: string, event: any) =>
|
||||
{
|
||||
console.log("Updating code")
|
||||
setUpdatedCode(value);
|
||||
forceUpdate();
|
||||
}
|
||||
|
||||
const updateCommitMessage = (event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
{
|
||||
setCommitMessage(event.target.value);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{position: "absolute", overflowY: "auto", height: "100%", width: "100%"}} p={6}>
|
||||
<Card sx={{height: "100%", p: 3}}>
|
||||
|
||||
<Snackbar open={errorAlert !== null && errorAlert !== ""} onClose={(event?: React.SyntheticEvent | Event, reason?: string) =>
|
||||
{
|
||||
if (reason === "clickaway")
|
||||
{
|
||||
return;
|
||||
}
|
||||
setErrorAlert("")
|
||||
}} anchorOrigin={{vertical: "top", horizontal: "center"}}>
|
||||
<Alert color="error" onClose={() => setErrorAlert("")}>
|
||||
{errorAlert}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
<Typography variant="h5" pb={1}>
|
||||
{title}
|
||||
</Typography>
|
||||
|
||||
<Box>
|
||||
<Typography variant="body2" display="inline" pr={1}>
|
||||
Tools:
|
||||
</Typography>
|
||||
<ToggleButtonGroup
|
||||
value={openTool}
|
||||
exclusive
|
||||
onChange={changeOpenTool}
|
||||
size="small"
|
||||
sx={{pb: 1}}
|
||||
>
|
||||
<ToggleButton value="preview">Preview</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 == "preview" &&
|
||||
<Box fontSize="14px" overflow="auto" height="100%" border="1px solid gray" pt={1}>
|
||||
<DataBagPreview json={updatedCode} />
|
||||
</Box>
|
||||
}
|
||||
</Box>
|
||||
*/}
|
||||
|
||||
<Box pt={1}>
|
||||
<Grid container alignItems="flex-end">
|
||||
<Box width="50%">
|
||||
<TextField id="commitMessage" label="Commit Message" variant="standard" fullWidth value={commitMessage} onChange={updateCommitMessage} />
|
||||
</Box>
|
||||
<Grid container justifyContent="flex-end" spacing={3}>
|
||||
<QCancelButton disabled={closing} onClickHandler={cancelClicked} />
|
||||
<QSaveButton disabled={closing} onClickHandler={saveClicked} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</Card>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default ScriptEditor;
|
394
src/qqq/components/widgets/misc/ScriptViewer.tsx
Normal file
394
src/qqq/components/widgets/misc/ScriptViewer.tsx
Normal file
@ -0,0 +1,394 @@
|
||||
/*
|
||||
* 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 {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException";
|
||||
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||
import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator";
|
||||
import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria";
|
||||
import {QFilterOrderBy} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterOrderBy";
|
||||
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
|
||||
import Alert from "@mui/material/Alert";
|
||||
import Avatar from "@mui/material/Avatar";
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
import Chip from "@mui/material/Chip";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import List from "@mui/material/List";
|
||||
import ListItem from "@mui/material/ListItem";
|
||||
import ListItemAvatar from "@mui/material/ListItemAvatar";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import Modal from "@mui/material/Modal";
|
||||
import Snackbar from "@mui/material/Snackbar";
|
||||
import Tab from "@mui/material/Tab";
|
||||
import Tabs from "@mui/material/Tabs";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import React, {useReducer, useState} from "react";
|
||||
import AceEditor from "react-ace";
|
||||
import TabPanel from "qqq/components/misc/TabPanel";
|
||||
import ScriptEditor, {ScriptEditorProps} from "qqq/components/scripts/ScriptEditor";
|
||||
import CustomWidthTooltip from "qqq/components/tooltips/CustomWidthTooltip";
|
||||
import {LoadingState} from "qqq/models/LoadingState";
|
||||
import DeveloperModeUtils from "qqq/utils/DeveloperModeUtils";
|
||||
import Client from "qqq/utils/qqq/Client";
|
||||
import ValueUtils from "qqq/utils/qqq/ValueUtils";
|
||||
|
||||
import "ace-builds/src-noconflict/mode-java";
|
||||
import "ace-builds/src-noconflict/mode-javascript";
|
||||
import "ace-builds/src-noconflict/mode-json";
|
||||
import "ace-builds/src-noconflict/theme-github";
|
||||
import "ace-builds/src-noconflict/ext-language_tools";
|
||||
|
||||
const qController = Client.getInstance();
|
||||
|
||||
// Declaring props types for ViewForm
|
||||
interface Props
|
||||
{
|
||||
scriptId: number
|
||||
}
|
||||
|
||||
ScriptViewer.defaultProps =
|
||||
{
|
||||
};
|
||||
|
||||
export default function ScriptViewer({scriptId}: Props): JSX.Element
|
||||
{
|
||||
const [scriptRecord, setScriptRecord] = useState(null as QRecord);
|
||||
const [asyncLoadInited, setAsyncLoadInited] = useState(false);
|
||||
const [versionRecordList, setVersionRecordList] = useState(null as QRecord[]);
|
||||
const [selectedVersionRecord, setSelectedVersionRecord] = useState(null as QRecord);
|
||||
const [currentVersionId , setCurrentVersionId] = useState(null as number);
|
||||
const [notFoundMessage, setNotFoundMessage] = useState(null);
|
||||
const [selectedTab, setSelectedTab] = useState(0);
|
||||
const [editorProps, setEditorProps] = useState(null as ScriptEditorProps);
|
||||
const [successText, setSuccessText] = useState(null as string);
|
||||
const [failText, setFailText] = useState(null as string)
|
||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||
|
||||
const [loadingSelectedVersion, _] = useState(new LoadingState(forceUpdate, "loading"));
|
||||
|
||||
if (!asyncLoadInited)
|
||||
{
|
||||
setAsyncLoadInited(true);
|
||||
|
||||
(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const scriptRecord = await qController.get("script", scriptId);
|
||||
setScriptRecord(scriptRecord);
|
||||
|
||||
const criteria = [new QFilterCriteria("scriptId", QCriteriaOperator.EQUALS, [scriptId])];
|
||||
const orderBys = [new QFilterOrderBy("sequenceNo", false)];
|
||||
const filter = new QQueryFilter(criteria, orderBys);
|
||||
const versions = await qController.query("scriptRevision", filter, 25, 0);
|
||||
console.log("Fetched versions:");
|
||||
console.log(versions);
|
||||
setVersionRecordList(versions);
|
||||
|
||||
if(versions && versions.length > 0)
|
||||
{
|
||||
setCurrentVersionId(versions[0].values.get("id"));
|
||||
const latestVersion = await qController.get("scriptRevision", versions[0].values.get("id"));
|
||||
console.log("Fetched latestVersion:");
|
||||
console.log(latestVersion);
|
||||
setSelectedVersionRecord(latestVersion);
|
||||
loadingSelectedVersion.setNotLoading();
|
||||
forceUpdate();
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
if (e instanceof QException)
|
||||
{
|
||||
if ((e as QException).status === "404")
|
||||
{
|
||||
setNotFoundMessage("Script code could not be found.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
setNotFoundMessage("Error loading Script code: " + e);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
const editData = (contents: string) =>
|
||||
{
|
||||
const editorProps = {} as ScriptEditorProps;
|
||||
editorProps.title = (contents ? "Editing Code for Script: " : "Initializing Code for Script: ") + scriptRecord?.values?.get("name");
|
||||
editorProps.contents = contents;
|
||||
editorProps.scriptId = scriptId;
|
||||
setEditorProps(editorProps);
|
||||
};
|
||||
|
||||
const closeEditingScript = (event: object, reason: string, alert: string = null) =>
|
||||
{
|
||||
if (reason === "backdropClick" || reason === "escapeKeyDown")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (reason === "saved")
|
||||
{
|
||||
setAsyncLoadInited(false);
|
||||
forceUpdate();
|
||||
|
||||
if (alert)
|
||||
{
|
||||
setSuccessText(alert);
|
||||
}
|
||||
}
|
||||
else if (reason === "failed")
|
||||
{
|
||||
setAsyncLoadInited(false);
|
||||
forceUpdate();
|
||||
|
||||
if (alert)
|
||||
{
|
||||
setFailText(alert);
|
||||
}
|
||||
}
|
||||
|
||||
setEditorProps(null);
|
||||
};
|
||||
|
||||
const changeTab = (newValue: number) =>
|
||||
{
|
||||
setSelectedTab(newValue);
|
||||
forceUpdate();
|
||||
};
|
||||
|
||||
const selectVersion = (version: QRecord) =>
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
// fetch the full version
|
||||
setSelectedVersionRecord(version);
|
||||
loadingSelectedVersion.setLoading();
|
||||
|
||||
const selectedVersion = await qController.get("scriptVersion", version.values.get("id"));
|
||||
console.log("Fetched selectedVersion:");
|
||||
console.log(selectedVersion);
|
||||
setSelectedVersionRecord(selectedVersion);
|
||||
loadingSelectedVersion.setNotLoading();
|
||||
forceUpdate();
|
||||
})();
|
||||
};
|
||||
|
||||
function getVersionsList(versionRecordList: QRecord[], selectedVersionRecord: QRecord)
|
||||
{
|
||||
return <List sx={{pl: 3, height: "400px", overflow: "auto"}}>
|
||||
{
|
||||
(versionRecordList == null || versionRecordList.length == 0) ?
|
||||
<Typography variant="body2">
|
||||
There are not any versions of this script.
|
||||
</Typography>
|
||||
: <></>
|
||||
}
|
||||
{
|
||||
versionRecordList?.map((version: any) => (
|
||||
<React.Fragment key={version.values.get("id")}>
|
||||
<ListItem sx={{p: 1}} alignItems="flex-start" selected={selectedVersionRecord?.values?.get("id") == version.values.get("id")} onClick={(event) => selectVersion(version)}>
|
||||
<ListItemAvatar>
|
||||
<Avatar sx={{bgcolor: DeveloperModeUtils.revToColor("", scriptId, version.values.get("sequenceNo"))}}>{`${version.values.get("sequenceNo")}`}</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primaryTypographyProps={{fontSize: "1rem"}}
|
||||
secondaryTypographyProps={{fontSize: ".85rem"}}
|
||||
primary={
|
||||
<div style={{whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis"}} title={version.values.get("commitMessage")}>
|
||||
{currentVersionId == version?.values?.get("id") && <Chip label="CURRENT" color="success" variant="outlined" size="small" sx={{mr: 1, fontSize: "0.75rem"}} />}
|
||||
{version.values.get("commitMessage")}
|
||||
</div>
|
||||
}
|
||||
secondary={
|
||||
<>
|
||||
{ValueUtils.formatDateTime(version.values.get("createDate"))}
|
||||
<br />
|
||||
{version.values.get("author")}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<Divider sx={{my: 0.5}} variant="inset" component="li" />
|
||||
</React.Fragment>
|
||||
))
|
||||
}
|
||||
</List>;
|
||||
}
|
||||
|
||||
let editButtonTooltip = "";
|
||||
let editButtonText = "Create New Version";
|
||||
if (currentVersionId)
|
||||
{
|
||||
if (currentVersionId === selectedVersionRecord?.values?.get("id"))
|
||||
{
|
||||
editButtonTooltip = "If you make any changes to this script, a new version will be created when you hit Save.";
|
||||
editButtonText = "Edit";
|
||||
}
|
||||
else
|
||||
{
|
||||
editButtonTooltip = "If you want to make this previous version active, bring up the Edit window, make any changes " +
|
||||
"to the old version if they are needed, then click Save. A new version will be created, and set as current.";
|
||||
editButtonText = "Edit and Activate";
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Box>
|
||||
{
|
||||
notFoundMessage
|
||||
?
|
||||
<Box>{notFoundMessage}</Box>
|
||||
:
|
||||
<Box>
|
||||
{
|
||||
successText ? (
|
||||
<Snackbar open={successText !== null && successText !== ""} autoHideDuration={6000} onClose={() => setSuccessText(null)} anchorOrigin={{vertical: "top", horizontal: "center"}}>
|
||||
<Alert color="success" onClose={() => setSuccessText(null)}>
|
||||
{successText}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
) : ("")
|
||||
}
|
||||
{
|
||||
failText ? (
|
||||
<Snackbar open={failText !== null && failText !== ""} autoHideDuration={6000} onClose={() => setFailText(null)} anchorOrigin={{vertical: "top", horizontal: "center"}}>
|
||||
<Alert color="error" onClose={() => setFailText(null)}>
|
||||
{failText}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
) : ("")
|
||||
}
|
||||
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<>
|
||||
<Box display="flex" alignItems="center" justifyContent="space-between" gap={2} mt={-6}>
|
||||
<Typography variant="h5" p={2}></Typography>
|
||||
<Tabs
|
||||
sx={{mr: 1}}
|
||||
value={selectedTab}
|
||||
onChange={(event, newValue) => changeTab(newValue)}
|
||||
variant="standard"
|
||||
>
|
||||
<Tab label="Raw Data" id="simple-tab-0" aria-controls="simple-tabpanel-0" sx={{width: "150px"}} />
|
||||
<Tab label="Data Preview" id="simple-tab-1" aria-controls="simple-tabpanel-1" sx={{width: "150px"}} />
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
<TabPanel index={0} value={selectedTab}>
|
||||
<Grid container>
|
||||
<Grid item xs={4}>
|
||||
<Box display="flex" alignItems="center" gap={2} pb={1} height="40px">
|
||||
<Typography variant="h6" pl={3}>Versions</Typography>
|
||||
</Box>
|
||||
{getVersionsList(versionRecordList, selectedVersionRecord)}
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={8}>
|
||||
<Box display="flex" alignItems="center" justifyContent="space-between" gap={2} pb={1} height="40px">
|
||||
{
|
||||
selectedVersionRecord ?
|
||||
<Typography variant="h6">
|
||||
Version {selectedVersionRecord.values.get("sequenceNo")}
|
||||
{
|
||||
currentVersionId === selectedVersionRecord.values.get("id")
|
||||
? (<> (Current)</>)
|
||||
: <></>
|
||||
}
|
||||
</Typography>
|
||||
: <></>
|
||||
}
|
||||
<CustomWidthTooltip title={editButtonTooltip}>
|
||||
<Button sx={{py: 0}} onClick={() => editData(selectedVersionRecord?.values?.get("contents"))}>
|
||||
{editButtonText}
|
||||
</Button>
|
||||
</CustomWidthTooltip>
|
||||
</Box>
|
||||
{
|
||||
loadingSelectedVersion.isNotLoading() && selectedVersionRecord && selectedVersionRecord.values.get("contents") ? (
|
||||
<>
|
||||
<AceEditor
|
||||
mode="javascript"
|
||||
theme="github"
|
||||
name={"viewData"}
|
||||
readOnly
|
||||
highlightActiveLine={false}
|
||||
editorProps={{$blockScrolling: true}}
|
||||
width="100%"
|
||||
height="400px"
|
||||
value={selectedVersionRecord?.values?.get("contents")}
|
||||
/>
|
||||
</>
|
||||
) : null
|
||||
}
|
||||
{
|
||||
loadingSelectedVersion.isLoadingSlow() && <Box fontSize="14px" pl={3}>Loading...</Box>
|
||||
}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</TabPanel>
|
||||
{/*
|
||||
<TabPanel index={1} value={selectedTab}>
|
||||
<Grid container height="440px">
|
||||
<Grid item xs={4}>
|
||||
<Box display="flex" alignItems="center" gap={2} pb={1} height="40px">
|
||||
<Typography variant="h6" pl={3}>Versions</Typography>
|
||||
</Box>
|
||||
{getVersionsList(versionRecordList, selectedVersionRecord)}
|
||||
</Grid>
|
||||
<Grid item xs={8}>
|
||||
<Box display="flex" alignItems="center" gap={2} pb={1} height="40px">
|
||||
<Typography variant="h6" pl={3}>Data Preview (Version {selectedVersionRecord?.values?.get("sequenceNo")})</Typography>
|
||||
</Box>
|
||||
<Box height="400px" overflow="auto" ml={1} fontSize="14px">
|
||||
{loadingSelectedVersion.isNotLoading() && selectedTab == 1 && selectedVersionRecord?.values?.get("data") && <ScriptPreview json={selectedVersionRecord?.values?.get("data")} /> }
|
||||
{loadingSelectedVersion.isLoadingSlow() && <Box pl={3}>Loading...</Box>}
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</TabPanel>
|
||||
*/}
|
||||
</>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{
|
||||
editorProps &&
|
||||
<Modal open={editorProps !== null} onClose={(event, reason) => closeEditingScript(event, reason)}>
|
||||
<ScriptEditor
|
||||
closeCallback={closeEditingScript}
|
||||
{... editorProps}
|
||||
/>
|
||||
</Modal>
|
||||
}
|
||||
|
||||
</Box>
|
||||
}
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.materialdashboard.tests;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.materialdashboard.lib.QBaseSeleniumTest;
|
||||
import com.kingsrook.qqq.materialdashboard.lib.javalin.QSeleniumJavalin;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Test for Associated Record Scripts functionality.
|
||||
*******************************************************************************/
|
||||
public class AssociatedRecordScriptTest extends QBaseSeleniumTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
protected void addJavalinRoutes(QSeleniumJavalin qSeleniumJavalin)
|
||||
{
|
||||
super.addJavalinRoutes(qSeleniumJavalin);
|
||||
qSeleniumJavalin.withRouteToFile("/data/person/1", "data/person/1701.json");
|
||||
qSeleniumJavalin.withRouteToFile("/data/person/1/developer", "data/person/1701.json");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testNavigatingBackAndForth()
|
||||
{
|
||||
qSeleniumLib.gotoAndWaitForBreadcrumbHeader("/peopleApp/greetingsApp/person/1", "John Doe");
|
||||
qSeleniumLib.waitForSelectorContaining("BUTTON", "actions").click();
|
||||
|
||||
qSeleniumLib.waitForSelectorContaining("LI", "Developer Mode").click();
|
||||
assertTrue(qSeleniumLib.driver.getCurrentUrl().endsWith("/1/dev"));
|
||||
|
||||
qSeleniumLib.waitForever();
|
||||
}
|
||||
|
||||
}
|
66
src/test/java/com/kingsrook/qqq/materialdashboard/tests/ScriptTableTest.java
Executable file
66
src/test/java/com/kingsrook/qqq/materialdashboard/tests/ScriptTableTest.java
Executable file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.materialdashboard.tests;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.materialdashboard.lib.QBaseSeleniumTest;
|
||||
import com.kingsrook.qqq.materialdashboard.lib.javalin.QSeleniumJavalin;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Test for the scripts table
|
||||
*******************************************************************************/
|
||||
public class ScriptTableTest extends QBaseSeleniumTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
protected void addJavalinRoutes(QSeleniumJavalin qSeleniumJavalin)
|
||||
{
|
||||
super.addJavalinRoutes(qSeleniumJavalin);
|
||||
qSeleniumJavalin
|
||||
.withRouteToFile("/data/script/1", "data/script/1.json")
|
||||
.withRouteToFile("/metaData/table/script", "metaData/table/script.json")
|
||||
.withRouteToFile("/widget/scriptViewer", "widget/scriptViewer.json")
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void test()
|
||||
{
|
||||
qSeleniumLib.gotoAndWaitForBreadcrumbHeader("/developer/script/1", "Hello, Script");
|
||||
|
||||
qSeleniumLib.waitForSelectorContaining("span", "uh, script");
|
||||
|
||||
qSeleniumLib.takeScreenshotToFile();
|
||||
// qSeleniumLib.waitForever();
|
||||
}
|
||||
|
||||
}
|
276
src/test/resources/fixtures/data/person/developer.json
Normal file
276
src/test/resources/fixtures/data/person/developer.json
Normal file
@ -0,0 +1,276 @@
|
||||
{
|
||||
"record": {
|
||||
"tableName": "client",
|
||||
"recordLabel": "John Doe",
|
||||
"values": {
|
||||
"name": "John Doe",
|
||||
"id": 120,
|
||||
"deposcoOrderOptimizationCoolingScriptId": 2,
|
||||
"createDate": "2022-08-30T00:31:00Z",
|
||||
"modifyDate": "2023-02-19T01:28:30Z",
|
||||
"isFulfillmentCenter": false,
|
||||
"infoplusLobId": 18698,
|
||||
"deposcoBusinessUnitName": "TRIFECTA",
|
||||
"deposcoBusinessUnitId": 77,
|
||||
"optimizationConfigId": 1,
|
||||
"nfCode": "Client 224"
|
||||
},
|
||||
"displayValues": {
|
||||
"optimizationConfigId": "Client: 120",
|
||||
"name": "John Doe",
|
||||
"id": "120",
|
||||
"deposcoOrderOptimizationCoolingScriptId": "2",
|
||||
"createDate": "2022-08-30T00:31:00Z",
|
||||
"modifyDate": "2023-02-19T01:28:30Z",
|
||||
"isFulfillmentCenter": "No",
|
||||
"infoplusLobId": "18698",
|
||||
"deposcoBusinessUnitName": "TRIFECTA",
|
||||
"deposcoBusinessUnitId": "77",
|
||||
"nfCode": "Client 224"
|
||||
}
|
||||
},
|
||||
"associatedScripts": [
|
||||
{
|
||||
"testInputFields": [
|
||||
{
|
||||
"name": "selectedTimeInTransitDays",
|
||||
"label": "Selected Time In Transit Days",
|
||||
"type": "INTEGER",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"isHeavy": false,
|
||||
"displayFormat": "%s"
|
||||
},
|
||||
{
|
||||
"name": "standardTimeInTransitDays",
|
||||
"label": "Standard Time In Transit Days",
|
||||
"type": "INTEGER",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"isHeavy": false,
|
||||
"displayFormat": "%s"
|
||||
}
|
||||
],
|
||||
"scriptType": {
|
||||
"tableName": "scriptType",
|
||||
"values": {
|
||||
"name": "Deposco Order Optimization Cooling",
|
||||
"id": 2,
|
||||
"createDate": "2022-10-31T19:06:50Z",
|
||||
"modifyDate": "2022-10-31T19:06:50Z"
|
||||
}
|
||||
},
|
||||
"scriptRevisions": [
|
||||
{
|
||||
"tableName": "scriptRevision",
|
||||
"values": {
|
||||
"id": 1,
|
||||
"contents": "1;",
|
||||
"createDate": "2023-02-19T01:28:30Z",
|
||||
"modifyDate": "2023-02-19T01:28:30Z",
|
||||
"scriptId": 2,
|
||||
"sequenceNo": 1,
|
||||
"commitMessage": "Initial version",
|
||||
"author": "Darin Kelkhoff"
|
||||
}
|
||||
}
|
||||
],
|
||||
"testOutputFields": [
|
||||
{
|
||||
"name": "sku",
|
||||
"label": "Sku",
|
||||
"type": "STRING",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"isHeavy": false,
|
||||
"displayFormat": "%s"
|
||||
},
|
||||
{
|
||||
"name": "quantityPerCarton",
|
||||
"label": "Quantity Per Carton",
|
||||
"type": "INTEGER",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"isHeavy": false,
|
||||
"displayFormat": "%s"
|
||||
},
|
||||
{
|
||||
"name": "useClientProvidedCoolingSolution",
|
||||
"label": "Use Client Provided Cooling Solution",
|
||||
"type": "BOOLEAN",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"isHeavy": false,
|
||||
"displayFormat": "%s"
|
||||
},
|
||||
{
|
||||
"name": "reason",
|
||||
"label": "Reason",
|
||||
"type": "STRING",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"isHeavy": false,
|
||||
"displayFormat": "%s"
|
||||
}
|
||||
],
|
||||
"script": {
|
||||
"tableName": "script",
|
||||
"values": {
|
||||
"name": "John Doe - Deposco Order Optimization Cooling",
|
||||
"id": 2,
|
||||
"scriptTypeId": 2,
|
||||
"createDate": "2023-02-19T01:28:30Z",
|
||||
"modifyDate": "2023-02-19T01:28:30Z",
|
||||
"currentScriptRevisionId": 1
|
||||
}
|
||||
},
|
||||
"associatedScript": {
|
||||
"fieldName": "deposcoOrderOptimizationCoolingScriptId",
|
||||
"scriptTypeId": 2,
|
||||
"scriptTester": {
|
||||
"name": "com.nutrifresh.one.processes.deposco.RunDeposcoOrderOptimizationCoolingScript",
|
||||
"codeType": "JAVA",
|
||||
"codeUsage": "SCRIPT_TESTER"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"testInputFields": [
|
||||
{
|
||||
"name": "selectedTimeInTransitDays",
|
||||
"label": "Selected Time In Transit Days",
|
||||
"type": "INTEGER",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"isHeavy": false,
|
||||
"displayFormat": "%s"
|
||||
},
|
||||
{
|
||||
"name": "standardTimeInTransitDays",
|
||||
"label": "Standard Time In Transit Days",
|
||||
"type": "INTEGER",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"isHeavy": false,
|
||||
"displayFormat": "%s"
|
||||
},
|
||||
{
|
||||
"name": "runtimeWeekday",
|
||||
"label": "Runtime Weekday",
|
||||
"type": "INTEGER",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"isHeavy": false,
|
||||
"displayFormat": "%s"
|
||||
}
|
||||
],
|
||||
"scriptType": {
|
||||
"tableName": "scriptType",
|
||||
"values": {
|
||||
"name": "Deposco Order Optimization Batch Name",
|
||||
"id": 1,
|
||||
"createDate": "2022-10-31T19:06:50Z",
|
||||
"modifyDate": "2022-10-31T19:06:50Z"
|
||||
}
|
||||
},
|
||||
"testOutputFields": [
|
||||
{
|
||||
"name": "batchName",
|
||||
"label": "Batch Name",
|
||||
"type": "STRING",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"isHeavy": false,
|
||||
"displayFormat": "%s"
|
||||
},
|
||||
{
|
||||
"name": "reason",
|
||||
"label": "Reason",
|
||||
"type": "STRING",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"isHeavy": false,
|
||||
"displayFormat": "%s"
|
||||
}
|
||||
],
|
||||
"associatedScript": {
|
||||
"fieldName": "deposcoOrderOptimizationBatchNameScriptId",
|
||||
"scriptTypeId": 1,
|
||||
"scriptTester": {
|
||||
"name": "com.nutrifresh.one.processes.deposco.RunDeposcoOrderOptimizationBatchNameScript",
|
||||
"codeType": "JAVA",
|
||||
"codeUsage": "SCRIPT_TESTER"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"testInputFields": [
|
||||
{
|
||||
"name": "selectedTimeInTransitDays",
|
||||
"label": "Selected Time In Transit Days",
|
||||
"type": "INTEGER",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"isHeavy": false,
|
||||
"displayFormat": "%s"
|
||||
},
|
||||
{
|
||||
"name": "standardTimeInTransitDays",
|
||||
"label": "Standard Time In Transit Days",
|
||||
"type": "INTEGER",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"isHeavy": false,
|
||||
"displayFormat": "%s"
|
||||
},
|
||||
{
|
||||
"name": "runtimeWeekday",
|
||||
"label": "Runtime Weekday",
|
||||
"type": "INTEGER",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"isHeavy": false,
|
||||
"displayFormat": "%s"
|
||||
}
|
||||
],
|
||||
"scriptType": {
|
||||
"tableName": "scriptType",
|
||||
"values": {
|
||||
"name": "Deposco Order Optimization Batch Name",
|
||||
"id": 1,
|
||||
"createDate": "2022-10-31T19:06:50Z",
|
||||
"modifyDate": "2022-10-31T19:06:50Z"
|
||||
}
|
||||
},
|
||||
"testOutputFields": [
|
||||
{
|
||||
"name": "batchName",
|
||||
"label": "Batch Name",
|
||||
"type": "STRING",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"isHeavy": false,
|
||||
"displayFormat": "%s"
|
||||
},
|
||||
{
|
||||
"name": "reason",
|
||||
"label": "Reason",
|
||||
"type": "STRING",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"isHeavy": false,
|
||||
"displayFormat": "%s"
|
||||
}
|
||||
],
|
||||
"associatedScript": {
|
||||
"fieldName": "deposcoOrderOptimizationCartonizationScriptId",
|
||||
"scriptTypeId": 1,
|
||||
"scriptTester": {
|
||||
"name": "com.nutrifresh.one.processes.deposco.RunDeposcoOrderOptimizationBatchNameScript",
|
||||
"codeType": "JAVA",
|
||||
"codeUsage": "SCRIPT_TESTER"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
20
src/test/resources/fixtures/data/script/1.json
Normal file
20
src/test/resources/fixtures/data/script/1.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"tableName": "script",
|
||||
"recordLabel": "Hello, Script",
|
||||
"values": {
|
||||
"name": "Hello, Script",
|
||||
"id": 1,
|
||||
"tableName": "client",
|
||||
"createDate": "2023-02-18T00:47:51Z",
|
||||
"modifyDate": "2023-02-18T00:47:51Z",
|
||||
"scriptTypeId": 1
|
||||
},
|
||||
"displayValues": {
|
||||
"tableName": "Client",
|
||||
"scriptTypeId": "Unknown",
|
||||
"name": "Hello, Script",
|
||||
"id": "1",
|
||||
"createDate": "2023-02-18T00:47:51Z",
|
||||
"modifyDate": "2023-02-18T00:47:51Z"
|
||||
}
|
||||
}
|
139
src/test/resources/fixtures/metaData/table/script.json
Normal file
139
src/test/resources/fixtures/metaData/table/script.json
Normal file
@ -0,0 +1,139 @@
|
||||
{
|
||||
"table": {
|
||||
"name": "script",
|
||||
"label": "Script",
|
||||
"isHidden": false,
|
||||
"primaryKeyField": "id",
|
||||
"iconName": "data_object",
|
||||
"fields": {
|
||||
"modifyDate": {
|
||||
"name": "modifyDate",
|
||||
"label": "Modify Date",
|
||||
"type": "DATE_TIME",
|
||||
"isRequired": false,
|
||||
"isEditable": false,
|
||||
"displayFormat": "%s"
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"label": "Name",
|
||||
"type": "STRING",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"displayFormat": "%s"
|
||||
},
|
||||
"currentScriptRevisionId": {
|
||||
"name": "currentScriptRevisionId",
|
||||
"label": "Current Script Revision",
|
||||
"type": "INTEGER",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"possibleValueSourceName": "scriptRevision",
|
||||
"displayFormat": "%s",
|
||||
"adornments": [
|
||||
{
|
||||
"type": "LINK",
|
||||
"values": {
|
||||
"toRecordFromTable": "scriptRevision"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"name": "id",
|
||||
"label": "Id",
|
||||
"type": "INTEGER",
|
||||
"isRequired": false,
|
||||
"isEditable": false,
|
||||
"displayFormat": "%s"
|
||||
},
|
||||
"tableName": {
|
||||
"name": "tableName",
|
||||
"label": "Table Name",
|
||||
"type": "STRING",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"possibleValueSourceName": "tables",
|
||||
"displayFormat": "%s"
|
||||
},
|
||||
"createDate": {
|
||||
"name": "createDate",
|
||||
"label": "Create Date",
|
||||
"type": "DATE_TIME",
|
||||
"isRequired": false,
|
||||
"isEditable": false,
|
||||
"displayFormat": "%s"
|
||||
},
|
||||
"scriptTypeId": {
|
||||
"name": "scriptTypeId",
|
||||
"label": "Script Type",
|
||||
"type": "INTEGER",
|
||||
"isRequired": false,
|
||||
"isEditable": true,
|
||||
"possibleValueSourceName": "scriptType",
|
||||
"displayFormat": "%s",
|
||||
"adornments": [
|
||||
{
|
||||
"type": "LINK",
|
||||
"values": {
|
||||
"toRecordFromTable": "scriptType"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"sections": [
|
||||
{
|
||||
"name": "identity",
|
||||
"label": "Identity",
|
||||
"tier": "T1",
|
||||
"fieldNames": [
|
||||
"id",
|
||||
"name",
|
||||
"scriptTypeId",
|
||||
"tableName",
|
||||
"currentScriptRevisionId"
|
||||
],
|
||||
"icon": {
|
||||
"name": "badge"
|
||||
},
|
||||
"isHidden": false
|
||||
},
|
||||
{
|
||||
"name": "contents",
|
||||
"label": "Contents",
|
||||
"tier": "T2",
|
||||
"widgetName": "scriptViewer",
|
||||
"icon": {
|
||||
"name": "data_object"
|
||||
},
|
||||
"isHidden": false
|
||||
},
|
||||
{
|
||||
"name": "dates",
|
||||
"label": "Dates",
|
||||
"tier": "T3",
|
||||
"fieldNames": [
|
||||
"createDate",
|
||||
"modifyDate"
|
||||
],
|
||||
"icon": {
|
||||
"name": "calendar_month"
|
||||
},
|
||||
"isHidden": false
|
||||
}
|
||||
],
|
||||
"capabilities": [
|
||||
"TABLE_COUNT",
|
||||
"TABLE_GET",
|
||||
"TABLE_QUERY",
|
||||
"TABLE_INSERT",
|
||||
"TABLE_DELETE",
|
||||
"TABLE_UPDATE"
|
||||
],
|
||||
"readPermission": true,
|
||||
"insertPermission": true,
|
||||
"editPermission": true,
|
||||
"deletePermission": true
|
||||
}
|
||||
}
|
7
src/test/resources/fixtures/widget/scriptViewer.json
Normal file
7
src/test/resources/fixtures/widget/scriptViewer.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"type": "scriptViewer",
|
||||
"queryParams": {
|
||||
"id": "1",
|
||||
"tableName": "script"
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user