Add workflow registry screen component and link from definition to instances

This commit is contained in:
Sipke Schoorstra 2021-04-22 22:20:38 +02:00
parent e40c66a1fe
commit 22e19545fe
8 changed files with 180 additions and 50 deletions

View file

@ -261,6 +261,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.Persistence.Ye
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Designer.Components.Web", "src\designer\bindings\aspnet\Elsa.Designer.Components.Web\Elsa.Designer.Components.Web.csproj", "{3DF05CFC-AC77-489F-9A42-C4FDB599AA69}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker", "docker", "{D7F75477-D477-4899-BA3B-67D6B56515B0}"
ProjectSection(SolutionItems) = preProject
docker\docker-compose.yaml = docker\docker-compose.yaml
docker\Dockerfile = docker\Dockerfile
docker\Dockerfile-with-npm = docker\Dockerfile-with-npm
docker\README.md = docker\README.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU

View file

@ -150,6 +150,8 @@ export namespace Components {
"serverUrl": string;
}
interface ElsaStudioWorkflowRegistry {
"history": RouterHistory;
"serverUrl": string;
}
interface ElsaSwitchCasesProperty {
"propertyDescriptor": ActivityPropertyDescriptor;
@ -197,6 +199,10 @@ export namespace Components {
"publishing": boolean;
"workflowDefinition": WorkflowDefinition;
}
interface ElsaWorkflowRegistryListScreen {
"history"?: RouterHistory;
"serverUrl": string;
}
interface ElsaWorkflowSettingsModal {
"workflowDefinition": WorkflowDefinition;
}
@ -418,6 +424,12 @@ declare global {
prototype: HTMLElsaWorkflowPublishButtonElement;
new (): HTMLElsaWorkflowPublishButtonElement;
};
interface HTMLElsaWorkflowRegistryListScreenElement extends Components.ElsaWorkflowRegistryListScreen, HTMLStencilElement {
}
var HTMLElsaWorkflowRegistryListScreenElement: {
prototype: HTMLElsaWorkflowRegistryListScreenElement;
new (): HTMLElsaWorkflowRegistryListScreenElement;
};
interface HTMLElsaWorkflowSettingsModalElement extends Components.ElsaWorkflowSettingsModal, HTMLStencilElement {
}
var HTMLElsaWorkflowSettingsModalElement: {
@ -461,6 +473,7 @@ declare global {
"elsa-workflow-instance-viewer-screen": HTMLElsaWorkflowInstanceViewerScreenElement;
"elsa-workflow-instances-list-screen": HTMLElsaWorkflowInstancesListScreenElement;
"elsa-workflow-publish-button": HTMLElsaWorkflowPublishButtonElement;
"elsa-workflow-registry-list-screen": HTMLElsaWorkflowRegistryListScreenElement;
"elsa-workflow-settings-modal": HTMLElsaWorkflowSettingsModalElement;
}
}
@ -608,6 +621,8 @@ declare namespace LocalJSX {
"serverUrl"?: string;
}
interface ElsaStudioWorkflowRegistry {
"history"?: RouterHistory;
"serverUrl"?: string;
}
interface ElsaSwitchCasesProperty {
"propertyDescriptor"?: ActivityPropertyDescriptor;
@ -651,6 +666,10 @@ declare namespace LocalJSX {
"publishing"?: boolean;
"workflowDefinition"?: WorkflowDefinition;
}
interface ElsaWorkflowRegistryListScreen {
"history"?: RouterHistory;
"serverUrl"?: string;
}
interface ElsaWorkflowSettingsModal {
"workflowDefinition"?: WorkflowDefinition;
}
@ -691,6 +710,7 @@ declare namespace LocalJSX {
"elsa-workflow-instance-viewer-screen": ElsaWorkflowInstanceViewerScreen;
"elsa-workflow-instances-list-screen": ElsaWorkflowInstancesListScreen;
"elsa-workflow-publish-button": ElsaWorkflowPublishButton;
"elsa-workflow-registry-list-screen": ElsaWorkflowRegistryListScreen;
"elsa-workflow-settings-modal": ElsaWorkflowSettingsModal;
}
}
@ -734,6 +754,7 @@ declare module "@stencil/core" {
"elsa-workflow-instance-viewer-screen": LocalJSX.ElsaWorkflowInstanceViewerScreen & JSXBase.HTMLAttributes<HTMLElsaWorkflowInstanceViewerScreenElement>;
"elsa-workflow-instances-list-screen": LocalJSX.ElsaWorkflowInstancesListScreen & JSXBase.HTMLAttributes<HTMLElsaWorkflowInstancesListScreenElement>;
"elsa-workflow-publish-button": LocalJSX.ElsaWorkflowPublishButton & JSXBase.HTMLAttributes<HTMLElsaWorkflowPublishButtonElement>;
"elsa-workflow-registry-list-screen": LocalJSX.ElsaWorkflowRegistryListScreen & JSXBase.HTMLAttributes<HTMLElsaWorkflowRegistryListScreenElement>;
"elsa-workflow-settings-modal": LocalJSX.ElsaWorkflowSettingsModal & JSXBase.HTMLAttributes<HTMLElsaWorkflowSettingsModalElement>;
}
}

