Skip to content

Commit b96f031

Browse files
Merge pull request #24 from thygesteffensen/issue/18/dountil
Issue/18/dountil Close #18
2 parents 0676934 + 86e5285 commit b96f031

File tree

6 files changed

+324
-2
lines changed

6 files changed

+324
-2
lines changed

PowerAutomateMockUp/ExpressionParser/Functions/Base/FlowRunnerDependencyExtension.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ public static void AddFlowRunner(this IServiceCollection services)
3636

3737
services.AddTransient<IFunction, TrimFunction>();
3838
services.AddTransient<IFunction, LengthFunction>();
39+
services.AddTransient<IFunction, GreaterFunction>();
3940

4041
services.AddFlowActionByFlowType<IfActionExecutor>("If");
4142
services.AddFlowActionByFlowType<ScopeActionExecutor>("Scope");
4243
services.AddFlowActionByFlowType<TerminateActionExecutor>("Terminate");
4344
services.AddFlowActionByFlowType<ForEachActionExecutor>("Foreach");
45+
services.AddFlowActionByFlowType<DoUntilActionExecutor>("Until");
4446
services.AddFlowActionByFlowType<SwitchActionExecutor>("Switch");
4547

4648
services.AddLogging();
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using Parser.ExpressionParser.Functions.Base;
2+
using Parser.ExpressionParser.Functions.CustomException;
3+
4+
namespace Parser.ExpressionParser.Functions.Implementations
5+
{
6+
public class GreaterFunction : Function
7+
{
8+
public GreaterFunction() : base("greater")
9+
{
10+
}
11+
12+
public override ValueContainer ExecuteFunction(params ValueContainer[] parameters)
13+
{
14+
if (parameters.Length < 2)
15+
{
16+
throw new ArgumentError("Too few arguments");
17+
}
18+
19+
return new ValueContainer(parameters[0].CompareTo(parameters[1]) > 0);
20+
}
21+
}
22+
}

