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.

This commit is contained in:
2025-05-28 16:30:15 -05:00
parent 5bdc3a6cd0
commit 07d116d9ba
4 changed files with 269 additions and 53 deletions

View File

@ -377,23 +377,57 @@ export default function App({authenticationMetaData}: Props)
}); });
}); });
const runRecordScriptProcess = metaData.processes.get("runRecordScript"); const materialDashboardInstanceMetaData = metaData.supplementalInstanceMetaData?.get("materialDashboard");
if (runRecordScriptProcess) if (materialDashboardInstanceMetaData)
{ {
const process = runRecordScriptProcess; const processNamesToAddToAllQueryAndViewScreens = materialDashboardInstanceMetaData.processNamesToAddToAllQueryAndViewScreens;
routeList.push({ if (processNamesToAddToAllQueryAndViewScreens)
name: process.label, {
key: process.name, for (let processName of processNamesToAddToAllQueryAndViewScreens)
route: `${path}/${process.name}`, {
component: <RecordQuery table={table} key={`${table.name}-${process.name}`} launchProcess={process} />, const process = metaData.processes.get(processName);
}); if (process)
{
routeList.push({
name: process.label,
key: process.name,
route: `${path}/${process.name}`,
component: <RecordQuery table={table} key={`${table.name}-${process.name}`} launchProcess={process} />,
});
routeList.push({ routeList.push({
name: process.label, name: process.label,
key: `${app.name}/${process.name}`, key: `${app.name}/${process.name}`,
route: `${path}/:id/${process.name}`, route: `${path}/:id/${process.name}`,
component: <RecordView table={table} launchProcess={process} />, component: <RecordView table={table} launchProcess={process} />,
}); });
}
}
}
}
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: <RecordQuery table={table} key={`${table.name}-${process.name}`} launchProcess={process} />,
});
routeList.push({
name: process.label,
key: `${app.name}/${process.name}`,
route: `${path}/:id/${process.name}`,
component: <RecordView table={table} launchProcess={process} />,
});
}
} }
const reportsForTable = ProcessUtils.getReportsForTable(metaData, table.name, true); const reportsForTable = ProcessUtils.getReportsForTable(metaData, table.name, true);

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String> 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<String> 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<String> processNamesToAddToAllQueryAndViewScreens)
{
this.processNamesToAddToAllQueryAndViewScreens = processNamesToAddToAllQueryAndViewScreens;
}
/*******************************************************************************
** Fluent setter for processNamesToAddToAllQueryAndViewScreens
*******************************************************************************/
public MaterialDashboardInstanceMetaData withProcessNamesToAddToAllQueryAndViewScreens(List<String> processNamesToAddToAllQueryAndViewScreens)
{
this.processNamesToAddToAllQueryAndViewScreens = processNamesToAddToAllQueryAndViewScreens;
return (this);
}
}

View File

