Incremental work on workflow instances list screen

This commit is contained in:
Sipke Schoorstra 2021-04-08 17:02:58 +02:00
parent 57e38425ae
commit 0bc41fc9b4
18 changed files with 479134 additions and 125 deletions

View file

@ -64,13 +64,14 @@ namespace Elsa.Bookmarks
var workflowInstanceIds = workflowInstanceList.Select(x => x.Id).ToList();
await DeleteBookmarksAsync(workflowInstanceIds, cancellationToken);
var workflowBlueprints = (await _workflowRegistry.ListAsync(cancellationToken)).ToDictionary(x => (x.Id, x.Version));
var workflowBlueprints = await _workflowRegistry.ListAsync(cancellationToken);
var workflowBlueprintsDictionary = workflowBlueprints.ToDictionary(x => (x.Id, x.Version));
var entities = new List<Bookmark>();
foreach (var workflowInstance in workflowInstanceList.Where(x => x.WorkflowStatus == WorkflowStatus.Suspended))
{
var workflowBlueprintKey = (workflowInstance.DefinitionId, workflowInstance.Version);
var workflowBlueprint = workflowBlueprints.ContainsKey(workflowBlueprintKey) ? workflowBlueprints[workflowBlueprintKey] : default;
var workflowBlueprint = workflowBlueprintsDictionary.ContainsKey(workflowBlueprintKey) ? workflowBlueprintsDictionary[workflowBlueprintKey] : default;
if (workflowBlueprint == null)
{

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,7 @@ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
import { MatchResults, RouterHistory } from "@stencil/router";
import { ActivityDefinitionProperty, ActivityDesignDisplayContext, ActivityModel, ActivityPropertyDescriptor, VersionOptions, WorkflowDefinition, WorkflowModel } from "./models";
import { MenuItem } from "./components/controls/elsa-context-menu/models";
import { DropdownButtonItem, DropdownButtonOrigin } from "./components/controls/elsa-dropdown-button/models";
import { MonacoValueChangedArgs } from "./components/controls/elsa-monaco/elsa-monaco";
import { ToastNotificationOptions } from "./components/shared/elsa-toast-notification/elsa-toast-notification";
export namespace Components {
@ -19,6 +20,11 @@ export namespace Components {
"serverUrl": string;
}
interface AppWorkflowDefinitionsList {
"history": RouterHistory;
"serverUrl": string;
}
interface AppWorkflowInstancesList {
"history": RouterHistory;
"serverUrl": string;
}
interface AppWorkflowRegistry {
@ -51,6 +57,12 @@ export namespace Components {
"displayContext": ActivityDesignDisplayContext;
"icon": string;
}
interface ElsaDropdownButton {
"icon"?: any;
"items": Array<DropdownButtonItem>;
"origin": DropdownButtonOrigin;
"text": string;
}
interface ElsaDropdownProperty {
"propertyDescriptor": ActivityPropertyDescriptor;
"propertyModel": ActivityDefinitionProperty;
@ -128,10 +140,15 @@ export namespace Components {
"workflowDefinitionId": string;
}
interface ElsaWorkflowDefinitionsListScreen {
"history"?: RouterHistory;
"serverUrl": string;
}
interface ElsaWorkflowEditorNotifications {
}
interface ElsaWorkflowInstancesListScreen {
"history"?: RouterHistory;
"serverUrl": string;
}
interface ElsaWorkflowPublishButton {
"publishing": boolean;
"workflowDefinition": WorkflowDefinition;
@ -139,6 +156,10 @@ export namespace Components {
interface ElsaWorkflowSettingsModal {
"workflowDefinition": WorkflowDefinition;
}
interface WorkflowInstancesListScreen {
"history"?: RouterHistory;
"serverUrl": string;
}
}
declare global {
interface HTMLAppRootElement extends Components.AppRoot, HTMLStencilElement {
@ -159,6 +180,12 @@ declare global {
prototype: HTMLAppWorkflowDefinitionsListElement;
new (): HTMLAppWorkflowDefinitionsListElement;
};
interface HTMLAppWorkflowInstancesListElement extends Components.AppWorkflowInstancesList, HTMLStencilElement {
}
var HTMLAppWorkflowInstancesListElement: {
prototype: HTMLAppWorkflowInstancesListElement;
new (): HTMLAppWorkflowInstancesListElement;
};
interface HTMLAppWorkflowRegistryElement extends Components.AppWorkflowRegistry, HTMLStencilElement {
}
var HTMLAppWorkflowRegistryElement: {
@ -213,6 +240,12 @@ declare global {
prototype: HTMLElsaDesignerTreeActivityElement;
new (): HTMLElsaDesignerTreeActivityElement;
};
interface HTMLElsaDropdownButtonElement extends Components.ElsaDropdownButton, HTMLStencilElement {
}
var HTMLElsaDropdownButtonElement: {
prototype: HTMLElsaDropdownButtonElement;
new (): HTMLElsaDropdownButtonElement;
};
interface HTMLElsaDropdownPropertyElement extends Components.ElsaDropdownProperty, HTMLStencilElement {
}
var HTMLElsaDropdownPropertyElement: {
@ -297,6 +330,12 @@ declare global {
prototype: HTMLElsaWorkflowEditorNotificationsElement;
new (): HTMLElsaWorkflowEditorNotificationsElement;
};
interface HTMLElsaWorkflowInstancesListScreenElement extends Components.ElsaWorkflowInstancesListScreen, HTMLStencilElement {
}
var HTMLElsaWorkflowInstancesListScreenElement: {
prototype: HTMLElsaWorkflowInstancesListScreenElement;
new (): HTMLElsaWorkflowInstancesListScreenElement;
};
interface HTMLElsaWorkflowPublishButtonElement extends Components.ElsaWorkflowPublishButton, HTMLStencilElement {
}
var HTMLElsaWorkflowPublishButtonElement: {
@ -309,10 +348,17 @@ declare global {
prototype: HTMLElsaWorkflowSettingsModalElement;
new (): HTMLElsaWorkflowSettingsModalElement;
};
interface HTMLWorkflowInstancesListScreenElement extends Components.WorkflowInstancesListScreen, HTMLStencilElement {
}
var HTMLWorkflowInstancesListScreenElement: {
prototype: HTMLWorkflowInstancesListScreenElement;
new (): HTMLWorkflowInstancesListScreenElement;
};
interface HTMLElementTagNameMap {
"app-root": HTMLAppRootElement;
"app-workflow-definitions-edit": HTMLAppWorkflowDefinitionsEditElement;
"app-workflow-definitions-list": HTMLAppWorkflowDefinitionsListElement;
"app-workflow-instances-list": HTMLAppWorkflowInstancesListElement;
"app-workflow-registry": HTMLAppWorkflowRegistryElement;
"elsa-activity-editor-modal": HTMLElsaActivityEditorModalElement;
"elsa-activity-picker-modal": HTMLElsaActivityPickerModalElement;
@ -322,6 +368,7 @@ declare global {
"elsa-context-menu": HTMLElsaContextMenuElement;
"elsa-designer-tree": HTMLElsaDesignerTreeElement;
"elsa-designer-tree-activity": HTMLElsaDesignerTreeActivityElement;
"elsa-dropdown-button": HTMLElsaDropdownButtonElement;
"elsa-dropdown-property": HTMLElsaDropdownPropertyElement;
"elsa-expression-editor": HTMLElsaExpressionEditorElement;
"elsa-input-tags": HTMLElsaInputTagsElement;
@ -336,8 +383,10 @@ declare global {
"elsa-workflow-definition-editor-screen": HTMLElsaWorkflowDefinitionEditorScreenElement;
"elsa-workflow-definitions-list-screen": HTMLElsaWorkflowDefinitionsListScreenElement;
"elsa-workflow-editor-notifications": HTMLElsaWorkflowEditorNotificationsElement;
"elsa-workflow-instances-list-screen": HTMLElsaWorkflowInstancesListScreenElement;
"elsa-workflow-publish-button": HTMLElsaWorkflowPublishButtonElement;
"elsa-workflow-settings-modal": HTMLElsaWorkflowSettingsModalElement;
"workflow-instances-list-screen": HTMLWorkflowInstancesListScreenElement;
}
}
declare namespace LocalJSX {
@ -349,6 +398,11 @@ declare namespace LocalJSX {
"serverUrl"?: string;
}
interface AppWorkflowDefinitionsList {
"history"?: RouterHistory;
"serverUrl"?: string;
}
interface AppWorkflowInstancesList {
"history"?: RouterHistory;
"serverUrl"?: string;
}
interface AppWorkflowRegistry {
@ -381,6 +435,13 @@ declare namespace LocalJSX {
"onEdit-activity"?: (event: CustomEvent<ActivityModel>) => void;
"onRemove-activity"?: (event: CustomEvent<ActivityModel>) => void;
}
interface ElsaDropdownButton {
"icon"?: any;
"items"?: Array<DropdownButtonItem>;
"onItemSelected"?: (event: CustomEvent<DropdownButtonItem>) => void;
"origin"?: DropdownButtonOrigin;
"text"?: string;
}
interface ElsaDropdownProperty {
"propertyDescriptor"?: ActivityPropertyDescriptor;
"propertyModel"?: ActivityDefinitionProperty;
@ -452,10 +513,15 @@ declare namespace LocalJSX {
"workflowDefinitionId"?: string;
}
interface ElsaWorkflowDefinitionsListScreen {
"history"?: RouterHistory;
"serverUrl"?: string;
}
interface ElsaWorkflowEditorNotifications {
}
interface ElsaWorkflowInstancesListScreen {
"history"?: RouterHistory;
"serverUrl"?: string;
}
interface ElsaWorkflowPublishButton {
"onExportClicked"?: (event: CustomEvent<any>) => void;
"onImportClicked"?: (event: CustomEvent<File>) => void;
@ -467,10 +533,15 @@ declare namespace LocalJSX {
interface ElsaWorkflowSettingsModal {
"workflowDefinition"?: WorkflowDefinition;
}
interface WorkflowInstancesListScreen {
"history"?: RouterHistory;
"serverUrl"?: string;
}
interface IntrinsicElements {
"app-root": AppRoot;
"app-workflow-definitions-edit": AppWorkflowDefinitionsEdit;
"app-workflow-definitions-list": AppWorkflowDefinitionsList;
"app-workflow-instances-list": AppWorkflowInstancesList;
"app-workflow-registry": AppWorkflowRegistry;
"elsa-activity-editor-modal": ElsaActivityEditorModal;
"elsa-activity-picker-modal": ElsaActivityPickerModal;
@ -480,6 +551,7 @@ declare namespace LocalJSX {
"elsa-context-menu": ElsaContextMenu;
"elsa-designer-tree": ElsaDesignerTree;
"elsa-designer-tree-activity": ElsaDesignerTreeActivity;
"elsa-dropdown-button": ElsaDropdownButton;
"elsa-dropdown-property": ElsaDropdownProperty;
"elsa-expression-editor": ElsaExpressionEditor;
"elsa-input-tags": ElsaInputTags;
@ -494,8 +566,10 @@ declare namespace LocalJSX {
"elsa-workflow-definition-editor-screen": ElsaWorkflowDefinitionEditorScreen;
"elsa-workflow-definitions-list-screen": ElsaWorkflowDefinitionsListScreen;
"elsa-workflow-editor-notifications": ElsaWorkflowEditorNotifications;
"elsa-workflow-instances-list-screen": ElsaWorkflowInstancesListScreen;
"elsa-workflow-publish-button": ElsaWorkflowPublishButton;
"elsa-workflow-settings-modal": ElsaWorkflowSettingsModal;
"workflow-instances-list-screen": WorkflowInstancesListScreen;
}
}
export { LocalJSX as JSX };
@ -505,6 +579,7 @@ declare module "@stencil/core" {
"app-root": LocalJSX.AppRoot & JSXBase.HTMLAttributes<HTMLAppRootElement>;
"app-workflow-definitions-edit": LocalJSX.AppWorkflowDefinitionsEdit & JSXBase.HTMLAttributes<HTMLAppWorkflowDefinitionsEditElement>;
"app-workflow-definitions-list": LocalJSX.AppWorkflowDefinitionsList & JSXBase.HTMLAttributes<HTMLAppWorkflowDefinitionsListElement>;
"app-workflow-instances-list": LocalJSX.AppWorkflowInstancesList & JSXBase.HTMLAttributes<HTMLAppWorkflowInstancesListElement>;
"app-workflow-registry": LocalJSX.AppWorkflowRegistry & JSXBase.HTMLAttributes<HTMLAppWorkflowRegistryElement>;
"elsa-activity-editor-modal": LocalJSX.ElsaActivityEditorModal & JSXBase.HTMLAttributes<HTMLElsaActivityEditorModalElement>;
"elsa-activity-picker-modal": LocalJSX.ElsaActivityPickerModal & JSXBase.HTMLAttributes<HTMLElsaActivityPickerModalElement>;
@ -514,6 +589,7 @@ declare module "@stencil/core" {
"elsa-context-menu": LocalJSX.ElsaContextMenu & JSXBase.HTMLAttributes<HTMLElsaContextMenuElement>;
"elsa-designer-tree": LocalJSX.ElsaDesignerTree & JSXBase.HTMLAttributes<HTMLElsaDesignerTreeElement>;
"elsa-designer-tree-activity": LocalJSX.ElsaDesignerTreeActivity & JSXBase.HTMLAttributes<HTMLElsaDesignerTreeActivityElement>;
"elsa-dropdown-button": LocalJSX.ElsaDropdownButton & JSXBase.HTMLAttributes<HTMLElsaDropdownButtonElement>;
"elsa-dropdown-property": LocalJSX.ElsaDropdownProperty & JSXBase.HTMLAttributes<HTMLElsaDropdownPropertyElement>;
"elsa-expression-editor": LocalJSX.ElsaExpressionEditor & JSXBase.HTMLAttributes<HTMLElsaExpressionEditorElement>;
"elsa-input-tags": LocalJSX.ElsaInputTags & JSXBase.HTMLAttributes<HTMLElsaInputTagsElement>;
@ -528,8 +604,10 @@ declare module "@stencil/core" {
"elsa-workflow-definition-editor-screen": LocalJSX.ElsaWorkflowDefinitionEditorScreen & JSXBase.HTMLAttributes<HTMLElsaWorkflowDefinitionEditorScreenElement>;
"elsa-workflow-definitions-list-screen": LocalJSX.ElsaWorkflowDefinitionsListScreen & JSXBase.HTMLAttributes<HTMLElsaWorkflowDefinitionsListScreenElement>;
"elsa-workflow-editor-notifications": LocalJSX.ElsaWorkflowEditorNotifications & JSXBase.HTMLAttributes<HTMLElsaWorkflowEditorNotificationsElement>;
"elsa-workflow-instances-list-screen": LocalJSX.ElsaWorkflowInstancesListScreen & JSXBase.HTMLAttributes<HTMLElsaWorkflowInstancesListScreenElement>;
"elsa-workflow-publish-button": LocalJSX.ElsaWorkflowPublishButton & JSXBase.HTMLAttributes<HTMLElsaWorkflowPublishButtonElement>;
"elsa-workflow-settings-modal": LocalJSX.ElsaWorkflowSettingsModal & JSXBase.HTMLAttributes<HTMLElsaWorkflowSettingsModalElement>;
"workflow-instances-list-screen": LocalJSX.WorkflowInstancesListScreen & JSXBase.HTMLAttributes<HTMLWorkflowInstancesListScreenElement>;
}
}
}

View file

@ -9,12 +9,20 @@ import {MenuItem} from "./models";
styleUrl: 'elsa-context-menu.css',
shadow: false,
})
export class AppContextMenu {
export class ElsaContextMenu {
@Prop() history: RouterHistory;
@Prop() menuItems: Array<MenuItem> = [];
navigate: (path: string) => void;
contextMenu: HTMLElement;
componentWillLoad(){
if(!!this.history)
this.navigate = this.history.push;
else
this.navigate = path => document.location.href = path;
}
closeContextMenu() {
leave(this.contextMenu);
}
@ -26,8 +34,9 @@ export class AppContextMenu {
async onMenuItemClick(e: Event, menuItem: MenuItem) {
e.preventDefault();
if (!!menuItem.anchorUrl)
this.history.push(menuItem.anchorUrl);
if (!!menuItem.anchorUrl) {
this.navigate(menuItem.anchorUrl);
}
else if (!!menuItem.clickHandler) {
menuItem.clickHandler(e);
}

View file

@ -0,0 +1,93 @@
import {Component, Event, EventEmitter, h, Prop} from '@stencil/core';
import {leave, toggle} from 'el-transition'
import {registerClickOutside} from "stencil-click-outside";
import {DropdownButtonItem, DropdownButtonOrigin} from "./models";
@Component({
tag: 'elsa-dropdown-button',
styleUrl: 'elsa-dropdown-button.css',
shadow: false,
})
export class ElsaContextMenu {
@Prop() text: string;
@Prop() icon?: any;
@Prop() origin: DropdownButtonOrigin = DropdownButtonOrigin.TopLeft;
@Prop() items: Array<DropdownButtonItem> = [];
@Event() itemSelected: EventEmitter<DropdownButtonItem>
contextMenu: HTMLElement;
closeContextMenu() {
if (!!this.contextMenu)
leave(this.contextMenu);
}
toggleMenu() {
if (!!this.contextMenu)
toggle(this.contextMenu);
}
getOriginClass(): string {
switch (this.origin) {
case DropdownButtonOrigin.TopLeft:
return `left-0 origin-top-left`;
case DropdownButtonOrigin.TopRight:
default:
return 'right-0 origin-top-right';
}
}
async onItemClick(e: Event, menuItem: DropdownButtonItem) {
e.preventDefault();
this.itemSelected.emit(menuItem);
this.closeContextMenu();
}
render() {
return (
<div class="relative" ref={el => registerClickOutside(this, el, this.closeContextMenu)}>
<button onClick={e => this.toggleMenu()} type="button"
class="w-full bg-white border border-gray-300 rounded-md shadow-sm px-4 py-2 inline-flex justify-center text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
aria-haspopup="true" aria-expanded="false">
{this.renderIcon()}
{this.text}
<svg class="ml-2.5 -mr-1.5 h-5 w-5 text-gray-400" x-description="Heroicon name: chevron-down" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"/>
</svg>
</button>
{this.renderMenu()}
</div>
);
}
renderMenu() {
if (this.items.length == 0)
return;
const originClass = this.getOriginClass();
return <div ref={el => this.contextMenu = el} class={`hidden ${originClass} z-10 absolute mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5`}>
<div class="py-1" role="menu" aria-orientation="vertical">
{this.renderItems()}
</div>
</div>;
}
renderItems() {
return this.items.map(item => {
const selectedCssClass = item.isSelected ? "bg-blue-600 hover:bg-blue-700 text-white" : "hover:bg-gray-100 text-gray-700 hover:text-gray-900";
return !!item.url
? <stencil-route-link onClick={e => this.closeContextMenu()} url={item.url} class={`block px-4 py-2 text-sm ${selectedCssClass}`} role="menuitem">{item.text}</stencil-route-link>
: <a href="#" onClick={e => this.onItemClick(e, item)} class={`block px-4 py-2 text-sm ${selectedCssClass}`} role="menuitem">{item.text}</a>;
})
}
renderIcon() {
if (!this.icon)
return;
return this.icon;
}
}

View file

@ -0,0 +1,12 @@
export enum DropdownButtonOrigin
{
TopLeft,
TopRight
}
export interface DropdownButtonItem {
name: string;
text: string;
url?: string;
isSelected?: boolean;
}

View file

@ -52,6 +52,7 @@ export class AppRoot {
<stencil-route url="/workflow-registry" component="app-workflow-registry"/>
<stencil-route url="/workflow-definitions" component="app-workflow-definitions-list" componentProps={{'serverUrl': serverUrl}} exact={true}/>
<stencil-route url="/workflow-definitions/:id" component="app-workflow-definitions-edit" componentProps={{'serverUrl': serverUrl}} />
<stencil-route url="/workflow-instances" component="app-workflow-instances-list" componentProps={{'serverUrl': serverUrl}} exact={true}/>
</stencil-route-switch>
</stencil-router>
{/*</Tunnel.Provider>*/}

View file

@ -1,4 +1,5 @@
import {Component, h, Prop, State} from '@stencil/core';
import {RouterHistory} from "@stencil/router";
@Component({
tag: 'app-workflow-definitions-list',
@ -6,7 +7,7 @@ import {Component, h, Prop, State} from '@stencil/core';
shadow: false,
})
export class AppWorkflowDefinitionsList {
@Prop() history: RouterHistory;
@Prop() serverUrl: string;
render() {
@ -26,7 +27,7 @@ export class AppWorkflowDefinitionsList {
</div>
</div>
<elsa-workflow-definitions-list-screen serverUrl={this.serverUrl} />
<elsa-workflow-definitions-list-screen history={this.history} serverUrl={this.serverUrl} />
</div>
);
}

View file

@ -0,0 +1,28 @@
import {Component, h, Prop, State} from '@stencil/core';
import {RouterHistory} from "@stencil/router";
@Component({
tag: 'app-workflow-instances-list',
styleUrl: 'app-workflow-instances-list.css',
shadow: false,
})
export class AppWorkflowInstancesList {
@Prop() history: RouterHistory;
@Prop() serverUrl: string;
render() {
return (
<div>
<div class="border-b border-gray-200 px-4 py-4 sm:flex sm:items-center sm:justify-between sm:px-6 lg:px-8 bg-white">
<div class="flex-1 min-w-0">
<h1 class="text-lg font-medium leading-6 text-gray-900 sm:truncate">
Workflow Instances
</h1>
</div>
</div>
<elsa-workflow-instances-list-screen history={this.history} serverUrl={this.serverUrl} />
</div>
);
}
}

View file

@ -3,6 +3,7 @@ import * as collection from 'lodash/collection';
import * as object from 'lodash/object';
import {createElsaClient} from "../../../../services/elsa-client";
import {PagedList, VersionOptions, WorkflowDefinitionSummary} from "../../../../models";
import {RouterHistory} from "@stencil/router";
@Component({
tag: 'elsa-workflow-definitions-list-screen',
@ -10,7 +11,7 @@ import {PagedList, VersionOptions, WorkflowDefinitionSummary} from "../../../../
shadow: false,
})
export class ElsaWorkflowDefinitionsListScreen {
@Prop() history?: RouterHistory;
@Prop() serverUrl: string;
@State() workflowDefinitions: PagedList<WorkflowDefinitionSummary> = {items: [], page: 1, pageSize: 50, totalCount: 0};
@ -121,7 +122,7 @@ export class ElsaWorkflowDefinitionsListScreen {
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-right">{latestVersionNumber}</td>
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-right">{publishedVersionNumber}</td>
<td class="pr-6">
<elsa-context-menu menuItems={[
<elsa-context-menu history={this.history} menuItems={[
{text: 'Edit', anchorUrl: editUrl, icon: editIcon},
{text: 'Delete', clickHandler: e => this.onDeleteClick(e, workflowDefinition), icon: deleteIcon}
]}/>

View file

@ -0,0 +1,202 @@
import {Component, h, Prop, State} from '@stencil/core';
import {RouterHistory} from "@stencil/router";
import * as collection from 'lodash/collection';
import * as array from 'lodash/array';
import {createElsaClient} from "../../../../services/elsa-client";
import {OrderBy, PagedList, VersionOptions, WorkflowBlueprintSummary, WorkflowDefinitionSummary, WorkflowStatus} from "../../../../models";
import {DropdownButtonItem, DropdownButtonOrigin} from "../../../controls/elsa-dropdown-button/models";
import {Map} from '../../../../utils/utils';
@Component({
tag: 'elsa-workflow-instances-list-screen',
styleUrl: 'elsa-workflow-instances-list-screen.css',
shadow: false,
})
export class ElsaWorkflowInstancesListScreen {
@Prop() history?: RouterHistory;
@Prop() serverUrl: string;
@State() workflowDefinitions: PagedList<WorkflowDefinitionSummary> = {items: [], page: 1, pageSize: 50, totalCount: 0};
@State() workflowBlueprints: Array<WorkflowBlueprintSummary> = [];
@State() selectedWorkflowId?: string;
@State() selectedWorkflowStatus?: WorkflowStatus;
@State() selectedOrderBy?: OrderBy;
confirmDialog: HTMLElsaConfirmDialogElement;
async componentWillLoad() {
await this.loadWorkflowBlueprints();
}
async loadWorkflowBlueprints() {
const elsaClient = this.createClient();
const page = 0;
const pageSize = 50;
const versionOptions: VersionOptions = {isLatest: true};
const workflowBlueprintPagedList = await elsaClient.workflowRegistryApi.list(page, pageSize, versionOptions);
this.workflowBlueprints = workflowBlueprintPagedList.items;
}
createClient() {
return createElsaClient(this.serverUrl);
}
getLatestWorkflowBlueprintVersions(): Array<WorkflowBlueprintSummary> {
const groups = collection.groupBy(this.workflowBlueprints, 'id');
return collection.map(groups, x => array.first(collection.sortBy(x, 'version', 'desc')));
}
buildFilterUrl(workflowId?: string, workflowStatus?: WorkflowStatus, orderBy?: OrderBy) {
const workflowStatusText = !!workflowStatus ? workflowStatus.toString() : null;
const orderByText = !!orderBy ? orderBy.toString() : null;
const filters: Map<string> = {};
if (!!workflowId)
filters['workflow'] = workflowId;
if (!!workflowStatusText)
filters['status'] = workflowStatusText;
if (!!orderByText)
filters['orderBy'] = orderByText;
const queryString = collection.map(filters, (v, k) => `${k}=${v}`).join('&');
return `/workflow-instances?${queryString}`;
}
render() {
const workflowDefinitions = this.workflowDefinitions.items;
const groupings = collection.groupBy(workflowDefinitions, 'definitionId');
return (
<div>
<div class="p-8 flex content-end justify-right bg-white space-x-4">
<div class="flex-shrink-0">
{this.renderBulkActions()}
</div>
<div class="flex-1">
&nbsp;
</div>
{this.renderWorkflowFilter()}
<elsa-dropdown-button text="@SelectedWorkflowStatusText" items={[]} icon={null} origin={DropdownButtonOrigin.TopRight}/>
<elsa-dropdown-button text="@SelectedOrderByText" items={[]} icon={null} origin={DropdownButtonOrigin.TopRight}/>
</div>
<div class="align-middle inline-block min-w-full border-b border-gray-200">
<table class="min-w-full">
<thead>
<tr class="border-t border-gray-200">
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider"><span class="lg:pl-2">Name</span></th>
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
Instances
</th>
<th class="hidden md:table-cell px-6 py-3 border-b border-gray-200 bg-gray-50 text-right text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
Latest Version
</th>
<th class="hidden md:table-cell px-6 py-3 border-b border-gray-200 bg-gray-50 text-right text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
Published Version
</th>
<th class="pr-6 py-3 border-b border-gray-200 bg-gray-50 text-right text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider"/>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-100">
{collection.map(groupings, group => {
const versions = collection.orderBy(group, 'version', 'desc');
const workflowDefinition = versions[0]
const latestVersionNumber = workflowDefinition.version;
const publishedVersion: WorkflowDefinitionSummary = versions.find(x => x.isPublished);
const publishedVersionNumber = !!publishedVersion ? publishedVersion.version : '-';
let workflowDisplayName = workflowDefinition.displayName;
if (!workflowDisplayName || workflowDisplayName.trim().length == 0)
workflowDisplayName = workflowDefinition.name;
if (!workflowDisplayName || workflowDisplayName.trim().length == 0)
workflowDisplayName = 'Untitled';
const editUrl = `/workflow-definitions/${workflowDefinition.definitionId}`;
const editIcon = (
<svg class="h-5 w-5 text-gray-500" width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
);
const deleteIcon = (
<svg class="h-5 w-5 text-gray-500" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z"/>
<line x1="4" y1="7" x2="20" y2="7"/>
<line x1="10" y1="11" x2="10" y2="17"/>
<line x1="14" y1="11" x2="14" y2="17"/>
<path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12"/>
<path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3"/>
</svg>
);
return (
<tr>
<td class="px-6 py-3 whitespace-no-wrap text-sm leading-5 font-medium text-gray-900">
<div class="flex items-center space-x-3 lg:pl-2">
<stencil-route-link url={editUrl} anchorClass="truncate hover:text-gray-600"><span>{workflowDisplayName}</span></stencil-route-link>
</div>
</td>
<td class="px-6 py-3 text-sm leading-5 text-gray-500 font-medium">
<div class="flex items-center space-x-2">
<div class="flex items-center space-x-2">
<div class="flex flex-shrink-0 -space-x-1"><a class="max-w-none h-9 w-9 rounded-full text-white shadow-solid p-2 text-xs bg-blue-500 hover:bg-blue-400" href="#">4</a>
<a class="max-w-none h-9 w-9 rounded-full text-white shadow-solid p-2 text-xs bg-green-500 hover:bg-green-400" href="#">999</a>
<a class="max-w-none h-9 w-9 rounded-full text-white shadow-solid p-2 text-xs bg-red-500 hover:bg-red-400" href="#">75</a></div>
</div>
</div>
</td>
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-right">{latestVersionNumber}</td>
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-right">{publishedVersionNumber}</td>
<td class="pr-6">
<elsa-context-menu history={this.history} menuItems={[
{text: 'Edit', anchorUrl: editUrl, icon: editIcon}
]}/>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
<elsa-confirm-dialog ref={el => this.confirmDialog = el}/>
</div>
);
}
renderBulkActions() {
const bulkActionIcon = <svg class="mr-3 h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M13 10V3L4 14h7v7l9-11h-7z"/>
</svg>;
const actions: Array<DropdownButtonItem> = [{
text: 'Delete',
name: 'Delete'
}];
return <elsa-dropdown-button text="Bulk Actions" items={actions} icon={bulkActionIcon} origin={DropdownButtonOrigin.TopLeft}/>
}
renderWorkflowFilter() {
const latestWorkflowBlueprints = this.getLatestWorkflowBlueprintVersions();
const selectedWorkflowId = this.selectedWorkflowId;
const selectedWorkflow = latestWorkflowBlueprints.find(x => x.id == selectedWorkflowId);
const selectedWorkflowText = !selectedWorkflowId ? "Workflow" : !!selectedWorkflow ? (selectedWorkflow.displayName || selectedWorkflow.name) : "Workflow";
const selectedWorkflowStatus = this.selectedWorkflowStatus;
const SelectedOrderBy = this.selectedOrderBy;
let items: Array<DropdownButtonItem> = latestWorkflowBlueprints.map(x => ({text: x.displayName!, name: x.id, url: this.buildFilterUrl(x.id, selectedWorkflowStatus, SelectedOrderBy), isSelected: x.id == selectedWorkflowId}));
items = [{text: 'All', name: 'All', url: this.buildFilterUrl(null, selectedWorkflowStatus, SelectedOrderBy), isSelected: !selectedWorkflowId}, ...items];
return <elsa-dropdown-button text={selectedWorkflowText} items={items} icon={null} origin={DropdownButtonOrigin.TopRight}/>
}
}

File diff suppressed because it is too large Load diff

View file

@ -16,7 +16,7 @@
<!--<elsa-workflow-editor id="workflowEditor" server-url="https://localhost:11000/" workflow-definition-id="7eed1333c2674370a1e9b300aa94fda2" monaco-lib-path="build/assets/js"></elsa-workflow-editor>-->
<app-root server-url="https://localhost:6080/"></app-root>
<app-root server-url="https://localhost:11000/"></app-root>
</body>
</html>

View file

@ -1,153 +1,180 @@
import {Map} from '../utils/utils';
export interface WorkflowDefinition {
id?: string;
definitionId?: string;
tenantId?: string;
name?: string;
displayName?: string;
description?: string;
version: number;
variables?: Variables;
customAttributes?: Variables;
contextOptions?: WorkflowContextOptions;
isSingleton?: boolean;
persistenceBehavior?: WorkflowPersistenceBehavior;
deleteCompletedInstances?: boolean;
isPublished?: boolean;
isLatest?: boolean;
activities: Array<ActivityDefinition>;
connections: Array<ConnectionDefinition>;
id?: string;
definitionId?: string;
tenantId?: string;
name?: string;
displayName?: string;
description?: string;
version: number;
variables?: Variables;
customAttributes?: Variables;
contextOptions?: WorkflowContextOptions;
isSingleton?: boolean;
persistenceBehavior?: WorkflowPersistenceBehavior;
deleteCompletedInstances?: boolean;
isPublished?: boolean;
isLatest?: boolean;
activities: Array<ActivityDefinition>;
connections: Array<ConnectionDefinition>;
}
export interface WorkflowDefinitionSummary {
id?: string;
definitionId?: string;
tenantId?: string;
name?: string;
displayName?: string;
description?: string;
version: number;
isSingleton?: boolean;
persistenceBehavior?: WorkflowPersistenceBehavior;
isPublished?: boolean;
isLatest?: boolean;
id?: string;
definitionId?: string;
tenantId?: string;
name?: string;
displayName?: string;
description?: string;
version: number;
isSingleton?: boolean;
persistenceBehavior?: WorkflowPersistenceBehavior;
isPublished?: boolean;
isLatest?: boolean;
}
export interface WorkflowBlueprintSummary {
id: string;
name?: string;
displayName?: string;
description?: string;
version: number;
tenantId?: string;
isSingleton: boolean;
isPublished: boolean;
isLatest: boolean;
}
export interface ActivityDefinition {
activityId: string;
type: string;
name: string;
displayName: string;
description: string;
persistWorkflow: boolean;
loadWorkflowContext: boolean;
saveWorkflowContext: boolean;
persistOutput: boolean;
properties: Array<ActivityDefinitionProperty>;
activityId: string;
type: string;
name: string;
displayName: string;
description: string;
persistWorkflow: boolean;
loadWorkflowContext: boolean;
saveWorkflowContext: boolean;
persistOutput: boolean;
properties: Array<ActivityDefinitionProperty>;
}
export interface ConnectionDefinition {
sourceActivityId?: string;
targetActivityId?: string;
outcome?: string;
sourceActivityId?: string;
targetActivityId?: string;
outcome?: string;
}
export interface ActivityDefinitionProperty {
name: string;
syntax?: string;
expressions: Map<string>;
name: string;
syntax?: string;
expressions: Map<string>;
}
export interface Variables {
data: Map<any>;
data: Map<any>;
}
export interface WorkflowContextOptions {
contextType: string;
contextFidelity: WorkflowContextFidelity;
contextType: string;
contextFidelity: WorkflowContextFidelity;
}
export enum WorkflowContextFidelity {
Burst,
Activity
Burst,
Activity
}
export enum WorkflowPersistenceBehavior {
Suspended,
WorkflowBurst,
WorkflowPassCompleted,
ActivityExecuted
Suspended,
WorkflowBurst,
WorkflowPassCompleted,
ActivityExecuted
}
export interface VersionOptions {
isLatest?: boolean;
isLatestOrPublished?: boolean;
isPublished?: boolean;
isDraft?: boolean;
allVersions?: boolean;
version?: number;
isLatest?: boolean;
isLatestOrPublished?: boolean;
isPublished?: boolean;
isDraft?: boolean;
allVersions?: boolean;
version?: number;
}
export enum WorkflowStatus {
Idle,
Running,
Finished,
Suspended,
Faulted,
Cancelled
}
export enum OrderBy {
Started,
LastExecuted,
Finished
}
export const getVersionOptionsString = (versionOptions?: VersionOptions) => {
if(!versionOptions)
return '';
if (!versionOptions)
return '';
return versionOptions.allVersions
? 'AllVersions'
: versionOptions.isDraft
? 'Draft'
: versionOptions.isLatest
? 'Latest'
: versionOptions.isPublished
? 'Published'
: versionOptions.isLatestOrPublished
? 'LatestOrPublished'
: versionOptions.version.toString();
return versionOptions.allVersions
? 'AllVersions'
: versionOptions.isDraft
? 'Draft'
: versionOptions.isLatest
? 'Latest'
: versionOptions.isPublished
? 'Published'
: versionOptions.isLatestOrPublished
? 'LatestOrPublished'
: versionOptions.version.toString();
};
export interface ActivityDescriptor {
type: string;
displayName: string;
description?: string;
category: string;
traits: ActivityTraits;
outcomes: Array<string>;
browsable: boolean;
properties: Array<ActivityPropertyDescriptor>;
type: string;
displayName: string;
description?: string;
category: string;
traits: ActivityTraits;
outcomes: Array<string>;
browsable: boolean;
properties: Array<ActivityPropertyDescriptor>;
}
export interface ActivityPropertyDescriptor {
name: string;
uiHint: string;
label?: string;
hint?: string;
options?: any;
category?: string;
defaultValue?: any;
defaultSyntax?: string;
supportedSyntaxes: Array<string>
name: string;
uiHint: string;
label?: string;
hint?: string;
options?: any;
category?: string;
defaultValue?: any;
defaultSyntax?: string;
supportedSyntaxes: Array<string>
}
export interface PagedList<T> {
items: Array<T>;
page: number;
pageSize: number;
totalCount: number;
items: Array<T>;
page: number;
pageSize: number;
totalCount: number;
}
export enum ActivityTraits {
Action = 1,
Trigger = 2,
Job = 4
Action = 1,
Trigger = 2,
Job = 4
}
export class SyntaxNames {
static readonly Literal = 'Literal';
static readonly JavaScript = 'JavaScript';
static readonly Liquid = 'Liquid';
static readonly Json = 'Json';
static Variable = 'Variable';
static Output = 'Output';
static readonly Literal = 'Literal';
static readonly JavaScript = 'JavaScript';
static readonly Liquid = 'Liquid';
static readonly Json = 'Json';
static Variable = 'Variable';
static Output = 'Output';
}

View file

@ -6,7 +6,7 @@ import {
getVersionOptionsString,
PagedList,
Variables,
VersionOptions,
VersionOptions, WorkflowBlueprintSummary,
WorkflowContextOptions,
WorkflowDefinition,
WorkflowDefinitionSummary,
@ -75,6 +75,13 @@ export const createElsaClient = function (serverUrl: string): ElsaClient {
return response.data;
}
},
workflowRegistryApi: {
list: async (page?: number, pageSize?: number, versionOptions?: VersionOptions): Promise<PagedList<WorkflowBlueprintSummary>> => {
const versionOptionsString = getVersionOptionsString(versionOptions);
const response = await httpClient.get<PagedList<WorkflowBlueprintSummary>>(`v1/workflow-registry?version=${versionOptionsString}`);
return response.data;
}
},
scriptingApi: {
getJavaScriptTypeDefinitions: async (workflowDefinitionId: string, context?: string): Promise<string> => {
context = context || '';
@ -88,6 +95,7 @@ export const createElsaClient = function (serverUrl: string): ElsaClient {
export interface ElsaClient {
activitiesApi: ActivitiesApi;
workflowDefinitionsApi: WorkflowDefinitionsApi;
workflowRegistryApi: WorkflowRegistryApi;
scriptingApi: ScriptingApi;
}
@ -111,6 +119,10 @@ export interface WorkflowDefinitionsApi {
import(workflowDefinitionId: string, file: File): Promise<WorkflowDefinition>
}
export interface WorkflowRegistryApi {
list(page?: number, pageSize?: number, versionOptions?: VersionOptions): Promise<PagedList<WorkflowBlueprintSummary>>
}
export interface ScriptingApi {
getJavaScriptTypeDefinitions(workflowDefinitionId: string, context?: string): Promise<string>
}