View file

@ -47,7 +47,7 @@ export class ElsaStudioRoot {
<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="elsa-studio-workflow-registry"/>
<stencil-route url="/workflow-registry" component="elsa-studio-workflow-registry" componentProps={{'serverUrl': serverUrl}} exact={true}/>
<stencil-route url="/workflow-definitions" component="elsa-studio-workflow-definitions-list" componentProps={{'serverUrl': serverUrl}} exact={true}/>
<stencil-route url="/workflow-definitions/:id" component="elsa-studio-workflow-definitions-edit" componentProps={{'serverUrl': serverUrl}}/>
<stencil-route url="/workflow-instances" component="elsa-studio-workflow-instances-list" componentProps={{'serverUrl': serverUrl}} exact={true}/>

View file

@ -1,10 +1,14 @@
import {Component, h} from '@stencil/core';
import {Component, h, Prop} from '@stencil/core';
import {RouterHistory} from "@stencil/router";
@Component({
tag: 'elsa-studio-workflow-registry',
shadow: false,
})
export class ElsaStudioWorkflowRegistry {
@Prop() history: RouterHistory;
@Prop() serverUrl: string;
render() {
return (
<div>
@ -21,47 +25,7 @@ export class ElsaStudioWorkflowRegistry {
</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>
<elsa-workflow-registry-list-screen history={this.history} serverUrl={this.serverUrl} />
</div>
);
}

View file

@ -70,7 +70,7 @@ export class ElsaWorkflowDefinitionsListScreen {
<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 workflowDefinition: WorkflowDefinitionSummary = versions[0];
const latestVersionNumber = workflowDefinition.version;
const publishedVersion: WorkflowDefinitionSummary = versions.find(x => x.isPublished);
const publishedVersionNumber = !!publishedVersion ? publishedVersion.version : '-';
@ -83,6 +83,7 @@ export class ElsaWorkflowDefinitionsListScreen {
workflowDisplayName = 'Untitled';
const editUrl = `/workflow-definitions/${workflowDefinition.definitionId}`;
const instancesUrl = `/workflow-instances?workflow=${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">
@ -111,14 +112,11 @@ export class ElsaWorkflowDefinitionsListScreen {
</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 class="flex items-center space-x-3 lg:pl-2">
<stencil-route-link url={instancesUrl} anchorClass="truncate hover:text-gray-600">Instances</stencil-route-link>
</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">

View file

@ -0,0 +1,111 @@
import {Component, h, Prop, State} from '@stencil/core';
import * as collection from 'lodash/collection';
import {createElsaClient} from "../../../../services/elsa-client";
import {PagedList, VersionOptions, WorkflowBlueprintSummary} from "../../../../models";
import {RouterHistory} from "@stencil/router";
@Component({
tag: 'elsa-workflow-registry-list-screen',
shadow: false,
})
export class ElsaWorkflowRegistryListScreen {
@Prop() history?: RouterHistory;
@Prop() serverUrl: string;
@State() workflowBlueprints: PagedList<WorkflowBlueprintSummary> = {items: [], page: 1, pageSize: 50, totalCount: 0};
async componentWillLoad() {
await this.loadWorkflowBlueprints();
}
async loadWorkflowBlueprints() {
const elsaClient = this.createClient();
const page = 0;
const pageSize = 50;
const versionOptions: VersionOptions = {allVersions: true};
this.workflowBlueprints = await elsaClient.workflowRegistryApi.list(page, pageSize, versionOptions);
}
createClient() {
return createElsaClient(this.serverUrl);
}
render() {
const workflowBlueprints = this.workflowBlueprints.items;
const groupings = collection.groupBy(workflowBlueprints, 'id');
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 workflowBlueprint: WorkflowBlueprintSummary = versions[0];
const latestVersionNumber = workflowBlueprint.version;
const publishedVersion: WorkflowBlueprintSummary = versions.find(x => x.isPublished);
const publishedVersionNumber = !!publishedVersion ? publishedVersion.version : '-';
let workflowDisplayName = workflowBlueprint.displayName;
if (!workflowDisplayName || workflowDisplayName.trim().length == 0)
workflowDisplayName = workflowBlueprint.name;
if (!workflowDisplayName || workflowDisplayName.trim().length == 0)
workflowDisplayName = 'Untitled';
const editUrl = `/workflow-definitions/${workflowBlueprint.id}`;
const instancesUrl = `/workflow-instances?workflow=${workflowBlueprint.id}`;
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>
);
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-3 lg:pl-2">
<stencil-route-link url={instancesUrl} anchorClass="truncate hover:text-gray-600">Instances</stencil-route-link>
</div>
</td>
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-right">{latestVersionNumber}</td>
<td class="hidden md:table-cell px-6 py-3 whitespace-no-wrap text-sm leading-5 text-gray-500 text-right">{publishedVersionNumber}</td>
<td class="pr-6">
<elsa-context-menu history={this.history} menuItems={[
{text: 'Edit', anchorUrl: editUrl, icon: editIcon},
]}/>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
);
}
}

View file

@ -1,6 +1,7 @@
using Elsa;
using Elsa.Persistence.EntityFramework.Core.Extensions;
using Elsa.Persistence.EntityFramework.Sqlite;
using ElsaDashboard.Samples.AspNetCore.Monolith.Workflows;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
@ -34,10 +35,15 @@ namespace ElsaDashboard.Samples.AspNetCore.Monolith
.AddEmailActivities(elsaSection.GetSection("Smtp").Bind)
.AddQuartzTemporalActivities()
.AddJavaScriptActivities()
.AddWorkflow<HeartbeatWorkflow>()
);
services
.AddElsaApiEndpoints();
// Allow arbitrary client browser apps to access the API for demo purposes only.
// In a production environment, make sure to allow only origins you trust.
services.AddCors(cors => cors.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().WithExposedHeaders("Content-Disposition")));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@ -56,6 +62,7 @@ namespace ElsaDashboard.Samples.AspNetCore.Monolith
app.UseStaticFiles();
app.UseHttpActivities();
app.UseCors();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>

View file

@ -0,0 +1,21 @@
using Elsa.Activities.Console;
using Elsa.Activities.Temporal;
using Elsa.Builders;
using NodaTime;
namespace ElsaDashboard.Samples.AspNetCore.Monolith.Workflows
{
public class HeartbeatWorkflow : IWorkflow
{
private readonly IClock _clock;
public HeartbeatWorkflow(IClock clock) => _clock = clock;
public void Build(IWorkflowBuilder builder)
{
builder
.WithDisplayName("Timer")
.Timer(Duration.FromSeconds(10))
.WriteLine(() => $"Heartbeat at {_clock.GetCurrentInstant()}");
}
}
}