Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions guidelines/mwm-workflow-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,34 @@ Example (run my-task-id when the patient is female):
}
```

The following examples both function the same and act as an AND condition.

```json
{
...task...
"task_destinations": [
{
"name": "my-task-id",
"conditions": ["{{context.input.dicom.series.all('0010','0040')}} == 'F' AND {{context.input.dicom.series.all('0010','0040')}} == 'M'"]
},
],
...
}
```

```json
{
...task...
"task_destinations": [
{
"name": "my-task-id",
"conditions": ["{{context.input.dicom.series.all('0010','0040')}} == 'F'", "{{context.input.dicom.series.all('0010','0040')}} == 'M'"]
},
],
...
}
```

### Retention Policies
_Future version: TBD_

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
* limitations under the License.
*/

using System.Text;

namespace Monai.Deploy.WorkflowManager.ConditionsResolver.Extensions
{
public static class StringExtensions
Expand Down Expand Up @@ -53,5 +55,32 @@ public static string TrimStartExt(this string input, string prefixToRemove, Stri
}
return string.Empty;
}

/// <summary>
/// Adds brackets to multiple conditions.
/// </summary>
/// <param name="input">Array of conditions.</param>
/// <returns></returns>
public static string CombineConditionString(this string[] input)
{
var value = new StringBuilder();

if (input.Length == 1)
{
return input.First();
}

for (var i = 0; i < input.Length; i++)
{
value.Append($"({input[i]})");

if (i != input.Length - 1)
{
value.Append(" AND ");
}
}

return value.ToString();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using Microsoft.Extensions.Logging;
using Monai.Deploy.WorkflowManager.Common.Interfaces;
using Monai.Deploy.WorkflowManager.ConditionsResolver.Constants;
using Monai.Deploy.WorkflowManager.ConditionsResolver.Extensions;
using Monai.Deploy.WorkflowManager.ConditionsResolver.Resolver;
using Monai.Deploy.WorkflowManager.Contracts.Models;
using Monai.Deploy.WorkflowManager.Storage.Services;
Expand Down Expand Up @@ -92,6 +93,24 @@ private set
}
}

public bool TryParse(string[] conditions, WorkflowInstance workflowInstance)
{
Guard.Against.NullOrEmpty(conditions);
Guard.Against.Null(workflowInstance);
try
{
var joinedConditions = conditions.CombineConditionString();
joinedConditions = ResolveParameters(joinedConditions, workflowInstance);
var conditionalGroup = ConditionalGroup.Create(joinedConditions);
return conditionalGroup.Evaluate();
}
catch (Exception ex)
{
_logger.LogWarning($"Failure attemping to parse condition - {conditions}", ex);
return false;
}
}

public bool TryParse(string conditions, WorkflowInstance workflowInstance)
{
Guard.Against.NullOrEmpty(conditions);
Expand All @@ -109,12 +128,6 @@ public bool TryParse(string conditions, WorkflowInstance workflowInstance)
}
}

/// <summary>
/// Resolves parameters in query string.
/// </summary>
/// <param name="conditions">The query string Example: {{ context.executions.task['other task'].'Fred' }}</param>
/// <param name="workflowInstance">workflow instance to resolve metadata parameter</param>
/// <returns></returns>
public string ResolveParameters(string conditions, WorkflowInstance workflowInstance)
{
Guard.Against.NullOrEmpty(conditions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,34 @@ namespace Monai.Deploy.WorkflowManager.ConditionsResolver.Parser
{
public interface IConditionalParameterParser
{
/// <summary>
/// Resolves parameters in query string.
/// </summary>
/// <param name="conditions">The query string Example: {{ context.executions.other_task.'Fred' }}</param>
/// <param name="workflowInstance">workflow instance to resolve metadata parameter</param>
/// <returns></returns>
string ResolveParameters(string conditions, WorkflowInstance workflowInstance);

/// <summary>
/// Resolves parameters in query string.
/// </summary>
/// <param name="conditions">The query string Example: {{ context.executions.other_task.'Fred' }}</param>
/// <param name="workflowInstanceId">workflow instance id to resolve metadata parameter</param>
/// <returns></returns>
string ResolveParameters(string conditions, string workflowInstanceId);

/// <summary>
/// Verifies if an array of strings of conditions evaluates to true.
/// </summary>
/// <param name="conditions">An array of strings of conditions.</param>
/// <param name="workflowInstance">The workflow instance of the task.</param>
bool TryParse(string[] conditions, WorkflowInstance workflowInstance);

/// <summary>
/// Verifies if a string of conditions evaluates to true.
/// </summary>
/// <param name="conditions">A string of conditions.</param>
/// <param name="workflowInstance">The workflow instance of the task.</param>
bool TryParse(string conditions, WorkflowInstance workflowInstance);
}
}
3 changes: 2 additions & 1 deletion src/WorkflowManager/Contracts/Models/TaskDestination.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/

using System;
using Newtonsoft.Json;

namespace Monai.Deploy.WorkflowManager.Contracts.Models
Expand All @@ -24,6 +25,6 @@ public class TaskDestination
public string Name { get; set; } = string.Empty;

[JsonProperty(PropertyName = "conditions")]
public string Conditions { get; set; } = string.Empty;
public string[] Conditions { get; set; } = Array.Empty<string>();
}
}
3 changes: 3 additions & 0 deletions src/WorkflowManager/Logging/Logging/Log.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,5 +162,8 @@ public static void LogControllerEndTime(this ILogger logger, ResultExecutedConte

[LoggerMessage(EventId = 32, Level = LogLevel.Debug, Message = "Verifying artifact existence on bucket {bucket}: {artifactKey}={artifactValue}.")]
public static partial void VerifyArtifactExistence(this ILogger logger, string bucket, string artifactKey, string artifactValue);

[LoggerMessage(EventId = 33, Level = LogLevel.Debug, Message = "Task destination condition for task {taskId} with condition: {conditions} resolved to false.")]
public static partial void TaskDestinationConditionFalse(this ILogger logger, string conditions, string taskId);
}
}
2 changes: 1 addition & 1 deletion src/WorkflowManager/Storage/Services/DicomService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ private static string GetPatientName(object[] values)
}
}

if(resultStr.Any() is true)
if (resultStr.Any() is true)
{
return string.Concat(resultStr);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
using Monai.Deploy.Storage.Configuration;
using Monai.Deploy.WorkflowManager.Common.Extensions;
using Monai.Deploy.WorkflowManager.Common.Interfaces;
using Monai.Deploy.WorkflowManager.ConditionsResolver.Extensions;
using Monai.Deploy.WorkflowManager.ConditionsResolver.Parser;
using Monai.Deploy.WorkflowManager.Configuration;
using Monai.Deploy.WorkflowManager.Contracts.Constants;
Expand Down Expand Up @@ -507,10 +508,12 @@ private async Task<List<TaskExecution>> CreateTaskDestinations(WorkflowInstance
foreach (var taskDest in currentTaskDestinations)
{
//Evaluate Conditional
if (!string.IsNullOrEmpty(taskDest.Conditions)
&& taskDest.Conditions != string.Empty
&& !_conditionalParameterParser.TryParse(taskDest.Conditions, workflowInstance))
if (taskDest.Conditions.IsNullOrEmpty() is false
&& taskDest.Conditions.Any(c => string.IsNullOrWhiteSpace(c) is false)
&& _conditionalParameterParser.TryParse(taskDest.Conditions, workflowInstance) is false)
{
_logger.TaskDestinationConditionFalse(taskDest.Conditions.CombineConditionString(), taskDest.Name);

continue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,23 @@ Scenario: Workflow instance status remains created when any task status is eithe
And Workflow Instance status is Created

@TaskDestinationConditions
Scenario: Workflow instance status is failed when a condition is invalid
Scenario: Single task destinations with multiple conditions true, single task dispatch message sent
Given I have a clinical workflow Multi_Task_Workflow_Destination_Single_Multi_Condition_True
And I have a Workflow Instance WFI_Task_Destination_Multi_Condition_True with no artifacts
When I publish a Task Update Message Task_Update_Task_Destination_Multi_Condition_True with status Succeeded
Then 1 Task Dispatch event is published
And Workflow Instance status is Created

@TaskDestinationConditions
Scenario: Single task destinations with one condition true and one false, workflow instance status is succeeded
Given I have a clinical workflow Multi_Task_Workflow_Destination_Single_Multi_Condition_False
And I have a Workflow Instance WFI_Task_Destination_Multi_Condition_False with no artifacts
When I publish a Task Update Message Task_Update_Task_Destination_Multi_Condition_False with status Succeeded
Then A Task Dispatch event is not published
And Workflow Instance status is Succeeded

@TaskDestinationConditions
Scenario: Workflow instance status is succeeded when a condition is invalid
Given I have a clinical workflow Multi_Task_Workflow_Task_Destination_Invalid_Condition
And I have a Workflow Instance WFI_Task_Destination_Invalid_Condition with no artifacts
When I publish a Task Update Message Task_Update_Task_Destination_Invalid_Condition with status Succeeded
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,38 @@ public static TaskUpdateEvent CreateTaskUpdateEvent(string workflowInstanceName)
}
},
new TaskUpdateTestData()
{
Name = "Task_Update_Task_Destination_Multi_Condition_True",
TaskUpdateEvent = new TaskUpdateEvent()
{
WorkflowInstanceId = Helper.GetWorkflowInstanceByName("WFI_Task_Destination_Multi_Condition_True").WorkflowInstance.Id,
ExecutionId = Helper.GetWorkflowInstanceByName("WFI_Task_Destination_Multi_Condition_True").WorkflowInstance.Tasks[0].ExecutionId,
CorrelationId = Guid.NewGuid().ToString(),
Reason = FailureReason.None,
Message = "Task Message",
TaskId = Helper.GetWorkflowInstanceByName("WFI_Task_Destination_Multi_Condition_True").WorkflowInstance.Tasks[0].TaskId,
Metadata = new Dictionary<string, object>()
{
}
}
},
new TaskUpdateTestData()
{
Name = "Task_Update_Task_Destination_Multi_Condition_False",
TaskUpdateEvent = new TaskUpdateEvent()
{
WorkflowInstanceId = Helper.GetWorkflowInstanceByName("WFI_Task_Destination_Multi_Condition_False").WorkflowInstance.Id,
ExecutionId = Helper.GetWorkflowInstanceByName("WFI_Task_Destination_Multi_Condition_False").WorkflowInstance.Tasks[0].ExecutionId,
CorrelationId = Guid.NewGuid().ToString(),
Reason = FailureReason.None,
Message = "Task Message",
TaskId = Helper.GetWorkflowInstanceByName("WFI_Task_Destination_Multi_Condition_False").WorkflowInstance.Tasks[0].TaskId,
Metadata = new Dictionary<string, object>()
{
}
}
},
new TaskUpdateTestData()
{
Name = "Task_Update_Task_Destination_Metadata_Condition_True",
TaskUpdateEvent = new TaskUpdateEvent()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,62 @@ public static WorkflowInstance CreateWorkflowInstance(string workflowName)
}
},
new WorkflowInstanceTestData()
{
Name = "WFI_Task_Destination_Multi_Condition_True",
WorkflowInstance = new WorkflowInstance()
{
Id = Guid.NewGuid().ToString(),
AeTitle = Helper.GetWorkflowByName("Multi_Task_Workflow_Destination_Single_Multi_Condition_True")?.WorkflowRevision?.Workflow?.InformaticsGateway?.AeTitle,
WorkflowId = Helper.GetWorkflowByName("Multi_Task_Workflow_Destination_Single_Multi_Condition_True")?.WorkflowRevision?.WorkflowId ?? "",
PayloadId = Guid.NewGuid().ToString(),
StartTime = DateTime.Now,
Status = Status.Created,
BucketId = "bucket_1",
InputMetaData = new Dictionary<string, string>()
{
{ "", "" }
},
Tasks = new List<TaskExecution>
{
new TaskExecution()
{
ExecutionId = Guid.NewGuid().ToString(),
TaskId = Helper.GetWorkflowByName("Multi_Task_Workflow_Destination_Single_Multi_Condition_True")?.WorkflowRevision?.Workflow?.Tasks.FirstOrDefault()?.Id,
TaskType = Helper.GetWorkflowByName("Multi_Task_Workflow_Destination_Single_Multi_Condition_True")?.WorkflowRevision?.Workflow?.Tasks.FirstOrDefault()?.Type,
Status = TaskExecutionStatus.Dispatched
}
}
}
},
new WorkflowInstanceTestData()
{
Name = "WFI_Task_Destination_Multi_Condition_False",
WorkflowInstance = new WorkflowInstance()
{
Id = Guid.NewGuid().ToString(),
AeTitle = Helper.GetWorkflowByName("Multi_Task_Workflow_Destination_Single_Multi_Condition_False")?.WorkflowRevision?.Workflow?.InformaticsGateway?.AeTitle,
WorkflowId = Helper.GetWorkflowByName("Multi_Task_Workflow_Destination_Single_Multi_Condition_False")?.WorkflowRevision?.WorkflowId ?? "",
PayloadId = Guid.NewGuid().ToString(),
StartTime = DateTime.Now,
Status = Status.Created,
BucketId = "bucket_1",
InputMetaData = new Dictionary<string, string>()
{
{ "", "" }
},
Tasks = new List<TaskExecution>
{
new TaskExecution()
{
ExecutionId = Guid.NewGuid().ToString(),
TaskId = Helper.GetWorkflowByName("Multi_Task_Workflow_Destination_Single_Multi_Condition_False")?.WorkflowRevision?.Workflow?.Tasks.FirstOrDefault()?.Id,
TaskType = Helper.GetWorkflowByName("Multi_Task_Workflow_Destination_Single_Multi_Condition_False")?.WorkflowRevision?.Workflow?.Tasks.FirstOrDefault()?.Type,
Status = TaskExecutionStatus.Dispatched
}
}
}
},
new WorkflowInstanceTestData()
{
Name = "WFI_Task_Destination_Metadata_Condition_False",
WorkflowInstance = new WorkflowInstance()
Expand Down
Loading