Skip to content

Commit

Permalink
Fix workflow input presentation (#620)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
Assem-Hafez and adhityamamallan authored Jul 29, 2024
1 parent 517328a commit b7593c8
Show file tree
Hide file tree
Showing 11 changed files with 309 additions and 20 deletions.
68 changes: 68 additions & 0 deletions server/helpers/parse-json-lines.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright (c) 2024 Uber Technologies Inc.
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

function parseJsonLines(input) {
if (!input) {
return null;
}

// Split the input by new lines
const lines = input.split('\n');

const jsonArray = [];
let currentJson = '';

lines.forEach(line => {
currentJson += line; // Append the line to the current JSON string

try {
// Try to parse the current JSON string
const jsonObject = JSON.parse(currentJson);

// If successful, add the object to the array
jsonArray.push(jsonObject);
// Reset currentJson for the next JSON object
currentJson = '';
} catch {
// If parsing fails, keep appending lines until we get a valid JSON
}
});

// Handle case where the last JSON object might be malformed
if (currentJson.trim() !== '') {
try {
const jsonObject = JSON.parse(currentJson);

jsonArray.push(jsonObject);
} catch {
console.error(
'Error parsing JSON string:',
currentJson,
',Original Input:',
input
);
}
}

return jsonArray;
}

module.exports = parseJsonLines;
101 changes: 101 additions & 0 deletions server/helpers/parse-json-lines.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright (c) 2024 Uber Technologies Inc.
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import parseJsonLines from './parse-json-lines';

describe('parseJsonLines', () => {
// empty data checks
it('should return null if input is null', () => {
const input = null;
const expected = null;

expect(parseJsonLines(input)).toEqual(expected);
});

it('should return null if input is undefined', () => {
const input = undefined;
const expected = null;

expect(parseJsonLines(input)).toEqual(expected);
});

it('should return null if input is an empty string', () => {
const input = '';
const expected = null;

expect(parseJsonLines(input)).toEqual(expected);
});
// end of empty data checks

it('should parse base64 encoded JSON lines correctly', () => {
const input = `{"name": "John", "age": 30}\n{"name": "Jane", "age": 25}`;
const expected = [
{ name: 'John', age: 30 },
{ name: 'Jane', age: 25 },
];

expect(parseJsonLines(input)).toEqual(expected);
});

it('should handle base64 encoded JSON with \\n within string values', () => {
const input = `{"name": "John\\nDoe", "age": 30}\n{"name": "Alice", "city": "Wonderland"}`;
const expected = [
{ name: 'John\nDoe', age: 30 },
{ name: 'Alice', city: 'Wonderland' },
];

expect(parseJsonLines(input)).toEqual(expected);
});

it('should handle base64 encoded malformed JSON gracefully', () => {
const input = `{"name": "John", "age": 30}\n{malformed JSON}`;
const expected = [{ name: 'John', age: 30 }];

expect(parseJsonLines(input)).toEqual(expected);
});
it('should handle base64 encoded mix of numbers, strings, arrays, nulls', () => {
const input = `42\n"Hello, World!"\n[1, 2, 3]\nnull`;
const expected = [42, 'Hello, World!', [1, 2, 3], null];

expect(parseJsonLines(input)).toEqual(expected);
});

it('should handle base64 encoded JSON objects with mixed types', () => {
const input = `{"number": 123, "string": "test", "array": [1, "two", 3], "nullValue": null}`;
const expected = [
{ number: 123, string: 'test', array: [1, 'two', 3], nullValue: null },
];

expect(parseJsonLines(input)).toEqual(expected);
});

it('should handle base64 encoded multiple mixed JSON objects', () => {
const input = `{"a": 1, "b": "text"}\n{"c": [1, 2, 3], "d": null}\n42\n"Hello"`;
const expected = [
{ a: 1, b: 'text' },
{ c: [1, 2, 3], d: null },
42,
'Hello',
];

expect(parseJsonLines(input)).toEqual(expected);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
// THE SOFTWARE.

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

const formatSignalExternalWorkflowExecutionInitiatedEventAttributes = ({
control,
Expand All @@ -31,7 +31,7 @@ const formatSignalExternalWorkflowExecutionInitiatedEventAttributes = ({
...eventAttributes,
control: control ? parseInt(atob(control)) : null,
decisionTaskCompletedEventId: parseInt(decisionTaskCompletedEventId),
input: formatPayload(input),
input: formatInputPayload(input),
});

module.exports = formatSignalExternalWorkflowExecutionInitiatedEventAttributes;
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

const atob = require('atob');
const formatEnum = require('../format-enum');
const formatPayload = require('../format-payload');
const formatInputPayload = require('../format-input-payload');
const formatPayloadMap = require('../format-payload-map');
const formatDurationToSeconds = require('../format-duration-to-seconds');
const formatRetryPolicy = require('./format-retry-policy');
Expand Down Expand Up @@ -50,7 +50,7 @@ const formatStartChildWorkflowExecutionInitiatedEventAttributes = ({
executionStartToCloseTimeout
),
header: formatPayloadMap(header, 'fields'),
input: formatPayload(input),
input: formatInputPayload(input),
memo: formatPayloadMap(memo, 'fields'),
parentClosePolicy: formatEnum(parentClosePolicy, 'PARENT_CLOSE_POLICY'),
retryPolicy: formatRetryPolicy(retryPolicy),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

const formatEnum = require('../format-enum');
const formatFailureDetails = require('../format-failure-details');
const formatPayload = require('../format-payload');
const formatInputPayload = require('../format-input-payload');
const formatPayloadMap = require('../format-payload-map');
const formatDurationToSeconds = require('../format-duration-to-seconds');

Expand Down Expand Up @@ -49,7 +49,7 @@ const formatWorkflowExecutionContinuedAsNewEventAttributes = ({
failureReason: failure?.reason || '',
header: formatPayloadMap(header, 'fields'),
initiator: formatEnum(initiator, 'CONTINUE_AS_NEW_INITIATOR'),
input: formatPayload(input),
input: formatInputPayload(input),
memo: formatPayloadMap(memo, 'fields'),
searchAttributes: formatPayloadMap(searchAttributes, 'indexedFields'),
taskList: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

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

const formatWorkflowExecutionSignaledEventAttributes = ({
input,
...eventAttributes
}) => ({
...eventAttributes,
input: formatPayload(input),
input: formatInputPayload(input),
});

module.exports = formatWorkflowExecutionSignaledEventAttributes;
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

const formatEnum = require('../format-enum');
const formatFailureDetails = require('../format-failure-details');
const formatPayload = require('../format-payload');
const formatInputPayload = require('../format-input-payload');
const formatPayloadMap = require('../format-payload-map');
const formatTimestampToDatetime = require('../format-timestamp-to-datetime');
const formatDurationToSeconds = require('../format-duration-to-seconds');
Expand Down Expand Up @@ -56,7 +56,7 @@ const formatWorkflowExecutionStartedEventAttributes = ({
kind: formatEnum(taskList?.kind, 'TASK_LIST_KIND'),
name: taskList?.name || null,
},
input: formatPayload(input),
input: formatInputPayload(input),
executionStartToCloseTimeoutSeconds: formatDurationToSeconds(
executionStartToCloseTimeout
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) 2024 Uber Technologies Inc.
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

const atob = require('atob');
const parseJsonLines = require('../../../helpers/parse-json-lines');

const formatInputPayload = payload => {
const data = payload?.data;

if (!data) {
return null;
}

const parsedData = atob(data);

return parseJsonLines(parsedData);
};

module.exports = formatInputPayload;
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) 2024 Uber Technologies Inc.
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import formatInputPayload from './format-input-payload';

describe('formatInputPayload', () => {
// empty data checks
it('should return null if payload is null', () => {
const input = null;
const expected = null;

expect(formatInputPayload(input)).toEqual(expected);
});

it('should return null if payload is undefined', () => {
const input = undefined;
const expected = null;

expect(formatInputPayload(input)).toEqual(expected);
});

it('should return null if data is missing', () => {
const input = {};
const expected = null;

expect(formatInputPayload(input)).toEqual(expected);
});

it('should return null if data is null', () => {
const input = { data: null };
const expected = null;

expect(formatInputPayload(input)).toEqual(expected);
});

it('should return null if data is an empty string', () => {
const input = { data: '' };
const expected = null;

expect(formatInputPayload(input)).toEqual(expected);
});
// end of empty data checks

it('should parse base64 encoded JSON lines correctly', () => {
const input = {
data: btoa(`{"name": "John", "age": 30}\n{"name": "Jane", "age": 25}`),
};
const expected = [
{ name: 'John', age: 30 },
{ name: 'Jane', age: 25 },
];

expect(formatInputPayload(input)).toEqual(expected);
});
});
Loading

0 comments on commit b7593c8

Please sign in to comment.