Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dispaly input and output in the workflow instance #143

Merged
merged 11 commits into from
Feb 19, 2024
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using Elsa.Api.Client.Contracts;
using Elsa.Studio.Contracts;
using Elsa.Studio.Login.ComponentProviders;
using Elsa.Studio.Login.Contracts;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public interface IWorkflowDefinitionService
/// <summary>
/// Finds all workflow definitions by their IDs.
/// </summary>
Task<IEnumerable<WorkflowDefinition>> FindManyByIdAsync(IEnumerable<string> ids, CancellationToken cancellationToken = default);
Task<IEnumerable<WorkflowDefinition>> FindManyByIdAsync(IEnumerable<string> ids, bool includeCompositeRoot = false, CancellationToken cancellationToken = default);

/// <summary>
/// Saves a workflow definition.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ public async Task<PagedListResponse<WorkflowDefinitionSummary>> ListAsync(ListWo
}

/// <inheritdoc />
public async Task<IEnumerable<WorkflowDefinition>> FindManyByIdAsync(IEnumerable<string> ids, CancellationToken cancellationToken = default)
public async Task<IEnumerable<WorkflowDefinition>> FindManyByIdAsync(IEnumerable<string> ids, bool includeCompositeRoot = false, CancellationToken cancellationToken = default)
{
var api = await GetApiAsync(cancellationToken);
var response = await api.GetManyByIdAsync(ids.ToList(), true, cancellationToken);
var response = await api.GetManyByIdAsync(ids.ToList(), includeCompositeRoot, cancellationToken);
return response.Items;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<MudTooltip Text="Edit Workflow Definition">
<MudIconButton
Icon="@Icons.Material.Outlined.EditNote"
OnClick="@(() => OnEditClicked(WorkflowDefinition.DefinitionId))">
OnClick="@(() => OnEditClicked())">
</MudIconButton>
</MudTooltip>
<ElapsedTime StartTime="WorkflowInstance.CreatedAt" EndTime="@(WorkflowInstance.Status == WorkflowStatus.Running ? DateTimeOffset.UtcNow : WorkflowInstance.FinishedAt ?? DateTimeOffset.UtcNow)"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using Elsa.Api.Client.Extensions;
using Elsa.Api.Client.Resources.ActivityDescriptors.Models;
Expand Down Expand Up @@ -71,6 +72,12 @@ public partial class WorkflowInstanceDesigner : IAsyncDisposable
/// </summary>
[Parameter] public EventCallback<string> EditWorkflowDefinition { get; set; }

/// <summary>
/// Gets or sets the current selected sub-workflow.
/// </summary>
[Parameter]
public JsonObject? SelectedSubWorkflow { get; set; } = default!;

[Inject] private IActivityRegistry ActivityRegistry { get; set; } = default!;
[Inject] private IDiagramDesignerService DiagramDesignerService { get; set; } = default!;
[Inject] private IDomAccessor DomAccessor { get; set; } = default!;
Expand Down Expand Up @@ -100,6 +107,16 @@ private RadzenSplitterPane ActivityPropertiesPane
private MudTabs PropertyTabs { get; set; } = default!;
private MudTabPanel EventsTabPanel { get; set; } = default!;

/// <summary>
/// Updates the selected sub-workflow.
/// </summary>
/// <param name="obj"></param>
public void UpdateSubWorkflow(JsonObject? obj)
{
SelectedSubWorkflow = obj;
StateHasChanged();
}

/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
Expand Down Expand Up @@ -254,8 +271,24 @@ private async Task OnResize(RadzenSplitterResizeEventArgs arg)
await UpdatePropertiesPaneHeightAsync();
}

