Incremental work on saving and loading workflow definitions

This commit is contained in:
Sipke Schoorstra 2020-11-17 20:12:18 +01:00
parent acf884c075
commit c193b28ba1
37 changed files with 474 additions and 249 deletions

View file

@ -9,7 +9,9 @@ indent_size=4
# ReSharper properties # ReSharper properties
resharper_csharp_max_line_length=240 resharper_csharp_max_line_length=240
resharper_keep_user_linebreaks=true resharper_keep_user_linebreaks=true
resharper_place_field_attribute_on_same_line=if_owner_is_single_line
resharper_space_within_single_line_array_initializer_braces=true resharper_space_within_single_line_array_initializer_braces=true
resharper_wrap_before_linq_expression=true
# Microsoft .NET properties # Microsoft .NET properties
csharp_new_line_before_members_in_object_initializers=false csharp_new_line_before_members_in_object_initializers=false

View file

@ -5,10 +5,6 @@ namespace Elsa.Client.Models
[DataContract] [DataContract]
public class ConnectionDefinition public class ConnectionDefinition
{ {
public ConnectionDefinition()
{
}
public ConnectionDefinition(string sourceActivityId, string targetActivityId, string outcome) public ConnectionDefinition(string sourceActivityId, string targetActivityId, string outcome)
{ {
SourceActivityId = sourceActivityId; SourceActivityId = sourceActivityId;
@ -16,8 +12,8 @@ namespace Elsa.Client.Models
Outcome = outcome; Outcome = outcome;
} }
[DataMember(Order = 1)] public string? SourceActivityId { get; set; } [DataMember(Order = 1)] public string SourceActivityId { get; set; }
[DataMember(Order = 2)] public string? TargetActivityId { get; set; } [DataMember(Order = 2)] public string TargetActivityId { get; set; }
[DataMember(Order = 3)] public string? Outcome { get; set; } [DataMember(Order = 3)] public string Outcome { get; set; }
} }
} }

View file

@ -1,9 +1,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.Serialization;
namespace Elsa.Client.Models namespace Elsa.Client.Models
{ {
[DataContract]
public class List<T> public class List<T>
{ {
public ICollection<T> Items { get; set; } = new System.Collections.Generic.List<T>(); [DataMember(Order = 1)] public ICollection<T> Items { get; set; } = new System.Collections.Generic.List<T>();
} }
} }

View file

@ -1,9 +1,12 @@
namespace Elsa.Client.Models using System.Runtime.Serialization;
namespace Elsa.Client.Models
{ {
[DataContract]
public class PagedList<T> : List<T> public class PagedList<T> : List<T>
{ {
public int? Page { get; set; } [DataMember(Order = 1)] public int? Page { get; set; }
public int? PageSize { get; set; } [DataMember(Order = 2)] public int? PageSize { get; set; }
public int TotalCount { get; set; } [DataMember(Order = 3)] public int TotalCount { get; set; }
} }
} }

View file

@ -7,16 +7,19 @@ namespace Elsa.Client.Services
{ {
public interface IWorkflowDefinitionsApi public interface IWorkflowDefinitionsApi
{ {
[Get("/v1/workflow-definitions/{workflowDefinitionId}")] [Get("/v1/workflow-definitions/{workflowDefinitionId}/{versionOptions}")]
Task<WorkflowDefinition> GetAsync(string workflowDefinitionId, VersionOptions? versionOptions = default, CancellationToken cancellationToken = default); Task<WorkflowDefinition> GetByDefinitionAndVersionAsync(string workflowDefinitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default);
[Get("/v1/workflow-definitions/{workflowDefinitionVersionId}")]
Task<WorkflowDefinition> GetByVersionIdAsync(string workflowDefinitionVersionId, CancellationToken cancellationToken = default);
[Get("/v1/workflow-definitions")] [Get("/v1/workflow-definitions")]
Task<WorkflowDefinition> ListAsync(string workflowDefinitionId, VersionOptions? versionOptions = default, CancellationToken cancellationToken = default); Task<PagedList<WorkflowDefinition>> ListAsync(int? page = default, int? pageSize = default, VersionOptions? versionOptions = default, CancellationToken cancellationToken = default);
[Post("/v1/workflow-definitions")] [Post("/v1/workflow-definitions")]
Task<WorkflowDefinition> PostAsync([Body(BodySerializationMethod.Serialized)]PostWorkflowDefinitionRequest request, CancellationToken cancellationToken = default); Task<WorkflowDefinition> PostAsync([Body(BodySerializationMethod.Serialized)] PostWorkflowDefinitionRequest request, CancellationToken cancellationToken = default);
[Post("/v1/workflow-definitions/{workflowDefinitionId}")] [Post("/v1/workflow-definitions/{workflowDefinitionId}")]
Task<WorkflowDefinition> PostAsync(string workflowDefinitionId, [Body(BodySerializationMethod.Serialized)]PostWorkflowDefinitionRequest request, CancellationToken cancellationToken = default); Task<WorkflowDefinition> PostAsync(string workflowDefinitionId, [Body(BodySerializationMethod.Serialized)] PostWorkflowDefinitionRequest request, CancellationToken cancellationToken = default);
} }
} }

View file

@ -0,0 +1,8 @@
namespace ElsaDashboard.Application.Server
{
public enum BlazorRuntimeModel
{
Server,
Browser
}
}

View file

