Remove Trigger separation

This commit is contained in:
Sipke Schoorstra 2022-03-04 21:42:27 +01:00
parent c600399f0d
commit 161a70e429
50 changed files with 128 additions and 994 deletions

View file

@ -1,32 +0,0 @@
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using Elsa.Management.Contracts;
using Elsa.Management.Serialization.Converters;
using Microsoft.AspNetCore.Http;
namespace Elsa.Api.Endpoints.TriggerDescriptors;
public static partial class TriggerDescriptors
{
public static IResult ListAsync(ITriggerRegistry registry, IWellKnownTypeRegistry wellKnownTypeRegistry)
{
var descriptors = registry.ListAll().ToList();
var model = new
{
TriggerDescriptors = descriptors
};
return Results.Json(model, new JsonSerializerOptions
{
Converters =
{
new JsonStringEnumConverter(),
new TypeJsonConverter(wellKnownTypeRegistry)
},
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault
});
}
}

View file

@ -1,32 +0,0 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
namespace Elsa.Api.Endpoints.TriggerDescriptors;
public class TriggerDescriptorsResource
{
private readonly IEndpointRouteBuilder _endpoints;
public TriggerDescriptorsResource(IEndpointRouteBuilder endpoints)
{
_endpoints = endpoints;
}
public TriggerDescriptorsResource List()
{
_endpoints.MapGet("api/descriptors/triggers", TriggerDescriptors.ListAsync);
return this;
}
}
public static class TriggerDescriptorsResourceExtensions
{
public static IEndpointRouteBuilder MapTriggerDescriptors(this IEndpointRouteBuilder endpoints, Action<TriggerDescriptorsResource> configureResource)
{
configureResource(new(endpoints));
return endpoints;
}
public static IEndpointRouteBuilder MapTriggerDescriptors(this IEndpointRouteBuilder endpoints) => endpoints.MapTriggerDescriptors(x => x.List());
}

View file

@ -17,7 +17,6 @@ public static partial class Workflows
string? Name,
string? Description,
IActivity? Root,
ICollection<ITrigger>? Triggers,
bool Publish);
public static async Task<IResult> PostAsync(
@ -49,12 +48,10 @@ public static partial class Workflows
// Update the draft with the received model.
var root = model.Root ?? new Sequence();
var triggers = model.Triggers ?? new List<ITrigger>();
draft = draft with
{
Root = root,
Triggers = triggers,
Metadata = draft.Metadata with
{
Name = model.Name,

View file

@ -1,6 +1,5 @@
using Elsa.Api.Endpoints.ActivityDescriptors;
using Elsa.Api.Endpoints.Events;
using Elsa.Api.Endpoints.TriggerDescriptors;
using Elsa.Api.Endpoints.WorkflowInstances;
using Elsa.Api.Endpoints.Workflows;
using Microsoft.AspNetCore.Routing;
@ -13,6 +12,5 @@ public static class EndpointRouteBuilderExtensions
.MapWorkflows()
.MapWorkflowInstances()
.MapActivityDescriptors()
.MapTriggerDescriptors()
.MapEvents();
}

View file

@ -10,7 +10,6 @@ public class WorkflowDefinitionBuilder : IWorkflowDefinitionBuilder
public string? DefinitionId { get; private set; }
public int Version { get; private set; } = 1;
public IActivity? Root { get; private set; }
public ICollection<ITrigger> Triggers { get; } = new List<ITrigger>();
public ICollection<Variable> Variables { get; set; } = new List<Variable>();
public IWorkflowDefinitionBuilder WithId(string id)
@ -37,12 +36,6 @@ public class WorkflowDefinitionBuilder : IWorkflowDefinitionBuilder
return this;
}
public IWorkflowDefinitionBuilder AddTrigger(ITrigger trigger)
{
Triggers.Add(trigger);
return this;
}
public Workflow BuildWorkflow()
{
var definitionId = DefinitionId ?? Guid.NewGuid().ToString("N");
@ -51,6 +44,6 @@ public class WorkflowDefinitionBuilder : IWorkflowDefinitionBuilder
var identity = new WorkflowIdentity(definitionId, Version, id);
var publication = WorkflowPublication.LatestAndPublished;
var metadata = new WorkflowMetadata();
return new Workflow(identity, publication, metadata, root, Triggers, Variables);
return new Workflow(identity, publication, metadata, root, Variables);
}
}

View file

@ -2,7 +2,22 @@ using Elsa.Models;
namespace Elsa.Contracts;
/// <summary>
/// Activities that act as a trigger should implement this interface.
/// </summary>
public interface ITrigger : INode
{
ValueTask<IEnumerable<object>> GetPayloadsAsync(TriggerIndexingContext context, CancellationToken cancellationToken = default);
/// <summary>
/// A value indicating whether this trigger can create a new workflow instances when triggered, or execute the workflow instance this trigger belongs to.
/// </summary>
TriggerMode TriggerMode { get; set; }
/// <summary>
/// Implementing triggers should return zero or more objects that represent a lookup value.
/// </summary>
///<example>
/// For example, if your trigger can be configured with a signal name to listen for, you would return an object with said signal name as a field.
/// Your auxiliary services would then use this signal name to create a hash and use it to lookup a trigger stored in the database by this hash.
/// </example>
ValueTask<IEnumerable<object>> GetTriggerPayloadsAsync(TriggerIndexingContext context, CancellationToken cancellationToken = default);
}

View file

@ -7,11 +7,9 @@ public interface IWorkflowDefinitionBuilder
string? DefinitionId { get; }
int Version { get; }
IActivity? Root { get; }
ICollection<ITrigger> Triggers { get; }
ICollection<Variable> Variables { get; set; }
IWorkflowDefinitionBuilder WithDefinitionId(string definitionId);
IWorkflowDefinitionBuilder WithVersion(int version);
IWorkflowDefinitionBuilder WithRoot(IActivity root);
IWorkflowDefinitionBuilder AddTrigger(ITrigger trigger);
Workflow BuildWorkflow();
}

View file

@ -1,22 +1,36 @@
using Elsa.Contracts;
using Elsa.Helpers;
namespace Elsa.Models;
public class Trigger : ITrigger
public abstract class Trigger : Activity, ITrigger
{
protected Trigger() => NodeType = TypeNameHelper.GenerateTypeName(GetType());
protected Trigger(string triggerType) => NodeType = triggerType;
public string Id { get; set; } = default!;
public string NodeType { get; set; }
public IDictionary<string, object> Metadata { get; set; } = new Dictionary<string, object>();
public virtual ValueTask<IEnumerable<object>> GetPayloadsAsync(TriggerIndexingContext context, CancellationToken cancellationToken = default)
protected Trigger()
{
var hashes = GetPayloads(context);
}
protected Trigger(string triggerType) : base(triggerType)
{
}
public TriggerMode TriggerMode { get; set; } = TriggerMode.WorkflowDefinition;
ValueTask<IEnumerable<object>> ITrigger.GetTriggerPayloadsAsync(TriggerIndexingContext context, CancellationToken cancellationToken) => GetTriggerPayloadsAsync(context, cancellationToken);
/// <summary>
/// Override this method to return a list of trigger payloads.
/// </summary>
protected virtual ValueTask<IEnumerable<object>> GetTriggerPayloadsAsync(TriggerIndexingContext context, CancellationToken cancellationToken = default)
{
var hashes = GetTriggerPayloads(context);
return ValueTask.FromResult(hashes);
}
protected virtual IEnumerable<object> GetPayloads(TriggerIndexingContext context) => Enumerable.Empty<object>();
/// <summary>
/// Override this method to return a list of trigger payloads.
/// </summary>
protected virtual IEnumerable<object> GetTriggerPayloads(TriggerIndexingContext context) => new[]{ GetTriggerPayload(context) };
/// <summary>
/// Override this method to return a trigger payload.
/// </summary>
protected virtual object GetTriggerPayload(TriggerIndexingContext context) => new();
}

View file

@ -1,41 +0,0 @@
using Elsa.Contracts;
namespace Elsa.Models;
public class TriggerActivity : Activity, ITrigger
{
protected TriggerActivity()
{
}
protected TriggerActivity(string triggerType) : base(triggerType)
{
}
public string TriggerType
{
get => NodeType;
set => NodeType = value;
}
ValueTask<IEnumerable<object>> ITrigger.GetPayloadsAsync(TriggerIndexingContext context, CancellationToken cancellationToken) => GetPayloadsAsync(context, cancellationToken);
/// <summary>
/// Override this method to return a list of trigger payloads.
/// </summary>
protected virtual ValueTask<IEnumerable<object>> GetPayloadsAsync(TriggerIndexingContext context, CancellationToken cancellationToken = default)
{
var hashes = GetPayloads(context);
return ValueTask.FromResult(hashes);
}
/// <summary>
/// Override this method to return a list of trigger payloads.
/// </summary>
protected virtual IEnumerable<object> GetPayloads(TriggerIndexingContext context) => new[]{ GetPayload(context) };
/// <summary>
/// Override this method to return a trigger payloads.
/// </summary>
protected virtual object GetPayload(TriggerIndexingContext context) => new();
}

View file

