Skip to content

Commit

Permalink
fix(Designer): Update serialization handling for the dictionary edito…
Browse files Browse the repository at this point in the history
…r with tokens (#4124)

* Update serialization handling for the dictionary editor with tokens

* Add 3 unit tests to help catch regressions going forward

* Update logic for serializing collapsed dictionary to reflect new changes

---------

Co-authored-by: Aleks Dziewulska <aleksd@microsoft.com>
  • Loading branch information
Aleks-gdb and Aleks Dziewulska committed Feb 8, 2024
1 parent 5692086 commit 60fe43a
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { DictionaryEditorItemProps } from '..';
import constants from '../../constants';
import type { ValueSegment } from '../../editor';
import { convertStringToSegments } from '../../editor/base/utils/editorToSegment';
import { getChildrenNodes, removeQuotes } from '../../editor/base/utils/helper';
import { getChildrenNodesWithTokenInterpolation, removeQuotes } from '../../editor/base/utils/helper';
import { guid } from '@microsoft/utils-logic-apps';
import type { LexicalEditor } from 'lexical';
import { $getRoot } from 'lexical';
Expand All @@ -16,7 +16,7 @@ export const serializeDictionary = (
) => {
editor.getEditorState().read(() => {
const nodeMap = new Map<string, ValueSegment>();
const editorString = getChildrenNodes($getRoot(), nodeMap);
const editorString = getChildrenNodesWithTokenInterpolation($getRoot(), nodeMap);
let jsonEditor;
try {
jsonEditor = JSON.parse(editorString);
Expand Down
48 changes: 47 additions & 1 deletion libs/designer-ui/src/lib/editor/base/utils/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ValueSegmentType } from '../../models/parameter';
import { $isTokenNode } from '../nodes/tokenNode';
import { guid } from '@microsoft/utils-logic-apps';
import type { ElementNode } from 'lexical';
import { $getNodeByKey, $isElementNode, $isTextNode } from 'lexical';
import { $getNodeByKey, $isElementNode, $isLineBreakNode, $isTextNode } from 'lexical';

export const removeFirstAndLast = (segments: ValueSegment[], removeFirst?: string, removeLast?: string): ValueSegment[] => {
const n = segments.length - 1;
Expand Down Expand Up @@ -56,6 +56,52 @@ export const getChildrenNodes = (node: ElementNode, nodeMap?: Map<string, ValueS
return text;
};

// interpolate tokens if they have not been interpolated
export const getChildrenNodesWithTokenInterpolation = (node: ElementNode, nodeMap?: Map<string, ValueSegment>): string => {
let text = '';
let lastNodeWasText = '';
let lastNodeWasToken = '';
let numberOfDoubleQuotes = 0;
let numberOfQuotesAdded = 0;
node.getChildren().forEach((child) => {
const childNode = $getNodeByKey(child.getKey());
if (childNode && $isElementNode(childNode)) {
return (text += getChildrenNodesWithTokenInterpolation(childNode, nodeMap));
}
if ($isTextNode(childNode)) {
const childNodeText = childNode.__text.trim();
if (childNodeText.includes('"')) {
numberOfQuotesAdded = 0; // reset, the interpolation will be added with childNode
}
const missingAClosingInterpolation = numberOfQuotesAdded !== 0 && numberOfQuotesAdded % 2 === 1;
if (lastNodeWasToken === childNode.__prev && missingAClosingInterpolation) {
text += `"`;
}
text += childNodeText;
lastNodeWasText = childNode.__key;
} else if ($isTokenNode(childNode)) {
numberOfDoubleQuotes = (text.replace(/\\"/g, '').match(/"/g) || []).length;
if (lastNodeWasText === childNode.__prev && numberOfDoubleQuotes % 2 !== 1) {
text += `"`;
numberOfQuotesAdded++;
}
text += childNode.toString();
nodeMap?.set(childNode.toString(), childNode.convertToSegment());
lastNodeWasToken = childNode.__key;
}
if ($isLineBreakNode(childNode)) {
if (lastNodeWasText === childNode.__prev) {
lastNodeWasText = childNode.__key;
}
if (lastNodeWasToken === childNode.__prev) {
lastNodeWasToken = childNode.__key;
}
}
return text;
});
return text;
};

export const findChildNode = (node: ElementNode, nodeKey: string, tokenType?: TokenType): ValueSegment | null => {
let foundNode: ValueSegment | null = null;
node.getChildren().find((child) => {
Expand Down
2 changes: 1 addition & 1 deletion libs/designer-ui/src/lib/editor/base/utils/keyvalueitem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const convertValueType = (value: ValueSegment[], type?: string): string |
const stringSegments = convertSegmentsToString(value).trim();
// checks for known types
if (
(stringSegments.startsWith('{') && stringSegments.endsWith('}')) ||
(stringSegments.startsWith('@{') && stringSegments.indexOf('}') === stringSegments.length - 1) ||
isNumber(stringSegments) ||
isBoolean(stringSegments) ||
/^\[.*\]$/.test(stringSegments)
Expand Down
183 changes: 183 additions & 0 deletions libs/designer/src/lib/core/utils/parameters/__test__/helper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,189 @@ describe('core/utils/parameters/helper', () => {
'{"newUnb3_1":"@{xpath(xml(triggerBody()), \'string(/*[local-name()=\\"DynamicsSOCSV\\"])\')}"}'
);
});

it('should return key value input from dictionary editor without wrapping single token in @{}', () => {
const parameterValue = [
{
id: '1',
type: ValueSegmentType.LITERAL,
value: '{\n ',
},
{
id: '2',
type: ValueSegmentType.LITERAL,
value: '"',
},
{
id: '3',
type: ValueSegmentType.LITERAL,
value: 'Key of the row',
},
{
id: '4',
type: ValueSegmentType.LITERAL,
value: '"',
},
{
id: '5',
type: ValueSegmentType.LITERAL,
value: ' : ',
},
{
id: '6',
type: ValueSegmentType.TOKEN,
value: 'item()?[\'someItem\']',
token: {
key: 'outputs.$',
tokenType: TokenType.OUTPUTS,
type: 'string',
title: 'someItem',
},
},
{
id: '7',
type: ValueSegmentType.LITERAL,
value: '\n}',
},
],
parameterJson = parameterValueToJSONString(parameterValue);

expect(parameterJson).toEqual(`{"Key of the row":"@item()?['someItem']"}`);
});

it('should return key value input from dictionary editor with wrapping token in @{} to honor the included string and stringify as a whole', () => {
const parameterValue = [
{
id: '1',
type: ValueSegmentType.LITERAL,
value: '{\n ',
},
{
id: '2',
type: ValueSegmentType.LITERAL,
value: '"',
},
{
id: '3',
type: ValueSegmentType.LITERAL,
value: 'Key of the row',
},
{
id: '4',
type: ValueSegmentType.LITERAL,
value: '"',
},
{
id: '5',
type: ValueSegmentType.LITERAL,
value: ' : ',
},
{
id: '6',
type: ValueSegmentType.LITERAL,
value: '"',
},
{
id: '7',
type: ValueSegmentType.LITERAL,
value: 'Value of the row:',
},
{
id: '8',
type: ValueSegmentType.TOKEN,
value: 'item()?[\'someItem\']',
token: {
key: 'outputs.$',
tokenType: TokenType.OUTPUTS,
type: 'string',
title: 'someItem',
},
},
{
id: '9',
type: ValueSegmentType.LITERAL,
value: '"',
},
{
id: '10',
type: ValueSegmentType.LITERAL,
value: '\n}',
},
],
parameterJson = parameterValueToJSONString(parameterValue);

expect(parameterJson).toEqual(`{"Key of the row":"Value of the row:@{item()?['someItem']}"}`);
});

it('should return key value input from dictionary editor with wrapping token in @{} to honor the other token input and stringify as a whole', () => {
const parameterValue = [
{
id: '1',
type: ValueSegmentType.LITERAL,
value: '{\n ',
},
{
id: '2',
type: ValueSegmentType.LITERAL,
value: '"',
},
{
id: '3',
type: ValueSegmentType.LITERAL,
value: 'Key of the row',
},
{
id: '4',
type: ValueSegmentType.LITERAL,
value: '"',
},
{
id: '5',
type: ValueSegmentType.LITERAL,
value: ' : ',
},
{
id: '6',
type: ValueSegmentType.LITERAL,
value: '"',
},
{
id: '7',
type: ValueSegmentType.TOKEN,
value: 'item()?[\'someItem1\']',
token: {
key: 'outputs.$',
tokenType: TokenType.OUTPUTS,
type: 'string',
title: 'someItem1',
},
},
{
id: '8',
type: ValueSegmentType.TOKEN,
value: 'item()?[\'someItem2\']',
token: {
key: 'outputs.$',
tokenType: TokenType.OUTPUTS,
type: 'string',
title: 'someItem2',
},
},
{
id: '9',
type: ValueSegmentType.LITERAL,
value: '"',
},
{
id: '10',
type: ValueSegmentType.LITERAL,
value: '\n}',
},
],
parameterJson = parameterValueToJSONString(parameterValue);

expect(parameterJson).toEqual(`{"Key of the row":"@{item()?['someItem1']}@{item()?['someItem2']}"}`);
});
});

describe('parameterValueToString', () => {
Expand Down

0 comments on commit 60fe43a

Please sign in to comment.