Skip to content

Commit 6799742

Browse files
kinyoklionyusinto
andauthored
fix: Produce a warning when track is called with a non-numeric metric value. (#449)
Co-authored-by: Yusinto Ngadiman <[email protected]>
1 parent 6ff8982 commit 6799742

File tree

5 files changed

+154
-2
lines changed

5 files changed

+154
-2
lines changed

packages/shared/common/src/internal/events/ClientMessages.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,11 @@
44
export default class ClientMessages {
55
static readonly missingContextKeyNoEvent =
66
'Context was unspecified or had no key; event will not be sent';
7+
8+
static invalidMetricValue(badType: string) {
9+
return (
10+
'The track function was called with a non-numeric "metricValue"' +
11+
` (${badType}), only numeric metric values are supported.`
12+
);
13+
}
714
}

packages/shared/sdk-client/src/LDClientImpl.events.test.ts

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { AutoEnvAttributes, clone, LDContext } from '@launchdarkly/js-sdk-common';
2-
import { InputIdentifyEvent } from '@launchdarkly/js-sdk-common/dist/internal';
2+
import { InputCustomEvent, InputIdentifyEvent } from '@launchdarkly/js-sdk-common/dist/internal';
33
import {
44
basicPlatform,
55
hasher,
@@ -31,6 +31,7 @@ jest.mock('@launchdarkly/js-sdk-common', () => {
3131
const testSdkKey = 'test-sdk-key';
3232
let ldc: LDClientImpl;
3333
let defaultPutResponse: Flags;
34+
const carContext: LDContext = { kind: 'car', key: 'test-car' };
3435

3536
describe('sdk-client object', () => {
3637
beforeEach(() => {
@@ -54,7 +55,6 @@ describe('sdk-client object', () => {
5455

5556
test('identify event', async () => {
5657
defaultPutResponse['dev-test-flag'].value = false;
57-
const carContext: LDContext = { kind: 'car', key: 'test-car' };
5858

5959
await ldc.identify(carContext);
6060

@@ -73,4 +73,85 @@ describe('sdk-client object', () => {
7373
}),
7474
);
7575
});
76+
77+
it('produces track events with data', async () => {
78+
await ldc.identify(carContext);
79+
80+
ldc.track('the-event', { the: 'data' }, undefined);
81+
expect(MockEventProcessor).toHaveBeenCalled();
82+
expect(ldc.eventProcessor!.sendEvent).toHaveBeenNthCalledWith(
83+
2,
84+
expect.objectContaining<InputCustomEvent>({
85+
kind: 'custom',
86+
key: 'the-event',
87+
context: expect.objectContaining({
88+
contexts: expect.objectContaining({
89+
car: { key: 'test-car' },
90+
}),
91+
}),
92+
data: { the: 'data' },
93+
samplingRatio: 1,
94+
creationDate: expect.any(Number),
95+
}),
96+
);
97+
expect(logger.warn).not.toHaveBeenCalled();
98+
});
99+
100+
it('produces track events with a metric value', async () => {
101+
await ldc.identify(carContext);
102+
103+
ldc.track('the-event', undefined, 12);
104+
expect(MockEventProcessor).toHaveBeenCalled();
105+
expect(ldc.eventProcessor!.sendEvent).toHaveBeenNthCalledWith(
106+
2,
107+
expect.objectContaining<InputCustomEvent>({
108+
kind: 'custom',
109+
key: 'the-event',
110+
context: expect.objectContaining({
111+
contexts: expect.objectContaining({
112+
car: { key: 'test-car' },
113+
}),
114+
}),
115+
metricValue: 12,
116+
samplingRatio: 1,
117+
creationDate: expect.any(Number),
118+
}),
119+
);
120+
expect(logger.warn).not.toHaveBeenCalled();
121+
});
122+
123+
it('produces track events with a metric value and data', async () => {
124+
await ldc.identify(carContext);
125+
126+
ldc.track('the-event', { the: 'data' }, 12);
127+
expect(MockEventProcessor).toHaveBeenCalled();
128+
expect(ldc.eventProcessor!.sendEvent).toHaveBeenNthCalledWith(
129+
2,
130+
expect.objectContaining<InputCustomEvent>({
131+
kind: 'custom',
132+
key: 'the-event',
133+
context: expect.objectContaining({
134+
contexts: expect.objectContaining({
135+
car: { key: 'test-car' },
136+
}),
137+
}),
138+
metricValue: 12,
139+
data: { the: 'data' },
140+
samplingRatio: 1,
141+
creationDate: expect.any(Number),
142+
}),
143+
);
144+
expect(logger.warn).not.toHaveBeenCalled();
145+
});
146+
147+
it('produces a warning when the metric value is non-numeric', async () => {
148+
// @ts-ignore
149+
await ldc.identify(carContext);
150+
// @ts-ignore
151+
ldc.track('the-event', { the: 'data' }, '12');
152+
153+
expect(logger.warn).toHaveBeenCalledWith(
154+
expect.stringMatching(/was called with a non-numeric/),
155+
);
156+
});
76157
});

packages/shared/sdk-client/src/LDClientImpl.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,11 @@ export default class LDClientImpl implements LDClient {
402402
return;
403403
}
404404

405+
// 0 is valid, so do not truthy check the metric value
406+
if (metricValue !== undefined && !TypeValidators.Number.is(metricValue)) {
407+
this.logger?.warn(ClientMessages.invalidMetricValue(typeof metricValue));
408+
}
409+
405410
this.eventProcessor?.sendEvent(
406411
this.eventFactoryDefault.customEvent(key, checkedContext!, data, metricValue),
407412
);

packages/shared/sdk-server/__tests__/LDClient.events.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as mocks from '@launchdarkly/private-js-mocks';
55

66
import { LDClientImpl } from '../src';
77
import TestData from '../src/integrations/test_data/TestData';
8+
import TestLogger, { LogLevel } from './Logger';
89
import makeCallbacks from './makeCallbacks';
910

1011
const defaultUser = { key: 'user' };
@@ -14,8 +15,10 @@ describe('given a client with mock event processor', () => {
1415
let client: LDClientImpl;
1516
let events: AsyncQueue<internal.InputEvent>;
1617
let td: TestData;
18+
let logger: TestLogger;
1719

1820
beforeEach(async () => {
21+
logger = new TestLogger();
1922
events = new AsyncQueue<internal.InputEvent>();
2023
jest
2124
.spyOn(internal.EventProcessor.prototype, 'sendEvent')
@@ -30,6 +33,7 @@ describe('given a client with mock event processor', () => {
3033
mocks.basicPlatform,
3134
{
3235
updateProcessor: td.getFactory(),
36+
logger,
3337
},
3438
makeCallbacks(false),
3539
);
@@ -300,4 +304,54 @@ describe('given a client with mock event processor', () => {
300304
default: 'c',
301305
});
302306
});
307+
308+
it('produces track events with data', async () => {
309+
client.track('the-event', defaultUser, { the: 'data' }, undefined);
310+
const e = await events.take();
311+
expect(e).toMatchObject({
312+
kind: 'custom',
313+
key: 'the-event',
314+
context: Context.fromLDContext(defaultUser),
315+
data: { the: 'data' },
316+
});
317+
expect(logger.getCount(LogLevel.Warn)).toBe(0);
318+
});
319+
320+
it('produces track events with a metric value', async () => {
321+
client.track('the-event', defaultUser, undefined, 12);
322+
const e = await events.take();
323+
expect(e).toMatchObject({
324+
kind: 'custom',
325+
key: 'the-event',
326+
context: Context.fromLDContext(defaultUser),
327+
metricValue: 12,
328+
});
329+
expect(logger.getCount(LogLevel.Warn)).toBe(0);
330+
});
331+
332+
it('produces track events with a metric value and data', async () => {
333+
client.track('the-event', defaultUser, { the: 'data' }, 12);
334+
const e = await events.take();
335+
expect(e).toMatchObject({
336+
kind: 'custom',
337+
key: 'the-event',
338+
context: Context.fromLDContext(defaultUser),
339+
metricValue: 12,
340+
data: { the: 'data' },
341+
});
342+
expect(logger.getCount(LogLevel.Warn)).toBe(0);
343+
});
344+
345+
it('produces a warning when the metric value is non-numeric', async () => {
346+
// @ts-ignore
347+
client.track('the-event', defaultUser, { the: 'data' }, '12');
348+
await events.take();
349+
expect(logger.getCount(LogLevel.Warn)).toBe(1);
350+
logger.expectMessages([
351+
{
352+
level: LogLevel.Warn,
353+
matches: /was called with a non-numeric/,
354+
},
355+
]);
356+
});
303357
});

packages/shared/sdk-server/src/LDClientImpl.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,11 @@ export default class LDClientImpl implements LDClient {
714714
return;
715715
}
716716

717+
// 0 is valid, so do not truthy check the metric value
718+
if (metricValue !== undefined && !TypeValidators.Number.is(metricValue)) {
719+
this.logger?.warn(ClientMessages.invalidMetricValue(typeof metricValue));
720+
}
721+
717722
this.eventProcessor.sendEvent(
718723
this.eventFactoryDefault.customEvent(key, checkedContext!, data, metricValue),
719724
);

0 commit comments

Comments
 (0)