Skip to content

Commit

Permalink
fix(designer): Remove double quotes wrapping from uncasted single tok…
Browse files Browse the repository at this point in the history
…ens in array editor (#4670)

* Remove quotes when converting to segments if single token

* Fix edge cases for casting and multiple tokens

* Add unit tests

* cleanup

---------

Co-authored-by: Shuktika Jain <shj@microsoft.com>
  • Loading branch information
shuktika12163 and Shuktika Jain committed Apr 23, 2024
1 parent 419a9d9 commit a9639dd
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 2 deletions.
2 changes: 1 addition & 1 deletion libs/designer-ui/src/lib/arrayeditor/index.tsx
Expand Up @@ -128,7 +128,7 @@ export const ArrayEditor: React.FC<ArrayEditorProps> = ({
setCollapsedValue(uncastedValue);
if (!collapsed) {
onChange?.({
value: castedValue,
value: suppressCastingForSerialize ? uncastedValue : castedValue,
viewModel: { arrayType, itemSchema, uncastedValue },
});
}
Expand Down
Expand Up @@ -102,6 +102,9 @@ export const parseComplexItems = (
});
return {
castedValue: convertStringToSegments(JSON.stringify(castedArrayVal, null, 4), nodeMap, { tokensEnabled: false }),
uncastedValue: convertStringToSegments(JSON.stringify(uncastedArrayVal, null, 4), nodeMap, { tokensEnabled: true }),
uncastedValue: convertStringToSegments(JSON.stringify(uncastedArrayVal, null, 4), nodeMap, {
tokensEnabled: true,
removeSingleTokenQuotesWrapping: suppressCastingForSerialize,
}),
};
};
Expand Up @@ -57,6 +57,31 @@ describe('lib/editor/base/utils/editorToSegment', () => {
},
value: "body('Create_new_folder')?['{Link}']",
});
const getOneDriveFileContentToken = (): SimplifiedValueSegment => ({
type: 'token',
token: {
key: 'body.$',
name: 'key-body-output',
type: 'string',
title: 'File content',
brandColor: '#0078D4',
icon: 'https://connectoricons-prod.azureedge.net/releases/v1.0.1669/1.0.1669.3522/onedriveforbusiness/icon.png',
description: 'The content of the file.',
tokenType: 'outputs',
required: true,
format: 'binary',
isSecure: false,
source: 'body',
schema: {
format: 'binary',
description: 'The content of the file.',
type: 'string',
'x-ms-summary': 'File content',
},
value: "body('Get_file_content')",
},
value: "body('Get_file_content')",
});

