From f4d17b56a4134d7da21ccc5e582fa984766f8071 Mon Sep 17 00:00:00 2001 From: Rafael Santiago Date: Mon, 28 Nov 2022 08:44:30 -0300 Subject: [PATCH 1/5] changed the way of store previous-stateId and stateId context variable to store to a single variable --- src/Take.Blip.Builder/FlowManager.cs | 28 +++++++------ src/Take.Blip.Builder/IStateManager.cs | 8 +--- src/Take.Blip.Builder/StateManager.cs | 54 +++++++++++++++++--------- 3 files changed, 52 insertions(+), 38 deletions(-) diff --git a/src/Take.Blip.Builder/FlowManager.cs b/src/Take.Blip.Builder/FlowManager.cs index bf5836d5..39c66fba 100644 --- a/src/Take.Blip.Builder/FlowManager.cs +++ b/src/Take.Blip.Builder/FlowManager.cs @@ -241,17 +241,14 @@ await context.SetVariableAsync(state.Input.Variable, lazyInput.SerializedContent // Determine the next state state = await ProcessOutputsAsync(lazyInput, context, flow, state, stateTrace?.Outputs, linkedCts.Token); - // Store the previous state - await _stateManager.SetPreviousStateIdAsync(context, previousStateId, linkedCts.Token); - if (IsSubflowState(state)) { parentStateIdQueue.Enqueue(state.Id); (flow, state, stateTrace, stateStopwatch) = await RedirectToSubflowAsync( - context, - userIdentity, + context, state, + previousStateId, flow, stateTrace, stateStopwatch, @@ -281,11 +278,19 @@ await GetParentStateIdAsync(context, parentStateIdQueue, linkedCts.Token), // Store the next state if (state != null) { - await _stateManager.SetStateIdAsync(context, state.Id, linkedCts.Token); + await _stateManager.SetStateIdAsync(context, state.Id, previousStateId, linkedCts.Token); } else { - await _stateManager.DeleteStateIdAsync(context, linkedCts.Token); + if (previousStateId.IsNullOrEmpty()) + { + await _stateManager.DeleteStateIdAsync(context, linkedCts.Token); + } + else + { + await _stateManager.SetStateIdAsync(context, "", previousStateId, linkedCts.Token); + } + } // Process the next state input actions @@ -387,7 +392,7 @@ private async Task ProcessGlobalOutputActionsAsync(IContext context, Flow flow, } } - private async Task<(Flow, State, StateTrace, Stopwatch)> RedirectToSubflowAsync(IContext context, Identity userIdentity, State state, Flow parentFlow, StateTrace stateTrace, Stopwatch stateStopwatch, InputTrace inputTrace, LazyInput lazyInput, CancellationToken cancellationToken) + private async Task<(Flow, State, StateTrace, Stopwatch)> RedirectToSubflowAsync(IContext context, State state, string previousStateId, Flow parentFlow, StateTrace stateTrace, Stopwatch stateStopwatch, InputTrace inputTrace, LazyInput lazyInput, CancellationToken cancellationToken) { var shortNameOfSubflow = state.GetExtensionDataValue(SHORTNAME_OF_SUBFLOW_EXTENSION_DATA); if (shortNameOfSubflow.IsNullOrEmpty()) @@ -398,11 +403,11 @@ private async Task ProcessGlobalOutputActionsAsync(IContext context, Flow flow, // Create trace instances, if required var (newStateTrace, newStateStopwatch) = _traceManager.CreateStateTrace(inputTrace, state, stateTrace, stateStopwatch); + await _stateManager.SetStateIdAsync(context, state.Id, previousStateId, cancellationToken); + // Process the next state input actions await ProcessStateInputActionsAsync(state, lazyInput, context, stateTrace, cancellationToken); - await _stateManager.SetStateIdAsync(context, state.Id, cancellationToken); - var subflow = await _flowLoader.LoadFlowAsync(FlowType.Subflow, parentFlow, shortNameOfSubflow, cancellationToken); if (subflow == null) { @@ -427,8 +432,7 @@ private async Task ProcessGlobalOutputActionsAsync(IContext context, Flow flow, throw new ArgumentNullException($"Error on return to parent flow of '{flow.Id}'"); } - await _stateManager.SetPreviousStateIdAsync(context, previousStateId, cancellationToken); - await _stateManager.DeleteStateIdAsync(context, cancellationToken); + await _stateManager.SetStateIdAsync(context, "", previousStateId, cancellationToken); context.Flow = parentFlow; var state = parentFlow.States.FirstOrDefault(s => s.Id == parentStateId) ?? parentFlow.States.Single(s => s.Root); diff --git a/src/Take.Blip.Builder/IStateManager.cs b/src/Take.Blip.Builder/IStateManager.cs index 63abd390..971a2a99 100644 --- a/src/Take.Blip.Builder/IStateManager.cs +++ b/src/Take.Blip.Builder/IStateManager.cs @@ -28,14 +28,8 @@ public interface IStateManager /// Sets the current state for the user in the flow. /// /// - Task SetStateIdAsync(IContext context, string stateId, CancellationToken cancellationToken); + Task SetStateIdAsync(IContext context, string stateId, string previousStateId, CancellationToken cancellationToken); - /// - /// Sets the previous state id for the user in the flow. - /// This action is only informative and do not affect the user navigation. - /// - /// - Task SetPreviousStateIdAsync(IContext context, string previousStateId, CancellationToken cancellationToken); /// /// Deletes the current state for the user in the flow. diff --git a/src/Take.Blip.Builder/StateManager.cs b/src/Take.Blip.Builder/StateManager.cs index d5335524..aa617e17 100644 --- a/src/Take.Blip.Builder/StateManager.cs +++ b/src/Take.Blip.Builder/StateManager.cs @@ -12,44 +12,60 @@ public class StateManager : IStateManager private const string PREVIOUS_STATE_PREFIX = "previous"; private const string STATE_ID_KEY = "stateId"; - public Task GetStateIdAsync(IContext context, CancellationToken cancellationToken) - { - return context.GetContextVariableAsync(GetStateKey(context.Flow.Id), cancellationToken); - } + private const int STATE_ID_INDEX = 0; + private const int PREVIOUS_STATE_ID_INDEX = 1; + private const string STATE_AND_PREVIOUS_SEPARATOR = ":"; + + public async Task GetStateIdAsync(IContext context, CancellationToken cancellationToken) => ExtractStateId(await GetStateIdContextVariable(context, cancellationToken)); - public Task GetPreviousStateIdAsync(IContext context, CancellationToken cancellationToken) + public async Task GetPreviousStateIdAsync(IContext context, CancellationToken cancellationToken) { - return context.GetContextVariableAsync(GetPreviousStateKey(context.Flow.Id), cancellationToken); + var previousStateId = ExtractPreviousStateId(await GetStateIdContextVariable(context, cancellationToken)); + + //using GetPreviousStateKey to compatibility with previous versions + return previousStateId.IsNullOrEmpty() ? await context.GetContextVariableAsync(GetPreviousStateKey(context.Flow.Id), cancellationToken) : previousStateId; } - public Task GetParentStateIdAsync(IContext context, CancellationToken cancellationToken) + public async Task GetParentStateIdAsync(IContext context, CancellationToken cancellationToken) { if (context.Flow.Parent == null) { return null; } - return context.GetContextVariableAsync(GetStateKey(context.Flow.Parent.Id), cancellationToken); + return ExtractStateId(await GetParentStateIdContextVariable(context, cancellationToken)); } - public Task SetStateIdAsync(IContext context, string stateId, CancellationToken cancellationToken) + public Task SetStateIdAsync(IContext context, string stateId, string previousStateId, CancellationToken cancellationToken) { var expiration = context.Flow?.BuilderConfiguration?.StateExpiration ?? default; - return context.SetVariableAsync(GetStateKey(context.Flow.Id), stateId, cancellationToken, expiration); - } - - public Task SetPreviousStateIdAsync(IContext context, string previousStateId, CancellationToken cancellationToken) - { - return context.SetVariableAsync(GetPreviousStateKey(context.Flow.Id), previousStateId, cancellationToken); + return context.SetVariableAsync(GetStateKey(context.Flow.Id), $"{stateId}{STATE_AND_PREVIOUS_SEPARATOR}{previousStateId}", cancellationToken, expiration); } - public Task DeleteStateIdAsync(IContext context, CancellationToken cancellationToken) - { - return context.DeleteVariableAsync(GetStateKey(context.Flow.Id), cancellationToken); - } + public Task DeleteStateIdAsync(IContext context, CancellationToken cancellationToken) => context.DeleteVariableAsync(GetStateKey(context.Flow.Id), cancellationToken); private static string GetStateKey(string flowId) => $"{STATE_ID_KEY}@{flowId}"; private static string GetPreviousStateKey(string flowId) => $"{PREVIOUS_STATE_PREFIX}-{STATE_ID_KEY}@{flowId}"; + + private static string ExtractStateId(string value) => value.IsNullOrEmpty() || !value.Contains(STATE_AND_PREVIOUS_SEPARATOR) ? value : value.Split(':')[STATE_ID_INDEX]; + + private static string ExtractPreviousStateId(string value) + { + if (value.IsNullOrEmpty()) + { + return value; + } + + if (value.Contains(STATE_AND_PREVIOUS_SEPARATOR)) + { + return value.Split(STATE_AND_PREVIOUS_SEPARATOR)[PREVIOUS_STATE_ID_INDEX]; + } + + return ""; + } + + private Task GetStateIdContextVariable(IContext context, CancellationToken cancellationToken) => context.GetContextVariableAsync(GetStateKey(context.Flow.Id), cancellationToken); + private Task GetParentStateIdContextVariable(IContext context, CancellationToken cancellationToken) => context.GetContextVariableAsync(GetStateKey(context.Flow.Parent.Id), cancellationToken); } } \ No newline at end of file From bbb3a05c6d5c6441caa6633e296139f31d22585f Mon Sep 17 00:00:00 2001 From: Rafael Santiago Date: Tue, 13 Dec 2022 09:08:12 -0300 Subject: [PATCH 2/5] changed unit tests to adapt to unify context variables changes --- .../FlowManagerTests.cs | 74 ++++++++++--------- .../InputValidations/InputValidationsTests.cs | 32 ++++---- .../OutputConditions/OutputConditionsTests.cs | 55 +++++++------- .../SubflowTests.cs | 24 +++--- src/Take.Blip.Builder/FlowManager.cs | 21 ++++-- 5 files changed, 112 insertions(+), 94 deletions(-) diff --git a/src/Take.Blip.Builder.UnitTests/FlowManagerTests.cs b/src/Take.Blip.Builder.UnitTests/FlowManagerTests.cs index ff0444a2..8d2891c7 100644 --- a/src/Take.Blip.Builder.UnitTests/FlowManagerTests.cs +++ b/src/Take.Blip.Builder.UnitTests/FlowManagerTests.cs @@ -75,8 +75,8 @@ public async Task FlowWithoutConditionsShouldChangeStateAndSendMessage() // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Is(i => i.Content == input), flow); - StateManager.Received(1).SetStateIdAsync(Context, "ping", Arg.Any()); - StateManager.Received(1).DeleteStateIdAsync(Context, Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "ping", "root", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "", "ping", Arg.Any()); Sender .Received(1) .SendMessageAsync( @@ -145,8 +145,8 @@ public async Task FlowWithActionWithVariableShouldBeReplaced() // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Is(i => i.Content == input), flow); - StateManager.Received(1).SetStateIdAsync(Context, "ping", Arg.Any()); - StateManager.Received(1).DeleteStateIdAsync(Context, Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "ping", "root", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "", "ping", Arg.Any()); Sender .Received(1) .SendMessageAsync( @@ -215,8 +215,8 @@ public async Task FlowWithActionWithJsonVariableShouldBeEscapedAndReplaced() // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Is(i => i.Content == input), flow); - StateManager.Received(1).SetStateIdAsync(Context, "ping", Arg.Any()); - StateManager.Received(1).DeleteStateIdAsync(Context, Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "ping", "root", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "", "ping", Arg.Any()); Sender .Received(1) .SendMessageAsync( @@ -281,8 +281,8 @@ public async Task FlowWithActionWithVariableThatNotExistsShouldBeReplacedByEmpty // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Is(i => i.Content == input), flow); - StateManager.Received(1).SetStateIdAsync(Context, "ping", Arg.Any()); - StateManager.Received(1).DeleteStateIdAsync(Context, Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "ping", "root", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "", "ping", Arg.Any()); Sender .Received(1) .SendMessageAsync( @@ -642,7 +642,8 @@ public async Task FlowWithInputVariableShouldSaveInContext() // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Is(i => i.Content == input), flow); Context.Received(1).SetVariableAsync(variableName, input.Text, Arg.Any()); - StateManager.Received(0).SetStateIdAsync(Arg.Any(), Arg.Any(), Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Arg.Any(), "", "root", Arg.Any()); + StateManager.DidNotReceive().SetStateIdAsync(Arg.Any(), Arg.Is(i => !i.IsNullOrEmpty()), "root", Arg.Any()); } [Fact] @@ -688,7 +689,8 @@ public async Task FlowWithoutInputVariableShouldNotSaveInContext() // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Is(i => i.Content == Message.Content), flow); Context.Received(0).SetVariableAsync(Arg.Any(), Arg.Any(), Arg.Any()); - StateManager.Received(1).SetStateIdAsync(Arg.Any(), Arg.Any(), Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Arg.Any(), "first", "root", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Arg.Any(), "", "first", Arg.Any()); } [Fact] @@ -859,9 +861,10 @@ public async Task FlowWithContextConditionsShouldChangeStateAndSendMessage() await target.ProcessInputAsync(Message, flow, CancellationToken); // Assert - StateManager.Received(1).SetStateIdAsync(Context, "ping", Arg.Any()); - StateManager.DidNotReceive().SetStateIdAsync(Context, "marco", Arg.Any()); - StateManager.Received(1).DeleteStateIdAsync(Context, Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "ping", "root", Arg.Any()); + StateManager.DidNotReceive().SetStateIdAsync(Context, "marco", "root", Arg.Any()); + StateManager.DidNotReceive().SetStateIdAsync(Context, "marco", "ping", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "", "ping", Arg.Any()); Sender .Received(1) .SendMessageAsync( @@ -1039,11 +1042,11 @@ public async Task FlowWithInputContextConditionsSatisfiedShouldKeepStateAndWaitN await target.ProcessInputAsync(inputOk, flow, CancellationToken); // Assert - StateManager.Received(1).SetStateIdAsync(Context, "Start", Arg.Any()); - StateManager.DidNotReceive().DeleteStateIdAsync(Context, Arg.Any()); - StateManager.DidNotReceive().SetStateIdAsync(Context, "error", Arg.Any()); - StateManager.DidNotReceive().SetStateIdAsync(Context, "Ok", Arg.Any()); - StateManager.DidNotReceive().SetStateIdAsync(Context, "NOk", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "Start", "root", Arg.Any()); + StateManager.DidNotReceive().SetStateIdAsync(Context, "", Arg.Any(), Arg.Any()); + StateManager.DidNotReceive().SetStateIdAsync(Context, "error", Arg.Any(), Arg.Any()); + StateManager.DidNotReceive().SetStateIdAsync(Context, "Ok", Arg.Any(), Arg.Any()); + StateManager.DidNotReceive().SetStateIdAsync(Context, "NOk", Arg.Any(), Arg.Any()); Sender .DidNotReceive() @@ -1217,11 +1220,11 @@ public async Task FlowWithInputContextConditionsNotSatisfiedShouldChangeStateAnd await target.ProcessInputAsync(inputNOk, flow, CancellationToken); // Assert - StateManager.Received(1).SetStateIdAsync(Context, "Start", Arg.Any()); - StateManager.DidNotReceive().SetStateIdAsync(Context, "error", Arg.Any()); - StateManager.Received(1).DeleteStateIdAsync(Context, Arg.Any()); - StateManager.Received(1).SetStateIdAsync(Context, "NOk", Arg.Any()); - StateManager.DidNotReceive().SetStateIdAsync(Context, "Ok", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "Start", "root", Arg.Any()); + StateManager.DidNotReceive().SetStateIdAsync(Context, "error", Arg.Any(), Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "NOk", "Start", Arg.Any()); + StateManager.DidNotReceive().SetStateIdAsync(Context, "Ok", Arg.Any(), Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "", "NOk", Arg.Any()); Sender .Received(1) @@ -1347,9 +1350,10 @@ public async Task FlowWithConditionsAndMultipleInputsShouldChangeStatesAndSendMe await target.ProcessInputAsync(input2, flow, CancellationToken); // Assert - StateManager.Received(1).SetStateIdAsync(Context, "ping", Arg.Any()); - StateManager.Received(1).SetStateIdAsync(Context, "marco", Arg.Any()); - StateManager.Received(2).DeleteStateIdAsync(Context, Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "ping", "root", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "", "ping", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "marco", "root", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "", "marco", Arg.Any()); Sender .Received(1) .SendMessageAsync( @@ -1472,8 +1476,8 @@ public async Task FlowWithoutIntentConditionsShouldChangeStateAndSendMessage() // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Is(i => i.Content == input), flow); - StateManager.Received(1).SetStateIdAsync(Context, "my-intent", Arg.Any()); - StateManager.Received(1).DeleteStateIdAsync(Context, Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "my-intent", "root", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "", "my-intent", Arg.Any()); Sender .Received(1) .SendMessageAsync( @@ -1591,8 +1595,8 @@ public async Task FlowWithoutEntityConditionsShouldChangeStateAndSendMessage() // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Is(i => i.Content == input), flow); - StateManager.Received(1).SetStateIdAsync(Context, "my-entity", Arg.Any()); - StateManager.Received(1).DeleteStateIdAsync(Context, Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "my-entity", "root", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "", "my-entity", Arg.Any()); Sender .Received(1) .SendMessageAsync( @@ -1765,8 +1769,8 @@ public async Task ActionWithInvalidSettingShouldNotBreakProcessingWhenContinueOn // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Is(i => i.Content == input), flow); - StateManager.Received(1).SetStateIdAsync(Context, "ping", Arg.Any()); - StateManager.Received(1).DeleteStateIdAsync(Context, Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "ping", "root", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "", "ping", Arg.Any()); } #region TemporaryInput @@ -1813,7 +1817,7 @@ public async Task FlowWithTemporaryInputShouldScheduleAInputExpirationTimeMessag // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Is(i => i.Content == input), flow); - StateManager.Received(1).SetStateIdAsync(Context, "ping", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "ping", "root", Arg.Any()); SchedulerExtension .Received(1) .ScheduleMessageAsync( @@ -1939,8 +1943,8 @@ public async Task FlowWithTemporaryInputShouldCancelScheduleWhenUserSendOtherInp // Assert ContextProvider.Received(2).CreateContext(UserIdentity, ApplicationIdentity, Arg.Is(i => i.Content == input), flow); - StateManager.Received(1).SetStateIdAsync(Context, "ping", Arg.Any()); - StateManager.Received(1).SetStateIdAsync(Context, "ping2", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "ping", "root", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "ping2", "ping", Arg.Any()); SchedulerExtension .Received(1) .ScheduleMessageAsync( diff --git a/src/Take.Blip.Builder.UnitTests/InputValidations/InputValidationsTests.cs b/src/Take.Blip.Builder.UnitTests/InputValidations/InputValidationsTests.cs index fdeedb74..f5378677 100644 --- a/src/Take.Blip.Builder.UnitTests/InputValidations/InputValidationsTests.cs +++ b/src/Take.Blip.Builder.UnitTests/InputValidations/InputValidationsTests.cs @@ -78,8 +78,8 @@ public async Task FlowWithRegexInputValidationShouldChangeStateProperly() await target.ProcessInputAsync(Message, flow, CancellationToken); // Assert - StateManager.Received(1).SetStateIdAsync(Context, "state2", Arg.Any()); - StateManager.Received(1).DeleteStateIdAsync(Context, Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "state2", "root", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "", "state2", Arg.Any()); Sender .Received(1) @@ -153,8 +153,8 @@ public async Task FlowWithInvalidRegexInputValidationShouldFailAndNotChangeState await target.ProcessInputAsync(Message, flow, CancellationToken); // Assert - StateManager.Received(0).SetStateIdAsync(Context, "state2", Arg.Any()); - StateManager.Received(0).DeleteStateIdAsync(Context, Arg.Any()); + StateManager.Received(0).SetStateIdAsync(Context, "state2", "root", Arg.Any()); + StateManager.Received(0).SetStateIdAsync(Context, "", "state2", Arg.Any()); Sender .Received(1) @@ -227,8 +227,8 @@ public async Task FlowWithNumberInputValidationShouldChangeStateProperly() await target.ProcessInputAsync(Message, flow, CancellationToken); // Assert - StateManager.Received(1).SetStateIdAsync(Context, "state2", Arg.Any()); - StateManager.Received(1).DeleteStateIdAsync(Context, Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "state2", "root", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "", "state2", Arg.Any()); Sender .Received(1) @@ -301,8 +301,8 @@ public async Task FlowWithInvalidNumberInputValidationShouldFailAndNotChangeStat await target.ProcessInputAsync(Message, flow, CancellationToken); // Assert - StateManager.Received(0).SetStateIdAsync(Context, "state2", Arg.Any()); - StateManager.Received(0).DeleteStateIdAsync(Context, Arg.Any()); + StateManager.Received(0).SetStateIdAsync(Context, "state2", "root", Arg.Any()); + StateManager.Received(0).SetStateIdAsync(Context, "", "state2", Arg.Any()); Sender .Received(1) @@ -382,8 +382,8 @@ public async Task FlowWithTypeInputValidationShouldSendMessageWhenInvalid() await target.ProcessInputAsync(Message, flow, CancellationToken); // Assert - StateManager.Received(1).SetStateIdAsync(Context, "ping", Arg.Any()); - StateManager.Received(1).DeleteStateIdAsync(Context, Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "ping", "root", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "", "ping", Arg.Any()); Sender .Received(1) .SendMessageAsync( @@ -471,8 +471,8 @@ public async Task FlowWithInvalidTypeInputValidationShouldFailAndNotChangeStateP await target.ProcessInputAsync(Message, flow, CancellationToken); // Assert - StateManager.Received(1).SetStateIdAsync(Context, "ping", Arg.Any()); - StateManager.Received(1).DeleteStateIdAsync(Context, Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "ping", "root", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "", "ping", Arg.Any()); Sender .Received(1) .SendMessageAsync( @@ -563,8 +563,8 @@ public async Task FlowWithDateValidationShouldChangeStateProperly(string format) await target.ProcessInputAsync(Message, flow, CancellationToken); // Assert - StateManager.Received(1).SetStateIdAsync(Context, "ping", Arg.Any()); - StateManager.Received(1).DeleteStateIdAsync(Context, Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "ping", "root", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "", "ping", Arg.Any()); Sender .Received(1) @@ -637,8 +637,8 @@ public async Task FlowWithInvalidDateValidationShouldFailAndNotChangeStateProper await target.ProcessInputAsync(Message, flow, CancellationToken); // Assert - StateManager.Received(0).SetStateIdAsync(Context, "ping", Arg.Any()); - StateManager.Received(0).DeleteStateIdAsync(Context, Arg.Any()); + StateManager.Received(0).SetStateIdAsync(Context, "ping", "root", Arg.Any()); + StateManager.Received(0).SetStateIdAsync(Context, "", "ping", Arg.Any()); Sender .Received(1) diff --git a/src/Take.Blip.Builder.UnitTests/OutputConditions/OutputConditionsTests.cs b/src/Take.Blip.Builder.UnitTests/OutputConditions/OutputConditionsTests.cs index f10f7505..2fd4af03 100644 --- a/src/Take.Blip.Builder.UnitTests/OutputConditions/OutputConditionsTests.cs +++ b/src/Take.Blip.Builder.UnitTests/OutputConditions/OutputConditionsTests.cs @@ -99,9 +99,10 @@ public async Task FlowWithOutputConditionsShouldChangeStateAndSendMessage() await target.ProcessInputAsync(Message, flow, CancellationToken); // Assert - await StateManager.Received(1).SetStateIdAsync(Context, "ping", Arg.Any()); - await StateManager.DidNotReceive().SetStateIdAsync(Context, "marco", Arg.Any()); - await StateManager.Received(1).DeleteStateIdAsync(Context, Arg.Any()); + await StateManager.Received(1).SetStateIdAsync(Context, "ping", "root", Arg.Any()); + await StateManager.DidNotReceive().SetStateIdAsync(Context, "marco", "ping", Arg.Any()); + await StateManager.DidNotReceive().SetStateIdAsync(Context, "", "marco", Arg.Any()); + await StateManager.Received(1).SetStateIdAsync(Context, "", "ping", Arg.Any()); await Sender .Received(1) .SendMessageAsync( @@ -198,9 +199,9 @@ public async Task FlowWithInvalidOutputConditionsShouldShouldFailAndNotChangeSta await target.ProcessInputAsync(Message, flow, CancellationToken); // Assert - await StateManager.DidNotReceive().SetStateIdAsync(Context, "ping", Arg.Any()); - await StateManager.DidNotReceive().SetStateIdAsync(Context, "marco", Arg.Any()); - await StateManager.Received(1).DeleteStateIdAsync(Context, Arg.Any()); + await StateManager.DidNotReceive().SetStateIdAsync(Context, "ping", "root", Arg.Any()); + await StateManager.DidNotReceive().SetStateIdAsync(Context, "marco", "ping", Arg.Any()); + await StateManager.Received(1).SetStateIdAsync(Context, "", "root", Arg.Any()); await Sender .DidNotReceive() .SendMessageAsync( @@ -280,7 +281,7 @@ public async Task FlowWithMatchTextContextOutputConditionsShouldChangeStateAndSe // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Is(i => i.Content == input), flow); - await StateManager.Received(1).SetStateIdAsync(Arg.Any(), Arg.Any(), Arg.Any()); + await StateManager.Received(2).SetStateIdAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()); await Context.Received(1).SetVariableAsync(variableName, input.Text, Arg.Any()); @@ -423,7 +424,7 @@ public async Task FlowWithOutputConditionEqualsShouldChangeStateAndSendMessage() // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Any(), flow); - await StateManager.Received(1).SetStateIdAsync(Context, "success", Arg.Any()); + await StateManager.Received(1).SetStateIdAsync(Context, "success", "root", Arg.Any()); await Context.Received(1).SetVariableAsync(variableName, input.Text, Arg.Any()); await Sender .Received(1) @@ -457,7 +458,7 @@ public async Task FlowWithOutputConditionEqualsShouldNotChangeState() // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Any(), flow); - await StateManager.DidNotReceive().SetStateIdAsync(Context, "success", Arg.Any()); + await StateManager.DidNotReceive().SetStateIdAsync(Context, "success", "root", Arg.Any()); await Context.Received(1).SetVariableAsync(variableName, input.Text, Arg.Any()); } @@ -482,7 +483,7 @@ public async Task FlowWithOutputConditionContainsShouldChangeStateAndSendMessage // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Any(), flow); - await StateManager.Received(1).SetStateIdAsync(Arg.Any(), "success", Arg.Any()); + await StateManager.Received(1).SetStateIdAsync(Arg.Any(), "success", "root", Arg.Any()); await Context.Received(1).SetVariableAsync(variableName, input.Text, Arg.Any()); await Sender .Received(1) @@ -516,7 +517,7 @@ public async Task FlowWithOutputConditionContainsShouldNotChangeState() // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Any(), flow); - await StateManager.DidNotReceive().SetStateIdAsync(Context, "success", Arg.Any()); + await StateManager.DidNotReceive().SetStateIdAsync(Context, "success", "root", Arg.Any()); await Context.Received(1).SetVariableAsync(variableName, input.Text, Arg.Any()); } @@ -541,7 +542,7 @@ public async Task FlowWithOutputConditionStartsShouldChangeStateAndSendMessage() // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Any(), flow); - await StateManager.Received(1).SetStateIdAsync(Context, "success", Arg.Any()); + await StateManager.Received(1).SetStateIdAsync(Context, "success", "root", Arg.Any()); await Context.Received(1).SetVariableAsync(variableName, input.Text, Arg.Any()); await Sender .Received(1) @@ -575,7 +576,7 @@ public async Task FlowWithOutputConditionStartsShouldNotChangeState() // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Any(), flow); - await StateManager.DidNotReceive().SetStateIdAsync(Context, "success", Arg.Any()); + await StateManager.DidNotReceive().SetStateIdAsync(Context, "success", "root", Arg.Any()); await Context.Received(1).SetVariableAsync(variableName, input.Text, Arg.Any()); } @@ -600,7 +601,7 @@ public async Task FlowWithOutputConditionEndsShouldChangeStateAndSendMessage() // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Any(), flow); - await StateManager.Received(1).SetStateIdAsync(Context, "success", Arg.Any()); + await StateManager.Received(1).SetStateIdAsync(Context, "success", "root", Arg.Any()); await Context.Received(1).SetVariableAsync(variableName, input.Text, Arg.Any()); await Sender .Received(1) @@ -634,7 +635,7 @@ public async Task FlowWithOutputConditionEndsShouldNotChangeState() // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Any(), flow); - await StateManager.DidNotReceive().SetStateIdAsync(Context, "success", Arg.Any()); + await StateManager.DidNotReceive().SetStateIdAsync(Context, "success", "root", Arg.Any()); await Context.Received(1).SetVariableAsync(variableName, input.Text, Arg.Any()); } @@ -659,7 +660,7 @@ public async Task FlowWithOutputConditionApproximateToShouldChangeStateAndSendMe // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Any(), flow); - await StateManager.Received(1).SetStateIdAsync(Context, "success", Arg.Any()); + await StateManager.Received(1).SetStateIdAsync(Context, "success", "root", Arg.Any()); await Context.Received(1).SetVariableAsync(variableName, input.Text, Arg.Any()); await Sender .Received(1) @@ -693,7 +694,7 @@ public async Task FlowWithOutputConditionApproximateToShouldNotChangeState() // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Any(), flow); - await StateManager.DidNotReceive().SetStateIdAsync(Context, "success", Arg.Any()); + await StateManager.DidNotReceive().SetStateIdAsync(Context, "success", "root", Arg.Any()); await Context.Received(1).SetVariableAsync(variableName, input.Text, Arg.Any()); } @@ -717,7 +718,7 @@ public async Task FlowWithOutputConditionExistsShouldChangeStateAndSendMessage() // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Any(), flow); - await StateManager.Received(1).SetStateIdAsync(Context, "success", Arg.Any()); + await StateManager.Received(1).SetStateIdAsync(Context, "success", "root", Arg.Any()); await Context.Received(1).SetVariableAsync(variableName, input.Text, Arg.Any()); await Sender .Received(1) @@ -750,7 +751,7 @@ public async Task FlowWithOutputConditionExistsShouldNotChangeState() // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Any(), flow); - await StateManager.DidNotReceive().SetStateIdAsync(Context, "success", Arg.Any()); + await StateManager.DidNotReceive().SetStateIdAsync(Context, "success", "root", Arg.Any()); await Context.Received(1).SetVariableAsync(variableName, input.Text, Arg.Any()); } @@ -774,7 +775,7 @@ public async Task FlowWithOutputConditionNotExistsShouldChangeStateAndSendMessag // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Any(), flow); - await StateManager.Received(1).SetStateIdAsync(Context, "success", Arg.Any()); + await StateManager.Received(1).SetStateIdAsync(Context, "success", "root", Arg.Any()); await Context.Received(1).SetVariableAsync(variableName, input.Text, Arg.Any()); await Sender .Received(1) @@ -807,7 +808,7 @@ public async Task FlowWithOutputConditionNotExistsShouldNotChange() // Assert ContextProvider.Received(1).CreateContext(UserIdentity, ApplicationIdentity, Arg.Any(), flow); - await StateManager.DidNotReceive().SetStateIdAsync(Context, "success", Arg.Any()); + await StateManager.DidNotReceive().SetStateIdAsync(Context, "success", "root", Arg.Any()); await Context.Received(1).SetVariableAsync(variableName, input.Text, Arg.Any()); } @@ -829,7 +830,7 @@ public async Task FlowWithStateVariableShouldSucceed() await target.ProcessInputAsync(Message, flow, CancellationToken); // Assert - await StateManager.Received(1).SetStateIdAsync(Context, state2, Arg.Any()); + await StateManager.Received(1).SetStateIdAsync(Context, state2, "root", Arg.Any()); } [Fact] @@ -842,12 +843,13 @@ public async Task FlowWithoutStateVariableShouldError() Message.Content = new PlainText() { Text = "hello" }; // Act - await Assert.ThrowsAsync( + var exception = await Assert.ThrowsAsync( async () => await target.ProcessInputAsync(Message, flow, CancellationToken) ); + Assert.IsType(exception.InnerException); // Assert - await StateManager.DidNotReceive().SetStateIdAsync(Context, "state2", Arg.Any()); + await StateManager.DidNotReceive().SetStateIdAsync(Context, "state2", "root", Arg.Any()); } [Theory] @@ -868,12 +870,13 @@ public async Task FlowWithInvalidStateVariableShouldError(string variableValue) Message.Content = new PlainText() { Text = "hello" }; // Act - await Assert.ThrowsAsync( + var exception = await Assert.ThrowsAsync( async () => await target.ProcessInputAsync(Message, flow, CancellationToken) ); + Assert.IsType(exception.InnerException); // Assert - await StateManager.DidNotReceive().SetStateIdAsync(Context, "state2", Arg.Any()); + await StateManager.DidNotReceive().SetStateIdAsync(Context, "state2", "root", Arg.Any()); } private Flow GetVariableStateFlow(string variableName) => new Flow diff --git a/src/Take.Blip.Builder.UnitTests/SubflowTests.cs b/src/Take.Blip.Builder.UnitTests/SubflowTests.cs index d972b808..7e40e1e0 100644 --- a/src/Take.Blip.Builder.UnitTests/SubflowTests.cs +++ b/src/Take.Blip.Builder.UnitTests/SubflowTests.cs @@ -90,12 +90,12 @@ public async Task FlowWithSubflowRedirectAndStateWithInputUserShouldSuccess() FlowLoader.Received(1).LoadFlowAsync(FlowType.Subflow, flow, subflowShortName, Arg.Any()); - StateManager.Received(1).SetStateIdAsync(context1, "ping", Arg.Any()); - StateManager.Received(1).SetStateIdAsync(context1, "subflow:subflowTest", Arg.Any()); - StateManager.Received(1).SetStateIdAsync(context1, "root", Arg.Any()); - StateManager.Received(1).SetStateIdAsync(context1, "pingSubflow", Arg.Any()); - StateManager.Received(1).SetStateIdAsync(context2, "end", Arg.Any()); - StateManager.Received(1).SetStateIdAsync(context2, "ping2", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(context1, "ping", "root", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(context1, "subflow:subflowTest", "ping", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(context1, "root", "", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(context1, "pingSubflow", "root", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(context2, "end", "pingSubflow", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(context2, "ping2", "subflow:subflowTest", Arg.Any()); context1.Received(1).SetVariableAsync("testSetVariableInput", "testSetVariableInputValue", Arg.Any(), Arg.Any()); context2.Received(1).SetVariableAsync("testSetVariableOutput", "testSetVariableOutputValue", Arg.Any(), Arg.Any()); @@ -159,12 +159,12 @@ public async Task FlowWithSubflowRedirectAndStateWithoutInputUserShouldSuccess() FlowLoader.Received(1).LoadFlowAsync(FlowType.Subflow, flow, subflowShortName, Arg.Any()); - StateManager.Received(1).SetStateIdAsync(Context, "ping", Arg.Any()); - StateManager.Received(1).SetStateIdAsync(Context, "subflow:subflowTest", Arg.Any()); - StateManager.Received(1).SetStateIdAsync(Context, "root", Arg.Any()); - StateManager.Received(1).SetStateIdAsync(Context, "pingSubflow", Arg.Any()); - StateManager.Received(1).SetStateIdAsync(Context, "end", Arg.Any()); - StateManager.Received(1).SetStateIdAsync(Context, "ping2", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "ping", "root", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "subflow:subflowTest", "ping", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "root", "", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "pingSubflow", "root", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "end", "pingSubflow", Arg.Any()); + StateManager.Received(1).SetStateIdAsync(Context, "ping2", "subflow:subflowTest", Arg.Any()); Context.Received(1).SetVariableAsync("testSetVariableInput", "testSetVariableInputValue", Arg.Any(), Arg.Any()); Context.Received(1).SetVariableAsync("testSetVariableOutput", "testSetVariableOutputValue", Arg.Any(), Arg.Any()); diff --git a/src/Take.Blip.Builder/FlowManager.cs b/src/Take.Blip.Builder/FlowManager.cs index 39c66fba..d8e23596 100644 --- a/src/Take.Blip.Builder/FlowManager.cs +++ b/src/Take.Blip.Builder/FlowManager.cs @@ -256,13 +256,14 @@ await context.SetVariableAsync(state.Input.Variable, lazyInput.SerializedContent lazyInput, linkedCts.Token ); + + previousStateId = ""; } } else { - (flow, state, stateTrace, stateStopwatch) = await RedirectToParentFlowAsync( + (flow, state, previousStateId, stateTrace, stateStopwatch) = await RedirectToParentFlowAsync( context, - userIdentity, flow, previousStateId, await GetParentStateIdAsync(context, parentStateIdQueue, linkedCts.Token), @@ -423,7 +424,7 @@ private async Task ProcessGlobalOutputActionsAsync(IContext context, Flow flow, return (subflow, newState, newStateTrace, newStateStopwatch); } - private async Task<(Flow, State, StateTrace, Stopwatch)> RedirectToParentFlowAsync(IContext context, Identity userIdentity, Flow flow, string previousStateId, string parentStateId, InputTrace inputTrace, LazyInput lazyInput, CancellationToken cancellationToken) + private async Task<(Flow, State, string, StateTrace, Stopwatch)> RedirectToParentFlowAsync(IContext context, Flow flow, string previousStateId, string parentStateId, InputTrace inputTrace, LazyInput lazyInput, CancellationToken cancellationToken) { var parentFlow = flow.Parent; @@ -445,9 +446,11 @@ private async Task ProcessGlobalOutputActionsAsync(IContext context, Flow flow, // Prepare to leave the current state executing the output actions await ProcessStateOutputActionsAsync(state, lazyInput, context, stateTrace, cancellationToken); + var newPreviousStateId = state?.Id; + state = await ProcessOutputsAsync(lazyInput, context, parentFlow, state, stateTrace?.Outputs, cancellationToken); - return (parentFlow, state, stateTrace, stateStopwatch); + return (parentFlow, state, newPreviousStateId, stateTrace, stateStopwatch); } private bool IsSubflowState(State state) => state != null && state.Id.StartsWith("subflow:"); @@ -580,6 +583,7 @@ private Boolean IsContextVariable(string stateId) private async Task ProcessOutputsAsync(LazyInput lazyInput, IContext context, Flow flow, State state, ICollection outputTraces, CancellationToken cancellationToken) { var outputs = state.Outputs; + var previousStateId = state?.Id; state = null; // If there's any output in the current state @@ -607,7 +611,14 @@ await output.Conditions.EvaluateConditionsAsync(lazyInput, context, cancellation if (state == null) { - await _stateManager.DeleteStateIdAsync(context, cancellationToken); + if (previousStateId.IsNullOrEmpty()) + { + await _stateManager.DeleteStateIdAsync(context, cancellationToken); + } + else + { + await _stateManager.SetStateIdAsync(context, "", previousStateId, cancellationToken); + } throw new InvalidOperationException($"Failed to process output condition, bacause the output context variable '{output.StateId}' is undefined or does not exist in the context."); } From ecc9e95797772baa0d95e4f3a85546b0c0484824 Mon Sep 17 00:00:00 2001 From: Daniel Silva da Fonseca Date: Thu, 16 Feb 2023 11:09:33 -0300 Subject: [PATCH 3/5] parameterize unify stateid and previousstateid --- .../Hosting/ConventionsConfiguration.cs | 3 ++ .../Hosting/IConfiguration.cs | 5 +++ src/Take.Blip.Builder/StateManager.cs | 40 ++++++++++++++++--- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/Take.Blip.Builder/Hosting/ConventionsConfiguration.cs b/src/Take.Blip.Builder/Hosting/ConventionsConfiguration.cs index 088aad35..340d567c 100644 --- a/src/Take.Blip.Builder/Hosting/ConventionsConfiguration.cs +++ b/src/Take.Blip.Builder/Hosting/ConventionsConfiguration.cs @@ -32,5 +32,8 @@ public class ConventionsConfiguration : IConfiguration public long ExecuteScriptLimitMemoryWarning => 10_000_000; // Nearly 10MB public TimeSpan ExecuteScriptTimeout => TimeSpan.FromSeconds(5); + + /// + public bool UnifyStateIdAndPreviousStateId => false; } } \ No newline at end of file diff --git a/src/Take.Blip.Builder/Hosting/IConfiguration.cs b/src/Take.Blip.Builder/Hosting/IConfiguration.cs index 1e6b26a2..38608e7c 100644 --- a/src/Take.Blip.Builder/Hosting/IConfiguration.cs +++ b/src/Take.Blip.Builder/Hosting/IConfiguration.cs @@ -33,5 +33,10 @@ public interface IConfiguration long ExecuteScriptLimitMemoryWarning { get; } TimeSpan ExecuteScriptTimeout { get; } + + /// + /// indicates whether the variables previous state id and state id should be merged into a single variable in the database or not + /// + public bool UnifyStateIdAndPreviousStateId { get; } } } \ No newline at end of file diff --git a/src/Take.Blip.Builder/StateManager.cs b/src/Take.Blip.Builder/StateManager.cs index 4ce4cdac..d53b17d4 100644 --- a/src/Take.Blip.Builder/StateManager.cs +++ b/src/Take.Blip.Builder/StateManager.cs @@ -4,6 +4,7 @@ using Lime.Protocol; using Lime.Protocol.Network; using Take.Blip.Client.Extensions.Context; +using Take.Blip.Builder.Hosting; namespace Take.Blip.Builder { @@ -16,6 +17,15 @@ public class StateManager : IStateManager private const int PREVIOUS_STATE_ID_INDEX = 1; private const string STATE_AND_PREVIOUS_SEPARATOR = ";"; + private readonly IConfiguration _configuration; + + public StateManager( + IConfiguration configuration + ) + { + _configuration = configuration; + } + public async Task GetStateIdAsync(IContext context, CancellationToken cancellationToken) => ExtractStateId(await GetStateIdContextVariable(context, cancellationToken)); public async Task GetPreviousStateIdAsync(IContext context, CancellationToken cancellationToken) @@ -38,8 +48,17 @@ public async Task GetParentStateIdAsync(IContext context, CancellationTo public Task SetStateIdAsync(IContext context, string stateId, string previousStateId, CancellationToken cancellationToken) { - var expiration = context.Flow?.BuilderConfiguration?.StateExpiration ?? default; - return context.SetVariableAsync(GetStateKey(context.Flow.Id), $"{stateId}{STATE_AND_PREVIOUS_SEPARATOR}{previousStateId}", cancellationToken, expiration); + var expiration = context.Flow?.BuilderConfiguration?.StateExpiration ?? default; + if (_configuration.UnifyStateIdAndPreviousStateId) + { + return context.SetVariableAsync(GetStateKey(context.Flow.Id), $"{stateId}{STATE_AND_PREVIOUS_SEPARATOR}{previousStateId}", cancellationToken, expiration); + } + else + { + return Task.WhenAll( + context.SetVariableAsync(GetStateKey(context.Flow.Id), stateId, cancellationToken, expiration), + context.SetVariableAsync(GetPreviousStateKey(context.Flow.Id), previousStateId, cancellationToken)); + } } public Task DeleteStateIdAsync(IContext context, CancellationToken cancellationToken) => context.DeleteVariableAsync(GetStateKey(context.Flow.Id), cancellationToken); @@ -48,13 +67,24 @@ public Task SetStateIdAsync(IContext context, string stateId, string previousSta private static string GetPreviousStateKey(string flowId) => $"{PREVIOUS_STATE_PREFIX}-{STATE_ID_KEY}@{flowId}"; - private static string ExtractStateId(string value) => value.IsNullOrEmpty() || !value.Contains(STATE_AND_PREVIOUS_SEPARATOR) ? value : value.Split(STATE_AND_PREVIOUS_SEPARATOR)[STATE_ID_INDEX]; + private static string ExtractStateId(string value) + { + if (value.IsNullOrEmpty()) + { + return null; + } + if (value.Contains(STATE_AND_PREVIOUS_SEPARATOR)) + { + return value.Split(STATE_AND_PREVIOUS_SEPARATOR)[STATE_ID_INDEX]; + } + return value; + } private static string ExtractPreviousStateId(string value) { if (value.IsNullOrEmpty()) { - return value; + return null; } if (value.Contains(STATE_AND_PREVIOUS_SEPARATOR)) @@ -62,7 +92,7 @@ private static string ExtractPreviousStateId(string value) return value.Split(STATE_AND_PREVIOUS_SEPARATOR)[PREVIOUS_STATE_ID_INDEX]; } - return ""; + return null; } private Task GetStateIdContextVariable(IContext context, CancellationToken cancellationToken) => context.GetContextVariableAsync(GetStateKey(context.Flow.Id), cancellationToken); From 7c9def08f78188e078d2dfd0a0a0afc5a04915e6 Mon Sep 17 00:00:00 2001 From: Daniel Silva da Fonseca Date: Thu, 16 Feb 2023 11:28:44 -0300 Subject: [PATCH 4/5] sonar correction --- src/Take.Blip.Builder/StateManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Take.Blip.Builder/StateManager.cs b/src/Take.Blip.Builder/StateManager.cs index d53b17d4..14851e13 100644 --- a/src/Take.Blip.Builder/StateManager.cs +++ b/src/Take.Blip.Builder/StateManager.cs @@ -6,6 +6,7 @@ using Take.Blip.Client.Extensions.Context; using Take.Blip.Builder.Hosting; + namespace Take.Blip.Builder { public class StateManager : IStateManager From ace288cb4113025e94f87c24790b87e3af0d17d3 Mon Sep 17 00:00:00 2001 From: Daniel Silva da Fonseca Date: Thu, 16 Feb 2023 14:49:09 -0300 Subject: [PATCH 5/5] merge corrections --- src/Take.Blip.Builder/FlowManager.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Take.Blip.Builder/FlowManager.cs b/src/Take.Blip.Builder/FlowManager.cs index 9a1ec230..3a7e586b 100644 --- a/src/Take.Blip.Builder/FlowManager.cs +++ b/src/Take.Blip.Builder/FlowManager.cs @@ -244,7 +244,7 @@ await context.SetVariableAsync(state.Input.Variable, lazyInput.SerializedContent state = await ProcessOutputsAsync(lazyInput, context, flow, state, stateTrace?.Outputs, linkedCts.Token); // Store the previous state - await _stateManager.SetPreviousStateIdAsync(context, previousStateId, linkedCts.Token); + await _stateManager.SetStateIdAsync(context, state.Id, previousStateId, cancellationToken); // Only execute the ProcessAfterStateActionsAsync when the user current state changed after ProcessOutputsAsync if (previousState.Id != state?.Id) @@ -258,7 +258,8 @@ await context.SetVariableAsync(state.Input.Variable, lazyInput.SerializedContent parentStateIdQueue.Enqueue(state.Id); (flow, state, stateTrace, stateStopwatch) = await RedirectToSubflowAsync( - context, + context, + userIdentity, state, previousStateId, flow, @@ -487,7 +488,7 @@ private async Task ProcessGlobalAfterStateChangedActionsAsync(IContext context, await ProcessAfterStateChangedActionsAsync(previousState, lazyInput, context, stateTrace, cancellationToken); } - return (parentFlow, state, stateTrace, stateStopwatch); + return (parentFlow, state, newPreviousStateId, stateTrace, stateStopwatch); } private bool IsSubflowState(State state) => state != null && state.Id.StartsWith("subflow:");