1 import 'core-js/actual/set/difference';
3 import metrics from '@proton/metrics';
4 import type { HttpsProtonMeDriveSyncErrorsTotalV1SchemaJson } from '@proton/metrics/types/drive_sync_errors_total_v1.schema';
5 import { getIsNetworkError, getIsOfflineError } from '@proton/shared/lib/api/helpers/apiErrorHelper';
6 import { EVENT_TYPES } from '@proton/shared/lib/drive/constants';
7 import type { DriveEventsResult } from '@proton/shared/lib/interfaces/drive/events';
9 import { is4xx, is5xx } from '../../utils/errorHandling/apiErrors';
10 import type { VolumeType } from '../_volumes';
11 import type { DriveEvent } from './interface';
13 const EVENT_METRICS = ['delete', 'create', 'update', 'update_metadata'] as const;
15 export function countEventsPerType(type: VolumeType, driveEvents: DriveEventsResult) {
16 if (!driveEvents.Events) {
20 const deleteCount = driveEvents.Events.filter((event) => event.EventType === EVENT_TYPES.DELETE).length;
22 metrics.drive_sync_event_total.increment(
25 eventType: EVENT_METRICS[EVENT_TYPES.DELETE],
31 const createCount = driveEvents.Events.filter((event) => event.EventType === EVENT_TYPES.CREATE).length;
33 metrics.drive_sync_event_total.increment(
36 eventType: EVENT_METRICS[EVENT_TYPES.CREATE],
42 const updateCount = driveEvents.Events.filter((event) => event.EventType === EVENT_TYPES.UPDATE).length;
44 metrics.drive_sync_event_total.increment(
47 eventType: EVENT_METRICS[EVENT_TYPES.UPDATE],
53 const updateMetadata = driveEvents.Events.filter((event) => event.EventType === EVENT_TYPES.UPDATE_METADATA).length;
55 metrics.drive_sync_event_total.increment(
58 eventType: EVENT_METRICS[EVENT_TYPES.UPDATE_METADATA],
65 type EVENT_METRICS_TYPE = (typeof EVENT_METRICS)[number];
67 [K in EVENT_METRICS_TYPE]?: Set<string>;
69 type ProcessMap = Map<string, StateEvents>;
71 export class EventsMetrics {
72 private state: ProcessMap = new Map();
74 private events: ProcessMap = new Map();
76 processed(eventId: string, event: DriveEvent) {
77 const { eventType, encryptedLink } = event;
78 const { volumeId, linkId } = encryptedLink;
79 const key = `${volumeId}-${eventId}`;
80 const metricKey = EVENT_METRICS[eventType];
81 const processedEvents = this.state.get(key) || {};
82 if (!processedEvents[metricKey]) {
83 processedEvents[metricKey] = new Set();
85 processedEvents[metricKey].add(linkId);
86 this.state.set(key, processedEvents);
89 batchStart(volumeId: string, driveEvents: DriveEventsResult) {
90 const key = `${volumeId}-${driveEvents.EventID}`;
91 this.events.set(key, {
93 driveEvents.Events.filter((event) => event.EventType === EVENT_TYPES.DELETE).map(
94 (event) => event.Link.LinkID
98 driveEvents.Events.filter((event) => event.EventType === EVENT_TYPES.CREATE).map(
99 (event) => event.Link.LinkID
103 driveEvents.Events.filter((event) => event.EventType === EVENT_TYPES.UPDATE).map(
104 (event) => event.Link.LinkID
107 update_metadata: new Set(
108 driveEvents.Events.filter((event) => event.EventType === EVENT_TYPES.UPDATE_METADATA).map(
109 (event) => event.Link.LinkID
115 batchCompleted(volumeId: string, eventId: string, type: VolumeType) {
116 const key = `${volumeId}-${eventId}`;
117 const batch = this.state.get(key) || {};
118 const events = this.events.get(key) || {};
119 this.processBatch(batch, events, type);
120 this.state.delete(key);
121 this.events.delete(key);
124 private processBatch(batch: StateEvents, events: StateEvents, type: VolumeType) {
125 EVENT_METRICS.forEach((eventType) => {
126 const processed = batch[eventType];
127 const total = events[eventType];
128 if (total !== undefined && total.size > 0) {
129 const difference = total.difference(processed || new Set());
130 if (difference.size) {
131 metrics.drive_sync_event_unecessary_total.increment(
134 eventType: eventType,
143 // TODO: DRVWEB-4319 Implement sync errors & sync erroring users and revamp this function with new metric
144 export function getErrorCategory(error: any): HttpsProtonMeDriveSyncErrorsTotalV1SchemaJson['Labels']['type'] {
145 if (getIsOfflineError(error) || getIsNetworkError(error)) {
146 return 'network_error';
147 } else if (error?.statusCode && is4xx(error?.statusCode)) {
149 } else if (error?.statusCode && is5xx(error?.statusCode)) {