Skip to content

Commit 3296e52

Browse files
committed
fix(Dashboard): nested tasks details
& improved rendering perf Signed-off-by: Jean-Baptiste Bianchi <[email protected]>
1 parent fa584a8 commit 3296e52

File tree

10 files changed

+252
-119
lines changed

10 files changed

+252
-119
lines changed

src/dashboard/Synapse.Dashboard/Components/DocumentDetails/DocumentDetails.razor

Lines changed: 57 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -18,58 +18,65 @@
1818
@using Synapse.Dashboard.Components.DocumentDetailsStateManagement
1919
@inherits StatefulComponent<DocumentDetails, DocumentDetailsStore, DocumentDetailsState>
2020

21-
<div class="d-flex justify-content-between cursor-pointer @ClassNames" @onclick="async (_) => await ToggleAsync()">
21+
<div class="d-flex justify-content-between cursor-pointer @ClassNames" @onclick="async (_) => await Store.ToggleAsync()">
2222
@if (LabelTemplate != null) {
2323
@LabelTemplate
2424
}
2525
else {
2626
<span class="label">@Label</span>
2727
}
28-
@if (collapse != null)
28+
@if (Store.Collapse != null)
2929
{
30-
<Icon Name="@(isOpen ? IconName.CaretUp : IconName.CaretDown)" />
30+
<Icon Name="@(isExpanded ? IconName.CaretUp : IconName.CaretDown)" />
3131
}
3232
</div>
33-
<Collapse @ref="collapse" OnShowing="Store.LoadReferencedDocumentAsync">
34-
@if (reference == null && Document == null)
35-
{
36-
<p>No document</p>
37-
}
38-
else if (!loaded)
39-
{
40-
<Spinner Class="me-3" Color="SpinnerColor.Primary" Size="SpinnerSize.Small" />
41-
}
42-
else
43-
{
44-
<div class="d-flex justify-content-end mb-2">
45-
<PreferredLanguageSelector PreferedLanguageChange="Store.ToggleTextBasedEditorLanguageAsync" />
46-
</div>
47-
<StandaloneCodeEditor @ref="Store.TextEditor"
48-
ConstructionOptions="Store.StandaloneEditorConstructionOptions"
49-
OnDidInit="Store.OnTextBasedEditorInitAsync"
50-
CssClass="h-300-px" />
51-
}
52-
@if (problemDetails != null)
53-
{
54-
<div class="problems">
55-
<Callout Type="CalloutType.Danger" Heading="@problemDetails.Title">
56-
@problemDetails.Detail
57-
</Callout>
58-
@if (problemDetails.Errors != null && problemDetails.Errors.Any())
59-
{
60-
foreach (KeyValuePair<string, string[]> errorContainer in problemDetails.Errors)
33+
<Collapse @ref="Store.Collapse" OnShowing="Store.LoadReferencedDocumentAsync">
34+
@if (isExpanded) // prevents early rendering when collapsed
35+
{
36+
@if (reference == null && Document == null)
37+
{
38+
<p>No document</p>
39+
}
40+
else if (!loaded)
41+
{
42+
<Spinner Class="me-3" Color="SpinnerColor.Primary" Size="SpinnerSize.Small" />
43+
}
44+
else
45+
{
46+
<div class="d-flex justify-content-end mb-2">
47+
<PreferredLanguageSelector PreferedLanguageChange="Store.ToggleTextBasedEditorLanguageAsync" />
48+
</div>
49+
<StandaloneCodeEditor @ref="Store.TextEditor"
50+
ConstructionOptions="Store.StandaloneEditorConstructionOptions"
51+
OnDidInit="Store.OnTextBasedEditorInitAsync"
52+
CssClass="h-300-px" />
53+
}
54+
@if (problemDetails != null)
55+
{
56+
<div class="problems">
57+
<Callout Type="CalloutType.Danger" Heading="@problemDetails.Title">
58+
@problemDetails.Detail
59+
</Callout>
60+
@if (problemDetails.Errors != null && problemDetails.Errors.Any())
6161
{
62-
<Callout Type="CalloutType.Danger" Heading="@errorContainer.Key">
63-
<ul>
64-
@foreach (string error in errorContainer.Value)
65-
{
66-
<li>@error</li>
67-
}
68-
</ul>
69-
</Callout>
62+
foreach (KeyValuePair<string, string[]> errorContainer in problemDetails.Errors)
63+
{
64+
<Callout Type="CalloutType.Danger" Heading="@errorContainer.Key">
65+
<ul>
66+
@foreach (string error in errorContainer.Value)
67+
{
68+
<li>@error</li>
69+
}
70+
</ul>
71+
</Callout>
72+
}
7073
}
71-
}
72-
</div>
74+
</div>
75+
}
76+
}
77+
else // mocks some content to enable opening animation
78+
{
79+
<div class="h-300-px"></div>
7380
}
7481
</Collapse>
7582
@code {
@@ -93,64 +100,48 @@
93100
/// </summary>
94101
[Parameter] public object? Document { get; set; }
95102

96-
/// <summary>
97-
/// The <see cref="Collapse" /> reference
98-
/// </summary>
99-
private Collapse collapse = default!;
100103
/// <summary>
101104
/// The state of the <see cref="Collapse" />
102105
/// </summary>
103-
private bool isOpen = false;
106+
bool isExpanded = false;
104107
/// <summary>
105108
/// The internal reference
106109
/// </summary>
107-
private string? reference;
110+
string? reference;
108111
/// <summary>
109112
/// The internal boolean indicating if the resource already loaded
110113
/// </summary>
111-
private bool loaded = false;
114+
bool loaded = false;
112115
/// <summary>
113116
/// The <see cref="ProblemDetails"/> that occurred when trying to save the resource, if any
114117
/// </summary>
115-
private ProblemDetails? problemDetails = null;
118+
ProblemDetails? problemDetails = null;
116119

117-
private object? _documentShadow;
120+
object? __documentShadow;
118121

119122
/// <inheritdoc/>
120123
protected override async Task OnInitializedAsync()
121124
{
122125
await base.OnInitializedAsync().ConfigureAwait(false);
123126
Store.Reference.Subscribe(value => OnStateChanged(_ => reference = value), token: CancellationTokenSource.Token);
124127
Store.Loaded.Subscribe(value => OnStateChanged(_ => loaded = value), token: CancellationTokenSource.Token);
128+
Store.IsExpanded.Subscribe(value => OnStateChanged(_ => isExpanded = value), token: CancellationTokenSource.Token);
125129
Store.ProblemDetails.Subscribe(value => OnStateChanged(c_mp => problemDetails = value), token: CancellationTokenSource.Token);
126130
}
127131

128132
/// <inheritdoc/>
129133
protected override async Task OnParametersSetAsync()
130134
{
131135
await base.OnParametersSetAsync();
132-
if (Document != _documentShadow)
136+
await this.Store.HideAsync();
137+
if (Document != __documentShadow)
133138
{
134139
Store.SetDocument(Document);
135-
_documentShadow = Document;
140+
__documentShadow = Document;
136141
}
137142
if (reference != Reference)
138143
{
139144
Store.SetReference(Reference);
140145
}
141-
if (collapse != null)
142-
{
143-
isOpen = false;
144-
await collapse.HideAsync();
145-
}
146-
}
147-
148-
async Task ToggleAsync()
149-
{
150-
if (collapse != null)
151-
{
152-
isOpen = !isOpen;
153-
await (isOpen ? collapse.ShowAsync() : collapse.HideAsync());
154-
}
155146
}
156147
}

src/dashboard/Synapse.Dashboard/Components/DocumentDetails/State.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ public record DocumentDetailsState
3939
/// </summary>
4040
public bool Loaded { get; set; } = false;
4141

42+
/// <summary>
43+
/// Gets/sets a boolean indicating if the logs panel is expanded
44+
/// </summary>
45+
public bool IsExpanded { get; set; } = false;
46+
4247
/// <summary>
4348
/// Gets/sets the <see cref="ProblemDetails"/> type that occurred when trying to save the resource, if any
4449
/// </summary>

src/dashboard/Synapse.Dashboard/Components/DocumentDetails/Store.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// limitations under the License.
1313

1414
using Synapse.Api.Client.Services;
15+
using Synapse.Dashboard.Components.WorkflowInstanceLogsStateManagement;
1516

1617
namespace Synapse.Dashboard.Components.DocumentDetailsStateManagement;
1718

@@ -70,6 +71,11 @@ IYamlSerializer yamlSerializer
7071
/// </summary>
7172
public StandaloneCodeEditor? TextEditor { get; set; }
7273

74+
/// <summary>
75+
/// Gets/sets the logs <see cref="Collapse"/> panel
76+
/// </summary>
77+
public Collapse? Collapse { get; set; }
78+
7379
#region Selectors
7480
/// <summary>
7581
/// Gets an <see cref="IObservable{T}"/> used to observe <see cref="DocumentDetailsState.Label"/> changes
@@ -91,6 +97,11 @@ IYamlSerializer yamlSerializer
9197
/// </summary>
9298
public IObservable<bool> Loaded => this.Select(state => state.Loaded).DistinctUntilChanged();
9399

100+
/// <summary>
101+
/// Gets an <see cref="IObservable{T}"/> used to observe <see cref="WorkflowInstanceLogsState.IsExpanded"/> changes
102+
/// </summary>
103+
public IObservable<bool> IsExpanded => this.Select(state => state.IsExpanded).DistinctUntilChanged();
104+
94105
/// <summary>
95106
/// Gets an <see cref="IObservable{T}"/> used to observe <see cref="DocumentDetailsState.ProblemType"/> changes
96107
/// </summary>
@@ -188,6 +199,37 @@ public void SetDocument(object? document)
188199
#endregion
189200

190201
#region Actions
202+
/// <summary>
203+
/// Toggles the <see cref="Collapse"/> panel
204+
/// </summary>
205+
public async Task ToggleAsync()
206+
{
207+
if (this.Collapse != null)
208+
{
209+
var isExpanded = !this.Get(state => state.IsExpanded);
210+
await (isExpanded ? this.Collapse.ShowAsync() : this.Collapse.HideAsync());
211+
this.Reduce(state => state with
212+
{
213+
IsExpanded = isExpanded
214+
});
215+
}
216+
}
217+
218+
/// <summary>
219+
/// Toggles the <see cref="Collapse"/> panel
220+
/// </summary>
221+
public async Task HideAsync()
222+
{
223+
if (this.Collapse != null)
224+
{
225+
await this.Collapse.HideAsync();
226+
this.Reduce(state => state with
227+
{
228+
IsExpanded = false
229+
});
230+
}
231+
}
232+
191233
/// <summary>
192234
/// Loads the referenced documents
193235
/// </summary>

src/dashboard/Synapse.Dashboard/Components/TaskInstanceDetails/TaskInstanceDetails.razor

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,32 @@
7171
<DocumentDetails Label='Output' Reference="@TaskInstance.OutputReference" />
7272
}
7373
</div>
74+
<h6 class="pt-3">Executed Tasks</h6>
75+
@if (SubTasks == null || SubTasks.Count() == 0)
76+
{
77+
@("-")
78+
}
79+
else
80+
{
81+
<table class="table table-hover">
82+
<thead>
83+
<tr>
84+
<th>Name</th>
85+
<th class="text-center">Status</th>
86+
<th class="text-center">Start Time</th>
87+
<th class="text-center">End Time</th>
88+
<th class="text-center">Duration</th>
89+
<th></th>
90+
</tr>
91+
</thead>
92+
<tbody>
93+
@foreach (var task in SubTasks)
94+
{
95+
<TaskInstanceRow TaskInstance="@task" Tasks="@Tasks"/>
96+
}
97+
</tbody>
98+
</table>
99+
}
74100
<h6 class="pt-3">Runs</h6>
75101
@if (TaskInstance.Runs == null || TaskInstance.Runs.Count == 0)
76102
{
@@ -137,4 +163,13 @@
137163

138164
@code {
139165
[Parameter] public TaskInstance? TaskInstance { get; set; }
166+
[Parameter] public IEnumerable<TaskInstance>? Tasks { get; set; }
167+
168+
IEnumerable<TaskInstance> SubTasks
169+
{
170+
get
171+
{
172+
return TaskInstance == null ? [] : (this.Tasks ?? []).Where(t => t.ParentId == TaskInstance.Id);
173+
}
174+
}
140175
}

src/dashboard/Synapse.Dashboard/Components/WorkflowDiagram/Templates/NodeActivityBadge.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@
9696
protected override void OnParametersSet()
9797
{
9898
double divider = this.Node.Shape == NodeShape.Circle || this.Node.Shape == NodeShape.Ellipse ? 1 : 2;
99-
double offset = radius * 4;
99+
double offset = radius * 2;
100100
double activeOffset = 0;
101101
double compensatedOffset = 0;
102102
double faultedOffset = 0;

src/dashboard/Synapse.Dashboard/Components/WorkflowDiagram/WorkflowDiagramStore.cs

Lines changed: 7 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -91,50 +91,28 @@ IWorkflowGraphBuilder workflowGraphBuilder
9191
(graph, instances) =>
9292
{
9393
var tasks = instances.SelectMany(instance => instance.Status?.Tasks ?? []);
94-
//var allNodes = ((IReadOnlyDictionary<string, IWorkflowNodeViewModel>)graph.AllNodes).Concat((IReadOnlyDictionary<string, IWorkflowNodeViewModel>)graph.AllClusters);
95-
foreach (var kvp in graph.AllNodes)
94+
var allNodes = graph.AllNodes.Values.Concat(graph.AllClusters.Values);
95+
foreach (var node in allNodes)
9696
{
9797
int activeCount, faultedCount;
98-
if (kvp.Key == "start-node")
98+
if (node.Id == "start-node")
9999
{
100100
activeCount = instances.Count(instance => instance.Status == null || instance.Status?.Phase == WorkflowInstanceStatusPhase.Pending || instance.Status?.Phase == WorkflowInstanceStatusPhase.Waiting);
101101
faultedCount = instances.Count(instance => (instance.Status?.Tasks == null || instance.Status.Tasks.Count == 0) && (instance.Status?.Phase == WorkflowInstanceStatusPhase.Cancelled || instance.Status?.Phase == WorkflowInstanceStatusPhase.Faulted));
102102
}
103-
else if (kvp.Key == "end-node")
103+
else if (node.Id == "end-node")
104104
{
105105
activeCount = instances.Count(instance => instance.Status?.Phase == WorkflowInstanceStatusPhase.Completed);
106106
faultedCount = instances.Count(instance => instance.Status?.Tasks?.Count > 0 && (instance.Status?.Phase == WorkflowInstanceStatusPhase.Cancelled || instance.Status?.Phase == WorkflowInstanceStatusPhase.Faulted));
107107
}
108108
else
109109
{
110-
var nodeTasks = tasks.Where(task => Regex.Replace(task.Reference.ToString(), @"/for/\d*", "") == kvp.Key);
110+
var nodeTasks = tasks.Where(task => Regex.Replace(task.Reference.ToString(), @"/for/\d*", "") == node.Id);
111111
activeCount = nodeTasks.Count(task => task.Status == TaskInstanceStatus.Running);
112112
faultedCount = nodeTasks.Count(task => task.Status == TaskInstanceStatus.Faulted || task.Status == TaskInstanceStatus.Cancelled);
113113
}
114-
((IWorkflowNodeViewModel)kvp.Value).OperativeInstancesCount = activeCount;
115-
((IWorkflowNodeViewModel)kvp.Value).FaultedInstancesCount = faultedCount;
116-
}
117-
foreach (var kvp in graph.AllClusters)
118-
{
119-
int activeCount, faultedCount;
120-
if (kvp.Key == "start-node")
121-
{
122-
activeCount = instances.Count(instance => instance.Status?.Phase == WorkflowInstanceStatusPhase.Pending || instance.Status?.Phase == WorkflowInstanceStatusPhase.Waiting);
123-
faultedCount = instances.Count(instance => (instance.Status?.Tasks == null || instance.Status.Tasks.Count == 0) && (instance.Status?.Phase == WorkflowInstanceStatusPhase.Cancelled || instance.Status?.Phase == WorkflowInstanceStatusPhase.Faulted));
124-
}
125-
else if (kvp.Key == "end-node")
126-
{
127-
activeCount = instances.Count(instance => instance.Status?.Phase == WorkflowInstanceStatusPhase.Completed);
128-
faultedCount = instances.Count(instance => instance.Status?.Tasks?.Count > 0 && (instance.Status?.Phase == WorkflowInstanceStatusPhase.Cancelled || instance.Status?.Phase == WorkflowInstanceStatusPhase.Faulted));
129-
}
130-
else
131-
{
132-
var nodeTasks = tasks.Where(task => Regex.Replace(task.Reference.ToString(), @"/for/\d*", "") == kvp.Key);
133-
activeCount = nodeTasks.Count(task => task.Status == TaskInstanceStatus.Running);
134-
faultedCount = nodeTasks.Count(task => task.Status == TaskInstanceStatus.Faulted || task.Status == TaskInstanceStatus.Cancelled);
135-
}
136-
((IWorkflowNodeViewModel)kvp.Value).OperativeInstancesCount = activeCount;
137-
((IWorkflowNodeViewModel)kvp.Value).FaultedInstancesCount = faultedCount;
114+
((IWorkflowNodeViewModel)node).OperativeInstancesCount = activeCount;
115+
((IWorkflowNodeViewModel)node).FaultedInstancesCount = faultedCount;
138116
}
139117
return graph;
140118
});

src/dashboard/Synapse.Dashboard/Components/WorkflowInstanceDetails/TaskInstanceRow.razor

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
<tr>
3939
<td colspan="999">
4040
<Collapse @ref="collapse">
41-
<TaskInstanceDetails TaskInstance="TaskInstance" />
41+
<TaskInstanceDetails TaskInstance="@TaskInstance" Tasks="@Tasks" />
4242
</Collapse>
4343
</td>
4444
</tr>
@@ -47,6 +47,7 @@
4747

4848
@code {
4949
[Parameter] public TaskInstance? TaskInstance { get; set; }
50+
[Parameter] public IEnumerable<TaskInstance>? Tasks { get; set; }
5051
bool isOpen = false;
5152
Collapse? collapse;
5253

0 commit comments

Comments
 (0)