diff --git a/src/modules/Elsa.Studio.Workflows/Components/WorkflowInstanceViewer/Components/ActivityDetailsTab.razor.cs b/src/modules/Elsa.Studio.Workflows/Components/WorkflowInstanceViewer/Components/ActivityDetailsTab.razor.cs
index 4f86a6de..97a0c29f 100644
--- a/src/modules/Elsa.Studio.Workflows/Components/WorkflowInstanceViewer/Components/ActivityDetailsTab.razor.cs
+++ b/src/modules/Elsa.Studio.Workflows/Components/WorkflowInstanceViewer/Components/ActivityDetailsTab.razor.cs
@@ -43,6 +43,7 @@ public record ActivityExecutionRecordTableRow(int Number, ActivityExecutionRecor
public void Refresh()
{
CreateDataModels();
+ StateHasChanged();
}
///
diff --git a/src/modules/Elsa.Studio.Workflows/Components/WorkflowInstanceViewer/Components/ActivityExecutionsTab.razor.cs b/src/modules/Elsa.Studio.Workflows/Components/WorkflowInstanceViewer/Components/ActivityExecutionsTab.razor.cs
index 33995f05..9531b34b 100644
--- a/src/modules/Elsa.Studio.Workflows/Components/WorkflowInstanceViewer/Components/ActivityExecutionsTab.razor.cs
+++ b/src/modules/Elsa.Studio.Workflows/Components/WorkflowInstanceViewer/Components/ActivityExecutionsTab.razor.cs
@@ -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.
/// The number of executions.
@@ -31,10 +32,12 @@ public record ActivityExecutionRecordTableRow(int Number, ActivityExecutionRecor
private IDictionary SelectedActivityState { get; set; } = new Dictionary();
private IDictionary SelectedOutcomesData { get; set; } = new Dictionary();
private IDictionary SelectedOutputData { get; set; } = new Dictionary();
+ private Timer? _refreshTimer;
/// Refreshes the component.
- public void Refresh()
+ public async Task RefreshAsync()
{
+ await StopRefreshTimerAsync();
SelectedItem = null;
SelectedActivityState = new Dictionary();
SelectedOutcomesData = new Dictionary();
@@ -70,11 +73,51 @@ private void CreateSelectedItemDataModels(ActivityExecutionRecord? record)
SelectedOutputData = outputData;
}
- private async Task OnActivityExecutionClicked(TableRowClickEventArgs 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 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();
}
}
\ No newline at end of file
diff --git a/src/modules/Elsa.Studio.Workflows/Components/WorkflowInstanceViewer/Components/WorkflowInstanceDesigner.razor.cs b/src/modules/Elsa.Studio.Workflows/Components/WorkflowInstanceViewer/Components/WorkflowInstanceDesigner.razor.cs
index c5fdeb93..955f4851 100644
--- a/src/modules/Elsa.Studio.Workflows/Components/WorkflowInstanceViewer/Components/WorkflowInstanceDesigner.razor.cs
+++ b/src/modules/Elsa.Studio.Workflows/Components/WorkflowInstanceViewer/Components/WorkflowInstanceDesigner.razor.cs
@@ -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;
@@ -72,6 +73,7 @@ public partial class WorkflowInstanceDesigner : IAsyncDisposable
private IWorkflowInstanceObserver? WorkflowInstanceObserver { get; set; } = default!;
private ICollection SelectedActivityExecutions { get; set; } = new List();
private ActivityExecutionRecord? LastActivityExecution { get; set; }
+ private Timer? _refreshTimer;
private RadzenSplitterPane ActivityPropertiesPane
{
@@ -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;
@@ -228,6 +230,7 @@ private void StopElapsedTimer()
private async Task HandleActivitySelectedAsync(JsonObject activity)
{
+ await StopRefreshActivityStatePeriodically();
await InvokeAsync(async () =>
{
var activityNodeId = activity.GetNodeId();
@@ -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> GetActivityExecutionRecordsAsync(string activityNodeId)
@@ -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
@@ -322,5 +369,8 @@ async ValueTask IAsyncDisposable.DisposeAsync()
{
StopElapsedTimer();
await DisposeObserverAsync();
+
+ if (_refreshTimer != null)
+ await _refreshTimer.DisposeAsync();
}
}
\ No newline at end of file
diff --git a/src/modules/Elsa.Studio.Workflows/Extensions/ActivityExecutionRecordExtensions.cs b/src/modules/Elsa.Studio.Workflows/Extensions/ActivityExecutionRecordExtensions.cs
new file mode 100644
index 00000000..79ee9d7f
--- /dev/null
+++ b/src/modules/Elsa.Studio.Workflows/Extensions/ActivityExecutionRecordExtensions.cs
@@ -0,0 +1,18 @@
+using Elsa.Api.Client.Resources.ActivityExecutions.Models;
+
+namespace Elsa.Studio.Workflows.Extensions;
+
+///
+/// Provides extension methods for .
+///
+public static class ActivityExecutionRecordExtensions
+{
+ ///
+ /// Determines if the specified activity execution record is fused.
+ ///
+ 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;
+ }
+}
\ No newline at end of file