< Summary

Information
Class: Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.Export.Export
Assembly: Elsa.Workflows.Api
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs
Line coverage
18%
Covered lines: 14
Uncovered lines: 61
Coverable lines: 75
Total lines: 145
Line coverage: 18.6%
Branch coverage
0%
Covered branches: 0
Total branches: 22
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
Configure()100%11100%
HandleAsync()0%2040%
DownloadMultipleWorkflowsAsync()0%7280%
DownloadSingleWorkflowAsync()0%2040%
GetFileName(...)0%620%
SerializeWorkflowDefinitionAsync()0%2040%
CreateWorkflowModelAsync()100%210%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Export/Endpoint.cs

#LineLine coverage
 1using System.IO.Compression;
 2using System.Text.Json;
 3using Elsa.Abstractions;
 4using Elsa.Common.Models;
 5using Elsa.Workflows.Management;
 6using Elsa.Workflows.Management.Entities;
 7using Elsa.Workflows.Management.Filters;
 8using Elsa.Workflows.Management.Mappers;
 9using Elsa.Workflows.Management.Models;
 10using Humanizer;
 11using JetBrains.Annotations;
 12
 13namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.Export;
 14
 15/// <summary>
 16/// Exports the specified workflow definition as JSON download.
 17/// </summary>
 18[UsedImplicitly]
 19internal class Export : ElsaEndpoint<Request>
 20{
 21    private readonly IApiSerializer _serializer;
 22    private readonly IWorkflowDefinitionStore _store;
 23    private readonly WorkflowDefinitionMapper _workflowDefinitionMapper;
 24
 25    /// <inheritdoc />
 126    public Export(
 127        IWorkflowDefinitionStore store,
 128        IWorkflowDefinitionService workflowDefinitionService,
 129        IApiSerializer serializer,
 130        WorkflowDefinitionMapper workflowDefinitionMapper,
 131        VariableDefinitionMapper variableDefinitionMapper)
 32    {
 133        _store = store;
 134        _serializer = serializer;
 135        _workflowDefinitionMapper = workflowDefinitionMapper;
 136    }
 37
 38    /// <inheritdoc />
 39    public override void Configure()
 40    {
 141        Routes("/bulk-actions/export/workflow-definitions", "/workflow-definitions/{definitionId}/export");
 142        Verbs(FastEndpoints.Http.GET, FastEndpoints.Http.POST);
 143        ConfigurePermissions("read:workflow-definitions");
 144    }
 45
 46    /// <inheritdoc />
 47    public override async Task HandleAsync(Request request, CancellationToken cancellationToken)
 48    {
 049        if (request.DefinitionId != null)
 050            await DownloadSingleWorkflowAsync(request.DefinitionId, request.VersionOptions, cancellationToken);
 051        else if (request.Ids != null)
 052            await DownloadMultipleWorkflowsAsync(request.Ids, cancellationToken);
 053        else await Send.NoContentAsync(cancellationToken);
 054    }
 55
 56    private async Task DownloadMultipleWorkflowsAsync(ICollection<string> ids, CancellationToken cancellationToken)
 57    {
 058        List<WorkflowDefinition> definitions = (await _store.FindManyAsync(new()
 059        {
 060            Ids = ids
 061        }, cancellationToken)).ToList();
 62
 063        if (!definitions.Any())
 64        {
 065            await Send.NoContentAsync(cancellationToken);
 066            return;
 67        }
 68
 069        var zipStream = new MemoryStream();
 070        using (var zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
 71        {
 72            // Create a JSON file for each workflow definition:
 073            foreach (var definition in definitions)
 74            {
 075                var model = await CreateWorkflowModelAsync(definition, cancellationToken);
 076                var binaryJson = await SerializeWorkflowDefinitionAsync(model, cancellationToken);
 077                var fileName = GetFileName(model);
 078                var entry = zipArchive.CreateEntry(fileName, CompressionLevel.Optimal);
 079                await using var entryStream = entry.Open();
 080                await entryStream.WriteAsync(binaryJson, cancellationToken);
 081            }
 082        }
 83
 84        // Send the zip file to the client:
 085        zipStream.Position = 0;
 086        await Send.BytesAsync(zipStream.ToArray(), "workflow-definitions.zip", cancellation: cancellationToken);
 087    }
 88
 89    private async Task DownloadSingleWorkflowAsync(string definitionId, string? versionOptions, CancellationToken cancel
 90    {
 091        var parsedVersionOptions = string.IsNullOrEmpty(versionOptions) ? VersionOptions.Latest : VersionOptions.FromStr
 092        WorkflowDefinition? definition = (await _store.FindManyAsync(new()
 093        {
 094            DefinitionId = definitionId,
 095            VersionOptions = parsedVersionOptions
 096        }, cancellationToken)).FirstOrDefault();
 97
 098        if (definition == null)
 99        {
 0100            await Send.NotFoundAsync(cancellationToken);
 0101            return;
 102        }
 103
 0104        var model = await CreateWorkflowModelAsync(definition, cancellationToken);
 0105        var binaryJson = await SerializeWorkflowDefinitionAsync(model, cancellationToken);
 0106        var fileName = GetFileName(model);
 107
 0108        await Send.BytesAsync(binaryJson, fileName, cancellation: cancellationToken);
 0109    }
 110
 111    private string GetFileName(WorkflowDefinitionModel definition)
 112    {
 0113        var hasWorkflowName = !string.IsNullOrWhiteSpace(definition.Name);
 0114        var workflowName = hasWorkflowName ? definition.Name!.Trim() : definition.DefinitionId;
 0115        var fileName = $"workflow-definition-{workflowName.Underscore().Dasherize().ToLowerInvariant()}.json";
 0116        return fileName;
 117    }
 118
 119    private async Task<byte[]> SerializeWorkflowDefinitionAsync(WorkflowDefinitionModel model, CancellationToken cancell
 120    {
 0121        var serializerOptions = _serializer.GetOptions();
 0122        var document = JsonSerializer.SerializeToDocument(model, serializerOptions);
 0123        var rootElement = document.RootElement;
 124
 0125        using var output = new MemoryStream();
 0126        await using var writer = new Utf8JsonWriter(output);
 127
 0128        writer.WriteStartObject();
 0129        writer.WriteString("$schema", "https://elsaworkflows.io/schemas/workflow-definition/v3.0.0/schema.json");
 130
 0131        foreach (var property in rootElement.EnumerateObject())
 0132            property.WriteTo(writer);
 133
 0134        writer.WriteEndObject();
 135
 0136        await writer.FlushAsync(cancellationToken);
 0137        var binaryJson = output.ToArray();
 0138        return binaryJson;
 0139    }
 140
 141    private async Task<WorkflowDefinitionModel> CreateWorkflowModelAsync(WorkflowDefinition definition, CancellationToke
 142    {
 0143        return await _workflowDefinitionMapper.MapAsync(definition, cancellationToken);
 0144    }
 145}