Skip to content

Commit

Permalink
Update import / export (#3471)
Browse files Browse the repository at this point in the history
  • Loading branch information
sfmskywalker authored Nov 25, 2022
1 parent ae15902 commit 6bab998
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 60 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Component, Event, EventEmitter, Host, h, Listen, Prop} from '@stencil/core';
import {leave, toggle} from 'el-transition'
import newButtonItemStore from "../../../data/new-button-item-store";
import {MenuItem} from "../../shared/context-menu/models";
import {MenuItem, MenuItemGroup} from "../../shared/context-menu/models";

@Component({
tag: 'elsa-new-button',
Expand Down Expand Up @@ -30,12 +30,6 @@ export class NewButton {
toggle(this.menu);
}

private onNewWorkflowDefinitionClick(e: Event) {
e.preventDefault();
this.newClicked.emit();
leave(this.menu);
}

private onItemClick = (e: MouseEvent, item: MenuItem) => {
e.preventDefault();

Expand All @@ -47,7 +41,7 @@ export class NewButton {

render() {

const items: Array<MenuItem> = newButtonItemStore.items;
const items: Array<MenuItemGroup> = newButtonItemStore.items;

const mainItem: MenuItem = newButtonItemStore.mainItem ?? {
text: 'New',
Expand Down Expand Up @@ -79,16 +73,21 @@ export class NewButton {
class="hidden origin-bottom-right absolute right-0 top-10 mb-2 -mr-1 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5">

<div class="divide-y divide-gray-100 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="option-menu">

{items.map(item => (
<div class="py-1" role="none">
<a href="#" onClick={e => this.onItemClick(e, item)}
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900"
role="menuitem">
{item.text}
</a>
</div>))}

{items.map(groupItem => {
return <div class="py-1" role="none">
{groupItem.menuItems.map(item => {
return (
<div class="py-1" role="none">
<a href="#" onClick={e => this.onItemClick(e, item)}
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900"
role="menuitem">
{item.text}
</a>
</div>);
})}

</div>
})}
</div>
</div>
</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {h} from "@stencil/core";
import {Container, Service} from "typedi";
import {ActivityDescriptor, Plugin} from "../../models";
import newButtonItemStore from "../../data/new-button-item-store";
import {MenuItem} from "../../components/shared/context-menu/models";
import {MenuItem, MenuItemGroup} from "../../components/shared/context-menu/models";
import {Flowchart} from "../flowchart/models";
import {generateUniqueActivityName} from '../../utils/generate-activity-name';
import descriptorsStore from "../../data/descriptors-store";
Expand Down Expand Up @@ -43,13 +43,17 @@ export class ActivityDefinitionsPlugin implements Plugin {
clickHandler: this.onNewActivityDefinitionClick
}

const newItemGroup: MenuItemGroup = {
menuItems: [newActivityDefinitionItem]
};

const activityDefinitionBrowserItem: ToolbarMenuItem = {
text: 'Activity Definitions',
onClick: this.onBrowseActivityDefinitions,
order: 5
};

newButtonItemStore.items = [...newButtonItemStore.items, newActivityDefinitionItem];
newButtonItemStore.items = [...newButtonItemStore.items, newItemGroup];
toolbarButtonMenuItemStore.items = [...toolbarButtonMenuItemStore.items, activityDefinitionBrowserItem];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ export class PublishButton {
@Event({bubbles: true}) importClicked: EventEmitter<File>;

menu: HTMLElement;
fileInput: HTMLInputElement;
element: HTMLElement;

@Listen('click', {target: 'window'})
Expand Down Expand Up @@ -70,22 +69,10 @@ export class PublishButton {

private async onImportClick(e: Event) {
e.preventDefault();
this.fileInput.value = null;
this.fileInput.click();

this.importClicked.emit();
leave(this.menu);
}

private async onFileInputChange(e: Event) {
const files = this.fileInput.files;

if (files.length == 0) {
return;
}

this.importClicked.emit(files[0]);
}

render() {
const newMenuItems: Array<MenuItem> = newButtonItemStore.items;

Expand Down Expand Up @@ -141,7 +128,6 @@ export class PublishButton {
</div>
</span>
</span>
<input type="file" class="hidden" onChange={e => this.onFileInputChange(e)} ref={el => this.fileInput = el}/>
</Host>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {h} from "@stencil/core";
import {Container, Service} from "typedi";
import {ActivityDescriptor, Plugin} from "../../models";
import newButtonItemStore from "../../data/new-button-item-store";
import {MenuItem} from "../../components/shared/context-menu/models";
import {MenuItem, MenuItemGroup} from "../../components/shared/context-menu/models";
import {Flowchart} from "../flowchart/models";
import {generateUniqueActivityName} from '../../utils/generate-activity-name';
import descriptorsStore from "../../data/descriptors-store";
Expand All @@ -17,9 +17,10 @@ import {WorkflowDefinitionManager} from "./services/manager";
import {WorkflowDefinition, WorkflowDefinitionSummary} from "./models/entities";
import {WorkflowDefinitionUpdatedArgs} from "./models/ui";
import {PublishClickedArgs} from "./components/publish-button";
import {WorkflowDefinitionsApi} from "./services/api";
import {ExportWorkflowRequest, ImportWorkflowRequest, WorkflowDefinitionsApi} from "./services/api";
import {DefaultModalActions, ModalDialogInstance, ModalDialogService} from "../../components/shared/modal-dialog";
import {isEqual} from 'lodash'
import {htmlToElement} from "../../utils";

const FlowchartTypeName = 'Elsa.Flowchart';

Expand All @@ -43,13 +44,22 @@ export class WorkflowDefinitionsPlugin implements Plugin {
clickHandler: this.onNewWorkflowDefinitionClick
}

const importWorkflowDefinitionItem: MenuItem = {
text: 'Import',
clickHandler: this.onImportWorkflowDefinitionClick
}

const newItemGroup: MenuItemGroup = {
menuItems: [newWorkflowDefinitionItem, importWorkflowDefinitionItem]
}

const workflowDefinitionBrowserItem: ToolbarMenuItem = {
text: 'Workflow Definitions',
onClick: this.onBrowseWorkflowDefinitions,
order: 5
};

newButtonItemStore.items = [...newButtonItemStore.items, newWorkflowDefinitionItem];
newButtonItemStore.items = [...newButtonItemStore.items, newItemGroup];
toolbarButtonMenuItemStore.items = [...toolbarButtonMenuItemStore.items, workflowDefinitionBrowserItem];
}

Expand Down Expand Up @@ -98,15 +108,41 @@ export class WorkflowDefinitionsPlugin implements Plugin {
}

private showWorkflowDefinitionEditor = (workflowDefinition: WorkflowDefinition) => {
toolbarComponentStore.components = [() => <elsa-workflow-publish-button onPublishClicked={this.onPublishClicked}/>];
toolbarComponentStore.components = [() => <elsa-workflow-publish-button onPublishClicked={this.onPublishClicked} onExportClicked={this.onExportClicked} onImportClicked={this.onImportClicked}/>];
studioComponentStore.activeComponentFactory = () => <elsa-workflow-definition-editor workflowDefinition={workflowDefinition} onWorkflowUpdated={this.onWorkflowUpdated} ref={el => this.workflowDefinitionEditorElement = el}/>;
};

private import = async () => {
const fileInput = htmlToElement<HTMLInputElement>('<input type="file" />');

document.body.append(fileInput);
fileInput.click();

fileInput.addEventListener('change', async e => {
const files = fileInput.files;

if(files.length == 0) {
fileInput.remove();
return;
}

const file = files[0];
const importedWorkflow = await this.workflowDefinitionManager.importWorkflow(null, file);
fileInput.remove();
this.showWorkflowDefinitionEditor(importedWorkflow);
});
};

private onNewWorkflowDefinitionClick = async () => {
await this.newWorkflow();
this.modalDialogService.hide(this.workflowDefinitionBrowserInstance);
};

private onImportWorkflowDefinitionClick = async () => {
await this.import();
this.modalDialogService.hide(this.workflowDefinitionBrowserInstance);
};

private onWorkflowUpdated = async (e: CustomEvent<WorkflowDefinitionUpdatedArgs>) => {
const updatedWorkflowDefinition = e.detail.workflowDefinition;

Expand Down Expand Up @@ -151,4 +187,13 @@ export class WorkflowDefinitionsPlugin implements Plugin {
await this.eventBus.emit(NotificationEventTypes.Update, this, {id: workflowDefinition.definitionId, message: `${workflowDefinition.name} publish finished`});
e.detail.complete();
}

private onExportClicked = async (e: CustomEvent) => {
const workflowDefinition = await this.workflowDefinitionEditorElement.getWorkflowDefinition();
await this.workflowDefinitionManager.exportWorkflow(workflowDefinition);
}

private onImportClicked = async (e: CustomEvent) => {
await this.import();
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { getVersionOptionsString, serializeQueryString } from '../../../utils';
import {getVersionOptionsString, serializeQueryString} from '../../../utils';
import {WorkflowDefinition, WorkflowDefinitionSummary} from "../models/entities";
import {Activity, PagedList, Variable, VersionedEntity, VersionOptions} from "../../../models";
import {Service} from "typedi";
import {ElsaApiClientProvider} from "../../../services";
import {AxiosResponse} from "axios";

@Service()
export class WorkflowDefinitionsApi {
Expand Down Expand Up @@ -114,15 +115,24 @@ export class WorkflowDefinitionsApi {
const definitionId = request.definitionId;
const json = await file.text();
const httpClient = await this.getHttpClient();

const response = await httpClient.post<WorkflowDefinition>(`workflow-definitions/${definitionId}/import`, json, {
headers: {
'Content-Type': 'application/json',
},
});
let response: AxiosResponse;

if (!definitionId) {
response = await httpClient.put<WorkflowDefinition>(`workflow-definitions/import`, json, {
headers: {
'Content-Type': 'application/json',
},
});
} else {
response = await httpClient.post<WorkflowDefinition>(`workflow-definitions/${definitionId}/import`, json, {
headers: {
'Content-Type': 'application/json',
},
});
}

const workflowDefinition = response.data;
return { workflowDefinition: workflowDefinition };
return {workflowDefinition: workflowDefinition};
}

async deleteMany(request: DeleteManyWorkflowDefinitionRequest): Promise<DeleteManyWorkflowDefinitionResponse> {
Expand Down Expand Up @@ -166,9 +176,15 @@ export interface SaveWorkflowDefinitionRequest {
export interface BaseManyWorkflowDefinitionRequest {
definitionIds: string[];
}
export interface DeleteManyWorkflowDefinitionRequest extends BaseManyWorkflowDefinitionRequest {}
export interface PublishManyWorkflowDefinitionRequest extends BaseManyWorkflowDefinitionRequest {}
export interface UnpublishManyWorkflowDefinitionRequest extends BaseManyWorkflowDefinitionRequest {}

export interface DeleteManyWorkflowDefinitionRequest extends BaseManyWorkflowDefinitionRequest {
}

export interface PublishManyWorkflowDefinitionRequest extends BaseManyWorkflowDefinitionRequest {
}

export interface UnpublishManyWorkflowDefinitionRequest extends BaseManyWorkflowDefinitionRequest {
}

export interface DeleteWorkflowDefinitionRequest {
definitionId: string;
Expand Down Expand Up @@ -208,7 +224,7 @@ export interface ExportWorkflowResponse {
}

export interface ImportWorkflowRequest {
definitionId: string;
definitionId?: string;
file: File;
}

Expand All @@ -220,6 +236,7 @@ export enum WorkflowDefinitionsOrderBy {
Name = 'Name',
CreatedAt = 'CreatedAt',
}

export interface ListWorkflowDefinitionsRequest {
page?: number;
pageSize?: number;
Expand All @@ -239,6 +256,7 @@ export interface PublishManyWorkflowDefinitionResponse {
alreadyPublished: string[];
notFound: string[];
}

export interface UnpublishManyWorkflowDefinitionResponse {
retracted: string[];
notPublished: string[];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {Container, Service} from "typedi";
import {WorkflowDefinition} from "../models/entities";
import {RetractWorkflowDefinitionRequest, SaveWorkflowDefinitionRequest, WorkflowDefinitionsApi} from "./api";
import {ExportWorkflowRequest, ImportWorkflowRequest, RetractWorkflowDefinitionRequest, SaveWorkflowDefinitionRequest, WorkflowDefinitionsApi} from "./api";
import {downloadFromBlob} from "../../../utils";

@Service()
export class WorkflowDefinitionManager {
Expand All @@ -22,13 +23,34 @@ export class WorkflowDefinitionManager {
};

return await this.api.post(request);
}
};

public retractWorkflow = async (workflow: WorkflowDefinition): Promise<WorkflowDefinition> => {
public retractWorkflow = async (definition: WorkflowDefinition): Promise<WorkflowDefinition> => {
const request: RetractWorkflowDefinitionRequest = {
definitionId: workflow.definitionId
definitionId: definition.definitionId
};

return await this.api.retract(request);
}
};

public exportWorkflow = async (definition: WorkflowDefinition): Promise<void> => {
const request: ExportWorkflowRequest = {
definitionId: definition.definitionId,
versionOptions: {version: definition.version}
};

const response = await this.api.export(request);
downloadFromBlob(response.data, {contentType: 'application/json', fileName: response.fileName});
};

public importWorkflow = async (definitionId: string, file: File): Promise<WorkflowDefinition> => {
try {
const importRequest: ImportWorkflowRequest = {definitionId, file};
const importResponse = await this.api.import(importRequest);
return importResponse.workflowDefinition;
} catch (e) {
console.error(e);
}
};

}
7 changes: 7 additions & 0 deletions src/designer/elsa-workflows-designer/src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,10 @@ export function durationToString(duration: moment.Duration) {
: `${duration.asMilliseconds()} ms`
: null;
}

export function htmlToElement<TElement>(html: string): TElement {
const template = document.createElement('template');
html = html.trim(); // Never return a text node of whitespace as the result
template.innerHTML = html;
return template.content.firstChild as unknown as TElement;
}
Loading

0 comments on commit 6bab998

Please sign in to comment.