Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(designer): Remove double quotes wrapping from uncasted single tokens in array editor #4670

Merged
merged 5 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion libs/designer-ui/src/lib/arrayeditor/index.tsx
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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,
}),
};
};
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
export interface SegmentParserOptions {
readonly?: boolean;
tokensEnabled?: boolean;
removeSingleTokenQuotesWrapping?: boolean;
}

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