@ -36,7 +36,7 @@
<a class="dismiss">🗙</a> <a class="dismiss">🗙</a>
</div> </div>
@if (Program.UseBlazorServer) @if (Program.RuntimeModel == BlazorRuntimeModel.Server)
{ {
<script src="_framework/blazor.server.js"></script> <script src="_framework/blazor.server.js"></script>
} }

View file

@ -6,9 +6,8 @@ namespace ElsaDashboard.Application.Server
{ {
public class Program public class Program
{ {
public static bool UseBlazorServer = true; public static BlazorRuntimeModel RuntimeModel => BlazorRuntimeModel.Browser;
public static bool UseBlazorWebAssembly => !UseBlazorServer; public static RenderMode RenderMode => RuntimeModel == BlazorRuntimeModel.Server ? RenderMode.ServerPrerendered : RenderMode.WebAssemblyPrerendered;
public static RenderMode RenderMode => UseBlazorServer ? RenderMode.ServerPrerendered: RenderMode.WebAssemblyPrerendered;
public static void Main(string[] args) public static void Main(string[] args)
{ {
@ -17,9 +16,6 @@ namespace ElsaDashboard.Application.Server
public static IHostBuilder CreateHostBuilder(string[] args) => public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args) Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
{
webBuilder.UseStartup<Startup>();
});
} }
} }

View file

