Implement variables panel on workflow instance viewer (#3589)
* Small visual improvement for embedded ports * Implement variables view for workflow instance viewer
This commit is contained in:
parent
32b7f85142
commit
cbbac57e6f
|
|
@ -40,7 +40,7 @@ services
|
|||
.UseWorkflowManagement(management => management.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString)))
|
||||
.UseWorkflowRuntime(runtime =>
|
||||
{
|
||||
runtime.UseProtoActor(proto => proto.PersistenceProvider = _ => new SqliteProvider(new SqliteConnectionStringBuilder(sqliteConnectionString)));
|
||||
//runtime.UseProtoActor(proto => proto.PersistenceProvider = _ => new SqliteProvider(new SqliteConnectionStringBuilder(sqliteConnectionString)));
|
||||
runtime.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString));
|
||||
runtime.UseAsyncWorkflowStateExporter();
|
||||
})
|
||||
|
|
|
|||
|
|
@ -243,6 +243,11 @@ export namespace Components {
|
|||
interface ElsaVariablesEditor {
|
||||
"variables"?: Array<Variable>;
|
||||
}
|
||||
interface ElsaVariablesViewer {
|
||||
"variables"?: Array<Variable>;
|
||||
"workflowDefinition": WorkflowDefinition;
|
||||
"workflowInstance": WorkflowInstance;
|
||||
}
|
||||
interface ElsaWidgets {
|
||||
"widgets": Array<Widget>;
|
||||
}
|
||||
|
|
@ -692,6 +697,12 @@ declare global {
|
|||
prototype: HTMLElsaVariablesEditorElement;
|
||||
new (): HTMLElsaVariablesEditorElement;
|
||||
};
|
||||
interface HTMLElsaVariablesViewerElement extends Components.ElsaVariablesViewer, HTMLStencilElement {
|
||||
}
|
||||
var HTMLElsaVariablesViewerElement: {
|
||||
prototype: HTMLElsaVariablesViewerElement;
|
||||
new (): HTMLElsaVariablesViewerElement;
|
||||
};
|
||||
interface HTMLElsaWidgetsElement extends Components.ElsaWidgets, HTMLStencilElement {
|
||||
}
|
||||
var HTMLElsaWidgetsElement: {
|
||||
|
|
@ -832,6 +843,7 @@ declare global {
|
|||
"elsa-variable-editor-dialog-content": HTMLElsaVariableEditorDialogContentElement;
|
||||
"elsa-variable-picker-input": HTMLElsaVariablePickerInputElement;
|
||||
"elsa-variables-editor": HTMLElsaVariablesEditorElement;
|
||||
"elsa-variables-viewer": HTMLElsaVariablesViewerElement;
|
||||
"elsa-widgets": HTMLElsaWidgetsElement;
|
||||
"elsa-workflow-definition-browser": HTMLElsaWorkflowDefinitionBrowserElement;
|
||||
"elsa-workflow-definition-editor": HTMLElsaWorkflowDefinitionEditorElement;
|
||||
|
|
@ -1070,6 +1082,11 @@ declare namespace LocalJSX {
|
|||
"onVariablesChanged"?: (event: ElsaVariablesEditorCustomEvent<Array<Variable>>) => void;
|
||||
"variables"?: Array<Variable>;
|
||||
}
|
||||
interface ElsaVariablesViewer {
|
||||
"variables"?: Array<Variable>;
|
||||
"workflowDefinition"?: WorkflowDefinition;
|
||||
"workflowInstance"?: WorkflowInstance;
|
||||
}
|
||||
interface ElsaWidgets {
|
||||
"widgets"?: Array<Widget>;
|
||||
}
|
||||
|
|
@ -1185,6 +1202,7 @@ declare namespace LocalJSX {
|
|||
"elsa-variable-editor-dialog-content": ElsaVariableEditorDialogContent;
|
||||
"elsa-variable-picker-input": ElsaVariablePickerInput;
|
||||
"elsa-variables-editor": ElsaVariablesEditor;
|
||||
"elsa-variables-viewer": ElsaVariablesViewer;
|
||||
"elsa-widgets": ElsaWidgets;
|
||||
"elsa-workflow-definition-browser": ElsaWorkflowDefinitionBrowser;
|
||||
"elsa-workflow-definition-editor": ElsaWorkflowDefinitionEditor;
|
||||
|
|
@ -1250,6 +1268,7 @@ declare module "@stencil/core" {
|
|||
"elsa-variable-editor-dialog-content": LocalJSX.ElsaVariableEditorDialogContent & JSXBase.HTMLAttributes<HTMLElsaVariableEditorDialogContentElement>;
|
||||
"elsa-variable-picker-input": LocalJSX.ElsaVariablePickerInput & JSXBase.HTMLAttributes<HTMLElsaVariablePickerInputElement>;
|
||||
"elsa-variables-editor": LocalJSX.ElsaVariablesEditor & JSXBase.HTMLAttributes<HTMLElsaVariablesEditorElement>;
|
||||
"elsa-variables-viewer": LocalJSX.ElsaVariablesViewer & JSXBase.HTMLAttributes<HTMLElsaVariablesViewerElement>;
|
||||
"elsa-widgets": LocalJSX.ElsaWidgets & JSXBase.HTMLAttributes<HTMLElsaWidgetsElement>;
|
||||
"elsa-workflow-definition-browser": LocalJSX.ElsaWorkflowDefinitionBrowser & JSXBase.HTMLAttributes<HTMLElsaWorkflowDefinitionBrowserElement>;
|
||||
"elsa-workflow-definition-editor": LocalJSX.ElsaWorkflowDefinitionEditor & JSXBase.HTMLAttributes<HTMLElsaWorkflowDefinitionEditorElement>;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
import {Component, Event, EventEmitter, h, Prop, State, Watch} from "@stencil/core";
|
||||
import {DeleteIcon, EditIcon} from "../../icons/tooling";
|
||||
import {StorageDriverDescriptor, Variable, WorkflowInstance} from "../../../models";
|
||||
import descriptorsStore from "../../../data/descriptors-store";
|
||||
import {ModalActionClickArgs, ModalActionDefinition, ModalActionType, ModalDialogInstance, ModalDialogService} from "../../shared/modal-dialog";
|
||||
import {Container} from "typedi";
|
||||
import {WorkflowDefinition} from "../../../modules/workflow-definitions/models/entities";
|
||||
|
||||
@Component({
|
||||
tag: 'elsa-variables-viewer',
|
||||
shadow: false
|
||||
})
|
||||
export class VariablesViewer {
|
||||
|
||||
@Prop() variables?: Array<Variable>;
|
||||
@Prop() workflowDefinition: WorkflowDefinition;
|
||||
@Prop() workflowInstance: WorkflowInstance;
|
||||
|
||||
render() {
|
||||
const variables = this.variables;
|
||||
const storageDrivers: Array<StorageDriverDescriptor> = descriptorsStore.storageDrivers;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div class="align-middle inline-block min-w-full border-b border-gray-200">
|
||||
<table class="default-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Storage</th>
|
||||
<th scope="col">Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{variables.map(variable => {
|
||||
const storage = storageDrivers.find(x => x.typeName == variable.storageDriverTypeName);
|
||||
const storageName = storage?.displayName ?? '-';
|
||||
const descriptor = descriptorsStore.variableDescriptors.find(x => x.typeName == variable.typeName);
|
||||
const typeDisplayName = descriptor?.displayName ?? variable.typeName;
|
||||
const variableValue = this.getVariableValue(variable, storage);
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td class="whitespace-nowrap">{variable.name}</td>
|
||||
<td class="whitespace-nowrap">{typeDisplayName}</td>
|
||||
<td>{storageName}</td>
|
||||
<td class="pr-6">{variableValue}</td>
|
||||
</tr>);
|
||||
}
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private getVariableValue(variable: Variable, storage: StorageDriverDescriptor) : any {
|
||||
if(storage.typeName !== 'Elsa.Workflows.Core.Implementations.WorkflowStorageDriver, Elsa.Workflows.Core')
|
||||
return null;
|
||||
|
||||
const workflowInstance = this.workflowInstance;
|
||||
const persistentVariables = workflowInstance.properties.PersistentVariablesDictionary;
|
||||
const key = `${workflowInstance.id}:Workflow1:${variable.name}`;
|
||||
return persistentVariables[key];
|
||||
}
|
||||
}
|
||||
|
|
@ -17,12 +17,12 @@ export const InfoList: FunctionalComponent<InfoListProps> = ({ title, dictionary
|
|||
<div class="mx-auto">
|
||||
<div>
|
||||
<div>
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">{title}</h3>
|
||||
<h3 class="text-md leading-6 font-medium text-gray-900">{title}</h3>
|
||||
</div>
|
||||
<div class="mt-5 border-t border-gray-200">
|
||||
<div class="mt-3 border-t border-gray-200">
|
||||
<dl class="sm:divide-y sm:divide-gray-200">
|
||||
{entries.map(([k, v]) => (
|
||||
<div class="py-4 sm:py-5 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<div class="py-3 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||
<dt class="text-sm font-medium text-gray-500">{k}</dt>
|
||||
<dd class="flex justify-between items-center mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{v}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export interface WorkflowInstanceSummary {
|
|||
}
|
||||
|
||||
export interface WorkflowInstance extends WorkflowInstanceSummary {
|
||||
workflowState: WorkflowState;
|
||||
properties: any;
|
||||
}
|
||||
|
||||
export interface PagedList<T> {
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ export class DefaultActivityTemplate {
|
|||
const hasEmbeddedPorts = embeddedPorts.length > 0;
|
||||
const canStartWorkflow = activity?.canStartWorkflow;
|
||||
const icon = this.icon;
|
||||
const hasIcon = !!icon;
|
||||
const textColor = canStartWorkflow ? 'text-white' : 'text-gray-700';
|
||||
const isTrigger = activityDescriptor?.kind == ActivityKind.Trigger;
|
||||
const backgroundColor = canStartWorkflow ? isTrigger ? 'bg-green-400' : 'bg-blue-400' : 'bg-white';
|
||||
|
|
@ -95,7 +96,7 @@ export class DefaultActivityTemplate {
|
|||
<div>
|
||||
<div class={`activity-wrapper border ${borderColor} ${backgroundColor} ${containerCssClass} rounded overflow-hidden`}>
|
||||
<div class="text-white">
|
||||
<div class={`flex flex-shrink items-center py-3 pr-3 ${iconBackgroundColor}`}>
|
||||
<div class={`flex flex-shrink items-center py-3 ${ hasIcon ? 'pr-3' : 'px-3' } ${iconBackgroundColor}`}>
|
||||
{this.renderIcon(icon)}
|
||||
<span>{displayText}</span>
|
||||
</div>
|
||||
|
|
@ -146,16 +147,11 @@ export class DefaultActivityTemplate {
|
|||
|
||||
private renderPort = (activity: Activity, port: Port) => {
|
||||
const canStartWorkflow = activity?.canStartWorkflow == true;
|
||||
const textColor = canStartWorkflow ? 'text-white' : 'text-gray-700';
|
||||
const displayTextClass = canStartWorkflow ? 'text-white' : 'text-gray-600';
|
||||
const borderColor = port.name == this.selectedPortName ? 'border-blue-600' : 'border-gray-300';
|
||||
const activityDescriptor = this.activityDescriptor;
|
||||
const portProvider = this.portProviderRegistry.get(activityDescriptor.typeName);
|
||||
const activityProperty = portProvider.resolvePort(port.name, {activity, activityDescriptor}) as Activity;
|
||||
const childActivityDescriptor: ActivityDescriptor = activityProperty != null ? descriptorsStore.activityDescriptors.find(x => x.typeName == activityProperty.type) : null;
|
||||
let childActivityDisplayText = activityProperty?.metadata?.displayText;
|
||||
|
||||
if (isNullOrWhitespace(childActivityDisplayText))
|
||||
childActivityDisplayText = childActivityDescriptor?.displayName;
|
||||
|
||||
const renderActivityProperty = () => {
|
||||
|
||||
|
|
@ -167,7 +163,7 @@ export class DefaultActivityTemplate {
|
|||
onMouseDown={e => e.stopPropagation()}
|
||||
class="text-gray-400 hover:text-gray-600">
|
||||
<div class="flex-grow">
|
||||
<span class={textColor}>{port.displayName}</span>
|
||||
<span class={`text-sm ${displayTextClass}`}>{port.displayName}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -184,7 +180,7 @@ export class DefaultActivityTemplate {
|
|||
onClick={e => this.onEditChildActivityClick(e, activity, port)}
|
||||
onMouseDown={e => e.stopPropagation()}>
|
||||
<div class="flex-grow">
|
||||
<span class={textColor}>{port.displayName}</span>
|
||||
<span class={`text-sm ${displayTextClass}`}>{port.displayName}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -199,7 +195,7 @@ export class DefaultActivityTemplate {
|
|||
onClick={e => this.onEditChildActivityClick(e, activity, port)}
|
||||
onMouseDown={e => e.stopPropagation()}>
|
||||
<div class="flex-grow">
|
||||
<span class={textColor}>{port.displayName}</span>
|
||||
<span class={`text-sm ${displayTextClass}`}>{port.displayName}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import {Component, Event, EventEmitter, h, Method, Prop, State, Watch} from '@stencil/core';
|
||||
import {TabChangedArgs, WorkflowInstance} from '../../../models';
|
||||
import {TabChangedArgs, Variable, WorkflowInstance} from '../../../models';
|
||||
import {InfoList} from "../../../components/shared/forms/info-list";
|
||||
import {formatTimestamp, isNullOrWhitespace} from "../../../utils";
|
||||
import {PropertiesTabModel, TabModel, WorkflowInstancePropertiesDisplayingArgs, WorkflowInstancePropertiesEventTypes, WorkflowInstancePropertiesViewerModel} from "../models";
|
||||
|
|
@ -147,8 +147,10 @@ export class WorkflowDefinitionPropertiesEditor {
|
|||
};
|
||||
|
||||
private renderVariablesTab = () => {
|
||||
const variables: Array<Variable> = this.workflowDefinition?.variables ?? [];
|
||||
|
||||
return <div>
|
||||
TODO: Variables editor
|
||||
<elsa-variables-viewer variables={variables} workflowDefinition={this.workflowDefinition} workflowInstance={this.workflowInstance} />
|
||||
</div>
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -12,10 +12,11 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net6.0'">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.1">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.12">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net7.0'">
|
||||
|
|
@ -23,14 +24,11 @@
|
|||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Elsa.EntityFrameworkCore\Elsa.EntityFrameworkCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net6.0'">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.1">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.12">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@
|
|||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net6.0'">
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.12" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.1">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.12" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.12">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net6.0'">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.1">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.12" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.12" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.12">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ namespace Elsa.EntityFrameworkCore.Modules.Management
|
|||
public void Configure(EntityTypeBuilder<WorkflowInstance> builder)
|
||||
{
|
||||
builder.Ignore(x => x.WorkflowState);
|
||||
builder.Ignore(x => x.Fault);
|
||||
builder.Property<string>("Data");
|
||||
builder.Property(x => x.Status).HasConversion<EnumToStringConverter<WorkflowStatus>>();
|
||||
builder.Property(x => x.SubStatus).HasConversion<EnumToStringConverter<WorkflowSubStatus>>();
|
||||
|
|
|
|||
|
|
@ -33,24 +33,30 @@ public class EFCoreWorkflowInstanceStore : IWorkflowInstanceStore
|
|||
public async Task<WorkflowInstance?> FindByIdAsync(string id, CancellationToken cancellationToken = default) =>
|
||||
await _store.FindAsync(x => x.Id == id, Load, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SaveAsync(WorkflowInstance record, CancellationToken cancellationToken = default) =>
|
||||
await _store.SaveAsync(record, Save, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SaveManyAsync(IEnumerable<WorkflowInstance> records, CancellationToken cancellationToken = default) =>
|
||||
await _store.SaveManyAsync(records, Save, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> DeleteAsync(string id, CancellationToken cancellationToken = default) =>
|
||||
await _store.DeleteWhereAsync(x => x.Id == id, cancellationToken) > 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> DeleteManyAsync(IEnumerable<string> ids, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var idList = ids.ToList();
|
||||
return await _store.DeleteWhereAsync(x => idList.Contains(x.Id), cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task DeleteManyByDefinitionIdAsync(string definitionId, CancellationToken cancellationToken = default) =>
|
||||
await _store.DeleteWhereAsync(x => x.DefinitionId == definitionId, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Page<WorkflowInstanceSummary>> FindManyAsync(FindWorkflowInstancesArgs args, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var dbContext = await _store.CreateDbContextAsync(cancellationToken);
|
||||
|
|
@ -94,9 +100,9 @@ public class EFCoreWorkflowInstanceStore : IWorkflowInstanceStore
|
|||
return await query.PaginateAsync(x => WorkflowInstanceSummary.FromInstance(x), pageArgs);
|
||||
}
|
||||
|
||||
public WorkflowInstance Save(ManagementElsaDbContext managementElsaDbContext, WorkflowInstance entity)
|
||||
private WorkflowInstance Save(ManagementElsaDbContext managementElsaDbContext, WorkflowInstance entity)
|
||||
{
|
||||
var data = new WorkflowInstanceState(entity.WorkflowState, entity.Fault);
|
||||
var data = entity.WorkflowState;
|
||||
var options = _serializerOptionsProvider.CreatePersistenceOptions(ReferenceHandler.Preserve);
|
||||
var json = JsonSerializer.Serialize(data, options);
|
||||
|
||||
|
|
@ -104,39 +110,22 @@ public class EFCoreWorkflowInstanceStore : IWorkflowInstanceStore
|
|||
return entity;
|
||||
}
|
||||
|
||||
public WorkflowInstance? Load(ManagementElsaDbContext managementElsaDbContext, WorkflowInstance? entity)
|
||||
private WorkflowInstance? Load(ManagementElsaDbContext managementElsaDbContext, WorkflowInstance? entity)
|
||||
{
|
||||
if (entity == null)
|
||||
return null;
|
||||
|
||||
var data = new WorkflowInstanceState(entity.WorkflowState, entity.Fault);
|
||||
var data = entity.WorkflowState;
|
||||
var json = (string?)managementElsaDbContext.Entry(entity).Property("Data").CurrentValue;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
var options = _serializerOptionsProvider.CreatePersistenceOptions(ReferenceHandler.Preserve);
|
||||
data = JsonSerializer.Deserialize<WorkflowInstanceState>(json, options)!;
|
||||
data = JsonSerializer.Deserialize<WorkflowState>(json, options)!;
|
||||
}
|
||||
|
||||
entity.WorkflowState = data.WorkflowState;
|
||||
entity.Fault = data.Fault;
|
||||
entity.WorkflowState = data;
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
private class WorkflowInstanceState
|
||||
{
|
||||
public WorkflowInstanceState()
|
||||
{
|
||||
}
|
||||
|
||||
public WorkflowInstanceState(WorkflowState workflowState, WorkflowFaultState? fault)
|
||||
{
|
||||
WorkflowState = workflowState;
|
||||
Fault = fault;
|
||||
}
|
||||
|
||||
public WorkflowState WorkflowState { get; init; } = default!;
|
||||
public WorkflowFaultState? Fault { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -7,20 +7,25 @@ using Microsoft.AspNetCore.Http;
|
|||
namespace Elsa.Http.Handlers;
|
||||
|
||||
// ReSharper disable once ClassWithVirtualMembersNeverInherited.Global
|
||||
/// <summary>
|
||||
/// A default fault handler that writes information about the fault to the <see cref="HttpResponse"/>.
|
||||
/// </summary>
|
||||
public class DefaultHttpEndpointWorkflowFaultHandler : IHttpEndpointWorkflowFaultHandler
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public virtual async ValueTask HandleAsync(HttpEndpointFaultedWorkflowContext context)
|
||||
{
|
||||
var httpContext = context.HttpContext;
|
||||
var workflowInstance = context.WorkflowInstance;
|
||||
var fault = workflowInstance.WorkflowState.Fault!;
|
||||
|
||||
httpContext.Response.ContentType = MediaTypeNames.Application.Json;
|
||||
httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
|
||||
|
||||
var faultedResponse = JsonSerializer.Serialize(new
|
||||
{
|
||||
errorMessage = $"Workflow faulted at {workflowInstance.FaultedAt!} with error: {workflowInstance.Fault!.Message}",
|
||||
exception = workflowInstance.Fault?.Exception,
|
||||
errorMessage = $"Workflow faulted at {workflowInstance.FaultedAt!} with error: {fault.Message}",
|
||||
exception = fault.Exception,
|
||||
workflow = new
|
||||
{
|
||||
name = workflowInstance.Name,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ public class WorkflowInstanceMapper : ResponseMapper<Response, WorkflowInstance>
|
|||
SubStatus = e.SubStatus,
|
||||
CorrelationId = e.CorrelationId,
|
||||
Name = e.Name,
|
||||
Fault = e.Fault,
|
||||
Properties = e.WorkflowState.Properties,
|
||||
Fault = e.WorkflowState.Fault,
|
||||
CancelledAt = e.CancelledAt,
|
||||
CreatedAt = e.CreatedAt,
|
||||
FaultedAt = e.FaultedAt,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ public class Response
|
|||
public WorkflowSubStatus SubStatus { get; set; }
|
||||
public string? CorrelationId { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public PropertyBag Properties { get; set; }
|
||||
public WorkflowFaultState? Fault { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
public DateTimeOffset? LastExecutedAt { get; set; }
|
||||
|
|
|
|||
|
|
@ -6,19 +6,23 @@ using Elsa.Workflows.Core.Models;
|
|||
namespace Elsa.Workflows.Core.Activities;
|
||||
|
||||
/// <summary>
|
||||
/// Sets a transient property on the workflow execution context the specified name value.
|
||||
/// This value is used by the <see cref="PersistWorkflowInstanceMiddleware"/> component to update the name of the workflow instance.
|
||||
/// Sets a property on the workflow execution context with the specified name value.
|
||||
/// </summary>
|
||||
[Activity("Elsa", "Workflows", "Set the name of the workflow instance to a specified value.")]
|
||||
public class SetName : Activity
|
||||
{
|
||||
internal static readonly object WorkflowInstanceNameKey = new();
|
||||
/// <summary>
|
||||
/// The property key name used to store the workflow instance name.
|
||||
/// </summary>
|
||||
public const string WorkflowInstanceNameKey = "WorkflowInstanceName";
|
||||
|
||||
/// <inheritdoc />
|
||||
[JsonConstructor]
|
||||
public SetName([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public SetName(Input<string> value, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(source, line)
|
||||
{
|
||||
Value = value;
|
||||
|
|
@ -29,9 +33,10 @@ public class SetName : Activity
|
|||
/// </summary>
|
||||
public Input<string> Value { get; set; } = new("");
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Execute(ActivityExecutionContext context)
|
||||
{
|
||||
var value = context.Get(Value);
|
||||
context.WorkflowExecutionContext.TransientProperties[WorkflowInstanceNameKey] = value!;
|
||||
context.WorkflowExecutionContext.SetProperty(WorkflowInstanceNameKey, value!);
|
||||
}
|
||||
}
|
||||
|
|
@ -49,7 +49,7 @@ public class WorkflowStateSerializer : IWorkflowStateSerializer
|
|||
|
||||
private void DeserializeProperties(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
|
||||
{
|
||||
workflowExecutionContext.Properties = state.Properties.Properties;
|
||||
workflowExecutionContext.Properties = state.Properties.Dictionary;
|
||||
}
|
||||
|
||||
private static void DeserializeCompletionCallbacks(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ public class ActivityExecutionContext
|
|||
/// <summary>
|
||||
/// A dictionary of values that can be associated with this activity execution context.
|
||||
/// </summary>
|
||||
public IDictionary<string, object> Properties { get; set; } = new Dictionary<string, object>();
|
||||
public PropertyBag Properties { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// A transient dictionary of values that can be associated with this activity execution context.
|
||||
|
|
@ -162,18 +162,18 @@ public class ActivityExecutionContext
|
|||
/// <summary>
|
||||
/// Returns a property value associated with the current activity context.
|
||||
/// </summary>
|
||||
public T? GetProperty<T>(string key) => Properties.TryGetValue<T?>(key, out var value) ? value : default;
|
||||
public T? GetProperty<T>(string key) => Properties.Dictionary.TryGetValue<T?>(key, out var value) ? value : default;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a property value associated with the current activity context.
|
||||
/// </summary>
|
||||
public T GetProperty<T>(string key, Func<T> defaultValue)
|
||||
{
|
||||
if (Properties.TryGetValue<T?>(key, out var value))
|
||||
if (Properties.Dictionary.TryGetValue<T?>(key, out var value))
|
||||
return value!;
|
||||
|
||||
value = defaultValue();
|
||||
Properties[key] = value!;
|
||||
Properties.Dictionary[key] = value!;
|
||||
|
||||
return value!;
|
||||
}
|
||||
|
|
@ -181,7 +181,7 @@ public class ActivityExecutionContext
|
|||
/// <summary>
|
||||
/// Stores a property associated with the current activity context.
|
||||
/// </summary>
|
||||
public void SetProperty<T>(string key, T? value) => Properties[key] = value!;
|
||||
public void SetProperty<T>(string key, T? value) => Properties.Dictionary[key] = value!;
|
||||
|
||||
/// <summary>
|
||||
/// Updates a property associated with the current activity context.
|
||||
|
|
@ -190,7 +190,7 @@ public class ActivityExecutionContext
|
|||
{
|
||||
var value = GetProperty<T?>(key);
|
||||
value = updater(value);
|
||||
Properties[key] = value;
|
||||
Properties.Dictionary[key] = value;
|
||||
return value;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ public class PropertyBag
|
|||
{
|
||||
}
|
||||
|
||||
public PropertyBag(IDictionary<string, object> properties)
|
||||
public PropertyBag(IDictionary<string, object> dictionary)
|
||||
{
|
||||
Properties = properties;
|
||||
Dictionary = dictionary;
|
||||
}
|
||||
|
||||
public IDictionary<string, object> Properties { get; init; }
|
||||
public IDictionary<string, object> Dictionary { get; init; }
|
||||
}
|
||||
|
|
@ -132,12 +132,12 @@ public class WorkflowExecutionContext
|
|||
public IDictionary<string, object> Output { get; } = new Dictionary<string, object>();
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary that can be used by application code and activities to store information. Values need to be serializable, since this dictionary will be persisted alongside the workflow instance.
|
||||
/// A dictionary that can be used by application code and activities to store information. Values need to be serializable.
|
||||
/// </summary>
|
||||
public IDictionary<string, object> Properties { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary that can be used by application code and middleware to store information and even services. Values do not need to be serializable, since this dictionary will not be persisted.
|
||||
/// A dictionary that can be used by application code and middleware to store information and even services. Values do not need to be serializable.
|
||||
/// All data will be gone once workflow execution completes.
|
||||
/// </summary>
|
||||
public IDictionary<object, object> TransientProperties { get; set; } = new Dictionary<object, object>();
|
||||
|
|
|
|||
|
|
@ -14,6 +14,6 @@ public class PropertyBagConverter : JsonConverter<PropertyBag>
|
|||
|
||||
public override void Write(Utf8JsonWriter writer, PropertyBag value, JsonSerializerOptions options)
|
||||
{
|
||||
JsonSerializer.Serialize(writer, value.Properties);
|
||||
JsonSerializer.Serialize(writer, value.Dictionary);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
using Elsa.Workflows.Core.Models;
|
||||
|
||||
namespace Elsa.Workflows.Core.State;
|
||||
|
||||
public class ActivityExecutionContextState
|
||||
|
|
@ -15,6 +17,6 @@ public class ActivityExecutionContextState
|
|||
public string? ParentContextId { get; set; }
|
||||
public string ScheduledActivityId { get; set; } = default!;
|
||||
public string? OwnerActivityId { get; set; }
|
||||
public IDictionary<string, object> Properties { get; set; } = new Dictionary<string, object>();
|
||||
public PropertyBag Properties { get; set; } = new();
|
||||
//public RegisterState Register { get; set; } = default!;
|
||||
}
|
||||
|
|
@ -15,7 +15,6 @@ public class WorkflowInstance : Entity
|
|||
public WorkflowSubStatus SubStatus { get; set; }
|
||||
public string? CorrelationId { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public WorkflowFaultState? Fault { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
public DateTimeOffset? LastExecutedAt { get; set; }
|
||||
public DateTimeOffset? FinishedAt { get; set; }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
using Elsa.Common.Models;
|
||||
using Elsa.Common.Services;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Mediator.Models;
|
||||
using Elsa.Mediator.Services;
|
||||
using Elsa.Workflows.Core.Activities;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
using Elsa.Workflows.Core.State;
|
||||
using Elsa.Workflows.Management.Entities;
|
||||
|
|
@ -70,6 +72,8 @@ public class AsyncWorkflowStateExporter : IWorkflowStateExporter, ICommandHandle
|
|||
workflowInstance.SubStatus = workflowState.SubStatus;
|
||||
workflowInstance.CorrelationId = workflowState.CorrelationId;
|
||||
workflowInstance.LastExecutedAt = now;
|
||||
workflowInstance.WorkflowState = workflowState;
|
||||
workflowInstance.Name = workflowState.Properties.Dictionary.GetValue(SetName.WorkflowInstanceNameKey) as string;
|
||||
|
||||
// TODO: Store timestamps such as CancelledAt, FaultedAt, etc.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue