Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<PackageVersion Include="Elsa.Api.Client" Version="3.8.0" />
</ItemGroup>
<ItemGroup>
<PackageVersion Include="Blazored.FluentValidation" Version="2.2.0" />
<PackageVersion Include="Blazilla" Version="2.3.0" />
<PackageVersion Include="Blazored.LocalStorage" Version="4.5.0" />
<PackageVersion Include="AngleSharp" Version="1.5.2" />
<PackageVersion Include="BlazorMonaco" Version="3.4.0" />
Expand Down
1 change: 1 addition & 0 deletions Elsa.Studio.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Blazilla/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Blazored/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ffffff/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Heroicons/@EntryIndexedValue">True</s:Boolean>
Expand Down
2 changes: 1 addition & 1 deletion src/framework/Elsa.Studio.Core/Elsa.Studio.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Blazored.FluentValidation"/>
<PackageReference Include="Blazilla"/>
<PackageReference Include="Blazored.LocalStorage"/>
<PackageReference Include="CodeBeam.MudBlazor.Extensions"/>
<PackageReference Include="FluentValidation"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,21 @@

<MudDialog>
<DialogContent>
<EditForm EditContext="_editContext" OnValidSubmit="OnValidSubmit">
<FluentValidationValidator @ref="_fluentValidationValidator" Validator="_validator" DisableAssemblyScanning="true"/>
<MudStack>
<MudTextField Label="@Localizer["Name"]" @bind-Value="_inputModel.Name" For="@(() => _inputModel.Name)" HelperText="@Localizer["The name of the new label."]" Variant="Variant.Outlined" />
<MudTextField Label="@Localizer["Description"]" @bind-Value="_inputModel.Description" For="@(() => _inputModel.Description)" HelperText="@Localizer["A brief description of the label."]" Lines="3" Variant="Variant.Outlined"/>
</MudStack>
</EditForm>
@* Blazilla's FluentValidator captures the Validator instance during OnInitialized and ignores later reassignment,
so the form must not render until _validator (assigned after an awaited API lookup) is available. *@
@if (_validator is not null)
{
<EditForm EditContext="_editContext" OnValidSubmit="OnValidSubmit">
<FluentValidator Validator="_validator"/>
<MudStack>
<MudTextField Label="@Localizer["Name"]" @bind-Value="_inputModel.Name" For="@(() => _inputModel.Name)" HelperText="@Localizer["The name of the new label."]" Variant="Variant.Outlined" />
<MudTextField Label="@Localizer["Description"]" @bind-Value="_inputModel.Description" For="@(() => _inputModel.Description)" HelperText="@Localizer["A brief description of the label."]" Lines="3" Variant="Variant.Outlined"/>
</MudStack>
</EditForm>
}
</DialogContent>
<DialogActions>
<MudButton OnClick="OnCancelClicked">@Localizer["Cancel"]</MudButton>
<MudButton Color="Color.Primary" OnClick="OnSubmitClicked">@Localizer["Ok"]</MudButton>
<MudButton Color="Color.Primary" OnClick="OnSubmitClicked" Disabled="@(_validator is null)">@Localizer["Ok"]</MudButton>
</DialogActions>
</MudDialog>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Blazored.FluentValidation;
using Blazilla;
using Elsa.Studio.Contracts;
using Elsa.Studio.Labels.Client;
using Elsa.Studio.Labels.Models;
Expand All @@ -14,8 +14,7 @@ public partial class CreateLabelDialog
{
private readonly LabelInputModel _inputModel = new() { Name = string.Empty };
private EditContext _editContext = null!;
private FluentValidationValidator _fluentValidationValidator = null!;
private LabelInputModelValidator _validator = null!;
private LabelInputModelValidator? _validator;

/// The default name of the agent to create.
[Parameter] public string LabelName { get; set; } = "";
Expand All @@ -40,7 +39,12 @@ private Task OnCancelClicked()

private async Task OnSubmitClicked()
{
if(!await _fluentValidationValidator.ValidateAsync())
// Guard against submitting while the validator is still loading: no FluentValidator is attached to
// _editContext yet, so ValidateAsync() would find no messages and return true, letting an empty label through.
if (_validator is null)
return;

if(!await _editContext.ValidateAsync())
return;

await OnValidSubmit();
Expand Down
2 changes: 1 addition & 1 deletion src/modules/Elsa.Studio.Labels/_Imports.razor
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@using Elsa.Studio.Components
@using Elsa.Studio.Localization
@using Blazored.FluentValidation
@using Blazilla
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Routing
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using Elsa.Api.Client.Resources.WorkflowDefinitions.Models;
using Elsa.Api.Client.Resources.WorkflowDefinitions.Requests;
using Elsa.Api.Client.Resources.WorkflowDefinitions.Responses;
using Elsa.Api.Client.Shared.Models;
using Elsa.Studio.Models;
using Elsa.Studio.Workflows.Domain.Contracts;
using Elsa.Studio.Workflows.Domain.Models;

namespace Elsa.Studio.Workflows.Tests.Support;

/// <summary>
/// An <see cref="IWorkflowDefinitionService"/> stub that lets a test control when an asynchronous name
/// uniqueness check completes and records how the create dialog invoked it. Every member other than
/// <see cref="GetIsNameUniqueAsync"/> and <see cref="CreateNewDefinitionAsync(string,string?,string?,Action{SaveWorkflowDefinitionRequest}?,CancellationToken)"/>
/// is unused by the tests that consume this stub and throws to flag an unexpected call.
/// </summary>
internal sealed class ControlledWorkflowDefinitionService : IWorkflowDefinitionService
{
private readonly Queue<PendingValidation> _pendingValidations = new();

public int CreateCallCount { get; private set; }
public string? CreatedName { get; private set; }

public PendingValidation EnqueueValidation()
{
var validation = new PendingValidation();
lock (_pendingValidations)
_pendingValidations.Enqueue(validation);
return validation;
}

public async Task<bool> GetIsNameUniqueAsync(string name, string? definitionId = null, CancellationToken cancellationToken = default)
{
PendingValidation validation;
lock (_pendingValidations)
validation = _pendingValidations.Dequeue();

validation.Name = name;
validation.Started.TrySetResult(true);
return await validation.Result.Task.WaitAsync(cancellationToken);
}

public Task<Result<WorkflowDefinition, ValidationErrors>> CreateNewDefinitionAsync(string name, string? description, string? rootActivityTemplateKey, Action<SaveWorkflowDefinitionRequest>? configureRequest = null, CancellationToken cancellationToken = default)
{
CreateCallCount++;
CreatedName = name;
return Task.FromResult(new Result<WorkflowDefinition, ValidationErrors>(new WorkflowDefinition
{
Name = name,
Description = description
}));
}

public sealed class PendingValidation
{
public string? Name { get; set; }
public TaskCompletionSource<bool> Started { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously);
public TaskCompletionSource<bool> Result { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously);
}

public Task<PagedListResponse<WorkflowDefinitionSummary>> ListAsync(ListWorkflowDefinitionsRequest request, VersionOptions? versionOptions = null, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<WorkflowDefinition?> FindByDefinitionIdAsync(string definitionId, VersionOptions? versionOptions = null, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<WorkflowDefinition?> FindByIdAsync(string id, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<IEnumerable<WorkflowDefinition>> FindManyByIdAsync(IEnumerable<string> ids, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<ActivityNode?> FindSubgraphAsync(string id, string? parentNodeId = null, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<GetPathSegmentsResponse?> GetPathSegmentsAsync(string id, string? childNodeId = null, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<bool> DeleteAsync(string definitionId, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<bool> DeleteVersionAsync(WorkflowDefinitionVersion workflowDefinitionVersion, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<SaveWorkflowDefinitionResponse> PublishAsync(string definitionId, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<Result<WorkflowDefinition, ValidationErrors>> RetractAsync(string definitionId, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<long> BulkDeleteAsync(IEnumerable<string> definitionIds, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<long> BulkDeleteVersionsAsync(IEnumerable<WorkflowDefinitionVersion> workflowDefinitionVersions, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<BulkPublishWorkflowDefinitionsResponse> BulkPublishAsync(IEnumerable<string> definitionIds, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<BulkRetractWorkflowDefinitionsResponse> BulkRetractAsync(IEnumerable<string> definitionIds, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<string> GenerateUniqueNameAsync(CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<Result<WorkflowDefinition, ValidationErrors>> CreateNewDefinitionAsync(string name, string? description = null, Action<SaveWorkflowDefinitionRequest>? configureRequest = null, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<FileDownload> ExportDefinitionAsync(string definitionId, VersionOptions? versionOptions = null, bool includeConsumingWorkflows = false, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<FileDownload> BulkExportDefinitionsAsync(IEnumerable<string> ids, bool includeConsumingWorkflows = false, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<UpdateConsumingWorkflowReferencesResponse> UpdateReferencesAsync(string definitionId, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<ExecuteWorkflowResult> ExecuteAsync(string definitionId, ExecuteWorkflowDefinitionRequest? request, CancellationToken cancellationToken = default) => throw new NotSupportedException();
}
Loading