Implement Export feature
This commit is contained in:
parent
f61df66148
commit
e66b410545
|
|
@ -87,6 +87,7 @@ export namespace Components {
|
|||
}
|
||||
interface ElsaWorkflowPublishButton {
|
||||
"publishing": boolean;
|
||||
"serverUrl": string;
|
||||
"workflowDefinition": WorkflowDefinition;
|
||||
}
|
||||
interface ElsaWorkflowSettingsModal {
|
||||
|
|
@ -301,6 +302,7 @@ declare namespace LocalJSX {
|
|||
"onPublishClicked"?: (event: CustomEvent<any>) => void;
|
||||
"onUnPublishClicked"?: (event: CustomEvent<any>) => void;
|
||||
"publishing"?: boolean;
|
||||
"serverUrl"?: string;
|
||||
"workflowDefinition"?: WorkflowDefinition;
|
||||
}
|
||||
interface ElsaWorkflowSettingsModal {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
import {Component, Host, h, Prop, State, Event, EventEmitter, Watch} from '@stencil/core';
|
||||
import {enter, leave, toggle} from 'el-transition'
|
||||
import {registerClickOutside} from "stencil-click-outside";
|
||||
import {ActivityModel, WorkflowDefinition} from "../../../../models";
|
||||
import {ActivityModel, VersionOptions, WorkflowDefinition} from "../../../../models";
|
||||
import {createElsaClient} from "../../../../services/elsa-client";
|
||||
import Tunnel, {WorkflowEditorState} from '../../../data/workflow-editor';
|
||||
import {ElsaTextProperty} from "../../properties/elsa-text-property/elsa-text-property";
|
||||
import {downloadFromBlob, downloadFromText} from "../../../../utils/download";
|
||||
|
||||
@Component({
|
||||
tag: 'elsa-workflow-publish-button',
|
||||
|
|
@ -12,6 +16,7 @@ export class ElsaWorkflowPublishButton {
|
|||
|
||||
@Prop() workflowDefinition: WorkflowDefinition;
|
||||
@Prop() publishing: boolean;
|
||||
@Prop({attribute: 'server-url', reflect: true}) serverUrl: string;
|
||||
@Event({bubbles: true}) publishClicked: EventEmitter;
|
||||
@Event({bubbles: true}) unPublishClicked: EventEmitter;
|
||||
|
||||
|
|
@ -34,6 +39,16 @@ export class ElsaWorkflowPublishButton {
|
|||
this.unPublishClicked.emit();
|
||||
}
|
||||
|
||||
async onExportClick(e: Event) {
|
||||
e.preventDefault();
|
||||
|
||||
const client = createElsaClient(this.serverUrl);
|
||||
const workflowDefinition = this.workflowDefinition;
|
||||
const versionOptions: VersionOptions = {version: workflowDefinition.version};
|
||||
const response = await client.workflowDefinitionsApi.export(workflowDefinition.definitionId, versionOptions);
|
||||
downloadFromBlob(response.data, { contentType: 'application/json', fileName: response.fileName });
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Host ref={el => registerClickOutside(this, el, this.closeMenu)}>
|
||||
|
|
@ -58,7 +73,7 @@ export class ElsaWorkflowPublishButton {
|
|||
<div class="divide-y divide-gray-100 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="option-menu">
|
||||
|
||||
<div class="py-1" role="none">
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900" role="menuitem">
|
||||
<a href="#" onClick={e => this.onExportClick(e)} class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900" role="menuitem">
|
||||
Export
|
||||
</a>
|
||||
|
||||
|
|
@ -114,3 +129,5 @@ export class ElsaWorkflowPublishButton {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
Tunnel.injectProps(ElsaWorkflowPublishButton, ['serverUrl']);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,21 @@ export const createElsaClient = function (serverUrl: string): ElsaClient {
|
|||
retract: async workflowDefinitionId => {
|
||||
const response = await httpClient.post<WorkflowDefinition>(`v1/workflow-definitions/${workflowDefinitionId}/retract`);
|
||||
return response.data;
|
||||
},
|
||||
export: async (workflowDefinitionId, versionOptions): Promise<ExportWorkflowResponse> => {
|
||||
const versionOptionsString = getVersionOptionsString(versionOptions);
|
||||
const response = await httpClient.post(`v1/workflow-definitions/${workflowDefinitionId}/${versionOptionsString}/export`, null, {
|
||||
responseType: 'blob'
|
||||
});
|
||||
|
||||
const contentDispositionHeader = response.headers["content-disposition"]; // Only available if the Elsa Server exposes the "Content-Disposition" header.
|
||||
const fileName = contentDispositionHeader ? contentDispositionHeader.split(";")[1].split("=")[1] : `workflow-definition-${workflowDefinitionId}.json`;
|
||||
const data = response.data;
|
||||
|
||||
return {
|
||||
fileName: fileName,
|
||||
data: data
|
||||
};
|
||||
}
|
||||
},
|
||||
scriptingApi: {
|
||||
|
|
@ -56,6 +71,8 @@ export interface WorkflowDefinitionsApi {
|
|||
save(request: SaveWorkflowDefinitionRequest): Promise<WorkflowDefinition>
|
||||
|
||||
retract(workflowDefinitionId: string): Promise<WorkflowDefinition>
|
||||
|
||||
export(workflowDefinitionId: string, versionOptions: VersionOptions): Promise<ExportWorkflowResponse>
|
||||
}
|
||||
|
||||
export interface ScriptingApi {
|
||||
|
|
@ -76,3 +93,8 @@ export interface SaveWorkflowDefinitionRequest {
|
|||
activities: Array<ActivityDefinition>
|
||||
connections: Array<ConnectionDefinition>
|
||||
}
|
||||
|
||||
export interface ExportWorkflowResponse {
|
||||
fileName: string;
|
||||
data: Blob;
|
||||
}
|
||||
|
|
|
|||
30
src/designer/elsa-workflows-studio/src/utils/download.ts
Normal file
30
src/designer/elsa-workflows-studio/src/utils/download.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
export interface DownloadOptions {
|
||||
fileName?: string;
|
||||
contentType?: string;
|
||||
}
|
||||
|
||||
export function downloadFromUrl(url: string, options: DownloadOptions) {
|
||||
const anchorElement = document.createElement('a');
|
||||
anchorElement.href = url;
|
||||
anchorElement.download = options.fileName !== null && options.fileName !== void 0 ? options.fileName : '';
|
||||
anchorElement.click();
|
||||
anchorElement.remove();
|
||||
}
|
||||
|
||||
export function downloadFromBytes(content: Uint8Array, options: DownloadOptions) {
|
||||
const contentType = options.contentType || 'application/octet-stream';
|
||||
const base64String = btoa(String.fromCharCode(...content));
|
||||
const url = `data:${contentType}";base64,${base64String}`;
|
||||
downloadFromUrl(url, {fileName: options.fileName});
|
||||
}
|
||||
|
||||
export function downloadFromText(content: string, options: DownloadOptions) {
|
||||
const utf8Encode = new TextEncoder();
|
||||
const bytes = utf8Encode.encode(content);
|
||||
downloadFromBytes(bytes, options)
|
||||
}
|
||||
|
||||
export function downloadFromBlob(content: Blob, options: DownloadOptions) {
|
||||
const url = window.URL.createObjectURL(content);
|
||||
downloadFromUrl(url, options)
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Net.Http.Headers;
|
||||
using Elsa;
|
||||
using Elsa.Persistence.EntityFramework.Core.Extensions;
|
||||
using Elsa.Persistence.EntityFramework.Sqlite;
|
||||
|
|
@ -57,7 +58,7 @@ namespace ElsaDashboard.Samples.Monolith
|
|||
|
||||
// Allow arbitrary client browser apps to access the API for demo purposes only.
|
||||
// In a production environment, make sure to allow only origins you trust.
|
||||
services.AddCors(cors => cors.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin()));
|
||||
services.AddCors(cors => cors.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().WithExposedHeaders("Content-Disposition")));
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Http\Elsa.Activities.Http.csproj" />
|
||||
<ProjectReference Include="..\..\..\dashboards\blazor\ElsaDashboard.Application\ElsaDashboard.Application.csproj" />
|
||||
<ProjectReference Include="..\..\..\dashboards\blazor\ElsaDashboard.Backend\ElsaDashboard.Backend.csproj" />
|
||||
<ProjectReference Include="..\ElsaDashboard.Samples.WebAssembly\ElsaDashboard.Samples.WebAssembly.csproj" />
|
||||
|
|
|
|||
|
|
@ -36,10 +36,10 @@ namespace Elsa.Samples.Server.Host
|
|||
services
|
||||
.AddElsaApiEndpoints()
|
||||
.AddElsaSwagger();
|
||||
|
||||
|
||||
// Allow arbitrary client browser apps to access the API for demo purposes only.
|
||||
// In a production environment, make sure to allow only origins you trust.
|
||||
services.AddCors(cors => cors.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin()));
|
||||
services.AddCors(cors => cors.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().WithExposedHeaders("Content-Disposition")));
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
|
|
@ -54,6 +54,7 @@ namespace Elsa.Samples.Server.Host
|
|||
app
|
||||
.UseCors()
|
||||
.UseHttpActivities()
|
||||
.UseCors()
|
||||
.UseRouting()
|
||||
.UseEndpoints(configure => configure.MapControllers());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Models;
|
||||
using Elsa.Persistence;
|
||||
using Elsa.Serialization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
|
||||
namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1")]
|
||||
[Route("v{apiVersion:apiVersion}/workflow-definitions/{workflowDefinitionId}/{versionOptions}/export")]
|
||||
[Produces("application/json")]
|
||||
public class Export : ControllerBase
|
||||
{
|
||||
private readonly IWorkflowDefinitionStore _workflowDefinitionStore;
|
||||
private readonly IContentSerializer _contentSerializer;
|
||||
|
||||
public Export(IWorkflowDefinitionStore workflowDefinitionStore, IContentSerializer contentSerializer)
|
||||
{
|
||||
_workflowDefinitionStore = workflowDefinitionStore;
|
||||
_contentSerializer = contentSerializer;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[SwaggerOperation(
|
||||
Summary = "Exports the specified workflow definition as a JSON download.",
|
||||
Description = "Exports the specified workflow definition as a JSON download.",
|
||||
OperationId = "WorkflowDefinitions.Export",
|
||||
Tags = new[] { "WorkflowDefinitions" })
|
||||
]
|
||||
public async Task<IActionResult> Handle(string workflowDefinitionId, VersionOptions versionOptions, ApiVersion apiVersion, CancellationToken cancellationToken)
|
||||
{
|
||||
var workflowDefinition = await _workflowDefinitionStore.FindByDefinitionIdAsync(workflowDefinitionId, versionOptions, cancellationToken);
|
||||
|
||||
if (workflowDefinition == null)
|
||||
return NotFound();
|
||||
|
||||
var json = _contentSerializer.Serialize(workflowDefinition);
|
||||
var bytes = Encoding.UTF8.GetBytes(json);
|
||||
var workflowName = workflowDefinition.Name is null or "" ? workflowDefinition.DefinitionId : workflowDefinition.Name.Trim();
|
||||
var fileName = $"workflow-definition-{workflowName}.json";
|
||||
|
||||
return File(bytes, "application/json", fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue