@servicetitan/journey 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__test-utils__/test-runtime.d.ts.map +1 -0
- package/dist/__test-utils__/test-runtime.js +47 -0
- package/dist/__test-utils__/test-runtime.js.map +1 -0
- package/dist/__tests__/core/ambient.test.d.ts.map +1 -0
- package/dist/__tests__/core/api.test.d.ts +2 -0
- package/dist/__tests__/core/api.test.d.ts.map +1 -0
- package/dist/__tests__/core/define-journey.test.d.ts.map +1 -0
- package/dist/__tests__/core/requests.test.d.ts.map +1 -0
- package/dist/__tests__/core/runtime.test.d.ts.map +1 -0
- package/dist/__tests__/core/step.test.d.ts.map +1 -0
- package/dist/__tests__/core/timeouts.test.d.ts.map +1 -0
- package/dist/__tests__/global.test.d.ts +3 -0
- package/dist/__tests__/global.test.d.ts.map +1 -0
- package/dist/__tests__/integrations/axios.test.d.ts.map +1 -0
- package/dist/__tests__/integrations/context.test.d.ts.map +1 -0
- package/dist/__tests__/integrations/expose-app-journey.test.d.ts.map +1 -0
- package/dist/__tests__/integrations/fetch.test.d.ts.map +1 -0
- package/dist/__tests__/integrations/jquery.test.d.ts.map +1 -0
- package/dist/__tests__/integrations/request-key.test.d.ts.map +1 -0
- package/dist/__tests__/integrations/scope.test.d.ts.map +1 -0
- package/dist/__tests__/sinks/console.test.d.ts +2 -0
- package/dist/__tests__/sinks/console.test.d.ts.map +1 -0
- package/dist/__tests__/sinks/datadog.test.d.ts.map +1 -0
- package/dist/__tests__/sinks/sinks.test.d.ts.map +1 -0
- package/dist/config.d.ts +8 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +19 -13
- package/dist/config.js.map +1 -1
- package/dist/core/{registry.d.ts → api.d.ts} +2 -22
- package/dist/core/api.d.ts.map +1 -0
- package/dist/core/{registry.js → api.js} +13 -40
- package/dist/core/api.js.map +1 -0
- package/dist/core/endpoint-policy.d.ts.map +1 -0
- package/dist/core/endpoint-policy.js.map +1 -0
- package/dist/core/index.d.ts +4 -4
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/runtime.d.ts +35 -7
- package/dist/core/runtime.d.ts.map +1 -1
- package/dist/core/runtime.js +39 -5
- package/dist/core/runtime.js.map +1 -1
- package/dist/core/step-tag.d.ts +6 -0
- package/dist/core/step-tag.d.ts.map +1 -1
- package/dist/core/step-tag.js +7 -0
- package/dist/core/step-tag.js.map +1 -1
- package/dist/core/types.d.ts +3 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/global.d.ts +67 -0
- package/dist/global.d.ts.map +1 -0
- package/dist/global.js +116 -0
- package/dist/global.js.map +1 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -5
- package/dist/index.js.map +1 -1
- package/dist/integrations/fetch.d.ts +3 -2
- package/dist/integrations/fetch.d.ts.map +1 -1
- package/dist/integrations/fetch.js.map +1 -1
- package/dist/integrations/jquery/index.d.ts +2 -2
- package/dist/integrations/jquery/index.d.ts.map +1 -1
- package/dist/integrations/jquery/index.js +3 -2
- package/dist/integrations/jquery/index.js.map +1 -1
- package/dist/integrations/react/scope.d.ts.map +1 -1
- package/dist/integrations/react/scope.js +3 -2
- package/dist/integrations/react/scope.js.map +1 -1
- package/dist/sinks/console.d.ts +11 -0
- package/dist/sinks/console.d.ts.map +1 -0
- package/dist/sinks/console.js +18 -0
- package/dist/sinks/console.js.map +1 -0
- package/dist/sinks/datadog.d.ts +12 -1
- package/dist/sinks/datadog.d.ts.map +1 -1
- package/dist/sinks/datadog.js +28 -7
- package/dist/sinks/datadog.js.map +1 -1
- package/package.json +2 -2
- package/src/{__mocks__ → __test-utils__}/test-runtime.ts +6 -2
- package/src/__tests__/config.test.ts +9 -12
- package/src/__tests__/{ambient.test.ts → core/ambient.test.ts} +8 -7
- package/src/__tests__/{registry.test.ts → core/api.test.ts} +30 -25
- package/src/__tests__/{define-journey.test.ts → core/define-journey.test.ts} +5 -4
- package/src/__tests__/{requests.test.ts → core/requests.test.ts} +12 -8
- package/src/__tests__/{runtime.test.ts → core/runtime.test.ts} +18 -18
- package/src/__tests__/{step.test.ts → core/step.test.ts} +16 -18
- package/src/__tests__/{timeouts.test.ts → core/timeouts.test.ts} +15 -10
- package/src/__tests__/global.test.ts +113 -0
- package/src/__tests__/{axios.test.ts → integrations/axios.test.ts} +46 -15
- package/src/__tests__/{context.test.tsx → integrations/context.test.tsx} +1 -1
- package/src/__tests__/{expose-app-journey.test.ts → integrations/expose-app-journey.test.ts} +2 -2
- package/src/__tests__/{fetch.test.ts → integrations/fetch.test.ts} +6 -6
- package/src/__tests__/{jquery.test.ts → integrations/jquery.test.ts} +49 -12
- package/src/__tests__/{request-key.test.ts → integrations/request-key.test.ts} +1 -1
- package/src/__tests__/{scope.test.tsx → integrations/scope.test.tsx} +4 -4
- package/src/__tests__/sinks/console.test.ts +98 -0
- package/src/__tests__/{datadog.test.ts → sinks/datadog.test.ts} +62 -5
- package/src/__tests__/{sinks.test.ts → sinks/sinks.test.ts} +13 -12
- package/src/config.ts +20 -12
- package/src/core/{registry.ts → api.ts} +12 -65
- package/src/core/index.ts +5 -14
- package/src/core/runtime.ts +56 -7
- package/src/core/step-tag.ts +9 -0
- package/src/core/types.ts +3 -1
- package/src/global.ts +196 -0
- package/src/index.ts +20 -7
- package/src/integrations/fetch.ts +2 -2
- package/src/integrations/jquery/index.ts +4 -9
- package/src/integrations/react/scope.tsx +3 -2
- package/src/sinks/console.ts +20 -0
- package/src/sinks/datadog.ts +31 -6
- package/dist/__mocks__/test-runtime.d.ts.map +0 -1
- package/dist/__tests__/ambient.test.d.ts.map +0 -1
- package/dist/__tests__/axios.test.d.ts.map +0 -1
- package/dist/__tests__/context.test.d.ts.map +0 -1
- package/dist/__tests__/datadog.test.d.ts.map +0 -1
- package/dist/__tests__/define-journey.test.d.ts.map +0 -1
- package/dist/__tests__/expose-app-journey.test.d.ts.map +0 -1
- package/dist/__tests__/fetch.test.d.ts.map +0 -1
- package/dist/__tests__/jquery.test.d.ts.map +0 -1
- package/dist/__tests__/registry.test.d.ts +0 -2
- package/dist/__tests__/registry.test.d.ts.map +0 -1
- package/dist/__tests__/request-key.test.d.ts.map +0 -1
- package/dist/__tests__/requests.test.d.ts.map +0 -1
- package/dist/__tests__/runtime.test.d.ts.map +0 -1
- package/dist/__tests__/scope.test.d.ts.map +0 -1
- package/dist/__tests__/sinks.test.d.ts.map +0 -1
- package/dist/__tests__/step.test.d.ts.map +0 -1
- package/dist/__tests__/timeouts.test.d.ts.map +0 -1
- package/dist/core/registry.d.ts.map +0 -1
- package/dist/core/registry.js.map +0 -1
- package/dist/endpoint-policy.d.ts.map +0 -1
- package/dist/endpoint-policy.js.map +0 -1
- /package/dist/{__mocks__ → __test-utils__}/test-runtime.d.ts +0 -0
- /package/dist/__tests__/{ambient.test.d.ts → core/ambient.test.d.ts} +0 -0
- /package/dist/__tests__/{define-journey.test.d.ts → core/define-journey.test.d.ts} +0 -0
- /package/dist/__tests__/{requests.test.d.ts → core/requests.test.d.ts} +0 -0
- /package/dist/__tests__/{runtime.test.d.ts → core/runtime.test.d.ts} +0 -0
- /package/dist/__tests__/{step.test.d.ts → core/step.test.d.ts} +0 -0
- /package/dist/__tests__/{timeouts.test.d.ts → core/timeouts.test.d.ts} +0 -0
- /package/dist/__tests__/{axios.test.d.ts → integrations/axios.test.d.ts} +0 -0
- /package/dist/__tests__/{context.test.d.ts → integrations/context.test.d.ts} +0 -0
- /package/dist/__tests__/{expose-app-journey.test.d.ts → integrations/expose-app-journey.test.d.ts} +0 -0
- /package/dist/__tests__/{fetch.test.d.ts → integrations/fetch.test.d.ts} +0 -0
- /package/dist/__tests__/{jquery.test.d.ts → integrations/jquery.test.d.ts} +0 -0
- /package/dist/__tests__/{request-key.test.d.ts → integrations/request-key.test.d.ts} +0 -0
- /package/dist/__tests__/{scope.test.d.ts → integrations/scope.test.d.ts} +0 -0
- /package/dist/__tests__/{datadog.test.d.ts → sinks/datadog.test.d.ts} +0 -0
- /package/dist/__tests__/{sinks.test.d.ts → sinks/sinks.test.d.ts} +0 -0
- /package/dist/{endpoint-policy.d.ts → core/endpoint-policy.d.ts} +0 -0
- /package/dist/{endpoint-policy.js → core/endpoint-policy.js} +0 -0
- /package/src/{endpoint-policy.ts → core/endpoint-policy.ts} +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/** @jest-environment jsdom */
|
|
2
|
-
import { useJourneyTestRuntime } from '
|
|
3
|
-
import { completeJourney, JourneyStepTag, journeyStep
|
|
4
|
-
import {
|
|
2
|
+
import { useJourneyTestRuntime } from '../../__test-utils__/test-runtime';
|
|
3
|
+
import { completeJourney, JourneyStepTag, journeyStep } from '../../core';
|
|
4
|
+
import { moduleRuntime } from '../../core/runtime';
|
|
5
|
+
import { instrumentAxios, type AxiosLikeInstance } from '../../integrations/axios';
|
|
5
6
|
|
|
6
7
|
const emitMock = jest.fn();
|
|
7
8
|
|
|
@@ -77,7 +78,7 @@ describe('[journey] instrumentAxios', () => {
|
|
|
77
78
|
const axios = createAxiosMock();
|
|
78
79
|
instrumentAxios(axios);
|
|
79
80
|
|
|
80
|
-
startJourney(def);
|
|
81
|
+
moduleRuntime.startJourney(def);
|
|
81
82
|
await journeyStep(def.name, 'book-job', () => {
|
|
82
83
|
const config = axios.requestHandlers[0]({ url: '/CallScreen/BookJob' });
|
|
83
84
|
expect(config.journeyStep).toBeInstanceOf(JourneyStepTag);
|
|
@@ -93,11 +94,41 @@ describe('[journey] instrumentAxios', () => {
|
|
|
93
94
|
expect(emitMock.mock.calls[0][0].journey.outcome).toBe('good');
|
|
94
95
|
});
|
|
95
96
|
|
|
97
|
+
test('stamped journeyStep is JSON-safe (AxiosError.toJSON includes config)', async () => {
|
|
98
|
+
const axios = createAxiosMock();
|
|
99
|
+
instrumentAxios(axios);
|
|
100
|
+
|
|
101
|
+
moduleRuntime.startJourney(def);
|
|
102
|
+
await journeyStep(def.name, 'book-job', step => {
|
|
103
|
+
// Ambient stamp — AxiosError.toJSON() embeds config by reference.
|
|
104
|
+
const ambient = axios.requestHandlers[0]({ url: '/CallScreen/BookJob' });
|
|
105
|
+
expect(ambient.journeyStep).toBeInstanceOf(JourneyStepTag);
|
|
106
|
+
const ambientSerialized = JSON.stringify({
|
|
107
|
+
message: 'Request failed',
|
|
108
|
+
name: 'AxiosError',
|
|
109
|
+
config: ambient,
|
|
110
|
+
status: 500,
|
|
111
|
+
});
|
|
112
|
+
expect(JSON.parse(ambientSerialized).config.journeyStep).toBe('[JourneyStepTag]');
|
|
113
|
+
|
|
114
|
+
// Explicit step.tag() spread into config — same circular risk.
|
|
115
|
+
const tagged = step.tag() as { journeyStep: unknown };
|
|
116
|
+
const explicit = axios.requestHandlers[0]({
|
|
117
|
+
url: '/CallScreen/BookJob',
|
|
118
|
+
journeyStep: tagged.journeyStep,
|
|
119
|
+
});
|
|
120
|
+
expect(explicit.journeyStep).toBe(tagged.journeyStep);
|
|
121
|
+
expect(explicit.journeyStep).toBeInstanceOf(JourneyStepTag);
|
|
122
|
+
expect(JSON.parse(JSON.stringify(explicit)).journeyStep).toBe('[JourneyStepTag]');
|
|
123
|
+
});
|
|
124
|
+
completeJourney(def.name);
|
|
125
|
+
});
|
|
126
|
+
|
|
96
127
|
test('marks journey bad on 5xx via the rejection interceptor', async () => {
|
|
97
128
|
const axios = createAxiosMock();
|
|
98
129
|
instrumentAxios(axios);
|
|
99
130
|
|
|
100
|
-
startJourney(def);
|
|
131
|
+
moduleRuntime.startJourney(def);
|
|
101
132
|
await expect(
|
|
102
133
|
journeyStep(def.name, 'book-job', async () => {
|
|
103
134
|
const config = axios.requestHandlers[0]({ url: '/CallScreen/BookJob' });
|
|
@@ -118,7 +149,7 @@ describe('[journey] instrumentAxios', () => {
|
|
|
118
149
|
const axios = createAxiosMock();
|
|
119
150
|
instrumentAxios(axios);
|
|
120
151
|
|
|
121
|
-
startJourney(def);
|
|
152
|
+
moduleRuntime.startJourney(def);
|
|
122
153
|
await journeyStep(def.name, 'book-job', async step => {
|
|
123
154
|
const tagged = step.tag() as { journeyStep: unknown };
|
|
124
155
|
const config = axios.requestHandlers[0]({
|
|
@@ -144,8 +175,8 @@ describe('[journey] instrumentAxios', () => {
|
|
|
144
175
|
const axios = createAxiosMock();
|
|
145
176
|
instrumentAxios(axios);
|
|
146
177
|
|
|
147
|
-
startJourney(def);
|
|
148
|
-
startJourney(other);
|
|
178
|
+
moduleRuntime.startJourney(def);
|
|
179
|
+
moduleRuntime.startJourney(other);
|
|
149
180
|
|
|
150
181
|
let releaseA!: () => void;
|
|
151
182
|
const gateA = new Promise<void>(r => {
|
|
@@ -184,8 +215,8 @@ describe('[journey] instrumentAxios', () => {
|
|
|
184
215
|
const axios = createAxiosMock();
|
|
185
216
|
instrumentAxios(axios);
|
|
186
217
|
|
|
187
|
-
startJourney(def);
|
|
188
|
-
startJourney(other);
|
|
218
|
+
moduleRuntime.startJourney(def);
|
|
219
|
+
moduleRuntime.startJourney(other);
|
|
189
220
|
|
|
190
221
|
let releaseA!: () => void;
|
|
191
222
|
const gateA = new Promise<void>(r => {
|
|
@@ -248,7 +279,7 @@ describe('[journey] instrumentAxios', () => {
|
|
|
248
279
|
const axios = createAxiosMock();
|
|
249
280
|
instrumentAxios(axios);
|
|
250
281
|
|
|
251
|
-
startJourney(def);
|
|
282
|
+
moduleRuntime.startJourney(def);
|
|
252
283
|
await journeyStep(def.name, 'book-job', step => {
|
|
253
284
|
const tagged = step.tag() as { journeyStep: unknown };
|
|
254
285
|
const config = {
|
|
@@ -268,7 +299,7 @@ describe('[journey] instrumentAxios', () => {
|
|
|
268
299
|
const axios = createAxiosMock();
|
|
269
300
|
instrumentAxios(axios);
|
|
270
301
|
|
|
271
|
-
startJourney({ ...def, requestTimeoutMs: 50 });
|
|
302
|
+
moduleRuntime.startJourney({ ...def, requestTimeoutMs: 50 });
|
|
272
303
|
await journeyStep(def.name, 'book-job', () => {
|
|
273
304
|
const config = axios.requestHandlers[0]({ url: '/CallScreen/BookJob' });
|
|
274
305
|
(globalThis.performance.now as jest.Mock).mockReturnValue(1_200);
|
|
@@ -284,7 +315,7 @@ describe('[journey] instrumentAxios', () => {
|
|
|
284
315
|
const axios = createAxiosMock();
|
|
285
316
|
instrumentAxios(axios);
|
|
286
317
|
|
|
287
|
-
startJourney(def);
|
|
318
|
+
moduleRuntime.startJourney(def);
|
|
288
319
|
await journeyStep(def.name, 'book-job', async () => {
|
|
289
320
|
const config = axios.requestHandlers[0]({ url: '/CallScreen/BookJob' });
|
|
290
321
|
(globalThis.performance.now as jest.Mock).mockReturnValue(1_020);
|
|
@@ -305,7 +336,7 @@ describe('[journey] instrumentAxios', () => {
|
|
|
305
336
|
const axios = createAxiosMock();
|
|
306
337
|
instrumentAxios(axios);
|
|
307
338
|
|
|
308
|
-
startJourney(def);
|
|
339
|
+
moduleRuntime.startJourney(def);
|
|
309
340
|
await expect(
|
|
310
341
|
journeyStep(def.name, 'book-job', async () => {
|
|
311
342
|
const config = axios.requestHandlers[0]({ url: '/CallScreen/BookJob' });
|
|
@@ -340,7 +371,7 @@ describe('[journey] instrumentAxios', () => {
|
|
|
340
371
|
const axios = createAxiosMock();
|
|
341
372
|
instrumentAxios(axios);
|
|
342
373
|
|
|
343
|
-
startJourney({
|
|
374
|
+
moduleRuntime.startJourney({
|
|
344
375
|
...def,
|
|
345
376
|
endpoints: [{ match: 'api.example.com/invoices', ignore: true }],
|
|
346
377
|
});
|
package/src/__tests__/{expose-app-journey.test.ts → integrations/expose-app-journey.test.ts}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** @jest-environment jsdom */
|
|
2
|
-
import { useJourneyTestRuntime } from '
|
|
3
|
-
import { exposeAppJourney, type WindowWithAppJourney } from '
|
|
2
|
+
import { useJourneyTestRuntime } from '../../__test-utils__/test-runtime';
|
|
3
|
+
import { exposeAppJourney, type WindowWithAppJourney } from '../../integrations/jquery';
|
|
4
4
|
|
|
5
5
|
const emitMock = jest.fn();
|
|
6
6
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/** @jest-environment jsdom */
|
|
2
|
-
import { activeJourneyStep, reportBackendRequest, type JourneyStepRef } from '
|
|
3
|
-
import { instrumentFetch, type InstrumentFetchOptions } from '
|
|
4
|
-
import { requestKey } from '
|
|
2
|
+
import { activeJourneyStep, reportBackendRequest, type JourneyStepRef } from '../../core';
|
|
3
|
+
import { instrumentFetch, type InstrumentFetchOptions } from '../../integrations/fetch';
|
|
4
|
+
import { requestKey } from '../../integrations/request-key';
|
|
5
5
|
|
|
6
|
-
jest.mock('
|
|
7
|
-
...jest.requireActual('
|
|
6
|
+
jest.mock('../../core', () => ({
|
|
7
|
+
...jest.requireActual('../../core'),
|
|
8
8
|
activeJourneyStep: jest.fn(),
|
|
9
9
|
reportBackendRequest: jest.fn(),
|
|
10
10
|
}));
|
|
11
|
-
jest.mock('
|
|
11
|
+
jest.mock('../../integrations/request-key');
|
|
12
12
|
|
|
13
13
|
describe('[journey] instrumentFetch', () => {
|
|
14
14
|
const KEY = 'resolved-endpoint-key';
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/** @jest-environment jsdom */
|
|
2
|
-
import { useJourneyTestRuntime } from '
|
|
3
|
-
import { completeJourney,
|
|
4
|
-
import {
|
|
2
|
+
import { useJourneyTestRuntime } from '../../__test-utils__/test-runtime';
|
|
3
|
+
import { completeJourney, JourneyStepTag, journeyStep } from '../../core';
|
|
4
|
+
import { moduleRuntime } from '../../core/runtime';
|
|
5
|
+
import { instrumentJquery } from '../../integrations/jquery/instrument';
|
|
5
6
|
|
|
6
7
|
const emitMock = jest.fn();
|
|
7
8
|
|
|
@@ -63,7 +64,7 @@ describe('[journey] instrumentJquery', () => {
|
|
|
63
64
|
const $ = createJqueryMock();
|
|
64
65
|
instrumentJquery($);
|
|
65
66
|
|
|
66
|
-
startJourney({
|
|
67
|
+
moduleRuntime.startJourney({
|
|
67
68
|
name: 'jq-idle',
|
|
68
69
|
team: 'test',
|
|
69
70
|
group: 'test',
|
|
@@ -79,11 +80,47 @@ describe('[journey] instrumentJquery', () => {
|
|
|
79
80
|
expect(emitMock.mock.calls[0][0].journey.outcome).toBe('good');
|
|
80
81
|
});
|
|
81
82
|
|
|
83
|
+
test('stamped journeyStep is JSON-safe on jqXHR and ajax settings', async () => {
|
|
84
|
+
const $ = createJqueryMock();
|
|
85
|
+
instrumentJquery($);
|
|
86
|
+
|
|
87
|
+
moduleRuntime.startJourney({
|
|
88
|
+
name: 'jq-json-safe',
|
|
89
|
+
team: 'test',
|
|
90
|
+
group: 'test',
|
|
91
|
+
service: 'test',
|
|
92
|
+
timeoutMs: 0,
|
|
93
|
+
requestTimeoutMs: 5_000,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
await journeyStep('jq-json-safe', 'book-job', step => {
|
|
97
|
+
// Ambient stamp lands on jqXHR — common to serialize for logging.
|
|
98
|
+
const ambientXhr: {
|
|
99
|
+
status: number;
|
|
100
|
+
journeyStep?: unknown;
|
|
101
|
+
journeyStart?: number;
|
|
102
|
+
} = { status: 200 };
|
|
103
|
+
const ambientSettings = { url: '/CallScreen/BookJob' };
|
|
104
|
+
for (const prefilter of $.__prefilters) {
|
|
105
|
+
prefilter(ambientSettings, ambientSettings, ambientXhr);
|
|
106
|
+
}
|
|
107
|
+
expect(ambientXhr.journeyStep).toBeInstanceOf(JourneyStepTag);
|
|
108
|
+
expect(JSON.parse(JSON.stringify(ambientXhr)).journeyStep).toBe('[JourneyStepTag]');
|
|
109
|
+
|
|
110
|
+
// Explicit step.tag() spread into ajax settings — same circular risk.
|
|
111
|
+
const tagged = step.tag() as { journeyStep: unknown };
|
|
112
|
+
const settings = { url: '/CallScreen/BookJob', ...tagged };
|
|
113
|
+
expect(settings.journeyStep).toBeInstanceOf(JourneyStepTag);
|
|
114
|
+
expect(JSON.parse(JSON.stringify(settings)).journeyStep).toBe('[JourneyStepTag]');
|
|
115
|
+
});
|
|
116
|
+
completeJourney('jq-json-safe');
|
|
117
|
+
});
|
|
118
|
+
|
|
82
119
|
test('marks journey bad on 5xx inside a journey step', async () => {
|
|
83
120
|
const $ = createJqueryMock();
|
|
84
121
|
instrumentJquery($);
|
|
85
122
|
|
|
86
|
-
startJourney({
|
|
123
|
+
moduleRuntime.startJourney({
|
|
87
124
|
name: 'jq-5xx',
|
|
88
125
|
team: 'test',
|
|
89
126
|
group: 'test',
|
|
@@ -106,7 +143,7 @@ describe('[journey] instrumentJquery', () => {
|
|
|
106
143
|
const $ = createJqueryMock();
|
|
107
144
|
instrumentJquery($);
|
|
108
145
|
|
|
109
|
-
startJourney({
|
|
146
|
+
moduleRuntime.startJourney({
|
|
110
147
|
name: 'jq-slow',
|
|
111
148
|
team: 'test',
|
|
112
149
|
group: 'test',
|
|
@@ -129,7 +166,7 @@ describe('[journey] instrumentJquery', () => {
|
|
|
129
166
|
const $ = createJqueryMock();
|
|
130
167
|
instrumentJquery($);
|
|
131
168
|
|
|
132
|
-
startJourney({
|
|
169
|
+
moduleRuntime.startJourney({
|
|
133
170
|
name: 'jq-network',
|
|
134
171
|
team: 'test',
|
|
135
172
|
group: 'test',
|
|
@@ -151,7 +188,7 @@ describe('[journey] instrumentJquery', () => {
|
|
|
151
188
|
const $ = createJqueryMock();
|
|
152
189
|
instrumentJquery($);
|
|
153
190
|
|
|
154
|
-
startJourney({
|
|
191
|
+
moduleRuntime.startJourney({
|
|
155
192
|
name: 'jq-4xx',
|
|
156
193
|
team: 'test',
|
|
157
194
|
group: 'test',
|
|
@@ -173,7 +210,7 @@ describe('[journey] instrumentJquery', () => {
|
|
|
173
210
|
const $ = createJqueryMock();
|
|
174
211
|
instrumentJquery($);
|
|
175
212
|
|
|
176
|
-
startJourney({
|
|
213
|
+
moduleRuntime.startJourney({
|
|
177
214
|
name: 'jq-explicit',
|
|
178
215
|
team: 'test',
|
|
179
216
|
group: 'test',
|
|
@@ -212,7 +249,7 @@ describe('[journey] instrumentJquery', () => {
|
|
|
212
249
|
const $ = createJqueryMock();
|
|
213
250
|
instrumentJquery($);
|
|
214
251
|
|
|
215
|
-
startJourney({
|
|
252
|
+
moduleRuntime.startJourney({
|
|
216
253
|
name: 'jq-no-start',
|
|
217
254
|
team: 'test',
|
|
218
255
|
group: 'test',
|
|
@@ -269,7 +306,7 @@ describe('[journey] instrumentJquery', () => {
|
|
|
269
306
|
return target;
|
|
270
307
|
}
|
|
271
308
|
|
|
272
|
-
startJourney({
|
|
309
|
+
moduleRuntime.startJourney({
|
|
273
310
|
name: 'jq-settings-tag',
|
|
274
311
|
team: 'test',
|
|
275
312
|
group: 'test',
|
|
@@ -305,7 +342,7 @@ describe('[journey] instrumentJquery', () => {
|
|
|
305
342
|
const $ = createJqueryMock();
|
|
306
343
|
instrumentJquery($);
|
|
307
344
|
|
|
308
|
-
startJourney({
|
|
345
|
+
moduleRuntime.startJourney({
|
|
309
346
|
name: 'jq-cross',
|
|
310
347
|
team: 'test',
|
|
311
348
|
group: 'test',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** @jest-environment jsdom */
|
|
2
|
-
import { requestKey } from '
|
|
2
|
+
import { requestKey } from '../../integrations/request-key';
|
|
3
3
|
|
|
4
4
|
describe('[journey] requestKey', () => {
|
|
5
5
|
// Do not redefine window.location — jsdom's property is non-configurable.
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import { render, screen } from '@testing-library/react';
|
|
3
3
|
import { useContext } from 'react';
|
|
4
4
|
|
|
5
|
-
import { useJourneyTestRuntime } from '
|
|
6
|
-
import { completeJourney, failJourney } from '
|
|
7
|
-
import { JourneyNameContext } from '
|
|
8
|
-
import { JourneyScope } from '
|
|
5
|
+
import { useJourneyTestRuntime } from '../../__test-utils__/test-runtime';
|
|
6
|
+
import { completeJourney, failJourney } from '../../core';
|
|
7
|
+
import { JourneyNameContext } from '../../integrations/react/context';
|
|
8
|
+
import { JourneyScope } from '../../integrations/react/scope';
|
|
9
9
|
|
|
10
10
|
const emitMock = jest.fn();
|
|
11
11
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/** @jest-environment jsdom */
|
|
2
|
+
import type { JourneyEvent } from '../../core/types';
|
|
3
|
+
import { JOURNEY_DEBUG_STORAGE_KEY, isJourneyDebugEnabled } from '../../global';
|
|
4
|
+
import { sendToConsole } from '../../sinks/console';
|
|
5
|
+
|
|
6
|
+
function sampleEvent(overrides: Partial<JourneyEvent['journey']> = {}): JourneyEvent {
|
|
7
|
+
return {
|
|
8
|
+
journey: {
|
|
9
|
+
name: 'Book Job',
|
|
10
|
+
team: 'jbce',
|
|
11
|
+
group: 'call-booking',
|
|
12
|
+
service: 'call-screen',
|
|
13
|
+
outcome: 'good',
|
|
14
|
+
durationMs: 120,
|
|
15
|
+
steps: [],
|
|
16
|
+
...overrides,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('[journey] sendToConsole / isJourneyDebugEnabled', () => {
|
|
22
|
+
let debugSpy: jest.SpyInstance;
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
localStorage.clear();
|
|
26
|
+
debugSpy = jest.spyOn(console, 'debug').mockImplementation(() => undefined);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
debugSpy.mockRestore();
|
|
31
|
+
localStorage.clear();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('when st:journey:debug is not set', () => {
|
|
35
|
+
test('isJourneyDebugEnabled is false', () => {
|
|
36
|
+
expect(isJourneyDebugEnabled()).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('does not log', () => {
|
|
40
|
+
sendToConsole(sampleEvent());
|
|
41
|
+
|
|
42
|
+
expect(debugSpy).not.toHaveBeenCalled();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('when st:journey:debug is a non-true value', () => {
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
localStorage.setItem(JOURNEY_DEBUG_STORAGE_KEY, '1');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('isJourneyDebugEnabled is false', () => {
|
|
52
|
+
expect(isJourneyDebugEnabled()).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('does not log', () => {
|
|
56
|
+
sendToConsole(sampleEvent());
|
|
57
|
+
|
|
58
|
+
expect(debugSpy).not.toHaveBeenCalled();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('when st:journey:debug is true', () => {
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
localStorage.setItem(JOURNEY_DEBUG_STORAGE_KEY, 'true');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('isJourneyDebugEnabled is true', () => {
|
|
68
|
+
expect(isJourneyDebugEnabled()).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('logs the journey name, outcome, and payload', () => {
|
|
72
|
+
const event = sampleEvent({ outcome: 'bad', reason: 'request-error' });
|
|
73
|
+
|
|
74
|
+
sendToConsole(event);
|
|
75
|
+
|
|
76
|
+
expect(debugSpy).toHaveBeenCalledTimes(1);
|
|
77
|
+
expect(debugSpy).toHaveBeenCalledWith('[journey] Book Job → bad', event.journey);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('when localStorage throws', () => {
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
jest.spyOn(Storage.prototype, 'getItem').mockImplementation(() => {
|
|
84
|
+
throw new Error('denied');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
afterEach(() => {
|
|
89
|
+
jest.restoreAllMocks();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('treats debug as disabled and does not throw', () => {
|
|
93
|
+
expect(isJourneyDebugEnabled()).toBe(false);
|
|
94
|
+
expect(() => sendToConsole(sampleEvent())).not.toThrow();
|
|
95
|
+
expect(debugSpy).not.toHaveBeenCalled();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** @jest-environment jsdom */
|
|
2
2
|
const addAction = jest.fn();
|
|
3
3
|
const importedAddAction = jest.fn();
|
|
4
|
+
const ddRumAddAction = jest.fn();
|
|
4
5
|
|
|
5
6
|
jest.mock('@datadog/browser-rum', () => ({
|
|
6
7
|
datadogRum: {
|
|
@@ -8,10 +9,19 @@ jest.mock('@datadog/browser-rum', () => ({
|
|
|
8
9
|
},
|
|
9
10
|
}));
|
|
10
11
|
|
|
11
|
-
import type { JourneyEvent } from '
|
|
12
|
-
import {
|
|
12
|
+
import type { JourneyEvent } from '../../core/types';
|
|
13
|
+
import { ST_JOURNEY_GLOBAL_KEY, type StJourneyGlobal } from '../../global';
|
|
14
|
+
import { normalizeTagValue, sendToDatadog, setJourneyRum } from '../../sinks/datadog';
|
|
13
15
|
|
|
14
|
-
const
|
|
16
|
+
const DD_RUM_KEY = 'DD_RUM';
|
|
17
|
+
|
|
18
|
+
function clearJourneyRumFallback(): void {
|
|
19
|
+
const st = (globalThis as Record<string, unknown>)[ST_JOURNEY_GLOBAL_KEY] as
|
|
20
|
+
StJourneyGlobal | undefined;
|
|
21
|
+
if (st) {
|
|
22
|
+
delete st.rum;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
15
25
|
|
|
16
26
|
function sampleEvent(overrides: Partial<JourneyEvent['journey']> = {}): JourneyEvent {
|
|
17
27
|
return {
|
|
@@ -29,10 +39,21 @@ function sampleEvent(overrides: Partial<JourneyEvent['journey']> = {}): JourneyE
|
|
|
29
39
|
}
|
|
30
40
|
|
|
31
41
|
describe('[journey] setJourneyRum / sendToDatadog', () => {
|
|
42
|
+
let warnSpy: jest.SpyInstance;
|
|
43
|
+
|
|
32
44
|
beforeEach(() => {
|
|
33
45
|
addAction.mockClear();
|
|
34
46
|
importedAddAction.mockClear();
|
|
35
|
-
|
|
47
|
+
ddRumAddAction.mockClear();
|
|
48
|
+
clearJourneyRumFallback();
|
|
49
|
+
delete (globalThis as Record<string, unknown>)[DD_RUM_KEY];
|
|
50
|
+
warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => undefined);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
afterEach(() => {
|
|
54
|
+
warnSpy.mockRestore();
|
|
55
|
+
clearJourneyRumFallback();
|
|
56
|
+
delete (globalThis as Record<string, unknown>)[DD_RUM_KEY];
|
|
36
57
|
});
|
|
37
58
|
|
|
38
59
|
describe('without a shared RUM instance', () => {
|
|
@@ -47,7 +68,37 @@ describe('[journey] setJourneyRum / sendToDatadog', () => {
|
|
|
47
68
|
});
|
|
48
69
|
});
|
|
49
70
|
|
|
50
|
-
describe('with
|
|
71
|
+
describe('with globalThis.DD_RUM', () => {
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
(globalThis as Record<string, unknown>)[DD_RUM_KEY] = { addAction: ddRumAddAction };
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('prefers DD_RUM over setJourneyRum and the import', () => {
|
|
77
|
+
setJourneyRum({ addAction });
|
|
78
|
+
warnSpy.mockClear();
|
|
79
|
+
sendToDatadog(sampleEvent());
|
|
80
|
+
|
|
81
|
+
expect(ddRumAddAction).toHaveBeenCalledTimes(1);
|
|
82
|
+
expect(addAction).not.toHaveBeenCalled();
|
|
83
|
+
expect(importedAddAction).not.toHaveBeenCalled();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('when setJourneyRum is called while globalThis.DD_RUM is set', () => {
|
|
88
|
+
test('logs a redundancy warning', () => {
|
|
89
|
+
(globalThis as Record<string, unknown>)[DD_RUM_KEY] = { addAction: ddRumAddAction };
|
|
90
|
+
|
|
91
|
+
setJourneyRum({ addAction });
|
|
92
|
+
|
|
93
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
94
|
+
expect.stringContaining(
|
|
95
|
+
'setJourneyRum() is unnecessary when globalThis.DD_RUM is set'
|
|
96
|
+
)
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('with setJourneyRum (no DD_RUM)', () => {
|
|
51
102
|
beforeEach(() => {
|
|
52
103
|
setJourneyRum({ addAction });
|
|
53
104
|
});
|
|
@@ -60,6 +111,7 @@ describe('[journey] setJourneyRum / sendToDatadog', () => {
|
|
|
60
111
|
|
|
61
112
|
expect(addAction).toHaveBeenCalledTimes(1);
|
|
62
113
|
expect(importedAddAction).not.toHaveBeenCalled();
|
|
114
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
63
115
|
});
|
|
64
116
|
|
|
65
117
|
test('normalizes identity fields', () => {
|
|
@@ -127,6 +179,11 @@ describe('[journey] setJourneyRum / sendToDatadog', () => {
|
|
|
127
179
|
expect(addAction.mock.calls[0][1].journey.reason).toBe('step_error');
|
|
128
180
|
});
|
|
129
181
|
|
|
182
|
+
test('normalizeTagValue matches Datadog metric-tag rules', () => {
|
|
183
|
+
expect(normalizeTagValue('Tenant A!')).toBe('tenant_a');
|
|
184
|
+
expect(normalizeTagValue('Foo@@@Bar___')).toBe('foo_bar');
|
|
185
|
+
});
|
|
186
|
+
|
|
130
187
|
test('maps camelCase fields to Datadog snake_case wire keys', () => {
|
|
131
188
|
setJourneyRum({ addAction });
|
|
132
189
|
sendToDatadog(
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/** @jest-environment jsdom */
|
|
2
|
-
jest.mock('
|
|
2
|
+
jest.mock('../../sinks/datadog', () => ({
|
|
3
3
|
sendToDatadog: jest.fn(),
|
|
4
4
|
}));
|
|
5
5
|
|
|
6
|
-
import { resetJourney } from '
|
|
7
|
-
import {
|
|
8
|
-
import type { JourneyEvent } from '
|
|
9
|
-
import {
|
|
6
|
+
import { resetJourney } from '../../core';
|
|
7
|
+
import { moduleRuntime } from '../../core/runtime';
|
|
8
|
+
import type { JourneyEvent } from '../../core/types';
|
|
9
|
+
import { sendToConsole } from '../../sinks/console';
|
|
10
|
+
import { sendToDatadog } from '../../sinks/datadog';
|
|
10
11
|
|
|
11
|
-
describe('[journey]
|
|
12
|
+
describe('[journey] moduleRuntime sinks', () => {
|
|
12
13
|
let payload: JourneyEvent;
|
|
13
14
|
|
|
14
15
|
beforeEach(() => {
|
|
@@ -28,10 +29,10 @@ describe('[journey] emitJourneyEvent', () => {
|
|
|
28
29
|
});
|
|
29
30
|
|
|
30
31
|
afterEach(() => {
|
|
31
|
-
resetJourney({ sinks: [sendToDatadog] });
|
|
32
|
+
resetJourney({ sinks: [sendToDatadog, sendToConsole] });
|
|
32
33
|
});
|
|
33
34
|
|
|
34
|
-
const subject = () =>
|
|
35
|
+
const subject = () => moduleRuntime.emit(payload);
|
|
35
36
|
|
|
36
37
|
test('forwards the payload to each configured sink', () => {
|
|
37
38
|
subject();
|
|
@@ -44,7 +45,7 @@ describe('[journey] emitJourneyEvent', () => {
|
|
|
44
45
|
|
|
45
46
|
beforeEach(() => {
|
|
46
47
|
extra = jest.fn();
|
|
47
|
-
|
|
48
|
+
moduleRuntime.addSink(extra);
|
|
48
49
|
});
|
|
49
50
|
|
|
50
51
|
test('invokes every configured sink', () => {
|
|
@@ -55,7 +56,7 @@ describe('[journey] emitJourneyEvent', () => {
|
|
|
55
56
|
});
|
|
56
57
|
|
|
57
58
|
test('continues to later sinks when one throws', () => {
|
|
58
|
-
|
|
59
|
+
moduleRuntime.setSinks([
|
|
59
60
|
() => {
|
|
60
61
|
throw new Error('boom');
|
|
61
62
|
},
|
|
@@ -74,7 +75,7 @@ describe('[journey] emitJourneyEvent', () => {
|
|
|
74
75
|
|
|
75
76
|
beforeEach(() => {
|
|
76
77
|
only = jest.fn();
|
|
77
|
-
|
|
78
|
+
moduleRuntime.setSinks([only]);
|
|
78
79
|
});
|
|
79
80
|
|
|
80
81
|
test('does not call the previous sinks', () => {
|
|
@@ -87,7 +88,7 @@ describe('[journey] emitJourneyEvent', () => {
|
|
|
87
88
|
|
|
88
89
|
describe('with an empty sink list', () => {
|
|
89
90
|
beforeEach(() => {
|
|
90
|
-
|
|
91
|
+
moduleRuntime.setSinks([]);
|
|
91
92
|
});
|
|
92
93
|
|
|
93
94
|
test('emits nowhere', () => {
|