@ -0,0 +1,17 @@
namespace Elsa.Models;
/// <summary>
/// A set of trigger modes.
/// </summary>
public enum TriggerMode
{
/// <summary>
/// The trigger will create a new workflow instance and execute said instance.
/// </summary>
WorkflowDefinition,
/// <summary>
/// The trigger will schedule the trigger for execution within the context of the workflow instance.
/// </summary>
WorkflowInstance
}

View file

@ -7,10 +7,9 @@ public record Workflow(
WorkflowPublication Publication,
WorkflowMetadata Metadata,
IActivity Root,
ICollection<ITrigger> Triggers,
ICollection<Variable> Variables)
{
public static Workflow FromActivity(IActivity root) => new(WorkflowIdentity.VersionOne, WorkflowPublication.LatestDraft, new WorkflowMetadata(), root, new List<ITrigger>(), new List<Variable>());
public static Workflow FromActivity(IActivity root) => new(WorkflowIdentity.VersionOne, WorkflowPublication.LatestDraft, new WorkflowMetadata(), root, new List<Variable>());
public Workflow WithVersion(int version) => this with { Identity = Identity with { Version = version } };
public Workflow IncrementVersion() => WithVersion(Identity.Version + 1);

View file

@ -13,22 +13,8 @@ public class IdentityGraphService : IIdentityGraphService
_activityWalker = activityWalker;
}
public void AssignIdentities(Workflow workflow)
{
AssignIdentities(workflow.Root);
public void AssignIdentities(Workflow workflow) => AssignIdentities(workflow.Root);
var triggers = workflow.Triggers;
if (triggers == null)
return;
foreach (var trigger in triggers)
{
if(trigger is IActivity activity)
AssignIdentities(activity);
}
}
public void AssignIdentities(IActivity root)
{
var graph = _activityWalker.Walk(root);

View file

@ -35,5 +35,4 @@ public static class ServiceCollectionExtensions
public static IServiceCollection ConfigureApiOptions(this IServiceCollection services, Action<ApiOptions> configure) => services.Configure(configure);
public static IServiceCollection AddActivity<T>(this IServiceCollection services) where T:IActivity => services.ConfigureApiOptions(options => options.ActivityTypes.Add(typeof(T)));
public static IServiceCollection AddTrigger<T>(this IServiceCollection services) where T:ITrigger => services.ConfigureApiOptions(options => options.TriggerTypes.Add(typeof(T)));
}

View file

@ -6,9 +6,4 @@ public class ApiOptions
/// A collection of activity types made available from the API.
/// </summary>
public HashSet<Type> ActivityTypes { get; set; } = new();
/// <summary>
/// A collection of trigger types made available from the API.
/// </summary>
public HashSet<Type> TriggerTypes { get; set; } = new();
}

View file

@ -34,7 +34,6 @@ namespace Elsa.Management.Services
WorkflowPublication.LatestDraft,
new WorkflowMetadata(CreatedAt: _systemClock.UtcNow),
new Sequence(),
new List<ITrigger>(),
new List<Variable>());
}

View file

@ -76,5 +76,4 @@ public static class ServiceCollectionExtensions
public static IServiceCollection AddStimulusHandler<T>(this IServiceCollection services) where T : class, IStimulusHandler => services.AddSingleton<IStimulusHandler, T>();
public static IServiceCollection AddInstructionInterpreter<T>(this IServiceCollection services) where T : class, IWorkflowInstructionInterpreter => services.AddSingleton<IWorkflowInstructionInterpreter, T>();
public static IServiceCollection ConfigureWorkflowRuntime(this IServiceCollection services, Action<WorkflowRuntimeOptions> configure) => services.Configure(configure);
public static IServiceCollection IndexWorkflowTriggers(this IServiceCollection services) => services.AddHostedService<IndexWorkflowTriggers>();
}

View file

