Skip to content

Commit eafa6ef

Browse files
committed
fix(Dashboard): improved graph legend display
Signed-off-by: Jean-Baptiste Bianchi <[email protected]>
1 parent 5afc5dd commit eafa6ef

File tree

8 files changed

+91
-152
lines changed

8 files changed

+91
-152
lines changed

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

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,19 @@
3636
</svg>
3737
<DagreGraph @ref="Store.DagreGraph" OnMouseUp="OnMouseUp" Options="options" Graph="graph">
3838
<ExtraControls>
39-
<button class="btn" type="button" title="legend" @onclick="async (_) => await Store.ShowLegendAsync()">
39+
<button class="btn" type="button" title="legend" @onclick="Store.ToggleLegendAsync">
4040
<svg>
4141
<use href="#legend" />
4242
</svg>
4343
</button>
4444
</ExtraControls>
4545
</DagreGraph>
46-
}
4746

48-
<Modal @ref="Store.LegendModal" />
47+
@if (isLegendVisible)
48+
{
49+
<WorkflowDiagramLegend />
50+
}
51+
}
4952

5053
@code {
5154
[Parameter] public EventCallback<GraphEventArgs<MouseEventArgs>> OnMouseUp { get; set; }
@@ -58,6 +61,7 @@
5861

5962
IGraphViewModel? graph;
6063
IDagreGraphOptions? options = null;
64+
bool isLegendVisible = false;
6165
bool isDirty = true; // moving isDirty to the State/Store seems to have unwanted behavior, the `ShouldRender` method doesn't seem to behave properly.
6266
6367
/// <inheritdoc/>
@@ -67,18 +71,25 @@
6771
Store.WorkflowDefinition.Subscribe(value => OnStateChanged(_ => workflowDefinition = value), token: CancellationTokenSource.Token);
6872
Store.WorkflowInstances.Subscribe(value => OnStateChanged(_ => workflowInstances = value), token: CancellationTokenSource.Token);
6973
Store.Orientation.Subscribe(value => OnStateChanged(_ => orientation = value), token: CancellationTokenSource.Token);
70-
Store.Graph.SubscribeAsync(async value =>
74+
Store.IsLegendVisible.Subscribe(value => OnStateChanged(_ =>
7175
{
72-
this.isDirty = true;
73-
OnStateChanged(_ => graph = value);
74-
if (this.Store.DagreGraph != null) await this.Store.DagreGraph.RefreshAsync();
75-
}, cancellationToken: CancellationTokenSource.Token);
76-
Store.Options.SubscribeAsync(async value =>
76+
isLegendVisible = value;
77+
isDirty = true;
78+
}), token: CancellationTokenSource.Token);
79+
Observable.CombineLatest(
80+
Store.Graph.Where(g => g != null),
81+
Store.Options.Where(o => o != null),
82+
(graph, options) => (graph, options)
83+
).Subscribe((values) =>
7784
{
78-
this.isDirty = true;
79-
OnStateChanged(_ => options = value);
80-
if (this.Store.DagreGraph != null) await this.Store.DagreGraph.RefreshAsync();
81-
}, cancellationToken: CancellationTokenSource.Token);
85+
var (newGraph, newOptions) = values;
86+
OnStateChanged(_ =>
87+
{
88+
graph = newGraph;
89+
options = newOptions;
90+
isDirty = true;
91+
});
92+
}, token: CancellationTokenSource.Token);
8293
}
8394

8495
protected override void OnParametersSet()

src/dashboard/Synapse.Dashboard/Components/WorkflowDiagram/WorkflowDiagramLegend.razor

Lines changed: 32 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -16,124 +16,40 @@
1616

1717
@namespace Synapse.Dashboard
1818

