< 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: 182
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 Elsa.Authorization;
 2using System.IO.Compression;
 3using System.Text.Json;
 4using System.Text.Json.Nodes;
 5using Elsa.Abstractions;
 6using Elsa.Common.Entities;
 7using Elsa.Common.Models;
 8using Elsa.Workflows.Api.Endpoints.WorkflowInstances.Get;
 9using Elsa.Workflows.Api.Models;
 10using Elsa.Workflows.Management;
 11using Elsa.Workflows.Management.Entities;
 12using Elsa.Workflows.Management.Filters;
 13using Elsa.Workflows.Runtime;
 14using Elsa.Workflows.Runtime.Entities;
 15using Elsa.Workflows.Runtime.Filters;
 16using Elsa.Workflows.Runtime.OrderDefinitions;
 17using Elsa.Workflows.State;
 18using JetBrains.Annotations;
 19
 20namespace Elsa.Workflows.Api.Endpoints.WorkflowInstances.Export;
 21
 22/// <summary>
 23/// Exports the specified workflow instances as JSON downloads. When selecting multiple instances, a zip file will be do
 24/// </summary>
 25[UsedImplicitly]
 26internal class Export : ElsaEndpointWithMapper<Request, WorkflowInstanceMapper>
 27{
 28    private readonly IWorkflowInstanceStore _workflowInstanceStore;
 29    private readonly IActivityExecutionStore _activityExecutionStore;
 30    private readonly IWorkflowExecutionLogStore _workflowExecutionLogStore;
 31    private readonly IBookmarkStore _bookmarkStore;
 32    private readonly IWorkflowStateSerializer _workflowStateSerializer;
 33    private readonly IPayloadSerializer _payloadSerializer;
 34    private readonly ISafeSerializer _safeSerializer;
 35    private readonly IWorkflowInstanceExportNameProvider _workflowInstanceExportNameProvider;
 36
 37    /// <inheritdoc />
 338    public Export(
 339        IWorkflowInstanceStore workflowInstanceStore,
 340        IActivityExecutionStore activityExecutionStore,
 341        IWorkflowExecutionLogStore workflowExecutionLogStore,
 342        IBookmarkStore bookmarkStore,
 343        IWorkflowStateSerializer workflowStateSerializer,
 344        IPayloadSerializer payloadSerializer,
 345        ISafeSerializer safeSerializer,
 346        IWorkflowInstanceExportNameProvider workFlowInstanceExportNameProvider)
 47    {
 348        _workflowInstanceStore = workflowInstanceStore;
 349        _activityExecutionStore = activityExecutionStore;
 350        _workflowExecutionLogStore = workflowExecutionLogStore;
 351        _bookmarkStore = bookmarkStore;
 352        _workflowStateSerializer = workflowStateSerializer;
 353        _payloadSerializer = payloadSerializer;
 354        _safeSerializer = safeSerializer;
 355        _workflowInstanceExportNameProvider = workFlowInstanceExportNameProvider;
 356    }
 57
 58    /// <inheritdoc />
 59    public override void Configure()
 60    {
 361        Routes("/bulk-actions/export/workflow-instances", "/workflow-instances/{id}/export");
 362        Verbs(FastEndpoints.Http.GET, FastEndpoints.Http.POST);
 363        RequirePermission(Elsa.Workflows.Api.Permissions.WorkflowPermissions.Instances, CoreVerbs.View);
 364    }
 65
 66    /// <inheritdoc />
 67    public override Task HandleAsync(Request request, CancellationToken cancellationToken)
 68    {
 069        if (request.Id != null || request.Ids.Count == 1)
 070            return DownloadSingleInstanceAsync(request, request.Id ?? request.Ids.First(), cancellationToken);
 071        return DownloadMultipleInstancesAsync(request, cancellationToken);
 72    }
 73
 74    private async Task DownloadMultipleInstancesAsync(Request request, CancellationToken cancellationToken)
 75    {
 076        var instances = (await _workflowInstanceStore.FindManyAsync(new WorkflowInstanceFilter { Ids = request.Ids }, ca
 77
 078        if (!instances.Any())
 79        {
 080            await Send.NoContentAsync(cancellationToken);
 081            return;
 82        }
 83
 084        var zipStream = new MemoryStream();
 085        using (var zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
 86        {
 87            // Create a JSON file for each workflow definition:
 088            foreach (var instance in instances)
 89            {
 090                var model = await CreateExportModelAsync(request, instance, cancellationToken);
 091                var binaryJson = SerializeWorkflowInstance(model);
 92
 093                var fileName = await _workflowInstanceExportNameProvider.GetFileNameAsync(instance, model, cancellationT
 94
 095                var entry = zipArchive.CreateEntry(fileName, CompressionLevel.Optimal);
 096                await using var entryStream = entry.Open();
 097                await entryStream.WriteAsync(binaryJson, cancellationToken);
 098            }
 099        }
 100
 101        // Send the zip file to the client:
 0102        zipStream.Position = 0;
 0103        await Send.BytesAsync(zipStream.ToArray(), "workflow-instances.zip", cancellation: cancellationToken);
 0104    }
 105
 106    private async Task DownloadSingleInstanceAsync(Request request, string id, CancellationToken cancellationToken)
 107    {
 0108        var instance = (await _workflowInstanceStore.FindManyAsync(new WorkflowInstanceFilter { Id = id }, cancellationT
 109
 0110        if (instance == null)
 111        {
 0112            await Send.NotFoundAsync(cancellationToken);
 0113            return;
 114        }
 115
 0116        var model = await CreateExportModelAsync(request, instance, cancellationToken);
 0117        var binaryJson = SerializeWorkflowInstance(model);
 0118        var fileName = await _workflowInstanceExportNameProvider.GetFileNameAsync(instance, model, cancellationToken);
 119
 0120        await Send.BytesAsync(binaryJson, fileName, cancellation: cancellationToken);
 0121    }
 122
 123    private async Task<ExportedWorkflowState> CreateExportModelAsync(Request request, WorkflowInstance instance, Cancell
 124    {
 0125        var workflowState = instance.WorkflowState;
 0126        var executionLogRecords = request.IncludeWorkflowExecutionLog ? await LoadWorkflowExecutionLogRecordsAsync(workf
 0127        var activityExecutionLogRecords = request.IncludeActivityExecutionLog ? await LoadActivityExecutionLogRecordsAsy
 0128        var bookmarks = request.IncludeBookmarks ? await LoadBookmarksAsync(workflowState.Id, cancellationToken) : null;
 0129        var workflowStateElement = _workflowStateSerializer.SerializeToElement(workflowState);
 0130        var bookmarksElement = bookmarks != null ? SerializeBookmarks(bookmarks) : default(JsonElement?);
 0131        var executionLogRecordsElement = executionLogRecords != null ? _safeSerializer.SerializeToElement(executionLogRe
 0132        var activityExecutionLogRecordsElement = activityExecutionLogRecords != null ? _safeSerializer.SerializeToElemen
 0133        var model = new ExportedWorkflowState(workflowStateElement, bookmarksElement, activityExecutionLogRecordsElement
 0134        return model;
 0135    }
 136
 137    private JsonElement SerializeBookmarks(IEnumerable<StoredBookmark> bookmarks)
 138    {
 0139        var jsonBookmarkNodes = bookmarks.Select(x => new JsonObject
 0140        {
 0141            ["id"] = x.Id,
 0142            ["activityTypeName"] = x.Name,
 0143            ["workflowInstanceId"] = x.WorkflowInstanceId,
 0144            ["activityInstanceId"] = x.ActivityInstanceId,
 0145            ["hash"] = x.Hash,
 0146            ["correlationId"] = x.CorrelationId,
 0147            ["createdAt"] = x.CreatedAt,
 0148            ["payload"] = JsonObject.Create(_payloadSerializer.SerializeToElement(x.Payload!)),
 0149            ["metadata"] = JsonObject.Create(_payloadSerializer.SerializeToElement(x.Metadata!))
 0150        }).Cast<JsonNode>().ToArray();
 151
 0152        var jsonBookmarkArray = new JsonArray(jsonBookmarkNodes);
 0153        return JsonSerializer.SerializeToElement(jsonBookmarkArray);
 154    }
 155
 156    private async Task<IEnumerable<StoredBookmark>> LoadBookmarksAsync(string workflowInstanceId, CancellationToken canc
 157    {
 0158        var filter = new BookmarkFilter { WorkflowInstanceId = workflowInstanceId };
 0159        return await _bookmarkStore.FindManyAsync(filter, cancellationToken);
 0160    }
 161
 162    private async Task<IEnumerable<ActivityExecutionRecord>> LoadActivityExecutionLogRecordsAsync(string workflowInstanc
 163    {
 0164        var filter = new ActivityExecutionRecordFilter { WorkflowInstanceId = workflowInstanceId };
 0165        var order = new ActivityExecutionRecordOrder<DateTimeOffset>(x => x.StartedAt, OrderDirection.Ascending);
 0166        return await _activityExecutionStore.FindManyAsync(filter, order, cancellationToken);
 0167    }
 168
 169    private async Task<IEnumerable<WorkflowExecutionLogRecord>> LoadWorkflowExecutionLogRecordsAsync(string workflowInst
 170    {
 0171        var filter = new WorkflowExecutionLogRecordFilter { WorkflowInstanceId = workflowInstanceId };
 0172        var order = new WorkflowExecutionLogRecordOrder<DateTimeOffset>(x => x.Timestamp, OrderDirection.Ascending);
 0173        var page = await _workflowExecutionLogStore.FindManyAsync(filter, PageArgs.All, order, cancellationToken);
 0174        return page.Items;
 0175    }
 176
 177    private static byte[] SerializeWorkflowInstance(ExportedWorkflowState model)
 178    {
 0179        var binaryJson = JsonSerializer.SerializeToUtf8Bytes(model);
 0180        return binaryJson;
 181    }
 182}