Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Text.Json.Nodes;
using Elsa.Api.Client.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Elsa.Studio.Workflows.Extensions;

Expand All @@ -14,7 +15,7 @@ public static class ActivityExtensions
/// <param name="activity"></param>
/// <returns></returns>
/// <exception cref="NotSupportedException"></exception>
public static JsonObject GetFlowchart(this JsonObject activity)
public static JsonObject? GetFlowchart(this JsonObject activity)
{
var activityTypeName = activity.GetTypeName();

Expand All @@ -24,6 +25,43 @@ public static JsonObject GetFlowchart(this JsonObject activity)
if (activityTypeName == "Elsa.Workflow")
return activity.GetRoot()!;

throw new NotSupportedException();
return null;
}
}

/// <summary>
/// Recursively looks for the first JsonObject that has an "activities" array.
/// If none is found, returns null.
/// </summary>
public static JsonObject? FindActivitiesContainer(this JsonObject activity)
{
// 1) If *this* object has an "activities" array, it is the container.
if (activity.TryGetPropertyValue("activities", out var maybeArr) && maybeArr is JsonArray)
return activity;

// 2) Otherwise, scan every child property...
foreach (var kvp in activity)
{
// �if it�s a JsonObject, recurse into it.
if (kvp.Value is JsonObject childObj)
{
var found = childObj.FindActivitiesContainer();
if (found != null)
return found;
}

// �if it�s a JsonArray, recurse into each item.
if (kvp.Value is JsonArray childArr)
{
foreach (var node in childArr.OfType<JsonObject>())
{
var found = node.FindActivitiesContainer();
if (found != null)
return found;
}
}
}

// 3) No container found.
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public static async Task<IDictionary<string, ActivityNode>> VisitAndMapAsync(thi
{
var graph = await visitor.VisitAsync(activity);
var nodes = graph.Flatten();
return nodes.ToDictionary(x => x.NodeId);
return nodes.GroupBy(n => n.NodeId).Select(g => g.First()).ToDictionary(x => x.NodeId);
}

/// Creates an activity graph based on the provided workflow definition.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Text.Json.Nodes;
using System.Text.Json.Nodes;
using Elsa.Api.Client.Extensions;
using Elsa.Studio.Workflows.Domain.Contracts;
using Elsa.Studio.Workflows.Domain.Models;
Expand All @@ -17,17 +17,57 @@ public class DefaultActivityResolver : IActivityResolver
public bool GetSupportsActivity(JsonObject activity) => true;

/// <inheritdoc />
public ValueTask<IEnumerable<EmbeddedActivity>> GetActivitiesAsync(JsonObject activity, CancellationToken cancellationToken = default)
public ValueTask<IEnumerable<EmbeddedActivity>> GetActivitiesAsync(
JsonObject activity,
CancellationToken cancellationToken = default
)
{
var containedActivities =
from prop in activity
where prop.Value is JsonObject jsonObject && jsonObject.IsActivity() || prop.Value is JsonArray
let isCollection = prop.Value is JsonArray
let containedItems = isCollection ? ((JsonArray)prop.Value).ToArray() : new[] { prop.Value.AsObject() }
from containedItem in containedItems where containedItem is JsonObject && containedItem.AsObject().IsActivity()
let containedObject = containedItem.AsObject()
select new EmbeddedActivity(containedObject, prop.Key);

return new(containedActivities.ToList());
var embedded = new List<EmbeddedActivity>();

foreach (var (propName, node) in activity)
{
switch (node)
{
// 1) direct child activity:
case JsonObject childObj when childObj.IsActivity():
embedded.Add(new EmbeddedActivity(childObj, propName));
break;

// 2) array of activities:
case JsonArray arr:
foreach (var item in arr.OfType<JsonObject>())
{
// e.g. expectedStatusCodes: pick up item.activity
if (
item.TryGetPropertyValue("activity", out var actNode)
&& actNode is JsonObject actObj
&& actObj.IsActivity()
)
{
embedded.Add(new EmbeddedActivity(actObj, propName));
}
else if (item.IsActivity())
{
embedded.Add(new EmbeddedActivity(item, propName));
}
}
break;
}

// 3) special‑case any nested Flowchart under "body":
if (
node is JsonObject container
&& container.TryGetPropertyValue("body", out var bodyNode)
&& bodyNode is JsonObject bodyObj
&& bodyObj.TryGetPropertyValue("activities", out var activitiesNode)
&& activitiesNode is JsonArray childActivities
)
{
foreach (var child in childActivities.OfType<JsonObject>())
embedded.Add(new EmbeddedActivity(child, propName));
}
}

return new ValueTask<IEnumerable<EmbeddedActivity>>(embedded);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -224,16 +224,42 @@ public async Task<JsonObject> ReadFlowchartAsync()
/// </summary>
/// <param name="activity">The flowchart to load.</param>
/// <param name="activityStats">The activity stats to load.</param>
public async Task LoadFlowchartAsync(JsonObject activity, IDictionary<string, ActivityStats>? activityStats)
public async Task LoadFlowchartAsync(
JsonObject activity,
IDictionary<string, ActivityStats>? activityStats
)
{
Flowchart = activity;
ActivityStats = activityStats;

// 2) Get the mapper as before
var flowchartMapper = await GetFlowchartMapperAsync();
var flowchart = activity.GetFlowchart();
var graph = flowchartMapper.Map(flowchart, activityStats);

// 3) Instead of unwrapping only Elsa.Flowchart, find any container
// with an 'activities' array (or fall back to a synthetic one)
var container = activity.FindActivitiesContainer() ?? CreateSyntheticContainer(activity);

// 4) Map and schedule the graph load
var graph = flowchartMapper.Map(container, activityStats);
await ScheduleGraphActionAsync(() => _graphApi.LoadGraphAsync(graph));
}

// Helper: wrap a single activity in an 'activities' array
// Helper: wrap a single activity in an 'activities' array
// by cloning it so it has no existing parent.
private JsonObject CreateSyntheticContainer(JsonObject single)
{
// Make a copy so we don't reparent the original
var cloned = (JsonObject)single.DeepClone()!;

// Put the clone into a brand-new array
var arr = new JsonArray();
arr.Add(cloned);

// Wrap it up
return new JsonObject { ["activities"] = arr };
}

/// <summary>
/// Adds an activity to the graph.
/// </summary>
Expand Down Expand Up @@ -263,10 +289,15 @@ public async Task SelectActivityAsync(string id)
public async Task CenterContentAsync() => await ScheduleGraphActionAsync(() => _graphApi.CenterContentAsync());

/// Update the Graph Layout.
public async Task AutoLayoutAsync(JsonObject activity, IDictionary<string, ActivityStats>? activityStats)
public async Task AutoLayoutAsync(
JsonObject activity,
IDictionary<string, ActivityStats>? activityStats
)
{
var flowchartMapper = await GetFlowchartMapperAsync();
var flowchart = activity.GetFlowchart();
if (flowchart == null)
return;
var graph = flowchartMapper.Map(flowchart, activityStats);
await ScheduleGraphActionAsync(() => _graphApi.AutoLayoutAsync(graph));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
WorkflowDefinitionVersionId="@WorkflowDefinition.Id"
Activity="RootActivity"
ActivitySelected="OnActivitySelected"
IsReadOnly="true"
IsReadOnly="false"
WorkflowInstanceId="@WorkflowInstance.Id"
PathChanged="PathChanged">
<CustomToolbarItems>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Elsa.Api.Client.Shared.Models;
using Elsa.Studio.Workflows.Designer.Components;
using Elsa.Studio.Workflows.Domain.Contracts;
using Elsa.Studio.Workflows.Extensions;
using Elsa.Studio.Workflows.Models;
using Elsa.Studio.Workflows.UI.Args;
using Elsa.Studio.Workflows.UI.Models;
Expand Down Expand Up @@ -60,19 +61,34 @@ public partial class FlowchartDesignerWrapper
[Inject] private IActivityNameGenerator ActivityNameGenerator { get; set; } = default!;
private FlowchartDesigner Designer { get; set; } = default!;

private string? _lastFlowchartId;

/// <summary>
/// Loads the specified flowchart activity into the designer.
/// </summary>
/// <param name="activity">The flowchart activity to load.</param>
/// <param name="activityStats">A map of activity stats.</param>
public async Task LoadFlowchartAsync(JsonObject activity, IDictionary<string, ActivityStats>? activityStats = default)
public async Task LoadFlowchartAsync(
JsonObject activity,
IDictionary<string, ActivityStats>? activityStats = default
)
{
if (activity.GetTypeName() != "Elsa.Flowchart")
throw new ArgumentException("Activity must be an Elsa.Flowchart", nameof(activity));
// 1) Unwrap any container to the real Elsa.Flowchart
var flowchart = activity.GetFlowchart() ?? activity.FindActivitiesContainer();

Flowchart = activity;
if (flowchart == null)
return;
// 2) Bail out if it's the exact same chart we already have
var id = flowchart.GetId();
if (id == _lastFlowchartId)
return;

// 3) Otherwise record and hand off
_lastFlowchartId = id;
Flowchart = flowchart;
ActivityStats = activityStats;
await Designer.LoadFlowchartAsync(activity, activityStats);

await Designer.LoadFlowchartAsync(flowchart, activityStats);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ private RenderFragment DisplayToolboxItem(string title, string icon, string desc

private async Task InvokeDesignerActionAsync(Func<FlowchartDesignerWrapper, Task> action)
{
if (_designerWrapper != null) await action(_designerWrapper);
if (_designerWrapper != null && action != null)
await action(_designerWrapper);
}

private Task OnZoomToFitClicked() => _designerWrapper != null ? _designerWrapper.ZoomToFitAsync() : Task.CompletedTask;
Expand Down
Loading