Incremental work on dashboard

This commit is contained in:
Sipke Schoorstra 2021-04-05 21:04:55 +02:00
parent aabf9ff2c2
commit 727191c0e8
66 changed files with 832 additions and 59 deletions

View file

@ -17,7 +17,7 @@ namespace Elsa.Activities.ControlFlow
DisplayName = "If/Else",
Category = "Control Flow",
Description = "Evaluate a Boolean expression and continue execution depending on the result.",
Outcomes = new[] { True, False, OutcomeNames.Done }
Outcomes = new[] { True, False }
)]
public class If : Activity, INotificationHandler<ScopeEvicted>
{

View file

@ -10,6 +10,8 @@ namespace Elsa.Persistence.Specifications.WorkflowDefinitions
public VersionOptionsSpecification(VersionOptions versionOptions) => VersionOptions = versionOptions;
public override Expression<Func<WorkflowDefinition, bool>> ToExpression()
{
if (VersionOptions.AllVersions)
return x => true;
if (VersionOptions.IsDraft)
return x => !x.IsPublished;
if (VersionOptions.IsLatest)

View file

@ -11,19 +11,6 @@ namespace ElsaDashboard.Application.Shared
private int From => Page!.Value * PageSize!.Value + 1;
private int To => Math.Min(From + PageSize!.Value, TotalCount);
private int PageCount => (int) (((float) TotalCount - 1) / PageSize!.Value) + 1;
// private int PageCount
// {
// get
// {
// var count = TotalCount / PageSize!.Value;
// var hasLeftOver = TotalCount % (float) PageSize!.Value == 0;
// var extra = hasLeftOver ? 1 : 0;
//
// return count + extra;
// }
// }
}
}

View file

@ -12,6 +12,7 @@
"@fullhuman/postcss-purgecss": "^4.0.3",
"@stencil/core": "^2.5.2",
"@stencil/postcss": "^2.0.0",
"@stencil/router": "^1.0.1",
"@stencil/state-tunnel": "^1.0.1",
"@stencil/store": "^1.4.1",
"@tailwindcss/aspect-ratio": "^0.2.0",
@ -258,6 +259,14 @@
"@stencil/core": ">=2.3.0"
}
},
"node_modules/@stencil/router": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@stencil/router/-/router-1.0.1.tgz",
"integrity": "sha512-ZMholl1BE+glNAc/8pcEb9RYkeH0XETxsJbx6D7f3azTmaTXqKYty1IACP/3BtVjuimpfLdxQJ+J95wKmnYBtA==",
"dependencies": {
"@stencil/state-tunnel": "^1.0.1"
}
},
"node_modules/@stencil/state-tunnel": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@stencil/state-tunnel/-/state-tunnel-1.0.1.tgz",
@ -10589,6 +10598,14 @@
"postcss": "~8.2.1"
}
},
"@stencil/router": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@stencil/router/-/router-1.0.1.tgz",
"integrity": "sha512-ZMholl1BE+glNAc/8pcEb9RYkeH0XETxsJbx6D7f3azTmaTXqKYty1IACP/3BtVjuimpfLdxQJ+J95wKmnYBtA==",
"requires": {
"@stencil/state-tunnel": "^1.0.1"
}
},
"@stencil/state-tunnel": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@stencil/state-tunnel/-/state-tunnel-1.0.1.tgz",

View file

