/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
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 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 BaseLayout from "layouts/BaseLayout";
import QContext from "QContext";
import CustomWidthTooltip from "qqq/components/CustomWidthTooltip/CustomWidthTooltip";
import AssociatedScriptEditor from "qqq/components/ScriptComponents/AssociatedScriptEditor";
import ScriptDocsForm from "qqq/components/ScriptComponents/ScriptDocsForm";
import ScriptLogsView from "qqq/components/ScriptComponents/ScriptLogsView";
import ScriptTestForm from "qqq/components/ScriptComponents/ScriptTestForm";
import TabPanel from "qqq/components/TabPanel/TabPanel";
import MDBox from "qqq/components/Temporary/MDBox";
import DeveloperModeUtils from "qqq/utils/DeveloperModeUtils";
import QClient from "qqq/utils/QClient";
import QValueUtils from "qqq/utils/QValueUtils";
import "ace-builds/src-noconflict/mode-java";
import "ace-builds/src-noconflict/mode-javascript";
import "ace-builds/src-noconflict/mode-json";
import "ace-builds/src-noconflict/theme-github";
import "ace-builds/src-noconflict/ext-language_tools";
const qController = QClient.getInstance();
// Declaring props types for ViewForm
interface Props
{
table?: QTableMetaData;
}
EntityDeveloperView.defaultProps =
{
table: null,
};
function EntityDeveloperView({table}: Props): JSX.Element
{
const {id} = useParams();
const tableName = table.name;
const [asyncLoadInited, setAsyncLoadInited] = useState(false);
const [tableMetaData, setTableMetaData] = useState(null);
const [record, setRecord] = useState(null as QRecord);
const [recordJSON, setRecordJSON] = useState("");
const [associatedScripts, setAssociatedScripts] = useState([] as any[]);
const [notFoundMessage, setNotFoundMessage] = useState(null);
const [selectedTabs, setSelectedTabs] = useState({} as any);
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);
const [, forceUpdate] = useReducer((x) => x + 1, 0);
if (!asyncLoadInited)
{
setAsyncLoadInited(true);
(async () =>
{
/////////////////////////////////////////////////////////////////////
// load the full table meta-data (the one we took in is a partial) //
/////////////////////////////////////////////////////////////////////
const tableMetaData = await qController.loadTableMetaData(tableName);
setTableMetaData(tableMetaData);
//////////////////////////////
// load top-level meta-data //
//////////////////////////////
const metaData = await qController.loadMetaData();
QValueUtils.qInstance = metaData;
/////////////////////
// load the record //
/////////////////////
let record: QRecord;
try
{
const developerModeData = await qController.getRecordDeveloperMode(tableName, id);
record = new QRecord(developerModeData.record);
console.log("Loaded record developer mode.");
setRecord(record);
setAssociatedScripts(developerModeData.associatedScripts);
const recordJSONObject = {} as any;
for (let key of record.values.keys())
{
recordJSONObject[key] = record.values.get(key);
}
setRecordJSON(JSON.stringify(recordJSONObject, null, 3));
}
catch (e)
{
if (e instanceof QException)
{
if ((e as QException).status === "404")
{
setNotFoundMessage(`${tableMetaData.label} ${id} could not be found.`);
return;
}
}
}
setPageHeader(record.recordLabel + " Developer Mode");
forceUpdate();
})();
}
const editScript = (fieldName: string, code: string, object: any) =>
{
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);
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
{
scriptRevisions ? <>> :
There are not any versions of this script.
}
{
scriptRevisions?.map((revision: any) => (
selectRevision(fieldName, revision.values.id)}>
{`${revision.values.sequenceNo}`}
{revision.values.id == currentScriptRevisionId && }
{revision.values.commitMessage}
}
secondary={
<>
{QValueUtils.formatDateTime(revision.values.createDate)}
{revision.values.author}
>
}
/>
settings
))
}
;
}
function getScriptLogs(revisionId: number)
{
const logs = scriptLogs[revisionId] as any[];
if (logs === null || logs === undefined)
{
return Loading...;
}
if (logs.length === 0)
{
return No logs available for this version.;
}
return ();
}
return (
{
notFoundMessage
?
{notFoundMessage}
:
{
alertText ? (
setAlertText(null)} anchorOrigin={{vertical: "top", horizontal: "center"}}>
setAlertText(null)}>
{alertText}
) : ("")
}
Record Raw Values as JSON
{
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 (
{field?.label} changeTab(newValue, fieldName)}
variant="standard"
>
Versions
{getRevisionsList(object.scriptRevisions, fieldName, currentScriptRevisionId)}
{
currentScriptRevisionId &&
{
currentScriptRevisionId === viewingRevisions[fieldName]
? (<>Current Version ({viewingSequenceNo})>)
: (<>Version {viewingSequenceNo}>)
}
}
{
code ? (
<>
>
) : null
}
Versions
{getRevisionsList(object.scriptRevisions, fieldName, currentScriptRevisionId)}
Script Logs (Version {viewingSequenceNo})
{getScriptLogs(viewingRevisions[fieldName])}
);
})
}
{
editingScript &&
closeEditingScript(event, reason)}>
}
}
);
}
export default EntityDeveloperView;