From ff0bbc57cc2d996e9527620014f038ffe1505002 Mon Sep 17 00:00:00 2001 From: Matias Lera <62261539+luislera@users.noreply.github.com> Date: Wed, 8 Mar 2023 15:58:08 -0300 Subject: [PATCH 1/4] Added support to timeout for the DTMF dialogs --- .../Actions/BatchFixedLengthInput.cs | 13 ++- packages/Telephony/Actions/BatchRegexInput.cs | 106 ++++++++++++++++++ .../Actions/BatchTerminationCharacterInput.cs | 17 ++- packages/Telephony/Readme.md | 19 +++- ...oft.Telephony.BatchFixedLengthInput.schema | 24 ++++ ...t.Telephony.BatchFixedLengthInput.uischema | 3 + ...Microsoft.Telephony.BatchRegexInput.schema | 24 ++++ ...crosoft.Telephony.BatchRegexInput.uischema | 18 +++ ...hony.BatchTerminationCharacterInput.schema | 24 ++++ ...ny.BatchTerminationCharacterInput.uischema | 3 + 10 files changed, 238 insertions(+), 13 deletions(-) diff --git a/packages/Telephony/Actions/BatchFixedLengthInput.cs b/packages/Telephony/Actions/BatchFixedLengthInput.cs index 4f29fa889b..ba96682769 100644 --- a/packages/Telephony/Actions/BatchFixedLengthInput.cs +++ b/packages/Telephony/Actions/BatchFixedLengthInput.cs @@ -66,16 +66,23 @@ public int BatchLength } /// - public override Task ContinueDialogAsync(DialogContext dc, CancellationToken cancellationToken = default) + public async override Task ContinueDialogAsync(DialogContext dc, CancellationToken cancellationToken = default) { if ((dc.Context.Activity.Type == ActivityTypes.Message) && (Regex.Match(dc.Context.Activity.Text, _dtmfCharacterRegex).Success || dc.State.GetValue(TurnPath.Interrupted, () => false))) { - return base.ContinueDialogAsync(dc, cancellationToken); + return await base.ContinueDialogAsync(dc, cancellationToken).ConfigureAwait(false); } else { - return Task.FromResult(new DialogTurnResult(DialogTurnStatus.Waiting)); + if (dc.Context.Activity.Name == ActivityEventNames.ContinueConversation) + { + return await EndDialogAsync(dc, cancellationToken).ConfigureAwait(false); + } + else + { + return new DialogTurnResult(DialogTurnStatus.Waiting); + } } } } diff --git a/packages/Telephony/Actions/BatchRegexInput.cs b/packages/Telephony/Actions/BatchRegexInput.cs index 138243d3da..f96e43aed9 100644 --- a/packages/Telephony/Actions/BatchRegexInput.cs +++ b/packages/Telephony/Actions/BatchRegexInput.cs @@ -4,12 +4,15 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Security.Claims; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using AdaptiveExpressions.Properties; +using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Builder.Dialogs.Adaptive; +using Microsoft.Bot.Components.Telephony.Common; using Microsoft.Bot.Schema; using Newtonsoft.Json; @@ -23,6 +26,7 @@ public class BatchRegexInput : Dialog [JsonProperty("$kind")] public const string Kind = "Microsoft.Telephony.BatchRegexInput"; protected const string AggregationDialogMemory = "this.aggregation"; + private static IStateMatrix stateMatrix = new LatchingStateMatrix(); /// /// Initializes a new instance of the class. @@ -85,9 +89,36 @@ public BatchRegexInput([CallerFilePath] string sourceFilePath = "", [CallerLineN [JsonProperty("interruptionMask")] public StringExpression InterruptionMask { get; set; } + /// + /// Gets or sets a value indicating how long to wait for before timing out and using the default value. + /// + [JsonProperty("timeOutInMilliseconds")] + public IntExpression TimeOutInMilliseconds { get; set; } + + /// + /// Gets or sets the default value for the input dialog when a Timeout is reached. + /// + /// + /// Value or expression which evaluates to a value. + /// + [JsonProperty("defaultValue")] + public ValueExpression DefaultValue { get; set; } + + /// + /// Gets or sets the activity template to send when a Timeout is reached and the default value is used. + /// + /// + /// An activity template. + /// + [JsonProperty("defaultValueResponse")] + public ITemplate DefaultValueResponse { get; set; } + /// public override async Task BeginDialogAsync(DialogContext dc, object options = null, CancellationToken cancellationToken = default(CancellationToken)) { + //start a timer that will continue this conversation + await InitTimeoutTimerAsync(dc, cancellationToken).ConfigureAwait(false); + return await PromptUserAsync(dc, cancellationToken).ConfigureAwait(false); } @@ -120,6 +151,19 @@ public override async Task ContinueDialogAsync(DialogContext d } else { + //If we didn't timeout then we have to manage our timer somehow. + //For starters, complete our existing timer. + var timerId = dc.State.GetValue("this.TimerId"); + + //Should never happen but if it does, it shouldn't be fatal. + if (timerId != null) + { + await stateMatrix.CompleteAsync(timerId).ConfigureAwait(false); + } + + // Restart the timeout timer + await InitTimeoutTimerAsync(dc, cancellationToken).ConfigureAwait(false); + //else, save the updated aggregation and end the turn dc.State.SetValue(AggregationDialogMemory, existingAggregation); return new DialogTurnResult(DialogTurnStatus.Waiting); @@ -158,6 +202,30 @@ protected override async Task OnPreBubbleEventAsync(DialogContext dc, Dial return false; } + protected async Task EndDialogAsync(DialogContext dc, CancellationToken cancellationToken) + { + // Set the default value to the output property and send the default value response to the user + if (this.DefaultValue != null) + { + var (value, error) = this.DefaultValue.TryGetValue(dc.State); + if (this.DefaultValueResponse != null) + { + var response = await this.DefaultValueResponse.BindAsync(dc, cancellationToken: cancellationToken).ConfigureAwait(false); + if (response != null) + { + await dc.Context.SendActivityAsync(response, cancellationToken).ConfigureAwait(false); + } + } + + // Set output property + dc.State.SetValue(this.Property.GetValue(dc.State), value); + + return await dc.EndDialogAsync(value, cancellationToken).ConfigureAwait(false); + } + + return await dc.EndDialogAsync(cancellationToken: cancellationToken).ConfigureAwait(false); + } + private async Task PromptUserAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken)) { //Do we already have a value stored? This would happen in the interruption case, a case in which we are looping over ourselves, or maybe we had a fatal error and had to restart the dialog tree @@ -198,5 +266,43 @@ protected override async Task OnPreBubbleEventAsync(DialogContext dc, Dial return new DialogTurnResult(DialogTurnStatus.Waiting); } + + private void CreateTimerForConversation(DialogContext dc, int timeout, string timerId, CancellationToken cancellationToken) + { + BotAdapter adapter = dc.Context.Adapter; + ConversationReference conversationReference = dc.Context.Activity.GetConversationReference(); + var identity = dc.Context.TurnState.Get("BotIdentity"); + var audience = dc.Context.TurnState.Get(BotAdapter.OAuthScopeKey); + + //Question remaining to be answered: Will this task get garbage collected? If so, we need to maintain a handle for it. + Task.Run(async () => + { + await Task.Delay(timeout).ConfigureAwait(false); + + //if we aren't already complete, go ahead and timeout + await stateMatrix.RunForStatusAsync(timerId, StateStatus.Running, async () => + { + await adapter.ContinueConversationAsync( + identity, + conversationReference, + audience, + BotWithLookup.OnTurn, //Leverage dirty hack to achieve Bot lookup from component + cancellationToken).ConfigureAwait(false); + }).ConfigureAwait(false); + }); + } + + private async Task InitTimeoutTimerAsync(DialogContext dc, CancellationToken cancellationToken) + { + var timeout = this.TimeOutInMilliseconds?.GetValue(dc.State) ?? 0; + + if (timeout > 0) + { + var timerId = Guid.NewGuid().ToString(); + CreateTimerForConversation(dc, timeout, timerId, cancellationToken); + await stateMatrix.StartAsync(timerId).ConfigureAwait(false); + dc.State.SetValue("this.TimerId", timerId); + } + } } } \ No newline at end of file diff --git a/packages/Telephony/Actions/BatchTerminationCharacterInput.cs b/packages/Telephony/Actions/BatchTerminationCharacterInput.cs index 559508cc6f..877617923f 100644 --- a/packages/Telephony/Actions/BatchTerminationCharacterInput.cs +++ b/packages/Telephony/Actions/BatchTerminationCharacterInput.cs @@ -60,16 +60,23 @@ public string TerminationCharacter } /// - public override Task ContinueDialogAsync(DialogContext dc, CancellationToken cancellationToken = default) + public override async Task ContinueDialogAsync(DialogContext dc, CancellationToken cancellationToken = default) { - if ((dc.Context.Activity.Type == ActivityTypes.Message) && - (Regex.Match(dc.Context.Activity.Text, _dtmfCharacterRegex).Success || dc.State.GetValue(TurnPath.Interrupted, () => false))) + if ((dc.Context.Activity.Type == ActivityTypes.Message) && + (Regex.Match(dc.Context.Activity.Text, _dtmfCharacterRegex).Success || dc.State.GetValue(TurnPath.Interrupted, () => false))) { - return base.ContinueDialogAsync(dc, cancellationToken); + return await base.ContinueDialogAsync(dc, cancellationToken).ConfigureAwait(false); } else { - return Task.FromResult(new DialogTurnResult(DialogTurnStatus.Waiting)); + if (dc.Context.Activity.Name == ActivityEventNames.ContinueConversation) + { + return await EndDialogAsync(dc, cancellationToken).ConfigureAwait(false); + } + else + { + return new DialogTurnResult(DialogTurnStatus.Waiting); + } } } } diff --git a/packages/Telephony/Readme.md b/packages/Telephony/Readme.md index bdb78892b8..26148e80c3 100644 --- a/packages/Telephony/Readme.md +++ b/packages/Telephony/Readme.md @@ -120,6 +120,8 @@ The Stop Recording action stops recording of the conversation. Note that it is n ## **Aggregate DTMF Input (n)** Prompts the user for multiple inputs that are aggregated until a specified character length is met or exceeded. Speech, DTMF inputs, and chat provided characters can all be used to provide input, but any inputs that aren't the characters 1,2,3,4,5,6,7,8,9,0,#,*, or some combination of said characters are dropped. +A timeout timer will be initialized when the dialog begins or when the user sent a response that has not meet or exceed the batch length. +When the timeout is reached, the dialog will end and if the Default Value is set, its value will be assigned to the Property field. Also, a response can be sent to the user using the Default value Response field. #### Parameters * Batch Length @@ -127,16 +129,18 @@ Speech, DTMF inputs, and chat provided characters can all be used to provide inp * Prompt * AllowInterruptions * AlwaysPrompt +* Timeout +* Default Value +* Default Value Response #### Usage * After started, each input the user sends will be appended to the last message until the user provides a number of characters equal to or greater than the batch length. #### Dialog Flow -* The dialog will only end and continue to the next dialog when the batch length is reached. +* The dialog will only end and continue to the next dialog when the batch length is reached or the timeout is reached. * If AllowInterruptions is true, the parent dialog will receive non-digit input and can handle it as an intent. * After the interruption is handled, control flow will resume with this dialog. If AlwaysPrompt is set to true, the dialog will attempt to start over, otherwise it will end this dialog without setting the output property. -* Best practice recommendation when using interruptions is to validate that the output property has been set and handle the case in which it is and isn't set.' - +* Best practice recommendation when using interruptions is to validate that the output property has been set and handle the case in which it is and isn't set. #### Failures * In the event that an exception occurs within the dialog, the dialog will end and the normal exception flow can be followed. @@ -145,6 +149,8 @@ Speech, DTMF inputs, and chat provided characters can all be used to provide inp ## **Aggregate DTMF Input (#)** Prompts the user for multiple inputs that are aggregated until the termination string is received. Speech, DTMF inputs, and chat provided characters can all be used to provide input, but any inputs that aren't the characters 1,2,3,4,5,6,7,8,9,0,#,*, or some combination of said characters are dropped. +A timeout timer will be initialized when the dialog begins or when the user sent a response without including the termination character. +When the timeout is reached, the dialog will end and if the Default Value is set, its value will be assigned to the Property field. Also, a response can be sent to the user using the Default value Response field. #### Parameters * Termination Character @@ -152,15 +158,18 @@ Speech, DTMF inputs, and chat provided characters can all be used to provide inp * Prompt * AllowInterruptions * AlwaysPrompt +* Timeout +* Default Value +* Default Value Response #### Usage * After started, each input the user sends will be appended to the last message until the user sends the provided termination character #### Dialog Flow -* The dialog will only end and continue to the next dialog when the termination character is sent. +* The dialog will only end and continue to the next dialog when the termination character is sent or the timeout is reached. * If AllowInterruptions is true, the parent dialog will receive non-digit input and can handle it as an intent. * After the interruption is handled, control flow will resume with this dialog. If AlwaysPrompt is set to true, the dialog will attempt to start over, otherwise it will end this dialog without setting the output property. -* Best practice recommendation when using interruptions is to validate that the output property has been set and handle the case in which it is and isn't set.' +* Best practice recommendation when using interruptions is to validate that the output property has been set and handle the case in which it is and isn't set. #### Failures * In the event that an exception occurs within the dialog, the dialog will end and the normal exception flow can be followed. diff --git a/packages/Telephony/Schemas/Microsoft.Telephony.BatchFixedLengthInput.schema b/packages/Telephony/Schemas/Microsoft.Telephony.BatchFixedLengthInput.schema index a591bfc4f8..f72b5af1ab 100644 --- a/packages/Telephony/Schemas/Microsoft.Telephony.BatchFixedLengthInput.schema +++ b/packages/Telephony/Schemas/Microsoft.Telephony.BatchFixedLengthInput.schema @@ -49,6 +49,30 @@ "$ref": "schema:#/definitions/booleanExpression", "title": "Always Prompt", "description": "When true, if batch is interrupted, it will attempt to restart the batch rather than abandon it." + }, + "timeOutInMilliseconds": { + "$ref": "schema:#/definitions/integerExpression", + "title": "Timeout in milliseconds", + "description": "After the specified amount of milliseconds the dialog will complete with its default value if the user doesn't respond.", + "examples": [ + "10", + "=conversation.xyz" + ] + }, + "defaultValue": { + "$ref": "schema:#/definitions/stringExpression", + "title": "Default value", + "description": "'Property' will be set to the value of this expression when a timeout is reached.", + "examples": [ + "hello world", + "Hello ${user.name}", + "=concat(user.firstname, user.lastName)" + ] + }, + "defaultValueResponse": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Default value response", + "description": "Message to send when a Timeout has been reached and the default value is selected as the value." } }, "$policies": { diff --git a/packages/Telephony/Schemas/Microsoft.Telephony.BatchFixedLengthInput.uischema b/packages/Telephony/Schemas/Microsoft.Telephony.BatchFixedLengthInput.uischema index a329253eac..1f81cf8dd6 100644 --- a/packages/Telephony/Schemas/Microsoft.Telephony.BatchFixedLengthInput.uischema +++ b/packages/Telephony/Schemas/Microsoft.Telephony.BatchFixedLengthInput.uischema @@ -9,6 +9,9 @@ "property", "allowInterruptions", "alwaysPrompt", + "timeOutInMilliseconds", + "defaultValue", + "defaultValueResponse", "*" ], "properties": { diff --git a/packages/Telephony/Schemas/Microsoft.Telephony.BatchRegexInput.schema b/packages/Telephony/Schemas/Microsoft.Telephony.BatchRegexInput.schema index 3fdb84df79..92433c066a 100644 --- a/packages/Telephony/Schemas/Microsoft.Telephony.BatchRegexInput.schema +++ b/packages/Telephony/Schemas/Microsoft.Telephony.BatchRegexInput.schema @@ -57,6 +57,30 @@ "$ref": "schema:#/definitions/booleanExpression", "title": "Always Prompt", "description": "When true, if batch is interrupted, it will attempt to restart the batch rather than abandon it." + }, + "timeOutInMilliseconds": { + "$ref": "schema:#/definitions/integerExpression", + "title": "Timeout in milliseconds", + "description": "After the specified amount of milliseconds the dialog will complete with its default value if the user doesn't respond.", + "examples": [ + "10", + "=conversation.xyz" + ] + }, + "defaultValue": { + "$ref": "schema:#/definitions/stringExpression", + "title": "Default value", + "description": "'Property' will be set to the value of this expression when a timeout is reached.", + "examples": [ + "hello world", + "Hello ${user.name}", + "=concat(user.firstname, user.lastName)" + ] + }, + "defaultValueResponse": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Default value response", + "description": "Message to send when a Timeout has been reached and the default value is selected as the value." } }, "$policies": { diff --git a/packages/Telephony/Schemas/Microsoft.Telephony.BatchRegexInput.uischema b/packages/Telephony/Schemas/Microsoft.Telephony.BatchRegexInput.uischema index f3a8ddf0c2..02bc872bf4 100644 --- a/packages/Telephony/Schemas/Microsoft.Telephony.BatchRegexInput.uischema +++ b/packages/Telephony/Schemas/Microsoft.Telephony.BatchRegexInput.uischema @@ -9,6 +9,9 @@ "property", "allowInterruptions", "alwaysPrompt", + "timeOutInMilliseconds", + "defaultValue", + "defaultValueResponse", "*" ], "properties": { @@ -36,6 +39,21 @@ "intellisenseScopes": [ "variable-scopes" ] + }, + "timeOutInMilliseconds": { + "intellisenseScopes": [ + "variable-scopes" + ] + }, + "defaultValue": { + "intellisenseScopes": [ + "variable-scopes" + ] + }, + "defaultValueResponse": { + "intellisenseScopes": [ + "variable-scopes" + ] } } }, diff --git a/packages/Telephony/Schemas/Microsoft.Telephony.BatchTerminationCharacterInput.schema b/packages/Telephony/Schemas/Microsoft.Telephony.BatchTerminationCharacterInput.schema index 62c89f2f1b..0151b636a1 100644 --- a/packages/Telephony/Schemas/Microsoft.Telephony.BatchTerminationCharacterInput.schema +++ b/packages/Telephony/Schemas/Microsoft.Telephony.BatchTerminationCharacterInput.schema @@ -49,6 +49,30 @@ "$ref": "schema:#/definitions/booleanExpression", "title": "Always Prompt", "description": "When true, if batch is interrupted, it will attempt to restart the batch rather than abandon it." + }, + "timeOutInMilliseconds": { + "$ref": "schema:#/definitions/integerExpression", + "title": "Timeout in milliseconds", + "description": "After the specified amount of milliseconds the dialog will complete with its default value if the user doesn't respond.", + "examples": [ + "10", + "=conversation.xyz" + ] + }, + "defaultValue": { + "$ref": "schema:#/definitions/stringExpression", + "title": "Default value", + "description": "'Property' will be set to the value of this expression when a timeout is reached.", + "examples": [ + "hello world", + "Hello ${user.name}", + "=concat(user.firstname, user.lastName)" + ] + }, + "defaultValueResponse": { + "$kind": "Microsoft.IActivityTemplate", + "title": "Default value response", + "description": "Message to send when a Timeout has been reached and the default value is selected as the value." } },"$policies": { "interactive": true diff --git a/packages/Telephony/Schemas/Microsoft.Telephony.BatchTerminationCharacterInput.uischema b/packages/Telephony/Schemas/Microsoft.Telephony.BatchTerminationCharacterInput.uischema index 1e563f0217..d2eafed60f 100644 --- a/packages/Telephony/Schemas/Microsoft.Telephony.BatchTerminationCharacterInput.uischema +++ b/packages/Telephony/Schemas/Microsoft.Telephony.BatchTerminationCharacterInput.uischema @@ -9,6 +9,9 @@ "property", "allowInterruptions", "alwaysPrompt", + "timeOutInMilliseconds", + "defaultValue", + "defaultValueResponse", "*" ], "properties": { From e62836329b0deff397a3c744bfc4b5819010b860 Mon Sep 17 00:00:00 2001 From: Matias Lera <62261539+luislera@users.noreply.github.com> Date: Tue, 28 Mar 2023 17:23:21 -0300 Subject: [PATCH 2/4] Applied requested changes --- packages/Telephony/Actions/BatchRegexInput.cs | 16 ++++++++-------- packages/Telephony/Readme.md | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/Telephony/Actions/BatchRegexInput.cs b/packages/Telephony/Actions/BatchRegexInput.cs index f96e43aed9..57401a42a3 100644 --- a/packages/Telephony/Actions/BatchRegexInput.cs +++ b/packages/Telephony/Actions/BatchRegexInput.cs @@ -26,6 +26,7 @@ public class BatchRegexInput : Dialog [JsonProperty("$kind")] public const string Kind = "Microsoft.Telephony.BatchRegexInput"; protected const string AggregationDialogMemory = "this.aggregation"; + private const string TimerId = "this.TimerId"; private static IStateMatrix stateMatrix = new LatchingStateMatrix(); /// @@ -153,16 +154,15 @@ public override async Task ContinueDialogAsync(DialogContext d { //If we didn't timeout then we have to manage our timer somehow. //For starters, complete our existing timer. - var timerId = dc.State.GetValue("this.TimerId"); + string timerId = dc.State.GetValue(TimerId); - //Should never happen but if it does, it shouldn't be fatal. if (timerId != null) { await stateMatrix.CompleteAsync(timerId).ConfigureAwait(false); - } - // Restart the timeout timer - await InitTimeoutTimerAsync(dc, cancellationToken).ConfigureAwait(false); + // Restart the timeout timer + await InitTimeoutTimerAsync(dc, cancellationToken).ConfigureAwait(false); + } //else, save the updated aggregation and end the turn dc.State.SetValue(AggregationDialogMemory, existingAggregation); @@ -269,8 +269,8 @@ protected async Task EndDialogAsync(DialogContext dc, Cancella private void CreateTimerForConversation(DialogContext dc, int timeout, string timerId, CancellationToken cancellationToken) { - BotAdapter adapter = dc.Context.Adapter; - ConversationReference conversationReference = dc.Context.Activity.GetConversationReference(); + var adapter = dc.Context.Adapter; + var conversationReference = dc.Context.Activity.GetConversationReference(); var identity = dc.Context.TurnState.Get("BotIdentity"); var audience = dc.Context.TurnState.Get(BotAdapter.OAuthScopeKey); @@ -301,7 +301,7 @@ private async Task InitTimeoutTimerAsync(DialogContext dc, CancellationToken can var timerId = Guid.NewGuid().ToString(); CreateTimerForConversation(dc, timeout, timerId, cancellationToken); await stateMatrix.StartAsync(timerId).ConfigureAwait(false); - dc.State.SetValue("this.TimerId", timerId); + dc.State.SetValue(TimerId, timerId); } } } diff --git a/packages/Telephony/Readme.md b/packages/Telephony/Readme.md index 26148e80c3..01b3024e24 100644 --- a/packages/Telephony/Readme.md +++ b/packages/Telephony/Readme.md @@ -120,8 +120,8 @@ The Stop Recording action stops recording of the conversation. Note that it is n ## **Aggregate DTMF Input (n)** Prompts the user for multiple inputs that are aggregated until a specified character length is met or exceeded. Speech, DTMF inputs, and chat provided characters can all be used to provide input, but any inputs that aren't the characters 1,2,3,4,5,6,7,8,9,0,#,*, or some combination of said characters are dropped. -A timeout timer will be initialized when the dialog begins or when the user sent a response that has not meet or exceed the batch length. -When the timeout is reached, the dialog will end and if the Default Value is set, its value will be assigned to the Property field. Also, a response can be sent to the user using the Default value Response field. +When the Timeout parameter is set an integer greater than 0, a timer will be set whenever the Aggreate DTMF Input(n) node begins. This timer will be reset whenever the user responds to the bot, until the expected batch length is met. +In case the timeout is reached, the dialog will end and if the Default Value is set, its value will be assigned to the Property field. Also, a response can be sent to the user using the Default value Response field. #### Parameters * Batch Length @@ -149,8 +149,8 @@ When the timeout is reached, the dialog will end and if the Default Value is set ## **Aggregate DTMF Input (#)** Prompts the user for multiple inputs that are aggregated until the termination string is received. Speech, DTMF inputs, and chat provided characters can all be used to provide input, but any inputs that aren't the characters 1,2,3,4,5,6,7,8,9,0,#,*, or some combination of said characters are dropped. -A timeout timer will be initialized when the dialog begins or when the user sent a response without including the termination character. -When the timeout is reached, the dialog will end and if the Default Value is set, its value will be assigned to the Property field. Also, a response can be sent to the user using the Default value Response field. +When the Timeout parameter is set an integer greater than 0, a timer will be set whenever the Aggreate DTMF Input(n) node begins. This timer will be reset whenever the user responds to the bot, until the expected batch length is met. +In case the timeout is reached, the dialog will end and if the Default Value is set, its value will be assigned to the Property field. Also, a response can be sent to the user using the Default value Response field. #### Parameters * Termination Character From 8e5fc0fe6104159795560ec6d808d82ed6e10b02 Mon Sep 17 00:00:00 2001 From: Matias Lera <62261539+luislera@users.noreply.github.com> Date: Tue, 28 Mar 2023 17:24:05 -0300 Subject: [PATCH 3/4] Added test cases --- .../BatchInputTests.cs | 103 +++++++++++++++++- 1 file changed, 99 insertions(+), 4 deletions(-) diff --git a/tests/unit/packages/Microsoft.Bot.Components.Telephony.Tests/BatchInputTests.cs b/tests/unit/packages/Microsoft.Bot.Components.Telephony.Tests/BatchInputTests.cs index 8c79d2a1b6..67fcbe1cc7 100644 --- a/tests/unit/packages/Microsoft.Bot.Components.Telephony.Tests/BatchInputTests.cs +++ b/tests/unit/packages/Microsoft.Bot.Components.Telephony.Tests/BatchInputTests.cs @@ -1,11 +1,19 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using System.Collections.Generic; -using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Dialogs.Adaptive.Templates; +using Microsoft.Bot.Builder.Dialogs.Memory; +using Microsoft.Bot.Builder.Dialogs.Memory.Scopes; +using Microsoft.Bot.Components.Telephony.Actions; using Microsoft.Bot.Connector; using Microsoft.Bot.Schema; using Microsoft.Extensions.Configuration; +using Moq; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using Xunit; namespace Microsoft.Bot.Components.Telephony.Tests @@ -74,6 +82,51 @@ await TestUtils.RunTestScript( .Build()); } + + [Fact] + public async Task BatchInput_Termination_WithTimeoutTriggered() + { + // Setup + var mockDefaultValue = "test value"; + var mockActivityText = "activity text"; + var mockTurnContext = new Mock(); + var configuration = new DialogStateManagerConfiguration + { + MemoryScopes = new List { new ThisMemoryScope(), new TurnMemoryScope() } + }; + + var turnState = new TurnContextStateCollection(); + turnState.Add(configuration); + + mockTurnContext + .SetupGet(ctx => ctx.Activity) + .Returns(new Activity { Type = ActivityTypes.Event, Name = ActivityEventNames.ContinueConversation }); + + mockTurnContext + .Setup(ctx => ctx.SendActivityAsync(It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(new ResourceResponse())); + + mockTurnContext + .SetupGet(ctx => ctx.TurnState) + .Returns(turnState); + + var dc = new DialogContext(new DialogSet(), mockTurnContext.Object, new DialogState()); + dc.Stack.Add(new DialogInstance { Id = "BatchTerminationCharacter" }); + + var batchFixedLengthInput = new BatchTerminationCharacterInput(); + batchFixedLengthInput.Property = "turn.result"; + batchFixedLengthInput.DefaultValue = mockDefaultValue; + batchFixedLengthInput.DefaultValueResponse = new ActivityTemplate(mockActivityText); + + // Act + var dialogTurnResult = await batchFixedLengthInput.ContinueDialogAsync(dc); + + // Assert + Assert.Equal(mockDefaultValue, dialogTurnResult.Result); + Assert.Equal(mockDefaultValue, dc.State.GetValue("turn.result", () => string.Empty)); + mockTurnContext.Verify(ctx => ctx.SendActivityAsync(It.Is(act => act.Text == mockActivityText), It.IsAny()), Times.Exactly(1)); + } + [Fact] public async Task BatchInput_FixedLength_WithTangent_InterruptionEnabled() { @@ -114,6 +167,50 @@ public async Task BatchInput_FixedLength_InterruptionIgnoredForMaskedDigits() await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, adapterChannel: Channels.Telephony); } + [Fact] + public async Task BatchInput_FixedLength_WithTimeoutTriggered() + { + // Setup + var mockDefaultValue = "test value"; + var mockActivityText = "activity text"; + var mockTurnContext = new Mock(); + var configuration = new DialogStateManagerConfiguration + { + MemoryScopes = new List { new ThisMemoryScope(), new TurnMemoryScope() } + }; + + var turnState = new TurnContextStateCollection(); + turnState.Add(configuration); + + mockTurnContext + .SetupGet(ctx => ctx.Activity) + .Returns(new Activity { Type = ActivityTypes.Event, Name = ActivityEventNames.ContinueConversation }); + + mockTurnContext + .Setup(ctx => ctx.SendActivityAsync(It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(new ResourceResponse())); + + mockTurnContext + .SetupGet(ctx => ctx.TurnState) + .Returns(turnState); + + var dc = new DialogContext(new DialogSet(), mockTurnContext.Object, new DialogState()); + dc.Stack.Add(new DialogInstance { Id = "BatchFixedLength" }); + + var batchFixedLengthInput = new BatchFixedLengthInput(); + batchFixedLengthInput.Property = "turn.result"; + batchFixedLengthInput.DefaultValue = mockDefaultValue; + batchFixedLengthInput.DefaultValueResponse = new ActivityTemplate(mockActivityText); + + // Act + var dialogTurnResult = await batchFixedLengthInput.ContinueDialogAsync(dc); + + // Assert + Assert.Equal(mockDefaultValue, dialogTurnResult.Result); + Assert.Equal(mockDefaultValue, dc.State.GetValue("turn.result", () => string.Empty)); + mockTurnContext.Verify(ctx => ctx.SendActivityAsync(It.Is(act => act.Text == mockActivityText), It.IsAny()), Times.Exactly(1)); + } + [Fact] public async Task BatchInput_Regex_WithTangent_InterruptionEnabled() { @@ -153,7 +250,5 @@ public async Task BatchInput_Regex_InterruptionIgnoredForMaskedDigits() { await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, adapterChannel: Channels.Telephony); } - - } } From 5e7b6a6f6593979191c907b5350240fa1e9d477b Mon Sep 17 00:00:00 2001 From: Matias Lera <62261539+luislera@users.noreply.github.com> Date: Wed, 29 Mar 2023 18:21:59 -0300 Subject: [PATCH 4/4] Update BatchInputTests.cs --- .../BatchInputTests.cs | 74 ++++++++----------- 1 file changed, 30 insertions(+), 44 deletions(-) diff --git a/tests/unit/packages/Microsoft.Bot.Components.Telephony.Tests/BatchInputTests.cs b/tests/unit/packages/Microsoft.Bot.Components.Telephony.Tests/BatchInputTests.cs index 67fcbe1cc7..fc8d9aade7 100644 --- a/tests/unit/packages/Microsoft.Bot.Components.Telephony.Tests/BatchInputTests.cs +++ b/tests/unit/packages/Microsoft.Bot.Components.Telephony.Tests/BatchInputTests.cs @@ -90,28 +90,7 @@ public async Task BatchInput_Termination_WithTimeoutTriggered() var mockDefaultValue = "test value"; var mockActivityText = "activity text"; var mockTurnContext = new Mock(); - var configuration = new DialogStateManagerConfiguration - { - MemoryScopes = new List { new ThisMemoryScope(), new TurnMemoryScope() } - }; - - var turnState = new TurnContextStateCollection(); - turnState.Add(configuration); - - mockTurnContext - .SetupGet(ctx => ctx.Activity) - .Returns(new Activity { Type = ActivityTypes.Event, Name = ActivityEventNames.ContinueConversation }); - - mockTurnContext - .Setup(ctx => ctx.SendActivityAsync(It.IsAny(), It.IsAny())) - .Returns(Task.FromResult(new ResourceResponse())); - - mockTurnContext - .SetupGet(ctx => ctx.TurnState) - .Returns(turnState); - - var dc = new DialogContext(new DialogSet(), mockTurnContext.Object, new DialogState()); - dc.Stack.Add(new DialogInstance { Id = "BatchTerminationCharacter" }); + var dc = GetDialogContext(mockTurnContext); var batchFixedLengthInput = new BatchTerminationCharacterInput(); batchFixedLengthInput.Property = "turn.result"; @@ -174,28 +153,7 @@ public async Task BatchInput_FixedLength_WithTimeoutTriggered() var mockDefaultValue = "test value"; var mockActivityText = "activity text"; var mockTurnContext = new Mock(); - var configuration = new DialogStateManagerConfiguration - { - MemoryScopes = new List { new ThisMemoryScope(), new TurnMemoryScope() } - }; - - var turnState = new TurnContextStateCollection(); - turnState.Add(configuration); - - mockTurnContext - .SetupGet(ctx => ctx.Activity) - .Returns(new Activity { Type = ActivityTypes.Event, Name = ActivityEventNames.ContinueConversation }); - - mockTurnContext - .Setup(ctx => ctx.SendActivityAsync(It.IsAny(), It.IsAny())) - .Returns(Task.FromResult(new ResourceResponse())); - - mockTurnContext - .SetupGet(ctx => ctx.TurnState) - .Returns(turnState); - - var dc = new DialogContext(new DialogSet(), mockTurnContext.Object, new DialogState()); - dc.Stack.Add(new DialogInstance { Id = "BatchFixedLength" }); + var dc = GetDialogContext(mockTurnContext); var batchFixedLengthInput = new BatchFixedLengthInput(); batchFixedLengthInput.Property = "turn.result"; @@ -250,5 +208,33 @@ public async Task BatchInput_Regex_InterruptionIgnoredForMaskedDigits() { await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer, adapterChannel: Channels.Telephony); } + + private DialogContext GetDialogContext(Mock turnContext) + { + var configuration = new DialogStateManagerConfiguration + { + MemoryScopes = new List { new ThisMemoryScope(), new TurnMemoryScope() } + }; + + var turnState = new TurnContextStateCollection(); + turnState.Add(configuration); + + turnContext + .SetupGet(ctx => ctx.Activity) + .Returns(new Activity { Type = ActivityTypes.Event, Name = ActivityEventNames.ContinueConversation }); + + turnContext + .Setup(ctx => ctx.SendActivityAsync(It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(new ResourceResponse())); + + turnContext + .SetupGet(ctx => ctx.TurnState) + .Returns(turnState); + + var dc = new DialogContext(new DialogSet(), turnContext.Object, new DialogState()); + dc.Stack.Add(new DialogInstance { Id = "DialogInstanceId" }); + + return dc; + } } }