it('does not parse segments into tokens if tokensEnabled is not set', () => {
const nodeMap = new Map<string, ValueSegment>();
Expand Down Expand Up @@ -175,5 +200,105 @@ describe('lib/editor/base/utils/editorToSegment', () => {

expect(simplifiedSegments).toEqual(expectedTokens);
});

it.each<[string, SimplifiedValueSegment[]]>([
// Single token in quotes - removes quotes
[
`Text before "@{variables('abc')}" text after`,
[
{ type: ValueSegmentType.LITERAL, value: 'Text before ' },
getInitializeVariableAbcToken(),
{ type: ValueSegmentType.LITERAL, value: ' text after' },
],
],
// Quotes around words but next to single token - do not remove quotes
[
`Text "before"@{variables('abc')}"text" after`,
[
{ type: ValueSegmentType.LITERAL, value: 'Text "before"' },
getInitializeVariableAbcToken(),
{ type: ValueSegmentType.LITERAL, value: '"text" after' },
],
],
// Single token in quotes in array editor - remove quotes
[
`[{"Name": "sampleName", "ContentBytes": "@{body('Get_file_content')}"}]`,
[
{ type: ValueSegmentType.LITERAL, value: `[{"Name": "sampleName", "ContentBytes": ` },
getOneDriveFileContentToken(),
{ type: ValueSegmentType.LITERAL, value: `}]` },
],
],
[
`[{"Name": "sample\\\"Name", "ContentBytes": "@{body('Get_file_content')}"}]`,
[
{ type: ValueSegmentType.LITERAL, value: `[{"Name": "sample\\\"Name", "ContentBytes": ` },
getOneDriveFileContentToken(),
{ type: ValueSegmentType.LITERAL, value: `}]` },
],
],
// Two tokens in quotes in parameter - keep quotes
[
`[{"Name": "sampleName", "ContentBytes": "@{body('Get_file_content')}@{body('Get_file_content')}"}]`,
[
{ type: ValueSegmentType.LITERAL, value: `[{"Name": "sampleName", "ContentBytes": "` },
getOneDriveFileContentToken(),
getOneDriveFileContentToken(),
{ type: ValueSegmentType.LITERAL, value: `"}]` },
],
],
// remove quotes param is not set - should not remove quotes
])('removes double quotes around single tokens if remove quotes parameter is true', (input, expectedTokens) => {
const nodeMap = new Map<string, ValueSegment>();
nodeMap.set(`@{variables('abc')}`, { id: '', ...getInitializeVariableAbcToken() });
nodeMap.set(`@{body('Get_file_content')}`, { id: '', ...getOneDriveFileContentToken() });

const segments = convertStringToSegments(input, nodeMap, { tokensEnabled: true, removeSingleTokenQuotesWrapping: true });
const simplifiedSegments = segments.map(
(segment): SimplifiedValueSegment => ({
// Remove IDs for easier comparison.
token: segment.token,
type: segment.type,
value: segment.value,
})
);

expect(simplifiedSegments).toEqual(expectedTokens);
});

it.each<[string, SimplifiedValueSegment[]]>([
[
`Text before "@{variables('abc')}" text after`,
[
{ type: ValueSegmentType.LITERAL, value: 'Text before "' },
getInitializeVariableAbcToken(),
{ type: ValueSegmentType.LITERAL, value: '" text after' },
],
],
[
`[{"Name": "sampleName", "ContentBytes": "@{body('Get_file_content')}"}]`,
[
{ type: ValueSegmentType.LITERAL, value: `[{"Name": "sampleName", "ContentBytes": "` },
getOneDriveFileContentToken(),
{ type: ValueSegmentType.LITERAL, value: `"}]` },
],
],
])('keep double quotes around single tokens if remove quotes parameter is false', (input, expectedTokens) => {
const nodeMap = new Map<string, ValueSegment>();
nodeMap.set(`@{variables('abc')}`, { id: '', ...getInitializeVariableAbcToken() });
nodeMap.set(`@{body('Get_file_content')}`, { id: '', ...getOneDriveFileContentToken() });

const segments = convertStringToSegments(input, nodeMap, { tokensEnabled: true, removeSingleTokenQuotesWrapping: false });
const simplifiedSegments = segments.map(
(segment): SimplifiedValueSegment => ({
// Remove IDs for easier comparison.
token: segment.token,
type: segment.type,
value: segment.value,
})
);

expect(simplifiedSegments).toEqual(expectedTokens);
});
});
});
25 changes: 25 additions & 0 deletions libs/designer-ui/src/lib/editor/base/utils/editorToSegment.ts
Expand Up @@ -54,11 +54,13 @@ export const convertStringToSegments = (

let currSegmentType: ValueSegmentType = ValueSegmentType.LITERAL;
let isInQuotedString = false;
let doubleQuotesStarted = false;
let segmentSoFar = '';

for (let currIndex = 0; currIndex < value.length; currIndex++) {
const currChar = value[currIndex];
const nextChar = value[currIndex + 1];
const prevChar = value[currIndex - 1];

if (currChar === `'`) {
if (isInQuotedString) {
Expand All @@ -69,6 +71,11 @@ export const convertStringToSegments = (
}
}

// If unescaped double quotes are encountered in string, they are not part of the value typed by user. It is likely from Json stringification for a key/value.
if (currChar === '"' && prevChar !== '\\' && currSegmentType === ValueSegmentType.LITERAL) {
doubleQuotesStarted = !doubleQuotesStarted;
}

if (!isInQuotedString && currChar === '@' && nextChar === '{') {
if (segmentSoFar) {
// If we found a new token, then even if `currSegmentType` is `ValueSegmentType.TOKEN`, we treat the
Expand Down Expand Up @@ -106,6 +113,24 @@ export const convertStringToSegments = (
}

if (token) {
// If remove quotes param is set, remove the quotes from previous and next segments if it's a single token
if (options?.removeSingleTokenQuotesWrapping && doubleQuotesStarted && returnSegments.length > 0) {
const prevSegment = returnSegments.pop();

// If previous and next segments are not tokens (i.e. this is a single token), and they end and start with quotes, remove the quotes
if (prevSegment?.type === ValueSegmentType.LITERAL && prevSegment?.value.endsWith('"') && nextChar === '"') {
prevSegment.value = prevSegment.value.slice(0, -1);
doubleQuotesStarted = false;

// Skip quotes starting in next segment
currIndex++;
}

if (prevSegment) {
returnSegments.push(prevSegment);
}
}

returnSegments.push(token);
currSegmentType = ValueSegmentType.LITERAL;
segmentSoFar = '';
Expand Down
Expand Up @@ -37,6 +37,7 @@ import {
export interface SegmentParserOptions {
readonly?: boolean;
tokensEnabled?: boolean;
removeSingleTokenQuotesWrapping?: boolean;
}

export const isEmptySegments = (segments: ValueSegment[]): boolean => {
Expand Down

0 comments on commit a9639dd

Please sign in to comment.