From 07d116d9bad3088a8c196bd4b9e341206c45e4f5 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Wed, 28 May 2025 16:30:15 -0500 Subject: [PATCH 01/22] Adding MaterialDashboardInstanceMetaData with processNamesToAddToAllQueryAndViewScreens - to remove hard-coded version of this which was scripts-menu only - opening up for run-workflows to be added to all tables. --- src/App.tsx | 64 +++++++--- .../MaterialDashboardInstanceMetaData.java | 113 ++++++++++++++++++ .../query/QueryScreenActionMenu.tsx | 90 +++++++++----- src/qqq/pages/records/view/RecordView.tsx | 55 +++++++-- 4 files changed, 269 insertions(+), 53 deletions(-) create mode 100644 src/main/java/com/kingsrook/qqq/frontend/materialdashboard/model/metadata/MaterialDashboardInstanceMetaData.java diff --git a/src/App.tsx b/src/App.tsx index ad58422..7eb9507 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -377,23 +377,57 @@ export default function App({authenticationMetaData}: Props) }); }); - const runRecordScriptProcess = metaData.processes.get("runRecordScript"); - if (runRecordScriptProcess) + const materialDashboardInstanceMetaData = metaData.supplementalInstanceMetaData?.get("materialDashboard"); + if (materialDashboardInstanceMetaData) { - const process = runRecordScriptProcess; - routeList.push({ - name: process.label, - key: process.name, - route: `${path}/${process.name}`, - component: , - }); + const processNamesToAddToAllQueryAndViewScreens = materialDashboardInstanceMetaData.processNamesToAddToAllQueryAndViewScreens; + if (processNamesToAddToAllQueryAndViewScreens) + { + for (let processName of processNamesToAddToAllQueryAndViewScreens) + { + const process = metaData.processes.get(processName); + if (process) + { + routeList.push({ + name: process.label, + key: process.name, + route: `${path}/${process.name}`, + component: , + }); - routeList.push({ - name: process.label, - key: `${app.name}/${process.name}`, - route: `${path}/:id/${process.name}`, - component: , - }); + routeList.push({ + name: process.label, + key: `${app.name}/${process.name}`, + route: `${path}/:id/${process.name}`, + component: , + }); + } + } + } + } + else + { + //////////////// + // deprecated // + //////////////// + const runRecordScriptProcess = metaData.processes.get("runRecordScript"); + if (runRecordScriptProcess) + { + const process = runRecordScriptProcess; + routeList.push({ + name: process.label, + key: process.name, + route: `${path}/${process.name}`, + component: , + }); + + routeList.push({ + name: process.label, + key: `${app.name}/${process.name}`, + route: `${path}/:id/${process.name}`, + component: , + }); + } } const reportsForTable = ProcessUtils.getReportsForTable(metaData, table.name, true); diff --git a/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/model/metadata/MaterialDashboardInstanceMetaData.java b/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/model/metadata/MaterialDashboardInstanceMetaData.java new file mode 100644 index 0000000..8c4906d --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/model/metadata/MaterialDashboardInstanceMetaData.java @@ -0,0 +1,113 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. 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 . + */ + +package com.kingsrook.qqq.frontend.materialdashboard.model.metadata; + + +import java.util.ArrayList; +import java.util.List; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.QSupplementalInstanceMetaData; + + +/******************************************************************************* + ** table-level meta-data for this module (handled as QSupplementalTableMetaData) + *******************************************************************************/ +public class MaterialDashboardInstanceMetaData implements QSupplementalInstanceMetaData +{ + public static final String TYPE = "materialDashboard"; + + private List processNamesToAddToAllQueryAndViewScreens = new ArrayList<>(); + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public String getName() + { + return (TYPE); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static MaterialDashboardInstanceMetaData ofOrWithNew(QInstance qInstance) + { + MaterialDashboardInstanceMetaData supplementalMetaData = (MaterialDashboardInstanceMetaData) qInstance.getSupplementalMetaData(TYPE); + if(supplementalMetaData == null) + { + supplementalMetaData = new MaterialDashboardInstanceMetaData(); + qInstance.withSupplementalMetaData(supplementalMetaData); + } + + return (supplementalMetaData); + } + + + + /******************************************************************************* + ** Getter for processNamesToAddToAllQueryAndViewScreens + *******************************************************************************/ + public List getProcessNamesToAddToAllQueryAndViewScreens() + { + return (this.processNamesToAddToAllQueryAndViewScreens); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public void addProcessNameToAddToAllQueryAndViewScreens(String processNamesToAddToAllQueryAndViewScreens) + { + if(this.processNamesToAddToAllQueryAndViewScreens == null) + { + this.processNamesToAddToAllQueryAndViewScreens = new ArrayList<>(); + } + this.processNamesToAddToAllQueryAndViewScreens.add(processNamesToAddToAllQueryAndViewScreens); + } + + + + /******************************************************************************* + ** Setter for processNamesToAddToAllQueryAndViewScreens + *******************************************************************************/ + public void setProcessNamesToAddToAllQueryAndViewScreens(List processNamesToAddToAllQueryAndViewScreens) + { + this.processNamesToAddToAllQueryAndViewScreens = processNamesToAddToAllQueryAndViewScreens; + } + + + + /******************************************************************************* + ** Fluent setter for processNamesToAddToAllQueryAndViewScreens + *******************************************************************************/ + public MaterialDashboardInstanceMetaData withProcessNamesToAddToAllQueryAndViewScreens(List processNamesToAddToAllQueryAndViewScreens) + { + this.processNamesToAddToAllQueryAndViewScreens = processNamesToAddToAllQueryAndViewScreens; + return (this); + } + +} diff --git a/src/qqq/components/query/QueryScreenActionMenu.tsx b/src/qqq/components/query/QueryScreenActionMenu.tsx index 73147a4..af1e5b7 100644 --- a/src/qqq/components/query/QueryScreenActionMenu.tsx +++ b/src/qqq/components/query/QueryScreenActionMenu.tsx @@ -29,9 +29,9 @@ import Icon from "@mui/material/Icon"; import ListItemIcon from "@mui/material/ListItemIcon"; import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; +import {QActionsMenuButton} from "qqq/components/buttons/DefaultButtons"; import React, {useState} from "react"; import {useNavigate} from "react-router-dom"; -import {QActionsMenuButton} from "qqq/components/buttons/DefaultButtons"; interface QueryScreenActionMenuProps { @@ -44,40 +44,35 @@ interface QueryScreenActionMenuProps processClicked: (process: QProcessMetaData) => void; } -QueryScreenActionMenu.defaultProps = { -}; +QueryScreenActionMenu.defaultProps = {}; export default function QueryScreenActionMenu({metaData, tableMetaData, tableProcesses, bulkLoadClicked, bulkEditClicked, bulkDeleteClicked, processClicked}: QueryScreenActionMenuProps): JSX.Element { - const [anchorElement, setAnchorElement] = useState(null) + const [anchorElement, setAnchorElement] = useState(null); const navigate = useNavigate(); const openActionsMenu = (event: any) => { setAnchorElement(event.currentTarget); - } + }; const closeActionsMenu = () => { setAnchorElement(null); - } - - const pushDividerIfNeeded = (menuItems: JSX.Element[]) => - { - if (menuItems.length > 0) - { - menuItems.push(); - } }; const runSomething = (handler: () => void) => { closeActionsMenu(); handler(); - } + }; const menuItems: JSX.Element[] = []; + + ////////////////////////////////////////////////////// + // start with bulk actions, if user has permissions // + ////////////////////////////////////////////////////// if (tableMetaData.capabilities.has(Capability.TABLE_INSERT) && tableMetaData.insertPermission) { menuItems.push( runSomething(bulkLoadClicked)}>library_addBulk Load); @@ -91,19 +86,7 @@ export default function QueryScreenActionMenu({metaData, tableMetaData, tablePro menuItems.push( runSomething(bulkDeleteClicked)}>deleteBulk Delete); } - const runRecordScriptProcess = metaData?.processes.get("runRecordScript"); - if (runRecordScriptProcess) - { - const process = runRecordScriptProcess; - menuItems.push( runSomething(() => processClicked(process))}>{process.iconName ?? "arrow_forward"}{process.label}); - } - - menuItems.push( navigate(`${metaData.getTablePathByName(tableMetaData.name)}/dev`)}>codeDeveloper Mode); - - if (tableProcesses && tableProcesses.length) - { - pushDividerIfNeeded(menuItems); - } + menuItems.push(); tableProcesses.sort((a, b) => a.label.localeCompare(b.label)); tableProcesses.map((process) => @@ -111,11 +94,62 @@ export default function QueryScreenActionMenu({metaData, tableMetaData, tablePro menuItems.push( runSomething(() => processClicked(process))}>{process.iconName ?? "arrow_forward"}{process.label}); }); + menuItems.push(); + + //////////////////////////////////////////// + // add processes that apply to all tables // + //////////////////////////////////////////// + const materialDashboardInstanceMetaData = metaData.supplementalInstanceMetaData?.get("materialDashboard"); + if (materialDashboardInstanceMetaData) + { + const processNamesToAddToAllQueryAndViewScreens = materialDashboardInstanceMetaData.processNamesToAddToAllQueryAndViewScreens; + if (processNamesToAddToAllQueryAndViewScreens) + { + for (let processName of processNamesToAddToAllQueryAndViewScreens) + { + const process = metaData?.processes.get(processName); + if (process) + { + menuItems.push( runSomething(() => processClicked(process))}>{process.iconName ?? "arrow_forward"}{process.label}); + } + } + } + } + else + { + ////////////////////////////////////// + // deprecated in favor of the above // + ////////////////////////////////////// + const runRecordScriptProcess = metaData?.processes.get("runRecordScript"); + if (runRecordScriptProcess) + { + const process = runRecordScriptProcess; + menuItems.push( runSomething(() => processClicked(process))}>{process.iconName ?? "arrow_forward"}{process.label}); + } + } + + //////////////////////////////////////// + // todo - any conditions around this? // + //////////////////////////////////////// + menuItems.push( navigate(`${metaData.getTablePathByName(tableMetaData.name)}/dev`)}>codeDeveloper Mode); + if (menuItems.length === 0) { menuItems.push(blockNo actions available); } + //////////////////////////////////////////////////////////////////////////////// + // remove any duplicated dividers, and any dividers in the first or last slot // + //////////////////////////////////////////////////////////////////////////////// + for (let i = 0; i < menuItems.length; i++) + { + if (menuItems[i].type == Divider && (i == 0 || (i > 0 && menuItems[i - 1].type == Divider) || i == menuItems.length - 1)) + { + menuItems.splice(i, 1); + i--; + } + } + return ( <> @@ -130,5 +164,5 @@ export default function QueryScreenActionMenu({metaData, tableMetaData, tablePro {menuItems} - ) + ); } diff --git a/src/qqq/pages/records/view/RecordView.tsx b/src/qqq/pages/records/view/RecordView.tsx index 3aeec37..f52e9de 100644 --- a/src/qqq/pages/records/view/RecordView.tsx +++ b/src/qqq/pages/records/view/RecordView.tsx @@ -440,6 +440,34 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX. }; + /*************************************************************************** + ** + ***************************************************************************/ + function getGenericProcesses(metaData: QInstance) + { + const genericProcesses: QProcessMetaData[] = []; + const materialDashboardInstanceMetaData = metaData?.supplementalInstanceMetaData?.get("materialDashboard"); + if (materialDashboardInstanceMetaData) + { + const processNamesToAddToAllQueryAndViewScreens = materialDashboardInstanceMetaData.processNamesToAddToAllQueryAndViewScreens; + if (processNamesToAddToAllQueryAndViewScreens) + { + for (let processName of processNamesToAddToAllQueryAndViewScreens) + { + genericProcesses.push(metaData?.processes?.get(processName)); + } + } + } + else + { + //////////////// + // deprecated // + //////////////// + genericProcesses.push(metaData?.processes.get("runRecordScript")); + } + return genericProcesses; + } + if (!asyncLoadInited) { setAsyncLoadInited(true); @@ -472,11 +500,16 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX. // 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) + const genericProcesses = getGenericProcesses(metaData); + + for (let genericProcess of genericProcesses) { - allTableProcesses.unshift(runRecordScriptProcess); + if (genericProcess) + { + allTableProcesses.unshift(genericProcess); + } } + setAllTableProcesses(allTableProcesses); if (launchingProcess) @@ -726,7 +759,6 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX. let hasEditOrDelete = (table.capabilities.has(Capability.TABLE_UPDATE) && table.editPermission) || (table.capabilities.has(Capability.TABLE_DELETE) && table.deletePermission); - const runRecordScriptProcess = metaData?.processes.get("runRecordScript"); const renderActionsMenu = ( 0 || hasEditOrDelete) && } { - runRecordScriptProcess && - processClicked(runRecordScriptProcess)}> - {runRecordScriptProcess.iconName ?? "arrow_forward"} - {runRecordScriptProcess.label} - + getGenericProcesses(metaData).map((process) => + ( + process && + processClicked(process)}> + {process.iconName ?? "arrow_forward"} + {process.label} + + )) } navigate("dev")}> code @@ -969,7 +1004,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX. { notFoundMessage ? - {notFoundMessage} + warning}>{notFoundMessage} : { From 96bdcf187413fcffa363b54bafacfb07fa7216be Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 29 May 2025 08:58:58 -0500 Subject: [PATCH 02/22] Add QControllerV1 usage and setGotAuthenticationInAllControllers method to replace calling it on each controller instance --- .../useAnonymousAuthenticationModule.tsx | 2 +- .../auth0/useAuth0AuthenticationModule.tsx | 3 ++- .../oauth2/useOAuth2AuthenticationModule.tsx | 4 ++-- src/qqq/utils/qqq/Client.ts | 18 ++++++++++++++++++ 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/qqq/authorization/anonymous/useAnonymousAuthenticationModule.tsx b/src/qqq/authorization/anonymous/useAnonymousAuthenticationModule.tsx index 9511ece..61f2f50 100644 --- a/src/qqq/authorization/anonymous/useAnonymousAuthenticationModule.tsx +++ b/src/qqq/authorization/anonymous/useAnonymousAuthenticationModule.tsx @@ -48,7 +48,7 @@ export default function useAnonymousAuthenticationModule({setIsFullyAuthenticate { console.log("Generating random token..."); setIsFullyAuthenticated(true); - qController.setGotAuthentication(); + Client.setGotAuthenticationInAllControllers(); setCookie(SESSION_UUID_COOKIE_NAME, Md5.hashStr(`${new Date()}`), {path: "/"}); console.log("Token generation complete."); }; diff --git a/src/qqq/authorization/auth0/useAuth0AuthenticationModule.tsx b/src/qqq/authorization/auth0/useAuth0AuthenticationModule.tsx index aa1c256..ac2f5af 100644 --- a/src/qqq/authorization/auth0/useAuth0AuthenticationModule.tsx +++ b/src/qqq/authorization/auth0/useAuth0AuthenticationModule.tsx @@ -30,6 +30,7 @@ import {useCookies} from "react-cookie"; import {useNavigate, useSearchParams} from "react-router-dom"; const qController = Client.getInstance(); +const qControllerV1 = Client.getInstanceV1(); interface Props { @@ -131,7 +132,7 @@ export default function useAuth0AuthenticationModule({setIsFullyAuthenticated, s } setIsFullyAuthenticated(true); - qController.setGotAuthentication(); + Client.setGotAuthenticationInAllControllers(); setLoggedInUser(auth0User); console.log("Token load complete."); diff --git a/src/qqq/authorization/oauth2/useOAuth2AuthenticationModule.tsx b/src/qqq/authorization/oauth2/useOAuth2AuthenticationModule.tsx index 0bd58c7..c6e15e6 100644 --- a/src/qqq/authorization/oauth2/useOAuth2AuthenticationModule.tsx +++ b/src/qqq/authorization/oauth2/useOAuth2AuthenticationModule.tsx @@ -80,7 +80,7 @@ export default function useOAuth2AuthenticationModule({setIsFullyAuthenticated, console.log(`we have new session UUID: ${newSessionUuid}`); setIsFullyAuthenticated(true); - qController.setGotAuthentication(); + Client.setGotAuthenticationInAllControllers(); setLoggedInUser(values?.user); console.log("Token load complete."); @@ -109,7 +109,7 @@ export default function useOAuth2AuthenticationModule({setIsFullyAuthenticated, const {values} = await qController.manageSession(null, sessionUuid, null); setIsFullyAuthenticated(true); - qController.setGotAuthentication(); + Client.setGotAuthenticationInAllControllers(); setLoggedInUser(values?.user); console.log("Token load complete."); diff --git a/src/qqq/utils/qqq/Client.ts b/src/qqq/utils/qqq/Client.ts index 99f9d29..68fe98d 100644 --- a/src/qqq/utils/qqq/Client.ts +++ b/src/qqq/utils/qqq/Client.ts @@ -20,6 +20,7 @@ */ import {QController} from "@kingsrook/qqq-frontend-core/lib/controllers/QController"; +import {QControllerV1} from "@kingsrook/qqq-frontend-core/lib/controllers/QControllerV1"; import {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException"; /******************************************************************************* @@ -29,6 +30,7 @@ import {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException class Client { private static qController: QController; + private static qControllerV1: QControllerV1; private static unauthorizedCallback: () => void; private static handleException(exception: QException) @@ -54,6 +56,22 @@ class Client return this.qController; } + public static getInstanceV1(path: string = "/qqq/v1") + { + if (this.qControllerV1 == null) + { + this.qControllerV1 = new QControllerV1(path, this.handleException); + } + + return this.qControllerV1; + } + + public static setGotAuthenticationInAllControllers() + { + Client.getInstance().setGotAuthentication(); + Client.getInstanceV1().setGotAuthentication(); + } + static setUnauthorizedCallback(unauthorizedCallback: () => void) { Client.unauthorizedCallback = unauthorizedCallback; From cb36f59090d05571695da00a519160724a8cf678 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 29 May 2025 10:26:36 -0500 Subject: [PATCH 03/22] Add java backend for field-level form adjusters --- .../formadjuster/FormAdjusterInput.java | 164 ++++++++++++ .../formadjuster/FormAdjusterInterface.java | 39 +++ .../formadjuster/FormAdjusterOutput.java | 165 ++++++++++++ .../formadjuster/FormAdjusterRegistry.java | 144 +++++++++++ .../formadjuster/RunFormAdjusterProcess.java | 120 +++++++++ .../MaterialDashboardFieldMetaData.java | 244 ++++++++++++++++++ 6 files changed, 876 insertions(+) create mode 100644 src/main/java/com/kingsrook/qqq/frontend/materialdashboard/actions/formadjuster/FormAdjusterInput.java create mode 100644 src/main/java/com/kingsrook/qqq/frontend/materialdashboard/actions/formadjuster/FormAdjusterInterface.java create mode 100644 src/main/java/com/kingsrook/qqq/frontend/materialdashboard/actions/formadjuster/FormAdjusterOutput.java create mode 100644 src/main/java/com/kingsrook/qqq/frontend/materialdashboard/actions/formadjuster/FormAdjusterRegistry.java create mode 100644 src/main/java/com/kingsrook/qqq/frontend/materialdashboard/actions/formadjuster/RunFormAdjusterProcess.java create mode 100644 src/main/java/com/kingsrook/qqq/frontend/materialdashboard/model/metadata/MaterialDashboardFieldMetaData.java diff --git a/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/actions/formadjuster/FormAdjusterInput.java b/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/actions/formadjuster/FormAdjusterInput.java new file mode 100644 index 0000000..9c0f600 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/actions/formadjuster/FormAdjusterInput.java @@ -0,0 +1,164 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2025. 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 . + */ + +package com.kingsrook.qqq.frontend.materialdashboard.actions.formadjuster; + + +import java.io.Serializable; +import java.util.Map; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class FormAdjusterInput +{ + private String event; + private String fieldName; + + private Serializable newValue; + private Map allValues; + + + + /******************************************************************************* + ** Getter for event + *******************************************************************************/ + public String getEvent() + { + return (this.event); + } + + + + /******************************************************************************* + ** Setter for event + *******************************************************************************/ + public void setEvent(String event) + { + this.event = event; + } + + + + /******************************************************************************* + ** Fluent setter for event + *******************************************************************************/ + public FormAdjusterInput withEvent(String event) + { + this.event = event; + return (this); + } + + + + /******************************************************************************* + ** Getter for fieldName + *******************************************************************************/ + public String getFieldName() + { + return (this.fieldName); + } + + + + /******************************************************************************* + ** Setter for fieldName + *******************************************************************************/ + public void setFieldName(String fieldName) + { + this.fieldName = fieldName; + } + + + + /******************************************************************************* + ** Fluent setter for fieldName + *******************************************************************************/ + public FormAdjusterInput withFieldName(String fieldName) + { + this.fieldName = fieldName; + return (this); + } + + + + /******************************************************************************* + ** Getter for newValue + *******************************************************************************/ + public Serializable getNewValue() + { + return (this.newValue); + } + + + + /******************************************************************************* + ** Setter for newValue + *******************************************************************************/ + public void setNewValue(Serializable newValue) + { + this.newValue = newValue; + } + + + + /******************************************************************************* + ** Fluent setter for newValue + *******************************************************************************/ + public FormAdjusterInput withNewValue(Serializable newValue) + { + this.newValue = newValue; + return (this); + } + + + + /******************************************************************************* + ** Getter for allValues + *******************************************************************************/ + public Map getAllValues() + { + return (this.allValues); + } + + + + /******************************************************************************* + ** Setter for allValues + *******************************************************************************/ + public void setAllValues(Map allValues) + { + this.allValues = allValues; + } + + + + /******************************************************************************* + ** Fluent setter for allValues + *******************************************************************************/ + public FormAdjusterInput withAllValues(Map allValues) + { + this.allValues = allValues; + return (this); + } + +} diff --git a/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/actions/formadjuster/FormAdjusterInterface.java b/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/actions/formadjuster/FormAdjusterInterface.java new file mode 100644 index 0000000..f3210bb --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/actions/formadjuster/FormAdjusterInterface.java @@ -0,0 +1,39 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2025. 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 . + */ + +package com.kingsrook.qqq.frontend.materialdashboard.actions.formadjuster; + + +import com.kingsrook.qqq.backend.core.exceptions.QException; + + +/******************************************************************************* + ** interface to be implemented by application-specific form-adjusters + *******************************************************************************/ +public interface FormAdjusterInterface +{ + + /*************************************************************************** + * + ***************************************************************************/ + FormAdjusterOutput execute(FormAdjusterInput input) throws QException; + +} diff --git a/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/actions/formadjuster/FormAdjusterOutput.java b/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/actions/formadjuster/FormAdjusterOutput.java new file mode 100644 index 0000000..30fbe48 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/actions/formadjuster/FormAdjusterOutput.java @@ -0,0 +1,165 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2025. 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 . + */ + +package com.kingsrook.qqq.frontend.materialdashboard.actions.formadjuster; + + +import java.io.Serializable; +import java.util.Map; +import java.util.Set; +import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendFieldMetaData; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class FormAdjusterOutput +{ + private Map updatedFieldMetaData = null; + private Map updatedFieldValues = null; + private Map updatedFieldDisplayValues = null; + private Set fieldsToClear = null; + + + + /******************************************************************************* + ** Getter for updatedFieldValues + *******************************************************************************/ + public Map getUpdatedFieldValues() + { + return (this.updatedFieldValues); + } + + + + /******************************************************************************* + ** Setter for updatedFieldValues + *******************************************************************************/ + public void setUpdatedFieldValues(Map updatedFieldValues) + { + this.updatedFieldValues = updatedFieldValues; + } + + + + /******************************************************************************* + ** Fluent setter for updatedFieldValues + *******************************************************************************/ + public FormAdjusterOutput withUpdatedFieldValues(Map updatedFieldValues) + { + this.updatedFieldValues = updatedFieldValues; + return (this); + } + + + + /******************************************************************************* + ** Getter for fieldsToClear + *******************************************************************************/ + public Set getFieldsToClear() + { + return (this.fieldsToClear); + } + + + + /******************************************************************************* + ** Setter for fieldsToClear + *******************************************************************************/ + public void setFieldsToClear(Set fieldsToClear) + { + this.fieldsToClear = fieldsToClear; + } + + + + /******************************************************************************* + ** Fluent setter for fieldsToClear + *******************************************************************************/ + public FormAdjusterOutput withFieldsToClear(Set fieldsToClear) + { + this.fieldsToClear = fieldsToClear; + return (this); + } + + + + /******************************************************************************* + ** Getter for updatedFieldMetaData + *******************************************************************************/ + public Map getUpdatedFieldMetaData() + { + return (this.updatedFieldMetaData); + } + + + + /******************************************************************************* + ** Setter for updatedFieldMetaData + *******************************************************************************/ + public void setUpdatedFieldMetaData(Map updatedFieldMetaData) + { + this.updatedFieldMetaData = updatedFieldMetaData; + } + + + + /******************************************************************************* + ** Fluent setter for updatedFieldMetaData + *******************************************************************************/ + public FormAdjusterOutput withUpdatedFieldMetaData(Map updatedFieldMetaData) + { + this.updatedFieldMetaData = updatedFieldMetaData; + return (this); + } + + + /******************************************************************************* + ** Getter for updatedFieldDisplayValues + *******************************************************************************/ + public Map getUpdatedFieldDisplayValues() + { + return (this.updatedFieldDisplayValues); + } + + + + /******************************************************************************* + ** Setter for updatedFieldDisplayValues + *******************************************************************************/ + public void setUpdatedFieldDisplayValues(Map updatedFieldDisplayValues) + { + this.updatedFieldDisplayValues = updatedFieldDisplayValues; + } + + + + /******************************************************************************* + ** Fluent setter for updatedFieldDisplayValues + *******************************************************************************/ + public FormAdjusterOutput withUpdatedFieldDisplayValues(Map updatedFieldDisplayValues) + { + this.updatedFieldDisplayValues = updatedFieldDisplayValues; + return (this); + } + + +} diff --git a/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/actions/formadjuster/FormAdjusterRegistry.java b/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/actions/formadjuster/FormAdjusterRegistry.java new file mode 100644 index 0000000..91b0394 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/actions/formadjuster/FormAdjusterRegistry.java @@ -0,0 +1,144 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2025. 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 . + */ + +package com.kingsrook.qqq.frontend.materialdashboard.actions.formadjuster; + + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.logging.QLogger; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; +import com.kingsrook.qqq.backend.javalin.QJavalinMetaData; +import com.kingsrook.qqq.frontend.materialdashboard.model.metadata.MaterialDashboardFieldMetaData; +import com.kingsrook.qqq.middleware.javalin.metadata.JavalinRouteProviderMetaData; + + +/******************************************************************************* + ** Class that stores code-references for the application's defined fromAdjusters + ** This class also, when registering its first formAdjuster, adds the route to + ** the javalin instance to service form-adjuster calls from the frontend. + *******************************************************************************/ +public class FormAdjusterRegistry +{ + private static final QLogger LOG = QLogger.getLogger(FormAdjusterRegistry.class); + + private static boolean didRegisterRouteProvider = false; + private static QInstance lastRegisteredQInstance = null; + + private static Map onChangeAdjusters = new HashMap<>(); + private static Map onLoadAdjusters = new HashMap<>(); + + + + /*************************************************************************** + ** + ***************************************************************************/ + public static void registerFormAdjusters(QInstance qInstance, MaterialDashboardFieldMetaData materialDashboardFieldMetaData) throws QException + { + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // support hot-swaps, by checking if the input qInstance is different from one we previously registered for // + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + if(didRegisterRouteProvider && lastRegisteredQInstance != qInstance) + { + didRegisterRouteProvider = false; + onChangeAdjusters.clear(); + onLoadAdjusters.clear(); + } + + //////////////////////////////////////////////////////////////////////////////// + // if we need to register the javalin router, do so (only once per qInstance) // + //////////////////////////////////////////////////////////////////////////////// + if(!didRegisterRouteProvider) + { + QJavalinMetaData javalinMetaData = QJavalinMetaData.ofOrWithNew(qInstance); + javalinMetaData.withRouteProvider(new JavalinRouteProviderMetaData() + .withHostedPath("/material-dashboard-backend/form-adjuster/{identifier}/{event}") + .withMethods(List.of("POST")) + .withProcessName(RunFormAdjusterProcess.NAME) + ); + + qInstance.add(new RunFormAdjusterProcess().produce(qInstance)); + + didRegisterRouteProvider = true; + lastRegisteredQInstance = qInstance; + } + + //////////////////////////////////////////////////////////////// + // add the code-references to the map of registered adjusters // + //////////////////////////////////////////////////////////////// + String identifier = materialDashboardFieldMetaData.getFormAdjusterIdentifier(); + + QCodeReference onChangeCode = materialDashboardFieldMetaData.getOnChangeFormAdjuster(); + if(onChangeCode != null) + { + if(onChangeAdjusters.containsKey(identifier)) + { + LOG.warn("Attempt to register more than one onChangeFormAdjuster with identifier: " + identifier); + } + onChangeAdjusters.put(identifier, onChangeCode); + } + + QCodeReference onLoadCode = materialDashboardFieldMetaData.getOnLoadFormAdjuster(); + if(onLoadCode != null) + { + if(onLoadAdjusters.containsKey(identifier)) + { + LOG.warn("Attempt to register more than one onLoadFormAdjuster with identifier: " + identifier); + } + onLoadAdjusters.put(identifier, onLoadCode); + } + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + static FormAdjusterInterface getOnChangeAdjuster(String identifier) + { + QCodeReference codeReference = onChangeAdjusters.get(identifier); + if(codeReference != null) + { + return QCodeLoader.getAdHoc(FormAdjusterInterface.class, codeReference); + } + return (null); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + static FormAdjusterInterface getOnLoadAdjuster(String identifier) + { + QCodeReference codeReference = onLoadAdjusters.get(identifier); + if(codeReference != null) + { + return QCodeLoader.getAdHoc(FormAdjusterInterface.class, codeReference); + } + return (null); + } + +} diff --git a/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/actions/formadjuster/RunFormAdjusterProcess.java b/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/actions/formadjuster/RunFormAdjusterProcess.java new file mode 100644 index 0000000..3f2f83d --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/actions/formadjuster/RunFormAdjusterProcess.java @@ -0,0 +1,120 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2025. 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 . + */ + +package com.kingsrook.qqq.frontend.materialdashboard.actions.formadjuster; + + +import java.io.Serializable; +import java.util.Collections; +import java.util.Map; +import com.fasterxml.jackson.core.type.TypeReference; +import com.kingsrook.qqq.backend.core.actions.processes.BackendStep; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.logging.QLogger; +import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput; +import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; +import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; +import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; +import com.kingsrook.qqq.backend.core.utils.JsonUtils; +import com.kingsrook.qqq.backend.core.utils.StringUtils; +import com.kingsrook.qqq.middleware.javalin.routeproviders.ProcessBasedRouterPayload; +import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair; + + +/******************************************************************************* + ** process that looks up a form adjuster from the registry, and then runs it + *******************************************************************************/ +public class RunFormAdjusterProcess implements BackendStep, MetaDataProducerInterface +{ + public static final String NAME = "MaterialDashboardRunFormAdjusterProcess"; + + private static final QLogger LOG = QLogger.getLogger(RunFormAdjusterProcess.class); + + public static final String EVENT_ON_LOAD = "onLoad"; + public static final String EVENT_ON_CHANGE = "onChange"; + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public QProcessMetaData produce(QInstance qInstance) throws QException + { + return new QProcessMetaData() + .withName(NAME) + .withStep(new QBackendStepMetaData() + .withName("execute") + .withCode(new QCodeReference(getClass()))); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException + { + ProcessBasedRouterPayload payload = runBackendStepInput.getProcessPayload(ProcessBasedRouterPayload.class); + + String identifier = payload.getPathParams().get("identifier"); + String event = payload.getPathParams().get("event"); + + try + { + FormAdjusterInterface formAdjuster = switch(event) + { + case EVENT_ON_CHANGE -> FormAdjusterRegistry.getOnChangeAdjuster(identifier); + case EVENT_ON_LOAD -> FormAdjusterRegistry.getOnLoadAdjuster(identifier); + default -> throw new QException("Unknown event type: " + event); + }; + + if(formAdjuster == null) + { + throw new QException("No form adjuster found for identifier: " + identifier + " and event: " + event); + } + + FormAdjusterInput input = new FormAdjusterInput(); + input.setEvent(event); + input.setFieldName(payload.getFormParam("fieldName")); + input.setNewValue(payload.getFormParam("newValue")); + + String allValuesJson = payload.getFormParam("allValues"); + Map allValues = StringUtils.hasContent(allValuesJson) ? JsonUtils.toObject(allValuesJson, new TypeReference<>() {}) : Collections.emptyMap(); + input.setAllValues(allValues); + + FormAdjusterOutput output = formAdjuster.execute(input); + + payload.setResponseString(JsonUtils.toJson(output)); + runBackendStepOutput.setProcessPayload(payload); + } + catch(Exception e) + { + LOG.warn("Error running form adjuster process", e, logPair("identifier", identifier), logPair("event", event)); + throw new QException("Error running form adjuster process: " + e.getMessage(), e); + } + } + +} diff --git a/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/model/metadata/MaterialDashboardFieldMetaData.java b/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/model/metadata/MaterialDashboardFieldMetaData.java new file mode 100644 index 0000000..09f19ac --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/model/metadata/MaterialDashboardFieldMetaData.java @@ -0,0 +1,244 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2025. 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 . + */ + +package com.kingsrook.qqq.frontend.materialdashboard.model.metadata; + + +import java.util.Set; +import com.kingsrook.qqq.backend.core.instances.QInstanceValidator; +import com.kingsrook.qqq.backend.core.logging.QLogger; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; +import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.fields.QSupplementalFieldMetaData; +import com.kingsrook.qqq.backend.core.utils.StringUtils; +import com.kingsrook.qqq.frontend.materialdashboard.actions.formadjuster.FormAdjusterInterface; +import com.kingsrook.qqq.frontend.materialdashboard.actions.formadjuster.FormAdjusterRegistry; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class MaterialDashboardFieldMetaData extends QSupplementalFieldMetaData +{ + public static final String TYPE = "materialDashboard"; + + private static final QLogger LOG = QLogger.getLogger(MaterialDashboardFieldMetaData.class); + + private String formAdjusterIdentifier = null; + private QCodeReference onChangeFormAdjuster = null; + private QCodeReference onLoadFormAdjuster = null; + private Set fieldsToDisableWhileRunningAdjusters = null; + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public boolean includeInFrontendMetaData() + { + return (true); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public String getType() + { + return TYPE; + } + + + + /******************************************************************************* + ** Getter for onChangeFormAdjuster + *******************************************************************************/ + public QCodeReference getOnChangeFormAdjuster() + { + return (this.onChangeFormAdjuster); + } + + + + /******************************************************************************* + ** Setter for onChangeFormAdjuster + *******************************************************************************/ + public void setOnChangeFormAdjuster(QCodeReference onChangeFormAdjuster) + { + this.onChangeFormAdjuster = onChangeFormAdjuster; + } + + + + /******************************************************************************* + ** Fluent setter for onChangeFormAdjuster + *******************************************************************************/ + public MaterialDashboardFieldMetaData withOnChangeFormAdjuster(QCodeReference onChangeFormAdjuster) + { + this.onChangeFormAdjuster = onChangeFormAdjuster; + return (this); + } + + + + /******************************************************************************* + ** Getter for onLoadFormAdjuster + *******************************************************************************/ + public QCodeReference getOnLoadFormAdjuster() + { + return (this.onLoadFormAdjuster); + } + + + + /******************************************************************************* + ** Setter for onLoadFormAdjuster + *******************************************************************************/ + public void setOnLoadFormAdjuster(QCodeReference onLoadFormAdjuster) + { + this.onLoadFormAdjuster = onLoadFormAdjuster; + } + + + + /******************************************************************************* + ** Fluent setter for onLoadFormAdjuster + *******************************************************************************/ + public MaterialDashboardFieldMetaData withOnLoadFormAdjuster(QCodeReference onLoadFormAdjuster) + { + this.onLoadFormAdjuster = onLoadFormAdjuster; + return (this); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public void enrich(QInstance qInstance, QFieldMetaData fieldMetaData) + { + try + { + FormAdjusterRegistry.registerFormAdjusters(qInstance, this); + } + catch(Exception e) + { + LOG.warn("Error enriching MaterialDashboardFieldMetaData", e); + } + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public void validate(QInstance qInstance, QFieldMetaData fieldMetaData, QInstanceValidator qInstanceValidator) + { + String prefix = "MaterialDashboardFieldMetaData for field [" + fieldMetaData.getName() + "]"; + + boolean needsFormAdjusterIdentifer = false; + if(onChangeFormAdjuster != null) + { + needsFormAdjusterIdentifer = true; + qInstanceValidator.validateSimpleCodeReference(prefix + ", onChangeFormAdjuster", onChangeFormAdjuster, FormAdjusterInterface.class); + } + + if(onLoadFormAdjuster != null) + { + needsFormAdjusterIdentifer = true; + qInstanceValidator.validateSimpleCodeReference(prefix + ", onLoadFormAdjuster", onLoadFormAdjuster, FormAdjusterInterface.class); + } + + if(needsFormAdjusterIdentifer) + { + qInstanceValidator.assertCondition(StringUtils.hasContent(formAdjusterIdentifier), prefix + ", formAdjusterIdentifier is required if using any FormAdjusters"); + } + } + + + + /******************************************************************************* + ** Getter for formAdjusterIdentifier + *******************************************************************************/ + public String getFormAdjusterIdentifier() + { + return (this.formAdjusterIdentifier); + } + + + + /******************************************************************************* + ** Setter for formAdjusterIdentifier + *******************************************************************************/ + public void setFormAdjusterIdentifier(String formAdjusterIdentifier) + { + this.formAdjusterIdentifier = formAdjusterIdentifier; + } + + + + /******************************************************************************* + ** Fluent setter for formAdjusterIdentifier + *******************************************************************************/ + public MaterialDashboardFieldMetaData withFormAdjusterIdentifier(String formAdjusterIdentifier) + { + this.formAdjusterIdentifier = formAdjusterIdentifier; + return (this); + } + + + /******************************************************************************* + ** Getter for fieldsToDisableWhileRunningAdjusters + *******************************************************************************/ + public Set getFieldsToDisableWhileRunningAdjusters() + { + return (this.fieldsToDisableWhileRunningAdjusters); + } + + + + /******************************************************************************* + ** Setter for fieldsToDisableWhileRunningAdjusters + *******************************************************************************/ + public void setFieldsToDisableWhileRunningAdjusters(Set fieldsToDisableWhileRunningAdjusters) + { + this.fieldsToDisableWhileRunningAdjusters = fieldsToDisableWhileRunningAdjusters; + } + + + + /******************************************************************************* + ** Fluent setter for fieldsToDisableWhileRunningAdjusters + *******************************************************************************/ + public MaterialDashboardFieldMetaData withFieldsToDisableWhileRunningAdjusters(Set fieldsToDisableWhileRunningAdjusters) + { + this.fieldsToDisableWhileRunningAdjusters = fieldsToDisableWhileRunningAdjusters; + return (this); + } + + +} From 78c788812af2339afd15f5374bbaf4e4cf000bed Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 29 May 2025 11:28:50 -0500 Subject: [PATCH 04/22] Add support for onLoad and onChange form adjusters, plus isHidden attribute on fields --- src/qqq/components/forms/DynamicForm.tsx | 223 ++++++++++++++++++++++- 1 file changed, 218 insertions(+), 5 deletions(-) diff --git a/src/qqq/components/forms/DynamicForm.tsx b/src/qqq/components/forms/DynamicForm.tsx index 7657abf..33a2724 100644 --- a/src/qqq/components/forms/DynamicForm.tsx +++ b/src/qqq/components/forms/DynamicForm.tsx @@ -20,15 +20,21 @@ */ import {AdornmentType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/AdornmentType"; +import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; import Box from "@mui/material/Box"; import Grid from "@mui/material/Grid"; +import {useFormikContext} from "formik"; import QDynamicFormField from "qqq/components/forms/DynamicFormField"; +import DynamicFormUtils from "qqq/components/forms/DynamicFormUtils"; import DynamicSelect from "qqq/components/forms/DynamicSelect"; import FileInputField from "qqq/components/forms/FileInputField"; import MDTypography from "qqq/components/legacy/MDTypography"; import HelpContent from "qqq/components/misc/HelpContent"; -import React from "react"; +import Client from "qqq/utils/qqq/Client"; +import React, {useEffect, useState} from "react"; + +const qController = Client.getInstance(); interface Props { @@ -43,7 +49,12 @@ interface Props function QDynamicForm({formData, formLabel, bulkEditMode, bulkEditSwitchChangeHandler, record, helpRoles, helpContentKeyPrefix}: Props): JSX.Element { - const {formFields, values, errors, touched} = formData; + const {formFields: origFormFields, errors, touched} = formData; + const {setFieldValue, values} = useFormikContext>(); + + const [formAdjustmentCounter, setFormAdjustmentCounter] = useState(0) + + const [formFields, setFormFields] = useState(origFormFields as {[key: string]: any}); const bulkEditSwitchChanged = (name: string, value: boolean) => { @@ -51,6 +62,204 @@ function QDynamicForm({formData, formLabel, bulkEditMode, bulkEditSwitchChangeHa }; + ///////////////////////////////////////// + // run on-load handlers if we have any // + ///////////////////////////////////////// + useEffect(() => + { + for (let fieldName in formFields) + { + const field = formFields[fieldName]; + + const materialDashboardFieldMetaData = field.fieldMetaData?.supplementalFieldMetaData?.get("materialDashboard"); + if(materialDashboardFieldMetaData?.onLoadFormAdjuster) + { + ////////////////////////////////////////////////////////////////////////////////////////////////////////// + // todo consider cases with multiple - do they need to list a sequenceNo? do they need to run serially? // + ////////////////////////////////////////////////////////////////////////////////////////////////////////// + considerRunningFormAdjuster("onLoad", fieldName, values[fieldName]); + } + } + }, []); + + + /*************************************************************************** + ** + ***************************************************************************/ + const handleFieldChange = async (fieldName: string, newValue: any) => + { + const field = formFields[fieldName]; + if (!field) + { + return; + } + + ////////////////////////////////////////////////////////////////////// + // map possible-value objects to ids - also capture their labels... // + ////////////////////////////////////////////////////////////////////// + let actualNewValue = newValue; + let possibleValueLabel: string = null; + if (field.possibleValueProps) + { + actualNewValue = newValue ? newValue.id : null; + possibleValueLabel = newValue ? newValue.label : null; + } + + ///////////////////////////////////////////////////////////////////////////////////////////// + // make sure formik has the value - and that we capture the possible-value label if needed // + ///////////////////////////////////////////////////////////////////////////////////////////// + setFieldValue(fieldName, actualNewValue); + if (field.possibleValueProps) + { + field.possibleValueProps.initialDisplayValue = possibleValueLabel; + } + + /////////////////////////////////////////// + // run onChange adjuster if there is one // + /////////////////////////////////////////// + considerRunningFormAdjuster("onChange", fieldName, actualNewValue); + } + + + /*************************************************************************** + ** + ***************************************************************************/ + const considerRunningFormAdjuster = async (event: "onChange" | "onLoad", fieldName: string, newValue: any) => + { + const field = formFields[fieldName]; + if (!field) + { + return; + } + + const materialDashboardFieldMetaData = field.fieldMetaData?.supplementalFieldMetaData?.get("materialDashboard"); + const adjuster = event == "onChange" ? materialDashboardFieldMetaData?.onChangeFormAdjuster : materialDashboardFieldMetaData?.onLoadFormAdjuster; + if (!adjuster) + { + return; + } + + console.log(`Running form adjuster for field ${fieldName} ${event} (value is: ${newValue})`); + + ////////////////////////////////////////////////////////////////// + // disable fields temporarily while waiting on backend response // + ////////////////////////////////////////////////////////////////// + const fieldNamesToTempDisable: string[] = materialDashboardFieldMetaData?.fieldsToDisableWhileRunningAdjusters ?? [] + const previousIsEditableValues: {[key: string]: boolean} = {}; + if(fieldNamesToTempDisable.length > 0) + { + for (let oldFieldName in formFields) + { + if (fieldNamesToTempDisable.indexOf(oldFieldName) > -1) + { + previousIsEditableValues[oldFieldName] = formFields[oldFieldName].isEditable; + formFields[oldFieldName].isEditable = false; + } + } + + setFormAdjustmentCounter(formAdjustmentCounter + 1); + setFormFields({...formFields}); + } + + //////////////////////////////////////////////////// + // build request to backend for field adjustments // + //////////////////////////////////////////////////// + const postBody = new FormData(); + postBody.append("event", event); + postBody.append("fieldName", fieldName); + postBody.append("newValue", newValue); + postBody.append("allValues", JSON.stringify(values)); + const response = await qController.axiosRequest( + { + method: "post", + url: `/material-dashboard-backend/form-adjuster/${encodeURIComponent(materialDashboardFieldMetaData.formAdjusterIdentifier)}/${event}`, + data: postBody, + headers: qController.defaultMultipartFormDataHeaders() + }); + console.log("Form adjuster response: " + JSON.stringify(response)); + + //////////////////////////////////////////////////// + // un-disable any temp disabled fields from above // + //////////////////////////////////////////////////// + if(fieldNamesToTempDisable.length > 0) + { + for (let oldFieldName in formFields) + { + if (fieldNamesToTempDisable.indexOf(oldFieldName) > -1) + { + formFields[oldFieldName].isEditable = previousIsEditableValues[oldFieldName]; + } + } + setFormFields({...formFields}); + } + + /////////////////////////////////////////////////// + // replace field definitions, if we have updates // + /////////////////////////////////////////////////// + const updatedFields: { [fieldName: string]: QFieldMetaData } = response.updatedFieldMetaData; + if(updatedFields) + { + for (let updatedFieldName in updatedFields) + { + const updatedField = new QFieldMetaData(updatedFields[updatedFieldName]); + const dynamicField = DynamicFormUtils.getDynamicField(updatedField); // todo dynamicallyDisabledFields? second param... + + const dynamicFieldInObject: any = {}; + dynamicFieldInObject[updatedFieldName] = dynamicField; + let tableName = null; + let processName = null; + let displayValues = new Map(); + + DynamicFormUtils.addPossibleValueProps(dynamicFieldInObject, [updatedFields[updatedFieldName]], tableName, processName, displayValues); + for (let oldFieldName in formFields) + { + if (oldFieldName == updatedFieldName) + { + formFields[updatedFieldName] = dynamicField; + } + } + } + + setFormAdjustmentCounter(formAdjustmentCounter + 2); + setFormFields({...formFields}); + } + + ///////////////////////// + // update field values // + ///////////////////////// + const updatedFieldValues: {[fieldName: string]: any} = response?.updatedFieldValues ?? {}; + for (let fieldNameToUpdate in updatedFieldValues) + { + setFieldValue(fieldNameToUpdate, updatedFieldValues[fieldNameToUpdate]); + /////////////////////////////////////////////////////////////////////////////////////// + // todo - track if a pvs field gets a value, but not a display value, and fetch it?? // + /////////////////////////////////////////////////////////////////////////////////////// + } + + ///////////////////////////////////////////////// + // set display values in PVS's if we have them // + ///////////////////////////////////////////////// + const updatedFieldDisplayValues: {[fieldName: string]: any} = response?.updatedFieldDisplayValues ?? {}; + for (let fieldNameToUpdate in updatedFieldDisplayValues) + { + const fieldToUpdate = formFields[fieldNameToUpdate]; + if(fieldToUpdate?.possibleValueProps) + { + fieldToUpdate.possibleValueProps.initialDisplayValue = updatedFieldDisplayValues[fieldNameToUpdate]; + } + } + + //////////////////////////////////////// + // clear field values if we have them // + //////////////////////////////////////// + const fieldsToClear: string[] = response?.fieldsToClear ?? []; + for (let fieldToClear of fieldsToClear) + { + setFieldValue(fieldToClear, ""); + } + }; + + return ( @@ -68,6 +277,8 @@ function QDynamicForm({formData, formLabel, bulkEditMode, bulkEditSwitchChangeHa return null; } + const display = field.fieldMetaData?.isHidden ? "none" : "initial"; + if (values[fieldName] === undefined) { values[fieldName] = ""; @@ -100,7 +311,7 @@ function QDynamicForm({formData, formLabel, bulkEditMode, bulkEditSwitchChangeHa } return ( - + {labelElement} @@ -119,7 +330,7 @@ function QDynamicForm({formData, formLabel, bulkEditMode, bulkEditSwitchChangeHa }); return ( - + {labelElement} handleFieldChange(fieldName, newValue)} /> {formattedHelpContent} @@ -140,7 +352,7 @@ function QDynamicForm({formData, formLabel, bulkEditMode, bulkEditSwitchChangeHa // everything else!! // /////////////////////// return ( - + {labelElement} handleFieldChange(fieldName, newValue)} /> {formattedHelpContent} From 6fc11bb0ba1a318e713c28e8420805da62569229 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 29 May 2025 11:30:17 -0500 Subject: [PATCH 05/22] Add support for using api-versioned query screen --- .../misc/FilterAndColumnsSetupWidget.tsx | 72 ++++++++++++++++--- 1 file changed, 64 insertions(+), 8 deletions(-) diff --git a/src/qqq/components/widgets/misc/FilterAndColumnsSetupWidget.tsx b/src/qqq/components/widgets/misc/FilterAndColumnsSetupWidget.tsx index 590eb58..79194bd 100644 --- a/src/qqq/components/widgets/misc/FilterAndColumnsSetupWidget.tsx +++ b/src/qqq/components/widgets/misc/FilterAndColumnsSetupWidget.tsx @@ -20,6 +20,7 @@ */ +import {ApiVersion} from "@kingsrook/qqq-frontend-core/lib/controllers/QControllerV1"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData"; import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator"; @@ -80,6 +81,7 @@ unborderedButtonSX.opacity = "0.7"; const qController = Client.getInstance(); +const qControllerV1 = Client.getInstanceV1(); /******************************************************************************* ** Component for editing the main setup of a report - that is: filter & columns @@ -90,13 +92,17 @@ export default function FilterAndColumnsSetupWidget({isEditable: isEditableProp, const [hideColumns] = useState(widgetData?.hideColumns); const [hidePreview] = useState(widgetData?.hidePreview); const [hideSortBy] = useState(widgetData?.hideSortBy); - const [isEditable] = useState(widgetData?.overrideIsEditable ?? isEditableProp) + const [isEditable] = useState(widgetData?.overrideIsEditable ?? isEditableProp); const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData); + const [isApiVersioned] = useState(widgetData?.isApiVersioned); + const [apiVersion, setApiVersion] = useState(null as ApiVersion | null); + const [filterFieldName] = useState(widgetData?.filterFieldName ?? "queryFilterJson"); const [columnsFieldName] = useState(widgetData?.columnsFieldName ?? "columnsJson"); const [alertContent, setAlertContent] = useState(null as string); + const [widgetFailureAlertContent, setWidgetFailureAlertContent] = useState(null as string); ////////////////////////////////////////////////////////////////////////////////////////////////// // we'll actually keep 2 copies of the query filter around here - // @@ -114,7 +120,9 @@ export default function FilterAndColumnsSetupWidget({isEditable: isEditableProp, ///////////////////////////// let columns: QQueryColumns = null; let usingDefaultEmptyFilter = false; - let queryFilter = recordValues[filterFieldName] && JSON.parse(recordValues[filterFieldName]) as QQueryFilter; + const rawFilterValueFromRecord = recordValues[filterFieldName]; + let queryFilter = rawFilterValueFromRecord && + ((typeof rawFilterValueFromRecord == "string" ? JSON.parse(rawFilterValueFromRecord) : rawFilterValueFromRecord) as QQueryFilter); const defaultFilterFields = widgetData?.filterDefaultFieldNames; if (!queryFilter) { @@ -167,16 +175,56 @@ export default function FilterAndColumnsSetupWidget({isEditable: isEditableProp, tableName = recordValues["tableName"]; } + let version: ApiVersion | null = null; + if (isApiVersioned) + { + let apiName = widgetData?.apiName; + let apiPath = widgetData?.apiPath; + let apiVersion = widgetData?.apiVersion; + + if (!apiName && recordValues["apiName"]) + { + apiName = recordValues["apiName"]; + } + + if (!apiPath && recordValues["apiPath"]) + { + apiPath = recordValues["apiPath"]; + } + + if (!apiVersion && recordValues["apiVersion"]) + { + apiVersion = recordValues["apiVersion"]; + } + + if (!apiName || !apiPath || !apiVersion) + { + console.log("API Name/Path/Version not set, but widget isApiVersioned, so cannot load table meta data..."); + return; + } + + version = {name: apiName, path: apiPath, version: apiVersion}; + setApiVersion(version); + } + if (tableName) { (async () => { - const tableMetaData = await qController.loadTableMetaData(tableName); - setTableMetaData(tableMetaData); + try + { + const tableMetaData = await qControllerV1.loadTableMetaData(tableName, version); + setTableMetaData(tableMetaData); - const queryFilterForFrontend = Object.assign({}, queryFilter); - await FilterUtils.cleanupValuesInFilerFromQueryString(qController, tableMetaData, queryFilterForFrontend); - setFrontendQueryFilter(queryFilterForFrontend); + const queryFilterForFrontend = Object.assign({}, queryFilter); + await FilterUtils.cleanupValuesInFilerFromQueryString(qController, tableMetaData, queryFilterForFrontend); + setFrontendQueryFilter(queryFilterForFrontend); + } + catch (e) + { + //@ts-ignore e.message + setWidgetFailureAlertContent("Error preparing filter widget: " + (e.message ?? "Details not available.")); + } })(); } }, [JSON.stringify(recordValues)]); @@ -337,7 +385,7 @@ export default function FilterAndColumnsSetupWidget({isEditable: isEditableProp, ///////////////////////////////////////////////// // add link to widget header for opening modal // ///////////////////////////////////////////////// - const selectTableFirstTooltipTitle = tableMetaData ? null : "You must select a table before you can set up your report filters and columns"; + const selectTableFirstTooltipTitle = tableMetaData ? null : `You must select a table${isApiVersioned ? " and API details" : ""} before you can set up your filters${hideColumns ? "" : " and columns"}`; const labelAdditionalElementsRight: JSX.Element[] = []; if (isEditable) { @@ -351,6 +399,12 @@ export default function FilterAndColumnsSetupWidget({isEditable: isEditableProp, } } + if (widgetFailureAlertContent) + { + return ( + {widgetFailureAlertContent} + ); + } return ( @@ -424,6 +478,7 @@ export default function FilterAndColumnsSetupWidget({isEditable: isEditableProp, isModal={true} initialQueryFilter={frontendQueryFilter} initialColumns={columns} + apiVersion={apiVersion} /> )} @@ -449,6 +504,7 @@ export default function FilterAndColumnsSetupWidget({isEditable: isEditableProp, isModal={true} initialQueryFilter={usingDefaultEmptyFilter ? null : frontendQueryFilter} initialColumns={columns} + apiVersion={apiVersion} /> } From 3d4f0ba24b0aa363ec4bbab1e402ae61c6155817 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 29 May 2025 11:33:00 -0500 Subject: [PATCH 06/22] Update qqq-frontend-core to 1.0.121 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index aeab4ca..478d0b9 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.119", + "@kingsrook/qqq-frontend-core": "1.0.121", "@mui/icons-material": "5.4.1", "@mui/material": "5.11.1", "@mui/styles": "5.11.1", From 7d6b083ae2bc0d3f3556d85d74a9c740bbb4bfff Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 29 May 2025 11:35:12 -0500 Subject: [PATCH 07/22] Try-catch around recordAnalytics calls; reformat file --- src/qqq/pages/processes/ProcessRun.tsx | 91 ++++++++++++++++---------- 1 file changed, 56 insertions(+), 35 deletions(-) diff --git a/src/qqq/pages/processes/ProcessRun.tsx b/src/qqq/pages/processes/ProcessRun.tsx index baf1185..80baab2 100644 --- a/src/qqq/pages/processes/ProcessRun.tsx +++ b/src/qqq/pages/processes/ProcessRun.tsx @@ -72,6 +72,7 @@ import {ChildRecordListData} from "qqq/components/widgets/misc/RecordGridWidget" import BaseLayout from "qqq/layouts/BaseLayout"; import ProcessWidgetBlockUtils from "qqq/pages/processes/ProcessWidgetBlockUtils"; import {TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT} from "qqq/pages/records/query/RecordQuery"; +import {AnalyticsModel} from "qqq/utils/GoogleAnalyticsUtils"; import Client from "qqq/utils/qqq/Client"; import TableUtils from "qqq/utils/qqq/TableUtils"; import ValueUtils from "qqq/utils/qqq/ValueUtils"; @@ -114,9 +115,14 @@ let formikSetTouched = ({}: any, touched: boolean): void => const cachedPossibleValueLabels: { [fieldName: string]: { [id: string | number]: string } } = {}; -export interface SubFormPreSubmitCallbackResultType {maySubmit: boolean; values: {[name: string]: any}} +export interface SubFormPreSubmitCallbackResultType +{ + maySubmit: boolean; + values: { [name: string]: any }; +} + type SubFormPreSubmitCallback = () => SubFormPreSubmitCallbackResultType; -type SubFormPreSubmitCallbackWithName = {name: string, callback: SubFormPreSubmitCallback} +type SubFormPreSubmitCallbackWithName = { name: string, callback: SubFormPreSubmitCallback } function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, isReport, recordIds, closeModalHandler, forceReInit, overrideLabel}: Props): JSX.Element { @@ -161,7 +167,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is const [previouslySeenUpdatedFieldMetaDataMap, setPreviouslySeenUpdatedFieldMetaDataMap] = useState(new Map); const [renderedWidgets, setRenderedWidgets] = useState({} as { [step: string]: { [widgetName: string]: any } }); - const [controlCallbacks, setControlCallbacks] = useState({} as {[name: string]: () => void}); + const [controlCallbacks, setControlCallbacks] = useState({} as { [name: string]: () => void }); const [subFormPreSubmitCallbacks, setSubFormPreSubmitCallbacks] = useState([] as SubFormPreSubmitCallbackWithName[]); const {pageHeader, recordAnalytics, setPageHeader, helpHelpActive} = useContext(QContext); @@ -237,7 +243,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is const bulkLoadFileMappingFormRef = useRef(); const bulkLoadValueMappingFormRef = useRef(); const bulkLoadProfileFormRef = useRef(); - const [bulkLoadValueMappingFormFields, setBulkLoadValueMappingFormFields] = useState([] as any[]) + const [bulkLoadValueMappingFormFields, setBulkLoadValueMappingFormFields] = useState([] as any[]); const doesStepHaveComponent = (step: QFrontendStepMetaData, type: QComponentType): boolean => { @@ -699,10 +705,10 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is //////////////////////////////////////////////////////////////////////////////// if (doesStepHaveComponent(activeStep, QComponentType.BULK_LOAD_FILE_MAPPING_FORM)) { - if(bulkLoadFileMappingFormRef?.current) + if (bulkLoadFileMappingFormRef?.current) { // @ts-ignore ... - addSubFormPreSubmitCallbacks("bulkLoadFileMappingForm", bulkLoadFileMappingFormRef?.current?.preSubmit) + addSubFormPreSubmitCallbacks("bulkLoadFileMappingForm", bulkLoadFileMappingFormRef?.current?.preSubmit); } } @@ -711,10 +717,10 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is ///////////////////////////////////////////////////////////////////////////////// if (doesStepHaveComponent(activeStep, QComponentType.BULK_LOAD_VALUE_MAPPING_FORM)) { - if(bulkLoadValueMappingFormRef?.current) + if (bulkLoadValueMappingFormRef?.current) { // @ts-ignore ... - addSubFormPreSubmitCallbacks("bulkLoadValueMappingForm", bulkLoadValueMappingFormRef?.current?.preSubmit) + addSubFormPreSubmitCallbacks("bulkLoadValueMappingForm", bulkLoadValueMappingFormRef?.current?.preSubmit); } } @@ -723,10 +729,10 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is /////////////////////////////////////////////////////////////////////////// if (doesStepHaveComponent(activeStep, QComponentType.BULK_LOAD_PROFILE_FORM)) { - if(bulkLoadProfileFormRef?.current) + if (bulkLoadProfileFormRef?.current) { // @ts-ignore ... - addSubFormPreSubmitCallbacks("bulkLoadProfileFormRef", bulkLoadProfileFormRef?.current?.preSubmit) + addSubFormPreSubmitCallbacks("bulkLoadProfileFormRef", bulkLoadProfileFormRef?.current?.preSubmit); } } @@ -1298,7 +1304,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is ///////////////////////////////////////////////////////////////// // Help make this component's fields work with our formik form // ///////////////////////////////////////////////////////////////// - if(activeStep && doesStepHaveComponent(activeStep, QComponentType.BULK_LOAD_VALUE_MAPPING_FORM)) + if (activeStep && doesStepHaveComponent(activeStep, QComponentType.BULK_LOAD_VALUE_MAPPING_FORM)) { const fileValues = processValues.fileValues ?? []; const valueMapping = processValues.valueMapping ?? {}; @@ -1314,22 +1320,22 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is for (let i = 0; i < fileValues.length; i++) { const dynamicField = DynamicFormUtils.getDynamicField(qFieldMetaData); - const wrappedField: any = {}; + const wrappedField: any = {}; wrappedField[field.name] = dynamicField; DynamicFormUtils.addPossibleValueProps(wrappedField, [field], fieldTableName, null, null); const initialValue = valueMapping[fileValues[i]]; - if(dynamicField.possibleValueProps) + if (dynamicField.possibleValueProps) { - dynamicField.possibleValueProps.initialDisplayValue = mappedValueLabels[initialValue] + dynamicField.possibleValueProps.initialDisplayValue = mappedValueLabels[initialValue]; } - addField(`${fieldFullName}.value.${i}`, dynamicField, initialValue, null) + addField(`${fieldFullName}.value.${i}`, dynamicField, initialValue, null); fieldsForComponent.push(dynamicField); } - setBulkLoadValueMappingFormFields(fieldsForComponent) + setBulkLoadValueMappingFormFields(fieldsForComponent); } if (Object.keys(dynamicFormFields).length > 0) @@ -1522,15 +1528,15 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is ***************************************************************************/ function addSubFormPreSubmitCallbacks(name: string, callback: SubFormPreSubmitCallback) { - if(subFormPreSubmitCallbacks.findIndex(c => c.name == name) == -1) + if (subFormPreSubmitCallbacks.findIndex(c => c.name == name) == -1) { - const newCallbacks: SubFormPreSubmitCallbackWithName[] = [] - for(let i = 0; i < subFormPreSubmitCallbacks.length; i++) + const newCallbacks: SubFormPreSubmitCallbackWithName[] = []; + for (let i = 0; i < subFormPreSubmitCallbacks.length; i++) { newCallbacks[i] = subFormPreSubmitCallbacks[i]; } - newCallbacks.push({name, callback}) - setSubFormPreSubmitCallbacks(newCallbacks) + newCallbacks.push({name, callback}); + setSubFormPreSubmitCallbacks(newCallbacks); } } @@ -1620,7 +1626,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is setRenderedWidgets({}); setSubFormPreSubmitCallbacks([]); setQJobRunning(null); - setBackStepName(qJobComplete.backStep) + setBackStepName(qJobComplete.backStep); if (formikSetFieldValueFunction) { @@ -1815,8 +1821,8 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is setProcessMetaData(processMetaData); setSteps(processMetaData.frontendSteps); - recordAnalytics({location: window.location, title: "Process: " + processMetaData?.label}); - recordAnalytics({category: "processEvents", action: "startProcess", label: processMetaData?.label}); + doRecordAnalytics({location: window.location, title: "Process: " + processMetaData?.label}); + doRecordAnalytics({category: "processEvents", action: "startProcess", label: processMetaData?.label}); if (processMetaData.tableName && !tableMetaData) { @@ -1838,17 +1844,17 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is return; } - if(urlSearchParams.get("defaultProcessValues")) + if (urlSearchParams.get("defaultProcessValues")) { - if(!defaultProcessValues) + if (!defaultProcessValues) { - defaultProcessValues = {} + defaultProcessValues = {}; } const values = JSON.parse(urlSearchParams.get("defaultProcessValues")); for (let key in values) { - defaultProcessValues[key] = values[key] + defaultProcessValues[key] = values[key]; } } @@ -1894,7 +1900,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is setTimeout(async () => { - recordAnalytics({category: "processEvents", action: "processStep", label: activeStep.label}); + doRecordAnalytics({category: "processEvents", action: "processStep", label: activeStep.label}); const processResponse = await qController.processStep( processName, @@ -1914,7 +1920,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is { setTimeout(async () => { - recordAnalytics({category: "processEvents", action: "processStep", label: activeStep.label}); + doRecordAnalytics({category: "processEvents", action: "processStep", label: activeStep.label}); const processResponse = await Client.getInstance().processStep( processName, @@ -1938,20 +1944,20 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is /////////////////////////////////////////////////////////////// // run any sub-form pre-submit callbacks that are registered // /////////////////////////////////////////////////////////////// - for(let i = 0; i < subFormPreSubmitCallbacks.length; i++) + for (let i = 0; i < subFormPreSubmitCallbacks.length; i++) { const {maySubmit, values: moreValues} = subFormPreSubmitCallbacks[i].callback(); - if(!maySubmit) + if (!maySubmit) { console.log(`May not submit form, per callback: ${subFormPreSubmitCallbacks[i].name}`); return; } - if(moreValues) + if (moreValues) { for (let key in moreValues) { - values[key] = moreValues[key] + values[key] = moreValues[key]; } } } @@ -2026,7 +2032,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is //////////////////////////////////////////////////////////////////////////////////////////////////////////// setLoadingRecords(true); - } + }; /******************************************************************************* @@ -2055,6 +2061,21 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is }; + /*************************************************************************** + ** + ***************************************************************************/ + function doRecordAnalytics(model: AnalyticsModel) + { + try + { + recordAnalytics(model); + } + catch (e) + { + console.log(`Error recording analytics: ${e}`); + } + } + const formStyles: any = {}; if (isWidget) { From 68c1f897af2e26b2dcfa4351ad0a7901c0af70be Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 29 May 2025 11:36:40 -0500 Subject: [PATCH 08/22] Add otherValues to form field possibleValues and queryString based on record values in widget load --- src/qqq/utils/qqq/QFMDBridge.tsx | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/qqq/utils/qqq/QFMDBridge.tsx b/src/qqq/utils/qqq/QFMDBridge.tsx index a3f2447..6994950 100644 --- a/src/qqq/utils/qqq/QFMDBridge.tsx +++ b/src/qqq/utils/qqq/QFMDBridge.tsx @@ -75,7 +75,7 @@ function QFMDBridgeForm({fields, record, handleChange, handleSubmit}: QFMDBridge initialValues[field.name] = record.values.get(field.name); } const [lastValues, setLastValues] = useState(initialValues); - const [loaded, setLoaded] = useState(false) + const [loaded, setLoaded] = useState(false); useEffect(() => { @@ -102,7 +102,7 @@ function QFMDBridgeForm({fields, record, handleChange, handleSubmit}: QFMDBridge if (!loaded) { - return (
Loading...
); + return (Loading...); } const { @@ -111,6 +111,18 @@ function QFMDBridgeForm({fields, record, handleChange, handleSubmit}: QFMDBridge } = DynamicFormUtils.getFormData(fields); DynamicFormUtils.addPossibleValueProps(dynamicFormFields, fields, null, null, record ? record.displayValues : new Map()); + const otherValuesMap = new Map(); + record.values.forEach((value, key) => otherValuesMap.set(key, value)); + + for (let fieldName in dynamicFormFields) + { + const dynamicFormField = dynamicFormFields[fieldName]; + if (dynamicFormField.possibleValueProps) + { + dynamicFormField.possibleValueProps.otherValues = otherValuesMap; + } + } + ///////////////////////////////////////////////////////////////////////////////// // re-introduce these two context providers, in case the child calls this // // method under a different root... maybe this should be optional per a param? // @@ -190,8 +202,14 @@ function QFMDBridgeWidget({widgetName, tableName, record, entityPrimaryKey, acti const qController = Client.getInstance(); const qInstance = await qController.loadMetaData(); + const queryStringParts: string[] = []; + for (let key of record?.values?.keys()) + { + queryStringParts.push(`${encodeURIComponent(key)}=${encodeURIComponent(record.values.get(key))}`); + } + setWidgetMetaData(qInstance.widgets.get(widgetName)); - setWidgetData(await qController.widget(widgetName, null)); // todo queryParams... ? + setWidgetData(await qController.widget(widgetName, queryStringParts.join("&"))); setReady(true); })(); @@ -199,7 +217,7 @@ function QFMDBridgeWidget({widgetName, tableName, record, entityPrimaryKey, acti if (!ready) { - return (
Loading...
); + return (Loading...); } /////////////////////////////////////////////////////////////////////////////////////////////////////////// From 020e174110bab21501cac02d0f4604e03852853c Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 29 May 2025 11:37:10 -0500 Subject: [PATCH 09/22] Support omitFieldNames to be specified in the widgetData --- .../components/widgets/misc/RecordGridWidget.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/qqq/components/widgets/misc/RecordGridWidget.tsx b/src/qqq/components/widgets/misc/RecordGridWidget.tsx index 5f9e8a6..0443afc 100644 --- a/src/qqq/components/widgets/misc/RecordGridWidget.tsx +++ b/src/qqq/components/widgets/misc/RecordGridWidget.tsx @@ -49,6 +49,7 @@ export interface ChildRecordListData extends WidgetData defaultValuesForNewChildRecords?: { [fieldName: string]: any }; disabledFieldsForNewChildRecords?: { [fieldName: string]: any }; defaultValuesForNewChildRecordsFromParentFields?: { [fieldName: string]: string }; + omitFieldNames?: string[]; } interface Props @@ -119,6 +120,19 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo const childTablePath = data.tablePath ? data.tablePath + (data.tablePath.endsWith("/") ? "" : "/") : data.tablePath; const columns = DataGridUtils.setupGridColumns(tableMetaData, childTablePath, null, "bySection"); + if (data.omitFieldNames) + { + for (let i = 0; i < columns.length; i++) + { + const column = columns[i]; + if (data.omitFieldNames.indexOf(column.field) > -1) + { + columns.splice(i, 1); + i--; + } + } + } + ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // capture all-columns to use for the export (before we might splice some away from the on-screen display) // ///////////////////////////////////////////////////////////////////////////////////////////////////////////// From c1ea7081f1e30cd1b15353c341d5922c549f1654 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 29 May 2025 12:23:11 -0500 Subject: [PATCH 10/22] Switch to use QControllerV1 for tableMetaData, query, and count calls, in support of apiVersions; add a pageState of error; setLoading when pageNo or rowsPerPage change; adjust handling of doSetCurrentSavedView, if the saved view record is null --- src/qqq/pages/records/query/RecordQuery.tsx | 66 +++++++++++++++------ 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/src/qqq/pages/records/query/RecordQuery.tsx b/src/qqq/pages/records/query/RecordQuery.tsx index 3a2a594..cefae62 100644 --- a/src/qqq/pages/records/query/RecordQuery.tsx +++ b/src/qqq/pages/records/query/RecordQuery.tsx @@ -20,6 +20,7 @@ */ import {QController} from "@kingsrook/qqq-frontend-core/lib/controllers/QController"; +import {ApiVersion} from "@kingsrook/qqq-frontend-core/lib/controllers/QControllerV1"; import {Capability} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Capability"; import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; @@ -69,9 +70,9 @@ import RecordQueryView from "qqq/models/query/RecordQueryView"; import ProcessRun from "qqq/pages/processes/ProcessRun"; import ColumnStats from "qqq/pages/records/query/ColumnStats"; import DataGridUtils from "qqq/utils/DataGridUtils"; +import {AnalyticsModel} from "qqq/utils/GoogleAnalyticsUtils"; import Client from "qqq/utils/qqq/Client"; import FilterUtils from "qqq/utils/qqq/FilterUtils"; -import {AnalyticsModel} from "qqq/utils/GoogleAnalyticsUtils"; import ProcessUtils from "qqq/utils/qqq/ProcessUtils"; import {SavedViewUtils} from "qqq/utils/qqq/SavedViewUtils"; import TableUtils from "qqq/utils/qqq/TableUtils"; @@ -89,6 +90,7 @@ export type QueryScreenUsage = "queryScreen" | "reportSetup" interface Props { table?: QTableMetaData; + apiVersion?: ApiVersion; launchProcess?: QProcessMetaData; usage?: QueryScreenUsage; isModal?: boolean; @@ -101,9 +103,10 @@ interface Props /////////////////////////////////////////////////////// // define possible values for our pageState variable // /////////////////////////////////////////////////////// -type PageState = "initial" | "loadingMetaData" | "loadedMetaData" | "loadingView" | "loadedView" | "preparingGrid" | "ready"; +type PageState = "initial" | "loadingMetaData" | "loadedMetaData" | "loadingView" | "loadedView" | "preparingGrid" | "ready" | "error"; const qController = Client.getInstance(); +const qControllerV1 = Client.getInstanceV1(); /******************************************************************************* ** function to produce standard version of the screen while we're "loading" @@ -127,7 +130,7 @@ const getLoadingScreen = (isModal: boolean) => ** ** Yuge component. The best. Lots of very smart people are saying so. *******************************************************************************/ -const RecordQuery = forwardRef(({table, usage, isModal, isPreview, allowVariables, initialQueryFilter, initialColumns}: Props, ref) => +const RecordQuery = forwardRef(({table, apiVersion, usage, isModal, isPreview, allowVariables, initialQueryFilter, initialColumns}: Props, ref) => { const tableName = table.name; const [searchParams] = useSearchParams(); @@ -979,7 +982,8 @@ const RecordQuery = forwardRef(({table, usage, isModal, isPreview, allowVariable } let includeDistinct = isJoinMany(tableMetaData, getVisibleJoinTables()); - qController.count(tableName, filterForBackend, queryJoins, includeDistinct, tableVariant).then(([count, distinctCount]) => + // qController.count(tableName, filterForBackend, queryJoins, includeDistinct, tableVariant).then(([count, distinctCount]) => + qControllerV1.count(tableName, apiVersion, filterForBackend, queryJoins, includeDistinct, tableVariant).then(([count, distinctCount]) => { console.log(`Received count results for query ${thisQueryId}: ${count} ${distinctCount}`); countResults[thisQueryId] = []; @@ -998,7 +1002,8 @@ const RecordQuery = forwardRef(({table, usage, isModal, isPreview, allowVariable setLastFetchedQFilterJSON(JSON.stringify(queryFilter)); setLastFetchedVariant(tableVariant); - qController.query(tableName, filterForBackend, queryJoins, tableVariant).then((results) => + // qController.query(tableName, filterForBackend, queryJoins, tableVariant).then((results) => + qControllerV1.query(tableName, apiVersion, filterForBackend, queryJoins, tableVariant).then((results) => { console.log(`Received results for query ${thisQueryId}`); queryResults[thisQueryId] = results; @@ -1141,6 +1146,7 @@ const RecordQuery = forwardRef(({table, usage, isModal, isPreview, allowVariable const handlePageNumberChange = (page: number) => { setPageNumber(page); + setLoading(true); }; /******************************************************************************* @@ -1149,6 +1155,7 @@ const RecordQuery = forwardRef(({table, usage, isModal, isPreview, allowVariable const handleRowsPerPageChange = (size: number) => { setRowsPerPage(size); + setLoading(true); view.rowsPerPage = size; doSetView(view); @@ -1672,8 +1679,9 @@ const RecordQuery = forwardRef(({table, usage, isModal, isPreview, allowVariable { if (savedViewRecord == null) { - console.log("doSetCurrentView called with a null view record - calling doClearCurrentSavedView instead."); + console.log("doSetCurrentView called with a null view record - calling doClearCurrentSavedView, and activating tableDefaultView instead."); doClearCurrentSavedView(); + activateView(buildTableDefaultView(tableMetaData)); return; } @@ -2435,23 +2443,34 @@ const RecordQuery = forwardRef(({table, usage, isModal, isPreview, allowVariable const metaData = await qController.loadMetaData(); setMetaData(metaData); - const tableMetaData = await qController.loadTableMetaData(tableName); - setTableMetaData(tableMetaData); - setTableLabel(tableMetaData.label); + try + { + // const tableMetaData = await qController.loadTableMetaData(tableName); + const tableMetaData = await qControllerV1.loadTableMetaData(tableName, apiVersion); + setTableMetaData(tableMetaData); + setTableLabel(tableMetaData.label); doRecordAnalytics({location: window.location, title: "Query: " + tableMetaData.label}); + doRecordAnalytics({location: window.location, title: "Query: " + tableMetaData.label}); - setTableProcesses(ProcessUtils.getProcessesForTable(metaData, tableName)); // these are the ones to show in the dropdown - setAllTableProcesses(ProcessUtils.getProcessesForTable(metaData, tableName, true)); // these include hidden ones (e.g., to find the bulks) + setTableProcesses(ProcessUtils.getProcessesForTable(metaData, tableName)); // these are the ones to show in the dropdown + setAllTableProcesses(ProcessUtils.getProcessesForTable(metaData, tableName, true)); // these include hidden ones (e.g., to find the bulks) - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // now that we know the table - build a default view - initially, only used by SavedViews component, for showing if there's anything to be saved. // - // but also used when user selects new-view from the view menu // - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - const newDefaultView = buildTableDefaultView(tableMetaData); - setTableDefaultView(newDefaultView); + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // now that we know the table - build a default view - initially, only used by SavedViews component, for showing if there's anything to be saved. // + // but also used when user selects new-view from the view menu // + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + const newDefaultView = buildTableDefaultView(tableMetaData); + setTableDefaultView(newDefaultView); - setPageState("loadedMetaData"); + setPageState("loadedMetaData"); + } + catch (e) + { + setPageState("error"); + //@ts-ignore e.message + setAlertContent("Error loading table: " + e?.message ?? "Details not available."); + } })(); } @@ -2719,6 +2738,16 @@ const RecordQuery = forwardRef(({table, usage, isModal, isPreview, allowVariable ); } + ////////////////////////////////////////////// + // render an error screen (alert) if needed // + ////////////////////////////////////////////// + if (pageState == "error") + { + console.log(`page state is ${pageState}... rendering an alert...`); + const errorBody = {alertContent}; + return isModal ? errorBody : {errorBody}; + } + /////////////////////////////////////////////////////////// // render a loading screen if the page state isn't ready // /////////////////////////////////////////////////////////// @@ -3069,6 +3098,7 @@ const RecordQuery = forwardRef(({table, usage, isModal, isPreview, allowVariable RecordQuery.defaultProps = { table: null, + apiVersion: null, usage: "queryScreen", launchProcess: null, isModal: false, From 5ab906bcfe10b207f16900504960468e62e568a8 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 29 May 2025 12:23:49 -0500 Subject: [PATCH 11/22] update disabled pagination icons to look disabled --- src/qqq/styles/qqq-override-styles.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/qqq/styles/qqq-override-styles.css b/src/qqq/styles/qqq-override-styles.css index dc3c861..23735a0 100644 --- a/src/qqq/styles/qqq-override-styles.css +++ b/src/qqq/styles/qqq-override-styles.css @@ -303,10 +303,15 @@ input[type="search"]::-webkit-search-results-decoration .MuiTablePagination-root .MuiSvgIcon-root { display: inline; - color: gray; + color: rgba(0, 0, 0, 0.54); right: 0.125rem; } +.MuiTablePagination-root .Mui-disabled .MuiSvgIcon-root +{ + color: rgba(0, 0, 0, 0.16); +} + .devDocumentation ul > li { margin-left: 30px; From 0a42b9d4f08305f7350fb9ce96f5cbb73dd5b348 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 29 May 2025 12:24:21 -0500 Subject: [PATCH 12/22] try to be a little more graceful with fields that don't exist (e.g., other api version use cases) --- src/qqq/models/query/QQueryColumns.ts | 5 +++++ src/qqq/utils/qqq/FilterUtils.tsx | 14 ++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/qqq/models/query/QQueryColumns.ts b/src/qqq/models/query/QQueryColumns.ts index 8b5dccb..a824c70 100644 --- a/src/qqq/models/query/QQueryColumns.ts +++ b/src/qqq/models/query/QQueryColumns.ts @@ -117,6 +117,11 @@ export default class QQueryColumns { const [field, tableForField] = TableUtils.getFieldAndTable(table, fieldName) + if(!field) + { + console.warn(`Couldn't find field ${fieldName} in tableMetaData - so not adding a column for it`); + } + let column: Column; if(tableForField.name == table.name) { diff --git a/src/qqq/utils/qqq/FilterUtils.tsx b/src/qqq/utils/qqq/FilterUtils.tsx index b5bb4f7..fdb64a8 100644 --- a/src/qqq/utils/qqq/FilterUtils.tsx +++ b/src/qqq/utils/qqq/FilterUtils.tsx @@ -108,6 +108,12 @@ class FilterUtils const criteria = queryFilter.criteria[i]; let [field, fieldTable] = TableUtils.getFieldAndTable(tableMetaData, criteria.fieldName); + if(!field) + { + console.warn(`Field ${criteria.fieldName} not found in tableMetaData - unable to clean up values for it..`); + return; + } + let values = criteria.values; let hasFilterVariable = false; @@ -401,21 +407,21 @@ class FilterUtils { const expression = new ThisOrLastPeriodExpression(value); let startOfPrefix = ""; - if (fieldMetaData.type == QFieldType.DATE_TIME || expression.timeUnit != "DAYS") + if (fieldMetaData?.type == QFieldType.DATE_TIME || expression.timeUnit != "DAYS") { startOfPrefix = "start of "; } labels.push(`${startOfPrefix}${expression.toString()}`); } - else if (fieldMetaData.type == QFieldType.BOOLEAN) + else if (fieldMetaData?.type == QFieldType.BOOLEAN) { labels.push(value == true ? "yes" : "no"); } - else if (fieldMetaData.type == QFieldType.DATE_TIME) + else if (fieldMetaData?.type == QFieldType.DATE_TIME) { labels.push(ValueUtils.formatDateTime(value)); } - else if (fieldMetaData.type == QFieldType.DATE) + else if (fieldMetaData?.type == QFieldType.DATE) { labels.push(ValueUtils.formatDate(value)); } From ce947bc0f74d58e95ac79b058325cbf9d95948f5 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 29 May 2025 12:25:39 -0500 Subject: [PATCH 13/22] Add proxy for /material-dashboard-backend/* (initially for field onLoad/Change form adjusters) --- src/setupProxy.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/setupProxy.js b/src/setupProxy.js index 714c072..e0812e9 100644 --- a/src/setupProxy.js +++ b/src/setupProxy.js @@ -59,4 +59,5 @@ module.exports = function (app) app.use("/*api", getRequestHandler()); app.use("/qqq/*", getRequestHandler()); app.use("/dynamic-qfmd-components/*", getRequestHandler()); + app.use("/material-dashboard-backend/*", getRequestHandler()); }; From 1da0f4f1de3602d57de7f54a6d6186150267ecf3 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 29 May 2025 12:26:41 -0500 Subject: [PATCH 14/22] Attempting to improve handling for non-countable tables (was showing 1 past the end sometimes); disable when can't go back or forward; min-width for more stable UI --- .../query/CustomPaginationComponent.tsx | 65 +++++++++++++++++-- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/src/qqq/components/query/CustomPaginationComponent.tsx b/src/qqq/components/query/CustomPaginationComponent.tsx index c6eccfc..1bf7e37 100644 --- a/src/qqq/components/query/CustomPaginationComponent.tsx +++ b/src/qqq/components/query/CustomPaginationComponent.tsx @@ -26,9 +26,9 @@ import Box from "@mui/material/Box"; import Icon from "@mui/material/Icon"; import IconButton from "@mui/material/IconButton"; import {GridRowsProp} from "@mui/x-data-grid-pro"; -import React from "react"; import CustomWidthTooltip from "qqq/components/tooltips/CustomWidthTooltip"; import ValueUtils from "qqq/utils/qqq/ValueUtils"; +import React from "react"; interface CustomPaginationProps { @@ -56,7 +56,7 @@ export default function CustomPaginationComponent({tableMetaData, rows, totalRec The number of rows shown on this screen may be greater than the number of {tableMetaData?.label} records that match your query, because you have included fields from other tables which may have more than one record associated with each {tableMetaData?.label}. - + ; let distinctPart = isJoinMany ? (  ({ValueUtils.safeToLocaleString(distinctRecords)} distinct info_outlined @@ -66,13 +66,23 @@ export default function CustomPaginationComponent({tableMetaData, rows, totalRec if (tableMetaData && !tableMetaData.capabilities.has(Capability.TABLE_COUNT)) { + if (loading) + { + return "Counting..."; + } + + if (!rows || rows.length == 0) + { + return "No rows"; + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // to avoid a non-countable table showing (this is what data-grid did) 91-100 even if there were only 95 records, // // we'll do this... not quite good enough, but better than the original // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// if (rows.length > 0 && rows.length < to - from) { - to = from + rows.length; + to = from + (rows.length - 1); } return (`Showing ${from.toLocaleString()} to ${to.toLocaleString()}`); } @@ -102,14 +112,55 @@ export default function CustomPaginationComponent({tableMetaData, rows, totalRec } }; + /////////////////////////////////////////////////////////////////////////////// + // the `count` param that we pass to below is very // + // important - it drives which of the < and > (prev & next) buttons are // + // enabled - and, it's a little tricky for tables where we don't do a count. // + /////////////////////////////////////////////////////////////////////////////// + let countForTablePagination: number; + if (tableMetaData && !tableMetaData.capabilities.has(Capability.TABLE_COUNT)) + { + //////////////////////////////////////////// + // handle tables where count is disabled. // + //////////////////////////////////////////// + if(!rows || rows.length == 0) + { + ///////////////////////////////////////////// + // if we have no rows, assume a count of 0 // + ///////////////////////////////////////////// + countForTablePagination = 0; + } + if(rows.length < rowsPerPage) + { + ////////////////////////////////////////////////////////////////////////////////////////////////// + // if the # of rows we have is less than the rowsPerPage, assume we're at the end of the query // + // so, setting count to pageNo*rowsPer + rows.length - leaves prev. enabled, but disables next. // + ////////////////////////////////////////////////////////////////////////////////////////////////// + countForTablePagination = (pageNumber * rowsPerPage) + rows.length; + } + else + { + /////////////////////////////////////////////////////////////////////////////////////////////////// + // else, we don't know how many more pages there could be - so, just assume it's at least 1 more // + /////////////////////////////////////////////////////////////////////////////////////////////////// + countForTablePagination = ((pageNumber + 1) * rowsPerPage) + 1; + } + } + else + { + //////////////////////////////////////////////////////////////////////////////// + // cases where count is enabled - they work much more like we'd expect: // + // if we don't know totalRecords (probably same as loading?) - use a -1, // + // which lets us see < and > both active; else, use totalRecords when known. // + //////////////////////////////////////////////////////////////////////////////// + countForTablePagination = totalRecords === null || totalRecords === undefined ? -1 : totalRecords; + } return ( Date: Thu, 29 May 2025 12:29:35 -0500 Subject: [PATCH 15/22] Remove non-existing fields with a warning (attempt to improve support for api-versioned use-case) --- .../misc/FilterAndColumnsSetupWidget.tsx | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/qqq/components/widgets/misc/FilterAndColumnsSetupWidget.tsx b/src/qqq/components/widgets/misc/FilterAndColumnsSetupWidget.tsx index 79194bd..4a22fe9 100644 --- a/src/qqq/components/widgets/misc/FilterAndColumnsSetupWidget.tsx +++ b/src/qqq/components/widgets/misc/FilterAndColumnsSetupWidget.tsx @@ -43,6 +43,7 @@ import QQueryColumns, {Column} from "qqq/models/query/QQueryColumns"; import RecordQuery from "qqq/pages/records/query/RecordQuery"; import Client from "qqq/utils/qqq/Client"; import FilterUtils from "qqq/utils/qqq/FilterUtils"; +import TableUtils from "qqq/utils/qqq/TableUtils"; import React, {useContext, useEffect, useRef, useState} from "react"; interface FilterAndColumnsSetupWidgetProps @@ -102,6 +103,7 @@ export default function FilterAndColumnsSetupWidget({isEditable: isEditableProp, const [columnsFieldName] = useState(widgetData?.columnsFieldName ?? "columnsJson"); const [alertContent, setAlertContent] = useState(null as string); + const [warning, setWarning] = useState(null as string); const [widgetFailureAlertContent, setWidgetFailureAlertContent] = useState(null as string); ////////////////////////////////////////////////////////////////////////////////////////////////// @@ -217,11 +219,28 @@ export default function FilterAndColumnsSetupWidget({isEditable: isEditableProp, setTableMetaData(tableMetaData); const queryFilterForFrontend = Object.assign({}, queryFilter); + + let warnings: string[] = []; + for (let i = 0; i < queryFilterForFrontend?.criteria?.length; i++) + { + const criteria = queryFilter.criteria[i]; + let [field, fieldTable] = TableUtils.getFieldAndTable(tableMetaData, criteria.fieldName); + if(!field) + { + warnings.push("Removing non-existing filter field: " + criteria.fieldName); + queryFilterForFrontend.criteria.splice(i, 1); + i--; + } + } + await FilterUtils.cleanupValuesInFilerFromQueryString(qController, tableMetaData, queryFilterForFrontend); setFrontendQueryFilter(queryFilterForFrontend); + + setWarning(warnings.join("; ")); } catch (e) { + console.log(e); //@ts-ignore e.message setWidgetFailureAlertContent("Error preparing filter widget: " + (e.message ?? "Details not available.")); } @@ -417,6 +436,9 @@ export default function FilterAndColumnsSetupWidget({isEditable: isEditableProp, setAlertContent(null)}>{alertContent} + + setWarning(null)}>{warning} +
{label ?? "Query Filter"}
From 69b46570cb273e10e7e0d9a216abb71b72b66b22 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 29 May 2025 19:15:26 -0500 Subject: [PATCH 16/22] Add optional dep for qqq-middleware-javalin; update version of qqq --- pom.xml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5b2eac0..72a02ab 100644 --- a/pom.xml +++ b/pom.xml @@ -66,7 +66,13 @@ com.kingsrook.qqq qqq-backend-core - 0.25.0-integration-sprint-62-20250307-205536 + 0.26.0-integration-20250529-234230 + + + com.kingsrook.qqq + qqq-middleware-javalin + true + 0.26.0-integration-20250529-234230 org.slf4j From b82b25156e603a51ed811876f652d5e97756a459 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 29 May 2025 19:16:06 -0500 Subject: [PATCH 17/22] Check if javalin classes are available before using (made dep on javalin optional) --- .../formadjuster/FormAdjusterRegistry.java | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/actions/formadjuster/FormAdjusterRegistry.java b/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/actions/formadjuster/FormAdjusterRegistry.java index 91b0394..7d60430 100644 --- a/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/actions/formadjuster/FormAdjusterRegistry.java +++ b/src/main/java/com/kingsrook/qqq/frontend/materialdashboard/actions/formadjuster/FormAdjusterRegistry.java @@ -30,6 +30,7 @@ import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; +import com.kingsrook.qqq.backend.core.utils.ClassPathUtils; import com.kingsrook.qqq.backend.javalin.QJavalinMetaData; import com.kingsrook.qqq.frontend.materialdashboard.model.metadata.MaterialDashboardFieldMetaData; import com.kingsrook.qqq.middleware.javalin.metadata.JavalinRouteProviderMetaData; @@ -67,19 +68,23 @@ public class FormAdjusterRegistry onLoadAdjusters.clear(); } - //////////////////////////////////////////////////////////////////////////////// - // if we need to register the javalin router, do so (only once per qInstance) // - //////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + // if we need to register the javalin router, do so (only once per qInstance) // + // note, javalin is optional dep, so make sure it's available before try to use it // + ///////////////////////////////////////////////////////////////////////////////////// if(!didRegisterRouteProvider) { - QJavalinMetaData javalinMetaData = QJavalinMetaData.ofOrWithNew(qInstance); - javalinMetaData.withRouteProvider(new JavalinRouteProviderMetaData() - .withHostedPath("/material-dashboard-backend/form-adjuster/{identifier}/{event}") - .withMethods(List.of("POST")) - .withProcessName(RunFormAdjusterProcess.NAME) - ); + if(ClassPathUtils.isClassAvailable(QJavalinMetaData.class.getName())) + { + QJavalinMetaData javalinMetaData = QJavalinMetaData.ofOrWithNew(qInstance); + javalinMetaData.withRouteProvider(new JavalinRouteProviderMetaData() + .withHostedPath("/material-dashboard-backend/form-adjuster/{identifier}/{event}") + .withMethods(List.of("POST")) + .withProcessName(RunFormAdjusterProcess.NAME) + ); - qInstance.add(new RunFormAdjusterProcess().produce(qInstance)); + qInstance.add(new RunFormAdjusterProcess().produce(qInstance)); + } didRegisterRouteProvider = true; lastRegisteredQInstance = qInstance; From 80ac2a304a1d51a643485f858507d0bdc77b0bb8 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 29 May 2025 19:18:13 -0500 Subject: [PATCH 18/22] Update qqq-frontend-core version to 1.0.123 (for qControllerV1 count) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 478d0b9..f7788d7 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.121", + "@kingsrook/qqq-frontend-core": "1.0.123", "@mui/icons-material": "5.4.1", "@mui/material": "5.11.1", "@mui/styles": "5.11.1", From 248040a99ff889aeac66d138c1b7bbe2ff84d3a4 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Fri, 30 May 2025 09:20:08 -0500 Subject: [PATCH 19/22] Fix dupe call to doRecordAnalytics --- src/qqq/pages/records/query/RecordQuery.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/qqq/pages/records/query/RecordQuery.tsx b/src/qqq/pages/records/query/RecordQuery.tsx index cefae62..fc4e606 100644 --- a/src/qqq/pages/records/query/RecordQuery.tsx +++ b/src/qqq/pages/records/query/RecordQuery.tsx @@ -2450,7 +2450,6 @@ const RecordQuery = forwardRef(({table, apiVersion, usage, isModal, isPreview, a setTableMetaData(tableMetaData); setTableLabel(tableMetaData.label); - doRecordAnalytics({location: window.location, title: "Query: " + tableMetaData.label}); doRecordAnalytics({location: window.location, title: "Query: " + tableMetaData.label}); setTableProcesses(ProcessUtils.getProcessesForTable(metaData, tableName)); // these are the ones to show in the dropdown From 0bca8e9361608142539bcb67a6fadd3cae63ca13 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Fri, 30 May 2025 20:24:38 -0500 Subject: [PATCH 20/22] Add new argument to qController.possibleValues call --- src/qqq/utils/qqq/QFMDBridge.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qqq/utils/qqq/QFMDBridge.tsx b/src/qqq/utils/qqq/QFMDBridge.tsx index 6994950..a25330d 100644 --- a/src/qqq/utils/qqq/QFMDBridge.tsx +++ b/src/qqq/utils/qqq/QFMDBridge.tsx @@ -88,7 +88,7 @@ function QFMDBridgeForm({fields, record, handleChange, handleSubmit}: QFMDBridge const value = record.values.get(field.name); if (field.possibleValueSourceName && value) { - const possibleValues = await qController.possibleValues(null, null, field.possibleValueSourceName, null, [value], record.values, "form"); + const possibleValues = await qController.possibleValues(null, null, field.possibleValueSourceName, null, [value], [], record.values, "form"); if (possibleValues && possibleValues.length > 0) { record.displayValues.set(field.name, possibleValues[0].label); From 0bf33a01f9789b15ee7a247f5601c52313f13bc6 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Fri, 30 May 2025 20:46:55 -0500 Subject: [PATCH 21/22] Copy fixture files to qqq/v1 api paths; update routes setup for fixtures too --- .../selenium/tests/AppPageNavTest.java | 4 +- .../selenium/tests/BulkEditTest.java | 2 + .../selenium/tests/SavedReportTest.java | 7 +- ...ueryScreenFilterInUrlAdvancedModeTest.java | 2 + .../QueryScreenFilterInUrlBasicModeTest.java | 2 + .../selenium/tests/query/QueryScreenTest.java | 22 +- .../selenium/tests/query/SavedViewsTest.java | 6 +- .../fixtures/qqq/v1/metaData/person.json | 168 +++++++++++ .../fixtures/qqq/v1/metaData/savedReport.json | 218 ++++++++++++++ .../fixtures/qqq/v1/metaData/script.json | 139 +++++++++ .../qqq/v1/metaData/scriptRevision.json | 152 ++++++++++ .../qqq/v1/table/audit/query-empty.json | 3 + .../fixtures/qqq/v1/table/audit/query.json | 245 ++++++++++++++++ .../fixtures/qqq/v1/table/city/count.json | 3 + .../fixtures/qqq/v1/table/person/1701.json | 16 + .../fixtures/qqq/v1/table/person/count.json | 3 + .../qqq/v1/table/person/developer.json | 276 ++++++++++++++++++ .../fixtures/qqq/v1/table/person/index.json | 64 ++++ .../person/possibleValues/homeCityId.json | 12 + .../person/possibleValues/homeCityId=1.json | 8 + .../qqq/v1/table/person/variants.json | 1 + .../savedReport/possibleValues/tableName.json | 16 + .../fixtures/qqq/v1/table/script/1.json | 22 ++ .../qqq/v1/table/scriptLog/query.json | 3 + .../qqq/v1/table/scriptRevision/100.json | 36 +++ .../qqq/v1/table/scriptRevision/query.json | 32 ++ .../fixtures/qqq/v1/table/scriptType/1.json | 13 + 27 files changed, 1460 insertions(+), 15 deletions(-) create mode 100644 src/test/resources/fixtures/qqq/v1/metaData/person.json create mode 100644 src/test/resources/fixtures/qqq/v1/metaData/savedReport.json create mode 100644 src/test/resources/fixtures/qqq/v1/metaData/script.json create mode 100644 src/test/resources/fixtures/qqq/v1/metaData/scriptRevision.json create mode 100644 src/test/resources/fixtures/qqq/v1/table/audit/query-empty.json create mode 100644 src/test/resources/fixtures/qqq/v1/table/audit/query.json create mode 100644 src/test/resources/fixtures/qqq/v1/table/city/count.json create mode 100644 src/test/resources/fixtures/qqq/v1/table/person/1701.json create mode 100644 src/test/resources/fixtures/qqq/v1/table/person/count.json create mode 100644 src/test/resources/fixtures/qqq/v1/table/person/developer.json create mode 100644 src/test/resources/fixtures/qqq/v1/table/person/index.json create mode 100644 src/test/resources/fixtures/qqq/v1/table/person/possibleValues/homeCityId.json create mode 100644 src/test/resources/fixtures/qqq/v1/table/person/possibleValues/homeCityId=1.json create mode 100644 src/test/resources/fixtures/qqq/v1/table/person/variants.json create mode 100644 src/test/resources/fixtures/qqq/v1/table/savedReport/possibleValues/tableName.json create mode 100644 src/test/resources/fixtures/qqq/v1/table/script/1.json create mode 100644 src/test/resources/fixtures/qqq/v1/table/scriptLog/query.json create mode 100644 src/test/resources/fixtures/qqq/v1/table/scriptRevision/100.json create mode 100644 src/test/resources/fixtures/qqq/v1/table/scriptRevision/query.json create mode 100644 src/test/resources/fixtures/qqq/v1/table/scriptType/1.json diff --git a/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/AppPageNavTest.java b/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/AppPageNavTest.java index 9d62a96..4ad9eee 100755 --- a/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/AppPageNavTest.java +++ b/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/AppPageNavTest.java @@ -46,7 +46,9 @@ public class AppPageNavTest extends QBaseSeleniumTest .withRouteToString("/widget/QuickSightChartRenderer", """ {"url": "http://www.google.com"}""") .withRouteToFile("/data/person/count", "data/person/count.json") - .withRouteToFile("/data/city/count", "data/city/count.json"); + .withRouteToFile("/data/city/count", "data/city/count.json") + .withRouteToFile("/qqq/v1/table/person/count", "qqq/v1/table/person/count.json") + .withRouteToFile("/qqq/v1/table/city/count", "qqq/v1/table/city/count.json"); } diff --git a/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/BulkEditTest.java b/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/BulkEditTest.java index f52deb5..a2ad6e8 100755 --- a/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/BulkEditTest.java +++ b/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/BulkEditTest.java @@ -63,6 +63,8 @@ public class BulkEditTest extends QBaseSeleniumTest qSeleniumJavalin.withRouteToFile("/data/person/count", "data/person/count.json"); qSeleniumJavalin.withRouteToFile("/data/person/query", "data/person/index.json"); qSeleniumJavalin.withRouteToFile("/data/person/variants", "data/person/variants.json"); + qSeleniumJavalin.withRouteToFile("/qqq/v1/table/person/count", "qqq/v1/table/person/count.json"); + qSeleniumJavalin.withRouteToFile("/qqq/v1/table/person/query", "qqq/v1/table/person/index.json"); qSeleniumJavalin.withRouteToString("/processes/person.bulkEdit/74a03a7d-2f53-4784-9911-3a21f7646c43/records", "[]"); } diff --git a/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/SavedReportTest.java b/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/SavedReportTest.java index 4d493c3..f31e891 100755 --- a/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/SavedReportTest.java +++ b/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/SavedReportTest.java @@ -51,12 +51,15 @@ public class SavedReportTest extends QBaseSeleniumTest super.addJavalinRoutes(qSeleniumJavalin); qSeleniumJavalin .withRouteToFile("/metaData/table/savedReport", "metaData/table/savedReport.json") + .withRouteToFile("/qqq/v1/metaData/table/savedReport", "qqq/v1/metaData/table/savedReport.json") .withRouteToFile("/widget/reportSetupWidget", "widget/reportSetupWidget.json") .withRouteToFile("/widget/pivotTableSetupWidget", "widget/pivotTableSetupWidget.json") .withRouteToFile("/data/savedReport/possibleValues/tableName", "data/savedReport/possibleValues/tableName.json") .withRouteToFile("/data/person/count", "data/person/count.json") .withRouteToFile("/data/person/query", "data/person/index.json") + .withRouteToFile("/qqq/v1/table/person/count", "qqq/v1/table/person/count.json") + .withRouteToFile("/qqq/v1/table/person/query", "qqq/v1/table/person/index.json") ; } @@ -93,8 +96,8 @@ public class SavedReportTest extends QBaseSeleniumTest //////////////////////////////////////////////////// qSeleniumJavalin.beginCapture(); qSeleniumLib.waitForSelectorContaining("button", "Edit Filters and Columns").click(); - qSeleniumJavalin.waitForCapturedPath("/data/person/count"); - qSeleniumJavalin.waitForCapturedPath("/data/person/query"); + qSeleniumJavalin.waitForCapturedPath("/qqq/v1/table/person/count"); + qSeleniumJavalin.waitForCapturedPath("/qqq/v1/table/person/query"); qSeleniumJavalin.endCapture(); QueryScreenLib queryScreenLib = new QueryScreenLib(qSeleniumLib); diff --git a/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/query/QueryScreenFilterInUrlAdvancedModeTest.java b/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/query/QueryScreenFilterInUrlAdvancedModeTest.java index 5cbeea6..eab4e60 100755 --- a/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/query/QueryScreenFilterInUrlAdvancedModeTest.java +++ b/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/query/QueryScreenFilterInUrlAdvancedModeTest.java @@ -53,6 +53,8 @@ public class QueryScreenFilterInUrlAdvancedModeTest extends QBaseSeleniumTest qSeleniumJavalin .withRouteToFile("/data/person/count", "data/person/count.json") .withRouteToFile("/data/person/query", "data/person/index.json") + .withRouteToFile("/qqq/v1/table/person/count", "qqq/v1/table/person/count.json") + .withRouteToFile("/qqq/v1/table/person/query", "qqq/v1/table/person/index.json") .withRouteToFile("/data/person/possibleValues/homeCityId", "data/person/possibleValues/homeCityId.json") .withRouteToFile("/data/person/variants", "data/person/variants.json") .withRouteToFile("/processes/querySavedView/init", "processes/querySavedView/init.json"); diff --git a/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/query/QueryScreenFilterInUrlBasicModeTest.java b/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/query/QueryScreenFilterInUrlBasicModeTest.java index acd6ed2..a0e8781 100755 --- a/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/query/QueryScreenFilterInUrlBasicModeTest.java +++ b/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/query/QueryScreenFilterInUrlBasicModeTest.java @@ -53,6 +53,8 @@ public class QueryScreenFilterInUrlBasicModeTest extends QBaseSeleniumTest qSeleniumJavalin .withRouteToFile("/data/person/count", "data/person/count.json") .withRouteToFile("/data/person/query", "data/person/index.json") + .withRouteToFile("/qqq/v1/table/person/count", "qqq/v1/table/person/count.json") + .withRouteToFile("/qqq/v1/table/person/query", "qqq/v1/table/person/index.json") .withRouteToFile("/data/person/possibleValues/homeCityId", "data/person/possibleValues/homeCityId.json") .withRouteToFile("/data/person/variants", "data/person/variants.json") .withRouteToFile("/processes/querySavedView/init", "processes/querySavedView/init.json"); diff --git a/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/query/QueryScreenTest.java b/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/query/QueryScreenTest.java index 0cb7ce4..9c8d986 100755 --- a/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/query/QueryScreenTest.java +++ b/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/query/QueryScreenTest.java @@ -48,6 +48,8 @@ public class QueryScreenTest extends QBaseSeleniumTest qSeleniumJavalin .withRouteToFile("/data/person/count", "data/person/count.json") .withRouteToFile("/data/person/query", "data/person/index.json") + .withRouteToFile("/qqq/v1/table/person/count", "qqq/v1/table/person/count.json") + .withRouteToFile("/qqq/v1/table/person/query", "qqq/v1/table/person/index.json") .withRouteToFile("/data/person/variants", "data/person/variants.json") .withRouteToFile("/data/person/possibleValues/homeCityId", "data/person/possibleValues/homeCityId.json") .withRouteToFile("/processes/querySavedView/init", "processes/querySavedView/init.json"); @@ -79,8 +81,8 @@ public class QueryScreenTest extends QBaseSeleniumTest /////////////////////////////////////////////////////////////////// String idEquals1FilterSubstring = """ {"fieldName":"id","operator":"EQUALS","values":["1"]}"""; - qSeleniumJavalin.waitForCapturedPathWithBodyContaining("/data/person/count", idEquals1FilterSubstring); - qSeleniumJavalin.waitForCapturedPathWithBodyContaining("/data/person/query", idEquals1FilterSubstring); + qSeleniumJavalin.waitForCapturedPathWithBodyContaining("/qqq/v1/table/person/count", idEquals1FilterSubstring); + qSeleniumJavalin.waitForCapturedPathWithBodyContaining("/qqq/v1/table/person/query", idEquals1FilterSubstring); qSeleniumJavalin.endCapture(); /////////////////////////////////////// @@ -99,8 +101,8 @@ public class QueryScreenTest extends QBaseSeleniumTest //////////////////////////////////////////////////////////////////// // assert that query & count both no longer have the filter value // //////////////////////////////////////////////////////////////////// - CapturedContext capturedCount = qSeleniumJavalin.waitForCapturedPath("/data/person/count"); - CapturedContext capturedQuery = qSeleniumJavalin.waitForCapturedPath("/data/person/query"); + CapturedContext capturedCount = qSeleniumJavalin.waitForCapturedPath("/qqq/v1/table/person/count"); + CapturedContext capturedQuery = qSeleniumJavalin.waitForCapturedPath("/qqq/v1/table/person/query"); assertThat(capturedCount).extracting("body").asString().doesNotContain(idEquals1FilterSubstring); assertThat(capturedQuery).extracting("body").asString().doesNotContain(idEquals1FilterSubstring); qSeleniumJavalin.endCapture(); @@ -132,9 +134,9 @@ public class QueryScreenTest extends QBaseSeleniumTest String expectedFilterContents2 = """ "booleanOperator":"OR\""""; - qSeleniumJavalin.waitForCapturedPathWithBodyContaining("/data/person/query", expectedFilterContents0); - qSeleniumJavalin.waitForCapturedPathWithBodyContaining("/data/person/query", expectedFilterContents1); - qSeleniumJavalin.waitForCapturedPathWithBodyContaining("/data/person/query", expectedFilterContents2); + qSeleniumJavalin.waitForCapturedPathWithBodyContaining("/qqq/v1/table/person/query", expectedFilterContents0); + qSeleniumJavalin.waitForCapturedPathWithBodyContaining("/qqq/v1/table/person/query", expectedFilterContents1); + qSeleniumJavalin.waitForCapturedPathWithBodyContaining("/qqq/v1/table/person/query", expectedFilterContents2); qSeleniumJavalin.endCapture(); } @@ -208,7 +210,7 @@ public class QueryScreenTest extends QBaseSeleniumTest qSeleniumJavalin.beginCapture(); queryScreenLib.setBasicBooleanFilter(fieldLabel, operatorLabel); queryScreenLib.waitForBasicFilterButtonMatchingRegex(expectButtonStringRegex); - qSeleniumJavalin.waitForCapturedPathWithBodyContaining("/data/person/query", expectFilterJsonContains); + qSeleniumJavalin.waitForCapturedPathWithBodyContaining("/qqq/v1/table/person/query", expectFilterJsonContains); qSeleniumJavalin.endCapture(); } @@ -222,7 +224,7 @@ public class QueryScreenTest extends QBaseSeleniumTest qSeleniumJavalin.beginCapture(); queryScreenLib.setBasicFilterPossibleValues(fieldLabel, operatorLabel, values); queryScreenLib.waitForBasicFilterButtonMatchingRegex(expectButtonStringRegex); - qSeleniumJavalin.waitForCapturedPathWithBodyContaining("/data/person/query", expectFilterJsonContains); + qSeleniumJavalin.waitForCapturedPathWithBodyContaining("/qqq/v1/table/person/query", expectFilterJsonContains); qSeleniumJavalin.endCapture(); } @@ -268,7 +270,7 @@ public class QueryScreenTest extends QBaseSeleniumTest queryScreenLib.addAdvancedQueryFilterInput(0, fieldLabel, operatorLabel, value, null); qSeleniumLib.clickBackdrop(); queryScreenLib.waitForAdvancedQueryStringMatchingRegex(expectQueryStringRegex); - qSeleniumJavalin.waitForCapturedPathWithBodyContaining("/data/person/query", expectFilterJsonContains); + qSeleniumJavalin.waitForCapturedPathWithBodyContaining("/qqq/v1/table/person/query", expectFilterJsonContains); qSeleniumJavalin.endCapture(); queryScreenLib.clickAdvancedFilterClearIcon(); } diff --git a/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/query/SavedViewsTest.java b/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/query/SavedViewsTest.java index 17c37fb..d60213d 100755 --- a/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/query/SavedViewsTest.java +++ b/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/tests/query/SavedViewsTest.java @@ -58,6 +58,8 @@ public class SavedViewsTest extends QBaseSeleniumTest super.addJavalinRoutes(qSeleniumJavalin); qSeleniumJavalin.withRouteToFile("/data/person/count", "data/person/count.json"); qSeleniumJavalin.withRouteToFile("/data/person/query", "data/person/index.json"); + qSeleniumJavalin.withRouteToFile("/qqq/v1/table/person/count", "qqq/v1/table/person/count.json"); + qSeleniumJavalin.withRouteToFile("/qqq/v1/table/person/query", "qqq/v1/table/person/index.json"); qSeleniumJavalin.withRouteToFile("/data/person/*", "data/person/1701.json"); } @@ -135,7 +137,7 @@ public class SavedViewsTest extends QBaseSeleniumTest qSeleniumLib.waitForCondition("Current URL should have filter id", () -> driver.getCurrentUrl().endsWith("/person/savedView/2")); queryScreenLib.assertSavedViewNameOnScreen("Some People"); qSeleniumLib.waitForSelectorContaining("DIV", "Unsaved Changes"); - CapturedContext capturedContext = qSeleniumJavalin.waitForCapturedPath("/data/person/query"); + CapturedContext capturedContext = qSeleniumJavalin.waitForCapturedPath("/qqq/v1/table/person/query"); assertTrue(capturedContext.getBody().contains("Kelkhoff")); qSeleniumJavalin.endCapture(); @@ -162,7 +164,7 @@ public class SavedViewsTest extends QBaseSeleniumTest qSeleniumLib.waitForSelectorContaining("A", "Person").click(); qSeleniumLib.waitForCondition("Current URL should not have filter id", () -> !driver.getCurrentUrl().endsWith("/person/savedView/2")); qSeleniumLib.waitForSelectorContaining("BUTTON", "Save View As"); - capturedContext = qSeleniumJavalin.waitForCapturedPath("/data/person/query"); + capturedContext = qSeleniumJavalin.waitForCapturedPath("/qqq/v1/table/person/query"); assertTrue(capturedContext.getBody().matches("(?s).*id.*LESS_THAN.*10.*")); qSeleniumJavalin.endCapture(); } diff --git a/src/test/resources/fixtures/qqq/v1/metaData/person.json b/src/test/resources/fixtures/qqq/v1/metaData/person.json new file mode 100644 index 0000000..3a3e00c --- /dev/null +++ b/src/test/resources/fixtures/qqq/v1/metaData/person.json @@ -0,0 +1,168 @@ +{ + "table": { + "name": "person", + "label": "Person", + "isHidden": false, + "primaryKeyField": "id", + "iconName": "person", + "deletePermission": true, + "editPermission": true, + "insertPermission": true, + "readPermission": true, + "fields": { + "firstName": { + "name": "firstName", + "label": "First Name", + "type": "STRING", + "isRequired": true, + "isEditable": true, + "displayFormat": "%s" + }, + "lastName": { + "name": "lastName", + "label": "Last Name", + "type": "STRING", + "isRequired": true, + "isEditable": true, + "displayFormat": "%s" + }, + "annualSalary": { + "name": "annualSalary", + "label": "Annual Salary", + "type": "DECIMAL", + "isRequired": false, + "isEditable": true, + "displayFormat": "$%,.2f" + }, + "modifyDate": { + "name": "modifyDate", + "label": "Modify Date", + "type": "DATE_TIME", + "isRequired": false, + "isEditable": false, + "displayFormat": "%s" + }, + "daysWorked": { + "name": "daysWorked", + "label": "Days Worked", + "type": "INTEGER", + "isRequired": false, + "isEditable": true, + "displayFormat": "%,d" + }, + "id": { + "name": "id", + "label": "Id", + "type": "INTEGER", + "isRequired": false, + "isEditable": false, + "displayFormat": "%s" + }, + "birthDate": { + "name": "birthDate", + "label": "Birth Date", + "type": "DATE", + "isRequired": false, + "isEditable": true, + "displayFormat": "%s" + }, + "isEmployed": { + "name": "isEmployed", + "label": "Is Employed", + "type": "BOOLEAN", + "isRequired": false, + "isEditable": true, + "displayFormat": "%s" + }, + "homeCityId": { + "name": "homeCityId", + "label": "Home City", + "type": "INTEGER", + "possibleValueSourceName": "city", + "isRequired": false, + "isEditable": true, + "displayFormat": "%s" + }, + "email": { + "name": "email", + "label": "Email", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "displayFormat": "%s" + }, + "createDate": { + "name": "createDate", + "label": "Create Date", + "type": "DATE_TIME", + "isRequired": false, + "isEditable": false, + "displayFormat": "%s" + } + }, + "sections": [ + { + "name": "identity", + "label": "Identity", + "tier": "T1", + "fieldNames": [ + "id", + "firstName", + "lastName" + ], + "icon": { + "name": "badge" + }, + "isHidden": false + }, + { + "name": "basicInfo", + "label": "Basic Info", + "tier": "T2", + "fieldNames": [ + "email", + "birthDate" + ], + "icon": { + "name": "dataset" + }, + "isHidden": false + }, + { + "name": "employmentInfo", + "label": "Employment Info", + "tier": "T2", + "fieldNames": [ + "isEmployed", + "annualSalary", + "daysWorked" + ], + "icon": { + "name": "work" + }, + "isHidden": false + }, + { + "name": "dates", + "label": "Dates", + "tier": "T3", + "fieldNames": [ + "createDate", + "modifyDate" + ], + "icon": { + "name": "calendar_month" + }, + "isHidden": false + } + ], + "capabilities": [ + "TABLE_COUNT", + "TABLE_GET", + "TABLE_QUERY", + "TABLE_DELETE", + "TABLE_INSERT", + "TABLE_UPDATE" + ] + } +} \ No newline at end of file diff --git a/src/test/resources/fixtures/qqq/v1/metaData/savedReport.json b/src/test/resources/fixtures/qqq/v1/metaData/savedReport.json new file mode 100644 index 0000000..71e256b --- /dev/null +++ b/src/test/resources/fixtures/qqq/v1/metaData/savedReport.json @@ -0,0 +1,218 @@ +{ + "table": { + "name": "savedReport", + "label": "Saved Report", + "isHidden": false, + "primaryKeyField": "id", + "iconName": "article", + "fields": { + "queryFilterJson": { + "name": "queryFilterJson", + "label": "Query Filter", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + "columnsJson": { + "name": "columnsJson", + "label": "Columns", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + "inputFieldsJson": { + "name": "inputFieldsJson", + "label": "Input Fields", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + "pivotTableJson": { + "name": "pivotTableJson", + "label": "Pivot Table", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + "modifyDate": { + "name": "modifyDate", + "label": "Modify Date", + "type": "DATE_TIME", + "isRequired": false, + "isEditable": false, + "isHeavy": false, + "displayFormat": "%s" + }, + "label": { + "name": "label", + "label": "Report Name", + "type": "STRING", + "isRequired": true, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + "id": { + "name": "id", + "label": "Id", + "type": "INTEGER", + "isRequired": false, + "isEditable": false, + "isHeavy": false, + "displayFormat": "%s" + }, + "userId": { + "name": "userId", + "label": "User Id", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + "tableName": { + "name": "tableName", + "label": "Table", + "type": "STRING", + "isRequired": true, + "isEditable": true, + "isHeavy": false, + "possibleValueSourceName": "tables", + "displayFormat": "%s" + }, + "createDate": { + "name": "createDate", + "label": "Create Date", + "type": "DATE_TIME", + "isRequired": false, + "isEditable": false, + "isHeavy": false, + "displayFormat": "%s" + } + }, + "sections": [ + { + "name": "identity", + "label": "Identity", + "tier": "T1", + "fieldNames": [ + "id", + "label", + "tableName" + ], + "icon": { + "name": "badge" + }, + "isHidden": false + }, + { + "name": "filtersAndColumns", + "label": "Filters and Columns", + "tier": "T2", + "widgetName": "reportSetupWidget", + "icon": { + "name": "table_chart" + }, + "isHidden": false + }, + { + "name": "pivotTable", + "label": "Pivot Table", + "tier": "T2", + "widgetName": "pivotTableSetupWidget", + "icon": { + "name": "pivot_table_chart" + }, + "isHidden": false + }, + { + "name": "data", + "label": "Data", + "tier": "T2", + "fieldNames": [ + "queryFilterJson", + "columnsJson", + "pivotTableJson" + ], + "icon": { + "name": "text_snippet" + }, + "isHidden": true + }, + { + "name": "hidden", + "label": "Hidden", + "tier": "T2", + "fieldNames": [ + "inputFieldsJson", + "userId" + ], + "icon": { + "name": "text_snippet" + }, + "isHidden": true + }, + { + "name": "dates", + "label": "Dates", + "tier": "T3", + "fieldNames": [ + "createDate", + "modifyDate" + ], + "icon": { + "name": "calendar_month" + }, + "isHidden": false + } + ], + "exposedJoins": [], + "supplementalTableMetaData": { + "materialDashboard": { + "fieldRules": [ + { + "trigger": "ON_CHANGE", + "sourceField": "tableName", + "action": "CLEAR_TARGET_FIELD", + "targetField": "queryFilterJson" + }, + { + "trigger": "ON_CHANGE", + "sourceField": "tableName", + "action": "CLEAR_TARGET_FIELD", + "targetField": "columnsJson" + }, + { + "trigger": "ON_CHANGE", + "sourceField": "tableName", + "action": "CLEAR_TARGET_FIELD", + "targetField": "pivotTableJson" + } + ], + "type": "materialDashboard" + } + }, + "capabilities": [ + "TABLE_COUNT", + "TABLE_GET", + "TABLE_QUERY", + "QUERY_STATS", + "TABLE_UPDATE", + "TABLE_INSERT", + "TABLE_DELETE" + ], + "readPermission": true, + "insertPermission": true, + "editPermission": true, + "deletePermission": true, + "usesVariants": false + } +} \ No newline at end of file diff --git a/src/test/resources/fixtures/qqq/v1/metaData/script.json b/src/test/resources/fixtures/qqq/v1/metaData/script.json new file mode 100644 index 0000000..e4b664f --- /dev/null +++ b/src/test/resources/fixtures/qqq/v1/metaData/script.json @@ -0,0 +1,139 @@ +{ + "table": { + "name": "script", + "label": "Script", + "isHidden": false, + "primaryKeyField": "id", + "iconName": "data_object", + "fields": { + "modifyDate": { + "name": "modifyDate", + "label": "Modify Date", + "type": "DATE_TIME", + "isRequired": false, + "isEditable": false, + "displayFormat": "%s" + }, + "name": { + "name": "name", + "label": "Name", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "displayFormat": "%s" + }, + "currentScriptRevisionId": { + "name": "currentScriptRevisionId", + "label": "Current Script Revision", + "type": "INTEGER", + "isRequired": false, + "isEditable": true, + "possibleValueSourceName": "scriptRevision", + "displayFormat": "%s", + "adornments": [ + { + "type": "LINK", + "values": { + "toRecordFromTable": "scriptRevision" + } + } + ] + }, + "id": { + "name": "id", + "label": "Id", + "type": "INTEGER", + "isRequired": false, + "isEditable": false, + "displayFormat": "%s" + }, + "tableName": { + "name": "tableName", + "label": "Table Name", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "possibleValueSourceName": "tables", + "displayFormat": "%s" + }, + "createDate": { + "name": "createDate", + "label": "Create Date", + "type": "DATE_TIME", + "isRequired": false, + "isEditable": false, + "displayFormat": "%s" + }, + "scriptTypeId": { + "name": "scriptTypeId", + "label": "Script Type", + "type": "INTEGER", + "isRequired": false, + "isEditable": true, + "possibleValueSourceName": "scriptType", + "displayFormat": "%s", + "adornments": [ + { + "type": "LINK", + "values": { + "toRecordFromTable": "scriptType" + } + } + ] + } + }, + "sections": [ + { + "name": "identity", + "label": "Identity", + "tier": "T1", + "fieldNames": [ + "id", + "name", + "scriptTypeId", + "tableName", + "currentScriptRevisionId" + ], + "icon": { + "name": "badge" + }, + "isHidden": false + }, + { + "name": "contents", + "label": "Contents", + "tier": "T2", + "widgetName": "scriptViewer", + "icon": { + "name": "data_object" + }, + "isHidden": false + }, + { + "name": "dates", + "label": "Dates", + "tier": "T3", + "fieldNames": [ + "createDate", + "modifyDate" + ], + "icon": { + "name": "calendar_month" + }, + "isHidden": false + } + ], + "capabilities": [ + "TABLE_COUNT", + "TABLE_GET", + "TABLE_QUERY", + "TABLE_INSERT", + "TABLE_DELETE", + "TABLE_UPDATE" + ], + "readPermission": true, + "insertPermission": true, + "editPermission": true, + "deletePermission": true + } +} \ No newline at end of file diff --git a/src/test/resources/fixtures/qqq/v1/metaData/scriptRevision.json b/src/test/resources/fixtures/qqq/v1/metaData/scriptRevision.json new file mode 100644 index 0000000..78440da --- /dev/null +++ b/src/test/resources/fixtures/qqq/v1/metaData/scriptRevision.json @@ -0,0 +1,152 @@ +{ + "table": { + "name": "scriptRevision", + "label": "Script Revision", + "isHidden": false, + "primaryKeyField": "id", + "iconName": "history_edu", + "fields": { + "scriptId": { + "name": "scriptId", + "label": "Script", + "type": "INTEGER", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "possibleValueSourceName": "script", + "displayFormat": "%s", + "adornments": [ + { + "type": "SIZE", + "values": { + "width": "large" + } + }, + { + "type": "LINK", + "values": { + "toRecordFromTable": "script" + } + } + ] + }, + "apiName": { + "name": "apiName", + "label": "API Name", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "possibleValueSourceName": "apiName", + "displayFormat": "%s" + }, + "sequenceNo": { + "name": "sequenceNo", + "label": "Sequence No", + "type": "INTEGER", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + "apiVersion": { + "name": "apiVersion", + "label": "API Version", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "possibleValueSourceName": "apiVersion", + "displayFormat": "%s" + }, + "commitMessage": { + "name": "commitMessage", + "label": "Commit Message", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + "modifyDate": { + "name": "modifyDate", + "label": "Modify Date", + "type": "DATE_TIME", + "isRequired": false, + "isEditable": false, + "isHeavy": false, + "displayFormat": "%s" + }, + "author": { + "name": "author", + "label": "Author", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + "id": { + "name": "id", + "label": "Id", + "type": "INTEGER", + "isRequired": false, + "isEditable": false, + "isHeavy": false, + "displayFormat": "%s" + }, + "createDate": { + "name": "createDate", + "label": "Create Date", + "type": "DATE_TIME", + "isRequired": false, + "isEditable": false, + "isHeavy": false, + "displayFormat": "%s" + } + }, + "sections": [ + { + "name": "identity", + "label": "Identity", + "tier": "T1", + "fieldNames": [ + "id", + "scriptId", + "sequenceNo" + ], + "icon": { + "name": "badge" + }, + "isHidden": false + }, + { + "name": "dates", + "label": "Dates", + "tier": "T3", + "fieldNames": [ + "createDate", + "modifyDate" + ], + "icon": { + "name": "calendar_month" + }, + "isHidden": false + } + ], + "exposedJoins": [], + "capabilities": [ + "TABLE_COUNT", + "TABLE_GET", + "TABLE_QUERY", + "TABLE_INSERT", + "TABLE_UPDATE", + "QUERY_STATS" + ], + "readPermission": true, + "insertPermission": true, + "editPermission": true, + "deletePermission": true, + "usesVariants": false + } +} diff --git a/src/test/resources/fixtures/qqq/v1/table/audit/query-empty.json b/src/test/resources/fixtures/qqq/v1/table/audit/query-empty.json new file mode 100644 index 0000000..efde9b0 --- /dev/null +++ b/src/test/resources/fixtures/qqq/v1/table/audit/query-empty.json @@ -0,0 +1,3 @@ +{ + "records": [] +} diff --git a/src/test/resources/fixtures/qqq/v1/table/audit/query.json b/src/test/resources/fixtures/qqq/v1/table/audit/query.json new file mode 100644 index 0000000..f40206f --- /dev/null +++ b/src/test/resources/fixtures/qqq/v1/table/audit/query.json @@ -0,0 +1,245 @@ +{ + "records": [ + { + "tableName": "audit", + "recordLabel": "Parcel 1191682", + "values": { + "id": 623577, + "auditTableId": 4, + "auditUserId": 2, + "recordId": 1191682, + "message": "Record was Inserted", + "timestamp": "2023-02-17T14:11:16Z", + "clientId": 107, + "auditDetail.id": 278660, + "auditDetail.auditId": 623577, + "auditDetail.message": "Set First Name to John", + "auditDetail.fieldName": "firstName", + "auditDetail.newValue": "John" + }, + "displayValues": { + "auditTableId": "Parcel", + "auditUserId": "QQQ User", + "clientId": "ACME", + "id": "623577", + "recordId": "1191682", + "message": "Record was Inserted", + "timestamp": "2023-02-17T14:11:16Z" + } + }, + { + "tableName": "audit", + "recordLabel": "Parcel 1191682", + "values": { + "id": 623577, + "auditTableId": 4, + "auditUserId": 2, + "recordId": 1191682, + "message": "Record was Inserted", + "timestamp": "2023-02-17T14:11:16Z", + "clientId": 107, + "auditDetail.id": 278661, + "auditDetail.auditId": 623577, + "auditDetail.message": "Removed Doe from Last Name", + "auditDetail.fieldName": "lastName", + "auditDetail.oldValue": "Doe" + }, + "displayValues": { + "auditTableId": "Parcel", + "auditUserId": "QQQ User", + "clientId": "ACME", + "id": "623577", + "recordId": "1191682", + "message": "Record was Inserted", + "timestamp": "2023-02-17T14:11:16Z" + } + }, + { + "tableName": "audit", + "recordLabel": "Parcel 1191682", + "values": { + "id": 623577, + "auditTableId": 4, + "auditUserId": 2, + "recordId": 1191682, + "message": "Record was Inserted", + "timestamp": "2023-02-17T14:11:16Z", + "clientId": 107, + "auditDetail.id": 278662, + "auditDetail.auditId": 623577, + "auditDetail.message": "Set Client to ACME", + "auditDetail.fieldName": "clientId", + "auditDetail.oldValue": "BetaMax", + "auditDetail.newValue": "ACME" + }, + "displayValues": { + "auditTableId": "Parcel", + "auditUserId": "QQQ User", + "clientId": "ACME", + "id": "623577", + "recordId": "1191682", + "message": "Record was Inserted", + "timestamp": "2023-02-17T14:11:16Z" + } + }, + { + "tableName": "audit", + "recordLabel": "Parcel 1191682", + "values": { + "id": 624804, + "auditTableId": 4, + "auditUserId": 2, + "recordId": 1191682, + "message": "Record was Edited", + "timestamp": "2023-02-17T14:13:16Z", + "clientId": 107, + "auditDetail.id": 278990, + "auditDetail.auditId": 624804, + "auditDetail.message": "Set SLA Expected Service Days to 2", + "auditDetail.fieldName": "slaExpectedServiceDays", + "auditDetail.newValue": "2" + }, + "displayValues": { + "auditTableId": "Parcel", + "auditUserId": "QQQ User", + "clientId": "ACME", + "id": "624804", + "recordId": "1191682", + "message": "Record was Edited", + "timestamp": "2023-02-17T14:13:16Z" + } + }, + { + "tableName": "audit", + "recordLabel": "Parcel 1191682", + "values": { + "id": 624804, + "auditTableId": 4, + "auditUserId": 2, + "recordId": 1191682, + "message": "Record was Edited", + "timestamp": "2023-02-17T14:13:16Z", + "clientId": 107, + "auditDetail.id": 278991, + "auditDetail.auditId": 624804, + "auditDetail.message": "Set SLA Status to \"Pending\"", + "auditDetail.fieldName": "slaStatusId", + "auditDetail.newValue": "Pending" + }, + "displayValues": { + "auditTableId": "Parcel", + "auditUserId": "QQQ User", + "clientId": "ACME", + "id": "624804", + "recordId": "1191682", + "message": "Record was Edited", + "timestamp": "2023-02-17T14:13:16Z" + } + }, + { + "tableName": "audit", + "recordLabel": "Parcel 1191682", + "values": { + "id": 624809, + "auditTableId": 4, + "auditUserId": 2, + "recordId": 1191682, + "message": "Audit message here", + "timestamp": "2023-02-17T14:13:16Z", + "clientId": 107, + "auditDetail.id": 279000, + "auditDetail.auditId": 624809, + "auditDetail.message": "This is a detail message" + }, + "displayValues": { + "auditTableId": "Parcel", + "auditUserId": "QQQ User", + "clientId": "ACME", + "id": "624809", + "recordId": "1191682", + "message": "Audit message here", + "timestamp": "2023-02-17T14:13:16Z" + } + }, + { + "tableName": "audit", + "recordLabel": "Parcel 1191682", + "values": { + "id": 737694, + "auditTableId": 4, + "auditUserId": 2, + "recordId": 1191682, + "message": "Record was Edited", + "timestamp": "2023-02-17T17:22:08Z", + "clientId": 107, + "auditDetail.id": 299222, + "auditDetail.auditId": 737694, + "auditDetail.message": "Set Estimated Delivery Date Time to 2023-02-18 07:00:00 PM EST", + "auditDetail.fieldName": "estimatedDeliveryDateTime", + "auditDetail.newValue": "2023-02-18 07:00:00 PM EST" + }, + "displayValues": { + "auditTableId": "Parcel", + "auditUserId": "QQQ User", + "clientId": "ACME", + "id": "737694", + "recordId": "1191682", + "message": "Record was Edited", + "timestamp": "2023-02-17T17:22:08Z" + } + }, + { + "tableName": "audit", + "recordLabel": "Parcel 1191682", + "values": { + "id": 737694, + "auditTableId": 4, + "auditUserId": 2, + "recordId": 1191682, + "message": "Record was Edited", + "timestamp": "2023-02-17T17:22:08Z", + "clientId": 107, + "auditDetail.id": 299223, + "auditDetail.auditId": 737694, + "auditDetail.message": "Changed Parcel Tracking Status from \"Unknown\" to \"Pre Transit\"", + "auditDetail.fieldName": "parcelTrackingStatusId", + "auditDetail.oldValue": "Unknown", + "auditDetail.newValue": "Pre Transit" + }, + "displayValues": { + "auditTableId": "Parcel", + "auditUserId": "QQQ User", + "clientId": "ACME", + "id": "737694", + "recordId": "1191682", + "message": "Record was Edited", + "timestamp": "2023-02-17T17:22:08Z" + } + }, + { + "tableName": "audit", + "recordLabel": "Parcel 1191682", + "values": { + "id": 737695, + "auditTableId": 4, + "auditUserId": 2, + "recordId": 1191682, + "message": "Updating Parcel based on updated tracking details", + "timestamp": "2023-02-17T17:22:09Z", + "clientId": 107, + "auditDetail.id": 299224, + "auditDetail.auditId": 737695, + "auditDetail.message": "Set Parcel Tracking Status to Pre Transit based on most recent tracking update: Shipment information sent to FedEx" + }, + "displayValues": { + "auditTableId": "Parcel", + "auditUserId": "QQQ User", + "clientId": "ACME", + "id": "737695", + "recordId": "1191682", + "message": "Updating Parcel based on updated tracking details", + "timestamp": "2023-02-17T17:22:09Z" + } + } + ] +} \ No newline at end of file diff --git a/src/test/resources/fixtures/qqq/v1/table/city/count.json b/src/test/resources/fixtures/qqq/v1/table/city/count.json new file mode 100644 index 0000000..92fba2e --- /dev/null +++ b/src/test/resources/fixtures/qqq/v1/table/city/count.json @@ -0,0 +1,3 @@ +{ + "count": 101406 +} diff --git a/src/test/resources/fixtures/qqq/v1/table/person/1701.json b/src/test/resources/fixtures/qqq/v1/table/person/1701.json new file mode 100644 index 0000000..54ceece --- /dev/null +++ b/src/test/resources/fixtures/qqq/v1/table/person/1701.json @@ -0,0 +1,16 @@ +{ + "tableName": "person", + "recordLabel": "John Doe", + "values": { + "name": "John Doe", + "id": 1710, + "createDate": "2022-08-30T00:31:00Z", + "modifyDate": "2022-08-30T00:31:00Z" + }, + "displayValues": { + "name": "John Doe", + "id": 1710, + "createDate": "2022-08-30T00:31:00Z", + "modifyDate": "2022-08-30T00:31:00Z" + } +} \ No newline at end of file diff --git a/src/test/resources/fixtures/qqq/v1/table/person/count.json b/src/test/resources/fixtures/qqq/v1/table/person/count.json new file mode 100644 index 0000000..92fba2e --- /dev/null +++ b/src/test/resources/fixtures/qqq/v1/table/person/count.json @@ -0,0 +1,3 @@ +{ + "count": 101406 +} diff --git a/src/test/resources/fixtures/qqq/v1/table/person/developer.json b/src/test/resources/fixtures/qqq/v1/table/person/developer.json new file mode 100644 index 0000000..3190cfe --- /dev/null +++ b/src/test/resources/fixtures/qqq/v1/table/person/developer.json @@ -0,0 +1,276 @@ +{ + "record": { + "tableName": "client", + "recordLabel": "John Doe", + "values": { + "name": "John Doe", + "id": 120, + "deposcoOrderOptimizationCoolingScriptId": 2, + "createDate": "2022-08-30T00:31:00Z", + "modifyDate": "2023-02-19T01:28:30Z", + "isFulfillmentCenter": false, + "infoplusLobId": 18698, + "deposcoBusinessUnitName": "TRIFECTA", + "deposcoBusinessUnitId": 77, + "optimizationConfigId": 1, + "nfCode": "Client 224" + }, + "displayValues": { + "optimizationConfigId": "Client: 120", + "name": "John Doe", + "id": "120", + "deposcoOrderOptimizationCoolingScriptId": "2", + "createDate": "2022-08-30T00:31:00Z", + "modifyDate": "2023-02-19T01:28:30Z", + "isFulfillmentCenter": "No", + "infoplusLobId": "18698", + "deposcoBusinessUnitName": "TRIFECTA", + "deposcoBusinessUnitId": "77", + "nfCode": "Client 224" + } + }, + "associatedScripts": [ + { + "testInputFields": [ + { + "name": "selectedTimeInTransitDays", + "label": "Selected Time In Transit Days", + "type": "INTEGER", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + { + "name": "standardTimeInTransitDays", + "label": "Standard Time In Transit Days", + "type": "INTEGER", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + } + ], + "scriptType": { + "tableName": "scriptType", + "values": { + "name": "Deposco Order Optimization Cooling", + "id": 2, + "createDate": "2022-10-31T19:06:50Z", + "modifyDate": "2022-10-31T19:06:50Z" + } + }, + "scriptRevisions": [ + { + "tableName": "scriptRevision", + "values": { + "id": 1, + "contents": "1;", + "createDate": "2023-02-19T01:28:30Z", + "modifyDate": "2023-02-19T01:28:30Z", + "scriptId": 2, + "sequenceNo": 1, + "commitMessage": "Initial version", + "author": "Darin Kelkhoff" + } + } + ], + "testOutputFields": [ + { + "name": "sku", + "label": "Sku", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + { + "name": "quantityPerCarton", + "label": "Quantity Per Carton", + "type": "INTEGER", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + { + "name": "useClientProvidedCoolingSolution", + "label": "Use Client Provided Cooling Solution", + "type": "BOOLEAN", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + { + "name": "reason", + "label": "Reason", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + } + ], + "script": { + "tableName": "script", + "values": { + "name": "John Doe - Deposco Order Optimization Cooling", + "id": 2, + "scriptTypeId": 2, + "createDate": "2023-02-19T01:28:30Z", + "modifyDate": "2023-02-19T01:28:30Z", + "currentScriptRevisionId": 1 + } + }, + "associatedScript": { + "fieldName": "deposcoOrderOptimizationCoolingScriptId", + "scriptTypeId": 2, + "scriptTester": { + "name": "com.coldtrack.live.processes.deposco.RunDeposcoOrderOptimizationCoolingScript", + "codeType": "JAVA", + "codeUsage": "SCRIPT_TESTER" + } + } + }, + { + "testInputFields": [ + { + "name": "selectedTimeInTransitDays", + "label": "Selected Time In Transit Days", + "type": "INTEGER", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + { + "name": "standardTimeInTransitDays", + "label": "Standard Time In Transit Days", + "type": "INTEGER", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + { + "name": "runtimeWeekday", + "label": "Runtime Weekday", + "type": "INTEGER", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + } + ], + "scriptType": { + "tableName": "scriptType", + "values": { + "name": "Deposco Order Optimization Batch Name", + "id": 1, + "createDate": "2022-10-31T19:06:50Z", + "modifyDate": "2022-10-31T19:06:50Z" + } + }, + "testOutputFields": [ + { + "name": "batchName", + "label": "Batch Name", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + { + "name": "reason", + "label": "Reason", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + } + ], + "associatedScript": { + "fieldName": "deposcoOrderOptimizationBatchNameScriptId", + "scriptTypeId": 1, + "scriptTester": { + "name": "com.coldtrack.live.processes.deposco.RunDeposcoOrderOptimizationBatchNameScript", + "codeType": "JAVA", + "codeUsage": "SCRIPT_TESTER" + } + } + }, + { + "testInputFields": [ + { + "name": "selectedTimeInTransitDays", + "label": "Selected Time In Transit Days", + "type": "INTEGER", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + { + "name": "standardTimeInTransitDays", + "label": "Standard Time In Transit Days", + "type": "INTEGER", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + { + "name": "runtimeWeekday", + "label": "Runtime Weekday", + "type": "INTEGER", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + } + ], + "scriptType": { + "tableName": "scriptType", + "values": { + "name": "Deposco Order Optimization Batch Name", + "id": 1, + "createDate": "2022-10-31T19:06:50Z", + "modifyDate": "2022-10-31T19:06:50Z" + } + }, + "testOutputFields": [ + { + "name": "batchName", + "label": "Batch Name", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + { + "name": "reason", + "label": "Reason", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + } + ], + "associatedScript": { + "fieldName": "deposcoOrderOptimizationCartonizationScriptId", + "scriptTypeId": 1, + "scriptTester": { + "name": "com.coldtrack.live.processes.deposco.RunDeposcoOrderOptimizationBatchNameScript", + "codeType": "JAVA", + "codeUsage": "SCRIPT_TESTER" + } + } + } + ] +} diff --git a/src/test/resources/fixtures/qqq/v1/table/person/index.json b/src/test/resources/fixtures/qqq/v1/table/person/index.json new file mode 100644 index 0000000..2b3c655 --- /dev/null +++ b/src/test/resources/fixtures/qqq/v1/table/person/index.json @@ -0,0 +1,64 @@ +{ + "records": [ + { + "tableName": "person", + "values": { + "id": 1, + "createDate": "2022-07-23T00:17:00", + "modifyDate": "2022-07-22T19:17:06", + "firstName": "Jonny", + "lastName": "Doe", + "birthDate": "1980-05-31", + "email": "jdoe@kingsrook.com" + } + }, + { + "tableName": "person", + "values": { + "id": 2, + "createDate": "2022-07-23T00:17:00", + "modifyDate": "2022-07-23T00:17:00", + "firstName": "James", + "lastName": "Maes", + "birthDate": "1980-05-15", + "email": "jmaes@mmltholdings.com" + } + }, + { + "tableName": "person", + "values": { + "id": 3, + "createDate": "2022-07-23T00:17:00", + "modifyDate": "2022-07-23T00:17:00", + "firstName": "Tim", + "lastName": "Chamberlain", + "birthDate": "1976-05-28", + "email": "tchamberlain@mmltholdings.com" + } + }, + { + "tableName": "person", + "values": { + "id": 4, + "createDate": "2022-07-23T00:17:00", + "modifyDate": "2022-07-23T00:17:00", + "firstName": "Tyler", + "lastName": "Samples", + "birthDate": "1986-05-28", + "email": "tsamples@mmltholdings.com" + } + }, + { + "tableName": "person", + "values": { + "id": 5, + "createDate": "2022-07-23T00:17:00", + "modifyDate": "2022-07-23T00:17:00", + "firstName": "Garret", + "lastName": "Richardson", + "birthDate": "1981-01-01", + "email": "grichardson@mmltholdings.com" + } + } + ] +} diff --git a/src/test/resources/fixtures/qqq/v1/table/person/possibleValues/homeCityId.json b/src/test/resources/fixtures/qqq/v1/table/person/possibleValues/homeCityId.json new file mode 100644 index 0000000..380b357 --- /dev/null +++ b/src/test/resources/fixtures/qqq/v1/table/person/possibleValues/homeCityId.json @@ -0,0 +1,12 @@ +{ + "options": [ + { + "id": 1, + "label": "St. Louis" + }, + { + "id": 2, + "label": "Chesterfield" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/fixtures/qqq/v1/table/person/possibleValues/homeCityId=1.json b/src/test/resources/fixtures/qqq/v1/table/person/possibleValues/homeCityId=1.json new file mode 100644 index 0000000..8fc7cd6 --- /dev/null +++ b/src/test/resources/fixtures/qqq/v1/table/person/possibleValues/homeCityId=1.json @@ -0,0 +1,8 @@ +{ + "options": [ + { + "id": 1, + "label": "St. Louis" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/fixtures/qqq/v1/table/person/variants.json b/src/test/resources/fixtures/qqq/v1/table/person/variants.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/src/test/resources/fixtures/qqq/v1/table/person/variants.json @@ -0,0 +1 @@ +[] diff --git a/src/test/resources/fixtures/qqq/v1/table/savedReport/possibleValues/tableName.json b/src/test/resources/fixtures/qqq/v1/table/savedReport/possibleValues/tableName.json new file mode 100644 index 0000000..2f642c6 --- /dev/null +++ b/src/test/resources/fixtures/qqq/v1/table/savedReport/possibleValues/tableName.json @@ -0,0 +1,16 @@ +{ + "options": [ + { + "id": "person", + "label": "Person" + }, + { + "id": "city", + "label": "City" + }, + { + "id": "savedReport", + "label": "Saved Report" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/fixtures/qqq/v1/table/script/1.json b/src/test/resources/fixtures/qqq/v1/table/script/1.json new file mode 100644 index 0000000..76c461f --- /dev/null +++ b/src/test/resources/fixtures/qqq/v1/table/script/1.json @@ -0,0 +1,22 @@ +{ + "tableName": "script", + "recordLabel": "Hello, Script", + "values": { + "name": "Hello, Script", + "id": 1, + "currentScriptRevisionId": 100, + "tableName": "client", + "createDate": "2023-02-18T00:47:51Z", + "modifyDate": "2023-02-18T00:47:51Z", + "scriptTypeId": 1 + }, + "displayValues": { + "tableName": "Client", + "scriptTypeId": "Record Script", + "name": "Hello, Script", + "currentScriptRevisionId": 100, + "id": "1", + "createDate": "2023-02-18T00:47:51Z", + "modifyDate": "2023-02-18T00:47:51Z" + } +} \ No newline at end of file diff --git a/src/test/resources/fixtures/qqq/v1/table/scriptLog/query.json b/src/test/resources/fixtures/qqq/v1/table/scriptLog/query.json new file mode 100644 index 0000000..13ee911 --- /dev/null +++ b/src/test/resources/fixtures/qqq/v1/table/scriptLog/query.json @@ -0,0 +1,3 @@ +{ + "records": [] +} \ No newline at end of file diff --git a/src/test/resources/fixtures/qqq/v1/table/scriptRevision/100.json b/src/test/resources/fixtures/qqq/v1/table/scriptRevision/100.json new file mode 100644 index 0000000..955ab34 --- /dev/null +++ b/src/test/resources/fixtures/qqq/v1/table/scriptRevision/100.json @@ -0,0 +1,36 @@ +{ + "tableName": "scriptRevision", + "recordLabel": "Hello, Script Revision", + "values": { + "id": "100", + "name": "Hello, Script Revision", + "sequenceNo": "22", + "commitMessage": "Initial checkin", + "author": "Jon Programmer", + "createDate": "2023-02-18T00:47:51Z", + "modifyDate": "2023-02-18T00:47:51Z" + }, + "displayValues": { + "id": "1", + "name": "Hello, Script Revision", + "scriptId": "1", + "sequenceNo": "22", + "createDate": "2023-02-18T00:47:51Z", + "modifyDate": "2023-02-18T00:47:51Z" + }, + "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" + } + } + ] + } +} diff --git a/src/test/resources/fixtures/qqq/v1/table/scriptRevision/query.json b/src/test/resources/fixtures/qqq/v1/table/scriptRevision/query.json new file mode 100644 index 0000000..e5ef09a --- /dev/null +++ b/src/test/resources/fixtures/qqq/v1/table/scriptRevision/query.json @@ -0,0 +1,32 @@ +{ + "records": [ + { + "tableName": "scriptRevision", + "values": { + "contents": "var hello;", + "id": 100, + "sequenceNo": 2, + "commitMessage": "2nd commit", + "author": "Jon Programmer", + "createDate": "2023-02-18T00:47:51Z", + "modifyDate": "2023-02-18T00:47:51Z" + }, + "displayValues": { + } + }, + { + "tableName": "scriptRevision", + "values": { + "contents": "var goodBye;", + "id": 99, + "sequenceNo": 1, + "commitMessage": "Initial checkin", + "author": "Jane Programmer", + "createDate": "2023-02-17T00:47:51Z", + "modifyDate": "2023-02-17T00:47:51Z" + }, + "displayValues": { + } + } + ] +} \ No newline at end of file diff --git a/src/test/resources/fixtures/qqq/v1/table/scriptType/1.json b/src/test/resources/fixtures/qqq/v1/table/scriptType/1.json new file mode 100644 index 0000000..b1bdbc6 --- /dev/null +++ b/src/test/resources/fixtures/qqq/v1/table/scriptType/1.json @@ -0,0 +1,13 @@ +{ + "tableName": "scriptType", + "recordLabel": "Record Script", + "values": { + "name": "Record Script", + "id": 1, + "createDate": "2023-02-18T00:47:51Z", + "modifyDate": "2023-02-18T00:47:51Z", + "fileMode": 1 + }, + "displayValues": { + } +} \ No newline at end of file From 6f15356b5175945d9a69faef95f0fde02d0d3918 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Mon, 2 Jun 2025 08:45:30 -0500 Subject: [PATCH 22/22] Adjustments to qqq/v1 test fixtures --- .../selenium/lib/QBaseSeleniumTest.java | 5 + .../fixtures/qqq/v1/metaData/person.json | 168 -------------- .../fixtures/qqq/v1/metaData/savedReport.json | 218 ------------------ .../fixtures/qqq/v1/metaData/script.json | 139 ----------- .../qqq/v1/metaData/scriptRevision.json | 152 ------------ .../qqq/v1/metaData/table/person.json | 166 +++++++++++++ .../qqq/v1/metaData/table/savedReport.json | 216 +++++++++++++++++ .../qqq/v1/metaData/table/script.json | 137 +++++++++++ .../qqq/v1/metaData/table/scriptRevision.json | 150 ++++++++++++ 9 files changed, 674 insertions(+), 677 deletions(-) delete mode 100644 src/test/resources/fixtures/qqq/v1/metaData/person.json delete mode 100644 src/test/resources/fixtures/qqq/v1/metaData/savedReport.json delete mode 100644 src/test/resources/fixtures/qqq/v1/metaData/script.json delete mode 100644 src/test/resources/fixtures/qqq/v1/metaData/scriptRevision.json create mode 100644 src/test/resources/fixtures/qqq/v1/metaData/table/person.json create mode 100644 src/test/resources/fixtures/qqq/v1/metaData/table/savedReport.json create mode 100644 src/test/resources/fixtures/qqq/v1/metaData/table/script.json create mode 100644 src/test/resources/fixtures/qqq/v1/metaData/table/scriptRevision.json diff --git a/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/lib/QBaseSeleniumTest.java b/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/lib/QBaseSeleniumTest.java index 5d6e058..b9f32b7 100755 --- a/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/lib/QBaseSeleniumTest.java +++ b/src/test/java/com/kingsrook/qqq/frontend/materialdashboard/selenium/lib/QBaseSeleniumTest.java @@ -181,7 +181,12 @@ public class QBaseSeleniumTest .withRouteToFile("/metaData/table/city", "metaData/table/person.json") .withRouteToFile("/metaData/table/script", "metaData/table/script.json") .withRouteToFile("/metaData/table/scriptRevision", "metaData/table/scriptRevision.json") + .withRouteToFile("/qqq/v1/metaData/table/person", "qqq/v1/metaData/table/person.json") + .withRouteToFile("/qqq/v1/metaData/table/city", "qqq/v1/metaData/table/city.json") + .withRouteToFile("/qqq/v1/metaData/table/script", "qqq/v1/metaData/table/script.json") + .withRouteToFile("/qqq/v1/metaData/table/scriptRevision", "qqq/v1/metaData/table/scriptRevision.json") .withRouteToFile("/processes/querySavedView/init", "processes/querySavedView/init.json"); + } diff --git a/src/test/resources/fixtures/qqq/v1/metaData/person.json b/src/test/resources/fixtures/qqq/v1/metaData/person.json deleted file mode 100644 index 3a3e00c..0000000 --- a/src/test/resources/fixtures/qqq/v1/metaData/person.json +++ /dev/null @@ -1,168 +0,0 @@ -{ - "table": { - "name": "person", - "label": "Person", - "isHidden": false, - "primaryKeyField": "id", - "iconName": "person", - "deletePermission": true, - "editPermission": true, - "insertPermission": true, - "readPermission": true, - "fields": { - "firstName": { - "name": "firstName", - "label": "First Name", - "type": "STRING", - "isRequired": true, - "isEditable": true, - "displayFormat": "%s" - }, - "lastName": { - "name": "lastName", - "label": "Last Name", - "type": "STRING", - "isRequired": true, - "isEditable": true, - "displayFormat": "%s" - }, - "annualSalary": { - "name": "annualSalary", - "label": "Annual Salary", - "type": "DECIMAL", - "isRequired": false, - "isEditable": true, - "displayFormat": "$%,.2f" - }, - "modifyDate": { - "name": "modifyDate", - "label": "Modify Date", - "type": "DATE_TIME", - "isRequired": false, - "isEditable": false, - "displayFormat": "%s" - }, - "daysWorked": { - "name": "daysWorked", - "label": "Days Worked", - "type": "INTEGER", - "isRequired": false, - "isEditable": true, - "displayFormat": "%,d" - }, - "id": { - "name": "id", - "label": "Id", - "type": "INTEGER", - "isRequired": false, - "isEditable": false, - "displayFormat": "%s" - }, - "birthDate": { - "name": "birthDate", - "label": "Birth Date", - "type": "DATE", - "isRequired": false, - "isEditable": true, - "displayFormat": "%s" - }, - "isEmployed": { - "name": "isEmployed", - "label": "Is Employed", - "type": "BOOLEAN", - "isRequired": false, - "isEditable": true, - "displayFormat": "%s" - }, - "homeCityId": { - "name": "homeCityId", - "label": "Home City", - "type": "INTEGER", - "possibleValueSourceName": "city", - "isRequired": false, - "isEditable": true, - "displayFormat": "%s" - }, - "email": { - "name": "email", - "label": "Email", - "type": "STRING", - "isRequired": false, - "isEditable": true, - "displayFormat": "%s" - }, - "createDate": { - "name": "createDate", - "label": "Create Date", - "type": "DATE_TIME", - "isRequired": false, - "isEditable": false, - "displayFormat": "%s" - } - }, - "sections": [ - { - "name": "identity", - "label": "Identity", - "tier": "T1", - "fieldNames": [ - "id", - "firstName", - "lastName" - ], - "icon": { - "name": "badge" - }, - "isHidden": false - }, - { - "name": "basicInfo", - "label": "Basic Info", - "tier": "T2", - "fieldNames": [ - "email", - "birthDate" - ], - "icon": { - "name": "dataset" - }, - "isHidden": false - }, - { - "name": "employmentInfo", - "label": "Employment Info", - "tier": "T2", - "fieldNames": [ - "isEmployed", - "annualSalary", - "daysWorked" - ], - "icon": { - "name": "work" - }, - "isHidden": false - }, - { - "name": "dates", - "label": "Dates", - "tier": "T3", - "fieldNames": [ - "createDate", - "modifyDate" - ], - "icon": { - "name": "calendar_month" - }, - "isHidden": false - } - ], - "capabilities": [ - "TABLE_COUNT", - "TABLE_GET", - "TABLE_QUERY", - "TABLE_DELETE", - "TABLE_INSERT", - "TABLE_UPDATE" - ] - } -} \ No newline at end of file diff --git a/src/test/resources/fixtures/qqq/v1/metaData/savedReport.json b/src/test/resources/fixtures/qqq/v1/metaData/savedReport.json deleted file mode 100644 index 71e256b..0000000 --- a/src/test/resources/fixtures/qqq/v1/metaData/savedReport.json +++ /dev/null @@ -1,218 +0,0 @@ -{ - "table": { - "name": "savedReport", - "label": "Saved Report", - "isHidden": false, - "primaryKeyField": "id", - "iconName": "article", - "fields": { - "queryFilterJson": { - "name": "queryFilterJson", - "label": "Query Filter", - "type": "STRING", - "isRequired": false, - "isEditable": true, - "isHeavy": false, - "displayFormat": "%s" - }, - "columnsJson": { - "name": "columnsJson", - "label": "Columns", - "type": "STRING", - "isRequired": false, - "isEditable": true, - "isHeavy": false, - "displayFormat": "%s" - }, - "inputFieldsJson": { - "name": "inputFieldsJson", - "label": "Input Fields", - "type": "STRING", - "isRequired": false, - "isEditable": true, - "isHeavy": false, - "displayFormat": "%s" - }, - "pivotTableJson": { - "name": "pivotTableJson", - "label": "Pivot Table", - "type": "STRING", - "isRequired": false, - "isEditable": true, - "isHeavy": false, - "displayFormat": "%s" - }, - "modifyDate": { - "name": "modifyDate", - "label": "Modify Date", - "type": "DATE_TIME", - "isRequired": false, - "isEditable": false, - "isHeavy": false, - "displayFormat": "%s" - }, - "label": { - "name": "label", - "label": "Report Name", - "type": "STRING", - "isRequired": true, - "isEditable": true, - "isHeavy": false, - "displayFormat": "%s" - }, - "id": { - "name": "id", - "label": "Id", - "type": "INTEGER", - "isRequired": false, - "isEditable": false, - "isHeavy": false, - "displayFormat": "%s" - }, - "userId": { - "name": "userId", - "label": "User Id", - "type": "STRING", - "isRequired": false, - "isEditable": true, - "isHeavy": false, - "displayFormat": "%s" - }, - "tableName": { - "name": "tableName", - "label": "Table", - "type": "STRING", - "isRequired": true, - "isEditable": true, - "isHeavy": false, - "possibleValueSourceName": "tables", - "displayFormat": "%s" - }, - "createDate": { - "name": "createDate", - "label": "Create Date", - "type": "DATE_TIME", - "isRequired": false, - "isEditable": false, - "isHeavy": false, - "displayFormat": "%s" - } - }, - "sections": [ - { - "name": "identity", - "label": "Identity", - "tier": "T1", - "fieldNames": [ - "id", - "label", - "tableName" - ], - "icon": { - "name": "badge" - }, - "isHidden": false - }, - { - "name": "filtersAndColumns", - "label": "Filters and Columns", - "tier": "T2", - "widgetName": "reportSetupWidget", - "icon": { - "name": "table_chart" - }, - "isHidden": false - }, - { - "name": "pivotTable", - "label": "Pivot Table", - "tier": "T2", - "widgetName": "pivotTableSetupWidget", - "icon": { - "name": "pivot_table_chart" - }, - "isHidden": false - }, - { - "name": "data", - "label": "Data", - "tier": "T2", - "fieldNames": [ - "queryFilterJson", - "columnsJson", - "pivotTableJson" - ], - "icon": { - "name": "text_snippet" - }, - "isHidden": true - }, - { - "name": "hidden", - "label": "Hidden", - "tier": "T2", - "fieldNames": [ - "inputFieldsJson", - "userId" - ], - "icon": { - "name": "text_snippet" - }, - "isHidden": true - }, - { - "name": "dates", - "label": "Dates", - "tier": "T3", - "fieldNames": [ - "createDate", - "modifyDate" - ], - "icon": { - "name": "calendar_month" - }, - "isHidden": false - } - ], - "exposedJoins": [], - "supplementalTableMetaData": { - "materialDashboard": { - "fieldRules": [ - { - "trigger": "ON_CHANGE", - "sourceField": "tableName", - "action": "CLEAR_TARGET_FIELD", - "targetField": "queryFilterJson" - }, - { - "trigger": "ON_CHANGE", - "sourceField": "tableName", - "action": "CLEAR_TARGET_FIELD", - "targetField": "columnsJson" - }, - { - "trigger": "ON_CHANGE", - "sourceField": "tableName", - "action": "CLEAR_TARGET_FIELD", - "targetField": "pivotTableJson" - } - ], - "type": "materialDashboard" - } - }, - "capabilities": [ - "TABLE_COUNT", - "TABLE_GET", - "TABLE_QUERY", - "QUERY_STATS", - "TABLE_UPDATE", - "TABLE_INSERT", - "TABLE_DELETE" - ], - "readPermission": true, - "insertPermission": true, - "editPermission": true, - "deletePermission": true, - "usesVariants": false - } -} \ No newline at end of file diff --git a/src/test/resources/fixtures/qqq/v1/metaData/script.json b/src/test/resources/fixtures/qqq/v1/metaData/script.json deleted file mode 100644 index e4b664f..0000000 --- a/src/test/resources/fixtures/qqq/v1/metaData/script.json +++ /dev/null @@ -1,139 +0,0 @@ -{ - "table": { - "name": "script", - "label": "Script", - "isHidden": false, - "primaryKeyField": "id", - "iconName": "data_object", - "fields": { - "modifyDate": { - "name": "modifyDate", - "label": "Modify Date", - "type": "DATE_TIME", - "isRequired": false, - "isEditable": false, - "displayFormat": "%s" - }, - "name": { - "name": "name", - "label": "Name", - "type": "STRING", - "isRequired": false, - "isEditable": true, - "displayFormat": "%s" - }, - "currentScriptRevisionId": { - "name": "currentScriptRevisionId", - "label": "Current Script Revision", - "type": "INTEGER", - "isRequired": false, - "isEditable": true, - "possibleValueSourceName": "scriptRevision", - "displayFormat": "%s", - "adornments": [ - { - "type": "LINK", - "values": { - "toRecordFromTable": "scriptRevision" - } - } - ] - }, - "id": { - "name": "id", - "label": "Id", - "type": "INTEGER", - "isRequired": false, - "isEditable": false, - "displayFormat": "%s" - }, - "tableName": { - "name": "tableName", - "label": "Table Name", - "type": "STRING", - "isRequired": false, - "isEditable": true, - "possibleValueSourceName": "tables", - "displayFormat": "%s" - }, - "createDate": { - "name": "createDate", - "label": "Create Date", - "type": "DATE_TIME", - "isRequired": false, - "isEditable": false, - "displayFormat": "%s" - }, - "scriptTypeId": { - "name": "scriptTypeId", - "label": "Script Type", - "type": "INTEGER", - "isRequired": false, - "isEditable": true, - "possibleValueSourceName": "scriptType", - "displayFormat": "%s", - "adornments": [ - { - "type": "LINK", - "values": { - "toRecordFromTable": "scriptType" - } - } - ] - } - }, - "sections": [ - { - "name": "identity", - "label": "Identity", - "tier": "T1", - "fieldNames": [ - "id", - "name", - "scriptTypeId", - "tableName", - "currentScriptRevisionId" - ], - "icon": { - "name": "badge" - }, - "isHidden": false - }, - { - "name": "contents", - "label": "Contents", - "tier": "T2", - "widgetName": "scriptViewer", - "icon": { - "name": "data_object" - }, - "isHidden": false - }, - { - "name": "dates", - "label": "Dates", - "tier": "T3", - "fieldNames": [ - "createDate", - "modifyDate" - ], - "icon": { - "name": "calendar_month" - }, - "isHidden": false - } - ], - "capabilities": [ - "TABLE_COUNT", - "TABLE_GET", - "TABLE_QUERY", - "TABLE_INSERT", - "TABLE_DELETE", - "TABLE_UPDATE" - ], - "readPermission": true, - "insertPermission": true, - "editPermission": true, - "deletePermission": true - } -} \ No newline at end of file diff --git a/src/test/resources/fixtures/qqq/v1/metaData/scriptRevision.json b/src/test/resources/fixtures/qqq/v1/metaData/scriptRevision.json deleted file mode 100644 index 78440da..0000000 --- a/src/test/resources/fixtures/qqq/v1/metaData/scriptRevision.json +++ /dev/null @@ -1,152 +0,0 @@ -{ - "table": { - "name": "scriptRevision", - "label": "Script Revision", - "isHidden": false, - "primaryKeyField": "id", - "iconName": "history_edu", - "fields": { - "scriptId": { - "name": "scriptId", - "label": "Script", - "type": "INTEGER", - "isRequired": false, - "isEditable": true, - "isHeavy": false, - "possibleValueSourceName": "script", - "displayFormat": "%s", - "adornments": [ - { - "type": "SIZE", - "values": { - "width": "large" - } - }, - { - "type": "LINK", - "values": { - "toRecordFromTable": "script" - } - } - ] - }, - "apiName": { - "name": "apiName", - "label": "API Name", - "type": "STRING", - "isRequired": false, - "isEditable": true, - "isHeavy": false, - "possibleValueSourceName": "apiName", - "displayFormat": "%s" - }, - "sequenceNo": { - "name": "sequenceNo", - "label": "Sequence No", - "type": "INTEGER", - "isRequired": false, - "isEditable": true, - "isHeavy": false, - "displayFormat": "%s" - }, - "apiVersion": { - "name": "apiVersion", - "label": "API Version", - "type": "STRING", - "isRequired": false, - "isEditable": true, - "isHeavy": false, - "possibleValueSourceName": "apiVersion", - "displayFormat": "%s" - }, - "commitMessage": { - "name": "commitMessage", - "label": "Commit Message", - "type": "STRING", - "isRequired": false, - "isEditable": true, - "isHeavy": false, - "displayFormat": "%s" - }, - "modifyDate": { - "name": "modifyDate", - "label": "Modify Date", - "type": "DATE_TIME", - "isRequired": false, - "isEditable": false, - "isHeavy": false, - "displayFormat": "%s" - }, - "author": { - "name": "author", - "label": "Author", - "type": "STRING", - "isRequired": false, - "isEditable": true, - "isHeavy": false, - "displayFormat": "%s" - }, - "id": { - "name": "id", - "label": "Id", - "type": "INTEGER", - "isRequired": false, - "isEditable": false, - "isHeavy": false, - "displayFormat": "%s" - }, - "createDate": { - "name": "createDate", - "label": "Create Date", - "type": "DATE_TIME", - "isRequired": false, - "isEditable": false, - "isHeavy": false, - "displayFormat": "%s" - } - }, - "sections": [ - { - "name": "identity", - "label": "Identity", - "tier": "T1", - "fieldNames": [ - "id", - "scriptId", - "sequenceNo" - ], - "icon": { - "name": "badge" - }, - "isHidden": false - }, - { - "name": "dates", - "label": "Dates", - "tier": "T3", - "fieldNames": [ - "createDate", - "modifyDate" - ], - "icon": { - "name": "calendar_month" - }, - "isHidden": false - } - ], - "exposedJoins": [], - "capabilities": [ - "TABLE_COUNT", - "TABLE_GET", - "TABLE_QUERY", - "TABLE_INSERT", - "TABLE_UPDATE", - "QUERY_STATS" - ], - "readPermission": true, - "insertPermission": true, - "editPermission": true, - "deletePermission": true, - "usesVariants": false - } -} diff --git a/src/test/resources/fixtures/qqq/v1/metaData/table/person.json b/src/test/resources/fixtures/qqq/v1/metaData/table/person.json new file mode 100644 index 0000000..1804a9c --- /dev/null +++ b/src/test/resources/fixtures/qqq/v1/metaData/table/person.json @@ -0,0 +1,166 @@ +{ + "name": "person", + "label": "Person", + "isHidden": false, + "primaryKeyField": "id", + "iconName": "person", + "deletePermission": true, + "editPermission": true, + "insertPermission": true, + "readPermission": true, + "fields": { + "firstName": { + "name": "firstName", + "label": "First Name", + "type": "STRING", + "isRequired": true, + "isEditable": true, + "displayFormat": "%s" + }, + "lastName": { + "name": "lastName", + "label": "Last Name", + "type": "STRING", + "isRequired": true, + "isEditable": true, + "displayFormat": "%s" + }, + "annualSalary": { + "name": "annualSalary", + "label": "Annual Salary", + "type": "DECIMAL", + "isRequired": false, + "isEditable": true, + "displayFormat": "$%,.2f" + }, + "modifyDate": { + "name": "modifyDate", + "label": "Modify Date", + "type": "DATE_TIME", + "isRequired": false, + "isEditable": false, + "displayFormat": "%s" + }, + "daysWorked": { + "name": "daysWorked", + "label": "Days Worked", + "type": "INTEGER", + "isRequired": false, + "isEditable": true, + "displayFormat": "%,d" + }, + "id": { + "name": "id", + "label": "Id", + "type": "INTEGER", + "isRequired": false, + "isEditable": false, + "displayFormat": "%s" + }, + "birthDate": { + "name": "birthDate", + "label": "Birth Date", + "type": "DATE", + "isRequired": false, + "isEditable": true, + "displayFormat": "%s" + }, + "isEmployed": { + "name": "isEmployed", + "label": "Is Employed", + "type": "BOOLEAN", + "isRequired": false, + "isEditable": true, + "displayFormat": "%s" + }, + "homeCityId": { + "name": "homeCityId", + "label": "Home City", + "type": "INTEGER", + "possibleValueSourceName": "city", + "isRequired": false, + "isEditable": true, + "displayFormat": "%s" + }, + "email": { + "name": "email", + "label": "Email", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "displayFormat": "%s" + }, + "createDate": { + "name": "createDate", + "label": "Create Date", + "type": "DATE_TIME", + "isRequired": false, + "isEditable": false, + "displayFormat": "%s" + } + }, + "sections": [ + { + "name": "identity", + "label": "Identity", + "tier": "T1", + "fieldNames": [ + "id", + "firstName", + "lastName" + ], + "icon": { + "name": "badge" + }, + "isHidden": false + }, + { + "name": "basicInfo", + "label": "Basic Info", + "tier": "T2", + "fieldNames": [ + "email", + "birthDate" + ], + "icon": { + "name": "dataset" + }, + "isHidden": false + }, + { + "name": "employmentInfo", + "label": "Employment Info", + "tier": "T2", + "fieldNames": [ + "isEmployed", + "annualSalary", + "daysWorked" + ], + "icon": { + "name": "work" + }, + "isHidden": false + }, + { + "name": "dates", + "label": "Dates", + "tier": "T3", + "fieldNames": [ + "createDate", + "modifyDate" + ], + "icon": { + "name": "calendar_month" + }, + "isHidden": false + } + ], + "capabilities": [ + "TABLE_COUNT", + "TABLE_GET", + "TABLE_QUERY", + "TABLE_DELETE", + "TABLE_INSERT", + "TABLE_UPDATE" + ] +} \ No newline at end of file diff --git a/src/test/resources/fixtures/qqq/v1/metaData/table/savedReport.json b/src/test/resources/fixtures/qqq/v1/metaData/table/savedReport.json new file mode 100644 index 0000000..0b12428 --- /dev/null +++ b/src/test/resources/fixtures/qqq/v1/metaData/table/savedReport.json @@ -0,0 +1,216 @@ +{ + "name": "savedReport", + "label": "Saved Report", + "isHidden": false, + "primaryKeyField": "id", + "iconName": "article", + "fields": { + "queryFilterJson": { + "name": "queryFilterJson", + "label": "Query Filter", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + "columnsJson": { + "name": "columnsJson", + "label": "Columns", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + "inputFieldsJson": { + "name": "inputFieldsJson", + "label": "Input Fields", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + "pivotTableJson": { + "name": "pivotTableJson", + "label": "Pivot Table", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + "modifyDate": { + "name": "modifyDate", + "label": "Modify Date", + "type": "DATE_TIME", + "isRequired": false, + "isEditable": false, + "isHeavy": false, + "displayFormat": "%s" + }, + "label": { + "name": "label", + "label": "Report Name", + "type": "STRING", + "isRequired": true, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + "id": { + "name": "id", + "label": "Id", + "type": "INTEGER", + "isRequired": false, + "isEditable": false, + "isHeavy": false, + "displayFormat": "%s" + }, + "userId": { + "name": "userId", + "label": "User Id", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + "tableName": { + "name": "tableName", + "label": "Table", + "type": "STRING", + "isRequired": true, + "isEditable": true, + "isHeavy": false, + "possibleValueSourceName": "tables", + "displayFormat": "%s" + }, + "createDate": { + "name": "createDate", + "label": "Create Date", + "type": "DATE_TIME", + "isRequired": false, + "isEditable": false, + "isHeavy": false, + "displayFormat": "%s" + } + }, + "sections": [ + { + "name": "identity", + "label": "Identity", + "tier": "T1", + "fieldNames": [ + "id", + "label", + "tableName" + ], + "icon": { + "name": "badge" + }, + "isHidden": false + }, + { + "name": "filtersAndColumns", + "label": "Filters and Columns", + "tier": "T2", + "widgetName": "reportSetupWidget", + "icon": { + "name": "table_chart" + }, + "isHidden": false + }, + { + "name": "pivotTable", + "label": "Pivot Table", + "tier": "T2", + "widgetName": "pivotTableSetupWidget", + "icon": { + "name": "pivot_table_chart" + }, + "isHidden": false + }, + { + "name": "data", + "label": "Data", + "tier": "T2", + "fieldNames": [ + "queryFilterJson", + "columnsJson", + "pivotTableJson" + ], + "icon": { + "name": "text_snippet" + }, + "isHidden": true + }, + { + "name": "hidden", + "label": "Hidden", + "tier": "T2", + "fieldNames": [ + "inputFieldsJson", + "userId" + ], + "icon": { + "name": "text_snippet" + }, + "isHidden": true + }, + { + "name": "dates", + "label": "Dates", + "tier": "T3", + "fieldNames": [ + "createDate", + "modifyDate" + ], + "icon": { + "name": "calendar_month" + }, + "isHidden": false + } + ], + "exposedJoins": [], + "supplementalTableMetaData": { + "materialDashboard": { + "fieldRules": [ + { + "trigger": "ON_CHANGE", + "sourceField": "tableName", + "action": "CLEAR_TARGET_FIELD", + "targetField": "queryFilterJson" + }, + { + "trigger": "ON_CHANGE", + "sourceField": "tableName", + "action": "CLEAR_TARGET_FIELD", + "targetField": "columnsJson" + }, + { + "trigger": "ON_CHANGE", + "sourceField": "tableName", + "action": "CLEAR_TARGET_FIELD", + "targetField": "pivotTableJson" + } + ], + "type": "materialDashboard" + } + }, + "capabilities": [ + "TABLE_COUNT", + "TABLE_GET", + "TABLE_QUERY", + "QUERY_STATS", + "TABLE_UPDATE", + "TABLE_INSERT", + "TABLE_DELETE" + ], + "readPermission": true, + "insertPermission": true, + "editPermission": true, + "deletePermission": true, + "usesVariants": false +} \ No newline at end of file diff --git a/src/test/resources/fixtures/qqq/v1/metaData/table/script.json b/src/test/resources/fixtures/qqq/v1/metaData/table/script.json new file mode 100644 index 0000000..969bfd0 --- /dev/null +++ b/src/test/resources/fixtures/qqq/v1/metaData/table/script.json @@ -0,0 +1,137 @@ +{ + "name": "script", + "label": "Script", + "isHidden": false, + "primaryKeyField": "id", + "iconName": "data_object", + "fields": { + "modifyDate": { + "name": "modifyDate", + "label": "Modify Date", + "type": "DATE_TIME", + "isRequired": false, + "isEditable": false, + "displayFormat": "%s" + }, + "name": { + "name": "name", + "label": "Name", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "displayFormat": "%s" + }, + "currentScriptRevisionId": { + "name": "currentScriptRevisionId", + "label": "Current Script Revision", + "type": "INTEGER", + "isRequired": false, + "isEditable": true, + "possibleValueSourceName": "scriptRevision", + "displayFormat": "%s", + "adornments": [ + { + "type": "LINK", + "values": { + "toRecordFromTable": "scriptRevision" + } + } + ] + }, + "id": { + "name": "id", + "label": "Id", + "type": "INTEGER", + "isRequired": false, + "isEditable": false, + "displayFormat": "%s" + }, + "tableName": { + "name": "tableName", + "label": "Table Name", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "possibleValueSourceName": "tables", + "displayFormat": "%s" + }, + "createDate": { + "name": "createDate", + "label": "Create Date", + "type": "DATE_TIME", + "isRequired": false, + "isEditable": false, + "displayFormat": "%s" + }, + "scriptTypeId": { + "name": "scriptTypeId", + "label": "Script Type", + "type": "INTEGER", + "isRequired": false, + "isEditable": true, + "possibleValueSourceName": "scriptType", + "displayFormat": "%s", + "adornments": [ + { + "type": "LINK", + "values": { + "toRecordFromTable": "scriptType" + } + } + ] + } + }, + "sections": [ + { + "name": "identity", + "label": "Identity", + "tier": "T1", + "fieldNames": [ + "id", + "name", + "scriptTypeId", + "tableName", + "currentScriptRevisionId" + ], + "icon": { + "name": "badge" + }, + "isHidden": false + }, + { + "name": "contents", + "label": "Contents", + "tier": "T2", + "widgetName": "scriptViewer", + "icon": { + "name": "data_object" + }, + "isHidden": false + }, + { + "name": "dates", + "label": "Dates", + "tier": "T3", + "fieldNames": [ + "createDate", + "modifyDate" + ], + "icon": { + "name": "calendar_month" + }, + "isHidden": false + } + ], + "capabilities": [ + "TABLE_COUNT", + "TABLE_GET", + "TABLE_QUERY", + "TABLE_INSERT", + "TABLE_DELETE", + "TABLE_UPDATE" + ], + "readPermission": true, + "insertPermission": true, + "editPermission": true, + "deletePermission": true +} \ No newline at end of file diff --git a/src/test/resources/fixtures/qqq/v1/metaData/table/scriptRevision.json b/src/test/resources/fixtures/qqq/v1/metaData/table/scriptRevision.json new file mode 100644 index 0000000..bac2b58 --- /dev/null +++ b/src/test/resources/fixtures/qqq/v1/metaData/table/scriptRevision.json @@ -0,0 +1,150 @@ +{ + "name": "scriptRevision", + "label": "Script Revision", + "isHidden": false, + "primaryKeyField": "id", + "iconName": "history_edu", + "fields": { + "scriptId": { + "name": "scriptId", + "label": "Script", + "type": "INTEGER", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "possibleValueSourceName": "script", + "displayFormat": "%s", + "adornments": [ + { + "type": "SIZE", + "values": { + "width": "large" + } + }, + { + "type": "LINK", + "values": { + "toRecordFromTable": "script" + } + } + ] + }, + "apiName": { + "name": "apiName", + "label": "API Name", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "possibleValueSourceName": "apiName", + "displayFormat": "%s" + }, + "sequenceNo": { + "name": "sequenceNo", + "label": "Sequence No", + "type": "INTEGER", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + "apiVersion": { + "name": "apiVersion", + "label": "API Version", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "possibleValueSourceName": "apiVersion", + "displayFormat": "%s" + }, + "commitMessage": { + "name": "commitMessage", + "label": "Commit Message", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + "modifyDate": { + "name": "modifyDate", + "label": "Modify Date", + "type": "DATE_TIME", + "isRequired": false, + "isEditable": false, + "isHeavy": false, + "displayFormat": "%s" + }, + "author": { + "name": "author", + "label": "Author", + "type": "STRING", + "isRequired": false, + "isEditable": true, + "isHeavy": false, + "displayFormat": "%s" + }, + "id": { + "name": "id", + "label": "Id", + "type": "INTEGER", + "isRequired": false, + "isEditable": false, + "isHeavy": false, + "displayFormat": "%s" + }, + "createDate": { + "name": "createDate", + "label": "Create Date", + "type": "DATE_TIME", + "isRequired": false, + "isEditable": false, + "isHeavy": false, + "displayFormat": "%s" + } + }, + "sections": [ + { + "name": "identity", + "label": "Identity", + "tier": "T1", + "fieldNames": [ + "id", + "scriptId", + "sequenceNo" + ], + "icon": { + "name": "badge" + }, + "isHidden": false + }, + { + "name": "dates", + "label": "Dates", + "tier": "T3", + "fieldNames": [ + "createDate", + "modifyDate" + ], + "icon": { + "name": "calendar_month" + }, + "isHidden": false + } + ], + "exposedJoins": [], + "capabilities": [ + "TABLE_COUNT", + "TABLE_GET", + "TABLE_QUERY", + "TABLE_INSERT", + "TABLE_UPDATE", + "QUERY_STATS" + ], + "readPermission": true, + "insertPermission": true, + "editPermission": true, + "deletePermission": true, + "usesVariants": false +}