@ -29,9 +29,9 @@ import Icon from "@mui/material/Icon";
import ListItemIcon from "@mui/material/ListItemIcon"; import ListItemIcon from "@mui/material/ListItemIcon";
import Menu from "@mui/material/Menu"; import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
import {QActionsMenuButton} from "qqq/components/buttons/DefaultButtons";
import React, {useState} from "react"; import React, {useState} from "react";
import {useNavigate} from "react-router-dom"; import {useNavigate} from "react-router-dom";
import {QActionsMenuButton} from "qqq/components/buttons/DefaultButtons";
interface QueryScreenActionMenuProps interface QueryScreenActionMenuProps
{ {
@ -44,40 +44,35 @@ interface QueryScreenActionMenuProps
processClicked: (process: QProcessMetaData) => void; processClicked: (process: QProcessMetaData) => void;
} }
QueryScreenActionMenu.defaultProps = { QueryScreenActionMenu.defaultProps = {};
};
export default function QueryScreenActionMenu({metaData, tableMetaData, tableProcesses, bulkLoadClicked, bulkEditClicked, bulkDeleteClicked, processClicked}: QueryScreenActionMenuProps): JSX.Element 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 navigate = useNavigate();
const openActionsMenu = (event: any) => const openActionsMenu = (event: any) =>
{ {
setAnchorElement(event.currentTarget); setAnchorElement(event.currentTarget);
} };
const closeActionsMenu = () => const closeActionsMenu = () =>
{ {
setAnchorElement(null); setAnchorElement(null);
}
const pushDividerIfNeeded = (menuItems: JSX.Element[]) =>
{
if (menuItems.length > 0)
{
menuItems.push(<Divider key="divider" />);
}
}; };
const runSomething = (handler: () => void) => const runSomething = (handler: () => void) =>
{ {
closeActionsMenu(); closeActionsMenu();
handler(); handler();
} };
const menuItems: JSX.Element[] = []; const menuItems: JSX.Element[] = [];
//////////////////////////////////////////////////////
// start with bulk actions, if user has permissions //
//////////////////////////////////////////////////////
if (tableMetaData.capabilities.has(Capability.TABLE_INSERT) && tableMetaData.insertPermission) if (tableMetaData.capabilities.has(Capability.TABLE_INSERT) && tableMetaData.insertPermission)
{ {
menuItems.push(<MenuItem key="bulkLoad" onClick={() => runSomething(bulkLoadClicked)}><ListItemIcon><Icon>library_add</Icon></ListItemIcon>Bulk Load</MenuItem>); menuItems.push(<MenuItem key="bulkLoad" onClick={() => runSomething(bulkLoadClicked)}><ListItemIcon><Icon>library_add</Icon></ListItemIcon>Bulk Load</MenuItem>);
@ -91,19 +86,7 @@ export default function QueryScreenActionMenu({metaData, tableMetaData, tablePro
menuItems.push(<MenuItem key="bulkDelete" onClick={() => runSomething(bulkDeleteClicked)}><ListItemIcon><Icon>delete</Icon></ListItemIcon>Bulk Delete</MenuItem>); menuItems.push(<MenuItem key="bulkDelete" onClick={() => runSomething(bulkDeleteClicked)}><ListItemIcon><Icon>delete</Icon></ListItemIcon>Bulk Delete</MenuItem>);
} }
const runRecordScriptProcess = metaData?.processes.get("runRecordScript"); menuItems.push(<Divider key="divider1" />);
if (runRecordScriptProcess)
{
const process = runRecordScriptProcess;
menuItems.push(<MenuItem key={process.name} onClick={() => runSomething(() => processClicked(process))}><ListItemIcon><Icon>{process.iconName ?? "arrow_forward"}</Icon></ListItemIcon>{process.label}</MenuItem>);
}
menuItems.push(<MenuItem key="developerMode" onClick={() => navigate(`${metaData.getTablePathByName(tableMetaData.name)}/dev`)}><ListItemIcon><Icon>code</Icon></ListItemIcon>Developer Mode</MenuItem>);
if (tableProcesses && tableProcesses.length)
{
pushDividerIfNeeded(menuItems);
}
tableProcesses.sort((a, b) => a.label.localeCompare(b.label)); tableProcesses.sort((a, b) => a.label.localeCompare(b.label));
tableProcesses.map((process) => tableProcesses.map((process) =>
@ -111,11 +94,62 @@ export default function QueryScreenActionMenu({metaData, tableMetaData, tablePro
menuItems.push(<MenuItem key={process.name} onClick={() => runSomething(() => processClicked(process))}><ListItemIcon><Icon>{process.iconName ?? "arrow_forward"}</Icon></ListItemIcon>{process.label}</MenuItem>); menuItems.push(<MenuItem key={process.name} onClick={() => runSomething(() => processClicked(process))}><ListItemIcon><Icon>{process.iconName ?? "arrow_forward"}</Icon></ListItemIcon>{process.label}</MenuItem>);
}); });
menuItems.push(<Divider key="divider2" />);
////////////////////////////////////////////
// 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(<MenuItem key={process.name} onClick={() => runSomething(() => processClicked(process))}><ListItemIcon><Icon>{process.iconName ?? "arrow_forward"}</Icon></ListItemIcon>{process.label}</MenuItem>);
}
}
}
}
else
{
//////////////////////////////////////
// deprecated in favor of the above //
//////////////////////////////////////
const runRecordScriptProcess = metaData?.processes.get("runRecordScript");
if (runRecordScriptProcess)
{
const process = runRecordScriptProcess;
menuItems.push(<MenuItem key={process.name} onClick={() => runSomething(() => processClicked(process))}><ListItemIcon><Icon>{process.iconName ?? "arrow_forward"}</Icon></ListItemIcon>{process.label}</MenuItem>);
}
}
////////////////////////////////////////
// todo - any conditions around this? //
////////////////////////////////////////
menuItems.push(<MenuItem key="developerMode" onClick={() => navigate(`${metaData.getTablePathByName(tableMetaData.name)}/dev`)}><ListItemIcon><Icon>code</Icon></ListItemIcon>Developer Mode</MenuItem>);
if (menuItems.length === 0) if (menuItems.length === 0)
{ {
menuItems.push(<MenuItem key="notAvaialableNow" disabled><ListItemIcon><Icon>block</Icon></ListItemIcon><i>No actions available</i></MenuItem>); menuItems.push(<MenuItem key="notAvaialableNow" disabled><ListItemIcon><Icon>block</Icon></ListItemIcon><i>No actions available</i></MenuItem>);
} }
////////////////////////////////////////////////////////////////////////////////
// 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 ( return (
<> <>
<QActionsMenuButton isOpen={anchorElement} onClickHandler={openActionsMenu} /> <QActionsMenuButton isOpen={anchorElement} onClickHandler={openActionsMenu} />
@ -130,5 +164,5 @@ export default function QueryScreenActionMenu({metaData, tableMetaData, tablePro
{menuItems} {menuItems}
</Menu> </Menu>
</> </>
) );
} }

