Incremental work on tests, API endpoint, versioning and cleanup

This commit is contained in:
Sipke Schoorstra 2020-10-20 23:17:08 +02:00
parent bd3b41440a
commit 400b4f827a
61 changed files with 248 additions and 1051 deletions

View file

@ -50,8 +50,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Scripting.Liquid", "sr
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "server", "server", "{468E498C-59DB-4541-8D8A-0D89DE9083AB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Server.Core", "src\server\Elsa.Server.Core\Elsa.Server.Core.csproj", "{D91E2F7B-D5B7-4D1C-A3F0-B0475C33F539}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Server.Host", "src\server\Elsa.Server.Host\Elsa.Server.Host.csproj", "{FC54EDBA-71FB-4A87-8FD5-54759DF77CCE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{AB1AE008-6FD6-414C-8E88-D735F42E1FA6}"
@ -152,10 +150,6 @@ Global
{01DC21DD-C6E2-4B21-864E-97D29448F777}.Debug|Any CPU.Build.0 = Debug|Any CPU
{01DC21DD-C6E2-4B21-864E-97D29448F777}.Release|Any CPU.ActiveCfg = Release|Any CPU
{01DC21DD-C6E2-4B21-864E-97D29448F777}.Release|Any CPU.Build.0 = Release|Any CPU
{D91E2F7B-D5B7-4D1C-A3F0-B0475C33F539}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D91E2F7B-D5B7-4D1C-A3F0-B0475C33F539}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D91E2F7B-D5B7-4D1C-A3F0-B0475C33F539}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D91E2F7B-D5B7-4D1C-A3F0-B0475C33F539}.Release|Any CPU.Build.0 = Release|Any CPU
{FC54EDBA-71FB-4A87-8FD5-54759DF77CCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FC54EDBA-71FB-4A87-8FD5-54759DF77CCE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC54EDBA-71FB-4A87-8FD5-54759DF77CCE}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -251,7 +245,6 @@ Global
{18BE8F72-E528-4617-B1B1-07BD35337DFC} = {9C3C685E-3D7D-4638-A031-24F71CF74B52}
{01DC21DD-C6E2-4B21-864E-97D29448F777} = {9C3C685E-3D7D-4638-A031-24F71CF74B52}
{468E498C-59DB-4541-8D8A-0D89DE9083AB} = {DA71CDAA-8DD3-4D5F-9FBD-8E4B37A2D925}
{D91E2F7B-D5B7-4D1C-A3F0-B0475C33F539} = {468E498C-59DB-4541-8D8A-0D89DE9083AB}
{FC54EDBA-71FB-4A87-8FD5-54759DF77CCE} = {468E498C-59DB-4541-8D8A-0D89DE9083AB}
{0A3C36AB-2ACF-4175-A512-66B4C3DB9371} = {AB1AE008-6FD6-414C-8E88-D735F42E1FA6}
{0A6747EA-7AC6-4B6F-BC0C-C40C8A80C44D} = {AB1AE008-6FD6-414C-8E88-D735F42E1FA6}

View file

@ -27,5 +27,7 @@ namespace Elsa
}
public static void SetState(this JObject state, string key, object? value) => state[key] = value != null ? JToken.FromObject(value, Serializer) : null;
public static bool HasKey(this JObject state, string key) => state.ContainsKey(key);
}
}

View file

