diff --git a/src/internal/base-component/__tests__/metrics.test.ts b/src/internal/base-component/__tests__/metrics.test.ts index 596c116..df42281 100644 --- a/src/internal/base-component/__tests__/metrics.test.ts +++ b/src/internal/base-component/__tests__/metrics.test.ts @@ -1,8 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { MetricsTestHelper, Metrics } from '../metrics/metrics'; -import { MetricDetail } from '../metrics/interfaces'; +import { clearOneTimeMetricsCache, Metrics } from '../metrics/metrics'; +import { ComponentMetricMinified } from '../metrics/interfaces'; declare global { interface Window { @@ -24,9 +24,9 @@ function mockConsoleError() { } describe('Client Metrics support', () => { - const metrics = new Metrics('dummy-package', '1.0'); + const metrics = new Metrics({ packageSource: 'dummy-package', packageVersion: '1.0', theme: 'default' }); - const checkMetric = (metricName: string, detailObject: MetricDetail) => { + const checkMetric = (metricName: string, detailObject: ComponentMetricMinified) => { expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', { eventType: 'awsui', eventContext: metricName, @@ -38,7 +38,6 @@ describe('Client Metrics support', () => { }; beforeEach(() => { - metrics.initMetrics('default'); window.AWSC = { Clog: { log: () => {}, @@ -51,19 +50,19 @@ describe('Client Metrics support', () => { afterEach(() => { jest.clearAllMocks(); - new MetricsTestHelper().resetOneTimeMetricsCache(); + clearOneTimeMetricsCache(); }); describe('sendMetric', () => { test('does nothing of both AWSC and panorama APIs are not available', () => { delete window.panorama; delete window.AWSC; - expect(() => metrics.sendMetric('name', 0)).not.toThrow(); + expect(() => metrics.sendOpsMetricValue('name', 0)).not.toThrow(); }); test('uses AWSC.Clog.log API as fallback when panorama is unavailable', () => { delete window.panorama; - metrics.sendMetric('name', 0); + metrics.sendOpsMetricValue('name', 0); expect(window.AWSC.Clog.log).toHaveBeenCalledWith('name', 0, undefined); }); @@ -85,7 +84,7 @@ describe('Client Metrics support', () => { setupIframe(); expect(window.parent.panorama).toBeUndefined(); - metrics.sendMetric('name', 0); // only proves no exception thrown + metrics.sendOpsMetricValue('name', 0); // only proves no exception thrown }); test('works if window.parent has panorama object', () => { @@ -96,7 +95,7 @@ describe('Client Metrics support', () => { expect(window.panorama).toBeUndefined(); expect(window.parent.panorama).toBeDefined(); - metrics.sendMetric('name', 0, undefined); + metrics.sendOpsMetricValue('name', 0); expect(window.parent.panorama).toHaveBeenCalledWith('trackCustomEvent', { eventType: 'awsui', eventContext: 'name', @@ -110,7 +109,7 @@ describe('Client Metrics support', () => { mockConsoleError(); test('delegates to window.panorama when defined', () => { - metrics.sendMetric('name', 0, undefined); + metrics.sendOpsMetricValue('name', 0); expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', { eventType: 'awsui', eventContext: 'name', @@ -122,12 +121,11 @@ describe('Client Metrics support', () => { describe('Metric name validation', () => { const tryValidMetric = (metricName: string) => { test(`calls window.panorama when valid metric name used (${metricName})`, () => { - metrics.sendMetric(metricName, 1, 'detail'); + metrics.sendOpsMetricValue(metricName, 1); expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', { eventType: 'awsui', eventContext: metricName, eventValue: '1', - eventDetail: 'detail', timestamp: expect.any(Number), }); }); @@ -135,7 +133,7 @@ describe('Client Metrics support', () => { const tryInvalidMetric = (metricName: string) => { test(`logs an error when invalid metric name used (${metricName})`, () => { - metrics.sendMetric(metricName, 0, 'detail'); + metrics.sendOpsMetricValue(metricName, 0); expect(console.error).toHaveBeenCalledWith(`Invalid metric name: ${metricName}`); jest.mocked(console.error).mockReset(); expect(window.panorama).not.toHaveBeenCalled(); @@ -146,7 +144,7 @@ describe('Client Metrics support', () => { test('logs and error when metric name is too long', () => { // 1001 char: too long const longName = '1234567890'.repeat(100) + 'x'; - metrics.sendMetric(longName, 0, 'detail'); + metrics.sendOpsMetricValue(longName, 0); expect(console.error).toHaveBeenCalledWith(`Metric name ${longName} is too long`); jest.mocked(console.error).mockReset(); }); @@ -161,35 +159,12 @@ describe('Client Metrics support', () => { tryInvalidMetric('colons:not:allowed'); // invalid characters tryInvalidMetric('spaces not allowed'); // invalid characters }); - - describe('Metric detail validation', () => { - test('accepts details below the character limit', () => { - const validDetail = 'a'.repeat(4000); - metrics.sendMetric('metricName', 1, validDetail); - expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', { - eventType: 'awsui', - eventContext: 'metricName', - eventValue: '1', - eventDetail: validDetail, - timestamp: expect.any(Number), - }); - }); - - test('throws an error when detail is too long', () => { - const invalidDetail = 'a'.repeat(4001); - metrics.sendMetric('metricName', 0, invalidDetail); - expect(console.error).toHaveBeenCalledWith(`Detail for metric metricName is too long: ${invalidDetail}`); - jest.mocked(console.error).mockReset(); - expect(window.panorama).not.toHaveBeenCalled(); - expect(window.AWSC.Clog.log).not.toHaveBeenCalled(); - }); - }); }); }); describe('sendMetricOnce', () => { - test('logs a metric name only once', () => { - metrics.sendMetricOnce('my-event', 1); + test('logs a metric of the same value only once', () => { + metrics.sendOpsMetricValue('my-event', 1); expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', { eventType: 'awsui', eventContext: 'my-event', @@ -198,13 +173,28 @@ describe('Client Metrics support', () => { }); expect(window.panorama).toHaveBeenCalledTimes(1); - metrics.sendMetricOnce('my-event', 2); + metrics.sendOpsMetricValue('my-event', 1); + expect(window.panorama).toHaveBeenCalledTimes(1); + }); + + test('does not deduplicate different values', () => { + metrics.sendOpsMetricValue('my-event', 1); + expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', { + eventType: 'awsui', + eventContext: 'my-event', + eventValue: '1', + timestamp: expect.any(Number), + }); expect(window.panorama).toHaveBeenCalledTimes(1); + + metrics.sendOpsMetricValue('my-event', 2); + expect(window.panorama).toHaveBeenCalledTimes(2); }); test('does not deduplicate metrics in different casing', () => { - metrics.sendMetricOnce('my-event', 1); - metrics.sendMetricOnce('My-Event', 2); + metrics.sendOpsMetricValue('my-event', 1); + metrics.sendOpsMetricValue('My-Event', 1); + expect(window.panorama).toHaveBeenCalledTimes(2); expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', { eventType: 'awsui', eventContext: 'my-event', @@ -214,44 +204,44 @@ describe('Client Metrics support', () => { expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', { eventType: 'awsui', eventContext: 'My-Event', - eventValue: '2', + eventValue: '1', timestamp: expect.any(Number), }); - expect(window.panorama).toHaveBeenCalledTimes(2); }); }); - describe('sendMetricObject', () => { - test('uses panorama API as fallback when AWSC.Clog.log is unavailable', () => { - window.AWSC = undefined; - metrics.sendMetricObject({ source: 'pkg', action: 'used', version: '5.0' }, 1); + describe('sendOpsMetricObject', () => { + test('sends metrics to panorama', () => { + metrics.sendOpsMetricObject('awsui-ops-demo', {}); expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', { eventType: 'awsui', - eventContext: 'awsui_pkg_d50', - eventDetail: '{"o":"main","s":"pkg","t":"default","a":"used","f":"react","v":"5.0"}', + eventContext: 'awsui-ops-demo', + eventDetail: '{"o":"main","t":"default","f":"react","v":"1.0"}', eventValue: '1', timestamp: expect.any(Number), }); }); + test('deduplicates metrics with same details', () => { + metrics.sendOpsMetricObject('awsui-ops-demo', { foo: 'something' }); + metrics.sendOpsMetricObject('awsui-ops-demo', { foo: 'something' }); + expect(window.panorama).toHaveBeenCalledTimes(1); + }); + + test('allows to send multiple metrics of same name but different details', () => { + metrics.sendOpsMetricObject('awsui-ops-demo', { foo: 'something' }); + metrics.sendOpsMetricObject('awsui-ops-demo', { foo: 'something-else' }); + expect(window.panorama).toHaveBeenCalledTimes(2); + }); + describe('correctly maps input object to metric name', () => { - test('applies default values for theme (default) and framework (react)', () => { - metrics.sendMetricObject( - { - source: 'pkg', - action: 'used', - version: '5.0', - }, - 1 - ); - checkMetric('awsui_pkg_d50', { + test('applies default values for origin, theme, version, framework', () => { + metrics.sendOpsMetricObject('awsui-ops-demo', {}); + checkMetric('awsui-ops-demo', { o: 'main', - s: 'pkg', t: 'default', - a: 'used', f: 'react', - v: '5.0', - c: undefined, + v: '1.0', }); }); @@ -262,120 +252,36 @@ describe('Client Metrics support', () => { ['5.7.0', ['57', '5.7.0']], ['5.7 dkjhkhsgdjh', ['57', '5.7dkjhkhsgdjh']], ['5.7.0 kjfhgjhdshjsjd', ['57', '5.7.0kjfhgjhdshjsjd']], - ]; + ] as const; versionTestCases.forEach(testCase => { test(`correctly interprets version ${testCase[0]}`, () => { - metrics.sendMetricObject( - { - source: 'pkg', - action: 'used', - version: testCase[0] as string, - }, - 1 - ); - checkMetric(`awsui_pkg_d${testCase[1][0]}`, { + const metrics = new Metrics('pkg', testCase[0]); + metrics.logComponentUsed('DummyComponentName', { props: {} }); + checkMetric(`awsui_DummyComponentName_u${testCase[1][0]}`, { o: 'main', - s: 'pkg', - t: 'default', - a: 'used', + t: 'unknown', f: 'react', v: testCase[1][1], - c: undefined, + a: 'used', + s: 'DummyComponentName', + c: { props: {} }, }); }); }); }); }); - describe('sendMetricObjectOnce', () => { - test('logs a metric only once if it is the same object', () => { - const metricObj = { - source: 'pkg', - action: 'used' as const, - version: '5.0', - }; - - metrics.sendMetricObjectOnce(metricObj, 1); - metrics.sendMetricObjectOnce(metricObj, 1); - expect(window.panorama).toHaveBeenCalledTimes(1); - }); - test('logs metric for each different version if same source and action', () => { - metrics.sendMetricObjectOnce( - { - source: 'pkg1', - action: 'used', - version: '5.0', - }, - 1 - ); - metrics.sendMetricObjectOnce( - { - source: 'pkg1', - action: 'used', - version: '6.0', - }, - 1 - ); - expect(window.panorama).toHaveBeenCalledTimes(2); - }); - test('logs a metric multiple times if same source but different actions', () => { - metrics.sendMetricObjectOnce( - { - source: 'pkg2', - action: 'used', - version: '5.0', - }, - 1 - ); - metrics.sendMetricObjectOnce( - { - source: 'pkg2', - action: 'loaded', - version: '5.0', - }, - 1 - ); - expect(window.panorama).toHaveBeenCalledTimes(2); - }); - }); - - describe('initMetrics', () => { - test('sets theme', () => { - const metrics = new Metrics('dummy-package', 'dummy-version'); - metrics.initMetrics('dummy-theme'); - - // check that the theme is correctly set - metrics.sendMetricObject( - { - source: 'pkg', - action: 'used', - version: '5.0', - }, - 1 - ); - checkMetric(`awsui_pkg_d50`, { - o: 'main', - s: 'pkg', - t: 'dummy-theme', - a: 'used', - f: 'react', - v: '5.0', - c: undefined, - }); - }); - }); - describe('logComponentUsed', () => { test('logs the usage of the given component', () => { metrics.logComponentUsed('DummyComponentName', { props: {} }); checkMetric(`awsui_DummyComponentName_d10`, { o: 'main', - s: 'DummyComponentName', t: 'default', - a: 'used', f: 'react', v: '1.0', + a: 'used', + s: 'DummyComponentName', c: { props: {} }, }); }); @@ -384,11 +290,11 @@ describe('Client Metrics support', () => { metrics.logComponentUsed('DummyComponentName', { props: { variant: 'primary' }, metadata: { isMobile: true } }); checkMetric(`awsui_DummyComponentName_d10`, { o: 'main', - s: 'DummyComponentName', t: 'default', - a: 'used', f: 'react', v: '1.0', + a: 'used', + s: 'DummyComponentName', c: { props: { variant: 'primary' }, metadata: { isMobile: true } }, }); }); @@ -400,11 +306,11 @@ describe('Client Metrics support', () => { }); checkMetric(`awsui_DummyComponentName_d10`, { o: 'main', - s: 'DummyComponentName', t: 'default', - a: 'used', f: 'react', v: '1.0', + a: 'used', + s: 'DummyComponentName', c: { props: {}, metadata: { nullValue: null } }, }); }); @@ -415,11 +321,11 @@ describe('Client Metrics support', () => { }); checkMetric(`awsui_DummyComponentName_d10`, { o: 'main', - s: 'DummyComponentName', t: 'default', - a: 'used', f: 'react', v: '1.0', + a: 'used', + s: 'DummyComponentName', c: { props: { count: 123, notANumber: 'NaN', maxSize: 'Infinity' } }, }); }); @@ -430,11 +336,11 @@ describe('Client Metrics support', () => { metrics.logComponentsLoaded(); checkMetric(`awsui_dummy-package_d10`, { o: 'main', - s: 'dummy-package', t: 'default', - a: 'loaded', f: 'react', v: '1.0', + a: 'loaded', + s: 'dummy-package', c: undefined, }); }); diff --git a/src/internal/base-component/__tests__/use-component-metrics.test.tsx b/src/internal/base-component/__tests__/use-component-metrics.test.tsx index 033653b..488a33c 100644 --- a/src/internal/base-component/__tests__/use-component-metrics.test.tsx +++ b/src/internal/base-component/__tests__/use-component-metrics.test.tsx @@ -3,8 +3,7 @@ import React from 'react'; import { render } from '@testing-library/react'; -import { MetricsTestHelper } from '../metrics/metrics'; -import { formatVersionForMetricName, formatMajorVersionForMetricDetail } from '../metrics/formatters'; +import { clearOneTimeMetricsCache } from '../metrics/metrics'; import { useComponentMetrics } from '../component-metrics'; declare global { @@ -46,7 +45,7 @@ function verifyMetricsAreLoggedOnlyOnce() { } function getExpectedMetricName(componentName: string) { - return `awsui_${componentName}_${formatVersionForMetricName('test', '3.0.0')}`; + return `awsui_${componentName}_t30`; } describe('useComponentMetrics', () => { @@ -65,7 +64,7 @@ describe('useComponentMetrics', () => { afterEach(() => { jest.clearAllMocks(); - new MetricsTestHelper().resetOneTimeMetricsCache(); + clearOneTimeMetricsCache(); }); test('component issues metrics upon rendering', () => { @@ -83,11 +82,11 @@ describe('useComponentMetrics', () => { 1, JSON.stringify({ o: 'main', - s: 'test-component-1', t: 'test', - a: 'used', f: 'react', - v: formatMajorVersionForMetricDetail('3.0.0'), + v: '3.0.0', + a: 'used', + s: 'test-component-1', c: { props: {} }, }) ); @@ -109,11 +108,11 @@ describe('useComponentMetrics', () => { 1, JSON.stringify({ o: 'main', - s: 'test-component-2', t: 'test', - a: 'used', f: 'react', - v: formatMajorVersionForMetricDetail('3.0.0'), + v: '3.0.0', + a: 'used', + s: 'test-component-2', c: { props: {} }, }) ); @@ -129,11 +128,11 @@ describe('useComponentMetrics', () => { const metricName = getExpectedMetricName('test-component-with-props'); const commonDetails = { o: 'main', - s: 'test-component-with-props', t: 'test', - a: 'used', f: 'react', - v: formatMajorVersionForMetricDetail('3.0.0'), + v: '3.0.0', + a: 'used', + s: 'test-component-with-props', }; expect(window.AWSC.Clog.log).toHaveBeenCalledTimes(1); expect(window.AWSC.Clog.log).toHaveBeenCalledWith( @@ -161,11 +160,11 @@ describe('useComponentMetrics', () => { 1, JSON.stringify({ o: 'custom', - s: 'test-component-1', t: 'test', - a: 'used', f: 'react', - v: formatMajorVersionForMetricDetail('3.0.0'), + v: '3.0.0', + a: 'used', + s: 'test-component-1', c: { props: {} }, }) ); diff --git a/src/internal/base-component/component-metrics.ts b/src/internal/base-component/component-metrics.ts index 4cbe7a3..0df3c66 100644 --- a/src/internal/base-component/component-metrics.ts +++ b/src/internal/base-component/component-metrics.ts @@ -9,16 +9,15 @@ export { ComponentConfiguration }; export function useComponentMetrics( componentName: string, - { packageSource, packageVersion, theme }: PackageSettings, + settings: PackageSettings, configuration: ComponentConfiguration = { props: {} } ) { useEffect(() => { - const metrics = new Metrics(packageSource, packageVersion); + const metrics = new Metrics(settings); - metrics.initMetrics(theme); if (typeof window !== 'undefined') { - metrics.sendMetricOnce('awsui-viewport-width', window.innerWidth || 0); - metrics.sendMetricOnce('awsui-viewport-height', window.innerHeight || 0); + metrics.sendOpsMetricValue('awsui-viewport-width', window.innerWidth || 0); + metrics.sendOpsMetricValue('awsui-viewport-height', window.innerHeight || 0); } metrics.logComponentsLoaded(); metrics.logComponentUsed(componentName.toLowerCase(), configuration); diff --git a/src/internal/base-component/metrics/formatters.ts b/src/internal/base-component/metrics/formatters.ts index ea37537..bedf655 100644 --- a/src/internal/base-component/metrics/formatters.ts +++ b/src/internal/base-component/metrics/formatters.ts @@ -1,28 +1,39 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { MetricDetail, MetricsLogItem } from './interfaces'; +import { PackageSettings, ComponentMetricDetail, JSONObject } from './interfaces'; declare const AWSUI_METRIC_ORIGIN: string | undefined; -// React is the only framework we're using. -const framework = 'react'; - -export function buildMetricDetail({ source, action, version, configuration }: MetricsLogItem, theme: string): string { +export function buildMetricDetail(detail: JSONObject, context: PackageSettings): string { const metricOrigin = typeof AWSUI_METRIC_ORIGIN !== 'undefined' ? AWSUI_METRIC_ORIGIN : 'main'; - const detailObject: MetricDetail = { + const detailObject = { o: metricOrigin, - s: source, - t: theme, - a: action, - f: framework, - v: formatMajorVersionForMetricDetail(version), - c: configuration as MetricDetail['c'], + t: context.theme, + // React is the only framework we're using. + f: 'react', + // Remove spaces from the version string for compactness + v: context.packageVersion.replace(/\s/g, ''), + ...detail, }; return jsonStringify(detailObject); } -export function jsonStringify(detailObject: any) { +export function buildComponentMetricDetail( + { componentName, action, configuration }: ComponentMetricDetail, + context: PackageSettings +): string { + return buildMetricDetail( + { + a: action, + s: componentName, + c: configuration as JSONObject | undefined, + }, + context + ); +} + +function jsonStringify(detailObject: any) { return JSON.stringify(detailObject, detailSerializer); } @@ -34,19 +45,7 @@ function detailSerializer(key: string, value: unknown) { return value; } -export function buildMetricName({ source, version }: MetricsLogItem, theme: string): string { - return ['awsui', source, `${formatVersionForMetricName(theme, version)}`].join('_'); -} - -export function formatMajorVersionForMetricDetail(version: string) { - return version.replace(/\s/g, ''); -} - -export function formatVersionForMetricName(theme: string, version: string) { - return `${theme.charAt(0)}${getMajorVersion(version).replace('.', '')}`; -} - -function getMajorVersion(versionString: string): string { +export function getMajorVersion(versionString: string): string { const majorVersionMatch = versionString.match(/^(\d+\.\d+)/); - return majorVersionMatch ? majorVersionMatch[1] : ''; + return majorVersionMatch ? majorVersionMatch[1].replace('.', '') : ''; } diff --git a/src/internal/base-component/metrics/interfaces.ts b/src/internal/base-component/metrics/interfaces.ts index 13d587a..cd85ca3 100644 --- a/src/internal/base-component/metrics/interfaces.ts +++ b/src/internal/base-component/metrics/interfaces.ts @@ -3,7 +3,7 @@ type JSONValue = string | number | boolean | null | undefined; -interface JSONObject { +export interface JSONObject { [key: string]: JSONObject | JSONValue; } @@ -20,29 +20,28 @@ export interface ComponentConfiguration { export type AnalyticsMetadata = JSONObject; -export interface MetricsLogItem { - source: string; +export interface ComponentMetricDetail { + componentName: string; // Currently logged actions // "loaded" – components package loaded // "used" – individual component used action: 'loaded' | 'used'; - version: string; configuration?: ComponentConfiguration; } -export interface MetricDetail { +export interface ComponentMetricMinified { // origin o: string; // source, e.g. component or package name - s: string; + s?: string; // theme t: string; // action - a: string; + a?: string; // framework f: string; // version and git commit v: string; // component configuration - c: JSONObject | undefined; + c?: JSONObject; } diff --git a/src/internal/base-component/metrics/metrics.ts b/src/internal/base-component/metrics/metrics.ts index e417333..766c6ed 100644 --- a/src/internal/base-component/metrics/metrics.ts +++ b/src/internal/base-component/metrics/metrics.ts @@ -2,45 +2,45 @@ // SPDX-License-Identifier: Apache-2.0 import { CLogClient, PanoramaClient, MetricsV2EventItem } from './log-clients'; -import { buildMetricDetail, buildMetricName, jsonStringify } from './formatters'; -import { ComponentConfiguration, MetricsLogItem } from './interfaces'; +import { buildComponentMetricDetail, buildMetricDetail, getMajorVersion } from './formatters'; +import { ComponentConfiguration, ComponentMetricDetail, PackageSettings } from './interfaces'; const oneTimeMetrics = new Set(); -// In case we need to override the theme for VR. -let theme = ''; -function setTheme(newTheme: string) { - theme = newTheme; -} - export class Metrics { - readonly source: string; - readonly packageVersion: string; - - private clog = new CLogClient(); - private panorama = new PanoramaClient(); - - constructor(source: string, packageVersion: string) { - this.source = source; - this.packageVersion = packageVersion; + private readonly context: PackageSettings; + private readonly clog = new CLogClient(); + private readonly panorama = new PanoramaClient(); + + constructor(packageSource: PackageSettings); + constructor(packageSource: string, packageVersion: string); + constructor(...args: [PackageSettings] | [string, string]) { + if (args.length === 1) { + this.context = args[0]; + } else { + const [packageSource, packageVersion] = args; + this.context = { packageSource, packageVersion, theme: 'unknown' }; + } } - initMetrics(theme: string) { - setTheme(theme); + private sendComponentMetric(metric: ComponentMetricDetail): void { + this.sendMetricOnce( + `awsui_${metric.componentName}_${this.context.theme.charAt(0)}${getMajorVersion(this.context.packageVersion)}`, + 1, + buildComponentMetricDetail(metric, this.context) + ); } - /** - * Calls Console Platform's client logging JS API with provided metric name, value, and detail. - * Does nothing if Console Platform client logging JS is not present in page. + /* + * Calls Console Platform's client logging only the first time the provided metricName is used. + * Subsequent calls with the same metricName are ignored. */ - sendMetric(metricName: string, value: number, detail?: string): void { - if (!theme) { - // Metrics need to be initialized first (initMetrics) - console.error('Metrics need to be initialized first.'); - return; + private sendMetricOnce(metricName: string, value: number, detail?: string): void { + const key = [metricName + value + detail].join('|'); + if (!oneTimeMetrics.has(key)) { + this.clog.sendMetric(metricName, value, detail); + oneTimeMetrics.add(key); } - - this.clog.sendMetric(metricName, value, detail); } /** @@ -51,27 +51,12 @@ export class Metrics { this.panorama.sendMetric(metric); } - sendMetricObject(metric: MetricsLogItem, value: number): void { - this.sendMetric(buildMetricName(metric, theme), value, buildMetricDetail(metric, theme)); + sendOpsMetricObject(metricName: string, detail: Record) { + this.sendMetricOnce(metricName, 1, buildMetricDetail(detail, this.context)); } - sendMetricObjectOnce(metric: MetricsLogItem, value: number): void { - const metricKey = jsonStringify(metric); - if (!oneTimeMetrics.has(metricKey)) { - this.sendMetricObject(metric, value); - oneTimeMetrics.add(metricKey); - } - } - - /* - * Calls Console Platform's client logging only the first time the provided metricName is used. - * Subsequent calls with the same metricName are ignored. - */ - sendMetricOnce(metricName: string, value: number, detail?: string): void { - if (!oneTimeMetrics.has(metricName)) { - this.sendMetric(metricName, value, detail); - oneTimeMetrics.add(metricName); - } + sendOpsMetricValue(metricName: string, value: number) { + this.sendMetricOnce(metricName, value); } /* @@ -80,7 +65,7 @@ export class Metrics { * service once per page view. */ logComponentsLoaded() { - this.sendMetricObjectOnce({ source: this.source, action: 'loaded', version: this.packageVersion }, 1); + this.sendComponentMetric({ componentName: this.context.packageSource, action: 'loaded' }); } /* @@ -89,22 +74,14 @@ export class Metrics { * service once per page view. */ logComponentUsed(componentName: string, configuration: ComponentConfiguration) { - this.sendMetricObjectOnce( - { - source: componentName, - action: 'used', - version: this.packageVersion, - configuration, - }, - 1 - ); + this.sendComponentMetric({ + action: 'used', + componentName, + configuration, + }); } } export function clearOneTimeMetricsCache(): void { oneTimeMetrics.clear(); } - -export class MetricsTestHelper { - resetOneTimeMetricsCache = clearOneTimeMetricsCache; -}