some test changes

This commit is contained in:
Andrey Mishurinsky 2021-09-02 09:47:31 +03:00
parent 230608fd86
commit a24fa583cc
15 changed files with 457 additions and 17 deletions

View file

@ -56,6 +56,13 @@ namespace Elsa.Activities.Http
[ActivityInput(Category = PropertyCategories.Advanced)]
public Type? TargetType { get; set; }
[ActivityInput(
Category = PropertyCategories.Advanced,
UIHint = ActivityInputUIHints.MultiLine,
DefaultSyntax = SyntaxNames.Json,
SupportedSyntaxes = new[] { SyntaxNames.Json })]
public string? Schema { get; set; }
[ActivityInput(
Hint = "Check to allow authenticated requests only",
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid },

View file

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Models;
@ -29,11 +29,21 @@ namespace Elsa.Services
/// </summary>
Task<StartableWorkflow?> FindStartableWorkflowAsync(string workflowDefinitionId, string? activityId = default, string? correlationId = default, string? contextId = default, string? tenantId = default, CancellationToken cancellationToken = default);
/// <summary>
/// Creates a new test workflow instance with execution pending for the specified workflow blueprint using the specified starting activity ID.
/// </summary>
Task<StartableWorkflow?> FindTestableWorkflowAsync(string workflowDefinitionId, string? activityId = default, string? correlationId = default, string? contextId = default, string? tenantId = default, CancellationToken cancellationToken = default);
/// <summary>
/// Creates a new workflow instance with execution pending for the specified workflow blueprint using the specified starting activity ID.
/// </summary>
Task<StartableWorkflow?> FindStartableWorkflowAsync(IWorkflowBlueprint workflowBlueprint, string? activityId = default, string? correlationId = default, string? contextId = default, string? tenantId = default, CancellationToken cancellationToken = default);
/// <summary>
/// Creates a new test workflow instance with execution pending for the specified workflow blueprint using the specified starting activity ID.
/// </summary>
Task<StartableWorkflow?> FindTestableWorkflowAsync(IWorkflowBlueprint workflowBlueprint, string? activityId = default, string? correlationId = default, string? contextId = default, string? tenantId = default, CancellationToken cancellationToken = default);
/// <summary>
/// Collects and executes the specified startable workflow.
/// </summary>
@ -78,7 +88,12 @@ namespace Elsa.Services
/// Executes the specified startable workflow.
/// </summary>
Task<RunWorkflowResult> ExecuteStartableWorkflowAsync(StartableWorkflow startableWorkflow, WorkflowInput? input = default, CancellationToken cancellationToken = default);
/// <summary>
/// Executes the specified testable workflow.
/// </summary>
Task<RunWorkflowResult> ExecuteTestableWorkflowAsync(StartableWorkflow startableWorkflow, WorkflowInput? input = default, CancellationToken cancellationToken = default);
/// <summary>
/// Dispatches the specified startable workflow.
/// </summary>

View file

@ -97,6 +97,22 @@ namespace Elsa.Services.Workflows
return await FindStartableWorkflowAsync(workflowBlueprint, activityId, correlationId, contextId, tenantId, cancellationToken);
}
public async Task<StartableWorkflow?> FindTestableWorkflowAsync(
string workflowDefinitionId,
string? activityId,
string? correlationId = default,
string? contextId = default,
string? tenantId = default,
CancellationToken cancellationToken = default)
{
var workflowBlueprint = await _workflowRegistry.GetAsync(workflowDefinitionId, tenantId, VersionOptions.Latest, cancellationToken);
if (workflowBlueprint == null)
return null;
return await FindTestableWorkflowAsync(workflowBlueprint, activityId, correlationId, contextId, tenantId, cancellationToken);
}
public async Task<StartableWorkflow?> FindStartableWorkflowAsync(
IWorkflowBlueprint workflowBlueprint,
string? activityId,
@ -113,6 +129,22 @@ namespace Elsa.Services.Workflows
return await CollectStartableWorkflowInternalAsync(workflowBlueprint, activityId, correlationId, contextId, tenantId, cancellationToken);
}
public async Task<StartableWorkflow?> FindTestableWorkflowAsync(
IWorkflowBlueprint workflowBlueprint,
string? activityId,
string? correlationId = default,
string? contextId = default,
string? tenantId = default,
CancellationToken cancellationToken = default)
{
correlationId ??= Guid.NewGuid().ToString("N");
// 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);
}
public async Task FindAndExecuteStartableWorkflowAsync(
string workflowDefinitionId,
string? activityId,
@ -179,6 +211,9 @@ namespace Elsa.Services.Workflows
public async Task<RunWorkflowResult> ExecuteStartableWorkflowAsync(StartableWorkflow startableWorkflow, WorkflowInput? input, CancellationToken cancellationToken = default) =>
await _workflowRunner.RunWorkflowAsync(startableWorkflow.WorkflowBlueprint, startableWorkflow.WorkflowInstance, startableWorkflow.ActivityId, input, cancellationToken);
public async Task<RunWorkflowResult> ExecuteTestableWorkflowAsync(StartableWorkflow startableWorkflow, WorkflowInput? input, CancellationToken cancellationToken = default) =>
await _workflowRunner.RunWorkflowAsync(startableWorkflow.WorkflowBlueprint, startableWorkflow.WorkflowInstance, startableWorkflow.ActivityId, input, cancellationToken);
public async Task<CollectedWorkflow> DispatchStartableWorkflowAsync(StartableWorkflow startableWorkflow, WorkflowInput? input, CancellationToken cancellationToken = default)
{
var pendingWorkflow = new CollectedWorkflow(startableWorkflow.WorkflowInstance.Id, startableWorkflow.ActivityId);

View file

@ -38,6 +38,7 @@ export class ElsaExpressionEditor {
async componentDidLoad() {
const elsaClient = createElsaClient(this.serverUrl);
debugger
const libSource = await elsaClient.scriptingApi.getJavaScriptTypeDefinitions(this.workflowDefinitionId, this.context);
const libUri = 'defaultLib:lib.es6.d.ts';
await this.monacoEditor.addJavaScriptLib(libSource, libUri);

View file

@ -27,6 +27,7 @@ export class ElsaScriptProperty {
async componentDidLoad() {
const elsaClient = createElsaClient(this.serverUrl);
debugger
const libSource = await elsaClient.scriptingApi.getJavaScriptTypeDefinitions(this.workflowDefinitionId, this.context);
const libUri = 'defaultLib:lib.es6.d.ts';
await this.monacoEditor.addJavaScriptLib(libSource, libUri);

View file

@ -5,6 +5,7 @@ import {i18n} from "i18next";
import {loadTranslations} from "../../../i18n/i18n-loader";
import {resources} from "./localizations";
import {createElsaClient} from "../../../../services";
import {v4 as uuid} from 'uuid';
import Tunnel from "../../../../data/dashboard";
interface Tab {
@ -39,8 +40,8 @@ export class ElsaWorkflowPropertiesPanel {
@State() expanded: boolean;
@State() selectedTabId: string = 'testProperties';
private i18next: i18n;
el: HTMLElement;
private testId: string;
el: HTMLElement;
tabs: Array<Tab> = [];
@Watch('workflowDefinition')
@ -59,6 +60,13 @@ export class ElsaWorkflowPropertiesPanel {
e.preventDefault();
this.selectedTabId = tab.id;
}
async onExecuteWorkflowClick() {
debugger
const elsaClient = this.createClient();
this.testId = uuid();
await elsaClient.workflowsApi.test(this.workflowDefinition.definitionId, this.testId);
}
render() {
@ -202,7 +210,8 @@ export class ElsaWorkflowPropertiesPanel {
<div class="elsa-h-full">
<div class="elsa-mt-16 elsa-p-6">
<div class="elsa-px-4 elsa-py-3 elsa-bg-gray-50 elsa-text-left sm:px-6">
<button type="submit"
<button type="button"
onClick={() => this.onExecuteWorkflowClick()}
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">
Execute Workflow
</button>

View file

@ -116,6 +116,18 @@ export const createElsaClient = function (serverUrl: string): ElsaClient {
return response.data;
}
},
workflowsApi: {
test: async (workflowDefinitionId, testId) => {
httpClient.interceptors.request.use(function (config) {
config.headers['Accept'] = 'application/json';
config.data = Object.assign({}, config.data, {});
return config;
})
const response = await httpClient.post<WorkflowDefinition>(`v1/workflows/${workflowDefinitionId}/${testId}/test`);
return response.data;
}
},
webhookDefinitionsApi: {
list: async (page?: number, pageSize?: number) => {
const response = await httpClient.get<PagedList<WebhookDefinitionSummary>>(`v1/webhook-definitions`);
@ -257,6 +269,7 @@ export interface ElsaClient {
workflowStorageProvidersApi: WorkflowStorageProvidersApi;
webhookDefinitionsApi: WebhookDefinitionsApi;
workflowChannelsApi: WorkflowChannelsApi;
workflowsApi: WorkflowsApi;
}
export interface ActivitiesApi {
@ -282,6 +295,11 @@ export interface WorkflowDefinitionsApi {
import(workflowDefinitionId: string, file: File): Promise<WorkflowDefinition>;
}
export interface WorkflowsApi {
test(workflowDefinitionId: string, testId: string): Promise<WorkflowDefinition>;
}
export interface WebhookDefinitionsApi {
list(page?: number, pageSize?: number): Promise<PagedList<WebhookDefinitionSummary>>;

View file

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:60342/",
"sslPort": 44317
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Elsa.Samples.MassTransitRabbitMq": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
}
}
}

View file

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Elsa.Samples.Server.Host
{
public class Customer
{
public string Id { get; set; } = default!;
public string Name { get; set; } = default!;
}
}

View file

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Elsa.Models;
using Elsa.Scripting.JavaScript.Services;
using Elsa.Services.Models;
using Microsoft.AspNetCore.Http;
using NodaTime;
namespace Elsa.Samples.Server.Host
{
public class CustomerTypeDefinitionProvider : TypeDefinitionProvider
{
private static readonly IDictionary<Type, string> TypeMap = new Dictionary<Type, string>
{
[typeof(Customer)] = "Customer"// "Elsa.Samples.Server.Host.Customer"
};
public override bool SupportsType(TypeDefinitionContext context, Type type) => TypeMap.ContainsKey(type);
public override bool ShouldRenderType(TypeDefinitionContext context, Type type) => true;
public override string GetTypeDefinition(TypeDefinitionContext context, Type type) => TypeMap[type];
public override IEnumerable<Type> CollectTypes(TypeDefinitionContext context) => new[]
{
typeof(Customer)
};
}
}

View file

@ -75,6 +75,8 @@ namespace Elsa.Samples.Server.Host
.AddElsaApiEndpoints()
.AddElsaSwagger();
services.AddJavaScriptTypeDefinitionProvider<CustomerTypeDefinitionProvider>();
// Allow arbitrary client browser apps to access the API for demo purposes only.
// In a production environment, make sure to allow only origins you trust.
services.AddCors(cors => cors.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().WithExposedHeaders("Content-Disposition")));

View file

@ -14,7 +14,7 @@ namespace Microsoft.Extensions.DependencyInjection
public static IServiceCollection AddJavaScriptExpressionEvaluator(this IServiceCollection services)
{
return services
.AddScoped<ITypeScriptDefinitionService, TypeScriptDefinitionService>()
.AddScoped<ITypeScriptDefinitionService, DotNetTypeScriptDefinitionService>()
.AddScoped<IJavaScriptService, JintJavaScriptEvaluator>()
.AddTransient(s => new JintEvaluationResultConverterFactory(s).GetConverter())
.AddTransient<IConvertsEnumerableToObject>(s => new EnumerableResultConverter(default))

View file

@ -0,0 +1,227 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AutoMapper.Internal;
using Elsa.Models;
using Elsa.Scripting.JavaScript.Events;
using MediatR;
namespace Elsa.Scripting.JavaScript.Services
{
public class DotNetTypeScriptDefinitionService : ITypeScriptDefinitionService
{
private readonly IEnumerable<ITypeDefinitionProvider> _providers;
private readonly IMediator _mediator;
public DotNetTypeScriptDefinitionService(IEnumerable<ITypeDefinitionProvider> providers, IMediator mediator)
{
_providers = providers;
_mediator = mediator;
}
public async Task<string> GenerateTypeScriptDefinitionsAsync(WorkflowDefinition? workflowDefinition = default, string? context = default, CancellationToken cancellationToken = default)
{
var builder = new StringBuilder();
var providerContext = new TypeDefinitionContext(workflowDefinition, context);
var types = await CollectTypesAsync(providerContext, cancellationToken);
providerContext.GetTypeScriptType = ((provider, type) => GetTypeScriptType(providerContext, type, types, provider));
// Render type declarations for anything except those listed in TypeConverters.
int i = 0;
foreach (var type in types)
{
var shouldRenderDeclaration = ShouldRenderTypeDeclaration(providerContext, type);
if (i == 192)
{
var d = 1;
}
if (shouldRenderDeclaration)
RenderTypeDeclaration(providerContext, type, types, builder);
i++;
}
if (i == 193)
{
var d = 1;
}
string GetTypeScriptTypeInternal(Type type) => GetTypeScriptType(providerContext, type, types);
if (i == 193)
{
var d = 1;
}
var renderingTypeScriptDefinitions = new RenderingTypeScriptDefinitions(workflowDefinition, GetTypeScriptTypeInternal, context, builder);
if (i == 193)
{
var d = 1;
}
await _mediator.Publish(renderingTypeScriptDefinitions, cancellationToken);
var sss = builder.ToString();
return sss;
return builder.ToString();
}
private async Task<ISet<Type>> CollectTypesAsync(TypeDefinitionContext context, CancellationToken cancellationToken = default)
{
var collectedTypes = new HashSet<Type>();
foreach (var provider in _providers)
{
var providedTypes = await provider.CollectTypesAsync(context, cancellationToken);
foreach (var providedType in providedTypes)
CollectType(providedType, collectedTypes);
}
return collectedTypes;
}
private static void CollectType(Type type, ISet<Type> collectedTypes)
{
if (type.IsNullableType())
{
CollectType(type.GetTypeOfNullable(), collectedTypes);
return;
}
if (collectedTypes.Contains(type))
return;
collectedTypes.Add(type);
// Collect generic type argument types.
foreach (var typeArgType in type.GenericTypeArguments.Where(x => !collectedTypes.Contains(x)))
CollectType(typeArgType, collectedTypes);
// Collect property types.
var propertyTypes = type.GetProperties().Select(x => x.PropertyType).Where(x => !collectedTypes.Contains(x));
foreach (var propertyType in propertyTypes)
CollectType(propertyType, collectedTypes);
// Collect method return and argument types.
var methods = type.GetMethods(BindingFlags.Public);
if (type.FullName == "Elsa.Samples.Server.Host.Customer")
{
var a = 1;
}
foreach (var method in methods)
{
var returnType = method.ReturnType;
if (returnType != typeof(void))
CollectType(returnType, collectedTypes);
var argTypes = method.GetParameters().Select(x => x.ParameterType).ToList();
foreach (var argType in argTypes)
CollectType(argType, collectedTypes);
}
}
private void RenderTypeDeclaration(TypeDefinitionContext context, Type type, ISet<Type> collectedTypes, StringBuilder output)
{
var symbol = type switch
{
{IsInterface: true} => "interface",
{IsClass: true} => "class",
{IsValueType: true} => "class",
{IsEnum: true} => "enum",
_ => "interface"
};
RenderTypeDeclaration(context, symbol, type, collectedTypes, output);
}
private void RenderTypeDeclaration(TypeDefinitionContext context, string symbol, Type type, ISet<Type> collectedTypes, StringBuilder output)
{
var typeName = GetTypeScriptType(context, type, collectedTypes);
var properties = type.GetProperties();
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static).Where(x => !x.IsSpecialName).ToList();
output.AppendLine($"declare {symbol} {typeName} {{");
foreach (var property in properties)
{
var typeScriptType = GetTypeScriptType(context, property.PropertyType, collectedTypes);
var propertyName = property.PropertyType.IsNullableType() ? $"{property.Name}?" : property.Name;
output.AppendLine($"{propertyName}: {typeScriptType};");
}
if (type.FullName == "Elsa.Samples.Server.Host.Customer")
{
var a = 1;
}
foreach (var method in methods)
{
if (method.Name.StartsWith("<"))
continue;
if (symbol == "interface" && method.IsStatic)
continue;
if (method.IsStatic)
output.Append("static ");
output.Append($"{method.Name}(");
var arguments = method.GetParameters().Select(x => $"{x.Name}:{GetTypeScriptType(context, x.ParameterType, collectedTypes)}");
output.Append(string.Join(", ", arguments));
output.Append(")");
//var returnType = method.ReturnType;
var returnType = method.ReflectedType;
if (returnType != typeof(void))
{
if (type.FullName == "Elsa.Samples.Server.Host.Customer")
{
var a = 1;
}
output.AppendFormat(":{0}", GetTypeScriptType(context, returnType, collectedTypes));
}
output.AppendLine(";");
}
output.AppendLine("}");
}
private string GetTypeScriptType(TypeDefinitionContext context, Type type, ISet<Type> collectedTypes, ITypeDefinitionProvider? excludeProvider = default)
{
if (type.IsNullableType())
type = type.GetTypeOfNullable();
var providers = _providers;
if (excludeProvider != null)
providers = providers.Where(x => x != excludeProvider);
var provider = providers.FirstOrDefault(x => x.SupportsType(context, type));
if (type.FullName == "Elsa.Samples.Server.Host.Customer")
{
var a = 1;
}
var typeScriptType = provider != null ? provider.GetTypeDefinition(context, type) : collectedTypes.Contains(type) ? type.Name : "any";
return GetSafeSymbol(typeScriptType);
}
private bool ShouldRenderTypeDeclaration(TypeDefinitionContext context, Type type)
{
var provider = _providers.FirstOrDefault(x => x.SupportsType(context, type));
return provider == null || provider.ShouldRenderType(context, type);
}
private string GetSafeSymbol(string symbol) => symbol.Replace("`", "");
}
}

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
@ -12,12 +12,12 @@ using MediatR;
namespace Elsa.Scripting.JavaScript.Services
{
public class TypeScriptDefinitionService : ITypeScriptDefinitionService
public class JsonTypeScriptDefinitionService : ITypeScriptDefinitionService
{
private readonly IEnumerable<ITypeDefinitionProvider> _providers;
private readonly IMediator _mediator;
public TypeScriptDefinitionService(IEnumerable<ITypeDefinitionProvider> providers, IMediator mediator)
public JsonTypeScriptDefinitionService(IEnumerable<ITypeDefinitionProvider> providers, IMediator mediator)
{
_providers = providers;
_mediator = mediator;
@ -28,7 +28,7 @@ namespace Elsa.Scripting.JavaScript.Services
var builder = new StringBuilder();
var providerContext = new TypeDefinitionContext(workflowDefinition, context);
var types = await CollectTypesAsync(providerContext, cancellationToken);
providerContext.GetTypeScriptType = ((provider, type) => GetTypeScriptType(providerContext, type, types, provider));
// Render type declarations for anything except those listed in TypeConverters.
@ -106,13 +106,13 @@ namespace Elsa.Scripting.JavaScript.Services
{
var symbol = type switch
{
{IsInterface: true} => "interface",
{IsClass: true} => "class",
{IsValueType: true} => "class",
{IsEnum: true} => "enum",
{ IsInterface: true } => "interface",
{ IsClass: true } => "class",
{ IsValueType: true } => "class",
{ IsEnum: true } => "enum",
_ => "interface"
};
RenderTypeDeclaration(context, symbol, type, collectedTypes, output);
}
@ -167,7 +167,7 @@ namespace Elsa.Scripting.JavaScript.Services
if (excludeProvider != null)
providers = providers.Where(x => x != excludeProvider);
var provider = providers.FirstOrDefault(x => x.SupportsType(context, type));
var typeScriptType = provider != null ? provider.GetTypeDefinition(context, type) : collectedTypes.Contains(type) ? type.Name : "any";
return GetSafeSymbol(typeScriptType);

View file

@ -0,0 +1,57 @@
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;
namespace Elsa.Server.Api.Endpoints.Workflows
{
[ApiController]
[ApiVersion("1")]
[Route("v{apiVersion:apiVersion}/workflows/{workflowDefinitionId}/{testId}/test")]
[Produces("application/json")]
public class Test : Controller
{
private readonly IWorkflowLaunchpad _workflowLaunchpad;
private readonly ITenantAccessor _tenantAccessor;
public Test(IWorkflowLaunchpad workflowLaunchpad, ITenantAccessor tenantAccessor)
{
_workflowLaunchpad = workflowLaunchpad;
_tenantAccessor = tenantAccessor;
}
[HttpPost]
[ElsaJsonFormatter]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ExecuteWorkflowDefinitionResponseModel))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[SwaggerOperation(
Summary = "Executes the specified workflow definition in test mode.",
Description = "Executes the specified workflow definition in test mode.",
OperationId = "WorkflowDefinitions.Test",
Tags = new[] { "WorkflowDefinitions" })
]
public async Task<IActionResult> Handle(string workflowDefinitionId, string testId, ExecuteWorkflowDefinitionRequestModel request, CancellationToken cancellationToken = default)
{
var tenantId = await _tenantAccessor.GetTenantIdAsync(cancellationToken);
var startableWorkflow = await _workflowLaunchpad.FindTestableWorkflowAsync(workflowDefinitionId, request.ActivityId, request.CorrelationId, request.ContextId, tenantId, cancellationToken);
if (startableWorkflow == null)
return NotFound();
var result = await _workflowLaunchpad.ExecuteTestableWorkflowAsync(startableWorkflow, new WorkflowInput(request.Input), cancellationToken);
if (Response.HasStarted)
return new EmptyResult();
return Ok(new ExecuteWorkflowDefinitionResponseModel(
result.Executed,
result.ActivityId,
result.WorkflowInstance
));
}
}
}