Initial version of workflow instance viewer

This commit is contained in:
Sipke Schoorstra 2021-04-19 21:28:59 +02:00
parent 2eb4366628
commit dca9bf2509
9 changed files with 546 additions and 183 deletions

View file

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
using Elsa.Client.Comparers;
using Newtonsoft.Json.Linq;
using NodaTime;
namespace Elsa.Client.Models
@ -13,7 +14,6 @@ namespace Elsa.Client.Models
public WorkflowInstance()
{
Variables = new Variables();
Activities = new List<ActivityInstance>();
ScheduledActivities = new SimpleStack<ScheduledActivity>();
Scopes = new SimpleStack<ActivityScope>();
}
@ -27,21 +27,24 @@ namespace Elsa.Client.Models
[DataMember(Order = 7)] public string? Name { get; set; }
[DataMember(Order = 8)] public Instant CreatedAt { get; set; }
[DataMember(Order = 9)] public Instant? LastExecutedAt { get; set; }
[DataMember(Order = 10)] public Instant? LastBurstAt { get; set; }
[DataMember(Order = 11)] public Instant? FinishedAt { get; set; }
[DataMember(Order = 12)] public Variables Variables { get; set; }
[DataMember(Order = 13)] public object? Output { get; set; }
[DataMember(Order = 14)] public ICollection<ActivityInstance> Activities { get; set; }
[DataMember(Order = 12)] public Instant? CancelledAt { get; set; }
[DataMember(Order = 13)] public Instant? FaultedAt { get; set; }
[DataMember(Order = 14)] public Variables Variables { get; set; }
[DataMember(Order = 15)] public object? Output { get; set; }
[DataMember(Order = 16)] public IDictionary<string, JObject> ActivityData { get; set; } = new Dictionary<string, JObject>();
[DataMember(Order = 17)] public IDictionary<string, object> ActivityOutput { get; set; } = new Dictionary<string, object>();
[DataMember(Order = 15)]
[DataMember(Order = 18)]
public HashSet<BlockingActivity> BlockingActivities
{
get => _blockingActivities;
set => _blockingActivities = new HashSet<BlockingActivity>(value, BlockingActivityEqualityComparer.Instance);
}
[DataMember(Order = 16)] public WorkflowFault? Fault { get; set; }
[DataMember(Order = 17)] public SimpleStack<ScheduledActivity> ScheduledActivities { get; set; }
[DataMember(Order = 18)]public SimpleStack<ActivityScope> Scopes { get; set; }
[DataMember(Order = 19)] public WorkflowFault? Fault { get; set; }
[DataMember(Order = 20)] public SimpleStack<ScheduledActivity> ScheduledActivities { get; set; }
[DataMember(Order = 21)] public SimpleStack<ActivityScope> Scopes { get; set; }
[DataMember(Order = 22)] public ScheduledActivity? CurrentActivity { get; set; }
}
}

View file

