diff --git a/Composer/packages/client/src/shell/utils.ts b/Composer/packages/client/src/shell/utils.ts index 6ecf068bd4..ec170d4c7f 100644 --- a/Composer/packages/client/src/shell/utils.ts +++ b/Composer/packages/client/src/shell/utils.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { LgMetaData, LgTemplateRef, LgType, extractTemplateNameFromExpression } from '@bfc/shared'; +import { LgMetaData, LgTemplateRef, LgType } from '@bfc/shared'; import { LgTemplate, MicrosoftIDialog, ShellApi } from '@botframework-composer/types'; type SerializableLg = { @@ -30,6 +30,7 @@ export const serializeLgTemplate = ( return ''; } + const exprRegex = /^\${(.*)\(\)}$/; const serializableLg: SerializableLg = { originalId: fromId, mainTemplateBody: lgTemplate?.body, @@ -43,8 +44,9 @@ export const serializeLgTemplate = ( ? (lgTemplate.properties[responseType] as string[]) : ([lgTemplate.properties[responseType]] as string[]); for (const subTemplateItem of subTemplateItems) { - const subTemplateId = extractTemplateNameFromExpression(subTemplateItem.trim()); - if (subTemplateId) { + const matched = subTemplateItem.trim().match(exprRegex); + if (matched && matched.length > 1) { + const subTemplateId = matched[1]; const subTemplate = lgTemplates.find((x) => x.name === subTemplateId); if (subTemplate) { if (!serializableLg.relatedLgTemplateBodies) { diff --git a/Composer/packages/lib/code-editor/src/lg/ModalityPivot.tsx b/Composer/packages/lib/code-editor/src/lg/ModalityPivot.tsx index bd14f2db0f..5b9a8b95b4 100644 --- a/Composer/packages/lib/code-editor/src/lg/ModalityPivot.tsx +++ b/Composer/packages/lib/code-editor/src/lg/ModalityPivot.tsx @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { CodeEditorSettings, LgTemplate, TelemetryClient, extractTemplateNameFromExpression } from '@bfc/shared'; +import { CodeEditorSettings, LgTemplate, TelemetryClient } from '@bfc/shared'; import { FluentTheme, FontSizes } from '@uifabric/fluent-theme'; import formatMessage from 'format-message'; import { IconButton } from 'office-ui-fabric-react/lib/Button'; @@ -20,7 +20,11 @@ import mergeWith from 'lodash/mergeWith'; import { LGOption } from '../utils'; import { ItemWithTooltip } from '../components/ItemWithTooltip'; -import { getTemplateId, structuredResponseToString } from '../utils/structuredResponse'; +import { + extractTemplateNameFromExpression, + getTemplateId, + structuredResponseToString, +} from '../utils/structuredResponse'; import { AttachmentModalityEditor } from './modalityEditors/AttachmentModalityEditor'; import { SpeechModalityEditor } from './modalityEditors/SpeechModalityEditor'; diff --git a/Composer/packages/lib/code-editor/src/lg/constants.ts b/Composer/packages/lib/code-editor/src/lg/constants.ts index f3c9d0b1bf..1408bfee0a 100644 --- a/Composer/packages/lib/code-editor/src/lg/constants.ts +++ b/Composer/packages/lib/code-editor/src/lg/constants.ts @@ -22,7 +22,7 @@ export type LgCardTemplateType = typeof lgCardAttachmentTemplates[number]; export const cardTemplates: Record = { adaptive: `> To learn more Adaptive Cards format, read the documentation at > https://docs.microsoft.com/en-us/adaptive-cards/getting-started/bots -- \${{ +- \`\`\`\${json({ "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", "version": "1.2", "type": "AdaptiveCard", @@ -34,7 +34,7 @@ export const cardTemplates: Record = { "isSubtle": false } ] -}}`, +})}\`\`\``, hero: `[HeroCard title = subtitle = diff --git a/Composer/packages/lib/code-editor/src/lg/modalityEditors/AttachmentModalityEditor.tsx b/Composer/packages/lib/code-editor/src/lg/modalityEditors/AttachmentModalityEditor.tsx index 7955b77be4..2da8010773 100644 --- a/Composer/packages/lib/code-editor/src/lg/modalityEditors/AttachmentModalityEditor.tsx +++ b/Composer/packages/lib/code-editor/src/lg/modalityEditors/AttachmentModalityEditor.tsx @@ -4,13 +4,14 @@ import formatMessage from 'format-message'; import React from 'react'; import { IDropdownOption, DropdownMenuItemType } from 'office-ui-fabric-react/lib/Dropdown'; -import { CodeEditorSettings, extractTemplateNameFromExpression } from '@bfc/shared'; +import { CodeEditorSettings } from '@bfc/shared'; import { AttachmentsStructuredResponseItem, AttachmentLayoutStructuredResponseItem, CommonModalityEditorProps, } from '../types'; +import { extractTemplateNameFromExpression } from '../../utils/structuredResponse'; import { ModalityEditorContainer } from './ModalityEditorContainer'; import { AttachmentArrayEditor } from './AttachmentArrayEditor'; @@ -45,11 +46,7 @@ const AttachmentModalityEditor = React.memo( (newItems: string[]) => { setItems(newItems); onUpdateResponseTemplate({ - Attachments: { - kind: 'Attachments', - value: newItems.map((item) => `\${json(${item}())}`), - valueType: 'direct', - }, + Attachments: { kind: 'Attachments', value: newItems.map((item) => `\${${item}()}`), valueType: 'direct' }, }); }, [setItems, onUpdateResponseTemplate] diff --git a/Composer/packages/lib/code-editor/src/utils/structuredResponse.ts b/Composer/packages/lib/code-editor/src/utils/structuredResponse.ts index b5040f272e..500f6f41f2 100644 --- a/Composer/packages/lib/code-editor/src/utils/structuredResponse.ts +++ b/Composer/packages/lib/code-editor/src/utils/structuredResponse.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { LgTemplate, extractTemplateNameFromExpression } from '@bfc/shared'; +import { LgTemplate } from '@bfc/shared'; import { activityTemplateType } from '../lg/constants'; import { @@ -20,6 +20,7 @@ import { } from '../lg/types'; const subTemplateNameRegex = /\${(.*)}/; +const templateNameExtractRegex = /\${(.*)(\(.*\))\s*}/; const defaultIndent = ' '; const getStructuredResponseHelper = (value: unknown, kind: 'Text' | 'Speak' | 'Attachments') => { @@ -112,6 +113,14 @@ export const getStructuredResponseFromTemplate = (lgTemplate?: LgTemplate): Part return Object.keys(structuredResponse).length ? structuredResponse : undefined; }; +/** + * Extracts template name from an LG expression + * ${templateName(params)} => templateName + * @param expression Expression to extract template name from. + */ +export const extractTemplateNameFromExpression = (expression: string): string | undefined => + expression.match(templateNameExtractRegex)?.[1]?.trim(); + export const structuredResponseToString = (structuredResponse: PartialStructuredResponse): string => { const keys = Object.keys(structuredResponse); diff --git a/Composer/packages/lib/shared/__tests__/lgUtils/stringBuilders/lgStringUtils.test.ts b/Composer/packages/lib/shared/__tests__/lgUtils/stringBuilders/lgStringUtils.test.ts index 853164b297..0ef0643726 100644 --- a/Composer/packages/lib/shared/__tests__/lgUtils/stringBuilders/lgStringUtils.test.ts +++ b/Composer/packages/lib/shared/__tests__/lgUtils/stringBuilders/lgStringUtils.test.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { parseTemplateBody, extractTemplateNameFromExpression } from '../../../src'; +import { parseTemplateBody } from '../../../src'; const normalTemplateBody = `- variation0 - variation1 @@ -87,14 +87,4 @@ describe('lgStringUtils', () => { expect(items.length).toBe(0); expect(items).toEqual([]); }); - - it('extractTemplateNameFromExpression: Extracts template name from an LG expression', () => { - const templateName = extractTemplateNameFromExpression('${templateName()}'); - expect(templateName).toEqual('templateName'); - }); - - it('extractTemplateNameFromExpression: Extracts template name from an LG expression in json method', () => { - const templateName = extractTemplateNameFromExpression('${json(templateName())}'); - expect(templateName).toEqual('templateName'); - }); }); diff --git a/Composer/packages/lib/shared/src/lgUtils/index.ts b/Composer/packages/lib/shared/src/lgUtils/index.ts index 386f45cefd..665d823de7 100644 --- a/Composer/packages/lib/shared/src/lgUtils/index.ts +++ b/Composer/packages/lib/shared/src/lgUtils/index.ts @@ -7,9 +7,4 @@ export { default as LgTemplateRef } from './models/LgTemplateRef'; export { default as LgType } from './models/LgType'; export { extractLgTemplateRefs } from './parsers/parseLgTemplateRef'; export { LgNamePattern } from './parsers/lgPatterns'; -export { - TemplateBodyItem, - parseTemplateBody, - templateBodyItemsToString, - extractTemplateNameFromExpression, -} from './stringBuilders/lgStringUtils'; +export { TemplateBodyItem, parseTemplateBody, templateBodyItemsToString } from './stringBuilders/lgStringUtils'; diff --git a/Composer/packages/lib/shared/src/lgUtils/stringBuilders/lgStringUtils.ts b/Composer/packages/lib/shared/src/lgUtils/stringBuilders/lgStringUtils.ts index 9bf959abf1..d907bb6bbe 100644 --- a/Composer/packages/lib/shared/src/lgUtils/stringBuilders/lgStringUtils.ts +++ b/Composer/packages/lib/shared/src/lgUtils/stringBuilders/lgStringUtils.ts @@ -1,6 +1,6 @@ +/* eslint-disable security/detect-unsafe-regex */ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -/* eslint-disable security/detect-unsafe-regex */ export type TemplateBodyItem = { kind: 'newline' | 'variation' | 'comment'; @@ -106,17 +106,3 @@ export const templateBodyItemsToString = (items: TemplateBodyItem[]) => } }) .join('\n'); - -const templateNameExtractRegex = /\${(?.*)(\(.*\))\s*}/; -const jsonTemplateNameExtractRegex = /\${json\((?.*)(\(.*\))\s*}/; - -/** - * Extracts template name from an LG expression - * ${templateName(params)} => templateName - * ${json(templateName(params))} => templateName - * @param expression Expression to extract template name from. - */ -export const extractTemplateNameFromExpression = (expression: string): string | undefined => - expression.startsWith('${json(') - ? expression.match(jsonTemplateNameExtractRegex)?.groups?.templateName?.trim() - : expression.match(templateNameExtractRegex)?.groups?.templateName?.trim(); diff --git a/Composer/packages/test-utils/src/react/jest.config.ts b/Composer/packages/test-utils/src/react/jest.config.ts index 859544db5c..7c960ed249 100644 --- a/Composer/packages/test-utils/src/react/jest.config.ts +++ b/Composer/packages/test-utils/src/react/jest.config.ts @@ -18,7 +18,6 @@ const babelConfig = { require.resolve('@babel/plugin-transform-runtime'), require.resolve('@babel/plugin-proposal-optional-chaining'), require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'), - require.resolve('@babel/plugin-transform-named-capturing-groups-regex'), ], }; diff --git a/Composer/packages/ui-plugins/lg/src/LgWidget/useLgTemplate.ts b/Composer/packages/ui-plugins/lg/src/LgWidget/useLgTemplate.ts index fab82af0ef..8c924725f6 100644 --- a/Composer/packages/ui-plugins/lg/src/LgWidget/useLgTemplate.ts +++ b/Composer/packages/ui-plugins/lg/src/LgWidget/useLgTemplate.ts @@ -1,11 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { LgTemplateRef, parseTemplateBody, extractTemplateNameFromExpression } from '@bfc/shared'; +import { LgTemplateRef, parseTemplateBody } from '@bfc/shared'; import { LgTemplate, useShellApi } from '@bfc/extension-client'; import { locateLgTemplatePosition } from '../locateLgTemplatePosition'; +const exprRegex = /^\${(.*)\(\)}$/; + const getAttachmentDisplayText = (template: LgTemplate) => { const cardType = template.properties?.$type as string; const title = (template.properties?.title as string) ?? ''; @@ -29,9 +31,10 @@ const getLgTemplateTextData = ( if (responseType === 'Text' || responseType === 'Speak') { const subTemplateItem = template.properties[responseType]; if (subTemplateItem && typeof subTemplateItem === 'string') { - const subTemplateId = extractTemplateNameFromExpression(subTemplateItem.trim()); + const matched = subTemplateItem.trim().match(exprRegex); //If it's a template - if (subTemplateId) { + if (matched && matched.length > 1) { + const subTemplateId = matched[1]; const subTemplate = templates.find((x) => x.name === subTemplateId); // If found template and it matches auto-generated names if (subTemplate) { @@ -54,9 +57,10 @@ const getLgTemplateTextData = ( : [template.properties[responseType]]) as string[]; const subTemplateItem = subTemplateItems[0]; if (subTemplateItem && typeof subTemplateItem === 'string') { - const subTemplateId = extractTemplateNameFromExpression(subTemplateItem.trim()); - // If it's a template - if (subTemplateId) { + const matched = subTemplateItem.trim().match(exprRegex); + //If it's a template + if (matched && matched.length > 1) { + const subTemplateId = matched[1]; const subTemplate = templates.find((x) => x.name === subTemplateId); if (subTemplate) { acc[responseType] = {