Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / packages / docs-core / lib / Services / Metrics / MetricService.ts
blob94906abf3ab5aacb94d9a86a656dd1aa3ef76cc4
1 import metrics from '@proton/metrics'
2 import { getBrowserForMetrics } from './getBrowserForMetrics'
3 import { sendTelemetryReport } from '@proton/shared/lib/helpers/metrics'
4 import type { Api } from '@proton/shared/lib/interfaces'
5 import type { TelemetryDocsEvents } from '@proton/shared/lib/api/telemetry'
6 import { TelemetryMeasurementGroups } from '@proton/shared/lib/api/telemetry'
7 import type { SuggestionSummaryType } from '@proton/docs-shared/lib/SuggestionType'
8 import { ConnectionCloseMetrics } from '../../Realtime/ConnectionCloseMetrics'
9 import type { ConnectionCloseReason } from '@proton/docs-proto'
11 const HEARTBEAT_INTERVAL = 60_000
13 type MetricSuggestionType = 'insertion' | 'replacement' | 'deletion' | 'formatting' | 'style' | 'other'
15 const SuggestionTypeToMetricSuggestionType: Record<SuggestionSummaryType, MetricSuggestionType> = {
16   insert: 'insertion',
17   delete: 'deletion',
18   'property-change': 'style',
19   split: 'insertion',
20   join: 'deletion',
21   'link-change': 'other',
22   'style-change': 'style',
23   replace: 'replacement',
24   'add-link': 'other',
25   'delete-link': 'other',
26   'image-change': 'other',
27   'insert-image': 'insertion',
28   'delete-image': 'deletion',
29   'indent-change': 'formatting',
30   'insert-table': 'insertion',
31   'delete-table': 'deletion',
32   'insert-table-row': 'insertion',
33   'duplicate-table-row': 'insertion',
34   'delete-table-row': 'deletion',
35   'insert-table-column': 'insertion',
36   'delete-table-column': 'deletion',
37   'duplicate-table-column': 'insertion',
38   'block-type-change': 'other',
39   'insert-divider': 'insertion',
40   'delete-divider': 'deletion',
41   'clear-formatting': 'style',
42   'align-change': 'formatting',
45 type SuggestionResolution = 'accepted' | 'rejected'
47 export class MetricService {
48   private heartbeatInterval: NodeJS.Timeout | null = null
50   constructor(private readonly api: Api) {}
52   initialize(): void {
53     this.heartbeat()
55     this.heartbeatInterval = setInterval(() => {
56       this.heartbeat()
57     }, HEARTBEAT_INTERVAL)
58   }
60   heartbeat(): void {
61     metrics.docs_open_documents_heartbeat_total.increment({
62       browser: getBrowserForMetrics(),
63     })
64   }
66   destroy(): void {
67     if (this.heartbeatInterval) {
68       clearInterval(this.heartbeatInterval)
69     }
70   }
72   reportSuggestionsTelemetry(event: TelemetryDocsEvents): void {
73     void sendTelemetryReport({
74       api: this.api,
75       measurementGroup: TelemetryMeasurementGroups.docsSuggestions,
76       event: event,
77     })
78   }
80   reportFullyBlockingErrorModal(): void {
81     metrics.docs_alert_modal_total.increment({})
82   }
84   reportSuggestionCreated(type?: SuggestionSummaryType): void {
85     const metricType = type ? SuggestionTypeToMetricSuggestionType[type] : 'other'
86     metrics.docs_suggestions_created_total.increment({ type: metricType })
87   }
89   reportSuggestionResolved(resolution: SuggestionResolution): void {
90     metrics.docs_suggestions_resolved_total.increment({ type: resolution })
91   }
93   reportRealtimeDisconnect(reason: ConnectionCloseReason): void {
94     let type = ConnectionCloseMetrics[reason.props.code]
95     if (!type) {
96       /** There is no 'other' option, so we use 'timeout' as a catch-all */
97       type = 'timeout'
98     }
100     metrics.docs_realtime_disconnect_error_total.increment({ type })
101   }