mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-17 21:00:45 +00:00
Switch to write recordIds/queryFilter to IndexedDB when launching process (instead of passing on query string).
This commit is contained in:
@ -44,9 +44,10 @@ import LinearProgress from "@mui/material/LinearProgress";
|
|||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import Modal from "@mui/material/Modal";
|
import Modal from "@mui/material/Modal";
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import {ColumnHeaderFilterIconButtonProps, DataGridPro, GridCallbackDetails, GridColDef, GridColumnHeaderParams, GridColumnMenuContainer, GridColumnMenuProps, GridColumnOrderChangeParams, GridColumnPinningMenuItems, GridColumnResizeParams, GridColumnsMenuItem, GridColumnVisibilityModel, GridDensity, GridEventListener, GridFilterMenuItem, GridPinnedColumns, gridPreferencePanelStateSelector, GridPreferencePanelsValue, GridRowId, GridRowParams, GridRowsProp, GridSelectionModel, GridSortItem, GridSortModel, GridState, GridToolbarColumnsButton, GridToolbarContainer, GridToolbarDensitySelector, GridToolbarExportContainer, HideGridColMenuItem, MuiEvent, SortGridMenuItems, useGridApiContext, useGridApiEventHandler, useGridApiRef, useGridSelector} from "@mui/x-data-grid-pro";
|
import {ColumnHeaderFilterIconButtonProps, DataGridPro, GridCallbackDetails, GridColDef, GridColumnHeaderParams, GridColumnMenuContainer, GridColumnMenuProps, GridColumnOrderChangeParams, GridColumnPinningMenuItems, GridColumnResizeParams, GridColumnVisibilityModel, GridDensity, GridEventListener, GridPinnedColumns, gridPreferencePanelStateSelector, GridPreferencePanelsValue, GridRowId, GridRowParams, GridRowsProp, GridSelectionModel, GridSortItem, GridSortModel, GridState, GridToolbarContainer, GridToolbarDensitySelector, HideGridColMenuItem, MuiEvent, SortGridMenuItems, useGridApiContext, useGridApiEventHandler, useGridApiRef, useGridSelector} from "@mui/x-data-grid-pro";
|
||||||
import {GridRowModel} from "@mui/x-data-grid/models/gridRows";
|
import {GridRowModel} from "@mui/x-data-grid/models/gridRows";
|
||||||
import FormData from "form-data";
|
import FormData from "form-data";
|
||||||
|
import {IDBPDatabase, openDB} from "idb";
|
||||||
import React, {forwardRef, useContext, useEffect, useReducer, useRef, useState} from "react";
|
import React, {forwardRef, useContext, useEffect, useReducer, useRef, useState} from "react";
|
||||||
import {useLocation, useNavigate, useSearchParams} from "react-router-dom";
|
import {useLocation, useNavigate, useSearchParams} from "react-router-dom";
|
||||||
import QContext from "QContext";
|
import QContext from "QContext";
|
||||||
@ -101,6 +102,13 @@ RecordQuery.defaultProps = {
|
|||||||
///////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////
|
||||||
type PageState = "initial" | "loadingMetaData" | "loadedMetaData" | "loadingView" | "loadedView" | "preparingGrid" | "ready";
|
type PageState = "initial" | "loadingMetaData" | "loadedMetaData" | "loadingView" | "loadedView" | "preparingGrid" | "ready";
|
||||||
|
|
||||||
|
//////////////////////////////////////////
|
||||||
|
// define IndexedDB store & field names //
|
||||||
|
//////////////////////////////////////////
|
||||||
|
const recordIdsForProcessesDBName = "qqq.recordIdsForProcesses";
|
||||||
|
const recordIdsForProcessesStoreName = "recordIdsForProcesses";
|
||||||
|
const timestampIndexAndFieldName = "timestamp";
|
||||||
|
|
||||||
const qController = Client.getInstance();
|
const qController = Client.getInstance();
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -114,7 +122,6 @@ const getLoadingScreen = () =>
|
|||||||
</BaseLayout>);
|
</BaseLayout>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** QQQ Record Query Screen component.
|
** QQQ Record Query Screen component.
|
||||||
**
|
**
|
||||||
@ -1407,17 +1414,114 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** launch/open a modal process. Ends up navigating to the process's path w/
|
** For the various functions that work with the recordIdsForProcess indexedDB,
|
||||||
** records selected via query string.
|
** open that database (creating if needed).
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
const openModalProcess = (process: QProcessMetaData = null) =>
|
const openRecordIdsForProcessIndexedDB = async () =>
|
||||||
{
|
{
|
||||||
|
return await openDB(recordIdsForProcessesDBName, 1, {
|
||||||
|
upgrade(db)
|
||||||
|
{
|
||||||
|
const store = db.createObjectStore(recordIdsForProcessesStoreName, {
|
||||||
|
keyPath: "uuid"
|
||||||
|
});
|
||||||
|
store.createIndex(timestampIndexAndFieldName, timestampIndexAndFieldName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** clean up old indexedDB records that were created to launch processes in the past.
|
||||||
|
*******************************************************************************/
|
||||||
|
const manageRecordIdsForProcessIndexedDB = async (db: IDBPDatabase) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const now = new Date().getTime();
|
||||||
|
const limit = now - (1000 * 60 * 60 * 24 * 7); // now minus 1 week
|
||||||
|
|
||||||
|
const expiredKeys = await db.getAllKeysFromIndex(recordIdsForProcessesStoreName, timestampIndexAndFieldName, IDBKeyRange.upperBound(limit, true));
|
||||||
|
for (let expiredKey of expiredKeys)
|
||||||
|
{
|
||||||
|
db.delete(recordIdsForProcessesStoreName, expiredKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
console.log("Error managing recordIdsForProcess in indexeddb: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** we used to pass recordIds (that is, either an array of ids (from checkboxes)
|
||||||
|
** or a json-query-filter) on the query string... but that gets too big, so,
|
||||||
|
** instead, write it ... not to local storage (5MB limit, and we want to keep
|
||||||
|
** them around for ... some period of time, for reloading processes), so,
|
||||||
|
** write it to indexedDB instead.
|
||||||
|
*******************************************************************************/
|
||||||
|
const storeRecordIdsForProcessInIndexedDB = async (recordIds: string[] | QQueryFilter): Promise<string> =>
|
||||||
|
{
|
||||||
|
const uuid = crypto.randomUUID()
|
||||||
|
|
||||||
|
let db = await openRecordIdsForProcessIndexedDB();
|
||||||
|
await db.add(recordIdsForProcessesStoreName, {
|
||||||
|
uuid: uuid,
|
||||||
|
json: JSON.stringify(recordIds),
|
||||||
|
timestamp: new Date().getTime()
|
||||||
|
});
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// we shouldn't need to await this function - it's just good to run the cleanup "sometimes" //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
manageRecordIdsForProcessIndexedDB(db);
|
||||||
|
|
||||||
|
return (uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** when launching a process, if we're to use recordIds(/filter) from indexedDB,
|
||||||
|
** then do that read (async), and open the modal process after it completes.
|
||||||
|
*******************************************************************************/
|
||||||
|
const launchModalProcessUsingRecordIdsFromIndexedDB = async (uuid: string, processMetaData: QProcessMetaData): Promise<void> =>
|
||||||
|
{
|
||||||
|
let db = await openRecordIdsForProcessIndexedDB();
|
||||||
|
const recordIds = await db.get(recordIdsForProcessesStoreName, uuid)
|
||||||
|
|
||||||
|
if(recordIds)
|
||||||
|
{
|
||||||
|
const recordIdsObject = JSON.parse(recordIds.json);
|
||||||
|
setRecordIdsForProcess(recordIdsObject);
|
||||||
|
setActiveModalProcess(processMetaData);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// closing the process will do a navigate - so we can't just set an alert, we need it to be passed in the navigation call as state - //
|
||||||
|
// so pass it as a param to closeModalProcess, which will set it in the navigation state. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
closeModalProcess({}, "failed to start", "Could not find query filter to start this process. Please try again.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** launch/open a modal process. Writes the records (ids or filter) to indexedDB,
|
||||||
|
** identified by a UUID. Then navigates to the process's path w/
|
||||||
|
** that UUID in the query string.
|
||||||
|
*******************************************************************************/
|
||||||
|
const openModalProcess = async (process: QProcessMetaData = null) =>
|
||||||
|
{
|
||||||
|
let uuid = "";
|
||||||
if (selectFullFilterState === "filter")
|
if (selectFullFilterState === "filter")
|
||||||
{
|
{
|
||||||
const filterForBackend = prepQueryFilterForBackend(queryFilter);
|
const filterForBackend = prepQueryFilterForBackend(queryFilter);
|
||||||
filterForBackend.skip = 0;
|
filterForBackend.skip = 0;
|
||||||
filterForBackend.limit = null;
|
filterForBackend.limit = null;
|
||||||
setRecordIdsForProcess(filterForBackend);
|
setRecordIdsForProcess(filterForBackend);
|
||||||
|
uuid = await storeRecordIdsForProcessInIndexedDB(filterForBackend);
|
||||||
}
|
}
|
||||||
else if (selectFullFilterState === "filterSubset")
|
else if (selectFullFilterState === "filterSubset")
|
||||||
{
|
{
|
||||||
@ -1425,24 +1529,27 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
filterForBackend.skip = 0;
|
filterForBackend.skip = 0;
|
||||||
filterForBackend.limit = selectionSubsetSize;
|
filterForBackend.limit = selectionSubsetSize;
|
||||||
setRecordIdsForProcess(filterForBackend);
|
setRecordIdsForProcess(filterForBackend);
|
||||||
|
uuid = await storeRecordIdsForProcessInIndexedDB(filterForBackend);
|
||||||
}
|
}
|
||||||
else if (selectedIds.length > 0)
|
else if (selectedIds.length > 0)
|
||||||
{
|
{
|
||||||
setRecordIdsForProcess(selectedIds);
|
setRecordIdsForProcess(selectedIds);
|
||||||
|
uuid = await storeRecordIdsForProcessInIndexedDB(selectedIds);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
setRecordIdsForProcess([]);
|
setRecordIdsForProcess([]);
|
||||||
|
uuid = await storeRecordIdsForProcessInIndexedDB([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
navigate(`${metaData?.getTablePathByName(tableName)}/${process.name}${getRecordsQueryString()}`);
|
navigate(`${metaData?.getTablePathByName(tableName)}/${process.name}?recordsParam=recordsKey&recordsKey=${uuid}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** close callback for modal processes
|
** close callback for modal processes
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
const closeModalProcess = (event: object, reason: string) =>
|
const closeModalProcess = (event: object, reason: string, warning?: string) =>
|
||||||
{
|
{
|
||||||
if (reason === "backdropClick" || reason === "escapeKeyDown")
|
if (reason === "backdropClick" || reason === "escapeKeyDown")
|
||||||
{
|
{
|
||||||
@ -1454,7 +1561,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
/////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////
|
||||||
const newPath = location.pathname.split("/");
|
const newPath = location.pathname.split("/");
|
||||||
newPath.pop();
|
newPath.pop();
|
||||||
navigate(newPath.join("/"));
|
navigate(newPath.join("/"), warning ? {state: {warning: warning}} : undefined);
|
||||||
|
|
||||||
updateTable("close modal process");
|
updateTable("close modal process");
|
||||||
};
|
};
|
||||||
@ -2333,22 +2440,38 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
/////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////
|
||||||
if (pathParts[pathParts.length - 2] === tableName)
|
if (pathParts[pathParts.length - 2] === tableName)
|
||||||
{
|
{
|
||||||
|
////////////////////////////////////////
|
||||||
|
// try to find a process by this name //
|
||||||
|
////////////////////////////////////////
|
||||||
const processName = pathParts[pathParts.length - 1];
|
const processName = pathParts[pathParts.length - 1];
|
||||||
const processList = allTableProcesses.filter(p => p.name == processName);
|
const processList = allTableProcesses.filter(p => p.name == processName);
|
||||||
if (processList.length > 0)
|
let process = processList.length > 0 ? processList[0] : null;
|
||||||
|
if(!process)
|
||||||
{
|
{
|
||||||
setActiveModalProcess(processList[0]);
|
process = metaData?.processes.get(processName)
|
||||||
}
|
}
|
||||||
else if (metaData?.processes.has(processName))
|
|
||||||
|
if(process)
|
||||||
{
|
{
|
||||||
///////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
// check for generic processes - should this be a specific attribute on the process? //
|
// check if a recordsKey UUID (e.g., from indexedDB) is in the query string //
|
||||||
///////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
setActiveModalProcess(metaData?.processes.get(processName));
|
const urlSearchParams = new URLSearchParams(location.search);
|
||||||
|
const uuid = urlSearchParams.get("recordsKey")
|
||||||
|
|
||||||
|
if(uuid)
|
||||||
|
{
|
||||||
|
await launchModalProcessUsingRecordIdsFromIndexedDB(uuid, processList[0]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setActiveModalProcess(processList[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
console.log(`Couldn't find process named ${processName}`);
|
console.log(`Couldn't find process named ${processName}`);
|
||||||
|
setWarningAlert(`Couldn't find process named ${processName}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user