From 02dbf172f50814c38f16e4dd628f65051e871fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?= Date: Wed, 19 Mar 2025 11:32:40 -0400 Subject: [PATCH] unit test latest metrics --- .../achievements/achievement-hub.spec.ts | 9 +++ .../examples/achievement-events.ts | 79 ++++++++++++++++++- .../tools/achievements/latest-metrics.spec.ts | 72 ++++++++++++++--- .../src/tools/achievements/latest-metrics.ts | 46 +++-------- 4 files changed, 159 insertions(+), 47 deletions(-) create mode 100644 libs/common/src/tools/achievements/achievement-hub.spec.ts diff --git a/libs/common/src/tools/achievements/achievement-hub.spec.ts b/libs/common/src/tools/achievements/achievement-hub.spec.ts new file mode 100644 index 00000000000..457ec2fabb9 --- /dev/null +++ b/libs/common/src/tools/achievements/achievement-hub.spec.ts @@ -0,0 +1,9 @@ +describe("AchievementHub", () => { + describe("earned$", () => {}); + + describe("metrics$", () => {}); + + describe("all$", () => {}); + + describe("named$", () => {}); +}); diff --git a/libs/common/src/tools/achievements/examples/achievement-events.ts b/libs/common/src/tools/achievements/examples/achievement-events.ts index 0e4ca0bc01a..6382e5c80b4 100644 --- a/libs/common/src/tools/achievements/examples/achievement-events.ts +++ b/libs/common/src/tools/achievements/examples/achievement-events.ts @@ -1,7 +1,75 @@ import { UserId } from "../../../types/guid"; -import { AchievementProgressEvent } from "../types"; +import { AchievementEarnedEvent, AchievementProgressEvent } from "../types"; -import { CredentialGeneratedProgress, ItemCreatedProgress } from "./example-validators"; +import { + CredentialGeneratedProgress, + ItemCreatedAchievement, + ItemCreatedProgress, + ThreeItemsCreatedAchievement, +} from "./example-validators"; + +const ItemCreatedEarnedEvent: AchievementEarnedEvent = { + "@timestamp": Date.now(), + event: { + kind: "metric", + category: "session", + }, + achievement: { type: "earned", name: ItemCreatedAchievement }, + service: { + name: "extension", + type: "client", + node: { + name: "an-installation-identifier-for-this-client-instance", + }, + environment: "local", + version: "2025.3.1-innovation-sprint", + }, + user: { + id: "some-guid" as UserId, + }, +}; + +const NextItemCreatedEarnedEvent: AchievementEarnedEvent = { + "@timestamp": Date.now() + 100, + event: { + kind: "metric", + category: "session", + }, + achievement: { type: "earned", name: ItemCreatedAchievement }, + service: { + name: "extension", + type: "client", + node: { + name: "an-installation-identifier-for-this-client-instance", + }, + environment: "local", + version: "2025.3.1-innovation-sprint", + }, + user: { + id: "some-guid" as UserId, + }, +}; + +const ThreeItemsCreatedEarnedEvent: AchievementEarnedEvent = { + "@timestamp": Date.now(), + event: { + kind: "metric", + category: "session", + }, + achievement: { type: "earned", name: ThreeItemsCreatedAchievement }, + service: { + name: "extension", + type: "client", + node: { + name: "an-installation-identifier-for-this-client-instance", + }, + environment: "local", + version: "2025.3.1-innovation-sprint", + }, + user: { + id: "some-guid" as UserId, + }, +}; const ItemCreatedProgressEvent: AchievementProgressEvent = { "@timestamp": Date.now(), @@ -67,7 +135,10 @@ const CredentialGeneratedProgressEvent: AchievementProgressEvent = { }; export { - ItemCreatedProgressEvent, - NextItemCreatedProgressEvent as ItemCreatedProgress2Event, CredentialGeneratedProgressEvent, + ItemCreatedEarnedEvent, + ItemCreatedProgressEvent, + NextItemCreatedEarnedEvent, + NextItemCreatedProgressEvent, + ThreeItemsCreatedEarnedEvent, }; diff --git a/libs/common/src/tools/achievements/latest-metrics.spec.ts b/libs/common/src/tools/achievements/latest-metrics.spec.ts index f21654e91b1..61a14e8c35a 100644 --- a/libs/common/src/tools/achievements/latest-metrics.spec.ts +++ b/libs/common/src/tools/achievements/latest-metrics.spec.ts @@ -3,13 +3,21 @@ import { BehaviorSubject, Subject } from "rxjs"; import { CredentialGeneratedProgressEvent, ItemCreatedProgressEvent, - ItemCreatedProgress2Event, + NextItemCreatedProgressEvent, + ItemCreatedEarnedEvent, + NextItemCreatedEarnedEvent, + ThreeItemsCreatedEarnedEvent, } from "./examples/achievement-events"; -import { CredentialGeneratedProgress, ItemCreatedProgress } from "./examples/example-validators"; -import { latestProgressMetrics } from "./latest-metrics"; -import { AchievementProgressEvent, MetricId } from "./types"; +import { + CredentialGeneratedProgress, + ItemCreatedAchievement, + ItemCreatedProgress, + ThreeItemsCreatedAchievement, +} from "./examples/example-validators"; +import { latestEarnedMetrics, latestProgressMetrics } from "./latest-metrics"; +import { AchievementEarnedEvent, AchievementId, AchievementProgressEvent, MetricId } from "./types"; -describe("latestMetrics", () => { +describe("latestProgressMetrics", () => { it("creates a map containing a metric", () => { const subject = new Subject(); const result = new BehaviorSubject(new Map()); @@ -38,9 +46,9 @@ describe("latestMetrics", () => { subject.pipe(latestProgressMetrics()).subscribe(result); subject.next(ItemCreatedProgressEvent); - subject.next(ItemCreatedProgress2Event); + subject.next(NextItemCreatedProgressEvent); - expect(result.value.get(ItemCreatedProgress)).toEqual(ItemCreatedProgress2Event); + expect(result.value.get(ItemCreatedProgress)).toEqual(NextItemCreatedProgressEvent); }); it("omits old events", () => { @@ -48,9 +56,55 @@ describe("latestMetrics", () => { const result = new BehaviorSubject(new Map()); subject.pipe(latestProgressMetrics()).subscribe(result); - subject.next(ItemCreatedProgress2Event); + subject.next(NextItemCreatedProgressEvent); subject.next(ItemCreatedProgressEvent); - expect(result.value.get(ItemCreatedProgress)).toEqual(ItemCreatedProgress2Event); + expect(result.value.get(ItemCreatedProgress)).toEqual(NextItemCreatedProgressEvent); + }); +}); + +describe("latestEarnedMetrics", () => { + it("creates a map containing a metric", () => { + const subject = new Subject(); + const result = new BehaviorSubject(new Map()); + + subject.pipe(latestEarnedMetrics()).subscribe(result); + subject.next(ItemCreatedEarnedEvent); + + expect(result.value.get(ItemCreatedAchievement)).toEqual(ItemCreatedEarnedEvent); + }); + + it("creates a map containing multiple metrics", () => { + const subject = new Subject(); + const result = new BehaviorSubject(new Map()); + + subject.pipe(latestEarnedMetrics()).subscribe(result); + subject.next(ItemCreatedEarnedEvent); + subject.next(ThreeItemsCreatedEarnedEvent); + + expect(result.value.get(ItemCreatedAchievement)).toEqual(ItemCreatedEarnedEvent); + expect(result.value.get(ThreeItemsCreatedAchievement)).toEqual(ThreeItemsCreatedEarnedEvent); + }); + + it("creates a map containing updated metrics", () => { + const subject = new Subject(); + const result = new BehaviorSubject(new Map()); + + subject.pipe(latestEarnedMetrics()).subscribe(result); + subject.next(ItemCreatedEarnedEvent); + subject.next(NextItemCreatedEarnedEvent); + + expect(result.value.get(ItemCreatedAchievement)).toEqual(NextItemCreatedEarnedEvent); + }); + + it("omits old events", () => { + const subject = new Subject(); + const result = new BehaviorSubject(new Map()); + + subject.pipe(latestEarnedMetrics()).subscribe(result); + subject.next(NextItemCreatedEarnedEvent); + subject.next(ItemCreatedEarnedEvent); + + expect(result.value.get(ItemCreatedAchievement)).toEqual(NextItemCreatedEarnedEvent); }); }); diff --git a/libs/common/src/tools/achievements/latest-metrics.ts b/libs/common/src/tools/achievements/latest-metrics.ts index 5353e9ba360..4744182c7de 100644 --- a/libs/common/src/tools/achievements/latest-metrics.ts +++ b/libs/common/src/tools/achievements/latest-metrics.ts @@ -1,36 +1,7 @@ -import { OperatorFunction, map, filter, pipe, scan } from "rxjs"; +import { OperatorFunction, pipe, scan } from "rxjs"; import { MetricId, AchievementProgressEvent, AchievementId, AchievementEarnedEvent } from "./types"; -function latestProgressEvents(): OperatorFunction< - AchievementProgressEvent, - AchievementProgressEvent -> { - type Accumulator = { - latest: Map; - captured?: AchievementProgressEvent; - }; - const acc: Accumulator = { latest: new Map() }; - - return pipe( - scan((acc, captured) => { - const { latest } = acc; - const current = latest.get(captured.achievement.name); - - // omit stale events - if (current && current["@timestamp"] > captured["@timestamp"]) { - return { latest }; - } - - latest.set(captured.achievement.name, captured); - return { latest, captured }; - }, acc), - // omit updates caused by stale events - filter(({ captured }) => !!captured), - map(({ captured }) => captured!), - ); -} - function latestProgressMetrics(): OperatorFunction< AchievementProgressEvent, Map @@ -56,11 +27,18 @@ function latestEarnedMetrics(): OperatorFunction< Map > { return pipe( - scan((earned, captured) => { - earned.set(captured.achievement.name, captured); - return earned; + scan((metrics, captured) => { + const metric = metrics.get(captured.achievement.name); + + // omit stale metrics + if (metric && metric["@timestamp"] > captured["@timestamp"]) { + return metrics; + } + + metrics.set(captured.achievement.name, captured); + return metrics; }, new Map()), ); } -export { latestProgressMetrics, latestProgressEvents, latestEarnedMetrics }; +export { latestProgressMetrics, latestEarnedMetrics };