Add workflow registry screen component and link from definition to instances
This commit is contained in:
parent
e40c66a1fe
commit
22e19545fe
8
Elsa.sln
8
Elsa.sln
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}/>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 =>
|
||||
|
|
|
|||
|
|
@ -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()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue