Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions src/internal/base-component/__tests__/metrics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ describe('Client Metrics support', () => {

const checkMetric = (metricName: string, detailObject: MetricDetail) => {
expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', {
eventName: metricName,
eventType: 'awsui',
eventContext: metricName,
eventDetail: JSON.stringify(detailObject),
eventValue: '1',
timestamp: expect.any(Number),
Expand Down Expand Up @@ -97,7 +98,8 @@ describe('Client Metrics support', () => {

metrics.sendMetric('name', 0, undefined);
expect(window.parent.panorama).toHaveBeenCalledWith('trackCustomEvent', {
eventName: 'name',
eventType: 'awsui',
eventContext: 'name',
eventValue: '0',
timestamp: expect.any(Number),
});
Expand All @@ -110,7 +112,8 @@ describe('Client Metrics support', () => {
test('delegates to window.panorama when defined', () => {
metrics.sendMetric('name', 0, undefined);
expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', {
eventName: 'name',
eventType: 'awsui',
eventContext: 'name',
eventValue: '0',
timestamp: expect.any(Number),
});
Expand All @@ -121,7 +124,8 @@ describe('Client Metrics support', () => {
test(`calls window.panorama when valid metric name used (${metricName})`, () => {
metrics.sendMetric(metricName, 1, 'detail');
expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', {
eventName: metricName,
eventType: 'awsui',
eventContext: metricName,
eventValue: '1',
eventDetail: 'detail',
timestamp: expect.any(Number),
Expand Down Expand Up @@ -163,7 +167,8 @@ describe('Client Metrics support', () => {
const validDetail = 'a'.repeat(4000);
metrics.sendMetric('metricName', 1, validDetail);
expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', {
eventName: 'metricName',
eventType: 'awsui',
eventContext: 'metricName',
eventValue: '1',
eventDetail: validDetail,
timestamp: expect.any(Number),
Expand All @@ -186,7 +191,8 @@ describe('Client Metrics support', () => {
test('logs a metric name only once', () => {
metrics.sendMetricOnce('my-event', 1);
expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', {
eventName: 'my-event',
eventType: 'awsui',
eventContext: 'my-event',
eventValue: '1',
timestamp: expect.any(Number),
});
Expand All @@ -200,12 +206,14 @@ describe('Client Metrics support', () => {
metrics.sendMetricOnce('my-event', 1);
metrics.sendMetricOnce('My-Event', 2);
expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', {
eventName: 'my-event',
eventType: 'awsui',
eventContext: 'my-event',
eventValue: '1',
timestamp: expect.any(Number),
});
expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', {
eventName: 'My-Event',
eventType: 'awsui',
eventContext: 'My-Event',
eventValue: '2',
timestamp: expect.any(Number),
});
Expand All @@ -218,8 +226,9 @@ describe('Client Metrics support', () => {
window.AWSC = undefined;
metrics.sendMetricObject({ source: 'pkg', action: 'used', version: '5.0' }, 1);
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"}',
eventName: 'awsui_pkg_d50',
eventValue: '1',
timestamp: expect.any(Number),
});
Expand Down
48 changes: 14 additions & 34 deletions src/internal/base-component/__tests__/panorama-metrics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,69 +23,47 @@ describe('PanoramaClient', () => {
});

test('sends simple metrics', () => {
panorama.sendMetric({ eventType: 'custom', eventValue: 'value' });
panorama.sendMetric({ eventValue: 'value' });

expect(window.panorama).toHaveBeenCalledWith(
'trackCustomEvent',
expect.objectContaining({ eventType: 'custom', eventValue: 'value' })
expect.objectContaining({ eventType: 'awsui', eventValue: 'value' })
);
});

test('converts objects to strings', () => {
panorama.sendMetric({ eventType: 'custom', eventValue: { test: 'value' }, eventDetail: { test: 'detail' } });
panorama.sendMetric({ eventValue: { test: 'value' }, eventDetail: { test: 'detail' } });

expect(window.panorama).toHaveBeenCalledWith(
'trackCustomEvent',
expect.objectContaining({ eventType: 'custom', eventValue: '{"test":"value"}', eventDetail: '{"test":"detail"}' })
expect.objectContaining({ eventValue: '{"test":"value"}', eventDetail: '{"test":"detail"}' })
Copy link

@dpitcock dpitcock Jan 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this also contain a eventType: 'awsui'?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it uses expect.objectContaining, it passes regardless.

I decided to skip, because testing this field is not a subject of this test. We have many others to do this check

);
});

test('prints an error when event details are too long', () => {
const eventDetail = 'a'.repeat(4001);
panorama.sendMetric({ eventType: 'custom', eventDetail });
panorama.sendMetric({ eventContext: 'custom', eventDetail });

expect(window.panorama).toHaveBeenCalledTimes(1);
expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', {
eventName: 'awsui-metric-error',
eventType: 'awsui',
eventContext: 'awsui-metric-error',
eventDetail: expect.stringMatching(/Event detail for metric is too long:.*/),
timestamp: expect.any(Number),
});
expect(consoleSpy).toHaveBeenCalledWith(`Event detail for metric is too long: ${eventDetail}`);
});

test('prints an error when event type is too long', () => {
const eventType = 'a'.repeat(51);
const errorMessage = `Event type for metric is too long: ${eventType}`;
panorama.sendMetric({ eventType });

expect(window.panorama).toHaveBeenCalledTimes(1);
expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', {
eventName: 'awsui-metric-error',
eventDetail: errorMessage,
});
expect(consoleSpy).toHaveBeenCalledWith(errorMessage);
});

test('prints an error when event name is too long', () => {
const eventName = 'a'.repeat(1001);
const errorMessage = `Event name for metric is too long: ${eventName}`;
panorama.sendMetric({ eventName });

expect(window.panorama).toHaveBeenCalledTimes(1);
expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', {
eventName: 'awsui-metric-error',
eventDetail: errorMessage,
});
expect(consoleSpy).toHaveBeenCalledWith(errorMessage);
});

test('prints an error when event value is too long', () => {
const eventValue = 'a'.repeat(4001);
panorama.sendMetric({ eventValue });

expect(window.panorama).toHaveBeenCalledTimes(1);
expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', {
eventName: 'awsui-metric-error',
eventType: 'awsui',
eventContext: 'awsui-metric-error',
eventDetail: expect.stringMatching(/Event value for metric is too long:.*/),
timestamp: expect.any(Number),
});
expect(consoleSpy).toHaveBeenCalledWith(`Event value for metric is too long: ${eventValue}`);
});
Expand All @@ -96,8 +74,10 @@ describe('PanoramaClient', () => {

expect(window.panorama).toHaveBeenCalledTimes(1);
expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', {
eventName: 'awsui-metric-error',
eventType: 'awsui',
eventContext: 'awsui-metric-error',
eventDetail: expect.stringMatching(/Event context for metric is too long:.*/),
timestamp: expect.any(Number),
});
expect(consoleSpy).toHaveBeenCalledWith(`Event context for metric is too long: ${eventContext}`);
});
Expand Down
56 changes: 30 additions & 26 deletions src/internal/base-component/metrics/log-clients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,23 @@ interface MetricsWindow extends Window {
}

export interface MetricsV2EventItem {
eventName?: string;
eventType?: string;
eventContext?: string;
eventDetail?: string | Record<string, string | number | boolean>;
eventValue?: string | Record<string, string | number | boolean>;
timestamp?: number;
}

type PanoramaFunction = (event: 'trackCustomEvent', data: MetricsV2EventItem) => void;
interface PanoramaMetric {
eventType: string;
eventContext?: string;
eventValue?: string;
eventDetail?: string;
timestamp: number;
}

type PanoramaFunction = (event: 'trackCustomEvent', data: PanoramaMetric) => void;

const AWSUI_EVENT = 'awsui';

function validateLength(value: string | undefined, maxLength: number): boolean {
return !value || value.length <= maxLength;
Expand All @@ -46,7 +54,7 @@ export class CLogClient {
return;
}
const wasSent = new PanoramaClient().sendMetric({
eventName: metricName,
eventContext: metricName,
eventDetail: detail,
eventValue: `${value}`,
timestamp: Date.now(),
Expand Down Expand Up @@ -91,33 +99,27 @@ export class PanoramaClient {
if (!panorama) {
return false;
}
if (typeof metric.eventDetail === 'object') {
metric.eventDetail = JSON.stringify(metric.eventDetail);
}
if (typeof metric.eventValue === 'object') {
metric.eventValue = JSON.stringify(metric.eventValue);
}
if (!validateLength(metric.eventName, 1000)) {
this.onMetricError(`Event name for metric is too long: ${metric.eventName}`);
return true;
}
if (!validateLength(metric.eventDetail, 4000)) {
this.onMetricError(`Event detail for metric is too long: ${metric.eventDetail}`);
return true;
}
if (!validateLength(metric.eventValue, 4000)) {
this.onMetricError(`Event value for metric is too long: ${metric.eventValue}`);
const payload: PanoramaMetric = {
eventType: AWSUI_EVENT,
timestamp: Date.now(),
...metric,
eventDetail: typeof metric.eventDetail === 'object' ? JSON.stringify(metric.eventDetail) : metric.eventDetail,
eventValue: typeof metric.eventValue === 'object' ? JSON.stringify(metric.eventValue) : metric.eventValue,
};

if (!validateLength(payload.eventDetail, 4000)) {
this.onMetricError(`Event detail for metric is too long: ${payload.eventDetail}`);
return true;
}
if (!validateLength(metric.eventContext, 4000)) {
this.onMetricError(`Event context for metric is too long: ${metric.eventContext}`);
if (!validateLength(payload.eventValue, 4000)) {
this.onMetricError(`Event value for metric is too long: ${payload.eventValue}`);
return true;
}
if (!validateLength(metric.eventType, 50)) {
this.onMetricError(`Event type for metric is too long: ${metric.eventType}`);
if (!validateLength(payload.eventContext, 4000)) {
this.onMetricError(`Event context for metric is too long: ${payload.eventContext}`);
return true;
}
panorama('trackCustomEvent', { timestamp: Date.now(), ...metric });
panorama('trackCustomEvent', payload);
return true;
}

Expand All @@ -126,8 +128,10 @@ export class PanoramaClient {
const panorama = this.findPanorama(window);
if (panorama) {
panorama('trackCustomEvent', {
eventName: 'awsui-metric-error',
eventType: AWSUI_EVENT,
eventContext: 'awsui-metric-error',
eventDetail: message.slice(0, 4000),
timestamp: Date.now(),
});
}
}
Expand Down
Loading