From c209fab86d504572c6815fd70a65ecc338e11e55 Mon Sep 17 00:00:00 2001 From: Tom Date: Wed, 19 Mar 2025 11:48:39 -0400 Subject: [PATCH] Adding more validators for counts --- .../achievements/examples/user-events.ts | 26 +++++++- .../config/item-created-count-config.ts | 29 +++++---- .../config/send-created-count-config.ts | 64 +++++++++++++++++++ .../item-collection-move-validator.ts | 42 ++++++++++++ .../validators/item-removed-validator.ts | 49 ++++++++++++++ .../validators/item-uri-added-validator.ts | 46 +++++++++++++ .../send-item-created-count-validator.ts | 55 ++++++++++++++++ 7 files changed, 296 insertions(+), 15 deletions(-) create mode 100644 libs/common/src/tools/achievements/validators/config/send-created-count-config.ts create mode 100644 libs/common/src/tools/achievements/validators/item-collection-move-validator.ts create mode 100644 libs/common/src/tools/achievements/validators/item-removed-validator.ts create mode 100644 libs/common/src/tools/achievements/validators/item-uri-added-validator.ts create mode 100644 libs/common/src/tools/achievements/validators/send-item-created-count-validator.ts diff --git a/libs/common/src/tools/achievements/examples/user-events.ts b/libs/common/src/tools/achievements/examples/user-events.ts index 91fb2249c9f..7e8b3ab1fd7 100644 --- a/libs/common/src/tools/achievements/examples/user-events.ts +++ b/libs/common/src/tools/achievements/examples/user-events.ts @@ -51,6 +51,30 @@ const itemUpdated$: Observable = of({ tags: ["with-folder"], }); +const itemDeleted$: Observable = of({ + "@timestamp": Date.now(), + user: { + id: "08f95669-1a65-4840-ba8b-3538cb3e1496" as UserId, + }, + event: { + kind: "event", + category: "session", + type: "deletion", + outcome: "success", + provider: "vault", + }, + service: { + name: "extension", + type: "client", + node: { name: "commotion-amused-rinse-trivial-sadly" }, + environment: "production", + version: "2025.3.1-innovation-sprint", + }, + action: "vault-item-removed", + labels: { "vault-item-type": "login" }, + tags: [], +}); + const itemMovedToCollection$: Observable = of( { "@timestamp": Date.now(), @@ -100,4 +124,4 @@ const itemMovedToCollection$: Observable = of( } satisfies UserActionEvent, ); -export { itemAdded$, itemUpdated$, itemMovedToCollection$ }; +export { itemAdded$, itemUpdated$, itemDeleted$, itemMovedToCollection$ }; diff --git a/libs/common/src/tools/achievements/validators/config/item-created-count-config.ts b/libs/common/src/tools/achievements/validators/config/item-created-count-config.ts index 16d45521c6c..73698f81b9a 100644 --- a/libs/common/src/tools/achievements/validators/config/item-created-count-config.ts +++ b/libs/common/src/tools/achievements/validators/config/item-created-count-config.ts @@ -65,22 +65,16 @@ export class ItemCreatedCountConfig implements Achievement { 1, CipherType.Card, ); - static readonly CardItemCreated10 = new ItemCreatedCountConfig( - "card-item-created-ten", - "10 card items added", - 10, + static readonly CardItemCreated3 = new ItemCreatedCountConfig( + "card-item-created-3", + "3rd card items added", + 3, CipherType.Card, ); - static readonly CardItemCreated50 = new ItemCreatedCountConfig( - "card-item-created-fifty", - "50 card items added", - 50, - CipherType.Card, - ); - static readonly CardItemCreated100 = new ItemCreatedCountConfig( - "card-item-created-one-hundred", - "100 card items added", - 100, + static readonly CardItemCreated5 = new ItemCreatedCountConfig( + "card-item-created-5", + "5th card item added", + 5, CipherType.Card, ); @@ -110,6 +104,13 @@ export class ItemCreatedCountConfig implements Achievement { CipherType.SecureNote, ); + // SSH Key - Achievements indicate only one so just set threshold at 1 + static readonly SSHKeyItemCreated = new ItemCreatedCountConfig( + "ssh-key-item-created", + "1st SSH Key added", + 1, + ); + base: Achievement; get achievement() { return this.base.achievement; diff --git a/libs/common/src/tools/achievements/validators/config/send-created-count-config.ts b/libs/common/src/tools/achievements/validators/config/send-created-count-config.ts new file mode 100644 index 00000000000..3978cb60ffc --- /dev/null +++ b/libs/common/src/tools/achievements/validators/config/send-created-count-config.ts @@ -0,0 +1,64 @@ +import { Type } from "../../data"; +import { Achievement, AchievementId, MetricId } from "../../types"; + +/** + * Send items added in it's own achievement. Right now only count achievements + * are included. There might be the need to create count achievements for + * different send types. Keeping send logic in it's own validator makes sense + * for readability. + */ +export class SendItemCreatedCountConfig implements Achievement { + // Define send count achievements here + static readonly SendItemCreated = new SendItemCreatedCountConfig( + "send-item-created", + "1st send item created", + 1, + ); + static readonly SendItemCreated10 = new SendItemCreatedCountConfig( + "send-item-created-10", + "10 send items created", + 10, + ); + static readonly SendItemCreated50 = new SendItemCreatedCountConfig( + "send-item-created-50", + "50 send items created", + 50, + ); + static readonly SendItemCreated100 = new SendItemCreatedCountConfig( + "send-item-created-100", + "100 send items created", + 100, + ); + + base: Achievement; + get achievement() { + return this.base.achievement; + } + + get name() { + return this.base.name; + } + + get validator() { + return Type.Threshold; + } + + get active() { + return this.base.active; + } + + get hidden() { + return false; + } + threshold: number; + private constructor(key: string, name: string, threshold: number) { + this.threshold = threshold; + this.base.achievement = key as AchievementId; + this.base.name = name; + this.base.active = { + metric: "send-item-quantity" as MetricId, + low: threshold - 1, + high: threshold, + }; + } +} diff --git a/libs/common/src/tools/achievements/validators/item-collection-move-validator.ts b/libs/common/src/tools/achievements/validators/item-collection-move-validator.ts new file mode 100644 index 00000000000..0011651cfdb --- /dev/null +++ b/libs/common/src/tools/achievements/validators/item-collection-move-validator.ts @@ -0,0 +1,42 @@ +import { earnedEvent } from "../achievement-events"; +import { Type } from "../data"; +import { + AchievementId, + AchievementProgressEvent, + AchievementValidator, + UserActionEvent, +} from "../types"; + +export class ItemCollectionMoveValidator implements AchievementValidator { + base: AchievementValidator; + get achievement() { + return "item-collection-move" as AchievementId; + } + get name() { + return "1st item moved to a collection"; + } + get validator() { + return Type.HasTag; + } + get active() { + return this.base.active; + } + get hidden() { + return false; + } + + constructor() { + this.base.active = "until-earned"; + } + + trigger(item: UserActionEvent) { + // This achievement is specific to moving an item into a collection. + // If there is ever an achievement for creating a vault item in a collection + // this class can be renamed and reused to define a move or create achievement. + return item.action === "vault-item-moved" && (item.tags?.includes("collection") ?? false); + } + + award(_measured: AchievementProgressEvent[]) { + return [earnedEvent(this.achievement)]; + } +} diff --git a/libs/common/src/tools/achievements/validators/item-removed-validator.ts b/libs/common/src/tools/achievements/validators/item-removed-validator.ts new file mode 100644 index 00000000000..933ef409054 --- /dev/null +++ b/libs/common/src/tools/achievements/validators/item-removed-validator.ts @@ -0,0 +1,49 @@ +import { earnedEvent, progressEvent } from "../achievement-events"; +import { Type } from "../data"; +import { + AchievementId, + AchievementProgressEvent, + AchievementValidator, + MetricId, + UserActionEvent, +} from "../types"; + +export class ItemRemovedValidator implements AchievementValidator { + base: AchievementValidator; + get achievement() { + return "item-removed" as AchievementId; + } + get name() { + return "1st item removed from vault"; + } + // Threshold validator because we are only looking + // for the action of removed and the threshold is 1 + get validator() { + return Type.Threshold; + } + get active() { + return this.base.active; + } + get hidden() { + return false; + } + private metric = "item-removed-quantity" as MetricId; + constructor() { + this.base.active = { + metric: this.metric, + high: 1, + }; + } + + trigger(item: UserActionEvent) { + return item.action === "vault-item-removed"; + } + + measure(item: UserActionEvent, metrics: Map) { + return [progressEvent(this.metric)]; + } + + award(_measured: AchievementProgressEvent[]) { + return [earnedEvent(this.achievement)]; + } +} diff --git a/libs/common/src/tools/achievements/validators/item-uri-added-validator.ts b/libs/common/src/tools/achievements/validators/item-uri-added-validator.ts new file mode 100644 index 00000000000..23c6bcb68db --- /dev/null +++ b/libs/common/src/tools/achievements/validators/item-uri-added-validator.ts @@ -0,0 +1,46 @@ +import { earnedEvent } from "../achievement-events"; +import { Type } from "../data"; +import { + AchievementId, + AchievementProgressEvent, + AchievementValidator, + UserActionEvent, +} from "../types"; + +export class ItemUriAddedValidator implements AchievementValidator { + base: AchievementValidator; + get achievement() { + return "item-uri-added" as AchievementId; + } + get name() { + return "1st time adding a uri to an item"; + } + get validator() { + return Type.Threshold; + } + get active() { + return this.base.active; + } + get hidden() { + return false; + } + + constructor() { + // If edit and the uri count is present the achievement is rewarded + this.base.active = "until-earned"; + } + + trigger(item: UserActionEvent) { + // The achievement states that the uri needs to be added + // to vault item. Indicating an edit/update + return ( + item.action === "vault-item-updated" && + Number.isNaN(item.labels?.["vault-item-uri-quantity"]) && + (item.labels?.["vault-item-uri-quantity"] as number) >= 1 + ); + } + + award(_measured: AchievementProgressEvent[]) { + return [earnedEvent(this.achievement)]; + } +} diff --git a/libs/common/src/tools/achievements/validators/send-item-created-count-validator.ts b/libs/common/src/tools/achievements/validators/send-item-created-count-validator.ts new file mode 100644 index 00000000000..16edb2583cb --- /dev/null +++ b/libs/common/src/tools/achievements/validators/send-item-created-count-validator.ts @@ -0,0 +1,55 @@ +import { earnedEvent, progressEvent } from "../achievement-events"; +import { + AchievementProgressEvent, + AchievementValidator, + MetricId, + UserActionEvent, +} from "../types"; + +import { SendItemCreatedCountConfig } from "./config/send-created-count-config"; + +export class SendItemCreatedCountValidator implements AchievementValidator { + private sendItemCreatedProgress: MetricId; + constructor(private config: SendItemCreatedCountConfig) { + // All of the configs for created items must have a metric. + // This checks the types to allow us to assign the metric. + if (config.active === "until-earned") { + throw new Error( + `${config.achievement}: invalid configuration; 'active' must contain a metric`, + ); + } + + this.sendItemCreatedProgress = config.active.metric; + } + + base: AchievementValidator; + get achievement() { + return this.config.achievement; + } + get name() { + return this.config.name; + } + get validator() { + return this.config.validator; + } + get active() { + return this.config.active; + } + get hidden() { + return this.config.hidden; + } + + trigger(item: UserActionEvent) { + return item.action === "send-item-added"; + } + + measure(_item: UserActionEvent, progress: Map) { + const value = 1 + (progress.get(this.sendItemCreatedProgress) ?? 0); + return [progressEvent(this.sendItemCreatedProgress, value)]; + } + + award(_measured: AchievementProgressEvent[], progress: Map) { + const value = progress.get(this.sendItemCreatedProgress) ?? 0; + return value >= this.config.threshold ? [earnedEvent(this.achievement)] : []; + } +}