19-
<div class="d-flex align-items-center justify-content-center">
20-
<svg style="height: @(height * 14)">
21-
<g class="node call-task-node legend" transform="translate(0, @(height * 0))">
22-
<rect class="node-rectangle" x="0" y="0" width="@width" height="@height"></rect>
23-
<foreignObject x="0" y="0" width="@width" height="50">
24-
<div>
25-
CALL
26-
</div>
27-
</foreignObject>
28-
</g>
29-
<g class="node do-task-node legend" transform="translate(0, @(height * 1))">
30-
<rect class="node-rectangle" x="0" y="0" width="@width" height="@height"></rect>
31-
<foreignObject x="0" y="0" width="@width" height="50">
32-
<div>
33-
DO
34-
</div>
35-
</foreignObject>
36-
</g>
37-
<g class="node fork-task-node legend" transform="translate(0, @(height * 2))">
38-
<rect class="node-rectangle" x="0" y="0" width="@width" height="@height"></rect>
39-
<foreignObject x="0" y="0" width="@width" height="50">
40-
<div>
41-
FORK
42-
</div>
43-
</foreignObject>
44-
</g>
45-
<g class="node for-task-node legend" transform="translate(0, @(height * 3))">
46-
<rect class="node-rectangle" x="0" y="0" width="@width" height="@height"></rect>
47-
<foreignObject x="0" y="0" width="@width" height="50">
48-
<div>
49-
FOR
50-
</div>
51-
</foreignObject>
52-
</g>
53-
<g class="node listen-task-node legend" transform="translate(0, @(height * 4))">
54-
<rect class="node-rectangle" x="0" y="0" width="@width" height="@height"></rect>
55-
<foreignObject x="0" y="0" width="@width" height="50">
56-
<div>
57-
LISTEN
58-
</div>
59-
</foreignObject>
60-
</g>
61-
<g class="node run-task-node legend" transform="translate(0, @(height * 5))">
62-
<rect class="node-rectangle" x="0" y="0" width="@width" height="@height"></rect>
63-
<foreignObject x="0" y="0" width="@width" height="50">
64-
<div>
65-
RUN
66-
</div>
67-
</foreignObject>
68-
</g>
69-
<g class="node set-task-node legend" transform="translate(0, @(height * 6))">
70-
<rect class="node-rectangle" x="0" y="0" width="@width" height="@height"></rect>
71-
<foreignObject x="0" y="0" width="@width" height="50">
72-
<div>
73-
SET
74-
</div>
75-
</foreignObject>
76-
</g>
77-
<g class="node switch-task-node legend" transform="translate(0, @(height * 7))">
78-
<rect class="node-rectangle" x="0" y="0" width="@width" height="@height"></rect>
79-
<foreignObject x="0" y="0" width="@width" height="50">
80-
<div>
81-
SWITCH
82-
</div>
83-
</foreignObject>
84-
</g>
85-
<g class="node try-catch-task-node legend" transform="translate(0, @(height * 8))">
86-
<rect class="node-rectangle" x="0" y="0" width="@width" height="@height"></rect>
87-
<foreignObject x="0" y="0" width="@width" height="50">
88-
<div>
89-
TRY..CATCH
90-
</div>
91-
</foreignObject>
92-
</g>
93-
<g class="node try-task-node legend" transform="translate(0, @(height * 9))">
94-
<rect class="node-rectangle" x="0" y="0" width="@width" height="@height"></rect>
95-
<foreignObject x="0" y="0" width="@width" height="50">
96-
<div>
97-
TRY
98-
</div>
99-
</foreignObject>
100-
</g>
101-
<g class="node emit-task-node legend" transform="translate(0, @(height * 10))">
102-
<rect class="node-rectangle" x="0" y="0" width="@width" height="@height"></rect>
103-
<foreignObject x="0" y="0" width="@width" height="50">
104-
<div>
105-
EMIT
106-
</div>
107-
</foreignObject>
108-
</g>
109-
<g class="node wait-task-node legend" transform="translate(0, @(height * 11))">
110-
<rect class="node-rectangle" x="0" y="0" width="@width" height="@height"></rect>
111-
<foreignObject x="0" y="0" width="@width" height="50">
112-
<div>
113-
WAIT
114-
</div>
115-
</foreignObject>
116-
</g>
117-
<g class="node catch-task-node legend" transform="translate(0, @(height * 12))">
118-
<rect class="node-rectangle" x="0" y="0" width="@width" height="@height"></rect>
119-
<foreignObject x="0" y="0" width="@width" height="50">
120-
<div>
121-
CATCH
122-
</div>
123-
</foreignObject>
124-
</g>
125-
<g class="node raise-task-node legend" transform="translate(0, @(height * 13))">
126-
<rect class="node-rectangle" x="0" y="0" width="@width" height="@height"></rect>
127-
<foreignObject x="0" y="0" width="@width" height="50">
128-
<div>
129-
RAISE
130-
</div>
131-
</foreignObject>
132-
</g>
19+
<div class="d-flex align-items-center justify-content-center position-absolute" style="bottom: @(height)px; left: @(height)px;">
20+
<svg style="height: @(height * nodeTypes.Count())px">
21+
@for(int i = 0, c = nodeTypes.Count(); i<c; i++)
22+
{
23+
var nodeType = nodeTypes.ElementAt(i);
24+
<g class="node @(nodeType)-task-node legend" transform="translate(0, @(height * i))">
25+
<rect class="node-rectangle" x="0" y="0" width="@width" height="@height"></rect>
26+
<foreignObject x="0" y="0" width="@width" height="@height">
27+
<div>
28+
@nodeType.ToUpper().Replace("-", "..")
29+
</div>
30+
</foreignObject>
31+
</g>
32+
}
13333
</svg>
13434
</div>
13535