private Task OnEditClicked(string definitionId)
private Task OnEditClicked()
{
var definitionId = WorkflowDefinition!.DefinitionId;

if (SelectedSubWorkflow != null)
{
var typeName = SelectedSubWorkflow.GetTypeName();
var version = SelectedSubWorkflow.GetVersion();
var descriptor = ActivityRegistry.Find(typeName, version);
var isWorkflowActivity = descriptor != null &&
descriptor.CustomProperties.TryGetValue("RootType", out var rootTypeNameElement) &&
((JsonElement)rootTypeNameElement).GetString() == "WorkflowDefinitionActivity";
if (isWorkflowActivity)
{
definitionId = SelectedSubWorkflow.GetWorkflowDefinitionId();
}
}

var editWorkflowDefinition = this.EditWorkflowDefinition;

if (editWorkflowDefinition.HasDelegate)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,32 +24,64 @@
</MudTabPanel>
<MudTabPanel Text="Variables">
<Well>
<MudTable
T="Variable"
Items="WorkflowDefinition.Variables">
<HeaderContent>
<MudTh>Name</MudTh>
<MudTh>Type</MudTh>
<MudTh>Storage</MudTh>
<MudTh>Value</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd>@context.Name</MudTd>
<MudTd>@context.GetTypeDisplayName()</MudTd>
<MudTd>@GetStorageDriverDisplayName(context.StorageDriverTypeName)</MudTd>
<MudTd>@GetVariableValue(context)</MudTd>
</RowTemplate>
<NoRecordsContent>
<div>
@if (WorkflowVariableData.Any())
{
<DataPanel Data="WorkflowVariableData" HideEmptyValues="false"/>
}
else
{
<Well>
No variables
</Well>
</NoRecordsContent>
</MudTable>
}
</div>
</Well>
</MudTabPanel>
<MudTabPanel Text="Input/output">
<Well>
<MudAlert Variant="Variant.Outlined">Input output</MudAlert>
<div>
<MudText Typo="Typo.overline" GutterBottom="true" Align="Align.Left">Inputs</MudText>
@if (WorkflowInputData.Any())
{
<DataPanel Data="WorkflowInputData" HideEmptyValues="false"/>
}
else
{
<Well>
No inputs
</Well>
}
</div>
<div>
<MudText Typo="Typo.overline" GutterBottom="true" Align="Align.Left">Outputs</MudText>
@if (WorkflowOutputData.Any())
{
<DataPanel Data="WorkflowOutputData" HideEmptyValues="false"/>
}
else
{
<Well>
No outputs
</Well>
}
</div>
</Well>
<Well>
@if (SubWorkflowInputData.Any())
{
<div>
<MudText Typo="Typo.overline" GutterBottom="true" Align="Align.Left">Sub-Workflow Inputs</MudText>
<DataPanel Data="SubWorkflowInputData" HideEmptyValues="false"/>
</div>
}
@if (SubWorkflowOutputData.Any())
{
<div>
<MudText Typo="Typo.overline" GutterBottom="true" Align="Align.Left">Sub-Workflow Outputs</MudText>
<DataPanel Data="SubWorkflowOutputData" HideEmptyValues="false"/>
</div>
}
</Well>
</MudTabPanel>
</MudTabs>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using Elsa.Api.Client.Extensions;
using Elsa.Api.Client.Resources.ActivityExecutions.Models;
using Elsa.Api.Client.Resources.StorageDrivers.Models;
using Elsa.Api.Client.Resources.WorkflowDefinitions.Models;
using Elsa.Api.Client.Resources.WorkflowInstances.Enums;
Expand Down Expand Up @@ -39,10 +40,17 @@ public partial class WorkflowInstanceDetails
[Parameter]
public JsonObject? SelectedSubWorkflow { get; set; } = default!;

/// <summary>
/// Gets or sets the current selected sub-workflow executions.
/// </summary>
[Parameter]
public ICollection<ActivityExecutionRecord>? SelectedSubWorkflowExecutions { get; set; } = default!;

[Inject] private IStorageDriverService StorageDriverService { get; set; } = default!;
[Inject] private IWorkflowInstanceObserverFactory WorkflowInstanceObserverFactory { get; set; } = default!;
[Inject] private IWorkflowInstanceService WorkflowInstanceService { get; set; } = default!;
[Inject] private IActivityRegistry ActivityRegistry { get; set; } = default!;
[Inject] private IActivityExecutionService ActivityExecutionService { get; set; } = default!;

private IDictionary<string, StorageDriverDescriptor> StorageDriverLookup { get; set; } =
new Dictionary<string, StorageDriverDescriptor>();
Expand Down Expand Up @@ -74,21 +82,59 @@ public partial class WorkflowInstanceDetails
}
}

