@outputai/core 0.6.0 → 0.6.1-next.383b24b.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 +1 -1
- package/src/activity_integration/context.d.ts +5 -9
- package/src/activity_integration/context.js +5 -4
- package/src/activity_integration/context.spec.js +10 -15
- package/src/activity_integration/events.d.ts +2 -4
- package/src/activity_integration/events.js +8 -3
- package/src/activity_integration/events.spec.js +58 -29
- package/src/bus.js +18 -9
- package/src/bus.spec.js +30 -0
- package/src/hooks/index.d.ts +112 -58
- package/src/hooks/index.js +15 -12
- package/src/hooks/index.spec.js +60 -32
- package/src/interface/workflow.js +19 -35
- package/src/interface/workflow.spec.js +104 -15
- package/src/internal_activities/index.js +3 -3
- package/src/internal_activities/index.spec.js +31 -1
- package/src/internal_utils/temporal_context.js +12 -0
- package/src/internal_utils/temporal_context.spec.ts +83 -0
- package/src/internal_utils/trace_info.js +21 -0
- package/src/internal_utils/trace_info.spec.js +47 -0
- package/src/internal_utils/workflow_context.js +29 -0
- package/src/internal_utils/workflow_context.spec.js +46 -0
- package/src/tracing/internal_interface.js +4 -4
- package/src/tracing/processors/local/index.js +21 -26
- package/src/tracing/processors/local/index.spec.js +39 -45
- package/src/tracing/processors/s3/index.js +13 -23
- package/src/tracing/processors/s3/index.spec.js +33 -26
- package/src/tracing/trace_attribute.js +0 -1
- package/src/tracing/trace_engine.js +8 -12
- package/src/tracing/trace_engine.spec.js +31 -27
- package/src/worker/interceptors/activity.js +31 -29
- package/src/worker/interceptors/activity.spec.js +58 -26
- package/src/worker/interceptors/workflow.js +7 -2
- package/src/worker/interceptors/workflow.spec.js +42 -6
- package/src/worker/log_hooks.js +35 -46
- package/src/worker/log_hooks.spec.js +43 -46
- package/src/worker/sinks.js +24 -24
- package/src/interface/workflow_context.js +0 -33
|
@@ -32,16 +32,12 @@ const processors = [
|
|
|
32
32
|
/**
|
|
33
33
|
* Returns the destinations for a given execution context
|
|
34
34
|
*
|
|
35
|
-
* @param {object}
|
|
36
|
-
* @param {string} executionContext.startTime
|
|
37
|
-
* @param {string} executionContext.workflowId
|
|
38
|
-
* @param {string} executionContext.workflowName
|
|
39
|
-
* @param {boolean} executionContext.disableTrace
|
|
35
|
+
* @param {object} traceInfo - The trace information object
|
|
40
36
|
* @returns {object} A trace destinations object: { [dest-name]: 'path' }
|
|
41
37
|
*/
|
|
42
|
-
export const getDestinations =
|
|
38
|
+
export const getDestinations = traceInfo =>
|
|
43
39
|
processors.reduce( ( o, p ) =>
|
|
44
|
-
Object.assign( o, { [p.name.toLowerCase()]: p.enabled && !
|
|
40
|
+
Object.assign( o, { [p.name.toLowerCase()]: p.enabled && !traceInfo.disableTrace ? p.getDestination( traceInfo ) : null } )
|
|
45
41
|
, {} );
|
|
46
42
|
|
|
47
43
|
/**
|
|
@@ -72,11 +68,11 @@ const serializeDetails = details => details instanceof Error ? serializeError( d
|
|
|
72
68
|
* @param {object} fields - All the trace fields
|
|
73
69
|
* @returns {void}
|
|
74
70
|
*/
|
|
75
|
-
export const addEventAction = ( action, { kind, name, id, parentId, details,
|
|
71
|
+
export const addEventAction = ( action, { kind, name, id, parentId, details, traceInfo } ) => {
|
|
76
72
|
// Ignores internal steps in the actual trace files, ignore trace if the flag is true
|
|
77
|
-
if ( kind !== ComponentType.INTERNAL_STEP && !
|
|
73
|
+
if ( kind !== ComponentType.INTERNAL_STEP && !traceInfo.disableTrace ) {
|
|
78
74
|
traceBus.emit( 'entry', {
|
|
79
|
-
|
|
75
|
+
traceInfo,
|
|
80
76
|
entry: { kind, action, name, id, parentId, timestamp: Date.now(), details: serializeDetails( details ) }
|
|
81
77
|
} );
|
|
82
78
|
}
|
|
@@ -93,7 +89,7 @@ export const addEventAction = ( action, { kind, name, id, parentId, details, exe
|
|
|
93
89
|
export function addEventActionWithContext( action, options ) {
|
|
94
90
|
const storeContent = Storage.load();
|
|
95
91
|
if ( storeContent ) { // If there is no storageContext this was not called from a Temporal environment
|
|
96
|
-
const { parentId,
|
|
92
|
+
const { parentId, traceInfo, addAttribute } = storeContent;
|
|
97
93
|
if ( action === EventAction.ADD_ATTR ) {
|
|
98
94
|
const attribute = options.details;
|
|
99
95
|
if ( !( attribute instanceof BaseAttribute ) ) {
|
|
@@ -102,6 +98,6 @@ export function addEventActionWithContext( action, options ) {
|
|
|
102
98
|
addAttribute( options.details );
|
|
103
99
|
}
|
|
104
100
|
}
|
|
105
|
-
addEventAction( action, { ...options, parentId,
|
|
101
|
+
addEventAction( action, { ...options, parentId, traceInfo } );
|
|
106
102
|
}
|
|
107
103
|
};
|
|
@@ -34,6 +34,14 @@ async function loadTraceEngine() {
|
|
|
34
34
|
return import( './trace_engine.js' );
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
const traceInfo = {
|
|
38
|
+
workflowId: 'w1',
|
|
39
|
+
runId: 'r1',
|
|
40
|
+
workflowType: 'WF',
|
|
41
|
+
startTime: 1,
|
|
42
|
+
disableTrace: false
|
|
43
|
+
};
|
|
44
|
+
|
|
37
45
|
describe( 'tracing/trace_engine', () => {
|
|
38
46
|
beforeEach( () => {
|
|
39
47
|
vi.clearAllMocks();
|
|
@@ -52,9 +60,8 @@ describe( 'tracing/trace_engine', () => {
|
|
|
52
60
|
expect( localInitMock ).toHaveBeenCalledTimes( 1 );
|
|
53
61
|
expect( s3InitMock ).not.toHaveBeenCalled();
|
|
54
62
|
|
|
55
|
-
const executionContext = { disableTrace: false };
|
|
56
63
|
addEventAction( 'start', {
|
|
57
|
-
kind: 'step', name: 'N', id: '1', parentId: 'p', details: { ok: true },
|
|
64
|
+
kind: 'step', name: 'N', id: '1', parentId: 'p', details: { ok: true }, traceInfo
|
|
58
65
|
} );
|
|
59
66
|
expect( localExecMock ).toHaveBeenCalledTimes( 1 );
|
|
60
67
|
const payload = localExecMock.mock.calls[0][0];
|
|
@@ -62,7 +69,7 @@ describe( 'tracing/trace_engine', () => {
|
|
|
62
69
|
expect( payload.entry.kind ).toBe( 'step' );
|
|
63
70
|
expect( payload.entry.action ).toBe( 'start' );
|
|
64
71
|
expect( payload.entry.details ).toEqual( { ok: true } );
|
|
65
|
-
expect( payload.
|
|
72
|
+
expect( payload.traceInfo ).toBe( traceInfo );
|
|
66
73
|
} );
|
|
67
74
|
|
|
68
75
|
it( 'addEventAction() emits an entry consumed by processors', async () => {
|
|
@@ -72,7 +79,7 @@ describe( 'tracing/trace_engine', () => {
|
|
|
72
79
|
|
|
73
80
|
addEventAction( 'end', {
|
|
74
81
|
kind: 'workflow', name: 'W', id: '2', parentId: 'p2', details: 'done',
|
|
75
|
-
|
|
82
|
+
traceInfo
|
|
76
83
|
} );
|
|
77
84
|
expect( localExecMock ).toHaveBeenCalledTimes( 1 );
|
|
78
85
|
const payload = localExecMock.mock.calls[0][0];
|
|
@@ -81,14 +88,14 @@ describe( 'tracing/trace_engine', () => {
|
|
|
81
88
|
expect( payload.entry.details ).toBe( 'done' );
|
|
82
89
|
} );
|
|
83
90
|
|
|
84
|
-
it( 'addEventAction() does not emit when
|
|
91
|
+
it( 'addEventAction() does not emit when traceInfo.disableTrace is true', async () => {
|
|
85
92
|
process.env.OUTPUT_TRACE_LOCAL_ON = '1';
|
|
86
93
|
const { init, addEventAction } = await loadTraceEngine();
|
|
87
94
|
await init();
|
|
88
95
|
|
|
89
96
|
addEventAction( 'start', {
|
|
90
97
|
kind: 'step', name: 'X', id: '1', parentId: 'p', details: {},
|
|
91
|
-
|
|
98
|
+
traceInfo: { ...traceInfo, disableTrace: true }
|
|
92
99
|
} );
|
|
93
100
|
expect( localExecMock ).not.toHaveBeenCalled();
|
|
94
101
|
} );
|
|
@@ -100,7 +107,7 @@ describe( 'tracing/trace_engine', () => {
|
|
|
100
107
|
|
|
101
108
|
addEventAction( 'start', {
|
|
102
109
|
kind: 'internal_step', name: 'Internal', id: '1', parentId: 'p', details: {},
|
|
103
|
-
|
|
110
|
+
traceInfo
|
|
104
111
|
} );
|
|
105
112
|
expect( localExecMock ).not.toHaveBeenCalled();
|
|
106
113
|
} );
|
|
@@ -109,7 +116,7 @@ describe( 'tracing/trace_engine', () => {
|
|
|
109
116
|
process.env.OUTPUT_TRACE_LOCAL_ON = 'true';
|
|
110
117
|
storageLoadMock.mockReturnValue( {
|
|
111
118
|
parentId: 'ctx-p',
|
|
112
|
-
|
|
119
|
+
traceInfo
|
|
113
120
|
} );
|
|
114
121
|
const { init, addEventActionWithContext } = await loadTraceEngine();
|
|
115
122
|
await init();
|
|
@@ -117,7 +124,7 @@ describe( 'tracing/trace_engine', () => {
|
|
|
117
124
|
addEventActionWithContext( 'tick', { kind: 'step', name: 'S', id: '3', details: 1 } );
|
|
118
125
|
expect( localExecMock ).toHaveBeenCalledTimes( 1 );
|
|
119
126
|
const payload = localExecMock.mock.calls[0][0];
|
|
120
|
-
expect( payload.
|
|
127
|
+
expect( payload.traceInfo ).toBe( traceInfo );
|
|
121
128
|
expect( payload.entry.parentId ).toBe( 'ctx-p' );
|
|
122
129
|
expect( payload.entry.name ).toBe( 'S' );
|
|
123
130
|
expect( payload.entry.action ).toBe( 'tick' );
|
|
@@ -126,10 +133,9 @@ describe( 'tracing/trace_engine', () => {
|
|
|
126
133
|
it( 'addEventActionWithContext() records ADD_ATTR attributes through storage context', async () => {
|
|
127
134
|
process.env.OUTPUT_TRACE_LOCAL_ON = 'true';
|
|
128
135
|
const addAttributeMock = vi.fn();
|
|
129
|
-
const executionContext = { runId: 'r1', disableTrace: false };
|
|
130
136
|
storageLoadMock.mockReturnValue( {
|
|
131
137
|
parentId: 'ctx-p',
|
|
132
|
-
|
|
138
|
+
traceInfo,
|
|
133
139
|
addAttribute: addAttributeMock
|
|
134
140
|
} );
|
|
135
141
|
const { init, addEventActionWithContext } = await loadTraceEngine();
|
|
@@ -144,7 +150,7 @@ describe( 'tracing/trace_engine', () => {
|
|
|
144
150
|
expect( addAttributeMock ).toHaveBeenCalledWith( attribute );
|
|
145
151
|
expect( localExecMock ).toHaveBeenCalledTimes( 1 );
|
|
146
152
|
expect( localExecMock.mock.calls[0][0] ).toEqual( {
|
|
147
|
-
|
|
153
|
+
traceInfo,
|
|
148
154
|
entry: {
|
|
149
155
|
kind: 'http',
|
|
150
156
|
action: EventAction.ADD_ATTR,
|
|
@@ -162,7 +168,7 @@ describe( 'tracing/trace_engine', () => {
|
|
|
162
168
|
const addAttributeMock = vi.fn();
|
|
163
169
|
storageLoadMock.mockReturnValue( {
|
|
164
170
|
parentId: 'ctx-p',
|
|
165
|
-
|
|
171
|
+
traceInfo,
|
|
166
172
|
addAttribute: addAttributeMock
|
|
167
173
|
} );
|
|
168
174
|
const { init, addEventActionWithContext } = await loadTraceEngine();
|
|
@@ -179,11 +185,11 @@ describe( 'tracing/trace_engine', () => {
|
|
|
179
185
|
expect( localExecMock ).not.toHaveBeenCalled();
|
|
180
186
|
} );
|
|
181
187
|
|
|
182
|
-
it( 'addEventActionWithContext() does not emit when storage
|
|
188
|
+
it( 'addEventActionWithContext() does not emit when storage traceInfo.disableTrace is true', async () => {
|
|
183
189
|
process.env.OUTPUT_TRACE_LOCAL_ON = '1';
|
|
184
190
|
storageLoadMock.mockReturnValue( {
|
|
185
191
|
parentId: 'ctx-p',
|
|
186
|
-
|
|
192
|
+
traceInfo: { ...traceInfo, disableTrace: true }
|
|
187
193
|
} );
|
|
188
194
|
const { init, addEventActionWithContext } = await loadTraceEngine();
|
|
189
195
|
await init();
|
|
@@ -203,21 +209,19 @@ describe( 'tracing/trace_engine', () => {
|
|
|
203
209
|
} );
|
|
204
210
|
|
|
205
211
|
describe( 'getDestinations()', () => {
|
|
206
|
-
const executionContext = { workflowId: 'w1', workflowName: 'WF', startTime: 1, disableTrace: false };
|
|
207
|
-
|
|
208
212
|
it( 'returns null for both when traces are off (env vars unset)', async () => {
|
|
209
213
|
const { getDestinations } = await loadTraceEngine();
|
|
210
|
-
const result = getDestinations(
|
|
214
|
+
const result = getDestinations( traceInfo );
|
|
211
215
|
expect( result ).toEqual( { local: null, remote: null } );
|
|
212
216
|
expect( localGetDestinationMock ).not.toHaveBeenCalled();
|
|
213
217
|
expect( s3GetDestinationMock ).not.toHaveBeenCalled();
|
|
214
218
|
} );
|
|
215
219
|
|
|
216
|
-
it( 'returns null for both when
|
|
220
|
+
it( 'returns null for both when traceInfo.disableTrace is true', async () => {
|
|
217
221
|
process.env.OUTPUT_TRACE_LOCAL_ON = '1';
|
|
218
222
|
process.env.OUTPUT_TRACE_REMOTE_ON = '1';
|
|
219
223
|
const { getDestinations } = await loadTraceEngine();
|
|
220
|
-
const result = getDestinations( { ...
|
|
224
|
+
const result = getDestinations( { ...traceInfo, disableTrace: true } );
|
|
221
225
|
expect( result ).toEqual( { local: null, remote: null } );
|
|
222
226
|
expect( localGetDestinationMock ).not.toHaveBeenCalled();
|
|
223
227
|
expect( s3GetDestinationMock ).not.toHaveBeenCalled();
|
|
@@ -227,24 +231,24 @@ describe( 'tracing/trace_engine', () => {
|
|
|
227
231
|
process.env.OUTPUT_TRACE_LOCAL_ON = '1';
|
|
228
232
|
process.env.OUTPUT_TRACE_REMOTE_ON = 'true';
|
|
229
233
|
const { getDestinations } = await loadTraceEngine();
|
|
230
|
-
const result = getDestinations(
|
|
234
|
+
const result = getDestinations( traceInfo );
|
|
231
235
|
expect( result ).toEqual( {
|
|
232
236
|
local: '/local/path.json',
|
|
233
237
|
remote: 'https://bucket.s3.amazonaws.com/key.json'
|
|
234
238
|
} );
|
|
235
239
|
expect( localGetDestinationMock ).toHaveBeenCalledTimes( 1 );
|
|
236
|
-
expect( localGetDestinationMock ).toHaveBeenCalledWith(
|
|
240
|
+
expect( localGetDestinationMock ).toHaveBeenCalledWith( traceInfo );
|
|
237
241
|
expect( s3GetDestinationMock ).toHaveBeenCalledTimes( 1 );
|
|
238
|
-
expect( s3GetDestinationMock ).toHaveBeenCalledWith(
|
|
242
|
+
expect( s3GetDestinationMock ).toHaveBeenCalledWith( traceInfo );
|
|
239
243
|
} );
|
|
240
244
|
|
|
241
245
|
it( 'returns local only when local trace on and remote off', async () => {
|
|
242
246
|
process.env.OUTPUT_TRACE_LOCAL_ON = '1';
|
|
243
247
|
process.env.OUTPUT_TRACE_REMOTE_ON = '0';
|
|
244
248
|
const { getDestinations } = await loadTraceEngine();
|
|
245
|
-
const result = getDestinations(
|
|
249
|
+
const result = getDestinations( traceInfo );
|
|
246
250
|
expect( result ).toEqual( { local: '/local/path.json', remote: null } );
|
|
247
|
-
expect( localGetDestinationMock ).toHaveBeenCalledWith(
|
|
251
|
+
expect( localGetDestinationMock ).toHaveBeenCalledWith( traceInfo );
|
|
248
252
|
expect( s3GetDestinationMock ).not.toHaveBeenCalled();
|
|
249
253
|
} );
|
|
250
254
|
|
|
@@ -252,10 +256,10 @@ describe( 'tracing/trace_engine', () => {
|
|
|
252
256
|
process.env.OUTPUT_TRACE_LOCAL_ON = '0';
|
|
253
257
|
process.env.OUTPUT_TRACE_REMOTE_ON = 'true';
|
|
254
258
|
const { getDestinations } = await loadTraceEngine();
|
|
255
|
-
const result = getDestinations(
|
|
259
|
+
const result = getDestinations( traceInfo );
|
|
256
260
|
expect( result ).toEqual( { local: null, remote: 'https://bucket.s3.amazonaws.com/key.json' } );
|
|
257
261
|
expect( localGetDestinationMock ).not.toHaveBeenCalled();
|
|
258
|
-
expect( s3GetDestinationMock ).toHaveBeenCalledWith(
|
|
262
|
+
expect( s3GetDestinationMock ).toHaveBeenCalledWith( traceInfo );
|
|
259
263
|
} );
|
|
260
264
|
} );
|
|
261
265
|
} );
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Context } from '@temporalio/activity';
|
|
1
|
+
import { Context, activityInfo as activityInfoFn } from '@temporalio/activity';
|
|
2
2
|
import { Storage } from '#async_storage';
|
|
3
3
|
import * as Tracing from '#tracing';
|
|
4
4
|
import { headersToObject } from '../sandboxed_utils.js';
|
|
@@ -25,7 +25,7 @@ const log = createChildLogger( 'ActivityInterceptor' );
|
|
|
25
25
|
|
|
26
26
|
Context information comes from two sources:
|
|
27
27
|
- Temporal's Activity Context (workflowId, activityId, activityType)
|
|
28
|
-
- Headers injected by the workflow interceptor
|
|
28
|
+
- Headers injected by the workflow interceptor
|
|
29
29
|
*/
|
|
30
30
|
export class ActivityExecutionInterceptor {
|
|
31
31
|
constructor( { activities, workflows, connection } ) {
|
|
@@ -42,25 +42,24 @@ export class ActivityExecutionInterceptor {
|
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* Returns a workflow entry by its name or throws error
|
|
45
|
-
* @param {string}
|
|
45
|
+
* @param {string} workflowType
|
|
46
46
|
* @returns {object} Workflow entry
|
|
47
47
|
* @throws {Error}
|
|
48
48
|
*/
|
|
49
|
-
getWorkflowEntry(
|
|
50
|
-
const workflowEntry = this.workflowsMap.get(
|
|
49
|
+
getWorkflowEntry( workflowType ) {
|
|
50
|
+
const workflowEntry = this.workflowsMap.get( workflowType );
|
|
51
51
|
if ( !workflowEntry ) {
|
|
52
|
-
throw new Error( `Activity interceptor: workflow "${
|
|
52
|
+
throw new Error( `Activity interceptor: workflow "${workflowType}" not found in workflowsMap.` );
|
|
53
53
|
}
|
|
54
54
|
return workflowEntry;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
async execute( input, next ) {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
const {
|
|
61
|
-
const {
|
|
62
|
-
const {
|
|
63
|
-
const { path: workflowFilename } = this.getWorkflowEntry( workflowName );
|
|
58
|
+
const activityInfo = activityInfoFn();
|
|
59
|
+
const { workflowExecution: { workflowId, runId }, activityId, activityType, workflowType } = activityInfo;
|
|
60
|
+
const { traceInfo, workflowDetails } = headersToObject( input.headers );
|
|
61
|
+
const { type: outputActivityKind } = this.activities?.[activityType]?.[METADATA_ACCESS_SYMBOL];
|
|
62
|
+
const { path: workflowFilename } = this.getWorkflowEntry( workflowType );
|
|
64
63
|
|
|
65
64
|
const state = {
|
|
66
65
|
heartbeat: null,
|
|
@@ -76,32 +75,35 @@ export class ActivityExecutionInterceptor {
|
|
|
76
75
|
const workflowHandle = client.workflow.getHandle( workflowId );
|
|
77
76
|
await workflowHandle.signal( Signal.SEND_AGGREGATIONS, aggregateAttributes( state.attributes ) );
|
|
78
77
|
} catch ( error ) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
stack: error.stack,
|
|
82
|
-
activityId: id,
|
|
83
|
-
activityName: name,
|
|
84
|
-
workflowId,
|
|
85
|
-
workflowName
|
|
86
|
-
} );
|
|
78
|
+
const errorContext = { message: error.message, stack: error.stack, activityId, activityType, workflowId, workflowType, runId };
|
|
79
|
+
log.warn( `Signal "${Signal.SEND_AGGREGATIONS}" failed`, errorContext );
|
|
87
80
|
}
|
|
88
81
|
}
|
|
89
82
|
};
|
|
90
83
|
|
|
91
|
-
//
|
|
92
|
-
const
|
|
84
|
+
// Adds context accessible information
|
|
85
|
+
const storageContext = {
|
|
86
|
+
parentId: activityId,
|
|
87
|
+
outputActivityKind,
|
|
88
|
+
activityInfo,
|
|
89
|
+
workflowDetails,
|
|
90
|
+
workflowFilename,
|
|
91
|
+
traceInfo,
|
|
92
|
+
addAttribute
|
|
93
|
+
};
|
|
93
94
|
|
|
94
|
-
messageBus.emit( BusEventType.ACTIVITY_START, {
|
|
95
|
-
Tracing.addEventStart( { id, name, kind, parentId:
|
|
95
|
+
messageBus.emit( BusEventType.ACTIVITY_START, { activityInfo, workflowDetails, outputActivityKind } );
|
|
96
|
+
Tracing.addEventStart( { id: activityId, name: activityType, kind: outputActivityKind, parentId: runId, details: input.args[0], traceInfo } );
|
|
96
97
|
|
|
97
98
|
try {
|
|
98
99
|
// Sends heartbeat to communicate that activity is still alive
|
|
99
100
|
state.heartbeat = activityHeartbeatEnabled && setInterval( () => Context.current().heartbeat(), activityHeartbeatIntervalMs );
|
|
100
101
|
|
|
101
|
-
const output = await Storage.runWithContext( async _ => next( input ),
|
|
102
|
+
const output = await Storage.runWithContext( async _ => next( input ), storageContext );
|
|
103
|
+
|
|
104
|
+
messageBus.emit( BusEventType.ACTIVITY_END, { activityInfo, workflowDetails, outputActivityKind } );
|
|
105
|
+
Tracing.addEventEnd( { id: activityId, details: output, traceInfo } );
|
|
102
106
|
|
|
103
|
-
messageBus.emit( BusEventType.ACTIVITY_END, { id, name, kind, workflowId, workflowName, duration: Date.now() - startDate } );
|
|
104
|
-
Tracing.addEventEnd( { id, details: output, executionContext } );
|
|
105
107
|
return {
|
|
106
108
|
[ACTIVITY_WRAPPER_VERSION_FIELD]: 1,
|
|
107
109
|
output,
|
|
@@ -109,8 +111,8 @@ export class ActivityExecutionInterceptor {
|
|
|
109
111
|
};
|
|
110
112
|
|
|
111
113
|
} catch ( error ) {
|
|
112
|
-
messageBus.emit( BusEventType.ACTIVITY_ERROR, {
|
|
113
|
-
Tracing.addEventError( { id, details: error,
|
|
114
|
+
messageBus.emit( BusEventType.ACTIVITY_ERROR, { activityInfo, workflowDetails, outputActivityKind, error } );
|
|
115
|
+
Tracing.addEventError( { id: activityId, details: error, traceInfo } );
|
|
114
116
|
|
|
115
117
|
await sendAggregationsViaSignal();
|
|
116
118
|
|
|
@@ -7,20 +7,36 @@ const workflowHandleMock = vi.hoisted( () => ( { signal: vi.fn() } ) );
|
|
|
7
7
|
const getHandleMock = vi.hoisted( () => vi.fn( () => workflowHandleMock ) );
|
|
8
8
|
const clientConstructorMock = vi.hoisted( () => vi.fn() );
|
|
9
9
|
const logWarnMock = vi.hoisted( () => vi.fn() );
|
|
10
|
-
|
|
11
|
-
const heartbeatMock = vi.fn();
|
|
10
|
+
const heartbeatMock = vi.hoisted( () => vi.fn() );
|
|
12
11
|
const runWithContextMock = vi.hoisted( () => vi.fn().mockImplementation( async fn => fn() ) );
|
|
13
|
-
const
|
|
14
|
-
workflowExecution: { workflowId: 'wf-1' },
|
|
12
|
+
const activityInfoMock = vi.hoisted( () => ( {
|
|
13
|
+
workflowExecution: { workflowId: 'wf-1', runId: 'run-1' },
|
|
15
14
|
activityId: 'act-1',
|
|
16
15
|
activityType: 'myWorkflow#myStep',
|
|
17
16
|
workflowType: 'myWorkflow'
|
|
18
|
-
};
|
|
17
|
+
} ) );
|
|
18
|
+
const traceInfoMock = vi.hoisted( () => ( {
|
|
19
|
+
workflowId: 'wf-1',
|
|
20
|
+
runId: 'run-1',
|
|
21
|
+
workflowType: 'myWorkflow',
|
|
22
|
+
startTime: 1710000000000,
|
|
23
|
+
disableTrace: false
|
|
24
|
+
} ) );
|
|
25
|
+
const workflowDetailsMock = vi.hoisted( () => ( {
|
|
26
|
+
workflowId: 'wf-1',
|
|
27
|
+
runId: 'run-1',
|
|
28
|
+
workflowType: 'myWorkflow',
|
|
29
|
+
firstExecutionRunId: 'run-1',
|
|
30
|
+
startTime: 1710000000000,
|
|
31
|
+
runStartTime: 1710000000000,
|
|
32
|
+
attempt: 1
|
|
33
|
+
} ) );
|
|
19
34
|
|
|
20
35
|
vi.mock( '@temporalio/activity', () => ( {
|
|
36
|
+
activityInfo: () => activityInfoMock,
|
|
21
37
|
Context: {
|
|
22
38
|
current: () => ( {
|
|
23
|
-
info:
|
|
39
|
+
info: activityInfoMock,
|
|
24
40
|
heartbeat: heartbeatMock
|
|
25
41
|
} )
|
|
26
42
|
}
|
|
@@ -58,7 +74,7 @@ vi.mock( '#tracing', () => ( {
|
|
|
58
74
|
} ) );
|
|
59
75
|
|
|
60
76
|
vi.mock( '../sandboxed_utils.js', () => ( {
|
|
61
|
-
headersToObject: () => ( {
|
|
77
|
+
headersToObject: () => ( { traceInfo: traceInfoMock, workflowDetails: workflowDetailsMock } )
|
|
62
78
|
} ) );
|
|
63
79
|
|
|
64
80
|
const messageBusEmitMock = vi.fn();
|
|
@@ -105,6 +121,7 @@ const httpRequestAttribute = {
|
|
|
105
121
|
describe( 'ActivityExecutionInterceptor', () => {
|
|
106
122
|
beforeEach( () => {
|
|
107
123
|
vi.clearAllMocks();
|
|
124
|
+
activityInfoMock.workflowType = 'myWorkflow';
|
|
108
125
|
workflowHandleMock.signal.mockResolvedValue( undefined );
|
|
109
126
|
vi.useFakeTimers();
|
|
110
127
|
vi.resetModules();
|
|
@@ -132,22 +149,34 @@ describe( 'ActivityExecutionInterceptor', () => {
|
|
|
132
149
|
aggregations: null,
|
|
133
150
|
[ACTIVITY_WRAPPER_VERSION_FIELD]: 1
|
|
134
151
|
} );
|
|
135
|
-
expect( messageBusEmitMock ).toHaveBeenCalledWith(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
152
|
+
expect( messageBusEmitMock ).toHaveBeenCalledWith(
|
|
153
|
+
BusEventType.ACTIVITY_START,
|
|
154
|
+
{ activityInfo: activityInfoMock, workflowDetails: workflowDetailsMock, outputActivityKind: 'step' }
|
|
155
|
+
);
|
|
156
|
+
expect( messageBusEmitMock ).toHaveBeenCalledWith(
|
|
157
|
+
BusEventType.ACTIVITY_END,
|
|
158
|
+
{ activityInfo: activityInfoMock, workflowDetails: workflowDetailsMock, outputActivityKind: 'step' }
|
|
159
|
+
);
|
|
160
|
+
expect( addEventStartMock ).toHaveBeenCalledWith( {
|
|
161
|
+
id: 'act-1',
|
|
162
|
+
name: 'myWorkflow#myStep',
|
|
163
|
+
kind: 'step',
|
|
164
|
+
parentId: 'run-1',
|
|
165
|
+
details: { someInput: 'data' },
|
|
166
|
+
traceInfo: traceInfoMock
|
|
167
|
+
} );
|
|
168
|
+
expect( addEventEndMock ).toHaveBeenCalledWith( { id: 'act-1', details: { result: 'ok' }, traceInfo: traceInfoMock } );
|
|
143
169
|
expect( addEventErrorMock ).not.toHaveBeenCalled();
|
|
144
170
|
expect( clientConstructorMock ).not.toHaveBeenCalled();
|
|
145
171
|
expect( runWithContextMock ).toHaveBeenCalledWith(
|
|
146
172
|
expect.any( Function ),
|
|
147
173
|
expect.objectContaining( {
|
|
148
174
|
parentId: 'act-1',
|
|
149
|
-
|
|
175
|
+
activityInfo: activityInfoMock,
|
|
176
|
+
workflowDetails: workflowDetailsMock,
|
|
177
|
+
outputActivityKind: 'step',
|
|
150
178
|
workflowFilename: '/workflows/myWorkflow.js',
|
|
179
|
+
traceInfo: traceInfoMock,
|
|
151
180
|
addAttribute: expect.any( Function )
|
|
152
181
|
} )
|
|
153
182
|
);
|
|
@@ -166,7 +195,7 @@ describe( 'ActivityExecutionInterceptor', () => {
|
|
|
166
195
|
} );
|
|
167
196
|
|
|
168
197
|
expect( messageBusEmitMock ).toHaveBeenCalledWith( BusEventType.ACTIVITY_END, expect.any( Object ) );
|
|
169
|
-
expect( addEventEndMock ).toHaveBeenCalledWith( { id: 'act-1', details: { result: 'sync' },
|
|
198
|
+
expect( addEventEndMock ).toHaveBeenCalledWith( { id: 'act-1', details: { result: 'sync' }, traceInfo: traceInfoMock } );
|
|
170
199
|
expect( addEventErrorMock ).not.toHaveBeenCalled();
|
|
171
200
|
} );
|
|
172
201
|
|
|
@@ -244,9 +273,10 @@ describe( 'ActivityExecutionInterceptor', () => {
|
|
|
244
273
|
expect( logWarnMock ).toHaveBeenCalledWith( `Signal "${Signal.SEND_AGGREGATIONS}" failed`, expect.objectContaining( {
|
|
245
274
|
message: 'signal failed',
|
|
246
275
|
activityId: 'act-1',
|
|
247
|
-
|
|
276
|
+
activityType: 'myWorkflow#myStep',
|
|
248
277
|
workflowId: 'wf-1',
|
|
249
|
-
|
|
278
|
+
workflowType: 'myWorkflow',
|
|
279
|
+
runId: 'run-1'
|
|
250
280
|
} ) );
|
|
251
281
|
} );
|
|
252
282
|
|
|
@@ -261,12 +291,14 @@ describe( 'ActivityExecutionInterceptor', () => {
|
|
|
261
291
|
|
|
262
292
|
await expect( promise ).rejects.toThrow( 'step failed' );
|
|
263
293
|
expect( messageBusEmitMock ).toHaveBeenCalledWith( BusEventType.ACTIVITY_START, expect.any( Object ) );
|
|
264
|
-
expect( messageBusEmitMock ).toHaveBeenCalledWith( BusEventType.ACTIVITY_ERROR,
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
294
|
+
expect( messageBusEmitMock ).toHaveBeenCalledWith( BusEventType.ACTIVITY_ERROR, {
|
|
295
|
+
activityInfo: activityInfoMock,
|
|
296
|
+
workflowDetails: workflowDetailsMock,
|
|
297
|
+
outputActivityKind: 'step',
|
|
298
|
+
error
|
|
299
|
+
} );
|
|
268
300
|
expect( addEventStartMock ).toHaveBeenCalledOnce();
|
|
269
|
-
expect( addEventErrorMock ).
|
|
301
|
+
expect( addEventErrorMock ).toHaveBeenCalledWith( { id: 'act-1', details: error, traceInfo: traceInfoMock } );
|
|
270
302
|
expect( addEventEndMock ).not.toHaveBeenCalled();
|
|
271
303
|
} );
|
|
272
304
|
|
|
@@ -331,7 +363,7 @@ describe( 'ActivityExecutionInterceptor', () => {
|
|
|
331
363
|
const interceptor = new ActivityExecutionInterceptor( { activities: makeActivities(), workflows } );
|
|
332
364
|
|
|
333
365
|
// Override context to use alias as workflowType
|
|
334
|
-
|
|
366
|
+
activityInfoMock.workflowType = 'myWorkflowOld';
|
|
335
367
|
const next = vi.fn().mockResolvedValue( { result: 'ok' } );
|
|
336
368
|
|
|
337
369
|
const promise = interceptor.execute( makeInput(), next );
|
|
@@ -345,7 +377,7 @@ describe( 'ActivityExecutionInterceptor', () => {
|
|
|
345
377
|
);
|
|
346
378
|
|
|
347
379
|
// Restore for other tests
|
|
348
|
-
|
|
380
|
+
activityInfoMock.workflowType = 'myWorkflow';
|
|
349
381
|
} );
|
|
350
382
|
|
|
351
383
|
it( 'does not heartbeat when OUTPUT_ACTIVITY_HEARTBEAT_ENABLED is false', async () => {
|
|
@@ -5,6 +5,7 @@ import { deepMerge } from '#utils';
|
|
|
5
5
|
import { METADATA_ACCESS_SYMBOL, WorkflowSpecialOutput } from '#consts';
|
|
6
6
|
// this is a dynamic generated file with activity configs overwrites
|
|
7
7
|
import stepOptions from '../temp/__activity_options.js';
|
|
8
|
+
import { createWorkflowDetails } from '#internal_utils/temporal_context';
|
|
8
9
|
|
|
9
10
|
/*
|
|
10
11
|
This is not an AI comment!
|
|
@@ -17,8 +18,12 @@ import stepOptions from '../temp/__activity_options.js';
|
|
|
17
18
|
*/
|
|
18
19
|
class HeadersInjectionInterceptor {
|
|
19
20
|
async scheduleActivity( input, next ) {
|
|
20
|
-
const
|
|
21
|
-
|
|
21
|
+
const info = workflowInfo();
|
|
22
|
+
const memo = info.memo ?? {};
|
|
23
|
+
Object.assign( input.headers, memoToHeaders( {
|
|
24
|
+
...memo,
|
|
25
|
+
workflowDetails: createWorkflowDetails( info )
|
|
26
|
+
} ) );
|
|
22
27
|
// apply per-invocation options passed as second argument by rewritten calls
|
|
23
28
|
const options = stepOptions[input.activityType];
|
|
24
29
|
if ( options ) {
|
|
@@ -7,6 +7,35 @@ const workflowStartMock = vi.fn();
|
|
|
7
7
|
const workflowEndMock = vi.fn();
|
|
8
8
|
const workflowErrorMock = vi.fn();
|
|
9
9
|
const isCancellationMock = vi.fn();
|
|
10
|
+
const startTime = new Date( '2026-06-02T09:00:00.000Z' );
|
|
11
|
+
const runStartTime = new Date( '2026-06-02T09:05:00.000Z' );
|
|
12
|
+
const workflowDetails = {
|
|
13
|
+
attempt: 1,
|
|
14
|
+
continuedFromExecutionRunId: undefined,
|
|
15
|
+
firstExecutionRunId: 'first-run',
|
|
16
|
+
parent: undefined,
|
|
17
|
+
root: undefined,
|
|
18
|
+
runId: 'run-1',
|
|
19
|
+
runStartTime: runStartTime.getTime(),
|
|
20
|
+
startTime: startTime.getTime(),
|
|
21
|
+
workflowId: 'workflow-1',
|
|
22
|
+
workflowType: 'MyWorkflow'
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const workflowInfo = {
|
|
26
|
+
attempt: 1,
|
|
27
|
+
continuedFromExecutionRunId: undefined,
|
|
28
|
+
firstExecutionRunId: 'first-run',
|
|
29
|
+
parent: undefined,
|
|
30
|
+
root: undefined,
|
|
31
|
+
runId: 'run-1',
|
|
32
|
+
runStartTime,
|
|
33
|
+
startTime,
|
|
34
|
+
workflowId: 'workflow-1',
|
|
35
|
+
workflowType: 'MyWorkflow',
|
|
36
|
+
memo: { traceInfo: { runId: 'root-run' } }
|
|
37
|
+
};
|
|
38
|
+
|
|
10
39
|
vi.mock( '@temporalio/workflow', () => ( {
|
|
11
40
|
workflowInfo: ( ...args ) => workflowInfoMock( ...args ),
|
|
12
41
|
proxySinks: () => ( {
|
|
@@ -53,7 +82,7 @@ describe( 'workflow interceptors', () => {
|
|
|
53
82
|
beforeEach( () => {
|
|
54
83
|
vi.clearAllMocks();
|
|
55
84
|
isCancellationMock.mockReturnValue( false );
|
|
56
|
-
workflowInfoMock.mockReturnValue(
|
|
85
|
+
workflowInfoMock.mockReturnValue( workflowInfo );
|
|
57
86
|
} );
|
|
58
87
|
|
|
59
88
|
describe( 'HeadersInjectionInterceptor', () => {
|
|
@@ -64,12 +93,19 @@ describe( 'workflow interceptors', () => {
|
|
|
64
93
|
const input = { headers: { existing: 'header' }, activityType: 'MyWorkflow#step1' };
|
|
65
94
|
const next = vi.fn().mockResolvedValue( 'result' );
|
|
66
95
|
|
|
67
|
-
memoToHeadersMock.mockReturnValue( {
|
|
96
|
+
memoToHeadersMock.mockReturnValue( { traceInfo: workflowInfo.memo.traceInfo, workflowDetails } );
|
|
68
97
|
|
|
69
98
|
const out = await interceptor.scheduleActivity( input, next );
|
|
70
99
|
|
|
71
|
-
expect( memoToHeadersMock ).toHaveBeenCalledWith( {
|
|
72
|
-
|
|
100
|
+
expect( memoToHeadersMock ).toHaveBeenCalledWith( {
|
|
101
|
+
traceInfo: workflowInfo.memo.traceInfo,
|
|
102
|
+
workflowDetails
|
|
103
|
+
} );
|
|
104
|
+
expect( input.headers ).toEqual( {
|
|
105
|
+
existing: 'header',
|
|
106
|
+
traceInfo: workflowInfo.memo.traceInfo,
|
|
107
|
+
workflowDetails
|
|
108
|
+
} );
|
|
73
109
|
expect( next ).toHaveBeenCalledWith( input );
|
|
74
110
|
expect( out ).toBe( 'result' );
|
|
75
111
|
} );
|
|
@@ -77,8 +113,8 @@ describe( 'workflow interceptors', () => {
|
|
|
77
113
|
it( 'merges stepOptions with memo.activityOptions when stepOptions exist for activityType', async () => {
|
|
78
114
|
stepOptionsDefault['MyWorkflow#step1'] = { scheduleToCloseTimeout: 60 };
|
|
79
115
|
workflowInfoMock.mockReturnValue( {
|
|
80
|
-
|
|
81
|
-
memo: {
|
|
116
|
+
...workflowInfo,
|
|
117
|
+
memo: { traceInfo: workflowInfo.memo.traceInfo, activityOptions: { heartbeatTimeout: 10 } }
|
|
82
118
|
} );
|
|
83
119
|
memoToHeadersMock.mockReturnValue( {} );
|
|
84
120
|
deepMergeMock.mockReturnValue( { heartbeatTimeout: 10, scheduleToCloseTimeout: 60 } );
|