@ -5,7 +5,7 @@
* It contains typing information for all components that exist in this project.
*/
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
import { ActionDefinition, ActionInvokedArgs, Activity, ActivityDescriptor, ActivitySelectedArgs, ContainerSelectedArgs, GraphUpdatedArgs, IntellisenseContext, SelectListItem, TabChangedArgs, TabDefinition, Trigger, TriggerDescriptor, Workflow, WorkflowInstance, WorkflowInstanceSummary, WorkflowSummary } from "./models";
import { ActionDefinition, ActionInvokedArgs, Activity, ActivityDescriptor, ActivitySelectedArgs, ContainerSelectedArgs, GraphUpdatedArgs, IntellisenseContext, SelectListItem, TabChangedArgs, TabDefinition, Workflow, WorkflowInstance, WorkflowInstanceSummary, WorkflowSummary } from "./models";
import { ActivityUpdatedArgs, DeleteActivityRequestedArgs } from "./components/designer/activity-properties-editor/activity-properties-editor";
import { ContainerActivityComponent } from "./components/activities/container-activity-component";
import { AddActivityArgs } from "./components/designer/canvas/canvas";
@ -18,8 +18,6 @@ import { ExpressionChangedArs } from "./components/designer/input-control-switch
import { MonacoLib, MonacoValueChangedArgs } from "./components/shared/monaco-editor/monaco-editor";
import { PagerData } from "./components/shared/pager/pager";
import { PanelPosition, PanelStateChangedArgs } from "./components/designer/panel/models";
import { TriggerDeselectedArgs, TriggerSelectedArgs, TriggersUpdatedArgs } from "./components/designer/trigger-container/trigger-container";
import { DeleteTriggerRequestedArgs, TriggerUpdatedArgs } from "./components/designer/trigger-properties-editor/trigger-properties-editor";
import { WorkflowUpdatedArgs } from "./components/designer/workflow-editor/workflow-editor";
import { ActivityDriverRegistry } from "./services";
import { WorkflowPropsUpdatedArgs } from "./components/designer/workflow-properties-editor/workflow-properties-editor";
@ -155,24 +153,6 @@ export namespace Components {
interface ElsaToolboxActivities {
"activityDescriptors": Array<ActivityDescriptor>;
"graph": Graph;
"triggerDescriptors": Array<TriggerDescriptor>;
}
interface ElsaToolboxTriggers {
"activityDescriptors": Array<ActivityDescriptor>;
"graph": Graph;
"triggerDescriptors": Array<TriggerDescriptor>;
}
interface ElsaTriggerContainer {
"deselectAll": () => Promise<void>;
"interactiveMode": boolean;
"triggerDescriptors": Array<TriggerDescriptor>;
"triggers": Array<Trigger>;
}
interface ElsaTriggerPropertiesEditor {
"hide": () => Promise<void>;
"show": () => Promise<void>;
"trigger"?: Trigger;
"triggerDescriptors": Array<TriggerDescriptor>;
}
interface ElsaWorkflowBrowser {
"hide": () => Promise<void>;
@ -186,7 +166,6 @@ export namespace Components {
"importWorkflowMetadata": (workflow: Workflow) => Promise<void>;
"monacoLibPath": string;
"registerActivityDrivers": (register: (registry: ActivityDriverRegistry) => void) => Promise<void>;
"triggerDescriptors": Array<TriggerDescriptor>;
}
interface ElsaWorkflowInstanceBrowser {
"hide": () => Promise<void>;
@ -356,24 +335,6 @@ declare global {
prototype: HTMLElsaToolboxActivitiesElement;
new (): HTMLElsaToolboxActivitiesElement;
};
interface HTMLElsaToolboxTriggersElement extends Components.ElsaToolboxTriggers, HTMLStencilElement {
}
var HTMLElsaToolboxTriggersElement: {
prototype: HTMLElsaToolboxTriggersElement;
new (): HTMLElsaToolboxTriggersElement;
};
interface HTMLElsaTriggerContainerElement extends Components.ElsaTriggerContainer, HTMLStencilElement {
}
var HTMLElsaTriggerContainerElement: {
prototype: HTMLElsaTriggerContainerElement;
new (): HTMLElsaTriggerContainerElement;
};
interface HTMLElsaTriggerPropertiesEditorElement extends Components.ElsaTriggerPropertiesEditor, HTMLStencilElement {
}
var HTMLElsaTriggerPropertiesEditorElement: {
prototype: HTMLElsaTriggerPropertiesEditorElement;
new (): HTMLElsaTriggerPropertiesEditorElement;
};
interface HTMLElsaWorkflowBrowserElement extends Components.ElsaWorkflowBrowser, HTMLStencilElement {
}
var HTMLElsaWorkflowBrowserElement: {
@ -442,9 +403,6 @@ declare global {
"elsa-switch-editor": HTMLElsaSwitchEditorElement;
"elsa-toolbox": HTMLElsaToolboxElement;
"elsa-toolbox-activities": HTMLElsaToolboxActivitiesElement;
"elsa-toolbox-triggers": HTMLElsaToolboxTriggersElement;
"elsa-trigger-container": HTMLElsaTriggerContainerElement;
"elsa-trigger-properties-editor": HTMLElsaTriggerPropertiesEditorElement;
"elsa-workflow-browser": HTMLElsaWorkflowBrowserElement;
"elsa-workflow-editor": HTMLElsaWorkflowEditorElement;
"elsa-workflow-instance-browser": HTMLElsaWorkflowInstanceBrowserElement;
@ -589,26 +547,6 @@ declare namespace LocalJSX {
interface ElsaToolboxActivities {
"activityDescriptors"?: Array<ActivityDescriptor>;
"graph"?: Graph;
"triggerDescriptors"?: Array<TriggerDescriptor>;
}
interface ElsaToolboxTriggers {
"activityDescriptors"?: Array<ActivityDescriptor>;
"graph"?: Graph;
"triggerDescriptors"?: Array<TriggerDescriptor>;
}
interface ElsaTriggerContainer {
"interactiveMode"?: boolean;
"onTriggerDeselected"?: (event: CustomEvent<TriggerDeselectedArgs>) => void;
"onTriggerSelected"?: (event: CustomEvent<TriggerSelectedArgs>) => void;
"onTriggersUpdated"?: (event: CustomEvent<TriggersUpdatedArgs>) => void;
"triggerDescriptors"?: Array<TriggerDescriptor>;
"triggers"?: Array<Trigger>;
}
interface ElsaTriggerPropertiesEditor {
"onDeleteTriggerRequested"?: (event: CustomEvent<DeleteTriggerRequestedArgs>) => void;
"onTriggerUpdated"?: (event: CustomEvent<TriggerUpdatedArgs>) => void;
"trigger"?: Trigger;
"triggerDescriptors"?: Array<TriggerDescriptor>;
}
interface ElsaWorkflowBrowser {
"onWorkflowDefinitionSelected"?: (event: CustomEvent<WorkflowSummary>) => void;
@ -617,7 +555,6 @@ declare namespace LocalJSX {
"activityDescriptors"?: Array<ActivityDescriptor>;
"monacoLibPath"?: string;
"onWorkflowUpdated"?: (event: CustomEvent<WorkflowUpdatedArgs>) => void;
"triggerDescriptors"?: Array<TriggerDescriptor>;
}
interface ElsaWorkflowInstanceBrowser {
"onWorkflowInstanceSelected"?: (event: CustomEvent<WorkflowInstanceSummary>) => void;
@ -664,9 +601,6 @@ declare namespace LocalJSX {
"elsa-switch-editor": ElsaSwitchEditor;
"elsa-toolbox": ElsaToolbox;
"elsa-toolbox-activities": ElsaToolboxActivities;
"elsa-toolbox-triggers": ElsaToolboxTriggers;
"elsa-trigger-container": ElsaTriggerContainer;
"elsa-trigger-properties-editor": ElsaTriggerPropertiesEditor;
"elsa-workflow-browser": ElsaWorkflowBrowser;
"elsa-workflow-editor": ElsaWorkflowEditor;
"elsa-workflow-instance-browser": ElsaWorkflowInstanceBrowser;
@ -705,9 +639,6 @@ declare module "@stencil/core" {
"elsa-switch-editor": LocalJSX.ElsaSwitchEditor & JSXBase.HTMLAttributes<HTMLElsaSwitchEditorElement>;
"elsa-toolbox": LocalJSX.ElsaToolbox & JSXBase.HTMLAttributes<HTMLElsaToolboxElement>;
"elsa-toolbox-activities": LocalJSX.ElsaToolboxActivities & JSXBase.HTMLAttributes<HTMLElsaToolboxActivitiesElement>;
"elsa-toolbox-triggers": LocalJSX.ElsaToolboxTriggers & JSXBase.HTMLAttributes<HTMLElsaToolboxTriggersElement>;
"elsa-trigger-container": LocalJSX.ElsaTriggerContainer & JSXBase.HTMLAttributes<HTMLElsaTriggerContainerElement>;
"elsa-trigger-properties-editor": LocalJSX.ElsaTriggerPropertiesEditor & JSXBase.HTMLAttributes<HTMLElsaTriggerPropertiesEditorElement>;
"elsa-workflow-browser": LocalJSX.ElsaWorkflowBrowser & JSXBase.HTMLAttributes<HTMLElsaWorkflowBrowserElement>;
"elsa-workflow-editor": LocalJSX.ElsaWorkflowEditor & JSXBase.HTMLAttributes<HTMLElsaWorkflowEditorElement>;
"elsa-workflow-instance-browser": LocalJSX.ElsaWorkflowInstanceBrowser & JSXBase.HTMLAttributes<HTMLElsaWorkflowInstanceBrowserElement>;

View file

@ -1,18 +1,16 @@
import {createProviderConsumer} from "@stencil/state-tunnel";
import {h} from "@stencil/core";
import {ActivityDescriptor, TriggerDescriptor, Workflow} from "../../models";
import {ActivityDescriptor, Workflow} from "../../models";
export interface WorkflowDesignerState {
workflow: Workflow;
activityDescriptors: Array<ActivityDescriptor>;
triggerDescriptors: Array<TriggerDescriptor>;
}
export default createProviderConsumer<WorkflowDesignerState>(
{
workflow: null,
activityDescriptors: [],
triggerDescriptors: []
activityDescriptors: []
},
(subscribe, child) => (<context-consumer subscribe={subscribe} renderer={child}/>)
);

View file

@ -2,7 +2,7 @@ import {Component, h, Prop, State, Watch} from "@stencil/core";
import {Addon, Graph} from '@antv/x6';
import groupBy from 'lodash/groupBy';
import {Container} from 'typedi';
import {ActivityDescriptor, TriggerDescriptor} from '../../../models';
import {ActivityDescriptor} from '../../../models';
import {ActivityDriverRegistry} from '../../../services';
import WorkflowEditorTunnel from '../state';
@ -18,7 +18,6 @@ interface ActivityCategoryModel {
export class ToolboxActivities {
@Prop() graph: Graph;
@Prop({mutable: true}) activityDescriptors: Array<ActivityDescriptor> = [];
@Prop({mutable: true}) triggerDescriptors: Array<TriggerDescriptor> = [];
@State() activityCategoryModels: Array<ActivityCategoryModel> = [];
private dnd: Addon.Dnd;
private renderedActivities: Map<string, string>;
@ -71,17 +70,9 @@ export class ToolboxActivities {
this.handleActivityDescriptorsChanged(this.activityDescriptors);
}
private onActivityStartDrag(e: DragEvent, activityDescriptor: ActivityDescriptor) {
private static onActivityStartDrag(e: DragEvent, activityDescriptor: ActivityDescriptor) {
const json = JSON.stringify(activityDescriptor);
const triggerDescriptor = this.triggerDescriptors.find(x => x.nodeType == activityDescriptor.nodeType);
const isTrigger = !!triggerDescriptor;
e.dataTransfer.setData('activity-descriptor', json);
if (isTrigger) {
const triggerDescriptorJson = JSON.stringify(triggerDescriptor);
e.dataTransfer.setData('trigger-descriptor', triggerDescriptorJson);
}
}
private onToggleActivityCategory(categoryModel: ActivityCategoryModel) {
@ -121,7 +112,7 @@ export class ToolboxActivities {
return (
<div class="w-full flex items-center pl-10 pr-2 py-2">
<div class="cursor-move"
onDragStart={e => this.onActivityStartDrag(e, activityDescriptor)}>
onDragStart={e => ToolboxActivities.onActivityStartDrag(e, activityDescriptor)}>
<div innerHTML={activityHtml} draggable={true}/>
</div>
</div>
@ -136,4 +127,4 @@ export class ToolboxActivities {
}
}
WorkflowEditorTunnel.injectProps(ToolboxActivities, ['activityDescriptors', 'triggerDescriptors']);
WorkflowEditorTunnel.injectProps(ToolboxActivities, ['activityDescriptors']);

View file

@ -1,139 +0,0 @@
import {Component, h, Prop, State, Watch} from '@stencil/core';
import {Addon, Graph} from '@antv/x6';
import groupBy from 'lodash/groupBy';
import {ActivityDescriptor, TriggerDescriptor} from '../../../models';
import WorkflowEditorTunnel from '../state';
import {Container} from "typedi";
import {TriggerDriverRegistry} from '../../../services/trigger-driver-registry';
interface TriggerCategoryModel {
category: string;
triggers: Array<TriggerDescriptor>;
expanded: boolean;
}
@Component({
tag: 'elsa-toolbox-triggers',
})
export class ToolboxTriggers {
@Prop() graph: Graph;
@Prop({mutable: true}) triggerDescriptors: Array<TriggerDescriptor> = [];
@Prop({mutable: true}) activityDescriptors: Array<ActivityDescriptor> = [];
@State() triggerCategoryModels: Array<TriggerCategoryModel> = [];
private dnd: Addon.Dnd;
private renderedTriggers: Map<string, string>;
@Watch('graph')
handleGraphChanged(value: Graph) {
if (this.dnd)
this.dnd.dispose();
this.dnd = new Addon.Dnd({
target: value,
scaled: false,
animation: true,
});
}
@Watch('triggerDescriptors')
handleTriggerDescriptorsChanged(value: Array<TriggerDescriptor>) {
const categorizedTriggersLookup = groupBy(value, x => x.category);
const categories = Object.keys(categorizedTriggersLookup);
const renderedTriggers: Map<string, string> = new Map<string, string>();
// Group triggers by category
this.triggerCategoryModels = categories.map(x => {
const model: TriggerCategoryModel = {
category: x,
expanded: false,
triggers: categorizedTriggersLookup[x]
};
return model;
});
// Render activities.
const driverRegistry = Container.get(TriggerDriverRegistry);
for (const triggerDescriptor of value) {
const triggerType = triggerDescriptor.nodeType;
const driver = driverRegistry.createDriver(triggerType);
const html = driver.display({displayType: 'picker', triggerDescriptor: triggerDescriptor});
renderedTriggers.set(triggerType, html);
}
this.renderedTriggers = renderedTriggers;
}
componentDidLoad() {
this.handleTriggerDescriptorsChanged(this.triggerDescriptors);
}
private onTriggerStartDrag(e: DragEvent, triggerDescriptor: TriggerDescriptor) {
const json = JSON.stringify(triggerDescriptor);
const activityDescriptor = this.activityDescriptors.find(x => x.nodeType == triggerDescriptor.nodeType);
const isActivity = !!activityDescriptor;
e.dataTransfer.setData('trigger-descriptor', json);
if (isActivity) {
const activityJson = JSON.stringify(activityDescriptor);
e.dataTransfer.setData('activity-descriptor', activityJson);
}
}
private onToggleTriggerCategory(categoryModel: TriggerCategoryModel) {
categoryModel.expanded = !categoryModel.expanded;
this.triggerCategoryModels = [...this.triggerCategoryModels];
}
render() {
const categoryModels = this.triggerCategoryModels;
const renderedTriggers = this.renderedTriggers;
return <nav class=" flex-1 px-2 space-y-1 font-sans text-sm text-gray-600">
{categoryModels.map(categoryModel => {
const category = categoryModel.category;
const triggers = categoryModel.triggers;
const categoryButtonClass = categoryModel.expanded ? 'rotate-90' : '';
const categoryContentClass = categoryModel.expanded ? '' : 'hidden';
return <div class="space-y-1">
<button type="button"
onClick={() => this.onToggleTriggerCategory(categoryModel)}
class="text-gray-600 hover:bg-gray-50 hover:text-gray-900 group w-full flex items-center pr-2 py-2 text-left text-sm font-medium rounded-md focus:outline-none">
<svg
class={`${categoryButtonClass} text-gray-300 mr-2 flex-shrink-0 h-5 w-5 transform group-hover:text-gray-400 transition-colors ease-in-out duration-150`}
viewBox="0 0 20 20" aria-hidden="true">
<path d="M6 6L14 10L6 14V6Z" fill="currentColor"/>
</svg>
{category}
</button>
<div class={`space-y-1 ${categoryContentClass}`}>
{triggers.map(trigger => {
const triggerHtml = renderedTriggers.get(trigger.nodeType);
return (
<div class="w-full flex items-center pl-10 pr-2 py-2">
<div class="cursor-move" onDragStart={e => this.onTriggerStartDrag(e, trigger)}>
<div innerHTML={triggerHtml} draggable={true}/>
</div>
</div>
);
})}
</div>
</div>;
}
)}
</nav>
}
}
WorkflowEditorTunnel.injectProps(ToolboxTriggers, ['activityDescriptors', 'triggerDescriptors']);

View file

@ -19,7 +19,6 @@ export class Toolbox {
const selectedCss = 'border-blue-500 text-blue-600';
const defaultCss = 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300';
const activitiesTabCssClass = selectedTabIndex == 0 ? selectedCss : defaultCss;
const triggersTabCssClass = selectedTabIndex == 1 ? selectedCss : defaultCss;
return (
@ -33,11 +32,6 @@ export class Toolbox {
Activities
</a>
<a href="#"
onClick={e => this.onTabSelected(e, 1)}
class={`${triggersTabCssClass} w-1/2 py-4 px-1 text-center border-b-2 font-medium text-sm`}>
Triggers
</a>
</nav>
</div>
@ -45,9 +39,6 @@ export class Toolbox {
graph={this.graph}
class={selectedTabIndex == 0 ? '' : 'hidden'}/>
<elsa-toolbox-triggers
graph={this.graph}
class={selectedTabIndex == 1 ? '' : 'hidden'}/>
</div>
);
}

View file

@ -1,158 +0,0 @@
import {Component, EventEmitter, h, Prop, State, Event, Method} from '@stencil/core';
import {v4 as uuid} from 'uuid';
import {Container} from 'typedi';
import {Activity, ActivitySelectedArgs, Trigger, TriggerDescriptor, Workflow} from '../../../models';
import {TriggerDriverRegistry} from '../../../services/trigger-driver-registry';
import WorkflowEditorTunnel from "../state";
export interface TriggersUpdatedArgs {
triggers: Array<Trigger>;
}
export interface TriggerSelectedArgs {
trigger: Trigger;
applyChanges: (trigger: Trigger) => void;
deleteTrigger: (trigger: Trigger) => void;
}
export interface TriggerDeselectedArgs {
trigger: Trigger;
}
@Component({
tag: 'elsa-trigger-container',
})
export class TriggerContainer {
private renderedTriggers: Map<string, string> = new Map<string, string>();
@Prop({mutable: true}) public triggerDescriptors: Array<TriggerDescriptor> = [];
@Prop({mutable: true}) public triggers: Array<Trigger> = [];
@Prop() public interactiveMode: boolean = true;
@Event() public triggersUpdated: EventEmitter<TriggersUpdatedArgs>;
@Event() triggerSelected: EventEmitter<TriggerSelectedArgs>;
@Event() triggerDeselected: EventEmitter<TriggerDeselectedArgs>;
@State() private selectedTriggers: Array<string> = [];
@Method()
public async deselectAll(): Promise<void> {
this.selectedTriggers = [];
}
public componentWillRender() {
const triggerDescriptors = this.triggerDescriptors;
const triggers = this.triggers;
const triggerDriverRegistry = Container.get(TriggerDriverRegistry);
const renderedTriggers = new Map<string, string>();
for (const trigger of triggers) {
const triggerType = trigger.nodeType;
const triggerDescriptor = triggerDescriptors.find(x => x.nodeType == triggerType);
const driver = triggerDriverRegistry.createDriver(triggerType);
const html = driver.display({triggerDescriptor: triggerDescriptor, trigger: trigger, displayType: 'designer'});
renderedTriggers.set(trigger.id, html);
}
this.renderedTriggers = renderedTriggers;
}
public render() {
return (
<div class="absolute left-0 top-0 right-0 bottom-0 overflow-auto"
onDragOver={this.onDragOver}
onDrop={e => this.onDrop(e)}>
{this.renderTriggers()}
</div>
);
}
private renderTriggers = () => {
const triggers = this.triggers;
if (triggers.length == 0) {
return <div>
<div class="border-2 border-dashed m-2 p-2 text-center">
<p class="mt-1 text-sm text-gray-600">Place your workflow triggers here to automatically invoke your workflow when a trigger executes.</p>
</div>
</div>
}
return <div class="flex p-2 pt-4 space-x-4">
{triggers.map(this.renderTrigger)}
</div>
};
private renderTrigger = (trigger: Trigger) => {
const renderedTriggers = this.renderedTriggers;
const html = renderedTriggers.get(trigger.id);
const isSelected = this.getIsTriggerSelected(trigger);
const cssClass = isSelected ? 'border-green-400' : 'border-transparent';
return (
<div class={`p-1 border-2 border-dashed outline-none ${cssClass}`} tabindex={0}
onKeyDown={e => this.onKeyPress(e)}>
<div class="flex-none cursor-pointer select-none" onClick={e => this.onTriggerClick(e, trigger)}>
<div innerHTML={html}/>
</div>
</div>
);
};
private updateTriggers = (triggers: Array<Trigger>) => {
this.triggers = triggers;
this.triggersUpdated.emit({triggers: triggers});
};
private deleteTrigger = (trigger: Trigger) => {
const triggers = this.triggers.filter(x => x != trigger);
this.updateTriggers(triggers);
};
private updateTrigger = (trigger: Trigger) => this.updateTriggers([...this.triggers]);
private getIsTriggerSelected = (trigger: Trigger) => this.selectedTriggers.findIndex(x => x == trigger.id) >= 0;
private onDrop = (e: DragEvent) => {
const json = e.dataTransfer.getData('trigger-descriptor');
const triggerDescriptor: TriggerDescriptor = JSON.parse(json);
const trigger: Trigger = {
id: uuid(),
nodeType: triggerDescriptor.nodeType,
metadata: {}
};
const triggers = [...this.triggers, trigger];
this.updateTriggers(triggers);
};
private onDragOver = (e: DragEvent) => {
const isTrigger = e.dataTransfer.types.indexOf('trigger-descriptor') >= 0;
if (this.interactiveMode && isTrigger)
e.preventDefault();
}
private onTriggerClick = (e: MouseEvent, trigger: Trigger) => {
const isSelected = this.getIsTriggerSelected(trigger);
if (e.ctrlKey)
this.selectedTriggers = isSelected ? this.selectedTriggers.filter(x => x != trigger.id) : [...this.selectedTriggers, trigger.id];
else
this.selectedTriggers = [trigger.id];
this.triggerSelected.emit({
trigger: trigger,
applyChanges: this.updateTrigger,
deleteTrigger: this.deleteTrigger
});
};
private onKeyPress = (e: KeyboardEvent) => {
if (e.key != 'Delete' || !this.interactiveMode)
return;
const selectedTriggers = this.selectedTriggers;
const triggers = this.triggers.filter(trigger => selectedTriggers.findIndex(x => x == trigger.id) < 0);
this.updateTriggers(triggers);
};
}
WorkflowEditorTunnel.injectProps(TriggerContainer, ['triggerDescriptors', 'workflow']);

View file

@ -1,165 +0,0 @@
import {Component, Event, EventEmitter, h, Method, Prop, State} from '@stencil/core';
import {camelCase} from 'lodash';
import WorkflowEditorTunnel from '../state';
import {
DefaultActions, InputDescriptor,
RenderNodePropContext,
RenderNodePropsContext,
TabChangedArgs,
TabDefinition,
Trigger,
TriggerDescriptor
} from '../../../models';
import {FormEntry} from "../../shared/forms/form-entry";
import {InputDriverRegistry} from "../../../services/input-driver-registry";
import {Container} from "typedi";
import {NodeInputContext} from "../../../services/node-input-driver";
export interface TriggerUpdatedArgs {
trigger: Trigger;
}
export interface DeleteTriggerRequestedArgs {
trigger: Trigger;
}
@Component({
tag: 'elsa-trigger-properties-editor',
})
export class TriggerPropertiesEditor {
private slideOverPanel: HTMLElsaSlideOverPanelElement;
private renderContext: RenderNodePropsContext;
private readonly inputDriverRegistry: InputDriverRegistry;
constructor() {
this.inputDriverRegistry = Container.get(InputDriverRegistry);
}
@Prop({mutable: true}) triggerDescriptors: Array<TriggerDescriptor> = [];
@Prop({mutable: true}) trigger?: Trigger;
@Event() triggerUpdated: EventEmitter<TriggerUpdatedArgs>;
@Event() deleteTriggerRequested: EventEmitter<DeleteTriggerRequestedArgs>;
@State() private selectedTabIndex: number = 0;
@Method()
public async show(): Promise<void> {
await this.slideOverPanel.show();
}
@Method()
public async hide(): Promise<void> {
await this.slideOverPanel.hide();
}
public componentWillRender() {
const trigger = this.trigger;
const triggerDescriptor = this.findDescriptor();
const title = triggerDescriptor?.displayName ?? triggerDescriptor?.nodeType ?? 'Unknown Trigger';
const driverRegistry = this.inputDriverRegistry;
const renderPropertyContexts: Array<RenderNodePropContext> = triggerDescriptor.inputProperties.map(inputDescriptor => {
const renderInputContext: NodeInputContext = {
node: trigger,
nodeDescriptor: triggerDescriptor,
inputDescriptor,
notifyInputChanged: () => this.triggerUpdated.emit({trigger}),
inputChanged: (v, s) => this.onPropertyEditorChanged(inputDescriptor, v, s)
};
const driver = driverRegistry.get(renderInputContext);
const control = driver?.renderInput(renderInputContext);
return {
inputContext: renderInputContext,
inputControl: control,
}
});
this.renderContext = {
node: trigger,
nodeDescriptor: triggerDescriptor,
title,
properties: renderPropertyContexts
}
}
public render() {
const {nodeDescriptor, title} = this.renderContext;
const propertiesTab: TabDefinition = {
displayText: 'Properties',
content: () => this.renderPropertiesTab()
};
const commonTab: TabDefinition = {
displayText: 'Common',
content: () => this.renderCommonTab()
};
const tabs = !!nodeDescriptor ? [propertiesTab, commonTab] : [];
const actions = [DefaultActions.Delete(this.onDeleteTrigger)];
return (
<elsa-form-panel
headerText={title} tabs={tabs} selectedTabIndex={this.selectedTabIndex}
onSelectedTabIndexChanged={e => this.onSelectedTabIndexChanged(e)}
actions={actions}/>
);
}
private findDescriptor = (): TriggerDescriptor => !!this.trigger ? this.triggerDescriptors.find(x => x.nodeType == this.trigger.nodeType) : null;
private onSelectedTabIndexChanged(e: CustomEvent<TabChangedArgs>) {
this.selectedTabIndex = e.detail.selectedTabIndex;
}
private onTriggerIdChanged = (e: any) => {
const trigger = this.trigger;
const inputElement = e.target as HTMLInputElement;
trigger.id = inputElement.value;
this.triggerUpdated.emit({trigger});
};
private onPropertyEditorChanged = (inputDescriptor: InputDescriptor, propertyValue: any, syntax: string) => {
const trigger = this.trigger;
const propertyName = inputDescriptor.name;
const camelCasePropertyName = camelCase(propertyName);
trigger[camelCasePropertyName] = {
type: inputDescriptor.type,
expression: {
type: syntax,
value: propertyValue
}
};
this.triggerUpdated.emit({trigger});
}
private onDeleteTrigger = () => this.deleteTriggerRequested.emit({trigger: this.trigger});
private renderPropertiesTab = () => {
const {node, properties} = this.renderContext;
const activityId = node.id;
return <div>
<FormEntry fieldId="ActivityId" label="ID" hint="The ID of the activity.">
<input type="text" name="ActivityId" id="ActivityId" value={activityId} onChange={e => this.onTriggerIdChanged(e)}/>
</FormEntry>
{properties.filter(x => !!x.inputControl).map(propertyContext => {
return propertyContext.inputControl;
})}
</div>
};
private renderCommonTab = () => {
return <div>
</div>
};
}
WorkflowEditorTunnel.injectProps(TriggerPropertiesEditor, ['triggerDescriptors']);

View file

@ -1,25 +1,13 @@
$activity-picker-width: 300px;
$trigger-panel-height: 76px;
$activity-editor-width: 580px;
elsa-canvas {
left: $activity-picker-width;
top: $trigger-panel-height;
top: 0;
right: $activity-editor-width;
bottom: 0;
}
elsa-panel.elsa-trigger-container {
left: $activity-picker-width;
right: $activity-editor-width;
}
elsa-panel.panel-state-expanded.elsa-trigger-container {
left: $activity-picker-width;
right: $activity-editor-width;
height: $trigger-panel-height;
}
elsa-panel.panel-state-expanded.elsa-activity-picker-container {
width: $activity-picker-width;
}
@ -39,21 +27,6 @@ elsa-panel.panel-state-expanded.elsa-activity-editor-container {
elsa-canvas {
left: 0;
}
elsa-panel.elsa-trigger-container {
left: 0;
}
}
.trigger-container-closed {
elsa-canvas {
top: 0;
}
elsa-trigger-container {
display: none;
}
}
.object-editor-closed {
@ -65,8 +38,4 @@ elsa-panel.panel-state-expanded.elsa-activity-editor-container {
elsa-canvas {
right: 0;
}
elsa-panel.elsa-trigger-container {
right: 0;
}
}

View file

@ -5,11 +5,15 @@ import {Container} from "typedi";
import {PanelPosition, PanelStateChangedArgs} from '../panel/models';
import {
Activity,
ActivityDescriptor, ActivityPropertyChangedEventArgs,
ActivitySelectedArgs, ContainerSelectedArgs, EventTypes,
GraphUpdatedArgs, Trigger,
TriggerDescriptor,
Workflow, WorkflowInstance
ActivityDescriptor,
ActivityPropertyChangedEventArgs,
ActivitySelectedArgs,
ContainerSelectedArgs,
EventTypes,
GraphUpdatedArgs,
Trigger,
Workflow,
WorkflowInstance
} from '../../../models';
import WorkflowEditorTunnel, {WorkflowDesignerState} from '../state';
import {
@ -17,8 +21,6 @@ import {
DeleteActivityRequestedArgs
} from '../activity-properties-editor/activity-properties-editor';
import {ActivityDriverRegistry, EventBus} from '../../../services';
import {TriggerSelectedArgs, TriggersUpdatedArgs} from '../trigger-container/trigger-container';
import {DeleteTriggerRequestedArgs, TriggerUpdatedArgs} from "../trigger-properties-editor/trigger-properties-editor";
import {WorkflowPropsUpdatedArgs} from "../workflow-properties-editor/workflow-properties-editor";
import {MonacoEditorSettings} from "../../../services/monaco-editor-settings";
import {PluginRegistry} from "../../../services/plugin-registry";
@ -37,7 +39,6 @@ export class WorkflowEditor {
private canvas: HTMLElsaCanvasElement;
private container: HTMLDivElement;
private toolbox: HTMLElsaToolboxElement;
private triggerContainer: HTMLElsaTriggerContainerElement;
private applyActivityChanges: (activity: Activity) => void;
private deleteActivity: (activity: Activity) => void;
private applyTriggerChanges: (trigger: Trigger) => void;
@ -49,8 +50,7 @@ export class WorkflowEditor {
root: null,
identity: {id: uuid(), definitionId: uuid(), version: 1},
publication: {isLatest: true, isPublished: false},
metadata: {},
triggers: []
metadata: {}
};
private workflowInstance?: WorkflowInstance;
@ -65,11 +65,8 @@ export class WorkflowEditor {
@Element() el: HTMLElsaWorkflowEditorElement;
@Prop({attribute: 'monaco-lib-path'}) public monacoLibPath: string;
@Prop() public activityDescriptors: Array<ActivityDescriptor> = [];
@Prop() public triggerDescriptors: Array<TriggerDescriptor> = [];
@Event() public workflowUpdated: EventEmitter<WorkflowUpdatedArgs>
@State() private selectedActivity?: Activity;
@State() private selectedTrigger?: Trigger;
@State() private triggers: Array<Trigger> = [];
private get interactiveMode(): boolean {
return !this.workflowInstance;
@ -94,32 +91,15 @@ export class WorkflowEditor {
@Listen('containerSelected')
private async handleContainerSelected(e: CustomEvent<ContainerSelectedArgs>) {
this.selectedActivity = null;
this.selectedTrigger = null;
await this.triggerContainer.deselectAll();
}
@Listen('activitySelected')
private async handleActivitySelected(e: CustomEvent<ActivitySelectedArgs>) {
this.selectedActivity = e.detail.activity;
this.selectedTrigger = null;
await this.triggerContainer.deselectAll();
this.applyActivityChanges = e.detail.applyChanges;
this.deleteActivity = e.detail.deleteActivity;
}
@Listen('triggerSelected')
private async handleTriggerSelected(e: CustomEvent<TriggerSelectedArgs>) {
this.selectedTrigger = e.detail.trigger;
this.selectedActivity = null;
this.applyTriggerChanges = e.detail.applyChanges;
this.deleteTrigger = e.detail.deleteTrigger;
}
@Listen('triggerDeselected')
private async handleTriggerDeselected(e: CustomEvent<TriggerSelectedArgs>) {
this.selectedTrigger = null;
}
@Listen('graphUpdated')
private handleGraphUpdated(e: CustomEvent<GraphUpdatedArgs>) {
if (this.interactiveMode)
@ -152,14 +132,12 @@ export class WorkflowEditor {
@Method()
public async importWorkflowMetadata(workflow: Workflow): Promise<void> {
this.workflow = workflow;
this.triggers = workflow.triggers;
}
public render() {
const tunnelState: WorkflowDesignerState = {
workflow: this.workflow,
activityDescriptors: this.activityDescriptors,
triggerDescriptors: this.triggerDescriptors
};
const interactiveMode = this.interactiveMode;
@ -173,17 +151,6 @@ export class WorkflowEditor {
onExpandedStateChanged={e => this.onActivityPickerPanelStateChanged(e.detail)}>
<elsa-toolbox ref={el => this.toolbox = el}/>
</elsa-panel>
<elsa-panel
class="elsa-trigger-container"
onExpandedStateChanged={e => this.onTriggerContainerPanelStateChanged(e.detail)}
position={PanelPosition.Top}>
<elsa-trigger-container
interactiveMode={interactiveMode}
triggerDescriptors={this.triggerDescriptors}
triggers={this.triggers}
onTriggersUpdated={e => this.onTriggersUpdated(e.detail)}
ref={el => this.triggerContainer = el}/>
</elsa-panel>
<elsa-canvas
class="absolute" ref={el => this.canvas = el}
interactiveMode={interactiveMode}
@ -204,12 +171,6 @@ export class WorkflowEditor {
}
private renderSelectedObject = () => {
if (!!this.selectedTrigger)
return <elsa-trigger-properties-editor
trigger={this.selectedTrigger}
onTriggerUpdated={e => this.onTriggerUpdated(e)}
onDeleteTriggerRequested={e => this.onDeleteTriggerRequested(e)}/>
if (!!this.selectedActivity)
return <elsa-activity-properties-editor
activity={this.selectedActivity}
@ -253,14 +214,8 @@ export class WorkflowEditor {
}
private onActivityPickerPanelStateChanged = async (e: PanelStateChangedArgs) => await this.updateContainerLayout('activity-picker-closed', e.expanded)
private onTriggerContainerPanelStateChanged = async (e: PanelStateChangedArgs) => await this.updateContainerLayout('trigger-container-closed', e.expanded)
private onActivityEditorPanelStateChanged = async (e: PanelStateChangedArgs) => await this.updateContainerLayout('object-editor-closed', e.expanded)
private onTriggersUpdated = async (e: TriggersUpdatedArgs) => {
this.workflow.triggers = e.triggers;
await this.saveChangesDebounced();
};
private onDragOver = (e: DragEvent) => {
if (this.interactiveMode)
e.preventDefault();
@ -280,21 +235,10 @@ export class WorkflowEditor {
this.saveChangesDebounced();
}
private onTriggerUpdated = (e: CustomEvent<TriggerUpdatedArgs>) => {
const updatedTrigger = e.detail.trigger;
this.applyTriggerChanges(updatedTrigger);
this.saveChangesDebounced();
}
private onWorkflowPropsUpdated = (e: CustomEvent<WorkflowPropsUpdatedArgs>) => this.saveChangesDebounced()
private onDeleteActivityRequested = (e: CustomEvent<DeleteActivityRequestedArgs>) => {
this.deleteActivity(e.detail.activity);
this.selectedActivity = null;
};
private onDeleteTriggerRequested = (e: CustomEvent<DeleteTriggerRequestedArgs>) => {
this.deleteTrigger(e.detail.trigger);
this.selectedTrigger = null;
};
}

View file

@ -2,7 +2,7 @@ import {Component, Element, h, Listen, Prop, Watch} from '@stencil/core';
import 'reflect-metadata';
import {Container} from 'typedi';
import {ElsaApiClientProvider, ElsaClient, SaveWorkflowRequest, ServerSettings} from '../../../services';
import {ActivityDescriptor, TriggerDescriptor, VersionOptions, Workflow, WorkflowInstanceSummary, WorkflowSummary} from '../../../models';
import {ActivityDescriptor, VersionOptions, Workflow, WorkflowInstanceSummary, WorkflowSummary} from '../../../models';
import {WorkflowUpdatedArgs} from '../../designer/workflow-editor/workflow-editor';
import {PublishClickedArgs} from "../../toolbar/workflow-publish-button/workflow-publish-button";
import {MonacoEditorSettings} from "../../../services/monaco-editor-settings";
@ -12,7 +12,6 @@ import {MonacoEditorSettings} from "../../../services/monaco-editor-settings";
})
export class Studio {
private activityDescriptors: Array<ActivityDescriptor>;
private triggerDescriptors: Array<TriggerDescriptor>;
private elsaClient: ElsaClient;
private workflowEditorElement?: HTMLElsaWorkflowEditorElement;
@ -88,12 +87,10 @@ export class Studio {
const elsaClientProvider = Container.get(ElsaApiClientProvider);
this.elsaClient = await elsaClientProvider.getClient();
this.activityDescriptors = await this.elsaClient.descriptors.activities.list();
this.triggerDescriptors = await this.elsaClient.descriptors.triggers.list();
this.workflowEditorElement = this.el.getElementsByTagName('elsa-workflow-editor')[0] as HTMLElsaWorkflowEditorElement;
if (!!this.workflowEditorElement) {
this.workflowEditorElement.activityDescriptors = this.activityDescriptors;
this.workflowEditorElement.triggerDescriptors = this.triggerDescriptors;
this.workflowEditorElement.monacoLibPath = this.monacoLibPath;
}
}
@ -108,7 +105,6 @@ export class Studio {
name: workflow.metadata.name,
description: workflow.metadata.description,
publish: publish,
triggers: workflow.triggers,
root: workflow.root
};

View file

@ -1,36 +0,0 @@
import {TriggerDisplayContext, TriggerDriver} from "../../services/trigger-driver";
export class DefaultTriggerDriver implements TriggerDriver {
display(context: TriggerDisplayContext): any {
const triggerDescriptor = context.triggerDescriptor;
const text = triggerDescriptor?.displayName;
const borderColor = 'border-green-600';
const backgroundColor = 'bg-green-400';
const iconBackgroundColor = 'bg-green-500';
return (`
<div>
<div class="activity-wrapper border ${borderColor} ${backgroundColor} rounded text-white overflow-hidden">
<div class="flex flex-row">
<div class="flex flex-shrink items-center ${iconBackgroundColor}">
<div class="px-2 py-1">
<svg class="h-6 w-6 text-white" 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"/>
<path d="M7 18a4.6 4.4 0 0 1 0 -9h0a5 4.5 0 0 1 11 2h1a3.5 3.5 0 0 1 0 7h-12"/>
</svg>
</div>
</div>
<div class="flex items-center">
<div class="px-4 py-1">
${text}
</div>
</div>
</div>
</div>
</div>
`);
}
}

View file

@ -14,7 +14,7 @@
<!--Use the studio shell component to handle automatic interaction with the Elsa Server API-->
<elsa-studio server="https://localhost:5001" monaco-lib-path="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.31.1/min">
<elsa-workflow-toolbar></elsa-workflow-toolbar>
<div class="absolute inset-0" style="top: 63px;">
<div class="absolute inset-0" style="top: 64px;">
<elsa-workflow-editor></elsa-workflow-editor>
</div>
<!-- <elsa-workflow-editor></elsa-workflow-editor>-->

View file

@ -18,13 +18,6 @@ export interface ActivityDescriptor extends NodeDescriptor {
outPorts: Array<Port>;
}
export interface TriggerDescriptor extends NodeDescriptor {
}
export interface TriggerDescriptorResponse {
triggerDescriptors: Array<TriggerDescriptor>;
}
export enum ActivityTraits {
Action = 1,
Trigger = 2

View file

@ -10,10 +10,10 @@ export interface Node {
[name: string]: any;
}
export interface Trigger extends Node {
export interface Activity extends Node {
}
export interface Activity extends Node {
export interface Trigger extends Activity {
}
export interface Container extends Activity {
@ -36,7 +36,6 @@ export interface Workflow {
publication: WorkflowPublication;
metadata: WorkflowMetadata;
root: Activity;
triggers: Array<Trigger>;
}
export interface WorkflowMetadata {

View file

@ -13,13 +13,12 @@ import {
PagedList,
SelectList,
Trigger,
TriggerDescriptor,
TriggerDescriptorResponse, VersionOptions,
VersionOptions,
Workflow, WorkflowInstance, WorkflowInstanceSummary, WorkflowStatus, WorkflowSummary
} from '../models';
import 'reflect-metadata';
import {ServerSettings} from './server-settings';
import {getVersionOptionsString} from "../utils/utils";
import {getVersionOptionsString} from "../utils";
function serializeQueryString(queryString: object): string {
const filteredItems = _(queryString).omitBy(_.isUndefined).omitBy(_.isNull).value();
@ -54,12 +53,6 @@ export async function createElsaClient(serverUrl: string): Promise<ElsaClient> {
const response = await httpClient.get<ActivityDescriptorResponse>('api/descriptors/activities');
return response.data.activityDescriptors;
}
},
triggers: {
async list(): Promise<Array<TriggerDescriptor>> {
const response = await httpClient.get<TriggerDescriptorResponse>('api/descriptors/triggers');
return response.data.triggerDescriptors;
}
}
},
workflows: {
@ -140,17 +133,12 @@ export interface ElsaClient {
export interface DescriptorsApi {
activities: ActivityDescriptorsApi;
triggers: TriggerDescriptorsApi;
}
export interface ActivityDescriptorsApi {
list(): Promise<Array<ActivityDescriptor>>;
}
export interface TriggerDescriptorsApi {
list(): Promise<Array<TriggerDescriptor>>;
}
export interface WorkflowsApi {
post(request: SaveWorkflowRequest): Promise<Workflow>;
@ -180,7 +168,6 @@ export interface SaveWorkflowRequest {
definitionId: string;
name?: string;
description?: string;
triggers?: Array<Trigger>;
publish: boolean;
root?: Activity
}

View file

@ -7,5 +7,3 @@ export * from './event-bus';
export * from './input-control-registry';
export * from './input-driver-registry';
export * from './server-settings';
export * from './trigger-driver';
export * from './trigger-driver-registry';

View file

@ -1,22 +0,0 @@
import {TriggerDriver, TriggerDriverFactory} from "./trigger-driver";
import {Service} from "typedi";
import {DefaultTriggerDriver} from "../drivers/trigger/default-trigger-driver";
@Service()
export class TriggerDriverRegistry {
private _driverMap: Map<string, TriggerDriverFactory> = new Map<string, TriggerDriverFactory>();
private _defaultDriverFactory: TriggerDriverFactory = () => new DefaultTriggerDriver();
public add(triggerType: string, driverFactory: TriggerDriverFactory) {
this._driverMap.set(triggerType, driverFactory);
}
public get(triggerType: string): TriggerDriverFactory {
return this._driverMap.get(triggerType);
}
public createDriver(triggerType: string): TriggerDriver {
const driverFactory = this.get(triggerType) ?? this._defaultDriverFactory;
return driverFactory();
}
}

View file

@ -1,16 +0,0 @@
import {Trigger, TriggerDescriptor} from "../models";
export interface TriggerDisplayContext {
triggerDescriptor: TriggerDescriptor;
displayType: TriggerDisplayType;
trigger?: Trigger;
}
export interface TriggerDriver {
display: (context: TriggerDisplayContext) => any;
}
export type TriggerDriverFactory = () => TriggerDriver;
export type TriggerDisplayType = string | 'designer' | 'picker';

View file

@ -5,7 +5,6 @@ GREATER : '>';
INCREMENT : '++';
DECREMENT : '--';
NEW : 'new';
TRIGGER : 'trigger';
VARIABLE : 'variable';
LET : 'let';
IF : 'if';

View file

@ -1,18 +0,0 @@
using Elsa.Contracts;
namespace Elsa.Dsl.Interpreters;
public partial class WorkflowDefinitionBuilderInterpreter
{
public override IWorkflowDefinitionBuilder VisitTrigger(ElsaParser.TriggerContext context)
{
VisitChildren(context);
var trigger = (ITrigger)_object.Get(context.@object());
_workflowDefinitionBuilder.AddTrigger(trigger);
return DefaultResult;
}
}

View file

@ -7,7 +7,7 @@ using Elsa.Modules.AzureServiceBus.Models;
namespace Elsa.Modules.AzureServiceBus.Activities;
[Activity("Azure.ServiceBus.MessageReceived", "Executes when a message is received from the configured queue or topic and subscription", "Azure Service Bus")]
public class MessageReceived : TriggerActivity
public class MessageReceived : Trigger
{
internal const string MessageReceivedInputKey = "ReceivedMessage";
public Input<string> QueueOrTopic { get; set; } = default!;
@ -33,7 +33,7 @@ public class MessageReceived : TriggerActivity
/// </summary>
public IFormatter? Formatter { get; set; }
protected override object GetPayload(TriggerIndexingContext context) => GetPayload(context.ExpressionExecutionContext);
protected override object GetTriggerPayload(TriggerIndexingContext context) => GetPayload(context.ExpressionExecutionContext);
protected override void Execute(ActivityExecutionContext context)
{

View file

@ -8,7 +8,7 @@ using Elsa.Modules.Http.Models;
namespace Elsa.Modules.Http;
public class HttpTrigger : TriggerActivity
public class HttpTrigger : Trigger
{
[Input] public Input<string> Path { get; set; } = default!;
@ -20,7 +20,7 @@ public class HttpTrigger : TriggerActivity
[Output] public Output<HttpRequestModel>? Result { get; set; }
protected override IEnumerable<object> GetPayloads(TriggerIndexingContext context) => GetHashInputs(context.ExpressionExecutionContext);
protected override IEnumerable<object> GetTriggerPayloads(TriggerIndexingContext context) => GetHashInputs(context.ExpressionExecutionContext);
protected override void Execute(ActivityExecutionContext context) => context.SetBookmarks(GetHashInputs(context.ExpressionExecutionContext));
private IEnumerable<object> GetHashInputs(ExpressionExecutionContext context)

View file

@ -13,7 +13,7 @@ public class Timer : Trigger
{
[Input] public Input<TimeSpan> Interval { get; set; } = default!;
protected override IEnumerable<object> GetPayloads(TriggerIndexingContext context)
protected override IEnumerable<object> GetTriggerPayloads(TriggerIndexingContext context)
{
var interval = context.ExpressionExecutionContext.Get(Interval);
var clock = context.ExpressionExecutionContext.GetRequiredService<ISystemClock>();

View file

@ -14,7 +14,6 @@ public class WorkflowDefinition : Entity
public DateTime CreatedAt { get; set; }
public int Version { get; set; } = 1;
public IActivity Root { get; set; } = default!;
public ICollection<ITrigger> Triggers { get; set; } = new List<ITrigger>();
public ICollection<Variable> Variables { get; set; } = new List<Variable>();
public bool IsLatest { get; set; }
public bool IsPublished { get; set; }

View file

@ -15,7 +15,6 @@ public class WorkflowDefinitionMapper
new WorkflowPublication(definition.IsLatest, definition.IsPublished),
new WorkflowMetadata(definition.Name, definition.Description, definition.CreatedAt),
definition.Root,
definition.Triggers,
definition.Variables);
}
@ -33,7 +32,6 @@ public class WorkflowDefinitionMapper
definition.Name = metadata.Name;
definition.Description = metadata.Description;
definition.Root = workflow.Root;
definition.Triggers = workflow.Triggers;
definition.CreatedAt = metadata.CreatedAt;
definition.IsLatest = publication.IsLatest;
definition.IsPublished = publication.IsPublished;

View file

@ -9,7 +9,6 @@ namespace Elsa.Persistence.EntityFrameworkCore.Configuration
public void Configure(EntityTypeBuilder<WorkflowDefinition> builder)
{
builder.Ignore(x => x.Root);
builder.Ignore(x => x.Triggers);
builder.Property<string>("Data");
builder.HasIndex(x => new {x.DefinitionId, x.Version}).HasDatabaseName($"IX_{nameof(WorkflowDefinition)}_{nameof(WorkflowDefinition.DefinitionId)}_{nameof(WorkflowDefinition.Version)}").IsUnique();

View file

@ -20,7 +20,6 @@ public class WorkflowDefinitionSerializer : IEntitySerializer<WorkflowDefinition
var data = new
{
entity.Root,
entity.Triggers
};
var options = _workflowSerializerOptionsProvider.CreatePersistenceOptions();
@ -31,7 +30,7 @@ public class WorkflowDefinitionSerializer : IEntitySerializer<WorkflowDefinition
public void Deserialize(ElsaDbContext dbContext, WorkflowDefinition entity)
{
var data = new WorkflowDefinitionState(entity.Root, entity.Triggers);
var data = new WorkflowDefinitionState(entity.Root);
var json = (string?) dbContext.Entry(entity).Property("Data").CurrentValue;
if (!string.IsNullOrWhiteSpace(json))
@ -41,7 +40,6 @@ public class WorkflowDefinitionSerializer : IEntitySerializer<WorkflowDefinition
}
entity.Root = data.Root;
entity.Triggers = data.Triggers;
}
// Can't use records when using System.Text.Json serialization and reference handling. Hence, using a class with default constructor.
@ -51,13 +49,11 @@ public class WorkflowDefinitionSerializer : IEntitySerializer<WorkflowDefinition
{
}
public WorkflowDefinitionState(IActivity root, ICollection<ITrigger> triggers)
public WorkflowDefinitionState(IActivity root)
{
Root = root;
Triggers = triggers;
}
public IActivity Root { get; init; } = default!;
public ICollection<ITrigger> Triggers { get; init; } = default!;
}
}

View file

@ -4,7 +4,7 @@ using Elsa.Runtime.Middleware;
namespace Elsa.Runtime.Extensions;
public static class WorkflowExecutionBuilderExtensions
public static class WorkflowExecutionPipelineBuilderExtensions
{
public static IWorkflowExecutionBuilder UsePersistence(this IWorkflowExecutionBuilder builder) => builder.UseMiddleware<PersistWorkflowInstanceMiddleware>();
public static IWorkflowExecutionBuilder UseWorkflowExecutionEvents(this IWorkflowExecutionBuilder builder) => builder.UseMiddleware<PublishWorkflowExecutionEventsMiddleware>();

View file

@ -12,7 +12,6 @@ using Elsa.Persistence.Entities;
using Elsa.Persistence.Requests;
using Elsa.Runtime.Contracts;
using Elsa.Runtime.Models;
using Elsa.Runtime.Notifications;
using Microsoft.Extensions.Logging;
namespace Elsa.Runtime.Services;
@ -26,6 +25,7 @@ namespace Elsa.Runtime.Services;
public class TriggerIndexer : ITriggerIndexer
{
private readonly IWorkflowRegistry _workflowRegistry;
private readonly IActivityWalker _activityWalker;
private readonly IExpressionEvaluator _expressionEvaluator;
private readonly IIdentityGenerator _identityGenerator;
private readonly IRequestSender _requestSender;
@ -37,6 +37,7 @@ public class TriggerIndexer : ITriggerIndexer
public TriggerIndexer(
IWorkflowRegistry workflowRegistry,
IActivityWalker activityWalker,
IExpressionEvaluator expressionEvaluator,
IIdentityGenerator identityGenerator,
IRequestSender requestSender,
@ -47,6 +48,7 @@ public class TriggerIndexer : ITriggerIndexer
ILogger<TriggerIndexer> logger)
{
_workflowRegistry = workflowRegistry;
_activityWalker = activityWalker;
_expressionEvaluator = expressionEvaluator;
_identityGenerator = identityGenerator;
_requestSender = requestSender;
@ -112,11 +114,11 @@ public class TriggerIndexer : ITriggerIndexer
private async IAsyncEnumerable<WorkflowTrigger> GetTriggersAsync(Workflow workflow, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var context = new WorkflowIndexingContext(workflow);
var triggerSources = workflow.Triggers;
var triggerNodes = _activityWalker.Walk(workflow.Root).Flatten().Where(x => x.Activity is ITrigger { TriggerMode: TriggerMode.WorkflowDefinition }).ToList();
foreach (var triggerSource in triggerSources)
foreach (var triggerNode in triggerNodes)
{
var triggers = await GetTriggersAsync(workflow, context, triggerSource, cancellationToken);
var triggers = await GetTriggersAsync(workflow, context, (ITrigger)triggerNode.Activity, cancellationToken);
foreach (var trigger in triggers)
yield return trigger;
@ -167,7 +169,7 @@ public class TriggerIndexer : ITriggerIndexer
{
try
{
return (await trigger.GetPayloadsAsync(context, cancellationToken)).ToList();
return (await trigger.GetTriggerPayloadsAsync(context, cancellationToken)).ToList();
}
catch (Exception e)
{

View file

@ -17,7 +17,7 @@ using Elsa.Modules.Scheduling.Triggers;
using Elsa.Persistence.EntityFrameworkCore.Extensions;
using Elsa.Persistence.EntityFrameworkCore.Sqlite;
using Elsa.Pipelines.WorkflowExecution.Components;
using Elsa.Runtime.Middleware;
using Elsa.Runtime.Extensions;
using Elsa.Runtime.ProtoActor.Extensions;
using Elsa.Samples.Web1.Activities;
using Elsa.Samples.Web1.Workflows;
@ -28,7 +28,6 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using WorkflowExecutionBuilderExtensions = Elsa.Runtime.Extensions.WorkflowExecutionBuilderExtensions;
var builder = WebApplication.CreateBuilder(args);
@ -70,16 +69,12 @@ services
.AddActivity<HttpTrigger>()
.AddActivity<Flowchart>()
.AddActivity<Delay>()
.AddActivity<Timer>()
.AddActivity<ForEach>()
.AddActivity<Switch>()
.AddActivity<SendMessage>()
;
// Register available triggers.
services
.AddTrigger<HttpTrigger>()
.AddTrigger<Timer>();
// Register scripting languages.
services
.AddJavaScriptExpressions()
@ -97,8 +92,12 @@ wellKnownTypeRegistry.RegisterType<bool>("boolean");
wellKnownTypeRegistry.RegisterType<string>("string");
// Configure workflow engine execution pipeline.
serviceProvider.ConfigureDefaultWorkflowExecutionPipeline(pipeline => WorkflowExecutionBuilderExtensions.UseWorkflowExecutionEvents(WorkflowExecutionBuilderExtensions.UseWorkflowExecutionLogPersistence(WorkflowExecutionBuilderExtensions.UsePersistence(pipeline)))
.UseActivityScheduler()
serviceProvider.ConfigureDefaultWorkflowExecutionPipeline(pipeline =>
pipeline
.UseWorkflowExecutionEvents()
.UseWorkflowExecutionLogPersistence()
.UsePersistence()
.UseActivityScheduler()
);
if (app.Environment.IsDevelopment())

View file

@ -15,18 +15,16 @@ public class ForkedHttpWorkflow : IWorkflow
{
public void Build(IWorkflowDefinitionBuilder workflow)
{
// Add triggers.
workflow.AddTrigger(new HttpTrigger
{
Path = new Input<string>("/fork"),
SupportedMethods = new Input<ICollection<string>>(new[] { HttpMethods.Get })
});
// Setup workflow graph.
workflow.WithRoot(new Sequence
{
Activities =
{
new HttpTrigger
{
Path = new Input<string>("/fork"),
SupportedMethods = new Input<ICollection<string>>(new[] { HttpMethods.Get })
},
new Fork
{
JoinMode = new Input<JoinMode>(JoinMode.WaitAll),

View file

@ -19,18 +19,16 @@ public class HttpWorkflow : IWorkflow
public void Build(IWorkflowDefinitionBuilder workflow)
{
// Add triggers.
workflow.AddTrigger(new HttpTrigger
{
Path = new Input<string>("/hello-world"),
SupportedMethods = new Input<ICollection<string>>(new[] { HttpMethods.Get })
});
// Setup workflow graph.
workflow.WithRoot(new Sequence
{
Activities =
{
new HttpTrigger
{
Path = new Input<string>("/hello-world"),
SupportedMethods = new Input<ICollection<string>>(new[] { HttpMethods.Get })
},
new If
{
Condition = new Input<bool>(true),

View file

@ -14,20 +14,19 @@ public class ReceiveMessageWorkflow : IWorkflow
{
public void Build(IWorkflowDefinitionBuilder workflow)
{
// var receivedMessage = new Variable<string>();
// workflow.Variables.Add(receivedMessage);
workflow.AddTrigger(new MessageReceived
{
QueueOrTopic = new Input<string>("inbox"),
//ReceivedMessageBody = new Output<object>(receivedMessage)
});
var receivedMessage = new Variable<string>();
workflow.WithRoot(new Sequence
{
Variables = { receivedMessage },
Activities =
{
new WriteLine(context => $"Message received: {context.Input}")
new MessageReceived
{
QueueOrTopic = new Input<string>("inbox"),
ReceivedMessageBody = new Output<object>(receivedMessage)
},
new WriteLine(context => $"Message received: {receivedMessage.Get(context)}")
}
});
}

View file

@ -51,7 +51,7 @@ class Program
var workflow13 = new Func<IActivity>(BlockingParallelForEachWorkflow.Create);
var workflow14 = new Func<IActivity>(FlowchartWorkflow.Create);
var workflowFactory = workflow1;
var workflowFactory = workflow10;
var workflowGraph = workflowFactory();
var workflow = Workflow.FromActivity(workflowGraph);