@ -1,9 +1,9 @@
using Elsa.Serialization.Extensions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Elsa.Models
{
public class Variables
public class Variables
{
public Variables()
{
@ -19,8 +19,8 @@ namespace Elsa.Models
Data = data;
}
public JObject Data { get; set; }
[JsonExtensionData] public JObject Data { get; set; }
public JToken? Get(string name) => Has(name) ? Data[name] : default;
public T Get<T>(string name) => Has(name) ? Data.Value<T>(name) : default!;

View file

@ -1,17 +0,0 @@
using System;
using Newtonsoft.Json.Linq;
namespace Elsa.Serialization.Extensions
{
public static class JTokenExtensions
{
public static T GetValue<T>(this JToken token, string key)
{
var value = ((JObject)token).GetValue(key, StringComparison.OrdinalIgnoreCase);
return value != null ? value.Value<T>() : default;
}
public static bool HasKey(this JToken token, string key) => ((JObject)token).ContainsKey(key);
}
}

View file

@ -1,23 +0,0 @@
using System.IO;
using System.Text;
namespace Elsa.Serialization.Extensions
{
public static class StringExtensions
{
public static Stream ToStream(this string text)
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(text);
writer.Flush();
stream.Position = 0;
return stream;
}
public static byte[] ToBytes(this string text)
{
return Encoding.UTF8.GetBytes(text);
}
}
}

View file

@ -1,12 +0,0 @@
using Newtonsoft.Json.Linq;
namespace Elsa.Serialization.Formatters
{
public interface ITokenFormatter
{
string Format { get; }
string ContentType { get; }
string ToString(JObject token);
JObject FromString(string data);
}
}

View file

@ -1,15 +0,0 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Elsa.Serialization.Handlers
{
public interface IValueHandler
{
int Priority { get; }
bool CanSerialize(JToken token, Type type, object value);
bool CanDeserialize(JToken token);
object? Deserialize(JsonSerializer serializer, JToken token);
void Serialize(JsonWriter writer, JsonSerializer serializer, Type type, JToken token, object value);
}
}

View file

@ -1,35 +0,0 @@
using System;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Elsa.Serialization.Extensions;
namespace Elsa.Serialization.Handlers
{
public abstract class PrimitiveValueHandler<T> : IValueHandler
{
public virtual int Priority => 0;
public bool CanSerialize(JToken token, Type type, object value) => type == typeof(T);
public bool CanDeserialize(JToken token) => token.Type == JTokenType.Object && token.GetValue<string>("Type") == TypeName;
protected virtual string TypeName => typeof(T).Name;
public virtual object Deserialize(JsonSerializer serializer, JToken token)
{
var valueToken = token["Value"];
return valueToken == null ? null! : ParseValue(valueToken);
}
public virtual void Serialize(JsonWriter writer, JsonSerializer serializer, Type type, JToken token, object? value)
{
var objectToken = new JObject
{
["Type"] = TypeName,
["Value"] = token
};
objectToken.WriteTo(writer, serializer.Converters.ToArray());
}
protected abstract object ParseValue(JToken value);
}
}

View file

@ -1,11 +0,0 @@
using Elsa.Services;
using Newtonsoft.Json.Linq;
namespace Elsa.Serialization
{
public interface IActivitySerializer
{
JObject Serialize<T>(T activity) where T : IActivity;
T Deserialize<T>(JObject data) where T : IActivity;
}
}

View file

@ -4,8 +4,9 @@ namespace Elsa.Serialization
{
public interface IContentSerializer
{
JObject Serialize<T>(T value);
string Serialize<T>(T value);
T Deserialize<T>(JToken token);
T Deserialize<T>(string json);
object GetSettings();
}
}

View file

@ -0,0 +1,7 @@
namespace Elsa.Services
{
public interface ICloner
{
T Clone<T>(T source);
}
}

View file

@ -8,13 +8,13 @@ namespace Elsa.Services
{
WorkflowDefinition New();
Task<WorkflowDefinition?> PublishAsync(string id, CancellationToken cancellationToken = default);
Task<WorkflowDefinition?> PublishAsync(string workflowDefinitionId, CancellationToken cancellationToken = default);
Task<WorkflowDefinition> PublishAsync(
WorkflowDefinition workflowDefinition,
CancellationToken cancellationToken = default);
Task<WorkflowDefinition?> GetDraftAsync(string id, CancellationToken cancellationToken= default);
Task<WorkflowDefinition?> GetDraftAsync(string workflowDefinitionId, CancellationToken cancellationToken= default);
Task<WorkflowDefinition> SaveDraftAsync(
WorkflowDefinition workflowDefinition,

View file

@ -1,5 +1,6 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using NodaTime;
using NodaTime.Serialization.JsonNet;
using YesSql;
@ -8,7 +9,25 @@ namespace Elsa.Data.Services
{
public class CustomJsonContentSerializer : IContentSerializer
{
private static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto }.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
private static readonly JsonSerializerSettings JsonSettings;
static CustomJsonContentSerializer()
{
JsonSettings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
}.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
JsonSettings.ContractResolver = new DefaultContractResolver
{
NamingStrategy = new DefaultNamingStrategy
{
ProcessDictionaryKeys = true,
ProcessExtensionDataNames = true
}
};
}
public object? Deserialize(string content, Type type) => JsonConvert.DeserializeObject(content, type, JsonSettings);
public dynamic? DeserializeDynamic(string content) => JsonConvert.DeserializeObject<dynamic>(content, JsonSettings);
public string Serialize(object item) => JsonConvert.SerializeObject(item, JsonSettings);

View file

@ -1,6 +1,7 @@
using System;
using Elsa.Caching;
using Elsa.DistributedLock;
using Elsa.Extensions;
using Elsa.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
@ -27,9 +28,15 @@ namespace Elsa
ServiceBusConfigurer = ConfigureInMemoryServiceBus;
JsonSerializerConfigurer = (sp, serializer) => {};
AddAutoMapper = () =>
{
services.AddAutoMapper(ServiceLifetime.Singleton);
services.AddSingleton(sp => sp.CreateAutoMapperConfiguration());
};
CreateJsonSerializer = sp =>
{
var serializer = ContentSerializer.CreateDefaultJsonSerializer();
var serializer = DefaultContentSerializer.CreateDefaultJsonSerializer();
JsonSerializerConfigurer(sp, serializer);
return serializer;
};
@ -42,6 +49,7 @@ namespace Elsa
internal Func<RebusConfigurer, IServiceProvider, RebusConfigurer> ServiceBusConfigurer { get; private set; }
internal Func<IServiceProvider, JsonSerializer> CreateJsonSerializer { get; private set; }
internal Action<IServiceProvider, JsonSerializer> JsonSerializerConfigurer { get; private set; }
internal Action AddAutoMapper { get; private set; }
public ElsaOptions UseDistributedLockProvider(Func<IServiceProvider, IDistributedLockProvider> factory)
{
@ -62,6 +70,12 @@ namespace Elsa
ConfigurePersistence = configure;
return this;
}
public ElsaOptions UseAutoMapper(Action addAutoMapper)
{
AddAutoMapper = addAutoMapper;
return this;
}
public ElsaOptions ConfigureServiceBus(Func<RebusConfigurer, IServiceProvider, RebusConfigurer> configure)
{

View file

@ -9,20 +9,17 @@ namespace Elsa.Extensions
{
public static IServiceCollection AddAutoMapper(this IServiceCollection services, ServiceLifetime lifetime)
{
services.TryAddSingleton(CreateConfigurationProvider);
services.TryAdd(new ServiceDescriptor(typeof(IMapper), sp => sp.GetRequiredService<IConfigurationProvider>().CreateMapper(sp.GetService), lifetime));
return services;
}
public static IServiceCollection AddAutoMapperProfile<TProfile>(this IServiceCollection services, ServiceLifetime lifetime) where TProfile : Profile
public static IServiceCollection AddAutoMapperProfile<TProfile>(this IServiceCollection services) where TProfile : Profile
{
services.AddAutoMapper(lifetime);
services.TryAddProvider<Profile, TProfile>(lifetime);
services.TryAddProvider<Profile, TProfile>(ServiceLifetime.Transient);
return services;
}
private static IConfigurationProvider CreateConfigurationProvider(IServiceProvider serviceProvider)
public static IConfigurationProvider CreateAutoMapperConfiguration(this IServiceProvider serviceProvider)
{
var profiles = serviceProvider.GetServices<Profile>();

View file

@ -15,6 +15,7 @@ using Elsa.Expressions;
using Elsa.Extensions;
using Elsa.Handlers;
using Elsa.Indexes;
using Elsa.Mapping;
using Elsa.Messages;
using Elsa.Metadata;
using Elsa.Metadata.Handlers;
@ -51,6 +52,7 @@ namespace Microsoft.Extensions.DependencyInjection
options.AddWorkflowsCore();
options.AddMediatR();
options.AddServiceBus();
options.AddAutoMapper();
return services;
}
@ -85,8 +87,7 @@ namespace Microsoft.Extensions.DependencyInjection
.AddTransient<Func<IEnumerable<IActivity>>>(sp => sp.GetServices<IActivity>)
.AddSingleton<IIdGenerator, IdGenerator>()
.AddSingleton(sp => sp.GetRequiredService<ElsaOptions>().CreateJsonSerializer(sp))
.AddSingleton<IContentSerializer, ContentSerializer>()
.AddSingleton<IActivitySerializer, ActivitySerializer>()
.AddSingleton<IContentSerializer, DefaultContentSerializer>()
.AddSingleton<TypeConverter>()
.TryAddProvider<IExpressionHandler, LiteralHandler>(ServiceLifetime.Singleton)
.TryAddProvider<IExpressionHandler, VariableHandler>(ServiceLifetime.Singleton)
@ -100,6 +101,7 @@ namespace Microsoft.Extensions.DependencyInjection
.AddScoped<IWorkflowSelector, WorkflowSelector>()
.AddScoped<IWorkflowDefinitionManager, WorkflowDefinitionManager>()
.AddScoped<IWorkflowInstanceManager, WorkflowInstanceManager>()
.AddScoped<IWorkflowPublisher, WorkflowPublisher>()
.AddIndexProvider<WorkflowDefinitionIndexProvider>()
.AddIndexProvider<WorkflowInstanceIndexProvider>()
.AddStartupRunner()
@ -107,6 +109,9 @@ namespace Microsoft.Extensions.DependencyInjection
.AddWorkflowProvider<CodeWorkflowProvider>()
.AddTransient<IWorkflowBuilder, WorkflowBuilder>()
.AddTransient<Func<IWorkflowBuilder>>(sp => sp.GetRequiredService<IWorkflowBuilder>)
.AddAutoMapperProfile<NodaTimeProfile>()
.AddAutoMapperProfile<CloningProfile>()
.AddSingleton<ICloner, AutoMapperCloner>()
.AddNotificationHandlers(typeof(ElsaServiceCollectionExtensions))
.AddStartupTask<StartServiceBusTask>()
.AddConsumer<RunWorkflow, RunWorkflowConsumer>()

View file

@ -0,0 +1,16 @@
using AutoMapper;
using Elsa.Models;
namespace Elsa.Mapping
{
/// <summary>
/// An AutoMapper profile that configures certain domain models to be mappable from and to itself, effectively creating deep clones.
/// </summary>
public class CloningProfile : Profile
{
public CloningProfile()
{
CreateMap<WorkflowDefinition, WorkflowDefinition>();
}
}
}

View file

@ -18,26 +18,11 @@ namespace Elsa.Mapping
CreateMap<DateTime?, Instant?>().ConvertUsing(this);
}
public DateTime Convert(Instant source, DateTime target, ResolutionContext context)
{
return source.ToDateTimeUtc();
}
public DateTime Convert(Instant source, DateTime target, ResolutionContext context) => source.ToDateTimeUtc();
public DateTime? Convert(Instant? source, DateTime? target, ResolutionContext context) => source?.ToDateTimeUtc();
public Instant Convert(DateTime source, Instant target, ResolutionContext context) => Convert(source);
public Instant? Convert(DateTime? source, Instant? target, ResolutionContext context) => source != null ? Convert(source.Value) : default(Instant?);
public DateTime? Convert(Instant? source, DateTime? target, ResolutionContext context)
{
return source?.ToDateTimeUtc();
}
public Instant Convert(DateTime source, Instant target, ResolutionContext context)
{
return Convert(source);
}
public Instant? Convert(DateTime? source, Instant? target, ResolutionContext context)
{
return source != null ? Convert(source.Value) : default(Instant?);
}
public Instant Convert(DateTime source)
{
var utcDateTime = source.Kind == DateTimeKind.Unspecified

View file

@ -21,12 +21,10 @@ namespace Elsa.Queries
VersionOptions = versionOptions;
}
public Expression<Func<IQuery<WorkflowDefinition>, IQuery<WorkflowDefinition>>> Query()
{
return query => query
public Expression<Func<IQuery<WorkflowDefinition>, IQuery<WorkflowDefinition>>> Query() =>
query => query
.With<WorkflowDefinitionIndex>(x => x.WorkflowDefinitionId == WorkflowDefinitionId)
.WithVersion(VersionOptions);
}
}
public static class WorkflowDefinitionByIdAndVersionQueryWorkflowDefinitionManagerExtensions
@ -34,18 +32,15 @@ namespace Elsa.Queries
public static IQuery<WorkflowDefinition> QueryByIdAndVersion(
this IWorkflowDefinitionManager manager,
string workflowDefinitionId,
VersionOptions versionOptions)
{
return manager.ExecuteQuery(
new WorkflowDefinitionByIdAndVersionQuery(workflowDefinitionId, versionOptions));
}
VersionOptions versionOptions) =>
manager.Query<WorkflowDefinitionIndex>(x => x.WorkflowDefinitionId == workflowDefinitionId).WithVersion(versionOptions);
//manager.ExecuteQuery(new WorkflowDefinitionByIdAndVersionQuery(workflowDefinitionId, versionOptions));
public static async Task<WorkflowDefinition?> GetAsync(
this IWorkflowDefinitionManager manager,
string workflowDefinitionId,
VersionOptions versionOptions,
CancellationToken cancellationToken = default) =>
await manager.QueryByIdAndVersion(workflowDefinitionId, versionOptions)
.FirstOrDefaultAsync();
await manager.QueryByIdAndVersion(workflowDefinitionId, versionOptions).FirstOrDefaultAsync();
}
}

