< 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
21%
Covered lines: 20
Uncovered lines: 71
Coverable lines: 91
Total lines: 182
Line coverage: 21.9%
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
 35    /// <inheritdoc />
 136    public Export(
 137        IWorkflowInstanceStore workflowInstanceStore,
 138        IActivityExecutionStore activityExecutionStore,
 139        IWorkflowExecutionLogStore workflowExecutionLogStore,
 140        IBookmarkStore bookmarkStore,
 141        IWorkflowStateSerializer workflowStateSerializer,
 142        IPayloadSerializer payloadSerializer,
 143        ISafeSerializer safeSerializer)
 44    {
 145        _workflowInstanceStore = workflowInstanceStore;
 146        _activityExecutionStore = activityExecutionStore;
 147        _workflowExecutionLogStore = workflowExecutionLogStore;
 148        _bookmarkStore = bookmarkStore;
 149        _workflowStateSerializer = workflowStateSerializer;
 150        _payloadSerializer = payloadSerializer;
 151        _safeSerializer = safeSerializer;
 152    }
 53
 54    /// <inheritdoc />
 55    public override void Configure()
 56    {
 157        Routes("/bulk-actions/export/workflow-instances", "/workflow-instances/{id}/export");
 158        Verbs(FastEndpoints.Http.GET, FastEndpoints.Http.POST);
 159        ConfigurePermissions("read:workflow-instances");
 160    }
 61
 62    /// <inheritdoc />
 63    public override Task HandleAsync(Request request, CancellationToken cancellationToken)
 64    {
 065        if (request.Id != null || request.Ids.Count == 1)
 066            return DownloadSingleInstanceAsync(request, request.Id ?? request.Ids.First(), cancellationToken);
 067        return DownloadMultipleInstancesAsync(request, cancellationToken);
 68    }
 69
 70    private async Task DownloadMultipleInstancesAsync(Request request, CancellationToken cancellationToken)
 71    {
 072        var instances = (await _workflowInstanceStore.FindManyAsync(new WorkflowInstanceFilter { Ids = request.Ids }, ca
 73
 074        if (!instances.Any())
 75        {
 076            await Send.NoContentAsync(cancellationToken);
 077            return;
 78        }
 79
 080        var zipStream = new MemoryStream();
 081        using (var zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
 82        {
 83            // Create a JSON file for each workflow definition:
 084            foreach (var instance in instances)
 85            {
 086                var model = await CreateExportModelAsync(request, instance, cancellationToken);
 087                var binaryJson = SerializeWorkflowInstance(model);
 088                var fileName = GetFileName(instance.WorkflowState);
 089                var entry = zipArchive.CreateEntry(fileName, CompressionLevel.Optimal);
 090                await using var entryStream = entry.Open();
 091                await entryStream.WriteAsync(binaryJson, cancellationToken);
 092            }
 093        }
 94
 95        // Send the zip file to the client:
 096        zipStream.Position = 0;
 097        await Send.BytesAsync(zipStream.ToArray(), "workflow-instances.zip", cancellation: cancellationToken);
 098    }
 99
 100    private async Task DownloadSingleInstanceAsync(Request request, string id, CancellationToken cancellationToken)
 101    {
 0102        var instance = (await _workflowInstanceStore.FindManyAsync(new WorkflowInstanceFilter { Id = id }, cancellationT
 103
 0104        if (instance == null)
 105        {
 0106            await Send.NotFoundAsync(cancellationToken);
 0107            return;
 108        }
 109
 0110        var model = await CreateExportModelAsync(request, instance, cancellationToken);
 0111        var binaryJson = SerializeWorkflowInstance(model);
 0112        var fileName = GetFileName(instance.WorkflowState);
 113
 0114        await Send.BytesAsync(binaryJson, fileName, cancellation: cancellationToken);
 0115    }
 116
 117    private async Task<ExportedWorkflowState> CreateExportModelAsync(Request request, WorkflowInstance instance, Cancell
 118    {
 0119        var workflowState = instance.WorkflowState;
 0120        var executionLogRecords = request.IncludeWorkflowExecutionLog ? await LoadWorkflowExecutionLogRecordsAsync(workf
 0121        var activityExecutionLogRecords = request.IncludeActivityExecutionLog ? await LoadActivityExecutionLogRecordsAsy
 0122        var bookmarks = request.IncludeBookmarks ? await LoadBookmarksAsync(workflowState.Id, cancellationToken) : null;
 0123        var workflowStateElement = _workflowStateSerializer.SerializeToElement(workflowState);
 0124        var bookmarksElement = bookmarks != null ? SerializeBookmarks(bookmarks) : default(JsonElement?);
 0125        var executionLogRecordsElement = executionLogRecords != null ? _safeSerializer.SerializeToElement(executionLogRe
 0126        var activityExecutionLogRecordsElement = activityExecutionLogRecords != null ? _safeSerializer.SerializeToElemen
 0127        var model = new ExportedWorkflowState(workflowStateElement, bookmarksElement, activityExecutionLogRecordsElement
 0128        return model;
 0129    }
 130
 131    private JsonElement SerializeBookmarks(IEnumerable<StoredBookmark> bookmarks)
 132    {
 0133        var jsonBookmarkNodes = bookmarks.Select(x => new JsonObject
 0134        {
 0135            ["id"] = x.Id,
 0136            ["activityTypeName"] = x.Name,
 0137            ["workflowInstanceId"] = x.WorkflowInstanceId,
 0138            ["activityInstanceId"] = x.ActivityInstanceId,
 0139            ["hash"] = x.Hash,
 0140            ["correlationId"] = x.CorrelationId,
 0141            ["createdAt"] = x.CreatedAt,
 0142            ["payload"] = JsonObject.Create(_payloadSerializer.SerializeToElement(x.Payload!)),
 0143            ["metadata"] = JsonObject.Create(_payloadSerializer.SerializeToElement(x.Metadata!))
 0144        }).Cast<JsonNode>().ToArray();
 145
 0146        var jsonBookmarkArray = new JsonArray(jsonBookmarkNodes);
 0147        return JsonSerializer.SerializeToElement(jsonBookmarkArray);
 148    }
 149
 150    private async Task<IEnumerable<StoredBookmark>> LoadBookmarksAsync(string workflowInstanceId, CancellationToken canc
 151    {
 0152        var filter = new BookmarkFilter { WorkflowInstanceId = workflowInstanceId };
 0153        return await _bookmarkStore.FindManyAsync(filter, cancellationToken);
 0154    }
 155
 156    private async Task<IEnumerable<ActivityExecutionRecord>> LoadActivityExecutionLogRecordsAsync(string workflowInstanc
 157    {
 0158        var filter = new ActivityExecutionRecordFilter { WorkflowInstanceId = workflowInstanceId };
 0159        var order = new ActivityExecutionRecordOrder<DateTimeOffset>(x => x.StartedAt, OrderDirection.Ascending);
 0160        return await _activityExecutionStore.FindManyAsync(filter, order, cancellationToken);
 0161    }
 162
 163    private async Task<IEnumerable<WorkflowExecutionLogRecord>> LoadWorkflowExecutionLogRecordsAsync(string workflowInst
 164    {
 0165        var filter = new WorkflowExecutionLogRecordFilter { WorkflowInstanceId = workflowInstanceId };
 0166        var order = new WorkflowExecutionLogRecordOrder<DateTimeOffset>(x => x.Timestamp, OrderDirection.Ascending);
 0167        var page = await _workflowExecutionLogStore.FindManyAsync(filter, PageArgs.All, order, cancellationToken);
 0168        return page.Items;
 0169    }
 170
 171    private static string GetFileName(WorkflowState instance)
 172    {
 0173        var fileName = $"workflow-instance-{instance.Id.ToLowerInvariant()}.json";
 0174        return fileName;
 175    }
 176
 177    private static byte[] SerializeWorkflowInstance(ExportedWorkflowState model)
 178    {
 0179        var binaryJson = JsonSerializer.SerializeToUtf8Bytes(model);
 0180        return binaryJson;
 181    }
 182}