Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/patch/3.2.x'
Browse files Browse the repository at this point in the history
  • Loading branch information
sfmskywalker committed Oct 17, 2024
2 parents 54efdd0 + 5fa3e33 commit f696d50
Show file tree
Hide file tree
Showing 34 changed files with 202 additions and 313 deletions.
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<PackageVersion Include="BlazorMonaco" Version="3.2.0" />
<PackageVersion Include="CodeBeam.MudBlazor.Extensions" Version="7.0.1" />
<PackageVersion Include="FluentValidation" Version="11.10.0" />
<PackageVersion Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageVersion Include="JetBrains.Annotations" Version="2024.2.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="all" />
<PackageVersion Include="Microsoft.AspNetCore.App" Version="2.2.8" />
<PackageVersion Include="MudBlazor" Version="7.10.0" />
Expand Down
28 changes: 28 additions & 0 deletions src/framework/Elsa.Studio.Core/Services/BlazorScopedProxyApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Reflection;
using Elsa.Studio.Contracts;

namespace Elsa.Studio.Services;

/// <summary>
/// Decorates an API client with a Blazor service accessor, ensuring that the service provider is available to the API client when calling DI-resolved delegating handlers.
/// </summary>
public class BlazorScopedProxyApi<T> : DispatchProxy
{
private T _decoratedApi = default!;
private IBlazorServiceAccessor _blazorServiceAccessor = default!;
private IServiceProvider _serviceProvider = default!;

internal void Initialize(T decoratedApi, IBlazorServiceAccessor blazorServiceAccessor, IServiceProvider serviceProvider)
{
_decoratedApi = decoratedApi;
_blazorServiceAccessor = blazorServiceAccessor;
_serviceProvider = serviceProvider;
}

/// <inheritdoc />
protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
{
_blazorServiceAccessor.Services = _serviceProvider;
return targetMethod?.Invoke(_decoratedApi, args);
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Reflection;
using Elsa.Api.Client.Extensions;
using Elsa.Studio.Contracts;

Expand All @@ -6,28 +7,18 @@ namespace Elsa.Studio.Services;
/// <summary>
/// Provides API clients to the remote backend.
/// </summary>
public class DefaultBackendApiClientProvider : IBackendApiClientProvider
public class DefaultRemoteBackendApiClientProvider(IRemoteBackendAccessor remoteBackendAccessor, IBlazorServiceAccessor blazorServiceAccessor, IServiceProvider serviceProvider) : IRemoteBackendApiClientProvider
{
private readonly IRemoteBackendAccessor _remoteBackendAccessor;
private readonly IServiceProvider _serviceProvider;

/// <summary>
/// Initializes a new instance of the <see cref="DefaultBackendApiClientProvider"/> class.
/// </summary>
public DefaultBackendApiClientProvider(IRemoteBackendAccessor remoteBackendAccessor, IServiceProvider serviceProvider)
{
_remoteBackendAccessor = remoteBackendAccessor;
_serviceProvider = serviceProvider;
}

/// <inheritdoc />
public Uri Url => _remoteBackendAccessor.RemoteBackend.Url;
public Uri Url => remoteBackendAccessor.RemoteBackend.Url;

/// <inheritdoc />
public ValueTask<T> GetApiAsync<T>(CancellationToken cancellationToken) where T : class
{
var backendUrl = _remoteBackendAccessor.RemoteBackend.Url;
var client = _serviceProvider.CreateApi<T>(backendUrl);
return new(client);
var backendUrl = remoteBackendAccessor.RemoteBackend.Url;
var client = serviceProvider.CreateApi<T>(backendUrl);
var decorator = DispatchProxy.Create<T, BlazorScopedProxyApi<T>>();
(decorator as BlazorScopedProxyApi<T>)!.Initialize(client, blazorServiceAccessor, serviceProvider);
return new(decorator);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,12 @@ namespace Elsa.Studio.Services;
/// <summary>
/// A feature service that uses a remote backend to retrieve feature flags.
/// </summary>
public class RemoteRemoteFeatureProvider : IRemoteFeatureProvider
public class RemoteRemoteFeatureProvider(IRemoteBackendApiClientProvider remoteBackendApiClientProvider) : IRemoteFeatureProvider
{
private readonly IBackendApiClientProvider _backendApiClientProvider;
private readonly IServiceProvider _serviceProvider;
private readonly IBlazorServiceAccessor _blazorServiceAccessor;

/// <summary>
/// Initializes a new instance of the <see cref="RemoteRemoteFeatureProvider"/> class.
/// </summary>
public RemoteRemoteFeatureProvider(IBackendApiClientProvider backendApiClientProvider, IServiceProvider serviceProvider, IBlazorServiceAccessor blazorServiceAccessor)
{
_backendApiClientProvider = backendApiClientProvider;
_serviceProvider = serviceProvider;
_blazorServiceAccessor = blazorServiceAccessor;
}

/// <inheritdoc />
public async Task<bool> IsEnabledAsync(string featureName, CancellationToken cancellationToken = default)
{
_blazorServiceAccessor.Services = _serviceProvider;
var api = await _backendApiClientProvider.GetApiAsync<IFeaturesApi>(cancellationToken);
var api = await remoteBackendApiClientProvider.GetApiAsync<IFeaturesApi>(cancellationToken);

try
{
Expand All @@ -45,7 +30,7 @@ public async Task<bool> IsEnabledAsync(string featureName, CancellationToken can
/// <inheritdoc />
public async Task<IEnumerable<FeatureDescriptor>> ListAsync(CancellationToken cancellationToken = default)
{
var api = await _backendApiClientProvider.GetApiAsync<IFeaturesApi>(cancellationToken);
var api = await remoteBackendApiClientProvider.GetApiAsync<IFeaturesApi>(cancellationToken);
var response = await api.ListAsync(cancellationToken);
return response.Items;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,102 +1,8 @@
using Elsa.Studio.Contracts;
using Elsa.Studio.Services;
using Microsoft.AspNetCore.Components;

namespace Elsa.Studio.Components;

/// Base class for components. This class sets the <see cref="BlazorServiceAccessor.Services"/> property to the <see cref="IServiceProvider"/> instance.
/// See https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/dependency-injection?view=aspnetcore-7.0#access-server-side-blazor-services-from-a-different-di-scope
public abstract class StudioComponentBase : ComponentBase, IHandleEvent, IHandleAfterRender
{
private bool _hasCalledOnAfterRender;

/// Gets the current IServiceProvider.
[Inject] protected IServiceProvider Services { get; set; } = default!;

/// Gets the current <see cref="IBlazorServiceAccessor"/>.
[Inject] protected IBlazorServiceAccessor BlazorServiceAccessor { get; set; } = default!;

/// <inheritdoc />
public override Task SetParametersAsync(ParameterView parameters) => InvokeWithBlazorServiceContext(() => base.SetParametersAsync(parameters));

Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object? arg)
{
return InvokeWithBlazorServiceContext(() =>
{
var task = callback.InvokeAsync(arg);
var shouldAwaitTask = task.Status != TaskStatus.RanToCompletion && task.Status != TaskStatus.Canceled;
StateHasChanged();
return shouldAwaitTask ? CallStateHasChangedOnAsyncCompletion(task) : Task.CompletedTask;
});
}

Task IHandleAfterRender.OnAfterRenderAsync()
{
return InvokeWithBlazorServiceContext(() =>
{
var firstRender = !_hasCalledOnAfterRender;
_hasCalledOnAfterRender = true;
OnAfterRender(firstRender);
return OnAfterRenderAsync(firstRender);
});
}

private async Task CallStateHasChangedOnAsyncCompletion(Task task)
{
try
{
await task;
}
catch
{
if (task.IsCanceled)
{
return;
}

throw;
}

StateHasChanged();
}

/// <summary>
/// Invokes the given function with the Blazor service context.
/// </summary>
/// <param name="func">The function to be invoked.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
protected async Task InvokeWithBlazorServiceContext(Func<Task> func)
{
try
{
BlazorServiceAccessor.Services = Services;
await func();
}
finally
{
BlazorServiceAccessor.Services = null!;
}
}

/// <summary>
/// Invokes the given function with the Blazor service context.
/// </summary>
/// <param name="func">The function to be invoked.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
protected async Task<T> InvokeWithBlazorServiceContext<T>(Func<Task<T>> func)
{
try
{
BlazorServiceAccessor.Services = Services;
return await func();
}
finally
{
BlazorServiceAccessor.Services = null!;
}
}
}
public abstract class StudioComponentBase : ComponentBase;
2 changes: 0 additions & 2 deletions src/framework/Elsa.Studio.Shared/Layouts/MainLayout.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ public partial class MainLayout : IDisposable
[Inject] private IFeatureService FeatureService { get; set; } = default!;
[Inject] private IDialogService DialogService { get; set; } = default!;
[Inject] private IServiceProvider ServiceProvider { get; set; } = default!;
[Inject] private IBlazorServiceAccessor BlazorServiceAccessor { get; set; } = default!;
[CascadingParameter] private Task<AuthenticationState>? AuthenticationState { get; set; }
private MudTheme CurrentTheme => ThemeService.CurrentTheme;
private bool IsDarkMode => ThemeService.IsDarkMode;
Expand All @@ -45,7 +44,6 @@ protected override async Task OnInitializedAsync()
var authState = await AuthenticationState;
if (authState.User.Identity?.IsAuthenticated == true && !authState.User.Claims.IsExpired())
{
BlazorServiceAccessor.Services = ServiceProvider;
await FeatureService.InitializeFeaturesAsync();
StateHasChanged();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Elsa.Api.Client.Resources.Scripting.Contracts;
using Elsa.Api.Client.Resources.Scripting.Contracts;
using Elsa.Api.Client.Resources.Scripting.Requests;
using Elsa.Studio.Contracts;

Expand All @@ -7,15 +7,14 @@ namespace Elsa.Studio.Services;
/// <summary>
/// A service that provides TypeScript type definitions.
/// </summary>
public class TypeDefinitionService(IBackendApiClientProvider backendApiClientProvider, IBlazorServiceAccessor blazorServiceAccessor, IServiceProvider serviceProvider)
public class TypeDefinitionService(IRemoteBackendApiClientProvider remoteBackendApiClientProvider)
{
/// <summary>
/// Gets the type definition for the specified activity type.
/// </summary>
public async Task<string> GetTypeDefinition(string definitionId, string activityTypeName, string propertyName, CancellationToken cancellationToken = default)
{
blazorServiceAccessor.Services = serviceProvider;
var api = await backendApiClientProvider.GetApiAsync<IJavaScriptApi>(cancellationToken);
var api = await remoteBackendApiClientProvider.GetApiAsync<IJavaScriptApi>(cancellationToken);
var data = await api.GetTypeDefinitions(definitionId, new GetWorkflowJavaScriptDefinitionRequest(definitionId, activityTypeName, propertyName), cancellationToken);
return await data.Content.ReadAsStringAsync(cancellationToken);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using Elsa.Api.Client.Resources.Identity.Responses;
using Elsa.Studio.Contracts;
using Elsa.Studio.Login.Contracts;
using Elsa.Studio.Login.Services;
using Microsoft.Extensions.DependencyInjection;

namespace Elsa.Studio.Login.HttpMessageHandlers;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public interface IWorkflowDefinitionService
/// <summary>
/// Saves a workflow definition.
/// </summary>
Task<Result<SaveWorkflowDefinitionResponse, ValidationErrors>> SaveAsync(SaveWorkflowDefinitionRequest request, CancellationToken cancellationToken = default);
Task<Result<SaveWorkflowDefinitionResponse, ValidationErrors>> SaveAsync(WorkflowDefinition workflowDefinition, bool publish, Func<WorkflowDefinition, Task>? workflowSavedCallback = null, CancellationToken cancellationToken = default);

/// <summary>
/// Deletes a workflow definition.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Elsa.Api.Client.Resources.WorkflowDefinitions.Models;
using Elsa.Studio.Contracts;

namespace Elsa.Studio.Workflows.Domain.Notifications;

/// Represents a notification sent when a workflow definition is published.
public record WorkflowDefinitionPublished(string WorkflowDefinitionId) : INotification;
public record WorkflowDefinitionPublished(WorkflowDefinition WorkflowDefinition) : INotification;
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Elsa.Api.Client.Resources.WorkflowDefinitions.Models;
using Elsa.Studio.Contracts;

namespace Elsa.Studio.Workflows.Domain.Notifications;

/// Represents a notification sent when a workflow definition is about to be published.
public record WorkflowDefinitionPublishing(string WorkflowDefinitionId) : INotification;
public record WorkflowDefinitionPublishing(WorkflowDefinition WorkflowDefinition) : INotification;
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
using Elsa.Api.Client.Resources.WorkflowDefinitions.Models;
using Elsa.Studio.Contracts;
using Elsa.Studio.Workflows.Domain.Models;

namespace Elsa.Studio.Workflows.Domain.Notifications;

public record WorkflowDefinitionPublishingFailed(WorkflowDefinitionVersion WorkflowDefinitionVersion, ValidationErrors Errors) : INotification;
/// <summary>
/// A notification sent when a workflow definition failed to publish.
/// </summary>
public record WorkflowDefinitionPublishingFailed(WorkflowDefinition WorkflowDefinition, ValidationErrors Errors) : INotification;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Elsa.Api.Client.Resources.WorkflowDefinitions.Models;
using Elsa.Studio.Contracts;
using Elsa.Studio.Workflows.Domain.Models;

namespace Elsa.Studio.Workflows.Domain.Notifications;

/// Represents a notification sent when a workflow definition has been saved.
public record WorkflowDefinitionSaved(WorkflowDefinitionVersion WorkflowDefinitionVersion) : INotification;
public record WorkflowDefinitionSaved(WorkflowDefinition WorkflowDefinition) : INotification;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Elsa.Api.Client.Resources.WorkflowDefinitions.Models;
using Elsa.Studio.Contracts;
using Elsa.Studio.Workflows.Domain.Models;

namespace Elsa.Studio.Workflows.Domain.Notifications;

/// Represents a notification sent when a workflow definition is about to be saved.
public record WorkflowDefinitionSaving(WorkflowDefinitionVersion WorkflowDefinitionVersion) : INotification;
public record WorkflowDefinitionSaving(WorkflowDefinition WorkflowDefinition) : INotification;
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
using Elsa.Api.Client.Resources.WorkflowDefinitions.Models;
using Elsa.Studio.Contracts;
using Elsa.Studio.Workflows.Domain.Models;

namespace Elsa.Studio.Workflows.Domain.Notifications;

public record WorkflowDefinitionSavingFailed(WorkflowDefinitionVersion WorkflowDefinitionVersion, ValidationErrors Errors) : INotification;
/// <summary>
/// A notification sent when a workflow definition failed to save.
/// </summary>
public record WorkflowDefinitionSavingFailed(WorkflowDefinition WorkflowDefinition, ValidationErrors Errors) : INotification;
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,12 @@ namespace Elsa.Studio.Workflows.Domain.Services;
/// <summary>
/// An activity registry provider that uses a remote backend to retrieve activity descriptors.
/// </summary>
public class RemoteActivityRegistryProvider : IActivityRegistryProvider
public class RemoteActivityRegistryProvider(IRemoteBackendApiClientProvider remoteBackendApiClientProvider) : IActivityRegistryProvider
{
private readonly IBackendApiClientProvider _backendApiClientProvider;

/// <summary>
/// Initializes a new instance of the <see cref="RemoteActivityRegistryProvider"/> class.
/// </summary>
public RemoteActivityRegistryProvider(IBackendApiClientProvider backendApiClientProvider)
{
_backendApiClientProvider = backendApiClientProvider;
}

/// <inheritdoc />
public async Task<IEnumerable<ActivityDescriptor>> ListAsync(CancellationToken cancellationToken = default)
{
var api = await _backendApiClientProvider.GetApiAsync<IActivityDescriptorsApi>(cancellationToken);
var api = await remoteBackendApiClientProvider.GetApiAsync<IActivityDescriptorsApi>(cancellationToken);
var request = new ListActivityDescriptorsRequest
{
Refresh = true
Expand Down
Loading

0 comments on commit f696d50

Please sign in to comment.