From bc4d5d1b15f4d9ada6dba4d412b2d28f0273c1de Mon Sep 17 00:00:00 2001 From: Chris McConnell Date: Fri, 4 Jun 2021 00:26:28 -0700 Subject: [PATCH] Singleton dialog merge was not working properly (#3304) so it was fixed to work and tests were added. Also made it so merge messages better reflect what is going on. Fixed some other minor issues as well. --- .../generation/generator/.vscode/launch.json | 2 +- .../packages/library/src/dialogGenerator.ts | 2 +- .../packages/library/src/mergeAssets.ts | 311 ++-- .../library/templates/standard/askIf.json | 2 +- .../templates/standard/number.template | 2 +- .../packages/library/test/generate.test.ts | 14 +- .../packages/library/test/makeOracles.cmd | 7 +- .../packages/library/test/merge.test.ts | 268 ++- .../merge_data/sandwichMerge-modified.form | 21 +- .../library/test/oracles/sandwichMerge.dialog | 1581 +++++++++++++++++ 10 files changed, 1968 insertions(+), 242 deletions(-) create mode 100644 experimental/generation/generator/packages/library/test/oracles/sandwichMerge.dialog diff --git a/experimental/generation/generator/.vscode/launch.json b/experimental/generation/generator/.vscode/launch.json index c30723b51b..282e5e495f 100644 --- a/experimental/generation/generator/.vscode/launch.json +++ b/experimental/generation/generator/.vscode/launch.json @@ -37,7 +37,7 @@ "999999", "--colors", "-g", - ".*merge: self.*" + ".*" ], "internalConsoleOptions": "openOnSessionStart", "outputCapture": "std", diff --git a/experimental/generation/generator/packages/library/src/dialogGenerator.ts b/experimental/generation/generator/packages/library/src/dialogGenerator.ts index 470fd77229..53292bf056 100644 --- a/experimental/generation/generator/packages/library/src/dialogGenerator.ts +++ b/experimental/generation/generator/packages/library/src/dialogGenerator.ts @@ -77,7 +77,7 @@ export function stringify(val: any, replacer?: any): string { return val } -function computeJSONHash(json: any): string { +export function computeJSONHash(json: any): string { return computeHash(stringify(json)) } diff --git a/experimental/generation/generator/packages/library/src/mergeAssets.ts b/experimental/generation/generator/packages/library/src/mergeAssets.ts index f6f5e4f35b..fd3aa7fccc 100644 --- a/experimental/generation/generator/packages/library/src/mergeAssets.ts +++ b/experimental/generation/generator/packages/library/src/mergeAssets.ts @@ -6,7 +6,7 @@ import * as fs from 'fs-extra'; import * as ppath from 'path'; import * as os from 'os'; -import {assetDirectory, Feedback, FeedbackType, getHashCode, isUnchanged, writeFile, stringify} from './dialogGenerator' +import {assetDirectory, computeJSONHash, Feedback, FeedbackType, getHashCode, isUnchanged, writeFile, stringify} from './dialogGenerator' const {Templates, SwitchCaseBodyContext} = require('botbuilder-lg'); const LUParser = require('@microsoft/bf-lu/lib/parser/lufile/luParser'); @@ -46,7 +46,7 @@ async function getHashCodeFromFile(fileList: string[], fileName: string): Promis * @param feedback Callback function for progress and errors. */ async function copySingleFile(sourcePath: string, destPath: string, fileName: string, sourceFileList: string[], feedback: Feedback): Promise { - const filePaths = sourceFileList.filter(file => file.match(fileName)) + const filePaths = sourceFileList.filter(file => file.endsWith(fileName)) if (filePaths.length !== 0) { const sourceFilePath = filePaths[0] const destFilePath = sourceFilePath.replace(sourcePath, destPath) @@ -67,7 +67,7 @@ async function copySingleFile(sourcePath: string, destPath: string, fileName: st * @param feedback Callback function for progress and errors. */ async function writeToFile(sourcePath: string, destPath: string, fileName: string, sourceFileList: string[], val: string, feedback: Feedback): Promise { - const filePaths = sourceFileList.filter(file => file.match(fileName)) + const filePaths = sourceFileList.filter(file => file.endsWith(fileName)) if (filePaths.length !== 0) { const sourceFilePath = filePaths[0] const destFilePath = sourceFilePath.replace(sourcePath, destPath) @@ -130,8 +130,7 @@ function refFilename(ref: string, feedback: Feedback): string { * 4) If a property exists in both old and new schema, but a file is not present * in the new directory, the file should not be copied over again and * references should not be added. - * 5) The order of .dialog triggers should be respected, i.e. if changed by the - * user it should remain the same. + * 5) The order of .dialog triggers should be the old order with new stuff coming after everything before it in old. * 6) If a file has changed and cannot be updated there will be a message to * merge manually. * @@ -165,7 +164,7 @@ export async function mergeAssets(schemaName: string, oldPath: string, newPath: await mergeDialogs(schemaName, oldPath, oldFileList, newPath, newFileList, mergedPath, locale, oldPropertySet, newPropertySet, feedback) await mergeRootLUFile(schemaName, oldPath, oldFileList, newPath, newFileList, mergedPath, locale, feedback) - await mergeRootFile(schemaName, oldPath, oldFileList, newPath, newFileList, mergedPath, locale, '.lg', oldPropertySet, newPropertySet, feedback) + await mergeRootLGFile(schemaName, oldPath, oldFileList, newPath, newFileList, mergedPath, locale, oldPropertySet, newPropertySet, feedback) await mergeOtherFiles(oldPath, oldFileList, newPath, newFileList, mergedPath, feedback) } } catch (e) { @@ -215,16 +214,15 @@ async function mergeOtherFiles(oldPath: string, oldFileList: string[], newPath: * @param newFileList List of new file paths. * @param mergedPath Path to the folder of the merged asset. * @param locale Locale. - * @param extension .lu or .lg * @param oldPropertySet Property Set from the old .schema file. * @param newPropertySet Property Set from the new .schema file. * @param feedback Callback function for progress and errors. */ -async function mergeRootFile(schemaName: string, oldPath: string, oldFileList: string[], newPath: string, newFileList: string[], mergedPath: string, locale: string, extension: string, oldPropertySet: Set, newPropertySet: Set, feedback: Feedback): Promise { - const outDir = assetDirectory(extension) - const oldText = await fs.readFile(ppath.join(oldPath, outDir, locale, `${schemaName}.${locale}${extension}`), 'utf8') +async function mergeRootLGFile(schemaName: string, oldPath: string, oldFileList: string[], newPath: string, newFileList: string[], mergedPath: string, locale: string, oldPropertySet: Set, newPropertySet: Set, feedback: Feedback): Promise { + const outDir = assetDirectory('.lg') + const oldText = await fs.readFile(ppath.join(oldPath, outDir, locale, `${schemaName}.${locale}${'.lg'}`), 'utf8') const oldRefs = oldText.split(os.EOL) - const newText = await fs.readFile(ppath.join(newPath, outDir, locale, `${schemaName}.${locale}${extension}`), 'utf8') + const newText = await fs.readFile(ppath.join(newPath, outDir, locale, `${schemaName}.${locale}${'.lg'}`), 'utf8') const newRefs = newText.split(os.EOL) const resultRefs: string[] = [] @@ -248,9 +246,7 @@ async function mergeRootFile(schemaName: string, oldPath: string, oldFileList: s resultRefs.push(ref) const file = refFilename(ref, feedback) if (file.match(`${extractedProperty}Value`)) { - if (extension === '.lg') { - await changeEntityEnumLG(oldPath, oldFileList, newFileList, mergedPath, file, feedback) - } + await changeEntityEnumLG(oldPath, oldFileList, newPath, newFileList, mergedPath, file, feedback) } else { if (await !isOldUnchanged(oldFileList, file) && await getHashCodeFromFile(oldFileList, file) !== await getHashCodeFromFile(newFileList, file)) { changedMessage(file, feedback) @@ -288,7 +284,7 @@ async function mergeRootFile(schemaName: string, oldPath: string, oldFileList: s val = val + os.EOL + oldText.substring(patternIndex) } - await writeToFile(oldPath, mergedPath, `${schemaName}.${locale}${extension}`, oldFileList, val, feedback) + await writeToFile(oldPath, mergedPath, `${schemaName}.${locale}.lg`, oldFileList, val, feedback) } /** @@ -352,7 +348,7 @@ async function mergeRootLUFile(schemaName: string, oldPath: string, oldFileList: } const valuePattern = /{@?([^=]*Value)\s*=([^}]*)}/g -const commentOutPattern = /^>\s*-/m +const commentOutPattern = /^>\s*-/m /** * @description: Get the set of deleted utterance patterns. @@ -361,7 +357,7 @@ const commentOutPattern = /^>\s*-/m * @param delUtteranceSet Set of deleted utterance patterns. */ async function getDeletedUtteranceSet(filename: string, oldFileList: string[], delUtteranceSet: Set): Promise { - const filePath = oldFileList.filter(file => file.match(filename))[0] + const filePath = oldFileList.filter(file => file.endsWith(filename))[0] const text = await fs.readFile(filePath, 'utf8') const lines = text.split(os.EOL) for (const line of lines) { @@ -382,21 +378,26 @@ async function getDeletedUtteranceSet(filename: string, oldFileList: string[], d * @param feedback Callback function for progress and errors. */ async function updateGeneratedLUFile(filename: string, newFileList: string[], newPath: string, mergedPath: string, delUtteranceSet: Set, feedback: Feedback): Promise { - const filePath = newFileList.filter(file => file.match(filename))[0] + const filePath = newFileList.filter(file => file.endsWith(filename))[0] const text = await fs.readFile(filePath, 'utf8') const lines = text.split(os.EOL) const resultLines: string[] = [] + let changed = false for (const line of lines) { - const newLine = await generatePatternUtterance(line) - if (delUtteranceSet.has(newLine)) { - resultLines.push(`>${line}`) + const newLine = await generatePatternUtterance(line) + if (delUtteranceSet.has(newLine)) { + changed = true + resultLines.push(`>${line}`) } else { resultLines.push(line) } - } - let val = resultLines.join(os.EOL) - await writeToFile(newPath, mergedPath, filename, newFileList, val, feedback) + if (changed) { + let val = resultLines.join(os.EOL) + await writeToFile(newPath, mergedPath, filename, newFileList, val, feedback) + } else { + await copySingleFile(newPath, mergedPath, filename, newFileList, feedback) + } } /** @@ -404,7 +405,7 @@ async function updateGeneratedLUFile(filename: string, newFileList: string[], ne * @param line Line of the lu file. */ async function generatePatternUtterance(line: string): Promise { - line = line.replace(/{@?[^=]+=|}|^>+\s*-?\s*|^\s*-\s*|^\s+|\s+$/gm, '') + line = line.replace(/{@?[^=]+=|}|^>+\s*-?\s*|^\s*-\s*|^\s+|\s+$/gm, '') return line } @@ -419,23 +420,26 @@ async function generatePatternUtterance(line: string): Promise { * @param feedback Callback function for progress and errors. */ async function updateCustomLUFile(schemaName: string, oldPath: string, newPath: string, oldFileList: string[], mergedPath: string, locale: string, feedback: Feedback): Promise { - const customLuFilePath = oldFileList.filter(file => file.match(`${schemaName}-custom.${locale}.lu`))[0] + const filename = `${schemaName}-custom.${locale}.lu` + const customLuFilePath = oldFileList.filter(file => file.endsWith(filename))[0] const text = await fs.readFile(customLuFilePath, 'utf8') const lines = text.split(os.EOL) const resultLines: string[] = [] const propertyValueSynonyms = await getSynonyms(schemaName, newPath, locale) + let changed = false for (const line of lines) { if (line.match(valuePattern)) { const newLine = await replaceLine(line, propertyValueSynonyms) resultLines.push(newLine) + changed = true } else { resultLines.push(line) } } const val = resultLines.join(os.EOL) - await writeToFile(oldPath, mergedPath, `${schemaName}-custom.${locale}.lu`, oldFileList, val, feedback) + await writeToFile(oldPath, mergedPath, filename, oldFileList, val, feedback) } /** @@ -449,7 +453,7 @@ async function replaceLine(line: string, propertyValueSynonyms: Map { +async function changeEntityEnumLG(oldPath: string, oldFileList: string[], newPath: string, newFileList: string[], mergedPath: string, filename: string, feedback: Feedback): Promise { const oldFilePath = oldFileList.filter(file => file.match(filename))[0] - const oldText = await fs.readFile(oldFilePath, 'utf8') - const oldStatements = oldText.split(os.EOL) - const oldTemplates = Templates.parseText(oldText) + if (await isOldUnchanged(oldFileList, filename)) { + await copySingleFile(newPath, mergedPath, filename, newFileList, feedback) + } else { + const oldText = await fs.readFile(oldFilePath, 'utf8') + const oldStatements = oldText.split(os.EOL) + const oldTemplates = Templates.parseText(oldText) - const newFilePath = newFileList.filter(file => file.match(filename))[0] - const newText = await fs.readFile(newFilePath, 'utf8') - const newStatements = newText.split(os.EOL) - const newTemplates = Templates.parseText(newText) + const newFilePath = newFileList.filter(file => file.endsWith(filename))[0] + const newText = await fs.readFile(newFilePath, 'utf8') + const newStatements = newText.split(os.EOL) + const newTemplates = Templates.parseText(newText) - let mergedStatements: string[] = [] + let mergedStatements: string[] = [] - const recordPart: object[] = [] + const recordPart: object[] = [] - for (const oldTemplate of oldTemplates) { - const oldBody = oldTemplate.templateBodyParseTree - if (oldBody === undefined) { - continue - } - if (oldBody instanceof SwitchCaseBodyContext) { - for (const newTemplate of newTemplates) { - if (newTemplate.name !== oldTemplate.name) { - continue - } - const newBody = newTemplate.templateBodyParseTree - if (newBody instanceof SwitchCaseBodyContext) { - const newSwitchStatements: string[] = [] - const newEnumValueMap = new Map() - const oldEnumEntitySet = new Set() - const newRules = newBody.switchCaseTemplateBody().switchCaseRule() - for (const rule of newRules) { - const state = rule.switchCaseStat() - // get enumEntity and its following statements - if (state.text.match('\s*-\s*CASE:')) { - const enumEntity = state.text.replace('-CASE:${', '').replace('}', '') - const start = state.start.line + newTemplate.sourceRange.range.start.line - newEnumValueMap.set(enumEntity, start) - } + for (const oldTemplate of oldTemplates) { + const oldBody = oldTemplate.templateBodyParseTree + if (oldBody === undefined) { + continue + } + if (oldBody instanceof SwitchCaseBodyContext) { + for (const newTemplate of newTemplates) { + if (newTemplate.name !== oldTemplate.name) { + continue } - const {startIndex, endIndex} = parseLGTemplate(oldTemplate, oldBody, oldStatements, newStatements, newEnumValueMap, oldEnumEntitySet, newSwitchStatements) - const statementInfo = { - start: startIndex, end: endIndex, newSStatements: newSwitchStatements + const newBody = newTemplate.templateBodyParseTree + if (newBody instanceof SwitchCaseBodyContext) { + const newSwitchStatements: string[] = [] + const newEnumValueMap = new Map() + const oldEnumEntitySet = new Set() + const newRules = newBody.switchCaseTemplateBody().switchCaseRule() + for (const rule of newRules) { + const state = rule.switchCaseStat() + // get enumEntity and its following statements + if (state.text.match('\s*-\s*CASE:')) { + const enumEntity = state.text.replace('-CASE:${', '').replace('}', '') + const start = state.start.line + newTemplate.sourceRange.range.start.line + newEnumValueMap.set(enumEntity, start) + } + } + const {startIndex, endIndex} = parseLGTemplate(oldTemplate, oldBody, oldStatements, newStatements, newEnumValueMap, oldEnumEntitySet, newSwitchStatements) + const statementInfo = { + start: startIndex, end: endIndex, newSStatements: newSwitchStatements + } + recordPart.push(statementInfo) } - recordPart.push(statementInfo) } } } - } - if (recordPart.length !== 0) { - let startSplit = 0 - const arrList: [string[]] = [[]] - for (const obj of recordPart) { - const arr = oldStatements.slice(startSplit, obj['start']) - arrList.push(arr) - arrList.push(obj['newSStatements']) - startSplit = obj['end'] - } + if (recordPart.length !== 0) { + let startSplit = 0 + const arrList: [string[]] = [[]] + for (const obj of recordPart) { + const arr = oldStatements.slice(startSplit, obj['start']) + arrList.push(arr) + arrList.push(obj['newSStatements']) + startSplit = obj['end'] + } - if (startSplit !== oldStatements.length) { - const arr = oldStatements.slice(startSplit) - arrList.push(arr) - } + if (startSplit !== oldStatements.length) { + const arr = oldStatements.slice(startSplit) + arrList.push(arr) + } - for (const arr of arrList) { - mergedStatements = mergedStatements.concat(arr) + for (const arr of arrList) { + mergedStatements = mergedStatements.concat(arr) + } + const val = mergedStatements.join(os.EOL) + await writeToFile(oldPath, mergedPath, filename, oldFileList, val, feedback) + } else { + await writeToFile(oldPath, mergedPath, filename, oldFileList, oldText, feedback) } - const val = mergedStatements.join(os.EOL) - await writeToFile(oldPath, mergedPath, filename, oldFileList, val, feedback) - } else { - await writeToFile(oldPath, mergedPath, filename, oldFileList, oldText, feedback) } } @@ -611,6 +619,30 @@ function parseLGTemplate(oldTemplate: any, oldBody: any, oldStatements: string[] return {startIndex, endIndex} } +// Insert a trigger from newTriggers into mergedTriggers so that it comes after every trigger before it +function insertTrigger(trigger: number, newTriggers: string[], mergedTriggers: string[]) { + const name = newTriggers[trigger] + let max = -1 + for (let i = 0; i < trigger; ++i) { + let pos = newTriggers.indexOf(name) + if (pos > max) { + max = pos + } + } + mergedTriggers.splice(max + 1, 0, name) +} + +async function isUnchangedTrigger(trigger: any, fileList: string[], feedback: Feedback): Promise { + if (typeof trigger === 'string') { + return await isOldUnchanged(fileList, trigger + '.dialog') + } else { + const oldHash = trigger.$Generator + delete trigger.$Generator + const newHash = computeJSONHash(trigger) + return oldHash === newHash + } +} + /** * @description: Merge two main .dialog files following the trigger ordering rule. * @param schemaName Name of the .schema file. @@ -626,81 +658,68 @@ function parseLGTemplate(oldTemplate: any, oldBody: any, oldStatements: string[] */ async function mergeDialogs(schemaName: string, oldPath: string, oldFileList: string[], newPath: string, newFileList: string[], mergedPath: string, locale: string, oldPropertySet: Set, newPropertySet: Set, feedback: Feedback): Promise { const oldObj = JSON.parse(await fs.readFile(ppath.join(oldPath, schemaName + '.dialog'), 'utf8')) - const newObj = JSON.parse(await fs.readFile(ppath.join(newPath, schemaName + '.dialog'), 'utf8')) - const newTriggers: string[] = [] const newTriggerMap = new Map() + const mergedTriggers: string[] = [] + const mergedTriggerMap = new Map() - // remove triggers whose property does not exist in new property set - const reducedOldTriggers: string[] = [] - const reducedOldTriggerMap = new Map() - - const mergedTriggers: any[] = [] - - for (const trigger of oldObj['triggers']) { - const triggerName = getTriggerName(trigger) - const extractedProperty = equalPattern(triggerName, oldPropertySet, schemaName) - if (extractedProperty !== undefined) { - if (newPropertySet.has(extractedProperty)) { - reducedOldTriggers.push(triggerName) - reducedOldTriggerMap.set(triggerName, trigger) - } - } else { - reducedOldTriggers.push(triggerName) - reducedOldTriggerMap.set(triggerName, trigger) - } - } - + // Collect new triggers where a trigger is either a string (for reference) or object (inline) + // Generated triggers have $source in object, custom triggers do not for (const trigger of newObj['triggers']) { const triggerName = getTriggerName(trigger) - const extractedProperty = equalPattern(triggerName, oldPropertySet, schemaName) - if (extractedProperty !== undefined && !reducedOldTriggerMap.has(triggerName)) { - continue - } newTriggers.push(triggerName) newTriggerMap.set(triggerName, trigger) } - let i = 0 - while (!reducedOldTriggerMap.has(newTriggers[i]) && i < newTriggers.length) { - const resultMergedTrigger = newTriggerMap.get(newTriggers[i]) - mergedTriggers.push(resultMergedTrigger) - if (typeof resultMergedTrigger === 'string') { - await copySingleFile(newPath, mergedPath, newTriggers[i] + '.dialog', newFileList, feedback) + // Loop over old triggers and create merged triggers + for (let trigger of oldObj['triggers']) { + const triggerName = getTriggerName(trigger) + const isGenerated = equalPattern(triggerName, oldPropertySet, schemaName) + const newTrigger = newTriggerMap.get(triggerName) + if (newTrigger && await isUnchangedTrigger(trigger, oldFileList, feedback)) { + // Use new trigger + trigger = newTrigger + if (typeof trigger === 'string') { + await copySingleFile(newPath, mergedPath, triggerName + '.dialog', newFileList, feedback) + } + } else if (!newTrigger && isGenerated) { + // Drop old generated triggers not in new + trigger = null + } else if (typeof trigger === 'string') { + // Use old trigger + await copySingleFile(oldPath, mergedPath, triggerName + '.dialog', oldFileList, feedback) + } + if (trigger) { + mergedTriggers.push(triggerName) + mergedTriggerMap.set(triggerName, trigger) } - i++ } - let j = 0 - - while (j < reducedOldTriggers.length) { - const resultReducedOldTrigger = reducedOldTriggerMap.get(reducedOldTriggers[j]) - mergedTriggers.push(resultReducedOldTrigger) - if (typeof resultReducedOldTrigger === 'string') { - if (newTriggers.includes(reducedOldTriggers[j]) && !await isOldUnchanged(oldFileList, reducedOldTriggers[j] + '.dialog') && await getHashCodeFromFile(oldFileList, reducedOldTriggers[j] + '.dialog') !== await getHashCodeFromFile(newFileList, reducedOldTriggers[j] + '.dialog')) { - changedMessage(reducedOldTriggers[j] + '.dialog', feedback) - } else { - await copySingleFile(oldPath, mergedPath, reducedOldTriggers[j] + '.dialog', oldFileList, feedback) + // Copy any new triggers not found in merged + for (let i = 0; i < newTriggers.length; ++i) { + const triggerName = newTriggers[i] + if (!mergedTriggerMap.has(triggerName)) { + // Not in merged triggers yet + const trigger = newTriggerMap.get(triggerName) + insertTrigger(i, newTriggers, mergedTriggers) + mergedTriggerMap.set(triggerName, trigger) + if (typeof trigger === 'string') { + await copySingleFile(oldPath, mergedPath, triggerName + '.dialog', oldFileList, feedback) } } - let index = newTriggers.indexOf(reducedOldTriggers[j]) - if (index !== -1) { - index++ - while (index < newTriggers.length && !reducedOldTriggerMap.has(newTriggers[index])) { - const resultMergedTrigger = newTriggerMap.get(newTriggers[index]) - mergedTriggers.push(resultMergedTrigger) - if (typeof resultMergedTrigger === 'string') { - await copySingleFile(newPath, mergedPath, newTriggers[index] + '.dialog', newFileList, feedback) - } - index++ - } - } - j++ } - oldObj['triggers'] = mergedTriggers + // Create array of triggers from newTriggers + const triggers: any[] = [] + for (const triggerName of mergedTriggers) { + triggers.push(mergedTriggerMap.get(triggerName)) + } + + oldObj['triggers'] = triggers await writeToFile(oldPath, mergedPath, schemaName + '.dialog', oldFileList, stringify(oldObj), feedback) + + // Copy recognized dialog await copySingleFile(newPath, mergedPath, schemaName + '.' + locale + '.lu.dialog', newFileList, feedback) } @@ -711,7 +730,7 @@ async function mergeDialogs(schemaName: string, oldPath: string, oldFileList: st function getTriggerName(trigger: any): string { let triggerName: string if (typeof trigger !== 'string') { - triggerName = trigger['$source'] + triggerName = trigger['$source'] ?? JSON.stringify(trigger) } else { triggerName = trigger } @@ -771,7 +790,7 @@ async function parseSchemas(schemaName: string, oldPath: string, newPath: string * @param schemaName Name of the .schema file. * @param newPath Path to the folder of the new asset. */ - async function getSynonyms(schemaName: string, newPath: string, locale: string): Promise>> { +async function getSynonyms(schemaName: string, newPath: string, locale: string): Promise>> { const template = await fs.readFile(ppath.join(newPath, schemaName + '.json'), 'utf8') const newObj = JSON.parse(template) const propertyValueSynonyms = new Map>() @@ -793,6 +812,6 @@ async function parseSchemas(schemaName: string, oldPath: string, newPath: string propertyValueSynonyms.set(`${property}Value`, synonymsSet) } } - + return propertyValueSynonyms } \ No newline at end of file diff --git a/experimental/generation/generator/packages/library/templates/standard/askIf.json b/experimental/generation/generator/packages/library/templates/standard/askIf.json index a8c1d40ec3..1d6577deb1 100644 --- a/experimental/generation/generator/packages/library/templates/standard/askIf.json +++ b/experimental/generation/generator/packages/library/templates/standard/askIf.json @@ -2,7 +2,7 @@ "$askIf": { "type": "string", "$role": "expression", - "title": "AskIF", + "title": "AskIf", "description": "Boolean expression to add for when a property should be asked for. For example, the property IsPregnant could have a condition like $gender = 'female'." } } \ No newline at end of file diff --git a/experimental/generation/generator/packages/library/templates/standard/number.template b/experimental/generation/generator/packages/library/templates/standard/number.template index ff7b0e8f27..edb26da2cb 100644 --- a/experimental/generation/generator/packages/library/templates/standard/number.template +++ b/experimental/generation/generator/packages/library/templates/standard/number.template @@ -3,7 +3,7 @@ "type": "number", "$generator": { "title": "Number", - "description": "Map a LUIS number entity like '1' or '3.2' into a number and units.", + "description": "Map a LUIS number entity like '1' or '3.2' into a number.", "allOf": [ { "$ref": "minmax.json" diff --git a/experimental/generation/generator/packages/library/test/generate.test.ts b/experimental/generation/generator/packages/library/test/generate.test.ts index 46f21ec5b3..1987fde78c 100644 --- a/experimental/generation/generator/packages/library/test/generate.test.ts +++ b/experimental/generation/generator/packages/library/test/generate.test.ts @@ -41,9 +41,9 @@ function removeHash(val: string): string { } // NOTE: If you update dialog:merge functionality you need to execute the makeOracles.cmd to update them -async function compareToOracle(name: string, oraclePath?: string): Promise { - let generatedPath = ppath.join(tempDir, name) - const generateds = removeHash(await fs.readFile(generatedPath, 'utf8')) +export async function compareToOracle(path: string, oraclePath?: string): Promise { + const name = ppath.basename(path) + const generateds = removeHash(await fs.readFile(path, 'utf8')) oraclePath = oraclePath ? ppath.join(tempDir, oraclePath) : ppath.join('test/oracles', name) const oracle = await fs.readFile(oraclePath, 'utf8') const oracles = removeHash(gen.normalizeEOL(oracle)) @@ -71,7 +71,7 @@ async function compareToOracle(name: string, oraclePath?: string): Promise console.log(`Oracle : ${oracles.substring(start, end)}`) console.log(`Generated: ${generateds.substring(start, end)}`) assert(false, - `${ppath.resolve(generatedPath)} does not match oracle ${ppath.resolve(oraclePath)}`) + `${ppath.resolve(path)} does not match oracle ${ppath.resolve(oraclePath)}`) } } @@ -160,7 +160,7 @@ describe('dialog:generate library', async () => { try { console.log('\n\nTranscript test') assert.ok(await generateTest('test/transcripts/sandwich.transcript', 'sandwich', output, false), 'Could not generate test script') - await compareToOracle('sandwich.test.dialog') + await compareToOracle(ppath.join(tempDir, 'sandwich.test.dialog')) } catch (e) { assert.fail(e.message) } @@ -170,7 +170,7 @@ describe('dialog:generate library', async () => { try { console.log('\n\nTranscript test') assert.ok(await generateTest('test/transcripts/addItemWithButton.transcript', 'msmeeting-actions', output, false), 'Could not generate test script') - await compareToOracle('addItemWithButton.test.dialog') + await compareToOracle(ppath.join(tempDir, 'addItemWithButton.test.dialog')) } catch (e) { assert.fail(e.message) } @@ -480,7 +480,7 @@ describe('dialog:generate library', async () => { })), 'Should not have failed generation') assert.strictEqual(errors, 0, 'Wrong number of errors') assert.strictEqual(warnings, 3, 'Wrong number of warnings') - await compareToOracle('unittest_transforms.dialog') + await compareToOracle(ppath.join(tempDir, 'unittest_transforms.dialog')) } catch (e) { assert.fail(e.message) } diff --git a/experimental/generation/generator/packages/library/test/makeOracles.cmd b/experimental/generation/generator/packages/library/test/makeOracles.cmd index 2793303582..31bf484396 100644 --- a/experimental/generation/generator/packages/library/test/makeOracles.cmd +++ b/experimental/generation/generator/packages/library/test/makeOracles.cmd @@ -6,5 +6,10 @@ set ds=..\..\cli\bin\run call node %ds% dialog:generate:test transcripts/sandwich.transcript sandwich -o oracles call node %ds% dialog:generate:test transcripts/addItemWithButton.transcript msmeeting-actions -o oracles + call node %ds% dialog:generate forms/unittest_transforms.form -o %temp%\unittest_transforms -t templates -t templates/override -t template:standard -T addOne -copy %temp%\unittest_transforms\unittest_transforms.dialog oracles \ No newline at end of file +copy %temp%\unittest_transforms\unittest_transforms.dialog oracles + +call node %ds% dialog:generate merge_data/sandwichMerge.form -p sandwichMerge -o %temp%\merge +call node %ds% dialog:generate merge_data/sandwichMerge-modified.form -p sandwichMerge -o %temp%\merge --merge +copy %temp%\merge\sandwichMerge.dialog oracles diff --git a/experimental/generation/generator/packages/library/test/merge.test.ts b/experimental/generation/generator/packages/library/test/merge.test.ts index b947ea7ed4..c75f23b196 100644 --- a/experimental/generation/generator/packages/library/test/merge.test.ts +++ b/experimental/generation/generator/packages/library/test/merge.test.ts @@ -10,8 +10,9 @@ import * as glob from 'globby' import 'mocha' import * as ppath from 'path' import * as os from 'os' -import * as assert from 'assert'; -import * as gen from '../src/dialogGenerator'; +import * as assert from 'assert' +import * as gen from '../src/dialogGenerator' +import {compareToOracle} from './generate.test' type Diff = { file: string, @@ -70,7 +71,7 @@ async function compareDirs(original: string, merged: string): Promise 0) { errors.push(`Found ${removed} in merged files`) } - return found; + return found } -describe('dialog:generate --merge library', async function () { - const output_dir = ppath.join(os.tmpdir(), 'mergeTest') - const merge_data = 'test/merge_data' - const modified_data = `${merge_data}/modified` - const originalSchema = ppath.join(merge_data, 'sandwichMerge.form') - const modifiedSchema = ppath.join(merge_data, 'sandwichMerge-modified.form') - const originalDir = ppath.join(output_dir, 'sandwichMerge-original') - const modifiedDir = ppath.join(output_dir, 'sandwichMerge-modified') - const mergedDir = ppath.join(output_dir, 'sandwichMerge-merged') - - function errorOnly(type: gen.FeedbackType, msg: string): void { - if ((type === gen.FeedbackType.warning && !msg.startsWith('Replace')) || type === gen.FeedbackType.error) { - assert.fail(`${type}: ${msg}`) - } +const output_dir = ppath.join(os.tmpdir(), 'mergeTest') +const merge_data = 'test/merge_data' +const modified_data = `${merge_data}/modified` +const originalSchema = ppath.join(merge_data, 'sandwichMerge.form') +const modifiedSchema = ppath.join(merge_data, 'sandwichMerge-modified.form') +const originalDir = ppath.join(output_dir, 'sandwichMerge-original') +const mergedDir = ppath.join(output_dir, 'sandwichMerge-merged') + +function errorOnly(type: gen.FeedbackType, msg: string): void { + if ((type === gen.FeedbackType.warning && !msg.startsWith('Replace')) || type === gen.FeedbackType.error) { + assert.fail(`${type}: ${msg}`) } +} - function feedback(type: gen.FeedbackType, msg: string): void { - if (type !== gen.FeedbackType.debug) { - console.log(`${type}: ${msg}`) - } - if ((type === gen.FeedbackType.warning && !msg.startsWith('Replace')) || type === gen.FeedbackType.error) { - assert.fail(`${type}: ${msg}`) - } +function feedback(type: gen.FeedbackType, msg: string): void { + if (type !== gen.FeedbackType.debug) { + console.log(`${type}: ${msg}`) + } + if ((type === gen.FeedbackType.warning && !msg.startsWith('Replace')) || type === gen.FeedbackType.error) { + assert.fail(`${type}: ${msg}`) } +} - async function assertContains(file: string, regex: RegExp, errors: string[]) { - const val = await fs.readFile(ppath.join(mergedDir, file), 'utf8') - if (!val.match(regex)) { - errors.push(`${file} does not contain expected ${regex}`) - } +async function assertContains(file: string, regex: RegExp, errors: string[]) { + const val = await fs.readFile(ppath.join(mergedDir, file), 'utf8') + if (!val.match(regex)) { + errors.push(`${file} does not contain expected ${regex}`) } +} - async function assertMissing(file: string, regex: RegExp, errors: string[]) { - const val = await fs.readFile(ppath.join(mergedDir, file), 'utf8') - if (val.match(regex)) { - errors.push(`${file} contains unexpected ${regex}`) - } +async function assertMissing(file: string, regex: RegExp, errors: string[]) { + const val = await fs.readFile(ppath.join(mergedDir, file), 'utf8') + if (val.match(regex)) { + errors.push(`${file} contains unexpected ${regex}`) } +} - async function assertUnchanged(file: string, expected: boolean, errors: string[]) { - const unchanged = await gen.isUnchanged(ppath.join(mergedDir, file)) - if (unchanged !== expected) { - errors.push(`${file} is unexpectedly ${expected ? 'changed' : 'unchanged'}`) - } +async function assertUnchanged(file: string, expected: boolean, errors: string[]) { + const unchanged = await gen.isUnchanged(ppath.join(mergedDir, file)) + if (unchanged !== expected) { + errors.push(`${file} is unexpectedly ${expected ? 'changed' : 'unchanged'}`) } +} - async function copyToMerged(pattern: string) { - const pathPattern = ppath.join(modified_data, pattern).replace(/\\/g, '/') - for (const path of await glob(pathPattern)) { - const file = ppath.relative(modified_data, path) - const dest = ppath.join(mergedDir, file) - console.log(`Modifying ${dest}`) - await fs.copyFile(path, dest) - } +async function copyToMerged(pattern: string) { + const pathPattern = ppath.join(modified_data, pattern).replace(/\\/g, '/') + for (const path of await glob(pathPattern)) { + const file = ppath.relative(modified_data, path) + const dest = ppath.join(mergedDir, file) + console.log(`Modifying ${dest}`) + await fs.copyFile(path, dest) } +} - async function deleteMerged(pattern: string) { - const pathPattern = ppath.join(mergedDir, pattern).replace(/\\/g, '/') - for (const file of await glob(pathPattern)) { - console.log(`Deleting ${file}`) - await fs.unlink(file) - } +async function deleteMerged(pattern: string) { + const pathPattern = ppath.join(mergedDir, pattern).replace(/\\/g, '/') + for (const file of await glob(pathPattern)) { + console.log(`Deleting ${file}`) + await fs.unlink(file) } +} - before(async function () { +function beforeSetup(singleton: boolean) { + return async function () { try { console.log('Deleting output directory') await fs.remove(output_dir) @@ -222,20 +222,17 @@ describe('dialog:generate --merge library', async function () { await gen.generate(originalSchema, { prefix: 'sandwichMerge', outDir: originalDir, - feedback: errorOnly - }) - console.log('Generating modified files') - await gen.generate(modifiedSchema, { - prefix: 'sandwichMerge', - outDir: modifiedDir, + singleton: singleton, feedback: errorOnly }) } catch (e) { assert.fail(e.message) } - }) + } +} - beforeEach(async function () { +function beforeEachSetup() { + return async function () { try { console.log('\n\nCopying original generated to merged') await fs.remove(mergedDir) @@ -243,7 +240,13 @@ describe('dialog:generate --merge library', async function () { } catch (e) { assert.fail(e.message) } - }) + } +} + +describe('dialog:generate --merge files', async function () { + before(beforeSetup(false)) + + beforeEach(beforeEachSetup()) // Ensure merge with no changes is unchanged it('merge: self', async function () { @@ -268,10 +271,11 @@ describe('dialog:generate --merge library', async function () { try { console.log('Modified merge') await gen.generate(modifiedSchema, { - prefix: 'sandwichMerge', - outDir: mergedDir, - merge: true, - feedback}) + prefix: 'sandwichMerge', + outDir: mergedDir, + merge: true, + feedback + }) const comparison = await compareDirs(originalDir, mergedDir) const errors = [] assertAddedProperty(comparison, 'Hobby', errors) @@ -284,7 +288,7 @@ describe('dialog:generate --merge library', async function () { await assertContains('language-generation/en-us/CheeseValue/sandwichMerge-CheeseValue.en-us.lg', /brie/, errors) // Unchanged hash + optional enum fixes = hash updated - await assertUnchanged('language-generation/en-us/BreadValue/sandwichMerge-BreadValue.en-us.lg', false, errors) + await assertUnchanged('language-generation/en-us/BreadValue/sandwichMerge-BreadValue.en-us.lg', true, errors) await assertUnchanged('language-generation/en-us/Name/sandwichMerge-Name.en-us.lg', true, errors) assertCheck(comparison, errors) } catch (e) { @@ -308,10 +312,11 @@ describe('dialog:generate --merge library', async function () { await copyToMerged('dialogs/sandwichMerge-foo-missing.dialog') await deleteMerged('dialogs/Price/sandwichMerge-price-remove-money.dialog') await gen.generate(modifiedSchema, { - prefix: 'sandwichMerge', - outDir: mergedDir, + prefix: 'sandwichMerge', + outDir: mergedDir, merge: true, - feedback}) + feedback + }) const comparison = await compareDirs(originalDir, mergedDir) const errors = [] @@ -320,7 +325,7 @@ describe('dialog:generate --merge library', async function () { await assertUnchanged('sandwichMerge.dialog', false, errors) // Despite enum update, hash updated so unchanged - await assertUnchanged('language-generation/en-us/CheeseValue/sandwichMerge-CheeseValue.en-us.lg', false, errors) + await assertUnchanged('language-generation/en-us/CheeseValue/sandwichMerge-CheeseValue.en-us.lg', true, errors) // Main should still be updated await assertContains('sandwichMerge.dialog', /sandwichMerge-foo/, errors) @@ -343,3 +348,118 @@ describe('dialog:generate --merge library', async function () { } }) }) + +describe('dialog:generate --merge singleton', async function () { + before(beforeSetup(true)) + + beforeEach(beforeEachSetup()) + + // Ensure merge with no changes is unchanged + it('merge singleton: self', async function () { + try { + console.log('Self merging') + await gen.generate(originalSchema, { + outDir: mergedDir, + merge: true, + singleton: true, + feedback + }) + const comparison = await compareDirs(originalDir, mergedDir) + const errors: string[] = [] + assertCompare(comparison, errors, comparison.originalFiles.length) + assertCheck(comparison, errors) + } catch (e) { + assert.fail(e.message) + } + }) + + // Ensure merge with modified schema changes as expected + it('merge singleton: modified', async function () { + try { + console.log('Modified merge') + await gen.generate(modifiedSchema, { + prefix: 'sandwichMerge', + outDir: mergedDir, + merge: true, + singleton: true, + feedback + }) + const comparison = await compareDirs(originalDir, mergedDir) + const errors = [] + assertAddedProperty(comparison, 'Hobby', errors) + assertRemovedProperty(comparison, 'Meat', errors) + assertRemovedProperty(comparison, 'Toppings', errors) + assertRemovedProperty(comparison, 'Sauces', errors) + await assertContains('language-generation/en-us/BreadValue/sandwichMerge-BreadValue.en-us.lg', /black/, errors) + await assertMissing('language-generation/en-us/BreadValue/sandwichMerge-BreadValue.en-us.lg', /white/, errors) + await assertContains('language-generation/en-us/CheeseValue/sandwichMerge-CheeseValue.en-us.lg', /brie/, errors) + + // Unchanged hash + optional enum fixes = hash updated + await assertUnchanged('language-generation/en-us/BreadValue/sandwichMerge-BreadValue.en-us.lg', true, errors) + await assertUnchanged('language-generation/en-us/Name/sandwichMerge-Name.en-us.lg', true, errors) + assertCheck(comparison, errors) + + await compareToOracle(ppath.join(mergedDir, 'sandwichMerge.dialog')) + } catch (e) { + assert.fail(e.message) + } + }) + + // Respect user changes + it('merge singleton: respect changes', async function () { + try { + // Modify a dialog file it should stay unchanged except for main.dialog which should be updated, but not hash updated + // Remove a dialog file and it should not come back + // Modify an .lu file and it should have enum updated, but not hash + // Modify an .lg file and it should have enum updated, but not hash + console.log('Respect changes merge') + + await copyToMerged('**/language-generation/en-us/Bread/*') + await copyToMerged('**/language-generation/en-us/BreadValue/*') + await copyToMerged('**/language-understanding/en-us/Bread/*') + await copyToMerged('**/language-understanding/en-us/form/*') + + // Modify an existing trigger and add a custom trigger + const dialogPath = ppath.join(mergedDir, 'sandwichMerge.dialog') + const oldDialog = await fs.readJSON(dialogPath) + const modifiedTrigger = oldDialog.triggers[1] + modifiedTrigger.actions.push({$kind: "Microsoft.SetProperty"}) + const newTrigger = {$kind: "Microsoft.OnCondition", actions: []} + oldDialog.triggers.splice(3, 0, newTrigger) + await fs.writeFile(dialogPath, gen.stringify(oldDialog)) + + await gen.generate(modifiedSchema, { + prefix: 'sandwichMerge', + outDir: mergedDir, + merge: true, + singleton: true, + feedback + }) + const comparison = await compareDirs(originalDir, mergedDir) + const errors = [] + + // Changed + optional enum fixes = hash not updated, so still changed + await assertUnchanged('language-generation/en-us/BreadValue/sandwichMerge-BreadValue.en-us.lg', false, errors) + await assertUnchanged('sandwichMerge.dialog', false, errors) + + // Despite enum update, hash updated so unchanged + await assertUnchanged('language-generation/en-us/CheeseValue/sandwichMerge-CheeseValue.en-us.lg', true, errors) + + // Still get enum updates + await assertContains('language-generation/en-us/BreadValue/sandwichMerge-BreadValue.en-us.lg', /black/, errors) + await assertMissing('language-generation/en-us/BreadValue/sandwichMerge-BreadValue.en-us.lg', /white/, errors) + await assertMissing('language-understanding/en-us/form/sandwichMerge-custom.en-us.lu', /pulled/, errors) + await assertContains('language-understanding/en-us/Bread/sandwichMerge-Bread-BreadValue.en-us.lu', />- {@BreadProperty={@BreadValue=rye}}/, errors) + await assertContains('language-understanding/en-us/Bread/sandwichMerge-Bread-BreadValue.en-us.lu', /black/, errors) + + assertCheck(comparison, errors) + + const mergedDialog = await fs.readJSON(dialogPath) + delete modifiedTrigger.$Generator + assert.deepStrictEqual(mergedDialog.triggers[1], modifiedTrigger, 'Did not preserve modified trigger') + assert.deepStrictEqual(mergedDialog.triggers[3], newTrigger, 'Did not preserve custom trigger') + } catch (e) { + assert.fail(e.message) + } + }) +}) diff --git a/experimental/generation/generator/packages/library/test/merge_data/sandwichMerge-modified.form b/experimental/generation/generator/packages/library/test/merge_data/sandwichMerge-modified.form index f54d25b6f1..3596c63669 100644 --- a/experimental/generation/generator/packages/library/test/merge_data/sandwichMerge-modified.form +++ b/experimental/generation/generator/packages/library/test/merge_data/sandwichMerge-modified.form @@ -13,15 +13,6 @@ "Name": { "$ref": "template:personName.template" }, - "Bread": { - "type": "string", - "enum": [ - "multiGrainWheat", - "rye", - "wholeWheat", - "black" - ] - }, "Cheese": { "type": "string", "enum": [ @@ -36,11 +27,21 @@ "none" ] }, + "Bread": { + "type": "string", + "enum": [ + "multiGrainWheat", + "rye", + "wholeWheat", + "black" + ] + }, "Hobby": { "type": "string", "$entities": [ "utterance" - ] + ], + "$askIf": "$Price" }, "Price": { "$ref": "template:money.template" diff --git a/experimental/generation/generator/packages/library/test/oracles/sandwichMerge.dialog b/experimental/generation/generator/packages/library/test/oracles/sandwichMerge.dialog new file mode 100644 index 0000000000..cd24de7dca --- /dev/null +++ b/experimental/generation/generator/packages/library/test/oracles/sandwichMerge.dialog @@ -0,0 +1,1581 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/botbuilder-samples/main/experimental/generation/runbot/RunBot.schema", + "id": "sandwichMerge", + "$kind": "Microsoft.AdaptiveDialog", + "recognizer": "sandwichMerge.lu", + "generator": "sandwichMerge.lg", + "schema": "sandwichMerge.json", + "triggers": [ + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Assign()", + "property": "Quantity", + "value": "number", + "actions": [ + { + "$kind": "Microsoft.IfCondition", + "condition": "@number < dialogClass.schema.properties.Quantity.minimum", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Quantity_TooSmall(@number)}" + } + ], + "elseActions": [ + { + "$kind": "Microsoft.IfCondition", + "condition": "@number > dialogClass.schema.properties.Quantity.maximum", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Quantity_TooBig(@number)}" + } + ], + "elseActions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${confirmationActivity('Quantity', @number)}" + }, + { + "$kind": "Microsoft.SetProperty", + "property": "$Quantity", + "value": "=@number" + }, + { + "$kind": "Microsoft.IfCondition", + "condition": "$PropertyToChange == 'Quantity'", + "actions": [ + { + "$kind": "Microsoft.DeleteProperty", + "property": "$PropertyToChange" + } + ] + } + ] + } + ] + } + ], + "$designer": { + "name": "Quantity - Assign(@number)" + }, + "$source": "sandwichMerge-Quantity-assign-number", + "$Generator": "3feed3466d7a2252bc5a9455b1b3047d" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Remove()", + "property": "Quantity", + "value": "number", + "actions": [ + { + "$kind": "Microsoft.IfCondition", + "condition": "$Quantity == @number", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Quantity_RemoveConfirmation(@number)}" + }, + { + "$kind": "Microsoft.DeleteProperty", + "property": "$Quantity" + }, + { + "$kind": "Microsoft.IfCondition", + "condition": "$PropertyToChange == 'Quantity'", + "actions": [ + { + "$kind": "Microsoft.DeleteProperty", + "property": "$PropertyToChange" + } + ] + } + ] + } + ], + "$designer": { + "name": "Quantity - Remove(@number)" + }, + "$source": "sandwichMerge-Quantity-remove-number", + "$Generator": "db94b6b45e653be9a3e2004fd8b40d9b" + }, + { + "$kind": "Microsoft.OnEndOfActions", + "condition": "!$Quantity", + "priority": "=form.missingPriority(['Quantity'])", + "actions": [ + { + "$kind": "Microsoft.Ask", + "activity": "${Quantity_MissingPrompt()}", + "expectedProperties": [ + "Quantity" + ] + } + ], + "$designer": { + "name": "Quantity - Missing()" + }, + "$source": "sandwichMerge-Quantity-missing", + "$Generator": "a24d691b5faedba87d5f6d3964eb5137" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Clear()", + "property": "Quantity", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Quantity_ClearConfirmation()}" + }, + { + "$kind": "Microsoft.DeleteProperty", + "property": "$Quantity" + } + ], + "$designer": { + "name": "Quantity - Clear()" + }, + "$source": "sandwichMerge-Quantity-clear", + "$Generator": "0cf25da4e57c4f27ae32c678dbdb7c08" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Show()", + "property": "Quantity", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Quantity_Show()}" + } + ], + "$designer": { + "name": "Quantity - Show()" + }, + "$source": "sandwichMerge-Quantity-show", + "$Generator": "822640bcd26d52a1c8b33e55eaf378b7" + }, + { + "$kind": "Microsoft.OnEndOfActions", + "condition": "$PropertyToChange == 'Quantity'", + "priority": 1, + "actions": [ + { + "$kind": "Microsoft.Ask", + "activity": "${Quantity_ChangePrompt()}", + "expectedProperties": [ + "Quantity" + ] + } + ], + "$designer": { + "name": "Quantity - Change()" + }, + "$source": "sandwichMerge-Quantity-change", + "$Generator": "5eae067b4474b71df3a1dd3adc092365" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Help()", + "property": "Quantity", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Quantity_Help()}" + } + ], + "$designer": { + "name": "Quantity - Help()" + }, + "$source": "sandwichMerge-Quantity-help", + "$Generator": "36d3b1368e023b05175d4e4dcaf41e03" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Assign()", + "property": "Length", + "value": "dimension", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${confirmationActivity('Length', @dimension)}" + }, + { + "$kind": "Microsoft.SetProperty", + "property": "$Length", + "value": "=@dimension" + }, + { + "$kind": "Microsoft.IfCondition", + "condition": "$PropertyToChange == 'Length'", + "actions": [ + { + "$kind": "Microsoft.DeleteProperty", + "property": "$PropertyToChange" + } + ] + } + ], + "$designer": { + "name": "Length - Assign(@dimension)" + }, + "$source": "sandwichMerge-Length-assign-dimension", + "$Generator": "3aa0972c21cb880dc19389957b289490" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Remove()", + "property": "Length", + "value": "dimension", + "actions": [ + { + "$kind": "Microsoft.IfCondition", + "condition": "$Length == @dimension", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Length_RemoveConfirmation(@dimension)}" + }, + { + "$kind": "Microsoft.DeleteProperty", + "property": "$Length" + }, + { + "$kind": "Microsoft.IfCondition", + "condition": "$PropertyToChange == 'Length'", + "actions": [ + { + "$kind": "Microsoft.DeleteProperty", + "property": "$PropertyToChange" + } + ] + } + ] + } + ], + "$designer": { + "name": "Length - Remove(@dimension)" + }, + "$source": "sandwichMerge-Length-remove-dimension", + "$Generator": "cfa23ccc143cb31a8c5bc696c8970d01" + }, + { + "$kind": "Microsoft.OnEndOfActions", + "condition": "!$Length", + "priority": "=form.missingPriority(['Length'])", + "actions": [ + { + "$kind": "Microsoft.Ask", + "activity": "${Length_MissingPrompt()}", + "expectedProperties": [ + "Length" + ] + } + ], + "$designer": { + "name": "Length - Missing()" + }, + "$source": "sandwichMerge-Length-missing", + "$Generator": "a65675ec687c9d8b4cb226fa6d87d6da" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Clear()", + "property": "Length", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Length_ClearConfirmation()}" + }, + { + "$kind": "Microsoft.DeleteProperty", + "property": "$Length" + } + ], + "$designer": { + "name": "Length - Clear()" + }, + "$source": "sandwichMerge-Length-clear", + "$Generator": "11f60fd1a69a0f90c946dcc1d22cee93" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Show()", + "property": "Length", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Length_Show()}" + } + ], + "$designer": { + "name": "Length - Show()" + }, + "$source": "sandwichMerge-Length-show", + "$Generator": "2f6cc6a78a5230f320ccf7becfa56f98" + }, + { + "$kind": "Microsoft.OnEndOfActions", + "condition": "$PropertyToChange == 'Length'", + "priority": 1, + "actions": [ + { + "$kind": "Microsoft.Ask", + "activity": "${Length_ChangePrompt()}", + "expectedProperties": [ + "Length" + ] + } + ], + "$designer": { + "name": "Length - Change()" + }, + "$source": "sandwichMerge-Length-change", + "$Generator": "512502acdbe6edff187a05c51875c860" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Help()", + "property": "Length", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Length_Help()}" + } + ], + "$designer": { + "name": "Length - Help()" + }, + "$source": "sandwichMerge-Length-help", + "$Generator": "a44880884dd4718e5124b60a4bc793f8" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Assign()", + "property": "Name", + "value": "personName", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${confirmationActivity('Name', @personName)}" + }, + { + "$kind": "Microsoft.SetProperty", + "property": "$Name", + "value": "=@personName" + }, + { + "$kind": "Microsoft.IfCondition", + "condition": "$PropertyToChange == 'Name'", + "actions": [ + { + "$kind": "Microsoft.DeleteProperty", + "property": "$PropertyToChange" + } + ] + } + ], + "$designer": { + "name": "Name - Assign(@personName)" + }, + "$source": "sandwichMerge-Name-assign-personName", + "$Generator": "da437cbbde6dacfb034a63aafd0c842e" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Remove()", + "property": "Name", + "value": "personName", + "actions": [ + { + "$kind": "Microsoft.IfCondition", + "condition": "$Name == @personName", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Name_RemoveConfirmation(@personName)}" + }, + { + "$kind": "Microsoft.DeleteProperty", + "property": "$Name" + }, + { + "$kind": "Microsoft.IfCondition", + "condition": "$PropertyToChange == 'Name'", + "actions": [ + { + "$kind": "Microsoft.DeleteProperty", + "property": "$PropertyToChange" + } + ] + } + ] + } + ], + "$designer": { + "name": "Name - Remove(@personName)" + }, + "$source": "sandwichMerge-Name-remove-personName", + "$Generator": "c63ff32e18372b06ec25244a1e031165" + }, + { + "$kind": "Microsoft.OnEndOfActions", + "condition": "!$Name", + "priority": "=form.missingPriority(['Name'])", + "actions": [ + { + "$kind": "Microsoft.Ask", + "activity": "${Name_MissingPrompt()}", + "expectedProperties": [ + "Name" + ] + } + ], + "$designer": { + "name": "Name - Missing()" + }, + "$source": "sandwichMerge-Name-missing", + "$Generator": "2c03053c3dd6f1e3f8096adb00876349" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Clear()", + "property": "Name", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Name_ClearConfirmation()}" + }, + { + "$kind": "Microsoft.DeleteProperty", + "property": "$Name" + } + ], + "$designer": { + "name": "Name - Clear()" + }, + "$source": "sandwichMerge-Name-clear", + "$Generator": "bbe0b1256d1e949a0e9a45487539bfc3" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Show()", + "property": "Name", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Name_Show()}" + } + ], + "$designer": { + "name": "Name - Show()" + }, + "$source": "sandwichMerge-Name-show", + "$Generator": "225a9ed6e4d8c030d088439d3b4c7c4c" + }, + { + "$kind": "Microsoft.OnEndOfActions", + "condition": "$PropertyToChange == 'Name'", + "priority": 1, + "actions": [ + { + "$kind": "Microsoft.Ask", + "activity": "${Name_ChangePrompt()}", + "expectedProperties": [ + "Name" + ] + } + ], + "$designer": { + "name": "Name - Change()" + }, + "$source": "sandwichMerge-Name-change", + "$Generator": "5365d9db1c94a680142cb8557cac0495" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Help()", + "property": "Name", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Name_Help()}" + } + ], + "$designer": { + "name": "Name - Help()" + }, + "$source": "sandwichMerge-Name-help", + "$Generator": "59f01203c752f1bc63e3a5b85ae0da5d" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Assign()", + "property": "Name", + "value": "utterance", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${confirmationActivity('Name', @utterance)}" + }, + { + "$kind": "Microsoft.SetProperty", + "property": "$Name", + "value": "=@utterance" + }, + { + "$kind": "Microsoft.IfCondition", + "condition": "$PropertyToChange == 'Name'", + "actions": [ + { + "$kind": "Microsoft.DeleteProperty", + "property": "$PropertyToChange" + } + ] + } + ], + "$designer": { + "name": "Name - Assign(@utterance)" + }, + "$source": "sandwichMerge-Name-assign-utterance", + "$Generator": "90ed8e37d94b4523bd67588f1e895710" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Remove()", + "property": "Name", + "value": "utterance", + "actions": [ + { + "$kind": "Microsoft.IfCondition", + "condition": "$Name == @utterance", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Name_RemoveConfirmation(@utterance)}" + }, + { + "$kind": "Microsoft.DeleteProperty", + "property": "$Name" + }, + { + "$kind": "Microsoft.IfCondition", + "condition": "$PropertyToChange == 'Name'", + "actions": [ + { + "$kind": "Microsoft.DeleteProperty", + "property": "$PropertyToChange" + } + ] + } + ] + } + ], + "$designer": { + "name": "Name - Remove(@utterance)" + }, + "$source": "sandwichMerge-Name-remove-utterance", + "$Generator": "12a7e40f63d7ec82801a1147dfeaf174" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Assign()", + "property": "Bread", + "value": "BreadValue", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${confirmationActivity('Bread', @BreadValue)}" + }, + { + "$kind": "Microsoft.SetProperty", + "property": "$Bread", + "value": "=@BreadValue" + }, + { + "$kind": "Microsoft.IfCondition", + "condition": "$PropertyToChange == 'Bread'", + "actions": [ + { + "$kind": "Microsoft.DeleteProperty", + "property": "$PropertyToChange" + } + ] + } + ], + "$designer": { + "name": "Bread - Assign(@BreadValue)" + }, + "$source": "sandwichMerge-Bread-assign-BreadValue", + "$Generator": "028b5157698e2dec0bcfe8b831f01722" + }, + { + "$kind": "Microsoft.OnChooseEntity", + "value": "BreadValue", + "actions": [ + { + "$kind": "Microsoft.Ask", + "activity": "${BreadValue_chooseEntity()}", + "expectedProperties": [ + "Bread" + ] + } + ], + "$designer": { + "name": "BreadValue - Choose()" + }, + "$source": "sandwichMerge-BreadValue-choose", + "$Generator": "82738e27bfaf933530b9b87d85717a17" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Remove()", + "property": "Bread", + "value": "BreadValue", + "actions": [ + { + "$kind": "Microsoft.IfCondition", + "condition": "$Bread == @BreadValue", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Bread_RemoveConfirmation(@BreadValue)}" + }, + { + "$kind": "Microsoft.DeleteProperty", + "property": "$Bread" + }, + { + "$kind": "Microsoft.IfCondition", + "condition": "$PropertyToChange == 'Bread'", + "actions": [ + { + "$kind": "Microsoft.DeleteProperty", + "property": "$PropertyToChange" + } + ] + } + ] + } + ], + "$designer": { + "name": "Bread - Remove(@BreadValue)" + }, + "$source": "sandwichMerge-Bread-remove-BreadValue", + "$Generator": "19fe9b28078dc32c576c774153db2b56" + }, + { + "$kind": "Microsoft.OnEndOfActions", + "condition": "$PropertyToChange == 'Bread'", + "priority": 1, + "actions": [ + { + "$kind": "Microsoft.Ask", + "activity": "${Bread_ChangePrompt()}", + "expectedProperties": [ + "Bread" + ] + } + ], + "$designer": { + "name": "Bread - Change()" + }, + "$source": "sandwichMerge-Bread-change", + "$Generator": "4ced07da22a80be670885da0b40d02d9" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Clear()", + "property": "Bread", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Bread_ClearConfirmation()}" + }, + { + "$kind": "Microsoft.DeleteProperty", + "property": "$Bread" + } + ], + "$designer": { + "name": "Bread - Clear()" + }, + "$source": "sandwichMerge-Bread-clear", + "$Generator": "f66b19a9f27bd23fda3322e816d8929f" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Help()", + "property": "Bread", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Bread_Help()}" + } + ], + "$designer": { + "name": "Bread - Help()" + }, + "$source": "sandwichMerge-Bread-help", + "$Generator": "06517808fb9559f0a0137dd14c8be04e" + }, + { + "$kind": "Microsoft.OnEndOfActions", + "condition": "!$Bread", + "priority": "=form.missingPriority(['Bread'])", + "actions": [ + { + "$kind": "Microsoft.Ask", + "activity": "${Bread_MissingPrompt()}", + "expectedProperties": [ + "Bread" + ] + } + ], + "$designer": { + "name": "Bread - Missing()" + }, + "$source": "sandwichMerge-Bread-missing", + "$Generator": "5cfb9ad0ef8ba774c69338ee7430a774" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Show()", + "property": "Bread", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Bread_Show()}" + } + ], + "$designer": { + "name": "Bread - Show()" + }, + "$source": "sandwichMerge-Bread-show", + "$Generator": "451d2435afc41e8015b1ef23a53dc2fd" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Assign()", + "property": "Cheese", + "value": "CheeseValue", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${confirmationActivity('Cheese', @CheeseValue)}" + }, + { + "$kind": "Microsoft.SetProperty", + "property": "$Cheese", + "value": "=@CheeseValue" + }, + { + "$kind": "Microsoft.IfCondition", + "condition": "$PropertyToChange == 'Cheese'", + "actions": [ + { + "$kind": "Microsoft.DeleteProperty", + "property": "$PropertyToChange" + } + ] + } + ], + "$designer": { + "name": "Cheese - Assign(@CheeseValue)" + }, + "$source": "sandwichMerge-Cheese-assign-CheeseValue", + "$Generator": "b30b29909f632ca012adcd8216fec565" + }, + { + "$kind": "Microsoft.OnChooseEntity", + "value": "CheeseValue", + "actions": [ + { + "$kind": "Microsoft.Ask", + "activity": "${CheeseValue_chooseEntity()}", + "expectedProperties": [ + "Cheese" + ] + } + ], + "$designer": { + "name": "CheeseValue - Choose()" + }, + "$source": "sandwichMerge-CheeseValue-choose", + "$Generator": "6b8a1bebed1cd71b51bf908d93fa8451" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Remove()", + "property": "Cheese", + "value": "CheeseValue", + "actions": [ + { + "$kind": "Microsoft.IfCondition", + "condition": "$Cheese == @CheeseValue", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Cheese_RemoveConfirmation(@CheeseValue)}" + }, + { + "$kind": "Microsoft.DeleteProperty", + "property": "$Cheese" + }, + { + "$kind": "Microsoft.IfCondition", + "condition": "$PropertyToChange == 'Cheese'", + "actions": [ + { + "$kind": "Microsoft.DeleteProperty", + "property": "$PropertyToChange" + } + ] + } + ] + } + ], + "$designer": { + "name": "Cheese - Remove(@CheeseValue)" + }, + "$source": "sandwichMerge-Cheese-remove-CheeseValue", + "$Generator": "4c8d028d7c27f50213b138e41baad3e4" + }, + { + "$kind": "Microsoft.OnEndOfActions", + "condition": "$PropertyToChange == 'Cheese'", + "priority": 1, + "actions": [ + { + "$kind": "Microsoft.Ask", + "activity": "${Cheese_ChangePrompt()}", + "expectedProperties": [ + "Cheese" + ] + } + ], + "$designer": { + "name": "Cheese - Change()" + }, + "$source": "sandwichMerge-Cheese-change", + "$Generator": "8377e1a4da4b87541f24950bacb8e76f" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Clear()", + "property": "Cheese", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Cheese_ClearConfirmation()}" + }, + { + "$kind": "Microsoft.DeleteProperty", + "property": "$Cheese" + } + ], + "$designer": { + "name": "Cheese - Clear()" + }, + "$source": "sandwichMerge-Cheese-clear", + "$Generator": "8c650a8e41730d95effe4a0f77053b92" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Help()", + "property": "Cheese", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Cheese_Help()}" + } + ], + "$designer": { + "name": "Cheese - Help()" + }, + "$source": "sandwichMerge-Cheese-help", + "$Generator": "5eaf7346b618d3495540b1751c1c58f9" + }, + { + "$kind": "Microsoft.OnEndOfActions", + "condition": "!$Cheese", + "priority": "=form.missingPriority(['Cheese'])", + "actions": [ + { + "$kind": "Microsoft.Ask", + "activity": "${Cheese_MissingPrompt()}", + "expectedProperties": [ + "Cheese" + ] + } + ], + "$designer": { + "name": "Cheese - Missing()" + }, + "$source": "sandwichMerge-Cheese-missing", + "$Generator": "72ec87ceebfe798563ce79ac80ec6bbe" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Show()", + "property": "Cheese", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Cheese_Show()}" + } + ], + "$designer": { + "name": "Cheese - Show()" + }, + "$source": "sandwichMerge-Cheese-show", + "$Generator": "9400f430060c264ad168f5b414905973" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Assign()", + "property": "Price", + "value": "money", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${confirmationActivity('Price', @money)}" + }, + { + "$kind": "Microsoft.SetProperty", + "property": "$Price", + "value": "=@money" + }, + { + "$kind": "Microsoft.IfCondition", + "condition": "$PropertyToChange == 'Price'", + "actions": [ + { + "$kind": "Microsoft.DeleteProperty", + "property": "$PropertyToChange" + } + ] + } + ], + "$designer": { + "name": "Price - Assign(@money)" + }, + "$source": "sandwichMerge-Price-assign-money", + "$Generator": "4965ef37e7adaffd657b43d8827f1b6f" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Assign()", + "property": "Hobby", + "value": "utterance", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${confirmationActivity('Hobby', @utterance)}" + }, + { + "$kind": "Microsoft.SetProperty", + "property": "$Hobby", + "value": "=@utterance" + }, + { + "$kind": "Microsoft.IfCondition", + "condition": "$PropertyToChange == 'Hobby'", + "actions": [ + { + "$kind": "Microsoft.DeleteProperty", + "property": "$PropertyToChange" + } + ] + } + ], + "$designer": { + "name": "Hobby - Assign(@utterance)" + }, + "$source": "sandwichMerge-Hobby-assign-utterance", + "$Generator": "442a84343ff21371ddabddaf12298099" + }, + { + "$kind": "Microsoft.OnChooseEntity", + "value": "utterance", + "actions": [ + { + "$kind": "Microsoft.Ask", + "activity": "${utterance_chooseEntity()}", + "expectedProperties": [ + "Hobby" + ] + } + ], + "$designer": { + "name": "utterance - Choose()" + }, + "$source": "sandwichMerge-utterance-choose", + "$Generator": "d73bd5e880105ceaac32f17d92402779" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Remove()", + "property": "Hobby", + "value": "utterance", + "actions": [ + { + "$kind": "Microsoft.IfCondition", + "condition": "$Hobby == @utterance", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Hobby_RemoveConfirmation(@utterance)}" + }, + { + "$kind": "Microsoft.DeleteProperty", + "property": "$Hobby" + }, + { + "$kind": "Microsoft.IfCondition", + "condition": "$PropertyToChange == 'Hobby'", + "actions": [ + { + "$kind": "Microsoft.DeleteProperty", + "property": "$PropertyToChange" + } + ] + } + ] + } + ], + "$designer": { + "name": "Hobby - Remove(@utterance)" + }, + "$source": "sandwichMerge-Hobby-remove-utterance", + "$Generator": "b0b547ec6a5484adb3d12e971af3b0f4" + }, + { + "$kind": "Microsoft.OnEndOfActions", + "condition": "$PropertyToChange == 'Hobby'", + "priority": 1, + "actions": [ + { + "$kind": "Microsoft.Ask", + "activity": "${Hobby_ChangePrompt()}", + "expectedProperties": [ + "Hobby" + ] + } + ], + "$designer": { + "name": "Hobby - Change()" + }, + "$source": "sandwichMerge-Hobby-change", + "$Generator": "eab8e1896825a06601c11b3e77d2cf71" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Clear()", + "property": "Hobby", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Hobby_ClearConfirmation()}" + }, + { + "$kind": "Microsoft.DeleteProperty", + "property": "$Hobby" + } + ], + "$designer": { + "name": "Hobby - Clear()" + }, + "$source": "sandwichMerge-Hobby-clear", + "$Generator": "3a0800be9cdbe268b8119428473c1d85" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Help()", + "property": "Hobby", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Hobby_Help()}" + } + ], + "$designer": { + "name": "Hobby - Help()" + }, + "$source": "sandwichMerge-Hobby-help", + "$Generator": "ea22ab45603bb09506055dfde0b7665a" + }, + { + "$kind": "Microsoft.OnEndOfActions", + "condition": "!$Hobby && $Price", + "priority": "=form.missingPriority(['Hobby'])", + "actions": [ + { + "$kind": "Microsoft.Ask", + "activity": "${Hobby_MissingPrompt()}", + "expectedProperties": [ + "Hobby" + ] + } + ], + "$designer": { + "name": "Hobby - Missing()" + }, + "$source": "sandwichMerge-Hobby-missing", + "$Generator": "6a01a023ce13e47f388ee9aa6f19b915" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Show()", + "property": "Hobby", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Hobby_Show()}" + } + ], + "$designer": { + "name": "Hobby - Show()" + }, + "$source": "sandwichMerge-Hobby-show", + "$Generator": "b16ee8b3d66d8a67d038483989d2778b" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Remove()", + "property": "Price", + "value": "money", + "actions": [ + { + "$kind": "Microsoft.IfCondition", + "condition": "$Price == @money", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Price_RemoveConfirmation(@money)}" + }, + { + "$kind": "Microsoft.DeleteProperty", + "property": "$Price" + }, + { + "$kind": "Microsoft.IfCondition", + "condition": "$PropertyToChange == 'Price'", + "actions": [ + { + "$kind": "Microsoft.DeleteProperty", + "property": "$PropertyToChange" + } + ] + } + ] + } + ], + "$designer": { + "name": "Price - Remove(@money)" + }, + "$source": "sandwichMerge-Price-remove-money", + "$Generator": "be8275ae3fed86a2f10a39795bba254c" + }, + { + "$kind": "Microsoft.OnEndOfActions", + "condition": "!$Price", + "priority": "=form.missingPriority(['Price'])", + "actions": [ + { + "$kind": "Microsoft.Ask", + "activity": "${Price_MissingPrompt()}", + "expectedProperties": [ + "Price" + ] + } + ], + "$designer": { + "name": "Price - Missing()" + }, + "$source": "sandwichMerge-Price-missing", + "$Generator": "faec68338f46cc29b442985afa6647d6" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Clear()", + "property": "Price", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Price_ClearConfirmation()}" + }, + { + "$kind": "Microsoft.DeleteProperty", + "property": "$Price" + } + ], + "$designer": { + "name": "Price - Clear()" + }, + "$source": "sandwichMerge-Price-clear", + "$Generator": "925edde7fe9874afc8ccc78b2e7247c6" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Show()", + "property": "Price", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Price_Show()}" + } + ], + "$designer": { + "name": "Price - Show()" + }, + "$source": "sandwichMerge-Price-show", + "$Generator": "7182bcd5c8d9c9f41f0fcefc7987fe49" + }, + { + "$kind": "Microsoft.OnEndOfActions", + "condition": "$PropertyToChange == 'Price'", + "priority": 1, + "actions": [ + { + "$kind": "Microsoft.Ask", + "activity": "${Price_ChangePrompt()}", + "expectedProperties": [ + "Price" + ] + } + ], + "$designer": { + "name": "Price - Change()" + }, + "$source": "sandwichMerge-Price-change", + "$Generator": "c0163ffbc50dc661a5fa24abddf2eaca" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Help()", + "property": "Price", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${Price_Help()}" + } + ], + "$designer": { + "name": "Price - Help()" + }, + "$source": "sandwichMerge-Price-help", + "$Generator": "3f1dd62c31433f8e1788a874d292a06c" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Set()", + "property": "CancelConfirmation", + "value": "boolean", + "actions": [ + { + "$kind": "Microsoft.SetProperty", + "property": "$CancelConfirmation", + "value": "=@boolean" + }, + { + "$kind": "Microsoft.IfCondition", + "condition": "$CancelConfirmation == 'true'", + "actions": [ + { + "$kind": "Microsoft.EndDialog" + } + ] + } + ], + "$designer": { + "name": "CancelConfirmation - Set(@boolean)" + }, + "$source": "sandwichMerge-form-CancelConfirmation-assign-boolean", + "$Generator": "4960a7815be941dc59a885f735a17b42" + }, + { + "$kind": "Microsoft.OnEndOfActions", + "condition": "empty(where($requiredProperties, property, !dialog[property]))", + "priority": 1000, + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${propertiesAsActivity('sandwichMerge', publicProperties())}" + }, + { + "$kind": "Microsoft.Ask", + "activity": "${changePropertyPrompt()}", + "expectedProperties": [ + "CompleteConfirmation" + ] + } + ], + "$designer": { + "name": "PropertyToChange - Missing()" + }, + "$source": "sandwichMerge-form-CompleteConfirmation", + "$Generator": "f4c6b8efa880dc19ed5a86cef1709650" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Set()", + "property": "CompleteConfirmation", + "value": "boolean", + "actions": [ + { + "$kind": "Microsoft.SetProperty", + "property": "$CompleteConfirmation", + "value": "=@boolean" + }, + { + "$kind": "Microsoft.IfCondition", + "condition": "$CompleteConfirmation == 'false'", + "actions": [ + { + "$kind": "Microsoft.EndDialog" + } + ], + "elseActions": [ + { + "$kind": "Microsoft.Ask", + "activity": "${choosePropertyPrompt()}", + "expectedProperties": [ + "PropertyToChange" + ] + } + ] + } + ], + "$designer": { + "name": "CompleteConfirmation - Set(@boolean)" + }, + "$source": "sandwichMerge-form-CompleteConfirmation-assign-boolean", + "$Generator": "85443619752af78fb6c5af9074d7df8f" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Set()", + "property": "CompleteConfirmation", + "actions": [ + { + "$kind": "Microsoft.SetProperty", + "property": "$PropertyToChange", + "value": "=turn.dialogEvent.value.value.name" + } + ], + "$designer": { + "name": "CompleteConfirmation - Set(@PROPERTYName)" + }, + "$source": "sandwichMerge-form-CompleteConfirmation-assign-PROPERTYName", + "$Generator": "8708945f9ffe81200d9fceaa088ec900" + }, + { + "$kind": "Microsoft.OnBeginDialog", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${helpGlobal()}" + }, + { + "$kind": "Microsoft.IfCondition", + "condition": "!turn.activityProcessed", + "actions": [ + { + "$kind": "Microsoft.EmitEvent", + "eventName": "activityReceived", + "eventValue": "=turn.activity", + "bubbleEvent": false + } + ] + } + ], + "$source": "sandwichMerge-form-BeginDialog", + "$Generator": "55d562533bc82c17f06ef41aaaf7c4cd" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Cancel()", + "actions": [ + { + "$kind": "Microsoft.Ask", + "activity": "${cancelPrompt()}", + "expectedProperties": [ + "CancelConfirmation" + ] + } + ], + "$designer": { + "name": "Cancel" + }, + "$source": "sandwichMerge-form-CancelGlobal", + "$Generator": "92a728d24563c6577a5ca596eedac117" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Change()", + "actions": [ + { + "$kind": "Microsoft.SetProperty", + "property": "$PropertyToChange", + "value": "=turn.dialogEvent.value.property" + } + ], + "$designer": { + "name": "Change property" + }, + "$source": "sandwichMerge-form-Change", + "$Generator": "dad698424d934ef1de5e5622e4eb542a" + }, + { + "$kind": "Microsoft.OnChooseProperty", + "actions": [ + { + "$kind": "Microsoft.Ask", + "activity": "${chooseProperties()}" + } + ], + "$designer": { + "name": "ChooseProperty()" + }, + "$source": "sandwichMerge-form-ChooseProperty", + "$Generator": "f1358134ff2db83a3b281fc529761fbc" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Help()", + "actions": [ + { + "$kind": "Microsoft.DeleteProperty", + "property": "$retries" + }, + { + "$kind": "Microsoft.SendActivity", + "activity": "${helpGlobal()}" + } + ], + "$designer": { + "name": "Help Global" + }, + "$source": "sandwichMerge-form-HelpGlobal", + "$Generator": "b6dc9224065cdde23a0039cf71350945" + }, + { + "$kind": "Microsoft.OnEndOfActions", + "condition": "$retries", + "runOnce": true, + "priority": 0, + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${helpReprompt()}" + } + ], + "$designer": { + "name": "Reprompt Help" + }, + "$source": "sandwichMerge-form-HelpReprompt", + "$Generator": "a8c89745ec7fdb8020ba3aca2097e3f0" + }, + { + "$kind": "Microsoft.OnIntent", + "intent": "None", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${notUnderstood()}" + } + ], + "$designer": { + "name": "OnNone" + }, + "$source": "sandwichMerge-form-NotUnderstood", + "$Generator": "1bdbe42785a10639c87ce5ecbe306b09" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Show()", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${propertiesAsActivity('sandwichMerge', publicProperties())}" + } + ], + "$designer": { + "name": "OnShow" + }, + "$source": "sandwichMerge-form-Show", + "$Generator": "ddb0e91df467263e7da3e6b5e5983793" + }, + { + "$kind": "Microsoft.OnAssignEntity", + "operation": "Skip()", + "actions": [ + { + "$kind": "Microsoft.IfCondition", + "condition": "count($events.Assignments) > 0", + "actions": [ + { + "$kind": "Microsoft.EditArray", + "changeType": "take", + "itemsProperty": "$events.Assignments" + } + ], + "elseActions": [ + { + "$kind": "Microsoft.IfCondition", + "condition": "$PropertyToChange", + "actions": [ + { + "$kind": "Microsoft.DeleteProperty", + "property": "$PropertyToChange" + } + ], + "elseActions": [ + { + "$kind": "Microsoft.SetProperty", + "property": "$requiredProperties", + "value": "=form.skipExpectedProperties()" + } + ] + } + ] + }, + { + "$kind": "Microsoft.DeleteProperty", + "property": "$retries" + } + ], + "$designer": { + "name": "OnSkip" + }, + "$source": "sandwichMerge-form-SkipIntent", + "$Generator": "d05718f93092f1649618c61be8c797ef" + }, + { + "$kind": "Microsoft.OnIntent", + "intent": "sandwichMerge-modified", + "actions": [ + { + "$kind": "Microsoft.IfCondition", + "condition": "count(turn.recognizedEntities) == 0", + "actions": [ + { + "$kind": "Microsoft.SendActivity", + "activity": "${notUnderstood()}" + } + ], + "elseActions": [] + } + ], + "$designer": { + "name": "OnsandwichMerge-modified" + }, + "$source": "sandwichMerge-trigger", + "$Generator": "763f25bb2993304fea6181948c290ea6" + } + ], + "$designer": { + "name": "sandwichMerge main dialog" + }, + "$Generator": "b6303c9c5ad992e40b15abd5340ed56f" +} \ No newline at end of file