BREAKING: Refactor SendHttpRequest content parser APIs
Allows user to select what content parser to use to handle response of requests made by SendHttpRequest activity.
This commit is contained in:
parent
2c9b7eb17f
commit
30ec4b20b5
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Http.Models;
|
||||
|
|
@ -11,6 +12,7 @@ using Elsa.ActivityResults;
|
|||
using Elsa.Attributes;
|
||||
using Elsa.Design;
|
||||
using Elsa.Expressions;
|
||||
using Elsa.Metadata;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
|
@ -26,7 +28,7 @@ namespace Elsa.Activities.Http
|
|||
Description = "Send an HTTP request.",
|
||||
Outcomes = new[] { OutcomeNames.Done }
|
||||
)]
|
||||
public class SendHttpRequest : Activity
|
||||
public class SendHttpRequest : Activity, IActivityPropertyOptionsProvider
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly IEnumerable<IHttpResponseContentReader> _parsers;
|
||||
|
|
@ -90,6 +92,23 @@ namespace Elsa.Activities.Http
|
|||
[ActivityInput(Hint = "Read the content of the response.", SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public bool ReadContent { get; set; }
|
||||
|
||||
[ActivityInput(
|
||||
Label = "Response Content Parser",
|
||||
Hint = "The parser to use to parse the response content.",
|
||||
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid },
|
||||
UIHint = ActivityInputUIHints.Dropdown,
|
||||
OptionsProvider = typeof(SendHttpRequest)
|
||||
)]
|
||||
public string? ResponseContentParserName { get; set; }
|
||||
|
||||
[ActivityInput(
|
||||
Label = "Response Content .NET Type",
|
||||
Hint = "The assembly-qualified .NET type name to deserialize the received content into.",
|
||||
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid },
|
||||
OptionsProvider = typeof(SendHttpRequest)
|
||||
)]
|
||||
public Type? ResponseContentTargetType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of HTTP status codes this activity can handle.
|
||||
/// </summary>
|
||||
|
|
@ -123,11 +142,11 @@ namespace Elsa.Activities.Http
|
|||
|
||||
if (hasContent && ReadContent)
|
||||
{
|
||||
var formatter = SelectContentParser(contentType);
|
||||
ResponseContent = await formatter.ReadAsync(response, cancellationToken);
|
||||
var formatter = SelectContentParser(ResponseContentParserName, contentType);
|
||||
ResponseContent = await formatter.ReadAsync(this, response, cancellationToken);
|
||||
}
|
||||
|
||||
var statusCode = (int) response.StatusCode;
|
||||
var statusCode = (int)response.StatusCode;
|
||||
var statusOutcome = statusCode.ToString();
|
||||
var supportedStatusCodes = SupportedStatusCodes;
|
||||
var isSupportedStatusCode = supportedStatusCodes == null || !supportedStatusCodes.Any() || SupportedStatusCodes?.Contains(statusCode) == true;
|
||||
|
|
@ -140,12 +159,24 @@ namespace Elsa.Activities.Http
|
|||
return Outcomes(outcomes);
|
||||
}
|
||||
|
||||
private IHttpResponseContentReader SelectContentParser(string? contentType)
|
||||
private IHttpResponseContentReader SelectContentParser(string? parserName, string? contentType)
|
||||
{
|
||||
var simpleContentType = contentType?.Split(';').First() ?? "";
|
||||
var formatters = _parsers.OrderByDescending(x => x.Priority).ToList();
|
||||
|
||||
return formatters.FirstOrDefault(x => x.GetSupportsContentType(simpleContentType)) ?? formatters.Last();
|
||||
if (string.IsNullOrWhiteSpace(parserName))
|
||||
{
|
||||
var simpleContentType = contentType?.Split(';').First() ?? "";
|
||||
var parser = _parsers.OrderByDescending(x => x.Priority).ToList();
|
||||
|
||||
return parser.FirstOrDefault(x => x.GetSupportsContentType(simpleContentType)) ?? parser.Last();
|
||||
}
|
||||
else
|
||||
{
|
||||
var parser = _parsers.FirstOrDefault(x => x.Name == parserName);
|
||||
|
||||
if (parser == null)
|
||||
throw new InvalidOperationException("The specified parser does not exist");
|
||||
|
||||
return parser;
|
||||
}
|
||||
}
|
||||
|
||||
private HttpRequestMessage CreateRequest()
|
||||
|
|
@ -174,6 +205,17 @@ namespace Elsa.Activities.Http
|
|||
|
||||
return request;
|
||||
}
|
||||
|
||||
object? IActivityPropertyOptionsProvider.GetOptions(PropertyInfo property)
|
||||
{
|
||||
if (property.Name != nameof(ResponseContentParserName))
|
||||
return null;
|
||||
|
||||
var items = _parsers.Select(x => new SelectListItem(x.Name, x.Name)).ToList();
|
||||
|
||||
items.Insert(0, new SelectListItem("Auto Select", ""));
|
||||
return items;
|
||||
}
|
||||
|
||||
private static bool GetMethodSupportsBody(string method)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ using Elsa.Services;
|
|||
using Elsa.Services.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Activities.Http
|
||||
|
|
@ -142,7 +143,7 @@ namespace Elsa.Activities.Http
|
|||
return;
|
||||
}
|
||||
|
||||
var json = _contentSerializer.Serialize(content);
|
||||
var json = JsonConvert.SerializeObject(content, DefaultContentSerializer.CreateDefaultJsonSerializationSettings());
|
||||
await response.WriteAsync(json, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,9 +43,12 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
.AddSingleton<IHttpRequestBodyParser, DefaultHttpRequestBodyParser>()
|
||||
.AddSingleton<IHttpRequestBodyParser, JsonHttpRequestBodyParser>()
|
||||
.AddSingleton<IHttpRequestBodyParser, FormHttpRequestBodyParser>()
|
||||
.AddSingleton<IHttpResponseContentReader, DefaultHttpResponseContentReader>()
|
||||
.AddSingleton<IHttpResponseContentReader, JsonHttpResponseContentReader>()
|
||||
.AddSingleton<IHttpResponseContentReader, FileResponseContentReader>()
|
||||
.AddSingleton<IHttpResponseContentReader, PlainTextHttpResponseContentReader>()
|
||||
.AddSingleton<IHttpResponseContentReader, JsonRawHttpResponseContentReader>()
|
||||
.AddSingleton<IHttpResponseContentReader, TypedHttpResponseContentReader>()
|
||||
.AddSingleton<IHttpResponseContentReader, ExpandoObjectHttpResponseContentReader>()
|
||||
.AddSingleton<IHttpResponseContentReader, JTokenHttpResponseContentReader>()
|
||||
.AddSingleton<IHttpResponseContentReader, FileHttpResponseContentReader>()
|
||||
.AddSingleton<IActionContextAccessor, ActionContextAccessor>()
|
||||
.AddSingleton<IAbsoluteUrlProvider, DefaultAbsoluteUrlProvider>()
|
||||
.AddSingleton(sp => sp.GetRequiredService<IOptions<HttpActivityOptions>>().Value.HttpEndpointAuthorizationHandlerFactory(sp))
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Http.Services;
|
||||
|
||||
namespace Elsa.Activities.Http.Parsers.Response
|
||||
{
|
||||
public class DefaultHttpResponseContentReader : IHttpResponseContentReader
|
||||
{
|
||||
public int Priority => -1;
|
||||
public bool GetSupportsContentType(string contentType) => true;
|
||||
public async Task<object> ReadAsync(HttpResponseMessage response, CancellationToken cancellationToken) => await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using System.Dynamic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Http.Services;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Elsa.Activities.Http.Parsers.Response
|
||||
{
|
||||
public class ExpandoObjectHttpResponseContentReader : IHttpResponseContentReader
|
||||
{
|
||||
public string Name => "Expando Object";
|
||||
public int Priority => 0;
|
||||
public bool GetSupportsContentType(string contentType) => contentType.Contains("/json", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public async Task<object> ReadAsync(SendHttpRequest activity, HttpResponseMessage response, CancellationToken cancellationToken)
|
||||
{
|
||||
var json = (await response.Content.ReadAsStringAsync()).Trim();
|
||||
|
||||
return json.StartsWith('[') ? JsonConvert.DeserializeObject<ExpandoObject[]>(json)! : JsonConvert.DeserializeObject<ExpandoObject>(json)!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Http.Services;
|
||||
|
||||
namespace Elsa.Activities.Http.Parsers.Response
|
||||
{
|
||||
public sealed class FileHttpResponseContentReader : IHttpResponseContentReader
|
||||
{
|
||||
public string Name => "File";
|
||||
public int Priority => 0;
|
||||
public bool GetSupportsContentType(string contentType)
|
||||
{
|
||||
var types = new[] { "audio", "video", "application", "pdf" };
|
||||
return types.Any(x => contentType.Contains(x, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public async Task<object> ReadAsync(SendHttpRequest activity, HttpResponseMessage response, CancellationToken cancellationToken) => await response.Content.ReadAsByteArrayAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Http.Services;
|
||||
|
||||
namespace Elsa.Activities.Http.Parsers.Response
|
||||
{
|
||||
public class FileResponseContentReader : IHttpResponseContentReader
|
||||
{
|
||||
public virtual int Priority => 5;
|
||||
public virtual bool GetSupportsContentType(string contentType)
|
||||
{
|
||||
var types = new[] { "audio", "video", "application", "pdf" };
|
||||
return types.Any(x => contentType.Contains(x, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public async Task<object> ReadAsync(HttpResponseMessage response, CancellationToken cancellationToken) => await response.Content.ReadAsByteArrayAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Http.Services;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Elsa.Activities.Http.Parsers.Response
|
||||
{
|
||||
public class JTokenHttpResponseContentReader : IHttpResponseContentReader
|
||||
{
|
||||
public string Name => "JToken";
|
||||
public int Priority => 0;
|
||||
public bool GetSupportsContentType(string contentType) => contentType.Contains("/json", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public async Task<object> ReadAsync(SendHttpRequest activity, HttpResponseMessage response, CancellationToken cancellationToken)
|
||||
{
|
||||
var json = (await response.Content.ReadAsStringAsync()).Trim();
|
||||
return JToken.Parse(json);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
using System;
|
||||
using System.Dynamic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Http.Services;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Elsa.Activities.Http.Parsers.Response
|
||||
{
|
||||
public class JsonHttpResponseContentReader : IHttpResponseContentReader
|
||||
{
|
||||
public int Priority => 10;
|
||||
public bool GetSupportsContentType(string contentType) => contentType.Contains("/json", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public async Task<object> ReadAsync(HttpResponseMessage response, CancellationToken cancellationToken)
|
||||
{
|
||||
#if NET
|
||||
var json = (await response.Content.ReadAsStringAsync(cancellationToken)).Trim();
|
||||
#else
|
||||
var json = (await response.Content.ReadAsStringAsync()).Trim();
|
||||
#endif
|
||||
if(json.StartsWith('['))
|
||||
return JsonConvert.DeserializeObject<ExpandoObject[]>(json)!;
|
||||
|
||||
return JsonConvert.DeserializeObject<ExpandoObject>(json)!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Http.Services;
|
||||
|
||||
namespace Elsa.Activities.Http.Parsers.Response
|
||||
{
|
||||
public class JsonRawHttpResponseContentReader : IHttpResponseContentReader
|
||||
{
|
||||
public string Name => "JSON";
|
||||
public int Priority => 100;
|
||||
public bool GetSupportsContentType(string contentType) => contentType.Contains("/json", StringComparison.OrdinalIgnoreCase);
|
||||
public async Task<object> ReadAsync(SendHttpRequest activity, HttpResponseMessage response, CancellationToken cancellationToken) => await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Http.Services;
|
||||
|
||||
namespace Elsa.Activities.Http.Parsers.Response
|
||||
{
|
||||
public class PlainTextHttpResponseContentReader : IHttpResponseContentReader
|
||||
{
|
||||
public string Name => "Plain Text";
|
||||
public int Priority => -1;
|
||||
public bool GetSupportsContentType(string contentType) => true;
|
||||
public async Task<object> ReadAsync(SendHttpRequest activity, HttpResponseMessage response, CancellationToken cancellationToken) => await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Http.Services;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Elsa.Activities.Http.Parsers.Response
|
||||
{
|
||||
public class TypedHttpResponseContentReader : IHttpResponseContentReader
|
||||
{
|
||||
public string Name => ".NET Type";
|
||||
public int Priority => 0;
|
||||
public bool GetSupportsContentType(string contentType) => GetIsJsonContentType(contentType);
|
||||
|
||||
public async Task<object> ReadAsync(SendHttpRequest activity, HttpResponseMessage response, CancellationToken cancellationToken)
|
||||
{
|
||||
var contentType = response.Content.Headers.ContentType.MediaType;
|
||||
|
||||
if (GetIsJsonContentType(contentType))
|
||||
{
|
||||
var json = (await response.Content.ReadAsStringAsync()).Trim();
|
||||
var targetType = activity.ResponseContentTargetType ?? typeof(ExpandoObject);
|
||||
return JsonConvert.DeserializeObject(json, targetType)!;
|
||||
}
|
||||
|
||||
if (GetIsXmlContentType(contentType))
|
||||
{
|
||||
// TODO: parse XML.
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
private bool GetIsJsonContentType(string contentType) => GetIsContentType(contentType, "json");
|
||||
private bool GetIsXmlContentType(string contentType) => GetIsContentType(contentType, "xml");
|
||||
private bool GetIsContentType(string contentType, string match) => contentType.Contains($"/{match}", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,8 +6,9 @@ namespace Elsa.Activities.Http.Services
|
|||
{
|
||||
public interface IHttpResponseContentReader
|
||||
{
|
||||
string Name { get; }
|
||||
int Priority { get; }
|
||||
bool GetSupportsContentType(string contentType);
|
||||
Task<object> ReadAsync(HttpResponseMessage response, CancellationToken cancellationToken);
|
||||
Task<object> ReadAsync(SendHttpRequest activity, HttpResponseMessage response, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,6 @@ namespace Elsa.Metadata
|
|||
{
|
||||
public interface IActivityPropertyOptionsProvider
|
||||
{
|
||||
object GetOptions(PropertyInfo property);
|
||||
object? GetOptions(PropertyInfo property);
|
||||
}
|
||||
}
|
||||
|
|
@ -811,6 +811,8 @@ declare namespace LocalJSX {
|
|||
"propertyModel"?: ActivityDefinitionProperty;
|
||||
}
|
||||
interface ElsaModalDialog {
|
||||
"onHidden"?: (event: CustomEvent<any>) => void;
|
||||
"onShown"?: (event: CustomEvent<any>) => void;
|
||||
}
|
||||
interface ElsaMonaco {
|
||||
"editorHeight"?: string;
|
||||
|
|
|
|||
|
|
@ -150,13 +150,13 @@ export class ElsaWorkflowDesigner {
|
|||
|
||||
@Method()
|
||||
async showActivityEditor(activity: ActivityModel, animate: boolean) {
|
||||
this.showActivityEditorInternal(activity, animate);
|
||||
await this.showActivityEditorInternal(activity, animate);
|
||||
}
|
||||
|
||||
async copyActivitiesToClipboard() {
|
||||
this.checkClipboardPermissions();
|
||||
await navigator.clipboard.writeText(JSON.stringify(this.selectedActivities));
|
||||
eventBus.emit(EventTypes.ClipboardCopied, this);
|
||||
await eventBus.emit(EventTypes.ClipboardCopied, this);
|
||||
}
|
||||
|
||||
async pasteActivitiesFromClipboard() {
|
||||
|
|
@ -167,7 +167,7 @@ export class ElsaWorkflowDesigner {
|
|||
await navigator.clipboard.readText().then(data => {
|
||||
copiedActivities = JSON.parse(data);
|
||||
});
|
||||
this.addActivitiesFromClipboard(copiedActivities)
|
||||
await this.addActivitiesFromClipboard(copiedActivities)
|
||||
}
|
||||
|
||||
async addActivitiesFromClipboard(copiedActivities: Array<ActivityModel>) {
|
||||
|
|
@ -305,12 +305,8 @@ export class ElsaWorkflowDesigner {
|
|||
};
|
||||
}
|
||||
|
||||
showActivityEditorInternal(activity: ActivityModel, animate: boolean) {
|
||||
eventBus.emit(EventTypes.ShowActivityEditor, this, activity, animate);
|
||||
}
|
||||
|
||||
handleEditActivity(activity: ActivityModel) {
|
||||
this.showActivityEditorInternal(activity, true);
|
||||
async showActivityEditorInternal(activity: ActivityModel, animate: boolean) {
|
||||
await eventBus.emit(EventTypes.ActivityEditor.Show, this, activity, animate);
|
||||
}
|
||||
|
||||
updateWorkflowModel(model: WorkflowModel, emitEvent: boolean = true) {
|
||||
|
|
@ -618,7 +614,7 @@ export class ElsaWorkflowDesigner {
|
|||
const activityDescriptor = args as ActivityDescriptor;
|
||||
const activityModel = this.newActivity(activityDescriptor);
|
||||
this.addingActivity = true;
|
||||
this.showActivityEditorInternal(activityModel, false);
|
||||
await this.showActivityEditorInternal(activityModel, false);
|
||||
};
|
||||
|
||||
onUpdateActivity = args => {
|
||||
|
|
@ -643,7 +639,7 @@ export class ElsaWorkflowDesigner {
|
|||
this.selectedActivities = {};
|
||||
activityModel.outcomes[0] = this.parentActivityOutcome;
|
||||
this.selectedActivities[activityModel.activityId] = activityModel;
|
||||
this.pasteActivitiesFromClipboard();
|
||||
await this.pasteActivitiesFromClipboard();
|
||||
};
|
||||
|
||||
onCopyPasteActivityEnabled = () => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
import {Component, Event, h, Host, Prop, State} from '@stencil/core';
|
||||
import {eventBus, propertyDisplayManager} from '../../../../services';
|
||||
import state from '../../../../utils/store';
|
||||
import {ActivityDescriptor, ActivityModel, ActivityPropertyDescriptor, EventTypes, WorkflowStorageDescriptor} from "../../../../models";
|
||||
import {
|
||||
ActivityDescriptor,
|
||||
ActivityModel,
|
||||
ActivityPropertyDescriptor,
|
||||
EventTypes,
|
||||
WorkflowStorageDescriptor
|
||||
} from "../../../../models";
|
||||
import {checkBox, FormContext, section, selectField, SelectOption, textArea, textInput} from "../../../../utils/forms";
|
||||
import {i18n} from "i18next";
|
||||
import {loadTranslations} from "../../../i18n/i18n-loader";
|
||||
|
|
@ -21,6 +27,11 @@ export interface ActivityEditorRenderProps {
|
|||
selectedTabName?: string;
|
||||
}
|
||||
|
||||
export interface ActivityEditorAppearingEventArgs {
|
||||
activityDescriptor: ActivityDescriptor;
|
||||
activityModel: ActivityModel;
|
||||
}
|
||||
|
||||
@Component({
|
||||
tag: 'elsa-activity-editor-modal',
|
||||
shadow: false,
|
||||
|
|
@ -33,7 +44,6 @@ export class ElsaActivityEditorModal {
|
|||
@State() renderProps: ActivityEditorRenderProps = {};
|
||||
i18next: i18n;
|
||||
dialog: HTMLElsaModalDialogElement;
|
||||
form: HTMLFormElement;
|
||||
formContext: FormContext;
|
||||
|
||||
// Force a new key every time we show the editor to make sure Stencil creates new components.
|
||||
|
|
@ -41,11 +51,11 @@ export class ElsaActivityEditorModal {
|
|||
timestamp: Date = new Date();
|
||||
|
||||
connectedCallback() {
|
||||
eventBus.on(EventTypes.ShowActivityEditor, this.onShowActivityEditor);
|
||||
eventBus.on(EventTypes.ActivityEditor.Show, this.onShowActivityEditor);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
eventBus.detach(EventTypes.ShowActivityEditor, this.onShowActivityEditor);
|
||||
eventBus.detach(EventTypes.ActivityEditor.Show, this.onShowActivityEditor);
|
||||
}
|
||||
|
||||
async componentWillLoad() {
|
||||
|
|
@ -64,10 +74,26 @@ export class ElsaActivityEditorModal {
|
|||
}
|
||||
|
||||
componentWillRender() {
|
||||
const activityDescriptor: ActivityDescriptor = this.activityDescriptor || {displayName: '', type: '', outcomes: [], category: '', traits: 0, browsable: false, inputProperties: [], outputProperties: [], description: ''};
|
||||
const activityDescriptor: ActivityDescriptor = this.activityDescriptor || {
|
||||
displayName: '',
|
||||
type: '',
|
||||
outcomes: [],
|
||||
category: '',
|
||||
traits: 0,
|
||||
browsable: false,
|
||||
inputProperties: [],
|
||||
outputProperties: [],
|
||||
description: ''
|
||||
};
|
||||
const propertyCategories = activityDescriptor.inputProperties.filter(x => x.category).map(x => x.category).distinct();
|
||||
const defaultProperties = activityDescriptor.inputProperties.filter(x => !x.category || x.category.length == 0);
|
||||
const activityModel: ActivityModel = this.activityModel || {type: '', activityId: '', outcomes: [], properties: [], propertyStorageProviders: {}};
|
||||
const activityModel: ActivityModel = this.activityModel || {
|
||||
type: '',
|
||||
activityId: '',
|
||||
outcomes: [],
|
||||
properties: [],
|
||||
propertyStorageProviders: {}
|
||||
};
|
||||
const t = this.t;
|
||||
let tabs: Array<TabModel> = [];
|
||||
|
||||
|
|
@ -106,8 +132,6 @@ export class ElsaActivityEditorModal {
|
|||
selectedTabName: this.renderProps.selectedTabName
|
||||
};
|
||||
|
||||
eventBus.emit(EventTypes.ActivityEditorDisplaying, this, this.renderProps);
|
||||
|
||||
let selectedTabName = this.renderProps.selectedTabName
|
||||
tabs = this.renderProps.tabs;
|
||||
|
||||
|
|
@ -119,7 +143,7 @@ export class ElsaActivityEditorModal {
|
|||
}
|
||||
|
||||
async onCancelClick() {
|
||||
await this.dialog.hide(true);
|
||||
await this.hide(true);
|
||||
}
|
||||
|
||||
onSubmit = async (e: Event) => {
|
||||
|
|
@ -127,8 +151,8 @@ export class ElsaActivityEditorModal {
|
|||
const form: any = e.target;
|
||||
const formData = new FormData(form);
|
||||
this.updateActivity(formData);
|
||||
eventBus.emit(EventTypes.UpdateActivity, this, this.activityModel);
|
||||
await this.dialog.hide(true);
|
||||
await eventBus.emit(EventTypes.UpdateActivity, this, this.activityModel);
|
||||
await this.hide(true);
|
||||
};
|
||||
|
||||
onTabClick = (e: Event, tab: TabModel) => {
|
||||
|
|
@ -143,15 +167,31 @@ export class ElsaActivityEditorModal {
|
|||
this.formContext = new FormContext(this.activityModel, newValue => this.activityModel = newValue);
|
||||
this.timestamp = new Date();
|
||||
this.renderProps = {};
|
||||
await this.dialog.show(animate);
|
||||
await this.show(animate);
|
||||
};
|
||||
|
||||
show = async (animate: boolean) => await this.dialog.show(animate);
|
||||
hide = async (animate: boolean) => await this.dialog.hide(animate);
|
||||
|
||||
onKeyDown = async (event: KeyboardEvent) => {
|
||||
if (event.ctrlKey && event.key === 'Enter') {
|
||||
(this.dialog.querySelector('button[type="submit"]') as HTMLButtonElement).click();
|
||||
}
|
||||
}
|
||||
|
||||
onDialogShown = async () => {
|
||||
const args: ActivityEditorAppearingEventArgs = {
|
||||
activityModel: this.activityModel,
|
||||
activityDescriptor: this.activityDescriptor
|
||||
};
|
||||
|
||||
await eventBus.emit(EventTypes.ActivityEditor.Appearing, this, args);
|
||||
};
|
||||
|
||||
onDialogHidden = async () => {
|
||||
await eventBus.emit(EventTypes.ActivityEditor.Disappearing);
|
||||
};
|
||||
|
||||
render() {
|
||||
const renderProps = this.renderProps;
|
||||
const activityDescriptor: ActivityDescriptor = renderProps.activityDescriptor;
|
||||
|
|
@ -163,9 +203,10 @@ export class ElsaActivityEditorModal {
|
|||
|
||||
return (
|
||||
<Host class="elsa-block">
|
||||
<elsa-modal-dialog ref={el => this.dialog = el}>
|
||||
<elsa-modal-dialog ref={el => this.dialog = el} onShown={this.onDialogShown} onHidden={this.onDialogHidden}>
|
||||
<div slot="content" class="elsa-py-8 elsa-pb-0">
|
||||
<form onSubmit={e => this.onSubmit(e)} ref={el => this.form = el} key={this.timestamp.getTime().toString()} onKeyDown={this.onKeyDown}>
|
||||
<form onSubmit={e => this.onSubmit(e)} key={this.timestamp.getTime().toString()} onKeyDown={this.onKeyDown}
|
||||
class='activity-editor-form'>
|
||||
<div class="elsa-flex elsa-px-8">
|
||||
<div class="elsa-space-y-8 elsa-divide-y elsa-divide-gray-200 elsa-w-full">
|
||||
<div>
|
||||
|
|
@ -183,7 +224,8 @@ export class ElsaActivityEditorModal {
|
|||
{tabs.map(tab => {
|
||||
const isSelected = tab.tabName === selectedTabName;
|
||||
const cssClass = isSelected ? selectedClass : inactiveClass;
|
||||
return <a href="#" onClick={e => this.onTabClick(e, tab)} class={`${cssClass} elsa-whitespace-nowrap elsa-py-4 elsa-px-1 elsa-border-b-2 elsa-font-medium elsa-text-sm`}>{tab.tabName}</a>;
|
||||
return <a href="#" onClick={e => this.onTabClick(e, tab)}
|
||||
class={`${cssClass} elsa-whitespace-nowrap elsa-py-4 elsa-px-1 elsa-border-b-2 elsa-font-medium elsa-text-sm`}>{tab.tabName}</a>;
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
|
|
@ -229,7 +271,10 @@ export class ElsaActivityEditorModal {
|
|||
renderStorageTab(activityModel: ActivityModel, activityDescriptor: ActivityDescriptor) {
|
||||
const formContext = this.formContext;
|
||||
const t = this.t;
|
||||
let storageDescriptorOptions: Array<SelectOption> = this.workflowStorageDescriptors.map(x => ({value: x.name, text: x.displayName}));
|
||||
let storageDescriptorOptions: Array<SelectOption> = this.workflowStorageDescriptors.map(x => ({
|
||||
value: x.name,
|
||||
text: x.displayName
|
||||
}));
|
||||
let outputProperties = activityDescriptor.outputProperties.filter(x => !x.disableWorkflowProviderSelection);
|
||||
let inputProperties = activityDescriptor.inputProperties.filter(x => !x.disableWorkflowProviderSelection);
|
||||
|
||||
|
|
@ -303,7 +348,8 @@ export class ElsaActivityEditorModal {
|
|||
renderPropertyEditor(activity: ActivityModel, property: ActivityPropertyDescriptor) {
|
||||
const key = `activity-property-input:${activity.activityId}:${property.name}`;
|
||||
const display = propertyDisplayManager.display(activity, property);
|
||||
return <elsa-control key={key} class="sm:elsa-col-span-6" content={display}/>;
|
||||
const id = `${property.name}Control`;
|
||||
return <elsa-control key={key} id={id} class="sm:elsa-col-span-6" content={display}/>;
|
||||
}
|
||||
|
||||
getHiddenClass(tab: string) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import {Component, Host, h, State, Listen, Method} from '@stencil/core';
|
||||
import {Component, Host, h, State, Listen, Method, Event, EventEmitter} from '@stencil/core';
|
||||
import {enter, leave} from 'el-transition'
|
||||
import {eventBus} from "../../../services";
|
||||
import {EventTypes} from "../../../models";
|
||||
|
|
@ -8,7 +8,8 @@ import {EventTypes} from "../../../models";
|
|||
shadow: false,
|
||||
})
|
||||
export class ElsaModalDialog {
|
||||
|
||||
@Event() shown: EventEmitter;
|
||||
@Event() hidden: EventEmitter;
|
||||
@State() isVisible: boolean;
|
||||
overlay: HTMLElement
|
||||
modal: HTMLElement
|
||||
|
|
@ -24,7 +25,7 @@ export class ElsaModalDialog {
|
|||
|
||||
@Method()
|
||||
async hide(animate: boolean) {
|
||||
eventBus.emit(EventTypes.HideModalDialog);
|
||||
await eventBus.emit(EventTypes.HideModalDialog);
|
||||
this.hideInternal(animate);
|
||||
}
|
||||
|
||||
|
|
@ -37,7 +38,7 @@ export class ElsaModalDialog {
|
|||
}
|
||||
|
||||
enter(this.overlay);
|
||||
enter(this.modal);
|
||||
enter(this.modal).then(this.shown.emit);
|
||||
}
|
||||
|
||||
hideInternal(animate: boolean) {
|
||||
|
|
@ -46,7 +47,10 @@ export class ElsaModalDialog {
|
|||
}
|
||||
|
||||
leave(this.overlay);
|
||||
leave(this.modal).then(() => this.isVisible = false);
|
||||
leave(this.modal).then(() => {
|
||||
this.isVisible = false;
|
||||
this.hidden.emit();
|
||||
});
|
||||
}
|
||||
|
||||
@Listen('keydown', {target: 'window'})
|
||||
|
|
@ -60,7 +64,8 @@ export class ElsaModalDialog {
|
|||
return (
|
||||
<Host class={{'hidden': !this.isVisible, 'elsa-block': true}}>
|
||||
<div class="elsa-fixed elsa-z-10 elsa-inset-0 elsa-overflow-y-auto">
|
||||
<div class="elsa-flex elsa-items-end elsa-justify-center elsa-min-h-screen elsa-pt-4 elsa-px-4 elsa-pb-20 elsa-text-center sm:elsa-block sm:elsa-p-0">
|
||||
<div
|
||||
class="elsa-flex elsa-items-end elsa-justify-center elsa-min-h-screen elsa-pt-4 elsa-px-4 elsa-pb-20 elsa-text-center sm:elsa-block sm:elsa-p-0">
|
||||
<div ref={el => this.overlay = el}
|
||||
onClick={() => this.hide(true)}
|
||||
data-transition-enter="elsa-ease-out elsa-duration-300" data-transition-enter-start="elsa-opacity-0"
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
<!-- Instead of using the full dashboard component, you can display lower-level components instead-->
|
||||
<!--<elsa-workflow-instance-list-screen></elsa-workflow-instance-list-screen>-->
|
||||
<!--<elsa-workflow-definition-editor-screen></elsa-workflow-definition-editor-screen>-->
|
||||
<!--<elsa-workflow-definition-editor-screen workflow-definition-id="{workflowDefinitionId}"></elsa-workflow-definition-editor-screen>-->
|
||||
</elsa-studio-root>
|
||||
|
||||
<!-- Keep this to avoid Stencil from stripping classes added by dagre-d3 and dynamically computed classes <!-->
|
||||
|
|
|
|||
|
|
@ -2,11 +2,14 @@
|
|||
import {ActivityDescriptor} from "./domain";
|
||||
|
||||
export const EventTypes = {
|
||||
ActivityEditor: {
|
||||
Show: 'show-activity-editor',
|
||||
Appearing: 'activity-editor-appearing',
|
||||
Disappearing: 'activity-editor-disappearing'
|
||||
},
|
||||
ShowActivityPicker: 'show-activity-picker',
|
||||
ShowWorkflowSettings: 'show-workflow-settings',
|
||||
ActivityPicked: 'activity-picked',
|
||||
ShowActivityEditor: 'show-activity-editor',
|
||||
ActivityEditorDisplaying: 'activity-editor-displaying',
|
||||
UpdateActivity: 'update-activity',
|
||||
UpdateWorkflowSettings: 'update-workflow-settings',
|
||||
WorkflowModelChanged: 'workflow-model-changed',
|
||||
|
|
@ -67,4 +70,4 @@ export interface ConfigureWorkflowRegistryColumnsContext {
|
|||
|
||||
export interface ConfigureWorkflowRegistryUpdatingContext {
|
||||
params: any;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
import {eventBus, ElsaPlugin} from "../services";
|
||||
import {ActivityDesignDisplayContext, EventTypes, SyntaxNames} from "../models";
|
||||
import {h} from "@stencil/core";
|
||||
import {parseJson} from "../utils/utils";
|
||||
|
||||
export class SendHttpRequestPlugin implements ElsaPlugin {
|
||||
constructor() {
|
||||
eventBus.on(EventTypes.ActivityDesignDisplaying, this.onActivityDesignDisplaying);
|
||||
}
|
||||
|
||||
onActivityDesignDisplaying(context: ActivityDesignDisplayContext) {
|
||||
const activityModel = context.activityModel;
|
||||
|
||||
if (activityModel.type !== 'SendHttpRequest')
|
||||
return;
|
||||
|
||||
const props = activityModel.properties || [];
|
||||
const syntax = SyntaxNames.Json;
|
||||
const supportedStatusCodes = props.find(x => x.name == 'SupportedStatusCodes') || {expressions: {'Json': '[]'}, syntax: syntax};
|
||||
const expression = supportedStatusCodes.expressions[syntax] || [];
|
||||
let outcomes = !!expression['$values'] ? expression['$values'] : Array.isArray(expression) ? expression : parseJson(expression) || [];
|
||||
|
||||
outcomes = [...outcomes, 'Done', 'Unsupported Status Code']
|
||||
|
||||
context.outcomes = outcomes;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import {eventBus, ElsaPlugin} from "../../services";
|
||||
import {EventTypes} from "../../models";
|
||||
import {h} from "@stencil/core";
|
||||
import {ActivityEditorAppearingEventArgs} from "../../components/screens/workflow-definition-editor/elsa-activity-editor-modal/elsa-activity-editor-modal";
|
||||
|
||||
export class SendHttpRequestPlugin implements ElsaPlugin {
|
||||
constructor() {
|
||||
eventBus.on(EventTypes.ActivityEditor.Appearing, this.onActivityEditorAppearing);
|
||||
eventBus.on(EventTypes.ActivityEditor.Disappearing, this.onActivityEditorDisappearing);
|
||||
|
||||
}
|
||||
|
||||
onActivityEditorAppearing = (args: ActivityEditorAppearingEventArgs) => {
|
||||
if (args.activityDescriptor.type != 'SendHttpRequest')
|
||||
return;
|
||||
|
||||
document.querySelector('#ReadContent').addEventListener('change', this.updateUI);
|
||||
document.querySelector('#ResponseContentParserName').addEventListener('change', this.updateUI);
|
||||
this.updateUI();
|
||||
};
|
||||
|
||||
onActivityEditorDisappearing = () => {
|
||||
document.querySelector('#ReadContent').removeEventListener('change', this.updateUI);
|
||||
document.querySelector('#ResponseContentParserName').removeEventListener('change', this.updateUI);
|
||||
};
|
||||
|
||||
updateUI = () => {
|
||||
const readContentCheckbox = document.querySelector('#ReadContent') as HTMLInputElement;
|
||||
const parserList = document.querySelector('#ResponseContentParserName') as HTMLSelectElement;
|
||||
const responseContentParserListControl = document.querySelector('#ResponseContentParserNameControl') as HTMLElsaControlElement;
|
||||
const responseContentTargetTypeControl = document.querySelector('#ResponseContentTargetTypeControl') as HTMLElsaControlElement;
|
||||
const selectedParserName = parserList.value;
|
||||
|
||||
responseContentParserListControl.classList.toggle('hidden', !readContentCheckbox.checked)
|
||||
responseContentTargetTypeControl.classList.toggle('hidden', (!readContentCheckbox.checked || selectedParserName != '.NET Type'));
|
||||
};
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ import {SignalReceivedPlugin} from "../plugins/signal-received-plugin";
|
|||
import {SendSignalPlugin} from "../plugins/send-signal-plugin";
|
||||
import {UserTaskPlugin} from "../plugins/user-task-plugin";
|
||||
import {StatePlugin} from "../plugins/state-plugin";
|
||||
import {SendHttpRequestPlugin} from "../plugins/send-http-request-plugin";
|
||||
import {SendHttpRequestPlugin} from "../plugins/send-http-request/send-http-request-plugin";
|
||||
import {RunWorkflowPlugin} from "../plugins/run-workflow-plugin";
|
||||
import {ElsaStudio} from "../models";
|
||||
|
||||
|
|
|
|||
|
|
@ -83,12 +83,17 @@ namespace Elsa.Activities.JavaScript
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
object IActivityPropertyOptionsProvider.GetOptions(PropertyInfo property) =>
|
||||
new
|
||||
object? IActivityPropertyOptionsProvider.GetOptions(PropertyInfo property)
|
||||
{
|
||||
if (property.Name != nameof(Script))
|
||||
return null;
|
||||
|
||||
return new
|
||||
{
|
||||
EditorHeight = "Large",
|
||||
Context = nameof(RunJavaScript),
|
||||
Syntax = JavaScriptExpressionHandler.SyntaxName
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue