Skip to content

Commit b7593c8

Browse files
Fix workflow input presentation (#620)
* fix input presentation * fix lint * update input json in mock * add missing fields * run lint fix * add missing fields in test --------- Co-authored-by: Adhitya Mamallan <[email protected]>
1 parent 517328a commit b7593c8

11 files changed

+309
-20
lines changed

server/helpers/parse-json-lines.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright (c) 2024 Uber Technologies Inc.
2+
//
3+
//
4+
// Permission is hereby granted, free of charge, to any person obtaining a copy
5+
// of this software and associated documentation files (the "Software"), to deal
6+
// in the Software without restriction, including without limitation the rights
7+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
// copies of the Software, and to permit persons to whom the Software is
9+
// furnished to do so, subject to the following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included in
12+
// all copies or substantial portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
// THE SOFTWARE.
21+
22+
function parseJsonLines(input) {
23+
if (!input) {
24+
return null;
25+
}
26+
27+
// Split the input by new lines
28+
const lines = input.split('\n');
29+
30+
const jsonArray = [];
31+
let currentJson = '';
32+
33+
lines.forEach(line => {
34+
currentJson += line; // Append the line to the current JSON string
35+
36+
try {
37+
// Try to parse the current JSON string
38+
const jsonObject = JSON.parse(currentJson);
39+
40+
// If successful, add the object to the array
41+
jsonArray.push(jsonObject);
42+
// Reset currentJson for the next JSON object
43+
currentJson = '';
44+
} catch {
45+
// If parsing fails, keep appending lines until we get a valid JSON
46+
}
47+
});
48+
49+
// Handle case where the last JSON object might be malformed
50+
if (currentJson.trim() !== '') {
51+
try {
52+
const jsonObject = JSON.parse(currentJson);
53+
54+
jsonArray.push(jsonObject);
55+
} catch {
56+
console.error(
57+
'Error parsing JSON string:',
58+
currentJson,
59+
',Original Input:',
60+
input
61+
);
62+
}
63+
}
64+
65+
return jsonArray;
66+
}
67+
68+
module.exports = parseJsonLines;
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright (c) 2024 Uber Technologies Inc.
2+
//
3+
//
4+
// Permission is hereby granted, free of charge, to any person obtaining a copy
5+
// of this software and associated documentation files (the "Software"), to deal
6+
// in the Software without restriction, including without limitation the rights
7+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
// copies of the Software, and to permit persons to whom the Software is
9+
// furnished to do so, subject to the following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included in
12+
// all copies or substantial portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
// THE SOFTWARE.
21+
22+
import parseJsonLines from './parse-json-lines';
23+
24+
describe('parseJsonLines', () => {
25+
// empty data checks
26+
it('should return null if input is null', () => {
27+
const input = null;
28+
const expected = null;
29+
30+
expect(parseJsonLines(input)).toEqual(expected);
31+
});
32+
33+
it('should return null if input is undefined', () => {
34+
const input = undefined;
35+
const expected = null;
36+
37+
expect(parseJsonLines(input)).toEqual(expected);
38+
});
39+
40+
it('should return null if input is an empty string', () => {
41+
const input = '';
42+
const expected = null;
43+
44+
expect(parseJsonLines(input)).toEqual(expected);
45+
});
46+
// end of empty data checks
47+
48+
it('should parse base64 encoded JSON lines correctly', () => {
49+
const input = `{"name": "John", "age": 30}\n{"name": "Jane", "age": 25}`;
50+
const expected = [
51+
{ name: 'John', age: 30 },
52+
{ name: 'Jane', age: 25 },
53+
];
54+
55+
expect(parseJsonLines(input)).toEqual(expected);
56+
});
57+
58+
it('should handle base64 encoded JSON with \\n within string values', () => {
59+
const input = `{"name": "John\\nDoe", "age": 30}\n{"name": "Alice", "city": "Wonderland"}`;
60+
const expected = [
61+
{ name: 'John\nDoe', age: 30 },
62+
{ name: 'Alice', city: 'Wonderland' },
63+
];
64+
65+
expect(parseJsonLines(input)).toEqual(expected);
66+
});
67+
68+
it('should handle base64 encoded malformed JSON gracefully', () => {
69+
const input = `{"name": "John", "age": 30}\n{malformed JSON}`;
70+
const expected = [{ name: 'John', age: 30 }];
71+
72+
expect(parseJsonLines(input)).toEqual(expected);
73+
});
74+
it('should handle base64 encoded mix of numbers, strings, arrays, nulls', () => {
75+
const input = `42\n"Hello, World!"\n[1, 2, 3]\nnull`;
76+
const expected = [42, 'Hello, World!', [1, 2, 3], null];
77+
78+
expect(parseJsonLines(input)).toEqual(expected);
79+
});
80+
81+
it('should handle base64 encoded JSON objects with mixed types', () => {
82+
const input = `{"number": 123, "string": "test", "array": [1, "two", 3], "nullValue": null}`;
83+
const expected = [
84+
{ number: 123, string: 'test', array: [1, 'two', 3], nullValue: null },
85+
];
86+
87+
expect(parseJsonLines(input)).toEqual(expected);
88+
});
89+
90+
it('should handle base64 encoded multiple mixed JSON objects', () => {
91+
const input = `{"a": 1, "b": "text"}\n{"c": [1, 2, 3], "d": null}\n42\n"Hello"`;
92+
const expected = [
93+
{ a: 1, b: 'text' },
94+
{ c: [1, 2, 3], d: null },
95+
42,
96+
'Hello',
97+
];
98+
99+
expect(parseJsonLines(input)).toEqual(expected);
100+
});
101+
});

server/middleware/grpc-client/format-response/format-history-event-details/format-signal-external-workflow-execution-initiated-event-attributes.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
// THE SOFTWARE.
2121

2222
const atob = require('atob');
23-
const formatPayload = require('../format-payload');
23+
const formatInputPayload = require('../format-input-payload');
2424

2525
const formatSignalExternalWorkflowExecutionInitiatedEventAttributes = ({
2626
control,
@@ -31,7 +31,7 @@ const formatSignalExternalWorkflowExecutionInitiatedEventAttributes = ({
3131
...eventAttributes,
3232
control: control ? parseInt(atob(control)) : null,
3333
decisionTaskCompletedEventId: parseInt(decisionTaskCompletedEventId),
34-
input: formatPayload(input),
34+
input: formatInputPayload(input),
3535
});
3636

3737
module.exports = formatSignalExternalWorkflowExecutionInitiatedEventAttributes;

server/middleware/grpc-client/format-response/format-history-event-details/format-start-child-workflow-execution-initiated-event-attributes.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
const atob = require('atob');
2323
const formatEnum = require('../format-enum');
24-
const formatPayload = require('../format-payload');
24+
const formatInputPayload = require('../format-input-payload');
2525
const formatPayloadMap = require('../format-payload-map');
2626
const formatDurationToSeconds = require('../format-duration-to-seconds');
2727
const formatRetryPolicy = require('./format-retry-policy');
@@ -50,7 +50,7 @@ const formatStartChildWorkflowExecutionInitiatedEventAttributes = ({
5050
executionStartToCloseTimeout
5151
),
5252
header: formatPayloadMap(header, 'fields'),
53-
input: formatPayload(input),
53+
input: formatInputPayload(input),
5454
memo: formatPayloadMap(memo, 'fields'),
5555
parentClosePolicy: formatEnum(parentClosePolicy, 'PARENT_CLOSE_POLICY'),
5656
retryPolicy: formatRetryPolicy(retryPolicy),

server/middleware/grpc-client/format-response/format-history-event-details/format-workflow-execution-continued-as-new-event-attributes.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
const formatEnum = require('../format-enum');
2323
const formatFailureDetails = require('../format-failure-details');
24-
const formatPayload = require('../format-payload');
24+
const formatInputPayload = require('../format-input-payload');
2525
const formatPayloadMap = require('../format-payload-map');
2626
const formatDurationToSeconds = require('../format-duration-to-seconds');
2727

@@ -49,7 +49,7 @@ const formatWorkflowExecutionContinuedAsNewEventAttributes = ({
4949
failureReason: failure?.reason || '',
5050
header: formatPayloadMap(header, 'fields'),
5151
initiator: formatEnum(initiator, 'CONTINUE_AS_NEW_INITIATOR'),
52-
input: formatPayload(input),
52+
input: formatInputPayload(input),
5353
memo: formatPayloadMap(memo, 'fields'),
5454
searchAttributes: formatPayloadMap(searchAttributes, 'indexedFields'),
5555
taskList: {

server/middleware/grpc-client/format-response/format-history-event-details/format-workflow-execution-signaled-event-attributes.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@
1919
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2020
// THE SOFTWARE.
2121

22-
const formatPayload = require('../format-payload');
22+
const formatInputPayload = require('../format-input-payload');
2323

2424
const formatWorkflowExecutionSignaledEventAttributes = ({
2525
input,
2626
...eventAttributes
2727
}) => ({
2828
...eventAttributes,
29-
input: formatPayload(input),
29+
input: formatInputPayload(input),
3030
});
3131

3232
module.exports = formatWorkflowExecutionSignaledEventAttributes;

server/middleware/grpc-client/format-response/format-history-event-details/format-workflow-execution-started-event-attributes.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
const formatEnum = require('../format-enum');
2323
const formatFailureDetails = require('../format-failure-details');
24-
const formatPayload = require('../format-payload');
24+
const formatInputPayload = require('../format-input-payload');
2525
const formatPayloadMap = require('../format-payload-map');
2626
const formatTimestampToDatetime = require('../format-timestamp-to-datetime');
2727
const formatDurationToSeconds = require('../format-duration-to-seconds');
@@ -56,7 +56,7 @@ const formatWorkflowExecutionStartedEventAttributes = ({
5656
kind: formatEnum(taskList?.kind, 'TASK_LIST_KIND'),
5757
name: taskList?.name || null,
5858
},
59-
input: formatPayload(input),
59+
input: formatInputPayload(input),
6060
executionStartToCloseTimeoutSeconds: formatDurationToSeconds(
6161
executionStartToCloseTimeout
6262
),
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright (c) 2024 Uber Technologies Inc.
2+
//
3+
//
4+
// Permission is hereby granted, free of charge, to any person obtaining a copy
5+
// of this software and associated documentation files (the "Software"), to deal
6+
// in the Software without restriction, including without limitation the rights
7+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
// copies of the Software, and to permit persons to whom the Software is
9+
// furnished to do so, subject to the following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included in
12+
// all copies or substantial portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
// THE SOFTWARE.
21+
22+
const atob = require('atob');
23+
const parseJsonLines = require('../../../helpers/parse-json-lines');
24+
25+
const formatInputPayload = payload => {
26+
const data = payload?.data;
27+
28+
if (!data) {
29+
return null;
30+
}
31+
32+
const parsedData = atob(data);
33+
34+
return parseJsonLines(parsedData);
35+
};
36+
37+
module.exports = formatInputPayload;
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright (c) 2024 Uber Technologies Inc.
2+
//
3+
//
4+
// Permission is hereby granted, free of charge, to any person obtaining a copy
5+
// of this software and associated documentation files (the "Software"), to deal
6+
// in the Software without restriction, including without limitation the rights
7+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
// copies of the Software, and to permit persons to whom the Software is
9+
// furnished to do so, subject to the following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included in
12+
// all copies or substantial portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
// THE SOFTWARE.
21+
22+
import formatInputPayload from './format-input-payload';
23+
24+
describe('formatInputPayload', () => {
25+
// empty data checks
26+
it('should return null if payload is null', () => {
27+
const input = null;
28+
const expected = null;
29+
30+
expect(formatInputPayload(input)).toEqual(expected);
31+
});
32+
33+
it('should return null if payload is undefined', () => {
34+
const input = undefined;
35+
const expected = null;
36+
37+
expect(formatInputPayload(input)).toEqual(expected);
38+
});
39+
40+
it('should return null if data is missing', () => {
41+
const input = {};
42+
const expected = null;
43+
44+
expect(formatInputPayload(input)).toEqual(expected);
45+
});
46+
47+
it('should return null if data is null', () => {
48+
const input = { data: null };
49+
const expected = null;
50+
51+
expect(formatInputPayload(input)).toEqual(expected);
52+
});
53+
54+
it('should return null if data is an empty string', () => {
55+
const input = { data: '' };
56+
const expected = null;
57+
58+
expect(formatInputPayload(input)).toEqual(expected);
59+
});
60+
// end of empty data checks
61+
62+
it('should parse base64 encoded JSON lines correctly', () => {
63+
const input = {
64+
data: btoa(`{"name": "John", "age": 30}\n{"name": "Jane", "age": 25}`),
65+
};
66+
const expected = [
67+
{ name: 'John', age: 30 },
68+
{ name: 'Jane', age: 25 },
69+
];
70+
71+
expect(formatInputPayload(input)).toEqual(expected);
72+
});
73+
});

0 commit comments

Comments
 (0)