private Dictionary<string, DataPanelItem> WorkflowVariableData
{
get
{
if (WorkflowDefinition == null)
return new Dictionary<string, DataPanelItem>();

return WorkflowDefinition.Variables.ToDictionary(entry => entry.Name,
entry => new DataPanelItem(@GetVariableValue(entry)));
}
}

private Dictionary<string, DataPanelItem> WorkflowInputData
{
get
{
if (_workflowInstance == null)
return new Dictionary<string, DataPanelItem>();

return _workflowInstance.WorkflowState.Input.ToDictionary(entry => entry.Key,
entry => new DataPanelItem(entry.Value.ToString()));
}
}

private Dictionary<string, DataPanelItem> WorkflowOutputData
{
get
{
if (_workflowInstance == null)
return new Dictionary<string, DataPanelItem>();

return _workflowInstance.WorkflowState.Output.ToDictionary(entry => entry.Key,
entry => new DataPanelItem(entry.Value.ToString()));
}
}

private Dictionary<string, DataPanelItem> WorkflowInstanceSubWorkflowData
{
get
{
if (SelectedSubWorkflow == null)
return new ();
return new();

var typeName = SelectedSubWorkflow.GetTypeName();
var version = SelectedSubWorkflow.GetVersion();
var descriptor = ActivityRegistry.Find(typeName, version);
var isWorkflowActivity = descriptor != null && descriptor.CustomProperties.TryGetValue("RootType", out var rootTypeNameElement) && ((JsonElement)rootTypeNameElement).GetString() == "WorkflowDefinitionActivity";
var isWorkflowActivity = descriptor != null &&
descriptor.CustomProperties.TryGetValue("RootType", out var rootTypeNameElement) &&
((JsonElement)rootTypeNameElement).GetString() == "WorkflowDefinitionActivity";
var workflowDefinitionId = isWorkflowActivity ? SelectedSubWorkflow.GetWorkflowDefinitionId() : default;

if (workflowDefinitionId == null)
return new ();
return new();

return new()
{
Expand All @@ -101,9 +147,72 @@ public partial class WorkflowInstanceDetails
}
}

public void UpdateSubWorkflow(JsonObject? obj)
private Dictionary<string, DataPanelItem> SubWorkflowInputData
{
get
{
if (SelectedSubWorkflowExecutions == null || SelectedSubWorkflow == null)
return new Dictionary<string, DataPanelItem>();

var execution = SelectedSubWorkflowExecutions.LastOrDefault();
var inputData = new Dictionary<string, DataPanelItem>();
var activityState = execution?.ActivityState;
if (activityState != null)
{
var activityDescriptor =
ActivityRegistry.Find(SelectedSubWorkflow.GetTypeName(), SelectedSubWorkflow.GetVersion())!;
foreach (var inputDescriptor in activityDescriptor.Inputs)
{
var inputValue = activityState.TryGetValue(inputDescriptor.Name, out var value) ? value : default;
inputData[inputDescriptor.Name] = new(inputValue?.ToString());
}
}

return inputData;
}
}

private Dictionary<string, DataPanelItem> SubWorkflowOutputData
{
get
{
if (SelectedSubWorkflowExecutions == null || SelectedSubWorkflow == null)
return new Dictionary<string, DataPanelItem>();

var execution = SelectedSubWorkflowExecutions.LastOrDefault();
var outputData = new Dictionary<string, DataPanelItem>();

if (execution != null)
{
var outputs = execution.Outputs;
var activityDescriptor =
ActivityRegistry.Find(SelectedSubWorkflow.GetTypeName(), SelectedSubWorkflow.GetVersion())!;
var outputDescriptors = activityDescriptor.Outputs;

foreach (var outputDescriptor in outputDescriptors)
{
var outputValue = outputs != null
? outputs.TryGetValue(outputDescriptor.Name, out var value) ? value : default
: default;
outputData[outputDescriptor.Name] = new(outputValue?.ToString());
}
}

return outputData;
}
}

/// <summary>
/// Updates the selected sub-workflow.
/// </summary>
/// <param name="obj"></param>
public async Task UpdateSubWorkflowAsync(JsonObject? obj)
{
SelectedSubWorkflow = obj;
SelectedSubWorkflowExecutions = obj == null
? null
: (await InvokeWithBlazorServiceContext(() =>
ActivityExecutionService.ListAsync(WorkflowInstance!.Id, obj.GetNodeId()!))).ToList();
StateHasChanged();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
var definition = WorkflowDefinitions.First(x => x.Id == workflowInstance.DefinitionVersionId);
<MudTabPanel Text="@workflowInstance.Id" ShowCloseIcon="false" Style="height: 100%">
<WorkflowInstanceDesigner
@ref="@_workflowInstanceDesigner"
WorkflowInstance="workflowInstance"
WorkflowDefinition="definition"
PathChanged="OnPathChanged"
Expand All @@ -33,7 +34,7 @@

</RadzenSplitterPane>
<RadzenSplitterPane Size="35%" Min="100px">
<WorkflowInstanceDetails @ref="@_workflowInstanceDetails" WorkflowInstance="@SelectedWorkflowInstance" WorkflowDefinition="SelectedWorkflowDefinition" />
<WorkflowInstanceDetails @ref="@_workflowInstanceDetails" WorkflowInstance="@SelectedWorkflowInstance" WorkflowDefinition="SelectedWorkflowDefinition"/>
</RadzenSplitterPane>

</RadzenSplitter>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Text.Json.Nodes;
using Elsa.Api.Client.Extensions;
using Elsa.Api.Client.Resources.ActivityExecutions.Models;
using Elsa.Api.Client.Resources.WorkflowDefinitions.Models;
using Elsa.Api.Client.Resources.WorkflowInstances.Models;
using Elsa.Studio.Workflows.Components.WorkflowDefinitionEditor.Components;
Expand All @@ -15,6 +16,7 @@ public partial class WorkflowInstanceWorkspace : IWorkspace
{
private MudDynamicTabs _dynamicTabs = default!;
private WorkflowInstanceDetails _workflowInstanceDetails = default!;
private WorkflowInstanceDesigner _workflowInstanceDesigner = default!;

[Parameter] public IList<WorkflowInstance> WorkflowInstances { get; set; } = default!;
[Parameter] public IList<WorkflowDefinition> WorkflowDefinitions { get; set; } = default!;
Expand All @@ -36,7 +38,7 @@ public partial class WorkflowInstanceWorkspace : IWorkspace
ActiveTabIndex >= 0 && ActiveTabIndex < WorkflowInstances.Count
? WorkflowInstances.ElementAtOrDefault(ActiveTabIndex)
: default;

private WorkflowDefinition? SelectedWorkflowDefinition
{
get
Expand Down Expand Up @@ -66,7 +68,8 @@ private async Task OnActivePanelIndexChanged(int value)

private async Task OnPathChanged(DesignerPathChangedArgs args)
{
_workflowInstanceDetails.UpdateSubWorkflow(args.CurrentActivity);
await _workflowInstanceDetails.UpdateSubWorkflowAsync(args.CurrentActivity);
_workflowInstanceDesigner.UpdateSubWorkflow(args.CurrentActivity);

if (PathChanged != null)
await PathChanged(args);
Expand Down