Incremental work on workflow instances list screen
This commit is contained in:
parent
f329db3b25
commit
22ce7e2e81
|
|
@ -67,7 +67,13 @@ export class ElsaContextMenu {
|
|||
|
||||
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`}>
|
||||
return <div ref={el => 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`}>
|
||||
<div class="py-1" role="menu" aria-orientation="vertical">
|
||||
{this.renderItems()}
|
||||
</div>
|
||||
|
|
@ -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
|
||||
? <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>
|
||||
? <stencil-route-link onClick={e => this.closeContextMenu()} url={item.url} anchorClass={`block px-4 py-2 text-sm ${selectedCssClass} cursor-pointer`} 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>;
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
}
|
||||
|
||||
export interface DropdownButtonItem {
|
||||
name: string;
|
||||
name?: string;
|
||||
value?: any;
|
||||
text: string;
|
||||
url?: string;
|
||||
isSelected?: boolean;
|
||||
|
|
|
|||
|
|
@ -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<WorkflowDefinitionSummary> = {items: [], page: 1, pageSize: 50, totalCount: 0};
|
||||
@State() workflowBlueprints: Array<WorkflowBlueprintSummary> = [];
|
||||
@State() workflowInstances: PagedList<WorkflowInstanceSummary> = {items: [], page: 1, pageSize: 50, totalCount: 0};
|
||||
@State() selectedWorkflowId?: string;
|
||||
@State() selectedWorkflowStatus?: WorkflowStatus;
|
||||
@State() selectedOrderBy?: OrderBy;
|
||||
@State() selectedWorkflowInstanceIds: Array<string> = [];
|
||||
|
||||
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<string> = {};
|
||||
|
|
@ -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 (
|
||||
<div>
|
||||
|
||||
<div class="p-8 flex content-end justify-right bg-white space-x-4">
|
||||
<div class="flex-shrink-0">
|
||||
{this.renderBulkActions()}
|
||||
|
|
@ -80,95 +162,105 @@ export class ElsaWorkflowInstancesListScreen {
|
|||
|
||||
</div>
|
||||
{this.renderWorkflowFilter()}
|
||||
<elsa-dropdown-button text="@SelectedWorkflowStatusText" items={[]} icon={null} origin={DropdownButtonOrigin.TopRight}/>
|
||||
{this.renderStatusFilter()}
|
||||
<elsa-dropdown-button text="@SelectedOrderByText" items={[]} icon={null} origin={DropdownButtonOrigin.TopRight}/>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 sm:block">
|
||||
<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">
|
||||
<input type="checkbox" value="true" checked={false} class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded"/>
|
||||
</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">
|
||||
<span class="lg:pl-2">ID</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">
|
||||
Workflow
|
||||
</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">
|
||||
Version
|
||||
</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">
|
||||
Instance Name
|
||||
</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">
|
||||
Status
|
||||
</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">
|
||||
Created
|
||||
</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">
|
||||
Finished
|
||||
</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">
|
||||
Last Executed
|
||||
</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">
|
||||
Faulted
|
||||
</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">
|
||||
{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];
|
||||
|
||||
<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>
|
||||
return <tr>
|
||||
<td class="px-6 py-3 whitespace-no-wrap text-sm leading-5 font-medium text-gray-900">
|
||||
<input type="checkbox" value={workflowInstance.id} checked={isSelected} 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>
|
||||
</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">
|
||||
{displayName}
|
||||
</a>
|
||||
</td>
|
||||
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-right">
|
||||
{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>
|
||||
</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">
|
||||
<stencil-route-link url={editUrl} anchorClass="truncate hover:text-gray-600"><span>{workflowDisplayName}</span></stencil-route-link>
|
||||
<div class="flex-shrink-0 w-2.5 h-2.5 rounded-full bg-@statusColor-600"/>
|
||||
<span>{workflowInstance.workflowStatus}</span>
|
||||
</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 class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-left">
|
||||
{workflowInstance.createdAt}
|
||||
</td>
|
||||
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-left">
|
||||
{!!workflowInstance.finishedAt ? workflowInstance.finishedAt.toString() : '-'}
|
||||
</td>
|
||||
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-left">
|
||||
{!!workflowInstance.lastExecutedAt ? workflowInstance.lastExecutedAt.toString() : '-'}
|
||||
</td>
|
||||
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-left">
|
||||
{!!workflowInstance.faultedAt ? workflowInstance.faultedAt.toString() : '-'}
|
||||
</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}
|
||||
]}/>
|
||||
<elsa-context-menu/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<elsa-pager page="@WorkflowInstances.Page" pageSize="@WorkflowInstances.PageSize" totalCount="@WorkflowInstances.TotalCount"/>
|
||||
</div>
|
||||
<elsa-confirm-dialog ref={el => this.confirmDialog = el}/>
|
||||
</div>
|
||||
|
||||
<elsa-confirm-dialog ref={el => this.confirmDialog = el}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -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<DropdownButtonItem> = latestWorkflowBlueprints.map(x => ({text: x.displayName!, name: x.id, url: this.buildFilterUrl(x.id, selectedWorkflowStatus, SelectedOrderBy), isSelected: x.id == selectedWorkflowId}));
|
||||
let items: Array<DropdownButtonItem> = 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 <elsa-dropdown-button text={selectedWorkflowText} items={items} icon={null} origin={DropdownButtonOrigin.TopRight}/>
|
||||
items = [{text: 'All', value: null, url: this.buildFilterUrl(null, selectedWorkflowStatus, SelectedOrderBy), isSelected: !selectedWorkflowId}, ...items];
|
||||
|
||||
return <elsa-dropdown-button text={selectedWorkflowText} items={items} icon={null} origin={DropdownButtonOrigin.TopRight} onItemSelected={e => this.selectedWorkflowId = e.detail.value as string}/>
|
||||
}
|
||||
|
||||
renderStatusFilter(){
|
||||
const selectedWorkflowStatus = this.selectedWorkflowStatus;
|
||||
const selectedWorkflowStatusText = selectedWorkflowStatus != null ? this.getStatusText(selectedWorkflowStatus) : 'Status';
|
||||
const statuses: Array<WorkflowStatus> = [null, WorkflowStatus.Running, WorkflowStatus.Suspended, WorkflowStatus.Finished, WorkflowStatus.Faulted, WorkflowStatus.Cancelled, WorkflowStatus.Idle];
|
||||
const items: Array<DropdownButtonItem> = 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 <elsa-dropdown-button text={selectedWorkflowStatusText} items={items} icon={null} origin={DropdownButtonOrigin.TopRight}/>
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<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;
|
||||
}
|
||||
},
|
||||
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;
|
||||
}
|
||||
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 = {};
|
||||
|
||||
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<PagedList<WorkflowInstanceSummary>>(`v1/workflow-instances${queryStringText}`);
|
||||
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;
|
||||
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>
|
||||
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>
|
||||
save(request: SaveWorkflowDefinitionRequest): Promise<WorkflowDefinition>
|
||||
|
||||
delete(definitionId: string): Promise<void>
|
||||
delete(definitionId: string): Promise<void>
|
||||
|
||||
retract(workflowDefinitionId: string): Promise<WorkflowDefinition>
|
||||
retract(workflowDefinitionId: string): Promise<WorkflowDefinition>
|
||||
|
||||
export(workflowDefinitionId: string, versionOptions: VersionOptions): Promise<ExportWorkflowResponse>
|
||||
export(workflowDefinitionId: string, versionOptions: VersionOptions): Promise<ExportWorkflowResponse>
|
||||
|
||||
import(workflowDefinitionId: string, file: File): Promise<WorkflowDefinition>
|
||||
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>>
|
||||
}
|
||||
|
||||
export interface WorkflowInstancesApi {
|
||||
list(page?: number, pageSize?: number, workflowDefinitionId?: string, workflowStatus?: WorkflowStatus, orderBy?: OrderBy, searchTerm?: string): Promise<PagedList<WorkflowInstanceSummary>>
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,201 +1,215 @@
|
|||
import {ActivityDefinition, ActivityDefinitionProperty, ActivityModel, ConnectionModel, WorkflowModel} from "../models";
|
||||
|
||||
declare global {
|
||||
interface Array<T> {
|
||||
distinct(): Array<T>;
|
||||
interface Array<T> {
|
||||
distinct(): Array<T>;
|
||||
|
||||
last(): T;
|
||||
}
|
||||
last(): T;
|
||||
}
|
||||
}
|
||||
|
||||
export type Map<T> = {
|
||||
[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<T> {
|
||||
distinct(): Array<T>;
|
||||
distinct(): Array<T>;
|
||||
|
||||
last(): T;
|
||||
last(): T;
|
||||
|
||||
find<S extends T>(predicate: (this: void, value: T, index: number, obj: T[]) => value is S, thisArg?: any): S | undefined;
|
||||
find<S extends T>(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<ActivityDefinitionProperty>, 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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue