mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-17 21:00:45 +00:00
Merge branch 'main' into feature/CE-1107-add-day-by-day-views-for-the
This commit is contained in:
@ -115,7 +115,7 @@ workflows:
|
|||||||
context: [ qqq-maven-registry-credentials, kingsrook-slack, build-qqq-sample-app ]
|
context: [ qqq-maven-registry-credentials, kingsrook-slack, build-qqq-sample-app ]
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
ignore: /main/
|
ignore: /(main|integration.*)/
|
||||||
tags:
|
tags:
|
||||||
ignore: /(version|snapshot)-.*/
|
ignore: /(version|snapshot)-.*/
|
||||||
deploy:
|
deploy:
|
||||||
@ -124,7 +124,7 @@ workflows:
|
|||||||
context: [ qqq-maven-registry-credentials, kingsrook-slack, build-qqq-sample-app ]
|
context: [ qqq-maven-registry-credentials, kingsrook-slack, build-qqq-sample-app ]
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only: /main/
|
only: /(main|integration.*)/
|
||||||
tags:
|
tags:
|
||||||
only: /(version|snapshot)-.*/
|
only: /(version|snapshot)-.*/
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"@auth0/auth0-react": "1.10.2",
|
"@auth0/auth0-react": "1.10.2",
|
||||||
"@emotion/react": "11.7.1",
|
"@emotion/react": "11.7.1",
|
||||||
"@emotion/styled": "11.6.0",
|
"@emotion/styled": "11.6.0",
|
||||||
"@kingsrook/qqq-frontend-core": "1.0.94",
|
"@kingsrook/qqq-frontend-core": "1.0.96",
|
||||||
"@mui/icons-material": "5.4.1",
|
"@mui/icons-material": "5.4.1",
|
||||||
"@mui/material": "5.11.1",
|
"@mui/material": "5.11.1",
|
||||||
"@mui/styles": "5.11.1",
|
"@mui/styles": "5.11.1",
|
||||||
@ -42,6 +42,7 @@
|
|||||||
"react-chartjs-2": "3.0.4",
|
"react-chartjs-2": "3.0.4",
|
||||||
"react-cookie": "4.1.1",
|
"react-cookie": "4.1.1",
|
||||||
"react-dom": "18.0.0",
|
"react-dom": "18.0.0",
|
||||||
|
"react-ga4": "2.1.0",
|
||||||
"react-github-btn": "1.2.1",
|
"react-github-btn": "1.2.1",
|
||||||
"react-google-drive-picker": "^1.2.0",
|
"react-google-drive-picker": "^1.2.0",
|
||||||
"react-markdown": "9.0.1",
|
"react-markdown": "9.0.1",
|
||||||
|
27
src/App.tsx
27
src/App.tsx
@ -33,12 +33,8 @@ import CssBaseline from "@mui/material/CssBaseline";
|
|||||||
import Icon from "@mui/material/Icon";
|
import Icon from "@mui/material/Icon";
|
||||||
import {ThemeProvider} from "@mui/material/styles";
|
import {ThemeProvider} from "@mui/material/styles";
|
||||||
import {LicenseInfo} from "@mui/x-license-pro";
|
import {LicenseInfo} from "@mui/x-license-pro";
|
||||||
import jwt_decode from "jwt-decode";
|
|
||||||
import React, {JSXElementConstructor, Key, ReactElement, useEffect, useState,} from "react";
|
|
||||||
import {useCookies} from "react-cookie";
|
|
||||||
import {Navigate, Route, Routes, useLocation, useSearchParams,} from "react-router-dom";
|
|
||||||
import {Md5} from "ts-md5/dist/md5";
|
|
||||||
import CommandMenu from "CommandMenu";
|
import CommandMenu from "CommandMenu";
|
||||||
|
import jwt_decode from "jwt-decode";
|
||||||
import QContext from "QContext";
|
import QContext from "QContext";
|
||||||
import Sidenav from "qqq/components/horseshoe/sidenav/SideNav";
|
import Sidenav from "qqq/components/horseshoe/sidenav/SideNav";
|
||||||
import theme from "qqq/components/legacy/Theme";
|
import theme from "qqq/components/legacy/Theme";
|
||||||
@ -53,8 +49,13 @@ import EntityEdit from "qqq/pages/records/edit/RecordEdit";
|
|||||||
import RecordQuery from "qqq/pages/records/query/RecordQuery";
|
import RecordQuery from "qqq/pages/records/query/RecordQuery";
|
||||||
import RecordDeveloperView from "qqq/pages/records/view/RecordDeveloperView";
|
import RecordDeveloperView from "qqq/pages/records/view/RecordDeveloperView";
|
||||||
import RecordView from "qqq/pages/records/view/RecordView";
|
import RecordView from "qqq/pages/records/view/RecordView";
|
||||||
|
import GoogleAnalyticsUtils, {AnalyticsModel} from "qqq/utils/GoogleAnalyticsUtils";
|
||||||
import Client from "qqq/utils/qqq/Client";
|
import Client from "qqq/utils/qqq/Client";
|
||||||
import ProcessUtils from "qqq/utils/qqq/ProcessUtils";
|
import ProcessUtils from "qqq/utils/qqq/ProcessUtils";
|
||||||
|
import React, {JSXElementConstructor, Key, ReactElement, useEffect, useState,} from "react";
|
||||||
|
import {useCookies} from "react-cookie";
|
||||||
|
import {Navigate, Route, Routes, useLocation, useSearchParams,} from "react-router-dom";
|
||||||
|
import {Md5} from "ts-md5/dist/md5";
|
||||||
|
|
||||||
|
|
||||||
const qController = Client.getInstance();
|
const qController = Client.getInstance();
|
||||||
@ -160,7 +161,7 @@ export default function App()
|
|||||||
if (shouldStoreNewToken(accessToken, lsAccessToken))
|
if (shouldStoreNewToken(accessToken, lsAccessToken))
|
||||||
{
|
{
|
||||||
console.log("Sending accessToken to backend, requesting a sessionUUID...");
|
console.log("Sending accessToken to backend, requesting a sessionUUID...");
|
||||||
const newSessionUuid = await qController.manageSession(accessToken, null);
|
const {uuid: newSessionUuid, values} = await qController.manageSession(accessToken, null);
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// the request to the backend should send a header to set the cookie, so we don't need to do it ourselves. //
|
// the request to the backend should send a header to set the cookie, so we don't need to do it ourselves. //
|
||||||
@ -168,6 +169,7 @@ export default function App()
|
|||||||
// setCookie(SESSION_UUID_COOKIE_NAME, newSessionUuid, {path: "/"});
|
// setCookie(SESSION_UUID_COOKIE_NAME, newSessionUuid, {path: "/"});
|
||||||
|
|
||||||
localStorage.setItem("accessToken", accessToken);
|
localStorage.setItem("accessToken", accessToken);
|
||||||
|
localStorage.setItem("sessionValues", JSON.stringify(values));
|
||||||
console.log("Got new sessionUUID from backend, and stored new accessToken");
|
console.log("Got new sessionUUID from backend, and stored new accessToken");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -663,6 +665,18 @@ export default function App()
|
|||||||
const [keyboardHelpOpen, setKeyboardHelpOpen] = useState(false);
|
const [keyboardHelpOpen, setKeyboardHelpOpen] = useState(false);
|
||||||
const [helpHelpActive] = useState(queryParams.has("helpHelp"));
|
const [helpHelpActive] = useState(queryParams.has("helpHelp"));
|
||||||
|
|
||||||
|
const [googleAnalyticsUtils] = useState(new GoogleAnalyticsUtils());
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
function recordAnalytics(model: AnalyticsModel)
|
||||||
|
{
|
||||||
|
googleAnalyticsUtils.recordAnalytics(model)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
appRoutes && (
|
appRoutes && (
|
||||||
@ -682,6 +696,7 @@ export default function App()
|
|||||||
setTableProcesses: (tableProcesses: QProcessMetaData[]) => setTableProcesses(tableProcesses),
|
setTableProcesses: (tableProcesses: QProcessMetaData[]) => setTableProcesses(tableProcesses),
|
||||||
setDotMenuOpen: (dotMenuOpent: boolean) => setDotMenuOpen(dotMenuOpent),
|
setDotMenuOpen: (dotMenuOpent: boolean) => setDotMenuOpen(dotMenuOpent),
|
||||||
setKeyboardHelpOpen: (keyboardHelpOpen: boolean) => setKeyboardHelpOpen(keyboardHelpOpen),
|
setKeyboardHelpOpen: (keyboardHelpOpen: boolean) => setKeyboardHelpOpen(keyboardHelpOpen),
|
||||||
|
recordAnalytics: recordAnalytics,
|
||||||
pathToLabelMap: pathToLabelMap,
|
pathToLabelMap: pathToLabelMap,
|
||||||
branding: branding
|
branding: branding
|
||||||
}}>
|
}}>
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
import {QBrandingMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QBrandingMetaData";
|
import {QBrandingMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QBrandingMetaData";
|
||||||
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
||||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||||
|
import {AnalyticsModel} from "qqq/utils/GoogleAnalyticsUtils";
|
||||||
import {createContext} from "react";
|
import {createContext} from "react";
|
||||||
|
|
||||||
interface QContext
|
interface QContext
|
||||||
@ -47,6 +48,11 @@ interface QContext
|
|||||||
tableProcesses?: QProcessMetaData[];
|
tableProcesses?: QProcessMetaData[];
|
||||||
setTableProcesses?: (tableProcesses: QProcessMetaData[]) => void;
|
setTableProcesses?: (tableProcesses: QProcessMetaData[]) => void;
|
||||||
|
|
||||||
|
///////////////////////////////////////////
|
||||||
|
// function to record an analytics event //
|
||||||
|
///////////////////////////////////////////
|
||||||
|
recordAnalytics?: (model: AnalyticsModel) => void;
|
||||||
|
|
||||||
///////////////////////////////////
|
///////////////////////////////////
|
||||||
// constants - no setters needed //
|
// constants - no setters needed //
|
||||||
///////////////////////////////////
|
///////////////////////////////////
|
||||||
|
@ -82,7 +82,7 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
const qController = Client.getInstance();
|
const qController = Client.getInstance();
|
||||||
const tableNameParam = useParams().tableName;
|
const tableNameParam = useParams().tableName;
|
||||||
const tableName = props.table === null ? tableNameParam : props.table.name;
|
const tableName = props.table === null ? tableNameParam : props.table.name;
|
||||||
const {accentColor} = useContext(QContext);
|
const {accentColor, recordAnalytics} = useContext(QContext);
|
||||||
|
|
||||||
const [formTitle, setFormTitle] = useState("");
|
const [formTitle, setFormTitle] = useState("");
|
||||||
const [validations, setValidations] = useState({});
|
const [validations, setValidations] = useState({});
|
||||||
@ -359,6 +359,7 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||||
setTableMetaData(tableMetaData);
|
setTableMetaData(tableMetaData);
|
||||||
|
recordAnalytics({location: window.location, title: (props.isCopy ? "Copy" : props.id ? "Edit" : "New") + ": " + tableMetaData.label});
|
||||||
|
|
||||||
const metaData = await qController.loadMetaData();
|
const metaData = await qController.loadMetaData();
|
||||||
setMetaData(metaData);
|
setMetaData(metaData);
|
||||||
@ -389,6 +390,7 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
record = await qController.get(tableName, props.id);
|
record = await qController.get(tableName, props.id);
|
||||||
setRecord(record);
|
setRecord(record);
|
||||||
|
recordAnalytics({category: "tableEvents", action: props.isCopy ? "copy" : "edit", label: tableMetaData?.label + " / " + record?.recordLabel});
|
||||||
|
|
||||||
const titleVerb = props.isCopy ? "Copy" : "Edit";
|
const titleVerb = props.isCopy ? "Copy" : "Edit";
|
||||||
setFormTitle(`${titleVerb} ${tableMetaData?.label}: ${record?.recordLabel}`);
|
setFormTitle(`${titleVerb} ${tableMetaData?.label}: ${record?.recordLabel}`);
|
||||||
@ -428,6 +430,7 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
// else handle preparing to do an insert //
|
// else handle preparing to do an insert //
|
||||||
///////////////////////////////////////////
|
///////////////////////////////////////////
|
||||||
setFormTitle(`Creating New ${tableMetaData?.label}`);
|
setFormTitle(`Creating New ${tableMetaData?.label}`);
|
||||||
|
recordAnalytics({category: "tableEvents", action: "new", label: tableMetaData?.label});
|
||||||
|
|
||||||
if (!props.isModal)
|
if (!props.isModal)
|
||||||
{
|
{
|
||||||
@ -757,6 +760,8 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
|
|
||||||
if (props.id !== null && !props.isCopy)
|
if (props.id !== null && !props.isCopy)
|
||||||
{
|
{
|
||||||
|
recordAnalytics({category: "tableEvents", action: "saveEdit", label: tableMetaData?.label});
|
||||||
|
|
||||||
///////////////////////
|
///////////////////////
|
||||||
// perform an update //
|
// perform an update //
|
||||||
///////////////////////
|
///////////////////////
|
||||||
@ -799,6 +804,8 @@ function EntityForm(props: Props): JSX.Element
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
recordAnalytics({category: "tableEvents", action: props.isCopy ? "saveCopy" : "saveNew", label: tableMetaData?.label});
|
||||||
|
|
||||||
/////////////////////////////////
|
/////////////////////////////////
|
||||||
// perform an insert //
|
// perform an insert //
|
||||||
// todo - audit if it's a dupe //
|
// todo - audit if it's a dupe //
|
||||||
|
@ -23,8 +23,9 @@ import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QT
|
|||||||
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
|
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import {GridColDef, GridExportMenuItemProps} from "@mui/x-data-grid-pro";
|
import {GridColDef, GridExportMenuItemProps} from "@mui/x-data-grid-pro";
|
||||||
import React from "react";
|
import QContext from "QContext";
|
||||||
import ValueUtils from "qqq/utils/qqq/ValueUtils";
|
import ValueUtils from "qqq/utils/qqq/ValueUtils";
|
||||||
|
import React, {useContext} from "react";
|
||||||
|
|
||||||
interface QExportMenuItemProps extends GridExportMenuItemProps<{}>
|
interface QExportMenuItemProps extends GridExportMenuItemProps<{}>
|
||||||
{
|
{
|
||||||
@ -43,6 +44,10 @@ export default function ExportMenuItem(props: QExportMenuItemProps)
|
|||||||
{
|
{
|
||||||
const {format, tableMetaData, totalRecords, columnsModel, columnVisibilityModel, queryFilter, hideMenu} = props;
|
const {format, tableMetaData, totalRecords, columnsModel, columnVisibilityModel, queryFilter, hideMenu} = props;
|
||||||
|
|
||||||
|
const {recordAnalytics} = useContext(QContext);
|
||||||
|
|
||||||
|
recordAnalytics({category: "tableEvents", action: "export", label: tableMetaData.label});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
disabled={totalRecords === 0}
|
disabled={totalRecords === 0}
|
||||||
|
@ -31,8 +31,6 @@ import Box from "@mui/material/Box";
|
|||||||
import Card from "@mui/material/Card";
|
import Card from "@mui/material/Card";
|
||||||
import Divider from "@mui/material/Divider";
|
import Divider from "@mui/material/Divider";
|
||||||
import Grid from "@mui/material/Grid";
|
import Grid from "@mui/material/Grid";
|
||||||
import React, {useContext, useEffect, useState} from "react";
|
|
||||||
import {Link, useLocation} from "react-router-dom";
|
|
||||||
import QContext from "QContext";
|
import QContext from "QContext";
|
||||||
import colors from "qqq/assets/theme/base/colors";
|
import colors from "qqq/assets/theme/base/colors";
|
||||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||||
@ -41,6 +39,8 @@ import DashboardWidgets from "qqq/components/widgets/DashboardWidgets";
|
|||||||
import MiniStatisticsCard from "qqq/components/widgets/statistics/MiniStatisticsCard";
|
import MiniStatisticsCard from "qqq/components/widgets/statistics/MiniStatisticsCard";
|
||||||
import BaseLayout from "qqq/layouts/BaseLayout";
|
import BaseLayout from "qqq/layouts/BaseLayout";
|
||||||
import Client from "qqq/utils/qqq/Client";
|
import Client from "qqq/utils/qqq/Client";
|
||||||
|
import React, {useContext, useEffect, useState} from "react";
|
||||||
|
import {Link, useLocation} from "react-router-dom";
|
||||||
|
|
||||||
const qController = Client.getInstance();
|
const qController = Client.getInstance();
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ function AppHome({app}: Props): JSX.Element
|
|||||||
const [updatedTableCounts, setUpdatedTableCounts] = useState(new Date());
|
const [updatedTableCounts, setUpdatedTableCounts] = useState(new Date());
|
||||||
const [widgets, setWidgets] = useState([] as any[]);
|
const [widgets, setWidgets] = useState([] as any[]);
|
||||||
|
|
||||||
const {pageHeader, setPageHeader} = useContext(QContext);
|
const {pageHeader, recordAnalytics, setPageHeader} = useContext(QContext);
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
@ -86,8 +86,9 @@ function AppHome({app}: Props): JSX.Element
|
|||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
// setPageHeader(app.label);
|
|
||||||
setPageHeader(null);
|
setPageHeader(null);
|
||||||
|
recordAnalytics({location: window.location, title: "App: " + app.label});
|
||||||
|
recordAnalytics({category: "appEvents", action: "loadAppScreen", label: app.label});
|
||||||
|
|
||||||
if (!qInstance)
|
if (!qInstance)
|
||||||
{
|
{
|
||||||
|
@ -124,7 +124,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
|||||||
const [showErrorDetail, setShowErrorDetail] = useState(false);
|
const [showErrorDetail, setShowErrorDetail] = useState(false);
|
||||||
const [showFullHelpText, setShowFullHelpText] = useState(false);
|
const [showFullHelpText, setShowFullHelpText] = useState(false);
|
||||||
|
|
||||||
const {pageHeader, setPageHeader} = useContext(QContext);
|
const {pageHeader, recordAnalytics, setPageHeader} = useContext(QContext);
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// for setting the processError state - call this function, which will also set the isUserFacingError state //
|
// for setting the processError state - call this function, which will also set the isUserFacingError state //
|
||||||
@ -1146,6 +1146,10 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
|||||||
const processMetaData = await Client.getInstance().loadProcessMetaData(processName);
|
const processMetaData = await Client.getInstance().loadProcessMetaData(processName);
|
||||||
setProcessMetaData(processMetaData);
|
setProcessMetaData(processMetaData);
|
||||||
setSteps(processMetaData.frontendSteps);
|
setSteps(processMetaData.frontendSteps);
|
||||||
|
|
||||||
|
recordAnalytics({location: window.location, title: "Process: " + processMetaData?.label});
|
||||||
|
recordAnalytics({category: "processEvents", action: "startProcess", label: processMetaData?.label});
|
||||||
|
|
||||||
if (processMetaData.tableName && !tableMetaData)
|
if (processMetaData.tableName && !tableMetaData)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -1251,6 +1255,8 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
|||||||
|
|
||||||
setTimeout(async () =>
|
setTimeout(async () =>
|
||||||
{
|
{
|
||||||
|
recordAnalytics({category: "processEvents", action: "processStep", label: activeStep.label});
|
||||||
|
|
||||||
const processResponse = await Client.getInstance().processStep(
|
const processResponse = await Client.getInstance().processStep(
|
||||||
processName,
|
processName,
|
||||||
processUUID,
|
processUUID,
|
||||||
|
@ -338,7 +338,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
// page context references //
|
// page context references //
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
const {accentColor, accentColorLight, setPageHeader, dotMenuOpen, keyboardHelpOpen} = useContext(QContext);
|
const {accentColor, accentColorLight, setPageHeader, recordAnalytics, dotMenuOpen, keyboardHelpOpen} = useContext(QContext);
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
// we use our own header - so clear out the context page header //
|
// we use our own header - so clear out the context page header //
|
||||||
@ -610,25 +610,25 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
|
|
||||||
if (validType && !dotMenuOpen && !keyboardHelpOpen && !activeModalProcess)
|
if (validType && !dotMenuOpen && !keyboardHelpOpen && !activeModalProcess)
|
||||||
{
|
{
|
||||||
if (!e.metaKey && e.key === "n" && table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission)
|
if (!e.metaKey && !e.ctrlKey && e.key === "n" && table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission)
|
||||||
{
|
{
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
navigate(`${metaData?.getTablePathByName(tableName)}/create`);
|
navigate(`${metaData?.getTablePathByName(tableName)}/create`);
|
||||||
}
|
}
|
||||||
else if (!e.metaKey && e.key === "r")
|
else if (!e.metaKey && !e.ctrlKey && e.key === "r")
|
||||||
{
|
{
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
updateTable("'r' keyboard event");
|
updateTable("'r' keyboard event");
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
// disable until we add a ... ref down to let us programmatically open Columns button
|
// disable until we add a ... ref down to let us programmatically open Columns button
|
||||||
else if (! e.metaKey && e.key === "c")
|
else if (! e.metaKey && !e.ctrlKey && e.key === "c")
|
||||||
{
|
{
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
gridApiRef.current.showPreferences(GridPreferencePanelsValue.columns)
|
gridApiRef.current.showPreferences(GridPreferencePanelsValue.columns)
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
else if (!e.metaKey && e.key === "f")
|
else if (!e.metaKey && !e.ctrlKey && e.key === "f")
|
||||||
{
|
{
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
@ -875,6 +875,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
recordAnalytics({category: "tableEvents", action: "query", label: tableMetaData.label});
|
||||||
|
|
||||||
console.log(`In updateTable for ${reason} ${JSON.stringify(queryFilter)}`);
|
console.log(`In updateTable for ${reason} ${JSON.stringify(queryFilter)}`);
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setRows([]);
|
setRows([]);
|
||||||
@ -1642,6 +1644,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
if (selectedSavedViewId != null)
|
if (selectedSavedViewId != null)
|
||||||
{
|
{
|
||||||
|
recordAnalytics({category: "tableEvents", action: "activateSavedView", label: tableMetaData.label});
|
||||||
|
|
||||||
//////////////////////////////////////////////
|
//////////////////////////////////////////////
|
||||||
// fetch, then activate the selected filter //
|
// fetch, then activate the selected filter //
|
||||||
//////////////////////////////////////////////
|
//////////////////////////////////////////////
|
||||||
@ -1657,6 +1661,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
/////////////////////////////////
|
/////////////////////////////////
|
||||||
// this is 'new view' - right? //
|
// this is 'new view' - right? //
|
||||||
/////////////////////////////////
|
/////////////////////////////////
|
||||||
|
recordAnalytics({category: "tableEvents", action: "activateNewView", label: tableMetaData.label});
|
||||||
|
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
// wipe away the saved view //
|
// wipe away the saved view //
|
||||||
@ -2327,6 +2332,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
setTableMetaData(tableMetaData);
|
setTableMetaData(tableMetaData);
|
||||||
setTableLabel(tableMetaData.label);
|
setTableLabel(tableMetaData.label);
|
||||||
|
|
||||||
|
recordAnalytics({location: window.location, title: "Query: " + tableMetaData.label});
|
||||||
|
|
||||||
setTableProcesses(ProcessUtils.getProcessesForTable(metaData, tableName)); // these are the ones to show in the dropdown
|
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)
|
setAllTableProcesses(ProcessUtils.getProcessesForTable(metaData, tableName, true)); // these include hidden ones (e.g., to find the bulks)
|
||||||
|
|
||||||
|
@ -69,13 +69,9 @@ function RecordDeveloperView({table}: Props): JSX.Element
|
|||||||
const [associatedScripts, setAssociatedScripts] = useState([] as any[]);
|
const [associatedScripts, setAssociatedScripts] = useState([] as any[]);
|
||||||
const [notFoundMessage, setNotFoundMessage] = useState(null);
|
const [notFoundMessage, setNotFoundMessage] = useState(null);
|
||||||
|
|
||||||
const [selectedTabs, setSelectedTabs] = useState({} as any);
|
|
||||||
const [viewingRevisions, setViewingRevisions] = useState({} as any);
|
|
||||||
const [scriptLogs, setScriptLogs] = useState({} as any);
|
|
||||||
|
|
||||||
const [alertText, setAlertText] = useState(null as string);
|
const [alertText, setAlertText] = useState(null as string);
|
||||||
|
|
||||||
const {setPageHeader} = useContext(QContext);
|
const {setPageHeader, recordAnalytics} = useContext(QContext);
|
||||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||||
|
|
||||||
if (!asyncLoadInited)
|
if (!asyncLoadInited)
|
||||||
@ -90,6 +86,8 @@ function RecordDeveloperView({table}: Props): JSX.Element
|
|||||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||||
setTableMetaData(tableMetaData);
|
setTableMetaData(tableMetaData);
|
||||||
|
|
||||||
|
recordAnalytics({location: window.location, title: "Developer Mode: " + tableMetaData.label});
|
||||||
|
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
// load top-level meta-data //
|
// load top-level meta-data //
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
|
@ -121,7 +121,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
|
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
|
||||||
const closeActionsMenu = () => setActionsMenu(null);
|
const closeActionsMenu = () => setActionsMenu(null);
|
||||||
|
|
||||||
const {accentColor, setPageHeader, tableMetaData, setTableMetaData, tableProcesses, setTableProcesses, dotMenuOpen, keyboardHelpOpen, helpHelpActive} = useContext(QContext);
|
const {accentColor, setPageHeader, tableMetaData, setTableMetaData, tableProcesses, setTableProcesses, dotMenuOpen, keyboardHelpOpen, helpHelpActive, recordAnalytics} = useContext(QContext);
|
||||||
|
|
||||||
if (localStorage.getItem(tableVariantLocalStorageKey))
|
if (localStorage.getItem(tableVariantLocalStorageKey))
|
||||||
{
|
{
|
||||||
@ -164,27 +164,27 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
|
|
||||||
if (validType && !dotMenuOpen && !keyboardHelpOpen && !showAudit && !showEditChildForm)
|
if (validType && !dotMenuOpen && !keyboardHelpOpen && !showAudit && !showEditChildForm)
|
||||||
{
|
{
|
||||||
if (!e.metaKey && e.key === "n" && table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission)
|
if (!e.metaKey && !e.ctrlKey && e.key === "n" && table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission)
|
||||||
{
|
{
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
gotoCreate();
|
gotoCreate();
|
||||||
}
|
}
|
||||||
else if (!e.metaKey && e.key === "e" && table.capabilities.has(Capability.TABLE_UPDATE) && table.editPermission)
|
else if (!e.metaKey && !e.ctrlKey && e.key === "e" && table.capabilities.has(Capability.TABLE_UPDATE) && table.editPermission)
|
||||||
{
|
{
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
navigate("edit");
|
navigate("edit");
|
||||||
}
|
}
|
||||||
else if (!e.metaKey && e.key === "c" && table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission)
|
else if (!e.metaKey && !e.ctrlKey && e.key === "c" && table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission)
|
||||||
{
|
{
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
navigate("copy");
|
navigate("copy");
|
||||||
}
|
}
|
||||||
else if (!e.metaKey && e.key === "d" && table.capabilities.has(Capability.TABLE_DELETE) && table.deletePermission)
|
else if (!e.metaKey && !e.ctrlKey && e.key === "d" && table.capabilities.has(Capability.TABLE_DELETE) && table.deletePermission)
|
||||||
{
|
{
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleClickDeleteButton();
|
handleClickDeleteButton();
|
||||||
}
|
}
|
||||||
else if (!e.metaKey && e.key === "a" && metaData && metaData.tables.has("audit"))
|
else if (!e.metaKey && !e.ctrlKey && e.key === "a" && metaData && metaData.tables.has("audit"))
|
||||||
{
|
{
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
navigate("#audit");
|
navigate("#audit");
|
||||||
@ -384,6 +384,8 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||||
setTableMetaData(tableMetaData);
|
setTableMetaData(tableMetaData);
|
||||||
|
|
||||||
|
recordAnalytics({location: window.location, title: "View: " + tableMetaData.label});
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
// load top-level meta-data (e.g., to find processes for table) //
|
// load top-level meta-data (e.g., to find processes for table) //
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
@ -430,6 +432,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
{
|
{
|
||||||
record = await qController.get(tableName, id, tableVariant, null, queryJoins);
|
record = await qController.get(tableName, id, tableVariant, null, queryJoins);
|
||||||
setRecord(record);
|
setRecord(record);
|
||||||
|
recordAnalytics({category: "tableEvents", action: "view", label: tableMetaData?.label + " / " + record?.recordLabel});
|
||||||
}
|
}
|
||||||
catch (e)
|
catch (e)
|
||||||
{
|
{
|
||||||
@ -631,6 +634,8 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
|||||||
event?.preventDefault();
|
event?.preventDefault();
|
||||||
(async () =>
|
(async () =>
|
||||||
{
|
{
|
||||||
|
recordAnalytics({category: "tableEvents", action: "delete", label: tableMetaData?.label + " / " + record?.recordLabel});
|
||||||
|
|
||||||
await qController.delete(tableName, id)
|
await qController.delete(tableName, id)
|
||||||
.then(() =>
|
.then(() =>
|
||||||
{
|
{
|
||||||
|
143
src/qqq/utils/GoogleAnalyticsUtils.ts
Normal file
143
src/qqq/utils/GoogleAnalyticsUtils.ts
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
||||||
|
import Client from "qqq/utils/qqq/Client";
|
||||||
|
import ReactGA from "react-ga4";
|
||||||
|
|
||||||
|
|
||||||
|
export interface PageView
|
||||||
|
{
|
||||||
|
location: Location;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserEvent
|
||||||
|
{
|
||||||
|
action: string;
|
||||||
|
category: string;
|
||||||
|
label?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AnalyticsModel = PageView | UserEvent;
|
||||||
|
|
||||||
|
const qController = Client.getInstance();
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Utilities for working with Google Analytics (through react-ga4)^
|
||||||
|
*******************************************************************************/
|
||||||
|
export default class GoogleAnalyticsUtils
|
||||||
|
{
|
||||||
|
private metaData: QInstance = null;
|
||||||
|
private active: boolean = false;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
constructor()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private send = (model: AnalyticsModel) =>
|
||||||
|
{
|
||||||
|
if(!this.active)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(model.hasOwnProperty("location"))
|
||||||
|
{
|
||||||
|
const pageView = model as PageView;
|
||||||
|
ReactGA.send({hitType: "pageview", page: pageView.location.pathname + pageView.location.search, title: pageView.title});
|
||||||
|
}
|
||||||
|
else if(model.hasOwnProperty("action") || model.hasOwnProperty("category") || model.hasOwnProperty("label"))
|
||||||
|
{
|
||||||
|
const userEvent = model as UserEvent;
|
||||||
|
ReactGA.event({action: userEvent.action, category: userEvent.category, label: userEvent.label})
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.log("Unrecognizable analytics model", model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private setup = async (): Promise<void> =>
|
||||||
|
{
|
||||||
|
this.metaData = await qController.loadMetaData();
|
||||||
|
|
||||||
|
let sessionValues: {[key: string]: any} = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
sessionValues = JSON.parse(localStorage.getItem("sessionValues"));
|
||||||
|
}
|
||||||
|
catch(e)
|
||||||
|
{
|
||||||
|
console.log("Error reading session values from localStorage: " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.metaData.environmentValues.get("GOOGLE_ANALYTICS_ENABLED") == "true" && this.metaData.environmentValues.get("GOOGLE_ANALYTICS_TRACKING_ID"))
|
||||||
|
{
|
||||||
|
this.active = true;
|
||||||
|
|
||||||
|
if(sessionValues && sessionValues["googleAnalyticsValues"])
|
||||||
|
{
|
||||||
|
ReactGA.gtag("set", "user_properties", sessionValues["googleAnalyticsValues"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactGA.initialize(this.metaData.environmentValues.get("GOOGLE_ANALYTICS_TRACKING_ID"),
|
||||||
|
{
|
||||||
|
gaOptions: {},
|
||||||
|
gtagOptions: {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public recordAnalytics = (model: AnalyticsModel) =>
|
||||||
|
{
|
||||||
|
if(this.metaData == null)
|
||||||
|
{
|
||||||
|
(async () =>
|
||||||
|
{
|
||||||
|
await this.setup();
|
||||||
|
})()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.send(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user