@ -25,7 +25,7 @@ namespace ElsaDashboard.Application.Server
services.AddElsaDashboardUI(); services.AddElsaDashboardUI();
services.AddElsaDashboardBackend(options => options.ServerUrl = new Uri("https://localhost:11000")); services.AddElsaDashboardBackend(options => options.ServerUrl = new Uri("https://localhost:11000"));
if (Program.UseBlazorServer) if (Program.RuntimeModel == BlazorRuntimeModel.Server)
services.AddServerSideBlazor(options => services.AddServerSideBlazor(options =>
{ {
options.DetailedErrors = !Environment.IsProduction(); options.DetailedErrors = !Environment.IsProduction();
@ -40,7 +40,7 @@ namespace ElsaDashboard.Application.Server
{ {
app.UseDeveloperExceptionPage(); app.UseDeveloperExceptionPage();
if(Program.UseBlazorWebAssembly) if (Program.RuntimeModel == BlazorRuntimeModel.Browser)
app.UseWebAssemblyDebugging(); app.UseWebAssemblyDebugging();
} }
else else
@ -50,7 +50,7 @@ namespace ElsaDashboard.Application.Server
app.UseHsts(); app.UseHsts();
} }
if(Program.UseBlazorWebAssembly) if (Program.RuntimeModel == BlazorRuntimeModel.Browser)
app.UseBlazorFrameworkFiles(); app.UseBlazorFrameworkFiles();
app.UseStaticFiles(); app.UseStaticFiles();
@ -58,7 +58,7 @@ namespace ElsaDashboard.Application.Server
app.UseElsaGrpcServices(); app.UseElsaGrpcServices();
app.UseEndpoints(endpoints => app.UseEndpoints(endpoints =>
{ {
if(Program.UseBlazorServer) if (Program.RuntimeModel == BlazorRuntimeModel.Server)
endpoints.MapBlazorHub(); endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host"); endpoints.MapFallbackToPage("/_Host");

View file

@ -266,7 +266,14 @@
</div> </div>
<main class="flex-1 relative z-0 overflow-y-auto focus:outline-none" tabindex="0" x-data="" x-init="$el.focus()"> <main class="flex-1 relative z-0 overflow-y-auto focus:outline-none" tabindex="0" x-data="" x-init="$el.focus()">
<!-- Page title & actions -->
<div class="border-b border-gray-200 px-4 py-4 sm:flex sm:items-center sm:justify-between sm:px-6 lg:px-8">
<div class="flex-1 min-w-0">
<h1 class="text-lg font-medium leading-6 text-gray-900 sm:truncate">
Home
</h1>
</div>
</div>
</main> </main>
</div> </div>

View file

@ -1,3 +1,10 @@
<module href="./src/partials/layout.html"> <module href="./src/partials/layout.html">
<!-- Page title & actions -->
<div class="border-b border-gray-200 px-4 py-4 sm:flex sm:items-center sm:justify-between sm:px-6 lg:px-8">
<div class="flex-1 min-w-0">
<h1 class="text-lg font-medium leading-6 text-gray-900 sm:truncate">
Home
</h1>
</div>
</div>
</module> </module>

View file

@ -1,16 +1,19 @@
namespace ElsaDashboard.Application.Models using Microsoft.AspNetCore.Components.Routing;
namespace ElsaDashboard.Application.Models
{ {
public class MenuItem public class MenuItem
{ {
public MenuItem(string text, string url, string icon) public MenuItem(string text, string url, string icon, NavLinkMatch match = NavLinkMatch.Prefix)
{ {
Text = text; Text = text;
Url = url; Url = url;
Icon = icon; Icon = icon;
} }
public string Text { get; set; } public string Text { get; init; }
public string Icon { get; set; } public string Icon { get; init; }
public string Url { get; set; } public string Url { get; init; }
public NavLinkMatch Match { get; init; } = NavLinkMatch.Prefix;
} }
} }

View file

@ -1,4 +1,5 @@
@page "/designer" @page "/workflows/designer"
@page "/workflows/{workflowDefinitionVersionId}/designer"
<div class="flex-0 border-b border-gray-200 px-4 py-4 sm:flex sm:items-center sm:justify-between sm:px-6 lg:px-8"> <div class="flex-0 border-b border-gray-200 px-4 py-4 sm:flex sm:items-center sm:justify-between sm:px-6 lg:px-8">
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
<h1 class="text-lg font-medium leading-6 text-gray-900 sm:truncate"> <h1 class="text-lg font-medium leading-6 text-gray-900 sm:truncate">
@ -13,4 +14,4 @@
</span> </span>
</div> </div>
</div> </div>
<WorkflowDesigner /> <WorkflowDesigner Model="WorkflowModel" />

View file

@ -1,6 +1,56 @@
namespace ElsaDashboard.Application.Pages using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Elsa.Client.Models;
using ElsaDashboard.Application.Models;
using ElsaDashboard.Shared.Rpc;
using Microsoft.AspNetCore.Components;
namespace ElsaDashboard.Application.Pages
{ {
partial class Designer partial class Designer
{ {
[Parameter]public string? WorkflowDefinitionVersionId { get; set; }
[Inject] private IWorkflowDefinitionService WorkflowDefinitionService { get; set; } = default!;
[Inject] private IActivityService ActivityService { get; set; } = default!;
private IDictionary<string, ActivityDescriptor> ActivityDescriptors { get; set; } = default!;
private WorkflowModel WorkflowModel { get; set; } = WorkflowModel.Blank();
protected override async Task OnInitializedAsync()
{
ActivityDescriptors = (await ActivityService.GetActivitiesAsync()).ToDictionary(x => x.Type);
if (WorkflowDefinitionVersionId != null)
{
var workflowDefinition = await WorkflowDefinitionService.GetByVersionIdAsync(WorkflowDefinitionVersionId);
WorkflowModel = CreateWorkflowModel(workflowDefinition);
}
else
{
WorkflowModel = WorkflowModel.Blank();
}
}
private WorkflowModel CreateWorkflowModel(WorkflowDefinition workflowDefinition)
{
return new WorkflowModel
{
Name = workflowDefinition.Name,
Activities = workflowDefinition.Activities.Select(CreateActivityModel).ToImmutableList(),
Connections = workflowDefinition.Connections.Select(CreateConnectionModel).ToImmutableList()
};
}
private ConnectionModel CreateConnectionModel(ConnectionDefinition connectionDefinition)
{
return new(connectionDefinition.SourceActivityId, connectionDefinition.TargetActivityId, connectionDefinition.Outcome);
}
private ActivityModel CreateActivityModel(ActivityDefinition activityDefinition)
{
var descriptor = ActivityDescriptors[activityDefinition.Type];
return new ActivityModel(activityDefinition.ActivityId, activityDefinition.Type, descriptor.Outcomes);
}
} }
} }

View file

@ -1,5 +1,10 @@
@page "/" @page "/"
<h1>Hello, world!</h1> <!-- Page title & actions -->
<div class="border-b border-gray-200 px-4 py-4 sm:flex sm:items-center sm:justify-between sm:px-6 lg:px-8">
Welcome to your new app. <div class="flex-1 min-w-0">
<h1 class="text-lg font-medium leading-6 text-gray-900 sm:truncate">
Home
</h1>
</div>
</div>

View file

@ -8,7 +8,7 @@
</div> </div>
<div class="mt-4 flex sm:mt-0 sm:ml-4"> <div class="mt-4 flex sm:mt-0 sm:ml-4">
<span class="order-0 sm:order-1 sm:ml-3 shadow-sm rounded-md"> <span class="order-0 sm:order-1 sm:ml-3 shadow-sm rounded-md">
<a href="/designer" type="button" class="inline-flex items-center px-4 py-2 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-blue-500 hover:bg-blue-400 focus:outline-none focus:shadow-outline-blue focus:border-blue-700 active:bg-blue-700 transition duration-150 ease-in-out"> <a href="workflows/designer" type="button" class="inline-flex items-center px-4 py-2 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-blue-500 hover:bg-blue-400 focus:outline-none focus:shadow-outline-blue focus:border-blue-700 active:bg-blue-700 transition duration-150 ease-in-out">
Create Create
</a> </a>
</span> </span>
@ -108,12 +108,15 @@
</tr> </tr>
</thead> </thead>
<tbody class="bg-white divide-y divide-gray-100"> <tbody class="bg-white divide-y divide-gray-100">
@foreach (var workflowDefinition in WorkflowDefinitions.Items)
{
var displayName = workflowDefinition.DisplayName ?? workflowDefinition.Name ?? "Unnamed";
<tr> <tr>
<td class="px-6 py-3 whitespace-no-wrap text-sm leading-5 font-medium text-gray-900"> <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 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> <div class="flex-shrink-0 w-2.5 h-2.5 rounded-full bg-pink-600"></div>
<a href="#" class="truncate hover:text-gray-600"> <a href="@($"workflows/{workflowDefinition.WorkflowDefinitionVersionId}/designer")" class="truncate hover:text-gray-600">
<span>Document Approval</span> <span>@displayName</span>
</a> </a>
</div> </div>
</td> </td>
@ -151,7 +154,7 @@
class="z-10 mx-3 origin-top-right absolute right-7 top-0 w-48 mt-1 rounded-md shadow-lg"> 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="rounded-md bg-white shadow-xs" role="menu" aria-orientation="vertical" aria-labelledby="project-options-menu-0">
<div class="py-1"> <div class="py-1">
<a href="#" 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="@($"workflows/{workflowDefinition.WorkflowDefinitionVersionId}/designer")" 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 --> <!-- 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"> <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"/> <path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"/>
@ -190,8 +193,7 @@
</div> </div>
</td> </td>
</tr> </tr>
}
<!-- More project rows... -->
</tbody> </tbody>
</table> </table>
<div class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6"> <div class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6">

View file

@ -0,0 +1,18 @@
using System.Threading.Tasks;
using Elsa.Client.Models;
using ElsaDashboard.Shared.Rpc;
using Microsoft.AspNetCore.Components;
namespace ElsaDashboard.Application.Pages
{
partial class Workflows
{
[Inject] private IWorkflowDefinitionService WorkflowDefinitionService { get; set; } = default!;
private PagedList<WorkflowDefinition> WorkflowDefinitions { get; set; } = new();
protected override async Task OnInitializedAsync()
{
WorkflowDefinitions = await WorkflowDefinitionService.ListAsync();
}
}
}

View file

@ -0,0 +1,37 @@
using System;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
namespace ElsaDashboard.Application.Services
{
public class BackgroundWorker
{
private Channel<Func<ValueTask>> _channel;
public BackgroundWorker()
{
_channel = Channel.CreateBounded<Func<ValueTask>>(10);
}
public async Task ScheduleTask(Func<ValueTask> task, CancellationToken cancellationToken = default) => await _channel.Writer.WriteAsync(task, cancellationToken);
public async Task ScheduleTask(Action task, CancellationToken cancellationToken = default) =>
await _channel.Writer.WriteAsync(() =>
{
task();
return ValueTask.CompletedTask;
}, cancellationToken);
public async Task StartAsync(CancellationToken cancellationToken = default)
{
while (await _channel.Reader.WaitToReadAsync(cancellationToken))
{
while (_channel.Reader.TryRead(out var task))
{
await task();
}
}
}
}
}

View file

@ -29,13 +29,13 @@
<div class="p-6 space-y-6 overflow-y-auto"> <div class="p-6 space-y-6 overflow-y-auto">
@foreach (var grouping in ActivityGroupings) @foreach (var grouping in ActivityGroupings.OrderBy(x => x.Key))
{ {
<div> <div>
<h2>@grouping.Key</h2> <h2>@grouping.Key</h2>
<ul class="grid grid-cols-1 gap-4 mt-3" x-max="1"> <ul class="grid grid-cols-1 gap-4 mt-3" x-max="1">
@foreach (var activity in grouping) @foreach (var activity in grouping.OrderBy(x => x.DisplayName))
{ {
<li @onclick="(() => OnActivityClick(activity))" class="relative col-span-1 flex shadow-sm rounded-md select-none cursor-pointer"> <li @onclick="(() => OnActivityClick(activity))" class="relative col-span-1 flex shadow-sm rounded-md select-none cursor-pointer">
<div class="flex-shrink-0 flex items-center justify-center w-16 bg-blue-600 text-white text-sm leading-5 font-medium rounded-l-md"> <div class="flex-shrink-0 flex items-center justify-center w-16 bg-blue-600 text-white text-sm leading-5 font-medium rounded-l-md">

View file

@ -28,7 +28,7 @@ namespace ElsaDashboard.Application.Shared
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
var response = (await ActivityService.GetActivitiesAsync()); var response = (await ActivityService.GetActivitiesAsync());
Activities = response.Activities.ToList(); Activities = response.ToList();
} }
private void OnActivityTypeFilterClick(ActivityTraitFilter activityTraitFilter) private void OnActivityTypeFilterClick(ActivityTraitFilter activityTraitFilter)

View file

@ -27,34 +27,6 @@
</div> </div>
</form> </form>
</div> </div>
<div class="flex items-center">
<!-- Profile dropdown -->
<div x-on:click.away="open = false" class="ml-3 relative" x-data="{ open: false }">
<div>
<button x-on:click="open = !open" class="max-w-xs flex items-center text-sm rounded-full focus:outline-none focus:shadow-outline" id="user-menu" aria-label="User menu" aria-haspopup="true" x-bind:aria-expanded="open">
<img class="h-8 w-8 rounded-full" src="https://images.unsplash.com/photo-1502685104226-ee32379fefbe?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80" alt="">
</button>
</div>
<div x-show="open" x-description="Profile dropdown panel, show/hide based on dropdown state." x-transition:enter="transition ease-out duration-100" x-transition:enter-start="transform opacity-0 scale-95" x-transition:enter-end="transform opacity-100 scale-100" x-transition:leave="transition ease-in duration-75" x-transition:leave-start="transform opacity-100 scale-100" x-transition:leave-end="transform opacity-0 scale-95" class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg" style="display: none;">
<div class="rounded-md bg-white shadow-xs" role="menu" aria-orientation="vertical" aria-labelledby="user-menu">
<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">View profile</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">Settings</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">Notifications</a>
</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">Get desktop app</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">Support</a>
</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">Logout</a>
</div>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
<main class="flex flex-col flex-1 relative z-0 overflow-y-auto focus:outline-none" tabindex="0" x-data="" x-init="$el.focus()"> <main class="flex flex-col flex-1 relative z-0 overflow-y-auto focus:outline-none" tabindex="0" x-data="" x-init="$el.focus()">

View file

@ -17,28 +17,15 @@
<div class="mt-5 flex-1 h-0 overflow-y-auto"> <div class="mt-5 flex-1 h-0 overflow-y-auto">
<nav class="px-2"> <nav class="px-2">
<div class="space-y-1"> <div class="space-y-1">
@foreach (var menuItem in MenuItems)
<a href="#" class="group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md text-gray-900 bg-gray-100 hover:text-gray-900 hover:bg-gray-100 focus:bg-gray-200 focus:outline-none transition ease-in-out duration-150"> {
const string activeClass = "text-gray-900 bg-gray-100 hover:text-gray-900 hover:bg-gray-100 focus:bg-gray-200";
<NavLink Match="@menuItem.Match" href="@menuItem.Url" class="group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md text-gray-600 hover:text-gray-900 hover:bg-gray-50 focus:bg-gray-50 focus:outline-none transition ease-in-out duration-150" ActiveClass="@activeClass">
<svg class="mr-3 h-6 w-6 text-gray-500 group-hover:text-gray-500 group-focus:text-gray-600 transition ease-in-out duration-150" x-description="Heroicon name: home" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg class="mr-3 h-6 w-6 text-gray-500 group-hover:text-gray-500 group-focus:text-gray-600 transition ease-in-out duration-150" x-description="Heroicon name: home" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="@menuItem.Icon"></path>
</svg> </svg>
Home @menuItem.Text
</a> </NavLink> }
<a href="#" class="group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md text-gray-600 hover:text-gray-900 hover:bg-gray-50 focus:bg-gray-50 focus:outline-none transition ease-in-out duration-150">
<svg class="mr-3 h-6 w-6 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-600 transition ease-in-out duration-150" x-description="Heroicon name: view-list" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16"></path>
</svg>
My tasks
</a>
<a href="#" class="group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md text-gray-600 hover:text-gray-900 hover:bg-gray-50 focus:bg-gray-50 focus:outline-none transition ease-in-out duration-150">
<svg class="mr-3 h-6 w-6 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-600 transition ease-in-out duration-150" x-description="Heroicon name: clock" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
Recent
</a>
</div> </div>
</nav> </nav>
</div> </div>
@ -72,27 +59,16 @@
<nav class="px-3 mt-6"> <nav class="px-3 mt-6">
<div class="space-y-1"> <div class="space-y-1">
<a href="#" class="group flex items-center px-2 py-2 text-sm leading-5 font-medium rounded-md text-gray-900 bg-gray-200 focus:outline-none focus:bg-gray-50 transition ease-in-out duration-150"> @foreach (var menuItem in MenuItems)
<svg class="mr-3 h-6 w-6 text-gray-500 group-hover:text-gray-500 group-focus:text-gray-600 transition ease-in-out duration-150" x-description="Heroicon name: home" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> {
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path> const string activeClass = "text-gray-900 bg-gray-200 focus:bg-gray-50";
<NavLink Match="@menuItem.Match" href="@menuItem.Url" class="group flex items-center px-2 py-2 text-sm leading-5 font-medium rounded-md text-gray-700 hover:text-gray-900 hover:bg-gray-50 focus:outline-none focus:bg-gray-50 transition ease-in-out duration-150" ActiveClass="@activeClass">
<svg class="mr-3 h-6 w-6 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-600 transition ease-in-out duration-150" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="@menuItem.Icon"></path>
</svg> </svg>
Home @menuItem.Text
</a> </NavLink>
}
<a href="#" class="group flex items-center px-2 py-2 text-sm leading-5 font-medium rounded-md text-gray-700 hover:text-gray-900 hover:bg-gray-50 focus:outline-none focus:bg-gray-50 transition ease-in-out duration-150">
<svg class="mr-3 h-6 w-6 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-600 transition ease-in-out duration-150" x-description="Heroicon name: view-list" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16"></path>
</svg>
My tasks
</a>
<a href="#" class="group flex items-center px-2 py-2 text-sm leading-5 font-medium rounded-md text-gray-700 hover:text-gray-900 hover:bg-gray-50 focus:outline-none focus:bg-gray-50 transition ease-in-out duration-150">
<svg class="mr-3 h-6 w-6 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-600 transition ease-in-out duration-150" x-description="Heroicon name: clock" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
Recent
</a>
</div> </div>
</nav> </nav>
</div> </div>

View file

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using ElsaDashboard.Application.Models; using ElsaDashboard.Application.Models;
using Microsoft.AspNetCore.Components.Routing;
namespace ElsaDashboard.Application.Shared namespace ElsaDashboard.Application.Shared
{ {
@ -9,8 +10,9 @@ namespace ElsaDashboard.Application.Shared
{ {
MenuItems = new[] MenuItems = new[]
{ {
new MenuItem("Dashboard", "/", "M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"), new MenuItem("Home", "", "M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6", NavLinkMatch.All),
new MenuItem("Workflows", "/workflows", "M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"), new MenuItem("Workflows", "workflows", "M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z", NavLinkMatch.Prefix),
new MenuItem("Composite Activities", "composite-activities", "M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z", NavLinkMatch.Prefix),
}; };
} }

View file

@ -3,12 +3,14 @@ using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Formats.Asn1; using System.Formats.Asn1;
using System.Linq; using System.Linq;
using System.Threading.Channels;
using System.Threading.Tasks; using System.Threading.Tasks;
using Elsa.Client.Models; using Elsa.Client.Models;
using ElsaDashboard.Application.Extensions; using ElsaDashboard.Application.Extensions;
using ElsaDashboard.Application.Models; using ElsaDashboard.Application.Models;
using ElsaDashboard.Application.Services; using ElsaDashboard.Application.Services;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop; using Microsoft.JSInterop;
@ -18,17 +20,36 @@ namespace ElsaDashboard.Application.Shared
{ {
private static Action<ConnectionModel> _connectionCreatedAction = default!; private static Action<ConnectionModel> _connectionCreatedAction = default!;
[Parameter] public WorkflowModel Model { private get; set; } = WorkflowModel.Demo(); [Parameter] public WorkflowModel Model { private get; set; } = WorkflowModel.Blank();
[Inject] private IJSRuntime JS { get; set; } = default!; [Inject] private IJSRuntime JS { get; set; } = default!;
[Inject] private IFlyoutPanelService FlyoutPanelService { get; set; } = default!; [Inject] private IFlyoutPanelService FlyoutPanelService { get; set; } = default!;
private IJSObjectReference _designerModule = default!; private IJSObjectReference _designerModule = default!;
private bool _connectionsChanged = true; private bool _connectionsChanged = true;
private EventCallbackFactory EventCallbackFactory { get; } = new(); private EventCallbackFactory EventCallbackFactory { get; } = new();
private BackgroundWorker BackgroundWorker { get; } = new();
[JSInvokableAttribute("InvokeConnectionCreated")] [JSInvokableAttribute("InvokeConnectionCreated")]
public static void InvokeConnectionCreated(ConnectionModel connection) => _connectionCreatedAction(connection); public static void InvokeConnectionCreated(ConnectionModel connection) => _connectionCreatedAction(connection);
protected override void OnInitialized() => _connectionCreatedAction = OnConnectionCreated;
int _currentCount = 0;
void IncrementCount()
{
_currentCount++;
}
protected override async Task OnInitializedAsync()
{
_connectionCreatedAction = OnConnectionCreated;
InvokeAsync(() => BackgroundWorker.StartAsync());
}
protected override void OnParametersSet()
{
ConnectionsHasChanged();
}
public async ValueTask DisposeAsync() public async ValueTask DisposeAsync()
{ {
@ -38,14 +59,24 @@ namespace ElsaDashboard.Application.Shared
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
if (firstRender) if (_designerModule == null!)
{
_designerModule = await JS.InvokeAsync<IJSObjectReference>("import", "./_content/ElsaDashboard.Application/workflowDesigner.js"); _designerModule = await JS.InvokeAsync<IJSObjectReference>("import", "./_content/ElsaDashboard.Application/workflowDesigner.js");
}
await RepaintConnections(); await RepaintConnections();
} }
private async Task UpdateModelAsync(WorkflowModel model)
{
Model = model;
ConnectionsHasChanged();
await BackgroundWorker.ScheduleTask(SaveWorkflowAsync);
}
private async ValueTask SaveWorkflowAsync()
{
}
private IEnumerable<ActivityModel> GetRootActivities() => Model.GetChildActivities(null); private IEnumerable<ActivityModel> GetRootActivities() => Model.GetChildActivities(null);
private async ValueTask RepaintConnections() private async ValueTask RepaintConnections()
@ -197,6 +228,7 @@ namespace ElsaDashboard.Application.Shared
Model = model; Model = model;
await FlyoutPanelService.HideAsync(); await FlyoutPanelService.HideAsync();
ConnectionsHasChanged(); ConnectionsHasChanged();
await BackgroundWorker.ScheduleTask(() => Console.WriteLine("TEST"));
} }
private void ConnectionsHasChanged() private void ConnectionsHasChanged()

View file

@ -20,6 +20,7 @@ namespace ElsaDashboard.Backend.Extensions
services.AddElsaClient(configure); services.AddElsaClient(configure);
services.AddCodeFirstGrpc(options => options.ResponseCompressionLevel = CompressionLevel.Optimal); services.AddCodeFirstGrpc(options => options.ResponseCompressionLevel = CompressionLevel.Optimal);
services.AddScoped<IActivityService, ActivityService>(); services.AddScoped<IActivityService, ActivityService>();
services.AddScoped<IWorkflowDefinitionService, WorkflowDefinitionService>();
return services; return services;
} }

View file

@ -1,5 +1,7 @@
using System.Threading.Tasks; using System.Collections.Generic;
using System.Threading.Tasks;
using Elsa.Client; using Elsa.Client;
using Elsa.Client.Models;
using ElsaDashboard.Shared.Rpc; using ElsaDashboard.Shared.Rpc;
using ProtoBuf.Grpc; using ProtoBuf.Grpc;
@ -14,10 +16,10 @@ namespace ElsaDashboard.Backend.Rpc
_elsaClient = elsaClient; _elsaClient = elsaClient;
} }
public async Task<GetActivitiesResponse> GetActivitiesAsync(CallContext callContext) public async Task<IEnumerable<ActivityDescriptor>> GetActivitiesAsync(CallContext callContext)
{ {
var result = await _elsaClient.Activities.ListAsync(callContext.CancellationToken); var result = await _elsaClient.Activities.ListAsync(callContext.CancellationToken);
return new GetActivitiesResponse(result.Items); return result.Items;
} }
} }
} }

View file

@ -1,7 +0,0 @@
namespace ElsaDashboard.Backend.Rpc
{
public class WorkflowDefinitionManager
{
}
}

View file

@ -0,0 +1,23 @@
using System.Threading.Tasks;
using Elsa.Client;
using Elsa.Client.Models;
using ElsaDashboard.Shared.Rpc;
using ProtoBuf.Grpc;
namespace ElsaDashboard.Backend.Rpc
{
public class WorkflowDefinitionService : IWorkflowDefinitionService
{
private readonly IElsaClient _elsaClient;
public WorkflowDefinitionService(IElsaClient elsaClient)
{
_elsaClient = elsaClient;
}
public async Task<PagedList<WorkflowDefinition>> ListAsync(ListWorkflowDefinitionsRequest request, CallContext context) =>
await _elsaClient.WorkflowDefinitions.ListAsync(request.Page, request.PageSize, request.VersionOptions, context.CancellationToken);
public Task<WorkflowDefinition> GetByVersionIdAsync(string workflowDefinitionVersionId, CallContext context = default) => _elsaClient.WorkflowDefinitions.GetByVersionIdAsync(workflowDefinitionVersionId, context.CancellationToken);
}
}

View file

@ -12,21 +12,6 @@ namespace ElsaDashboard.Shared.Rpc
public interface IActivityService public interface IActivityService
{ {
[ProtoBehavior] [ProtoBehavior]
Task<GetActivitiesResponse> GetActivitiesAsync(CallContext context = default); Task<IEnumerable<ActivityDescriptor>> GetActivitiesAsync(CallContext context = default);
}
[ProtoContract]
public sealed class GetActivitiesResponse
{
public GetActivitiesResponse()
{
}
public GetActivitiesResponse(IEnumerable<ActivityDescriptor> activities)
{
Activities = activities;
}
[ProtoMember(1)] public IEnumerable<ActivityDescriptor> Activities { get; set; } = new System.Collections.Generic.List<ActivityDescriptor>();
} }
} }