13636
@code {
137-
int width = 300;
138-
int height = 50;
37+
int width = 100;
38+
int height = 25;
39+
IEnumerable<string> nodeTypes = [
40+
"call",
41+
"do",
42+
"fork",
43+
"for",
44+
"listen",
45+
"run",
46+
"set",
47+
"switch",
48+
"try-catch",
49+
"try",
50+
"emit",
51+
"wait",
52+
"catch",
53+
"raise"
54+
];
13955
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,9 @@ public record WorkflowDiagramState
3535
/// Gets/sets the <see cref="WorkflowInstance"/>s to get the activity counts from
3636
/// </summary>
3737
public EquatableList<WorkflowInstance> WorkflowInstances { get; set; } = [];
38+
39+
/// <summary>
40+
/// Gets/sets a boolean indicating if the legend is visible
41+
/// </summary>
42+
public bool IsLegendVisible { get; set; } = false;
3843
}

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

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ IWorkflowGraphBuilder workflowGraphBuilder
6161
/// </summary>
6262
public IObservable<EquatableList<WorkflowInstance>> WorkflowInstances => this.Select(state => state.WorkflowInstances).DistinctUntilChanged();
6363

64+
/// <summary>
65+
/// Gets an <see cref="IObservable{T}"/> used to observe <see cref="WorkflowDiagramState.IsLegendVisible"/> changes
66+
/// </summary>
67+
public IObservable<bool> IsLegendVisible => this.Select(state => state.IsLegendVisible).DistinctUntilChanged();
68+
6469
/// <summary>
6570
/// Gets an <see cref="IObservable{T}"/> used to observe <see cref="DagreGraphOptions"/> changes
6671
/// </summary>
@@ -115,7 +120,8 @@ IWorkflowGraphBuilder workflowGraphBuilder
115120
((IWorkflowNodeViewModel)node).FaultedInstancesCount = faultedCount;
116121
}
117122
return graph;
118-
});
123+
})
124+
.DistinctUntilChanged();
119125
#endregion
120126

121127
#region Setters
@@ -158,15 +164,15 @@ public void SetWorkflowInstances(EquatableList<WorkflowInstance> workflowInstanc
158164

159165
#region Actions
160166
/// <summary>
161-
/// Shows the legend modal
167+
/// Toggles the legend visibily
162168
/// </summary>
163-
/// <returns></returns>
164-
public async Task ShowLegendAsync()
169+
public void ToggleLegendAsync()
165170
{
166-
if (this.LegendModal != null)
171+
var isLegendVisible = this.Get(state => state.IsLegendVisible);
172+
this.Reduce(state => state with
167173
{
168-
await this.LegendModal.ShowAsync<WorkflowDiagramLegend>(title: "Legend");
169-
}
174+
IsLegendVisible = !isLegendVisible
175+
});
170176
}
171177
#endregion
172178
}

