Skip to content

Commit 4588954

Browse files
authored
fix(node): Make sure we use same ID for checkIns (#8050)
1 parent 79e8e10 commit 4588954

File tree

5 files changed

+76
-14
lines changed

5 files changed

+76
-14
lines changed

packages/node/src/client.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,25 +153,30 @@ export class NodeClient extends BaseClient<NodeClientOptions> {
153153
* @param checkIn An object that describes a check in.
154154
* @param upsertMonitorConfig An optional object that describes a monitor config. Use this if you want
155155
* to create a monitor automatically when sending a check in.
156+
* @returns A string representing the id of the check in.
156157
*/
157-
public captureCheckIn(checkIn: CheckIn, monitorConfig?: MonitorConfig): void {
158+
public captureCheckIn(checkIn: CheckIn, monitorConfig?: MonitorConfig): string {
159+
const id = checkIn.status !== 'in_progress' && checkIn.checkInId ? checkIn.checkInId : uuid4();
158160
if (!this._isEnabled()) {
159161
__DEBUG_BUILD__ && logger.warn('SDK not enabled, will not capture checkin.');
160-
return;
162+
return id;
161163
}
162164

163165
const options = this.getOptions();
164166
const { release, environment, tunnel } = options;
165167

166168
const serializedCheckIn: SerializedCheckIn = {
167-
check_in_id: uuid4(),
169+
check_in_id: id,
168170
monitor_slug: checkIn.monitorSlug,
169171
status: checkIn.status,
170-
duration: checkIn.duration,
171172
release,
172173
environment,
173174
};
174175

176+
if (checkIn.status !== 'in_progress') {
177+
serializedCheckIn.duration = checkIn.duration;
178+
}
179+
175180
if (monitorConfig) {
176181
serializedCheckIn.monitor_config = {
177182
schedule: monitorConfig.schedule,
@@ -183,6 +188,7 @@ export class NodeClient extends BaseClient<NodeClientOptions> {
183188

184189
const envelope = createCheckInEnvelope(serializedCheckIn, this.getSdkMetadata(), tunnel, this.getDsn());
185190
void this._sendEnvelope(envelope);
191+
return id;
186192
}
187193

188194
/**

packages/node/src/sdk.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
logger,
1414
nodeStackLineParser,
1515
stackParserFromStackParserOptions,
16+
uuid4,
1617
} from '@sentry/utils';
1718

1819
import { setNodeAsyncContextStrategy } from './async';
@@ -273,12 +274,17 @@ export function captureCheckIn(
273274
checkIn: CheckIn,
274275
upsertMonitorConfig?: MonitorConfig,
275276
): ReturnType<NodeClient['captureCheckIn']> {
277+
const capturedCheckIn =
278+
checkIn.status !== 'in_progress' && checkIn.checkInId ? checkIn : { ...checkIn, checkInId: uuid4() };
279+
276280
const client = getCurrentHub().getClient<NodeClient>();
277281
if (client) {
278-
return client.captureCheckIn(checkIn, upsertMonitorConfig);
282+
client.captureCheckIn(capturedCheckIn, upsertMonitorConfig);
283+
} else {
284+
__DEBUG_BUILD__ && logger.warn('Cannot capture check in. No client defined.');
279285
}
280286

281-
__DEBUG_BUILD__ && logger.warn('Cannot capture check in. No client defined.');
287+
return capturedCheckIn.checkInId;
282288
}
283289

284290
/** Node.js stack parser */

packages/node/test/client.test.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -294,8 +294,8 @@ describe('NodeClient', () => {
294294
// @ts-ignore accessing private method
295295
const sendEnvelopeSpy = jest.spyOn(client, '_sendEnvelope');
296296

297-
client.captureCheckIn(
298-
{ monitorSlug: 'foo', status: 'ok', duration: 1222 },
297+
const id = client.captureCheckIn(
298+
{ monitorSlug: 'foo', status: 'in_progress' },
299299
{
300300
schedule: {
301301
type: 'crontab',
@@ -314,10 +314,9 @@ describe('NodeClient', () => {
314314
[
315315
expect.any(Object),
316316
{
317-
check_in_id: expect.any(String),
318-
duration: 1222,
317+
check_in_id: id,
319318
monitor_slug: 'foo',
320-
status: 'ok',
319+
status: 'in_progress',
321320
release: '1.0.0',
322321
environment: 'dev',
323322
monitor_config: {
@@ -333,6 +332,26 @@ describe('NodeClient', () => {
333332
],
334333
],
335334
]);
335+
336+
client.captureCheckIn({ monitorSlug: 'foo', status: 'ok', duration: 1222, checkInId: id });
337+
338+
expect(sendEnvelopeSpy).toHaveBeenCalledTimes(2);
339+
expect(sendEnvelopeSpy).toHaveBeenCalledWith([
340+
expect.any(Object),
341+
[
342+
[
343+
expect.any(Object),
344+
{
345+
check_in_id: id,
346+
monitor_slug: 'foo',
347+
duration: 1222,
348+
status: 'ok',
349+
release: '1.0.0',
350+
environment: 'dev',
351+
},
352+
],
353+
],
354+
]);
336355
});
337356

338357
it('does not send a checkIn envelope if disabled', () => {
@@ -342,7 +361,7 @@ describe('NodeClient', () => {
342361
// @ts-ignore accessing private method
343362
const sendEnvelopeSpy = jest.spyOn(client, '_sendEnvelope');
344363

345-
client.captureCheckIn({ monitorSlug: 'foo', status: 'ok', duration: 1222 });
364+
client.captureCheckIn({ monitorSlug: 'foo', status: 'in_progress' });
346365

347366
expect(sendEnvelopeSpy).toHaveBeenCalledTimes(0);
348367
});

packages/node/test/sdk.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import { getCurrentHub } from '@sentry/core';
12
import type { Integration } from '@sentry/types';
23

4+
import type { NodeClient } from '../build/types';
35
import { init } from '../src/sdk';
46
import * as sdk from '../src/sdk';
57

@@ -90,3 +92,21 @@ describe('init()', () => {
9092
expect(newIntegration.setupOnce as jest.Mock).toHaveBeenCalledTimes(1);
9193
});
9294
});
95+
96+
describe('captureCheckIn', () => {
97+
it('always returns an id', () => {
98+
const hub = getCurrentHub();
99+
const client = hub.getClient<NodeClient>();
100+
expect(client).toBeDefined();
101+
102+
const captureCheckInSpy = jest.spyOn(client!, 'captureCheckIn');
103+
104+
// test if captureCheckIn returns an id even if client is not defined
105+
hub.bindClient(undefined);
106+
107+
expect(captureCheckInSpy).toHaveBeenCalledTimes(0);
108+
expect(sdk.captureCheckIn({ monitorSlug: 'gogogo', status: 'in_progress' })).toBeTruthy();
109+
110+
hub.bindClient(client);
111+
});
112+
});

packages/types/src/checkin.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,26 @@ export interface SerializedCheckIn {
3838
};
3939
}
4040

41-
export interface CheckIn {
41+
interface InProgressCheckIn {
4242
// The distinct slug of the monitor.
4343
monitorSlug: SerializedCheckIn['monitor_slug'];
4444
// The status of the check-in.
45-
status: SerializedCheckIn['status'];
45+
status: 'in_progress';
46+
}
47+
48+
export interface FinishedCheckIn {
49+
// The distinct slug of the monitor.
50+
monitorSlug: SerializedCheckIn['monitor_slug'];
51+
// The status of the check-in.
52+
status: 'ok' | 'error';
53+
// Check-In ID (unique and client generated).
54+
checkInId: SerializedCheckIn['check_in_id'];
4655
// The duration of the check-in in seconds. Will only take effect if the status is ok or error.
4756
duration?: SerializedCheckIn['duration'];
4857
}
4958

59+
export type CheckIn = InProgressCheckIn | FinishedCheckIn;
60+
5061
type SerializedMonitorConfig = NonNullable<SerializedCheckIn['monitor_config']>;
5162

5263
export interface MonitorConfig {

0 commit comments

Comments
 (0)