Skip to content

Commit 257144b

Browse files
authored
chore: Try sending metrics to panorama before clog API (#109)
1 parent 57d049f commit 257144b

File tree

2 files changed

+98
-70
lines changed

2 files changed

+98
-70
lines changed

src/internal/base-component/__tests__/metrics.test.ts

+77-53
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,13 @@ describe('Client Metrics support', () => {
2727
const metrics = new Metrics('dummy-package', '1.0');
2828

2929
const checkMetric = (metricName: string, detailObject: MetricDetail) => {
30-
expect(window.AWSC.Clog.log).toHaveBeenCalledWith(metricName, 1, JSON.stringify(detailObject));
31-
expect(window.AWSC.Clog.log).toHaveBeenCalledTimes(1);
30+
expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', {
31+
eventName: metricName,
32+
eventDetail: JSON.stringify(detailObject),
33+
eventValue: '1',
34+
timestamp: expect.any(Number),
35+
});
36+
expect(window.panorama).toHaveBeenCalledTimes(1);
3237
};
3338

3439
beforeEach(() => {
@@ -49,31 +54,16 @@ describe('Client Metrics support', () => {
4954
});
5055

5156
describe('sendMetric', () => {
52-
test('does nothing when window.AWSC is undefined', () => {
57+
test('does nothing of both AWSC and panorama APIs are not available', () => {
58+
delete window.panorama;
5359
delete window.AWSC;
54-
metrics.sendMetric('name', 0); // only proves no exception thrown
55-
});
56-
57-
test('does nothing when window.AWSC.Clog is undefined', () => {
58-
window.AWSC = {};
59-
metrics.sendMetric('name', 0); // only proves no exception thrown
60+
expect(() => metrics.sendMetric('name', 0)).not.toThrow();
6061
});
6162

62-
test('uses panorama API as fallback when AWSC.Clog.log is unavailable', () => {
63-
delete window.AWSC;
63+
test('uses AWSC.Clog.log API as fallback when panorama is unavailable', () => {
64+
delete window.panorama;
6465
metrics.sendMetric('name', 0);
65-
expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', {
66-
eventName: 'name',
67-
eventValue: '0',
68-
timestamp: expect.any(Number),
69-
});
70-
});
71-
72-
test('does nothing when window.AWSC.Clog.log is undefined', () => {
73-
window.AWSC = {
74-
Clog: undefined,
75-
};
76-
metrics.sendMetric('name', 0); // only proves no exception thrown
66+
expect(window.AWSC.Clog.log).toHaveBeenCalledWith('name', 0, undefined);
7767
});
7868

7969
describe('within an iframe', () => {
@@ -82,51 +72,60 @@ describe('Client Metrics support', () => {
8272

8373
afterEach(() => {
8474
Object.defineProperty(window, 'parent', originalWindowParent);
85-
expect(window.parent.AWSC).toBeUndefined();
75+
expect(window.parent.panorama).toBeUndefined();
8676
});
8777

8878
const setupIframe = () => {
8979
Object.defineProperty(window, 'parent', { configurable: true, writable: true, value: { parent: {} } });
9080
};
9181

92-
test('does nothing when AWSC is not defined in the parent iframe', () => {
93-
delete window.AWSC;
82+
test('does nothing when window.panorama is not defined in the parent iframe', () => {
83+
delete window.panorama;
9484
setupIframe();
95-
expect(window.parent.AWSC).toBeUndefined();
85+
expect(window.parent.panorama).toBeUndefined();
9686

9787
metrics.sendMetric('name', 0); // only proves no exception thrown
9888
});
9989

100-
test('works if parent has AWSC', () => {
90+
test('works if window.parent has panorama object', () => {
10191
setupIframe();
102-
delete window.AWSC;
103-
window.parent.AWSC = {
104-
Clog: {
105-
log: () => {},
106-
},
107-
};
108-
jest.spyOn(window.parent.AWSC.Clog, 'log');
109-
expect(window.AWSC).toBeUndefined();
110-
expect(window.parent.AWSC).toBeDefined();
92+
delete window.panorama;
93+
window.parent.panorama = () => {};
94+
jest.spyOn(window.parent, 'panorama');
95+
expect(window.panorama).toBeUndefined();
96+
expect(window.parent.panorama).toBeDefined();
11197

11298
metrics.sendMetric('name', 0, undefined);
113-
expect(window.parent.AWSC.Clog.log).toHaveBeenCalledWith('name', 0, undefined);
99+
expect(window.parent.panorama).toHaveBeenCalledWith('trackCustomEvent', {
100+
eventName: 'name',
101+
eventValue: '0',
102+
timestamp: expect.any(Number),
103+
});
114104
});
115105
});
116106

117-
describe('when window.AWSC.Clog.log is defined', () => {
107+
describe('when window.panorama is defined', () => {
118108
mockConsoleError();
119109

120-
test('delegates to window.AWSC.Clog.log when defined', () => {
110+
test('delegates to window.panorama when defined', () => {
121111
metrics.sendMetric('name', 0, undefined);
122-
expect(window.AWSC.Clog.log).toHaveBeenCalledWith('name', 0, undefined);
112+
expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', {
113+
eventName: 'name',
114+
eventValue: '0',
115+
timestamp: expect.any(Number),
116+
});
123117
});
124118

125119
describe('Metric name validation', () => {
126120
const tryValidMetric = (metricName: string) => {
127-
test(`calls AWSC.Clog.log when valid metric name used (${metricName})`, () => {
121+
test(`calls window.panorama when valid metric name used (${metricName})`, () => {
128122
metrics.sendMetric(metricName, 1, 'detail');
129-
expect(window.AWSC.Clog.log).toHaveBeenCalledWith(metricName, 1, 'detail');
123+
expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', {
124+
eventName: metricName,
125+
eventValue: '1',
126+
eventDetail: 'detail',
127+
timestamp: expect.any(Number),
128+
});
130129
});
131130
};
132131

@@ -135,6 +134,8 @@ describe('Client Metrics support', () => {
135134
metrics.sendMetric(metricName, 0, 'detail');
136135
expect(console.error).toHaveBeenCalledWith(`Invalid metric name: ${metricName}`);
137136
jest.mocked(console.error).mockReset();
137+
expect(window.panorama).not.toHaveBeenCalled();
138+
expect(window.AWSC.Clog.log).not.toHaveBeenCalled();
138139
});
139140
};
140141

@@ -161,14 +162,21 @@ describe('Client Metrics support', () => {
161162
test('accepts details below the character limit', () => {
162163
const validDetail = 'a'.repeat(4000);
163164
metrics.sendMetric('metricName', 1, validDetail);
164-
expect(window.AWSC.Clog.log).toHaveBeenCalledWith('metricName', 1, validDetail);
165+
expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', {
166+
eventName: 'metricName',
167+
eventValue: '1',
168+
eventDetail: validDetail,
169+
timestamp: expect.any(Number),
170+
});
165171
});
166172

167173
test('throws an error when detail is too long', () => {
168174
const invalidDetail = 'a'.repeat(4001);
169175
metrics.sendMetric('metricName', 0, invalidDetail);
170176
expect(console.error).toHaveBeenCalledWith(`Detail for metric metricName is too long: ${invalidDetail}`);
171177
jest.mocked(console.error).mockReset();
178+
expect(window.panorama).not.toHaveBeenCalled();
179+
expect(window.AWSC.Clog.log).not.toHaveBeenCalled();
172180
});
173181
});
174182
});
@@ -177,15 +185,31 @@ describe('Client Metrics support', () => {
177185
describe('sendMetricOnce', () => {
178186
test('logs a metric name only once', () => {
179187
metrics.sendMetricOnce('my-event', 1);
180-
expect(window.AWSC.Clog.log).toHaveBeenCalledWith('my-event', 1, undefined);
181-
expect(window.AWSC.Clog.log).toHaveBeenCalledTimes(1);
188+
expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', {
189+
eventName: 'my-event',
190+
eventValue: '1',
191+
timestamp: expect.any(Number),
192+
});
193+
expect(window.panorama).toHaveBeenCalledTimes(1);
182194

183195
metrics.sendMetricOnce('my-event', 2);
184-
expect(window.AWSC.Clog.log).toHaveBeenCalledTimes(1);
196+
expect(window.panorama).toHaveBeenCalledTimes(1);
197+
});
185198

186-
metrics.sendMetricOnce('My-Event', 3);
187-
expect(window.AWSC.Clog.log).toHaveBeenCalledWith('My-Event', 3, undefined);
188-
expect(window.AWSC.Clog.log).toHaveBeenCalledTimes(2);
199+
test('does not deduplicate metrics in different casing', () => {
200+
metrics.sendMetricOnce('my-event', 1);
201+
metrics.sendMetricOnce('My-Event', 2);
202+
expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', {
203+
eventName: 'my-event',
204+
eventValue: '1',
205+
timestamp: expect.any(Number),
206+
});
207+
expect(window.panorama).toHaveBeenCalledWith('trackCustomEvent', {
208+
eventName: 'My-Event',
209+
eventValue: '2',
210+
timestamp: expect.any(Number),
211+
});
212+
expect(window.panorama).toHaveBeenCalledTimes(2);
189213
});
190214
});
191215

@@ -265,7 +289,7 @@ describe('Client Metrics support', () => {
265289

266290
metrics.sendMetricObjectOnce(metricObj, 1);
267291
metrics.sendMetricObjectOnce(metricObj, 1);
268-
expect(window.AWSC.Clog.log).toHaveBeenCalledTimes(1);
292+
expect(window.panorama).toHaveBeenCalledTimes(1);
269293
});
270294
test('logs metric for each different version if same source and action', () => {
271295
metrics.sendMetricObjectOnce(
@@ -284,7 +308,7 @@ describe('Client Metrics support', () => {
284308
},
285309
1
286310
);
287-
expect(window.AWSC.Clog.log).toHaveBeenCalledTimes(2);
311+
expect(window.panorama).toHaveBeenCalledTimes(2);
288312
});
289313
test('logs a metric multiple times if same source but different actions', () => {
290314
metrics.sendMetricObjectOnce(
@@ -303,7 +327,7 @@ describe('Client Metrics support', () => {
303327
},
304328
1
305329
);
306-
expect(window.AWSC.Clog.log).toHaveBeenCalledTimes(2);
330+
expect(window.panorama).toHaveBeenCalledTimes(2);
307331
});
308332
});
309333

src/internal/base-component/metrics/log-clients.ts

+21-17
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,18 @@ export class CLogClient {
4343
console.error(`Detail for metric ${metricName} is too long: ${detail}`);
4444
return;
4545
}
46+
const wasSent = new PanoramaClient().sendMetric({
47+
eventName: metricName,
48+
eventDetail: detail,
49+
eventValue: `${value}`,
50+
timestamp: Date.now(),
51+
});
52+
if (wasSent) {
53+
return;
54+
}
4655
const AWSC = this.findAWSC(window);
4756
if (typeof AWSC === 'object' && typeof AWSC.Clog === 'object' && typeof AWSC.Clog.log === 'function') {
4857
AWSC.Clog.log(metricName, value, detail);
49-
} else {
50-
new PanoramaClient().sendMetric({
51-
eventName: metricName,
52-
eventDetail: detail,
53-
eventValue: `${value}`,
54-
timestamp: Date.now(),
55-
});
5658
}
5759
}
5860

@@ -82,7 +84,11 @@ export class PanoramaClient {
8284
/**
8385
* Sends metric but only if Console Platform client v2 logging JS API is present in the page.
8486
*/
85-
sendMetric(metric: MetricsV2EventItem): void {
87+
sendMetric(metric: MetricsV2EventItem): boolean {
88+
const panorama = this.findPanorama(window);
89+
if (typeof panorama !== 'function') {
90+
return false;
91+
}
8692
if (typeof metric.eventDetail === 'object') {
8793
metric.eventDetail = JSON.stringify(metric.eventDetail);
8894
}
@@ -91,28 +97,26 @@ export class PanoramaClient {
9197
}
9298
if (!validateLength(metric.eventName, 1000)) {
9399
console.error(`Event name for metric is too long: ${metric.eventName}`);
94-
return;
100+
return true;
95101
}
96102
if (!validateLength(metric.eventDetail, 4000)) {
97103
console.error(`Event detail for metric is too long: ${metric.eventDetail}`);
98-
return;
104+
return true;
99105
}
100106
if (!validateLength(metric.eventValue, 4000)) {
101107
console.error(`Event value for metric is too long: ${metric.eventValue}`);
102-
return;
108+
return true;
103109
}
104110
if (!validateLength(metric.eventContext, 4000)) {
105111
console.error(`Event context for metric is too long: ${metric.eventContext}`);
106-
return;
112+
return true;
107113
}
108114
if (!validateLength(metric.eventType, 50)) {
109115
console.error(`Event type for metric is too long: ${metric.eventType}`);
110-
return;
111-
}
112-
const panorama = this.findPanorama(window);
113-
if (typeof panorama === 'function') {
114-
panorama('trackCustomEvent', { timestamp: Date.now(), ...metric });
116+
return true;
115117
}
118+
panorama('trackCustomEvent', { timestamp: Date.now(), ...metric });
119+
return true;
116120
}
117121

118122
private findPanorama(currentWindow?: MetricsWindow): any | undefined {

0 commit comments

Comments
 (0)