View file

@ -1,13 +0,0 @@
using Elsa.Services;
using Newtonsoft.Json.Linq;
namespace Elsa.Serialization
{
public class ActivitySerializer : IActivitySerializer
{
private readonly IContentSerializer _ijSerializer;
public ActivitySerializer(IContentSerializer ijSerializer) => _ijSerializer = ijSerializer;
public JObject Serialize<T>(T activity) where T : IActivity => _ijSerializer.Serialize(activity);
public T Deserialize<T>(JObject data) where T : IActivity => _ijSerializer.Deserialize<T>(data);
}
}

View file

@ -1,47 +0,0 @@
using Elsa.Converters;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using NodaTime;
using NodaTime.Serialization.JsonNet;
namespace Elsa.Serialization
{
public class ContentSerializer : IContentSerializer
{
public ContentSerializer(JsonSerializer serializer)
{
Serializer = serializer;
}
private JsonSerializer Serializer { get; }
public JObject Serialize<T>(T value) => JObject.FromObject(value!, Serializer);
public T Deserialize<T>(JToken token) => token.ToObject<T>(Serializer)!;
public T Deserialize<T>(string json)
{
var token = JObject.Parse(json);
return Deserialize<T>(token);
}
public static JsonSerializer CreateDefaultJsonSerializer()
{
var jsonSerializer = new JsonSerializer();
jsonSerializer.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
jsonSerializer.NullValueHandling = NullValueHandling.Ignore;
jsonSerializer.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
jsonSerializer.PreserveReferencesHandling = PreserveReferencesHandling.All;
jsonSerializer.TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple;
jsonSerializer.TypeNameHandling = TypeNameHandling.None;
jsonSerializer.ContractResolver = new CamelCasePropertyNamesContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy
{
ProcessDictionaryKeys = false
}
};
jsonSerializer.Converters.Add(new TypeConverter());
return jsonSerializer;
}
}
}

View file

@ -0,0 +1,57 @@
using Elsa.Converters;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using NodaTime;
using NodaTime.Serialization.JsonNet;
namespace Elsa.Serialization
{
public class DefaultContentSerializer : IContentSerializer
{
public DefaultContentSerializer(JsonSerializer serializer)
{
Serializer = serializer;
}
private JsonSerializer Serializer { get; }
public string Serialize<T>(T value) => JObject.FromObject(value!, Serializer).ToString();
public T Deserialize<T>(JToken token) => token.ToObject<T>(Serializer)!;
public T Deserialize<T>(string json)
{
var token = JObject.Parse(json);
return Deserialize<T>(token);
}
public object GetSettings() => CreateDefaultJsonSerializationSettings();
public static void ConfigureDefaultJsonSerializationSettings(JsonSerializerSettings settings)
{
settings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
settings.NullValueHandling = NullValueHandling.Ignore;
settings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
settings.PreserveReferencesHandling = PreserveReferencesHandling.All;
settings.TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple;
settings.TypeNameHandling = TypeNameHandling.None;
settings.ContractResolver = new CamelCasePropertyNamesContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy
{
ProcessDictionaryKeys = true,
ProcessExtensionDataNames = true
}
};
settings.Converters.Add(new TypeConverter());
}
public static JsonSerializerSettings CreateDefaultJsonSerializationSettings()
{
var settings = new JsonSerializerSettings();
ConfigureDefaultJsonSerializationSettings(settings);
return settings;
}
public static JsonSerializer CreateDefaultJsonSerializer() => JsonSerializer.Create(CreateDefaultJsonSerializationSettings());
}
}

