< Summary

Information
Class: Elsa.Workflows.Api.Endpoints.WorkflowInstances.Export.Export
Assembly: Elsa.Workflows.Api
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/Export/Endpoint.cs
Line coverage
24%
Covered lines: 22
Uncovered lines: 69
Coverable lines: 91
Total lines: 181
Line coverage: 24.1%
Branch coverage
0%
Covered branches: 0
Total branches: 28
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

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

#LineLine coverage
 1using System.IO.Compression;
 2using System.Text.Json;
 3using System.Text.Json.Nodes;
 4using Elsa.Abstractions;
 5using Elsa.Common.Entities;
 6using Elsa.Common.Models;
 7using Elsa.Workflows.Api.Endpoints.WorkflowInstances.Get;
 8using Elsa.Workflows.Api.Models;
 9using Elsa.Workflows.Management;
 10using Elsa.Workflows.Management.Entities;
 11using Elsa.Workflows.Management.Filters;
 12using Elsa.Workflows.Runtime;
 13using Elsa.Workflows.Runtime.Entities;
 14using Elsa.Workflows.Runtime.Filters;
 15using Elsa.Workflows.Runtime.OrderDefinitions;
 16using Elsa.Workflows.State;
 17using JetBrains.Annotations;
 18
 19namespace Elsa.Workflows.Api.Endpoints.WorkflowInstances.Export;
 20
 21/// <summary>
 22/// Exports the specified workflow instances as JSON downloads. When selecting multiple instances, a zip file will be do
 23/// </summary>
 24[UsedImplicitly]
 25internal class Export : ElsaEndpointWithMapper<Request, WorkflowInstanceMapper>
 26{
 27    private readonly IWorkflowInstanceStore _workflowInstanceStore;
 28    private readonly IActivityExecutionStore _activityExecutionStore;
 29    private readonly IWorkflowExecutionLogStore _workflowExecutionLogStore;
 30    private readonly IBookmarkStore _bookmarkStore;
 31    private readonly IWorkflowStateSerializer _workflowStateSerializer;
 32    private readonly IPayloadSerializer _payloadSerializer;
 33    private readonly ISafeSerializer _safeSerializer;
 34    private readonly IWorkflowInstanceExportNameProvider _workflowInstanceExportNameProvider;
 35
 36    /// <inheritdoc />
 137    public Export(
 138        IWorkflowInstanceStore workflowInstanceStore,
 139        IActivityExecutionStore activityExecutionStore,
 140        IWorkflowExecutionLogStore workflowExecutionLogStore,
 141        IBookmarkStore bookmarkStore,
 142        IWorkflowStateSerializer workflowStateSerializer,
 143        IPayloadSerializer payloadSerializer,
 144        ISafeSerializer safeSerializer,
 145        IWorkflowInstanceExportNameProvider workFlowInstanceExportNameProvider)
 46    {
 147        _workflowInstanceStore = workflowInstanceStore;
 148        _activityExecutionStore = activityExecutionStore;
 149        _workflowExecutionLogStore = workflowExecutionLogStore;
 150        _bookmarkStore = bookmarkStore;
 151        _workflowStateSerializer = workflowStateSerializer;
 152        _payloadSerializer = payloadSerializer;
 153        _safeSerializer = safeSerializer;
 154        _workflowInstanceExportNameProvider = workFlowInstanceExportNameProvider;
 155    }
 56
 57    /// <inheritdoc />
 58    public override void Configure()
 59    {
 160        Routes("/bulk-actions/export/workflow-instances", "/workflow-instances/{id}/export");
 161        Verbs(FastEndpoints.Http.GET, FastEndpoints.Http.POST);
 162        ConfigurePermissions("read:workflow-instances");
 163    }
 64
 65    /// <inheritdoc />
 66    public override Task HandleAsync(Request request, CancellationToken cancellationToken)
 67    {
 068        if (request.Id != null || request.Ids.Count == 1)
 069            return DownloadSingleInstanceAsync(request, request.Id ?? request.Ids.First(), cancellationToken);
 070        return DownloadMultipleInstancesAsync(request, cancellationToken);
 71    }
 72
 73    private async Task DownloadMultipleInstancesAsync(Request request, CancellationToken cancellationToken)
 74    {
 075        var instances = (await _workflowInstanceStore.FindManyAsync(new WorkflowInstanceFilter { Ids = request.Ids }, ca
 76
 077        if (!instances.Any())
 78        {
 079            await Send.NoContentAsync(cancellationToken);
 080            return;
 81        }
 82
 083        var zipStream = new MemoryStream();
 084        using (var zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
 85        {
 86            // Create a JSON file for each workflow definition:
 087            foreach (var instance in instances)
 88            {
 089                var model = await CreateExportModelAsync(request, instance, cancellationToken);
 090                var binaryJson = SerializeWorkflowInstance(model);
 91
 092                var fileName = await _workflowInstanceExportNameProvider.GetFileNameAsync(instance, model, cancellationT
 93
 094                var entry = zipArchive.CreateEntry(fileName, CompressionLevel.Optimal);
 095                await using var entryStream = entry.Open();
 096                await entryStream.WriteAsync(binaryJson, cancellationToken);
 097            }
 098        }
 99
 100        // Send the zip file to the client:
 0101        zipStream.Position = 0;
 0102        await Send.BytesAsync(zipStream.ToArray(), "workflow-instances.zip", cancellation: cancellationToken);
 0103    }
 104
 105    private async Task DownloadSingleInstanceAsync(Request request, string id, CancellationToken cancellationToken)
 106    {
 0107        var instance = (await _workflowInstanceStore.FindManyAsync(new WorkflowInstanceFilter { Id = id }, cancellationT
 108
 0109        if (instance == null)
 110        {
 0111            await Send.NotFoundAsync(cancellationToken);
 0112            return;
 113        }
 114
 0115        var model = await CreateExportModelAsync(request, instance, cancellationToken);
 0116        var binaryJson = SerializeWorkflowInstance(model);
 0117        var fileName = await _workflowInstanceExportNameProvider.GetFileNameAsync(instance, model, cancellationToken);
 118
 0119        await Send.BytesAsync(binaryJson, fileName, cancellation: cancellationToken);
 0120    }
 121
 122    private async Task<ExportedWorkflowState> CreateExportModelAsync(Request request, WorkflowInstance instance, Cancell
 123    {
 0124        var workflowState = instance.WorkflowState;
 0125        var executionLogRecords = request.IncludeWorkflowExecutionLog ? await LoadWorkflowExecutionLogRecordsAsync(workf
 0126        var activityExecutionLogRecords = request.IncludeActivityExecutionLog ? await LoadActivityExecutionLogRecordsAsy
 0127        var bookmarks = request.IncludeBookmarks ? await LoadBookmarksAsync(workflowState.Id, cancellationToken) : null;
 0128        var workflowStateElement = _workflowStateSerializer.SerializeToElement(workflowState);
 0129        var bookmarksElement = bookmarks != null ? SerializeBookmarks(bookmarks) : default(JsonElement?);
 0130        var executionLogRecordsElement = executionLogRecords != null ? _safeSerializer.SerializeToElement(executionLogRe
 0131        var activityExecutionLogRecordsElement = activityExecutionLogRecords != null ? _safeSerializer.SerializeToElemen
 0132        var model = new ExportedWorkflowState(workflowStateElement, bookmarksElement, activityExecutionLogRecordsElement
 0133        return model;
 0134    }
 135
 136    private JsonElement SerializeBookmarks(IEnumerable<StoredBookmark> bookmarks)
 137    {
 0138        var jsonBookmarkNodes = bookmarks.Select(x => new JsonObject
 0139        {
 0140            ["id"] = x.Id,
 0141            ["activityTypeName"] = x.Name,
 0142            ["workflowInstanceId"] = x.WorkflowInstanceId,
 0143            ["activityInstanceId"] = x.ActivityInstanceId,
 0144            ["hash"] = x.Hash,
 0145            ["correlationId"] = x.CorrelationId,
 0146            ["createdAt"] = x.CreatedAt,
 0147            ["payload"] = JsonObject.Create(_payloadSerializer.SerializeToElement(x.Payload!)),
 0148            ["metadata"] = JsonObject.Create(_payloadSerializer.SerializeToElement(x.Metadata!))
 0149        }).Cast<JsonNode>().ToArray();
 150
 0151        var jsonBookmarkArray = new JsonArray(jsonBookmarkNodes);
 0152        return JsonSerializer.SerializeToElement(jsonBookmarkArray);
 153    }
 154
 155    private async Task<IEnumerable<StoredBookmark>> LoadBookmarksAsync(string workflowInstanceId, CancellationToken canc
 156    {
 0157        var filter = new BookmarkFilter { WorkflowInstanceId = workflowInstanceId };
 0158        return await _bookmarkStore.FindManyAsync(filter, cancellationToken);
 0159    }
 160
 161    private async Task<IEnumerable<ActivityExecutionRecord>> LoadActivityExecutionLogRecordsAsync(string workflowInstanc
 162    {
 0163        var filter = new ActivityExecutionRecordFilter { WorkflowInstanceId = workflowInstanceId };
 0164        var order = new ActivityExecutionRecordOrder<DateTimeOffset>(x => x.StartedAt, OrderDirection.Ascending);
 0165        return await _activityExecutionStore.FindManyAsync(filter, order, cancellationToken);
 0166    }
 167
 168    private async Task<IEnumerable<WorkflowExecutionLogRecord>> LoadWorkflowExecutionLogRecordsAsync(string workflowInst
 169    {
 0170        var filter = new WorkflowExecutionLogRecordFilter { WorkflowInstanceId = workflowInstanceId };
 0171        var order = new WorkflowExecutionLogRecordOrder<DateTimeOffset>(x => x.Timestamp, OrderDirection.Ascending);
 0172        var page = await _workflowExecutionLogStore.FindManyAsync(filter, PageArgs.All, order, cancellationToken);
 0173        return page.Items;
 0174    }
 175
 176    private static byte[] SerializeWorkflowInstance(ExportedWorkflowState model)
 177    {
 0178        var binaryJson = JsonSerializer.SerializeToUtf8Bytes(model);
 0179        return binaryJson;
 180    }
 181}