@ -139,10 +139,18 @@ export namespace Components {
"history": RouterHistory;
"serverUrl": string;
}
interface ElsaStudioWorkflowInstanceView {
"match": MatchResults;
"serverUrl": string;
}
interface ElsaStudioWorkflowInstancesList {
"history": RouterHistory;
"serverUrl": string;
}
interface ElsaStudioWorkflowInstancesView {
"match": MatchResults;
"serverUrl": string;
}
interface ElsaStudioWorkflowRegistry {
}
interface ElsaSwitchCasesProperty {
@ -168,6 +176,11 @@ export namespace Components {
"history"?: RouterHistory;
"serverUrl": string;
}
interface ElsaWorkflowInstanceViewerScreen {
"getServerUrl": () => Promise<string>;
"serverUrl": string;
"workflowInstanceId": string;
}
interface ElsaWorkflowInstancesListScreen {
"history"?: RouterHistory;
"serverUrl": string;
@ -325,12 +338,24 @@ declare global {
prototype: HTMLElsaStudioWorkflowDefinitionsListElement;
new (): HTMLElsaStudioWorkflowDefinitionsListElement;
};
interface HTMLElsaStudioWorkflowInstanceViewElement extends Components.ElsaStudioWorkflowInstanceView, HTMLStencilElement {
}
var HTMLElsaStudioWorkflowInstanceViewElement: {
prototype: HTMLElsaStudioWorkflowInstanceViewElement;
new (): HTMLElsaStudioWorkflowInstanceViewElement;
};
interface HTMLElsaStudioWorkflowInstancesListElement extends Components.ElsaStudioWorkflowInstancesList, HTMLStencilElement {
}
var HTMLElsaStudioWorkflowInstancesListElement: {
prototype: HTMLElsaStudioWorkflowInstancesListElement;
new (): HTMLElsaStudioWorkflowInstancesListElement;
};
interface HTMLElsaStudioWorkflowInstancesViewElement extends Components.ElsaStudioWorkflowInstancesView, HTMLStencilElement {
}
var HTMLElsaStudioWorkflowInstancesViewElement: {
prototype: HTMLElsaStudioWorkflowInstancesViewElement;
new (): HTMLElsaStudioWorkflowInstancesViewElement;
};
interface HTMLElsaStudioWorkflowRegistryElement extends Components.ElsaStudioWorkflowRegistry, HTMLStencilElement {
}
var HTMLElsaStudioWorkflowRegistryElement: {
@ -367,6 +392,12 @@ declare global {
prototype: HTMLElsaWorkflowDefinitionsListScreenElement;
new (): HTMLElsaWorkflowDefinitionsListScreenElement;
};
interface HTMLElsaWorkflowInstanceViewerScreenElement extends Components.ElsaWorkflowInstanceViewerScreen, HTMLStencilElement {
}
var HTMLElsaWorkflowInstanceViewerScreenElement: {
prototype: HTMLElsaWorkflowInstanceViewerScreenElement;
new (): HTMLElsaWorkflowInstanceViewerScreenElement;
};
interface HTMLElsaWorkflowInstancesListScreenElement extends Components.ElsaWorkflowInstancesListScreen, HTMLStencilElement {
}
var HTMLElsaWorkflowInstancesListScreenElement: {
@ -410,13 +441,16 @@ declare global {
"elsa-studio-root": HTMLElsaStudioRootElement;
"elsa-studio-workflow-definitions-edit": HTMLElsaStudioWorkflowDefinitionsEditElement;
"elsa-studio-workflow-definitions-list": HTMLElsaStudioWorkflowDefinitionsListElement;
"elsa-studio-workflow-instance-view": HTMLElsaStudioWorkflowInstanceViewElement;
"elsa-studio-workflow-instances-list": HTMLElsaStudioWorkflowInstancesListElement;
"elsa-studio-workflow-instances-view": HTMLElsaStudioWorkflowInstancesViewElement;
"elsa-studio-workflow-registry": HTMLElsaStudioWorkflowRegistryElement;
"elsa-switch-cases-property": HTMLElsaSwitchCasesPropertyElement;
"elsa-toast-notification": HTMLElsaToastNotificationElement;
"elsa-workflow-definition-editor-notifications": HTMLElsaWorkflowDefinitionEditorNotificationsElement;
"elsa-workflow-definition-editor-screen": HTMLElsaWorkflowDefinitionEditorScreenElement;
"elsa-workflow-definitions-list-screen": HTMLElsaWorkflowDefinitionsListScreenElement;
"elsa-workflow-instance-viewer-screen": HTMLElsaWorkflowInstanceViewerScreenElement;
"elsa-workflow-instances-list-screen": HTMLElsaWorkflowInstancesListScreenElement;
"elsa-workflow-publish-button": HTMLElsaWorkflowPublishButtonElement;
"elsa-workflow-settings-modal": HTMLElsaWorkflowSettingsModalElement;
@ -551,10 +585,18 @@ declare namespace LocalJSX {
"history"?: RouterHistory;
"serverUrl"?: string;
}
interface ElsaStudioWorkflowInstanceView {
"match"?: MatchResults;
"serverUrl"?: string;
}
interface ElsaStudioWorkflowInstancesList {
"history"?: RouterHistory;
"serverUrl"?: string;
}
interface ElsaStudioWorkflowInstancesView {
"match"?: MatchResults;
"serverUrl"?: string;
}
interface ElsaStudioWorkflowRegistry {
}
interface ElsaSwitchCasesProperty {
@ -575,6 +617,10 @@ declare namespace LocalJSX {
"history"?: RouterHistory;
"serverUrl"?: string;
}
interface ElsaWorkflowInstanceViewerScreen {
"serverUrl"?: string;
"workflowInstanceId"?: string;
}
interface ElsaWorkflowInstancesListScreen {
"history"?: RouterHistory;
"serverUrl"?: string;
@ -615,13 +661,16 @@ declare namespace LocalJSX {
"elsa-studio-root": ElsaStudioRoot;
"elsa-studio-workflow-definitions-edit": ElsaStudioWorkflowDefinitionsEdit;
"elsa-studio-workflow-definitions-list": ElsaStudioWorkflowDefinitionsList;
"elsa-studio-workflow-instance-view": ElsaStudioWorkflowInstanceView;
"elsa-studio-workflow-instances-list": ElsaStudioWorkflowInstancesList;
"elsa-studio-workflow-instances-view": ElsaStudioWorkflowInstancesView;
"elsa-studio-workflow-registry": ElsaStudioWorkflowRegistry;
"elsa-switch-cases-property": ElsaSwitchCasesProperty;
"elsa-toast-notification": ElsaToastNotification;
"elsa-workflow-definition-editor-notifications": ElsaWorkflowDefinitionEditorNotifications;
"elsa-workflow-definition-editor-screen": ElsaWorkflowDefinitionEditorScreen;
"elsa-workflow-definitions-list-screen": ElsaWorkflowDefinitionsListScreen;
"elsa-workflow-instance-viewer-screen": ElsaWorkflowInstanceViewerScreen;
"elsa-workflow-instances-list-screen": ElsaWorkflowInstancesListScreen;
"elsa-workflow-publish-button": ElsaWorkflowPublishButton;
"elsa-workflow-settings-modal": ElsaWorkflowSettingsModal;
@ -655,13 +704,16 @@ declare module "@stencil/core" {
"elsa-studio-root": LocalJSX.ElsaStudioRoot & JSXBase.HTMLAttributes<HTMLElsaStudioRootElement>;
"elsa-studio-workflow-definitions-edit": LocalJSX.ElsaStudioWorkflowDefinitionsEdit & JSXBase.HTMLAttributes<HTMLElsaStudioWorkflowDefinitionsEditElement>;
"elsa-studio-workflow-definitions-list": LocalJSX.ElsaStudioWorkflowDefinitionsList & JSXBase.HTMLAttributes<HTMLElsaStudioWorkflowDefinitionsListElement>;
"elsa-studio-workflow-instance-view": LocalJSX.ElsaStudioWorkflowInstanceView & JSXBase.HTMLAttributes<HTMLElsaStudioWorkflowInstanceViewElement>;
"elsa-studio-workflow-instances-list": LocalJSX.ElsaStudioWorkflowInstancesList & JSXBase.HTMLAttributes<HTMLElsaStudioWorkflowInstancesListElement>;
"elsa-studio-workflow-instances-view": LocalJSX.ElsaStudioWorkflowInstancesView & JSXBase.HTMLAttributes<HTMLElsaStudioWorkflowInstancesViewElement>;
"elsa-studio-workflow-registry": LocalJSX.ElsaStudioWorkflowRegistry & JSXBase.HTMLAttributes<HTMLElsaStudioWorkflowRegistryElement>;
"elsa-switch-cases-property": LocalJSX.ElsaSwitchCasesProperty & JSXBase.HTMLAttributes<HTMLElsaSwitchCasesPropertyElement>;
"elsa-toast-notification": LocalJSX.ElsaToastNotification & JSXBase.HTMLAttributes<HTMLElsaToastNotificationElement>;
"elsa-workflow-definition-editor-notifications": LocalJSX.ElsaWorkflowDefinitionEditorNotifications & JSXBase.HTMLAttributes<HTMLElsaWorkflowDefinitionEditorNotificationsElement>;
"elsa-workflow-definition-editor-screen": LocalJSX.ElsaWorkflowDefinitionEditorScreen & JSXBase.HTMLAttributes<HTMLElsaWorkflowDefinitionEditorScreenElement>;
"elsa-workflow-definitions-list-screen": LocalJSX.ElsaWorkflowDefinitionsListScreen & JSXBase.HTMLAttributes<HTMLElsaWorkflowDefinitionsListScreenElement>;
"elsa-workflow-instance-viewer-screen": LocalJSX.ElsaWorkflowInstanceViewerScreen & JSXBase.HTMLAttributes<HTMLElsaWorkflowInstanceViewerScreenElement>;
"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>;

View file

@ -49,8 +49,9 @@ export class ElsaStudioRoot {
<stencil-route url="/" routeRender={props => <stencil-route-redirect url="/workflow-registry"/>} exact={true}/>
<stencil-route url="/workflow-registry" component="elsa-studio-workflow-registry"/>
<stencil-route url="/workflow-definitions" component="elsa-studio-workflow-definitions-list" componentProps={{'serverUrl': serverUrl}} exact={true}/>
<stencil-route url="/workflow-definitions/:id" component="elsa-studio-workflow-definitions-edit" componentProps={{'serverUrl': serverUrl}} />
<stencil-route url="/workflow-definitions/:id" component="elsa-studio-workflow-definitions-edit" componentProps={{'serverUrl': serverUrl}}/>
<stencil-route url="/workflow-instances" component="elsa-studio-workflow-instances-list" componentProps={{'serverUrl': serverUrl}} exact={true}/>
<stencil-route url="/workflow-instances/:id" component="elsa-studio-workflow-instances-view" componentProps={{'serverUrl': serverUrl}}/>
</stencil-route-switch>
</stencil-router>
{/*</Tunnel.Provider>*/}

View file

@ -14,7 +14,7 @@ export class ElsaStudioWorkflowDefinitionsEdit {
componentWillLoad() {
let id = this.match.params.id;
if (id == 'new')
if (!!id && id.toLowerCase() == 'new')
id = null;
this.id = id;

View file

@ -0,0 +1,25 @@
import {Component, Prop, h} from '@stencil/core';
import {MatchResults} from '@stencil/router';
@Component({
tag: 'elsa-studio-workflow-instances-view',
shadow: false,
})
export class ElsaStudioWorkflowInstancesView {
@Prop() match: MatchResults;
@Prop() serverUrl: string;
id?: string;
componentWillLoad() {
this.id = this.match.params.id;
}
render() {
const id = this.id;
return <div>
<elsa-workflow-instance-viewer-screen server-url={this.serverUrl} workflowInstanceId={id}/>
</div>;
}
}

View file

@ -0,0 +1,175 @@
import {Component, h, Host, Method, Prop, State, Watch} from '@stencil/core';
import {eventBus} from '../../../../services/event-bus';
import * as collection from 'lodash/collection';
import {
ActivityBlueprint, ActivityDefinitionProperty,
ActivityDescriptor,
ActivityModel, Connection,
ConnectionModel,
EventTypes, SyntaxNames,
WorkflowBlueprint,
WorkflowInstance,
WorkflowModel,
WorkflowPersistenceBehavior,
WorkflowStatus
} from "../../../../models";
import {createElsaClient} from "../../../../services/elsa-client";
import {pluginManager} from '../../../../services/plugin-manager';
import state from '../../../../utils/store';
@Component({
tag: 'elsa-workflow-instance-viewer-screen',
shadow: false,
})
export class ElsaWorkflowInstanceViewerScreen {
constructor() {
pluginManager.initialize();
}
@Prop() workflowInstanceId: string;
@Prop({attribute: 'server-url', reflect: true}) serverUrl: string;
@State() workflowInstance: WorkflowInstance;
@State() workflowBlueprint: WorkflowBlueprint;
@State() workflowModel: WorkflowModel;
el: HTMLElement;
designer: HTMLElsaDesignerTreeElement;
@Method()
async getServerUrl(): Promise<string> {
return this.serverUrl;
}
@Watch('workflowInstanceId')
async workflowInstanceIdChangedHandler(newValue: string) {
const workflowInstanceId = newValue;
let workflowInstance: WorkflowInstance = {
id: null,
definitionId: null,
version: null,
workflowStatus: WorkflowStatus.Idle,
variables: {data: {}},
blockingActivities: [],
scheduledActivities: [],
scopes: [],
currentActivity: {activityId: ''}
};
let workflowBlueprint: WorkflowBlueprint = {
id: null,
version: 1,
activities: [],
connections: [],
persistenceBehavior: WorkflowPersistenceBehavior.WorkflowBurst,
customAttributes: {data: {}},
persistWorkflow: false,
isLatest: false,
isPublished: false,
loadWorkflowContext: false,
isSingleton: false,
persistOutput: false,
saveWorkflowContext: false,
variables: {data: {}},
type: null,
properties: {data: {}}
};
const client = createElsaClient(this.serverUrl);
if (workflowInstanceId && workflowInstanceId.length > 0) {
try {
workflowInstance = await client.workflowInstancesApi.get(workflowInstanceId);
workflowBlueprint = await client.workflowRegistryApi.get(workflowInstance.definitionId, {version: workflowInstance.version});
} catch {
console.warn(`The specified workflow definition does not exist. Creating a new one.`);
}
}
this.updateModels(workflowInstance, workflowBlueprint);
}
@Watch("serverUrl")
async serverUrlChangedHandler(newValue: string) {
if (newValue && newValue.length > 0)
await this.loadActivityDescriptors();
}
async componentWillLoad() {
await this.serverUrlChangedHandler(this.serverUrl);
await this.workflowInstanceIdChangedHandler(this.workflowInstanceId);
}
componentDidLoad() {
if (!this.designer) {
this.designer = this.el.querySelector("elsa-designer-tree") as HTMLElsaDesignerTreeElement;
this.designer.model = this.workflowModel;
}
}
async loadActivityDescriptors() {
const client = createElsaClient(this.serverUrl);
state.activityDescriptors = await client.activitiesApi.list();
}
updateModels(workflowInstance: WorkflowInstance, workflowBlueprint: WorkflowBlueprint) {
this.workflowInstance = workflowInstance;
this.workflowBlueprint = workflowBlueprint;
this.workflowModel = this.mapWorkflowModel(workflowBlueprint);
}
mapWorkflowModel(workflowBlueprint: WorkflowBlueprint): WorkflowModel {
return {
activities: workflowBlueprint.activities.map(this.mapActivityModel),
connections: workflowBlueprint.connections.map(this.mapConnectionModel),
persistenceBehavior: workflowBlueprint.persistenceBehavior,
};
}
mapActivityModel(source: ActivityBlueprint): ActivityModel {
const descriptors: Array<ActivityDescriptor> = state.activityDescriptors;
const descriptor = descriptors.find(x => x.type == source.type);
const properties: Array<ActivityDefinitionProperty> = collection.map(source.properties.data, (v, k) => ({name: k, expressions: {'Literal': v}, syntax: SyntaxNames.Literal}));
return {
activityId: source.id,
description: source.description,
displayName: source.displayName,
name: source.name,
type: source.type,
properties: properties,
outcomes: [...descriptor.outcomes],
persistOutput: source.persistOutput,
persistWorkflow: source.persistWorkflow,
saveWorkflowContext: source.saveWorkflowContext,
loadWorkflowContext: source.loadWorkflowContext
}
}
mapConnectionModel(connection: Connection): ConnectionModel {
return {
sourceId: connection.sourceActivityId,
targetId: connection.targetActivityId,
outcome: connection.outcome
}
}
onShowWorkflowSettingsClick() {
eventBus.emit(EventTypes.ShowWorkflowSettings);
}
render() {
return (
<Host class="flex flex-col w-full" ref={el => this.el = el}>
{this.renderCanvas()}
</Host>
);
}
renderCanvas() {
return (
<div class="flex-1 flex relative">
<elsa-designer-tree model={this.workflowModel} class="flex-1" ref={el => this.designer = el}/>
</div>
);
}
}

View file

@ -108,7 +108,6 @@ export class ElsaWorkflowInstancesListScreen {
}
async routeChanged(e: LocationSegments) {
debugger;
if (e.pathname.toLowerCase().indexOf('workflow-instances') < 0)
return;
@ -286,7 +285,7 @@ export class ElsaWorkflowInstancesListScreen {
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 viewUrl = `/workflow-instances/${workflowInstance.id}`;
const instanceName = !workflowInstance.name ? '' : workflowInstance.name;
const isSelected = this.selectedWorkflowInstanceIds.findIndex(x => x === workflowInstance.id) >= 0;
//var displayContext = WorkflowInstanceDisplayContexts[workflowInstance];
@ -297,7 +296,7 @@ export class ElsaWorkflowInstancesListScreen {
class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded"/>
</td>
<td class="px-6 py-3 whitespace-no-wrap text-sm leading-5 font-medium text-gray-900">
<a href={viewUrl} class="truncate hover:text-gray-600">{workflowInstance.id}</a>
<stencil-route-link url={viewUrl} anchorClass="truncate hover:text-gray-600">{workflowInstance.id}</stencil-route-link>
</td>
<td class="px-6 py-3 whitespace-no-wrap text-sm leading-5 font-medium text-gray-900 text-left">
<a href={`/workflow-registry/${workflowInstance.definitionId}/viewer`} class="truncate hover:text-gray-600">
@ -308,7 +307,7 @@ export class ElsaWorkflowInstancesListScreen {
{workflowInstance.version}
</td>
<td class="px-6 py-3 whitespace-no-wrap text-sm leading-5 font-medium text-gray-900 text-left">
<a href={`"@($" workflow-registry/{workflowInstance.DefinitionId}/viewer")"`} class="truncate hover:text-gray-600">{instanceName}</a>
<stencil-route-link url={`"@($" workflow-registry/{workflowInstance.definitionId}/viewer")"`} anchorClass="truncate hover:text-gray-600">{instanceName}</stencil-route-link>
</td>
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-right">
<div class="flex items-center space-x-3 lg:pl-2">

View file

@ -34,6 +34,43 @@ export interface WorkflowDefinitionSummary {
isLatest?: boolean;
}
export interface ActivityBlueprint {
id: string;
name?: string;
displayName?: string;
description?: string;
type: string;
persistWorkflow: boolean;
loadWorkflowContext: boolean;
saveWorkflowContext: boolean;
persistOutput: boolean;
source?: string;
properties: Variables;
}
export interface Connection {
sourceActivityId: string;
targetActivityId: string;
outcome: string;
}
export interface CompositeActivityBlueprint extends ActivityBlueprint {
activities: Array<ActivityBlueprint>;
connections: Array<Connection>;
}
export interface WorkflowBlueprint extends CompositeActivityBlueprint {
version: number;
tenantId?: string;
isSingleton: boolean;
isPublished: boolean;
isLatest: boolean;
contextOptions?: WorkflowContextOptions;
variables: Variables;
customAttributes: Variables;
persistenceBehavior: WorkflowPersistenceBehavior;
}
export interface WorkflowBlueprintSummary {
id: string;
name?: string;
@ -46,6 +83,31 @@ export interface WorkflowBlueprintSummary {
isLatest: boolean;
}
export interface WorkflowInstance {
id: string;
definitionId: string;
tenantId?: string;
version: number;
workflowStatus: WorkflowStatus;
correlationId?: string;
contextType?: string;
contextId?: string;
name?: string;
createdAt?: Date
lastExecutedAt?: Date;
finishedAt?: Date;
faultedAt?: Date;
variables: Variables;
output?: any;
activityData?: Map<any>;
activityOutput?: Map<any>;
blockingActivities: Array<BlockingActivity>;
fault?: WorkflowFault;
scheduledActivities: Array<ScheduledActivity>;
scopes: Array<ActivityScope>;
currentActivity: ScheduledActivity;
}
export interface WorkflowInstanceSummary {
id: string;
definitionId: string;
@ -83,11 +145,11 @@ export interface ConnectionDefinition {
}
export interface ConnectionDefinitionMapped {
sourceId: string;
sourceActivityId: string;
targetId: string;
targetActivityId: string;
outcome: string;
sourceId: string;
sourceActivityId: string;
targetId: string;
targetActivityId: string;
outcome: string;
}
export interface ActivityDefinitionProperty {
@ -96,6 +158,22 @@ export interface ActivityDefinitionProperty {
expressions: Map<string>;
}
interface BlockingActivity {
activityId: string;
activityType: string;
tag?: string;
}
interface ScheduledActivity {
activityId: string;
input?: any;
}
interface ActivityScope {
activityId: string;
variables: Variables;
}
export interface Variables {
data: Map<any>;
}
@ -141,23 +219,20 @@ export enum OrderBy {
Finished = 'Finished'
}
export const getVersionOptionsString = (versionOptions?: VersionOptions) => {
interface SimpleException {
type: string;
message: string;
stackTrace: string;
innerException?: SimpleException;
}
if (!versionOptions)
return '';
return versionOptions.allVersions
? 'AllVersions'
: versionOptions.isDraft
? 'Draft'
: versionOptions.isLatest
? 'Latest'
: versionOptions.isPublished
? 'Published'
: versionOptions.isLatestOrPublished
? 'LatestOrPublished'
: versionOptions.version.toString();
};
interface WorkflowFault {
exception?: SimpleException;
message: string;
faultedActivityId?: string;
activityInput?: any;
resuming: boolean;
}
export interface ActivityDescriptor {
type: string;
@ -203,3 +278,21 @@ export class SyntaxNames {
static Variable = 'Variable';
static Output = 'Output';
}
export const getVersionOptionsString = (versionOptions?: VersionOptions) => {
if (!versionOptions)
return '';
return versionOptions.allVersions
? 'AllVersions'
: versionOptions.isDraft
? 'Draft'
: versionOptions.isLatest
? 'Latest'
: versionOptions.isPublished
? 'Published'
: versionOptions.isLatestOrPublished
? 'LatestOrPublished'
: versionOptions.version.toString();
};

View file

@ -1,203 +1,218 @@
import axios, {AxiosRequestConfig} from "axios";
import * as collection from 'lodash/collection';
import {
ActivityDefinition,
ActivityDescriptor,
ConnectionDefinition,
getVersionOptionsString, OrderBy,
PagedList,
Variables,
VersionOptions, WorkflowBlueprintSummary,
WorkflowContextOptions,
WorkflowDefinition,
WorkflowDefinitionSummary, WorkflowInstanceSummary,
WorkflowPersistenceBehavior, WorkflowStatus
ActivityDefinition,
ActivityDescriptor,
ConnectionDefinition,
getVersionOptionsString, OrderBy,
PagedList,
Variables,
VersionOptions, WorkflowBlueprint, WorkflowBlueprintSummary,
WorkflowContextOptions,
WorkflowDefinition,
WorkflowDefinitionSummary, WorkflowInstance, 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<Array<ActivityDescriptor>>('v1/activities');
return response.data;
}
},
workflowDefinitionsApi: {
list: async (page?: number, pageSize?: number, versionOptions?: VersionOptions) => {
const versionOptionsString = getVersionOptionsString(versionOptions);
const response = await httpClient.get<PagedList<WorkflowDefinitionSummary>>(`v1/workflow-definitions?version=${versionOptionsString}`);
return response.data;
},
getByDefinitionAndVersion: async (definitionId: string, versionOptions: VersionOptions) => {
const versionOptionsString = getVersionOptionsString(versionOptions);
const response = await httpClient.get<WorkflowDefinition>(`v1/workflow-definitions/${definitionId}/${versionOptionsString}`);
return response.data;
},
save: async request => {
const response = await httpClient.post<WorkflowDefinition>('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<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'
});
return {
activitiesApi: {
list: async () => {
const response = await httpClient.get<Array<ActivityDescriptor>>('v1/activities');
return response.data;
}
},
workflowDefinitionsApi: {
list: async (page?: number, pageSize?: number, versionOptions?: VersionOptions) => {
const versionOptionsString = getVersionOptionsString(versionOptions);
const response = await httpClient.get<PagedList<WorkflowDefinitionSummary>>(`v1/workflow-definitions?version=${versionOptionsString}`);
return response.data;
},
getByDefinitionAndVersion: async (definitionId: string, versionOptions: VersionOptions) => {
const versionOptionsString = getVersionOptionsString(versionOptions);
const response = await httpClient.get<WorkflowDefinition>(`v1/workflow-definitions/${definitionId}/${versionOptionsString}`);
return response.data;
},
save: async request => {
const response = await httpClient.post<WorkflowDefinition>('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<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;
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<WorkflowDefinition> => {
const formData = new FormData();
formData.append("file", file);
const response = await httpClient.post<WorkflowDefinition>(`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<PagedList<WorkflowBlueprintSummary>> => {
const versionOptionsString = getVersionOptionsString(versionOptions);
const response = await httpClient.get<PagedList<WorkflowBlueprintSummary>>(`v1/workflow-registry?version=${versionOptionsString}`);
return response.data;
}
},
workflowInstancesApi: {
list: async (page?: number, pageSize?: number, workflowDefinitionId?: string, workflowStatus?: WorkflowStatus, orderBy?: OrderBy, searchTerm?: string): Promise<PagedList<WorkflowInstanceSummary>> => {
const queryString = {};
return {
fileName: fileName,
data: data
};
},
import: async (workflowDefinitionId, file: File): Promise<WorkflowDefinition> => {
const formData = new FormData();
formData.append("file", file);
const response = await httpClient.post<WorkflowDefinition>(`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<PagedList<WorkflowBlueprintSummary>> => {
const versionOptionsString = getVersionOptionsString(versionOptions);
const response = await httpClient.get<PagedList<WorkflowBlueprintSummary>>(`v1/workflow-registry?version=${versionOptionsString}`);
return response.data;
},
if (!!workflowDefinitionId)
queryString['workflow'] = workflowDefinitionId;
get: async (id: string, versionOptions: VersionOptions) => {
const versionOptionsString = getVersionOptionsString(versionOptions);
const response = await httpClient.get<WorkflowBlueprint>(`v1/workflow-registry/${id}/${versionOptionsString}`);
return response.data;
}
},
workflowInstancesApi: {
list: async (page?: number, pageSize?: number, workflowDefinitionId?: string, workflowStatus?: WorkflowStatus, orderBy?: OrderBy, searchTerm?: string): Promise<PagedList<WorkflowInstanceSummary>> => {
const queryString = {};
if (workflowStatus != null)
queryString['status'] = workflowStatus;
if (!!workflowDefinitionId)
queryString['workflow'] = workflowDefinitionId;
if (!!orderBy)
queryString['orderBy'] = orderBy;
if (workflowStatus != null)
queryString['status'] = workflowStatus;
if (!!searchTerm)
queryString['searchTerm'] = searchTerm;
if (!!orderBy)
queryString['orderBy'] = orderBy;
if (!!page)
queryString['page'] = page;
if (!!searchTerm)
queryString['searchTerm'] = searchTerm;
if (!!pageSize)
queryString['pageSize'] = pageSize;
if (!!page)
queryString['page'] = page;
const queryStringItems = collection.map(queryString, (v, k) => `${k}=${v}`);
const queryStringText = queryStringItems.length > 0 ? `?${queryStringItems.join('&')}` : '';
const response = await httpClient.get<PagedList<WorkflowInstanceSummary>>(`v1/workflow-instances${queryStringText}`);
return response.data;
},
delete: async id => {
await httpClient.delete(`v1/workflow-instances/${id}`);
},
bulkDelete: async request => {
const response = await httpClient.delete(`v1/workflow-instances/bulk`, {
data: request
});
return response.data;
}
},
scriptingApi: {
getJavaScriptTypeDefinitions: async (workflowDefinitionId: string, context?: string): Promise<string> => {
context = context || '';
const response = await httpClient.get<string>(`v1/scripting/javascript/type-definitions/${workflowDefinitionId}?t=${new Date().getTime()}&context=${context}`);
return response.data;
}
}
if (!!pageSize)
queryString['pageSize'] = pageSize;
const queryStringItems = collection.map(queryString, (v, k) => `${k}=${v}`);
const queryStringText = queryStringItems.length > 0 ? `?${queryStringItems.join('&')}` : '';
const response = await httpClient.get<PagedList<WorkflowInstanceSummary>>(`v1/workflow-instances${queryStringText}`);
return response.data;
},
get: async id => {
const response = await httpClient.get(`v1/workflow-instances/${id}`);
return response.data;
},
delete: async id => {
await httpClient.delete(`v1/workflow-instances/${id}`);
},
bulkDelete: async request => {
const response = await httpClient.delete(`v1/workflow-instances/bulk`, {
data: request
});
return response.data;
}
},
scriptingApi: {
getJavaScriptTypeDefinitions: async (workflowDefinitionId: string, context?: string): Promise<string> => {
context = context || '';
const response = await httpClient.get<string>(`v1/scripting/javascript/type-definitions/${workflowDefinitionId}?t=${new Date().getTime()}&context=${context}`);
return response.data;
}
}
}
}
export interface ElsaClient {
activitiesApi: ActivitiesApi;
workflowDefinitionsApi: WorkflowDefinitionsApi;
workflowRegistryApi: WorkflowRegistryApi;
workflowInstancesApi: WorkflowInstancesApi;
scriptingApi: ScriptingApi;
activitiesApi: ActivitiesApi;
workflowDefinitionsApi: WorkflowDefinitionsApi;
workflowRegistryApi: WorkflowRegistryApi;
workflowInstancesApi: WorkflowInstancesApi;
scriptingApi: ScriptingApi;
}
export interface ActivitiesApi {
list(): Promise<Array<ActivityDescriptor>>;
list(): Promise<Array<ActivityDescriptor>>;
}
export interface WorkflowDefinitionsApi {
getByDefinitionAndVersion(definitionId: string, versionOptions: VersionOptions): Promise<WorkflowDefinition>;
list(page?: number, pageSize?: number, versionOptions?: VersionOptions): Promise<PagedList<WorkflowDefinitionSummary>>;
list(page?: number, pageSize?: number, versionOptions?: VersionOptions): Promise<PagedList<WorkflowDefinitionSummary>>;
save(request: SaveWorkflowDefinitionRequest): Promise<WorkflowDefinition>;
getByDefinitionAndVersion(definitionId: string, versionOptions: VersionOptions): Promise<WorkflowDefinition>;
delete(definitionId: string): Promise<void>;
save(request: SaveWorkflowDefinitionRequest): Promise<WorkflowDefinition>;
retract(workflowDefinitionId: string): Promise<WorkflowDefinition>;
delete(definitionId: string): Promise<void>;
export(workflowDefinitionId: string, versionOptions: VersionOptions): Promise<ExportWorkflowResponse>;
retract(workflowDefinitionId: string): Promise<WorkflowDefinition>;
import(workflowDefinitionId: string, file: File): Promise<WorkflowDefinition>;
export(workflowDefinitionId: string, versionOptions: VersionOptions): Promise<ExportWorkflowResponse>;
import(workflowDefinitionId: string, file: File): Promise<WorkflowDefinition>;
}
export interface WorkflowRegistryApi {
list(page?: number, pageSize?: number, versionOptions?: VersionOptions): Promise<PagedList<WorkflowBlueprintSummary>>;
list(page?: number, pageSize?: number, versionOptions?: VersionOptions): Promise<PagedList<WorkflowBlueprintSummary>>;
get(id: string, versionOptions: VersionOptions): Promise<WorkflowBlueprint>;
}
export interface WorkflowInstancesApi {
list(page?: number, pageSize?: number, workflowDefinitionId?: string, workflowStatus?: WorkflowStatus, orderBy?: OrderBy, searchTerm?: string): Promise<PagedList<WorkflowInstanceSummary>>;
list(page?: number, pageSize?: number, workflowDefinitionId?: string, workflowStatus?: WorkflowStatus, orderBy?: OrderBy, searchTerm?: string): Promise<PagedList<WorkflowInstanceSummary>>;
delete(id: string): Promise<void>;
get(id: string): Promise<WorkflowInstance>;
bulkDelete(request: BulkDeleteWorkflowsRequest): Promise<BulkDeleteWorkflowsResponse>;
delete(id: string): Promise<void>;
bulkDelete(request: BulkDeleteWorkflowsRequest): Promise<BulkDeleteWorkflowsResponse>;
}
export interface BulkDeleteWorkflowsRequest {
workflowInstanceIds: Array<string>;
workflowInstanceIds: Array<string>;
}
export interface BulkDeleteWorkflowsResponse {
deletedWorkflowCount: number;
deletedWorkflowCount: number;
}
export interface ScriptingApi {
getJavaScriptTypeDefinitions(workflowDefinitionId: string, context?: string): Promise<string>
getJavaScriptTypeDefinitions(workflowDefinitionId: string, context?: string): Promise<string>
}
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<ActivityDefinition>;
connections: Array<ConnectionDefinition>;
workflowDefinitionId?: string;
name?: string;
displayName?: string;
description?: string;
variables?: Variables;
contextOptions?: WorkflowContextOptions;
isSingleton?: boolean;
persistenceBehavior?: WorkflowPersistenceBehavior;
deleteCompletedInstances?: boolean;
publish?: boolean;
activities: Array<ActivityDefinition>;
connections: Array<ConnectionDefinition>;
}
export interface ExportWorkflowResponse {
fileName: string;
data: Blob;
fileName: string;
data: Blob;
}