View file

@ -0,0 +1,15 @@
using AutoMapper;
namespace Elsa.Services
{
/// <summary>
/// An implementation that relies on AutoMapper to clone objects. This requires the types that need to be supported to be registered with AutoMapper.
/// We might eventually consider doing a different implementation that does not require explicitly registering which types support cloning.
/// </summary>
public class AutoMapperCloner : ICloner
{
private readonly IMapper _mapper;
public AutoMapperCloner(IMapper mapper) => _mapper = mapper;
public T Clone<T>(T source) => _mapper.Map<T>(source);
}
}

View file

@ -34,13 +34,7 @@ namespace Elsa.Services
public async ValueTask<IEnumerable<WorkflowDefinition>> ListAsync(CancellationToken cancellationToken = default) => await Query().ListAsync();
public IQuery<WorkflowDefinition> Query() => _session.Query<WorkflowDefinition>(CollectionNames.WorkflowDefinitions);
public IQuery<WorkflowDefinition, TIndex> Query<TIndex>() where TIndex : class, IIndex => _session.Query<WorkflowDefinition, TIndex>(CollectionNames.WorkflowDefinitions);
public IQuery<WorkflowDefinition, TIndex> Query<TIndex>(Expression<Func<TIndex, bool>> predicate)
where TIndex : class, IIndex => _session.Query<WorkflowDefinition, TIndex>(
predicate,
CollectionNames.WorkflowDefinitions);
public IQuery<WorkflowDefinition> ExecuteQuery(ICompiledQuery<WorkflowDefinition> query) =>
_session.ExecuteQuery(query, CollectionNames.WorkflowDefinitions);
public IQuery<WorkflowDefinition, TIndex> Query<TIndex>(Expression<Func<TIndex, bool>> predicate) where TIndex : class, IIndex => _session.Query<WorkflowDefinition, TIndex>(predicate, CollectionNames.WorkflowDefinitions);
public IQuery<WorkflowDefinition> ExecuteQuery(ICompiledQuery<WorkflowDefinition> query) => _session.ExecuteQuery(query, CollectionNames.WorkflowDefinitions);
}
}

View file

