Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: track user resetting flow #2307

Merged
merged 6 commits into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion editor.planx.uk/src/@planx/components/Notice/Public.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Card from "@planx/components/shared/Preview/Card";
import { contentFlowSpacing } from "@planx/components/shared/Preview/Card";
import QuestionHeader from "@planx/components/shared/Preview/QuestionHeader";
import { PublicProps } from "@planx/components/ui";
import { useAnalyticsTracking } from "pages/FlowEditor/lib/analyticsProvider";
import React from "react";
import { getContrastTextColor } from "styleUtils";
import { FONT_WEIGHT_SEMI_BOLD } from "theme";
Expand Down Expand Up @@ -75,6 +76,13 @@ const NoticeComponent: React.FC<Props> = (props) => {
? () => props.handleSubmit?.()
: undefined;

const { trackResetFlow } = useAnalyticsTracking();

const handleNoticeResetClick = () => {
trackResetFlow()
props.resetPreview && props.resetPreview()
}
jessicamcinchak marked this conversation as resolved.
Show resolved Hide resolved

return (
<Card handleSubmit={handleSubmit} isValid>
<>
Expand Down Expand Up @@ -105,7 +113,7 @@ const NoticeComponent: React.FC<Props> = (props) => {
variant="contained"
size="large"
type="submit"
onClick={props.resetPreview}
onClick={handleNoticeResetClick}
sx={contentFlowSpacing}
>
Back to start
Expand Down
3 changes: 3 additions & 0 deletions editor.planx.uk/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -315,13 +315,16 @@ const PublicToolbar: React.FC<{
const showCentredServiceTitle = useMediaQuery((theme: Theme) =>
theme.breakpoints.up("md"),
);

const { trackResetFlow } = useAnalyticsTracking();

const handleRestart = async () => {
if (
confirm(
"Are you sure you want to restart? This will delete your previous answers",
)
) {
trackResetFlow();
if (path === ApplicationPath.SingleSession) {
clearLocalFlow(id);
window.location.reload();
Expand Down
29 changes: 28 additions & 1 deletion editor.planx.uk/src/pages/FlowEditor/lib/analyticsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @jessicamcinchak something I hadn't realised is that this AnalyticsType is related to the analytics model which tracks the overarching analytics session? It stores whether the user's session was an init or a resume?

I believe the log direction only applies to analytics_log model instances which are tied together with an instance of the analytics model?

Based on the way the "reset" is being recorded against an analytics_log if it make more sense to think of it as a log direction i.e. an extreme case of going backwards where you go back to the the start?

Copy link
Member

@jessicamcinchak jessicamcinchak Oct 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely sure I'm following this one yet, but my instinct here is that "reset" ends the given session and therefore, at least in my mind, it's an analytics type rather than pure "direction" (eg directions like "forwards" and "backwards" are always within a session). But also totally trust your judgement on this!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's a really good point although in this PR the "reset" is only ever applied within a session as a flow_direction

As in:

  • The last analytics_log for that analytic session would have a flow_direction of "reset".
  • The type of the aforementioned analytic session would remain whatever type it had been i.e. "init" or "resume"
  • The new analytic session would default to "init"

So currently the AnalyticType is only applied as a flow_direction.

Does that make sense? I'm maybe over focussing on the semantics but I'm just trying to get my head around who things have been working and what we're looking for with this new functionality.

Copy link
Member

@jessicamcinchak jessicamcinchak Oct 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this clarification, this makes a lot more sense now.

I'm thinking:

  • analytic session type captures how the session starts, it's never captured how it "ends" before now
  • therefore continuing to just capture reset as a flow_direction on analytics_log seems consistent?

There's an existing chart in metabase about "Where applicants drop-off" - and if we're considering "reset" as similar to "drop-off" - maybe looking at that underlying SQL query might offter confirmation if this approach will be able to be aggregated/summarised/charted similarly or be difficult without carrying over "reset" as a new analytic.type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a very helpful framing of the situation, thanks Jess!

I'll have a look at "Where applicants drop-off" and see how it's being approached there and if there are any implications with this change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a wee look at the SQL query:

SELECT date_trunc('week', analytics_logs.created_at) as "Week", count(*) as "Exits"
FROM analytics_logs
JOIN analytics ON (analytics.id = analytics_logs.analytics_id)
WHERE analytics_logs.user_exit = true
AND analytics_logs.node_type = 3
AND analytics.flow_id = {{flow_id}}::uuid
GROUP BY "Week"
ORDER BY "Week"

The drop-off uses the user_exit property which to my understanding is marked as true when a user stops viewing the page:

const onPageExit = () => {
if (lastAnalyticsLogId && shouldTrackAnalytics) {
if (document.visibilityState === "hidden") {
send(
`${
process.env.REACT_APP_API_URL
}/analytics/log-user-exit?analyticsLogId=${lastAnalyticsLogId.toString()}`,
);
}
if (document.visibilityState === "visible") {
send(
`${
process.env.REACT_APP_API_URL
}/analytics/log-user-resume?analyticsLogId=${lastAnalyticsLogId?.toString()}`,
);
}
}
};
useEffect(() => {
if (shouldTrackAnalytics)
document.addEventListener("visibilitychange", onPageExit);
return () => {
if (shouldTrackAnalytics)
document.removeEventListener("visibilitychange", onPageExit);
};
}, []);

I had thought of a user deciding to reset as not actually exiting the page and therefore not being a user_exit: true.

Although in the context of drop off I'm not so sure 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually on second thoughts I think we'll be able to handle the reset state in a similar fashion as the query above so we should be okay I think 👍

type AnalyticsLogDirection = AnalyticsType | "forwards" | "backwards";

export type HelpClickMetadata = Record<string, string>;
Expand All @@ -18,11 +18,13 @@ const analyticsContext = createContext<{
createAnalytics: (type: AnalyticsType) => Promise<void>;
trackHelpClick: (metadata?: HelpClickMetadata) => Promise<void>;
trackNextStepsLinkClick: (metadata?: SelectedUrlsMetadata) => Promise<void>;
trackResetFlow: () => Promise<void>;
node: Store.node | null;
}>({
createAnalytics: () => Promise.resolve(),
trackHelpClick: () => Promise.resolve(),
trackNextStepsLinkClick: () => Promise.resolve(),
trackResetFlow: () => Promise.resolve(),
node: null,
});
const { Provider } = analyticsContext;
Expand Down Expand Up @@ -104,6 +106,7 @@ export const AnalyticsProvider: React.FC<{ children: React.ReactNode }> = ({
createAnalytics,
trackHelpClick,
trackNextStepsLinkClick,
trackResetFlow,
node,
}}
>
Expand Down Expand Up @@ -199,6 +202,30 @@ export const AnalyticsProvider: React.FC<{ children: React.ReactNode }> = ({
}
}

async function trackResetFlow() {
if (shouldTrackAnalytics && lastAnalyticsLogId) {
await publicClient.mutate({
mutation: gql`
mutation UpdateHasResetFlow(
$id: bigint!
$flow_direction: String
) {
update_analytics_logs_by_pk(
pk_columns: { id: $id }
_set: { flow_direction: $flow_direction }
) {
id
}
}
`,
variables: {
id: lastAnalyticsLogId,
flow_direction: 'reset'
},
});
}
}

async function createAnalytics(type: AnalyticsType) {
if (shouldTrackAnalytics) {
const response = await publicClient.mutate({
Expand Down
1 change: 1 addition & 0 deletions hasura.planx.uk/metadata/tables.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
- role: public
permission:
columns:
- flow_direction
jessicamcinchak marked this conversation as resolved.
Show resolved Hide resolved
- has_clicked_help
- metadata
filter: {}
Expand Down
Loading