From 7c24485f6604cc9a13a0a24058dd5f5a1b0f972e Mon Sep 17 00:00:00 2001 From: MG Date: Thu, 12 Dec 2024 12:19:17 +0000 Subject: [PATCH] fix: posthog consistent event properties (#440) --- apps/nextjs/src/lib/analytics/helpers.ts | 3 +- .../src/lib/analytics/keyStages.test.ts | 123 ++++++++++++++++++ apps/nextjs/src/lib/analytics/keyStages.ts | 89 +++++++++++++ 3 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 apps/nextjs/src/lib/analytics/keyStages.test.ts create mode 100644 apps/nextjs/src/lib/analytics/keyStages.ts diff --git a/apps/nextjs/src/lib/analytics/helpers.ts b/apps/nextjs/src/lib/analytics/helpers.ts index 000ee732a..a10786c4e 100644 --- a/apps/nextjs/src/lib/analytics/helpers.ts +++ b/apps/nextjs/src/lib/analytics/helpers.ts @@ -8,6 +8,7 @@ import type { ProductValueType, } from "../avo/Avo"; import { ModeratedContentType } from "../avo/Avo"; +import { parseKeyStage } from "./keyStages"; /** * These are the actions which a user could take which result in a message @@ -34,7 +35,7 @@ export function getLessonTrackingProps({ product: "ai lesson assistant", lessonPlanTitle: lesson.title ?? "", subjectSlug: lesson.subject ?? "", - keyStageSlug: lesson.keyStage ?? "", + keyStageSlug: parseKeyStage(lesson.keyStage ?? ""), }; } diff --git a/apps/nextjs/src/lib/analytics/keyStages.test.ts b/apps/nextjs/src/lib/analytics/keyStages.test.ts new file mode 100644 index 000000000..4c0eec7ff --- /dev/null +++ b/apps/nextjs/src/lib/analytics/keyStages.test.ts @@ -0,0 +1,123 @@ +import { parseKeyStage } from "./keyStages"; + +const testKeyStageMap: Record< + string, + "ks1" | "ks2" | "ks3" | "ks4" | "ks5" | "early-years-foundation-stage" +> = { + "1-2": "ks1", + "10": "ks4", + "12": "ks5", + "6": "ks2", + "A Level": "ks5", + "A level": "ks5", + "A-Level": "ks5", + "A-level": "ks5", + ALevel: "ks5", + EYFS: "early-years-foundation-stage", + "EYFS SEN": "early-years-foundation-stage", + "Early Years": "early-years-foundation-stage", + "Early Years Foundation Stage": "early-years-foundation-stage", + "Early Years Foundation Stage (EYFS)": "early-years-foundation-stage", + "Foundation Stage": "early-years-foundation-stage", + GCSE: "ks4", + "GCSE Higher": "ks4", + "GCSE OCR": "ks4", + KS1: "ks1", + KS2: "ks2", + "KS2 Upper": "ks2", + "KS2 Year 4": "ks2", + "KS2 Year 5": "ks2", + KS3: "ks3", + "KS3 Geography": "ks3", + "KS3 Geography L1 . Mrs Karaphillides": "ks3", + "KS3 and KS4": "ks3", + "KS3,4": "ks3", + "KS3-4": "ks3", + KS4: "ks4", + "KS4 and KS5": "ks4", + KS5: "ks5", + "Key Stage 1": "ks1", + "Key Stage 1 & 2": "ks1", + "Key Stage 1 and Key Stage 2": "ks1", + "Key Stage 1/2": "ks1", + "Key Stage 2": "ks2", + "Key Stage 2 Year 4": "ks2", + "Key Stage 2, Year 3": "ks2", + "Key Stage 2/3": "ks2", + "Key Stage 3": "ks3", + "Key Stage 3 & 4": "ks3", + "Key Stage 3, 4": "ks3", + "Key Stage 3, Year 9": "ks3", + "Key Stage 4": "ks4", + "Key Stage 4 (Year 11, GCSE)": "ks4", + "Key Stage 5": "ks5", + "Key Stage 5 (AS Level)": "ks5", + "Key Stage 5+": "ks5", + Reception: "early-years-foundation-stage", + "Year 1": "ks1", + "Year 1/2": "ks1", + "Year 10": "ks4", + "Year 11": "ks4", + "Year 12": "ks5", + "Year 2": "ks1", + "Year 3": "ks2", + "Year 3 & 4": "ks2", + "Year 4": "ks2", + "Year 5": "ks2", + "Year 5 - Key Stage 2": "ks2", + "Year 6": "ks2", + "Year 7": "ks3", + "Year 8": "ks3", + "Year 9": "ks3", + "early-years": "early-years-foundation-stage", + "early-years-foundation-stage": "early-years-foundation-stage", + "early-years-foundation-stage, key-stage-1, key-stage-2": + "early-years-foundation-stage", + eyfs: "early-years-foundation-stage", + "key stage 3": "ks3", + "key stage 4": "ks4", + "key-stage-1": "ks1", + "key-stage-1 and key-stage-2": "ks1", + "key-stage-1, key-stage-2": "ks1", + "key-stage-2": "ks2", + "key-stage-2 Year 4": "ks2", + "key-stage-2 year 6": "ks2", + "key-stage-2, key-stage-3": "ks2", + "key-stage-3": "ks3", + "key-stage-3, key-stage-4": "ks3", + "key-stage-3|key-stage-4": "ks3", + "key-stage-4": "ks4", + "key-stage-5": "ks5", + "sixth form": "ks5", + "year 1": "ks1", + "year 4": "ks2", + "year-3": "ks2", + "year-4": "ks2", + "year-5": "ks2", + "year-7": "ks3", + "year-8": "ks3", + "year-9": "ks3", +}; + +describe("Analytics helpers", () => { + describe("parseKeyStage", () => { + test("Converts 'Key stage {n}' to 'ks{n}'", () => { + expect(parseKeyStage("Key stage 3")).toBe("ks3"); + }); + test("Converts 'key-stage-{n}' to 'ks{n}'", () => { + expect(parseKeyStage("key-stage-2")).toBe("ks2"); + }); + test("Converts 'KS{n}' to 'ks{n}'", () => { + expect(parseKeyStage("KS1")).toBe("ks1"); + }); + test("Converts '{n} to 'ks{n}'", () => { + expect(parseKeyStage("1")).toBe("ks1"); + }); + test.each(Object.entries(testKeyStageMap))( + "Converts '%s' to '%s'", + (input, expected) => { + expect(parseKeyStage(input)).toBe(expected); + }, + ); + }); +}); diff --git a/apps/nextjs/src/lib/analytics/keyStages.ts b/apps/nextjs/src/lib/analytics/keyStages.ts new file mode 100644 index 000000000..ebf68d368 --- /dev/null +++ b/apps/nextjs/src/lib/analytics/keyStages.ts @@ -0,0 +1,89 @@ +type KeyStage = + | "ks1" + | "ks2" + | "ks3" + | "ks4" + | "ks5" + | "early-years-foundation-stage"; +/** + * If a string starts with any of the keys in this map, it will be mapped to the + * corresponding value. + */ +const startKeyStageMap: Record = { + ks1: "ks1", + ks2: "ks2", + ks3: "ks3", + ks4: "ks4", + ks5: "ks5", + key_stage_1: "ks1", + key_stage_2: "ks2", + key_stage_3: "ks3", + key_stage_4: "ks4", + key_stage_5: "ks5", + eyfs: "early-years-foundation-stage", + early_years: "early-years-foundation-stage", + foundation_stage: "early-years-foundation-stage", + reception: "early-years-foundation-stage", + sixth_form: "ks5", + gcse: "ks4", + a_level: "ks5", + alevel: "ks5", + year_2: "ks1", + year_3: "ks2", + year_4: "ks2", + year_5: "ks2", + year_6: "ks2", + year_7: "ks3", + year_8: "ks3", + year_9: "ks3", + year_10: "ks4", + year_11: "ks4", + year_12: "ks5", + year_13: "ks5", +}; + +/** + * If a string is an exact key in this map, it will be mapped to the corresponding value. + */ +const exactKeyStageMap: Record = { + year_1: "ks1", + year_1_2: "ks1", + "1_2": "ks1", + // 1-5 assume keystage + 1: "ks1", + 2: "ks2", + 3: "ks3", + 4: "ks4", + 5: "ks5", + // 6-13 assume year + 6: "ks2", + 7: "ks3", + 8: "ks3", + 9: "ks3", + 10: "ks4", + 11: "ks4", + 12: "ks5", + 13: "ks5", +}; + +export function parseKeyStage(maybeKeyStage: string): string { + const strippedMaybeKeyStage = maybeKeyStage + .toLowerCase() + .replace(/[^a-z0-9]/g, "_"); + + // Check for exact matches first + const exactMatch = exactKeyStageMap[strippedMaybeKeyStage]; + if (exactMatch) { + return exactMatch; + } + + // Check for a partial match + const startMatch = Object.entries(startKeyStageMap).find(([key]) => + strippedMaybeKeyStage.startsWith(key), + ); + if (startMatch) { + return startMatch[1]; // Return the mapped value + } + + return maybeKeyStage; +}