diff --git a/Samples.sln.DotSettings b/Samples.sln.DotSettings index 4f264434f..cde7b3f37 100644 --- a/Samples.sln.DotSettings +++ b/Samples.sln.DotSettings @@ -10,6 +10,7 @@ True True True + True True True True \ No newline at end of file diff --git a/src/clients/Elsa.Client/Class1.cs b/src/clients/Elsa.Client/Class1.cs deleted file mode 100644 index 0a701b853..000000000 --- a/src/clients/Elsa.Client/Class1.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace Elsa.Client -{ - public class Class1 - { - } -} \ No newline at end of file diff --git a/src/clients/Elsa.Client/Comparers/BlockingActivityEqualityComparer.cs b/src/clients/Elsa.Client/Comparers/BlockingActivityEqualityComparer.cs new file mode 100644 index 000000000..0d8b443ec --- /dev/null +++ b/src/clients/Elsa.Client/Comparers/BlockingActivityEqualityComparer.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using Elsa.Client.Models; + +namespace Elsa.Client.Comparers +{ + public class BlockingActivityEqualityComparer : IEqualityComparer + { + public static BlockingActivityEqualityComparer Instance { get; } = new BlockingActivityEqualityComparer(); + + public bool Equals(BlockingActivity x, BlockingActivity y) => x.ActivityId.Equals(y.ActivityId); + public int GetHashCode(BlockingActivity obj) => obj.ActivityId.GetHashCode(); + } +} \ No newline at end of file diff --git a/src/clients/Elsa.Client/Elsa.Client.csproj b/src/clients/Elsa.Client/Elsa.Client.csproj index 27560206d..9e2df3b5f 100644 --- a/src/clients/Elsa.Client/Elsa.Client.csproj +++ b/src/clients/Elsa.Client/Elsa.Client.csproj @@ -2,6 +2,26 @@ netstandard2.0 + latest + 1.0.0 + Elsa Contributors + + Elsa is a set of workflow libraries and tools that enable lean and mean workflowing capabilities in any .NET Core application. + This package provides a client using Refit for Elsa's REST API endpoints. + + 2020 + https://github.com/elsa-workflows/elsa-core + https://github.com/elsa-workflows/elsa-core + GitHub + elsa, workflows, rest, api, client + icon.png + enable + + + + + + diff --git a/src/clients/Elsa.Client/ElsaClient.cs b/src/clients/Elsa.Client/ElsaClient.cs new file mode 100644 index 000000000..497a2ff1c --- /dev/null +++ b/src/clients/Elsa.Client/ElsaClient.cs @@ -0,0 +1,14 @@ +using Elsa.Client.Services; + +namespace Elsa.Client +{ + internal class ElsaClient : IElsaClient + { + public ElsaClient(IWorkflowDefinitionsApi workflowDefinitionsApi) + { + WorkflowDefinitions = workflowDefinitionsApi; + } + + public IWorkflowDefinitionsApi WorkflowDefinitions { get; } + } +} \ No newline at end of file diff --git a/src/clients/Elsa.Client/IElsaClient.cs b/src/clients/Elsa.Client/IElsaClient.cs new file mode 100644 index 000000000..48c0818dc --- /dev/null +++ b/src/clients/Elsa.Client/IElsaClient.cs @@ -0,0 +1,9 @@ +using Elsa.Client.Services; + +namespace Elsa.Client +{ + public interface IElsaClient + { + IWorkflowDefinitionsApi WorkflowDefinitions { get; } + } +} \ No newline at end of file diff --git a/src/clients/Elsa.Client/Models/ActivityDefinition.cs b/src/clients/Elsa.Client/Models/ActivityDefinition.cs new file mode 100644 index 000000000..d962bde9c --- /dev/null +++ b/src/clients/Elsa.Client/Models/ActivityDefinition.cs @@ -0,0 +1,17 @@ +namespace Elsa.Client.Models +{ + public class ActivityDefinition + { + public string ActivityId { get; set; } = default!; + public string Type { get; set; } = default!; + public string? Name { get; set; } + public string? DisplayName { get; set; } + public string? Description { get; set; } + public int? Left { get; set; } + public int? Top { get; set; } + public bool PersistWorkflow { get; set; } + public bool LoadWorkflowContext { get; set; } + public bool SaveWorkflowContext { get; set; } + public ActivityDefinitionProperties Properties { get; set; } = new ActivityDefinitionProperties(); + } +} \ No newline at end of file diff --git a/src/clients/Elsa.Client/Models/ActivityDefinitionProperties.cs b/src/clients/Elsa.Client/Models/ActivityDefinitionProperties.cs new file mode 100644 index 000000000..84f639469 --- /dev/null +++ b/src/clients/Elsa.Client/Models/ActivityDefinitionProperties.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace Elsa.Client.Models +{ + public class ActivityDefinitionProperties : Dictionary + { + } +} \ No newline at end of file diff --git a/src/clients/Elsa.Client/Models/ActivityDefinitionPropertyValue.cs b/src/clients/Elsa.Client/Models/ActivityDefinitionPropertyValue.cs new file mode 100644 index 000000000..28e6fe179 --- /dev/null +++ b/src/clients/Elsa.Client/Models/ActivityDefinitionPropertyValue.cs @@ -0,0 +1,26 @@ +using System; + +namespace Elsa.Client.Models +{ + public class ActivityDefinitionPropertyValue + { + public static ActivityDefinitionPropertyValue Literal(T value) => new ActivityDefinitionPropertyValue(value!.ToString(), "Literal", typeof(T)); + public static ActivityDefinitionPropertyValue Liquid(string expression) => new ActivityDefinitionPropertyValue(expression, "Liquid", typeof(T)); + public static ActivityDefinitionPropertyValue JavaScript(string expression) => new ActivityDefinitionPropertyValue(expression, "JavaScript", typeof(T)); + + public ActivityDefinitionPropertyValue() + { + } + + public ActivityDefinitionPropertyValue(string expression, string syntax, Type type) + { + Expression = expression; + Syntax = syntax; + Type = type; + } + + public Type Type { get; set; } = default!; + public string Syntax { get; set; } = default!; + public string Expression { get; set; } = default!; + } +} \ No newline at end of file diff --git a/src/clients/Elsa.Client/Models/ActivityInstance.cs b/src/clients/Elsa.Client/Models/ActivityInstance.cs new file mode 100644 index 000000000..5a1ada856 --- /dev/null +++ b/src/clients/Elsa.Client/Models/ActivityInstance.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json.Linq; + +namespace Elsa.Client.Models +{ + public class ActivityInstance + { + public ActivityInstance() + { + } + + public ActivityInstance(string id, string type, object? output, JObject data) + { + Id = id; + Type = type; + Data = data; + Output = output; + } + + public string? Id { get; set; } + public string? Type { get; set; } + public JObject Data { get; set; } = new JObject(); + public object? Output { get; set; } + } +} \ No newline at end of file diff --git a/src/clients/Elsa.Client/Models/BlockingActivity.cs b/src/clients/Elsa.Client/Models/BlockingActivity.cs new file mode 100644 index 000000000..b55524398 --- /dev/null +++ b/src/clients/Elsa.Client/Models/BlockingActivity.cs @@ -0,0 +1,19 @@ +namespace Elsa.Client.Models +{ + public class BlockingActivity + { + public BlockingActivity() + { + } + + public BlockingActivity(string activityId, string activityType) + { + ActivityId = activityId; + ActivityType = activityType; + } + + public string ActivityId { get; set; } = default!; + public string ActivityType { get; set; }= default!; + public string? Tag { get; set; } + } +} \ No newline at end of file diff --git a/src/clients/Elsa.Client/Models/CompositeActivityDefinition.cs b/src/clients/Elsa.Client/Models/CompositeActivityDefinition.cs new file mode 100644 index 000000000..2a35a5e4c --- /dev/null +++ b/src/clients/Elsa.Client/Models/CompositeActivityDefinition.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace Elsa.Client.Models +{ + public class CompositeActivityDefinition : ActivityDefinition + { + public CompositeActivityDefinition() + { + Activities = new List(); + Connections = new List(); + Type = "CompositeActivity"; + } + + public ICollection Activities { get; set; } + public ICollection Connections { get; set; } + } +} \ No newline at end of file diff --git a/src/clients/Elsa.Client/Models/ConnectionDefinition.cs b/src/clients/Elsa.Client/Models/ConnectionDefinition.cs new file mode 100644 index 000000000..434faab49 --- /dev/null +++ b/src/clients/Elsa.Client/Models/ConnectionDefinition.cs @@ -0,0 +1,20 @@ +namespace Elsa.Client.Models +{ + public class ConnectionDefinition + { + public ConnectionDefinition() + { + } + + public ConnectionDefinition(string sourceActivityId, string targetActivityId, string outcome) + { + SourceActivityId = sourceActivityId; + TargetActivityId = targetActivityId; + Outcome = outcome; + } + + public string? SourceActivityId { get; set; } + public string? TargetActivityId { get; set; } + public string? Outcome { get; set; } + } +} \ No newline at end of file diff --git a/src/clients/Elsa.Client/Models/ExecutionLogEntry.cs b/src/clients/Elsa.Client/Models/ExecutionLogEntry.cs new file mode 100644 index 000000000..053908578 --- /dev/null +++ b/src/clients/Elsa.Client/Models/ExecutionLogEntry.cs @@ -0,0 +1,20 @@ +using NodaTime; + +namespace Elsa.Client.Models +{ + public class ExecutionLogEntry + { + public ExecutionLogEntry() + { + } + + public ExecutionLogEntry(string activityId, Instant timestamp) + { + ActivityId = activityId; + Timestamp = timestamp; + } + + public string ActivityId { get; set; } = default!; + public Instant Timestamp { get; set; } + } +} \ No newline at end of file diff --git a/src/clients/Elsa.Client/Models/ScheduledActivity.cs b/src/clients/Elsa.Client/Models/ScheduledActivity.cs new file mode 100644 index 000000000..1965c8828 --- /dev/null +++ b/src/clients/Elsa.Client/Models/ScheduledActivity.cs @@ -0,0 +1,18 @@ +namespace Elsa.Client.Models +{ + public class ScheduledActivity + { + public ScheduledActivity() + { + } + + public ScheduledActivity(string activityId, object? input = default) + { + ActivityId = activityId; + Input = input; + } + + public string ActivityId { get; set; } = default!; + public object? Input { get; set; } + } +} \ No newline at end of file diff --git a/src/clients/Elsa.Client/Models/Scheduler.cs b/src/clients/Elsa.Client/Models/Scheduler.cs new file mode 100644 index 000000000..4f859f80a --- /dev/null +++ b/src/clients/Elsa.Client/Models/Scheduler.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Elsa.Client.Models +{ + public class Scheduler + { + private readonly Stack _stack = new Stack(); + + public bool HasScheduledActivities => _stack.Any(); + + public ScheduledActivity Schedule(string activityId, object? input = null) + { + var scheduledActivity = new ScheduledActivity(activityId, input); + _stack.Push(scheduledActivity); + return scheduledActivity; + } + + public ScheduledActivity Next() => _stack.Pop(); + } +} \ No newline at end of file diff --git a/src/clients/Elsa.Client/Models/Variables.cs b/src/clients/Elsa.Client/Models/Variables.cs new file mode 100644 index 000000000..6179a0526 --- /dev/null +++ b/src/clients/Elsa.Client/Models/Variables.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; + +namespace Elsa.Client.Models +{ + public class Variables + { + public Variables() + { + Data = new Dictionary(); + } + + public Variables(Variables other) : this(other.Data) + { + } + + public Variables(IDictionary data) + { + Data = data; + } + + public IDictionary Data { get; set; } + + public object? Get(string name) => Has(name) ? Data[name] : default; + + public T Get(string name) + { + if (!Has(name)) + return default!; + + var value = Get(name); + + if (value == null) + return default!; + + return (T)value!; + } + + public Variables Set(string name, object? value) + { + Data[name] = value; + return this; + } + + public bool Has(string name) => Data.ContainsKey(name); + } +} \ No newline at end of file diff --git a/src/clients/Elsa.Client/Models/VersionOptions.cs b/src/clients/Elsa.Client/Models/VersionOptions.cs new file mode 100644 index 000000000..abf854da5 --- /dev/null +++ b/src/clients/Elsa.Client/Models/VersionOptions.cs @@ -0,0 +1,42 @@ +namespace Elsa.Client.Models +{ + public struct VersionOptions + { + /// + /// Gets the latest version. + /// + public static readonly VersionOptions Latest = new VersionOptions { IsLatest = true }; + + /// + /// Gets the published version. + /// + public static readonly VersionOptions Published = new VersionOptions { IsPublished = true }; + + /// + /// Gets the latest or published version. + /// + public static readonly VersionOptions LatestOrPublished = new VersionOptions { IsLatestOrPublished = true }; + + /// + /// Gets the draft version. + /// + public static readonly VersionOptions Draft = new VersionOptions { IsDraft = true }; + + /// + /// Gets all versions. + /// + public static readonly VersionOptions All = new VersionOptions { AllVersions = true }; + + /// + /// Gets a specific version. + /// + public static VersionOptions SpecificVersion(int version) => new VersionOptions { Version = version }; + + public bool IsLatest { get; private set; } + public bool IsLatestOrPublished { get; private set; } + public bool IsPublished { get; private set; } + public bool IsDraft { get; private set; } + public bool AllVersions { get; private set; } + public int Version { get; private set; } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Services/Models/WorkflowContextFidelity.cs b/src/clients/Elsa.Client/Models/WorkflowContextFidelity.cs similarity index 90% rename from src/core/Elsa.Abstractions/Services/Models/WorkflowContextFidelity.cs rename to src/clients/Elsa.Client/Models/WorkflowContextFidelity.cs index bbf48b70d..fbc9228f2 100644 --- a/src/core/Elsa.Abstractions/Services/Models/WorkflowContextFidelity.cs +++ b/src/clients/Elsa.Client/Models/WorkflowContextFidelity.cs @@ -1,4 +1,4 @@ -namespace Elsa.Services.Models +namespace Elsa.Client.Models { public enum WorkflowContextFidelity { diff --git a/src/core/Elsa.Abstractions/Services/Models/WorkflowContextOptions.cs b/src/clients/Elsa.Client/Models/WorkflowContextOptions.cs similarity index 86% rename from src/core/Elsa.Abstractions/Services/Models/WorkflowContextOptions.cs rename to src/clients/Elsa.Client/Models/WorkflowContextOptions.cs index 37eff80dd..3a0b2c037 100644 --- a/src/core/Elsa.Abstractions/Services/Models/WorkflowContextOptions.cs +++ b/src/clients/Elsa.Client/Models/WorkflowContextOptions.cs @@ -1,6 +1,6 @@ using System; -namespace Elsa.Services.Models +namespace Elsa.Client.Models { public class WorkflowContextOptions { diff --git a/src/clients/Elsa.Client/Models/WorkflowDefinition.cs b/src/clients/Elsa.Client/Models/WorkflowDefinition.cs new file mode 100644 index 000000000..416e1fbb5 --- /dev/null +++ b/src/clients/Elsa.Client/Models/WorkflowDefinition.cs @@ -0,0 +1,24 @@ +namespace Elsa.Client.Models +{ + public class WorkflowDefinition : CompositeActivityDefinition + { + public WorkflowDefinition() + { + Variables = new Variables(); + Type = "Workflow"; + } + + public int Id { get; set; } + public string WorkflowDefinitionId { get; set; } = default!; + public string WorkflowDefinitionVersionId { get; set; } = default!; + public int Version { get; set; } + public Variables? Variables { get; set; } + public WorkflowContextOptions? ContextOptions { get; set; } + public bool IsSingleton { get; set; } + public WorkflowPersistenceBehavior PersistenceBehavior { get; set; } + public bool DeleteCompletedInstances { get; set; } + public bool IsEnabled { get; set; } + public bool IsPublished { get; set; } + public bool IsLatest { get; set; } + } +} \ No newline at end of file diff --git a/src/clients/Elsa.Client/Models/WorkflowExecutionScope.cs b/src/clients/Elsa.Client/Models/WorkflowExecutionScope.cs new file mode 100644 index 000000000..33297e39a --- /dev/null +++ b/src/clients/Elsa.Client/Models/WorkflowExecutionScope.cs @@ -0,0 +1,17 @@ +namespace Elsa.Client.Models +{ + public class WorkflowExecutionScope + { + public WorkflowExecutionScope() + { + Variables = new Variables(); + } + + public WorkflowExecutionScope(Variables variables) + { + Variables = variables; + } + + public Variables Variables { get; } + } +} \ No newline at end of file diff --git a/src/clients/Elsa.Client/Models/WorkflowFault.cs b/src/clients/Elsa.Client/Models/WorkflowFault.cs new file mode 100644 index 000000000..6822de3f1 --- /dev/null +++ b/src/clients/Elsa.Client/Models/WorkflowFault.cs @@ -0,0 +1,8 @@ +namespace Elsa.Client.Models +{ + public class WorkflowFault + { + public string? FaultedActivityId { get; set; } + public string? Message { get; set; } + } +} \ No newline at end of file diff --git a/src/clients/Elsa.Client/Models/WorkflowInstance.cs b/src/clients/Elsa.Client/Models/WorkflowInstance.cs new file mode 100644 index 000000000..ecad13631 --- /dev/null +++ b/src/clients/Elsa.Client/Models/WorkflowInstance.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using Elsa.Client.Comparers; +using NodaTime; + +namespace Elsa.Client.Models +{ + public class WorkflowInstance + { + private HashSet _blockingActivities = new HashSet(BlockingActivityEqualityComparer.Instance); + + public WorkflowInstance() + { + Variables = new Variables(); + Activities = new List(); + ExecutionLog = new List(); + ScheduledActivities = new Stack(); + PostScheduledActivities = new Stack(); + } + + public int Id { get; set; } + public string WorkflowInstanceId { get; set; } = default!; + public string WorkflowDefinitionId { get; set; } = default!; + public int Version { get; set; } + public WorkflowStatus Status { get; set; } + public string? CorrelationId { get; set; } + public string? ContextId { get; set; } + public Instant CreatedAt { get; set; } + public Variables Variables { get; set; } + public object? Output { get; set; } + public ICollection Activities { get; set; } + + public HashSet BlockingActivities + { + get => _blockingActivities; + set => _blockingActivities = new HashSet(value, BlockingActivityEqualityComparer.Instance); + } + + public ICollection ExecutionLog { get; set; } + public WorkflowFault? Fault { get; set; } + public Stack ScheduledActivities { get; set; } + public Stack PostScheduledActivities { get; set; } + } +} \ No newline at end of file diff --git a/src/clients/Elsa.Client/Models/WorkflowPersistenceBehavior.cs b/src/clients/Elsa.Client/Models/WorkflowPersistenceBehavior.cs new file mode 100644 index 000000000..1342a47b0 --- /dev/null +++ b/src/clients/Elsa.Client/Models/WorkflowPersistenceBehavior.cs @@ -0,0 +1,20 @@ +namespace Elsa.Client.Models +{ + public enum WorkflowPersistenceBehavior + { + /// + /// Workflow instances are persisted only when being suspended. + /// + Suspended, + + /// + /// Workflow instances are persisted after the workflow executed scheduled activities. + /// + WorkflowPassCompleted, + + /// + /// Workflow instances are persisted after each activity that executed. + /// + ActivityExecuted + } +} \ No newline at end of file diff --git a/src/clients/Elsa.Client/Models/WorkflowStatus.cs b/src/clients/Elsa.Client/Models/WorkflowStatus.cs new file mode 100644 index 000000000..695ed7992 --- /dev/null +++ b/src/clients/Elsa.Client/Models/WorkflowStatus.cs @@ -0,0 +1,12 @@ +namespace Elsa.Client.Models +{ + public enum WorkflowStatus + { + Idle, + Running, + Finished, + Suspended, + Faulted, + Cancelled + } +} diff --git a/src/clients/Elsa.Client/Services/IWorkflowDefinitionsApi.cs b/src/clients/Elsa.Client/Services/IWorkflowDefinitionsApi.cs new file mode 100644 index 000000000..217cca87f --- /dev/null +++ b/src/clients/Elsa.Client/Services/IWorkflowDefinitionsApi.cs @@ -0,0 +1,16 @@ +using System.Threading; +using System.Threading.Tasks; +using Elsa.Client.Models; +using Refit; + +namespace Elsa.Client.Services +{ + public interface IWorkflowDefinitionsApi + { + [Get("/v1/workflow-definitions/{id}/latest")] + Task GetLatestAsync([AliasAs("id")] string workflowDefinitionId, CancellationToken cancellationToken = default); + + [Get("/v1/workflow-definitions/{id}/published")] + Task GetPublishedAsync([AliasAs("id")] string workflowDefinitionId, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Extensions/WorkflowBlueprintExtensions.cs b/src/core/Elsa.Abstractions/Extensions/WorkflowBlueprintExtensions.cs index 7e1aa0e22..94703e5df 100644 --- a/src/core/Elsa.Abstractions/Extensions/WorkflowBlueprintExtensions.cs +++ b/src/core/Elsa.Abstractions/Extensions/WorkflowBlueprintExtensions.cs @@ -42,19 +42,16 @@ namespace Elsa } public static T GetActivityState( - this IWorkflowBlueprint workflowBlueprint, - string activityId, + this IWorkflowBlueprint workflowBlueprint, Expression> propertyExpression, ActivityExecutionContext activityExecutionContext) where TActivity : IActivity { var expression = (MemberExpression)propertyExpression.Body; string propertyName = expression.Member.Name; - return GetActivityState(workflowBlueprint, activityId, propertyName, activityExecutionContext); + return GetActivityState(propertyName, activityExecutionContext); } public static T GetActivityState( - this IWorkflowBlueprint workflowBlueprint, - string activityId, string propertyName, ActivityExecutionContext activityExecutionContext) { diff --git a/src/core/Elsa.Abstractions/Models/ActivityDefinitionPropertyValue.cs b/src/core/Elsa.Abstractions/Models/ActivityDefinitionPropertyValue.cs index fad658632..20e5edca5 100644 --- a/src/core/Elsa.Abstractions/Models/ActivityDefinitionPropertyValue.cs +++ b/src/core/Elsa.Abstractions/Models/ActivityDefinitionPropertyValue.cs @@ -1,4 +1,6 @@ using System; +using Elsa.Converters; +using Newtonsoft.Json; namespace Elsa.Models { @@ -19,7 +21,7 @@ namespace Elsa.Models Type = type; } - public Type Type { get; set; } = default!; + [JsonConverter(typeof(TypeConverter))] public Type Type { get; set; } = default!; public string Syntax { get; set; } = default!; public string Expression { get; set; } = default!; } diff --git a/src/core/Elsa.Abstractions/Models/WorkflowContextFidelity.cs b/src/core/Elsa.Abstractions/Models/WorkflowContextFidelity.cs new file mode 100644 index 000000000..ae914bbc3 --- /dev/null +++ b/src/core/Elsa.Abstractions/Models/WorkflowContextFidelity.cs @@ -0,0 +1,15 @@ +namespace Elsa.Models +{ + public enum WorkflowContextFidelity + { + /// + /// The workflow context is loaded only once per burst of execution. + /// + Burst, + + /// + /// The workflow context is loaded before executing an activity. + /// + Activity + } +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Models/WorkflowContextOptions.cs b/src/core/Elsa.Abstractions/Models/WorkflowContextOptions.cs new file mode 100644 index 000000000..60464da40 --- /dev/null +++ b/src/core/Elsa.Abstractions/Models/WorkflowContextOptions.cs @@ -0,0 +1,11 @@ +using System; +using Elsa.Services.Models; + +namespace Elsa.Models +{ + public class WorkflowContextOptions + { + public Type ContextType { get; set; } = default!; + public WorkflowContextFidelity ContextFidelity { get; set; } + } +} \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Services/IWorkflowDefinitionManager.cs b/src/core/Elsa.Abstractions/Services/IWorkflowDefinitionManager.cs index aa3ecf6df..51e0a7d58 100644 --- a/src/core/Elsa.Abstractions/Services/IWorkflowDefinitionManager.cs +++ b/src/core/Elsa.Abstractions/Services/IWorkflowDefinitionManager.cs @@ -11,9 +11,10 @@ namespace Elsa.Services { public interface IWorkflowDefinitionManager { - ValueTask SaveAsync(WorkflowDefinition workflowDefinition, CancellationToken cancellationToken = default); - ValueTask DeleteAsync(WorkflowDefinition workflowDefinition, CancellationToken cancellationToken = default); - ValueTask> ListAsync(CancellationToken cancellationToken = default); + Task SaveAsync(WorkflowDefinition workflowDefinition, CancellationToken cancellationToken = default); + Task DeleteAsync(WorkflowDefinition workflowDefinition, CancellationToken cancellationToken = default); + Task CountAsync(VersionOptions? version = default, CancellationToken cancellationToken = default); + Task> ListAsync(int? skip = default, int? take = default, VersionOptions? version = default, CancellationToken cancellationToken = default); IQuery Query(); IQuery Query() where TIndex : class, IIndex; diff --git a/src/core/Elsa.Abstractions/Triggers/ActivityBlueprintWrapper.cs b/src/core/Elsa.Abstractions/Triggers/ActivityBlueprintWrapper.cs index dbf36223e..907bdcab4 100644 --- a/src/core/Elsa.Abstractions/Triggers/ActivityBlueprintWrapper.cs +++ b/src/core/Elsa.Abstractions/Triggers/ActivityBlueprintWrapper.cs @@ -26,8 +26,7 @@ namespace Elsa.Triggers public T GetState(Expression> propertyExpression) { var workflowBlueprint = _activityExecutionContext.WorkflowExecutionContext.WorkflowBlueprint; - var activityId = _activityExecutionContext.ActivityBlueprint.Id; - return workflowBlueprint.GetActivityState(activityId, propertyExpression, _activityExecutionContext); + return workflowBlueprint.GetActivityState(propertyExpression, _activityExecutionContext); } } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Services/WorkflowDefinitionManager.cs b/src/core/Elsa.Core/Services/WorkflowDefinitionManager.cs index d51832ee0..0a85530fd 100644 --- a/src/core/Elsa.Core/Services/WorkflowDefinitionManager.cs +++ b/src/core/Elsa.Core/Services/WorkflowDefinitionManager.cs @@ -4,6 +4,7 @@ using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; using Elsa.Data; +using Elsa.Extensions; using Elsa.Models; using YesSql; using YesSql.Indexes; @@ -19,19 +20,36 @@ namespace Elsa.Services _session = session; } - public ValueTask SaveAsync(WorkflowDefinition workflowDefinition, CancellationToken cancellationToken = default) + public Task SaveAsync(WorkflowDefinition workflowDefinition, CancellationToken cancellationToken = default) { _session.Save(workflowDefinition, CollectionNames.WorkflowDefinitions); - return new ValueTask(); + return Task.CompletedTask; } - public ValueTask DeleteAsync(WorkflowDefinition workflowDefinition, CancellationToken cancellationToken = default) + public Task DeleteAsync(WorkflowDefinition workflowDefinition, CancellationToken cancellationToken = default) { _session.Delete(workflowDefinition, CollectionNames.WorkflowDefinitions); - return new ValueTask(); + return Task.CompletedTask; + } + + public async Task CountAsync(VersionOptions? version = default, CancellationToken cancellationToken = default) => + await Query() + .WithVersion(version ?? VersionOptions.Published) + .CountAsync(); + + public async Task> ListAsync(int? skip = default, int? take = default, VersionOptions? version = default, CancellationToken cancellationToken = default) + { + var query = Query().WithVersion(version ?? VersionOptions.Published); + + if (skip != null) + query = query.Skip(skip.Value); + + if (take != null) + query = query.Take(take.Value); + + return await query.ListAsync(); } - public async ValueTask> ListAsync(CancellationToken cancellationToken = default) => await Query().ListAsync(); public IQuery Query() => _session.Query(CollectionNames.WorkflowDefinitions); public IQuery Query() where TIndex : class, IIndex => _session.Query(CollectionNames.WorkflowDefinitions); public IQuery Query(Expression> predicate) where TIndex : class, IIndex => _session.Query(predicate, CollectionNames.WorkflowDefinitions); diff --git a/src/core/Elsa.Core/WorkflowProviders/DatabaseWorkflowProvider.cs b/src/core/Elsa.Core/WorkflowProviders/DatabaseWorkflowProvider.cs index f28baee9d..c7e8e66c4 100644 --- a/src/core/Elsa.Core/WorkflowProviders/DatabaseWorkflowProvider.cs +++ b/src/core/Elsa.Core/WorkflowProviders/DatabaseWorkflowProvider.cs @@ -23,7 +23,7 @@ namespace Elsa.WorkflowProviders protected override async ValueTask> OnGetWorkflowsAsync(CancellationToken cancellationToken) { - var workflowDefinitions = await _workflowDefinitionManager.ListAsync(cancellationToken); + var workflowDefinitions = await _workflowDefinitionManager.ListAsync(cancellationToken: cancellationToken); return workflowDefinitions.Select(_workflowBlueprintMaterializer.CreateWorkflowBlueprint); } } diff --git a/src/server/Elsa.Server.Api/Elsa.Server.Api.csproj b/src/server/Elsa.Server.Api/Elsa.Server.Api.csproj index 113af2433..9700a14f0 100644 --- a/src/server/Elsa.Server.Api/Elsa.Server.Api.csproj +++ b/src/server/Elsa.Server.Api/Elsa.Server.Api.csproj @@ -28,6 +28,7 @@ + diff --git a/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/Get.cs b/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/Get.cs index 47940db17..e4761b98c 100644 --- a/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/Get.cs +++ b/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/Get.cs @@ -1,32 +1,41 @@ -using System.Threading; +using System.Net; +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 Newtonsoft.Json.Serialization; +using Swashbuckle.AspNetCore.Filters; namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions { [ApiController] [ApiVersion("1")] - [Route("v{version:apiVersion}/workflow-definitions/{id}")] + [Route("v{version:apiVersion}/workflow-definitions/{id:int}")] + [Produces("application/json")] public class Get : Controller { private readonly IWorkflowDefinitionManager _workflowDefinitionManager; private readonly IContentSerializer _serializer; - public Get(IWorkflowDefinitionManager workflowDefinitionManager, IContentSerializer serializer) + protected Get(IWorkflowDefinitionManager workflowDefinitionManager, IContentSerializer serializer) { - _serializer = serializer; _workflowDefinitionManager = workflowDefinitionManager; + _serializer = serializer; } [HttpGet] - public async Task Handle([FromRoute(Name = "id")] string workflowDefinitionId, CancellationToken cancellationToken) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(WorkflowDefinition))] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [SwaggerResponseExample(StatusCodes.Status200OK, typeof(WorkflowDefinitionExample))] + public async Task Handle(string workflowDefinitionId, VersionOptions? version = default, CancellationToken cancellationToken = default) { - var workflowDefinition = await _workflowDefinitionManager.GetAsync(workflowDefinitionId, VersionOptions.Latest, cancellationToken); - return workflowDefinition == null ? (IActionResult)NotFound() : Json(workflowDefinition, _serializer.GetSettings()); + var workflowDefinition = await _workflowDefinitionManager.GetAsync(workflowDefinitionId, version ?? VersionOptions.Latest, cancellationToken); + return workflowDefinition == null ? (IActionResult) NotFound() : Json(workflowDefinition, _serializer.GetSettings()); } } } \ No newline at end of file diff --git a/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/List.cs b/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/List.cs new file mode 100644 index 000000000..251d1f973 --- /dev/null +++ b/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/List.cs @@ -0,0 +1,37 @@ +using System.Threading; +using System.Threading.Tasks; +using Elsa.Models; +using Elsa.Serialization; +using Elsa.Server.Api.Models; +using Elsa.Services; +using Microsoft.AspNetCore.Mvc; + +namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions +{ + [ApiController] + [ApiVersion("1")] + [Route("v{version:apiVersion}/workflow-definitions")] + [Produces("application/json")] + public class List : Controller + { + private readonly IWorkflowDefinitionManager _workflowDefinitionManager; + private readonly IContentSerializer _serializer; + + public List(IWorkflowDefinitionManager workflowDefinitionManager, IContentSerializer serializer) + { + _workflowDefinitionManager = workflowDefinitionManager; + _serializer = serializer; + } + + [HttpGet] + public async Task>> Handle(int page = 0, int pageSize = 50, VersionOptions? version = default, CancellationToken cancellationToken = default) + { + version ??= VersionOptions.Latest; + var totalCount = await _workflowDefinitionManager.CountAsync(version, cancellationToken); + var items = await _workflowDefinitionManager.ListAsync(page, pageSize, version, cancellationToken); + var pagedList = new PagedList(items, page, pageSize, totalCount); + + return Json(pagedList, _serializer.GetSettings()); + } + } +} \ No newline at end of file diff --git a/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/Post.cs b/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/Post.cs index 45f1923d8..568948e0b 100644 --- a/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/Post.cs +++ b/src/server/Elsa.Server.Api/Endpoints/WorkflowDefinitions/Post.cs @@ -2,18 +2,18 @@ using System.Threading.Tasks; using Elsa.Services; using Microsoft.AspNetCore.Mvc; -using YesSql; namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions { [ApiController] [ApiVersion("1")] [Route("v{version:apiVersion}/workflow-definitions")] + [Produces("application/json")] public class Post : ControllerBase { private readonly IWorkflowPublisher _workflowPublisher; - public Post(IWorkflowDefinitionManager workflowDefinitionManager, IWorkflowPublisher workflowPublisher, ISession session) + public Post(IWorkflowPublisher workflowPublisher) { _workflowPublisher = workflowPublisher; } diff --git a/src/server/Elsa.Server.Api/Models/PagedList.cs b/src/server/Elsa.Server.Api/Models/PagedList.cs new file mode 100644 index 000000000..6e4a6a535 --- /dev/null +++ b/src/server/Elsa.Server.Api/Models/PagedList.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Elsa.Server.Api.Models +{ + public class PagedList + { + public PagedList(IEnumerable items, int page, int pageSize, int totalCount) + { + Page = page; + PageSize = pageSize; + TotalCount = totalCount; + Items = items.ToList(); + } + + public ICollection Items { get; } + public int Page { get; } + public int PageSize { get; } + public int TotalCount { get; } + } +} \ No newline at end of file diff --git a/src/server/Elsa.Server.Api/Swagger/WorkflowDefinitionExample.cs b/src/server/Elsa.Server.Api/Swagger/WorkflowDefinitionExample.cs new file mode 100644 index 000000000..54501dc73 --- /dev/null +++ b/src/server/Elsa.Server.Api/Swagger/WorkflowDefinitionExample.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using Elsa.Models; +using Swashbuckle.AspNetCore.Filters; + +namespace Elsa.Server.Api.Swagger +{ + public class WorkflowDefinitionExample : IExamplesProvider + { + public WorkflowDefinition GetExamples() + { + return new WorkflowDefinition + { + Id = 1, + Name = "ProcessOrderWorkflow", + DisplayName = "Process Order Workflow", + Description = "Process new orders", + Version = 1, + IsPublished = true, + WorkflowDefinitionId = "26bd1dca39954c8f93364a18c2a35528", + WorkflowDefinitionVersionId = "c8c57402e68949598a45411c74e463e1", + Type = "Workflow", + ContextOptions = new WorkflowContextOptions + { + ContextFidelity = WorkflowContextFidelity.Burst, + ContextType = typeof(Order) + }, + Activities = new[] + { + new ActivityDefinition + { + ActivityId = "activity-1", + } + } + }; + } + } + + public class Order + { + } +} \ No newline at end of file diff --git a/src/server/Elsa.Server.Host/Elsa.Server.Host.csproj b/src/server/Elsa.Server.Host/Elsa.Server.Host.csproj index 8781dc2f2..1044c98a1 100644 --- a/src/server/Elsa.Server.Host/Elsa.Server.Host.csproj +++ b/src/server/Elsa.Server.Host/Elsa.Server.Host.csproj @@ -2,6 +2,7 @@ net5.0 + latest diff --git a/src/server/Elsa.Server.Host/Properties/launchSettings.json b/src/server/Elsa.Server.Host/Properties/launchSettings.json index 15a75cfa7..cacd3665c 100644 --- a/src/server/Elsa.Server.Host/Properties/launchSettings.json +++ b/src/server/Elsa.Server.Host/Properties/launchSettings.json @@ -1,26 +1,13 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:11000" + "profiles": { + "Elsa.Server.Host": { + "commandName": "Project", + "launchBrowser": false, + "applicationUrl": "http://localhost:11000", + "launchUrl": "http://localhost:11000/swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": false, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Elsa.Server.Host": { - "commandName": "Project", - "launchBrowser": false, - "applicationUrl": "http://localhost:11000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } } diff --git a/src/server/Elsa.Server.Host/Startup.cs b/src/server/Elsa.Server.Host/Startup.cs index 4510027d5..a51b07a94 100644 --- a/src/server/Elsa.Server.Host/Startup.cs +++ b/src/server/Elsa.Server.Host/Startup.cs @@ -1,4 +1,5 @@ using Elsa.Runtime; +using Elsa.Server.Api.Swagger; using Elsa.StartupTasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -6,6 +7,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.Filters; using YesSql.Provider.Sqlite; namespace Elsa.Server.Host @@ -31,8 +33,12 @@ namespace Elsa.Server.Host elsa => elsa .UsePersistence(config => config.UseSqLite(connectionString))); + services.AddSingleton(); services - .AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo {Title = "Elsa", Version = "v1"}); }) + .AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo {Title = "Elsa", Version = "v1"}); + }) .AddElsaApiEndpoints() .AddCors( options => options.AddDefaultPolicy(