View file

@ -1,12 +1,38 @@
using System.ServiceModel;
using System.Threading.Tasks;
using Elsa.Client.Models;
using ProtoBuf; using ProtoBuf;
using ProtoBuf.Grpc;
namespace ElsaDashboard.Shared.Rpc namespace ElsaDashboard.Shared.Rpc
{ {
[ProtoContract] [ServiceContract]
public interface IWorkflowDefinitionService public interface IWorkflowDefinitionService
{ {
//ValueTask<ICollection<Test>> GetAsync(string workflowDefinitionId, VersionOptions version); Task<PagedList<WorkflowDefinition>> ListAsync(ListWorkflowDefinitionsRequest request, CallContext context = default);
Task<WorkflowDefinition> GetByVersionIdAsync(string workflowDefinitionVersionId, CallContext context = default);
} }
public record Test(int a, bool b); [ProtoContract]
public record ListWorkflowDefinitionsRequest
{
public ListWorkflowDefinitionsRequest(int? page = default, int? pageSize = default, VersionOptions? versionOptions = default)
{
Page = page;
PageSize = pageSize;
VersionOptions = versionOptions;
}
[ProtoMember(1)] public int? Page { get; init; }
[ProtoMember(2)] public int? PageSize { get; init; }
[ProtoMember(3)] public VersionOptions? VersionOptions { get; init; }
}
public static class WorkflowDefinitionServiceExtensions
{
public static Task<PagedList<WorkflowDefinition>> ListAsync(this IWorkflowDefinitionService service, int? page = default, int? pageSize = default, VersionOptions? versionOptions = default)
{
return service.ListAsync(new ListWorkflowDefinitionsRequest(page, pageSize, versionOptions));
}
}
} }

