From 8b7f886d40b98a849a5148ad25facc31113fb991 Mon Sep 17 00:00:00 2001 From: Rory Doak <138574807+RODO94@users.noreply.github.com> Date: Wed, 20 Nov 2024 11:56:08 +0000 Subject: [PATCH] feat: add `user_clicked_save` to `analytics_summary` view (#3972) --- .../shared/Preview/SaveResumeButton.tsx | 1 + .../FlowEditor/lib/analytics/mutations.ts | 12 ++++ .../FlowEditor/lib/analytics/provider.tsx | 12 ++-- .../pages/FlowEditor/lib/analytics/types.ts | 14 ++++- hasura.planx.uk/metadata/tables.yaml | 2 + .../down.sql | 61 ++++++++++++++++++ .../up.sql | 63 +++++++++++++++++++ 7 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 hasura.planx.uk/migrations/1732016907414_alter_table_public_analytics_logs_add_column_has_user_clicked_save/down.sql create mode 100644 hasura.planx.uk/migrations/1732016907414_alter_table_public_analytics_logs_add_column_has_user_clicked_save/up.sql diff --git a/editor.planx.uk/src/@planx/components/shared/Preview/SaveResumeButton.tsx b/editor.planx.uk/src/@planx/components/shared/Preview/SaveResumeButton.tsx index e5bcb896bd..96bb8cd30c 100644 --- a/editor.planx.uk/src/@planx/components/shared/Preview/SaveResumeButton.tsx +++ b/editor.planx.uk/src/@planx/components/shared/Preview/SaveResumeButton.tsx @@ -21,6 +21,7 @@ const SaveResumeButton: React.FC = () => { const handleClick = () => { if (saveToEmail) { + trackEvent({ event: "saveClick", metadata: null }); trackEvent({ event: "flowDirectionChange", metadata: null, diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/analytics/mutations.ts b/editor.planx.uk/src/pages/FlowEditor/lib/analytics/mutations.ts index fcaead276f..22eb5a966f 100644 --- a/editor.planx.uk/src/pages/FlowEditor/lib/analytics/mutations.ts +++ b/editor.planx.uk/src/pages/FlowEditor/lib/analytics/mutations.ts @@ -52,6 +52,18 @@ export const UPDATE_HAS_CLICKED_HELP = gql` } `; +export const UPDATE_HAS_CLICKED_SAVE = gql` + mutation UpdateHasClickedSave($id: bigint!, $metadata: jsonb = {}) { + update_analytics_logs_by_pk( + pk_columns: { id: $id } + _set: { has_clicked_save: true } + _append: { metadata: $metadata } + ) { + id + } + } +`; + export const UPDATE_ANALYTICS_LOG_METADATA = gql` mutation UpdateAnalyticsLogMetadata($id: bigint!, $metadata: jsonb = {}) { update_analytics_logs_by_pk( diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/analytics/provider.tsx b/editor.planx.uk/src/pages/FlowEditor/lib/analytics/provider.tsx index 6d979cfb7f..f6c3d46b72 100644 --- a/editor.planx.uk/src/pages/FlowEditor/lib/analytics/provider.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/lib/analytics/provider.tsx @@ -15,6 +15,7 @@ import { UPDATE_ANALYTICS_LOG_METADATA, UPDATE_FLOW_DIRECTION, UPDATE_HAS_CLICKED_HELP, + UPDATE_HAS_CLICKED_SAVE, UPDATE_NEXT_LOG_CREATED_AT, } from "./mutations"; import { @@ -256,7 +257,7 @@ export const AnalyticsProvider: React.FC<{ children: React.ReactNode }> = ({ return !shouldTrackAnalytics || !lastVisibleNodeAnalyticsLogId; } - async function updateMetadata(mutation: DocumentNode, metadata: Metadata) { + async function updateMetadata(mutation: DocumentNode, metadata?: Metadata) { await publicClient.mutate({ mutation: mutation, variables: { @@ -268,14 +269,17 @@ export const AnalyticsProvider: React.FC<{ children: React.ReactNode }> = ({ async function trackEvent(eventData: EventData) { if (shouldSkipTracking()) return; - const { event, metadata } = eventData; + const { event } = eventData; switch (event) { case "helpClick": - updateMetadata(UPDATE_HAS_CLICKED_HELP, metadata); + updateMetadata(UPDATE_HAS_CLICKED_HELP, eventData.metadata); + return; + case "saveClick": + updateMetadata(UPDATE_HAS_CLICKED_SAVE); return; case "nextStepsClick": case "helpTextFeedback": - updateMetadata(UPDATE_ANALYTICS_LOG_METADATA, metadata); + updateMetadata(UPDATE_ANALYTICS_LOG_METADATA, eventData.metadata); return; case "backwardsNavigation": { const { initiator, nodeId } = eventData; diff --git a/editor.planx.uk/src/pages/FlowEditor/lib/analytics/types.ts b/editor.planx.uk/src/pages/FlowEditor/lib/analytics/types.ts index fda09035d2..611e5b0c6f 100644 --- a/editor.planx.uk/src/pages/FlowEditor/lib/analytics/types.ts +++ b/editor.planx.uk/src/pages/FlowEditor/lib/analytics/types.ts @@ -95,7 +95,8 @@ export type EventData = | BackwardsNavigation | FlowDirectionChange | InputErrors - | HelpTextFeedback; + | HelpTextFeedback + | SaveClick; /** * Capture when a user clicks on the `More Information` i.e. the help on a @@ -108,6 +109,17 @@ type HelpClick = { metadata: HelpClickMetadata; }; +/** + * Captured when a user clicks Save and Return. + * The mutation sets the "has_clicked_help: true" when Save and + * Return button is clicked + */ + +type SaveClick = { + event: "saveClick"; + metadata: null; +}; + /** * A user gets to a `NextSteps` component. Track every time a user selects a * link and appends it to the array. diff --git a/hasura.planx.uk/metadata/tables.yaml b/hasura.planx.uk/metadata/tables.yaml index fcf2b0f3ea..ea03c776ed 100644 --- a/hasura.planx.uk/metadata/tables.yaml +++ b/hasura.planx.uk/metadata/tables.yaml @@ -55,6 +55,7 @@ - created_at - flow_direction - has_clicked_help + - has_clicked_save - id - input_errors - metadata @@ -79,6 +80,7 @@ - allow_list_answers - flow_direction - has_clicked_help + - has_clicked_save - input_errors - metadata - next_log_created_at diff --git a/hasura.planx.uk/migrations/1732016907414_alter_table_public_analytics_logs_add_column_has_user_clicked_save/down.sql b/hasura.planx.uk/migrations/1732016907414_alter_table_public_analytics_logs_add_column_has_user_clicked_save/down.sql new file mode 100644 index 0000000000..a18398fd07 --- /dev/null +++ b/hasura.planx.uk/migrations/1732016907414_alter_table_public_analytics_logs_add_column_has_user_clicked_save/down.sql @@ -0,0 +1,61 @@ +alter table "public"."analytics_logs" drop column "has_clicked_save"; + +CREATE OR REPLACE VIEW "public"."analytics_summary" AS + SELECT a.id AS analytics_id, + al.id AS analytics_log_id, + f.slug AS service_slug, + t.slug AS team_slug, + a.type AS analytics_type, + al.created_at AS analytics_log_created_at, + a.created_at AS analytics_created_at, + ((a.user_agent -> 'os'::text) ->> 'name'::text) AS operating_system, + ((a.user_agent -> 'browser'::text) ->> 'name'::text) AS browser, + ((a.user_agent -> 'platform'::text) ->> 'type'::text) AS platform, + a.referrer, + al.flow_direction, + (al.metadata ->> 'change'::text) AS change_metadata, + (al.metadata ->> 'back'::text) AS back_metadata, + (al.metadata ->> 'selectedUrls'::text) AS selected_urls, + (al.metadata ->> 'flag'::text) AS result_flag, + ((al.metadata -> 'flagSet'::text))::text AS result_flagset, + ((al.metadata -> 'displayText'::text) ->> 'heading'::text) AS result_heading, + ((al.metadata -> 'displayText'::text) ->> 'description'::text) AS result_description, + ((al.metadata -> 'helpTextUseful'::text))::text AS help_text_useful, + CASE + WHEN al.has_clicked_help THEN al.metadata + ELSE NULL::jsonb + END AS help_metadata, + al.user_exit AS is_user_exit, + al.node_type, + al.node_title, + al.has_clicked_help, + al.input_errors, + (date_part('epoch'::text, (al.next_log_created_at - al.created_at)))::numeric(10,1) AS time_spent_on_node_seconds, + a.ended_at AS analytics_ended_at, + ((date_part('epoch'::text, (a.ended_at - a.created_at)) / (60)::double precision))::numeric(10,1) AS time_spent_on_analytics_session_minutes, + al.node_id, + al.allow_list_answers, + ((al.allow_list_answers -> 'proposal.projectType'::text))::text AS proposal_project_type, + ((al.allow_list_answers -> 'application.declaration.connection'::text))::text AS application_declaration_connection, + ((al.allow_list_answers -> 'property.type'::text))::text AS property_type, + ((al.allow_list_answers -> 'drawBoundary.action'::text))::text AS draw_boundary_action, + ((al.allow_list_answers -> 'user.role'::text))::text AS user_role, + ((al.allow_list_answers -> 'property.constraints.planning'::text))::text AS property_constraints_planning, + ((al.allow_list_answers -> 'findProperty.action'::text))::text AS find_property_action, + ((al.allow_list_answers -> 'usedFOIYNPP'::text))::text AS used_foiynpp, + ((al.allow_list_answers -> 'propertyInformation.action'::text))::text AS property_information_action, + ((al.allow_list_answers -> 'planningConstraints.action'::text))::text AS planning_constraints_action, + ((al.allow_list_answers -> '_overrides'::text))::text AS overrides, + ((al.allow_list_answers -> 'rab.exitReason'::text))::text AS rab_exit_reason, + ((al.allow_list_answers -> 'service.type'::text))::text AS pre_app_service_type, + ((al.allow_list_answers -> 'application.information.harmful'::text))::text AS pre_app_harmful_info, + ((al.allow_list_answers -> 'application.information.sensitive'::text))::text AS pre_app_sensitive_info, + al.allow_list_answers -> 'application.type' ->> 0 AS application_type, + ((al.allow_list_answers -> '_feedback') ->> 'feedbackScore'::text)::int AS feedback_score, + al.allow_list_answers -> 'applicant.researchOptIn' ->> 0 AS applicant_research_opt_in + FROM (((analytics a + LEFT JOIN analytics_logs al ON ((a.id = al.analytics_id))) + LEFT JOIN flows f ON ((a.flow_id = f.id))) + LEFT JOIN teams t ON ((t.id = f.team_id))); + +GRANT SELECT ON "public"."analytics_summary" TO metabase_read_only; \ No newline at end of file diff --git a/hasura.planx.uk/migrations/1732016907414_alter_table_public_analytics_logs_add_column_has_user_clicked_save/up.sql b/hasura.planx.uk/migrations/1732016907414_alter_table_public_analytics_logs_add_column_has_user_clicked_save/up.sql new file mode 100644 index 0000000000..bdbcb67b69 --- /dev/null +++ b/hasura.planx.uk/migrations/1732016907414_alter_table_public_analytics_logs_add_column_has_user_clicked_save/up.sql @@ -0,0 +1,63 @@ +alter table "public"."analytics_logs" add column "has_clicked_save" boolean + null default 'false'; + +CREATE OR REPLACE VIEW "public"."analytics_summary" AS + SELECT a.id AS analytics_id, + al.id AS analytics_log_id, + f.slug AS service_slug, + t.slug AS team_slug, + a.type AS analytics_type, + al.created_at AS analytics_log_created_at, + a.created_at AS analytics_created_at, + ((a.user_agent -> 'os'::text) ->> 'name'::text) AS operating_system, + ((a.user_agent -> 'browser'::text) ->> 'name'::text) AS browser, + ((a.user_agent -> 'platform'::text) ->> 'type'::text) AS platform, + a.referrer, + al.flow_direction, + (al.metadata ->> 'change'::text) AS change_metadata, + (al.metadata ->> 'back'::text) AS back_metadata, + (al.metadata ->> 'selectedUrls'::text) AS selected_urls, + (al.metadata ->> 'flag'::text) AS result_flag, + ((al.metadata -> 'flagSet'::text))::text AS result_flagset, + ((al.metadata -> 'displayText'::text) ->> 'heading'::text) AS result_heading, + ((al.metadata -> 'displayText'::text) ->> 'description'::text) AS result_description, + ((al.metadata -> 'helpTextUseful'::text))::text AS help_text_useful, + CASE + WHEN al.has_clicked_help THEN al.metadata + ELSE NULL::jsonb + END AS help_metadata, + al.user_exit AS is_user_exit, + al.node_type, + al.node_title, + al.has_clicked_help, + al.input_errors, + (date_part('epoch'::text, (al.next_log_created_at - al.created_at)))::numeric(10,1) AS time_spent_on_node_seconds, + a.ended_at AS analytics_ended_at, + ((date_part('epoch'::text, (a.ended_at - a.created_at)) / (60)::double precision))::numeric(10,1) AS time_spent_on_analytics_session_minutes, + al.node_id, + al.allow_list_answers, + ((al.allow_list_answers -> 'proposal.projectType'::text))::text AS proposal_project_type, + ((al.allow_list_answers -> 'application.declaration.connection'::text))::text AS application_declaration_connection, + ((al.allow_list_answers -> 'property.type'::text))::text AS property_type, + ((al.allow_list_answers -> 'drawBoundary.action'::text))::text AS draw_boundary_action, + ((al.allow_list_answers -> 'user.role'::text))::text AS user_role, + ((al.allow_list_answers -> 'property.constraints.planning'::text))::text AS property_constraints_planning, + ((al.allow_list_answers -> 'findProperty.action'::text))::text AS find_property_action, + ((al.allow_list_answers -> 'usedFOIYNPP'::text))::text AS used_foiynpp, + ((al.allow_list_answers -> 'propertyInformation.action'::text))::text AS property_information_action, + ((al.allow_list_answers -> 'planningConstraints.action'::text))::text AS planning_constraints_action, + ((al.allow_list_answers -> '_overrides'::text))::text AS overrides, + ((al.allow_list_answers -> 'rab.exitReason'::text))::text AS rab_exit_reason, + ((al.allow_list_answers -> 'service.type'::text))::text AS pre_app_service_type, + ((al.allow_list_answers -> 'application.information.harmful'::text))::text AS pre_app_harmful_info, + ((al.allow_list_answers -> 'application.information.sensitive'::text))::text AS pre_app_sensitive_info, + al.allow_list_answers -> 'application.type' ->> 0 AS application_type, + ((al.allow_list_answers -> '_feedback') ->> 'feedbackScore'::text)::int AS feedback_score, + al.allow_list_answers -> 'applicant.researchOptIn' ->> 0 AS applicant_research_opt_in, + al.has_clicked_save + FROM (((analytics a + LEFT JOIN analytics_logs al ON ((a.id = al.analytics_id))) + LEFT JOIN flows f ON ((a.flow_id = f.id))) + LEFT JOIN teams t ON ((t.id = f.team_id))); + + GRANT SELECT ON "public"."analytics_summary" TO metabase_read_only; \ No newline at end of file