buffering for telemetry + centralized metric constants

- Metrics are buffered on the frontend for 5 seconds before being sent in batches to the `/_/fe_telemetry` endpoint. This reduces network traffic.
- Telemetry constants in one place

Change-Id: Ib7bd0b6aa089b1eb5e0966a500324dc96524502e
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/1077936
Commit-Queue: Sergei Rudenkov <sergeirudenkov@google.com>
Reviewed-by: Marcin Mordecki <mordeckimarcin@google.com>
diff --git a/perf/modules/explore-multi-sk/explore-multi-sk.ts b/perf/modules/explore-multi-sk/explore-multi-sk.ts
index c4d75b3..6746358 100644
--- a/perf/modules/explore-multi-sk/explore-multi-sk.ts
+++ b/perf/modules/explore-multi-sk/explore-multi-sk.ts
@@ -49,7 +49,7 @@
   Trace,
   TraceMetadata,
 } from '../json';
-import { recordSummary } from '../telemetry/telemetry';
+import { SummaryMetric, telemetry } from '../telemetry/telemetry';
 
 import '../../../elements-sk/modules/spinner-sk';
 import '../explore-simple-sk';
@@ -442,9 +442,13 @@
     } catch (err: any) {
       errorMessage(err.message || "Something went wrong, can't plot the graphs.");
     } finally {
-      recordSummary('fe_multi_graph_data_load_time_s', (performance.now() - startTime) / 1000, {
-        url: window.location.href,
-      });
+      telemetry.recordSummary(
+        SummaryMetric.MultiGraphDataLoadTime,
+        (performance.now() - startTime) / 1000,
+        {
+          url: window.location.href,
+        }
+      );
       this.updateShortcutMultiview();
       this.setProgress('');
       this.checkDataLoaded();
diff --git a/perf/modules/plot-google-chart-sk/plot-google-chart-sk.ts b/perf/modules/plot-google-chart-sk/plot-google-chart-sk.ts
index 21cdb8a..b3dd187 100644
--- a/perf/modules/plot-google-chart-sk/plot-google-chart-sk.ts
+++ b/perf/modules/plot-google-chart-sk/plot-google-chart-sk.ts
@@ -19,7 +19,7 @@
 import { define } from '../../../elements-sk/modules/define';
 import { AnomalyMap } from '../json';
 import { defaultColors, mainChartOptions } from '../common/plot-builder';
-import { recordSummary } from '../telemetry/telemetry';
+import { telemetry } from '../telemetry/telemetry';
 import {
   dataframeAnomalyContext,
   dataframeUserIssueContext,
@@ -33,6 +33,7 @@
 import { VResizableBoxSk } from './v-resizable-box-sk';
 import { SidePanelCheckboxClickDetails, SidePanelSk } from './side-panel-sk';
 import { DragToZoomBox } from './drag-to-zoom-box-sk';
+import { SummaryMetric } from '../telemetry/telemetry';
 
 export interface PlotSelectionEventDetails {
   value: range;
@@ -51,8 +52,6 @@
 export class PlotGoogleChartSk extends LitElement {
   private static readonly MOUSE_DOWN_HOLD_TIMEOUT = 3000; // 3 seconds
 
-  private static readonly GRAPH_PERF_METRIC_NAME = 'fe_google_graph_plot_time_s';
-
   private mouseDownTimeoutId: number | null = null;
 
   // TODO(b/362831653): Adjust height to 100% once plot-summary-sk is deprecated
@@ -425,7 +424,7 @@
 
     plot.view = view;
     this.updateOptions();
-    recordSummary(PlotGoogleChartSk.GRAPH_PERF_METRIC_NAME, (performance.now() - start) / 1000, {
+    telemetry.recordSummary(SummaryMetric.GoogleGraphPlotTime, (performance.now() - start) / 1000, {
       type: 'update-data-view',
     });
   }
@@ -1059,7 +1058,7 @@
     // them to the new locations, but the rendering internal may already do this for us.
     // We should only do this optimization if we see a performance issue.
     anomalyDiv.replaceChildren(...allDivs);
-    recordSummary(PlotGoogleChartSk.GRAPH_PERF_METRIC_NAME, (performance.now() - start) / 1000, {
+    telemetry.recordSummary(SummaryMetric.GoogleGraphPlotTime, (performance.now() - start) / 1000, {
       type: 'draw-anomaly',
     });
   }
@@ -1165,7 +1164,7 @@
     });
 
     userIssueDiv.replaceChildren(...allDivs);
-    recordSummary(PlotGoogleChartSk.GRAPH_PERF_METRIC_NAME, (performance.now() - start) / 1000, {
+    telemetry.recordSummary(SummaryMetric.GoogleGraphPlotTime, (performance.now() - start) / 1000, {
       type: 'draw-user-issues',
     });
   }
@@ -1228,7 +1227,7 @@
     this.drawAnomaly(this.chart);
     this.drawUserIssues(this.chart);
     this.drawXbar(this.chart);
-    recordSummary(PlotGoogleChartSk.GRAPH_PERF_METRIC_NAME, (performance.now() - start) / 1000, {
+    telemetry.recordSummary(SummaryMetric.GoogleGraphPlotTime, (performance.now() - start) / 1000, {
       type: 'main-chart',
     });
   }
@@ -1272,7 +1271,7 @@
         plot.options = options;
       }
     }
-    recordSummary(PlotGoogleChartSk.GRAPH_PERF_METRIC_NAME, (performance.now() - start) / 1000, {
+    telemetry.recordSummary(SummaryMetric.GoogleGraphPlotTime, (performance.now() - start) / 1000, {
       type: 'update-bounds',
     });
   }
diff --git a/perf/modules/plot-summary-sk/plot-summary-sk.ts b/perf/modules/plot-summary-sk/plot-summary-sk.ts
index 6815e0c..47e9de4 100644
--- a/perf/modules/plot-summary-sk/plot-summary-sk.ts
+++ b/perf/modules/plot-summary-sk/plot-summary-sk.ts
@@ -32,7 +32,7 @@
 } from '../dataframe/dataframe_context';
 import { range } from '../dataframe/index';
 import { define } from '../../../elements-sk/modules/define';
-import { recordSummary } from '../telemetry/telemetry';
+import { SummaryMetric, telemetry } from '../telemetry/telemetry';
 
 import { style } from './plot-summary-sk.css';
 import { HResizableBoxSk } from './h_resizable_box_sk';
@@ -133,7 +133,7 @@
     view.setColumns(cols);
     plot.view = view;
     plot.options = options;
-    recordSummary('fe_google_graph_plot_time_s', (performance.now() - start) / 1000, {
+    telemetry.recordSummary(SummaryMetric.GoogleGraphPlotTime, (performance.now() - start) / 1000, {
       type: 'summary',
     });
   }
diff --git a/perf/modules/regressions-page-sk/regressions-page-sk.ts b/perf/modules/regressions-page-sk/regressions-page-sk.ts
index b99b62a..4111b3b 100644
--- a/perf/modules/regressions-page-sk/regressions-page-sk.ts
+++ b/perf/modules/regressions-page-sk/regressions-page-sk.ts
@@ -19,7 +19,7 @@
 import '@material/web/button/outlined-button.js';
 import { HintableObject } from '../../../infra-sk/modules/hintable';
 import { errorMessage } from '../errorMessage';
-import { increaseCounter } from '../telemetry/telemetry';
+import { CountMetric, telemetry } from '../telemetry/telemetry';
 
 // State is the local UI state of regressions-page-sk
 interface State {
@@ -184,7 +184,7 @@
         await this.anomaliesTable!.populateTable(this.cpAnomalies);
       })
       .catch((msg) => {
-        increaseCounter('fe_data_fetch_failure', {
+        telemetry.increaseCounter(CountMetric.DataFetchFailure, {
           page: 'regressions',
           endpoint: '/_/anomalies/anomaly_list',
         });
diff --git a/perf/modules/report-page-sk/report-page-sk.ts b/perf/modules/report-page-sk/report-page-sk.ts
index 84a2d3c..326cc60 100644
--- a/perf/modules/report-page-sk/report-page-sk.ts
+++ b/perf/modules/report-page-sk/report-page-sk.ts
@@ -18,7 +18,7 @@
 import { upgradeProperty } from '../../../elements-sk/modules/upgradeProperty';
 import '../../../elements-sk/modules/icons/camera-roll-icon-sk';
 import { PlotSelectionEventDetails } from '../plot-google-chart-sk/plot-google-chart-sk';
-import { increaseCounter, recordSummary } from '../telemetry/telemetry';
+import { CountMetric, SummaryMetric, telemetry } from '../telemetry/telemetry';
 
 const weekInSeconds = 7 * 24 * 60 * 60;
 
@@ -230,7 +230,7 @@
         this.setCurrentlyLoading('');
       })
       .catch((msg: any) => {
-        increaseCounter('fe_data_fetch_failure', {
+        telemetry.increaseCounter(CountMetric.DataFetchFailure, {
           page: 'report',
           endpoint: '/_/anomalies/group_report',
         });
@@ -264,10 +264,13 @@
               this.anomalyTracker.setGraph(anomaly.id, graphElement);
 
               const listener = () => {
-                recordSummary(
-                  'fe_single_graph_load_time_s',
+                telemetry.recordSummary(
+                  SummaryMetric.SingleGraphLoadTime,
                   (performance.now() - startTime) / 1000,
-                  { page: 'report', url: window.location.href }
+                  {
+                    page: 'report',
+                    url: window.location.href,
+                  }
                 );
                 graphElement.removeEventListener('data-loaded', listener);
                 loadedCount++;
diff --git a/perf/modules/telemetry/BUILD.bazel b/perf/modules/telemetry/BUILD.bazel
index dae6480..15be920 100644
--- a/perf/modules/telemetry/BUILD.bazel
+++ b/perf/modules/telemetry/BUILD.bazel
@@ -1,4 +1,16 @@
-load("//infra-sk:index.bzl", "ts_library")
+load("//infra-sk:index.bzl", "karma_test", "ts_library")
+
+karma_test(
+    name = "telemetry_test",
+    src = "telemetry_test.ts",
+    deps = [
+        ":telemetry_ts_lib",
+        "//:node_modules/@types/chai",
+        "//:node_modules/@types/sinon",
+        "//:node_modules/chai",
+        "//:node_modules/sinon",
+    ],
+)
 
 ts_library(
     name = "telemetry_ts_lib",
diff --git a/perf/modules/telemetry/telemetry.ts b/perf/modules/telemetry/telemetry.ts
index 6e8bc98..f07165e 100644
--- a/perf/modules/telemetry/telemetry.ts
+++ b/perf/modules/telemetry/telemetry.ts
@@ -1,15 +1,20 @@
 /**
- * @fileoverview This file defines functions for sending frontend telemetry metrics.
- * These metrics are used to track user interactions and performance within the
- * application.
+ * @fileoverview This file defines a Telemetry class for sending frontend metrics.
+ * A singleton instance is exported for application-wide use. These metrics are
+ * used to track user interactions and performance.
+ *
+ * Metrics are buffered on the frontend for 5 seconds before being sent in batches
+ * to the `/_/fe_telemetry` endpoint. This reduces network traffic. Any pending metrics
+ * are also sent when the page visibility changes to 'hidden' (e.g., when the user
+ * navigates away or closes the tab) to prevent data loss.
  *
  * To add a new counter metric:
- * 1. Add the metric name to the `CountMetricName` type.
- * 2. Call the `increaseCounter` function with the new metric name and optional tags.
+ * 1. Add the metric name to the `CountMetric` enum.
+ * 2. Call `telemetry.increaseCounter()` with the new metric name and optional tags.
  *
  * To add a new summary metric:
- * 1. Add the metric name to the `SummaryMetricName` type.
- * 2. Call the `recordSummary` function with the new metric name, value, and optional tags.
+ * 1. Add the metric name to the `SummaryMetric` enum.
+ * 2. Call `telemetry.recordSummary()` with the new metric name, value, and optional tags.
  */
 interface FrontendMetric {
   metric_name: string;
@@ -18,50 +23,121 @@
   metric_type: 'counter' | 'summary';
 }
 
-type CountMetricName =
-  // Counts data request failures
-  | 'fe_data_fetch_failure'
-  // Counts the specific triage actions users perform (e.g., filing a bug, ignoring an anomaly).
-  | 'fe_triage_action_taken';
+export enum CountMetric {
+  // go/keep-sorted start
+  DataFetchFailure = 'fe_data_fetch_failure',
+  TriageActionTaken = 'fe_triage_action_taken',
+  // go/keep-sorted end
+}
 
-type SummaryMetricName =
-  // Measures the time it takes for google.graph to plot, when data is already fetched.
-  | 'fe_google_graph_plot_time_s'
-  // Measures the time taken to fetch and process data when plotting new graphs.
-  | 'fe_multi_graph_data_load_time_s'
-  // Measures the time it takes to render each individual graph
-  | 'fe_single_graph_load_time_s';
+export enum SummaryMetric {
+  // go/keep-sorted start
+  GoogleGraphPlotTime = 'fe_google_graph_plot_time_s',
+  MultiGraphDataLoadTime = 'fe_multi_graph_data_load_time_s',
+  ReportAnomaliesTableLoadTime = 'fe_report_anomalies_table_load_time_s',
+  ReportChartContainerLoadTime = 'fe_report_chart_container_load_time_s',
+  ReportGraphChunkLoadTime = 'fe_report_graph_chunk_load_time_s',
+  SingleGraphLoadTime = 'fe_single_graph_load_time_s',
+  // go/keep-sorted end
+}
 
-export async function increaseCounter(metricName: CountMetricName, tags = {}) {
-  sendMetrics([
-    {
-      metric_name: metricName as string,
+class Telemetry {
+  private static readonly BUFFER_FLUSH_INTERVAL_MS = 5000; // 5 seconds
+
+  private static readonly MAX_BUFFER_SIZE = 1000; // Max 1000 metrics in buffer
+
+  private metricsBuffer: FrontendMetric[] = [];
+
+  private timerId: number | null = null;
+
+  constructor() {
+    // When the page visibility changes, flush the buffer. This helps ensure we
+    // capture metrics before the user navigates away or closes the tab.
+    document.addEventListener('visibilitychange', () => {
+      if (document.visibilityState === 'hidden') {
+        if (this.timerId) {
+          clearTimeout(this.timerId);
+          this.timerId = null;
+        }
+        this.sendBufferedMetrics();
+      }
+    });
+  }
+
+  // Flushes the metrics buffer by sending the data to the telemetry endpoint.
+  private async sendBufferedMetrics() {
+    if (this.metricsBuffer.length === 0) {
+      return;
+    }
+
+    const metricsToSend = [...this.metricsBuffer];
+    this.metricsBuffer.length = 0;
+
+    try {
+      await fetch('/_/fe_telemetry', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify(metricsToSend),
+      });
+    } catch (e) {
+      console.error(e, 'Failed to send frontend metrics:', metricsToSend);
+      this.queueMetrics(metricsToSend);
+    }
+  }
+
+  private queueMetric(metric: FrontendMetric) {
+    this.queueMetrics([metric]);
+  }
+
+  private queueMetrics(metrics: FrontendMetric[]) {
+    for (const m of metrics) {
+      if (this.metricsBuffer.length >= Telemetry.MAX_BUFFER_SIZE) {
+        console.warn('Frontend metrics buffer full, removing oldest metric to make space.');
+        this.metricsBuffer.shift(); // Remove the oldest metric (FIFO)
+      }
+      this.metricsBuffer.push(m);
+    }
+
+    if (!this.timerId) {
+      this.timerId = window.setTimeout(() => {
+        this.sendBufferedMetrics();
+        this.timerId = null;
+      }, Telemetry.BUFFER_FLUSH_INTERVAL_MS);
+    }
+  }
+
+  increaseCounter(metricName: CountMetric, tags = {}) {
+    this.queueMetric({
+      metric_name: metricName,
       metric_value: 1,
       tags: tags,
       metric_type: 'counter',
-    },
-  ]);
-}
+    });
+  }
 
-export async function recordSummary(metricName: SummaryMetricName, val: number, tags = {}) {
-  sendMetrics([
-    {
-      metric_name: metricName as string,
+  recordSummary(metricName: SummaryMetric, val: number, tags = {}) {
+    this.queueMetric({
+      metric_name: metricName,
       metric_value: val,
       tags: tags,
       metric_type: 'summary',
+    });
+  }
+
+  // The following are exposed for testing purposes.
+  _forTesting = {
+    reset: () => {
+      this.metricsBuffer.length = 0;
+      if (this.timerId) {
+        clearTimeout(this.timerId);
+        this.timerId = null;
+      }
     },
-  ]);
+    getBuffer: () => this.metricsBuffer,
+    MAX_BUFFER_SIZE: Telemetry.MAX_BUFFER_SIZE,
+  };
 }
 
-async function sendMetrics(metrics: FrontendMetric[]) {
-  fetch('/_/fe_telemetry', {
-    method: 'POST',
-    headers: {
-      'Content-Type': 'application/json',
-    },
-    body: JSON.stringify(metrics),
-  }).catch((e) => {
-    console.error(e, 'Failed to send frontend metrics:', metrics);
-  });
-}
+export const telemetry = new Telemetry();
diff --git a/perf/modules/telemetry/telemetry_test.ts b/perf/modules/telemetry/telemetry_test.ts
new file mode 100644
index 0000000..fad5a2e
--- /dev/null
+++ b/perf/modules/telemetry/telemetry_test.ts
@@ -0,0 +1,163 @@
+import { expect } from 'chai';
+import * as sinon from 'sinon';
+import { CountMetric, SummaryMetric, telemetry } from './telemetry';
+
+describe('telemetry', () => {
+  const BUFFER_FLUSH_INTERVAL_MS = 5000; // 5 seconds
+
+  let fetchStub: sinon.SinonStub;
+  let setTimeoutStub: sinon.SinonStub;
+  let clearTimeoutStub: sinon.SinonStub;
+
+  beforeEach(() => {
+    // Mock fetch to prevent actual network requests
+    fetchStub = sinon.stub(window, 'fetch').resolves(new Response());
+    // Mock setTimeout and clearTimeout to control time in tests
+    setTimeoutStub = sinon.stub(window, 'setTimeout').returns(123 as any);
+    clearTimeoutStub = sinon.stub(window, 'clearTimeout');
+
+    // Reset the internal buffer and timer before each test
+    telemetry._forTesting.reset();
+  });
+
+  afterEach(() => {
+    sinon.restore();
+    telemetry._forTesting.reset();
+  });
+
+  it('should buffer increaseCounter metrics and send them after the interval', async () => {
+    telemetry.increaseCounter(CountMetric.DataFetchFailure, { test: 'tag1' });
+    telemetry.increaseCounter(CountMetric.TriageActionTaken, { test: 'tag2' });
+
+    // Expect fetch not to have been called immediately
+    expect(fetchStub.callCount).to.equal(0);
+    // Expect setTimeout to have been called to schedule the flush
+    expect(setTimeoutStub.callCount).to.equal(1);
+    expect(setTimeoutStub.getCall(0).args[1]).to.equal(BUFFER_FLUSH_INTERVAL_MS);
+
+    // Manually trigger the setTimeout callback
+    setTimeoutStub.getCall(0).args[0]();
+
+    // Expect fetch to have been called with the buffered metrics
+    expect(fetchStub.callCount).to.equal(1);
+    const expectedBody = JSON.stringify([
+      {
+        metric_name: CountMetric.DataFetchFailure,
+        metric_value: 1,
+        tags: { test: 'tag1' },
+        metric_type: 'counter',
+      },
+      {
+        metric_name: CountMetric.TriageActionTaken,
+        metric_value: 1,
+        tags: { test: 'tag2' },
+        metric_type: 'counter',
+      },
+    ]);
+    expect(fetchStub.getCall(0).args[0]).to.equal('/_/fe_telemetry');
+    expect(fetchStub.getCall(0).args[1]).to.deep.equal({
+      method: 'POST',
+      headers: { 'Content-Type': 'application/json' },
+      body: expectedBody,
+    });
+  });
+
+  it('should buffer recordSummary metrics and send them after the interval', async () => {
+    telemetry.recordSummary(SummaryMetric.GoogleGraphPlotTime, 100, { test: 'tag3' });
+    telemetry.recordSummary(SummaryMetric.MultiGraphDataLoadTime, 200, { test: 'tag4' });
+
+    expect(fetchStub.callCount).to.equal(0);
+    expect(setTimeoutStub.callCount).to.equal(1);
+    expect(setTimeoutStub.getCall(0).args[1]).to.equal(BUFFER_FLUSH_INTERVAL_MS);
+
+    setTimeoutStub.getCall(0).args[0]();
+
+    expect(fetchStub.callCount).to.equal(1);
+    const expectedBody = JSON.stringify([
+      {
+        metric_name: SummaryMetric.GoogleGraphPlotTime,
+        metric_value: 100,
+        tags: { test: 'tag3' },
+        metric_type: 'summary',
+      },
+      {
+        metric_name: SummaryMetric.MultiGraphDataLoadTime,
+        metric_value: 200,
+        tags: { test: 'tag4' },
+        metric_type: 'summary',
+      },
+    ]);
+    expect(fetchStub.getCall(0).args[0]).to.equal('/_/fe_telemetry');
+    expect(fetchStub.getCall(0).args[1]).to.deep.equal({
+      method: 'POST',
+      headers: { 'Content-Type': 'application/json' },
+      body: expectedBody,
+    });
+  });
+
+  it('should send buffered metrics when document visibility changes to hidden', async () => {
+    telemetry.increaseCounter(CountMetric.DataFetchFailure);
+    expect(fetchStub.callCount).to.equal(0);
+    expect(setTimeoutStub.callCount).to.equal(1); // Timer started
+
+    // Simulate visibility change to hidden
+    Object.defineProperty(document, 'visibilityState', {
+      value: 'hidden',
+      writable: true,
+    });
+    document.dispatchEvent(new Event('visibilitychange'));
+
+    // Expect clearTimeout to be called
+    expect(clearTimeoutStub.callCount).to.equal(1);
+    expect(clearTimeoutStub.getCall(0).args[0]).to.equal(123); // 123 is the mocked timerId
+
+    // Expect fetch to have been called immediately
+    expect(fetchStub.callCount).to.equal(1);
+    const expectedBody = JSON.stringify([
+      {
+        metric_name: CountMetric.DataFetchFailure,
+        metric_value: 1,
+        tags: {},
+        metric_type: 'counter',
+      },
+    ]);
+    expect(fetchStub.getCall(0).args[0]).to.equal('/_/fe_telemetry');
+    expect(fetchStub.getCall(0).args[1]).to.deep.equal({
+      method: 'POST',
+      headers: { 'Content-Type': 'application/json' },
+      body: expectedBody,
+    });
+  });
+
+  it('should implement FIFO behavior when buffer is full', async () => {
+    const MAX_BUFFER_SIZE = telemetry._forTesting.MAX_BUFFER_SIZE;
+    // Fill the buffer to its maximum capacity
+    for (let i = 0; i < MAX_BUFFER_SIZE; i++) {
+      telemetry.increaseCounter(CountMetric.DataFetchFailure, { id: `metric_${i}` });
+    }
+    expect(telemetry._forTesting.getBuffer().length).to.equal(MAX_BUFFER_SIZE);
+
+    // Add one more metric, which should trigger FIFO behavior
+    telemetry.increaseCounter(CountMetric.TriageActionTaken, { id: 'new_metric' });
+
+    // Expect the buffer size to remain the same
+    expect(telemetry._forTesting.getBuffer().length).to.equal(MAX_BUFFER_SIZE);
+
+    // Expect the oldest metric (id: metric_0) to be removed
+    const buffer = telemetry._forTesting.getBuffer();
+
+    // Expect the second oldest metric (id: metric_1) to now be the oldest
+    expect(buffer[0].tags.id).to.equal('metric_1');
+  });
+
+  it('should not send metrics if buffer is empty', async () => {
+    telemetry.increaseCounter(CountMetric.DataFetchFailure);
+    // Clear the buffer, but the timer is still scheduled.
+    telemetry._forTesting.getBuffer().length = 0;
+    // Trigger the timer.
+    setTimeoutStub.getCall(0).args[0]();
+
+    // Expect fetch not to have been called because the buffer was empty.
+    expect(fetchStub.callCount).to.equal(0);
+  });
+});
diff --git a/perf/modules/triage-menu-sk/triage-menu-sk.ts b/perf/modules/triage-menu-sk/triage-menu-sk.ts
index 4c76213..5e7d531 100644
--- a/perf/modules/triage-menu-sk/triage-menu-sk.ts
+++ b/perf/modules/triage-menu-sk/triage-menu-sk.ts
@@ -18,7 +18,7 @@
 import { ExistingBugDialogSk } from '../existing-bug-dialog-sk/existing-bug-dialog-sk';
 import { Anomaly } from '../json';
 import { AnomalyData } from '../common/anomaly-data';
-import { increaseCounter } from '../telemetry/telemetry';
+import { CountMetric, telemetry } from '../telemetry/telemetry';
 
 import '../new-bug-dialog-sk/new-bug-dialog-sk';
 import '../existing-bug-dialog-sk/existing-bug-dialog-sk';
@@ -115,7 +115,7 @@
   }
 
   fileBug() {
-    increaseCounter('fe_triage_action_taken', { action: 'file_bug' });
+    telemetry.increaseCounter(CountMetric.TriageActionTaken, { action: 'file_bug' });
     this.newBugDialog!.fileNewBug();
   }
 
@@ -124,12 +124,12 @@
   }
 
   openExistingBugDialog() {
-    increaseCounter('fe_triage_action_taken', { action: 'associate_bug' });
+    telemetry.increaseCounter(CountMetric.TriageActionTaken, { action: 'associate_bug' });
     this.existingBugDialog!.open();
   }
 
   ignoreAnomaly() {
-    increaseCounter('fe_triage_action_taken', { action: 'ignore' });
+    telemetry.increaseCounter(CountMetric.TriageActionTaken, { action: 'ignore' });
     this.makeEditAnomalyRequest(this._anomalies, this._trace_names, 'IGNORE');
   }