From b7103188fde3713cf3e60ffb8fa7e3f03810d9ed Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Sun, 24 Nov 2019 22:02:14 +0100 Subject: [PATCH] Incremental work on GraphQL --- .../Services/ActivityBase.cs | 2 +- .../ServiceConfigurationExtensions.cs | 12 ++-- .../CosmosDbWorkflowDefinitionStore.cs | 6 +- .../Serialization/JsonSerializerBase.cs | 4 +- .../Elsa.Server.GraphQL/ElsaMutation.cs | 67 ++---------------- src/server/Elsa.Server.GraphQL/ElsaQuery.cs | 68 ++----------------- src/server/Elsa.Server.GraphQL/ElsaSchema.cs | 2 +- .../GraphQLServiceCollectionExtensions.cs | 21 ++++++ .../Mutations/DefineWorkflow.cs | 51 ++++++++++++++ .../Mutations/DeleteWorkflowDefinition.cs | 32 +++++++++ .../Mutations/PublishWorkflow.cs | 33 +++++++++ .../Mutations/RunWorkflow.cs | 44 ++++++++++++ .../Mutations/UpdateWorkflowDefinition.cs | 60 ++++++++++++++++ .../Queries/GetWorkflowDefinition.cs | 45 ++++++++++++ .../Queries/ListWorkflowDefinitions.cs | 35 ++++++++++ .../Queries/ListWorkflowInstances.cs | 29 ++++++++ .../Services/IMutationProvider.cs | 7 ++ .../Services/IQueryProvider.cs | 7 ++ .../Types/ActivityDefinitionType.cs | 2 +- .../Types/ActivityInstanceType.cs | 2 +- .../ActivityDefinitionInputType.cs | 5 +- .../ConnectionDefinitionInputType.cs | 2 +- .../DefineWorkflowDefinitionInputType.cs | 2 +- .../UpdateWorkflowDefinitionInputType.cs | 2 +- .../Scalars/Json/JsonAstValueConverter.cs | 2 +- .../{ => Types}/Scalars/Json/JsonType.cs | 4 +- .../{ => Types}/Scalars/Json/JsonValue.cs | 2 +- .../Elsa.Server.GraphQL/Types/VariableType.cs | 2 +- .../Types/WorkflowInstanceType.cs | 16 ++--- src/server/Elsa.Server.Host/Startup.cs | 2 + 30 files changed, 409 insertions(+), 159 deletions(-) create mode 100644 src/server/Elsa.Server.GraphQL/Mutations/DefineWorkflow.cs create mode 100644 src/server/Elsa.Server.GraphQL/Mutations/DeleteWorkflowDefinition.cs create mode 100644 src/server/Elsa.Server.GraphQL/Mutations/PublishWorkflow.cs create mode 100644 src/server/Elsa.Server.GraphQL/Mutations/RunWorkflow.cs create mode 100644 src/server/Elsa.Server.GraphQL/Mutations/UpdateWorkflowDefinition.cs create mode 100644 src/server/Elsa.Server.GraphQL/Queries/GetWorkflowDefinition.cs create mode 100644 src/server/Elsa.Server.GraphQL/Queries/ListWorkflowDefinitions.cs create mode 100644 src/server/Elsa.Server.GraphQL/Queries/ListWorkflowInstances.cs create mode 100644 src/server/Elsa.Server.GraphQL/Services/IMutationProvider.cs create mode 100644 src/server/Elsa.Server.GraphQL/Services/IQueryProvider.cs rename src/server/Elsa.Server.GraphQL/Types/{ => Input}/ActivityDefinitionInputType.cs (87%) rename src/server/Elsa.Server.GraphQL/Types/{ => Input}/ConnectionDefinitionInputType.cs (93%) rename src/server/Elsa.Server.GraphQL/Types/{ => Input}/DefineWorkflowDefinitionInputType.cs (97%) rename src/server/Elsa.Server.GraphQL/Types/{ => Input}/UpdateWorkflowDefinitionInputType.cs (96%) rename src/server/Elsa.Server.GraphQL/{ => Types}/Scalars/Json/JsonAstValueConverter.cs (87%) rename src/server/Elsa.Server.GraphQL/{ => Types}/Scalars/Json/JsonType.cs (84%) rename src/server/Elsa.Server.GraphQL/{ => Types}/Scalars/Json/JsonValue.cs (86%) diff --git a/src/core/Elsa.Abstractions/Services/ActivityBase.cs b/src/core/Elsa.Abstractions/Services/ActivityBase.cs index fd1f9d00d..18986a9a0 100644 --- a/src/core/Elsa.Abstractions/Services/ActivityBase.cs +++ b/src/core/Elsa.Abstractions/Services/ActivityBase.cs @@ -49,7 +49,7 @@ namespace Elsa.Services Id = Id, Type = Type, State = new JObject(State), - Output = JObject.FromObject(Output) + Output = Output != null ? JObject.FromObject(Output) : null }; public Task ResumeAsync(WorkflowExecutionContext context, CancellationToken cancellationToken) => OnResumeAsync(context, cancellationToken); diff --git a/src/persistence/Elsa.Persistence.DocumentDb/Extensions/ServiceConfigurationExtensions.cs b/src/persistence/Elsa.Persistence.DocumentDb/Extensions/ServiceConfigurationExtensions.cs index c293b9979..cee5bfc60 100644 --- a/src/persistence/Elsa.Persistence.DocumentDb/Extensions/ServiceConfigurationExtensions.cs +++ b/src/persistence/Elsa.Persistence.DocumentDb/Extensions/ServiceConfigurationExtensions.cs @@ -30,16 +30,16 @@ namespace Elsa.Persistence.DocumentDb.Extensions return new CosmosDbElsaBuilder(builder.Services); } - public static CosmosDbElsaBuilder AddWorkflowInstanceStore(this CosmosDbElsaBuilder configuration) + public static CosmosDbElsaBuilder AddWorkflowInstanceStore(this CosmosDbElsaBuilder builder) { - configuration.Services.AddSingleton(); - return configuration; + builder.Services.AddSingleton(); + return builder; } - public static CosmosDbElsaBuilder AddWorkflowDefinitionStore(this CosmosDbElsaBuilder configuration) + public static CosmosDbElsaBuilder AddWorkflowDefinitionStore(this CosmosDbElsaBuilder builder) { - configuration.Services.AddSingleton(); - return configuration; + builder.Services.AddSingleton(); + return builder; } } } \ No newline at end of file diff --git a/src/persistence/Elsa.Persistence.DocumentDb/Services/CosmosDbWorkflowDefinitionStore.cs b/src/persistence/Elsa.Persistence.DocumentDb/Services/CosmosDbWorkflowDefinitionStore.cs index 55c513fa8..ff270c6f4 100644 --- a/src/persistence/Elsa.Persistence.DocumentDb/Services/CosmosDbWorkflowDefinitionStore.cs +++ b/src/persistence/Elsa.Persistence.DocumentDb/Services/CosmosDbWorkflowDefinitionStore.cs @@ -49,12 +49,12 @@ namespace Elsa.Persistence.DocumentDb.Services public async Task DeleteAsync(string id, CancellationToken cancellationToken = default) { var client = storage.Client; - var records = await client.CreateDocumentQuery(storage.CollectionUri).Where(c => c.DefinitionId == id).ToQueryResultAsync(); - foreach (var record in records) + var workflowDefinitionDocuments = await client.CreateDocumentQuery(storage.CollectionUri).Where(c => c.DefinitionId == id).ToQueryResultAsync(); + foreach (var record in workflowDefinitionDocuments) { await client.DeleteDocumentAsync(record.Id, cancellationToken: cancellationToken); } - return records.Count; + return workflowDefinitionDocuments.Count; } public Task> ListAsync(VersionOptions version, CancellationToken cancellationToken = default) diff --git a/src/persistence/Elsa.Persistence.MongoDb/Serialization/JsonSerializerBase.cs b/src/persistence/Elsa.Persistence.MongoDb/Serialization/JsonSerializerBase.cs index 3e85dcfd2..d4c269ddc 100644 --- a/src/persistence/Elsa.Persistence.MongoDb/Serialization/JsonSerializerBase.cs +++ b/src/persistence/Elsa.Persistence.MongoDb/Serialization/JsonSerializerBase.cs @@ -25,8 +25,8 @@ namespace Elsa.Persistence.MongoDb.Serialization public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, T value) { - var json = JsonConvert.SerializeObject(value, serializerSettings); - var document = BsonDocument.Parse(json); + var json = value != null ? JsonConvert.SerializeObject(value, serializerSettings) : null; + var document = json != null ? BsonDocument.Parse(json) : new BsonDocument(); BsonDocumentSerializer.Instance.Serialize(context, document); } } diff --git a/src/server/Elsa.Server.GraphQL/ElsaMutation.cs b/src/server/Elsa.Server.GraphQL/ElsaMutation.cs index 5749d5c48..39c9592b0 100644 --- a/src/server/Elsa.Server.GraphQL/ElsaMutation.cs +++ b/src/server/Elsa.Server.GraphQL/ElsaMutation.cs @@ -1,74 +1,15 @@ using System.Collections.Generic; -using AutoMapper; -using Elsa.Models; -using Elsa.Persistence; -using Elsa.Server.GraphQL.Models; -using Elsa.Server.GraphQL.Types; -using Elsa.Services; +using Elsa.Server.GraphQL.Services; using GraphQL.Types; -using Newtonsoft.Json; namespace Elsa.Server.GraphQL { public class ElsaMutation : ObjectGraphType { - public ElsaMutation(IWorkflowDefinitionStore workflowDefinitionStore, IWorkflowPublisher publisher, IIdGenerator idGenerator, IMapper mapper) + public ElsaMutation(IEnumerable providers) { - FieldAsync( - "defineWorkflow", - "Create a new workflow definitions", - new QueryArguments(new QueryArgument> { Name = "workflowDefinition" }), - async context => - { - var model = context.GetArgument("workflowDefinition"); - var publish = model.Publish; - var workflowDefinitionVersion = mapper.Map(model); - - workflowDefinitionVersion.Id = idGenerator.Generate(); - - if (string.IsNullOrWhiteSpace(workflowDefinitionVersion.DefinitionId)) - workflowDefinitionVersion.DefinitionId = idGenerator.Generate(); - - if (publish) - await publisher.PublishAsync(workflowDefinitionVersion, context.CancellationToken); - else - await publisher.SaveDraftAsync(workflowDefinitionVersion, context.CancellationToken); - - return workflowDefinitionVersion; - }); - - FieldAsync( - "updateWorkflow", - "Update an existing workflow definition", - new QueryArguments( - new QueryArgument> { Name = "id" }, - new QueryArgument> { Name = "workflowDefinition" }), - async context => - { - var id = context.GetArgument("id"); - var workflowDefinitionVersion = await workflowDefinitionStore.GetByIdAsync(id, VersionOptions.Latest, context.CancellationToken); - - if (workflowDefinitionVersion == null) - return null; - - var updateModel = context.GetArgument("workflowDefinition"); - var props = (IDictionary)updateModel; - var publish = props.ContainsKey("publish") && (bool)props["publish"]; - - // "Patch" the existing workflow definition version with the posted model values. - var json = JsonConvert.SerializeObject(updateModel); - JsonConvert.PopulateObject(json, workflowDefinitionVersion, new JsonSerializerSettings - { - ObjectCreationHandling = ObjectCreationHandling.Replace - }); - - if (publish) - workflowDefinitionVersion = await publisher.PublishAsync(workflowDefinitionVersion, context.CancellationToken); - else - workflowDefinitionVersion = await publisher.SaveDraftAsync(workflowDefinitionVersion, context.CancellationToken); - - return workflowDefinitionVersion; - }); + foreach (var provider in providers) + provider.Setup(this); } } } \ No newline at end of file diff --git a/src/server/Elsa.Server.GraphQL/ElsaQuery.cs b/src/server/Elsa.Server.GraphQL/ElsaQuery.cs index 9c2bb0164..da16b541f 100644 --- a/src/server/Elsa.Server.GraphQL/ElsaQuery.cs +++ b/src/server/Elsa.Server.GraphQL/ElsaQuery.cs @@ -1,73 +1,15 @@ -using System.Threading.Tasks; -using AutoMapper; -using Elsa.Models; -using Elsa.Persistence; -using Elsa.Server.GraphQL.Models; -using Elsa.Server.GraphQL.Types; +using System.Collections.Generic; +using Elsa.Server.GraphQL.Services; using GraphQL.Types; namespace Elsa.Server.GraphQL { public class ElsaQuery : ObjectGraphType { - private readonly IWorkflowDefinitionStore workflowDefinitionStore; - private readonly IWorkflowInstanceStore workflowInstanceStore; - private readonly IMapper mapper; - - public ElsaQuery(IWorkflowDefinitionStore workflowDefinitionStore, IWorkflowInstanceStore workflowInstanceStore, IMapper mapper) + public ElsaQuery(IEnumerable providers) { - this.workflowDefinitionStore = workflowDefinitionStore; - this.workflowInstanceStore = workflowInstanceStore; - this.mapper = mapper; - - FieldAsync>( - "workflowDefinitions", - arguments: new QueryArguments(new QueryArgument>{ Name = "version", Description = "Filter workflow definitions by the specified version options."}), - resolve: ResolveWorkflowDefinitions); - - FieldAsync( - "workflowDefinition", - arguments: new QueryArguments( - new QueryArgument { Name = "versionId", Description = "Get a specific workflow definition by its version ID."}, - new QueryArgument { Name = "definitionId", Description = "Get a specific workflow definition by ID."}, - new QueryArgument{ Name = "version", Description = "Filter workflow definitions by the specified version options."}), - resolve: ResolveWorkflowDefinition); - - FieldAsync>( - "workflowInstances", - arguments: new QueryArguments( - new QueryArgument{ Name = "definitionId", Description = "Filter workflow instances by the specified workflow definition ID."}, - new QueryArgument>{ Name = "status", Description = "Filter workflow instances by the specified workflow status."} - ), - resolve: ResolveWorkflowInstances); - } - - private async Task ResolveWorkflowInstances(ResolveFieldContext context) - { - return await workflowInstanceStore.ListAllAsync(context.CancellationToken); - } - - private async Task ResolveWorkflowDefinitions(ResolveFieldContext context) - { - var versionOptionsModel = context.GetArgument("version"); - var versionOptions = mapper.Map(versionOptionsModel); - return await workflowDefinitionStore.ListAsync(versionOptions, context.CancellationToken); - } - - private async Task ResolveWorkflowDefinition(ResolveFieldContext context) => await ResolveWorkflowDefinitionInternal(context); - - private async Task ResolveWorkflowDefinitionInternal(ResolveFieldContext context) - { - var versionId = context.GetArgument("versionId"); - - if (versionId != null) - return await workflowDefinitionStore.GetByIdAsync(versionId, context.CancellationToken); - - var versionOptionsModel = context.GetArgument("version") ?? new VersionOptionsModel { Latest = true }; - var versionOptions = mapper.Map(versionOptionsModel); - var definitionId = context.GetArgument("definitionId"); - - return await workflowDefinitionStore.GetByIdAsync(definitionId, versionOptions, context.CancellationToken); + foreach (var provider in providers) + provider.Setup(this); } } } \ No newline at end of file diff --git a/src/server/Elsa.Server.GraphQL/ElsaSchema.cs b/src/server/Elsa.Server.GraphQL/ElsaSchema.cs index 08999cd50..384698be5 100644 --- a/src/server/Elsa.Server.GraphQL/ElsaSchema.cs +++ b/src/server/Elsa.Server.GraphQL/ElsaSchema.cs @@ -1,5 +1,5 @@ using System; -using Elsa.Server.GraphQL.Scalars.Json; +using Elsa.Server.GraphQL.Types.Scalars.Json; using GraphQL.Types; namespace Elsa.Server.GraphQL diff --git a/src/server/Elsa.Server.GraphQL/Extensions/GraphQLServiceCollectionExtensions.cs b/src/server/Elsa.Server.GraphQL/Extensions/GraphQLServiceCollectionExtensions.cs index 43f7f9358..73d15edd1 100644 --- a/src/server/Elsa.Server.GraphQL/Extensions/GraphQLServiceCollectionExtensions.cs +++ b/src/server/Elsa.Server.GraphQL/Extensions/GraphQLServiceCollectionExtensions.cs @@ -4,6 +4,9 @@ using Elsa.AutoMapper.Extensions; using Elsa.Models; using Elsa.Server.GraphQL; using Elsa.Server.GraphQL.Mapping; +using Elsa.Server.GraphQL.Mutations; +using Elsa.Server.GraphQL.Queries; +using Elsa.Server.GraphQL.Services; using GraphQL.NodaTime; using GraphQL.Server; using GraphQL.Types; @@ -21,6 +24,14 @@ namespace Microsoft.Extensions.DependencyInjection .AddScoped() .AddSingleton() .AddSingleton>() + .AddQueryProvider() + .AddQueryProvider() + .AddQueryProvider() + .AddMutationProvider() + .AddMutationProvider() + .AddMutationProvider() + .AddMutationProvider() + .AddMutationProvider() .AddAutoMapperProfile(ServiceLifetime.Singleton) .AddLogging(); @@ -33,5 +44,15 @@ namespace Microsoft.Extensions.DependencyInjection return builder; } + + public static IServiceCollection AddMutationProvider(this IServiceCollection services) where T : class, IMutationProvider + { + return services.AddScoped(); + } + + public static IServiceCollection AddQueryProvider(this IServiceCollection services) where T : class, IQueryProvider + { + return services.AddScoped(); + } } } \ No newline at end of file diff --git a/src/server/Elsa.Server.GraphQL/Mutations/DefineWorkflow.cs b/src/server/Elsa.Server.GraphQL/Mutations/DefineWorkflow.cs new file mode 100644 index 000000000..f12909bb5 --- /dev/null +++ b/src/server/Elsa.Server.GraphQL/Mutations/DefineWorkflow.cs @@ -0,0 +1,51 @@ +using AutoMapper; +using Elsa.Models; +using Elsa.Server.GraphQL.Models; +using Elsa.Server.GraphQL.Services; +using Elsa.Server.GraphQL.Types; +using Elsa.Server.GraphQL.Types.Input; +using Elsa.Services; +using GraphQL.Types; + +namespace Elsa.Server.GraphQL.Mutations +{ + public class DefineWorkflow : IMutationProvider + { + private readonly IWorkflowPublisher publisher; + private readonly IIdGenerator idGenerator; + private readonly IMapper mapper; + + public DefineWorkflow(IWorkflowPublisher publisher, IIdGenerator idGenerator, IMapper mapper) + { + this.publisher = publisher; + this.idGenerator = idGenerator; + this.mapper = mapper; + } + + public void Setup(ElsaMutation mutation) + { + mutation.FieldAsync( + "defineWorkflow", + "Create a new workflow definitions", + new QueryArguments(new QueryArgument> { Name = "workflowDefinition" }), + async context => + { + var model = context.GetArgument("workflowDefinition"); + var publish = model.Publish; + var workflowDefinitionVersion = mapper.Map(model); + + workflowDefinitionVersion.Id = idGenerator.Generate(); + + if (string.IsNullOrWhiteSpace(workflowDefinitionVersion.DefinitionId)) + workflowDefinitionVersion.DefinitionId = idGenerator.Generate(); + + if (publish) + await publisher.PublishAsync(workflowDefinitionVersion, context.CancellationToken); + else + await publisher.SaveDraftAsync(workflowDefinitionVersion, context.CancellationToken); + + return workflowDefinitionVersion; + }); + } + } +} \ No newline at end of file diff --git a/src/server/Elsa.Server.GraphQL/Mutations/DeleteWorkflowDefinition.cs b/src/server/Elsa.Server.GraphQL/Mutations/DeleteWorkflowDefinition.cs new file mode 100644 index 000000000..63b46931c --- /dev/null +++ b/src/server/Elsa.Server.GraphQL/Mutations/DeleteWorkflowDefinition.cs @@ -0,0 +1,32 @@ +using Elsa.Persistence; +using Elsa.Server.GraphQL.Services; +using GraphQL.Types; + +namespace Elsa.Server.GraphQL.Mutations +{ + public class DeleteWorkflowDefinition : IMutationProvider + { + private readonly IWorkflowDefinitionStore workflowDefinitionStore; + + public DeleteWorkflowDefinition(IWorkflowDefinitionStore workflowDefinitionStore) + { + this.workflowDefinitionStore = workflowDefinitionStore; + } + + public void Setup(ElsaMutation mutation) + { + mutation.FieldAsync( + "deleteWorkflowDefinition", + "Delete a specified workflow definition.", + new QueryArguments( + new QueryArgument> { Name = "workflowDefinitionId", Description = "The ID of the workflow definition to delete." } + ), + async context => + { + var workflowDefinitionId = context.GetArgument("workflowDefinitionId"); + + return await workflowDefinitionStore.DeleteAsync(workflowDefinitionId, context.CancellationToken); + }); + } + } +} \ No newline at end of file diff --git a/src/server/Elsa.Server.GraphQL/Mutations/PublishWorkflow.cs b/src/server/Elsa.Server.GraphQL/Mutations/PublishWorkflow.cs new file mode 100644 index 000000000..c9928a2a6 --- /dev/null +++ b/src/server/Elsa.Server.GraphQL/Mutations/PublishWorkflow.cs @@ -0,0 +1,33 @@ +using Elsa.Server.GraphQL.Services; +using Elsa.Server.GraphQL.Types; +using Elsa.Services; +using GraphQL.Types; + +namespace Elsa.Server.GraphQL.Mutations +{ + public class PublishWorkflow : IMutationProvider + { + private readonly IWorkflowPublisher publisher; + + public PublishWorkflow(IWorkflowPublisher publisher) + { + this.publisher = publisher; + } + + public void Setup(ElsaMutation mutation) + { + mutation.FieldAsync( + "publishWorkflow", + "Publish a specified workflow.", + new QueryArguments( + new QueryArgument> { Name = "workflowDefinitionId", Description = "The ID of the workflow definition to publish." } + ), + async context => + { + var workflowDefinitionId = context.GetArgument("workflowDefinitionId"); + + return await publisher.PublishAsync(workflowDefinitionId, context.CancellationToken); + }); + } + } +} \ No newline at end of file diff --git a/src/server/Elsa.Server.GraphQL/Mutations/RunWorkflow.cs b/src/server/Elsa.Server.GraphQL/Mutations/RunWorkflow.cs new file mode 100644 index 000000000..1ac3a2f3c --- /dev/null +++ b/src/server/Elsa.Server.GraphQL/Mutations/RunWorkflow.cs @@ -0,0 +1,44 @@ +using Elsa.Models; +using Elsa.Persistence; +using Elsa.Server.GraphQL.Services; +using Elsa.Server.GraphQL.Types; +using Elsa.Services; +using GraphQL.Types; + +namespace Elsa.Server.GraphQL.Mutations +{ + public class RunWorkflow : IMutationProvider + { + private readonly IWorkflowInvoker workflowInvoker; + private readonly IWorkflowDefinitionStore workflowDefinitionStore; + + public RunWorkflow(IWorkflowInvoker workflowInvoker, IWorkflowDefinitionStore workflowDefinitionStore) + { + this.workflowInvoker = workflowInvoker; + this.workflowDefinitionStore = workflowDefinitionStore; + } + + public void Setup(ElsaMutation mutation) + { + mutation.FieldAsync( + "runWorkflow", + "Run a specified workflow.", + new QueryArguments( + new QueryArgument> { Name = "workflowDefinitionId", Description = "The ID of the workflow definition to run." }, + new QueryArgument { Name = "correlationId", Description = "The correlation ID to associate the workflow with." } + ), + async context => + { + var workflowDefinitionId = context.GetArgument("workflowDefinitionId"); + var workflowDefinition = await workflowDefinitionStore.GetByIdAsync(workflowDefinitionId, VersionOptions.Published, context.CancellationToken); + + if (workflowDefinition == null) + return null; + + var correlationId = context.GetArgument("correlationId"); + var executionContext = await workflowInvoker.StartAsync(workflowDefinition, correlationId: correlationId, cancellationToken: context.CancellationToken); + return executionContext.Workflow.ToInstance(); + }); + } + } +} \ No newline at end of file diff --git a/src/server/Elsa.Server.GraphQL/Mutations/UpdateWorkflowDefinition.cs b/src/server/Elsa.Server.GraphQL/Mutations/UpdateWorkflowDefinition.cs new file mode 100644 index 000000000..c539291c1 --- /dev/null +++ b/src/server/Elsa.Server.GraphQL/Mutations/UpdateWorkflowDefinition.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using Elsa.Models; +using Elsa.Persistence; +using Elsa.Server.GraphQL.Services; +using Elsa.Server.GraphQL.Types; +using Elsa.Server.GraphQL.Types.Input; +using Elsa.Services; +using GraphQL.Types; +using Newtonsoft.Json; + +namespace Elsa.Server.GraphQL.Mutations +{ + public class UpdateWorkflowDefinition : IMutationProvider + { + private readonly IWorkflowDefinitionStore workflowDefinitionStore; + private readonly IWorkflowPublisher publisher; + + public UpdateWorkflowDefinition(IWorkflowDefinitionStore workflowDefinitionStore, IWorkflowPublisher publisher) + { + this.workflowDefinitionStore = workflowDefinitionStore; + this.publisher = publisher; + } + + public void Setup(ElsaMutation mutation) + { + mutation.FieldAsync( + "updateWorkflow", + "Update an existing workflow definition", + new QueryArguments( + new QueryArgument> { Name = "id" }, + new QueryArgument> { Name = "workflowDefinition" }), + async context => + { + var id = context.GetArgument("id"); + var workflowDefinitionVersion = await workflowDefinitionStore.GetByIdAsync(id, VersionOptions.Latest, context.CancellationToken); + + if (workflowDefinitionVersion == null) + return null; + + var updateModel = context.GetArgument("workflowDefinition"); + var props = (IDictionary)updateModel; + var publish = props.ContainsKey("publish") && (bool)props["publish"]; + + // "Patch" the existing workflow definition version with the posted model values. + var json = JsonConvert.SerializeObject(updateModel); + JsonConvert.PopulateObject(json, workflowDefinitionVersion, new JsonSerializerSettings + { + ObjectCreationHandling = ObjectCreationHandling.Replace + }); + + if (publish) + workflowDefinitionVersion = await publisher.PublishAsync(workflowDefinitionVersion, context.CancellationToken); + else + workflowDefinitionVersion = await publisher.SaveDraftAsync(workflowDefinitionVersion, context.CancellationToken); + + return workflowDefinitionVersion; + }); + } + } +} \ No newline at end of file diff --git a/src/server/Elsa.Server.GraphQL/Queries/GetWorkflowDefinition.cs b/src/server/Elsa.Server.GraphQL/Queries/GetWorkflowDefinition.cs new file mode 100644 index 000000000..154243f78 --- /dev/null +++ b/src/server/Elsa.Server.GraphQL/Queries/GetWorkflowDefinition.cs @@ -0,0 +1,45 @@ +using AutoMapper; +using Elsa.Models; +using Elsa.Persistence; +using Elsa.Server.GraphQL.Models; +using Elsa.Server.GraphQL.Services; +using Elsa.Server.GraphQL.Types; +using GraphQL.Types; + +namespace Elsa.Server.GraphQL.Queries +{ + public class GetWorkflowDefinition : IQueryProvider + { + private readonly IWorkflowDefinitionStore workflowDefinitionStore; + private readonly IMapper mapper; + + public GetWorkflowDefinition(IWorkflowDefinitionStore workflowDefinitionStore, IMapper mapper) + { + this.workflowDefinitionStore = workflowDefinitionStore; + this.mapper = mapper; + } + + public void Setup(ElsaQuery query) + { + query.FieldAsync( + "workflowDefinition", + arguments: new QueryArguments( + new QueryArgument { Name = "versionId", Description = "Get a specific workflow definition by its version ID."}, + new QueryArgument { Name = "definitionId", Description = "Get a specific workflow definition by ID."}, + new QueryArgument{ Name = "version", Description = "Filter workflow definitions by the specified version options."}), + resolve: async context => + { + var versionId = context.GetArgument("versionId"); + + if (versionId != null) + return await workflowDefinitionStore.GetByIdAsync(versionId, context.CancellationToken); + + var versionOptionsModel = context.GetArgument("version") ?? new VersionOptionsModel { Latest = true }; + var versionOptions = mapper.Map(versionOptionsModel); + var definitionId = context.GetArgument("definitionId"); + + return await workflowDefinitionStore.GetByIdAsync(definitionId, versionOptions, context.CancellationToken); + }); + } + } +} \ No newline at end of file diff --git a/src/server/Elsa.Server.GraphQL/Queries/ListWorkflowDefinitions.cs b/src/server/Elsa.Server.GraphQL/Queries/ListWorkflowDefinitions.cs new file mode 100644 index 000000000..725448775 --- /dev/null +++ b/src/server/Elsa.Server.GraphQL/Queries/ListWorkflowDefinitions.cs @@ -0,0 +1,35 @@ +using AutoMapper; +using Elsa.Models; +using Elsa.Persistence; +using Elsa.Server.GraphQL.Models; +using Elsa.Server.GraphQL.Services; +using Elsa.Server.GraphQL.Types; +using GraphQL.Types; + +namespace Elsa.Server.GraphQL.Queries +{ + public class ListWorkflowDefinitions : IQueryProvider + { + private readonly IWorkflowDefinitionStore workflowDefinitionStore; + private readonly IMapper mapper; + + public ListWorkflowDefinitions(IWorkflowDefinitionStore workflowDefinitionStore, IMapper mapper) + { + this.workflowDefinitionStore = workflowDefinitionStore; + this.mapper = mapper; + } + + public void Setup(ElsaQuery query) + { + query.FieldAsync>( + "workflowDefinitions", + arguments: new QueryArguments(new QueryArgument>{ Name = "version", Description = "Filter workflow definitions by the specified version options."}), + resolve: async context => + { + var versionOptionsModel = context.GetArgument("version"); + var versionOptions = mapper.Map(versionOptionsModel); + return await workflowDefinitionStore.ListAsync(versionOptions, context.CancellationToken); + }); + } + } +} \ No newline at end of file diff --git a/src/server/Elsa.Server.GraphQL/Queries/ListWorkflowInstances.cs b/src/server/Elsa.Server.GraphQL/Queries/ListWorkflowInstances.cs new file mode 100644 index 000000000..110624852 --- /dev/null +++ b/src/server/Elsa.Server.GraphQL/Queries/ListWorkflowInstances.cs @@ -0,0 +1,29 @@ +using Elsa.Models; +using Elsa.Persistence; +using Elsa.Server.GraphQL.Services; +using Elsa.Server.GraphQL.Types; +using GraphQL.Types; + +namespace Elsa.Server.GraphQL.Queries +{ + public class ListWorkflowInstances : IQueryProvider + { + private readonly IWorkflowInstanceStore workflowInstanceStore; + + public ListWorkflowInstances(IWorkflowInstanceStore workflowInstanceStore) + { + this.workflowInstanceStore = workflowInstanceStore; + } + + public void Setup(ElsaQuery query) + { + query.FieldAsync>( + "workflowInstances", + arguments: new QueryArguments( + new QueryArgument{ Name = "definitionId", Description = "Filter workflow instances by the specified workflow definition ID."}, + new QueryArgument>{ Name = "status", Description = "Filter workflow instances by the specified workflow status."} + ), + resolve: async context => await workflowInstanceStore.ListAllAsync(context.CancellationToken)); + } + } +} \ No newline at end of file diff --git a/src/server/Elsa.Server.GraphQL/Services/IMutationProvider.cs b/src/server/Elsa.Server.GraphQL/Services/IMutationProvider.cs new file mode 100644 index 000000000..03bc4338f --- /dev/null +++ b/src/server/Elsa.Server.GraphQL/Services/IMutationProvider.cs @@ -0,0 +1,7 @@ +namespace Elsa.Server.GraphQL.Services +{ + public interface IMutationProvider + { + void Setup(ElsaMutation mutation); + } +} \ No newline at end of file diff --git a/src/server/Elsa.Server.GraphQL/Services/IQueryProvider.cs b/src/server/Elsa.Server.GraphQL/Services/IQueryProvider.cs new file mode 100644 index 000000000..2599a6299 --- /dev/null +++ b/src/server/Elsa.Server.GraphQL/Services/IQueryProvider.cs @@ -0,0 +1,7 @@ +namespace Elsa.Server.GraphQL.Services +{ + public interface IQueryProvider + { + void Setup(ElsaQuery query); + } +} \ No newline at end of file diff --git a/src/server/Elsa.Server.GraphQL/Types/ActivityDefinitionType.cs b/src/server/Elsa.Server.GraphQL/Types/ActivityDefinitionType.cs index 0fef9e734..6219c3f9d 100644 --- a/src/server/Elsa.Server.GraphQL/Types/ActivityDefinitionType.cs +++ b/src/server/Elsa.Server.GraphQL/Types/ActivityDefinitionType.cs @@ -1,5 +1,5 @@ using Elsa.Models; -using Elsa.Server.GraphQL.Scalars.Json; +using Elsa.Server.GraphQL.Types.Scalars.Json; using GraphQL.Types; namespace Elsa.Server.GraphQL.Types diff --git a/src/server/Elsa.Server.GraphQL/Types/ActivityInstanceType.cs b/src/server/Elsa.Server.GraphQL/Types/ActivityInstanceType.cs index 9e547bc34..58fdaf9bc 100644 --- a/src/server/Elsa.Server.GraphQL/Types/ActivityInstanceType.cs +++ b/src/server/Elsa.Server.GraphQL/Types/ActivityInstanceType.cs @@ -1,5 +1,5 @@ using Elsa.Models; -using Elsa.Server.GraphQL.Scalars.Json; +using Elsa.Server.GraphQL.Types.Scalars.Json; using GraphQL.Types; namespace Elsa.Server.GraphQL.Types diff --git a/src/server/Elsa.Server.GraphQL/Types/ActivityDefinitionInputType.cs b/src/server/Elsa.Server.GraphQL/Types/Input/ActivityDefinitionInputType.cs similarity index 87% rename from src/server/Elsa.Server.GraphQL/Types/ActivityDefinitionInputType.cs rename to src/server/Elsa.Server.GraphQL/Types/Input/ActivityDefinitionInputType.cs index 40298c441..fadfaf996 100644 --- a/src/server/Elsa.Server.GraphQL/Types/ActivityDefinitionInputType.cs +++ b/src/server/Elsa.Server.GraphQL/Types/Input/ActivityDefinitionInputType.cs @@ -1,8 +1,8 @@ using Elsa.Models; -using Elsa.Server.GraphQL.Scalars.Json; +using Elsa.Server.GraphQL.Types.Scalars.Json; using GraphQL.Types; -namespace Elsa.Server.GraphQL.Types +namespace Elsa.Server.GraphQL.Types.Input { public class ActivityDefinitionInputType : InputObjectGraphType { @@ -10,6 +10,7 @@ namespace Elsa.Server.GraphQL.Types { Name = "ActivityDefinitionInput"; + Field(x => x.Id).Description("The ID of the activity."); Field(x => x.Type).Description("The activity type name."); Field(x => x.Description, true).Description("A description for the activity"); Field(x => x.Name, true).Description("A name for the activity. Named activities make it easy to be referenced from workflow expressions."); diff --git a/src/server/Elsa.Server.GraphQL/Types/ConnectionDefinitionInputType.cs b/src/server/Elsa.Server.GraphQL/Types/Input/ConnectionDefinitionInputType.cs similarity index 93% rename from src/server/Elsa.Server.GraphQL/Types/ConnectionDefinitionInputType.cs rename to src/server/Elsa.Server.GraphQL/Types/Input/ConnectionDefinitionInputType.cs index c06f6c1a0..b45c49f33 100644 --- a/src/server/Elsa.Server.GraphQL/Types/ConnectionDefinitionInputType.cs +++ b/src/server/Elsa.Server.GraphQL/Types/Input/ConnectionDefinitionInputType.cs @@ -1,7 +1,7 @@ using Elsa.Models; using GraphQL.Types; -namespace Elsa.Server.GraphQL.Types +namespace Elsa.Server.GraphQL.Types.Input { public class ConnectionDefinitionInputType : InputObjectGraphType { diff --git a/src/server/Elsa.Server.GraphQL/Types/DefineWorkflowDefinitionInputType.cs b/src/server/Elsa.Server.GraphQL/Types/Input/DefineWorkflowDefinitionInputType.cs similarity index 97% rename from src/server/Elsa.Server.GraphQL/Types/DefineWorkflowDefinitionInputType.cs rename to src/server/Elsa.Server.GraphQL/Types/Input/DefineWorkflowDefinitionInputType.cs index 94d2cb99d..1059505fb 100644 --- a/src/server/Elsa.Server.GraphQL/Types/DefineWorkflowDefinitionInputType.cs +++ b/src/server/Elsa.Server.GraphQL/Types/Input/DefineWorkflowDefinitionInputType.cs @@ -1,7 +1,7 @@ using Elsa.Server.GraphQL.Models; using GraphQL.Types; -namespace Elsa.Server.GraphQL.Types +namespace Elsa.Server.GraphQL.Types.Input { public class DefineWorkflowDefinitionInputType : InputObjectGraphType { diff --git a/src/server/Elsa.Server.GraphQL/Types/UpdateWorkflowDefinitionInputType.cs b/src/server/Elsa.Server.GraphQL/Types/Input/UpdateWorkflowDefinitionInputType.cs similarity index 96% rename from src/server/Elsa.Server.GraphQL/Types/UpdateWorkflowDefinitionInputType.cs rename to src/server/Elsa.Server.GraphQL/Types/Input/UpdateWorkflowDefinitionInputType.cs index e70c6bab9..72f4a9379 100644 --- a/src/server/Elsa.Server.GraphQL/Types/UpdateWorkflowDefinitionInputType.cs +++ b/src/server/Elsa.Server.GraphQL/Types/Input/UpdateWorkflowDefinitionInputType.cs @@ -1,7 +1,7 @@ using Elsa.Server.GraphQL.Models; using GraphQL.Types; -namespace Elsa.Server.GraphQL.Types +namespace Elsa.Server.GraphQL.Types.Input { public class UpdateWorkflowDefinitionInputType : InputObjectGraphType { diff --git a/src/server/Elsa.Server.GraphQL/Scalars/Json/JsonAstValueConverter.cs b/src/server/Elsa.Server.GraphQL/Types/Scalars/Json/JsonAstValueConverter.cs similarity index 87% rename from src/server/Elsa.Server.GraphQL/Scalars/Json/JsonAstValueConverter.cs rename to src/server/Elsa.Server.GraphQL/Types/Scalars/Json/JsonAstValueConverter.cs index 7cf498756..7bc9dea68 100644 --- a/src/server/Elsa.Server.GraphQL/Scalars/Json/JsonAstValueConverter.cs +++ b/src/server/Elsa.Server.GraphQL/Types/Scalars/Json/JsonAstValueConverter.cs @@ -2,7 +2,7 @@ using GraphQL.Types; using Newtonsoft.Json.Linq; -namespace Elsa.Server.GraphQL.Scalars.Json +namespace Elsa.Server.GraphQL.Types.Scalars.Json { public class JsonAstValueConverter : IAstFromValueConverter { diff --git a/src/server/Elsa.Server.GraphQL/Scalars/Json/JsonType.cs b/src/server/Elsa.Server.GraphQL/Types/Scalars/Json/JsonType.cs similarity index 84% rename from src/server/Elsa.Server.GraphQL/Scalars/Json/JsonType.cs rename to src/server/Elsa.Server.GraphQL/Types/Scalars/Json/JsonType.cs index e5d0abcc7..f1a11cdc9 100644 --- a/src/server/Elsa.Server.GraphQL/Scalars/Json/JsonType.cs +++ b/src/server/Elsa.Server.GraphQL/Types/Scalars/Json/JsonType.cs @@ -2,7 +2,7 @@ using GraphQL.Types; using Newtonsoft.Json.Linq; -namespace Elsa.Server.GraphQL.Scalars.Json +namespace Elsa.Server.GraphQL.Types.Scalars.Json { public class JsonType : ScalarGraphType { @@ -11,7 +11,7 @@ namespace Elsa.Server.GraphQL.Scalars.Json Name = "Json"; } - public override object Serialize(object value) => value.ToString(); + public override object Serialize(object value) => value; public override object ParseValue(object value) => JObject.Parse((string)value); diff --git a/src/server/Elsa.Server.GraphQL/Scalars/Json/JsonValue.cs b/src/server/Elsa.Server.GraphQL/Types/Scalars/Json/JsonValue.cs similarity index 86% rename from src/server/Elsa.Server.GraphQL/Scalars/Json/JsonValue.cs rename to src/server/Elsa.Server.GraphQL/Types/Scalars/Json/JsonValue.cs index e3425a042..63e81757e 100644 --- a/src/server/Elsa.Server.GraphQL/Scalars/Json/JsonValue.cs +++ b/src/server/Elsa.Server.GraphQL/Types/Scalars/Json/JsonValue.cs @@ -1,7 +1,7 @@ using GraphQL.Language.AST; using Newtonsoft.Json.Linq; -namespace Elsa.Server.GraphQL.Scalars.Json +namespace Elsa.Server.GraphQL.Types.Scalars.Json { public class JsonValue : ValueNode { diff --git a/src/server/Elsa.Server.GraphQL/Types/VariableType.cs b/src/server/Elsa.Server.GraphQL/Types/VariableType.cs index 74c8d25f9..2f9e16e52 100644 --- a/src/server/Elsa.Server.GraphQL/Types/VariableType.cs +++ b/src/server/Elsa.Server.GraphQL/Types/VariableType.cs @@ -1,5 +1,5 @@ using Elsa.Models; -using Elsa.Server.GraphQL.Scalars.Json; +using Elsa.Server.GraphQL.Types.Scalars.Json; using GraphQL.Types; namespace Elsa.Server.GraphQL.Types diff --git a/src/server/Elsa.Server.GraphQL/Types/WorkflowInstanceType.cs b/src/server/Elsa.Server.GraphQL/Types/WorkflowInstanceType.cs index 66dca2154..500eafe63 100644 --- a/src/server/Elsa.Server.GraphQL/Types/WorkflowInstanceType.cs +++ b/src/server/Elsa.Server.GraphQL/Types/WorkflowInstanceType.cs @@ -15,18 +15,18 @@ namespace Elsa.Server.GraphQL.Types Field(x => x.DefinitionId).Description("The ID of the workflow definition this workflow is an instance of."); Field(x => x.Version).Description("The version of the workflow definition this workflow is an instance of."); Field(x => x.Status).Description("The status of the workflow."); - Field(x => x.CorrelationId).Description("The correlation ID of the workflow."); - Field(x => x.Activities, type: typeof(ListGraphType)).Description("A hash of activity instances."); - Field(x => x.BlockingActivities, type: typeof(ListGraphType)).Description("A hash of activities that are blocking workflow execution."); + Field(x => x.CorrelationId, true).Description("The correlation ID of the workflow."); + Field(x => x.Activities, true, typeof(ListGraphType)).Description("A hash of activity instances."); + Field(x => x.BlockingActivities, true, typeof(ListGraphType)).Description("A hash of activities that are blocking workflow execution."); Field(x => x.Input, true, typeof(VariablesType)).Description("Holds input variables."); Field(x => x.Scopes, type: typeof(ListGraphType)).Description("Holds workflow variables in a stack of scopes."); - Field(x => x.ExecutionLog, type: typeof(ListGraphType)).Description("A log of executed activities."); + Field(x => x.ExecutionLog, true, typeof(ListGraphType)).Description("A log of executed activities."); Field(x => x.Fault, true, typeof(WorkflowFaultType)).Description("If the workflow is in a Faulted state, this property holds details about the fault."); Field(x => x.CreatedAt, type: typeof(InstantGraphType)).Description("The time stamp at which this workflow instance was created."); - Field(x => x.StartedAt, type: typeof(InstantGraphType)).Description("The time stamp at which the workflow has started."); - Field(x => x.FinishedAt, type: typeof(InstantGraphType)).Description("The time stamp at which the workflow has finished."); - Field(x => x.FaultedAt, type: typeof(InstantGraphType)).Description("The time stamp at which the workflow has faulted."); - Field(x => x.AbortedAt, type: typeof(InstantGraphType)).Description("The time stamp at which the workflow was aborted."); + Field(x => x.StartedAt, true, typeof(InstantGraphType)).Description("The time stamp at which the workflow has started."); + Field(x => x.FinishedAt, true, typeof(InstantGraphType)).Description("The time stamp at which the workflow has finished."); + Field(x => x.FaultedAt, true, typeof(InstantGraphType)).Description("The time stamp at which the workflow has faulted."); + Field(x => x.AbortedAt, true, typeof(InstantGraphType)).Description("The time stamp at which the workflow was aborted."); } } } \ No newline at end of file diff --git a/src/server/Elsa.Server.Host/Startup.cs b/src/server/Elsa.Server.Host/Startup.cs index d6e63dc7b..0dfc783c0 100644 --- a/src/server/Elsa.Server.Host/Startup.cs +++ b/src/server/Elsa.Server.Host/Startup.cs @@ -1,3 +1,4 @@ +using Elsa.Activities.Console.Extensions; using Elsa.Server.GraphQL.Extensions; using GraphQL.Server.Ui.GraphiQL; using GraphQL.Server.Ui.Playground; @@ -31,6 +32,7 @@ namespace Elsa.Server.Host .AddMongoDbStores(Configuration, "ElsaServer", "MongoDb") .AddGraphQL(options => Configuration.GetSection("GraphQL").Bind(options))) + .AddConsoleActivities() .AddHttpActivities(options => options.Bind(elsaSection.GetSection("Http"))) .AddEmailActivities(options => options.Bind(elsaSection.GetSection("Smtp"))) .AddTimerActivities(options => options.Bind(elsaSection.GetSection("BackgroundRunner")))