Use as Json Schema implementation

This commit is contained in:
axeleron007 2021-09-15 12:32:41 +03:00
parent 08235ce8d4
commit b11ccbbc2d
19 changed files with 409 additions and 188 deletions

View file

@ -340,10 +340,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.WorkflowSettings.Persi
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.WorkflowSettings.Persistence.EntityFramework.PostgreSql", "src\modules\workflowsettings\Elsa.WorkflowSettings.Persistence.EntityFramework.PostgreSql\Elsa.WorkflowSettings.Persistence.EntityFramework.PostgreSql.csproj", "{F006F3CC-22E9-4FDD-A8D4-2E8CBDFDA54A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ElsaDashboard.Samples.AspNetCore.SubPath", "src\samples\dashboard\aspnetcore\ElsaDashboard.Samples.AspNetCore.SubPath\ElsaDashboard.Samples.AspNetCore.SubPath.csproj", "{BB715C0C-BE88-4F7F-A43F-1F8DFC9A1388}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Samples.MassTransitRabbitMq", "src\samples\aspnet\Elsa.Samples.MassTransitRabbitMq\Elsa.Samples.MassTransitRabbitMq.csproj", "{6BDC89F4-2E0A-4534-9BE4-10E771D06C93}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Webhooks.Api", "src\activities\webhooks\Elsa.Webhooks.Api\Elsa.Webhooks.Api.csproj", "{9D4B7BD1-AC36-4C94-9770-C42E4030E699}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.WorkflowSettings.Api", "src\modules\workflowsettings\Elsa.WorkflowSettings.Api\Elsa.WorkflowSettings.Api.csproj", "{7595784C-97DA-4C75-BCBC-D99C30E63973}"
@ -362,8 +358,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ElsaDashboard.Samples.Blazo
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Samples.HttpEndpointSecurity", "src\samples\aspnet\Elsa.Samples.HttpEndpointSecurity\Elsa.Samples.HttpEndpointSecurity.csproj", "{82B115DA-E3D0-49D7-AD08-DE9387656756}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.WatchDirectoryWorker", "src\samples\worker\Elsa.Samples.WatchDirectoryWorker\Elsa.Samples.WatchDirectoryWorker.csproj", "{8EC3CF51-EBC1-4B45-881E-4B1B8190D579}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.InfiniteLoopDetection", "src\samples\aspnet\Elsa.Samples.InfiniteLoopDetection\Elsa.Samples.InfiniteLoopDetection.csproj", "{A2572DBF-AF11-4A02-AF99-B343871F2D85}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Samples.WatchDirectoryWorker", "src\samples\worker\Elsa.Samples.WatchDirectoryWorker\Elsa.Samples.WatchDirectoryWorker.csproj", "{8EC3CF51-EBC1-4B45-881E-4B1B8190D579}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Samples.InfiniteLoopDetection", "src\samples\aspnet\Elsa.Samples.InfiniteLoopDetection\Elsa.Samples.InfiniteLoopDetection.csproj", "{A2572DBF-AF11-4A02-AF99-B343871F2D85}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -775,6 +772,14 @@ Global
{6E191628-927B-42FB-8805-D2977D8FC4E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6E191628-927B-42FB-8805-D2977D8FC4E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6E191628-927B-42FB-8805-D2977D8FC4E3}.Release|Any CPU.Build.0 = Release|Any CPU
{BB715C0C-BE88-4F7F-A43F-1F8DFC9A1388}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BB715C0C-BE88-4F7F-A43F-1F8DFC9A1388}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BB715C0C-BE88-4F7F-A43F-1F8DFC9A1388}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BB715C0C-BE88-4F7F-A43F-1F8DFC9A1388}.Release|Any CPU.Build.0 = Release|Any CPU
{6BDC89F4-2E0A-4534-9BE4-10E771D06C93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6BDC89F4-2E0A-4534-9BE4-10E771D06C93}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6BDC89F4-2E0A-4534-9BE4-10E771D06C93}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6BDC89F4-2E0A-4534-9BE4-10E771D06C93}.Release|Any CPU.Build.0 = Release|Any CPU
{AB926BCA-B73E-463D-8549-84D3D124F089}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AB926BCA-B73E-463D-8549-84D3D124F089}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AB926BCA-B73E-463D-8549-84D3D124F089}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -807,14 +812,6 @@ Global
{F006F3CC-22E9-4FDD-A8D4-2E8CBDFDA54A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F006F3CC-22E9-4FDD-A8D4-2E8CBDFDA54A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F006F3CC-22E9-4FDD-A8D4-2E8CBDFDA54A}.Release|Any CPU.Build.0 = Release|Any CPU
{BB715C0C-BE88-4F7F-A43F-1F8DFC9A1388}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BB715C0C-BE88-4F7F-A43F-1F8DFC9A1388}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BB715C0C-BE88-4F7F-A43F-1F8DFC9A1388}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BB715C0C-BE88-4F7F-A43F-1F8DFC9A1388}.Release|Any CPU.Build.0 = Release|Any CPU
{6BDC89F4-2E0A-4534-9BE4-10E771D06C93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6BDC89F4-2E0A-4534-9BE4-10E771D06C93}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6BDC89F4-2E0A-4534-9BE4-10E771D06C93}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6BDC89F4-2E0A-4534-9BE4-10E771D06C93}.Release|Any CPU.Build.0 = Release|Any CPU
{9D4B7BD1-AC36-4C94-9770-C42E4030E699}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9D4B7BD1-AC36-4C94-9770-C42E4030E699}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9D4B7BD1-AC36-4C94-9770-C42E4030E699}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -997,6 +994,8 @@ Global
{4D4022DD-244B-47EE-95FA-7071EC687B24} = {B43B546E-23F3-46E8-ACB7-D04F05CDA180}
{39DE1EE2-DF4F-44EB-9F5A-551BEADD6673} = {4673732F-2853-47BD-91B8-C95C229D2C89}
{6E191628-927B-42FB-8805-D2977D8FC4E3} = {22E75696-6FE9-436A-9097-EE21C603F818}
{BB715C0C-BE88-4F7F-A43F-1F8DFC9A1388} = {39DE1EE2-DF4F-44EB-9F5A-551BEADD6673}
{6BDC89F4-2E0A-4534-9BE4-10E771D06C93} = {22E75696-6FE9-436A-9097-EE21C603F818}
{69BB424A-AAA3-415C-9651-9F4349CA3AC5} = {DA71CDAA-8DD3-4D5F-9FBD-8E4B37A2D925}
{513B0FAD-F92F-4C7B-B756-D735F8C3C5E1} = {69BB424A-AAA3-415C-9651-9F4349CA3AC5}
{AB926BCA-B73E-463D-8549-84D3D124F089} = {513B0FAD-F92F-4C7B-B756-D735F8C3C5E1}
@ -1007,8 +1006,6 @@ Global
{770456A5-547D-43AE-AB00-CF17BA799AF7} = {513B0FAD-F92F-4C7B-B756-D735F8C3C5E1}
{F51C54C3-7C68-41E9-BC05-798589740F39} = {513B0FAD-F92F-4C7B-B756-D735F8C3C5E1}
{F006F3CC-22E9-4FDD-A8D4-2E8CBDFDA54A} = {513B0FAD-F92F-4C7B-B756-D735F8C3C5E1}
{BB715C0C-BE88-4F7F-A43F-1F8DFC9A1388} = {39DE1EE2-DF4F-44EB-9F5A-551BEADD6673}
{6BDC89F4-2E0A-4534-9BE4-10E771D06C93} = {22E75696-6FE9-436A-9097-EE21C603F818}
{9D4B7BD1-AC36-4C94-9770-C42E4030E699} = {90EC2D29-DE36-40AF-BDA7-70FFD538B8CA}
{7595784C-97DA-4C75-BCBC-D99C30E63973} = {513B0FAD-F92F-4C7B-B756-D735F8C3C5E1}
{6242E81F-688E-4098-948A-F0C59593328A} = {B43B546E-23F3-46E8-ACB7-D04F05CDA180}

