Skip to content

Commit

Permalink
Incremental work on connecting activities
Browse files Browse the repository at this point in the history
  • Loading branch information
sfmskywalker committed Nov 16, 2020
1 parent 4794352 commit acf884c
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 29 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,16 @@ Version 1.0

Version 2.0

- [x] Container Activities
- [x] Composite Activities API
- [x] Service Bus Messaging
- [ ] Generic Command & Event Activities
- [x] Workflow Host REST API
- [ ] Workflow Host gRPC API
- [x] Workflow Server
- [x] Distributed Hosting Support (support for multi-node environments)
- [ ] New Workflow Designer + Dashboard
- [ ] Generic Command & Event Activities

Version 3.0
- [ ] Composite Activity Definitions (with designer support)
- [ ] Localization Support
- [ ] State Machines
- [ ] Sagas
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ namespace Elsa.Activities.Workflows
{
[Activity(
Category = "Workflows",
Description = "Runs a child workflow."
Description = "Runs a child workflow.",
Outcomes = new[] { OutcomeNames.Done }
)]
public class RunWorkflow : Activity
{
Expand Down Expand Up @@ -51,11 +52,11 @@ protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(Acti

protected override IActivityExecutionResult OnResume(ActivityExecutionContext context)
{
var input = (FinishedWorkflowModel)context.WorkflowExecutionContext.Input!;
var input = (FinishedWorkflowModel) context.WorkflowExecutionContext.Input!;
var childWorkflowIds = ChildWorkflowInstanceIds;
childWorkflowIds.Remove(input.WorkflowInstanceId);
ChildWorkflowInstanceIds = childWorkflowIds;
return childWorkflowIds.Any() ? (IActivityExecutionResult)Suspend() : Done();
return childWorkflowIds.Any() ? (IActivityExecutionResult) Suspend() : Done();
}

public enum RunWorkflowMode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ namespace ElsaDashboard.Application.Server
{
public class Startup
{
public Startup(IConfiguration configuration)
public Startup(IConfiguration configuration, IHostEnvironment environment)
{
Configuration = configuration;
Environment = environment;
}

public IConfiguration Configuration { get; }
public IHostEnvironment Environment { get; }

public void ConfigureServices(IServiceCollection services)
{
Expand All @@ -24,7 +26,11 @@ public void ConfigureServices(IServiceCollection services)
services.AddElsaDashboardBackend(options => options.ServerUrl = new Uri("https://localhost:11000"));

if (Program.UseBlazorServer)
services.AddServerSideBlazor();
services.AddServerSideBlazor(options =>
{
options.DetailedErrors = !Environment.IsProduction();
options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds(30);
});
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static IEnumerable<ActivityModel> GetChildActivities(this WorkflowModel w
{
var targetIds = workflowModel.Connections.Where(x => x.SourceId == parentId).Select(x => x.TargetId).Distinct().ToLookup(x => x);
var children = workflowModel.Activities.Where(x => targetIds.Contains(x.ActivityId)).ToList();

return children;
}
}
Expand All @@ -31,5 +31,38 @@ public static IEnumerable<ActivityModel> GetLeafActivities(this WorkflowModel wo

return leaves;
}

public static IEnumerable<ConnectionModel> GetInboundConnections(this WorkflowModel workflowBlueprint, string activityId) => workflowBlueprint.Connections.Where(x => x.TargetId == activityId).ToList();

public static IEnumerable<ConnectionModel> GetOutboundConnections(this WorkflowModel workflowModel, string activityId) => workflowModel.Connections.Where(x => x.SourceId == activityId).ToList();

/// <summary>
/// Returns the full path of incoming activities.
/// </summary>
public static IEnumerable<string> GetInboundActivityPath(this WorkflowModel workflowModel, string activityId)
{
var inspectedActivityIds = new HashSet<string>();

return workflowModel.GetInboundActivityPathInternal(activityId, inspectedActivityIds)
.Distinct().ToList();
}

private static IEnumerable<string> GetInboundActivityPathInternal(this WorkflowModel workflowModel, string activityId, HashSet<string> inspectedActivityIds)
{
foreach (var connection in workflowModel.GetInboundConnections(activityId))
{
// Circuit breaker: Detect workflows that implement repeating flows to prevent an infinite loop here.
if (inspectedActivityIds.Contains(connection.SourceId))
yield break;

yield return connection.SourceId;

foreach (var parentActivityId in workflowModel.GetInboundActivityPathInternal(connection.SourceId, inspectedActivityIds).Distinct())
{
inspectedActivityIds.Add(parentActivityId);
yield return parentActivityId;
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@ public static WorkflowModel Demo() =>
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)};
public WorkflowModel AddConnection(string sourceId, string targetId, string outcome) => AddConnection(new ConnectionModel(sourceId, targetId, outcome));
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div x-data="{ open: false }"
x-on:keydown.escape="open = false"
x-on:click.away="open = false"
id="@Model.ActivityId"
id="activity-@Model.ActivityId"
class="activity border-2 border-solid border-white rounded bg-white text-left text-black text-lg hover:border-blue-600 select-none max-w-md shadow-sm"
@onclick="OnClick">
<div class="p-5 border-b border-b-solid">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
@using ElsaDashboard.Application.Extensions

@{
var renderedActivities = new HashSet<string>();
}

<div class="flex flex-1 relative">
<div id="workflow-canvas" class="flex-1 flex">
<div class="flex-1 text-gray-200">
Expand All @@ -17,7 +21,7 @@
Start
</button>
</div>
@RenderTree(GetRootActivities(), true, OnAddActivityClick)
@RenderTree(GetRootActivities(), true, OnAddActivityClick, renderedActivities)
</li>
</ul>
</div>
Expand All @@ -30,7 +34,7 @@

@code {

private RenderFragment RenderAnchorButton(string id, string outcome, Func<Task> callback) =>
private RenderFragment RenderOutcomeButton(string id, string outcome, Func<Task> callback) =>
@<div class="my-6 flex flex-col items-center">
@if (!string.IsNullOrEmpty(outcome))
{
Expand All @@ -51,16 +55,19 @@
@<div class="flex flex-row space-x-6">
@foreach (var outcome in activity.Outcomes)
{
@RenderAnchorButton($"{activityId}-{outcome}", outcome, () => onAddActivityClickHandler(activityId, null, outcome))
@RenderOutcomeButton($"{activityId}-{outcome}", outcome, () => onAddActivityClickHandler(activityId, null, outcome))
}
</div>;
}

private RenderFragment RenderTree(IEnumerable<ActivityModel> activities, bool isRoot, Func<string, string, string, Task> onAddActivityClickHandler)
private RenderFragment RenderTree(IEnumerable<ActivityModel> activities, bool isRoot, Func<string, string, string, Task> onAddActivityClickHandler, HashSet<string> renderedActivities)
{
var list = activities.ToList();
var list = activities.Where(x => !renderedActivities.Contains(x.ActivityId)).ToList();
var cssClass = isRoot ? "root" : default!;

foreach (var activity in list)
renderedActivities.Add(activity.ActivityId);

return
@<text>
@if (list.Any())
Expand All @@ -71,20 +78,20 @@
var activityId = activity.ActivityId;
var children = Model.GetChildActivities(activityId).ToList();

<li @key=@activity.ActivityId>
<li @key=@activityId>
<div class="inline-flex flex flex-col items-center">

@if (isRoot)
{
@RenderAnchorButton($"start-button-plus-{activityId}", "", () => onAddActivityClickHandler(null, activityId, null))
@RenderOutcomeButton($"start-button-plus-{activityId}", "", () => onAddActivityClickHandler(null, activityId, null))
}

<Activity Model="@activity"/>
@RenderOutcomeButtons(activity, onAddActivityClickHandler)
</div>
@if (children.Any())
{
@RenderTree(children, false, onAddActivityClickHandler)
@RenderTree(children, false, onAddActivityClickHandler, renderedActivities)
}
</li>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,20 @@ namespace ElsaDashboard.Application.Shared
{
partial class WorkflowDesigner : IAsyncDisposable
{
[Parameter] public WorkflowModel Model { private get; set; } = WorkflowModel.Blank();
private static Action<ConnectionModel> _connectionCreatedAction = default!;

[Parameter] public WorkflowModel Model { private get; set; } = WorkflowModel.Demo();
[Inject] private IJSRuntime JS { get; set; } = default!;
[Inject] private IFlyoutPanelService FlyoutPanelService { get; set; } = default!;
private IJSObjectReference _designerModule = default!;
private bool _connectionsChanged = true;
private EventCallbackFactory EventCallbackFactory { get; } = new();

[JSInvokableAttribute("InvokeConnectionCreated")]
public static void InvokeConnectionCreated(ConnectionModel connection) => _connectionCreatedAction(connection);

protected override void OnInitialized() => _connectionCreatedAction = OnConnectionCreated;

public async ValueTask DisposeAsync()
{
if (_designerModule != null!)
Expand All @@ -48,6 +55,14 @@ private async ValueTask RepaintConnections()

_connectionsChanged = false;

var connections = GetJsPlumbConnections();
var sourceEndpoints = GetJsPlumbSourceEndpoints();
var targets = GetJsPlumbTargets();
await _designerModule.InvokeVoidAsync("updateConnections", (object) connections, (object) sourceEndpoints, (object) targets);
}

private IEnumerable<object> GetJsPlumbConnections()
{
var rootActivities = Model.GetChildActivities(null).ToList();

var rootConnections = rootActivities.SelectMany(x =>
Expand All @@ -57,37 +72,75 @@ private async ValueTask RepaintConnections()
new
{
sourceId = "start-button",
targetId = $"start-button-plus-{x.ActivityId}"
sourceActivityId = default(string)!,
targetId = $"start-button-plus-{x.ActivityId}",
targetActivityId = x.ActivityId,
outcome = default(string)!
},
new
{
sourceId = $"start-button-plus-{x.ActivityId}",
targetId = x.ActivityId
sourceActivityId = default(string)!,
targetId = $"activity-{x.ActivityId}",
targetActivityId = x.ActivityId,
outcome = default(string)!
},
};
});

// Connections from activity outcome to anchor:
var sourceConnections = Model.Activities.SelectMany(activity => activity.Outcomes.Select(x =>
new
{
sourceId = activity.ActivityId,
targetId = $"{activity.ActivityId}-{x}"
sourceId = $"activity-{activity.ActivityId}",
sourceActivityId = activity.ActivityId,
targetId = $"{activity.ActivityId}-{x}",
targetActivityId = default(string)!,
outcome = x
}));

var connections = Model.Connections.SelectMany(x => new[]
{
new
{
sourceId = $"{x.SourceId}-{x.Outcome}",
targetId = x.TargetId
sourceActivityId = x.SourceId,
targetId = $"activity-{x.TargetId}",
targetActivityId = x.TargetId!,
outcome = x.Outcome
},
});

var allConnections = rootConnections.Concat(connections).Concat(sourceConnections);
await _designerModule.InvokeVoidAsync("updateConnections", (object) allConnections);
return rootConnections.Concat(connections).Concat(sourceConnections);
}

private IEnumerable<object> GetJsPlumbSourceEndpoints()
{
var rootActivities = Model.GetChildActivities(null).ToList();

var rootSourceEndpoints = rootActivities.Select(x => new
{
sourceId = $"start-button-plus-{x.ActivityId}",
sourceActivityId = x.ActivityId,
outcome = default(string)!
});

var otherSourceEndpoints = Model.Activities.SelectMany(activity => activity.Outcomes.Select(x => new
{
sourceId = $"{activity.ActivityId}-{x}",
sourceActivityId = activity.ActivityId,
outcome = x
}));

return rootSourceEndpoints.Concat(otherSourceEndpoints);
}

private IEnumerable<object> GetJsPlumbTargets() =>
Model.Activities.Select(x => new
{
targetId = $"activity-{x.ActivityId}",
targetActivityId = x.ActivityId
});

private async Task ClosePanelAsync() => await FlyoutPanelService.HideAsync();

private async Task ShowActivityPickerAsync(string? parentActivityId, string? targetActivityId, string? outcome)
Expand All @@ -100,6 +153,8 @@ await FlyoutPanelService.ShowAsync<ActivityPicker>(

private async Task AddActivityAsync(ActivityDescriptor activityDescriptor, string? sourceActivityId, string? targetActivityId, string? outcome)
{
outcome ??= "Done";

var activity = new ActivityModel(Guid.NewGuid().ToString("N"), activityDescriptor.Type, activityDescriptor.Outcomes);
var model = Model.AddActivity(activity);

Expand All @@ -115,8 +170,7 @@ private async Task AddActivityAsync(ActivityDescriptor activityDescriptor, strin
}
else
{
var connection = new ConnectionModel(activity.ActivityId, targetActivityId, outcome!);
model = model.AddConnection(connection);
model = model.AddConnection(activity.ActivityId, targetActivityId, outcome);
}
}

Expand Down Expand Up @@ -168,5 +222,11 @@ private async ValueTask OnActivityClick(MouseEventArgs e)
{
await FlyoutPanelService.ShowAsync<ActivityEditor>("Timer Properties");
}

private void OnConnectionCreated(ConnectionModel connection)
{
Model = Model.AddConnection(connection);
ConnectionsHasChanged();
}
}
}

0 comments on commit acf884c

Please sign in to comment.