Skip to content

Commit

Permalink
fix actions (microsoft#6476)
Browse files Browse the repository at this point in the history
Co-authored-by: Soroush <[email protected]>
  • Loading branch information
hatpick and sorgh authored Mar 18, 2021
1 parent 897b8d2 commit 3afb128
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 21 deletions.
38 changes: 17 additions & 21 deletions Composer/packages/client/src/shell/actionApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,18 @@ import {
deepCopyActions,
deleteActions as destructActions,
FieldProcessorAsync,
walkAdaptiveActionList,
LgType,
LgMetaData,
LgTemplateRef,
LuType,
LuMetaData,
LuType,
walkAdaptiveActionList,
} from '@bfc/shared';
import { LuIntentSection, MicrosoftIDialog } from '@botframework-composer/types';

import TelemetryClient from '../telemetry/TelemetryClient';

import { useLgApi } from './lgApi';
import { useLuApi } from './luApi';
import { deserializeLgTemplate, serializeLgTemplate } from './utils';

export const useActionApi = (projectId: string) => {
const { getLgTemplates, removeLgTemplates, addLgTemplate } = useLgApi(projectId);
Expand All @@ -37,20 +36,16 @@ export const useActionApi = (projectId: string) => {

const createLgTemplate = async (
lgFileId: string,
toId: string,
lgText: string,
hostActionId: string,
hostActionData: MicrosoftIDialog,
hostFieldName: string
): Promise<string> => {
if (!lgText) return '';
const newLgType = new LgType(hostActionData.$kind, hostFieldName).toString();
const newLgTemplateName = new LgMetaData(newLgType, hostActionId).toString();
const newLgTemplateRefStr = new LgTemplateRef(newLgTemplateName).toString();
await addLgTemplate(lgFileId, newLgTemplateName, lgText);
return newLgTemplateRefStr;
return await deserializeLgTemplate(lgFileId, toId, lgText, hostActionData, hostFieldName, addLgTemplate);
};

const readLgTemplate = (lgText: string) => {
const readLgTemplate = (lgText: string, fromId: string) => {
if (!lgText) return '';

const inputLgRef = LgTemplateRef.parse(lgText);
Expand All @@ -59,12 +54,13 @@ export const useActionApi = (projectId: string) => {
const lgTemplates = getLgTemplates(inputLgRef.name);
if (!Array.isArray(lgTemplates) || !lgTemplates.length) return lgText;

const targetTemplate = lgTemplates.find((x) => x.name === inputLgRef.name);
return targetTemplate ? targetTemplate.body : lgText;
const serializedLg = serializeLgTemplate(inputLgRef.name, fromId, lgText, lgTemplates);

return serializedLg;
};

const createLuIntent = async (
luFildId: string,
luFileId: string,
intent: LuIntentSection | undefined,
hostResourceId: string,
hostResourceData: MicrosoftIDialog
Expand All @@ -74,7 +70,7 @@ export const useActionApi = (projectId: string) => {
const newLuIntentType = new LuType(hostResourceData.$kind).toString();
const newLuIntentName = new LuMetaData(newLuIntentType, hostResourceId).toString();
const newLuIntent: LuIntentSection = { ...intent, Name: newLuIntentName };
await addLuIntent(luFildId, newLuIntentName, newLuIntent);
await addLuIntent(luFileId, newLuIntentName, newLuIntent);
return newLuIntentName;
};

Expand All @@ -90,7 +86,7 @@ export const useActionApi = (projectId: string) => {
});
// '- hi' -> 'SendActivity_1234'
const referenceLgText: FieldProcessorAsync<string> = async (fromId, fromAction, toId, toAction, lgFieldName) =>
createLgTemplate(dialogId, fromAction[lgFieldName] as string, toId, toAction, lgFieldName);
createLgTemplate(dialogId, toId, fromAction[lgFieldName] as string, toAction, lgFieldName);

// LuIntentSection -> 'TextInput_Response_1234'
const referenceLuIntent: FieldProcessorAsync<any> = async (fromId, fromAction, toId, toAction) => {
Expand All @@ -106,7 +102,7 @@ export const useActionApi = (projectId: string) => {
async function copyActions(dialogId: string, actions: MicrosoftIDialog[]) {
// 'SendActivity_1234' -> '- hi'
const dereferenceLg: FieldProcessorAsync<string> = async (fromId, fromAction, toId, toAction, lgFieldName) =>
readLgTemplate(fromAction[lgFieldName] as string);
readLgTemplate(fromAction[lgFieldName] as string, fromId);

// 'TextInput_Response_1234' -> LuIntentSection | undefined
const dereferenceLu: FieldProcessorAsync<any> = async (fromId, fromAction, toId, toAction) => {
Expand All @@ -129,10 +125,6 @@ export const useActionApi = (projectId: string) => {
return copiedAction;
}

async function deleteAction(dialogId: string, action: MicrosoftIDialog) {
return deleteActions(dialogId, [action]);
}

async function deleteActions(dialogId: string, actions: MicrosoftIDialog[]) {
actions.forEach(({ $kind }) => {
TelemetryClient.track('ActionDeleted', { type: $kind });
Expand All @@ -144,6 +136,10 @@ export const useActionApi = (projectId: string) => {
);
}

async function deleteAction(dialogId: string, action: MicrosoftIDialog) {
return deleteActions(dialogId, [action]);
}

return {
constructAction,
constructActions,
Expand Down
112 changes: 112 additions & 0 deletions Composer/packages/client/src/shell/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { LgMetaData, LgTemplateRef, LgType } from '@bfc/shared';
import { LgTemplate, MicrosoftIDialog, ShellApi } from '@botframework-composer/types';

type SerializableLg = {
originalId: string;
mainTemplateBody?: string;
relatedLgTemplateBodies?: Record<string, string>;
};

/**
* Serializes Lg template to JSON format.
* @param templateName Name of the template to be serialized.
* @param fromId Original id of the wrapper template.
* @param lgText Body of the template.
* @param lgTemplates List of all available Lg templates
* @returns A serialized string representing the Lg template.
*/
export const serializeLgTemplate = (
templateName: string,
fromId: string,
lgText: string,
lgTemplates: LgTemplate[]
) => {
const lgTemplate = lgTemplates.find((x) => x.name === templateName);

if (!lgTemplate) {
return '';
}

const exprRegex = /^\${(.*)\(\)}$/;
const serializableLg: SerializableLg = {
originalId: fromId,
mainTemplateBody: lgTemplate?.body,
};

// This section serializes structured responses.
if (lgTemplate?.properties?.$type === 'Activity') {
for (const responseType of ['Text', 'Speak', 'Attachments']) {
if (lgTemplate.properties[responseType]) {
const subTemplateItems = Array.isArray(lgTemplate.properties[responseType])
? (lgTemplate.properties[responseType] as string[])
: ([lgTemplate.properties[responseType]] as string[]);
for (const subTemplateItem of subTemplateItems) {
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) {
serializableLg.relatedLgTemplateBodies = {};
}
serializableLg.relatedLgTemplateBodies[subTemplateId] = subTemplate.body;
}
}
}
}
}
}

return lgTemplate ? JSON.stringify(serializableLg) : lgText;
};

/**
* Deserialize serialized Lg template and create all required templates.
* @param lgFileId Lg file id that hosts the template.
* @param toId New wrapper if for the Lg template.
* @param lgText Serialized body of the template.
* @param hostActionData Hosting dialog data.
* @param hostFieldName Hosting field name.
* @param addLgTemplate Api for creating a new template.
* @returns Deserialized template expression.
*/
export const deserializeLgTemplate = async (
lgFileId: string,
toId: string,
lgText: string,
hostActionData: MicrosoftIDialog,
hostFieldName: string,
addLgTemplate: ShellApi['addLgTemplate']
) => {
const newLgType = new LgType(hostActionData.$kind, hostFieldName).toString();
const newLgTemplateName = new LgMetaData(newLgType, toId).toString();
const newLgTemplateRefStr = new LgTemplateRef(newLgTemplateName).toString();

try {
const serializableLg = JSON.parse(lgText) as SerializableLg;
// It's a serialized JSON string
const { originalId, mainTemplateBody, relatedLgTemplateBodies } = serializableLg;

const pattern = `${originalId}`;
// eslint-disable-next-line security/detect-non-literal-regexp
const regex = new RegExp(pattern, 'g');

// Re-create related Lg templates
if (relatedLgTemplateBodies) {
for (const subTemplateId of Object.keys(relatedLgTemplateBodies)) {
const subTemplateBody = relatedLgTemplateBodies[subTemplateId];
await addLgTemplate(lgFileId, subTemplateId.replace(regex, toId), subTemplateBody);
}
}

// Create the target Lg template
await addLgTemplate(lgFileId, newLgTemplateName, mainTemplateBody?.replace(regex, toId) ?? '');
} catch {
// It's a normal string, just create the target Lg template
await addLgTemplate(lgFileId, newLgTemplateName, lgText);
}
return newLgTemplateRefStr;
};

0 comments on commit 3afb128

Please sign in to comment.