Implement outcomes for SendHttpRequest activity (#3787)
This commit is contained in:
parent
4fd5c0e52a
commit
08069c40d2
|
|
@ -127,6 +127,9 @@ export namespace Components {
|
|||
}
|
||||
interface ElsaHomePage {
|
||||
}
|
||||
interface ElsaHttpStatusCodesEditor {
|
||||
"inputContext": ActivityInputContext;
|
||||
}
|
||||
interface ElsaInputControlSwitch {
|
||||
"activityType": string;
|
||||
"codeEditorHeight": string;
|
||||
|
|
@ -561,6 +564,12 @@ declare global {
|
|||
prototype: HTMLElsaHomePageElement;
|
||||
new (): HTMLElsaHomePageElement;
|
||||
};
|
||||
interface HTMLElsaHttpStatusCodesEditorElement extends Components.ElsaHttpStatusCodesEditor, HTMLStencilElement {
|
||||
}
|
||||
var HTMLElsaHttpStatusCodesEditorElement: {
|
||||
prototype: HTMLElsaHttpStatusCodesEditorElement;
|
||||
new (): HTMLElsaHttpStatusCodesEditorElement;
|
||||
};
|
||||
interface HTMLElsaInputControlSwitchElement extends Components.ElsaInputControlSwitch, HTMLStencilElement {
|
||||
}
|
||||
var HTMLElsaInputControlSwitchElement: {
|
||||
|
|
@ -856,6 +865,7 @@ declare global {
|
|||
"elsa-flowchart": HTMLElsaFlowchartElement;
|
||||
"elsa-form-panel": HTMLElsaFormPanelElement;
|
||||
"elsa-home-page": HTMLElsaHomePageElement;
|
||||
"elsa-http-status-codes-editor": HTMLElsaHttpStatusCodesEditorElement;
|
||||
"elsa-input-control-switch": HTMLElsaInputControlSwitchElement;
|
||||
"elsa-input-tags": HTMLElsaInputTagsElement;
|
||||
"elsa-input-tags-dropdown": HTMLElsaInputTagsDropdownElement;
|
||||
|
|
@ -996,6 +1006,9 @@ declare namespace LocalJSX {
|
|||
}
|
||||
interface ElsaHomePage {
|
||||
}
|
||||
interface ElsaHttpStatusCodesEditor {
|
||||
"inputContext"?: ActivityInputContext;
|
||||
}
|
||||
interface ElsaInputControlSwitch {
|
||||
"activityType"?: string;
|
||||
"codeEditorHeight"?: string;
|
||||
|
|
@ -1236,6 +1249,7 @@ declare namespace LocalJSX {
|
|||
"elsa-flowchart": ElsaFlowchart;
|
||||
"elsa-form-panel": ElsaFormPanel;
|
||||
"elsa-home-page": ElsaHomePage;
|
||||
"elsa-http-status-codes-editor": ElsaHttpStatusCodesEditor;
|
||||
"elsa-input-control-switch": ElsaInputControlSwitch;
|
||||
"elsa-input-tags": ElsaInputTags;
|
||||
"elsa-input-tags-dropdown": ElsaInputTagsDropdown;
|
||||
|
|
@ -1306,6 +1320,7 @@ declare module "@stencil/core" {
|
|||
"elsa-flowchart": LocalJSX.ElsaFlowchart & JSXBase.HTMLAttributes<HTMLElsaFlowchartElement>;
|
||||
"elsa-form-panel": LocalJSX.ElsaFormPanel & JSXBase.HTMLAttributes<HTMLElsaFormPanelElement>;
|
||||
"elsa-home-page": LocalJSX.ElsaHomePage & JSXBase.HTMLAttributes<HTMLElsaHomePageElement>;
|
||||
"elsa-http-status-codes-editor": LocalJSX.ElsaHttpStatusCodesEditor & JSXBase.HTMLAttributes<HTMLElsaHttpStatusCodesEditorElement>;
|
||||
"elsa-input-control-switch": LocalJSX.ElsaInputControlSwitch & JSXBase.HTMLAttributes<HTMLElsaInputControlSwitchElement>;
|
||||
"elsa-input-tags": LocalJSX.ElsaInputTags & JSXBase.HTMLAttributes<HTMLElsaInputTagsElement>;
|
||||
"elsa-input-tags-dropdown": LocalJSX.ElsaInputTagsDropdown & JSXBase.HTMLAttributes<HTMLElsaInputTagsDropdownElement>;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import 'reflect-metadata';
|
||||
import {h} from '@stencil/core';
|
||||
import {Container, Service} from "typedi";
|
||||
import {ActivityIconRegistry, PortProviderRegistry} from "../../../services";
|
||||
import {ActivityIconRegistry, InputControlRegistry, PortProviderRegistry} from "../../../services";
|
||||
import {Plugin} from "../../../models";
|
||||
import {HttpRequestPortProvider} from "./http-request-port-provider";
|
||||
import {HttpRequestIcon} from "../icons";
|
||||
|
|
@ -14,9 +14,11 @@ export class HttpRequestPlugin implements Plugin {
|
|||
const activityTypeName = HttpRequestPlugin.ActivityTypeName;
|
||||
const portProviderRegistry = Container.get(PortProviderRegistry);
|
||||
const iconRegistry = Container.get(ActivityIconRegistry);
|
||||
const inputControlRegistry = Container.get(InputControlRegistry);
|
||||
|
||||
portProviderRegistry.add(activityTypeName, () => Container.get(HttpRequestPortProvider));
|
||||
iconRegistry.add(HttpRequestPlugin.ActivityTypeName, settings => <HttpRequestIcon size={settings?.size}/>);
|
||||
inputControlRegistry.add('http-status-codes', c => <elsa-http-status-codes-editor inputContext={c}/>);
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -13,33 +13,38 @@ export class HttpRequestPortProvider implements PortProvider {
|
|||
if(activity == null)
|
||||
return [];
|
||||
|
||||
const defaultPort = {name: 'unmatchedStatusCode', displayName: 'Unmatched status code', mode: PortMode.Embedded, isBrowsable: false}; // Hide the port from the designer until the editor uI is finished.
|
||||
const casesArray = this.getCases(activity);
|
||||
const ports = casesArray.map(x => ({name: x.statusCode.toString(), displayName: x.statusCode.toString(), mode: PortMode.Embedded}));
|
||||
const defaultPort = {name: 'unmatchedStatusCode', displayName: 'Unmatched status code', mode: PortMode.Embedded}; // Hide the port from the designer until the editor uI is finished.
|
||||
const statusCodes = activity.expectedStatusCodes ?? [];
|
||||
const ports = statusCodes.map(x => ({name: x.statusCode.toString(), displayName: x.statusCode.toString(), mode: PortMode.Embedded}));
|
||||
|
||||
return [...ports, defaultPort];
|
||||
}
|
||||
|
||||
resolvePort(portName: string, context: PortProviderContext): Activity | Array<Activity> {
|
||||
const activity = context.activity as SendHttpRequest;
|
||||
const cases = this.getCases(activity);
|
||||
const matchingStatusCode = cases.find(x => x.statusCode.toString() == portName);
|
||||
return !matchingStatusCode ? activity.unmatchedStatusCode : matchingStatusCode.activity;
|
||||
|
||||
if(portName == 'unmatchedStatusCode')
|
||||
return activity.unmatchedStatusCode;
|
||||
|
||||
const expectedStatusCodes: Array<HttpStatusCodeCase> = activity.expectedStatusCodes ?? [];
|
||||
const matchingStatusCode = expectedStatusCodes.find(x => x.statusCode.toString() == portName);
|
||||
return matchingStatusCode?.activity;
|
||||
}
|
||||
|
||||
assignPort(portName: string, activity: Activity, context: PortProviderContext) {
|
||||
const sendHttpRequestActivity = context.activity as SendHttpRequest;
|
||||
const cases = this.getCases(sendHttpRequestActivity);
|
||||
const matchingCase = cases.find(x => x.statusCode.toString() === portName);
|
||||
|
||||
if(!matchingCase)
|
||||
if(portName == 'unmatchedStatusCode') {
|
||||
sendHttpRequestActivity.unmatchedStatusCode = activity;
|
||||
return;
|
||||
}
|
||||
|
||||
const statusCodes = sendHttpRequestActivity.expectedStatusCodes ?? [];
|
||||
const matchingStatusCode = statusCodes.find(x => x.statusCode.toString() === portName);
|
||||
|
||||
if(!matchingStatusCode)
|
||||
return;
|
||||
|
||||
matchingCase.activity = activity;
|
||||
}
|
||||
|
||||
private getCases(activity: SendHttpRequest): Array<HttpStatusCodeCase> {
|
||||
const cases = activity.expectedStatusCodes;
|
||||
return !cases ? [] : cases;
|
||||
matchingStatusCode.activity = activity;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
import {Component, h, Prop} from "@stencil/core";
|
||||
import {ActivityInputContext} from "../../../services/activity-input-driver";
|
||||
import {FormEntry} from "../../../components/shared/forms/form-entry";
|
||||
import {getPropertyValue} from "../../../utils";
|
||||
import {HttpStatusCodeCase, SendHttpRequest} from "./models";
|
||||
|
||||
@Component({
|
||||
tag: 'elsa-http-status-codes-editor',
|
||||
shadow: false
|
||||
})
|
||||
export class HttpStatusCodesEditor {
|
||||
@Prop() inputContext: ActivityInputContext;
|
||||
|
||||
render() {
|
||||
const inputContext = this.inputContext;
|
||||
const inputDescriptor = inputContext.inputDescriptor;
|
||||
const fieldId = inputDescriptor.name;
|
||||
const displayName = inputDescriptor.displayName;
|
||||
const hint = inputDescriptor.description;
|
||||
const expectedStatusCodes: Array<HttpStatusCodeCase> = getPropertyValue(inputContext);
|
||||
const statusCodeTags = expectedStatusCodes.map(x => x.statusCode.toString());
|
||||
|
||||
return (
|
||||
<FormEntry fieldId={fieldId} label={displayName} hint={hint}>
|
||||
<elsa-input-tags fieldId={fieldId} values={statusCodeTags} onValueChanged={this.onPropertyEditorChanged} placeHolder="Add status code"/>
|
||||
</FormEntry>
|
||||
);
|
||||
}
|
||||
|
||||
private onPropertyEditorChanged = (e: CustomEvent<Array<string>>) => {
|
||||
const statusCodes = e.detail;
|
||||
const activity = this.inputContext.activity as SendHttpRequest;
|
||||
const expectedStatusCodes = activity.expectedStatusCodes ?? [];
|
||||
|
||||
// Push new status codes.
|
||||
for (const statusCode of statusCodes) {
|
||||
if (expectedStatusCodes.findIndex(x => x.statusCode.toString() == statusCode) == -1)
|
||||
expectedStatusCodes.push({statusCode: parseInt(statusCode)});
|
||||
}
|
||||
|
||||
// Remove status codes that are no longer present.
|
||||
for (let i = expectedStatusCodes.length - 1; i >= 0; i--) {
|
||||
const statusCode = expectedStatusCodes[i].statusCode.toString();
|
||||
if (statusCodes.findIndex(x => x == statusCode) == -1)
|
||||
expectedStatusCodes.splice(i, 1);
|
||||
}
|
||||
|
||||
activity.expectedStatusCodes = expectedStatusCodes;
|
||||
this.inputContext.notifyInputChanged();
|
||||
};
|
||||
}
|
||||
|
|
@ -51,10 +51,7 @@ public class SendHttpRequest : SendHttpRequestBase
|
|||
/// </summary>
|
||||
[Input(
|
||||
Description = "A list of expected status codes to handle and the corresponding activity to execute when the status code matches.",
|
||||
UIHint = InputUIHints.MultiText,
|
||||
|
||||
// TODO: Need to implement a custom UI hint for this.
|
||||
IsBrowsable = false
|
||||
UIHint = "http-status-codes"
|
||||
)]
|
||||
public ICollection<HttpStatusCodeCase> ExpectedStatusCodes { get; set; } = new List<HttpStatusCodeCase>();
|
||||
|
||||
|
|
@ -62,8 +59,7 @@ public class SendHttpRequest : SendHttpRequestBase
|
|||
/// The activity to execute when the HTTP status code does not match any of the expected status codes.
|
||||
/// </summary>
|
||||
[Port]
|
||||
[Browsable(false)] // TODO: Need to implement a custom UI hint for this.
|
||||
public IActivity? CatchAll { get; set; }
|
||||
public IActivity? UnmatchedStatusCode { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async ValueTask HandleResponseAsync(ActivityExecutionContext context, HttpResponseMessage response)
|
||||
|
|
@ -71,7 +67,7 @@ public class SendHttpRequest : SendHttpRequestBase
|
|||
var expectedStatusCodes = ExpectedStatusCodes;
|
||||
var statusCode = (int)response.StatusCode;
|
||||
var matchingCase = expectedStatusCodes.FirstOrDefault(x => x.StatusCode == statusCode);
|
||||
var activity = matchingCase?.Activity ?? CatchAll;
|
||||
var activity = matchingCase?.Activity ?? UnmatchedStatusCode;
|
||||
|
||||
await context.ScheduleActivityAsync(activity, OnChildActivityCompletedAsync);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,12 @@ using Elsa.Http.Handlers;
|
|||
using Elsa.Http.Models;
|
||||
using Elsa.Http.Options;
|
||||
using Elsa.Http.Parsers;
|
||||
using Elsa.Http.PortResolvers;
|
||||
using Elsa.Http.Providers;
|
||||
using Elsa.Http.Services;
|
||||
using Elsa.JavaScript.Features;
|
||||
using Elsa.Liquid.Features;
|
||||
using Elsa.Workflows.Core.Contracts;
|
||||
using Elsa.Workflows.Management.Contracts;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
|
@ -110,6 +112,9 @@ public class HttpFeature : FeatureBase
|
|||
|
||||
// Activity property options providers.
|
||||
.AddSingleton<IActivityPropertyOptionsProvider, WriteHttpResponseContentTypeOptionsProvider>()
|
||||
|
||||
// Port resolvers.
|
||||
.AddSingleton<IActivityPortResolver, SendHttpRequestActivityPortResolver>()
|
||||
|
||||
// Add Http endpoint handlers.
|
||||
.AddSingleton(HttpEndpointWorkflowFaultHandler)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
using Elsa.Workflows.Core.Contracts;
|
||||
|
||||
namespace Elsa.Http.PortResolvers;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of outbound activities for a given <see cref="SendHttpRequest"/> activity's expected status codes.
|
||||
/// </summary>
|
||||
public class SendHttpRequestActivityPortResolver : IActivityPortResolver
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int Priority => 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool GetSupportsActivity(IActivity activity) => activity is SendHttpRequest;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask<IEnumerable<IActivity>> GetPortsAsync(IActivity activity, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var ports = GetPortsInternal(activity);
|
||||
return new(ports);
|
||||
}
|
||||
|
||||
private IEnumerable<IActivity> GetPortsInternal(IActivity activity)
|
||||
{
|
||||
var sendHttpRequest = (SendHttpRequest)activity;
|
||||
var cases = sendHttpRequest.ExpectedStatusCodes.Where(x => x.Activity != null);
|
||||
|
||||
foreach (var @case in cases)
|
||||
yield return @case.Activity!;
|
||||
|
||||
if (sendHttpRequest.UnmatchedStatusCode != null)
|
||||
yield return sendHttpRequest.UnmatchedStatusCode;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ using Elsa.Features.Abstractions;
|
|||
using Elsa.Features.Attributes;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.Workflows.Core.ActivationValidators;
|
||||
using Elsa.Workflows.Core.ActivityNodeResolvers;
|
||||
using Elsa.Workflows.Core.Builders;
|
||||
using Elsa.Workflows.Core.Contracts;
|
||||
using Elsa.Workflows.Core.Expressions;
|
||||
|
|
@ -14,6 +13,7 @@ using Elsa.Workflows.Core.Middleware.Activities;
|
|||
using Elsa.Workflows.Core.Middleware.Workflows;
|
||||
using Elsa.Workflows.Core.Pipelines.ActivityExecution;
|
||||
using Elsa.Workflows.Core.Pipelines.WorkflowExecution;
|
||||
using Elsa.Workflows.Core.PortResolvers;
|
||||
using Elsa.Workflows.Core.Serialization;
|
||||
using Elsa.Workflows.Core.Serialization.Providers;
|
||||
using Elsa.Workflows.Core.Services;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,21 @@
|
|||
using System.Reflection;
|
||||
using Elsa.Workflows.Core.Attributes;
|
||||
using Elsa.Workflows.Core.Contracts;
|
||||
using Elsa.Workflows.Core.Services;
|
||||
|
||||
namespace Elsa.Workflows.Core.ActivityNodeResolvers;
|
||||
namespace Elsa.Workflows.Core.PortResolvers;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of outbound activities for a given activity.
|
||||
/// </summary>
|
||||
public class OutboundActivityPortResolver : IActivityPortResolver
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int Priority => -1;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool GetSupportsActivity(IActivity activity) => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask<IEnumerable<IActivity>> GetPortsAsync(IActivity activity, CancellationToken cancellationToken = default) =>
|
||||
new(GetSinglePorts(activity)
|
||||
.Where(x => x != null)
|
||||
|
|
@ -1,17 +1,20 @@
|
|||
using Elsa.Workflows.Core.Activities;
|
||||
using Elsa.Workflows.Core.Contracts;
|
||||
using Elsa.Workflows.Core.Services;
|
||||
|
||||
namespace Elsa.Workflows.Core.ActivityNodeResolvers;
|
||||
namespace Elsa.Workflows.Core.PortResolvers;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of outbound activities for a given <see cref="Switch"/> activity's branches.
|
||||
/// </summary>
|
||||
public class SwitchActivityPortResolver : IActivityPortResolver
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int Priority => 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool GetSupportsActivity(IActivity activity) => activity is Switch;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask<IEnumerable<IActivity>> GetPortsAsync(IActivity activity, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var ports = GetPortsInternal(activity);
|
||||
|
|
@ -13,7 +13,6 @@ public interface IWorkflowStateStore
|
|||
/// </summary>
|
||||
ValueTask SaveAsync(string id, WorkflowState state, CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Load the <see cref="WorkflowState"/> by the specified ID.
|
||||
/// </summary>
|
||||
|
|
|
|||
Loading…
Reference in a new issue