View file

@ -0,0 +1,11 @@
using Elsa.Models;
namespace Elsa.Events
{
public class WorkflowDefinitionUpdated : WorkflowDefinitionNotification
{
public WorkflowDefinitionUpdated(WorkflowDefinition workflowDefinition) : base(workflowDefinition)
{
}
}
}

View file

@ -12,6 +12,7 @@ namespace Elsa.Services
Task<WorkflowDefinition?> RetractAsync(string workflowDefinitionId, CancellationToken cancellationToken = default);
Task<WorkflowDefinition> RetractAsync(WorkflowDefinition workflowDefinition, CancellationToken cancellationToken = default);
Task<WorkflowDefinition?> GetDraftAsync(string workflowDefinitionId, CancellationToken cancellationToken= default);
Task<WorkflowDefinition?> GetAsync(string workflowDefinitionId, int version, CancellationToken cancellationToken = default);
Task<WorkflowDefinition> SaveDraftAsync(WorkflowDefinition workflowDefinition, CancellationToken cancellationToken = default);
Task DeleteAsync(string workflowDefinitionId, CancellationToken cancellationToken = default);
Task DeleteAsync(WorkflowDefinition workflowDefinition, CancellationToken cancellationToken = default);

View file

@ -150,7 +150,8 @@ namespace Elsa.Services.Workflows
// Acquire a lock on correlation ID to prevent duplicate workflow instances from being created.
await using var correlationLockHandle = await AcquireLockAsync(correlationId, cancellationToken);
return await CollectStartableWorkflowInternalAsync(workflowBlueprint, activityId, correlationId, contextId, tenantId, cancellationToken);
var startableWorkflowDefinition = await CollectStartableWorkflowInternalAsync(workflowBlueprint, activityId, correlationId, contextId, tenantId, cancellationToken);
return startableWorkflowDefinition != null ? await InstantiateStartableWorkflow(startableWorkflowDefinition, cancellationToken) : default;
}
public async Task FindAndExecuteStartableWorkflowAsync(

View file

@ -135,6 +135,16 @@ namespace Elsa.Services.Workflows
return draft;
}
public async Task<WorkflowDefinition?> GetAsync(string workflowDefinitionId, int version, CancellationToken cancellationToken = default)
{
var definition = await _workflowDefinitionStore.FindByDefinitionIdAsync(
workflowDefinitionId,
VersionOptions.SpecificVersion(version),
cancellationToken);
return definition;
}
public async Task<WorkflowDefinition> SaveDraftAsync(WorkflowDefinition workflowDefinition, CancellationToken cancellationToken = default)
{
var draft = workflowDefinition;

View file

@ -1,13 +1,13 @@
import {Component, Event, EventEmitter, h, Host, Method, Prop, State, Watch} from '@stencil/core';
import {Component, Prop, Event, EventEmitter, h, Method, State, Watch, Host} from '@stencil/core';
import {HubConnection, HubConnectionBuilder} from '@microsoft/signalr';
import {enter, leave} from "el-transition"
import * as collection from 'lodash/collection';
import {WorkflowDefinition, WorkflowDefinitionSummary, WorkflowTestActivityMessage} from "../../../../models";
import {i18n} from "i18next";
import {loadTranslations} from "../../../i18n/i18n-loader";
import {resources} from "./localizations";
import {createElsaClient} from "../../../../services";
import {createElsaClient, WorkflowTestExecuteRequest, WorkflowTestSaveRequest} from "../../../../services";
import Tunnel from "../../../../data/dashboard";
import { isJSDocThisTag } from 'typescript';
interface Tab {
id: string;
@ -49,8 +49,6 @@ export class ElsaWorkflowPropertiesPanel {
tabs: Array<Tab> = [];
testActivity: WorkflowTestActivityMessage;
t = (key: string) => this.i18next.t(key);
@Method()
async selectTestActivity(message?: WorkflowTestActivityMessage) {
const messageInternal = !!message ? message : null;
@ -81,7 +79,7 @@ export class ElsaWorkflowPropertiesPanel {
});
this.hubConnection.on('DispatchMessage', (message) => {
debugger
message.data = JSON.parse(message.data);
this.testActivityMessageReceived.emit(message);
});
@ -92,23 +90,40 @@ export class ElsaWorkflowPropertiesPanel {
onTabClick(e: Event, tab: Tab) {
e.preventDefault();
this.selectedTabId = tab.id;
}
async onExecuteWorkflowClick() {
this.testActivityMessageReceived.emit(null);
const elsaClient = this.createClient();
await elsaClient.workflowsApi.test(this.workflowDefinition.definitionId, this.workflowDefinition.version, this.signalRConnectionId);
const request: WorkflowTestExecuteRequest = {
workflowDefinitionId: this.workflowDefinition.definitionId,
version: this.workflowDefinition.version,
signalRConnectionId: this.signalRConnectionId
};
await elsaClient.workflowTestApi.execute(request);
}
async onUseAsSchemaClick() {
const elsaClient = this.createClient();
const value = this.testActivity.data["Inbound Request"];
const request: WorkflowTestSaveRequest = {
workflowDefinitionId: this.workflowDefinition.definitionId,
version: this.workflowDefinition.version,
activityId: this.testActivity.activityId,
json: value
};
await elsaClient.workflowTestApi.save(request);
}
selectTestActivityMessageInternal(message?: WorkflowTestActivityMessage) {
this.testActivity = message;
}
}
render() {
return (
@ -121,89 +136,72 @@ export class ElsaWorkflowPropertiesPanel {
renderPropertiesButton() {
const {workflowDefinition, expanded, expandButtonPosition} = this;
const name = workflowDefinition.name || this.i18next.t("Untitled");
const {expanded, expandButtonPosition} = this;
const expandPositionClass = expandButtonPosition == 1 ? "elsa-right-12" : "elsa-right-28";
return (
<button type="button"
onClick={this.toggle}
class={`${expanded ? "hidden" : expandPositionClass} workflow-settings-button elsa-fixed elsa-top-20 elsa-inline-flex elsa-items-center elsa-p-2 elsa-rounded-full elsa-border elsa-border-transparent elsa-bg-white shadow elsa-text-gray-400 hover:elsa-text-blue-500 focus:elsa-text-blue-500 hover:elsa-ring-2 hover:elsa-ring-offset-2 hover:elsa-ring-blue-500 focus:elsa-outline-none focus:elsa-ring-2 focus:elsa-ring-offset-2 focus:elsa-ring-blue-500 elsa-z-10`}>
onClick={this.toggle}
class={`${expanded ? "hidden" : expandPositionClass} workflow-settings-button elsa-fixed elsa-top-20 elsa-inline-flex elsa-items-center elsa-p-2 elsa-rounded-full elsa-border elsa-border-transparent elsa-bg-white shadow elsa-text-gray-400 hover:elsa-text-blue-500 focus:elsa-text-blue-500 hover:elsa-ring-2 hover:elsa-ring-offset-2 hover:elsa-ring-blue-500 focus:elsa-outline-none focus:elsa-ring-2 focus:elsa-ring-offset-2 focus:elsa-ring-blue-500 elsa-z-10`}>
<svg xmlns="http://www.w3.org/2000/svg" class="elsa-h-8 elsa-w-8" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 19l-7-7 7-7m8 14l-7-7 7-7"/>
</svg>
</button>
</button>
);
}
}
renderPanel() {
const t = (x, params?) => this.i18next.t(x, params);
const {workflowDefinition} = this;
const name = workflowDefinition.name || this.i18next.t("Untitled");
const {isPublished} = workflowDefinition;
const tabs = this.tabs;
const selectedTabId = this.selectedTabId;
const selectedTab = tabs.find(x => x.id === selectedTabId);
return (
<section class={`${this.expanded ? '' : 'hidden'} elsa-fixed elsa-top-4 elsa-right-0 elsa-bottom-0 elsa-overflow-hidden`} aria-labelledby="slide-over-title" role="dialog" aria-modal="true">
<section
class={`${this.expanded ? '' : 'hidden'} elsa-fixed elsa-top-4 elsa-right-0 elsa-bottom-0 elsa-overflow-hidden`}
aria-labelledby="slide-over-title" role="dialog" aria-modal="true">
<div class="elsa-absolute elsa-inset-0 elsa-overflow-hidden">
<div class="elsa-absolute elsa-inset-0" aria-hidden="true"/>
<div class="elsa-fixed elsa-inset-y-0 elsa-top-16 elsa-right-0 max-elsa-w-full elsa-flex">
<div ref={el => this.el = el}
data-transition-enter="elsa-transform elsa-transition elsa-ease-in-out elsa-duration-300 sm:elsa-duration-700"
data-transition-enter-start="elsa-translate-x-full"
data-transition-enter-end="elsa-translate-x-0"
data-transition-leave="elsa-transform elsa-transition elsa-ease-in-out elsa-duration-300 sm:elsa-duration-700"
data-transition-leave-start="elsa-translate-x-0"
data-transition-leave-end="elsa-translate-x-full"
class="elsa-w-screen elsa-max-w-lg elsa-h-full">
<button type="button"
onClick={this.toggle}
class="workflow-settings-button elsa-absolute elsa-top-4 elsa-left-2 elsa-inline-flex elsa-items-center elsa-p-2 elsa-rounded-full elsa-border elsa-border-transparent elsa-bg-white shadow elsa-text-gray-400 hover:elsa-text-blue-500 focus:elsa-text-blue-500 hover:elsa-ring-2 hover:elsa-ring-offset-2 hover:elsa-ring-blue-500 focus:elsa-outline-none focus:elsa-ring-2 focus:elsa-ring-offset-2 focus:elsa-ring-blue-500 elsa-z-10">
<svg xmlns="http://www.w3.org/2000/svg" class="elsa-h-8 elsa-w-8" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M13 5l7 7-7 7M5 5l7 7-7 7"/>
</svg>
</button>
<div
class="elsa-h-full elsa-flex elsa-flex-col elsa-py-6 elsa-bg-white elsa-shadow-xl elsa-overflow-y-scroll elsa-bg-white">
<div class="elsa-h-full">
<div class="elsa-mt-16 elsa-p-6">
<div class="elsa-font-medium elsa-leading-8 elsa-overflow-hidden">
<p
class="elsa-overflow-ellipsis">{t('Properties', {name: workflowDefinition.displayName || name})}</p>
</div>
<div>
<dl
class="elsa-mt-2 elsa-border-t elsa-border-b elsa-border-gray-200 elsa-divide-y elsa-divide-gray-200">
<div class="elsa-py-3 elsa-flex elsa-justify-between elsa-text-sm elsa-font-medium">
<dt class="elsa-text-gray-500">{t('Name')}</dt>
<dd class="elsa-text-gray-900">{name}</dd>
</div>
<div class="elsa-py-3 elsa-flex elsa-justify-between elsa-text-sm elsa-font-medium">
<dt class="elsa-text-gray-500">{t('Id')}</dt>
<dd class="elsa-text-gray-900 elsa-break-all">{workflowDefinition.definitionId || '-'}</dd>
</div>
<div class="elsa-py-3 elsa-flex elsa-justify-between elsa-text-sm elsa-font-medium">
<dt class="elsa-text-gray-500">{t('Version')}</dt>
<dd class="elsa-text-gray-900">{workflowDefinition.version}</dd>
</div>
<div class="elsa-py-3 elsa-flex elsa-justify-between elsa-text-sm elsa-font-medium">
<dt class="elsa-text-gray-500">{t('PublishedVersion')}</dt>
<dd class="elsa-text-gray-900">{this.publishedVersion || '-'}</dd>
</div>
<div class="elsa-py-3 elsa-flex elsa-justify-between elsa-text-sm elsa-font-medium">
<dt class="elsa-text-gray-500">{t('Status')}</dt>
<dd
class={`${isPublished ? 'elsa-text-green-600' : 'elsa-text-yellow-700'}`}>{isPublished ? t('Published') : t('Draft')}</dd>
</div>
</dl>
</div>
</div>
<div
class="elsa-fixed elsa-inset-y-0 elsa-top-16 elsa-right-0 max-elsa-w-full elsa-flex">
<div
ref={el => this.el = el}
data-transition-enter="elsa-transform elsa-transition elsa-ease-in-out elsa-duration-300 sm:elsa-duration-700"
data-transition-enter-start="elsa-translate-x-full"
data-transition-enter-end="elsa-translate-x-0"
data-transition-leave="elsa-transform elsa-transition elsa-ease-in-out elsa-duration-300 sm:elsa-duration-700"
data-transition-leave-start="elsa-translate-x-0"
data-transition-leave-end="elsa-translate-x-full"
class="elsa-w-screen elsa-max-w-lg elsa-h-full">
<div class="elsa-h-full elsa-flex elsa-flex-col elsa-py-6 elsa-bg-white elsa-shadow-xl elsa-overflow-y-scroll">
<div class="elsa-px-4 sm:elsa-px-6">
<div class="elsa-flex elsa-flex-col elsa-items-end">
<div class="elsa-ml-3 h-7 elsa-flex elsa-items-center"></div>
<button type="button"
onClick={this.toggle}
class="workflow-settings-button elsa-absolute elsa-top-4 elsa-left-2 elsa-inline-flex elsa-items-center elsa-p-2 elsa-rounded-full elsa-border elsa-border-transparent elsa-bg-white shadow elsa-text-gray-400 hover:elsa-text-blue-500 focus:elsa-text-blue-500 hover:elsa-ring-2 hover:elsa-ring-offset-2 hover:elsa-ring-blue-500 focus:elsa-outline-none focus:elsa-ring-2 focus:elsa-ring-offset-2 focus:elsa-ring-blue-500 elsa-z-10">
<svg xmlns="http://www.w3.org/2000/svg" class="elsa-h-8 elsa-w-8" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M13 5l7 7-7 7M5 5l7 7-7 7"/>
</svg>
</button>
</div>
</div>
<div class="elsa-border-b elsa-border-gray-200">
<nav class="-elsa-mb-px elsa-flex elsa-space-x-8" aria-label="Tabs">
{tabs.map(tab => {
const className = tab.id == selectedTabId ? 'elsa-border-blue-500 elsa-text-blue-600' : 'elsa-border-transparent elsa-text-gray-500 hover:elsa-text-gray-700 hover:elsa-border-gray-300';
return <a href="#" onClick={e => this.onTabClick(e, tab)} class={`${className} elsa-whitespace-nowrap elsa-py-4 elsa-px-1 elsa-border-b-2 elsa-font-medium elsa-text-sm`}>{tab.text}</a>;
})}
</nav>
</div>
<div class="elsa-mt-6 elsa-relative elsa-flex-1 elsa-px-4 sm:elsa-px-6">
<div class="elsa-absolute elsa-inset-0 elsa-px-4 sm:elsa-px-6">
{selectedTab.view()}
@ -260,7 +258,8 @@ export class ElsaWorkflowPropertiesPanel {
</div>
</div>
</div>
</div>);
</div>
);
}
renderTestTab = () => {
@ -279,7 +278,6 @@ export class ElsaWorkflowPropertiesPanel {
</button>
</div>
{this.renderActivityMessage()}
{this.renderUseAsSchemaButton()}
</div>
</div>
</div>
@ -289,52 +287,87 @@ export class ElsaWorkflowPropertiesPanel {
renderActivityMessage() {
const t = (x, params?) => this.i18next.t(x, params);
const {testActivity} = this;
if (testActivity)
if (testActivity == undefined)
return
return (
<dl
class="elsa-mt-2 elsa-border-t elsa-border-b elsa-border-gray-200 elsa-divide-y elsa-divide-gray-200">
<div class="elsa-py-3 elsa-flex elsa-justify-between elsa-text-sm elsa-font-medium">
<dt class="elsa-text-gray-500">{t('Instance Id')}</dt>
<dd class="elsa-text-gray-900">{testActivity.workflowInstanceId}</dd>
const t = (x, params?) => this.i18next.t(x, params);
const filteredData = {};
const wellKnownDataKeys = {State: true, Input: null, Outcomes: true, Exception: true};
let dataKey = null;
for (const key in testActivity.data) {
if (!testActivity.data.hasOwnProperty(key))
continue;
if (!!wellKnownDataKeys[key])
continue;
const value = testActivity.data[key];
if (!value && value != 0)
continue;
let valueText = null;
dataKey = key;
if (typeof value == 'string')
valueText = value;
else if (typeof value == 'object')
valueText = JSON.stringify(value, null, 1);
else if (typeof value == 'undefined')
valueText = null;
else
valueText = value.toString();
filteredData[key] = valueText;
}
const isInboundRequest = dataKey === "Inbound Request";
return (
<dl
class="elsa-mt-2 elsa-border-t elsa-border-b elsa-border-gray-200 elsa-divide-y elsa-divide-gray-200">
<div class="elsa-py-3 elsa-flex elsa-justify-between elsa-text-sm elsa-font-medium">
<dt class="elsa-text-gray-500">{t('Instance Id')}</dt>
<dd class="elsa-text-gray-900">{testActivity.workflowInstanceId}</dd>
</div>
<div class="elsa-py-3 elsa-flex elsa-justify-between elsa-text-sm elsa-font-medium">
<dt class="elsa-text-gray-500">{t('Correlation Id')}</dt>
<dd class="elsa-text-gray-900">{testActivity.correlationId}</dd>
</div>
<div class="elsa-py-3 elsa-flex elsa-justify-between elsa-text-sm elsa-font-medium">
<dt class="elsa-text-gray-500">{t('Activity Id')}</dt>
<dd class="elsa-text-gray-900">{testActivity.activityId}</dd>
</div>
<div class="elsa-py-3 elsa-flex elsa-justify-between elsa-text-sm elsa-font-medium">
<dt class="elsa-text-gray-500">{t('Status')}</dt>
<dd class="elsa-text-gray-900 elsa-break-all">{testActivity.status || '-'}</dd>
</div>
{collection.map(filteredData, (v, k) => (
<div>
<div class="sm:elsa-col-span-2">
<dt class="elsa-text-sm elsa-font-medium elsa-text-gray-500 elsa-capitalize">{k}</dt>
<dd class="elsa-mt-1 elsa-text-sm elsa-text-gray-900 elsa-mb-2 elsa-overflow-x-auto">{v}</dd>
</div>
{isInboundRequest ?
<div class="elsa-px-4 elsa-py-3 elsa-bg-gray-50 elsa-text-left sm:px-6">
<button type="button"
onClick={() => this.onUseAsSchemaClick()}
class="elsa-ml-0 elsa-w-full elsa-inline-flex elsa-justify-center elsa-rounded-md elsa-border elsa-border-transparent elsa-shadow-sm elsa-px-4 elsa-py-2 elsa-bg-blue-600 elsa-text-base elsa-font-medium elsa-text-white hover:elsa-bg-blue-700 focus:elsa-outline-none focus:elsa-ring-2 focus:elsa-ring-offset-2 focus:elsa-ring-blue-500 sm:elsa-ml-3 sm:elsa-w-auto sm:elsa-text-sm">
{t('UseAsSchema')}
</button>
</div>
:
null
}
</div>
<div class="elsa-py-3 elsa-flex elsa-justify-between elsa-text-sm elsa-font-medium">
<dt class="elsa-text-gray-500">{t('Correlation Id')}</dt>
<dd class="elsa-text-gray-900">{testActivity.correlationId}</dd>
</div>
<div class="elsa-py-3 elsa-flex elsa-justify-between elsa-text-sm elsa-font-medium">
<dt class="elsa-text-gray-500">{t('Activity Id')}</dt>
<dd class="elsa-text-gray-900">{testActivity.activityId}</dd>
</div>
<div class="elsa-py-3 elsa-flex elsa-justify-between elsa-text-sm elsa-font-medium">
<dt class="elsa-text-gray-500">{t('Status')}</dt>
<dd class="elsa-text-gray-900 elsa-break-all">{testActivity.status || '-'}</dd>
</div>
</dl>
))}
</dl>
);
}
renderUseAsSchemaButton() {
const t = (x, params?) => this.i18next.t(x, params);
const {testActivity} = this;
if (testActivity)
return (
<div class="elsa-px-4 elsa-py-3 elsa-bg-gray-50 elsa-text-left sm:px-6">
<button type="button"
onClick={() => this.onUseAsSchemaClick()}
class="elsa-ml-0 elsa-w-full elsa-inline-flex elsa-justify-center elsa-rounded-md elsa-border elsa-border-transparent elsa-shadow-sm elsa-px-4 elsa-py-2 elsa-bg-blue-600 elsa-text-base elsa-font-medium elsa-text-white hover:elsa-bg-blue-700 focus:elsa-outline-none focus:elsa-ring-2 focus:elsa-ring-offset-2 focus:elsa-ring-blue-500 sm:elsa-ml-3 sm:elsa-w-auto sm:elsa-text-sm">
{t('UseAsSchema')}
</button>
</div>
);
}
createClient() {
return createElsaClient(this.serverUrl);
}
@ -361,4 +394,4 @@ export class ElsaWorkflowPropertiesPanel {
}
}
Tunnel.injectProps(ElsaWorkflowPropertiesPanel, ['serverUrl', 'culture']);
Tunnel.injectProps(ElsaWorkflowPropertiesPanel, ['serverUrl', 'culture']);

View file

@ -328,6 +328,7 @@ export interface WorkflowTestActivityMessage {
correlationId: string;
activityId: string;
status: string;
data?: any;
}
export const getVersionOptionsString = (versionOptions?: VersionOptions) => {

View file

@ -63,6 +63,7 @@ export const createElsaClient = function (serverUrl: string): ElsaClient {
},
workflowDefinitionsApi: {
list: async (page?: number, pageSize?: number, versionOptions?: VersionOptions) => {
debugger
const versionOptionsString = getVersionOptionsString(versionOptions);
const response = await httpClient.get<PagedList<WorkflowDefinitionSummary>>(`v1/workflow-definitions?version=${versionOptionsString}`);
return response.data;
@ -114,39 +115,19 @@ export const createElsaClient = function (serverUrl: string): ElsaClient {
return response.data;
}
},
workflowsApi: {
test: async (workflowDefinitionId, version, signalRConnectionId) => {
httpClient.interceptors.request.use(function (config) {
config.headers['Accept'] = 'application/json';
config.data = Object.assign({}, config.data, {});
return config;
})
const response = await httpClient.post<void>(`v1/workflows/${workflowDefinitionId}/${version}/${signalRConnectionId}/test`);
return response.data;
}
},
webhookDefinitionsApi: {
list: async (page?: number, pageSize?: number) => {
const response = await httpClient.get<PagedList<WebhookDefinitionSummary>>(`v1/webhook-definitions`);
return response.data;
},
getByWebhookId: async (webhookId: string) => {
const response = await httpClient.get<WebhookDefinition>(`v1/webhook-definitions/${webhookId}`);
return response.data;
workflowTestApi: {
execute: async (request) => {
httpClient.interceptors.request.use(function (config) {
config.headers['Accept'] = 'application/json';
config.data = Object.assign({}, config.data, {});
return config;
})
await httpClient.post<void>(`v1/workflow-test/execute`, request);
},
save: async request => {
const response = await httpClient.post<WebhookDefinition>('v1/webhook-definitions', request);
return response.data;
},
update: async request => {
const response = await httpClient.put<WebhookDefinition>('v1/webhook-definitions', request);
return response.data;
},
delete: async webhookId => {
await httpClient.delete(`v1/webhook-definitions/${webhookId}`);
},
},
await httpClient.post<void>(`v1/workflow-test/save`, request);
},
},
workflowRegistryApi: {
list: async (page?: number, pageSize?: number, versionOptions?: VersionOptions): Promise<PagedList<WorkflowBlueprintSummary>> => {
const versionOptionsString = getVersionOptionsString(versionOptions);
@ -276,7 +257,7 @@ export interface ElsaClient {
activityStatsApi: ActivityStatsApi;
workflowStorageProvidersApi: WorkflowStorageProvidersApi;
workflowChannelsApi: WorkflowChannelsApi;
workflowsApi: WorkflowsApi;
workflowTestApi: WorkflowTestApi;
}
export interface ActivitiesApi {
@ -302,22 +283,11 @@ export interface WorkflowDefinitionsApi {
import(workflowDefinitionId: string, file: File): Promise<WorkflowDefinition>;
}
export interface WorkflowsApi {
export interface WorkflowTestApi {
test(workflowDefinitionId: string, version: number, signalRConnectionId: string): Promise<void>;
}
execute(request: WorkflowTestExecuteRequest): Promise<void>;
export interface WebhookDefinitionsApi {
list(page?: number, pageSize?: number): Promise<PagedList<WebhookDefinitionSummary>>;
getByWebhookId(webhookId: string): Promise<WebhookDefinition>;
save(request: SaveWebhookDefinitionRequest): Promise<WebhookDefinition>;
update(request: SaveWebhookDefinitionRequest): Promise<WebhookDefinition>;
delete(webhookId: string): Promise<void>;
save(request: WorkflowTestSaveRequest): Promise<void>;
}
export interface WorkflowRegistryApi {
@ -403,6 +373,19 @@ export interface SaveWorkflowDefinitionRequest {
connections: Array<ConnectionDefinition>;
}
export interface WorkflowTestExecuteRequest {
workflowDefinitionId?: string,
version?: number,
signalRConnectionId?: string
}
export interface WorkflowTestSaveRequest {
workflowDefinitionId?: string,
version?: number,
activityId?: string,
json?: any
}
export interface ExportWorkflowResponse {
fileName: string;
data: Blob;

View file

@ -8,7 +8,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.9" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
<PackageReference Include="System.Text.Encodings.Web" Version="5.0.1" />
</ItemGroup>

View file

@ -19,6 +19,7 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="5.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NJsonSchema" Version="10.5.2" />
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.4" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.1.4" />

View file

@ -0,0 +1,54 @@
using Elsa.Server.Api.ActionFilters;
using Elsa.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Models;
using Elsa.Server.Api.Endpoints.Workflows;
namespace Elsa.Server.Api.Endpoints.WorkflowTest
{
[ApiController]
[ApiVersion("1")]
[Route("v{apiVersion:apiVersion}/workflow-test/execute")]
[Produces("application/json")]
public class Execute : Controller
{
private readonly IWorkflowLaunchpad _workflowLaunchpad;
private readonly ITenantAccessor _tenantAccessor;
public Execute(IWorkflowLaunchpad workflowLaunchpad, ITenantAccessor tenantAccessor)
{
_workflowLaunchpad = workflowLaunchpad;
_tenantAccessor = tenantAccessor;
}
[HttpPost]
[ElsaJsonFormatter]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(WorkflowTestExecuteRequest))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[SwaggerOperation(
Summary = "Executes the specified workflow definition version in test mode.",
Description = "Executes the specified workflow definition vesrion in test mode.",
OperationId = "WorkflowTest.Execute",
Tags = new[] { "WorkflowTest" })
]
public async Task<IActionResult> Handle([FromBody] WorkflowTestExecuteRequest request, CancellationToken cancellationToken = default)
{
var tenantId = await _tenantAccessor.GetTenantIdAsync(cancellationToken);
var testableWorkflow = await _workflowLaunchpad.FindTestableWorkflowAsync(request.WorkflowDefinitionId!, request.Version, null, null, null, tenantId, request.SignalRConnectionId, cancellationToken);
if (testableWorkflow == null)
return NotFound();
var result = await _workflowLaunchpad.ExecuteTestableWorkflowAsync(testableWorkflow, null, cancellationToken);
if (Response.HasStarted)
return new EmptyResult();
return Ok();
}
}
}

View file

@ -0,0 +1,17 @@
namespace Elsa.Server.Api.Endpoints.WorkflowTest
{
public sealed record WorkflowTestExecuteRequest
{
public string? WorkflowDefinitionId { get; init; }
public int Version { get; init; }
public string? SignalRConnectionId { get; init; }
}
public sealed record WorkflowTestSaveRequest
{
public string? WorkflowDefinitionId { get; init; }
public int Version { get; init; }
public string? ActivityId { get; init; }
public object? Json { get; init; }
}
}

View file

@ -0,0 +1,69 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Events;
using Elsa.Server.Api.ActionFilters;
using Elsa.Services;
using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using NJsonSchema;
using Swashbuckle.AspNetCore.Annotations;
namespace Elsa.Server.Api.Endpoints.WorkflowTest
{
[ApiController]
[ApiVersion("1")]
[Route("v{apiVersion:apiVersion}/workflow-test/save")]
[Produces("application/json")]
public class Save : Controller
{
private readonly IWorkflowPublisher _workflowPublisher;
private readonly IMediator _mediator;
public Save(IWorkflowPublisher workflowPublisher, IMediator mediator)
{
_workflowPublisher = workflowPublisher;
_mediator = mediator;
}
[HttpPost]
[ElsaJsonFormatter]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(WorkflowTestSaveRequest))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[SwaggerOperation(
Summary = "Updates Json Schema for the specified workflow definition version activity in test mode.",
Description = "Updates Json Schema the specified workflow definition vesrion activity in test mode.",
OperationId = "WorkflowTest.Save",
Tags = new[] { "WorkflowTest" })
]
public async Task<IActionResult> Handle([FromBody] WorkflowTestSaveRequest request, CancellationToken cancellationToken = default)
{
var workflowDefinitionId = request.WorkflowDefinitionId;
var workflowDefinition = !string.IsNullOrWhiteSpace(workflowDefinitionId) ? await _workflowPublisher.GetAsync(workflowDefinitionId, request.Version, cancellationToken) : default;
if (workflowDefinition == null)
return NotFound();
var schema = JsonSchema.FromSampleJson(JsonConvert.SerializeObject(request.Json));
var schemaJson = schema.ToJson();
var activity = workflowDefinition.Activities.FirstOrDefault(x => x.ActivityId == request.ActivityId);
if (activity != null)
{
var property = activity.Properties.FirstOrDefault(x => x.Name == "Schema");
if (property != null)
{
property.Expressions.Remove("Literal");
property.Expressions.Add("Literal", schemaJson);
}
}
workflowDefinition = await _workflowPublisher.SaveDraftAsync(workflowDefinition, cancellationToken);
await _mediator.Publish(new WorkflowDefinitionUpdated(workflowDefinition), cancellationToken);
return CreatedAtAction("Handle", "GetByVersionId", new { versionId = workflowDefinition.Id }, workflowDefinition);
}
}
}

View file

@ -55,6 +55,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddSingleton<IEndpointContentSerializerSettingsProvider, EndpointContentSerializerSettingsProvider>();
services.AddAutoMapperProfile<AutoMapperProfile>();
services.AddNotificationHandlersFrom<ActivityExecutionResultExecutedHandler>();
services.AddNotificationHandlersFrom<WorkflowRegistryCacheHandler>();
services.AddSignalR();
return services;
}

View file

@ -1,10 +1,12 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Events;
using Elsa.Models;
using Elsa.Server.Api.Models;
using Elsa.Server.Api.Services;
using MediatR;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Elsa.Server.Api.Handlers
{
@ -19,16 +21,29 @@ namespace Elsa.Server.Api.Handlers
public async Task Handle(ActivityExecutionResultExecuted notification, CancellationToken cancellationToken)
{
var signalRConnectionId = notification.ActivityExecutionContext.WorkflowExecutionContext.WorkflowBlueprint.SignalRConnectionId;
var context = notification.ActivityExecutionContext;
var signalRConnectionId = context.WorkflowExecutionContext.WorkflowBlueprint.SignalRConnectionId;
if (string.IsNullOrWhiteSpace(signalRConnectionId)) return;
var data = new JObject
{
["Outcomes"] = JToken.FromObject(context.Outcomes)
};
foreach (var entry in context.JournalData)
data[entry.Key] = entry.Value != null ? JToken.FromObject(entry.Value) : JValue.CreateNull();
var message = new WorkflowTestMessage
{
SignalRConnectionId = signalRConnectionId,
WorkflowInstanceId = notification.ActivityExecutionContext.WorkflowInstance.Id,
CorrelationId = notification.ActivityExecutionContext.CorrelationId,
ActivityId = notification.ActivityExecutionContext.ActivityId,
Status = notification.ActivityExecutionContext.WorkflowExecutionContext.Status.ToString()
WorkflowInstanceId = context.WorkflowInstance.Id,
CorrelationId = context.CorrelationId,
ActivityId = context.ActivityId,
Status = context.WorkflowExecutionContext.Status == WorkflowStatus.Running
? "Executed"
: context.WorkflowExecutionContext.Status.ToString(),
Data = JsonConvert.SerializeObject(data, Formatting.Indented)
};
await _workflowTestService.DispatchMessage(message);

View file

@ -0,0 +1,24 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Caching;
using Elsa.Events;
using Elsa.Services.Workflows;
using MediatR;
namespace Elsa.Server.Api.Handlers
{
public class WorkflowRegistryCacheHandler : INotificationHandler<WorkflowDefinitionUpdated>
{
private readonly ICacheSignal _cacheSignal;
public WorkflowRegistryCacheHandler(ICacheSignal cacheSignal)
{
_cacheSignal = cacheSignal;
}
public async Task Handle(WorkflowDefinitionUpdated notification, CancellationToken cancellationToken)
{
await _cacheSignal.TriggerTokenAsync(ActivityTypeService.CacheKey);
}
}
}

View file

@ -1,3 +1,5 @@
using Newtonsoft.Json.Linq;
namespace Elsa.Server.Api.Models
{
public class WorkflowTestMessage
@ -7,5 +9,6 @@ namespace Elsa.Server.Api.Models
public string CorrelationId { get; set; } = default!;
public string ActivityId { get; set; } = default!;
public string Status { get; set; } = default!;
public object? Data { get; set; }
}
}

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
@ -30,7 +30,7 @@
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.7.22" />
<PackageReference Include="Hangfire.InMemory" Version="0.3.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.9" />
</ItemGroup>
<ItemGroup>

View file

@ -15,13 +15,13 @@
<PackageReference Include="AutoFixture" Version="4.17.0" />
<PackageReference Include="Autofixture.AutoMoq" Version="4.17.0" />
<PackageReference Include="autofixture.xunit2" Version="4.17.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
<PackageReference Include="NodaTime" Version="3.0.5" />
<PackageReference Include="System.ComponentModel.TypeConverter" Version="4.3.0" />
<PackageReference Include="xunit.extensibility.core" Version="2.4.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.9" />
</ItemGroup>
<ItemGroup>