Skip to content

Commit

Permalink
Merge pull request #33 from thygesteffensen/issue/22
Browse files Browse the repository at this point in the history
feat: Added a Interface for scope to have their own way of handling i…
  • Loading branch information
thygesteffensen authored Jan 21, 2021
2 parents a5871ab + db4c9d5 commit 7cd855f
Show file tree
Hide file tree
Showing 9 changed files with 77 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,42 @@ namespace Parser.ExpressionParser.Functions.Base
{
public class ActionExecutorFactory
{
private readonly IServiceProvider _serviceProvider;
private readonly IServiceProvider _sp;

public ActionExecutorFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
_sp = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
}

public ActionExecutorBase ResolveActionByKey(string key)
{
var axRegistrationList = _serviceProvider.GetRequiredService<IEnumerable<ActionExecutorRegistration>>();
var axRegistration = axRegistrationList.LastOrDefault(x => x.ActionName == key);
var axRegistrationList = _sp.GetRequiredService
<IEnumerable<ActionExecutorRegistration>>();
var axRegistration = axRegistrationList
.LastOrDefault(x => x.ActionName == key);
return axRegistration == null
? null
: _serviceProvider.GetRequiredService(axRegistration.Type) as ActionExecutorBase;
: _sp.GetRequiredService(axRegistration.Type)
as ActionExecutorBase;
}

public ActionExecutorBase ResolveActionByType(string actionType)
{
var axRegistrationList = _serviceProvider.GetRequiredService<IEnumerable<ActionExecutorRegistration>>();
var axRegistrationList = _sp.GetRequiredService<IEnumerable<ActionExecutorRegistration>>();
var axRegistration = axRegistrationList.LastOrDefault(x => x.ActionType == actionType);
return axRegistration == null
? null
: _serviceProvider.GetRequiredService(axRegistration.Type) as ActionExecutorBase;
: _sp.GetRequiredService(axRegistration.Type) as ActionExecutorBase;
}

public ActionExecutorBase ResolveActionByApiId(string apiId, string operationName)
{
var axRegistrationList = _serviceProvider.GetRequiredService<IEnumerable<ActionExecutorRegistration>>();
var axRegistrationList = _sp.GetRequiredService<IEnumerable<ActionExecutorRegistration>>();
var axRegistration = axRegistrationList.LastOrDefault(
x => x.ActionApiId == apiId && x.SupportedOperationNames.Contains(operationName));
return axRegistration == null
? null
: _serviceProvider.GetRequiredService(axRegistration.Type) as ActionExecutorBase;
: _sp.GetRequiredService(axRegistration.Type) as ActionExecutorBase;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ namespace Parser.ExpressionParser.Functions.Storage
{
public class ItemsFunction : Function
{
private readonly IState _variableRetriever;
private readonly IItemsRetriever _variableRetriever;

public ItemsFunction(IState variableRetriever) : base("items")
public ItemsFunction(IItemsRetriever variableRetriever) : base("items")
{
_variableRetriever = variableRetriever ?? throw new ArgumentNullException(nameof(variableRetriever));
}
Expand All @@ -21,8 +21,8 @@ public override ValueContainer ExecuteFunction(params ValueContainer[] parameter
}

var variableName = parameters[0].GetValue<string>();
// TODO: Maybe implement another storage option?
var value = _variableRetriever.GetOutputs($"item_{variableName}");

var value = _variableRetriever.GetCurrentItem(variableName);

return value;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

namespace Parser.FlowParser.ActionExecutors.Implementations
{
public class ForEachActionExecutor : DefaultBaseActionExecutor, IScopeActionExecutor
public class ForEachActionExecutor : DefaultBaseActionExecutor, IScopeActionExecutor, IItemHandler
{
private readonly IState _state;
private readonly IScopeDepthManager _scopeDepthManager;
Expand All @@ -18,7 +18,9 @@ public class ForEachActionExecutor : DefaultBaseActionExecutor, IScopeActionExec
private JProperty[] _actionDescriptions;
private string _firstScopeActionName;


private List<ValueContainer> _items;
private int _currentItemIndex = 0;

public ForEachActionExecutor(
IState state,
Expand Down Expand Up @@ -71,34 +73,39 @@ private void SetupForEach(ValueContainer values)
!(ad.Value.SelectToken("$.runAfter") ??
throw new InvalidOperationException("Json must contain runAfter token.")).Any()).Name;

// TODO: Add scope relevant storage to store stuff like this, which cannot interfere with the state.
_items = values.GetValue<IEnumerable<ValueContainer>>().ToList();

_state.AddItemHandler(ActionName, this);
}

private void UpdateScopeAndSetItemValue()
{
_scopeDepthManager.Push(ActionName, _actionDescriptions, this);

_state.AddOutputs($"item_{ActionName}", _items.First());
_items = _items.Skip(1).ToList();
_currentItemIndex++;
}


public Task<ActionResult> ExitScope(ActionStatus scopeStatus)
{
if (_items.Count > 0)
if (_currentItemIndex >= _items.Count)
{
_logger.LogInformation("Exited foreach...");
return Task.FromResult(new ActionResult());
}
else
{
_logger.LogInformation("Continuing foreach.");

UpdateScopeAndSetItemValue();

return Task.FromResult(new ActionResult {NextAction = _firstScopeActionName});
}
else
{
_logger.LogInformation("Exited foreach...");
return Task.FromResult(new ActionResult());
}
}

public ValueContainer GetCurrentItem()
{
return _items[_currentItemIndex];
}
}
}
3 changes: 0 additions & 3 deletions PowerAutomateMockUp/FlowParser/ScopeDepthManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,6 @@ public async Task<ActionResult> TryPopScope(ActionStatus scopeStatus)
}
#endif

// Todo: Finish the State Machine and figure out how this should be handled properly


if (!scopePopSuccessful)
{
return null;
Expand Down
1 change: 1 addition & 0 deletions PowerAutomateMockUp/FlowRunnerDependencyExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public static void AddFlowRunner(this IServiceCollection services)
services.AddScoped<IVariableRetriever>(x => x.GetRequiredService<IState>());
services.AddScoped<IOutputsRetriever>(x => x.GetRequiredService<IState>());
services.AddScoped<ITriggerOutputsRetriever>(x => x.GetRequiredService<IState>());
services.AddScoped<IItemsRetriever>(x => x.GetRequiredService<IState>());
services.AddScoped<IExpressionEngine, ExpressionEngine>();
services.AddScoped<ExpressionGrammar>();

Expand Down
13 changes: 12 additions & 1 deletion PowerAutomateMockUp/IState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@

namespace Parser
{
public interface IState : IVariableRetriever, ITriggerOutputsRetriever, IOutputsRetriever
public interface IState : IVariableRetriever, ITriggerOutputsRetriever, IOutputsRetriever, IItemsRetriever
{
void AddTriggerOutputs(ValueContainer triggerOutputs);
void AddVariable(string variableName, IEnumerable<ValueContainer> values);
void AddVariable(string variableName, ValueContainer valueContainer);
void AddOutputs(string actionName, ValueContainer valueContainer);
void AddOutputs(string actionName, IEnumerable<ValueContainer> values);
void AddItemHandler(string actionName, IItemHandler itemHandler);
}

public interface IItemHandler
{
ValueContainer GetCurrentItem();
}

public interface IVariableRetriever
Expand All @@ -26,4 +32,9 @@ public interface IOutputsRetriever
{
ValueContainer GetOutputs(string actionName);
}

public interface IItemsRetriever
{
ValueContainer GetCurrentItem(string actionName);
}
}
19 changes: 19 additions & 0 deletions PowerAutomateMockUp/State.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ public class State : IState
{
private readonly Dictionary<string, ValueContainer> _variables = new Dictionary<string, ValueContainer>();
private readonly Dictionary<string, ValueContainer> _outputs = new Dictionary<string, ValueContainer>();

private readonly Dictionary<string, IItemHandler> _itemHandlers = new Dictionary<string, IItemHandler>();

private ValueContainer _triggerOutputs;

public void AddVariable(string variableName, IEnumerable<ValueContainer> values)
Expand All @@ -30,6 +33,11 @@ public void AddOutputs(string actionName, IEnumerable<ValueContainer> values)
_outputs[actionName] = new ValueContainer(values);
}

public void AddItemHandler(string actionName, IItemHandler itemHandler)
{
_itemHandlers[actionName] = itemHandler;
}

public void AddTriggerOutputs(ValueContainer triggerOutputs)
{
_triggerOutputs = triggerOutputs ?? throw new ArgumentNullException(nameof(triggerOutputs));
Expand All @@ -54,5 +62,16 @@ public ValueContainer GetOutputs(string actionName)
var successfulRetrieve = _outputs.TryGetValue(actionName, out var value);
return successfulRetrieve ? value : new ValueContainer();
}

public ValueContainer GetCurrentItem(string actionName)
{
var successfulHandlerRetrieve = _itemHandlers.TryGetValue(actionName, out var handler);
if (!successfulHandlerRetrieve)
{
throw new PowerAutomateMockUpException("Cannot resolve handler for item");
}

return handler.GetCurrentItem();
}
}
}
16 changes: 9 additions & 7 deletions ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ var path = "<path to flow definition>";
// from Microsoft.Extensions.DependencyInjection
var services = new ServiceCollection();

services.Configure<FlowSettings>(x => { });
services.Configure<FlowSettings>(x => { }); // Optional way to add settings
// Required to set up required dependencies
services.AddFlowRunner();
Expand All @@ -66,7 +66,7 @@ await flowRunner.Trigger();
```

### Adding actions
Actions can added in three ways
Actions can be added in three ways

1. Using specific action name
2. Using Connection ApiId and supported OperationIds
Expand All @@ -77,7 +77,7 @@ Actions can added in three ways
services.AddFlowActionByName<GetMsnWeather>("Get_forecast_for_today_(Metric)");
```

When the action named *Get_forecast_for_today_(Metric)* is reached and about to be executed, the class with type GetMsnWeather is retrieved from the ServiceProvider and called.
When the action named *Get_forecast_for_today_(Metric)* is reached and about to be executed, the class with type GetMsnWeather is retrieved from the ServiceProvider and used to execute the action.

#### 2. Using Connection ApiId and supported OperationIds
```c#
Expand All @@ -87,18 +87,20 @@ services.AddFlowActionByApiIdAndOperationsName<Notification>(
new []{ "SendEmailNotification", "SendNotification" });
```

When an action from the **Notification** connector with one of the supported types is reached in the flow, a action executor instance of type Notification is created and used.
When an action from the **Notification** connector with one of the supported types is reached in the flow, a action executor instance of type `Notification` is created and used to execute the action.

#### 3. Using Action type (**Not recommended**)
```c#
services.AddFlowActionByFlowType<IfActionExecutor>("If");
```
When the generic action type **If** i reached, a action executor instance of type Notification is created and used.
When the generic action type **If** i reached, an action executor instance of type `IfActionExecutor` is created and used to execute the action.

This is not recommended due to the fact that every OpenApiConnection connector will have the type OpenApiConnection. This means that both Common Data Service (current environment) and many others, will use the same action executors, which is not the correct way to do it.
This is not recommended due to the fact that every OpenApiConnection connector will have the type **OpenApiConnection**. This means that both Common Data Service (current environment) and many others, will use the same action executors, which is not the correct way to do it.

This way of resolving an action executor is only used to resolve actions, where only one Action uses that type. This is **If**, **DoUntil** etc.

### Creating action executors.
Currently there are two classes to extend, the one is **DefaultBaseActionExecutor** and the other are **OpenApiConnectionBaseActionExecutor**.
Currently there are two classes to extend, one is **DefaultBaseActionExecutor** and the other is **OpenApiConnectionBaseActionExecutor**.

#### DefaultBaseActionExecutor
```c#
Expand Down
6 changes: 3 additions & 3 deletions Test/ActionTests/ForEachActionExecutorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,15 @@ public async Task Test()

Assert.AreEqual("Update_a_record", response.NextAction);

stateMock.Verify(x => x.AddOutputs(It.IsAny<string>(), It.IsAny<ValueContainer>()), Times.Exactly(1));
stateMock.Verify(x => x.AddItemHandler(It.IsAny<string>(), It.IsAny<IItemHandler>()), Times.Once);
sdmMock.Verify(x => x.Push(It.IsAny<string>(), It.IsAny<IEnumerable<JProperty>>(),
It.Is<ForEachActionExecutor>(actionExecutor =>
actionExecutor.Equals(forEachActionExecutor))), Times.Exactly(1));

var exitAnswer1 = await forEachActionExecutor.ExitScope(ActionStatus.Succeeded);
Assert.AreEqual("Update_a_record", exitAnswer1.NextAction);

stateMock.Verify(x => x.AddOutputs(It.IsAny<string>(), It.IsAny<ValueContainer>()), Times.Exactly(2));
stateMock.Verify(x => x.AddItemHandler(It.IsAny<string>(), It.IsAny<IItemHandler>()), Times.Once);
sdmMock.Verify(x => x.Push(It.IsAny<string>(), It.IsAny<IEnumerable<JProperty>>(),
It.Is<ForEachActionExecutor>(actionExecutor =>
actionExecutor.Equals(forEachActionExecutor))), Times.Exactly(2));
Expand All @@ -105,7 +105,7 @@ public async Task Test()

Assert.AreEqual("Update_a_record", exitAnswer2.NextAction);

stateMock.Verify(x => x.AddOutputs(It.IsAny<string>(), It.IsAny<ValueContainer>()), Times.Exactly(3));
stateMock.Verify(x => x.AddItemHandler(It.IsAny<string>(), It.IsAny<IItemHandler>()), Times.Once);
sdmMock.Verify(x => x.Push(It.IsAny<string>(), It.IsAny<IEnumerable<JProperty>>(),
It.Is<ForEachActionExecutor>(actionExecutor =>
actionExecutor.Equals(forEachActionExecutor))), Times.Exactly(3));
Expand Down

0 comments on commit 7cd855f

Please sign in to comment.