From 83c235bc9230152792f3b0a6d2d2b0aa8f5ea1c8 Mon Sep 17 00:00:00 2001 From: Joel Mut <62260472+sw-joelmut@users.noreply.github.com> Date: Fri, 5 Nov 2021 15:51:45 -0300 Subject: [PATCH] fix: [#3969] useLanguagePolicy setting turn state, instead of languagePolicy (#3972) * Revert "Revert "port: Register missingProperties custom function to get all variables the template contains (#3885)" (#3970)" This reverts commit 619ac1399df7cea3d738cbb613d0f5868e0c124f. * fix: useLanguagePolicy setting turn state, instead of languagePolicy --- .../tests/action.test.js | 4 + .../Action_MissingProperty.test.dialog | 167 ++++++++++++++++++ .../tests/resources/ActionTests/main.lg | 3 + .../tests/resources/ActionTests/sub.lg | 2 + .../etc/botbuilder-dialogs-adaptive.api.md | 14 +- .../botbuilder-dialogs-adaptive/package.json | 3 +- .../src/adaptiveDialog.ts | 7 +- .../src/functions/index.ts | 1 + .../functions/missingPropertiesFunction.ts | 75 ++++++++ .../generators/multiLanguageGeneratorBase.ts | 124 +++++++++---- .../templateEngineLanguageGenerator.ts | 31 +++- .../src/languageGenerator.ts | 12 ++ .../src/languageGeneratorExtensions.ts | 6 +- .../tests/LGGenerator.test.js | 63 +++++-- .../tests/lg/properties.lg | 2 + .../etc/botbuilder-dialogs.api.md | 4 + .../botbuilder-dialogs/src/dialogContext.ts | 4 +- .../botbuilder-dialogs/src/memory/turnPath.ts | 6 + 18 files changed, 472 insertions(+), 56 deletions(-) create mode 100644 libraries/botbuilder-dialogs-adaptive-testing/tests/resources/ActionTests/Action_MissingProperty.test.dialog create mode 100644 libraries/botbuilder-dialogs-adaptive-testing/tests/resources/ActionTests/sub.lg create mode 100644 libraries/botbuilder-dialogs-adaptive/src/functions/missingPropertiesFunction.ts create mode 100644 libraries/botbuilder-dialogs-adaptive/tests/lg/properties.lg diff --git a/libraries/botbuilder-dialogs-adaptive-testing/tests/action.test.js b/libraries/botbuilder-dialogs-adaptive-testing/tests/action.test.js index 9ae74163b7..7eaaf7ed4b 100644 --- a/libraries/botbuilder-dialogs-adaptive-testing/tests/action.test.js +++ b/libraries/botbuilder-dialogs-adaptive-testing/tests/action.test.js @@ -390,6 +390,10 @@ describe('ActionTests', function () { await TestUtils.runTestScript(resourceExplorer, 'Action_SetProperties'); }); + it('MissingProperty', async () => { + await TestUtils.runTestScript(resourceExplorer, 'Action_MissingProperty'); + }); + it('SetProperty', async () => { await TestUtils.runTestScript(resourceExplorer, 'Action_SetProperty'); }); diff --git a/libraries/botbuilder-dialogs-adaptive-testing/tests/resources/ActionTests/Action_MissingProperty.test.dialog b/libraries/botbuilder-dialogs-adaptive-testing/tests/resources/ActionTests/Action_MissingProperty.test.dialog new file mode 100644 index 0000000000..c766a054c8 --- /dev/null +++ b/libraries/botbuilder-dialogs-adaptive-testing/tests/resources/ActionTests/Action_MissingProperty.test.dialog @@ -0,0 +1,167 @@ +{ + "$schema": "../../../tests.schema", + "$kind": "Microsoft.Test.Script", + "dialog": { + "$kind": "Microsoft.AdaptiveDialog", + "id": "planningTest", + "generator": "main.lg", + "triggers": [ + { + "$kind": "Microsoft.OnBeginDialog", + "actions": [ + { + "$kind": "Microsoft.SetProperty", + "property": "user.missingProperties", + "value": "=missingProperties('${dialog.first} and ${dialog.second}')" + }, + { + "$kind": "Microsoft.Foreach", + "itemsProperty": "user.missingProperties", + "actions": [ + { + "$kind": "Microsoft.IfCondition", + "condition": "empty(getProperty(dialog.foreach.value))", + "actions": [ + { + "$kind": "Microsoft.TextInput", + "property": "=dialog.foreach.value", + "prompt": "Hello, please input ${dialog.foreach.value}" + } + ] + } + ] + }, + { + "$kind": "Microsoft.SendActivity", + "activity": "You finish all slot filling. And get result: ${dialog.first} and ${dialog.second}" + }, + { + "$kind": "Microsoft.SetProperty", + "property": "user.missingProperties", + "value": "=missingProperties('${nameAndAge()}')" + }, + { + "$kind": "Microsoft.Foreach", + "itemsProperty": "user.missingProperties", + "actions": [ + { + "$kind": "Microsoft.IfCondition", + "condition": "empty(getProperty(dialog.foreach.value))", + "actions": [ + { + "$kind": "Microsoft.TextInput", + "property": "=dialog.foreach.value", + "prompt": "Hello, please input ${dialog.foreach.value}" + } + ] + } + ] + }, + { + "$kind": "Microsoft.SendActivity", + "activity": "You finish all slot filling. And get result: ${nameAndAge()}" + }, + { + "$kind": "Microsoft.SetProperty", + "property": "user.missingProperties", + "value": "=missingProperties('${nameAndAge()}')" + }, + { + "$kind": "Microsoft.SendActivity", + "activity": "${count(user.missingProperties)}" + }, + { + "$kind": "Microsoft.BeginDialog", + "options": {}, + "dialog": { + "$kind": "Microsoft.AdaptiveDialog", + "generator": "sub.lg", + "triggers": [ + { + "$kind": "Microsoft.OnBeginDialog", + "actions": [ + { + "$kind": "Microsoft.SetProperty", + "property": "user.missingProperties", + "value": "=missingProperties('${showPetName()}')" + }, + { + "$kind": "Microsoft.SendActivity", + "activity": "${count(user.missingProperties)}" + } + ] + } + ] + } + }, + { + "$kind": "Microsoft.SetProperty", + "property": "user.missingProperties", + "value": "=missingProperties('${nameAndAge()}')" + }, + { + "$kind": "Microsoft.SendActivity", + "activity": "${count(user.missingProperties)}" + } + ] + } + ] + }, + "script": [ + { + "$kind": "Microsoft.Test.UserConversationUpdate" + }, + { + "$kind": "Microsoft.Test.AssertReply", + "text": "Hello, please input dialog.first" + }, + { + "$kind": "Microsoft.Test.UserSays", + "text": "1" + }, + { + "$kind": "Microsoft.Test.AssertReply", + "text": "Hello, please input dialog.second" + }, + { + "$kind": "Microsoft.Test.UserSays", + "text": "2" + }, + { + "$kind": "Microsoft.Test.AssertReply", + "text": "You finish all slot filling. And get result: 1 and 2" + }, + { + "$kind": "Microsoft.Test.AssertReply", + "text": "Hello, please input user.name" + }, + { + "$kind": "Microsoft.Test.UserSays", + "text": "Jack" + }, + { + "$kind": "Microsoft.Test.AssertReply", + "text": "Hello, please input user.age" + }, + { + "$kind": "Microsoft.Test.UserSays", + "text": "20" + }, + { + "$kind": "Microsoft.Test.AssertReply", + "text": "You finish all slot filling. And get result: my name is Jack and my age is 20" + }, + { + "$kind": "Microsoft.Test.AssertReply", + "text": "2" + }, + { + "$kind": "Microsoft.Test.AssertReply", + "text": "1" + }, + { + "$kind": "Microsoft.Test.AssertReply", + "text": "2" + } + ] +} diff --git a/libraries/botbuilder-dialogs-adaptive-testing/tests/resources/ActionTests/main.lg b/libraries/botbuilder-dialogs-adaptive-testing/tests/resources/ActionTests/main.lg index 8457d4f11f..ba864d3ad2 100644 --- a/libraries/botbuilder-dialogs-adaptive-testing/tests/resources/ActionTests/main.lg +++ b/libraries/botbuilder-dialogs-adaptive-testing/tests/resources/ActionTests/main.lg @@ -18,3 +18,6 @@ # length - length in main + +# nameAndAge +- my name is ${user.name} and my age is ${user.age} \ No newline at end of file diff --git a/libraries/botbuilder-dialogs-adaptive-testing/tests/resources/ActionTests/sub.lg b/libraries/botbuilder-dialogs-adaptive-testing/tests/resources/ActionTests/sub.lg new file mode 100644 index 0000000000..df3743368b --- /dev/null +++ b/libraries/botbuilder-dialogs-adaptive-testing/tests/resources/ActionTests/sub.lg @@ -0,0 +1,2 @@ +# showPetName +- the pet's name is ${petName} \ No newline at end of file diff --git a/libraries/botbuilder-dialogs-adaptive/etc/botbuilder-dialogs-adaptive.api.md b/libraries/botbuilder-dialogs-adaptive/etc/botbuilder-dialogs-adaptive.api.md index e88ab2dbff..2327a416ae 100644 --- a/libraries/botbuilder-dialogs-adaptive/etc/botbuilder-dialogs-adaptive.api.md +++ b/libraries/botbuilder-dialogs-adaptive/etc/botbuilder-dialogs-adaptive.api.md @@ -43,9 +43,11 @@ import { Headers as Headers_2 } from 'node-fetch'; import { ImportResolverDelegate } from 'botbuilder-lg'; import { IntExpression } from 'adaptive-expressions'; import { ListStyle } from 'botbuilder-dialogs'; +import { MemoryInterface } from 'adaptive-expressions'; import { ModelResult } from 'botbuilder-dialogs'; import { NumberExpression } from 'adaptive-expressions'; import { ObjectExpression } from 'adaptive-expressions'; +import { Options } from 'adaptive-expressions'; import { PromptOptions } from 'botbuilder-dialogs'; import { Recognizer } from 'botbuilder-dialogs'; import { RecognizerConfiguration } from 'botbuilder-dialogs'; @@ -1364,6 +1366,7 @@ export class LanguageGenerationBotComponent extends BotComponent { // @public export interface LanguageGenerator> { generate(dialogContext: DialogContext, template: string, data: D): Promise; + missingProperties(dialogContext: DialogContext, template: string, state?: MemoryInterface, options?: Options): string[]; } // @public @@ -1445,6 +1448,12 @@ export class MentionEntityRecognizer extends TextEntityRecognizer { protected _recognize(text: string, culture: string): ModelResult[]; } +// @public +export class MissingPropertiesFunction extends ExpressionEvaluator { + constructor(context: DialogContext); + static readonly functionName = "missingProperties"; + } + // @public (undocumented) export class MostSpecificSelector extends TriggerSelector implements MostSpecificSelectorConfiguration { // (undocumented) @@ -1480,6 +1489,8 @@ export abstract class MultiLanguageGeneratorBase; @@ -2328,7 +2339,8 @@ export class TemplateEngineLanguageGenerator; // (undocumented) id: string; - } + missingProperties(dialogContext: DialogContext, template: string, _state?: MemoryInterface, _options?: Options): string[]; +} // @public (undocumented) export interface TemplateEngineLanguageGeneratorConfiguration { diff --git a/libraries/botbuilder-dialogs-adaptive/package.json b/libraries/botbuilder-dialogs-adaptive/package.json index 29f24208dd..ecbf7bc0be 100644 --- a/libraries/botbuilder-dialogs-adaptive/package.json +++ b/libraries/botbuilder-dialogs-adaptive/package.json @@ -36,7 +36,8 @@ "botbuilder-dialogs-declarative": "4.1.6", "botbuilder-lg": "4.1.6", "lodash": "^4.17.21", - "node-fetch": "^2.6.0" + "node-fetch": "^2.6.0", + "uuid": "^8.3.2" }, "devDependencies": { "@microsoft/recognizers-text": "~1.1.4", diff --git a/libraries/botbuilder-dialogs-adaptive/src/adaptiveDialog.ts b/libraries/botbuilder-dialogs-adaptive/src/adaptiveDialog.ts index d6bfb659e0..acb27798d2 100644 --- a/libraries/botbuilder-dialogs-adaptive/src/adaptiveDialog.ts +++ b/libraries/botbuilder-dialogs-adaptive/src/adaptiveDialog.ts @@ -6,7 +6,7 @@ * Licensed under the MIT License. */ -import { BoolExpression, BoolExpressionConverter, IntExpression } from 'adaptive-expressions'; +import { BoolExpression, BoolExpressionConverter, Expression, IntExpression } from 'adaptive-expressions'; import { Activity, ActivityTypes, @@ -48,6 +48,7 @@ import { EntityAssignment } from './entityAssignment'; import { EntityAssignmentComparer } from './entityAssignmentComparer'; import { EntityAssignments } from './entityAssignments'; import { EntityInfo, NormalizedEntityInfos } from './entityInfo'; +import { MissingPropertiesFunction } from './functions'; import { LanguageGenerator } from './languageGenerator'; import { languageGeneratorKey } from './languageGeneratorExtensions'; import { BoolProperty } from './properties'; @@ -775,6 +776,10 @@ export class AdaptiveDialog extends DialogContainer im protected onSetScopedServices(dialogContext: DialogContext): void { if (this.generator) { dialogContext.services.set(languageGeneratorKey, this.generator); + Expression.functions.set( + MissingPropertiesFunction.functionName, + new MissingPropertiesFunction(dialogContext) + ); } } diff --git a/libraries/botbuilder-dialogs-adaptive/src/functions/index.ts b/libraries/botbuilder-dialogs-adaptive/src/functions/index.ts index 50e62b43f5..32b48d1647 100644 --- a/libraries/botbuilder-dialogs-adaptive/src/functions/index.ts +++ b/libraries/botbuilder-dialogs-adaptive/src/functions/index.ts @@ -8,3 +8,4 @@ export * from './hasPendingActionsFunction'; export * from './isDialogActiveFunction'; +export * from './missingPropertiesFunction'; diff --git a/libraries/botbuilder-dialogs-adaptive/src/functions/missingPropertiesFunction.ts b/libraries/botbuilder-dialogs-adaptive/src/functions/missingPropertiesFunction.ts new file mode 100644 index 0000000000..e56abc1ab3 --- /dev/null +++ b/libraries/botbuilder-dialogs-adaptive/src/functions/missingPropertiesFunction.ts @@ -0,0 +1,75 @@ +/** + * @module botbuilder-dialogs-adaptive + */ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +import { + Expression, + ExpressionEvaluator, + FunctionUtils, + MemoryInterface, + Options, + ReturnType, + ValueWithError, +} from 'adaptive-expressions'; +import { LanguageGenerator } from '../languageGenerator'; + +import { DialogContext, DialogStateManager } from 'botbuilder-dialogs'; +/** + * Defines missingProperties(template) expression function. + * This expression will get all variables the template contains. + * + * @example missingProperties('${a} ${b}') + */ +export class MissingPropertiesFunction extends ExpressionEvaluator { + /** + * Function identifier name. + */ + public static readonly functionName = 'missingProperties'; // `name` is reserved in JavaScript. + + private static readonly generatorPath = 'dialogclass.generator'; + + private static dialogContext: DialogContext; + + /** + * Intializes a new instance of the [MissingProperties](xref:botbuilder-dialogs-adaptive.MissingProperties) class. + * + * @param context dialog context. + */ + public constructor(context: DialogContext) { + super( + MissingPropertiesFunction.functionName, + MissingPropertiesFunction.function, + ReturnType.Array, + FunctionUtils.validateUnaryString + ); + + MissingPropertiesFunction.dialogContext = context; + } + + private static function(expression: Expression, state: MemoryInterface, options: Options): ValueWithError { + const { args, error } = FunctionUtils.evaluateChildren(expression, state, options); + if (error != null) { + return { value: undefined, error }; + } + + const templateBody = args[0].toString(); + + const generator = (state as DialogStateManager).getValue(MissingPropertiesFunction.generatorPath); + if (generator) { + return { + value: generator.missingProperties( + MissingPropertiesFunction.dialogContext, + templateBody, + state, + options + ), + error: undefined, + }; + } + return { value: undefined, error: undefined }; + } +} diff --git a/libraries/botbuilder-dialogs-adaptive/src/generators/multiLanguageGeneratorBase.ts b/libraries/botbuilder-dialogs-adaptive/src/generators/multiLanguageGeneratorBase.ts index 6b81d0b5b1..7118079ed9 100644 --- a/libraries/botbuilder-dialogs-adaptive/src/generators/multiLanguageGeneratorBase.ts +++ b/libraries/botbuilder-dialogs-adaptive/src/generators/multiLanguageGeneratorBase.ts @@ -6,10 +6,12 @@ * Licensed under the MIT License. */ -import { Configurable, Converter, ConverterFactory, DialogContext } from 'botbuilder-dialogs'; +import { Configurable, Converter, ConverterFactory, DialogContext, TurnPath } from 'botbuilder-dialogs'; import { LanguageGenerator } from '../languageGenerator'; import { LanguagePolicy, LanguagePolicyConverter } from '../languagePolicy'; import { languagePolicyKey } from '../languageGeneratorExtensions'; +import { MemoryInterface, Options } from 'adaptive-expressions'; +import { TemplateEngineLanguageGenerator } from './templateEngineLanguageGenerator'; export interface MultiLanguageGeneratorBaseConfiguration { languagePolicy?: Record | LanguagePolicy; @@ -55,33 +57,109 @@ export abstract class MultiLanguageGeneratorBase< * @param data Data to bind to. */ public async generate(dialogContext: DialogContext, template: string, data: D): Promise { + const languagePolicy = this.getLanguagePolicy(dialogContext); + const targetLocale = this.getCurrentLocale(dialogContext); + const fallbackLocales = this.getFallbackLocales(languagePolicy, targetLocale); + const generators = this.getGenerators(dialogContext, fallbackLocales); + + if (generators.length === 0) { + throw Error(`No generator found for language ${targetLocale}`); + } + + const errors: string[] = []; + for (const generator of generators) { + try { + return await generator.generate(dialogContext, template, data); + } catch (e) { + errors.push(e); + } + } + + throw Error(errors.join(',\n')); + } + + public missingProperties( + dialogContext: DialogContext, + template: string, + state?: MemoryInterface, + options?: Options + ): string[] { + const currentLocale = this.getCurrentLocale(dialogContext, state, options); + const languagePolicy = this.getLanguagePolicy(dialogContext, state); + const fallbackLocales = this.getFallbackLocales(languagePolicy, currentLocale); + const generators = this.getGenerators(dialogContext, fallbackLocales); + + if (generators.length === 0) { + generators.push(new TemplateEngineLanguageGenerator()); + } + + for (const generator of generators) { + try { + return generator.missingProperties(dialogContext, template, state, options); + } catch { + // retry the next generator + } + } + + return []; + } + + private getLanguagePolicy(dialogContext: DialogContext, memory?: MemoryInterface): LanguagePolicy { // priority // 1. local policy + // 2. turn.languagePolicy // 2. shared policy in turnContext // 3. default policy - if (!this.languagePolicy) { - this.languagePolicy = dialogContext.services.get(languagePolicyKey); - if (!this.languagePolicy) { - this.languagePolicy = new LanguagePolicy(); + if (this.languagePolicy) { + return this.languagePolicy; + } + + if (memory) { + const lpInTurn = memory.getValue(TurnPath.languagePolicy); + if (lpInTurn != null) { + return lpInTurn; } } - // see if we have any locales that match - const fallbackLocales = []; - const targetLocale = dialogContext.getLocale().toLowerCase(); - if (this.languagePolicy.has(targetLocale)) { - this.languagePolicy.get(targetLocale).forEach((u: string): number => fallbackLocales.push(u)); + const lpInDc = dialogContext.services.get(languagePolicyKey); + if (lpInDc) { + return lpInDc; } - // append empty as fallback to end - if (targetLocale !== '' && this.languagePolicy.has('')) { - this.languagePolicy.get('').forEach((u: string): number => fallbackLocales.push(u)); + return new LanguagePolicy(); + } + + private getCurrentLocale(dialogContext: DialogContext, memory?: MemoryInterface, options?: Options): string { + // order + // 1. turn.locale + // 2. options.locale + // 3. Context.Activity.Locale + // 4. Thread.CurrentThread.CurrentCulture.Name + if (memory) { + const localeInTurn = memory.getValue(TurnPath.locale); + if (localeInTurn) { + return localeInTurn.toString(); + } } - if (fallbackLocales.length === 0) { - throw Error(`No supported language found for ${targetLocale}`); + return options?.locale ?? dialogContext.getLocale(); + } + + private getFallbackLocales(languagePolicy: LanguagePolicy, targetLocale: string): string[] { + const fallbackLocales = []; + targetLocale = targetLocale.toLowerCase(); + if (languagePolicy.has(targetLocale)) { + fallbackLocales.push(...languagePolicy.get(targetLocale)); } + // append empty as fallback to end + if (targetLocale !== '' && languagePolicy.has('')) { + fallbackLocales.push(...languagePolicy.get('')); + } + return fallbackLocales; + } + + private getGenerators(dialogContext: DialogContext, fallbackLocales: string[]): LanguageGenerator[] { const generators: LanguageGenerator[] = []; for (const locale of fallbackLocales) { const result = this.tryGetGenerator(dialogContext, locale); @@ -89,20 +167,6 @@ export abstract class MultiLanguageGeneratorBase< generators.push(result.result); } } - - if (generators.length === 0) { - throw Error(`No generator found for language ${targetLocale}`); - } - - const errors: string[] = []; - for (const generator of generators) { - try { - return await generator.generate(dialogContext, template, data); - } catch (e) { - errors.push(e); - } - } - - throw Error(errors.join(',\n')); + return generators; } } diff --git a/libraries/botbuilder-dialogs-adaptive/src/generators/templateEngineLanguageGenerator.ts b/libraries/botbuilder-dialogs-adaptive/src/generators/templateEngineLanguageGenerator.ts index d4eac54543..90d5e85794 100644 --- a/libraries/botbuilder-dialogs-adaptive/src/generators/templateEngineLanguageGenerator.ts +++ b/libraries/botbuilder-dialogs-adaptive/src/generators/templateEngineLanguageGenerator.ts @@ -9,9 +9,11 @@ import { Configurable, DialogContext } from 'botbuilder-dialogs'; import { Resource } from 'botbuilder-dialogs-declarative'; import { Templates, LGResource, EvaluationOptions } from 'botbuilder-lg'; +import { MemoryInterface, Options } from 'adaptive-expressions'; import { LanguageGenerator } from '../languageGenerator'; import { LanguageResourceLoader } from '../languageResourceLoader'; import { LanguageGeneratorManager } from './languageGeneratorManager'; +import { v4 as uuidv4 } from 'uuid'; export interface TemplateEngineLanguageGeneratorConfiguration { id?: string; @@ -31,7 +33,7 @@ export class TemplateEngineLanguageGenerator> { * @returns Result of rendering template using data. */ generate(dialogContext: DialogContext, template: string, data: D): Promise; + + /** + * Method to get missing properties. + * + * @param dialogContext DialogContext. + * @param template Template. + * @param state Memory state. + * @param options Options. + * @returns Property list. + */ + missingProperties(dialogContext: DialogContext, template: string, state?: MemoryInterface, options?: Options): string[]; } diff --git a/libraries/botbuilder-dialogs-adaptive/src/languageGeneratorExtensions.ts b/libraries/botbuilder-dialogs-adaptive/src/languageGeneratorExtensions.ts index ef3299b472..1164aaf7ed 100644 --- a/libraries/botbuilder-dialogs-adaptive/src/languageGeneratorExtensions.ts +++ b/libraries/botbuilder-dialogs-adaptive/src/languageGeneratorExtensions.ts @@ -6,7 +6,7 @@ * Licensed under the MIT License. */ -import { DialogManager } from 'botbuilder-dialogs'; +import { DialogManager, TurnPath } from 'botbuilder-dialogs'; import { ResourceExplorer } from 'botbuilder-dialogs-declarative'; import { LanguageGeneratorManager, @@ -78,6 +78,10 @@ export class LanguageGeneratorExtensions { */ public static useLanguagePolicy(dialogManager: DialogManager, policy: LanguagePolicy): DialogManager { dialogManager.initialTurnState.set(languagePolicyKey, policy); + + // put global language policy into turn scope for lg functions fallback + dialogManager.initialTurnState.set(TurnPath.languagePolicy, policy); + return dialogManager; } } diff --git a/libraries/botbuilder-dialogs-adaptive/tests/LGGenerator.test.js b/libraries/botbuilder-dialogs-adaptive/tests/LGGenerator.test.js index aeb50cce09..f53c6f1f11 100644 --- a/libraries/botbuilder-dialogs-adaptive/tests/LGGenerator.test.js +++ b/libraries/botbuilder-dialogs-adaptive/tests/LGGenerator.test.js @@ -43,22 +43,22 @@ describe('LGLanguageGenerator', function() { it('assert empty locale', async () => { assert(lgResourceGroup.has('')); var resourceNames = lgResourceGroup.get('').map(u => u.id); - assert.equal(resourceNames.length, 7); - assert.deepStrictEqual(new Set(resourceNames), new Set(['a.lg', 'b.lg', 'c.lg', 'NormalStructuredLG.lg','root.lg', 'subDialog.lg', 'test.lg'])); + assert.equal(resourceNames.length, 8); + assert.deepStrictEqual(new Set(resourceNames), new Set(['properties.lg', 'a.lg', 'b.lg', 'c.lg', 'NormalStructuredLG.lg','root.lg', 'subDialog.lg', 'test.lg'])); }); it('assert en-us locale', async () => { assert(lgResourceGroup.has('en-us')); var resourceNames = lgResourceGroup.get('en-us').map(u => u.id); - assert.equal(resourceNames.length, 7); - assert.deepStrictEqual(new Set(resourceNames), new Set(['a.en-US.lg', 'b.en-us.lg', 'test.en-US.lg', 'c.en.lg', 'NormalStructuredLG.lg','root.lg', 'subDialog.lg'])); + assert.equal(resourceNames.length, 8); + assert.deepStrictEqual(new Set(resourceNames), new Set(['properties.lg', 'a.en-US.lg', 'b.en-us.lg', 'test.en-US.lg', 'c.en.lg', 'NormalStructuredLG.lg','root.lg', 'subDialog.lg'])); }); it('assert en locale', async () => { assert(lgResourceGroup.has('en')); var resourceNames = lgResourceGroup.get('en').map(u => u.id); - assert.equal(resourceNames.length, 7); - assert.deepStrictEqual(new Set(resourceNames), new Set(['c.en.lg', 'test.en.lg', 'a.lg', 'b.lg', 'NormalStructuredLG.lg','root.lg', 'subDialog.lg'])); + assert.equal(resourceNames.length, 8); + assert.deepStrictEqual(new Set(resourceNames), new Set(['properties.lg', 'c.en.lg', 'test.en.lg', 'a.lg', 'b.lg', 'NormalStructuredLG.lg','root.lg', 'subDialog.lg'])); }); }); @@ -86,11 +86,11 @@ describe('LGLanguageGenerator', function() { it('should throw for missing template: "${greeting()}", no data', async () => { assert.throws(() => {generator.generate(getDialogContext(), '${greeting()}', undefined);}); - }); + }); }); - - describe('Test Multi-Language Import with no locale', function() { - let generator; + + describe('Test Multi-Language Import with no locale', function() { + let generator; this.beforeAll(async function() { const resource = resourceExplorer.getResource('a.lg'); generator = new TemplateEngineLanguageGenerator(resource, lgResourceGroup); @@ -114,7 +114,7 @@ describe('LGLanguageGenerator', function() { it('"${greeting()}", no data', async () => { const result = await generator.generate(getDialogContext(), '${greeting()}', undefined); assert.strictEqual(result, 'hi'); - }); + }); }); }); @@ -125,22 +125,22 @@ describe('LGLanguageGenerator', function() { const multiLanguageResources = await LanguageResourceLoader.groupByLocale(resourceExplorer); //Should have a setup for the threadLocale here. - + let resource = resourceExplorer.getResource('test.lg'); lg.languageGenerators.set('', new TemplateEngineLanguageGenerator(resource, multiLanguageResources)); - + resource = resourceExplorer.getResource('test.de.lg'); lg.languageGenerators.set('de', new TemplateEngineLanguageGenerator(resource, multiLanguageResources)); - + resource = resourceExplorer.getResource('test.en.lg'); lg.languageGenerators.set('en', new TemplateEngineLanguageGenerator(resource, multiLanguageResources)); - + resource = resourceExplorer.getResource('test.en-US.lg'); lg.languageGenerators.set('en-us', new TemplateEngineLanguageGenerator(resource, multiLanguageResources)); - + resource = resourceExplorer.getResource('test.en-GB.lg'); lg.languageGenerators.set('en-gb', new TemplateEngineLanguageGenerator(resource, multiLanguageResources)); - + resource = resourceExplorer.getResource('test.fr.lg'); lg.languageGenerators.set('fr', new TemplateEngineLanguageGenerator(resource, multiLanguageResources)); }); @@ -255,4 +255,31 @@ describe('LGLanguageGenerator', function() { }); */ }); -}); \ No newline at end of file + + describe('TestMissingPropertiesInGenerator', () => { + let lgResourceGroup; + this.beforeAll(async function() { + lgResourceGroup = await LanguageResourceLoader.groupByLocale(resourceExplorer); + }); + + it('empty TemplateEngineLanguageGenerator', () => { + const generator = new TemplateEngineLanguageGenerator(); + const properties = generator.missingProperties(getDialogContext(''), '${user.name} and ${user.age}'); + assert.deepStrictEqual(properties, ['user.name', 'user.age']); + }); + + it('TemplateEngineLanguageGenerator', () => { + const resource = resourceExplorer.getResource('properties.lg'); + const generator = new TemplateEngineLanguageGenerator(resource, lgResourceGroup); + + const properties = generator.missingProperties(getDialogContext(''), '${nameAndAge()}'); + assert.deepStrictEqual(properties, ['user.name', 'user.age']); + }); + + it('ResourceMultiLanguageGenerator', () => { + const generator = new ResourceMultiLanguageGenerator('properties.lg'); + const properties = generator.missingProperties(getDialogContext(''), '${nameAndAge()}'); + assert.deepStrictEqual(properties, ['user.name', 'user.age']); + }); + }); +}); diff --git a/libraries/botbuilder-dialogs-adaptive/tests/lg/properties.lg b/libraries/botbuilder-dialogs-adaptive/tests/lg/properties.lg new file mode 100644 index 0000000000..2c39bc9558 --- /dev/null +++ b/libraries/botbuilder-dialogs-adaptive/tests/lg/properties.lg @@ -0,0 +1,2 @@ +# nameAndAge +- my name is ${user.name} and my age is ${user.age} \ No newline at end of file diff --git a/libraries/botbuilder-dialogs/etc/botbuilder-dialogs.api.md b/libraries/botbuilder-dialogs/etc/botbuilder-dialogs.api.md index 5eddc4ee4c..8705acb050 100644 --- a/libraries/botbuilder-dialogs/etc/botbuilder-dialogs.api.md +++ b/libraries/botbuilder-dialogs/etc/botbuilder-dialogs.api.md @@ -820,8 +820,12 @@ export class TurnPath { // (undocumented) static readonly interrupted = "turn.interrupted"; // (undocumented) + static readonly languagePolicy = "turn.languagePolicy"; + // (undocumented) static readonly lastResult = "turn.lastresult"; // (undocumented) + static readonly locale = "turn.locale"; + // (undocumented) static readonly recognized = "turn.recognized"; // (undocumented) static readonly recognizedEntities = "turn.recognizedEntities"; diff --git a/libraries/botbuilder-dialogs/src/dialogContext.ts b/libraries/botbuilder-dialogs/src/dialogContext.ts index 76356a9673..931eddf625 100644 --- a/libraries/botbuilder-dialogs/src/dialogContext.ts +++ b/libraries/botbuilder-dialogs/src/dialogContext.ts @@ -194,9 +194,7 @@ export class DialogContext { * @returns a locale string. */ public getLocale(): string { - const _turnLocaleProperty = 'turn.locale'; - - const turnLocaleValue = this.state.getValue(_turnLocaleProperty); + const turnLocaleValue = this.state.getValue(TurnPath.locale); if (turnLocaleValue) { return turnLocaleValue; } diff --git a/libraries/botbuilder-dialogs/src/memory/turnPath.ts b/libraries/botbuilder-dialogs/src/memory/turnPath.ts index bf7eaf40f3..c5af3ae5e2 100644 --- a/libraries/botbuilder-dialogs/src/memory/turnPath.ts +++ b/libraries/botbuilder-dialogs/src/memory/turnPath.ts @@ -45,4 +45,10 @@ export class TurnPath { /// This is a bool which if set means that the turncontext.activity has been consumed by some component in the system. public static readonly activityProcessed = 'turn.activityProcessed'; + + // Language Policy. + static readonly languagePolicy = "turn.languagePolicy"; + + /// Locale. + static readonly locale = "turn.locale"; }