mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-17 21:00:45 +00:00
CE-1123 - Update google analytics to work with events as well as page views; add calls to it to most actual pages.
This commit is contained in:
25
src/App.tsx
25
src/App.tsx
@ -49,7 +49,7 @@ 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 GoogleAnalyticsUtils, {AnalyticsModel} 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";
|
||||
@ -656,7 +656,7 @@ export default function App()
|
||||
},
|
||||
);
|
||||
|
||||
const [pageHeader, setPageHeaderState] = useState("" as string | JSX.Element);
|
||||
const [pageHeader, setPageHeader] = useState("" as string | JSX.Element);
|
||||
const [accentColor, setAccentColor] = useState("#0062FF");
|
||||
const [accentColorLight, setAccentColorLight] = useState("#C0D6F7")
|
||||
const [tableMetaData, setTableMetaData] = useState(null);
|
||||
@ -671,26 +671,9 @@ export default function App()
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function setPageHeader(header: string | JSX.Element)
|
||||
function recordAnalytics(model: AnalyticsModel)
|
||||
{
|
||||
setPageHeaderState(header);
|
||||
if(typeof header == "string")
|
||||
{
|
||||
recordAnalytics(header)
|
||||
}
|
||||
else
|
||||
{
|
||||
recordAnalytics("Title not available")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function recordAnalytics(title: string)
|
||||
{
|
||||
googleAnalyticsUtils.recordAnalytics(location, title)
|
||||
googleAnalyticsUtils.recordAnalytics(model)
|
||||
}
|
||||
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
import {QBrandingMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QBrandingMetaData";
|
||||
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
import {AnalyticsModel} from "qqq/utils/GoogleAnalyticsUtils";
|
||||
import {createContext} from "react";
|
||||
|
||||
interface QContext
|
||||
@ -50,7 +51,7 @@ interface QContext
|
||||
///////////////////////////////////////////
|
||||
// function to record an analytics event //
|
||||
///////////////////////////////////////////
|
||||
recordAnalytics?: (title: string) => void;
|
||||
recordAnalytics?: (model: AnalyticsModel) => void;
|
||||
|
||||
///////////////////////////////////
|
||||
// constants - no setters needed //
|
||||
|
@ -82,7 +82,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
const qController = Client.getInstance();
|
||||
const tableNameParam = useParams().tableName;
|
||||
const tableName = props.table === null ? tableNameParam : props.table.name;
|
||||
const {accentColor} = useContext(QContext);
|
||||
const {accentColor, recordAnalytics} = useContext(QContext);
|
||||
|
||||
const [formTitle, setFormTitle] = useState("");
|
||||
const [validations, setValidations] = useState({});
|
||||
@ -359,6 +359,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
{
|
||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||
setTableMetaData(tableMetaData);
|
||||
recordAnalytics({location: window.location, title: (props.isCopy ? "Copy" : props.id ? "Edit" : "New") + ": " + tableMetaData.label});
|
||||
|
||||
const metaData = await qController.loadMetaData();
|
||||
setMetaData(metaData);
|
||||
@ -389,6 +390,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
{
|
||||
record = await qController.get(tableName, props.id);
|
||||
setRecord(record);
|
||||
recordAnalytics({category: "tableEvents", action: props.isCopy ? "copy" : "edit", label: tableMetaData?.label + " / " + record?.recordLabel});
|
||||
|
||||
const titleVerb = props.isCopy ? "Copy" : "Edit";
|
||||
setFormTitle(`${titleVerb} ${tableMetaData?.label}: ${record?.recordLabel}`);
|
||||
@ -428,6 +430,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
// else handle preparing to do an insert //
|
||||
///////////////////////////////////////////
|
||||
setFormTitle(`Creating New ${tableMetaData?.label}`);
|
||||
recordAnalytics({category: "tableEvents", action: "new", label: tableMetaData?.label});
|
||||
|
||||
if (!props.isModal)
|
||||
{
|
||||
@ -757,6 +760,8 @@ function EntityForm(props: Props): JSX.Element
|
||||
|
||||
if (props.id !== null && !props.isCopy)
|
||||
{
|
||||
recordAnalytics({category: "tableEvents", action: "saveEdit", label: tableMetaData?.label});
|
||||
|
||||
///////////////////////
|
||||
// perform an update //
|
||||
///////////////////////
|
||||
@ -799,6 +804,8 @@ function EntityForm(props: Props): JSX.Element
|
||||
}
|
||||
else
|
||||
{
|
||||
recordAnalytics({category: "tableEvents", action: props.isCopy ? "saveCopy" : "saveNew", label: tableMetaData?.label});
|
||||
|
||||
/////////////////////////////////
|
||||
// perform an insert //
|
||||
// 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 MenuItem from "@mui/material/MenuItem";
|
||||
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 React, {useContext} from "react";
|
||||
|
||||
interface QExportMenuItemProps extends GridExportMenuItemProps<{}>
|
||||
{
|
||||
@ -43,6 +44,10 @@ export default function ExportMenuItem(props: QExportMenuItemProps)
|
||||
{
|
||||
const {format, tableMetaData, totalRecords, columnsModel, columnVisibilityModel, queryFilter, hideMenu} = props;
|
||||
|
||||
const {recordAnalytics} = useContext(QContext);
|
||||
|
||||
recordAnalytics({category: "tableEvents", action: "export", label: tableMetaData.label});
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
disabled={totalRecords === 0}
|
||||
|
@ -31,8 +31,6 @@ import Box from "@mui/material/Box";
|
||||
import Card from "@mui/material/Card";
|
||||
import Divider from "@mui/material/Divider";
|
||||
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 colors from "qqq/assets/theme/base/colors";
|
||||
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 BaseLayout from "qqq/layouts/BaseLayout";
|
||||
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();
|
||||
|
||||
@ -62,7 +62,7 @@ function AppHome({app}: Props): JSX.Element
|
||||
const [updatedTableCounts, setUpdatedTableCounts] = useState(new Date());
|
||||
const [widgets, setWidgets] = useState([] as any[]);
|
||||
|
||||
const {pageHeader, setPageHeader} = useContext(QContext);
|
||||
const {pageHeader, recordAnalytics, setPageHeader} = useContext(QContext);
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
@ -86,8 +86,9 @@ function AppHome({app}: Props): JSX.Element
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
// setPageHeader(app.label);
|
||||
setPageHeader(null);
|
||||
recordAnalytics({location: window.location, title: "App: " + app.label});
|
||||
recordAnalytics({category: "appEvents", action: "loadAppScreen", label: app.label});
|
||||
|
||||
if (!qInstance)
|
||||
{
|
||||
|
@ -124,7 +124,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
const [showErrorDetail, setShowErrorDetail] = 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 //
|
||||
@ -1146,6 +1146,10 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
const processMetaData = await Client.getInstance().loadProcessMetaData(processName);
|
||||
setProcessMetaData(processMetaData);
|
||||
setSteps(processMetaData.frontendSteps);
|
||||
|
||||
recordAnalytics({location: window.location, title: "Process: " + processMetaData?.label});
|
||||
recordAnalytics({category: "processEvents", action: "startProcess", label: processMetaData?.label});
|
||||
|
||||
if (processMetaData.tableName && !tableMetaData)
|
||||
{
|
||||
try
|
||||
@ -1251,6 +1255,8 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
|
||||
setTimeout(async () =>
|
||||
{
|
||||
recordAnalytics({category: "processEvents", action: "processStep", label: activeStep.label});
|
||||
|
||||
const processResponse = await Client.getInstance().processStep(
|
||||
processName,
|
||||
processUUID,
|
||||
|
@ -338,7 +338,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
/////////////////////////////
|
||||
// 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 //
|
||||
@ -875,6 +875,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
return;
|
||||
}
|
||||
|
||||
recordAnalytics({category: "tableEvents", action: "query", label: tableMetaData.label});
|
||||
|
||||
console.log(`In updateTable for ${reason} ${JSON.stringify(queryFilter)}`);
|
||||
setLoading(true);
|
||||
setRows([]);
|
||||
@ -1642,6 +1644,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
{
|
||||
if (selectedSavedViewId != null)
|
||||
{
|
||||
recordAnalytics({category: "tableEvents", action: "activateSavedView", label: tableMetaData.label});
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// fetch, then activate the selected filter //
|
||||
//////////////////////////////////////////////
|
||||
@ -1657,6 +1661,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
/////////////////////////////////
|
||||
// this is 'new view' - right? //
|
||||
/////////////////////////////////
|
||||
recordAnalytics({category: "tableEvents", action: "activateNewView", label: tableMetaData.label});
|
||||
|
||||
//////////////////////////////
|
||||
// wipe away the saved view //
|
||||
@ -2327,6 +2332,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
setTableMetaData(tableMetaData);
|
||||
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
|
||||
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 [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 {setPageHeader} = useContext(QContext);
|
||||
const {setPageHeader, recordAnalytics} = useContext(QContext);
|
||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||
|
||||
if (!asyncLoadInited)
|
||||
@ -90,6 +86,8 @@ function RecordDeveloperView({table}: Props): JSX.Element
|
||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||
setTableMetaData(tableMetaData);
|
||||
|
||||
recordAnalytics({location: window.location, title: "Developer Mode: " + tableMetaData.label});
|
||||
|
||||
//////////////////////////////
|
||||
// load top-level meta-data //
|
||||
//////////////////////////////
|
||||
|
@ -121,7 +121,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
|
||||
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))
|
||||
{
|
||||
@ -384,6 +384,8 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||
setTableMetaData(tableMetaData);
|
||||
|
||||
recordAnalytics({location: window.location, title: "View: " + tableMetaData.label});
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// 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);
|
||||
setRecord(record);
|
||||
recordAnalytics({category: "tableEvents", action: "view", label: tableMetaData?.label + " / " + record?.recordLabel});
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
@ -631,6 +634,8 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
event?.preventDefault();
|
||||
(async () =>
|
||||
{
|
||||
recordAnalytics({category: "tableEvents", action: "delete", label: tableMetaData?.label + " / " + record?.recordLabel});
|
||||
|
||||
await qController.delete(tableName, id)
|
||||
.then(() =>
|
||||
{
|
||||
|
@ -24,6 +24,21 @@ 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();
|
||||
|
||||
/*******************************************************************************
|
||||
@ -46,14 +61,27 @@ export default class GoogleAnalyticsUtils
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private send = (location: Location, title: string) =>
|
||||
private send = (model: AnalyticsModel) =>
|
||||
{
|
||||
if(!this.active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ReactGA.send({hitType: "pageview", page: location.pathname + location.search, title: title});
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -99,7 +127,7 @@ export default class GoogleAnalyticsUtils
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public recordAnalytics = (location: Location, title: string) =>
|
||||
public recordAnalytics = (model: AnalyticsModel) =>
|
||||
{
|
||||
if(this.metaData == null)
|
||||
{
|
||||
@ -109,7 +137,7 @@ export default class GoogleAnalyticsUtils
|
||||
})()
|
||||
}
|
||||
|
||||
this.send(location, title);
|
||||
this.send(model);
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user