PowerAutomateMockUp/ExpressionParser/ValueContainer.cs

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
namespace Parser.ExpressionParser
88
{
99
[JsonConverter(typeof(ValueContainerConverter))]
10-
public class ValueContainer
10+
public class ValueContainer : IComparable, IEquatable<ValueContainer>
1111
{
1212
private readonly dynamic _value;
1313
private readonly ValueType _type;
@@ -254,7 +254,7 @@ private ValueContainer JsonToValueContainer(JToken json)
254254
private ValueContainer JArrayToValueContainer(JArray json)
255255
{
256256
var list = new List<ValueContainer>();
257-
257+
258258
foreach (var jToken in json)
259259
{
260260
if (jToken.GetType() != typeof(JValue))
@@ -311,6 +311,61 @@ public bool IsNull()
311311
{
312312
return _type == ValueType.Null;
313313
}
314+
315+
public int CompareTo(object? obj)
316+
{
317+
if (obj == null || obj.GetType() != GetType()) throw new InvalidOperationException("Cannot compare these two...");
318+
319+
var other = (ValueContainer) obj;
320+
if (other.Type() != _type)
321+
{
322+
// TODO: Fix comparison
323+
324+
throw new InvalidOperationException("Cannot compare two different ValueContainers");
325+
}
326+
else
327+
{
328+
switch (_value)
329+
{
330+
case bool b:
331+
return b.CompareTo(other._value);
332+
case int i:
333+
return i.CompareTo(other._value);
334+
case float f:
335+
return f.CompareTo(other._value);
336+
case string s:
337+
return s.CompareTo(other._value);
338+
case Dictionary<string, ValueContainer> d:
339+
var d2 = (Dictionary<string, ValueContainer>) other._value;
340+
return d.Count - d2.Count;
341+
case IEnumerable<ValueContainer> l:
342+
var l2 = (IEnumerable<ValueContainer>) other._value;
343+
return l.Count() - l2.Count();
344+
default:
345+
return -1;
346+
}
347+
}
348+
}
349+
350+
public bool Equals(ValueContainer other)
351+
{
352+
if (ReferenceEquals(null, other)) return false;
353+
if (ReferenceEquals(this, other)) return true;
354+
return Equals(_value, other._value) && _type == other._type;
355+
}
356+
357+
public override bool Equals(object obj)
358+
{
359+
if (ReferenceEquals(null, obj)) return false;
360+
if (ReferenceEquals(this, obj)) return true;
361+
if (obj.GetType() != GetType()) return false;
362+
return Equals((ValueContainer) obj);
363+
}
364+
365+
/*public override int GetHashCode()
366+
{
367+
return HashCode.Combine(_value, (int) _type);
368+
}*/
314369
}
315370

316371
static class ValueContainerExtensions
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
using System;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using Microsoft.Extensions.Logging;
5+
using Newtonsoft.Json.Linq;
6+
using Parser.ExpressionParser;
7+
8+
namespace Parser.FlowParser.ActionExecutors.Implementations
9+
{
10+
public class DoUntilActionExecutor : DefaultBaseActionExecutor, IScopeActionExecutor
11+
{
12+
private readonly IScopeDepthManager _scopeDepthManager;
13+
private readonly ILogger<DoUntilActionExecutor> _logger;
14+
private readonly IExpressionEngine _expressionEngine;
15+
16+
private JProperty[] _actionDescriptions;
17+
private string _firstScopeActionName;
18+
private string _expressionString;
19+
private int _countLimit;
20+
private int _counts = 0;
21+
22+
public DoUntilActionExecutor(
23+
IScopeDepthManager scopeDepthManager,
24+
ILogger<DoUntilActionExecutor> logger,
25+
IExpressionEngine expressionEngine)
26+
{
27+
_scopeDepthManager = scopeDepthManager ?? throw new ArgumentNullException(nameof(scopeDepthManager));
28+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
29+
_expressionEngine = expressionEngine ?? throw new ArgumentNullException(nameof(expressionEngine));
30+
}
31+
32+
public override Task<ActionResult> Execute()
33+
{
34+
_logger.LogInformation("Entered do until...");
35+
36+
if (Json == null)
37+
{
38+
throw new PowerAutomateMockUpException($"Json cannot be null - cannot execute {ActionName}.");
39+
}
40+
41+
SetupDoUntil();
42+
43+
if (!EvaluateExpressionResult())
44+
{
45+
return Task.FromResult(new ActionResult());
46+
}
47+
48+
PushActions();
49+
50+
return Task.FromResult(new ActionResult {NextAction = _firstScopeActionName});
51+
}
52+
53+
private void SetupDoUntil()
54+
{
55+
_expressionString = (Json.SelectToken("$..expression") ??
56+
throw new InvalidOperationException("Json must contain expression token."))
57+
.Value<string>();
58+
59+
_countLimit = (Json.SelectToken("$..count")
60+
?? throw new InvalidOperationException("Json must contain count token.")
61+
).Value<int>();
62+
63+
var scopeActionDescriptions = (Json.SelectToken("$..actions") ??
64+
throw new InvalidOperationException("Json must contain actions token."))
65+
.OfType<JProperty>();
66+
_actionDescriptions = scopeActionDescriptions as JProperty[] ?? scopeActionDescriptions.ToArray();
67+
_firstScopeActionName = _actionDescriptions.First(ad =>
68+
!(ad.Value.SelectToken("$.runAfter") ??
69+
throw new InvalidOperationException("Json must contain runAfter token.")).Any()).Name;
70+
}
71+
72+
private void PushActions()
73+
{
74+
_scopeDepthManager.Push(ActionName, _actionDescriptions, this);
75+
}
76+
77+
public Task<ActionResult> ExitScope(ActionStatus scopeStatus)
78+
{
79+
_counts++;
80+
if (_counts >= _countLimit)
81+
{
82+
_logger.LogInformation($"Exited do until loop due to count limit of {_counts}...");
83+
return Task.FromResult(new ActionResult());
84+
}
85+
86+
if (EvaluateExpressionResult())
87+
{
88+
_logger.LogInformation("Continuing do until loop.");
89+
90+
PushActions();
91+
92+
return Task.FromResult(new ActionResult {NextAction = _firstScopeActionName});
93+
}
94+
else
95+
{
96+
_logger.LogInformation("Exited do until loop...");
97+
return Task.FromResult(new ActionResult());
98+
}
99+
}
100+
101+
private bool EvaluateExpressionResult()
102+
{
103+
var expressionResult = _expressionEngine.ParseToValueContainer(_expressionString);
104+
if (expressionResult.Type() != ValueContainer.ValueType.Boolean)
105+
{
106+
throw new PowerAutomateMockUpException("Do until expression did not resolve to boolean type.");
107+
}
108+
109+
return expressionResult.GetValue<bool>();
110+
}
111+
}
112+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
using System.Collections.Generic;
2+
using System.Threading.Tasks;
3+
using Moq;
4+
using Newtonsoft.Json.Linq;
5+
using NUnit.Framework;
6+
using Parser;
7+
using Parser.ExpressionParser;
8+
using Parser.FlowParser.ActionExecutors;
9+
using Parser.FlowParser.ActionExecutors.Implementations;
10+
11+
namespace Test.ActionTests
12+
{
13+
[TestFixture]
14+
public class DoUntilActionExecutorTest
15+
{
16+
string DoUntilAction =
17+
"{\"Do_until\": {" +
18+
"\"actions\": {" +
19+
"\"Increment_variable\": {" +
20+
"\"runAfter\": {}," +
21+
"\"type\": \"IncrementVariable\"," +
22+
"\"inputs\": {" +
23+
"\"name\": \"Variable\"," +
24+
"\"value\": 1" +
25+
"}}}," +
26+
"\"runAfter\": { \"Initialize_variable\": [ \"Succeeded\" ] }," +
27+
"\"expression\": \"@greater(variables('Variable'), 10)\"," +
28+
"\"limit\": {" +
29+
"\"count\": 3," +
30+
"\"timeout\": \"PT1H\"" +
31+
"}," +
32+
"\"type\": \"Until\"" +
33+
"}," +
34+
"\"Initialize_variable\": {" +
35+
"\"runAfter\": {}," +
36+
"\"type\": \"InitializeVariable\"," +
37+
"\"inputs\": {" +
38+
"\"variables\": [" +
39+
"{" +
40+
"\"name\": \"Variable\"," +
41+
"\"type\": \"integer\"" +
42+
"}]}}}";
43+
44+
[Test]
45+
public async Task TestDoUntilLimit()
46+
{
47+
var loggerMock = TestLogger.Create<DoUntilActionExecutor>();
48+
49+
var sdm = new Mock<IScopeDepthManager>();
50+
51+
var expr = new Mock<IExpressionEngine>();
52+
expr.Setup(x => x.ParseToValueContainer(It.IsAny<string>())).Returns(new ValueContainer(true));
53+
54+
var doUntil = new DoUntilActionExecutor(sdm.Object, loggerMock, expr.Object);
55+
doUntil.InitializeActionExecutor("DoUntil", JToken.Parse(DoUntilAction));
56+
57+
var result = await doUntil.Execute();
58+
Assert.AreEqual(ActionStatus.Succeeded, result.ActionStatus);
59+
Assert.NotNull(result.NextAction);
60+
61+
sdm.Verify(
62+
x => x.Push(
63+
It.IsAny<string>(),
64+
It.IsAny<IEnumerable<JProperty>>(),
65+
It.IsAny<IScopeActionExecutor>()),
66+
Times.Once);
67+
68+
var exit1 = await doUntil.ExitScope(ActionStatus.Succeeded);
69+
Assert.NotNull(exit1.NextAction);
70+
sdm.Verify(
71+
x => x.Push(
72+
It.IsAny<string>(),
73+
It.IsAny<IEnumerable<JProperty>>(),
74+
It.IsAny<IScopeActionExecutor>()),
75+
Times.Exactly(2));
76+
77+
78+
var exit2 = await doUntil.ExitScope(ActionStatus.Succeeded);
79+
Assert.NotNull(exit2.NextAction);
80+
sdm.Verify(
81+
x => x.Push(
82+
It.IsAny<string>(),
83+
It.IsAny<IEnumerable<JProperty>>(),
84+
It.IsAny<IScopeActionExecutor>()),
85+
Times.Exactly(3));
86+
87+
var exit3 = await doUntil.ExitScope(ActionStatus.Succeeded);
88+
Assert.IsNull(exit3.NextAction);
89+
sdm.Verify(
90+
x => x.Push(
91+
It.IsAny<string>(),
92+
It.IsAny<IEnumerable<JProperty>>(),
93+
It.IsAny<IScopeActionExecutor>()),
94+
Times.Exactly(3));
95+
}
96+
97+
[Test]
98+
public async Task TestDoUntilNone()
99+
{
100+
var loggerMock = TestLogger.Create<DoUntilActionExecutor>();
101+
102+
var sdm = new Mock<IScopeDepthManager>();
103+
104+
var expr = new Mock<IExpressionEngine>();
105+
expr.Setup(x => x.ParseToValueContainer(It.IsAny<string>())).Returns(new ValueContainer(false));
106+
107+
var doUntil = new DoUntilActionExecutor(sdm.Object, loggerMock, expr.Object);
108+
doUntil.InitializeActionExecutor("DoUntil", JToken.Parse(DoUntilAction));
109+
110+
var result = await doUntil.Execute();
111+
Assert.AreEqual(ActionStatus.Succeeded, result.ActionStatus);
112+
Assert.IsNull(result.NextAction);
113+
114+
sdm.Verify(
115+
x => x.Push(
116+
It.IsAny<string>(),
117+
It.IsAny<IEnumerable<JProperty>>(),
118+
It.IsAny<IScopeActionExecutor>()),
119+
Times.Never);
120+
}
121+
}
122+
}

Test/ParserTest.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,15 @@ public void TestInternalsFlowStorageValueContainer(TestInput input)
304304
VariableKey = "dictionary",
305305
ValueContainers = BigValue,
306306
StorageOption = StorageOption.Variables
307+
},
308+
new TestInput
309+
(
310+
"@greater(variables('Variable'), 10)",
311+
ValueContainer.ValueType.Boolean)
312+
{
313+
VariableKey = "Variable",
314+
ValueContainers = new[] {new ValueContainer(11)},
315+
StorageOption = StorageOption.Variables
307316
}
308317
};
309318

0 commit comments

Comments
 (0)