mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 21:30:45 +00:00
Merged feature/CE-1123-expose-access-lo-data-in into integration/sprint-40
This commit is contained in:
@ -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.94",
|
||||
"@kingsrook/qqq-frontend-core": "1.0.96",
|
||||
"@mui/icons-material": "5.4.1",
|
||||
"@mui/material": "5.11.1",
|
||||
"@mui/styles": "5.11.1",
|
||||
@ -44,6 +44,7 @@
|
||||
"react-dnd": "16.0.1",
|
||||
"react-dnd-html5-backend": "16.0.1",
|
||||
"react-dom": "18.0.0",
|
||||
"react-ga4": "2.1.0",
|
||||
"react-github-btn": "1.2.1",
|
||||
"react-google-drive-picker": "^1.2.0",
|
||||
"react-markdown": "9.0.1",
|
||||
|
46
src/App.tsx
46
src/App.tsx
@ -33,12 +33,8 @@ import CssBaseline from "@mui/material/CssBaseline";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import {ThemeProvider} from "@mui/material/styles";
|
||||
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 jwt_decode from "jwt-decode";
|
||||
import QContext from "QContext";
|
||||
import Sidenav from "qqq/components/horseshoe/sidenav/SideNav";
|
||||
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 RecordDeveloperView from "qqq/pages/records/view/RecordDeveloperView";
|
||||
import RecordView from "qqq/pages/records/view/RecordView";
|
||||
import GoogleAnalyticsUtils from "qqq/utils/GoogleAnalyticsUtils";
|
||||
import Client from "qqq/utils/qqq/Client";
|
||||
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();
|
||||
@ -160,7 +161,7 @@ export default function App()
|
||||
if (shouldStoreNewToken(accessToken, lsAccessToken))
|
||||
{
|
||||
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. //
|
||||
@ -168,6 +169,7 @@ export default function App()
|
||||
// setCookie(SESSION_UUID_COOKIE_NAME, newSessionUuid, {path: "/"});
|
||||
|
||||
localStorage.setItem("accessToken", accessToken);
|
||||
localStorage.setItem("sessionValues", JSON.stringify(values));
|
||||
console.log("Got new sessionUUID from backend, and stored new accessToken");
|
||||
}
|
||||
else
|
||||
@ -654,7 +656,7 @@ export default function App()
|
||||
},
|
||||
);
|
||||
|
||||
const [pageHeader, setPageHeader] = useState("" as string | JSX.Element);
|
||||
const [pageHeader, setPageHeaderState] = useState("" as string | JSX.Element);
|
||||
const [accentColor, setAccentColor] = useState("#0062FF");
|
||||
const [accentColorLight, setAccentColorLight] = useState("#C0D6F7");
|
||||
const [tableMetaData, setTableMetaData] = useState(null);
|
||||
@ -663,6 +665,35 @@ export default function App()
|
||||
const [keyboardHelpOpen, setKeyboardHelpOpen] = useState(false);
|
||||
const [helpHelpActive] = useState(queryParams.has("helpHelp"));
|
||||
|
||||
const [googleAnalyticsUtils] = useState(new GoogleAnalyticsUtils());
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function setPageHeader(header: string | JSX.Element)
|
||||
{
|
||||
setPageHeaderState(header);
|
||||
if(typeof header == "string")
|
||||
{
|
||||
recordAnalytics(header)
|
||||
}
|
||||
else
|
||||
{
|
||||
recordAnalytics("Title not available")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function recordAnalytics(title: string)
|
||||
{
|
||||
googleAnalyticsUtils.recordAnalytics(location, title)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
|
||||
appRoutes && (
|
||||
@ -682,6 +713,7 @@ export default function App()
|
||||
setTableProcesses: (tableProcesses: QProcessMetaData[]) => setTableProcesses(tableProcesses),
|
||||
setDotMenuOpen: (dotMenuOpent: boolean) => setDotMenuOpen(dotMenuOpent),
|
||||
setKeyboardHelpOpen: (keyboardHelpOpen: boolean) => setKeyboardHelpOpen(keyboardHelpOpen),
|
||||
recordAnalytics: recordAnalytics,
|
||||
pathToLabelMap: pathToLabelMap,
|
||||
branding: branding
|
||||
}}>
|
||||
|
@ -47,6 +47,11 @@ interface QContext
|
||||
tableProcesses?: QProcessMetaData[];
|
||||
setTableProcesses?: (tableProcesses: QProcessMetaData[]) => void;
|
||||
|
||||
///////////////////////////////////////////
|
||||
// function to record an analytics event //
|
||||
///////////////////////////////////////////
|
||||
recordAnalytics?: (title: string) => void;
|
||||
|
||||
///////////////////////////////////
|
||||
// constants - no setters needed //
|
||||
///////////////////////////////////
|
||||
|
@ -623,25 +623,25 @@ const RecordQuery = forwardRef(({table, usage, isModal}: Props, ref) =>
|
||||
|
||||
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();
|
||||
navigate(`${metaData?.getTablePathByName(tableName)}/create`);
|
||||
}
|
||||
else if (!e.metaKey && e.key === "r")
|
||||
else if (!e.metaKey && !e.ctrlKey && e.key === "r")
|
||||
{
|
||||
e.preventDefault();
|
||||
updateTable("'r' keyboard event");
|
||||
}
|
||||
/*
|
||||
// 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()
|
||||
gridApiRef.current.showPreferences(GridPreferencePanelsValue.columns)
|
||||
}
|
||||
*/
|
||||
else if (!e.metaKey && e.key === "f")
|
||||
else if (!e.metaKey && !e.ctrlKey && e.key === "f")
|
||||
{
|
||||
e.preventDefault();
|
||||
|
||||
|
@ -164,27 +164,27 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
|
||||
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();
|
||||
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();
|
||||
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();
|
||||
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();
|
||||
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();
|
||||
navigate("#audit");
|
||||
|
115
src/qqq/utils/GoogleAnalyticsUtils.ts
Normal file
115
src/qqq/utils/GoogleAnalyticsUtils.ts
Normal file
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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";
|
||||
|
||||
|
||||
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 = (location: Location, title: string) =>
|
||||
{
|
||||
if(!this.active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ReactGA.send({hitType: "pageview", page: location.pathname + location.search, title: title});
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
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 = (location: Location, title: string) =>
|
||||
{
|
||||
if(this.metaData == null)
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
await this.setup();
|
||||
})()
|
||||
}
|
||||
|
||||
this.send(location, title);
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user