View File

@ -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) if (!asyncLoadInited)
{ {
setAsyncLoadInited(true); setAsyncLoadInited(true);
@ -472,11 +500,16 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
// load processes that the routing needs to respect // // 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 allTableProcesses = ProcessUtils.getProcessesForTable(metaData, tableName, true); // these include hidden ones (e.g., to find the bulks)
const runRecordScriptProcess = metaData?.processes.get("runRecordScript"); const genericProcesses = getGenericProcesses(metaData);
if (runRecordScriptProcess)
for (let genericProcess of genericProcesses)
{ {
allTableProcesses.unshift(runRecordScriptProcess); if (genericProcess)
{
allTableProcesses.unshift(genericProcess);
}
} }
setAllTableProcesses(allTableProcesses); setAllTableProcesses(allTableProcesses);
if (launchingProcess) 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); 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 = ( const renderActionsMenu = (
<Menu <Menu
@ -785,11 +817,14 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
))} ))}
{(tableProcesses?.length > 0 || hasEditOrDelete) && <Divider />} {(tableProcesses?.length > 0 || hasEditOrDelete) && <Divider />}
{ {
runRecordScriptProcess && getGenericProcesses(metaData).map((process) =>
<MenuItem key={runRecordScriptProcess.name} onClick={() => processClicked(runRecordScriptProcess)}> (
<ListItemIcon><Icon>{runRecordScriptProcess.iconName ?? "arrow_forward"}</Icon></ListItemIcon> process &&
{runRecordScriptProcess.label} <MenuItem key={process.name} onClick={() => processClicked(process)}>
</MenuItem> <ListItemIcon><Icon>{process.iconName ?? "arrow_forward"}</Icon></ListItemIcon>
{process.label}
</MenuItem>
))
} }
<MenuItem onClick={() => navigate("dev")}> <MenuItem onClick={() => navigate("dev")}>
<ListItemIcon><Icon>code</Icon></ListItemIcon> <ListItemIcon><Icon>code</Icon></ListItemIcon>
@ -969,7 +1004,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
{ {
notFoundMessage notFoundMessage
? ?
<Alert color="error" sx={{mb: 3}}>{notFoundMessage}</Alert> <Alert color="error" sx={{mb: 3}} icon={<Icon>warning</Icon>}>{notFoundMessage}</Alert>
: :
<Box pb={3}> <Box pb={3}>
{ {