@ -23,13 +23,14 @@
"test": "stencil test --spec --e2e",
"test.watch": "stencil test --spec --e2e --watchAll",
"generate": "stencil generate",
"build:tailwind:dev": "cross-env NODE_ENV=development tailwind build ./tailwind.css -o ./src/globals/tailwind.css & copyfiles -u 2 \"./src/globals/tailwind.css\" \"./src/assets\"",
"build:tailwind:prod": "cross-env NODE_ENV=production tailwind build ./tailwind.css -o ./src/globals/tailwind.css & copyfiles -u 2 \"./src/globals/tailwind.css\" \"./src/assets\""
"build:tailwind:dev": "cross-env NODE_ENV=development tailwind build ./tailwind.css -o ./src/globals/tailwind.css & copyfiles -u 2 \"./src/globals/tailwind.css\" \"./src/assets/styles\"",
"build:tailwind:prod": "cross-env NODE_ENV=production tailwind build ./tailwind.css -o ./src/globals/tailwind.css & copyfiles -u 2 \"./src/globals/tailwind.css\" \"./src/assets/styles\""
},
"dependencies": {
"@fullhuman/postcss-purgecss": "^4.0.3",
"@stencil/core": "^2.5.2",
"@stencil/postcss": "^2.0.0",
"@stencil/router": "^1.0.1",
"@stencil/state-tunnel": "^1.0.1",
"@stencil/store": "^1.4.1",
"@tailwindcss/aspect-ratio": "^0.2.0",
@ -43,6 +44,7 @@
"eslint": "7.23.0",
"js-event-bus": "^1.0.3",
"jsplumb": "2.15.5",
"lodash": "^4.17.21",
"monaco-editor": "^0.23.0",
"postcss": "^8.2.9",
"postcss-import": "^14.0.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

View file

@ -5,10 +5,24 @@
* It contains typing information for all components that exist in this project.
*/
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
import { MatchResults, RouterHistory } from "@stencil/router";
import { ActivityDefinitionProperty, ActivityDesignDisplayContext, ActivityModel, ActivityPropertyDescriptor, VersionOptions, WorkflowDefinition, WorkflowModel } from "./models";
import { MonacoValueChangedArgs } from "./components/editors/monaco/elsa-monaco/elsa-monaco";
import { MenuItem } from "./components/controls/elsa-context-menu/models";
import { MonacoValueChangedArgs } from "./components/controls/elsa-monaco/elsa-monaco";
import { ToastNotificationOptions } from "./components/shared/elsa-toast-notification/elsa-toast-notification";
export namespace Components {
interface AppRoot {
"serverUrl": string;
}
interface AppWorkflowDefinitionsEdit {
"match": MatchResults;
"serverUrl": string;
}
interface AppWorkflowDefinitionsList {
"serverUrl": string;
}
interface AppWorkflowRegistry {
}
interface ElsaActivityEditorModal {
}
interface ElsaActivityPickerModal {
@ -21,6 +35,14 @@ export namespace Components {
"propertyDescriptor": ActivityPropertyDescriptor;
"propertyModel": ActivityDefinitionProperty;
}
interface ElsaConfirmDialog {
"hide": () => Promise<void>;
"show": (caption: string, message: string) => Promise<boolean>;
}
interface ElsaContextMenu {
"history": RouterHistory;
"menuItems": Array<MenuItem>;
}
interface ElsaDesignerTree {
"destroyJsPlumb": () => Promise<void>;
"model": WorkflowModel;
@ -96,7 +118,7 @@ export namespace Components {
"hide": () => Promise<void>;
"show": (options: ToastNotificationOptions) => Promise<void>;
}
interface ElsaWorkflowEditor {
interface ElsaWorkflowDefinitionEditorScreen {
"exportWorkflow": () => Promise<void>;
"getServerUrl": () => Promise<string>;
"getWorkflowDefinitionId": () => Promise<string>;
@ -105,6 +127,9 @@ export namespace Components {
"serverUrl": string;
"workflowDefinitionId": string;
}
interface ElsaWorkflowDefinitionsListScreen {
"serverUrl": string;
}
interface ElsaWorkflowEditorNotifications {
}
interface ElsaWorkflowPublishButton {
@ -116,6 +141,30 @@ export namespace Components {
}
}
declare global {
interface HTMLAppRootElement extends Components.AppRoot, HTMLStencilElement {
}
var HTMLAppRootElement: {
prototype: HTMLAppRootElement;
new (): HTMLAppRootElement;
};
interface HTMLAppWorkflowDefinitionsEditElement extends Components.AppWorkflowDefinitionsEdit, HTMLStencilElement {
}
var HTMLAppWorkflowDefinitionsEditElement: {
prototype: HTMLAppWorkflowDefinitionsEditElement;
new (): HTMLAppWorkflowDefinitionsEditElement;
};
interface HTMLAppWorkflowDefinitionsListElement extends Components.AppWorkflowDefinitionsList, HTMLStencilElement {
}
var HTMLAppWorkflowDefinitionsListElement: {
prototype: HTMLAppWorkflowDefinitionsListElement;
new (): HTMLAppWorkflowDefinitionsListElement;
};
interface HTMLAppWorkflowRegistryElement extends Components.AppWorkflowRegistry, HTMLStencilElement {
}
var HTMLAppWorkflowRegistryElement: {
prototype: HTMLAppWorkflowRegistryElement;
new (): HTMLAppWorkflowRegistryElement;
};
interface HTMLElsaActivityEditorModalElement extends Components.ElsaActivityEditorModal, HTMLStencilElement {
}
var HTMLElsaActivityEditorModalElement: {
@ -140,6 +189,18 @@ declare global {
prototype: HTMLElsaCheckboxPropertyElement;
new (): HTMLElsaCheckboxPropertyElement;
};
interface HTMLElsaConfirmDialogElement extends Components.ElsaConfirmDialog, HTMLStencilElement {
}
var HTMLElsaConfirmDialogElement: {
prototype: HTMLElsaConfirmDialogElement;
new (): HTMLElsaConfirmDialogElement;
};
interface HTMLElsaContextMenuElement extends Components.ElsaContextMenu, HTMLStencilElement {
}
var HTMLElsaContextMenuElement: {
prototype: HTMLElsaContextMenuElement;
new (): HTMLElsaContextMenuElement;
};
interface HTMLElsaDesignerTreeElement extends Components.ElsaDesignerTree, HTMLStencilElement {
}
var HTMLElsaDesignerTreeElement: {
@ -218,11 +279,17 @@ declare global {
prototype: HTMLElsaToastNotificationElement;
new (): HTMLElsaToastNotificationElement;
};
interface HTMLElsaWorkflowEditorElement extends Components.ElsaWorkflowEditor, HTMLStencilElement {
interface HTMLElsaWorkflowDefinitionEditorScreenElement extends Components.ElsaWorkflowDefinitionEditorScreen, HTMLStencilElement {
}
var HTMLElsaWorkflowEditorElement: {
prototype: HTMLElsaWorkflowEditorElement;
new (): HTMLElsaWorkflowEditorElement;
var HTMLElsaWorkflowDefinitionEditorScreenElement: {
prototype: HTMLElsaWorkflowDefinitionEditorScreenElement;
new (): HTMLElsaWorkflowDefinitionEditorScreenElement;
};
interface HTMLElsaWorkflowDefinitionsListScreenElement extends Components.ElsaWorkflowDefinitionsListScreen, HTMLStencilElement {
}
var HTMLElsaWorkflowDefinitionsListScreenElement: {
prototype: HTMLElsaWorkflowDefinitionsListScreenElement;
new (): HTMLElsaWorkflowDefinitionsListScreenElement;
};
interface HTMLElsaWorkflowEditorNotificationsElement extends Components.ElsaWorkflowEditorNotifications, HTMLStencilElement {
}
@ -243,10 +310,16 @@ declare global {
new (): HTMLElsaWorkflowSettingsModalElement;
};
interface HTMLElementTagNameMap {
"app-root": HTMLAppRootElement;
"app-workflow-definitions-edit": HTMLAppWorkflowDefinitionsEditElement;
"app-workflow-definitions-list": HTMLAppWorkflowDefinitionsListElement;
"app-workflow-registry": HTMLAppWorkflowRegistryElement;
"elsa-activity-editor-modal": HTMLElsaActivityEditorModalElement;
"elsa-activity-picker-modal": HTMLElsaActivityPickerModalElement;
"elsa-check-list-property": HTMLElsaCheckListPropertyElement;
"elsa-checkbox-property": HTMLElsaCheckboxPropertyElement;
"elsa-confirm-dialog": HTMLElsaConfirmDialogElement;
"elsa-context-menu": HTMLElsaContextMenuElement;
"elsa-designer-tree": HTMLElsaDesignerTreeElement;
"elsa-designer-tree-activity": HTMLElsaDesignerTreeActivityElement;
"elsa-dropdown-property": HTMLElsaDropdownPropertyElement;
@ -260,13 +333,26 @@ declare global {
"elsa-script-property": HTMLElsaScriptPropertyElement;
"elsa-single-line-property": HTMLElsaSingleLinePropertyElement;
"elsa-toast-notification": HTMLElsaToastNotificationElement;
"elsa-workflow-editor": HTMLElsaWorkflowEditorElement;
"elsa-workflow-definition-editor-screen": HTMLElsaWorkflowDefinitionEditorScreenElement;
"elsa-workflow-definitions-list-screen": HTMLElsaWorkflowDefinitionsListScreenElement;
"elsa-workflow-editor-notifications": HTMLElsaWorkflowEditorNotificationsElement;
"elsa-workflow-publish-button": HTMLElsaWorkflowPublishButtonElement;
"elsa-workflow-settings-modal": HTMLElsaWorkflowSettingsModalElement;
}
}
declare namespace LocalJSX {
interface AppRoot {
"serverUrl"?: string;
}
interface AppWorkflowDefinitionsEdit {
"match"?: MatchResults;
"serverUrl"?: string;
}
interface AppWorkflowDefinitionsList {
"serverUrl"?: string;
}
interface AppWorkflowRegistry {
}
interface ElsaActivityEditorModal {
}
interface ElsaActivityPickerModal {
@ -279,6 +365,12 @@ declare namespace LocalJSX {
"propertyDescriptor"?: ActivityPropertyDescriptor;
"propertyModel"?: ActivityDefinitionProperty;
}
interface ElsaConfirmDialog {
}
interface ElsaContextMenu {
"history"?: RouterHistory;
"menuItems"?: Array<MenuItem>;
}
interface ElsaDesignerTree {
"model"?: WorkflowModel;
"onWorkflow-changed"?: (event: CustomEvent<WorkflowModel>) => void;
@ -353,11 +445,15 @@ declare namespace LocalJSX {
}
interface ElsaToastNotification {
}
interface ElsaWorkflowEditor {
interface ElsaWorkflowDefinitionEditorScreen {
"monacoLibPath"?: string;
"onWorkflowSaved"?: (event: CustomEvent<WorkflowDefinition>) => void;
"serverUrl"?: string;
"workflowDefinitionId"?: string;
}
interface ElsaWorkflowDefinitionsListScreen {
"serverUrl"?: string;
}
interface ElsaWorkflowEditorNotifications {
}
interface ElsaWorkflowPublishButton {
@ -372,10 +468,16 @@ declare namespace LocalJSX {
"workflowDefinition"?: WorkflowDefinition;
}
interface IntrinsicElements {
"app-root": AppRoot;
"app-workflow-definitions-edit": AppWorkflowDefinitionsEdit;
"app-workflow-definitions-list": AppWorkflowDefinitionsList;
"app-workflow-registry": AppWorkflowRegistry;
"elsa-activity-editor-modal": ElsaActivityEditorModal;
"elsa-activity-picker-modal": ElsaActivityPickerModal;
"elsa-check-list-property": ElsaCheckListProperty;
"elsa-checkbox-property": ElsaCheckboxProperty;
"elsa-confirm-dialog": ElsaConfirmDialog;
"elsa-context-menu": ElsaContextMenu;
"elsa-designer-tree": ElsaDesignerTree;
"elsa-designer-tree-activity": ElsaDesignerTreeActivity;
"elsa-dropdown-property": ElsaDropdownProperty;
@ -389,7 +491,8 @@ declare namespace LocalJSX {
"elsa-script-property": ElsaScriptProperty;
"elsa-single-line-property": ElsaSingleLineProperty;
"elsa-toast-notification": ElsaToastNotification;
"elsa-workflow-editor": ElsaWorkflowEditor;
"elsa-workflow-definition-editor-screen": ElsaWorkflowDefinitionEditorScreen;
"elsa-workflow-definitions-list-screen": ElsaWorkflowDefinitionsListScreen;
"elsa-workflow-editor-notifications": ElsaWorkflowEditorNotifications;
"elsa-workflow-publish-button": ElsaWorkflowPublishButton;
"elsa-workflow-settings-modal": ElsaWorkflowSettingsModal;
@ -399,10 +502,16 @@ export { LocalJSX as JSX };
declare module "@stencil/core" {
export namespace JSX {
interface IntrinsicElements {
"app-root": LocalJSX.AppRoot & JSXBase.HTMLAttributes<HTMLAppRootElement>;
"app-workflow-definitions-edit": LocalJSX.AppWorkflowDefinitionsEdit & JSXBase.HTMLAttributes<HTMLAppWorkflowDefinitionsEditElement>;
"app-workflow-definitions-list": LocalJSX.AppWorkflowDefinitionsList & JSXBase.HTMLAttributes<HTMLAppWorkflowDefinitionsListElement>;
"app-workflow-registry": LocalJSX.AppWorkflowRegistry & JSXBase.HTMLAttributes<HTMLAppWorkflowRegistryElement>;
"elsa-activity-editor-modal": LocalJSX.ElsaActivityEditorModal & JSXBase.HTMLAttributes<HTMLElsaActivityEditorModalElement>;
"elsa-activity-picker-modal": LocalJSX.ElsaActivityPickerModal & JSXBase.HTMLAttributes<HTMLElsaActivityPickerModalElement>;
"elsa-check-list-property": LocalJSX.ElsaCheckListProperty & JSXBase.HTMLAttributes<HTMLElsaCheckListPropertyElement>;
"elsa-checkbox-property": LocalJSX.ElsaCheckboxProperty & JSXBase.HTMLAttributes<HTMLElsaCheckboxPropertyElement>;
"elsa-confirm-dialog": LocalJSX.ElsaConfirmDialog & JSXBase.HTMLAttributes<HTMLElsaConfirmDialogElement>;
"elsa-context-menu": LocalJSX.ElsaContextMenu & JSXBase.HTMLAttributes<HTMLElsaContextMenuElement>;
"elsa-designer-tree": LocalJSX.ElsaDesignerTree & JSXBase.HTMLAttributes<HTMLElsaDesignerTreeElement>;
"elsa-designer-tree-activity": LocalJSX.ElsaDesignerTreeActivity & JSXBase.HTMLAttributes<HTMLElsaDesignerTreeActivityElement>;
"elsa-dropdown-property": LocalJSX.ElsaDropdownProperty & JSXBase.HTMLAttributes<HTMLElsaDropdownPropertyElement>;
@ -416,7 +525,8 @@ declare module "@stencil/core" {
"elsa-script-property": LocalJSX.ElsaScriptProperty & JSXBase.HTMLAttributes<HTMLElsaScriptPropertyElement>;
"elsa-single-line-property": LocalJSX.ElsaSingleLineProperty & JSXBase.HTMLAttributes<HTMLElsaSingleLinePropertyElement>;
"elsa-toast-notification": LocalJSX.ElsaToastNotification & JSXBase.HTMLAttributes<HTMLElsaToastNotificationElement>;
"elsa-workflow-editor": LocalJSX.ElsaWorkflowEditor & JSXBase.HTMLAttributes<HTMLElsaWorkflowEditorElement>;
"elsa-workflow-definition-editor-screen": LocalJSX.ElsaWorkflowDefinitionEditorScreen & JSXBase.HTMLAttributes<HTMLElsaWorkflowDefinitionEditorScreenElement>;
"elsa-workflow-definitions-list-screen": LocalJSX.ElsaWorkflowDefinitionsListScreen & JSXBase.HTMLAttributes<HTMLElsaWorkflowDefinitionsListScreenElement>;
"elsa-workflow-editor-notifications": LocalJSX.ElsaWorkflowEditorNotifications & JSXBase.HTMLAttributes<HTMLElsaWorkflowEditorNotificationsElement>;
"elsa-workflow-publish-button": LocalJSX.ElsaWorkflowPublishButton & JSXBase.HTMLAttributes<HTMLElsaWorkflowPublishButtonElement>;
"elsa-workflow-settings-modal": LocalJSX.ElsaWorkflowSettingsModal & JSXBase.HTMLAttributes<HTMLElsaWorkflowSettingsModalElement>;

View file

@ -0,0 +1,73 @@
import {Component, h, Prop} from '@stencil/core';
import {RouterHistory} from "@stencil/router";
import {leave, toggle} from 'el-transition'
import {registerClickOutside} from "stencil-click-outside";
import {MenuItem} from "./models";
@Component({
tag: 'elsa-context-menu',
styleUrl: 'elsa-context-menu.css',
shadow: false,
})
export class AppContextMenu {
@Prop() history: RouterHistory;
@Prop() menuItems: Array<MenuItem> = [];
contextMenu: HTMLElement;
closeContextMenu() {
leave(this.contextMenu);
}
toggleMenu() {
toggle(this.contextMenu);
}
async onMenuItemClick(e: Event, menuItem: MenuItem) {
e.preventDefault();
if (!!menuItem.anchorUrl)
this.history.push(menuItem.anchorUrl);
else if (!!menuItem.clickHandler) {
menuItem.clickHandler(e);
}
this.closeContextMenu();
}
render() {
return (
<div class="relative flex justify-end items-center" ref={el => registerClickOutside(this, el, this.closeContextMenu)}>
<button onClick={() => this.toggleMenu()} aria-has-popup="true" type="button"
class="w-8 h-8 inline-flex items-center justify-center text-gray-400 rounded-full bg-transparent hover:text-gray-500 focus:outline-none focus:text-gray-500 focus:bg-gray-100 transition ease-in-out duration-150">
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"/>
</svg>
</button>
<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 z-10 mx-3 origin-top-right absolute right-7 top-0 w-48 mt-1 rounded-md shadow-lg">
<div class="rounded-md bg-white shadow-xs" role="menu" aria-orientation="vertical" aria-labelledby="project-options-menu-0">
{this.menuItems.map(menuItem => {
const anchorUrl = menuItem.anchorUrl || '#';
return (
<div class="py-1">
<a href={anchorUrl}
onClick={e => this.onMenuItemClick(e, menuItem)}
class="group flex items-center px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">
{menuItem.icon ? <span class="mr-3 ">{menuItem.icon}</span> : undefined}
{menuItem.text}
</a>
</div>
);
})}
</div>
</div>
</div>
);
}
}

View file

@ -0,0 +1,6 @@
export interface MenuItem {
text: string;
anchorUrl?: string;
clickHandler?: (e: Event) => void;
icon?: any;
}

View file

@ -1,6 +1,6 @@
import {Component, Host, h, Prop, State, Listen, Method, Watch, Event, EventEmitter} from '@stencil/core';
import {initializeMonacoWorker} from "./elsa-monaco-utils";
import state from '../../../../utils/store';
import state from '../../../utils/store';
// Until I figure out why the ESM loader doesn't work properly, we need to include these scripts manually from index.html
// import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';

View file

@ -0,0 +1,15 @@
header {
background: #5851ff;
color: white;
height: 56px;
display: flex;
align-items: center;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26);
}
h1 {
font-size: 1.4rem;
font-weight: 500;
color: #fff;
padding: 0 12px;
}

View file

@ -0,0 +1,62 @@
import {Component, h, Prop} from '@stencil/core';
import Tunnel, {DashboardState} from '../../../../data/dashboard';
@Component({
tag: 'app-root',
styleUrl: 'app-root.css',
shadow: false,
})
export class AppRoot {
@Prop({attribute: 'server-url', reflect: true}) serverUrl: string;
render() {
const serverUrl = this.serverUrl;
// TODO: Tunneling doesn't appear to be working in combination with the router.
// const tunnelState: DashboardState = {
// serverUrl: serverUrl,
// };
return (
<div class="h-screen bg-gray-100">
<nav class="bg-gray-800">
<div class="px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-16">
<div class="flex items-center">
<div class="flex-shrink-0">
<img class="h-8 w-8" src="/build/assets/images/logo.png" alt="Workflow"/>
</div>
<div class="hidden md:block">
<div class="ml-10 flex items-baseline space-x-4">
<stencil-route-link url="/workflow-definitions" anchorClass="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium" activeClass="text-white bg-gray-900">Workflow Definitions
</stencil-route-link>
<stencil-route-link url="/workflow-instances" anchorClass="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium" activeClass="text-white bg-gray-900">Workflow Instances
</stencil-route-link>
<stencil-route-link url="/workflow-registry" anchorClass="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium" activeClass="text-white bg-gray-900">Workflow Registry
</stencil-route-link>
<stencil-route-link url="/custom-activities" class="text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">Custom Activities</stencil-route-link>
</div>
</div>
</div>
</div>
</div>
</nav>
<main>
{/*<Tunnel.Provider state={tunnelState}>*/}
<stencil-router>
<stencil-route-switch scrollTopOffset={0}>
<stencil-route url="/" routeRender={props => <stencil-route-redirect url="/workflow-registry"/>} exact={true}/>
<stencil-route url="/workflow-registry" component="app-workflow-registry"/>
<stencil-route url="/workflow-definitions" component="app-workflow-definitions-list" componentProps={{'serverUrl': serverUrl}} exact={true}/>
<stencil-route url="/workflow-definitions/:id" component="app-workflow-definitions-edit" componentProps={{'serverUrl': serverUrl}} />
</stencil-route-switch>
</stencil-router>
{/*</Tunnel.Provider>*/}
</main>
</div>
);
}
}

View file

@ -0,0 +1,31 @@
import {Component, Prop, h} from '@stencil/core';
import {MatchResults} from '@stencil/router';
@Component({
tag: 'app-workflow-definitions-edit',
styleUrl: 'app-workflow-definitions-edit.css',
shadow: false,
})
export class AppWorkflowDefinitionsEdit {
@Prop() match: MatchResults;
@Prop() serverUrl: string;
id?: string;
componentWillLoad() {
let id = this.match.params.id;
if (id == 'new')
id = null;
this.id = id;
}
render() {
const id = this.id;
return <div>
<elsa-workflow-definition-editor-screen server-url={this.serverUrl} workflow-definition-id={id} monaco-lib-path="build/assets/js"/>
</div>;
}
}

View file

@ -0,0 +1,33 @@
import {Component, h, Prop, State} from '@stencil/core';
@Component({
tag: 'app-workflow-definitions-list',
styleUrl: 'app-workflow-definitions-list.css',
shadow: false,
})
export class AppWorkflowDefinitionsList {
@Prop() serverUrl: string;
render() {
return (
<div>
<div class="border-b border-gray-200 px-4 py-4 sm:flex sm:items-center sm:justify-between sm:px-6 lg:px-8 bg-white">
<div class="flex-1 min-w-0">
<h1 class="text-lg font-medium leading-6 text-gray-900 sm:truncate">
Workflow Definitions
</h1>
</div>
<div class="mt-4 flex sm:mt-0 sm:ml-4">
<stencil-route-link url="/workflow-definitions/new"
class="order-0 inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:order-1 sm:ml-3">
Create Workflow
</stencil-route-link>
</div>
</div>
<elsa-workflow-definitions-list-screen serverUrl={this.serverUrl} />
</div>
);
}
}

View file

@ -0,0 +1,69 @@
import {Component, h} from '@stencil/core';
@Component({
tag: 'app-workflow-registry',
styleUrl: 'app-workflow-registry.css',
shadow: false,
})
export class AppHome {
render() {
return (
<div>
<div class="border-b border-gray-200 px-4 py-4 sm:flex sm:items-center sm:justify-between sm:px-6 lg:px-8 bg-white">
<div class="flex-1 min-w-0">
<h1 class="text-lg font-medium leading-6 text-gray-900 sm:truncate">
Workflow Registry
</h1>
</div>
<div class="mt-4 flex sm:mt-0 sm:ml-4">
<stencil-route-link url="/workflow-definitions/new" class="order-0 inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:order-1 sm:ml-3">
Create Workflow
</stencil-route-link>
</div>
</div>
<div class="align-middle inline-block min-w-full border-b border-gray-200">
<table class="min-w-full">
<thead>
<tr class="border-t border-gray-200">
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider"><span class="lg:pl-2">Name</span></th>
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
Instances
</th>
<th class="hidden md:table-cell px-6 py-3 border-b border-gray-200 bg-gray-50 text-right text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
Latest Version
</th>
<th class="hidden md:table-cell px-6 py-3 border-b border-gray-200 bg-gray-50 text-right text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
Published Version
</th>
<th class="pr-6 py-3 border-b border-gray-200 bg-gray-50 text-right text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider"/>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-100">
<tr>
<td class="px-6 py-3 whitespace-no-wrap text-sm leading-5 font-medium text-gray-900">
<div class="flex items-center space-x-3 lg:pl-2"><a href="/workflow-registry/7eed1333c2674370a1e9b300aa94fda2/viewer" class="truncate hover:text-gray-600"><span>Customer Call Flow</span></a></div>
</td>
<td class="px-6 py-3 text-sm leading-5 text-gray-500 font-medium">
<div class="flex items-center space-x-2">
<div class="flex items-center space-x-2">
<div class="flex flex-shrink-0 -space-x-1"><a class="max-w-none h-9 w-9 rounded-full text-white shadow-solid p-2 text-xs bg-blue-500 hover:bg-blue-400" href="#">4</a>
<a class="max-w-none h-9 w-9 rounded-full text-white shadow-solid p-2 text-xs bg-green-500 hover:bg-green-400" href="#">999</a>
<a class="max-w-none h-9 w-9 rounded-full text-white shadow-solid p-2 text-xs bg-red-500 hover:bg-red-400" href="#">75</a></div>
</div>
</div>
</td>
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-right">47</td>
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-right">47</td>
<td class="pr-6">
<elsa-context-menu/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
);
}
}

View file

@ -1,9 +1,7 @@
import {Component, Event, EventEmitter, h, Host, Prop, State} from '@stencil/core';
import {leave, toggle} from 'el-transition'
import {registerClickOutside} from "stencil-click-outside";
import {ActivityIcon} from '../../../icons/activity-icon';
import {ActivityModel, ActivityDesignDisplayContext, EventTypes} from "../../../../models";
import {eventBus} from '../../../../services/event-bus';
@Component({
tag: 'elsa-designer-tree-activity',
@ -49,8 +47,8 @@ export class ElsaDesignerTreeActivity {
const displayName = activity.displayName && activity.displayName.length > 0 ? activity.displayName : activity.name && activity.name.length > 0 ? activity.name : activity.type
return (
<Host id={`activity-${activityId}`}
onDblClick={e => this.onEditActivityClick(e)}
<Host id={`activity-${activityId}`}
onDblClick={e => this.onEditActivityClick(e)}
class="activity border-2 border-solid border-white rounded bg-white text-left text-black text-lg hover:border-blue-600 select-none max-w-md shadow-sm relative">
<div class="p-5">
<div class="flex justify-between space-x-8">

View file

@ -3,7 +3,7 @@
}
.workflow-canvas {
background-image: url(build/assets/tile.png);
background-image: url(/build/assets/tile.png);
background-repeat: repeat;
background-size: 30px 30px;
background-color: #FBFBFB;

View file

@ -1,7 +1,7 @@
import {Component, EventEmitter, h, Host, Method, Prop, State, Watch, Event} from '@stencil/core';
import {createElsaClient} from "../../../../services/elsa-client";
import {createElsaClient} from "../../../services/elsa-client";
import Tunnel from '../../../data/workflow-editor';
import {MonacoValueChangedArgs} from "../../monaco/elsa-monaco/elsa-monaco";
import {MonacoValueChangedArgs} from "../../controls/elsa-monaco/elsa-monaco";
@Component({
tag: 'elsa-expression-editor',

View file

@ -1,5 +1,5 @@
import {Component, h, Prop, State, Event, EventEmitter} from '@stencil/core';
import {ActivityDefinitionProperty, ActivityPropertyDescriptor, SyntaxNames} from "../../../../models";
import {ActivityDefinitionProperty, ActivityPropertyDescriptor, SyntaxNames} from "../../../models";
import {registerClickOutside} from "stencil-click-outside";
import {enter, leave, toggle} from 'el-transition'

View file

@ -1,8 +1,8 @@
import {Component, h, Prop, State} from '@stencil/core';
import {ActivityDefinitionProperty, ActivityPropertyDescriptor} from "../../../../models";
import {createElsaClient} from "../../../../services/elsa-client";
import Tunnel from '../../../data/workflow-editor';
import {MonacoValueChangedArgs} from "../../monaco/elsa-monaco/elsa-monaco";
import Tunnel from '../../../../data/workflow-editor';
import {MonacoValueChangedArgs} from "../../../controls/elsa-monaco/elsa-monaco";
@Component({
tag: 'elsa-script-property',

View file

@ -4,20 +4,21 @@ import {ActivityDefinition, ActivityDescriptor, ActivityModel, ConnectionDefinit
import {createElsaClient, SaveWorkflowDefinitionRequest} from "../../../../services/elsa-client";
import {pluginManager} from '../../../../services/plugin-manager';
import state from '../../../../utils/store';
import Tunnel, {WorkflowEditorState} from '../../../data/workflow-editor';
import Tunnel, {WorkflowEditorState} from '../../../../data/workflow-editor';
import {downloadFromBlob} from "../../../../utils/download";
@Component({
tag: 'elsa-workflow-editor',
styleUrl: 'elsa-workflow-editor.css',
tag: 'elsa-workflow-definition-editor-screen',
styleUrl: 'elsa-workflow-definition-editor-screen.css',
shadow: false,
})
export class ElsaWorkflowDefinitionEditor {
export class ElsaWorkflowDefinitionEditorScreen {
constructor() {
pluginManager.initialize();
}
@Event() workflowSaved: EventEmitter<WorkflowDefinition>;
@Prop({attribute: 'workflow-definition-id', reflect: true}) workflowDefinitionId: string;
@Prop({attribute: 'server-url', reflect: true}) serverUrl: string;
@Prop({attribute: 'monaco-lib-path', reflect: true}) monacoLibPath: string;
@ -213,6 +214,7 @@ export class ElsaWorkflowDefinitionEditor {
this.saved = !publish;
this.publishing = false;
setTimeout(() => this.saved = false, 500);
this.workflowSaved.emit(workflowDefinition);
} catch (e) {
console.error(e);
this.saving = false;
@ -343,7 +345,7 @@ export class ElsaWorkflowDefinitionEditor {
renderWorkflowSettingsButton() {
return (
<button onClick={() => this.onShowWorkflowSettingsClick()} type="button"
class="workflow-settings-button fixed top-10 right-12 inline-flex items-center p-2 rounded-full border border-transparent bg-white shadow text-gray-400 hover:text-blue-500 focus:text-blue-500 hover:ring-2 hover:ring-offset-2 hover:ring-blue-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
class="workflow-settings-button fixed top-20 right-12 inline-flex items-center p-2 rounded-full border border-transparent bg-white shadow text-gray-400 hover:text-blue-500 focus:text-blue-500 hover:ring-2 hover:ring-offset-2 hover:ring-blue-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke="currentColor" fill="none" class="h-8 w-8">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>

View file

@ -3,7 +3,7 @@ import {enter, leave, toggle} from 'el-transition'
import {registerClickOutside} from "stencil-click-outside";
import {VersionOptions, WorkflowDefinition} from "../../../../models";
import {createElsaClient} from "../../../../services/elsa-client";
import Tunnel from '../../../data/workflow-editor';
import Tunnel from '../../../../data/workflow-editor';
@Component({
tag: 'elsa-workflow-publish-button',

View file

@ -2,7 +2,7 @@ import {Component, Event, h, Host, Prop, State, Watch} from '@stencil/core';
import {eventBus} from "../../../../services/event-bus";
import {Map} from "../../../../utils/utils";
import {EventTypes, Variables, WorkflowContextFidelity, WorkflowContextOptions, WorkflowDefinition} from "../../../../models";
import {MonacoValueChangedArgs} from "../../monaco/elsa-monaco/elsa-monaco";
import {MonacoValueChangedArgs} from "../../../controls/elsa-monaco/elsa-monaco";
import {MarkerSeverity} from "monaco-editor";
import {checkBox, FormContext, selectField, SelectOption, textArea, textInput} from "../../../../utils/forms";

View file

@ -0,0 +1,140 @@
import {Component, h, Prop, State} from '@stencil/core';
import * as collection from 'lodash/collection';
import * as object from 'lodash/object';
import {createElsaClient} from "../../../../services/elsa-client";
import {PagedList, VersionOptions, WorkflowDefinitionSummary} from "../../../../models";
@Component({
tag: 'elsa-workflow-definitions-list-screen',
styleUrl: 'elsa-workflow-definitions-list-screen.css',
shadow: false,
})
export class ElsaWorkflowDefinitionsListScreen {
@Prop() serverUrl: string;
@State() workflowDefinitions: PagedList<WorkflowDefinitionSummary> = {items: [], page: 1, pageSize: 50, totalCount: 0};
confirmDialog: HTMLElsaConfirmDialogElement;
async componentWillLoad() {
await this.loadWorkflowDefinitions();
}
async onDeleteClick(e: Event, workflowDefinition: WorkflowDefinitionSummary) {
const result = await this.confirmDialog.show('Delete Workflow Definition', 'Are you sure you wish to permanently delete this workflow, including all of its workflow instances?');
if (!result)
return;
const elsaClient = this.createClient();
await elsaClient.workflowDefinitionsApi.delete(workflowDefinition.definitionId);
await this.loadWorkflowDefinitions();
}
async loadWorkflowDefinitions() {
const elsaClient = this.createClient();
const page = 0;
const pageSize = 50;
const versionOptions: VersionOptions = {allVersions: true};
this.workflowDefinitions = await elsaClient.workflowDefinitionsApi.list(page, pageSize, versionOptions);
}
createClient() {
return createElsaClient(this.serverUrl);
}
render() {
const workflowDefinitions = this.workflowDefinitions.items;
const groupings = collection.groupBy(workflowDefinitions, 'definitionId');
return (
<div>
<div class="align-middle inline-block min-w-full border-b border-gray-200">
<table class="min-w-full">
<thead>
<tr class="border-t border-gray-200">
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider"><span class="lg:pl-2">Name</span></th>
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
Instances
</th>
<th class="hidden md:table-cell px-6 py-3 border-b border-gray-200 bg-gray-50 text-right text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
Latest Version
</th>
<th class="hidden md:table-cell px-6 py-3 border-b border-gray-200 bg-gray-50 text-right text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
Published Version
</th>
<th class="pr-6 py-3 border-b border-gray-200 bg-gray-50 text-right text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider"/>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-100">
{collection.map(groupings, group => {
const versions = collection.orderBy(group, 'version', 'desc');
const workflowDefinition = versions[0]
const latestVersionNumber = workflowDefinition.version;
const publishedVersion: WorkflowDefinitionSummary = versions.find(x => x.isPublished);
const publishedVersionNumber = !!publishedVersion ? publishedVersion.version : '-';
let workflowDisplayName = workflowDefinition.displayName;
if (!workflowDisplayName || workflowDisplayName.trim().length == 0)
workflowDisplayName = workflowDefinition.name;
if (!workflowDisplayName || workflowDisplayName.trim().length == 0)
workflowDisplayName = 'Untitled';
const editUrl = `/workflow-definitions/${workflowDefinition.definitionId}`;
const editIcon = (
<svg class="h-5 w-5 text-gray-500" width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
);
const deleteIcon = (
<svg class="h-5 w-5 text-gray-500" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z"/>
<line x1="4" y1="7" x2="20" y2="7"/>
<line x1="10" y1="11" x2="10" y2="17"/>
<line x1="14" y1="11" x2="14" y2="17"/>
<path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12"/>
<path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3"/>
</svg>
);
return (
<tr>
<td class="px-6 py-3 whitespace-no-wrap text-sm leading-5 font-medium text-gray-900">
<div class="flex items-center space-x-3 lg:pl-2">
<stencil-route-link url={editUrl} anchorClass="truncate hover:text-gray-600"><span>{workflowDisplayName}</span></stencil-route-link>
</div>
</td>
<td class="px-6 py-3 text-sm leading-5 text-gray-500 font-medium">
<div class="flex items-center space-x-2">
<div class="flex items-center space-x-2">
<div class="flex flex-shrink-0 -space-x-1"><a class="max-w-none h-9 w-9 rounded-full text-white shadow-solid p-2 text-xs bg-blue-500 hover:bg-blue-400" href="#">4</a>
<a class="max-w-none h-9 w-9 rounded-full text-white shadow-solid p-2 text-xs bg-green-500 hover:bg-green-400" href="#">999</a>
<a class="max-w-none h-9 w-9 rounded-full text-white shadow-solid p-2 text-xs bg-red-500 hover:bg-red-400" href="#">75</a></div>
</div>
</div>
</td>
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-right">{latestVersionNumber}</td>
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-right">{publishedVersionNumber}</td>
<td class="pr-6">
<elsa-context-menu menuItems={[
{text: 'Edit', anchorUrl: editUrl, icon: editIcon},
{text: 'Delete', clickHandler: e => this.onDeleteClick(e, workflowDefinition), icon: deleteIcon}
]}/>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
<elsa-confirm-dialog ref={el => this.confirmDialog = el}/>
</div>
);
}
}

View file

@ -0,0 +1,97 @@
import {Component, h, Host, Method, State} from '@stencil/core';
@Component({
tag: 'elsa-confirm-dialog',
shadow: false,
})
export class ElsaConfirmDialog {
@State() caption: string;
@State() message: string;
dialog: HTMLElsaModalDialogElement;
fulFill: (value: (PromiseLike<boolean> | boolean)) => void;
reject: () => void;
@Method()
async show(caption: string, message: string): Promise<boolean> {
this.caption = caption;
this.message = message;
await this.dialog.show(true);
return new Promise<boolean>((fulfill, reject) => {
this.fulFill = fulfill;
this.reject = reject;
});
}
@Method()
async hide() {
await this.dialog.hide(true);
}
async onDismissClick() {
this.fulFill(false);
await this.hide();
}
async onAcceptClick() {
this.fulFill(true);
this.fulFill = null;
this.reject = null;
await this.hide();
}
render() {
return (
<Host>
<elsa-modal-dialog ref={el => this.dialog = el}>
<div slot="content" class="py-8 px-4">
<div class="hidden sm:block absolute top-0 right-0 pt-4 pr-4">
<button type="button"
onClick={() => this.onDismissClick()}
class="bg-white rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
<span class="sr-only">Close</span>
<svg class="h-6 w-6" x-description="Heroicon name: outline/x" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<div class="sm:flex sm:items-start">
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<svg class="h-6 w-6 text-red-600" x-description="Heroicon name: outline/exclamation" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
</svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-title">
{this.caption}
</h3>
<div class="mt-2">
<p class="text-sm text-gray-500">
{this.message}
</p>
</div>
</div>
</div>
</div>
<div slot="buttons">
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button type="button"
onClick={() => this.onAcceptClick()}
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">
Yes
</button>
<button type="button"
onClick={() => this.onDismissClick()}
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:w-auto sm:text-sm">
No
</button>
</div>
</div>
</elsa-modal-dialog>
</Host>
);
}
}

View file

@ -0,0 +1,13 @@
import {createProviderConsumer} from "@stencil/state-tunnel";
import {h} from "@stencil/core";
export interface DashboardState {
serverUrl: string;
}
export default createProviderConsumer<DashboardState>(
{
serverUrl: null
},
(subscribe, child) => (<context-consumer subscribe={subscribe} renderer={child}/>)
);

View file

@ -7,14 +7,16 @@
<link rel="stylesheet" href="/build/assets/fonts/inter/inter.css">
<!-- Including a pre-built tailwind.css file for development. Don't include this when using a production build, since tailwind would already be included -->
<link href="/build/assets/tailwind.css" rel="stylesheet">
<link href="/build/assets/styles/tailwind.css" rel="stylesheet">
<script src="/build/assets/js/monaco-editor/min/vs/loader.js"></script>
<script type="module" src="/build/elsa-workflows-studio.esm.js"></script>
<script nomodule src="/build/elsa-workflows-studio.js"></script>
</head>
<body class="h-screen" style="background-size: 30px 30px; background-image: url(/build/assets/tile.png); background-color: #FBFBFB;">
<elsa-workflow-editor id="workflowEditor" server-url="https://localhost:11000/" workflow-definition-id="7eed1333c2674370a1e9b300aa94fda2" monaco-lib-path="build/assets/js"></elsa-workflow-editor>
<!--<elsa-workflow-editor id="workflowEditor" server-url="https://localhost:11000/" workflow-definition-id="7eed1333c2674370a1e9b300aa94fda2" monaco-lib-path="build/assets/js"></elsa-workflow-editor>-->
<app-root server-url="https://localhost:11000/"></app-root>
</body>
</html>

View file

@ -1 +1,2 @@
export { Components, JSX } from './components';
import '@stencil/router';

View file

@ -20,6 +20,20 @@ export interface WorkflowDefinition {
connections: Array<ConnectionDefinition>;
}
export interface WorkflowDefinitionSummary {
id?: string;
definitionId?: string;
tenantId?: string;
name?: string;
displayName?: string;
description?: string;
version: number;
isSingleton?: boolean;
persistenceBehavior?: WorkflowPersistenceBehavior;
isPublished?: boolean;
isLatest?: boolean;
}
export interface ActivityDefinition {
activityId: string;
type: string;
@ -75,17 +89,21 @@ export interface VersionOptions {
version?: number;
}
export const getVersionOptionsString = (versionOptions: VersionOptions) => {
export const getVersionOptionsString = (versionOptions?: VersionOptions) => {
if(!versionOptions)
return '';
return versionOptions.allVersions
? "AllVersions"
? 'AllVersions'
: versionOptions.isDraft
? "Draft"
? 'Draft'
: versionOptions.isLatest
? "Latest"
? 'Latest'
: versionOptions.isPublished
? "Published"
? 'Published'
: versionOptions.isLatestOrPublished
? "LatestOrPublished"
? 'LatestOrPublished'
: versionOptions.version.toString();
};
@ -112,6 +130,13 @@ export interface ActivityPropertyDescriptor {
supportedSyntaxes: Array<string>
}
export interface PagedList<T> {
items: Array<T>;
page: number;
pageSize: number;
totalCount: number;
}
export enum ActivityTraits {
Action = 1,
Trigger = 2,

View file

@ -1,5 +1,17 @@
import axios, {AxiosRequestConfig} from "axios";
import {ActivityDefinition, ActivityDescriptor, ConnectionDefinition, getVersionOptionsString, Variables, VersionOptions, WorkflowContextOptions, WorkflowDefinition, WorkflowPersistenceBehavior} from "../models";
import {
ActivityDefinition,
ActivityDescriptor,
ConnectionDefinition,
getVersionOptionsString,
PagedList,
Variables,
VersionOptions,
WorkflowContextOptions,
WorkflowDefinition,
WorkflowDefinitionSummary,
WorkflowPersistenceBehavior
} from "../models";
export const createElsaClient = function (serverUrl: string): ElsaClient {
const config: AxiosRequestConfig = {
@ -16,6 +28,11 @@ export const createElsaClient = function (serverUrl: string): ElsaClient {
}
},
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}`);
@ -25,6 +42,9 @@ export const createElsaClient = function (serverUrl: string): ElsaClient {
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;
@ -78,8 +98,12 @@ export interface ActivitiesApi {
export interface WorkflowDefinitionsApi {
getByDefinitionAndVersion(definitionId: string, versionOptions: VersionOptions): Promise<WorkflowDefinition>
list(page?: number, pageSize?: number, versionOptions?: VersionOptions): Promise<PagedList<WorkflowDefinitionSummary>>
save(request: SaveWorkflowDefinitionRequest): Promise<WorkflowDefinition>
delete(definitionId: string): Promise<void>
retract(workflowDefinitionId: string): Promise<WorkflowDefinition>
export(workflowDefinitionId: string, versionOptions: VersionOptions): Promise<ExportWorkflowResponse>

View file

@ -1,6 +1,8 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AutoMapper;
using Elsa.Models;
using Elsa.Persistence;
using Elsa.Persistence.Specifications;
@ -23,30 +25,33 @@ namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions
{
private readonly IWorkflowDefinitionStore _workflowDefinitionStore;
private readonly IContentSerializer _serializer;
private readonly IMapper _mapper;
public List(IWorkflowDefinitionStore workflowDefinitionStore, IContentSerializer serializer)
public List(IWorkflowDefinitionStore workflowDefinitionStore, IContentSerializer serializer, IMapper mapper)
{
_workflowDefinitionStore = workflowDefinitionStore;
_serializer = serializer;
_mapper = mapper;
}
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(PagedList<WorkflowDefinition>))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(PagedList<WorkflowDefinitionSummaryModel>))]
[SwaggerResponseExample(StatusCodes.Status200OK, typeof(WorkflowDefinitionPagedListExample))]
[SwaggerOperation(
Summary = "Returns a list of workflow definitions.",
Description = "Returns paginated a list of workflow definitions. When no version options are specified, the latest versions are returned.",
Description = "Returns paginated a list of workflow definition summaries. When no version options are specified, the latest versions are returned.",
OperationId = "WorkflowDefinitions.List",
Tags = new[] { "WorkflowDefinitions" })
]
public async Task<ActionResult<PagedList<WorkflowDefinition>>> Handle(int? page = default, int? pageSize = default, VersionOptions? version = default, CancellationToken cancellationToken = default)
public async Task<ActionResult<PagedList<WorkflowDefinitionSummaryModel>>> Handle(int? page = default, int? pageSize = default, VersionOptions? version = default, CancellationToken cancellationToken = default)
{
version ??= VersionOptions.Latest;
var specification = new VersionOptionsSpecification(version.Value);
var totalCount = await _workflowDefinitionStore.CountAsync(specification, cancellationToken: cancellationToken);
var totalCount = await _workflowDefinitionStore.CountAsync(specification, cancellationToken);
var paging = page == null || pageSize == null ? default : Paging.Page(page.Value, pageSize.Value);
var items = await _workflowDefinitionStore.FindManyAsync(specification, paging: paging, cancellationToken: cancellationToken);
var pagedList = new PagedList<WorkflowDefinition>(items.ToList(), page, pageSize, totalCount);
var summaries = _mapper.Map<IList<WorkflowDefinitionSummaryModel>>(items);
var pagedList = new PagedList<WorkflowDefinitionSummaryModel>(summaries, page, pageSize, totalCount);
return Json(pagedList, _serializer.GetSettings());
}

View file

@ -0,0 +1,16 @@
using Elsa.Models;
namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions
{
public record WorkflowDefinitionSummaryModel(
string Id,
string DefinitionId,
string? Name,
string? DisplayName,
string? Description,
int Version,
bool IsSingleton,
WorkflowPersistenceBehavior PersistenceBehavior,
bool IsPublished,
bool IsLatest);
}

View file

@ -1,5 +1,6 @@
using AutoMapper;
using Elsa.Models;
using Elsa.Server.Api.Endpoints.WorkflowDefinitions;
using Elsa.Server.Api.Endpoints.WorkflowInstances;
using Elsa.Server.Api.Endpoints.WorkflowRegistry;
using Elsa.Services.Models;
@ -16,6 +17,7 @@ namespace Elsa.Server.Api.Mapping
CreateMap<ICompositeActivityBlueprint, CompositeActivityBlueprintModel>();
CreateMap<IConnection, ConnectionModel>().ConvertUsing<ConnectionConverter>();
CreateMap<WorkflowInstance, WorkflowInstanceSummaryModel>();
CreateMap<WorkflowDefinition, WorkflowDefinitionSummaryModel>();
}
}
}

View file

@ -1,5 +1,6 @@
using System;
using Elsa.Activities.Webhooks.Models;
using Elsa.Server.Api.Endpoints.WorkflowDefinitions;
using Swashbuckle.AspNetCore.Filters;
namespace Elsa.Server.Api.Swagger.Examples

View file

@ -0,0 +1,24 @@
using System;
using Elsa.Models;
using Elsa.Server.Api.Endpoints.WorkflowDefinitions;
using Swashbuckle.AspNetCore.Filters;
namespace Elsa.Server.Api.Swagger.Examples
{
public class WorkflowDefinitionSummaryModelExample : IExamplesProvider<WorkflowDefinitionSummaryModel>
{
public WorkflowDefinitionSummaryModel GetExamples() =>
new(
Guid.NewGuid().ToString("N"),
Guid.NewGuid().ToString("N"),
"ProcessOrderWorkflow",
"Process Order Workflow",
"Process new orders",
1,
false,
WorkflowPersistenceBehavior.Suspended,
true,
false
);
}
}

View file

@ -0,0 +1,16 @@
using System.Linq;
using Elsa.Server.Api.Endpoints.WorkflowDefinitions;
using Elsa.Server.Api.Models;
using Swashbuckle.AspNetCore.Filters;
namespace Elsa.Server.Api.Swagger.Examples
{
public class WorkflowDefinitionSummaryModelPagedListExample : IExamplesProvider<PagedList<WorkflowDefinitionSummaryModel>>
{
public PagedList<WorkflowDefinitionSummaryModel> GetExamples()
{
var examples = Enumerable.Range(1, 3).Select(_ => new WorkflowDefinitionSummaryModelExample().GetExamples()).ToList();
return new PagedList<WorkflowDefinitionSummaryModel>(examples, 0, 10, examples.Count);
}
}
}