From 26b61029f3bcc6a27e4765c082a6bb6372f455bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dafydd=20Ll=C5=B7r=20Pearson?= Date: Mon, 20 Nov 2023 12:53:15 +0000 Subject: [PATCH] feat: Analytics logs for input errors caught by `ErrorWrapper` (#2454) --- .../@planx/components/Checklist/Public.tsx | 4 +-- .../src/@planx/components/Send/Editor.tsx | 4 +-- .../FlowEditor/lib/analyticsProvider.tsx | 33 +++++++++++++++++-- editor.planx.uk/src/ui/ErrorWrapper.tsx | 22 ++++++++----- hasura.planx.uk/metadata/tables.yaml | 2 ++ .../down.sql | 2 ++ .../up.sql | 1 + 7 files changed, 53 insertions(+), 15 deletions(-) create mode 100644 hasura.planx.uk/migrations/1700308790539_alter_table_public_analytics_logs_add_column_input_errors/down.sql create mode 100644 hasura.planx.uk/migrations/1700308790539_alter_table_public_analytics_logs_add_column_input_errors/up.sql diff --git a/editor.planx.uk/src/@planx/components/Checklist/Public.tsx b/editor.planx.uk/src/@planx/components/Checklist/Public.tsx index f944eac47d..e26d49f642 100644 --- a/editor.planx.uk/src/@planx/components/Checklist/Public.tsx +++ b/editor.planx.uk/src/@planx/components/Checklist/Public.tsx @@ -4,7 +4,7 @@ import type { Checklist, Group } from "@planx/components/Checklist/model"; import ImageButton from "@planx/components/shared/Buttons/ImageButton"; import Card from "@planx/components/shared/Preview/Card"; import QuestionHeader from "@planx/components/shared/Preview/QuestionHeader"; -import { useFormik } from "formik"; +import { getIn, useFormik } from "formik"; import React, { useState } from "react"; import ChecklistItem from "ui/ChecklistItem"; import ErrorWrapper from "ui/ErrorWrapper"; @@ -139,7 +139,7 @@ const ChecklistComponent: React.FC = ({ img={img} /> - + {options ? ( options.map((option) => diff --git a/editor.planx.uk/src/@planx/components/Send/Editor.tsx b/editor.planx.uk/src/@planx/components/Send/Editor.tsx index ef464f5931..3d29215891 100644 --- a/editor.planx.uk/src/@planx/components/Send/Editor.tsx +++ b/editor.planx.uk/src/@planx/components/Send/Editor.tsx @@ -2,7 +2,7 @@ import Warning from "@mui/icons-material/Warning"; import Box from "@mui/material/Box"; import Grid from "@mui/material/Grid"; import Typography from "@mui/material/Typography"; -import { useFormik } from "formik"; +import { getIn, useFormik } from "formik"; import React from "react"; import ChecklistItem from "ui/ChecklistItem"; import ErrorWrapper from "ui/ErrorWrapper"; @@ -89,7 +89,7 @@ const SendComponent: React.FC = (props) => { /> - + {options.map((option) => ( diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/analyticsProvider.tsx b/editor.planx.uk/src/pages/FlowEditor/lib/analyticsProvider.tsx index 1df33f30ed..c0338e85ad 100644 --- a/editor.planx.uk/src/pages/FlowEditor/lib/analyticsProvider.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/lib/analyticsProvider.tsx @@ -21,7 +21,7 @@ type AnalyticsLogDirection = export type HelpClickMetadata = Record; export type SelectedUrlsMetadata = Record<"selectedUrls", string[]>; -export type BackwardsNaviagtionInitiatorType = "change" | "back"; +export type BackwardsNavigationInitiatorType = "change" | "back"; type NodeMetadata = { flagset?: FlagSet; @@ -45,9 +45,10 @@ const analyticsContext = createContext<{ ) => Promise; trackBackwardsNavigationByNodeId: ( nodeId: string, - backwardsNavigationType: BackwardsNaviagtionInitiatorType, + backwardsNavigationType: BackwardsNavigationInitiatorType, ) => Promise; node: Store.node | null; + trackInputErrors: (error: string) => Promise; }>({ createAnalytics: () => Promise.resolve(), trackHelpClick: () => Promise.resolve(), @@ -55,6 +56,7 @@ const analyticsContext = createContext<{ trackFlowDirectionChange: () => Promise.resolve(), trackBackwardsNavigationByNodeId: () => Promise.resolve(), node: null, + trackInputErrors: () => Promise.resolve(), }); const { Provider } = analyticsContext; @@ -140,6 +142,7 @@ export const AnalyticsProvider: React.FC<{ children: React.ReactNode }> = ({ trackFlowDirectionChange, trackBackwardsNavigationByNodeId, node, + trackInputErrors, }} > {children} @@ -309,7 +312,7 @@ export const AnalyticsProvider: React.FC<{ children: React.ReactNode }> = ({ async function trackBackwardsNavigationByNodeId( nodeId: string, - initiator: BackwardsNaviagtionInitiatorType, + initiator: BackwardsNavigationInitiatorType, ) { const targetNodeMetadata = getTitleAndTypeFromFlow(nodeId); const metadata: Record = {}; @@ -401,6 +404,30 @@ export const AnalyticsProvider: React.FC<{ children: React.ReactNode }> = ({ }; return nodeMetadata; } + + /** + * Capture user input errors caught by ErrorWrapper component + */ + async function trackInputErrors(error: string) { + if (shouldTrackAnalytics && lastAnalyticsLogId) { + await publicClient.mutate({ + mutation: gql` + mutation TrackInputErrors($id: bigint!, $error: jsonb) { + update_analytics_logs_by_pk( + pk_columns: { id: $id } + _append: { input_errors: $error } + ) { + id + } + } + `, + variables: { + id: lastAnalyticsLogId, + error, + }, + }); + } + } }; /** diff --git a/editor.planx.uk/src/ui/ErrorWrapper.tsx b/editor.planx.uk/src/ui/ErrorWrapper.tsx index d4307d01e7..7600899b2c 100644 --- a/editor.planx.uk/src/ui/ErrorWrapper.tsx +++ b/editor.planx.uk/src/ui/ErrorWrapper.tsx @@ -2,11 +2,12 @@ import Box, { BoxProps } from "@mui/material/Box"; import { styled } from "@mui/material/styles"; import Typography from "@mui/material/Typography"; import { ERROR_MESSAGE } from "@planx/components/shared/constants"; -import React, { ReactElement } from "react"; +import { useAnalyticsTracking } from "pages/FlowEditor/lib/analyticsProvider"; +import React, { ReactElement, useEffect } from "react"; import { FONT_WEIGHT_SEMI_BOLD } from "theme"; export interface Props { - error: string | string[] | undefined; + error?: string; children?: ReactElement; id?: string; } @@ -34,16 +35,21 @@ const ErrorText = styled(Typography)(({ theme }) => ({ fontWeight: FONT_WEIGHT_SEMI_BOLD, })); -export default function ErrorWrapper(props: Props): FCReturn { - const id = props.id ? `${ERROR_MESSAGE}-${props.id}` : undefined; +export default function ErrorWrapper({ id, error, children }: Props): FCReturn { + const inputId = id ? `${ERROR_MESSAGE}-${id}` : undefined; + const { trackInputErrors } = useAnalyticsTracking(); + + useEffect(() => { + error && trackInputErrors(error); + }, [error, trackInputErrors]); return ( // role="status" immediately announces the error to screenreaders without interrupting focus - - - {props?.error} + + + {error && error} - {props.children || null} + {children || null} ); } diff --git a/hasura.planx.uk/metadata/tables.yaml b/hasura.planx.uk/metadata/tables.yaml index 54dae90087..95083ccf42 100644 --- a/hasura.planx.uk/metadata/tables.yaml +++ b/hasura.planx.uk/metadata/tables.yaml @@ -38,6 +38,7 @@ - flow_direction - has_clicked_help - id + - input_errors - metadata - node_title - node_type @@ -57,6 +58,7 @@ columns: - flow_direction - has_clicked_help + - input_errors - metadata - next_log_created_at - user_exit diff --git a/hasura.planx.uk/migrations/1700308790539_alter_table_public_analytics_logs_add_column_input_errors/down.sql b/hasura.planx.uk/migrations/1700308790539_alter_table_public_analytics_logs_add_column_input_errors/down.sql new file mode 100644 index 0000000000..59e84c83f3 --- /dev/null +++ b/hasura.planx.uk/migrations/1700308790539_alter_table_public_analytics_logs_add_column_input_errors/down.sql @@ -0,0 +1,2 @@ +ALTER TABLE "public"."analytics_logs" +DROP COLUMN "input_errors" diff --git a/hasura.planx.uk/migrations/1700308790539_alter_table_public_analytics_logs_add_column_input_errors/up.sql b/hasura.planx.uk/migrations/1700308790539_alter_table_public_analytics_logs_add_column_input_errors/up.sql new file mode 100644 index 0000000000..4226913657 --- /dev/null +++ b/hasura.planx.uk/migrations/1700308790539_alter_table_public_analytics_logs_add_column_input_errors/up.sql @@ -0,0 +1 @@ +alter table "public"."analytics_logs" add column "input_errors" jsonb NULL DEFAULT '[]'; \ No newline at end of file