@ -9,11 +9,13 @@ namespace Elsa.Services
{
private readonly IWorkflowDefinitionManager _workflowDefinitionManager;
private readonly IIdGenerator _idGenerator;
private readonly ICloner _cloner;
public WorkflowPublisher(IWorkflowDefinitionManager workflowDefinitionManager, IIdGenerator idGenerator)
public WorkflowPublisher(IWorkflowDefinitionManager workflowDefinitionManager, IIdGenerator idGenerator, ICloner cloner)
{
_workflowDefinitionManager = workflowDefinitionManager;
_idGenerator = idGenerator;
_cloner = cloner;
}
public WorkflowDefinition New()
@ -34,11 +36,11 @@ namespace Elsa.Services
}
public async Task<WorkflowDefinition?> PublishAsync(
string id,
string workflowDefinitionId,
CancellationToken cancellationToken)
{
var definition = await _workflowDefinitionManager.GetAsync(
id,
workflowDefinitionId,
VersionOptions.Latest,
cancellationToken);
@ -48,16 +50,14 @@ namespace Elsa.Services
return await PublishAsync(definition, cancellationToken);
}
public async Task<WorkflowDefinition> PublishAsync(
WorkflowDefinition workflowDefinition,
CancellationToken cancellationToken)
{
public async Task<WorkflowDefinition> PublishAsync(WorkflowDefinition workflowDefinition, CancellationToken cancellationToken)
{
var publishedDefinition = await _workflowDefinitionManager.GetAsync(
workflowDefinition.WorkflowDefinitionVersionId,
workflowDefinition.WorkflowDefinitionId,
VersionOptions.Published,
cancellationToken);
if (publishedDefinition != null)
if (publishedDefinition != null)
{
publishedDefinition.IsPublished = false;
publishedDefinition.IsLatest = false;
@ -66,7 +66,6 @@ namespace Elsa.Services
if (workflowDefinition.IsPublished)
{
workflowDefinition.WorkflowDefinitionId = _idGenerator.Generate();
workflowDefinition.Version++;
}
else
@ -82,12 +81,10 @@ namespace Elsa.Services
return workflowDefinition;
}
public async Task<WorkflowDefinition?> GetDraftAsync(
string id,
CancellationToken cancellationToken)
public async Task<WorkflowDefinition?> GetDraftAsync(string workflowDefinitionId, CancellationToken cancellationToken)
{
var definition = await _workflowDefinitionManager.GetAsync(
id,
workflowDefinitionId,
VersionOptions.Latest,
cancellationToken);
@ -97,8 +94,9 @@ namespace Elsa.Services
if (!definition.IsPublished)
return definition;
var draft = definition;
draft.WorkflowDefinitionId = _idGenerator.Generate();
var draft = _cloner.Clone(definition);
draft.Id = 0;
draft.WorkflowDefinitionVersionId = _idGenerator.Generate();
draft.IsPublished = false;
draft.IsLatest = true;
draft.Version++;
@ -106,22 +104,18 @@ namespace Elsa.Services
return draft;
}
public async Task<WorkflowDefinition> SaveDraftAsync(
WorkflowDefinition workflowDefinition,
CancellationToken cancellationToken)
public async Task<WorkflowDefinition> SaveDraftAsync(WorkflowDefinition workflowDefinition, CancellationToken cancellationToken)
{
var draft = workflowDefinition;
var latestVersion = await _workflowDefinitionManager.GetAsync(
workflowDefinition.WorkflowDefinitionVersionId,
workflowDefinition.WorkflowDefinitionId,
VersionOptions.Latest,
cancellationToken);
if (latestVersion != null && latestVersion.IsPublished && latestVersion.IsLatest)
{
latestVersion.IsLatest = false;
draft.WorkflowDefinitionId = _idGenerator.Generate();
draft.Version++;
await _workflowDefinitionManager.SaveAsync(latestVersion, cancellationToken);
}

View file

@ -9,13 +9,10 @@
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Elsa.Server.Core\Elsa.Server.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.9" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="4.1.1" />
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="5.6.3" />
@ -26,4 +23,8 @@
<DependentUpon>Post.cs</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\core\Elsa\Elsa.csproj" />
</ItemGroup>
</Project>

View file

@ -2,6 +2,7 @@
using System.Threading.Tasks;
using Elsa.Models;
using Elsa.Queries;
using Elsa.Serialization;
using Elsa.Services;
using Microsoft.AspNetCore.Mvc;
@ -10,16 +11,22 @@ namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions
[ApiController]
[ApiVersion("1")]
[Route("v{version:apiVersion}/workflow-definitions/{id}")]
public class Get : ControllerBase
public class Get : Controller
{
private readonly IWorkflowDefinitionManager _workflowDefinitionManager;
public Get(IWorkflowDefinitionManager workflowDefinitionManager) => _workflowDefinitionManager = workflowDefinitionManager;
private readonly IContentSerializer _serializer;
public Get(IWorkflowDefinitionManager workflowDefinitionManager, IContentSerializer serializer)
{
_serializer = serializer;
_workflowDefinitionManager = workflowDefinitionManager;
}
[HttpGet]
public async Task<IActionResult> Handle([FromRoute(Name = "id")] string workflowDefinitionId, CancellationToken cancellationToken)
{
var workflowDefinition = await _workflowDefinitionManager.GetAsync(workflowDefinitionId, VersionOptions.Latest, cancellationToken);
return workflowDefinition == null ? (IActionResult)NotFound() : Ok(workflowDefinition);
return workflowDefinition == null ? (IActionResult)NotFound() : Json(workflowDefinition, _serializer.GetSettings());
}
}
}

View file

@ -3,7 +3,7 @@ using Elsa.Models;
namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions
{
public class CreateWorkflowDefinitionRequest
public class SaveWorkflowDefinitionRequest
{
public string WorkflowDefinitionId { get; set; } = default!;
public string? Name { get; set; }

View file

@ -1,8 +1,8 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Models;
using Elsa.Services;
using Microsoft.AspNetCore.Mvc;
using YesSql;
namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions
{
@ -12,34 +12,45 @@ namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions
public class Post : ControllerBase
{
private readonly IWorkflowDefinitionManager _workflowDefinitionManager;
private readonly IWorkflowPublisher _workflowPublisher;
private readonly ISession _session;
public Post(IWorkflowDefinitionManager workflowDefinitionManager)
public Post(IWorkflowDefinitionManager workflowDefinitionManager, IWorkflowPublisher workflowPublisher, ISession session)
{
_workflowDefinitionManager = workflowDefinitionManager;
_workflowPublisher = workflowPublisher;
_session = session;
}
[HttpPost]
public async Task<IActionResult> Handle(CreateWorkflowDefinitionRequest request, ApiVersion apiVersion, CancellationToken cancellationToken)
public async Task<IActionResult> Handle(SaveWorkflowDefinitionRequest request, ApiVersion apiVersion, CancellationToken cancellationToken)
{
var workflowDefinition = new WorkflowDefinition
{
WorkflowDefinitionId = request.WorkflowDefinitionId.Trim(),
Activities = request.Activities,
Connections = request.Connections,
Description = request.Description?.Trim(),
Name = request.Name?.Trim(),
Variables = request.Variables,
IsEnabled = request.Enabled,
IsLatest = true,
IsPublished = request.Publish,
Version = 1,
IsSingleton = request.IsSingleton,
PersistenceBehavior = request.PersistenceBehavior,
DeleteCompletedInstances = request.DeleteCompletedInstances
};
var workflowDefinition = await _workflowPublisher.GetDraftAsync(request.WorkflowDefinitionId, cancellationToken);
await _workflowDefinitionManager.SaveAsync(workflowDefinition, cancellationToken);
return CreatedAtAction("Handle", "Get", new { version = apiVersion.ToString() }, workflowDefinition);
if (workflowDefinition == null)
{
workflowDefinition = _workflowPublisher.New();
if (!string.IsNullOrWhiteSpace(request.WorkflowDefinitionId))
workflowDefinition.WorkflowDefinitionId = request.WorkflowDefinitionId.Trim();
}
workflowDefinition.Activities = request.Activities;
workflowDefinition.Connections = request.Connections;
workflowDefinition.Description = request.Description?.Trim();
workflowDefinition.Name = request.Name?.Trim();
workflowDefinition.Variables = request.Variables;
workflowDefinition.IsEnabled = request.Enabled;
workflowDefinition.IsSingleton = request.IsSingleton;
workflowDefinition.PersistenceBehavior = request.PersistenceBehavior;
workflowDefinition.DeleteCompletedInstances = request.DeleteCompletedInstances;
if (request.Publish)
await _workflowPublisher.PublishAsync(workflowDefinition, cancellationToken);
else
await _workflowPublisher.SaveDraftAsync(workflowDefinition, cancellationToken);
return CreatedAtAction("Handle", "Get", new { id = workflowDefinition.WorkflowDefinitionId, version = apiVersion.ToString() }, workflowDefinition);
}
}
}

View file

@ -1,18 +1,18 @@
using System.Text.Json;
using System;
using Elsa.Serialization;
using Microsoft.Extensions.DependencyInjection;
using NodaTime;
using NodaTime.Serialization.SystemTextJson;
using Newtonsoft.Json;
namespace Elsa.Server.Api.Extensions
{
public static class JsonMvcBuilderExtensions
{
public static IMvcBuilder AddJsonSerialization(this IMvcBuilder builder) =>
builder.AddJsonOptions(
public static IMvcBuilder AddJsonSerialization(this IMvcBuilder builder, Action<JsonSerializerSettings>? configure = default) =>
builder.AddNewtonsoftJson(
options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
configure ??= DefaultContentSerializer.ConfigureDefaultJsonSerializationSettings;
//configure(options.SerializerSettings);
});
}
}

View file

@ -8,7 +8,7 @@ namespace Microsoft.Extensions.DependencyInjection
{
public static IServiceCollection AddElsaApiEndpoints(this IServiceCollection services)
{
services.AddControllers().SetCompatibilityVersion(CompatibilityVersion.Latest).AddJsonSerialization();
services.AddControllers().AddJsonSerialization();
services.AddRouting(options => options.LowercaseUrls = true);
services.AddApiVersioning(

View file

@ -1,31 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<PackageVersion>1.0.0</PackageVersion>
<Authors>Elsa Contributors</Authors>
<Description>
Elsa is a set of workflow libraries and tools that enable lean and mean workflowing capabilities in any .NET Core application.
This package provides services to manage workflows, such as serialization and versioning.
</Description>
<Copyright>2019</Copyright>
<PackageProjectUrl>https://github.com/elsa-workflows/elsa-core</PackageProjectUrl>
<RepositoryUrl>https://github.com/elsa-workflows/elsa-core</RepositoryUrl>
<RepositoryType>GitHub</RepositoryType>
<PackageTags>elsa, workflows</PackageTags>
<PackageIcon>icon.png</PackageIcon>
</PropertyGroup>
<ItemGroup>
<None Include="icon.png">
<Pack>True</Pack>
<PackagePath />
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\core\Elsa\Elsa.csproj" />
</ItemGroup>
</Project>

View file

@ -1,20 +0,0 @@
using System;
using Elsa.Server.Core.Options;
using Microsoft.Extensions.Options;
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddElsaServer(
this IServiceCollection services,
Action<OptionsBuilder<ElsaServerOptions>> options = default)
{
var optionsBuilder = services.AddOptions<ElsaServerOptions>();
options?.Invoke(optionsBuilder);
return services;
}
}
}

View file

@ -1,6 +0,0 @@
namespace Elsa.Server.Core.Options
{
public class ElsaServerOptions
{
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View file

@ -1,38 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<PackageVersion>1.0.0</PackageVersion>
<Authors>Elsa Contributors</Authors>
<Description>
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 GraphQL endpoint to interface with the workflow host.
</Description>
<Copyright>2019</Copyright>
<PackageProjectUrl>https://github.com/elsa-workflows/elsa-core</PackageProjectUrl>
<RepositoryUrl>https://github.com/elsa-workflows/elsa-core</RepositoryUrl>
<RepositoryType>GitHub</RepositoryType>
<PackageTags>elsa, workflows</PackageTags>
<PackageIcon>icon.png</PackageIcon>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\core\Elsa\Elsa.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="HotChocolate.AspNetCore" Version="10.5.3" />
<PackageReference Include="HotChocolate.AspNetCore.Playground" Version="10.5.3" />
<PackageReference Include="HotChocolate.AspNetCore.Voyager" Version="10.5.3" />
</ItemGroup>
<ItemGroup>
<None Include="icon.png">
<Pack>True</Pack>
<PackagePath />
</None>
</ItemGroup>
</Project>

View file

@ -1,26 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Elsa.Server.GraphQL.Extensions
{
public static class ParserComposer
{
public static object FirstNonThrowing<T>(IEnumerable<Func<string, T>> parsers, string value)
{
if (!parsers.Any())
return null;
foreach (var nextParser in parsers)
{
try
{
return nextParser(value);
}
catch (Exception) { continue; }
}
return null;
}
}
}

View file

@ -1,37 +0,0 @@
using Elsa.Extensions;
using Elsa.Metadata;
using Elsa.Server.GraphQL.Mapping;
using Elsa.Server.GraphQL.Types;
using Elsa.Services;
using HotChocolate;
using Microsoft.Extensions.DependencyInjection;
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddElsaGraphQL(this IServiceCollection services)
{
return services
.AddScoped<IWorkflowPublisher, WorkflowPublisher>()
.AddSingleton<VersionOptionsConverter>()
.AddSingleton<IActivityDescriber, ActivityDescriber>()
.AddAutoMapperProfile<MappingProfile>(ServiceLifetime.Singleton)
.AddGraphQL(sp => SchemaBuilder.New()
.AddServices(sp)
.AddQueryType<QueryType>()
.AddMutationType<MutationType>()
.AddType<ActivityDescriptorType>()
.AddType<ActivityPropertyDescriptorType>()
.AddType<WorkflowInputType>()
.AddType<ActivityDefinitionType>()
.AddType<ActivityDefinitionInputType>()
.AddType<ConnectionDefinitionType>()
.AddType<WorkflowDefinitionVersionType>()
.AddType<VersionOptionsInputType>()
.AddType<InstantType>()
.Create());
}
}
}

View file

@ -1,14 +0,0 @@
using AutoMapper;
using Elsa.Models;
using Elsa.Server.GraphQL.Models;
namespace Elsa.Server.GraphQL.Mapping
{
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<VersionOptionsInput, VersionOptions>().ConvertUsing<VersionOptionsConverter>();
}
}
}

View file

@ -1,25 +0,0 @@
using AutoMapper;
using Elsa.Models;
using Elsa.Server.GraphQL.Models;
namespace Elsa.Server.GraphQL.Mapping
{
public class VersionOptionsConverter : ITypeConverter<VersionOptionsInput, VersionOptions>
{
public VersionOptions Convert(VersionOptionsInput source, VersionOptions destination, ResolutionContext context)
{
if (source.AllVersions == true)
return VersionOptions.All;
if (source.Draft == true)
return VersionOptions.Draft;
if (source.Latest == true)
return VersionOptions.Latest;
if (source.Published == true)
return VersionOptions.Published;
if (source.LatestOrPublished == true)
return VersionOptions.LatestOrPublished;
return VersionOptions.SpecificVersion(source?.Version ?? 0);
}
}
}

View file

@ -1,12 +0,0 @@
namespace Elsa.Server.GraphQL.Models
{
public class VersionOptionsInput
{
public bool? Latest { get; set; }
public bool? LatestOrPublished { get; set; }
public bool? Published { get; set; }
public bool? Draft { get; set; }
public bool? AllVersions { get; set; }
public int? Version { get; set; }
}
}

View file

@ -1,19 +0,0 @@
using System.Collections.Generic;
using Elsa.Models;
using Elsa.Server.GraphQL.Types;
namespace Elsa.Server.GraphQL.Models
{
public class WorkflowInput
{
public string? Name { get; set; }
public string? Description { get; set; }
public bool? IsSingleton { get; set; }
public bool? IsEnabled { get; set; }
public bool? DeleteCompletedInstances { get; set; }
public string? Variables { get; set; }
public WorkflowPersistenceBehavior? PersistenceBehavior { get; set; }
public ICollection<ActivityDefinitionInput>? Activities { get; set; }
public ICollection<ConnectionDefinition>? Connections { get; set; }
}
}

View file

@ -1,8 +0,0 @@
namespace Elsa.Server.GraphQL.Models
{
public enum WorkflowSaveAction
{
Draft,
Publish
}
}

View file

@ -1,104 +0,0 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AutoMapper;
using Elsa.Models;
using Elsa.Queries;
using Elsa.Serialization;
using Elsa.Server.GraphQL.Models;
using Elsa.Server.GraphQL.Types;
using Elsa.Services;
using HotChocolate;
using Open.Linq.AsyncExtensions;
using IIdGenerator = Elsa.Services.IIdGenerator;
namespace Elsa.Server.GraphQL
{
public class Mutation
{
private readonly IMapper _mapper;
public Mutation(IMapper mapper)
{
_mapper = mapper;
}
public async Task<WorkflowDefinition> SaveWorkflowDefinition(
string? id,
WorkflowSaveAction saveAction,
WorkflowInput workflowInput,
[Service] IWorkflowDefinitionManager manager,
[Service] IIdGenerator idGenerator,
[Service] IContentSerializer serializer,
[Service] IWorkflowPublisher publisher,
CancellationToken cancellationToken)
{
var workflowDefinition = id != null
? await manager.GetAsync(id, VersionOptions.Latest, cancellationToken)
: default;
if (workflowDefinition == null)
{
workflowDefinition = new WorkflowDefinition
{
WorkflowDefinitionId = idGenerator.Generate(),
WorkflowDefinitionVersionId = idGenerator.Generate(),
Version = 1,
IsLatest = true
};
}
if (workflowInput.Activities != null)
workflowDefinition.Activities = workflowInput.Activities.Select(ToActivityDefinition).ToList();
if (workflowInput.Connections != null)
workflowDefinition.Connections = workflowInput.Connections;
if (workflowInput.Description != null)
workflowDefinition.Description = workflowInput.Description.Trim();
if (workflowInput.Name != null)
workflowDefinition.Name = workflowInput.Name.Trim();
if (workflowInput.IsEnabled != null)
workflowDefinition.IsEnabled = workflowInput.IsEnabled.Value;
if (workflowInput.IsSingleton != null)
workflowDefinition.IsSingleton = workflowInput.IsSingleton.Value;
if (workflowInput.DeleteCompletedInstances != null)
workflowDefinition.DeleteCompletedInstances = workflowInput.DeleteCompletedInstances.Value;
if (workflowInput.Variables != null)
workflowDefinition.Variables = serializer.Deserialize<Variables>(workflowInput.Variables);
if (workflowInput.PersistenceBehavior != null)
workflowDefinition.PersistenceBehavior = workflowInput.PersistenceBehavior.Value;
if (saveAction == WorkflowSaveAction.Publish)
workflowDefinition = await publisher.PublishAsync(workflowDefinition, cancellationToken);
else
workflowDefinition = await publisher.SaveDraftAsync(workflowDefinition, cancellationToken);
return workflowDefinition;
}
public async Task<int> DeleteWorkflowDefinition(
string id,
[Service] IWorkflowDefinitionManager manager,
CancellationToken cancellationToken)
{
var workflowDefinitions = await manager.QueryByIdAndVersion(id, VersionOptions.All)
.ListAsync()
.ToList();
foreach (var workflowDefinition in workflowDefinitions)
await manager.DeleteAsync(workflowDefinition, cancellationToken);
return workflowDefinitions.Count;
}
private ActivityDefinition ToActivityDefinition(ActivityDefinitionInput source) =>
_mapper.Map<ActivityDefinition>(source);
}
}

View file

@ -1,76 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AutoMapper;
using Elsa.Metadata;
using Elsa.Models;
using Elsa.Queries;
using Elsa.Server.GraphQL.Models;
using Elsa.Services;
using HotChocolate;
namespace Elsa.Server.GraphQL
{
public class Query
{
public IEnumerable<ActivityDescriptor> GetActivityDescriptors(
[Service] IActivityActivator activityActivator,
[Service] IActivityDescriber describer) =>
activityActivator.GetActivityTypes().Select(describer.Describe).ToList();
public ActivityDescriptor? GetActivityDescriptor(
[Service] IActivityActivator activityActivator,
[Service] IActivityDescriber describer,
string typeName)
{
var type = activityActivator.GetActivityType(typeName);
return type == null ? default : describer.Describe(type);
}
public async Task<IEnumerable<WorkflowDefinition>> GetWorkflowDefinitions(
VersionOptionsInput? version,
[Service] IWorkflowDefinitionManager manager,
[Service] IMapper mapper,
CancellationToken cancellationToken)
{
var mappedVersion = mapper.Map<VersionOptions?>(version);
return await manager.ListAsync(mappedVersion ?? VersionOptions.Latest, cancellationToken);
}
public async Task<WorkflowDefinition?> GetWorkflowDefinition(
string? workflowDefinitionVersionId,
string? workflowDefinitionId,
VersionOptionsInput? version,
[Service] IWorkflowDefinitionManager manager,
[Service] IMapper mapper,
CancellationToken cancellationToken)
{
if (workflowDefinitionVersionId != null)
return await manager.GetByVersionIdAsync(workflowDefinitionVersionId, cancellationToken);
var mappedVersion = mapper.Map<VersionOptions?>(version);
return await manager.GetAsync(
workflowDefinitionId!,
mappedVersion ?? VersionOptions.Latest,
cancellationToken);
}
public async Task<IEnumerable<WorkflowInstance>> GetWorkflowInstances(
string definitionId,
WorkflowStatus? status,
[Service] IWorkflowInstanceManager manager,
CancellationToken cancellationToken) =>
status == null
? await manager.ListByDefinitionAsync(definitionId, cancellationToken)
: await manager.ListByDefinitionAndStatusAsync(definitionId, status.Value, cancellationToken);
public async Task<WorkflowInstance?> GetWorkflowInstance(
string id,
[Service] IWorkflowInstanceManager manager,
CancellationToken cancellationToken) =>
await manager.GetByWorkflowInstanceIdAsync(id, cancellationToken);
}
}

View file

@ -1,14 +0,0 @@
namespace Elsa.Server.GraphQL.Types
{
public class ActivityDefinitionInput
{
public string Id { get; set; }
public string Type { get; set; }
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 string? State { get; set; }
}
}

View file

@ -1,14 +0,0 @@
using HotChocolate.Types;
namespace Elsa.Server.GraphQL.Types
{
public class ActivityDefinitionInputType : InputObjectType<ActivityDefinitionInput>
{
protected override void Configure(IInputObjectTypeDescriptor<ActivityDefinitionInput> descriptor)
{
descriptor.Field(x => x.State).Type<StringType>();
//descriptor.Field(x => x.State).Type<MyAnyType>();
//descriptor.Field(x => x.State).Type<AnyType>();
}
}
}

View file

@ -1,15 +0,0 @@
using Elsa.Models;
using HotChocolate.Types;
namespace Elsa.Server.GraphQL.Types
{
public class ActivityDefinitionType : ObjectType<ActivityDefinition>
{
protected override void Configure(IObjectTypeDescriptor<ActivityDefinition> descriptor)
{
//descriptor.Field(x => x.State).Type<VariablesType>();
//descriptor.Field(x => x.State).Type<MyAnyType>();
//descriptor.Field(x => x.State).Type<AnyType>();
}
}
}

View file

@ -1,13 +0,0 @@
using Elsa.Metadata;
using HotChocolate.Types;
namespace Elsa.Server.GraphQL.Types
{
public class ActivityDescriptorType : ObjectType<ActivityDescriptor>
{
protected override void Configure(IObjectTypeDescriptor<ActivityDescriptor> descriptor)
{
descriptor.Field(x => x.Type).Type<NonNullType<StringType>>();
}
}
}

View file

@ -1,13 +0,0 @@
using Elsa.Metadata;
using HotChocolate.Types;
namespace Elsa.Server.GraphQL.Types
{
public class ActivityPropertyDescriptorType : ObjectType<ActivityPropertyDescriptor>
{
protected override void Configure(IObjectTypeDescriptor<ActivityPropertyDescriptor> descriptor)
{
descriptor.Field(x => x.Options).Type<JObjectType>();
}
}
}

View file

@ -1,9 +0,0 @@
using Elsa.Models;
using HotChocolate.Types;
namespace Elsa.Server.GraphQL.Types
{
public class ConnectionDefinitionType : ObjectType<ConnectionDefinition>
{
}
}

View file

@ -1,107 +0,0 @@
using System;
using System.Globalization;
using Elsa.Server.GraphQL.Extensions;
using HotChocolate.Language;
using HotChocolate.Types;
using NodaTime;
using NodaTime.Text;
namespace Elsa.Server.GraphQL.Types
{
public class InstantType : ScalarType
{
public InstantType() : base("Instant")
{
Description = "Represents an instant on the global timeline.";
}
public override Type ClrType => typeof(Instant);
public override bool IsInstanceOfType(IValueNode literal)
{
if (literal == null)
{
throw new ArgumentNullException(nameof(literal));
}
if (literal is NullValueNode)
{
return true;
}
return literal is StringValueNode stringLiteral && TryParseLiteral(stringLiteral, out _);
}
private bool TryParseLiteral(StringValueNode literal, out object obj) =>
TryDeserializeFromString(literal.Value, out obj);
protected bool TryDeserializeFromString(
string serialized,
out object obj)
{
obj = null;
return false;
}
public override object ParseLiteral(IValueNode literal)
{
if (literal is StringValueNode stringValue)
{
return FromString(stringValue.Value);
}
return null;
}
public override IValueNode ParseValue(object value)
{
return null;
}
public override object Serialize(object value)
{
if (value is string)
return value;
if (value is DateTime dateTime)
return dateTime.ToString("o", CultureInfo.InvariantCulture);
if (value is Instant instant)
return InstantPattern.ExtendedIso
.WithCulture(CultureInfo.InvariantCulture)
.Format(instant);
return null;
}
public override bool TryDeserialize(object serialized, out object value)
{
throw new NotImplementedException();
}
private static object FromString(string stringValue)
{
return ParserComposer.FirstNonThrowing(new Func<string, Instant>[]
{
str => OffsetDateTimePattern.ExtendedIso
.WithCulture(CultureInfo.InvariantCulture)
.Parse(str).GetValueOrThrow().ToInstant(),
str => OffsetDateTimePattern
.CreateWithInvariantCulture("yyyy'-'MM'-'dd'T'HH':'mm':'sso<+HHmm>")
.Parse(str).GetValueOrThrow().ToInstant(),
str => InstantPattern.ExtendedIso
.WithCulture(CultureInfo.InvariantCulture)
.Parse(str).GetValueOrThrow(),
}, stringValue);
}
private static object FromDateTimeUtc(DateTime dateTime)
{
try
{
return Instant.FromDateTimeUtc(dateTime);
}
catch (Exception)
{
return null;
}
}
}
}

View file

@ -1,56 +0,0 @@
using System;
using System.Collections.Generic;
using HotChocolate.Language;
using HotChocolate.Types;
using Humanizer;
using Newtonsoft.Json.Linq;
namespace Elsa.Server.GraphQL.Types
{
public class JObjectType : ScalarType
{
public JObjectType() : base("JObjectType")
{
}
public override Type ClrType => typeof(JObject);
public override bool IsInstanceOfType(IValueNode literal)
{
return literal == null || literal is ObjectValueNode || literal is NullValueNode;
}
public override object ParseLiteral(IValueNode literal)
{
return new JObject();
}
public override IValueNode ParseValue(object value)
{
if (value is null)
return NullValueNode.Default;
return new ObjectValueNode();
}
public override object Serialize(object value)
{
if (value == null)
return null;
var jObject = (JObject)value;
var dictionary = new Dictionary<string, object>();
foreach (var variable in jObject)
dictionary[variable.Key.Camelize()] = variable.Value;
return dictionary;
}
public override bool TryDeserialize(object serialized, out object value)
{
value = null;
return false;
}
}
}

View file

@ -1,18 +0,0 @@
using HotChocolate.Types;
namespace Elsa.Server.GraphQL.Types
{
public class MutationType : ObjectType<Mutation>
{
protected override void Configure(IObjectTypeDescriptor<Mutation> descriptor)
{
descriptor
.Field(x => x.SaveWorkflowDefinition(default, default, default, default, default, default, default, default))
.Argument("id", x => x.Type<IdType>());
descriptor
.Field(x => x.DeleteWorkflowDefinition(default, default, default))
.Argument("id", x => x.Type<IdType>());
}
}
}

View file

@ -1,32 +0,0 @@
using HotChocolate.Types;
namespace Elsa.Server.GraphQL.Types
{
public class QueryType : ObjectType<Query>
{
protected override void Configure(IObjectTypeDescriptor<Query> descriptor)
{
descriptor
.Field(x => x.GetActivityDescriptor(default, default, default))
.Argument("typeName", x => x.Type<IdType>());
descriptor
.Field(x => x.GetWorkflowDefinitions(default, default, default, default))
.Argument("version", x => x.Type<VersionOptionsInputType>());
descriptor
.Field(x => x.GetWorkflowDefinition(default, default, default, default, default, default))
.Argument("id", x => x.Type<IdType>())
.Argument("definitionId", x => x.Type<IdType>())
.Argument("version", x => x.Type<VersionOptionsInputType>());
descriptor
.Field(x => x.GetWorkflowInstances(default, default, default, default))
.Argument("definitionId", x => x.Type<IdType>());
descriptor
.Field(x => x.GetWorkflowInstance(default, default, default))
.Argument("id", x => x.Type<IdType>());
}
}
}

View file

@ -1,13 +0,0 @@
using Elsa.Server.GraphQL.Models;
using HotChocolate.Types;
namespace Elsa.Server.GraphQL.Types
{
public class VersionOptionsInputType : InputObjectType<VersionOptionsInput>
{
protected override void Configure(IInputObjectTypeDescriptor<VersionOptionsInput> descriptor)
{
descriptor.Field(x => x.Version).Type<IntType>();
}
}
}

View file

@ -1,13 +0,0 @@
using Elsa.Models;
using HotChocolate.Types;
namespace Elsa.Server.GraphQL.Types
{
public class WorkflowDefinitionVersionType : ObjectType<WorkflowDefinition>
{
protected override void Configure(IObjectTypeDescriptor<WorkflowDefinition> descriptor)
{
//descriptor.Field(x => x.Variables).Type<VariablesType>();
}
}
}

View file

@ -1,13 +0,0 @@
using Elsa.Server.GraphQL.Models;
using HotChocolate.Types;
namespace Elsa.Server.GraphQL.Types
{
public class WorkflowInputType : InputObjectType<WorkflowInput>
{
protected override void Configure(IInputObjectTypeDescriptor<WorkflowInput> descriptor)
{
descriptor.Field(x => x.Variables).Type<StringType>();
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View file

@ -26,17 +26,18 @@ namespace Elsa.Server.Host
var connectionString = Configuration.GetConnectionString("Sqlite");
services
.AddElsa(elsa => elsa
.UsePersistence(config => config.UseSqLite(connectionString)));
.AddElsa(
elsa => elsa
.UsePersistence(config => config.UseSqLite(connectionString)));
services
.AddElsaServer()
.AddElsaApiEndpoints()
.AddCors(options => options.AddDefaultPolicy(cors => cors
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()))
.AddCors(
options => options.AddDefaultPolicy(
cors => cors
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()))
.AddConsoleActivities()
.AddHttpActivities(options => options.Bind(elsaSection.GetSection("Http")))
@ -54,7 +55,9 @@ namespace Elsa.Server.Host
app
.UseCors()
.UseHttpActivities();
.UseHttpActivities()
.UseRouting()
.UseEndpoints(configure => configure.MapControllers());
}
}
}

View file

@ -39,7 +39,7 @@ namespace Elsa.ComponentTests.Endpoints.WorkflowDefinitions
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
}
private CreateWorkflowDefinitionRequest CreateWorkflowDefinitionRequest()
private SaveWorkflowDefinitionRequest CreateWorkflowDefinitionRequest()
{
var writeLine = new ActivityDefinition
{
@ -55,7 +55,7 @@ namespace Elsa.ComponentTests.Endpoints.WorkflowDefinitions
var activities = new[] { writeLine, readLine };
var connections = new[] { new ConnectionDefinition(writeLine.Id, readLine.Id, OutcomeNames.Done) };
return _fixture.Build<CreateWorkflowDefinitionRequest>()
return _fixture.Build<SaveWorkflowDefinitionRequest>()
.With(x => x.Activities, activities)
.With(x => x.Connections, connections)
.Create();