View file

@ -17,10 +17,6 @@ namespace ElsaDashboard.Shared.Surrogates
SerializerSettings = new JsonSerializerSettings().ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); SerializerSettings = new JsonSerializerSettings().ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
} }
public ActivityPropertyDescriptorSurrogate()
{
}
public ActivityPropertyDescriptorSurrogate(ActivityPropertyDescriptor value) public ActivityPropertyDescriptorSurrogate(ActivityPropertyDescriptor value)
{ {
Name = value.Name; Name = value.Name;

View file

@ -12,7 +12,8 @@ namespace ElsaDashboard.Shared.Surrogates
{ {
private static readonly IDictionary<Type, Type> SurrogateMapping = new Dictionary<Type, Type> private static readonly IDictionary<Type, Type> SurrogateMapping = new Dictionary<Type, Type>
{ {
[typeof(ActivityPropertyDescriptor)] = typeof(ActivityPropertyDescriptorSurrogate) [typeof(ActivityPropertyDescriptor)] = typeof(ActivityPropertyDescriptorSurrogate),
[typeof(VersionOptions)] = typeof(VersionOptionsSurrogate)
}; };
/// <summary> /// <summary>

View file

@ -0,0 +1,27 @@
using Elsa.Client.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NodaTime;
using NodaTime.Serialization.JsonNet;
using ProtoBuf;
namespace ElsaDashboard.Shared.Surrogates
{
[ProtoContract(IgnoreListHandling = true)]
public class VersionOptionsSurrogate
{
public VersionOptionsSurrogate()
{
}
public VersionOptionsSurrogate(VersionOptions value)
{
Value = value.ToString();
}
[ProtoMember(1)] public string Value { get; } = default!;
public static implicit operator VersionOptions(VersionOptionsSurrogate surrogate) => VersionOptions.FromString(surrogate.Value);
public static implicit operator VersionOptionsSurrogate(VersionOptions source) => new(source);
}
}

View file

@ -1,4 +1,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using AutoMapper;
using Elsa.Extensions;
using Elsa.Services; using Elsa.Services;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -13,6 +15,7 @@ namespace Elsa.Samples.HelloWorldConsole
.AddElsa() .AddElsa()
.AddConsoleActivities() .AddConsoleActivities()
.AddWorkflow<HelloWorld>() .AddWorkflow<HelloWorld>()
.AddAutoMapperProfiles<Program>()
.BuildServiceProvider(); .BuildServiceProvider();
// Run startup actions (not needed when registering Elsa with a Host). // Run startup actions (not needed when registering Elsa with a Host).

View file

@ -14,14 +14,14 @@ namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions
{ {
[ApiController] [ApiController]
[ApiVersion("1")] [ApiVersion("1")]
[Route("v{apiVersion:apiVersion}/workflow-definitions/{workflowDefinitionId}")] [Route("v{apiVersion:apiVersion}/workflow-definitions/{workflowDefinitionId}/{versionOptions}")]
[Produces("application/json")] [Produces("application/json")]
public class Get : Controller public class GetByDefinitionAndVersion : Controller
{ {
private readonly IWorkflowDefinitionManager _workflowDefinitionManager; private readonly IWorkflowDefinitionManager _workflowDefinitionManager;
private readonly IContentSerializer _serializer; private readonly IContentSerializer _serializer;
public Get(IWorkflowDefinitionManager workflowDefinitionManager, IContentSerializer serializer) public GetByDefinitionAndVersion(IWorkflowDefinitionManager workflowDefinitionManager, IContentSerializer serializer)
{ {
_workflowDefinitionManager = workflowDefinitionManager; _workflowDefinitionManager = workflowDefinitionManager;
_serializer = serializer; _serializer = serializer;
@ -33,13 +33,13 @@ namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[SwaggerOperation( [SwaggerOperation(
Summary = "Returns a single workflow definition.", Summary = "Returns a single workflow definition.",
Description = "Returns a single workflow definition using the specified workflow definition ID and optional version options. When no version options are specified, the latest version is returned.", Description = "Returns a single workflow definition using the specified workflow definition ID and version options.",
OperationId = "WorkflowDefinitions.Get", OperationId = "WorkflowDefinitions.Get",
Tags = new[] { "WorkflowDefinitions" }) Tags = new[] { "WorkflowDefinitions" })
] ]
public async Task<IActionResult> Handle(string workflowDefinitionId, [FromQuery]VersionOptions? versionOptions = default, CancellationToken cancellationToken = default) public async Task<IActionResult> Handle(string workflowDefinitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default)
{ {
var workflowDefinition = await _workflowDefinitionManager.GetAsync(workflowDefinitionId, versionOptions ?? VersionOptions.Latest, cancellationToken); var workflowDefinition = await _workflowDefinitionManager.GetAsync(workflowDefinitionId, versionOptions, cancellationToken);
return workflowDefinition == null ? (IActionResult) NotFound() : Json(workflowDefinition, _serializer.GetSettings()); return workflowDefinition == null ? (IActionResult) NotFound() : Json(workflowDefinition, _serializer.GetSettings());
} }
} }

View file

@ -0,0 +1,46 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Extensions;
using Elsa.Models;
using Elsa.Serialization;
using Elsa.Server.Api.Swagger;
using Elsa.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;
using Swashbuckle.AspNetCore.Filters;
namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions
{
[ApiController]
[ApiVersion("1")]
[Route("v{apiVersion:apiVersion}/workflow-definitions/{workflowDefinitionVersionId}")]
[Produces("application/json")]
public class GetByVersionId : Controller
{
private readonly IWorkflowDefinitionManager _workflowDefinitionManager;
private readonly IContentSerializer _serializer;
public GetByVersionId(IWorkflowDefinitionManager workflowDefinitionManager, IContentSerializer serializer)
{
_workflowDefinitionManager = workflowDefinitionManager;
_serializer = serializer;
}
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(WorkflowDefinition))]
[SwaggerResponseExample(StatusCodes.Status200OK, typeof(WorkflowDefinitionExample))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[SwaggerOperation(
Summary = "Returns a single workflow definition.",
Description = "Returns a single workflow definition using the specified workflow definition version ID.",
OperationId = "WorkflowDefinitions.Get",
Tags = new[] { "WorkflowDefinitions" })
]
public async Task<IActionResult> Handle(string workflowDefinitionVersionId, CancellationToken cancellationToken = default)
{
var workflowDefinition = await _workflowDefinitionManager.GetByVersionIdAsync(workflowDefinitionVersionId, cancellationToken);
return workflowDefinition == null ? (IActionResult) NotFound() : Json(workflowDefinition, _serializer.GetSettings());
}
}
}

View file

@ -66,7 +66,7 @@ namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions
else else
await _workflowPublisher.SaveDraftAsync(workflowDefinition, cancellationToken); await _workflowPublisher.SaveDraftAsync(workflowDefinition, cancellationToken);
return CreatedAtAction("Handle", "Get", new {workflowDefinitionId = workflowDefinition.WorkflowDefinitionId, apiVersion = apiVersion.ToString()}, workflowDefinition); return CreatedAtAction("Handle", "GetByVersionId", new {workflowDefinitionVersionId = workflowDefinition.WorkflowDefinitionVersionId, apiVersion = apiVersion.ToString()}, workflowDefinition);
} }
} }
} }