mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 05:10:45 +00:00
Merge branch 'integration/sprint-28' into feature/CTLE-214-dot-menu
This commit is contained in:
@ -6,7 +6,7 @@
|
|||||||
"@auth0/auth0-react": "1.10.2",
|
"@auth0/auth0-react": "1.10.2",
|
||||||
"@emotion/react": "11.7.1",
|
"@emotion/react": "11.7.1",
|
||||||
"@emotion/styled": "11.6.0",
|
"@emotion/styled": "11.6.0",
|
||||||
"@kingsrook/qqq-frontend-core": "1.0.69",
|
"@kingsrook/qqq-frontend-core": "1.0.71",
|
||||||
"@mui/icons-material": "5.4.1",
|
"@mui/icons-material": "5.4.1",
|
||||||
"@mui/material": "5.11.1",
|
"@mui/material": "5.11.1",
|
||||||
"@mui/styles": "5.11.1",
|
"@mui/styles": "5.11.1",
|
||||||
|
@ -200,7 +200,7 @@ function SavedFilters({qController, metaData, tableMetaData, currentSavedFilter,
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
formData.append("tableName", tableMetaData.name);
|
formData.append("tableName", tableMetaData.name);
|
||||||
formData.append("filterJson", JSON.stringify(FilterUtils.buildQFilterFromGridFilter(tableMetaData, filterModel, columnSortModel)));
|
formData.append("filterJson", JSON.stringify(FilterUtils.convertFilterPossibleValuesToIds(FilterUtils.buildQFilterFromGridFilter(tableMetaData, filterModel, columnSortModel))));
|
||||||
|
|
||||||
if (isSaveFilterAs || isRenameFilter || currentSavedFilter == null)
|
if (isSaveFilterAs || isRenameFilter || currentSavedFilter == null)
|
||||||
{
|
{
|
||||||
|
@ -62,6 +62,7 @@ function ScriptDocsForm({helpText, exampleCode, aceEditorHeight}: Props): JSX.El
|
|||||||
width="100%"
|
width="100%"
|
||||||
showPrintMargin={false}
|
showPrintMargin={false}
|
||||||
height="100%"
|
height="100%"
|
||||||
|
style={{borderBottomRightRadius: "1rem", borderBottomLeftRadius: "1rem"}}
|
||||||
/>
|
/>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -23,7 +23,7 @@ import {QController} from "@kingsrook/qqq-frontend-core/lib/controllers/QControl
|
|||||||
import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError";
|
import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError";
|
||||||
import {QPossibleValue} from "@kingsrook/qqq-frontend-core/lib/model/QPossibleValue";
|
import {QPossibleValue} from "@kingsrook/qqq-frontend-core/lib/model/QPossibleValue";
|
||||||
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||||
import {ToggleButton, ToggleButtonGroup, Typography} from "@mui/material";
|
import {IconButton, SelectChangeEvent, ToggleButton, ToggleButtonGroup, Typography} from "@mui/material";
|
||||||
import Alert from "@mui/material/Alert";
|
import Alert from "@mui/material/Alert";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Card from "@mui/material/Card";
|
import Card from "@mui/material/Card";
|
||||||
@ -31,9 +31,14 @@ import Dialog from "@mui/material/Dialog";
|
|||||||
import DialogActions from "@mui/material/DialogActions";
|
import DialogActions from "@mui/material/DialogActions";
|
||||||
import DialogContent from "@mui/material/DialogContent";
|
import DialogContent from "@mui/material/DialogContent";
|
||||||
import DialogTitle from "@mui/material/DialogTitle";
|
import DialogTitle from "@mui/material/DialogTitle";
|
||||||
|
import FormControl from "@mui/material/FormControl/FormControl";
|
||||||
import Grid from "@mui/material/Grid";
|
import Grid from "@mui/material/Grid";
|
||||||
|
import Icon from "@mui/material/Icon";
|
||||||
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
|
import Select from "@mui/material/Select/Select";
|
||||||
import Snackbar from "@mui/material/Snackbar";
|
import Snackbar from "@mui/material/Snackbar";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import FormData from "form-data";
|
import FormData from "form-data";
|
||||||
import React, {useEffect, useReducer, useRef, useState} from "react";
|
import React, {useEffect, useReducer, useRef, useState} from "react";
|
||||||
import AceEditor from "react-ace";
|
import AceEditor from "react-ace";
|
||||||
@ -56,22 +61,82 @@ export interface ScriptEditorProps
|
|||||||
tableName: string;
|
tableName: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
recordId: any;
|
recordId: any;
|
||||||
scriptDefinition: any;
|
|
||||||
scriptTypeRecord: QRecord;
|
scriptTypeRecord: QRecord;
|
||||||
|
scriptTypeFileSchemaList: QRecord[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const qController = Client.getInstance();
|
const qController = Client.getInstance();
|
||||||
|
|
||||||
function ScriptEditor({title, scriptId, scriptRevisionRecord, closeCallback, tableName, fieldName, recordId, scriptDefinition, scriptTypeRecord}: ScriptEditorProps): JSX.Element
|
function buildInitialFileContentsMap(scriptRevisionRecord: QRecord, scriptTypeFileSchemaList: QRecord[]): { [name: string]: string }
|
||||||
|
{
|
||||||
|
const rs: {[name: string]: string} = {};
|
||||||
|
|
||||||
|
if(!scriptTypeFileSchemaList)
|
||||||
|
{
|
||||||
|
console.log("Missing scriptTypeFileSchemaList");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
let files = scriptRevisionRecord?.associatedRecords?.get("files")
|
||||||
|
|
||||||
|
for (let i = 0; i < scriptTypeFileSchemaList.length; i++)
|
||||||
|
{
|
||||||
|
let scriptTypeFileSchema = scriptTypeFileSchemaList[i];
|
||||||
|
let name = scriptTypeFileSchema.values.get("name");
|
||||||
|
let contents = "";
|
||||||
|
|
||||||
|
for (let j = 0; j < files?.length; j++)
|
||||||
|
{
|
||||||
|
let file = files[j];
|
||||||
|
if(file.values.get("fileName") == name)
|
||||||
|
{
|
||||||
|
contents = file.values.get("contents");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rs[name] = contents;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (rs);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildFileTypeMap(scriptTypeFileSchemaList: QRecord[]): { [name: string]: string }
|
||||||
|
{
|
||||||
|
const rs: {[name: string]: string} = {};
|
||||||
|
|
||||||
|
if(!scriptTypeFileSchemaList)
|
||||||
|
{
|
||||||
|
console.log("Missing scriptTypeFileSchemaList");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (let i = 0; i < scriptTypeFileSchemaList.length; i++)
|
||||||
|
{
|
||||||
|
let name = scriptTypeFileSchemaList[i].values.get("name");
|
||||||
|
rs[name] = scriptTypeFileSchemaList[i].values.get("fileType");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (rs);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ScriptEditor({title, scriptId, scriptRevisionRecord, closeCallback, tableName, fieldName, recordId, scriptTypeRecord, scriptTypeFileSchemaList}: ScriptEditorProps): JSX.Element
|
||||||
{
|
{
|
||||||
const [closing, setClosing] = useState(false);
|
const [closing, setClosing] = useState(false);
|
||||||
|
|
||||||
const [updatedCode, setUpdatedCode] = useState(scriptRevisionRecord ? scriptRevisionRecord.values.get("contents") : "");
|
|
||||||
const [apiName, setApiName] = useState(scriptRevisionRecord ? scriptRevisionRecord.values.get("apiName") : null)
|
const [apiName, setApiName] = useState(scriptRevisionRecord ? scriptRevisionRecord.values.get("apiName") : null)
|
||||||
const [apiNameLabel, setApiNameLabel] = useState(scriptRevisionRecord ? scriptRevisionRecord.displayValues.get("apiName") : null)
|
const [apiNameLabel, setApiNameLabel] = useState(scriptRevisionRecord ? scriptRevisionRecord.displayValues.get("apiName") : null)
|
||||||
const [apiVersion, setApiVersion] = useState(scriptRevisionRecord ? scriptRevisionRecord.values.get("apiVersion") : null)
|
const [apiVersion, setApiVersion] = useState(scriptRevisionRecord ? scriptRevisionRecord.values.get("apiVersion") : null)
|
||||||
const [apiVersionLabel, setApiVersionLabel] = useState(scriptRevisionRecord ? scriptRevisionRecord.displayValues.get("apiVersion") : null)
|
const [apiVersionLabel, setApiVersionLabel] = useState(scriptRevisionRecord ? scriptRevisionRecord.displayValues.get("apiVersion") : null)
|
||||||
|
|
||||||
|
const fileNamesFromSchema = scriptTypeFileSchemaList.map((schemaRecord) => schemaRecord.values.get("name"))
|
||||||
|
const [availableFileNames, setAvailableFileNames] = useState(fileNamesFromSchema);
|
||||||
|
const [openEditorFileNames, setOpenEditorFileNames] = useState([fileNamesFromSchema[0]])
|
||||||
|
const [fileContents, setFileContents] = useState(buildInitialFileContentsMap(scriptRevisionRecord, scriptTypeFileSchemaList))
|
||||||
|
const [fileTypes, setFileTypes] = useState(buildFileTypeMap(scriptTypeFileSchemaList))
|
||||||
|
console.log(`file types: ${JSON.stringify(fileTypes)}`);
|
||||||
|
|
||||||
const [commitMessage, setCommitMessage] = useState("")
|
const [commitMessage, setCommitMessage] = useState("")
|
||||||
const [openTool, setOpenTool] = useState(null);
|
const [openTool, setOpenTool] = useState(null);
|
||||||
const [errorAlert, setErrorAlert] = useState("")
|
const [errorAlert, setErrorAlert] = useState("")
|
||||||
@ -200,7 +265,6 @@ function ScriptEditor({title, scriptId, scriptRevisionRecord, closeCallback, tab
|
|||||||
{
|
{
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("scriptId", scriptId);
|
formData.append("scriptId", scriptId);
|
||||||
formData.append("contents", updatedCode);
|
|
||||||
formData.append("commitMessage", overrideCommitMessage ?? commitMessage);
|
formData.append("commitMessage", overrideCommitMessage ?? commitMessage);
|
||||||
|
|
||||||
if(apiName)
|
if(apiName)
|
||||||
@ -213,6 +277,15 @@ function ScriptEditor({title, scriptId, scriptRevisionRecord, closeCallback, tab
|
|||||||
formData.append("apiVersion", apiVersion);
|
formData.append("apiVersion", apiVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const fileNamesFromSchema = scriptTypeFileSchemaList.map((schemaRecord) => schemaRecord.values.get("name"))
|
||||||
|
formData.append("fileNames", fileNamesFromSchema.join(","));
|
||||||
|
|
||||||
|
for (let fileName in fileContents)
|
||||||
|
{
|
||||||
|
formData.append("fileContents:" + fileName, fileContents[fileName]);
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
// we don't want this job to go async, so, pass a large timeout //
|
// we don't want this job to go async, so, pass a large timeout //
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
@ -249,10 +322,9 @@ function ScriptEditor({title, scriptId, scriptRevisionRecord, closeCallback, tab
|
|||||||
closeCallback(null, "cancelled");
|
closeCallback(null, "cancelled");
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateCode = (value: string, event: any) =>
|
const updateCode = (value: string, event: any, index: number) =>
|
||||||
{
|
{
|
||||||
console.log("Updating code")
|
fileContents[openEditorFileNames[index]] = value;
|
||||||
setUpdatedCode(value);
|
|
||||||
forceUpdate();
|
forceUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,8 +372,34 @@ function ScriptEditor({title, scriptId, scriptRevisionRecord, closeCallback, tab
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSelectingFile = (event: SelectChangeEvent, index: number) =>
|
||||||
|
{
|
||||||
|
openEditorFileNames[index] = event.target.value
|
||||||
|
setOpenEditorFileNames(openEditorFileNames);
|
||||||
|
forceUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
const splitEditorClicked = () =>
|
||||||
|
{
|
||||||
|
openEditorFileNames.push(availableFileNames[0])
|
||||||
|
setOpenEditorFileNames(openEditorFileNames);
|
||||||
|
forceUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeEditorClicked = (index: number) =>
|
||||||
|
{
|
||||||
|
openEditorFileNames.splice(index, 1)
|
||||||
|
setOpenEditorFileNames(openEditorFileNames);
|
||||||
|
forceUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
const computeEditorWidth = (): string =>
|
||||||
|
{
|
||||||
|
return (100 / openEditorFileNames.length) + "%"
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{position: "absolute", overflowY: "auto", height: "100%", width: "100%"}} p={6}>
|
<Box className="scriptEditor" sx={{position: "absolute", overflowY: "auto", height: "100%", width: "100%"}} p={6}>
|
||||||
<Card sx={{height: "100%", p: 3}}>
|
<Card sx={{height: "100%", p: 3}}>
|
||||||
|
|
||||||
<Snackbar open={errorAlert !== null && errorAlert !== ""} onClose={(event?: React.SyntheticEvent | Event, reason?: string) =>
|
<Snackbar open={errorAlert !== null && errorAlert !== ""} onClose={(event?: React.SyntheticEvent | Event, reason?: string) =>
|
||||||
@ -348,8 +446,42 @@ function ScriptEditor({title, scriptId, scriptRevisionRecord, closeCallback, tab
|
|||||||
<DynamicSelect fieldName={"apiVersion"} initialValue={apiVersion} initialDisplayValue={apiVersionLabel} fieldLabel={"API Version *"} tableName={"scriptRevision"} inForm={false} onChange={changeApiVersion} />
|
<DynamicSelect fieldName={"apiVersion"} initialValue={apiVersion} initialDisplayValue={apiVersionLabel} fieldLabel={"API Version *"} tableName={"scriptRevision"} inForm={false} onChange={changeApiVersion} />
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Box display="flex" sx={{height: "100%"}}>
|
||||||
|
{openEditorFileNames.map((fileName, index) =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<Box key={`${fileName}-${index}`} sx={{height: "100%", width: computeEditorWidth()}}>
|
||||||
|
<Box sx={{borderBottom: 1, borderColor: "divider"}} display="flex" justifyContent="space-between" alignItems="flex-end">
|
||||||
|
<FormControl className="selectedFileTab" variant="standard" sx={{verticalAlign: "bottom"}}>
|
||||||
|
<Select value={openEditorFileNames[index]} onChange={(event) => handleSelectingFile(event, index)}>
|
||||||
|
{
|
||||||
|
availableFileNames.map((name) => (
|
||||||
|
<MenuItem key={name} value={name}>{name}</MenuItem>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<Box>
|
||||||
|
{
|
||||||
|
openEditorFileNames.length > 1 &&
|
||||||
|
<Tooltip title="Close this editor split" enterDelay={500}>
|
||||||
|
<IconButton size="small" onClick={() => closeEditorClicked(index)}>
|
||||||
|
<Icon>close</Icon>
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
index == openEditorFileNames.length - 1 &&
|
||||||
|
<Tooltip title="Open a new editor split" enterDelay={500}>
|
||||||
|
<IconButton size="small" onClick={splitEditorClicked}>
|
||||||
|
<Icon>vertical_split</Icon>
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<AceEditor
|
<AceEditor
|
||||||
mode="javascript"
|
mode={fileTypes[openEditorFileNames[index]] ?? "javascript"}
|
||||||
theme="github"
|
theme="github"
|
||||||
name="editor"
|
name="editor"
|
||||||
editorProps={{$blockScrolling: true}}
|
editorProps={{$blockScrolling: true}}
|
||||||
@ -358,19 +490,23 @@ function ScriptEditor({title, scriptId, scriptRevisionRecord, closeCallback, tab
|
|||||||
enableBasicAutocompletion: true,
|
enableBasicAutocompletion: true,
|
||||||
enableLiveAutocompletion: true,
|
enableLiveAutocompletion: true,
|
||||||
}}
|
}}
|
||||||
onChange={updateCode}
|
onChange={(value, event) => updateCode(value, event, index)}
|
||||||
width="100%"
|
width="100%"
|
||||||
height="calc(100% - 58px)"
|
height="calc(100% - 88px)"
|
||||||
value={updatedCode}
|
value={fileContents[openEditorFileNames[index]]}
|
||||||
style={{border: "1px solid gray"}}
|
style={{border: "1px solid gray"}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
{
|
{
|
||||||
openTool &&
|
openTool &&
|
||||||
<Box sx={{height: "45%"}} pt={2}>
|
<Box sx={{height: "45%"}} pt={2}>
|
||||||
{
|
{
|
||||||
openTool == "test" && <ScriptTestForm scriptId={scriptId} scriptDefinition={scriptDefinition} tableName={tableName} fieldName={fieldName} recordId={recordId} code={updatedCode} apiName={apiName} apiVersion={apiVersion} />
|
openTool == "test" && <ScriptTestForm scriptId={scriptId} scriptType={scriptTypeRecord} tableName={tableName} fieldName={fieldName} recordId={recordId} fileContents={fileContents} apiName={apiName} apiVersion={apiVersion} />
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
openTool == "docs" && <ScriptDocsForm helpText={scriptTypeRecord?.values.get("helpText")} exampleCode={scriptTypeRecord?.values.get("sampleCode")} aceEditorHeight="100%" />
|
openTool == "docs" && <ScriptDocsForm helpText={scriptTypeRecord?.values.get("helpText")} exampleCode={scriptTypeRecord?.values.get("sampleCode")} aceEditorHeight="100%" />
|
||||||
|
@ -24,6 +24,7 @@ import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QF
|
|||||||
import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType";
|
import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType";
|
||||||
import {QJobComplete} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobComplete";
|
import {QJobComplete} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobComplete";
|
||||||
import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError";
|
import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError";
|
||||||
|
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||||
import {Typography} from "@mui/material";
|
import {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";
|
||||||
@ -51,19 +52,21 @@ interface AssociatedScriptDefinition
|
|||||||
export interface ScriptTestFormProps
|
export interface ScriptTestFormProps
|
||||||
{
|
{
|
||||||
scriptId: number;
|
scriptId: number;
|
||||||
scriptDefinition: AssociatedScriptDefinition;
|
scriptType: QRecord;
|
||||||
tableName: string;
|
tableName: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
recordId: any;
|
recordId: any;
|
||||||
code: string;
|
fileContents: {[name: string]: string};
|
||||||
apiName: string;
|
apiName: string;
|
||||||
apiVersion: string;
|
apiVersion: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const qController = Client.getInstance();
|
const qController = Client.getInstance();
|
||||||
|
|
||||||
function ScriptTestForm({scriptId, scriptDefinition, tableName, fieldName, recordId, code, apiName, apiVersion}: ScriptTestFormProps): JSX.Element
|
function ScriptTestForm({scriptId, scriptType, tableName, fieldName, recordId, fileContents, apiName, apiVersion}: ScriptTestFormProps): JSX.Element
|
||||||
{
|
{
|
||||||
|
const [testInputFields, setTestInputFields] = useState(null as QFieldMetaData[])
|
||||||
|
const [testOutputFields, setTestOutputFields] = useState(null as QFieldMetaData[])
|
||||||
const [testInputValues, setTestInputValues] = useState({} as any);
|
const [testInputValues, setTestInputValues] = useState({} as any);
|
||||||
const [testOutputValues, setTestOutputValues] = useState({} as any);
|
const [testOutputValues, setTestOutputValues] = useState({} as any);
|
||||||
const [logLines, setLogLines] = useState([] as any[])
|
const [logLines, setLogLines] = useState([] as any[])
|
||||||
@ -77,10 +80,46 @@ function ScriptTestForm({scriptId, scriptDefinition, tableName, fieldName, recor
|
|||||||
|
|
||||||
if(firstRender)
|
if(firstRender)
|
||||||
{
|
{
|
||||||
scriptDefinition.testInputFields.forEach((field: QFieldMetaData) =>
|
(async () =>
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
// call backend to load details about how to test this script type //
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("scriptTypeId", scriptType.values.get("id"));
|
||||||
|
const processResult = await qController.processRun("loadScriptTestDetails", formData, null, true);
|
||||||
|
|
||||||
|
if (processResult instanceof QJobError)
|
||||||
|
{
|
||||||
|
const jobError = processResult as QJobError
|
||||||
|
setTestException(jobError.userFacingError ?? jobError.error)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const jobComplete = processResult as QJobComplete
|
||||||
|
|
||||||
|
const testInputFields = [] as QFieldMetaData[];
|
||||||
|
for(let i = 0; i <jobComplete?.values?.testInputFields?.length; i++)
|
||||||
|
{
|
||||||
|
testInputFields.push(new QFieldMetaData(jobComplete.values.testInputFields[i]));
|
||||||
|
}
|
||||||
|
setTestInputFields(testInputFields);
|
||||||
|
|
||||||
|
const testOutputFields = [] as QFieldMetaData[];
|
||||||
|
for(let i = 0; i <jobComplete?.values?.testOutputFields?.length; i++)
|
||||||
|
{
|
||||||
|
testOutputFields.push(new QFieldMetaData(jobComplete.values.testOutputFields[i]));
|
||||||
|
}
|
||||||
|
setTestOutputFields(testOutputFields);
|
||||||
|
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
// set a default value in each input field //
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
testInputFields.forEach((field: QFieldMetaData) =>
|
||||||
{
|
{
|
||||||
testInputValues[field.name] = field.defaultValue ?? "";
|
testInputValues[field.name] = field.defaultValue ?? "";
|
||||||
});
|
});
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildFullExceptionMessage = (exception: any): string =>
|
const buildFullExceptionMessage = (exception: any): string =>
|
||||||
@ -91,9 +130,9 @@ function ScriptTestForm({scriptId, scriptDefinition, tableName, fieldName, recor
|
|||||||
const testScript = () =>
|
const testScript = () =>
|
||||||
{
|
{
|
||||||
const inputValues = new Map<string, any>();
|
const inputValues = new Map<string, any>();
|
||||||
if (scriptDefinition.testInputFields)
|
if (testInputFields)
|
||||||
{
|
{
|
||||||
scriptDefinition.testInputFields.forEach((field: QFieldMetaData) =>
|
testInputFields.forEach((field: QFieldMetaData) =>
|
||||||
{
|
{
|
||||||
inputValues.set(field.name, testInputValues[field.name]);
|
inputValues.set(field.name, testInputValues[field.name]);
|
||||||
});
|
});
|
||||||
@ -108,6 +147,7 @@ function ScriptTestForm({scriptId, scriptDefinition, tableName, fieldName, recor
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
let output;
|
let output;
|
||||||
|
/*
|
||||||
if(tableName && recordId && fieldName)
|
if(tableName && recordId && fieldName)
|
||||||
{
|
{
|
||||||
/////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////
|
||||||
@ -115,15 +155,21 @@ function ScriptTestForm({scriptId, scriptDefinition, tableName, fieldName, recor
|
|||||||
/////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////
|
||||||
inputValues.set("apiName", apiName);
|
inputValues.set("apiName", apiName);
|
||||||
inputValues.set("apiVersion", apiVersion);
|
inputValues.set("apiVersion", apiVersion);
|
||||||
output = await qController.testScript(tableName, recordId, fieldName, code, inputValues);
|
output = await qController.testScript(tableName, recordId, fieldName, "todo!", inputValues);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
*/
|
||||||
{
|
{
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("scriptId", scriptId);
|
formData.append("scriptId", scriptId);
|
||||||
formData.append("apiName", apiName);
|
formData.append("apiName", apiName);
|
||||||
formData.append("apiVersion", apiVersion);
|
formData.append("apiVersion", apiVersion);
|
||||||
formData.append("code", code);
|
|
||||||
|
formData.append("fileNames", Object.keys(fileContents).join(","))
|
||||||
|
for (let fileName in fileContents)
|
||||||
|
{
|
||||||
|
formData.append("fileContents:" + fileName, fileContents[fileName]);
|
||||||
|
}
|
||||||
|
|
||||||
for(let fieldName of inputValues.keys())
|
for(let fieldName of inputValues.keys())
|
||||||
{
|
{
|
||||||
@ -195,7 +241,7 @@ function ScriptTestForm({scriptId, scriptDefinition, tableName, fieldName, recor
|
|||||||
<Typography variant="h6" p={2} pb={1}>Test Input</Typography>
|
<Typography variant="h6" p={2} pb={1}>Test Input</Typography>
|
||||||
<Box px={2} pb={2}>
|
<Box px={2} pb={2}>
|
||||||
{
|
{
|
||||||
scriptDefinition.testInputFields && testInputValues && scriptDefinition.testInputFields.map((field: QFieldMetaData) =>
|
testInputFields && testInputValues && testInputFields.map((field: QFieldMetaData) =>
|
||||||
{
|
{
|
||||||
return (<TextField
|
return (<TextField
|
||||||
key={field.name}
|
key={field.name}
|
||||||
@ -234,16 +280,20 @@ function ScriptTestForm({scriptId, scriptDefinition, tableName, fieldName, recor
|
|||||||
</Typography>
|
</Typography>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
scriptDefinition.testOutputFields && scriptDefinition.testOutputFields.map((f: any) =>
|
testOutputFields && testOutputFields.map((f: any) =>
|
||||||
{
|
{
|
||||||
const field = new QFieldMetaData(f);
|
const field = new QFieldMetaData(f);
|
||||||
return (
|
return (
|
||||||
<Box key={field.name} flexDirection="row" pr={2}>
|
<Box key={field.name} flexDirection="row" pr={2}>
|
||||||
<Typography variant="button" fontWeight="bold" pr={1}>
|
<Typography variant="button" textTransform="none" fontWeight="bold" pr={1}>
|
||||||
{field.label}:
|
{field.label}:
|
||||||
</Typography>
|
</Typography>
|
||||||
<MDTypography variant="button" fontWeight="regular" color="text">
|
<MDTypography variant="button" fontWeight="regular" color="text">
|
||||||
{ValueUtils.getValueForDisplay(field, testOutputValues[field.name], testOutputValues[field.name], "view")}
|
{
|
||||||
|
testOutputValues.values ?
|
||||||
|
ValueUtils.getValueForDisplay(field, testOutputValues.values[field.name], testOutputValues.displayValues[field.name], "view") :
|
||||||
|
ValueUtils.getValueForDisplay(field, testOutputValues[field.name], testOutputValues[field.name], "view")
|
||||||
|
}
|
||||||
</MDTypography>
|
</MDTypography>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
@ -27,18 +27,22 @@ import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QC
|
|||||||
import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria";
|
import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria";
|
||||||
import {QFilterOrderBy} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterOrderBy";
|
import {QFilterOrderBy} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterOrderBy";
|
||||||
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
|
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
|
||||||
|
import {SelectChangeEvent} from "@mui/material";
|
||||||
import Alert from "@mui/material/Alert";
|
import Alert from "@mui/material/Alert";
|
||||||
import Avatar from "@mui/material/Avatar";
|
import Avatar from "@mui/material/Avatar";
|
||||||
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 Chip from "@mui/material/Chip";
|
import Chip from "@mui/material/Chip";
|
||||||
import Divider from "@mui/material/Divider";
|
import Divider from "@mui/material/Divider";
|
||||||
|
import FormControl from "@mui/material/FormControl/FormControl";
|
||||||
import Grid from "@mui/material/Grid";
|
import Grid from "@mui/material/Grid";
|
||||||
import List from "@mui/material/List";
|
import List from "@mui/material/List";
|
||||||
import ListItem from "@mui/material/ListItem";
|
import ListItem from "@mui/material/ListItem";
|
||||||
import ListItemAvatar from "@mui/material/ListItemAvatar";
|
import ListItemAvatar from "@mui/material/ListItemAvatar";
|
||||||
import ListItemText from "@mui/material/ListItemText";
|
import ListItemText from "@mui/material/ListItemText";
|
||||||
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import Modal from "@mui/material/Modal";
|
import Modal from "@mui/material/Modal";
|
||||||
|
import Select from "@mui/material/Select/Select";
|
||||||
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 Tabs from "@mui/material/Tabs";
|
import Tabs from "@mui/material/Tabs";
|
||||||
@ -59,6 +63,7 @@ import ValueUtils from "qqq/utils/qqq/ValueUtils";
|
|||||||
|
|
||||||
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";
|
||||||
|
import "ace-builds/src-noconflict/mode-velocity";
|
||||||
import "ace-builds/src-noconflict/mode-json";
|
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";
|
||||||
@ -94,7 +99,9 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
|
|||||||
const [selectedVersionRecord, setSelectedVersionRecord] = useState(null as QRecord);
|
const [selectedVersionRecord, setSelectedVersionRecord] = useState(null as QRecord);
|
||||||
const [scriptLogs, setScriptLogs] = useState({} as any);
|
const [scriptLogs, setScriptLogs] = useState({} as any);
|
||||||
const [scriptTypeRecord, setScriptTypeRecord] = useState(null as QRecord)
|
const [scriptTypeRecord, setScriptTypeRecord] = useState(null as QRecord)
|
||||||
const [testScriptDefinitionObject, setTestScriptDefinitionObject] = useState({} as any)
|
const [scriptTypeFileSchemaList, setScriptTypeFileSchemaList] = useState(null as QRecord[])
|
||||||
|
const [availableFileNames, setAvailableFileNames] = useState([] as string[]);
|
||||||
|
const [selectedFileName, setSelectedFileName] = useState("");
|
||||||
const [currentVersionId , setCurrentVersionId] = useState(null as number);
|
const [currentVersionId , setCurrentVersionId] = useState(null as number);
|
||||||
const [notFoundMessage, setNotFoundMessage] = useState(null);
|
const [notFoundMessage, setNotFoundMessage] = useState(null);
|
||||||
const [selectedTab, setSelectedTab] = useState(0);
|
const [selectedTab, setSelectedTab] = useState(0);
|
||||||
@ -118,17 +125,32 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
|
|||||||
const scriptRecord = await qController.get("script", scriptId);
|
const scriptRecord = await qController.get("script", scriptId);
|
||||||
setScriptRecord(scriptRecord);
|
setScriptRecord(scriptRecord);
|
||||||
|
|
||||||
setScriptTypeRecord(await qController.get("scriptType", scriptRecord.values.get("scriptTypeId")));
|
const scriptTypeRecord = await qController.get("scriptType", scriptRecord.values.get("scriptTypeId"));
|
||||||
|
setScriptTypeRecord(scriptTypeRecord);
|
||||||
|
|
||||||
if(testInputFields !== null || testOutputFields !== null)
|
let fileMode = scriptTypeRecord.values.get("fileMode");
|
||||||
|
let scriptTypeFileSchemaList: QRecord[] = null;
|
||||||
|
if(fileMode == 1) // SINGLE
|
||||||
{
|
{
|
||||||
setTestScriptDefinitionObject({testInputFields: testInputFields, testOutputFields: testOutputFields});
|
scriptTypeFileSchemaList = [new QRecord({values: {name: "Script.js", fileType: "javascript"}})];
|
||||||
}
|
}
|
||||||
else
|
else if(fileMode == 2) // MULTI_PRE_DEFINED
|
||||||
{
|
{
|
||||||
setTestScriptDefinitionObject({testInputFields: [
|
const filter = new QQueryFilter([new QFilterCriteria("scriptTypeId", QCriteriaOperator.EQUALS, [scriptRecord.values.get("scriptTypeId")])], [new QFilterOrderBy("id")])
|
||||||
new QFieldMetaData({name: "recordPrimaryKeyList", label: "Record Primary Key List"})
|
scriptTypeFileSchemaList = await qController.query("scriptTypeFileSchema", filter);
|
||||||
], testOutputFields: []})
|
}
|
||||||
|
else // MULTI AD_HOC
|
||||||
|
{
|
||||||
|
// todo - not yet supported
|
||||||
|
console.log(`Script Type File Mode of ${fileMode} is not yet supported.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
setScriptTypeFileSchemaList(scriptTypeFileSchemaList);
|
||||||
|
if(scriptTypeFileSchemaList)
|
||||||
|
{
|
||||||
|
const availableFileNames = scriptTypeFileSchemaList.map((fileSchemaRecord) => fileSchemaRecord.values.get("name"))
|
||||||
|
setAvailableFileNames(availableFileNames);
|
||||||
|
setSelectedFileName(availableFileNames[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
const criteria = [new QFilterCriteria("scriptId", QCriteriaOperator.EQUALS, [scriptId])];
|
const criteria = [new QFilterCriteria("scriptId", QCriteriaOperator.EQUALS, [scriptId])];
|
||||||
@ -141,13 +163,7 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
|
|||||||
|
|
||||||
if(versions && versions.length > 0)
|
if(versions && versions.length > 0)
|
||||||
{
|
{
|
||||||
setCurrentVersionId(versions[0].values.get("id"));
|
selectVersion(versions[0]);
|
||||||
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)
|
catch (e)
|
||||||
@ -174,8 +190,8 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
|
|||||||
editorProps.tableName = associatedScriptTableName;
|
editorProps.tableName = associatedScriptTableName;
|
||||||
editorProps.fieldName = associatedScriptFieldName;
|
editorProps.fieldName = associatedScriptFieldName;
|
||||||
editorProps.recordId = associatedScriptRecordId;
|
editorProps.recordId = associatedScriptRecordId;
|
||||||
editorProps.scriptDefinition = testScriptDefinitionObject;
|
|
||||||
editorProps.scriptTypeRecord = scriptTypeRecord;
|
editorProps.scriptTypeRecord = scriptTypeRecord;
|
||||||
|
editorProps.scriptTypeFileSchemaList = scriptTypeFileSchemaList;
|
||||||
setEditorProps(editorProps);
|
setEditorProps(editorProps);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -223,8 +239,10 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
|
|||||||
setCurrentVersionId(version.values.get("id"));
|
setCurrentVersionId(version.values.get("id"));
|
||||||
loadingSelectedVersion.setLoading();
|
loadingSelectedVersion.setLoading();
|
||||||
|
|
||||||
// fetch the full version
|
//////////////////////////////////////////////////////////////////////////////////////
|
||||||
const selectedVersion = await qController.get("scriptRevision", version.values.get("id"));
|
// fetch the full version - including its associated scriptRevisionFile sub-records //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
const selectedVersion = await qController.get("scriptRevision", version.values.get("id"), true);
|
||||||
console.log("Fetched selectedVersion:");
|
console.log("Fetched selectedVersion:");
|
||||||
console.log(selectedVersion);
|
console.log(selectedVersion);
|
||||||
setSelectedVersionRecord(selectedVersion);
|
setSelectedVersionRecord(selectedVersion);
|
||||||
@ -233,6 +251,44 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
|
|||||||
})();
|
})();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSelectFile = (event: SelectChangeEvent) =>
|
||||||
|
{
|
||||||
|
setSelectedFileName(event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSelectedFileCode = (): string =>
|
||||||
|
{
|
||||||
|
return (getSelectedVersionCode()[selectedFileName] ?? "");
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSelectedFileType = (): string =>
|
||||||
|
{
|
||||||
|
for (let i = 0; i < scriptTypeFileSchemaList.length; i++)
|
||||||
|
{
|
||||||
|
let name = scriptTypeFileSchemaList[i].values.get("name");
|
||||||
|
if(name == selectedFileName)
|
||||||
|
{
|
||||||
|
return (scriptTypeFileSchemaList[i].values.get("fileType"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ("javascript"); // have some default...
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSelectedVersionCode = (): {[name: string]: string} =>
|
||||||
|
{
|
||||||
|
let rs: {[name: string]: string} = {}
|
||||||
|
let files = selectedVersionRecord?.associatedRecords?.get("files")
|
||||||
|
|
||||||
|
for (let j = 0; j < files?.length; j++)
|
||||||
|
{
|
||||||
|
let file = files[j];
|
||||||
|
rs[file.values.get("fileName")] = file.values.get("contents");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (rs);
|
||||||
|
}
|
||||||
|
|
||||||
function getVersionsList(versionRecordList: QRecord[], selectedVersionRecord: QRecord)
|
function getVersionsList(versionRecordList: QRecord[], selectedVersionRecord: QRecord)
|
||||||
{
|
{
|
||||||
return <List sx={{pl: 3, height: "400px", overflow: "auto"}}>
|
return <List sx={{pl: 3, height: "400px", overflow: "auto"}}>
|
||||||
@ -344,7 +400,7 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container>
|
<Grid container className="scriptViewer">
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Box>
|
<Box>
|
||||||
{
|
{
|
||||||
@ -420,10 +476,22 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
|
|||||||
</CustomWidthTooltip>
|
</CustomWidthTooltip>
|
||||||
</Box>
|
</Box>
|
||||||
{
|
{
|
||||||
loadingSelectedVersion.isNotLoading() && selectedVersionRecord && selectedVersionRecord.values.get("contents") ? (
|
loadingSelectedVersion.isNotLoading() && selectedVersionRecord ? (
|
||||||
<>
|
<>
|
||||||
|
{
|
||||||
|
availableFileNames && availableFileNames.length > 1 &&
|
||||||
|
<FormControl className="selectedFileTab" variant="standard" sx={{verticalAlign: "bottom", pl: "4px"}}>
|
||||||
|
<Select value={selectedFileName} onChange={(event) => handleSelectFile(event)}>
|
||||||
|
{
|
||||||
|
availableFileNames.map((name) => (
|
||||||
|
<MenuItem key={name} value={name}>{name}</MenuItem>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
}
|
||||||
<AceEditor
|
<AceEditor
|
||||||
mode="javascript"
|
mode={getSelectedFileType()}
|
||||||
theme="github"
|
theme="github"
|
||||||
name={"viewData"}
|
name={"viewData"}
|
||||||
readOnly
|
readOnly
|
||||||
@ -431,8 +499,9 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
|
|||||||
editorProps={{$blockScrolling: true}}
|
editorProps={{$blockScrolling: true}}
|
||||||
setOptions={{useWorker: false}}
|
setOptions={{useWorker: false}}
|
||||||
width="100%"
|
width="100%"
|
||||||
height="400px"
|
height="368px"
|
||||||
value={selectedVersionRecord?.values?.get("contents")}
|
value={getSelectedFileCode()}
|
||||||
|
style={{borderTop: "1px solid lightgray", borderBottomRightRadius: "1rem"}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : null
|
) : null
|
||||||
@ -473,11 +542,11 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
|
|||||||
<TabPanel index={2} value={selectedTab}>
|
<TabPanel index={2} value={selectedTab}>
|
||||||
<Box sx={{height: "455px"}} px={2} pb={1}>
|
<Box sx={{height: "455px"}} px={2} pb={1}>
|
||||||
<ScriptTestForm scriptId={scriptId}
|
<ScriptTestForm scriptId={scriptId}
|
||||||
scriptDefinition={testScriptDefinitionObject}
|
scriptType={scriptTypeRecord}
|
||||||
tableName={associatedScriptTableName}
|
tableName={associatedScriptTableName}
|
||||||
fieldName={associatedScriptFieldName}
|
fieldName={associatedScriptFieldName}
|
||||||
recordId={associatedScriptRecordId}
|
recordId={associatedScriptRecordId}
|
||||||
code={selectedVersionRecord?.values.get("contents")}
|
fileContents={getSelectedVersionCode()}
|
||||||
apiName={selectedVersionRecord?.values.get("apiName")}
|
apiName={selectedVersionRecord?.values.get("apiName")}
|
||||||
apiVersion={selectedVersionRecord?.values.get("apiVersion")} />
|
apiVersion={selectedVersionRecord?.values.get("apiVersion")} />
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -929,12 +929,21 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSortChange = (gridSort: GridSortModel) =>
|
const handleSortChangeForDataGrid = (gridSort: GridSortModel) =>
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// this method just wraps handleSortChange, but w/o the optional 2nd param, so we can use it in data grid //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
handleSortChange(gridSort);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSortChange = (gridSort: GridSortModel, overrideFilterModel?: GridFilterModel) =>
|
||||||
{
|
{
|
||||||
if (gridSort && gridSort.length > 0)
|
if (gridSort && gridSort.length > 0)
|
||||||
{
|
{
|
||||||
setColumnSortModel(gridSort);
|
setColumnSortModel(gridSort);
|
||||||
setQueryFilter(FilterUtils.buildQFilterFromGridFilter(tableMetaData, filterModel, gridSort, rowsPerPage));
|
const gridFilterModelToUse = overrideFilterModel ?? filterModel;
|
||||||
|
setQueryFilter(FilterUtils.buildQFilterFromGridFilter(tableMetaData, gridFilterModelToUse, gridSort, rowsPerPage));
|
||||||
localStorage.setItem(sortLocalStorageKey, JSON.stringify(gridSort));
|
localStorage.setItem(sortLocalStorageKey, JSON.stringify(gridSort));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1286,13 +1295,13 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
|
|
||||||
const models = await FilterUtils.determineFilterAndSortModels(qController, tableMetaData, qRecord.values.get("filterJson"), null, null, null);
|
const models = await FilterUtils.determineFilterAndSortModels(qController, tableMetaData, qRecord.values.get("filterJson"), null, null, null);
|
||||||
handleFilterChange(models.filter);
|
handleFilterChange(models.filter);
|
||||||
handleSortChange(models.sort);
|
handleSortChange(models.sort, models.filter);
|
||||||
localStorage.setItem(currentSavedFilterLocalStorageKey, selectedSavedFilterId.toString());
|
localStorage.setItem(currentSavedFilterLocalStorageKey, selectedSavedFilterId.toString());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
handleFilterChange({items: []} as GridFilterModel);
|
handleFilterChange({items: []} as GridFilterModel);
|
||||||
handleSortChange([{field: tableMetaData.primaryKeyField, sort: "desc"}]);
|
handleSortChange([{field: tableMetaData.primaryKeyField, sort: "desc"}], {items: []} as GridFilterModel);
|
||||||
localStorage.removeItem(currentSavedFilterLocalStorageKey);
|
localStorage.removeItem(currentSavedFilterLocalStorageKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1692,7 +1701,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
menuItems.push(<MenuItem key={process.name} onClick={() => processClicked(process)}><ListItemIcon><Icon>{process.iconName ?? "arrow_forward"}</Icon></ListItemIcon>{process.label}</MenuItem>);
|
menuItems.push(<MenuItem key={process.name} onClick={() => processClicked(process)}><ListItemIcon><Icon>{process.iconName ?? "arrow_forward"}</Icon></ListItemIcon>{process.label}</MenuItem>);
|
||||||
}
|
}
|
||||||
|
|
||||||
menuItems.push(<MenuItem key="developerMode" onClick={() => navigate("dev")}><ListItemIcon><Icon>code</Icon></ListItemIcon>Developer Mode</MenuItem>);
|
menuItems.push(<MenuItem key="developerMode" onClick={() => navigate(`${metaData.getTablePathByName(tableName)}/dev`)}><ListItemIcon><Icon>code</Icon></ListItemIcon>Developer Mode</MenuItem>);
|
||||||
|
|
||||||
if (tableProcesses && tableProcesses.length)
|
if (tableProcesses && tableProcesses.length)
|
||||||
{
|
{
|
||||||
@ -1919,7 +1928,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
onColumnVisibilityModelChange={handleColumnVisibilityChange}
|
onColumnVisibilityModelChange={handleColumnVisibilityChange}
|
||||||
onColumnOrderChange={handleColumnOrderChange}
|
onColumnOrderChange={handleColumnOrderChange}
|
||||||
onSelectionModelChange={selectionChanged}
|
onSelectionModelChange={selectionChanged}
|
||||||
onSortModelChange={handleSortChange}
|
onSortModelChange={handleSortChangeForDataGrid}
|
||||||
sortingOrder={["asc", "desc"]}
|
sortingOrder={["asc", "desc"]}
|
||||||
sortModel={columnSortModel}
|
sortModel={columnSortModel}
|
||||||
getRowClassName={(params) => (params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd")}
|
getRowClassName={(params) => (params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd")}
|
||||||
|
@ -329,10 +329,24 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
const metaData = await qController.loadMetaData();
|
const metaData = await qController.loadMetaData();
|
||||||
setMetaData(metaData);
|
setMetaData(metaData);
|
||||||
ValueUtils.qInstance = metaData;
|
ValueUtils.qInstance = metaData;
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////
|
||||||
|
// load the processes to show in the action menu //
|
||||||
|
///////////////////////////////////////////////////
|
||||||
const processesForTable = ProcessUtils.getProcessesForTable(metaData, tableName);
|
const processesForTable = ProcessUtils.getProcessesForTable(metaData, tableName);
|
||||||
processesForTable.sort((a, b) => a.label.localeCompare(b.label));
|
processesForTable.sort((a, b) => a.label.localeCompare(b.label));
|
||||||
setTableProcesses(processesForTable);
|
setTableProcesses(processesForTable);
|
||||||
setAllTableProcesses(ProcessUtils.getProcessesForTable(metaData, tableName, true)); // these include hidden ones (e.g., to find the bulks)
|
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
// load processes that the routing needs to respect //
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
const allTableProcesses = ProcessUtils.getProcessesForTable(metaData, tableName, true) // these include hidden ones (e.g., to find the bulks)
|
||||||
|
const runRecordScriptProcess = metaData?.processes.get("runRecordScript");
|
||||||
|
if (runRecordScriptProcess)
|
||||||
|
{
|
||||||
|
allTableProcesses.unshift(runRecordScriptProcess)
|
||||||
|
}
|
||||||
|
setAllTableProcesses(allTableProcesses);
|
||||||
|
|
||||||
if (launchingProcess)
|
if (launchingProcess)
|
||||||
{
|
{
|
||||||
@ -570,6 +584,14 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
|
|
||||||
let hasEditOrDelete = (table.capabilities.has(Capability.TABLE_UPDATE) && table.editPermission) || (table.capabilities.has(Capability.TABLE_DELETE) && table.deletePermission);
|
let hasEditOrDelete = (table.capabilities.has(Capability.TABLE_UPDATE) && table.editPermission) || (table.capabilities.has(Capability.TABLE_DELETE) && table.deletePermission);
|
||||||
|
|
||||||
|
function gotoCreate()
|
||||||
|
{
|
||||||
|
const path = `${pathParts.slice(0, -1).join("/")}/create`;
|
||||||
|
navigate(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
const runRecordScriptProcess = metaData?.processes.get("runRecordScript");
|
||||||
|
|
||||||
const renderActionsMenu = (
|
const renderActionsMenu = (
|
||||||
<Menu
|
<Menu
|
||||||
anchorEl={actionsMenu}
|
anchorEl={actionsMenu}
|
||||||
@ -626,8 +648,15 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
{(tableProcesses?.length > 0 || hasEditOrDelete) && <Divider />}
|
{(tableProcesses?.length > 0 || hasEditOrDelete) && <Divider />}
|
||||||
|
{
|
||||||
|
runRecordScriptProcess &&
|
||||||
|
<MenuItem key={runRecordScriptProcess.name} onClick={() => processClicked(runRecordScriptProcess)}>
|
||||||
|
<ListItemIcon><Icon>{runRecordScriptProcess.iconName ?? "arrow_forward"}</Icon></ListItemIcon>
|
||||||
|
{runRecordScriptProcess.label}
|
||||||
|
</MenuItem>
|
||||||
|
}
|
||||||
<MenuItem onClick={() => navigate("dev")}>
|
<MenuItem onClick={() => navigate("dev")}>
|
||||||
<ListItemIcon><Icon>data_object</Icon></ListItemIcon>
|
<ListItemIcon><Icon>code</Icon></ListItemIcon>
|
||||||
Developer Mode
|
Developer Mode
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{
|
{
|
||||||
@ -853,7 +882,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
activeModalProcess &&
|
activeModalProcess &&
|
||||||
<Modal open={activeModalProcess !== null} onClose={(event, reason) => closeModalProcess(event, reason)}>
|
<Modal open={activeModalProcess !== null} onClose={(event, reason) => closeModalProcess(event, reason)}>
|
||||||
<div className="modalProcess">
|
<div className="modalProcess">
|
||||||
<ProcessRun process={activeModalProcess} isModal={true} recordIds={id} closeModalHandler={closeModalProcess} />
|
<ProcessRun process={activeModalProcess} isModal={true} table={tableMetaData} recordIds={id} closeModalHandler={closeModalProcess} />
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
}
|
}
|
||||||
|
@ -523,3 +523,25 @@ input[type="search"]::-webkit-search-results-decoration { display: none; }
|
|||||||
padding-right: 0.25rem;
|
padding-right: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* file-select box in script editor & viewer - make it look tabby */
|
||||||
|
.scriptEditor .selectedFileTab div.MuiSelect-select,
|
||||||
|
.scriptViewer .selectedFileTab div.MuiSelect-select
|
||||||
|
{
|
||||||
|
padding: 0.25rem 1.5rem 0.25rem 1rem !important;
|
||||||
|
border: 1px solid lightgray;
|
||||||
|
border-radius: 0.375rem 0.375rem 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scriptEditor .selectedFileTab,
|
||||||
|
.scriptViewer .selectedFileTab
|
||||||
|
{
|
||||||
|
margin-bottom: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* show the down-arrow in the file-select box in script editor & viewer */
|
||||||
|
.scriptEditor .selectedFileTab .MuiSelect-iconStandard,
|
||||||
|
.scriptViewer .selectedFileTab .MuiSelect-iconStandard
|
||||||
|
{
|
||||||
|
display: inline;
|
||||||
|
right: .5rem
|
||||||
|
}
|
||||||
|
@ -62,10 +62,21 @@ export default class HtmlUtils
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Download a server-side generated file.
|
** Download a server-side generated file (or the contents of a data: url)
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
static downloadUrlViaIFrame = (url: string) =>
|
static downloadUrlViaIFrame = (url: string, filename: string) =>
|
||||||
{
|
{
|
||||||
|
if(url.startsWith("data:"))
|
||||||
|
{
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.download = filename;
|
||||||
|
link.href = url;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (document.getElementById("downloadIframe"))
|
if (document.getElementById("downloadIframe"))
|
||||||
{
|
{
|
||||||
document.body.removeChild(document.getElementById("downloadIframe"));
|
document.body.removeChild(document.getElementById("downloadIframe"));
|
||||||
@ -101,10 +112,21 @@ export default class HtmlUtils
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Open a server-side generated file from a url in a new window.
|
** Open a server-side generated file from a url in a new window (or a data: url)
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
static openInNewWindow = (url: string, filename: string) =>
|
static openInNewWindow = (url: string, filename: string) =>
|
||||||
{
|
{
|
||||||
|
if(url.startsWith("data:"))
|
||||||
|
{
|
||||||
|
const openInWindow = window.open("", "_blank");
|
||||||
|
openInWindow.document.write(`<html lang="en">
|
||||||
|
<body style="margin: 0">
|
||||||
|
<iframe src="${url}" width="100%" height="100%" style="border: 0">
|
||||||
|
</body>
|
||||||
|
`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const openInWindow = window.open("", "_blank");
|
const openInWindow = window.open("", "_blank");
|
||||||
openInWindow.document.write(`<html lang="en">
|
openInWindow.document.write(`<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
@ -39,6 +39,7 @@ import HtmlUtils from "qqq/utils/HtmlUtils";
|
|||||||
import Client from "qqq/utils/qqq/Client";
|
import Client from "qqq/utils/qqq/Client";
|
||||||
|
|
||||||
import "ace-builds/src-noconflict/mode-sql";
|
import "ace-builds/src-noconflict/mode-sql";
|
||||||
|
import "ace-builds/src-noconflict/mode-velocity";
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Utility class for working with QQQ Values
|
** Utility class for working with QQQ Values
|
||||||
@ -637,7 +638,7 @@ function BlobComponent({field, url, filename, usage}: BlobComponentProps): JSX.E
|
|||||||
const download = (event: React.MouseEvent<HTMLSpanElement>) =>
|
const download = (event: React.MouseEvent<HTMLSpanElement>) =>
|
||||||
{
|
{
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
HtmlUtils.downloadUrlViaIFrame(url);
|
HtmlUtils.downloadUrlViaIFrame(url, filename);
|
||||||
};
|
};
|
||||||
|
|
||||||
const open = (event: React.MouseEvent<HTMLSpanElement>) =>
|
const open = (event: React.MouseEvent<HTMLSpanElement>) =>
|
||||||
|
@ -44,6 +44,7 @@ public class ScriptTableTest extends QBaseSeleniumTest
|
|||||||
.withRouteToFile("/data/script/1", "data/script/1.json")
|
.withRouteToFile("/data/script/1", "data/script/1.json")
|
||||||
.withRouteToFile("/data/scriptType/1", "data/scriptType/1.json")
|
.withRouteToFile("/data/scriptType/1", "data/scriptType/1.json")
|
||||||
.withRouteToFile("/data/scriptRevision/query", "data/scriptRevision/query.json")
|
.withRouteToFile("/data/scriptRevision/query", "data/scriptRevision/query.json")
|
||||||
|
.withRouteToFile("/data/scriptLog/query", "data/scriptLog/query.json")
|
||||||
.withRouteToFile("/data/scriptRevision/100", "data/scriptRevision/100.json")
|
.withRouteToFile("/data/scriptRevision/100", "data/scriptRevision/100.json")
|
||||||
.withRouteToFile("/metaData/table/script", "metaData/table/script.json")
|
.withRouteToFile("/metaData/table/script", "metaData/table/script.json")
|
||||||
.withRouteToFile("/widget/scriptViewer", "widget/scriptViewer.json")
|
.withRouteToFile("/widget/scriptViewer", "widget/scriptViewer.json")
|
||||||
|
3
src/test/resources/fixtures/data/scriptLog/query.json
Normal file
3
src/test/resources/fixtures/data/scriptLog/query.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"records": []
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"tableName": "scriptRevision",
|
"tableName": "scriptRevision",
|
||||||
"values": {
|
"values": {
|
||||||
"contents": "var hello;",
|
|
||||||
"id": 100,
|
"id": 100,
|
||||||
"commitMessage": "Initial checkin",
|
"commitMessage": "Initial checkin",
|
||||||
"author": "Jon Programmer",
|
"author": "Jon Programmer",
|
||||||
@ -10,5 +9,20 @@
|
|||||||
},
|
},
|
||||||
"displayValues": {
|
"displayValues": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"associatedRecords": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"tableName": "scriptRevisionFile",
|
||||||
|
"values": {
|
||||||
|
"id": 101,
|
||||||
|
"fileName": "Script.js",
|
||||||
|
"contents": "var hello;",
|
||||||
|
"scriptRevisionId": 100,
|
||||||
|
"createDate": "2023-06-23T21:59:57Z",
|
||||||
|
"modifyDate": "2023-06-23T21:59:57Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,7 +5,8 @@
|
|||||||
"name": "Record Script",
|
"name": "Record Script",
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"createDate": "2023-02-18T00:47:51Z",
|
"createDate": "2023-02-18T00:47:51Z",
|
||||||
"modifyDate": "2023-02-18T00:47:51Z"
|
"modifyDate": "2023-02-18T00:47:51Z",
|
||||||
|
"fileMode": 1
|
||||||
},
|
},
|
||||||
"displayValues": {
|
"displayValues": {
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user