1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-10 05:30:01 +00:00

Adding more validators for counts

This commit is contained in:
Tom
2025-03-19 11:48:39 -04:00
parent 02dbf172f5
commit c209fab86d
7 changed files with 296 additions and 15 deletions

View File

@@ -51,6 +51,30 @@ const itemUpdated$: Observable<UserActionEvent> = of({
tags: ["with-folder"],
});
const itemDeleted$: Observable<UserActionEvent> = 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<UserActionEvent> = of(
{
"@timestamp": Date.now(),
@@ -100,4 +124,4 @@ const itemMovedToCollection$: Observable<UserActionEvent> = of(
} satisfies UserActionEvent,
);
export { itemAdded$, itemUpdated$, itemMovedToCollection$ };
export { itemAdded$, itemUpdated$, itemDeleted$, itemMovedToCollection$ };

View File

@@ -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;

View File

@@ -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,
};
}
}

View File

@@ -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)];
}
}

View File

@@ -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<MetricId, number>) {
return [progressEvent(this.metric)];
}
award(_measured: AchievementProgressEvent[]) {
return [earnedEvent(this.achievement)];
}
}

View File

@@ -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)];
}
}

View File

@@ -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<MetricId, number>) {
const value = 1 + (progress.get(this.sendItemCreatedProgress) ?? 0);
return [progressEvent(this.sendItemCreatedProgress, value)];
}
award(_measured: AchievementProgressEvent[], progress: Map<MetricId, number>) {
const value = progress.get(this.sendItemCreatedProgress) ?? 0;
return value >= this.config.threshold ? [earnedEvent(this.achievement)] : [];
}
}