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`}>
+ return
this.contextMenu = el}
+ data-transition-enter="transition ease-out duration-100"
+ data-transition-enter-start="transform opacity-0 scale-95"
+ data-transition-leave="transition ease-in duration-75"
+ data-transition-leave-start="transform opacity-100 scale-100"
+ data-transition-leave-end="transform opacity-0 scale-95"
+ class={`hidden ${originClass} z-10 absolute mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5`}>
{this.renderItems()}
@@ -79,7 +85,7 @@ export class ElsaContextMenu {
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
- ?
this.closeContextMenu()} url={item.url} class={`block px-4 py-2 text-sm ${selectedCssClass}`} role="menuitem">{item.text}
+ ?
this.closeContextMenu()} url={item.url} anchorClass={`block px-4 py-2 text-sm ${selectedCssClass} cursor-pointer`} role="menuitem">{item.text}
:
this.onItemClick(e, item)} class={`block px-4 py-2 text-sm ${selectedCssClass}`} role="menuitem">{item.text};
})
}
diff --git a/src/designer/elsa-workflows-studio/src/components/controls/elsa-dropdown-button/models.tsx b/src/designer/elsa-workflows-studio/src/components/controls/elsa-dropdown-button/models.tsx
index 496154f2d..a68023b6a 100644
--- a/src/designer/elsa-workflows-studio/src/components/controls/elsa-dropdown-button/models.tsx
+++ b/src/designer/elsa-workflows-studio/src/components/controls/elsa-dropdown-button/models.tsx
@@ -5,7 +5,8 @@
}
export interface DropdownButtonItem {
- name: string;
+ name?: string;
+ value?: any;
text: string;
url?: string;
isSelected?: boolean;
diff --git a/src/designer/elsa-workflows-studio/src/components/screens/workflow-instances-list/elsa-workflow-instances-list-screen/elsa-workflow-instances-list-screen.tsx b/src/designer/elsa-workflows-studio/src/components/screens/workflow-instances-list/elsa-workflow-instances-list-screen/elsa-workflow-instances-list-screen.tsx
index c1bfda93f..1e0ae6087 100644
--- a/src/designer/elsa-workflows-studio/src/components/screens/workflow-instances-list/elsa-workflow-instances-list-screen/elsa-workflow-instances-list-screen.tsx
+++ b/src/designer/elsa-workflows-studio/src/components/screens/workflow-instances-list/elsa-workflow-instances-list-screen/elsa-workflow-instances-list-screen.tsx
@@ -1,11 +1,12 @@
import {Component, h, Prop, State} from '@stencil/core';
-import {RouterHistory} from "@stencil/router";
+import {LocationSegments, 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 {OrderBy, PagedList, VersionOptions, WorkflowBlueprintSummary, WorkflowInstanceSummary, WorkflowStatus} from "../../../../models";
import {DropdownButtonItem, DropdownButtonOrigin} from "../../../controls/elsa-dropdown-button/models";
-import {Map} from '../../../../utils/utils';
+import {Map, parseQuery} from '../../../../utils/utils';
+import {parseQueryString} from "@stencil/router/dist/types/utils/path-utils";
@Component({
tag: 'elsa-workflow-instances-list-screen',
@@ -15,27 +16,53 @@ import {Map} from '../../../../utils/utils';
export class ElsaWorkflowInstancesListScreen {
@Prop() history?: RouterHistory;
@Prop() serverUrl: string;
- @State() workflowDefinitions: PagedList
= {items: [], page: 1, pageSize: 50, totalCount: 0};
@State() workflowBlueprints: Array = [];
+ @State() workflowInstances: PagedList = {items: [], page: 1, pageSize: 50, totalCount: 0};
@State() selectedWorkflowId?: string;
@State() selectedWorkflowStatus?: WorkflowStatus;
@State() selectedOrderBy?: OrderBy;
+ @State() selectedWorkflowInstanceIds: Array = [];
confirmDialog: HTMLElsaConfirmDialogElement;
async componentWillLoad() {
+ this.history.listen(e => this.routeChanged(e));
+ this.applyQueryString(this.history.location.search);
+
await this.loadWorkflowBlueprints();
+ await this.loadWorkflowInstances();
+ }
+
+ async routeChanged(e: LocationSegments) {
+ if(!e.pathname.toLowerCase().indexOf('workflow-instances'))
+ return;
+
+ this.applyQueryString(e.search);
+ await this.loadWorkflowInstances();
+ }
+
+ applyQueryString(queryString?: string){
+ const query = parseQuery(queryString);
+
+ this.selectedWorkflowId = !!query.workflow ? query.workflow : null;
+ this.selectedWorkflowStatus = !!query.status ? this.parseStatusText(query.status) : null;
}
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);
+ const workflowBlueprintPagedList = await elsaClient.workflowRegistryApi.list(null, null, versionOptions);
this.workflowBlueprints = workflowBlueprintPagedList.items;
}
+ async loadWorkflowInstances() {
+ const elsaClient = this.createClient();
+ const page = 1;
+ const pageSize = 25;
+
+ this.workflowInstances = await elsaClient.workflowInstancesApi.list(page, pageSize, this.selectedWorkflowId, this.selectedWorkflowStatus, this.selectedOrderBy);
+ }
+
createClient() {
return createElsaClient(this.serverUrl);
}
@@ -46,7 +73,7 @@ export class ElsaWorkflowInstancesListScreen {
}
buildFilterUrl(workflowId?: string, workflowStatus?: WorkflowStatus, orderBy?: OrderBy) {
- const workflowStatusText = !!workflowStatus ? workflowStatus.toString() : null;
+ const workflowStatusText = workflowStatus != null ? this.getStatusText(workflowStatus) : null;
const orderByText = !!orderBy ? orderBy.toString() : null;
const filters: Map = {};
@@ -64,14 +91,69 @@ export class ElsaWorkflowInstancesListScreen {
return `/workflow-instances?${queryString}`;
}
- render() {
- const workflowDefinitions = this.workflowDefinitions.items;
- const groupings = collection.groupBy(workflowDefinitions, 'definitionId');
+ getStatusColor(status: WorkflowStatus) {
+ switch (status) {
+ default:
+ case WorkflowStatus.Idle:
+ return "gray";
+ case WorkflowStatus.Running:
+ return "pink";
+ case WorkflowStatus.Suspended:
+ return "blue";
+ case WorkflowStatus.Finished:
+ return "green";
+ case WorkflowStatus.Faulted:
+ return "red";
+ case WorkflowStatus.Cancelled:
+ return "yellow";
+ }
+ }
+
+ getStatusText(status: WorkflowStatus){
+ switch (status) {
+ default:
+ case WorkflowStatus.Idle:
+ return "Idle";
+ case WorkflowStatus.Running:
+ return "Running";
+ case WorkflowStatus.Suspended:
+ return "Suspended";
+ case WorkflowStatus.Finished:
+ return "Finished";
+ case WorkflowStatus.Faulted:
+ return "Faulted";
+ case WorkflowStatus.Cancelled:
+ return "Cancelled";
+ }
+ }
+ parseStatusText(status?: string): WorkflowStatus {
+ if(!status)
+ return null;
+
+ switch (status.toLowerCase()) {
+ default:
+ case 'idle':
+ return WorkflowStatus.Idle;
+ case 'running':
+ return WorkflowStatus.Running;
+ case 'suspended':
+ return WorkflowStatus.Suspended;
+ case 'finished':
+ return WorkflowStatus.Finished;
+ case 'faulted':
+ return WorkflowStatus.Faulted;
+ case 'cancelled':
+ return WorkflowStatus.Cancelled;
+ }
+ }
+
+ render() {
+ const workflowInstances = this.workflowInstances.items;
+ const workflowBlueprints = this.workflowBlueprints;
return (
-
{this.renderBulkActions()}
@@ -80,95 +162,105 @@ export class ElsaWorkflowInstancesListScreen {
{this.renderWorkflowFilter()}
-
+ {this.renderStatusFilter()}
+
+
+
+
+
+
+ |
+
+ |
+
+ ID
+ |
+
+ Workflow
+ |
+
+ Version
+ |
+
+ Instance Name
+ |
+
+ Status
+ |
+
+ Created
+ |
+
+ Finished
+ |
+
+ Last Executed
+ |
+
+ Faulted
+ |
+ |
+
+
+
+ {workflowInstances.map(workflowInstance => {
+ const workflowBlueprint = workflowBlueprints.find(x => x.id == workflowInstance.definitionId && x.version == workflowInstance.version) ?? {displayName: '(Workflow definition not found)'};
+ const displayName = workflowBlueprint.displayName;
+ const statusColor = this.getStatusColor(workflowInstance.workflowStatus);
+ const viewUrl = `/workflow-instances/${workflowInstance.id}/viewer`;
+ const instanceName = !workflowInstance.name ? '' : workflowInstance.name;
+ const isSelected = this.selectedWorkflowInstanceIds.findIndex(x => x === workflowInstance.id) >= 0;
+ //var displayContext = WorkflowInstanceDisplayContexts[workflowInstance];
-
-
-
-
- | Name |
-
- Instances
- |
-
- Latest Version
- |
-
- Published Version
- |
- |
-
-
-
- {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 = (
-
- );
-
- const deleteIcon = (
-
- );
-
- return (
-
+ return
|
+
+ |
+
+ {workflowInstance.id}
+ |
+
+
+ {displayName}
+
+ |
+
+ {workflowInstance.version}
+ |
+
+ {instanceName}
+ |
+
- {workflowDisplayName}
+
+ {workflowInstance.workflowStatus}
|
-
-
-
+ |
+ {workflowInstance.createdAt}
+ |
+
+ {!!workflowInstance.finishedAt ? workflowInstance.finishedAt.toString() : '-'}
+ |
+
+ {!!workflowInstance.lastExecutedAt ? workflowInstance.lastExecutedAt.toString() : '-'}
+ |
+
+ {!!workflowInstance.faultedAt ? workflowInstance.faultedAt.toString() : '-'}
|
- {latestVersionNumber} |
- {publishedVersionNumber} |
-
+
|
- );
- })}
-
-
+ })}
+
+
+
+
+
this.confirmDialog = el}/>
-
-
this.confirmDialog = el}/>
);
}
@@ -193,10 +285,23 @@ export class ElsaWorkflowInstancesListScreen {
const selectedWorkflowText = !selectedWorkflowId ? "Workflow" : !!selectedWorkflow ? (selectedWorkflow.displayName || selectedWorkflow.name) : "Workflow";
const selectedWorkflowStatus = this.selectedWorkflowStatus;
const SelectedOrderBy = this.selectedOrderBy;
- let items: Array = latestWorkflowBlueprints.map(x => ({text: x.displayName!, name: x.id, url: this.buildFilterUrl(x.id, selectedWorkflowStatus, SelectedOrderBy), isSelected: x.id == selectedWorkflowId}));
+ let items: Array = latestWorkflowBlueprints.map(x => ({text: x.displayName!, value: 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
+ items = [{text: 'All', value: null, url: this.buildFilterUrl(null, selectedWorkflowStatus, SelectedOrderBy), isSelected: !selectedWorkflowId}, ...items];
+
+ return this.selectedWorkflowId = e.detail.value as string}/>
+ }
+
+ renderStatusFilter(){
+ const selectedWorkflowStatus = this.selectedWorkflowStatus;
+ const selectedWorkflowStatusText = selectedWorkflowStatus != null ? this.getStatusText(selectedWorkflowStatus) : 'Status';
+ const statuses: Array = [null, WorkflowStatus.Running, WorkflowStatus.Suspended, WorkflowStatus.Finished, WorkflowStatus.Faulted, WorkflowStatus.Cancelled, WorkflowStatus.Idle];
+ const items: Array = statuses.map(x => {
+ const enumText = x != null ? this.getStatusText(x) : null;
+ const text = !!enumText ? enumText : 'All';
+ return ({text: text, url: this.buildFilterUrl(this.selectedWorkflowId, x, this.selectedOrderBy), isSelected: x == selectedWorkflowStatus});
+ });
+
+ return
}
}
diff --git a/src/designer/elsa-workflows-studio/src/models/domain.ts b/src/designer/elsa-workflows-studio/src/models/domain.ts
index 59cf118bc..b1a96379b 100644
--- a/src/designer/elsa-workflows-studio/src/models/domain.ts
+++ b/src/designer/elsa-workflows-studio/src/models/domain.ts
@@ -46,6 +46,24 @@ export interface WorkflowBlueprintSummary {
isLatest: boolean;
}
+export interface WorkflowInstanceSummary
+{
+ id: string;
+ definitionId: string;
+ tenantId?: string;
+ version: number;
+ workflowStatus: WorkflowStatus;
+ correlationId?: string;
+ contextType?: string;
+ contextId?: string;
+ name?: string;
+ createdAt?: Date
+ lastExecutedAt?: Date;
+ finishedAt?: Date;
+ cancelledAt?: Date;
+ faultedAt?: Date;
+}
+
export interface ActivityDefinition {
activityId: string;
type: string;
diff --git a/src/designer/elsa-workflows-studio/src/services/elsa-client.ts b/src/designer/elsa-workflows-studio/src/services/elsa-client.ts
index 577a2b063..74281f6a3 100644
--- a/src/designer/elsa-workflows-studio/src/services/elsa-client.ts
+++ b/src/designer/elsa-workflows-studio/src/services/elsa-client.ts
@@ -1,148 +1,176 @@
import axios, {AxiosRequestConfig} from "axios";
+import * as collection from 'lodash/collection';
import {
- ActivityDefinition,
- ActivityDescriptor,
- ConnectionDefinition,
- getVersionOptionsString,
- PagedList,
- Variables,
- VersionOptions, WorkflowBlueprintSummary,
- WorkflowContextOptions,
- WorkflowDefinition,
- WorkflowDefinitionSummary,
- WorkflowPersistenceBehavior
+ ActivityDefinition,
+ ActivityDescriptor,
+ ConnectionDefinition,
+ getVersionOptionsString, OrderBy,
+ PagedList,
+ Variables,
+ VersionOptions, WorkflowBlueprintSummary,
+ WorkflowContextOptions,
+ WorkflowDefinition,
+ WorkflowDefinitionSummary, WorkflowInstanceSummary,
+ WorkflowPersistenceBehavior, WorkflowStatus
} from "../models";
export const createElsaClient = function (serverUrl: string): ElsaClient {
- const config: AxiosRequestConfig = {
- baseURL: serverUrl
- };
+ const config: AxiosRequestConfig = {
+ baseURL: serverUrl
+ };
- const httpClient = axios.create(config);
+ const httpClient = axios.create(config);
- return {
- activitiesApi: {
- list: async () => {
- const response = await httpClient.get>('v1/activities');
- return response.data;
- }
- },
- workflowDefinitionsApi: {
- list: async (page?: number, pageSize?: number, versionOptions?: VersionOptions) => {
- const versionOptionsString = getVersionOptionsString(versionOptions);
- const response = await httpClient.get>(`v1/workflow-definitions?version=${versionOptionsString}`);
- return response.data;
- },
- getByDefinitionAndVersion: async (definitionId: string, versionOptions: VersionOptions) => {
- const versionOptionsString = getVersionOptionsString(versionOptions);
- const response = await httpClient.get(`v1/workflow-definitions/${definitionId}/${versionOptionsString}`);
- return response.data;
- },
- save: async request => {
- const response = await httpClient.post('v1/workflow-definitions', request);
- return response.data;
- },
- delete: async definitionId => {
- await httpClient.delete(`v1/workflow-definitions/${definitionId}`);
- },
- retract: async workflowDefinitionId => {
- const response = await httpClient.post(`v1/workflow-definitions/${workflowDefinitionId}/retract`);
- return response.data;
- },
- export: async (workflowDefinitionId, versionOptions): Promise => {
- const versionOptionsString = getVersionOptionsString(versionOptions);
- const response = await httpClient.post(`v1/workflow-definitions/${workflowDefinitionId}/${versionOptionsString}/export`, null, {
- responseType: 'blob'
- });
+ return {
+ activitiesApi: {
+ list: async () => {
+ const response = await httpClient.get>('v1/activities');
+ return response.data;
+ }
+ },
+ workflowDefinitionsApi: {
+ list: async (page?: number, pageSize?: number, versionOptions?: VersionOptions) => {
+ const versionOptionsString = getVersionOptionsString(versionOptions);
+ const response = await httpClient.get>(`v1/workflow-definitions?version=${versionOptionsString}`);
+ return response.data;
+ },
+ getByDefinitionAndVersion: async (definitionId: string, versionOptions: VersionOptions) => {
+ const versionOptionsString = getVersionOptionsString(versionOptions);
+ const response = await httpClient.get(`v1/workflow-definitions/${definitionId}/${versionOptionsString}`);
+ return response.data;
+ },
+ save: async request => {
+ const response = await httpClient.post('v1/workflow-definitions', request);
+ return response.data;
+ },
+ delete: async definitionId => {
+ await httpClient.delete(`v1/workflow-definitions/${definitionId}`);
+ },
+ retract: async workflowDefinitionId => {
+ const response = await httpClient.post(`v1/workflow-definitions/${workflowDefinitionId}/retract`);
+ return response.data;
+ },
+ export: async (workflowDefinitionId, versionOptions): Promise => {
+ 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;
+ 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
- };
- },
- import: async (workflowDefinitionId, file: File): Promise => {
- const formData = new FormData();
- formData.append("file", file);
- const response = await httpClient.post(`v1/workflow-definitions/${workflowDefinitionId}/import`, formData, {
- headers: {
- 'Content-Type': 'multipart/form-data'
- }
- });
- return response.data;
- }
- },
- workflowRegistryApi: {
- list: async (page?: number, pageSize?: number, versionOptions?: VersionOptions): Promise> => {
- const versionOptionsString = getVersionOptionsString(versionOptions);
- const response = await httpClient.get>(`v1/workflow-registry?version=${versionOptionsString}`);
- return response.data;
- }
- },
- scriptingApi: {
- getJavaScriptTypeDefinitions: async (workflowDefinitionId: string, context?: string): Promise => {
- context = context || '';
- const response = await httpClient.get(`v1/scripting/javascript/type-definitions/${workflowDefinitionId}?t=${new Date().getTime()}&context=${context}`);
- return response.data;
- }
+ return {
+ fileName: fileName,
+ data: data
+ };
+ },
+ import: async (workflowDefinitionId, file: File): Promise => {
+ const formData = new FormData();
+ formData.append("file", file);
+ const response = await httpClient.post(`v1/workflow-definitions/${workflowDefinitionId}/import`, formData, {
+ headers: {
+ 'Content-Type': 'multipart/form-data'
+ }
+ });
+ return response.data;
+ }
+ },
+ workflowRegistryApi: {
+ list: async (page?: number, pageSize?: number, versionOptions?: VersionOptions): Promise> => {
+ const versionOptionsString = getVersionOptionsString(versionOptions);
+ const response = await httpClient.get>(`v1/workflow-registry?version=${versionOptionsString}`);
+ return response.data;
+ }
+ },
+ workflowInstancesApi: {
+ list: async (page?: number, pageSize?: number, workflowDefinitionId?: string, workflowStatus?: WorkflowStatus, orderBy?: OrderBy, searchTerm?: string): Promise> => {
+ const queryString = {};
+
+ if(!!workflowDefinitionId)
+ queryString['workflow'] = workflowDefinitionId;
+
+ if(workflowStatus != null)
+ queryString['status'] = workflowStatus;
+
+ if(!!orderBy)
+ queryString['orderBy'] = orderBy;
+
+ if(!!searchTerm)
+ queryString['searchTerm'] = searchTerm;
+
+ const queryStringItems = collection.map(queryString, (v, k) => `${k}=${v}`);
+ const queryStringText = queryStringItems.length > 0 ? `?${queryStringItems.join('&')}` : '';
+ const response = await httpClient.get>(`v1/workflow-instances${queryStringText}`);
+ return response.data;
+ }
+ },
+ scriptingApi: {
+ getJavaScriptTypeDefinitions: async (workflowDefinitionId: string, context?: string): Promise => {
+ context = context || '';
+ const response = await httpClient.get(`v1/scripting/javascript/type-definitions/${workflowDefinitionId}?t=${new Date().getTime()}&context=${context}`);
+ return response.data;
+ }
+ }
}
- }
}
export interface ElsaClient {
- activitiesApi: ActivitiesApi;
- workflowDefinitionsApi: WorkflowDefinitionsApi;
- workflowRegistryApi: WorkflowRegistryApi;
- scriptingApi: ScriptingApi;
+ activitiesApi: ActivitiesApi;
+ workflowDefinitionsApi: WorkflowDefinitionsApi;
+ workflowRegistryApi: WorkflowRegistryApi;
+ workflowInstancesApi: WorkflowInstancesApi;
+ scriptingApi: ScriptingApi;
}
export interface ActivitiesApi {
- list(): Promise>;
+ list(): Promise>;
}
export interface WorkflowDefinitionsApi {
- getByDefinitionAndVersion(definitionId: string, versionOptions: VersionOptions): Promise
+ getByDefinitionAndVersion(definitionId: string, versionOptions: VersionOptions): Promise
- list(page?: number, pageSize?: number, versionOptions?: VersionOptions): Promise>
+ list(page?: number, pageSize?: number, versionOptions?: VersionOptions): Promise>
- save(request: SaveWorkflowDefinitionRequest): Promise
+ save(request: SaveWorkflowDefinitionRequest): Promise
- delete(definitionId: string): Promise
+ delete(definitionId: string): Promise
- retract(workflowDefinitionId: string): Promise
+ retract(workflowDefinitionId: string): Promise
- export(workflowDefinitionId: string, versionOptions: VersionOptions): Promise
+ export(workflowDefinitionId: string, versionOptions: VersionOptions): Promise
- import(workflowDefinitionId: string, file: File): Promise
+ import(workflowDefinitionId: string, file: File): Promise
}
export interface WorkflowRegistryApi {
- list(page?: number, pageSize?: number, versionOptions?: VersionOptions): Promise>
+ list(page?: number, pageSize?: number, versionOptions?: VersionOptions): Promise>
+}
+
+export interface WorkflowInstancesApi {
+ list(page?: number, pageSize?: number, workflowDefinitionId?: string, workflowStatus?: WorkflowStatus, orderBy?: OrderBy, searchTerm?: string): Promise>
}
export interface ScriptingApi {
- getJavaScriptTypeDefinitions(workflowDefinitionId: string, context?: string): Promise
+ getJavaScriptTypeDefinitions(workflowDefinitionId: string, context?: string): Promise
}
export interface SaveWorkflowDefinitionRequest {
- workflowDefinitionId?: string
- name?: string
- displayName?: string
- description?: string
- variables?: Variables
- contextOptions?: WorkflowContextOptions
- isSingleton?: boolean
- persistenceBehavior?: WorkflowPersistenceBehavior
- deleteCompletedInstances?: boolean
- publish?: boolean
- activities: Array
- connections: Array
+ workflowDefinitionId?: string
+ name?: string
+ displayName?: string
+ description?: string
+ variables?: Variables
+ contextOptions?: WorkflowContextOptions
+ isSingleton?: boolean
+ persistenceBehavior?: WorkflowPersistenceBehavior
+ deleteCompletedInstances?: boolean
+ publish?: boolean
+ activities: Array
+ connections: Array
}
export interface ExportWorkflowResponse {
- fileName: string;
- data: Blob;
+ fileName: string;
+ data: Blob;
}
diff --git a/src/designer/elsa-workflows-studio/src/utils/utils.ts b/src/designer/elsa-workflows-studio/src/utils/utils.ts
index c6e39efcc..d7dc7f93c 100644
--- a/src/designer/elsa-workflows-studio/src/utils/utils.ts
+++ b/src/designer/elsa-workflows-studio/src/utils/utils.ts
@@ -1,201 +1,215 @@
import {ActivityDefinition, ActivityDefinitionProperty, ActivityModel, ConnectionModel, WorkflowModel} from "../models";
declare global {
- interface Array {
- distinct(): Array;
+ interface Array {
+ distinct(): Array;
- last(): T;
- }
+ last(): T;
+ }
}
export type Map = {
- [key: string]: T
+ [key: string]: T
};
export function format(first: string, middle: string, last: string): string {
- return (first || '') + (middle ? ` ${middle}` : '') + (last ? ` ${last}` : '');
+ return (first || '') + (middle ? ` ${middle}` : '') + (last ? ` ${last}` : '');
}
export interface Array {
- distinct(): Array;
+ distinct(): Array;
- last(): T;
+ last(): T;
- find(predicate: (this: void, value: T, index: number, obj: T[]) => value is S, thisArg?: any): S | undefined;
+ find(predicate: (this: void, value: T, index: number, obj: T[]) => value is S, thisArg?: any): S | undefined;
- find(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): T | undefined;
+ find(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): T | undefined;
- push(...items: T[]): number;
+ push(...items: T[]): number;
}
Array.prototype.distinct = function () {
- return [...new Set(this)];
+ return [...new Set(this)];
}
if (!Array.prototype.last) {
- Array.prototype.last = function () {
- return this[this.length - 1];
- };
+ Array.prototype.last = function () {
+ return this[this.length - 1];
+ };
}
export function isNumeric(str: string): boolean {
- return !isNaN(str as any) && // use type coercion to parse the _entirety_ of the string (`parseFloat` alone does not do this)...
- !isNaN(parseFloat(str)) // ...and ensure strings of whitespace fail
+ return !isNaN(str as any) && // use type coercion to parse the _entirety_ of the string (`parseFloat` alone does not do this)...
+ !isNaN(parseFloat(str)) // ...and ensure strings of whitespace fail
}
export function getChildActivities(workflowModel: WorkflowModel, parentId?: string) {
- if (parentId == null) {
- const targetIds = new Set(workflowModel.connections.map(x => x.targetId));
- return workflowModel.activities.filter(x => !targetIds.has(x.activityId));
- } else {
- const targetIds = new Set(workflowModel.connections.filter(x => x.sourceId === parentId).map(x => x.targetId));
- return workflowModel.activities.filter(x => targetIds.has(x.activityId));
- }
+ if (parentId == null) {
+ const targetIds = new Set(workflowModel.connections.map(x => x.targetId));
+ return workflowModel.activities.filter(x => !targetIds.has(x.activityId));
+ } else {
+ const targetIds = new Set(workflowModel.connections.filter(x => x.sourceId === parentId).map(x => x.targetId));
+ return workflowModel.activities.filter(x => targetIds.has(x.activityId));
+ }
}
export function getInboundConnections(workflowModel: WorkflowModel, activityId: string) {
- return workflowModel.connections.filter(x => x.targetId === activityId);
+ return workflowModel.connections.filter(x => x.targetId === activityId);
}
export function getOutboundConnections(workflowModel: WorkflowModel, activityId: string) {
- return workflowModel.connections.filter(x => x.sourceId === activityId);
+ return workflowModel.connections.filter(x => x.sourceId === activityId);
}
export function removeActivity(workflowModel: WorkflowModel, activityId: string): WorkflowModel {
- const inboundConnections = getInboundConnections(workflowModel, activityId);
- const outboundConnections = getOutboundConnections(workflowModel, activityId);
- const connectionsToRemove = [...inboundConnections, ...outboundConnections];
+ const inboundConnections = getInboundConnections(workflowModel, activityId);
+ const outboundConnections = getOutboundConnections(workflowModel, activityId);
+ const connectionsToRemove = [...inboundConnections, ...outboundConnections];
- return {
- ...workflowModel,
- activities: workflowModel.activities.filter(x => x.activityId != activityId),
- connections: workflowModel.connections.filter(x => connectionsToRemove.indexOf(x) < 0)
- };
+ return {
+ ...workflowModel,
+ activities: workflowModel.activities.filter(x => x.activityId != activityId),
+ connections: workflowModel.connections.filter(x => connectionsToRemove.indexOf(x) < 0)
+ };
}
export function findActivity(workflowModel: WorkflowModel, activityId: string) {
- return workflowModel.activities.find(x => x.activityId === activityId);
+ return workflowModel.activities.find(x => x.activityId === activityId);
}
export function addConnection(workflowModel: WorkflowModel, connection: ConnectionModel);
export function addConnection(workflowModel: WorkflowModel, sourceId: string, targetId: string, outcome: string);
export function addConnection(workflowModel: WorkflowModel, ...args: any) {
- const connection = typeof (args) == 'object' ? args as ConnectionModel : {sourceId: args[0], targetId: args[1], outcome: args[3]};
+ const connection = typeof (args) == 'object' ? args as ConnectionModel : {sourceId: args[0], targetId: args[1], outcome: args[3]};
- return {
- ...workflowModel,
- connections: [...workflowModel.connections, connection]
- };
+ return {
+ ...workflowModel,
+ connections: [...workflowModel.connections, connection]
+ };
}
export function setActivityDefinitionProperty(activityDefinition: ActivityDefinition, name: string, expression: string, syntax: string) {
- setProperty(activityDefinition.properties, name, expression, syntax);
+ setProperty(activityDefinition.properties, name, expression, syntax);
}
export function setActivityModelProperty(activityModel: ActivityModel, name: string, expression: string, syntax: string) {
- setProperty(activityModel.properties, name, expression, syntax);
+ setProperty(activityModel.properties, name, expression, syntax);
}
export function setProperty(properties: Array, name: string, expression: string, syntax?: string) {
- let property: ActivityDefinitionProperty = properties.find(x => x.name == name);
+ let property: ActivityDefinitionProperty = properties.find(x => x.name == name);
- if(!syntax)
- syntax = 'Literal';
+ if (!syntax)
+ syntax = 'Literal';
- if (!property) {
- const expressions = {};
- expressions[syntax] = expression;
- property = {name: name, expressions: expressions, syntax: syntax};
- properties.push(property);
- } else {
- property.expressions[syntax] = expression;
- property.syntax = syntax;
- }
+ if (!property) {
+ const expressions = {};
+ expressions[syntax] = expression;
+ property = {name: name, expressions: expressions, syntax: syntax};
+ properties.push(property);
+ } else {
+ property.expressions[syntax] = expression;
+ property.syntax = syntax;
+ }
}
-export function getOrCreateProperty(activity: ActivityModel , name: string, defaultExpression?: () => string, defaultSyntax?: () => string): ActivityDefinitionProperty {
- let property: ActivityDefinitionProperty = activity.properties.find(x => x.name == name);
+export function getOrCreateProperty(activity: ActivityModel, name: string, defaultExpression?: () => string, defaultSyntax?: () => string): ActivityDefinitionProperty {
+ let property: ActivityDefinitionProperty = activity.properties.find(x => x.name == name);
- if (!property) {
- const expressions = {};
- let syntax = defaultSyntax ? defaultSyntax() : undefined;
+ if (!property) {
+ const expressions = {};
+ let syntax = defaultSyntax ? defaultSyntax() : undefined;
- if(!syntax)
- syntax = 'Literal';
+ if (!syntax)
+ syntax = 'Literal';
- expressions[syntax] = defaultExpression ? defaultExpression() : undefined;
- property = {name: name, expressions: expressions, syntax: null};
- activity.properties.push(property);
- }
+ expressions[syntax] = defaultExpression ? defaultExpression() : undefined;
+ property = {name: name, expressions: expressions, syntax: null};
+ activity.properties.push(property);
+ }
- return property;
+ return property;
}
export function parseJson(json: string): any {
- try {
- return JSON.parse(json);
- } catch (e) {
- console.warn(`Error parsing JSON: ${e}`);
- }
- return undefined;
+ try {
+ return JSON.parse(json);
+ } catch (e) {
+ console.warn(`Error parsing JSON: ${e}`);
+ }
+ return undefined;
+}
+
+export function parseQuery(queryString?: string): any {
+
+ if (!queryString)
+ return {};
+
+ const query = {};
+ const pairs = (queryString[0] === '?' ? queryString.substr(1) : queryString).split('&');
+ for (let i = 0; i < pairs.length; i++) {
+ const pair = pairs[i].split('=');
+ query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || '');
+ }
+ return query;
}
export function timeSince(time) {
- switch (typeof time) {
- case 'number':
- break;
- case 'string':
- time = +new Date(time);
- break;
- case 'object':
- if (time.constructor === Date) time = time.getTime();
- break;
- default:
- time = +new Date();
- }
- const time_formats = [
- [60, 'seconds', 1], // 60
- [120, '1 minute ago', '1 minute from now'], // 60*2
- [3600, 'minutes', 60], // 60*60, 60
- [7200, '1 hour ago', '1 hour from now'], // 60*60*2
- [86400, 'hours', 3600], // 60*60*24, 60*60
- [172800, 'Yesterday', 'Tomorrow'], // 60*60*24*2
- [604800, 'days', 86400], // 60*60*24*7, 60*60*24
- [1209600, 'Last week', 'Next week'], // 60*60*24*7*4*2
- [2419200, 'weeks', 604800], // 60*60*24*7*4, 60*60*24*7
- [4838400, 'Last month', 'Next month'], // 60*60*24*7*4*2
- [29030400, 'months', 2419200], // 60*60*24*7*4*12, 60*60*24*7*4
- [58060800, 'Last year', 'Next year'], // 60*60*24*7*4*12*2
- [2903040000, 'years', 29030400], // 60*60*24*7*4*12*100, 60*60*24*7*4*12
- [5806080000, 'Last century', 'Next century'], // 60*60*24*7*4*12*100*2
- [58060800000, 'centuries', 2903040000] // 60*60*24*7*4*12*100*20, 60*60*24*7*4*12*100
- ];
- let seconds = (+new Date() - time) / 1000,
- token = 'ago',
- list_choice = 1;
-
- if (seconds === 0) {
- return 'Just now';
- }
- if (seconds < 0) {
- seconds = Math.abs(seconds);
- token = 'from now';
- list_choice = 2;
- }
- let i = 0,
- format;
-
- // tslint:disable-next-line: no-conditional-assignment
- while (format = time_formats[i++]) {
- if (seconds < format[0]) {
- if (typeof format[2] === 'string') {
- return format[list_choice];
- } else {
- return Math.floor(seconds / format[2]) + ' ' + format[1] + ' ' + token;
- }
+ switch (typeof time) {
+ case 'number':
+ break;
+ case 'string':
+ time = +new Date(time);
+ break;
+ case 'object':
+ if (time.constructor === Date) time = time.getTime();
+ break;
+ default:
+ time = +new Date();
}
- }
- return time;
+ const time_formats = [
+ [60, 'seconds', 1], // 60
+ [120, '1 minute ago', '1 minute from now'], // 60*2
+ [3600, 'minutes', 60], // 60*60, 60
+ [7200, '1 hour ago', '1 hour from now'], // 60*60*2
+ [86400, 'hours', 3600], // 60*60*24, 60*60
+ [172800, 'Yesterday', 'Tomorrow'], // 60*60*24*2
+ [604800, 'days', 86400], // 60*60*24*7, 60*60*24
+ [1209600, 'Last week', 'Next week'], // 60*60*24*7*4*2
+ [2419200, 'weeks', 604800], // 60*60*24*7*4, 60*60*24*7
+ [4838400, 'Last month', 'Next month'], // 60*60*24*7*4*2
+ [29030400, 'months', 2419200], // 60*60*24*7*4*12, 60*60*24*7*4
+ [58060800, 'Last year', 'Next year'], // 60*60*24*7*4*12*2
+ [2903040000, 'years', 29030400], // 60*60*24*7*4*12*100, 60*60*24*7*4*12
+ [5806080000, 'Last century', 'Next century'], // 60*60*24*7*4*12*100*2
+ [58060800000, 'centuries', 2903040000] // 60*60*24*7*4*12*100*20, 60*60*24*7*4*12*100
+ ];
+ let seconds = (+new Date() - time) / 1000,
+ token = 'ago',
+ list_choice = 1;
+
+ if (seconds === 0) {
+ return 'Just now';
+ }
+ if (seconds < 0) {
+ seconds = Math.abs(seconds);
+ token = 'from now';
+ list_choice = 2;
+ }
+ let i = 0,
+ format;
+
+ // tslint:disable-next-line: no-conditional-assignment
+ while (format = time_formats[i++]) {
+ if (seconds < format[0]) {
+ if (typeof format[2] === 'string') {
+ return format[list_choice];
+ } else {
+ return Math.floor(seconds / format[2]) + ' ' + format[1] + ' ' + token;
+ }
+ }
+ }
+ return time;
}