diff --git a/package.json b/package.json
index f9906b9..883e1d8 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
"@auth0/auth0-react": "1.10.2",
"@emotion/react": "11.7.1",
"@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/material": "5.11.1",
"@mui/styles": "5.11.1",
diff --git a/src/qqq/components/misc/SavedFilters.tsx b/src/qqq/components/misc/SavedFilters.tsx
index ef52bc6..eead528 100644
--- a/src/qqq/components/misc/SavedFilters.tsx
+++ b/src/qqq/components/misc/SavedFilters.tsx
@@ -200,7 +200,7 @@ function SavedFilters({qController, metaData, tableMetaData, currentSavedFilter,
else
{
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)
{
diff --git a/src/qqq/components/scripts/ScriptDocsForm.tsx b/src/qqq/components/scripts/ScriptDocsForm.tsx
index b07836e..8296997 100644
--- a/src/qqq/components/scripts/ScriptDocsForm.tsx
+++ b/src/qqq/components/scripts/ScriptDocsForm.tsx
@@ -62,6 +62,7 @@ function ScriptDocsForm({helpText, exampleCode, aceEditorHeight}: Props): JSX.El
width="100%"
showPrintMargin={false}
height="100%"
+ style={{borderBottomRightRadius: "1rem", borderBottomLeftRadius: "1rem"}}
/>
diff --git a/src/qqq/components/scripts/ScriptEditor.tsx b/src/qqq/components/scripts/ScriptEditor.tsx
index c50b08a..9ee7ca1 100644
--- a/src/qqq/components/scripts/ScriptEditor.tsx
+++ b/src/qqq/components/scripts/ScriptEditor.tsx
@@ -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 {QPossibleValue} from "@kingsrook/qqq-frontend-core/lib/model/QPossibleValue";
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 Box from "@mui/material/Box";
import Card from "@mui/material/Card";
@@ -31,9 +31,14 @@ import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import DialogTitle from "@mui/material/DialogTitle";
+import FormControl from "@mui/material/FormControl/FormControl";
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 TextField from "@mui/material/TextField";
+import Tooltip from "@mui/material/Tooltip";
import FormData from "form-data";
import React, {useEffect, useReducer, useRef, useState} from "react";
import AceEditor from "react-ace";
@@ -56,22 +61,82 @@ export interface ScriptEditorProps
tableName: string;
fieldName: string;
recordId: any;
- scriptDefinition: any;
scriptTypeRecord: QRecord;
+ scriptTypeFileSchemaList: QRecord[];
}
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 [updatedCode, setUpdatedCode] = useState(scriptRevisionRecord ? scriptRevisionRecord.values.get("contents") : "");
const [apiName, setApiName] = useState(scriptRevisionRecord ? scriptRevisionRecord.values.get("apiName") : null)
const [apiNameLabel, setApiNameLabel] = useState(scriptRevisionRecord ? scriptRevisionRecord.displayValues.get("apiName") : null)
const [apiVersion, setApiVersion] = useState(scriptRevisionRecord ? scriptRevisionRecord.values.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 [openTool, setOpenTool] = useState(null);
const [errorAlert, setErrorAlert] = useState("")
@@ -200,7 +265,6 @@ function ScriptEditor({title, scriptId, scriptRevisionRecord, closeCallback, tab
{
const formData = new FormData();
formData.append("scriptId", scriptId);
- formData.append("contents", updatedCode);
formData.append("commitMessage", overrideCommitMessage ?? commitMessage);
if(apiName)
@@ -213,6 +277,15 @@ function ScriptEditor({title, scriptId, scriptRevisionRecord, closeCallback, tab
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 //
//////////////////////////////////////////////////////////////////
@@ -249,10 +322,9 @@ function ScriptEditor({title, scriptId, scriptRevisionRecord, closeCallback, tab
closeCallback(null, "cancelled");
}
- const updateCode = (value: string, event: any) =>
+ const updateCode = (value: string, event: any, index: number) =>
{
- console.log("Updating code")
- setUpdatedCode(value);
+ fileContents[openEditorFileNames[index]] = value;
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 (
-
+
@@ -348,29 +446,67 @@ function ScriptEditor({title, scriptId, scriptRevisionRecord, closeCallback, tab
-
+
+ {openEditorFileNames.map((fileName, index) =>
+ {
+ return (
+
+
+
+
+
+
+ {
+ openEditorFileNames.length > 1 &&
+
+ closeEditorClicked(index)}>
+ close
+
+
+ }
+ {
+ index == openEditorFileNames.length - 1 &&
+
+
+ vertical_split
+
+
+ }
+
+
+ updateCode(value, event, index)}
+ width="100%"
+ height="calc(100% - 88px)"
+ value={fileContents[openEditorFileNames[index]]}
+ style={{border: "1px solid gray"}}
+ />
+
+ );
+ })}
+
{
openTool &&
{
- openTool == "test" &&
+ openTool == "test" &&
}
{
openTool == "docs" &&
diff --git a/src/qqq/components/scripts/ScriptTestForm.tsx b/src/qqq/components/scripts/ScriptTestForm.tsx
index 11b16da..a70fa4c 100644
--- a/src/qqq/components/scripts/ScriptTestForm.tsx
+++ b/src/qqq/components/scripts/ScriptTestForm.tsx
@@ -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 {QJobComplete} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobComplete";
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 Box from "@mui/material/Box";
import Button from "@mui/material/Button";
@@ -51,19 +52,21 @@ interface AssociatedScriptDefinition
export interface ScriptTestFormProps
{
scriptId: number;
- scriptDefinition: AssociatedScriptDefinition;
+ scriptType: QRecord;
tableName: string;
fieldName: string;
recordId: any;
- code: string;
+ fileContents: {[name: string]: string};
apiName: string;
apiVersion: string;
}
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 [testOutputValues, setTestOutputValues] = useState({} as any);
const [logLines, setLogLines] = useState([] as any[])
@@ -77,10 +80,46 @@ function ScriptTestForm({scriptId, scriptDefinition, tableName, fieldName, recor
if(firstRender)
{
- scriptDefinition.testInputFields.forEach((field: QFieldMetaData) =>
+ (async () =>
{
- testInputValues[field.name] = field.defaultValue ?? "";
- });
+ /////////////////////////////////////////////////////////////////////
+ // 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
+ {
+ testInputValues[field.name] = field.defaultValue ?? "";
+ });
+ })();
}
const buildFullExceptionMessage = (exception: any): string =>
@@ -91,9 +130,9 @@ function ScriptTestForm({scriptId, scriptDefinition, tableName, fieldName, recor
const testScript = () =>
{
const inputValues = new Map();
- if (scriptDefinition.testInputFields)
+ if (testInputFields)
{
- scriptDefinition.testInputFields.forEach((field: QFieldMetaData) =>
+ testInputFields.forEach((field: QFieldMetaData) =>
{
inputValues.set(field.name, testInputValues[field.name]);
});
@@ -108,6 +147,7 @@ function ScriptTestForm({scriptId, scriptDefinition, tableName, fieldName, recor
try
{
let output;
+ /*
if(tableName && recordId && fieldName)
{
/////////////////////////////////////////////////////////////////
@@ -115,15 +155,21 @@ function ScriptTestForm({scriptId, scriptDefinition, tableName, fieldName, recor
/////////////////////////////////////////////////////////////////
inputValues.set("apiName", apiName);
inputValues.set("apiVersion", apiVersion);
- output = await qController.testScript(tableName, recordId, fieldName, code, inputValues);
+ output = await qController.testScript(tableName, recordId, fieldName, "todo!", inputValues);
}
else
+ */
{
const formData = new FormData();
formData.append("scriptId", scriptId);
formData.append("apiName", apiName);
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())
{
@@ -195,7 +241,7 @@ function ScriptTestForm({scriptId, scriptDefinition, tableName, fieldName, recor
Test Input
{
- scriptDefinition.testInputFields && testInputValues && scriptDefinition.testInputFields.map((field: QFieldMetaData) =>
+ testInputFields && testInputValues && testInputFields.map((field: QFieldMetaData) =>
{
return (
}
{
- scriptDefinition.testOutputFields && scriptDefinition.testOutputFields.map((f: any) =>
+ testOutputFields && testOutputFields.map((f: any) =>
{
const field = new QFieldMetaData(f);
return (
-
+
{field.label}:
- {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")
+ }
);
diff --git a/src/qqq/components/widgets/misc/ScriptViewer.tsx b/src/qqq/components/widgets/misc/ScriptViewer.tsx
index 2f25223..afd01ee 100644
--- a/src/qqq/components/widgets/misc/ScriptViewer.tsx
+++ b/src/qqq/components/widgets/misc/ScriptViewer.tsx
@@ -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 {QFilterOrderBy} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterOrderBy";
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
+import {SelectChangeEvent} from "@mui/material";
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 FormControl from "@mui/material/FormControl/FormControl";
import Grid from "@mui/material/Grid";
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 MenuItem from "@mui/material/MenuItem";
import Modal from "@mui/material/Modal";
+import Select from "@mui/material/Select/Select";
import Snackbar from "@mui/material/Snackbar";
import Tab from "@mui/material/Tab";
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-javascript";
+import "ace-builds/src-noconflict/mode-velocity";
import "ace-builds/src-noconflict/mode-json";
import "ace-builds/src-noconflict/theme-github";
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 [scriptLogs, setScriptLogs] = useState({} as any);
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 [notFoundMessage, setNotFoundMessage] = useState(null);
const [selectedTab, setSelectedTab] = useState(0);
@@ -118,17 +125,32 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
const scriptRecord = await qController.get("script", scriptId);
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: [
- new QFieldMetaData({name: "recordPrimaryKeyList", label: "Record Primary Key List"})
- ], testOutputFields: []})
+ const filter = new QQueryFilter([new QFilterCriteria("scriptTypeId", QCriteriaOperator.EQUALS, [scriptRecord.values.get("scriptTypeId")])], [new QFilterOrderBy("id")])
+ scriptTypeFileSchemaList = await qController.query("scriptTypeFileSchema", filter);
+ }
+ 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])];
@@ -141,13 +163,7 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
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();
+ selectVersion(versions[0]);
}
}
catch (e)
@@ -174,8 +190,8 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
editorProps.tableName = associatedScriptTableName;
editorProps.fieldName = associatedScriptFieldName;
editorProps.recordId = associatedScriptRecordId;
- editorProps.scriptDefinition = testScriptDefinitionObject;
editorProps.scriptTypeRecord = scriptTypeRecord;
+ editorProps.scriptTypeFileSchemaList = scriptTypeFileSchemaList;
setEditorProps(editorProps);
};
@@ -223,8 +239,10 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
setCurrentVersionId(version.values.get("id"));
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(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)
{
return
@@ -344,7 +400,7 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
*/
return (
-
+
{
@@ -420,10 +476,22 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
{
- loadingSelectedVersion.isNotLoading() && selectedVersionRecord && selectedVersionRecord.values.get("contents") ? (
+ loadingSelectedVersion.isNotLoading() && selectedVersionRecord ? (
<>
+ {
+ availableFileNames && availableFileNames.length > 1 &&
+
+
+
+ }
>
) : null
@@ -473,11 +542,11 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
diff --git a/src/qqq/pages/records/query/RecordQuery.tsx b/src/qqq/pages/records/query/RecordQuery.tsx
index cb47647..63ef5ca 100644
--- a/src/qqq/pages/records/query/RecordQuery.tsx
+++ b/src/qqq/pages/records/query/RecordQuery.tsx
@@ -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)
{
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));
}
};
@@ -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);
handleFilterChange(models.filter);
- handleSortChange(models.sort);
+ handleSortChange(models.sort, models.filter);
localStorage.setItem(currentSavedFilterLocalStorageKey, selectedSavedFilterId.toString());
}
else
{
handleFilterChange({items: []} as GridFilterModel);
- handleSortChange([{field: tableMetaData.primaryKeyField, sort: "desc"}]);
+ handleSortChange([{field: tableMetaData.primaryKeyField, sort: "desc"}], {items: []} as GridFilterModel);
localStorage.removeItem(currentSavedFilterLocalStorageKey);
}
}
@@ -1692,7 +1701,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
menuItems.push();
}
- menuItems.push();
+ menuItems.push();
if (tableProcesses && tableProcesses.length)
{
@@ -1919,7 +1928,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
onColumnVisibilityModelChange={handleColumnVisibilityChange}
onColumnOrderChange={handleColumnOrderChange}
onSelectionModelChange={selectionChanged}
- onSortModelChange={handleSortChange}
+ onSortModelChange={handleSortChangeForDataGrid}
sortingOrder={["asc", "desc"]}
sortModel={columnSortModel}
getRowClassName={(params) => (params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd")}
diff --git a/src/qqq/pages/records/view/RecordView.tsx b/src/qqq/pages/records/view/RecordView.tsx
index 2903e5f..7a3e75e 100644
--- a/src/qqq/pages/records/view/RecordView.tsx
+++ b/src/qqq/pages/records/view/RecordView.tsx
@@ -329,10 +329,24 @@ function RecordView({table, launchProcess}: Props): JSX.Element
const metaData = await qController.loadMetaData();
setMetaData(metaData);
ValueUtils.qInstance = metaData;
+
+ ///////////////////////////////////////////////////
+ // load the processes to show in the action menu //
+ ///////////////////////////////////////////////////
const processesForTable = ProcessUtils.getProcessesForTable(metaData, tableName);
processesForTable.sort((a, b) => a.label.localeCompare(b.label));
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)
{
@@ -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);
+ function gotoCreate()
+ {
+ const path = `${pathParts.slice(0, -1).join("/")}/create`;
+ navigate(path);
+ }
+
+ const runRecordScriptProcess = metaData?.processes.get("runRecordScript");
+
const renderActionsMenu = (