/* * 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;