src/dashboard/Synapse.Dashboard/Pages/Workflows/Details/Store.cs

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,6 @@ public async Task ToggleTextBasedEditorLanguageAsync(string _)
245245
/// <returns></returns>
246246
public async Task OnTextBasedEditorInitAsync()
247247
{
248-
await Task.Delay(1);
249248
await this.SetTextBasedEditorLanguageAsync();
250249
await this.SetTextEditorValueAsync();
251250
}
@@ -361,20 +360,6 @@ public async Task DeleteWorkflowInstanceAsync(WorkflowInstance workflowInstance)
361360
/// <inheritdoc/>
362361
public override async Task InitializeAsync()
363362
{
364-
this.WorkflowDefinition.Where(definition => definition != null).SubscribeAsync(async (definition) =>
365-
{
366-
await Task.Delay(1);
367-
var document = this.JsonSerializer.SerializeToText(definition.Clone());
368-
this.Reduce(state => state with
369-
{
370-
WorkflowDefinitionJson = document
371-
});
372-
await this.SetTextEditorValueAsync();
373-
if (this.MonacoEditorHelper.PreferredLanguage != PreferredLanguage.YAML)
374-
{
375-
await this.MonacoEditorHelper.ChangePreferredLanguageAsync(PreferredLanguage.YAML);
376-
}
377-
}, cancellationToken: this.CancellationTokenSource.Token);
378363
Observable.CombineLatest(
379364
this.Namespace.Where(ns => !string.IsNullOrWhiteSpace(ns)),
380365
this.ActiveResourceName.Where(name => !string.IsNullOrWhiteSpace(name)),
@@ -390,6 +375,20 @@ public override async Task InitializeAsync()
390375
this.RemoveLabelSelector(SynapseDefaults.Resources.Labels.WorkflowVersion);
391376
this.AddLabelSelector(new(SynapseDefaults.Resources.Labels.WorkflowVersion, LabelSelectionOperator.Equals, version!));
392377
});
378+
this.WorkflowDefinition.Where(definition => definition != null).SubscribeAsync(async (definition) =>
379+
{
380+
await Task.Delay(1);
381+
var document = this.JsonSerializer.SerializeToText(definition.Clone());
382+
this.Reduce(state => state with
383+
{
384+
WorkflowDefinitionJson = document
385+
});
386+
await this.SetTextEditorValueAsync();
387+
if (this.MonacoEditorHelper.PreferredLanguage != PreferredLanguage.YAML)
388+
{
389+
await this.MonacoEditorHelper.ChangePreferredLanguageAsync(PreferredLanguage.YAML);
390+
}
391+
}, cancellationToken: this.CancellationTokenSource.Token);
393392
await base.InitializeAsync();
394393
}
395394

src/dashboard/Synapse.Dashboard/Pages/Workflows/Details/View.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
</Button>
4141
</Content>
4242
</HorizontalCollapsible>
43-
<HorizontalCollapsible class="user-select-none">
43+
<HorizontalCollapsible class="user-select-none position-relative">
4444
<Label>Graph</Label>
4545
<Content>
4646
@if (workflowDefinition == null)

src/dashboard/Synapse.Dashboard/Services/WorkflowGraphBuilder.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public IGraphViewModel Build(WorkflowDefinition workflow)
5757
Stopwatch sw = Stopwatch.StartNew();
5858
var isEmpty = workflow.Do.Count < 1;
5959
var graph = new GraphViewModel();
60+
//graph.EnableProfiling = true;
6061
var startNode = this.BuildStartNode(!isEmpty);
6162
var endNode = this.BuildEndNode();
6263
graph.AddNode(startNode);

src/dashboard/Synapse.Dashboard/Synapse.Dashboard.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
</ItemGroup>
3939

4040
<ItemGroup>
41+
<ProjectReference Include="..\..\..\..\framework\src\Neuroglia.Blazor.Dagre\Neuroglia.Blazor.Dagre.csproj" />
4142
<ProjectReference Include="..\..\api\Synapse.Api.Client.Http\Synapse.Api.Client.Http.csproj" />
4243
<ProjectReference Include="..\Synapse.Dashboard.StateManagement\Synapse.Dashboard.StateManagement.csproj" />
4344
</ItemGroup>

0 commit comments

Comments
 (0)