Skip to content

Commit 20e6569

Browse files
RalfvandenBurgCCRalfvandenBurgsfmskywalker
authored
Add studio labels (#712)
* Initial * Fix selecting labels * removed identity * Solved warnings * Renamed secrets to labels in labels module * Duplicate code from merge? * Reformatted document * Added PoCo to studio project * Rename IWorkflowContextsProvider.cs to IWorkflowDefinitionLabelsProvider.cs * Revise interface documentation for IWorkflowDefinitionLabelsProvider Updated summary and method documentation for clarity. --------- Co-authored-by: Ralf <Ralf@careconnections.nl> Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
1 parent 5765147 commit 20e6569

31 files changed

Lines changed: 1202 additions & 0 deletions

Elsa.Studio.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Studio.Translations",
8484
EndProject
8585
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Studio.Host.HostedWasm", "src\hosts\Elsa.Studio.Host.HostedWasm\Elsa.Studio.Host.HostedWasm.csproj", "{25BA3052-4F17-4D24-9AE9-01FBD75E8804}"
8686
EndProject
87+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Studio.Labels", "src\modules\Elsa.Studio.Labels\Elsa.Studio.Labels.csproj", "{F6F4CD65-8E0C-5401-A668-108C6C0E8CD1}"
88+
EndProject
8789
Global
8890
GlobalSection(SolutionConfigurationPlatforms) = preSolution
8991
Debug|Any CPU = Debug|Any CPU
@@ -198,6 +200,10 @@ Global
198200
{25BA3052-4F17-4D24-9AE9-01FBD75E8804}.Debug|Any CPU.Build.0 = Debug|Any CPU
199201
{25BA3052-4F17-4D24-9AE9-01FBD75E8804}.Release|Any CPU.ActiveCfg = Release|Any CPU
200202
{25BA3052-4F17-4D24-9AE9-01FBD75E8804}.Release|Any CPU.Build.0 = Release|Any CPU
203+
{F6F4CD65-8E0C-5401-A668-108C6C0E8CD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
204+
{F6F4CD65-8E0C-5401-A668-108C6C0E8CD1}.Debug|Any CPU.Build.0 = Debug|Any CPU
205+
{F6F4CD65-8E0C-5401-A668-108C6C0E8CD1}.Release|Any CPU.ActiveCfg = Release|Any CPU
206+
{F6F4CD65-8E0C-5401-A668-108C6C0E8CD1}.Release|Any CPU.Build.0 = Release|Any CPU
201207
EndGlobalSection
202208
GlobalSection(SolutionProperties) = preSolution
203209
HideSolutionNode = FALSE
@@ -234,6 +240,7 @@ Global
234240
{DE57FD2C-3874-486A-89B1-D982726A1189} = {D66B9A40-8608-46F3-9868-625C50EACE43}
235241
{76C60D97-FA22-4023-BDB3-6BC47D097E40} = {C5288F1B-F4E5-423C-AEE8-049996613668}
236242
{25BA3052-4F17-4D24-9AE9-01FBD75E8804} = {2AA1AEE9-017E-4F8B-B5FC-2BEA37E83514}
243+
{F6F4CD65-8E0C-5401-A668-108C6C0E8CD1} = {D66B9A40-8608-46F3-9868-625C50EACE43}
237244
EndGlobalSection
238245
GlobalSection(ExtensibilityGlobals) = postSolution
239246
SolutionGuid = {5B8719CC-CF87-45E1-BE1A-13842F951B28}

src/hosts/Elsa.Studio.Host.Server/Elsa.Studio.Host.Server.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<ProjectReference Include="..\..\framework\Elsa.Studio.Core.BlazorServer\Elsa.Studio.Core.BlazorServer.csproj" />
1616
<ProjectReference Include="..\..\framework\Elsa.Studio.Translations\Elsa.Studio.Translations.csproj" />
1717
<ProjectReference Include="..\..\modules\Elsa.Studio.Dashboard\Elsa.Studio.Dashboard.csproj" />
18+
<ProjectReference Include="..\..\modules\Elsa.Studio.Labels\Elsa.Studio.Labels.csproj" />
1819
<ProjectReference Include="..\..\modules\Elsa.Studio.Localization.BlazorServer\Elsa.Studio.Localization.BlazorServer.csproj" />
1920
<ProjectReference Include="..\..\modules\Elsa.Studio.Login.BlazorServer\Elsa.Studio.Login.BlazorServer.csproj" />
2021
<ProjectReference Include="..\..\modules\Elsa.Studio.Login\Elsa.Studio.Login.csproj" />
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using Elsa.Api.Client.Shared.Models;
2+
using Elsa.Studio.Labels.Models;
3+
using Refit;
4+
5+
namespace Elsa.Studio.Labels.Client;
6+
7+
/// Represents a client API for managing labels.
8+
public interface ILabelsApi
9+
{
10+
/// Lists all labels.
11+
[Get("/labels")]
12+
Task<ListResponse<Label>> ListAsync(CancellationToken cancellationToken = default);
13+
14+
/// Gets a label by ID.
15+
/// <param name="id">The ID of the label to retrieve.</param>
16+
/// <param name="cancellationToken">A token to cancel the operation.</param>
17+
/// <returns>The label with the specified ID.</returns>
18+
[Get("/labels/{id}")]
19+
Task<Label> GetAsync(string id, CancellationToken cancellationToken = default);
20+
21+
/// Deletes a label by ID.
22+
/// <param name="id">The ID of the label to delete.</param>
23+
/// <param name="cancellationToken">A token to cancel the operation.</param>
24+
[Delete("/labels/{id}")]
25+
Task DeleteAsync(string id, CancellationToken cancellationToken = default);
26+
27+
/// Updates a label by ID.
28+
/// <param name="id">The ID of the label to update.</param>
29+
/// <param name="model">The updated label data.</param>
30+
/// <param name="cancellationToken">A token to cancel the operation.</param>
31+
[Post("/labels/{id}")]
32+
Task UpdateAsync(string id, LabelInputModel model, CancellationToken cancellationToken = default);
33+
34+
/// Creates a new label.
35+
/// <param name="inputModel">The data for the new label.</param>
36+
/// <param name="cancellationToken">A token to cancel the operation.</param>
37+
/// <returns>The created label.</returns>
38+
[Post("/labels")]
39+
Task<Label> CreateAsync(LabelInputModel? inputModel, CancellationToken cancellationToken = default);
40+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using Elsa.Studio.Labels.Models;
2+
using Refit;
3+
4+
namespace Elsa.Studio.Labels.Client;
5+
6+
/// <summary>
7+
/// Defines the API for managing workflow definition labels.
8+
/// </summary>
9+
public interface IWorkflowDefinitionLabelsApi
10+
{
11+
/// <summary>
12+
/// Retrieves the list of labels associated with a specific workflow definition.
13+
/// </summary>
14+
/// <param name="id">The unique identifier of the workflow definition.</param>
15+
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
16+
/// <returns>A task that represents the asynchronous operation. The task result contains the response with the list of labels.</returns>
17+
[Get("/workflow-definitions/{id}/labels")]
18+
Task<WorkflowDefinitionLabelsListResponse> ListAsync(string id, CancellationToken cancellationToken = default);
19+
20+
/// <summary>
21+
/// Updates the labels associated with a specific workflow definition.
22+
/// </summary>
23+
/// <param name="id">The unique identifier of the workflow definition.</param>
24+
/// <param name="request">The request object containing the updated labels.</param>
25+
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
26+
/// <returns>A task that represents the asynchronous operation. The task result contains the response after updating the labels.</returns>
27+
[Post("/workflow-definitions/{id}/labels")]
28+
Task<WorkflowDefinitionLabelsUpdateResponse> UpdateAsync(string id, [Body] WorkflowDefinitionLabelsUpdateRequest request, CancellationToken cancellationToken = default);
29+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
using Elsa.Studio.Labels.Models;
3+
4+
namespace Elsa.Studio.Labels.Comparers;
5+
6+
/// <summary>
7+
/// Compares two <see cref="Label"/> objects for equality based on their LabelId.
8+
/// </summary>
9+
public class LabelComparer : IEqualityComparer<Label>
10+
{
11+
/// <summary>
12+
/// Determines whether the specified <see cref="Label"/> objects are equal.
13+
/// </summary>
14+
/// <param name="x">The first <see cref="Label"/> to compare.</param>
15+
/// <param name="y">The second <see cref="Label"/> to compare.</param>
16+
/// <returns><c>true</c> if the specified <see cref="Label"/> objects are equal; otherwise, <c>false</c>.</returns>
17+
public bool Equals(Label? x, Label? y)
18+
{
19+
if (x is null && y is null)
20+
{
21+
return true;
22+
}
23+
24+
return x?.Id == y?.Id;
25+
}
26+
27+
/// <summary>
28+
/// Returns a hash code for the specified <see cref="Label"/>.
29+
/// </summary>
30+
/// <param name="obj">The <see cref="Label"/> for which a hash code is to be returned.</param>
31+
/// <returns>A hash code for the specified <see cref="Label"/>.</returns>
32+
public int GetHashCode([DisallowNull] Label obj)
33+
{
34+
return obj.Id.GetHashCode();
35+
}
36+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
@inherits StudioComponentBase
2+
@using Elsa.Studio.Workflows.Services
3+
@using MudBlazor.Utilities
4+
@inject ILocalizer Localizer
5+
6+
<MudText Typo="Typo.overline">@Localizer["Labels"]</MudText>
7+
<MudPaper>
8+
<MudChipSet T="string" AllClosable OnClose="@( chip => OnCloseAsync(chip))">
9+
@foreach (var value in Labels)
10+
{
11+
<MudChip Value="@value.Id" Text="@value.Name" />
12+
}
13+
</MudChipSet>
14+
15+
<div class="d-flex flex-column align-center">
16+
<MudButton StartIcon="@Icons.Material.Filled.Add" OnClick="AddLabelAsync">Add Label</MudButton>
17+
</div>
18+
</MudPaper>
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
using Elsa.Api.Client.Resources.WorkflowDefinitions.Models;
2+
using Elsa.Api.Client.Resources.WorkflowExecutionContexts.Models;
3+
using Elsa.Studio.Labels.Client;
4+
using Elsa.Studio.Labels.Contracts;
5+
using Elsa.Studio.Labels.Models;
6+
using Elsa.Studio.Labels.UI.Components;
7+
using Elsa.Studio.Workflows.Components.WorkflowDefinitionEditor.Components.WorkflowProperties.Tabs.InputOutput.Components.Outputs;
8+
using Microsoft.AspNetCore.Components;
9+
using MudBlazor;
10+
using MudBlazor.Utilities;
11+
using MudExtensions;
12+
13+
namespace Elsa.Studio.Labels.Components;
14+
15+
/// <summary>
16+
/// A component that renders the workflow context editor.
17+
/// </summary>
18+
public partial class WorkflowDefinitionLabelsEditor
19+
{
20+
/// <summary>
21+
/// Gets or sets the workflow definition.
22+
/// </summary>
23+
[Parameter]
24+
public WorkflowDefinition WorkflowDefinition { get; set; } = default!;
25+
26+
/// <summary>
27+
/// Gets or sets the callback that is invoked when the workflow definition is updated.
28+
/// </summary>
29+
[Parameter]
30+
public EventCallback WorkflowDefinitionUpdated { get; set; }
31+
32+
[Inject] private IDialogService DialogService { get; set; } = default!;
33+
[Inject] private ISnackbar Snackbar { get; set; } = default!;
34+
[Inject] private IWorkflowDefinitionLabelsProvider workflowDefinitionLabelsProvider { get; set; } = default!;
35+
36+
private ICollection<WorkflowDefinitionLabelDescriptor> Labels { get; set; } = new List<WorkflowDefinitionLabelDescriptor>();
37+
38+
/// <inheritdoc />
39+
protected override async Task OnInitializedAsync()
40+
{
41+
Labels = (await workflowDefinitionLabelsProvider.ListAsync(WorkflowDefinition.Id)).ToList();
42+
}
43+
44+
/// <inheritdoc />
45+
protected override void OnParametersSet()
46+
{
47+
}
48+
49+
private async Task AddLabelAsync()
50+
{
51+
var parameters = new DialogParameters<SelectLabelDialog>
52+
{
53+
[nameof(SelectLabelDialog.SelectedLabels)] = Labels.Select(ToLabel).ToHashSet(),
54+
};
55+
56+
var options = new DialogOptions
57+
{
58+
CloseOnEscapeKey = true,
59+
Position = DialogPosition.Center,
60+
CloseButton = true,
61+
FullWidth = true,
62+
MaxWidth = MaxWidth.Small
63+
};
64+
65+
var dialogInstance = await DialogService.ShowAsync<SelectLabelDialog>("Selecteer Label", parameters, options);
66+
var dialogResult = await dialogInstance.Result;
67+
if (dialogResult is { Canceled: false, Data: IEnumerable<Label> selectedLabels })
68+
{
69+
try
70+
{
71+
Labels = (await workflowDefinitionLabelsProvider.UpdateAsync(WorkflowDefinition.Id, selectedLabels.Select(it => it.Id))).ToList();
72+
}
73+
catch (Exception e)
74+
{
75+
Snackbar.Add(e.Message, Severity.Error);
76+
}
77+
}
78+
}
79+
80+
private Label ToLabel(WorkflowDefinitionLabelDescriptor descriptor) => new Label
81+
{
82+
Id = descriptor.Id,
83+
Name = descriptor.Name,
84+
Color = descriptor.Color,
85+
NormalizedName = descriptor.Name?.ToUpperInvariant() ?? string.Empty,
86+
};
87+
88+
private async Task OnCloseAsync(MudBlazor.MudChip<string> chip)
89+
{
90+
var labelToRemove = Labels.FirstOrDefault(x => x.Id == chip.Value);
91+
if (labelToRemove != null)
92+
{
93+
Labels.Remove(labelToRemove);
94+
Labels = (await workflowDefinitionLabelsProvider.UpdateAsync(WorkflowDefinition.Id, Labels.Select(it => it.Id))).ToList();
95+
}
96+
}
97+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using Elsa.Studio.Labels.Models;
2+
3+
namespace Elsa.Studio.Labels.Contracts;
4+
5+
/// <summary>
6+
/// Provides services to list and update workflow definition labels.
7+
/// </summary>
8+
public interface IWorkflowDefinitionLabelsProvider
9+
{
10+
/// <summary>
11+
/// Returns a list of labels associated with the specified workflow definition ID.
12+
/// </summary>
13+
/// <param name="workflowDefinitionId">The ID of the workflow definition.</param>
14+
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
15+
/// <returns>A list of workflow definition label descriptors.</returns>
16+
Task<IEnumerable<WorkflowDefinitionLabelDescriptor>> ListAsync(string workflowDefinitionId, CancellationToken cancellationToken = default);
17+
18+
/// <summary>
19+
/// Updates the labels associated with a workflow definition.
20+
/// </summary>
21+
/// <param name="workflowDefinitionId">The ID of the workflow definition.</param>
22+
/// <param name="selectedLabelsIds">The IDs of the selected labels to associate with the workflow definition.</param>
23+
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
24+
/// <returns>A list of updated workflow definition label descriptors.</returns>
25+
Task<IEnumerable<WorkflowDefinitionLabelDescriptor>> UpdateAsync(string workflowDefinitionId, IEnumerable<string> selectedLabelsIds, CancellationToken cancellationToken = default);
26+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Razor">
2+
3+
<PropertyGroup>
4+
<Description>Adds labels management to the studio.</Description>
5+
<PackageTags>elsa studio module</PackageTags>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<SupportedPlatform Include="browser" />
10+
</ItemGroup>
11+
12+
<ItemGroup>
13+
<ProjectReference Include="..\..\framework\Elsa.Studio.Shared\Elsa.Studio.Shared.csproj" />
14+
<ProjectReference Include="..\Elsa.Studio.Workflows\Elsa.Studio.Workflows.csproj" />
15+
</ItemGroup>
16+
17+
</Project>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=extensions/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

0 commit comments

Comments
 (0)