Elsa 2.0/workflow editor (#541)
* Fix connection model porperty names * Add workflow instance name to index * Enable sample workflows * Incremental work on activity displays * Add custom activity display support * Optimize workflow registry list endpoint model
This commit is contained in:
parent
92e1a1c3bf
commit
1bf53fb59a
6
Elsa.sln
6
Elsa.sln
|
|
@ -185,6 +185,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.DistributedLocking.Sql
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Activities.Timers", "src\activities\Elsa.Activities.Timers\Elsa.Activities.Timers.csproj", "{74E83393-50AF-41E3-A1C8-97202673CD85}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ElsaDashboard.Abstractions", "src\dashboards\blazor\ElsaDashboard.Abstractions\ElsaDashboard.Abstractions.csproj", "{8FC11EB7-38FE-445C-BE58-4D96BB14AAB4}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Activities.Rebus", "src\activities\Elsa.Activities.Rebus\Elsa.Activities.Rebus.csproj", "{A9843C3F-74F7-4282-A7B7-841F6B8EB12D}"
|
||||
EndProject
|
||||
Global
|
||||
|
|
@ -445,6 +446,10 @@ Global
|
|||
{74E83393-50AF-41E3-A1C8-97202673CD85}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{74E83393-50AF-41E3-A1C8-97202673CD85}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{74E83393-50AF-41E3-A1C8-97202673CD85}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8FC11EB7-38FE-445C-BE58-4D96BB14AAB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8FC11EB7-38FE-445C-BE58-4D96BB14AAB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8FC11EB7-38FE-445C-BE58-4D96BB14AAB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8FC11EB7-38FE-445C-BE58-4D96BB14AAB4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A9843C3F-74F7-4282-A7B7-841F6B8EB12D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A9843C3F-74F7-4282-A7B7-841F6B8EB12D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A9843C3F-74F7-4282-A7B7-841F6B8EB12D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
|
@ -535,6 +540,7 @@ Global
|
|||
{C90CD39C-8CDD-4BCB-AB6A-CF981D468DD2} = {7CD5C8D5-EC78-4A99-A514-F01CA7197AC8}
|
||||
{78F2A9DC-3092-4371-ABFE-87C5DA4DE351} = {7CD5C8D5-EC78-4A99-A514-F01CA7197AC8}
|
||||
{74E83393-50AF-41E3-A1C8-97202673CD85} = {37D2E628-5CC1-4C8E-8C02-3B0A29E2ACAA}
|
||||
{8FC11EB7-38FE-445C-BE58-4D96BB14AAB4} = {E02BD53A-5B08-47A0-920E-DD537A6AFAAA}
|
||||
{A9843C3F-74F7-4282-A7B7-841F6B8EB12D} = {B43B546E-23F3-46E8-ACB7-D04F05CDA180}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
|
|
|
|||
|
|
@ -11,5 +11,6 @@ namespace Elsa.Client.Models
|
|||
[DataMember(Order = 4)] public bool PersistWorkflow { get; set; }
|
||||
[DataMember(Order = 5)] public bool LoadWorkflowContext { get; set; }
|
||||
[DataMember(Order = 6)] public bool SaveWorkflowContext { get; set; }
|
||||
[DataMember(Order = 7)] public Variables Properties { get; set; } = new();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Elsa.Client.Models
|
||||
|
|
@ -20,11 +22,11 @@ namespace Elsa.Client.Models
|
|||
Data = data;
|
||||
}
|
||||
|
||||
[DataMember(Order = 1)]public IDictionary<string, object?> Data { get; set; }
|
||||
[DataMember(Order = 1)] public IDictionary<string, object?> Data { get; set; }
|
||||
|
||||
public object? Get(string name) => Has(name) ? Data[name] : default;
|
||||
|
||||
public T Get<T>(string name)
|
||||
public T? Get<T>(string name)
|
||||
{
|
||||
if (!Has(name))
|
||||
return default!;
|
||||
|
|
@ -34,7 +36,14 @@ namespace Elsa.Client.Models
|
|||
if (value == null)
|
||||
return default!;
|
||||
|
||||
return (T)value!;
|
||||
if (value is T convertedValue)
|
||||
return convertedValue;
|
||||
|
||||
if (value == default!)
|
||||
return default!;
|
||||
|
||||
var converter = TypeDescriptor.GetConverter(typeof(T));
|
||||
return converter.CanConvertFrom(value.GetType()) ? (T?) converter.ConvertFrom(value) : default;
|
||||
}
|
||||
|
||||
public Variables Set(string name, object? value)
|
||||
|
|
|
|||
|
|
@ -31,6 +31,14 @@ namespace Elsa
|
|||
string propertyName,
|
||||
ActivityExecutionContext activityExecutionContext,
|
||||
CancellationToken cancellationToken)
|
||||
=> (T) await workflowBlueprint.GetActivityPropertyValue(activityId, propertyName, activityExecutionContext, cancellationToken);
|
||||
|
||||
public static async ValueTask<object> GetActivityPropertyValue(
|
||||
this IWorkflowBlueprint workflowBlueprint,
|
||||
string activityId,
|
||||
string propertyName,
|
||||
ActivityExecutionContext activityExecutionContext,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var provider = workflowBlueprint.ActivityPropertyProviders.GetProvider(activityId, propertyName);
|
||||
|
||||
|
|
@ -38,7 +46,7 @@ namespace Elsa
|
|||
return default!;
|
||||
|
||||
var value = await provider.GetValueAsync(activityExecutionContext, cancellationToken);
|
||||
return (T) value!;
|
||||
return value!;
|
||||
}
|
||||
|
||||
public static T? GetActivityState<TActivity, T>(
|
||||
|
|
|
|||
|
|
@ -19,12 +19,8 @@ namespace Elsa
|
|||
return query;
|
||||
}
|
||||
|
||||
public static IActivityBlueprintWrapper<TActivity>? FindActivity<TActivity>(this IWorkflowBlueprintWrapper workflowBlueprintWrapper, Func<IActivityBlueprintWrapper<TActivity>, bool>? predicate = default)
|
||||
where TActivity : IActivity =>
|
||||
workflowBlueprintWrapper.Filter(predicate).FirstOrDefault();
|
||||
|
||||
public static IActivityBlueprintWrapper<TActivity> GetActivity<TActivity>(this IWorkflowBlueprintWrapper workflowBlueprintWrapper, string id)
|
||||
where TActivity : IActivity =>
|
||||
workflowBlueprintWrapper.FindActivity<TActivity>(x => x.ActivityBlueprint.Id == id)!;
|
||||
public static IActivityBlueprintWrapper<TActivity>? FindActivity<TActivity>(this IWorkflowBlueprintWrapper workflowBlueprintWrapper, Func<IActivityBlueprintWrapper<TActivity>, bool>? predicate = default) where TActivity : IActivity => workflowBlueprintWrapper.Filter(predicate).FirstOrDefault();
|
||||
public static IActivityBlueprintWrapper<TActivity>? GetActivity<TActivity>(this IWorkflowBlueprintWrapper workflowBlueprintWrapper, string id) where TActivity : IActivity => workflowBlueprintWrapper.FindActivity<TActivity>(x => x.ActivityBlueprint.Id == id)!;
|
||||
public static IActivityBlueprintWrapper? GetActivity(this IWorkflowBlueprintWrapper workflowBlueprintWrapper, string id) => workflowBlueprintWrapper.Activities.FirstOrDefault(x => x.ActivityBlueprint.Id == id);
|
||||
}
|
||||
}
|
||||
|
|
@ -34,8 +34,7 @@ namespace Elsa.Services
|
|||
properties[propertyName] = provider;
|
||||
}
|
||||
|
||||
public IDictionary<string, IActivityPropertyValueProvider>? GetProviders(string activityId) =>
|
||||
_providers.TryGetValue(activityId, out var properties) ? properties : null;
|
||||
public IDictionary<string, IActivityPropertyValueProvider>? GetProviders(string activityId) => _providers.TryGetValue(activityId, out var properties) ? properties : null;
|
||||
|
||||
public IActivityPropertyValueProvider? GetProvider(string activityId, string propertyName) =>
|
||||
_providers.TryGetValue(activityId, out var properties)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ namespace Elsa.Services
|
|||
{
|
||||
void AddProvider(string activityId, string propertyName, IActivityPropertyValueProvider provider);
|
||||
IActivityPropertyValueProvider? GetProvider(string activityId, string propertyName);
|
||||
IDictionary<string, IActivityPropertyValueProvider>? GetProviders(string activityId);
|
||||
|
||||
ValueTask SetActivityPropertiesAsync(
|
||||
IActivity activity,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,22 @@ namespace Elsa.Services.Models
|
|||
public IActivityBlueprint ActivityBlueprint => ActivityExecutionContext.ActivityBlueprint;
|
||||
|
||||
public IActivityBlueprintWrapper<TActivity> As<TActivity>() where TActivity : IActivity => new ActivityBlueprintWrapper<TActivity>(ActivityExecutionContext);
|
||||
|
||||
public async ValueTask<object?> GetPropertyValueAsync(string propertyName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var workflowBlueprint = ActivityExecutionContext.WorkflowExecutionContext.WorkflowBlueprint;
|
||||
var activityId = ActivityExecutionContext.ActivityBlueprint.Id;
|
||||
|
||||
// Computed property setters that depend on actual workflow state might fault, since we might be using a fake activity execution context.
|
||||
try
|
||||
{
|
||||
return await workflowBlueprint.GetActivityPropertyValue(activityId, propertyName, ActivityExecutionContext, cancellationToken);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ActivityBlueprintWrapper<TActivity> : ActivityBlueprintWrapper, IActivityBlueprintWrapper<TActivity> where TActivity : IActivity
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ namespace Elsa.Services.Models
|
|||
{
|
||||
IActivityBlueprint ActivityBlueprint { get; }
|
||||
IActivityBlueprintWrapper<TActivity> As<TActivity>() where TActivity : IActivity;
|
||||
ValueTask<object?> GetPropertyValueAsync(string propertyName, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public interface IActivityBlueprintWrapper<TActivity> : IActivityBlueprintWrapper where TActivity:IActivity
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
using ElsaDashboard.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace ElsaDashboard.Components
|
||||
{
|
||||
public abstract class ActivityDisplayComponent : ComponentBase
|
||||
{
|
||||
[Parameter] public ActivityDisplayContext DisplayContext { protected get; set; } = default!;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>ElsaDashboard</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components" Version="5.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\clients\Elsa.Client\Elsa.Client.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using ElsaDashboard.Components;
|
||||
using ElsaDashboard.Models;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
|
||||
namespace ElsaDashboard.Extensions
|
||||
{
|
||||
public static class RenderTreeBuilderExtensions
|
||||
{
|
||||
public static RenderTreeBuilder AddActivityDisplayComponent<T>(this RenderTreeBuilder builder, ActivityDisplayContext context) where T : ActivityDisplayComponent => AddActivityDisplayComponent(builder, typeof(T), context);
|
||||
|
||||
public static RenderTreeBuilder AddActivityDisplayComponent(this RenderTreeBuilder builder, Type type, ActivityDisplayContext context)
|
||||
{
|
||||
builder.OpenComponent(0, type);
|
||||
builder.AddAttribute(1, "DisplayContext", context);
|
||||
builder.CloseComponent();
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
using ElsaDashboard.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace ElsaDashboard.Extensions
|
||||
{
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddActivityDisplayProvider<T>(this IServiceCollection services) where T : class, IActivityDisplayProvider
|
||||
{
|
||||
return services.AddTransient<IActivityDisplayProvider, T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
using Elsa.Client.Models;
|
||||
|
||||
namespace ElsaDashboard.Models
|
||||
{
|
||||
public record ActivityDisplayContext(string ActivityType, Variables Properties);
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace ElsaDashboard.Models
|
||||
{
|
||||
public class ActivityDisplayDescriptor
|
||||
{
|
||||
public string ActivityType { get; init; } = default!;
|
||||
public Func<ActivityDisplayContext, RenderFragment>? RenderBody { get; init; }
|
||||
public Func<ActivityDisplayContext, RenderFragment>? RenderIcon { get; init; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ElsaDashboard.Models;
|
||||
|
||||
namespace ElsaDashboard.Services
|
||||
{
|
||||
public abstract class ActivityDisplayProvider : IActivityDisplayProvider
|
||||
{
|
||||
public virtual ValueTask<IEnumerable<ActivityDisplayDescriptor>> GetDescriptorsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var descriptors = GetDescriptors();
|
||||
return ValueTask.FromResult(descriptors);
|
||||
}
|
||||
|
||||
protected virtual IEnumerable<ActivityDisplayDescriptor> GetDescriptors()
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ElsaDashboard.Models;
|
||||
|
||||
namespace ElsaDashboard.Services
|
||||
{
|
||||
public interface IActivityDisplayProvider
|
||||
{
|
||||
ValueTask<IEnumerable<ActivityDisplayDescriptor>> GetDescriptorsAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ElsaDashboard.Models;
|
||||
|
||||
namespace ElsaDashboard.Services
|
||||
{
|
||||
public interface IActivityDisplayService
|
||||
{
|
||||
ValueTask<ActivityDisplayDescriptor?> GetDisplayDescriptorAsync(string activityType, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
@using NodaTime
|
||||
@inherits ActivityDisplayComponent
|
||||
|
||||
<p>Every <a href="#" class="font-medium border-b border-solid border-gray-200 text-black">@(DisplayContext.Properties.Get<Duration>("Timeout"))</a></p>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ElsaDashboard.Application.Activities;
|
||||
using ElsaDashboard.Extensions;
|
||||
using ElsaDashboard.Models;
|
||||
using ElsaDashboard.Services;
|
||||
|
||||
namespace ElsaDashboard.Application.Display
|
||||
{
|
||||
public class TimerDisplayProvider : ActivityDisplayProvider
|
||||
{
|
||||
protected override IEnumerable<ActivityDisplayDescriptor> GetDescriptors()
|
||||
{
|
||||
yield return new ActivityDisplayDescriptor
|
||||
{
|
||||
ActivityType = "Timer",
|
||||
RenderBody = context => builder => builder.AddActivityDisplayComponent<TimerActivity>(context)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -28,6 +28,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ElsaDashboard.Abstractions\ElsaDashboard.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\ElsaDashboard.Shared\ElsaDashboard.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
using Blazored.Modal;
|
||||
using ElsaDashboard.Application.Display;
|
||||
using ElsaDashboard.Application.Services;
|
||||
using ElsaDashboard.Extensions;
|
||||
using ElsaDashboard.Services;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
|
|
@ -11,7 +14,9 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
return services
|
||||
.AddBlazoredModal()
|
||||
.AddScoped<IConfirmDialogService, ConfirmDialogService>()
|
||||
.AddScoped<IFlyoutPanelService, FlyoutPanelService>();
|
||||
.AddScoped<IFlyoutPanelService, FlyoutPanelService>()
|
||||
.AddSingleton<IActivityDisplayService, ActivityDisplayService>()
|
||||
.AddActivityDisplayProvider<TimerDisplayProvider>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,19 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Elsa.Client.Models;
|
||||
using ElsaDashboard.Models;
|
||||
|
||||
namespace ElsaDashboard.Application.Models
|
||||
{
|
||||
public record ActivityModel
|
||||
{
|
||||
public ActivityModel(string activityId, string type) => (ActivityId, Type) = (activityId, type);
|
||||
public ActivityModel(string activityId, string type, IEnumerable<string> outcomes) => (ActivityId, Type, Outcomes) = (activityId, type, outcomes.ToList());
|
||||
public string ActivityId { get; init; }
|
||||
public string Type { get; init; }
|
||||
public string? Name { get; init; }
|
||||
public string? DisplayName { get; init; }
|
||||
public string? Description { get; set; }
|
||||
|
||||
public ICollection<string> Outcomes { get; init; } = new List<string>();
|
||||
public Variables Properties { get; set; } = new();
|
||||
public ActivityDisplayDescriptor? DisplayDescriptor { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -18,26 +18,6 @@ namespace ElsaDashboard.Application.Models
|
|||
public IImmutableList<ConnectionModel> Connections { get; init; } = Array.Empty<ConnectionModel>().ToImmutableList();
|
||||
|
||||
public static WorkflowModel Blank() => new("New Workflow");
|
||||
|
||||
public static WorkflowModel Demo() =>
|
||||
new WorkflowModel
|
||||
{
|
||||
Activities = new[]
|
||||
{
|
||||
new ActivityModel("activity-1", "ReadLine", new[] { "Done" }),
|
||||
new ActivityModel("activity-2", "IfElse", new[] { "True", "False" }),
|
||||
new ActivityModel("activity-3", "WriteLine", new[] { "Done" }),
|
||||
new ActivityModel("activity-4", "WriteLine", new[] { "Done" })
|
||||
}.ToImmutableList(),
|
||||
|
||||
Connections = new[]
|
||||
{
|
||||
new ConnectionModel("activity-1", "activity-2", "Done"),
|
||||
new ConnectionModel("activity-2", "activity-3", "True"),
|
||||
new ConnectionModel("activity-2", "activity-4", "False")
|
||||
}.ToImmutableList()
|
||||
};
|
||||
|
||||
public WorkflowModel AddActivity(ActivityModel activity) => this with { Activities = Activities.Add(activity)};
|
||||
public WorkflowModel RemoveActivity(ActivityModel activity) => this with { Activities = Activities.Where(x => x != activity).ToImmutableList()};
|
||||
public WorkflowModel AddConnection(ConnectionModel connection) => this with { Connections = Connections.Add(connection)};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using Elsa.Client.Models;
|
||||
|
||||
namespace ElsaDashboard.Application.Models
|
||||
{
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ namespace ElsaDashboard.Application.Pages.WorkflowDefinitions
|
|||
|
||||
private ActivityDefinition CreateActivityDefinition(ActivityModel activityModel)
|
||||
{
|
||||
return new ActivityDefinition
|
||||
return new()
|
||||
{
|
||||
ActivityId = activityModel.ActivityId,
|
||||
Type = activityModel.Type
|
||||
|
|
@ -78,7 +78,7 @@ namespace ElsaDashboard.Application.Pages.WorkflowDefinitions
|
|||
|
||||
private WorkflowModel CreateWorkflowModel(WorkflowDefinition workflowDefinition)
|
||||
{
|
||||
return new WorkflowModel
|
||||
return new()
|
||||
{
|
||||
Name = workflowDefinition.Name,
|
||||
Activities = workflowDefinition.Activities.Select(CreateActivityModel).ToImmutableList(),
|
||||
|
|
@ -94,7 +94,14 @@ namespace ElsaDashboard.Application.Pages.WorkflowDefinitions
|
|||
private ActivityModel CreateActivityModel(ActivityDefinition activityDefinition)
|
||||
{
|
||||
var descriptor = ActivityDescriptors[activityDefinition.Type];
|
||||
return new ActivityModel(activityDefinition.ActivityId, activityDefinition.Type, descriptor.Outcomes);
|
||||
return new ActivityModel
|
||||
{
|
||||
Name = activityDefinition.Name,
|
||||
ActivityId = activityDefinition.ActivityId,
|
||||
Type = activityDefinition.Type,
|
||||
DisplayName = descriptor.DisplayName,
|
||||
Outcomes = descriptor.Outcomes
|
||||
};
|
||||
}
|
||||
|
||||
private void StartBackgroundWorker()
|
||||
|
|
|
|||
|
|
@ -32,9 +32,9 @@
|
|||
<div class="flex-1">
|
||||
<h3>Filters</h3>
|
||||
</div>
|
||||
<ButtonDropdown Text="@SelectedWorkflowText" Items="@WorkflowFilterItems" IconType="@typeof(Icons.Squares)"/>
|
||||
<ButtonDropdown Text="@SelectedWorkflowStatusText" Items="@WorkflowStatusFilterItems" IconType="@typeof(Icons.PlayCircle)"/>
|
||||
<ButtonDropdown Text="@SelectedOrderByText" Items="@OrderByItems" IconType="@typeof(Icons.SortDescending)"/>
|
||||
<ButtonDropdown Text="@SelectedWorkflowText" Items="@WorkflowFilterItems" IconType="@typeof(SquaresIcon)"/>
|
||||
<ButtonDropdown Text="@SelectedWorkflowStatusText" Items="@WorkflowStatusFilterItems" IconType="@typeof(PlayCircleIcon)"/>
|
||||
<ButtonDropdown Text="@SelectedOrderByText" Items="@OrderByItems" IconType="@typeof(SortDescendingIcon)"/>
|
||||
</div>
|
||||
|
||||
<!-- Workflow instances (only on smallest breakpoint) -->
|
||||
|
|
|
|||
|
|
@ -101,14 +101,18 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-100">
|
||||
@foreach (var workflowBlueprint in WorkflowBlueprints.Items)
|
||||
@foreach (var grouping in VersionedWorkflowBlueprints)
|
||||
{
|
||||
var displayName = workflowBlueprint.DisplayName ?? workflowBlueprint.Name ?? "Unnamed";
|
||||
var workflowBlueprintId = grouping.Key;
|
||||
var workflowBlueprints = grouping.OrderByDescending(x => x.Version).ToList();
|
||||
var latestVersion = workflowBlueprints.First();
|
||||
var publishedVersion = workflowBlueprints.FirstOrDefault(x => x.IsPublished);
|
||||
var displayName = latestVersion.DisplayName ?? latestVersion.Name ?? "Unnamed";
|
||||
<tr>
|
||||
<td class="px-6 py-3 whitespace-no-wrap text-sm leading-5 font-medium text-gray-900">
|
||||
<div class="flex items-center space-x-3 lg:pl-2">
|
||||
<div class="flex-shrink-0 w-2.5 h-2.5 rounded-full bg-pink-600"></div>
|
||||
<a href="@($"workflow-registry/{workflowBlueprint.Id}/viewer")" class="truncate hover:text-gray-600">
|
||||
<a href="@($"workflow-registry/{workflowBlueprintId}/viewer")" class="truncate hover:text-gray-600">
|
||||
<span>@displayName</span>
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -125,10 +129,10 @@
|
|||
</div>
|
||||
</td>
|
||||
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-right">
|
||||
1
|
||||
@latestVersion.Version
|
||||
</td>
|
||||
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-right">
|
||||
1
|
||||
@(publishedVersion?.Version.ToString() ?? "-")
|
||||
</td>
|
||||
<td class="pr-6">
|
||||
<div x-data="{ open: false }" x-on:keydown.escape="open = false" x-on:click.away="open = false" class="relative flex justify-end items-center">
|
||||
|
|
@ -147,7 +151,7 @@
|
|||
class="z-10 mx-3 origin-top-right absolute right-7 top-0 w-48 mt-1 rounded-md shadow-lg">
|
||||
<div class="rounded-md bg-white shadow-xs" role="menu" aria-orientation="vertical" aria-labelledby="project-options-menu-0">
|
||||
<div class="py-1">
|
||||
<a href="@($"workflow-registry/{workflowBlueprint.Id}/viewer")" class="group flex items-center px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">
|
||||
<a href="@($"workflow-registry/{workflowBlueprintId}/viewer")" class="group flex items-center px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">
|
||||
<!-- Heroicon name: pencil-alt -->
|
||||
<svg class="mr-3 h-5 w-5 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"/>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Client.Models;
|
||||
using ElsaDashboard.Shared.Rpc;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
|
@ -9,6 +11,7 @@ namespace ElsaDashboard.Application.Pages.WorkflowRegistry
|
|||
{
|
||||
[Inject] private IWorkflowRegistryService WorkflowRegistryService { get; set; } = default!;
|
||||
private PagedList<WorkflowBlueprint> WorkflowBlueprints { get; set; } = new();
|
||||
private IEnumerable<IGrouping<string, WorkflowBlueprint>> VersionedWorkflowBlueprints => WorkflowBlueprints.Items.GroupBy(x => x.Id);
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using Elsa.Client.Models;
|
||||
using ElsaDashboard.Application.Models;
|
||||
using ElsaDashboard.Services;
|
||||
using ElsaDashboard.Shared.Rpc;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
|
|
@ -14,6 +15,7 @@ namespace ElsaDashboard.Application.Pages.WorkflowRegistry
|
|||
[Parameter] public string WorkflowDefinitionId { get; set; } = default!;
|
||||
[Inject] private IWorkflowRegistryService WorkflowRegistryService { get; set; } = default!;
|
||||
[Inject] private IActivityService ActivityService { get; set; } = default!;
|
||||
[Inject] private IActivityDisplayService ActivityDisplayService { get; set; } = default!;
|
||||
private IDictionary<string, ActivityInfo> ActivityDescriptors { get; set; } = default!;
|
||||
private WorkflowBlueprint WorkflowBlueprint { get; set; } = default!;
|
||||
private WorkflowModel WorkflowModel { get; set; } = WorkflowModel.Blank();
|
||||
|
|
@ -26,15 +28,17 @@ namespace ElsaDashboard.Application.Pages.WorkflowRegistry
|
|||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
WorkflowBlueprint = (await WorkflowRegistryService.GetById(WorkflowDefinitionId, VersionOptions.Latest))!;
|
||||
WorkflowModel = CreateWorkflowModel(WorkflowBlueprint);
|
||||
WorkflowModel = await CreateWorkflowModelAsync(WorkflowBlueprint);
|
||||
}
|
||||
|
||||
private WorkflowModel CreateWorkflowModel(WorkflowBlueprint workflowBlueprint)
|
||||
private async ValueTask<WorkflowModel> CreateWorkflowModelAsync(WorkflowBlueprint workflowBlueprint)
|
||||
{
|
||||
var activities = await Task.WhenAll(workflowBlueprint.Activities.Select(async x => await CreateActivityModelAsync(x)));
|
||||
|
||||
return new()
|
||||
{
|
||||
Name = workflowBlueprint.Name,
|
||||
Activities = workflowBlueprint.Activities.Select(CreateActivityModel).ToImmutableList(),
|
||||
Activities = activities.ToImmutableList(),
|
||||
Connections = workflowBlueprint.Connections.Select(CreateConnectionModel).ToImmutableList()
|
||||
};
|
||||
}
|
||||
|
|
@ -44,10 +48,22 @@ namespace ElsaDashboard.Application.Pages.WorkflowRegistry
|
|||
return new(connectionDefinition.SourceActivityId, connectionDefinition.TargetActivityId, connectionDefinition.Outcome);
|
||||
}
|
||||
|
||||
private ActivityModel CreateActivityModel(ActivityBlueprint activityBlueprint)
|
||||
private async ValueTask<ActivityModel> CreateActivityModelAsync(ActivityBlueprint activityBlueprint)
|
||||
{
|
||||
var descriptor = ActivityDescriptors[activityBlueprint.Type];
|
||||
return new ActivityModel(activityBlueprint.Id, activityBlueprint.Type, descriptor.Outcomes);
|
||||
var displayDescriptor = await ActivityDisplayService.GetDisplayDescriptorAsync(activityBlueprint.Type);
|
||||
|
||||
return new ActivityModel
|
||||
{
|
||||
Name = activityBlueprint.Name,
|
||||
Type = activityBlueprint.Type,
|
||||
DisplayName = descriptor.DisplayName,
|
||||
Outcomes = descriptor.Outcomes,
|
||||
ActivityId = activityBlueprint.Id,
|
||||
Description = descriptor.Description,
|
||||
Properties = activityBlueprint.Properties,
|
||||
DisplayDescriptor = displayDescriptor
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ElsaDashboard.Models;
|
||||
using ElsaDashboard.Services;
|
||||
|
||||
namespace ElsaDashboard.Application.Services
|
||||
{
|
||||
public class ActivityDisplayService : IActivityDisplayService
|
||||
{
|
||||
private readonly IEnumerable<IActivityDisplayProvider> _providers;
|
||||
|
||||
public ActivityDisplayService(IEnumerable<IActivityDisplayProvider> providers)
|
||||
{
|
||||
_providers = providers;
|
||||
}
|
||||
|
||||
public async ValueTask<ActivityDisplayDescriptor?> GetDisplayDescriptorAsync(string activityType, CancellationToken cancellationToken = default)
|
||||
{
|
||||
foreach (var provider in _providers)
|
||||
{
|
||||
var descriptors = await provider.GetDescriptorsAsync(cancellationToken);
|
||||
var descriptor = descriptors.FirstOrDefault(x => x.ActivityType == activityType);
|
||||
|
||||
if (descriptor != null)
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,14 +7,10 @@
|
|||
<div class="p-5 border-b border-b-solid">
|
||||
<div class="flex justify-between space-x-8">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-10 w-10 text-blue-500" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<circle cx="12" cy="12" r="9"/>
|
||||
<polyline points="12 7 12 12 15 15"/>
|
||||
</svg>
|
||||
@Icon
|
||||
</div>
|
||||
<div class="flex-1 font-medium leading-8">
|
||||
<p>@Model.Type</p>
|
||||
<p>@DisplayName</p>
|
||||
</div>
|
||||
<div class="flex-shrink-0">
|
||||
<button x-on:click="open = !open" aria-haspopup="true" class="w-8 h-8 inline-flex items-center justify-center text-gray-400 rounded-full bg-transparent hover:text-gray-500 focus:outline-none focus:text-gray-500 focus:bg-gray-100 transition ease-in-out duration-150">
|
||||
|
|
@ -39,10 +35,7 @@
|
|||
</div>
|
||||
<div class="border-t border-gray-100"></div>
|
||||
<div class="py-1">
|
||||
<a href="#" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">
|
||||
Removed
|
||||
from pinned
|
||||
</a>
|
||||
<a href="#" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -51,6 +44,6 @@
|
|||
|
||||
</div>
|
||||
<div class="p-6 text-gray-400 text-sm">
|
||||
Every <a href="#" class="font-medium border-b border-solid border-gray-200 text-black">5 minutes</a>
|
||||
@Body
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
using ElsaDashboard.Application.Models;
|
||||
using Elsa.Client.Models;
|
||||
using ElsaDashboard.Application.Icons;
|
||||
using ElsaDashboard.Application.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
|
||||
|
|
@ -6,7 +8,37 @@ namespace ElsaDashboard.Application.Shared
|
|||
{
|
||||
partial class Activity
|
||||
{
|
||||
[Parameter] public ActivityModel Model { get; set; } = new ActivityModel("", "");
|
||||
public const string DefaultIconClass = "h-10 w-10 text-blue-500";
|
||||
[Parameter] public ActivityModel Model { get; set; } = new();
|
||||
[Parameter] public EventCallback<MouseEventArgs> OnClick { get; set; }
|
||||
[Parameter] public RenderFragment Body { get; set; } = default!;
|
||||
[Parameter] public RenderFragment Icon { get; set; } = default!;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
if (Body == null!)
|
||||
Body = GetDefaultBody();
|
||||
|
||||
if (Icon == null!)
|
||||
Icon = GetDefaultIcon();
|
||||
}
|
||||
|
||||
private RenderFragment GetDefaultBody() =>
|
||||
builder =>
|
||||
{
|
||||
builder.OpenElement(0, "p");
|
||||
builder.AddContent(1, Model.Description);
|
||||
builder.CloseElement();
|
||||
};
|
||||
|
||||
private RenderFragment GetDefaultIcon() =>
|
||||
builder =>
|
||||
{
|
||||
builder.OpenComponent<ActivityIcon>(0);
|
||||
builder.AddAttribute(1, "CssClass", DefaultIconClass);
|
||||
builder.CloseComponent();
|
||||
};
|
||||
|
||||
private string DisplayName => string.IsNullOrWhiteSpace(Model.DisplayName) ? Model.Type : Model.DisplayName;
|
||||
}
|
||||
}
|
||||
|
|
@ -12,11 +12,11 @@ namespace ElsaDashboard.Application.Shared
|
|||
{
|
||||
MenuItems = new[]
|
||||
{
|
||||
new MenuItem("Home", "", GetIcon<Home>(), NavLinkMatch.All),
|
||||
new MenuItem("Workflow Registry", "workflow-registry", GetIcon<Squares>(), NavLinkMatch.Prefix),
|
||||
new MenuItem("Workflow Instances", "workflow-instances", GetIcon<PlayCircle>(), NavLinkMatch.Prefix),
|
||||
new MenuItem("Workflow Definitions", "workflow-definitions", GetIcon<Database>(), NavLinkMatch.Prefix),
|
||||
new MenuItem("Composite Activities", "composite-activities", GetIcon<Icons.Activity>(), NavLinkMatch.Prefix),
|
||||
new MenuItem("Home", "", GetIcon<HomeIcon>(), NavLinkMatch.All),
|
||||
new MenuItem("Workflow Registry", "workflow-registry", GetIcon<SquaresIcon>(), NavLinkMatch.Prefix),
|
||||
new MenuItem("Workflow Instances", "workflow-instances", GetIcon<PlayCircleIcon>(), NavLinkMatch.Prefix),
|
||||
new MenuItem("Workflow Definitions", "workflow-definitions", GetIcon<DatabaseIcon>(), NavLinkMatch.Prefix),
|
||||
new MenuItem("Composite Activities", "composite-activities", GetIcon<ActivityIcon>(), NavLinkMatch.Prefix),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
@using ElsaDashboard.Application.Extensions
|
||||
@using ElsaDashboard.Models
|
||||
|
||||
@{
|
||||
var renderedActivities = new HashSet<string>();
|
||||
|
|
@ -86,7 +87,7 @@
|
|||
@RenderOutcomeButton($"start-button-plus-{activityId}", "", () => onAddActivityClickHandler(null, activityId, null))
|
||||
}
|
||||
|
||||
<Activity Model="@activity"/>
|
||||
@RenderActivity(activity)
|
||||
@RenderOutcomeButtons(activity, onAddActivityClickHandler)
|
||||
</div>
|
||||
@if (children.Any())
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ using Elsa.Client.Models;
|
|||
using ElsaDashboard.Application.Extensions;
|
||||
using ElsaDashboard.Application.Models;
|
||||
using ElsaDashboard.Application.Services;
|
||||
using ElsaDashboard.Models;
|
||||
using ElsaDashboard.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
|
|
@ -19,7 +21,7 @@ namespace ElsaDashboard.Application.Shared
|
|||
partial class WorkflowDesigner : IAsyncDisposable
|
||||
{
|
||||
private static Func<ConnectionModel, Task> _connectionCreatedAction = default!;
|
||||
|
||||
|
||||
[Parameter] public WorkflowModel Model { private get; set; } = WorkflowModel.Blank();
|
||||
[Parameter] public EventCallback<WorkflowModelChangedEventArgs> WorkflowChanged { get; set; }
|
||||
[Inject] private IJSRuntime JS { get; set; } = default!;
|
||||
|
|
@ -28,6 +30,15 @@ namespace ElsaDashboard.Application.Shared
|
|||
private bool _connectionsChanged = true;
|
||||
private EventCallbackFactory EventCallbackFactory { get; } = new();
|
||||
|
||||
private readonly ActivityInfo _unknownActivityDescriptor =
|
||||
new()
|
||||
{
|
||||
Type = "Unknown",
|
||||
Category = "Unknown",
|
||||
Description = "Activity type not found",
|
||||
DisplayName = "Unknown"
|
||||
};
|
||||
|
||||
[JSInvokableAttribute("InvokeConnectionCreated")]
|
||||
public static Task InvokeConnectionCreated(ConnectionModel connection) => _connectionCreatedAction(connection);
|
||||
|
||||
|
|
@ -59,7 +70,7 @@ namespace ElsaDashboard.Application.Shared
|
|||
{
|
||||
Model = model;
|
||||
ConnectionsHasChanged();
|
||||
|
||||
|
||||
await WorkflowChanged.InvokeAsync(new WorkflowModelChangedEventArgs(Model));
|
||||
}
|
||||
|
||||
|
|
@ -164,7 +175,7 @@ namespace ElsaDashboard.Application.Shared
|
|||
{
|
||||
await FlyoutPanelService.ShowAsync<ActivityPicker>(
|
||||
"Activities",
|
||||
new { ActivitySelected = EventCallbackFactory.Create<ActivityDescriptorSelectedEventArgs>(this, e => OnActivityPickedAsync(e, parentActivityId, targetActivityId, outcome)) },
|
||||
new {ActivitySelected = EventCallbackFactory.Create<ActivityDescriptorSelectedEventArgs>(this, e => OnActivityPickedAsync(e, parentActivityId, targetActivityId, outcome))},
|
||||
ButtonDescriptor.Create("Cancel", _ => ClosePanelAsync()));
|
||||
}
|
||||
|
||||
|
|
@ -172,7 +183,14 @@ namespace ElsaDashboard.Application.Shared
|
|||
{
|
||||
outcome ??= "Done";
|
||||
|
||||
var activity = new ActivityModel(Guid.NewGuid().ToString("N"), activityInfo.Type, activityInfo.Outcomes);
|
||||
var activity = new ActivityModel
|
||||
{
|
||||
ActivityId = Guid.NewGuid().ToString("N"),
|
||||
Type = activityInfo.Type,
|
||||
Outcomes = activityInfo.Outcomes,
|
||||
DisplayName = activityInfo.DisplayName
|
||||
};
|
||||
|
||||
var model = Model.AddActivity(activity);
|
||||
|
||||
if (targetActivityId != null)
|
||||
|
|
@ -210,10 +228,35 @@ namespace ElsaDashboard.Application.Shared
|
|||
model = model.AddConnection(connection);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await UpdateModelAsync(model);
|
||||
await FlyoutPanelService.HideAsync();
|
||||
}
|
||||
|
||||
private RenderFragment RenderActivity(ActivityModel activityModel)
|
||||
{
|
||||
return builder =>
|
||||
{
|
||||
var index = 0;
|
||||
builder.OpenComponent<Activity>(index++);
|
||||
builder.AddAttribute(index++, "Model", activityModel);
|
||||
|
||||
var displayDescriptor = activityModel.DisplayDescriptor;
|
||||
|
||||
if (displayDescriptor != null)
|
||||
{
|
||||
var context = new ActivityDisplayContext(activityModel.Type, activityModel.Properties);
|
||||
|
||||
if(displayDescriptor.RenderBody != null)
|
||||
builder.AddAttribute(index++, "Body", displayDescriptor.RenderBody(context));
|
||||
|
||||
if(displayDescriptor.RenderIcon != null)
|
||||
builder.AddAttribute(index, "Icon", displayDescriptor.RenderIcon(context));
|
||||
}
|
||||
|
||||
builder.CloseComponent();
|
||||
};
|
||||
}
|
||||
|
||||
private void ConnectionsHasChanged()
|
||||
{
|
||||
|
|
@ -229,7 +272,7 @@ namespace ElsaDashboard.Application.Shared
|
|||
|
||||
await FlyoutPanelService.ShowAsync<ActivityEditor>(
|
||||
activityInfo.DisplayName,
|
||||
new { ActivityInfo = activityInfo },
|
||||
new {ActivityInfo = activityInfo},
|
||||
ButtonDescriptor.Create("Cancel", _ => ShowActivityPickerAsync(sourceActivityId, targetActivityId, outcome)),
|
||||
ButtonDescriptor.Create("OK", _ => AddActivityAsync(activityInfo, sourceActivityId, targetActivityId, outcome), true));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@
|
|||
@using Blazored.Modal
|
||||
@using Blazored.Modal.Services
|
||||
@using ElsaDashboard.Application
|
||||
@using ElsaDashboard.Application.Icons
|
||||
@using ElsaDashboard.Application.Models
|
||||
@using ElsaDashboard.Application.Shared
|
||||
@using ElsaDashboard.Components
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop
|
||||
@using Microsoft.JSInterop
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
using Elsa.Client.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.JsonNet;
|
||||
using ProtoBuf;
|
||||
|
|
@ -16,7 +18,15 @@ namespace ElsaDashboard.Shared.Surrogates
|
|||
SerializerSettings = new JsonSerializerSettings
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.Auto
|
||||
}.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
|
||||
};
|
||||
|
||||
SerializerSettings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
|
||||
SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
|
||||
SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
|
||||
SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.All;
|
||||
SerializerSettings.TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple;
|
||||
SerializerSettings.TypeNameHandling = TypeNameHandling.Auto;
|
||||
SerializerSettings.Converters.Add(new StringEnumConverter(new DefaultNamingStrategy()));
|
||||
}
|
||||
|
||||
public WorkflowBlueprintSurrogate(WorkflowBlueprint value)
|
||||
|
|
|
|||
|
|
@ -28,8 +28,9 @@ namespace Elsa.Persistence.MongoDb.Services
|
|||
var tenantKeysDefinition = Builders<WorkflowInstance>.IndexKeys.Ascending(x => x.TenantId);
|
||||
var versionKeysDefinition = Builders<WorkflowInstance>.IndexKeys.Ascending(x => x.Version);
|
||||
var workflowStatusKeysDefinition = Builders<WorkflowInstance>.IndexKeys.Ascending(x => x.WorkflowStatus);
|
||||
var workflowNameKeysDefinition = Builders<WorkflowInstance>.IndexKeys.Ascending(x => x.Name);
|
||||
|
||||
await CreateIndexesAsync(_mongoClient.WorkflowInstances, cancellationToken, tenantKeysDefinition, versionKeysDefinition, workflowStatusKeysDefinition);
|
||||
await CreateIndexesAsync(_mongoClient.WorkflowInstances, cancellationToken, tenantKeysDefinition, versionKeysDefinition, workflowStatusKeysDefinition, workflowNameKeysDefinition);
|
||||
}
|
||||
|
||||
private async Task CreateWorkflowDefinitionsIndexes(CancellationToken cancellationToken)
|
||||
|
|
@ -41,7 +42,7 @@ namespace Elsa.Persistence.MongoDb.Services
|
|||
|
||||
await CreateIndexesAsync(_mongoClient.WorkflowDefinitions, cancellationToken, tenantKeysDefinition, definitionVersionIdKeysDefinition, versionKeysDefinition, nameKeysDefinition);
|
||||
}
|
||||
|
||||
|
||||
private async Task CreateWorkflowExecutionLogIndexes(CancellationToken cancellationToken)
|
||||
{
|
||||
var tenantKeysDefinition = Builders<WorkflowExecutionLogRecord>.IndexKeys.Ascending(x => x.TenantId);
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ namespace Elsa.Samples.Timers
|
|||
.AddTimerActivities(options => options.UseQuartzProvider())
|
||||
.AddWorkflow<RecurringTaskWorkflow>()
|
||||
.AddWorkflow<CancelTimerWorkflow>()
|
||||
//.AddWorkflow<CronTaskWorkflow>()
|
||||
//.AddWorkflow(new OneOffWorkflow(SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromSeconds(5))))
|
||||
.AddWorkflow<CronTaskWorkflow>()
|
||||
.AddWorkflow(new OneOffWorkflow(SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromSeconds(5))))
|
||||
;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
|||
using AutoMapper;
|
||||
using Elsa.Models;
|
||||
using Elsa.Server.Api.Models;
|
||||
using Elsa.Server.Api.Services;
|
||||
using Elsa.Server.Api.Swagger.Examples;
|
||||
using Elsa.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
|
@ -19,12 +20,12 @@ namespace Elsa.Server.Api.Endpoints.WorkflowRegistry
|
|||
public class Get : Controller
|
||||
{
|
||||
private readonly IWorkflowRegistry _workflowRegistry;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IWorkflowBlueprintMapper _workflowBlueprintMapper;
|
||||
|
||||
public Get(IWorkflowRegistry workflowRegistry, IMapper mapper)
|
||||
public Get(IWorkflowRegistry workflowRegistry, IWorkflowBlueprintMapper workflowBlueprintMapper)
|
||||
{
|
||||
_workflowRegistry = workflowRegistry;
|
||||
_mapper = mapper;
|
||||
_workflowBlueprintMapper = workflowBlueprintMapper;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
|
|
@ -44,7 +45,7 @@ namespace Elsa.Server.Api.Endpoints.WorkflowRegistry
|
|||
if (workflowBlueprint == null)
|
||||
return NotFound();
|
||||
|
||||
return _mapper.Map<WorkflowBlueprintModel>(workflowBlueprint);
|
||||
return await _workflowBlueprintMapper.MapAsync(workflowBlueprint, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,7 @@
|
|||
using System.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AutoMapper;
|
||||
|
|
@ -6,8 +9,10 @@ using Elsa.Models;
|
|||
using Elsa.Server.Api.Models;
|
||||
using Elsa.Server.Api.Swagger.Examples;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
using Swashbuckle.AspNetCore.Filters;
|
||||
|
||||
|
|
@ -21,23 +26,24 @@ namespace Elsa.Server.Api.Endpoints.WorkflowRegistry
|
|||
{
|
||||
private readonly IWorkflowRegistry _workflowRegistry;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public List(IWorkflowRegistry workflowRegistry, IMapper mapper)
|
||||
public List(IWorkflowRegistry workflowRegistry, IWorkflowBlueprintReflector workflowBlueprintReflector, IMapper mapper, IServiceProvider serviceProvider)
|
||||
{
|
||||
_workflowRegistry = workflowRegistry;
|
||||
_mapper = mapper;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(PagedList<WorkflowBlueprintModel>))]
|
||||
[SwaggerResponseExample(StatusCodes.Status200OK, typeof(WorkflowBlueprintModelPagedListExample))]
|
||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(PagedList<WorkflowBlueprintSummaryModel>))]
|
||||
[SwaggerOperation(
|
||||
Summary = "Returns a list of workflow blueprints.",
|
||||
Description = "Returns paginated a list of workflow blueprints. When no version options are specified, the latest version is returned.",
|
||||
OperationId = "WorkflowBlueprints.List",
|
||||
Tags = new[] { "WorkflowBlueprints" })
|
||||
Tags = new[] {"WorkflowBlueprints"})
|
||||
]
|
||||
public async Task<ActionResult<PagedList<WorkflowBlueprintModel>>> Handle(int? page = default, int? pageSize = default, VersionOptions? version = default, CancellationToken cancellationToken = default)
|
||||
public async Task<ActionResult<PagedList<WorkflowBlueprintSummaryModel>>> Handle(int? page = default, int? pageSize = default, VersionOptions? version = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
version ??= VersionOptions.Latest;
|
||||
var workflowBlueprints = await _workflowRegistry.GetWorkflowsAsync(cancellationToken).Where(x => x.WithVersion(version.Value)).ToListAsync(cancellationToken);
|
||||
|
|
@ -45,16 +51,16 @@ namespace Elsa.Server.Api.Endpoints.WorkflowRegistry
|
|||
var skip = page * pageSize;
|
||||
var items = workflowBlueprints.AsEnumerable();
|
||||
|
||||
if (skip != null)
|
||||
if (skip != null)
|
||||
items = items.Skip(skip.Value);
|
||||
|
||||
if (pageSize != null)
|
||||
items = items.Take(pageSize.Value);
|
||||
|
||||
var mappedItems = items.Select(x => _mapper.Map<WorkflowBlueprintModel>(x)).ToList();
|
||||
var pagedList = new PagedList<WorkflowBlueprintModel>(mappedItems, page, pageSize, totalCount);
|
||||
|
||||
return pagedList;
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
var mappedItems = _mapper.Map<IEnumerable<WorkflowBlueprintSummaryModel>>(items).ToList();
|
||||
|
||||
return new PagedList<WorkflowBlueprintSummaryModel>(mappedItems, page, pageSize, totalCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -28,8 +28,8 @@ namespace Elsa.Server.Api.Endpoints.WorkflowRegistry
|
|||
|
||||
public class ConnectionModel
|
||||
{
|
||||
public string SourceId { get; set; } = default!;
|
||||
public string TargetId { get; set; } = default!;
|
||||
public string SourceActivityId { get; set; } = default!;
|
||||
public string TargetActivityId { get; set; } = default!;
|
||||
public string Outcome { get; set; } = default!;
|
||||
}
|
||||
|
||||
|
|
@ -41,5 +41,19 @@ namespace Elsa.Server.Api.Endpoints.WorkflowRegistry
|
|||
public bool PersistWorkflow { get; set; }
|
||||
public bool LoadWorkflowContext { get; set; }
|
||||
public bool SaveWorkflowContext { get; set; }
|
||||
public Variables Properties { get; set; } = new();
|
||||
}
|
||||
|
||||
public class WorkflowBlueprintSummaryModel
|
||||
{
|
||||
public string Id { get; set; } = default!;
|
||||
public int Version { get; set; }
|
||||
public string? TenantId { get; set; }
|
||||
public bool IsSingleton { get; set; }
|
||||
public bool IsEnabled { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public bool IsPublished { get; set; }
|
||||
public bool IsLatest { get; set; }
|
||||
public string? DisplayName { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ using Elsa;
|
|||
using Elsa.Models;
|
||||
using Elsa.Server.Api.Extensions;
|
||||
using Elsa.Server.Api.Mapping;
|
||||
using Elsa.Server.Api.Services;
|
||||
using Elsa.Server.Api.Swagger.Examples;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.OpenApi.Any;
|
||||
|
|
@ -35,6 +36,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
});
|
||||
|
||||
services.AddSingleton<ConnectionConverter>();
|
||||
services.AddSingleton<ActivityBlueprintConverter>();
|
||||
services.AddSingleton<IWorkflowBlueprintMapper, WorkflowBlueprintMapper>();
|
||||
services.AddAutoMapperProfile<AutoMapperProfile>();
|
||||
return services;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
using System.Collections.Generic;
|
||||
using AutoMapper;
|
||||
using Elsa.Models;
|
||||
using Elsa.Server.Api.Endpoints.WorkflowRegistry;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
namespace Elsa.Server.Api.Mapping
|
||||
{
|
||||
public class ActivityBlueprintConverter : ITypeConverter<IActivityBlueprint, ActivityBlueprintModel>
|
||||
{
|
||||
public const string ActivityPropertiesKey = "ActivityProperties";
|
||||
|
||||
public ActivityBlueprintModel Convert(IActivityBlueprint source, ActivityBlueprintModel destination, ResolutionContext context)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Id = source.Id,
|
||||
Name = source.Name,
|
||||
Type = source.Type,
|
||||
PersistWorkflow = source.PersistWorkflow,
|
||||
LoadWorkflowContext = source.LoadWorkflowContext,
|
||||
SaveWorkflowContext = source.SaveWorkflowContext,
|
||||
Properties = GetProperties(source, context)
|
||||
};
|
||||
}
|
||||
|
||||
private Variables GetProperties(IActivityBlueprint activityBlueprint, ResolutionContext context)
|
||||
{
|
||||
if (!context.Items.ContainsKey(ActivityPropertiesKey))
|
||||
return new Variables();
|
||||
|
||||
var dictionary = (IDictionary<string, Variables>) context.Items[ActivityPropertiesKey];
|
||||
return dictionary[activityBlueprint.Id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,8 @@ namespace Elsa.Server.Api.Mapping
|
|||
public AutoMapperProfile()
|
||||
{
|
||||
CreateMap<IWorkflowBlueprint, WorkflowBlueprintModel>();
|
||||
CreateMap<IActivityBlueprint, ActivityBlueprintModel>();
|
||||
CreateMap<IWorkflowBlueprint, WorkflowBlueprintSummaryModel>();
|
||||
CreateMap<IActivityBlueprint, ActivityBlueprintModel>().ConvertUsing<ActivityBlueprintConverter>();
|
||||
CreateMap<ICompositeActivityBlueprint, CompositeActivityBlueprintModel>();
|
||||
CreateMap<IConnection, ConnectionModel>().ConvertUsing<ConnectionConverter>();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ namespace Elsa.Server.Api.Mapping
|
|||
public ConnectionModel Convert(IConnection source, ConnectionModel destination, ResolutionContext context) =>
|
||||
new()
|
||||
{
|
||||
SourceId = source.Source.Activity.Id,
|
||||
TargetId = source.Target.Activity.Id,
|
||||
SourceActivityId = source.Source.Activity.Id,
|
||||
TargetActivityId = source.Target.Activity.Id,
|
||||
Outcome = source.Source.Outcome
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Server.Api.Endpoints.WorkflowRegistry;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
namespace Elsa.Server.Api.Services
|
||||
{
|
||||
public interface IWorkflowBlueprintMapper
|
||||
{
|
||||
ValueTask<WorkflowBlueprintModel> MapAsync(IWorkflowBlueprint workflowBlueprint, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AutoMapper;
|
||||
using Elsa.Models;
|
||||
using Elsa.Server.Api.Endpoints.WorkflowRegistry;
|
||||
using Elsa.Server.Api.Mapping;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Server.Api.Services
|
||||
{
|
||||
public class WorkflowBlueprintMapper : IWorkflowBlueprintMapper
|
||||
{
|
||||
private readonly IWorkflowBlueprintReflector _workflowBlueprintReflector;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public WorkflowBlueprintMapper(IWorkflowBlueprintReflector workflowBlueprintReflector, IMapper mapper, IServiceProvider serviceProvider)
|
||||
{
|
||||
_workflowBlueprintReflector = workflowBlueprintReflector;
|
||||
_mapper = mapper;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public async ValueTask<WorkflowBlueprintModel> MapAsync(IWorkflowBlueprint workflowBlueprint, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
|
||||
var wrapper = await _workflowBlueprintReflector.ReflectAsync(scope, workflowBlueprint, cancellationToken);
|
||||
var activityProperties = await Task.WhenAll(wrapper.Activities.Select(async x => (x.ActivityBlueprint.Id, await GetActivityPropertiesAsync(wrapper, x, cancellationToken))));
|
||||
var activityPropertyDictionary = activityProperties.ToDictionary(x => x.Id, x => x.Item2);
|
||||
return _mapper.Map<WorkflowBlueprintModel>(workflowBlueprint, options => options.Items[ActivityBlueprintConverter.ActivityPropertiesKey] = activityPropertyDictionary);
|
||||
}
|
||||
|
||||
private async ValueTask<Variables> GetActivityPropertiesAsync(IWorkflowBlueprintWrapper workflowBlueprintWrapper, IActivityBlueprintWrapper activityBlueprintWrapper, CancellationToken cancellationToken)
|
||||
{
|
||||
var workflowBlueprint = workflowBlueprintWrapper.WorkflowBlueprint;
|
||||
var activityBlueprint = activityBlueprintWrapper.ActivityBlueprint;
|
||||
var activityId = activityBlueprint.Id;
|
||||
var activityPropertyValueProviders = workflowBlueprint.ActivityPropertyProviders.GetProviders(activityId);
|
||||
var activityWrapper = workflowBlueprintWrapper.GetActivity(activityId)!;
|
||||
var properties = new Variables();
|
||||
|
||||
if (activityPropertyValueProviders == null)
|
||||
return properties;
|
||||
|
||||
foreach (var valueProvider in activityPropertyValueProviders)
|
||||
{
|
||||
var value = await activityWrapper.GetPropertyValueAsync(valueProvider.Key, cancellationToken);
|
||||
properties.Set(valueProvider.Key, value);
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ namespace Elsa.Server.Api.Swagger.Examples
|
|||
Name = "Activity2",
|
||||
}
|
||||
},
|
||||
Connections = new[] { new ConnectionModel { SourceId = "activity-1", TargetId = "activity-2", Outcome = OutcomeNames.Done } }
|
||||
Connections = new[] { new ConnectionModel { SourceActivityId = "activity-1", TargetActivityId = "activity-2", Outcome = OutcomeNames.Done } }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
using Elsa.Activities.Console;
|
||||
using Elsa.Activities.ControlFlow;
|
||||
using Elsa.Activities.Primitives;
|
||||
using Elsa.Activities.Timers;
|
||||
using Elsa.Builders;
|
||||
using NodaTime;
|
||||
|
||||
namespace Elsa.Server.Host.Workflows
|
||||
{
|
||||
|
|
@ -13,6 +15,7 @@ namespace Elsa.Server.Host.Workflows
|
|||
.WithDisplayName("Conditions")
|
||||
.WriteLine("What is your age?")
|
||||
.ReadLine()
|
||||
.Timer(Duration.FromMinutes(5))
|
||||
.SetVariable("Age", context => int.Parse(context.GetInput<string>()))
|
||||
.IfElse(context => context.GetVariable<int>("Age") < 18, ifElse =>
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue