diff --git a/api.planx.uk/modules/analytics/controller.ts b/api.planx.uk/modules/analytics/controller.ts index d47ec08e60..bc81a2e40a 100644 --- a/api.planx.uk/modules/analytics/controller.ts +++ b/api.planx.uk/modules/analytics/controller.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { trackAnalyticsLogExit } from "./service"; +import { trackAnalyticsLogExit, trackUserReset } from "./service"; import { ValidatedRequestHandler } from "../../shared/middleware/validate"; export const logAnalyticsSchema = z.object({ @@ -24,3 +24,9 @@ export const logUserResumeController: LogAnalytics = async (req, res) => { trackAnalyticsLogExit({ id: Number(analyticsLogId), isUserExit: false }); res.status(204).send(); }; + +export const logUserResetController: LogAnalytics = async (req, res) => { + const { analyticsLogId } = req.query; + trackUserReset({ id: Number(analyticsLogId), flow_direction: "reset" }); + res.status(204).send(); +}; diff --git a/api.planx.uk/modules/analytics/routes.ts b/api.planx.uk/modules/analytics/routes.ts index 8bab98574f..3483f4064a 100644 --- a/api.planx.uk/modules/analytics/routes.ts +++ b/api.planx.uk/modules/analytics/routes.ts @@ -4,6 +4,7 @@ import { logAnalyticsSchema, logUserExitController, logUserResumeController, + logUserResetController, } from "./controller"; const router = Router(); @@ -18,5 +19,10 @@ router.post( validate(logAnalyticsSchema), logUserResumeController, ); +router.post( + "/log-user-reset", + validate(logAnalyticsSchema), + logUserResetController, +); export default router; diff --git a/api.planx.uk/modules/analytics/service.ts b/api.planx.uk/modules/analytics/service.ts index 01018a2eb7..ac459251e2 100644 --- a/api.planx.uk/modules/analytics/service.ts +++ b/api.planx.uk/modules/analytics/service.ts @@ -55,3 +55,40 @@ export const trackAnalyticsLogExit = async ({ return; }; + +export const trackUserReset = async ({ + id, + flow_direction, +}: { + id: number; + flow_direction: string; +}) => { + try { + await adminClient.request( + gql` + mutation UpdateAnalyticsLogUserReset( + $id: bigint! + $flow_direction: String + ) { + update_analytics_logs_by_pk( + pk_columns: { id: $id } + _set: { flow_direction: $flow_direction } + ) { + id + } + } + `, + { + id, + flow_direction: flow_direction, + }, + ); + } catch (e) { + // We need to catch this exception here otherwise the exception would become an unhandled rejection which brings down the whole node.js process + console.error( + "There's been an error while recording metrics for analytics but because this thread is non-blocking we didn't reject the request", + (e as Error).stack, + ); + } + return; +}; diff --git a/editor.planx.uk/src/components/Header.tsx b/editor.planx.uk/src/components/Header.tsx index d8ea18c2c9..33f216d9e1 100644 --- a/editor.planx.uk/src/components/Header.tsx +++ b/editor.planx.uk/src/components/Header.tsx @@ -309,6 +309,7 @@ const PublicToolbar: React.FC<{ state.id, state.teamTheme, ]); + const { trackUserResetConfirmation } = useAnalyticsTracking(); // Center the service title on desktop layouts, or drop it to second line on mobile // ref https://design-system.service.gov.uk/styles/page-template/ @@ -322,6 +323,7 @@ const PublicToolbar: React.FC<{ "Are you sure you want to restart? This will delete your previous answers", ) ) { + trackUserResetConfirmation(); if (path === ApplicationPath.SingleSession) { clearLocalFlow(id); window.location.reload(); diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/analyticsProvider.tsx b/editor.planx.uk/src/pages/FlowEditor/lib/analyticsProvider.tsx index 3aef3a108f..a95cd378a8 100644 --- a/editor.planx.uk/src/pages/FlowEditor/lib/analyticsProvider.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/lib/analyticsProvider.tsx @@ -6,7 +6,7 @@ import React, { createContext, useContext, useEffect, useState } from "react"; import { Store, useStore } from "./store"; -export type AnalyticsType = "init" | "resume"; +export type AnalyticsType = "init" | "resume" | "reset" type AnalyticsLogDirection = AnalyticsType | "forwards" | "backwards"; export type HelpClickMetadata = Record; @@ -18,11 +18,13 @@ const analyticsContext = createContext<{ createAnalytics: (type: AnalyticsType) => Promise; trackHelpClick: (metadata?: HelpClickMetadata) => Promise; trackNextStepsLinkClick: (metadata?: SelectedUrlsMetadata) => Promise; + trackUserResetConfirmation: () => Promise; node: Store.node | null; }>({ createAnalytics: () => Promise.resolve(), trackHelpClick: () => Promise.resolve(), trackNextStepsLinkClick: () => Promise.resolve(), + trackUserResetConfirmation: () => Promise.resolve(), node: null, }); const { Provider } = analyticsContext; @@ -104,6 +106,7 @@ export const AnalyticsProvider: React.FC<{ children: React.ReactNode }> = ({ createAnalytics, trackHelpClick, trackNextStepsLinkClick, + trackUserResetConfirmation, node, }} > @@ -199,6 +202,16 @@ export const AnalyticsProvider: React.FC<{ children: React.ReactNode }> = ({ } } + async function trackUserResetConfirmation(){ + if (lastAnalyticsLogId && shouldTrackAnalytics) { + send( + `${ + process.env.REACT_APP_API_URL + }/analytics/log-user-reset?analyticsLogId=${lastAnalyticsLogId.toString()}`, + ); + } + }; + async function createAnalytics(type: AnalyticsType) { if (shouldTrackAnalytics) { const response = await publicClient.mutate({