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

Auto-refresh of Activity Execution Record Details #301

Open
wants to merge 9 commits into
base: blueberry
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public record ActivityExecutionRecordTableRow(int Number, ActivityExecutionRecor
public void Refresh()
{
CreateDataModels();
StateHasChanged();
}

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
using Elsa.Api.Client.Resources.ActivityExecutions.Models;
using Elsa.Studio.Models;
using Elsa.Studio.Workflows.Domain.Contracts;
using Elsa.Studio.Workflows.Extensions;
using Microsoft.AspNetCore.Components;
using MudBlazor;

namespace Elsa.Studio.Workflows.Components.WorkflowInstanceViewer.Components;

/// Displays the details of an activity.
public partial class ActivityExecutionsTab
public partial class ActivityExecutionsTab : IAsyncDisposable
{
/// Represents a row in the table of activity executions.
/// <param name="Number">The number of executions.</param>
Expand All @@ -31,10 +32,12 @@ public record ActivityExecutionRecordTableRow(int Number, ActivityExecutionRecor
private IDictionary<string, DataPanelItem> SelectedActivityState { get; set; } = new Dictionary<string, DataPanelItem>();
private IDictionary<string, DataPanelItem> SelectedOutcomesData { get; set; } = new Dictionary<string, DataPanelItem>();
private IDictionary<string, DataPanelItem> SelectedOutputData { get; set; } = new Dictionary<string, DataPanelItem>();
private Timer? _refreshTimer;

/// Refreshes the component.
public void Refresh()
public async Task RefreshAsync()
{
await StopRefreshTimerAsync();
SelectedItem = null;
SelectedActivityState = new Dictionary<string, DataPanelItem>();
SelectedOutcomesData = new Dictionary<string, DataPanelItem>();
Expand Down Expand Up @@ -70,11 +73,51 @@ private void CreateSelectedItemDataModels(ActivityExecutionRecord? record)
SelectedOutputData = outputData;
}

private async Task OnActivityExecutionClicked(TableRowClickEventArgs<ActivityExecutionRecordTableRow> arg)
private async Task RefreshSelectedItemAsync(string id)
{
var id = arg.Item.ActivityExecutionSummary.Id;
SelectedItem = await ActivityExecutionService.GetAsync(id);
CreateSelectedItemDataModels(SelectedItem);
StateHasChanged();
await InvokeAsync(StateHasChanged);
}

private async Task OnActivityExecutionClicked(TableRowClickEventArgs<ActivityExecutionRecordTableRow> arg)
{
await StopRefreshTimerAsync();
var id = arg.Item.ActivityExecution.Id;
await RefreshSelectedItemAsync(id);

if (SelectedItem == null)
return;

if (SelectedItem.IsFused())
return;

RefreshSelectedItemPeriodically(id);
}

private void RefreshSelectedItemPeriodically(string id)
{
async void Callback(object? _)
{
await RefreshSelectedItemAsync(id);

if (SelectedItem == null || SelectedItem.IsFused())
await StopRefreshTimerAsync();
}

_refreshTimer = new Timer(Callback, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
}

private async Task StopRefreshTimerAsync()
{
if (_refreshTimer == null) return;
await _refreshTimer.DisposeAsync();
_refreshTimer = null;
}

async ValueTask IAsyncDisposable.DisposeAsync()
{
if (_refreshTimer != null) await _refreshTimer.DisposeAsync();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Elsa.Studio.DomInterop.Contracts;
using Elsa.Studio.Workflows.Contracts;
using Elsa.Studio.Workflows.Domain.Contracts;
using Elsa.Studio.Workflows.Extensions;
using Elsa.Studio.Workflows.Models;
using Elsa.Studio.Workflows.Pages.WorkflowInstances.View.Models;
using Elsa.Studio.Workflows.Shared.Args;
Expand Down Expand Up @@ -72,6 +73,7 @@ public partial class WorkflowInstanceDesigner : IAsyncDisposable
private IWorkflowInstanceObserver? WorkflowInstanceObserver { get; set; } = default!;
private ICollection<ActivityExecutionRecordSummary> SelectedActivityExecutions { get; set; } = new List<ActivityExecutionRecordSummary>();
private ActivityExecutionRecord? LastActivityExecution { get; set; }
private Timer? _refreshTimer;

private RadzenSplitterPane ActivityPropertiesPane
{
Expand Down Expand Up @@ -190,7 +192,7 @@ private async Task DisposeObserverAsync()
private async Task OnActivityExecutionLogUpdated(ActivityExecutionLogUpdatedMessage message)
{
if (_designer == null) return;

foreach (var stats in message.Stats)
{
var activityNodeId = stats.ActivityNodeId;
Expand Down Expand Up @@ -228,6 +230,7 @@ private void StopElapsedTimer()

private async Task HandleActivitySelectedAsync(JsonObject activity)
{
await StopRefreshActivityStatePeriodically();
await InvokeAsync(async () =>
{
var activityNodeId = activity.GetNodeId();
Expand All @@ -236,8 +239,19 @@ await InvokeAsync(async () =>
SelectedActivityExecutions = await GetActivityExecutionRecordsAsync(activityNodeId);
StateHasChanged();
_activityDetailsTab?.Refresh();
_activityExecutionsTab?.Refresh();

if (_activityExecutionsTab != null)
await _activityExecutionsTab.RefreshAsync();
});

if (SelectedActivityExecutions.Any())
{
var lastRecord = SelectedActivityExecutions.Last();
LastActivityExecution = await InvokeWithBlazorServiceContext(() => ActivityExecutionService.GetAsync(lastRecord.Id));

if (LastActivityExecution != null)
RefreshActivityStatePeriodically(LastActivityExecution.Id);
}
}

private async Task<ICollection<ActivityExecutionRecordSummary>> GetActivityExecutionRecordsAsync(string activityNodeId)
Expand All @@ -264,6 +278,39 @@ private async Task UpdatePropertiesPaneHeightAsync()
_propertiesPaneHeight = (int)visibleHeight - 50;
}

private async Task RefreshSelectedItemAsync(string id)
{
var record = await InvokeWithBlazorServiceContext(() => ActivityExecutionService.GetAsync(id));
LastActivityExecution = record;
await InvokeAsync(() =>
{
StateHasChanged();
_activityDetailsTab?.Refresh();
});
}

private void RefreshActivityStatePeriodically(string id)
{
async void Callback(object? _)
{
await RefreshSelectedItemAsync(id);

if (LastActivityExecution == null || LastActivityExecution.IsFused())
await StopRefreshActivityStatePeriodically();
}

_refreshTimer = new Timer(Callback, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
}

private async Task StopRefreshActivityStatePeriodically()
{
if (_refreshTimer != null)
{
await _refreshTimer.DisposeAsync();
_refreshTimer = null;
}
}

private static ActivityStats Map(ActivityExecutionStats source)
{
return new ActivityStats
Expand Down Expand Up @@ -322,5 +369,8 @@ async ValueTask IAsyncDisposable.DisposeAsync()
{
StopElapsedTimer();
await DisposeObserverAsync();

if (_refreshTimer != null)
await _refreshTimer.DisposeAsync();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Elsa.Api.Client.Resources.ActivityExecutions.Models;

namespace Elsa.Studio.Workflows.Extensions;

/// <summary>
/// Provides extension methods for <see cref="ActivityExecutionRecord"/>.
/// </summary>
public static class ActivityExecutionRecordExtensions
{
/// <summary>
/// Determines if the specified activity execution record is fused.
/// </summary>
public static bool IsFused(this ActivityExecutionRecord record)
{
// TODO: with blueberry, consider introducing a new property to ActivityExecutionRecord to indicate whether the record has all details or not.
return record.ActivityState != null || record.Outputs != null || record.Exception != null || record.Payload != null || record.Properties.Count > 0;
}
}