@outputai/core 0.2.1-next.bd54540.0 → 0.2.1-next.c3a8722.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/package.json +2 -2
- package/src/activity_integration/tracing.d.ts +30 -17
- package/src/activity_integration/tracing.js +33 -17
- package/src/tracing/internal_interface.js +34 -28
- package/src/tracing/processors/local/index.js +8 -7
- package/src/tracing/processors/local/index.spec.js +6 -6
- package/src/tracing/processors/s3/index.js +8 -4
- package/src/tracing/processors/s3/index.spec.js +7 -7
- package/src/tracing/tools/build_trace_tree.js +17 -12
- package/src/tracing/tools/build_trace_tree.spec.js +63 -20
- package/src/tracing/trace_consts.js +9 -0
- package/src/tracing/trace_engine.js +10 -10
- package/src/tracing/trace_engine.spec.js +23 -23
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@outputai/core",
|
|
3
|
-
"version": "0.2.1-next.
|
|
3
|
+
"version": "0.2.1-next.c3a8722.0",
|
|
4
4
|
"description": "The core module of the output framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"output-copy-assets": "./bin/copy_assets.js"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@aws-sdk/client-s3": "3.
|
|
35
|
+
"@aws-sdk/client-s3": "3.1038.0",
|
|
36
36
|
"@babel/generator": "7.29.1",
|
|
37
37
|
"@babel/parser": "7.29.2",
|
|
38
38
|
"@babel/traverse": "7.29.0",
|
|
@@ -1,32 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Creates a new event.
|
|
3
3
|
*
|
|
4
|
-
* @param args
|
|
5
|
-
* @param args.id - A unique id for the Event
|
|
4
|
+
* @param args
|
|
5
|
+
* @param args.id - A unique id for the Event.
|
|
6
6
|
* @param args.kind - The kind of Event, like HTTP, DiskWrite, DBOp, etc.
|
|
7
|
-
* @param args.name - The human
|
|
8
|
-
* @param args.details - Arbitrary
|
|
7
|
+
* @param args.name - The human-friendly name of the Event: query, request, create.
|
|
8
|
+
* @param args.details - Arbitrary data to add to this event, it will be used as the "input" field.
|
|
9
9
|
*/
|
|
10
10
|
export declare function addEventStart( args: { id: string; kind: string; name: string; details: unknown } ): void;
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* Concludes an event.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* @param args -
|
|
18
|
-
* @param args.id - Identifier matching the event's start phase.
|
|
19
|
-
* @param args.details - Arbitrary metadata associated with this phase (e.g., results, response body).
|
|
15
|
+
* @param args
|
|
16
|
+
* @param args.id - The id of the event to conclude.
|
|
17
|
+
* @param args.details - Arbitrary data to add to this event, it will be used as the "output" field.
|
|
20
18
|
*/
|
|
21
19
|
export declare function addEventEnd( args: { id: string; details: unknown } ): void;
|
|
22
20
|
|
|
23
21
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* Use the same id as the start phase to correlate phases.
|
|
22
|
+
* Concludes an event with an error.
|
|
27
23
|
*
|
|
28
|
-
* @param args
|
|
29
|
-
* @param args.id -
|
|
30
|
-
* @param args.details - Arbitrary
|
|
24
|
+
* @param args
|
|
25
|
+
* @param args.id - The id of the event to conclude.
|
|
26
|
+
* @param args.details - Arbitrary data to add to this event, it will be used as the "error" field.
|
|
31
27
|
*/
|
|
32
28
|
export declare function addEventError( args: { id: string; details: unknown } ): void;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Adds an attribute to an event.
|
|
32
|
+
*
|
|
33
|
+
* @param args
|
|
34
|
+
* @param args.eventId - The id of the event to attach the attribute to.
|
|
35
|
+
* @param args.name - The attribute name
|
|
36
|
+
* @param args.value - The attribute value
|
|
37
|
+
*/
|
|
38
|
+
export declare function addEventAttribute( args: { eventId: string; name: string, value: unknown } ): void;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Known attributes.
|
|
42
|
+
*/
|
|
43
|
+
export declare const Attribute: {
|
|
44
|
+
COST: 'cost';
|
|
45
|
+
};
|
|
@@ -1,37 +1,53 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { addEventActionWithContext, EventAction } from '#tracing';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Creates a new event.
|
|
5
5
|
*
|
|
6
6
|
* @param {object} args
|
|
7
|
-
* @param {string} args.id - A unique id for the Event
|
|
7
|
+
* @param {string} args.id - A unique id for the Event.
|
|
8
8
|
* @param {string} args.kind - The kind of Event, like HTTP, DiskWrite, DBOp, etc.
|
|
9
|
-
* @param {string} args.name - The human
|
|
10
|
-
* @param {object} args.details -
|
|
9
|
+
* @param {string} args.name - The human-friendly name of the Event: query, request, create.
|
|
10
|
+
* @param {object} args.details - Arbitrary data to add to this event, it will be used as the "input" field.
|
|
11
11
|
* @returns {void}
|
|
12
12
|
*/
|
|
13
|
-
export const addEventStart = ( { id, kind, name, details } ) =>
|
|
13
|
+
export const addEventStart = ( { id, kind, name, details } ) =>
|
|
14
|
+
addEventActionWithContext( EventAction.START, { kind, name, details, id } );
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* It needs to use the same id of the start phase.
|
|
17
|
+
* Concludes an event.
|
|
19
18
|
*
|
|
20
19
|
* @param {object} args
|
|
21
|
-
* @param {string} args.id -
|
|
22
|
-
* @param {object} args.details -
|
|
20
|
+
* @param {string} args.id - The id of the event to conclude.
|
|
21
|
+
* @param {object} args.details - Arbitrary data to add to this event, it will be used as the "output" field.
|
|
23
22
|
* @returns {void}
|
|
24
23
|
*/
|
|
25
|
-
export const addEventEnd = ( { id, details } ) =>
|
|
24
|
+
export const addEventEnd = ( { id, details } ) => addEventActionWithContext( EventAction.END, { id, details } );
|
|
26
25
|
|
|
27
26
|
/**
|
|
28
|
-
*
|
|
27
|
+
* Concludes an event with an error.
|
|
29
28
|
*
|
|
30
|
-
*
|
|
29
|
+
* @param {object} args
|
|
30
|
+
* @param {string} args.id - The id of the event to conclude.
|
|
31
|
+
* @param {object} args.details - Arbitrary data to add to this event, it will be used as the "error" field.
|
|
32
|
+
* @returns {void}
|
|
33
|
+
*/
|
|
34
|
+
export const addEventError = ( { id, details } ) => addEventActionWithContext( EventAction.ERROR, { id, details } );
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Adds an attribute to an event.
|
|
31
38
|
*
|
|
32
39
|
* @param {object} args
|
|
33
|
-
* @param {string} args.
|
|
34
|
-
* @param {
|
|
40
|
+
* @param {string} args.eventId - The id of the event to attach the attribute to.
|
|
41
|
+
* @param {string} args.name - The attribute name
|
|
42
|
+
* @param {unknown} args.value - The attribute value
|
|
35
43
|
* @returns {void}
|
|
36
44
|
*/
|
|
37
|
-
export const
|
|
45
|
+
export const addEventAttribute = ( { eventId, name, value } ) =>
|
|
46
|
+
addEventActionWithContext( EventAction.ADD_ATTR, { id: eventId, details: { name, value } } );
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Known attributes
|
|
50
|
+
*/
|
|
51
|
+
export const Attribute = {
|
|
52
|
+
COST: 'cost'
|
|
53
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { addEventAction, addEventActionWithContext, init, getDestinations } from './trace_engine.js';
|
|
2
|
+
import { EventAction } from './trace_consts.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Init method, if not called, no processors are attached and trace functions are dummy
|
|
@@ -6,66 +7,71 @@ import { addEventPhase, addEventPhaseWithContext, init, getDestinations } from '
|
|
|
6
7
|
export { init, getDestinations };
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
|
-
* Internal use only - adds
|
|
10
|
+
* Internal use only - adds an action with AsyncLocalStorage context resolution
|
|
10
11
|
*/
|
|
11
|
-
export {
|
|
12
|
+
export { addEventActionWithContext };
|
|
13
|
+
|
|
14
|
+
export { EventAction };
|
|
12
15
|
|
|
13
16
|
/**
|
|
14
17
|
* Trace nomenclature
|
|
15
18
|
*
|
|
16
19
|
* Trace - The collection of Events;
|
|
17
|
-
* Event - Any entry in the Trace file
|
|
18
|
-
*
|
|
20
|
+
* Event - Any entry in the Trace file must have both START and END/ERROR actions, plus any number of ADD_ATTR actions;
|
|
21
|
+
* Action - A specific part of an Event: START, END, ERROR, or ADD_ATTR;
|
|
19
22
|
*/
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
25
|
* Internal use only
|
|
23
26
|
*
|
|
24
|
-
*
|
|
27
|
+
* Creates a new event and appends it to the trace.
|
|
25
28
|
*
|
|
26
29
|
* @param {object} args
|
|
27
|
-
* @param {string} args.id - A unique id for the Event
|
|
30
|
+
* @param {string} args.id - A unique id for the Event.
|
|
28
31
|
* @param {string} args.kind - The kind of Event, like HTTP, DiskWrite, DBOp, etc.
|
|
29
|
-
* @param {string} args.name - The human
|
|
30
|
-
* @param {any} args.details -
|
|
31
|
-
* @param {string} args.parentId - The parent Event, used to build a
|
|
32
|
+
* @param {string} args.name - The human-friendly name of the Event: query, request, create.
|
|
33
|
+
* @param {any} args.details - Arbitrary data to add to this event, it will be used as the "input" field.
|
|
34
|
+
* @param {string} args.parentId - The parent Event, used to build a tree.
|
|
32
35
|
* @param {object} args.executionContext - The original execution context from the workflow
|
|
33
36
|
* @returns {void}
|
|
34
37
|
*/
|
|
35
|
-
export const addEventStart = options =>
|
|
38
|
+
export const addEventStart = options => addEventAction( EventAction.START, options );
|
|
36
39
|
|
|
37
40
|
/**
|
|
38
41
|
* Internal use only
|
|
39
42
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* It needs to use the same id of the start phase.
|
|
43
|
+
* Concludes an event, matching by its id.
|
|
43
44
|
*
|
|
44
45
|
* @param {object} args
|
|
45
|
-
* @param {string} args.id -
|
|
46
|
-
* @param {
|
|
47
|
-
* @param {string} args.name - The human friendly name of the Event: query, request, create.
|
|
48
|
-
* @param {any} args.details - All details attached to this Event Phase. DB queried records, HTTP response body.
|
|
49
|
-
* @param {string} args.parentId - The parent Event, used to build a three.
|
|
46
|
+
* @param {string} args.id - The id of the event to conclude.
|
|
47
|
+
* @param {any} args.details - Arbitrary data to add to the event; it is used as the "output" field.
|
|
50
48
|
* @param {object} args.executionContext - The original execution context from the workflow
|
|
51
49
|
* @returns {void}
|
|
52
50
|
*/
|
|
53
|
-
export const addEventEnd = options =>
|
|
51
|
+
export const addEventEnd = options => addEventAction( EventAction.END, options );
|
|
54
52
|
|
|
55
53
|
/**
|
|
56
54
|
* Internal use only
|
|
57
55
|
*
|
|
58
|
-
*
|
|
56
|
+
* Concludes an event with an error, matching by its id.
|
|
57
|
+
*
|
|
58
|
+
* @param {object} args
|
|
59
|
+
* @param {string} args.id - The id of the event to conclude.
|
|
60
|
+
* @param {any} args.details - Arbitrary data to add to the event; it is used as the "error" field.
|
|
61
|
+
* @param {object} args.executionContext - The original execution context from the workflow
|
|
62
|
+
* @returns {void}
|
|
63
|
+
*/
|
|
64
|
+
export const addEventError = options => addEventAction( EventAction.ERROR, options );
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Internal use only
|
|
59
68
|
*
|
|
60
|
-
*
|
|
69
|
+
* Adds an attribute to an event using its id.
|
|
61
70
|
*
|
|
62
71
|
* @param {object} args
|
|
63
|
-
* @param {string} args.id -
|
|
64
|
-
* @param {
|
|
65
|
-
* @param {string} args.name - The human friendly name of the Event: query, request, create.
|
|
66
|
-
* @param {any} args.details - All details attached to this Event Phase. DB queried records, HTTP response body.
|
|
67
|
-
* @param {string} args.parentId - The parent Event, used to build a three.
|
|
72
|
+
* @param {string} args.id - The id of the event to attach the attribute to.
|
|
73
|
+
* @param {object} args.details - The attribute to add to this event, must be in `{ name: string, value: any }` format.
|
|
68
74
|
* @param {object} args.executionContext - The original execution context from the workflow
|
|
69
75
|
* @returns {void}
|
|
70
76
|
*/
|
|
71
|
-
export const
|
|
77
|
+
export const addEventAttribute = options => addEventAction( EventAction.ADD_ATTR, options );
|
|
@@ -23,7 +23,7 @@ const tempTraceFilesDir = join( __dirname, 'temp', 'traces' );
|
|
|
23
23
|
const createTempFilePath = ( { workflowId, startTime } ) => join( tempTraceFilesDir, `${startTime}_${workflowId}.trace` );
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* Adds
|
|
26
|
+
* Adds a trace entry to the accumulation file.
|
|
27
27
|
* @param {object} entry - The trace entry
|
|
28
28
|
* @param {string} path - Accumulation file path
|
|
29
29
|
*/
|
|
@@ -100,12 +100,13 @@ export const init = () => {
|
|
|
100
100
|
/**
|
|
101
101
|
* Execute this processor:
|
|
102
102
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
103
|
+
* Appends each trace entry to a temp file.
|
|
104
|
+
*
|
|
105
|
+
* When the root workflow ends or the entry is an error action, build the trace tree and write the JSON file.
|
|
105
106
|
*
|
|
106
107
|
* @param {object} args
|
|
107
|
-
* @param {object} entry -
|
|
108
|
-
* @param {object} executionContext - Execution info: workflowId, workflowName, startTime
|
|
108
|
+
* @param {object} args.entry - The trace entry to append.
|
|
109
|
+
* @param {object} args.executionContext - Execution info: workflowId, workflowName, startTime
|
|
109
110
|
* @returns {void}
|
|
110
111
|
*/
|
|
111
112
|
export const exec = ( { entry, executionContext } ) => {
|
|
@@ -113,8 +114,8 @@ export const exec = ( { entry, executionContext } ) => {
|
|
|
113
114
|
const tempFilePath = createTempFilePath( executionContext );
|
|
114
115
|
addEntry( entry, tempFilePath );
|
|
115
116
|
|
|
116
|
-
const isRootWorkflowEnd = entry.id === workflowId && entry.
|
|
117
|
-
const isError = entry.
|
|
117
|
+
const isRootWorkflowEnd = entry.id === workflowId && entry.action !== 'start';
|
|
118
|
+
const isError = entry.action === 'error';
|
|
118
119
|
|
|
119
120
|
if ( !isRootWorkflowEnd && !isError ) {
|
|
120
121
|
return;
|
|
@@ -25,10 +25,10 @@ vi.mock( 'node:fs', () => ( {
|
|
|
25
25
|
const buildTraceTreeMock = vi.fn( entries => ( { count: entries.length } ) );
|
|
26
26
|
vi.mock( '../../tools/build_trace_tree.js', () => ( { default: buildTraceTreeMock } ) );
|
|
27
27
|
|
|
28
|
-
/** Flush happens when root id matches workflowId and
|
|
29
|
-
const rootStart = ( workflowId, ts ) => ( { id: workflowId,
|
|
30
|
-
const rootEnd = ( workflowId, ts ) => ( { id: workflowId,
|
|
31
|
-
const childTick = ( id, ts ) => ( { id,
|
|
28
|
+
/** Flush happens when the root id matches workflowId and action is not 'start', or when action is 'error'. */
|
|
29
|
+
const rootStart = ( workflowId, ts ) => ( { id: workflowId, action: 'start', timestamp: ts } );
|
|
30
|
+
const rootEnd = ( workflowId, ts ) => ( { id: workflowId, action: 'end', timestamp: ts } );
|
|
31
|
+
const childTick = ( id, ts ) => ( { id, action: 'tick', timestamp: ts } );
|
|
32
32
|
|
|
33
33
|
describe( 'tracing/processors/local', () => {
|
|
34
34
|
beforeEach( () => {
|
|
@@ -86,7 +86,7 @@ describe( 'tracing/processors/local', () => {
|
|
|
86
86
|
expect( writeFileSyncMock ).not.toHaveBeenCalled();
|
|
87
87
|
} );
|
|
88
88
|
|
|
89
|
-
it( 'exec(): flushes on error
|
|
89
|
+
it( 'exec(): flushes on error action before root end', async () => {
|
|
90
90
|
const { exec, init } = await import( './index.js' );
|
|
91
91
|
init();
|
|
92
92
|
|
|
@@ -95,7 +95,7 @@ describe( 'tracing/processors/local', () => {
|
|
|
95
95
|
const ctx = { executionContext: { workflowId, workflowName: 'WF', startTime } };
|
|
96
96
|
|
|
97
97
|
exec( { ...ctx, entry: rootStart( workflowId, startTime ) } );
|
|
98
|
-
exec( { ...ctx, entry: { id: 'step-1',
|
|
98
|
+
exec( { ...ctx, entry: { id: 'step-1', action: 'error', timestamp: startTime + 1 } } );
|
|
99
99
|
|
|
100
100
|
expect( buildTraceTreeMock ).toHaveBeenCalledTimes( 1 );
|
|
101
101
|
expect( buildTraceTreeMock.mock.calls[0][0] ).toHaveLength( 2 );
|
|
@@ -67,11 +67,15 @@ export const init = async () => {
|
|
|
67
67
|
};
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
|
-
* Execute this processor:
|
|
70
|
+
* Execute this processor:
|
|
71
|
+
*
|
|
72
|
+
* Appends each trace entry to Redis.
|
|
73
|
+
*
|
|
74
|
+
* When the root workflow ends or the entry is an error action, it builds the trace tree and uploads it to S3.
|
|
71
75
|
*
|
|
72
76
|
* @param {object} args
|
|
73
|
-
* @param {object} entry -
|
|
74
|
-
* @param {object} executionContext - Execution info: workflowId, workflowName, startTime
|
|
77
|
+
* @param {object} args.entry - The trace entry to append
|
|
78
|
+
* @param {object} args.executionContext - Execution info: workflowId, workflowName, startTime
|
|
75
79
|
*/
|
|
76
80
|
export const exec = async ( { entry, executionContext } ) => {
|
|
77
81
|
const { workflowName, workflowId, startTime } = executionContext;
|
|
@@ -79,7 +83,7 @@ export const exec = async ( { entry, executionContext } ) => {
|
|
|
79
83
|
|
|
80
84
|
await addEntry( entry, cacheKey );
|
|
81
85
|
|
|
82
|
-
const isRootWorkflowEnd = entry.id === workflowId && entry.
|
|
86
|
+
const isRootWorkflowEnd = entry.id === workflowId && entry.action !== 'start';
|
|
83
87
|
if ( !isRootWorkflowEnd ) {
|
|
84
88
|
return;
|
|
85
89
|
}
|
|
@@ -52,9 +52,9 @@ describe( 'tracing/processors/s3', () => {
|
|
|
52
52
|
|
|
53
53
|
redisMulti.exec.mockResolvedValue( [] );
|
|
54
54
|
|
|
55
|
-
const workflowStart = { id: 'id1', name: 'WF', kind: 'workflow',
|
|
56
|
-
const activityStart = { id: 'act-1', name: 'DoSomething', kind: 'step', parentId: 'id1',
|
|
57
|
-
const workflowEnd = { id: 'id1',
|
|
55
|
+
const workflowStart = { id: 'id1', name: 'WF', kind: 'workflow', action: 'start', details: {}, timestamp: startTime };
|
|
56
|
+
const activityStart = { id: 'act-1', name: 'DoSomething', kind: 'step', parentId: 'id1', action: 'start', details: {}, timestamp: startTime + 1 };
|
|
57
|
+
const workflowEnd = { id: 'id1', action: 'end', details: { ok: true }, timestamp: startTime + 2 };
|
|
58
58
|
zRangeMock.mockResolvedValue( [
|
|
59
59
|
JSON.stringify( workflowStart ),
|
|
60
60
|
JSON.stringify( activityStart ),
|
|
@@ -97,7 +97,7 @@ describe( 'tracing/processors/s3', () => {
|
|
|
97
97
|
|
|
98
98
|
redisMulti.exec.mockResolvedValue( [] );
|
|
99
99
|
const workflowStart = {
|
|
100
|
-
kind: 'workflow', id: 'id1', name: 'WF', parentId: undefined,
|
|
100
|
+
kind: 'workflow', id: 'id1', name: 'WF', parentId: undefined, action: 'start', details: {}, timestamp: startTime
|
|
101
101
|
};
|
|
102
102
|
zRangeMock.mockResolvedValue( [ JSON.stringify( workflowStart ) ] );
|
|
103
103
|
|
|
@@ -113,8 +113,8 @@ describe( 'tracing/processors/s3', () => {
|
|
|
113
113
|
const ctx = { executionContext: { workflowId: 'id1', workflowName: 'WF', startTime } };
|
|
114
114
|
|
|
115
115
|
redisMulti.exec.mockResolvedValue( [] );
|
|
116
|
-
const workflowStart = { id: 'id1', name: 'WF', kind: 'workflow',
|
|
117
|
-
const stepEndNoParent = { id: 'step-1',
|
|
116
|
+
const workflowStart = { id: 'id1', name: 'WF', kind: 'workflow', action: 'start', details: {}, timestamp: startTime };
|
|
117
|
+
const stepEndNoParent = { id: 'step-1', action: 'end', details: { done: true }, timestamp: startTime + 1 };
|
|
118
118
|
zRangeMock.mockResolvedValue( [
|
|
119
119
|
JSON.stringify( workflowStart ),
|
|
120
120
|
JSON.stringify( stepEndNoParent )
|
|
@@ -136,7 +136,7 @@ describe( 'tracing/processors/s3', () => {
|
|
|
136
136
|
|
|
137
137
|
redisMulti.exec.mockResolvedValue( [] );
|
|
138
138
|
const workflowEnd = {
|
|
139
|
-
kind: 'workflow', id: 'id1', name: 'WF', parentId: undefined,
|
|
139
|
+
kind: 'workflow', id: 'id1', name: 'WF', parentId: undefined, action: 'end', details: {}, timestamp: startTime
|
|
140
140
|
};
|
|
141
141
|
zRangeMock.mockResolvedValue( [ JSON.stringify( workflowEnd ) ] );
|
|
142
142
|
buildTraceTreeMock.mockReturnValueOnce( null );
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EventAction } from '../trace_consts.js';
|
|
1
2
|
/**
|
|
2
3
|
* @typedef {object} NodeEntry
|
|
3
4
|
* @property {string} id
|
|
@@ -27,19 +28,21 @@ const createEntry = id => ( {
|
|
|
27
28
|
input: undefined,
|
|
28
29
|
output: undefined,
|
|
29
30
|
error: undefined,
|
|
30
|
-
children: []
|
|
31
|
+
children: [],
|
|
32
|
+
attributes: {}
|
|
31
33
|
} );
|
|
32
34
|
|
|
33
35
|
/**
|
|
34
|
-
*
|
|
36
|
+
* Builds a tree of nodes from a list of entries.
|
|
35
37
|
*
|
|
36
38
|
* Each node will have: id, name, kind, children, input, output or error, startedAt, endedAt.
|
|
37
39
|
*
|
|
38
|
-
* Entries with same id
|
|
39
|
-
* - The details of the
|
|
40
|
-
* - The details of the
|
|
41
|
-
* - The details of the
|
|
42
|
-
* -
|
|
40
|
+
* Entries with the same id are combined according to their actions.
|
|
41
|
+
* - The details of the START action become input, and timestamp becomes startedAt;
|
|
42
|
+
* - The details of the END action become output, timestamp becomes endedAt;
|
|
43
|
+
* - The details of the ERROR action become error, timestamp becomes endedAt;
|
|
44
|
+
* - The details of the ADD_ATTR action are attached to `.attributes`;
|
|
45
|
+
* - Only the START action's `kind` and `name` fields are used;
|
|
43
46
|
*
|
|
44
47
|
*
|
|
45
48
|
* Children are added according to the parentId of each entry.
|
|
@@ -53,18 +56,20 @@ export default entries => {
|
|
|
53
56
|
const ensureNode = id => nodes.get( id ) ?? nodes.set( id, createEntry( id ) ).get( id );
|
|
54
57
|
|
|
55
58
|
for ( const entry of entries ) {
|
|
56
|
-
const { kind, id, name, parentId, details,
|
|
59
|
+
const { kind, id, name, parentId, details, action, timestamp } = entry;
|
|
57
60
|
const node = ensureNode( id );
|
|
58
61
|
|
|
59
|
-
if (
|
|
62
|
+
if ( action === EventAction.START ) {
|
|
60
63
|
Object.assign( node, { input: details, startedAt: timestamp, kind, name } );
|
|
61
|
-
} else if (
|
|
64
|
+
} else if ( action === EventAction.ADD_ATTR ) {
|
|
65
|
+
node.attributes[details.name] = details.value;
|
|
66
|
+
} else if ( action === EventAction.END ) {
|
|
62
67
|
Object.assign( node, { output: details, endedAt: timestamp } );
|
|
63
|
-
} else if (
|
|
68
|
+
} else if ( action === EventAction.ERROR ) {
|
|
64
69
|
Object.assign( node, { error: details, endedAt: timestamp } );
|
|
65
70
|
}
|
|
66
71
|
|
|
67
|
-
if ( parentId &&
|
|
72
|
+
if ( parentId && action === EventAction.START ) {
|
|
68
73
|
const parent = ensureNode( parentId );
|
|
69
74
|
parent.children.push( node );
|
|
70
75
|
parent.children.sort( ( a, b ) => a.startedAt - b.startedAt );
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { EventAction } from '../trace_consts.js';
|
|
2
3
|
import buildTraceTree from './build_trace_tree.js';
|
|
3
4
|
|
|
4
5
|
describe( 'build_trace_tree', () => {
|
|
@@ -6,9 +7,9 @@ describe( 'build_trace_tree', () => {
|
|
|
6
7
|
expect( buildTraceTree( [] ) ).toBeNull();
|
|
7
8
|
} );
|
|
8
9
|
|
|
9
|
-
it( 'sets root output with a fixed message when workflow has no end/error
|
|
10
|
+
it( 'sets root output with a fixed message when workflow has no end/error action yet', () => {
|
|
10
11
|
const entries = [
|
|
11
|
-
{ kind: 'workflow', id: 'wf', parentId: undefined,
|
|
12
|
+
{ kind: 'workflow', id: 'wf', parentId: undefined, action: EventAction.START, name: 'wf', details: {}, timestamp: 1000 }
|
|
12
13
|
];
|
|
13
14
|
const result = buildTraceTree( entries );
|
|
14
15
|
expect( result ).not.toBeNull();
|
|
@@ -19,17 +20,52 @@ this can indicate it timed out or was interrupted.>>' );
|
|
|
19
20
|
|
|
20
21
|
it( 'returns null when there is no root (all entries have parentId)', () => {
|
|
21
22
|
const entries = [
|
|
22
|
-
{ id: 'a', parentId: 'x',
|
|
23
|
-
{ id: 'b', parentId: 'a',
|
|
23
|
+
{ id: 'a', parentId: 'x', action: EventAction.START, name: 'a', timestamp: 1 },
|
|
24
|
+
{ id: 'b', parentId: 'a', action: EventAction.START, name: 'b', timestamp: 2 }
|
|
24
25
|
];
|
|
25
26
|
expect( buildTraceTree( entries ) ).toBeNull();
|
|
26
27
|
} );
|
|
27
28
|
|
|
28
|
-
it( '
|
|
29
|
+
it( 'add_attr action merges details.name and details.value into node.attributes', () => {
|
|
29
30
|
const entries = [
|
|
30
|
-
{ kind: '
|
|
31
|
-
{ kind: 'step', id: 's', parentId: '
|
|
32
|
-
{ id: 's',
|
|
31
|
+
{ kind: 'workflow', id: 'wf', parentId: undefined, action: EventAction.START, name: 'wf', details: {}, timestamp: 100 },
|
|
32
|
+
{ kind: 'step', id: 's', parentId: 'wf', action: EventAction.START, name: 'step', details: {}, timestamp: 200 },
|
|
33
|
+
{ id: 's', action: EventAction.ADD_ATTR, details: { name: 'latency_ms', value: 42 }, timestamp: 250 },
|
|
34
|
+
{ id: 's', action: EventAction.ADD_ATTR, details: { name: 'retries', value: 1 }, timestamp: 260 },
|
|
35
|
+
{ id: 'wf', action: EventAction.END, details: {}, timestamp: 300 }
|
|
36
|
+
];
|
|
37
|
+
const result = buildTraceTree( entries );
|
|
38
|
+
expect( result ).not.toBeNull();
|
|
39
|
+
expect( result.children[0].attributes ).toEqual( { latency_ms: 42, retries: 1 } );
|
|
40
|
+
} );
|
|
41
|
+
|
|
42
|
+
it( 'add_attr action overwrites prior value for the same attribute name', () => {
|
|
43
|
+
const entries = [
|
|
44
|
+
{ kind: 'workflow', id: 'wf', parentId: undefined, action: EventAction.START, name: 'wf', details: {}, timestamp: 1 },
|
|
45
|
+
{ id: 'wf', action: EventAction.ADD_ATTR, details: { name: 'x', value: 1 }, timestamp: 2 },
|
|
46
|
+
{ id: 'wf', action: EventAction.ADD_ATTR, details: { name: 'x', value: 2 }, timestamp: 3 },
|
|
47
|
+
{ id: 'wf', action: EventAction.END, details: {}, timestamp: 4 }
|
|
48
|
+
];
|
|
49
|
+
const result = buildTraceTree( entries );
|
|
50
|
+
expect( result.attributes ).toEqual( { x: 2 } );
|
|
51
|
+
} );
|
|
52
|
+
|
|
53
|
+
it( 'add_attr does not attach nodes as children (only start does)', () => {
|
|
54
|
+
const entries = [
|
|
55
|
+
{ kind: 'workflow', id: 'wf', parentId: undefined, action: EventAction.START, name: 'wf', details: {}, timestamp: 1 },
|
|
56
|
+
{ id: 'orphan', parentId: 'wf', action: EventAction.ADD_ATTR, details: { name: 'k', value: 'v' }, timestamp: 2 },
|
|
57
|
+
{ id: 'wf', action: EventAction.END, details: {}, timestamp: 3 }
|
|
58
|
+
];
|
|
59
|
+
const result = buildTraceTree( entries );
|
|
60
|
+
expect( result.children ).toHaveLength( 0 );
|
|
61
|
+
expect( result.attributes ).toEqual( {} );
|
|
62
|
+
} );
|
|
63
|
+
|
|
64
|
+
it( 'error action sets error and endedAt on node', () => {
|
|
65
|
+
const entries = [
|
|
66
|
+
{ kind: 'wf', id: 'r', parentId: undefined, action: EventAction.START, name: 'root', details: {}, timestamp: 100 },
|
|
67
|
+
{ kind: 'step', id: 's', parentId: 'r', action: EventAction.START, name: 'step', details: {}, timestamp: 200 },
|
|
68
|
+
{ id: 's', action: EventAction.ERROR, details: { message: 'failed' }, timestamp: 300 }
|
|
33
69
|
];
|
|
34
70
|
const result = buildTraceTree( entries );
|
|
35
71
|
expect( result ).not.toBeNull();
|
|
@@ -41,27 +77,28 @@ this can indicate it timed out or was interrupted.>>' );
|
|
|
41
77
|
it( 'builds a tree from workflow/step/IO entries with grouping and sorting', () => {
|
|
42
78
|
const entries = [
|
|
43
79
|
// workflow start
|
|
44
|
-
{ kind: 'workflow',
|
|
80
|
+
{ kind: 'workflow', action: EventAction.START, name: 'wf', id: 'wf', parentId: undefined, details: { a: 1 }, timestamp: 1000 },
|
|
45
81
|
// evaluator start/stop
|
|
46
|
-
{ kind: 'evaluator',
|
|
47
|
-
{ id: 'eval',
|
|
82
|
+
{ kind: 'evaluator', action: EventAction.START, name: 'eval', id: 'eval', parentId: 'wf', details: { z: 0 }, timestamp: 1500 },
|
|
83
|
+
{ id: 'eval', action: EventAction.END, details: { z: 1 }, timestamp: 1600 },
|
|
48
84
|
// step1 start
|
|
49
|
-
{ kind: 'step',
|
|
85
|
+
{ kind: 'step', action: EventAction.START, name: 'step-1', id: 's1', parentId: 'wf', details: { x: 1 }, timestamp: 2000 },
|
|
86
|
+
{ id: 's1', action: EventAction.ADD_ATTR, details: { name: 'step_tag', value: 'alpha' }, timestamp: 2050 },
|
|
50
87
|
// IO under step1
|
|
51
|
-
{ kind: 'IO',
|
|
88
|
+
{ kind: 'IO', action: EventAction.START, name: 'test-1', id: 'io1', parentId: 's1', details: { y: 2 }, timestamp: 2300 },
|
|
52
89
|
// step2 start
|
|
53
|
-
{ kind: 'step',
|
|
90
|
+
{ kind: 'step', action: EventAction.START, name: 'step-2', id: 's2', parentId: 'wf', details: { x: 2 }, timestamp: 2400 },
|
|
54
91
|
// IO under step2
|
|
55
|
-
{ kind: 'IO',
|
|
56
|
-
{ id: 'io2',
|
|
92
|
+
{ kind: 'IO', action: EventAction.START, name: 'test-2', id: 'io2', parentId: 's2', details: { y: 3 }, timestamp: 2500 },
|
|
93
|
+
{ id: 'io2', action: EventAction.END, details: { y: 4 }, timestamp: 2600 },
|
|
57
94
|
// IO under step1 ends
|
|
58
|
-
{ id: 'io1',
|
|
95
|
+
{ id: 'io1', action: EventAction.END, details: { y: 5 }, timestamp: 2700 },
|
|
59
96
|
// step1 end
|
|
60
|
-
{ id: 's1',
|
|
97
|
+
{ id: 's1', action: EventAction.END, details: { done: true }, timestamp: 2800 },
|
|
61
98
|
// step2 end
|
|
62
|
-
{ id: 's2',
|
|
99
|
+
{ id: 's2', action: EventAction.END, details: { done: true }, timestamp: 2900 },
|
|
63
100
|
// workflow end
|
|
64
|
-
{ id: 'wf',
|
|
101
|
+
{ id: 'wf', action: EventAction.END, details: { ok: true }, timestamp: 3000 }
|
|
65
102
|
];
|
|
66
103
|
|
|
67
104
|
const result = buildTraceTree( entries );
|
|
@@ -74,6 +111,7 @@ this can indicate it timed out or was interrupted.>>' );
|
|
|
74
111
|
endedAt: 3000,
|
|
75
112
|
input: { a: 1 },
|
|
76
113
|
output: { ok: true },
|
|
114
|
+
attributes: {},
|
|
77
115
|
children: [
|
|
78
116
|
{
|
|
79
117
|
id: 'eval',
|
|
@@ -83,6 +121,7 @@ this can indicate it timed out or was interrupted.>>' );
|
|
|
83
121
|
endedAt: 1600,
|
|
84
122
|
input: { z: 0 },
|
|
85
123
|
output: { z: 1 },
|
|
124
|
+
attributes: {},
|
|
86
125
|
children: []
|
|
87
126
|
},
|
|
88
127
|
{
|
|
@@ -93,6 +132,7 @@ this can indicate it timed out or was interrupted.>>' );
|
|
|
93
132
|
endedAt: 2800,
|
|
94
133
|
input: { x: 1 },
|
|
95
134
|
output: { done: true },
|
|
135
|
+
attributes: { step_tag: 'alpha' },
|
|
96
136
|
children: [
|
|
97
137
|
{
|
|
98
138
|
id: 'io1',
|
|
@@ -102,6 +142,7 @@ this can indicate it timed out or was interrupted.>>' );
|
|
|
102
142
|
endedAt: 2700,
|
|
103
143
|
input: { y: 2 },
|
|
104
144
|
output: { y: 5 },
|
|
145
|
+
attributes: {},
|
|
105
146
|
children: []
|
|
106
147
|
}
|
|
107
148
|
]
|
|
@@ -114,6 +155,7 @@ this can indicate it timed out or was interrupted.>>' );
|
|
|
114
155
|
endedAt: 2900,
|
|
115
156
|
input: { x: 2 },
|
|
116
157
|
output: { done: true },
|
|
158
|
+
attributes: {},
|
|
117
159
|
children: [
|
|
118
160
|
{
|
|
119
161
|
id: 'io2',
|
|
@@ -123,6 +165,7 @@ this can indicate it timed out or was interrupted.>>' );
|
|
|
123
165
|
endedAt: 2600,
|
|
124
166
|
input: { y: 3 },
|
|
125
167
|
output: { y: 4 },
|
|
168
|
+
attributes: {},
|
|
126
169
|
children: []
|
|
127
170
|
}
|
|
128
171
|
]
|
|
@@ -64,34 +64,34 @@ export const init = async () => {
|
|
|
64
64
|
const serializeDetails = details => details instanceof Error ? serializeError( details ) : details;
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
|
-
*
|
|
67
|
+
* Emits an event action to the event bus.
|
|
68
68
|
*
|
|
69
|
-
* @param {string}
|
|
69
|
+
* @param {string} action - The action
|
|
70
70
|
* @param {object} fields - All the trace fields
|
|
71
71
|
* @returns {void}
|
|
72
72
|
*/
|
|
73
|
-
export const
|
|
73
|
+
export const addEventAction = ( action, { kind, name, id, parentId, details, executionContext } ) => {
|
|
74
74
|
// Ignores internal steps in the actual trace files, ignore trace if the flag is true
|
|
75
75
|
if ( kind !== ComponentType.INTERNAL_STEP && !executionContext.disableTrace ) {
|
|
76
76
|
traceBus.emit( 'entry', {
|
|
77
77
|
executionContext,
|
|
78
|
-
entry: { kind,
|
|
78
|
+
entry: { kind, action, name, id, parentId, timestamp: Date.now(), details: serializeDetails( details ) }
|
|
79
79
|
} );
|
|
80
80
|
}
|
|
81
81
|
};
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
|
-
*
|
|
84
|
+
* Attaches contextual information to an event action before calling the method to emit it to the bus.
|
|
85
85
|
*
|
|
86
|
-
* This function
|
|
87
|
-
* so it is safe to
|
|
86
|
+
* This function has no effect if called outside a Temporal Workflow/Activity environment,
|
|
87
|
+
* so it is safe to use in unit tests or dependencies that might be used elsewhere.
|
|
88
88
|
*
|
|
89
89
|
* @param {object} options - The common trace configurations
|
|
90
90
|
*/
|
|
91
|
-
export function
|
|
91
|
+
export function addEventActionWithContext( action, options ) {
|
|
92
92
|
const storeContent = Storage.load();
|
|
93
|
-
if ( storeContent ) { // If there is no storageContext this was not called from
|
|
93
|
+
if ( storeContent ) { // If there is no storageContext this was not called from a Temporal environment
|
|
94
94
|
const { parentId, executionContext } = storeContent;
|
|
95
|
-
|
|
95
|
+
addEventAction( action, { ...options, parentId, executionContext } );
|
|
96
96
|
}
|
|
97
97
|
};
|
|
@@ -39,7 +39,7 @@ describe( 'tracing/trace_engine', () => {
|
|
|
39
39
|
it( 'init() starts only enabled processors and attaches listeners', async () => {
|
|
40
40
|
process.env.OUTPUT_TRACE_LOCAL_ON = '1';
|
|
41
41
|
process.env.OUTPUT_TRACE_REMOTE_ON = '0';
|
|
42
|
-
const { init,
|
|
42
|
+
const { init, addEventAction } = await loadTraceEngine();
|
|
43
43
|
|
|
44
44
|
await init();
|
|
45
45
|
|
|
@@ -47,96 +47,96 @@ describe( 'tracing/trace_engine', () => {
|
|
|
47
47
|
expect( s3InitMock ).not.toHaveBeenCalled();
|
|
48
48
|
|
|
49
49
|
const executionContext = { disableTrace: false };
|
|
50
|
-
|
|
50
|
+
addEventAction( 'start', {
|
|
51
51
|
kind: 'step', name: 'N', id: '1', parentId: 'p', details: { ok: true }, executionContext
|
|
52
52
|
} );
|
|
53
53
|
expect( localExecMock ).toHaveBeenCalledTimes( 1 );
|
|
54
54
|
const payload = localExecMock.mock.calls[0][0];
|
|
55
55
|
expect( payload.entry.name ).toBe( 'N' );
|
|
56
56
|
expect( payload.entry.kind ).toBe( 'step' );
|
|
57
|
-
expect( payload.entry.
|
|
57
|
+
expect( payload.entry.action ).toBe( 'start' );
|
|
58
58
|
expect( payload.entry.details ).toEqual( { ok: true } );
|
|
59
59
|
expect( payload.executionContext ).toBe( executionContext );
|
|
60
60
|
} );
|
|
61
61
|
|
|
62
|
-
it( '
|
|
62
|
+
it( 'addEventAction() emits an entry consumed by processors', async () => {
|
|
63
63
|
process.env.OUTPUT_TRACE_LOCAL_ON = 'on';
|
|
64
|
-
const { init,
|
|
64
|
+
const { init, addEventAction } = await loadTraceEngine();
|
|
65
65
|
await init();
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
addEventAction( 'end', {
|
|
68
68
|
kind: 'workflow', name: 'W', id: '2', parentId: 'p2', details: 'done',
|
|
69
69
|
executionContext: { disableTrace: false }
|
|
70
70
|
} );
|
|
71
71
|
expect( localExecMock ).toHaveBeenCalledTimes( 1 );
|
|
72
72
|
const payload = localExecMock.mock.calls[0][0];
|
|
73
73
|
expect( payload.entry.name ).toBe( 'W' );
|
|
74
|
-
expect( payload.entry.
|
|
74
|
+
expect( payload.entry.action ).toBe( 'end' );
|
|
75
75
|
expect( payload.entry.details ).toBe( 'done' );
|
|
76
76
|
} );
|
|
77
77
|
|
|
78
|
-
it( '
|
|
78
|
+
it( 'addEventAction() does not emit when executionContext.disableTrace is true', async () => {
|
|
79
79
|
process.env.OUTPUT_TRACE_LOCAL_ON = '1';
|
|
80
|
-
const { init,
|
|
80
|
+
const { init, addEventAction } = await loadTraceEngine();
|
|
81
81
|
await init();
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
addEventAction( 'start', {
|
|
84
84
|
kind: 'step', name: 'X', id: '1', parentId: 'p', details: {},
|
|
85
85
|
executionContext: { disableTrace: true }
|
|
86
86
|
} );
|
|
87
87
|
expect( localExecMock ).not.toHaveBeenCalled();
|
|
88
88
|
} );
|
|
89
89
|
|
|
90
|
-
it( '
|
|
90
|
+
it( 'addEventAction() does not emit when kind is INTERNAL_STEP', async () => {
|
|
91
91
|
process.env.OUTPUT_TRACE_LOCAL_ON = '1';
|
|
92
|
-
const { init,
|
|
92
|
+
const { init, addEventAction } = await loadTraceEngine();
|
|
93
93
|
await init();
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
addEventAction( 'start', {
|
|
96
96
|
kind: 'internal_step', name: 'Internal', id: '1', parentId: 'p', details: {},
|
|
97
97
|
executionContext: { disableTrace: false }
|
|
98
98
|
} );
|
|
99
99
|
expect( localExecMock ).not.toHaveBeenCalled();
|
|
100
100
|
} );
|
|
101
101
|
|
|
102
|
-
it( '
|
|
102
|
+
it( 'addEventActionWithContext() uses storage when available', async () => {
|
|
103
103
|
process.env.OUTPUT_TRACE_LOCAL_ON = 'true';
|
|
104
104
|
storageLoadMock.mockReturnValue( {
|
|
105
105
|
parentId: 'ctx-p',
|
|
106
106
|
executionContext: { runId: 'r1', disableTrace: false }
|
|
107
107
|
} );
|
|
108
|
-
const { init,
|
|
108
|
+
const { init, addEventActionWithContext } = await loadTraceEngine();
|
|
109
109
|
await init();
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
addEventActionWithContext( 'tick', { kind: 'step', name: 'S', id: '3', details: 1 } );
|
|
112
112
|
expect( localExecMock ).toHaveBeenCalledTimes( 1 );
|
|
113
113
|
const payload = localExecMock.mock.calls[0][0];
|
|
114
114
|
expect( payload.executionContext ).toEqual( { runId: 'r1', disableTrace: false } );
|
|
115
115
|
expect( payload.entry.parentId ).toBe( 'ctx-p' );
|
|
116
116
|
expect( payload.entry.name ).toBe( 'S' );
|
|
117
|
-
expect( payload.entry.
|
|
117
|
+
expect( payload.entry.action ).toBe( 'tick' );
|
|
118
118
|
} );
|
|
119
119
|
|
|
120
|
-
it( '
|
|
120
|
+
it( 'addEventActionWithContext() does not emit when storage executionContext.disableTrace is true', async () => {
|
|
121
121
|
process.env.OUTPUT_TRACE_LOCAL_ON = '1';
|
|
122
122
|
storageLoadMock.mockReturnValue( {
|
|
123
123
|
parentId: 'ctx-p',
|
|
124
124
|
executionContext: { runId: 'r1', disableTrace: true }
|
|
125
125
|
} );
|
|
126
|
-
const { init,
|
|
126
|
+
const { init, addEventActionWithContext } = await loadTraceEngine();
|
|
127
127
|
await init();
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
addEventActionWithContext( 'tick', { kind: 'step', name: 'S', id: '3', details: 1 } );
|
|
130
130
|
expect( localExecMock ).not.toHaveBeenCalled();
|
|
131
131
|
} );
|
|
132
132
|
|
|
133
|
-
it( '
|
|
133
|
+
it( 'addEventActionWithContext() is a no-op when storage is absent', async () => {
|
|
134
134
|
process.env.OUTPUT_TRACE_LOCAL_ON = '1';
|
|
135
135
|
storageLoadMock.mockReturnValue( undefined );
|
|
136
|
-
const { init,
|
|
136
|
+
const { init, addEventActionWithContext } = await loadTraceEngine();
|
|
137
137
|
await init();
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
addEventActionWithContext( 'noop', { kind: 'step', name: 'X', id: '4', details: null } );
|
|
140
140
|
expect( localExecMock ).not.toHaveBeenCalled();
|
|
141
141
|
} );
|
|
142
142
|
|