Checkpoint refactoring script out of just associated-record, for more general purpose

This commit is contained in:
2023-03-06 09:43:47 -06:00
parent 7e2bcea5de
commit 516f874a56
5 changed files with 156 additions and 346 deletions

View File

@ -22,33 +22,18 @@
import {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException";
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
import {Alert, Chip, Icon, ListItem, ListItemAvatar, Typography} from "@mui/material";
import Avatar from "@mui/material/Avatar";
import {Alert, Typography} from "@mui/material";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Card from "@mui/material/Card";
import Divider from "@mui/material/Divider";
import Grid from "@mui/material/Grid";
import List from "@mui/material/List";
import ListItemIcon from "@mui/material/ListItemIcon";
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 React, {useContext, useReducer, useState} from "react";
import AceEditor from "react-ace";
import {useParams} from "react-router-dom";
import QContext from "QContext";
import TabPanel from "qqq/components/misc/TabPanel";
import AssociatedScriptEditor from "qqq/components/scripts/AssociatedScriptEditor";
import ScriptDocsForm from "qqq/components/scripts/ScriptDocsForm";
import ScriptLogsView from "qqq/components/scripts/ScriptLogsView";
import ScriptTestForm from "qqq/components/scripts/ScriptTestForm";
import CustomWidthTooltip from "qqq/components/tooltips/CustomWidthTooltip";
import ScriptViewer from "qqq/components/widgets/misc/ScriptViewer";
import BaseLayout from "qqq/layouts/BaseLayout";
import DeveloperModeUtils from "qqq/utils/DeveloperModeUtils";
import Client from "qqq/utils/qqq/Client";
import ValueUtils from "qqq/utils/qqq/ValueUtils";
@ -60,7 +45,6 @@ import "ace-builds/src-noconflict/ext-language_tools";
const qController = Client.getInstance();
// Declaring props types for ViewForm
interface Props
{
table?: QTableMetaData;
@ -89,7 +73,6 @@ function RecordDeveloperView({table}: Props): JSX.Element
const [viewingRevisions, setViewingRevisions] = useState({} as any);
const [scriptLogs, setScriptLogs] = useState({} as any);
const [editingScript, setEditingScript] = useState(null as any);
const [alertText, setAlertText] = useState(null as string);
const {setPageHeader} = useContext(QContext);
@ -152,135 +135,13 @@ function RecordDeveloperView({table}: Props): JSX.Element
})();
}
const editScript = (fieldName: string, code: string, object: any) =>
const createScript = async (fieldName: string) =>
{
const editingScript = {} as any;
editingScript.fieldName = fieldName;
editingScript.titlePrefix = code ? "Editing Script" : "Creating New Script";
editingScript.code = code;
editingScript.scriptDefinitionObject = object;
setEditingScript(editingScript);
};
const closeEditingScript = (event: object, reason: string, alert: string = null) =>
{
if (reason === "backdropClick" || reason === "escapeKeyDown")
{
return;
}
if (reason === "saved")
{
setAsyncLoadInited(false);
setAssociatedScripts([]);
viewingRevisions[editingScript.fieldName] = null;
setViewingRevisions(viewingRevisions);
forceUpdate();
}
if (alert)
{
setAlertText(alert);
}
setEditingScript(null);
};
const changeTab = (newValue: number, fieldName: string) =>
{
selectedTabs[fieldName] = newValue;
setSelectedTabs(selectedTabs);
const rs = await qController.storeRecordAssociatedScript(tableName, id, fieldName, "// Edit this new script to define its code.", "Initial version");
record.values.set(fieldName, rs.scriptId);
forceUpdate();
};
const selectRevision = (fieldName: string, revisionId: number) =>
{
viewingRevisions[fieldName] = revisionId;
setViewingRevisions(viewingRevisions);
scriptLogs[revisionId] = null;
setScriptLogs(scriptLogs);
loadRevisionLogs(fieldName, revisionId);
forceUpdate();
};
const loadRevisionLogs = (fieldName: string, revisionId: number) =>
{
(async () =>
{
const rs = await qController.getRecordAssociatedScriptLogs(tableName, id, fieldName, revisionId);
scriptLogs[revisionId] = [];
if (rs["scriptLogRecords"])
{
scriptLogs[revisionId] = rs["scriptLogRecords"];
}
console.log("Script logs:");
console.log(scriptLogs[revisionId]);
setScriptLogs(scriptLogs);
forceUpdate();
})();
};
function getRevisionsList(scriptRevisions: any, fieldName: any, currentScriptRevisionId: any)
{
return <List sx={{pl: 3, height: "400px", overflow: "auto"}}>
{
scriptRevisions ? <></> :
<Typography variant="body2">
There are not any versions of this script.
</Typography>
}
{
scriptRevisions?.map((revision: any) => (
<React.Fragment key={revision.values.id}>
<ListItem sx={{p: 1}} alignItems="flex-start" selected={viewingRevisions[fieldName] == revision.values.id} onClick={(event) => selectRevision(fieldName, revision.values.id)}>
<ListItemAvatar>
<Avatar sx={{bgcolor: DeveloperModeUtils.revToColor(fieldName, id, revision.values.sequenceNo)}}>{`${revision.values.sequenceNo}`}</Avatar>
</ListItemAvatar>
<ListItemText
primaryTypographyProps={{fontSize: "1rem"}}
secondaryTypographyProps={{fontSize: ".85rem"}}
primary={
<div style={{whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis"}} title={revision.values.commitMessage}>
{revision.values.id == currentScriptRevisionId && <Chip label="CURRENT" color="success" variant="outlined" size="small" sx={{mr: 1, fontSize: "0.75rem"}} />}
{revision.values.commitMessage}
</div>
}
secondary={
<>
{ValueUtils.formatDateTime(revision.values.createDate)}
<br />
{revision.values.author}
</>
}
/>
<ListItemIcon sx={{minWidth: "auto", px: 1}}><Icon>settings</Icon></ListItemIcon>
</ListItem>
<Divider sx={{my: 0.5}} variant="inset" component="li" />
</React.Fragment>
))
}
</List>;
}
function getScriptLogs(revisionId: number)
{
const logs = scriptLogs[revisionId] as any[];
if (logs === null || logs === undefined)
{
return <Typography variant="body2" p={3}>Loading...</Typography>;
}
if (logs.length === 0)
{
return <Typography variant="body2" p={3}>No logs available for this version.</Typography>;
}
return (<ScriptLogsView logs={logs} />);
}
return (
<BaseLayout>
<Box>
@ -289,8 +150,7 @@ function RecordDeveloperView({table}: Props): JSX.Element
<Box mb={3}>
{
notFoundMessage
?
<Box>{notFoundMessage}</Box>
? <Box>{notFoundMessage}</Box>
:
<Box pb={3}>
{
@ -323,18 +183,33 @@ function RecordDeveloperView({table}: Props): JSX.Element
{
associatedScripts && associatedScripts.map((object) =>
{
console.log(object);
let fieldName = object.associatedScript?.fieldName;
let field = tableMetaData.fields.get(fieldName);
let scriptId = recordJSONString ? recordJSONObject[fieldName] : null;
let scriptId = record?.values.get(fieldName);
return (
<div key={fieldName}>
<Card sx={{mb: 3}}>
<Typography variant="h5" p={2}>{field?.label}</Typography>
<Typography variant="h6" p={2} pl={3} pb={1}>{field?.label}</Typography>
<Box display="flex" alignItems="center" justifyContent="space-between" gap={2}>
{scriptId ?
<ScriptViewer scriptId={scriptId}></ScriptViewer>
: <>No script here yet. {/* todo! */}</>
<ScriptViewer
scriptId={scriptId}
associatedScriptTableName={tableName}
associatedScriptFieldName={fieldName}
associatedScriptRecordId={id}
testInputFields={object.testInputFields}
testOutputFields={object.testOutputFields}
></ScriptViewer>
: <>
<Box p={3} fontSize={"1rem"}>
No script has been created in this field for this record at this time.
</Box>
<Box>
<Button onClick={() => createScript(fieldName)}>Create Script</Button>
</Box>
</>
}
</Box>
</Card>
@ -343,171 +218,8 @@ function RecordDeveloperView({table}: Props): JSX.Element
})
}
{/*
associatedScripts && associatedScripts.map((object) =>
{
let fieldName = object.associatedScript?.fieldName;
let field = tableMetaData.fields.get(fieldName);
let currentScriptRevisionId = object.script?.values?.currentScriptRevisionId;
if (!selectedTabs[fieldName])
{
selectedTabs[fieldName] = 0;
}
if (!viewingRevisions[fieldName] || viewingRevisions[fieldName] === -1)
{
console.log(`Defaulting revision for ${fieldName} to ${currentScriptRevisionId}`);
viewingRevisions[fieldName] = currentScriptRevisionId;
if (!scriptLogs[currentScriptRevisionId])
{
loadRevisionLogs(fieldName, currentScriptRevisionId);
}
}
const viewingRevisionArray = object.scriptRevisions?.filter((rev: any) => rev?.values?.id === viewingRevisions[fieldName]);
const code = viewingRevisionArray?.length > 0 ? viewingRevisionArray[0].values.contents : "";
const viewingSequenceNo = viewingRevisionArray?.length > 0 ? viewingRevisionArray[0].values.sequenceNo : "";
let editButtonTooltip = "";
let editButtonText = "Create New Script";
if (currentScriptRevisionId)
{
if (currentScriptRevisionId === viewingRevisions[fieldName])
{
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 (
<Card key={fieldName} id={`associatedScript.${fieldName}`} sx={{mb: 3}}>
<Box display="flex" alignItems="center" justifyContent="space-between" gap={2}>
<Typography variant="h5" p={2}>{field?.label}</Typography>
<Tabs
sx={{mr: 1}}
value={selectedTabs[fieldName]}
onChange={(event, newValue) => changeTab(newValue, fieldName)}
variant="standard"
>
<Tab label="Code" id="simple-tab-0" aria-controls="simple-tabpanel-0" sx={{width: "100px"}} />
<Tab label="Logs" id="simple-tab-1" aria-controls="simple-tabpanel-1" sx={{width: "100px"}} />
<Tab label="Test" id="simple-tab-2" aria-controls="simple-tabpanel-2" sx={{width: "100px"}} />
<Tab label="Docs" id="simple-tab-3" aria-controls="simple-tabpanel-3" sx={{width: "100px"}} />
</Tabs>
</Box>
<TabPanel index={0} value={selectedTabs[fieldName]}>
<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>
{getRevisionsList(object.scriptRevisions, fieldName, currentScriptRevisionId)}
</Grid>
<Grid item xs={8}>
<Box display="flex" alignItems="center" gap={2} pb={1} height="40px">
{
currentScriptRevisionId &&
<Typography variant="h6">
{
currentScriptRevisionId === viewingRevisions[fieldName]
? (<>Current Version ({viewingSequenceNo})</>)
: (<>Version {viewingSequenceNo}</>)
}
</Typography>
}
<CustomWidthTooltip title={editButtonTooltip}>
<Button sx={{py: 0}} onClick={() => editScript(fieldName, code, object)}>
{editButtonText}
</Button>
</CustomWidthTooltip>
</Box>
{
code ? (
<>
<AceEditor
mode="javascript"
theme="github"
name={`view-${fieldName}`}
readOnly
highlightActiveLine={false}
editorProps={{$blockScrolling: true}}
width="100%"
height="400px"
value={code}
/>
</>
) : null
}
</Grid>
</Grid>
</TabPanel>
<TabPanel index={1} value={selectedTabs[fieldName]}>
<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>
{getRevisionsList(object.scriptRevisions, fieldName, currentScriptRevisionId)}
</Grid>
<Grid item xs={8}>
<Box display="flex" alignItems="center" gap={2} pb={1} height="40px">
<Typography variant="h6" pl={3}>Script Logs (Version {viewingSequenceNo})</Typography>
</Box>
<Box height="400px" overflow="auto">
{getScriptLogs(viewingRevisions[fieldName])}
</Box>
</Grid>
</Grid>
</TabPanel>
<TabPanel index={2} value={selectedTabs[fieldName]}>
<Box sx={{height: "455px"}} px={2} pb={1}>
<ScriptTestForm scriptDefinition={object} tableName={tableName} fieldName={fieldName} recordId={id} code={code} />
</Box>
</TabPanel>
<TabPanel index={3} value={selectedTabs[fieldName]}>
<Box sx={{height: "455px"}} px={2} pb={1}>
<ScriptDocsForm helpText={object.scriptType.values.helpText} exampleCode={object.scriptType.values.sampleCode} />
</Box>
</TabPanel>
</Card>
);
})
*/}
</Grid>
</Grid>
{
editingScript &&
<Modal open={editingScript as boolean} onClose={(event, reason) => closeEditingScript(event, reason)}>
<AssociatedScriptEditor
scriptDefinition={editingScript.scriptDefinitionObject}
tableName={tableName}
primaryKey={id}
fieldName={editingScript.fieldName}
titlePrefix={editingScript.titlePrefix}
recordLabel={record.recordLabel}
scriptName={tableMetaData.fields.get(editingScript.fieldName).label}
code={editingScript.code}
closeCallback={closeEditingScript}
/>
</Modal>
}
</Box>
}
</Box>