Incremental work on workflow journal UI

This commit is contained in:
Sipke Schoorstra 2021-05-19 22:20:18 +02:00
parent c1c5673726
commit e724d6030a
8 changed files with 127 additions and 53 deletions

View file

@ -2,8 +2,21 @@
namespace Elsa.Models
{
public record SimpleException(Type Type, string Message, string StackTrace, SimpleException? InnerException = default)
public class SimpleException
{
public SimpleException(Type type, string message, string stackTrace, SimpleException? innerException = default)
{
Type = type;
Message = message;
StackTrace = stackTrace;
InnerException = innerException;
}
public Type Type { get; set; }
public string Message { get; set; }
public string StackTrace { get; set; }
public SimpleException? InnerException { get; set; }
public static SimpleException? FromException(Exception? ex)
{
if (ex == null)
@ -12,7 +25,7 @@ namespace Elsa.Models
var simpleException = new SimpleException(ex.GetType(), ex.Message, ex.StackTrace);
if (ex.InnerException != null)
simpleException = simpleException with { InnerException = FromException(ex.InnerException) };
simpleException.InnerException = FromException(ex.InnerException);
return simpleException;
}

View file

@ -212,6 +212,8 @@ namespace Microsoft.Extensions.DependencyInjection
services
.AddAutoMapperProfile<NodaTimeProfile>()
.AddAutoMapperProfile<CloningProfile>()
.AddAutoMapperProfile<ExceptionProfile>()
.AddTransient<ExceptionConverter>()
.AddSingleton<ICloner, AutoMapperCloner>();
// Caching.

View file

@ -1,6 +1,8 @@
using System.Threading;
using System.Threading.Tasks;
using AutoMapper;
using Elsa.Events;
using Elsa.Models;
using Elsa.Services;
using Elsa.Services.Models;
using MediatR;
@ -10,8 +12,14 @@ namespace Elsa.Handlers
public class WriteWorkflowExecutionLog : INotificationHandler<ActivityExecuting>, INotificationHandler<ActivityExecutionResultExecuted>, INotificationHandler<ActivityFaulted>
{
private readonly IWorkflowExecutionLog _workflowExecutionLog;
public WriteWorkflowExecutionLog(IWorkflowExecutionLog workflowExecutionLog) => _workflowExecutionLog = workflowExecutionLog;
private readonly IMapper _mapper;
public WriteWorkflowExecutionLog(IWorkflowExecutionLog workflowExecutionLog, IMapper mapper)
{
_workflowExecutionLog = workflowExecutionLog;
_mapper = mapper;
}
public async Task Handle(ActivityExecuting notification, CancellationToken cancellationToken)
{
var activityExecutionContext = notification.ActivityExecutionContext;
@ -43,11 +51,11 @@ namespace Elsa.Handlers
public async Task Handle(ActivityFaulted notification, CancellationToken cancellationToken)
{
var exception = notification.Exception;
var exceptionModel = _mapper.Map<SimpleException>(exception);
var data = new
{
exception.Message,
exception.StackTrace
exception = exceptionModel
};
await WriteEntryAsync("Faulted", exception.Message, notification.ActivityExecutionContext, data, cancellationToken);

View file

@ -0,0 +1,28 @@
using System;
using AutoMapper;
using Elsa.Models;
namespace Elsa.Mapping
{
public class ExceptionConverter : ITypeConverter<Exception, SimpleException>
{
public SimpleException Convert(Exception source, SimpleException? destination, ResolutionContext context)
{
var model = SimpleException.FromException(source)!;
if (destination != null)
{
destination.Message = model.Message;
destination.Type = model.Type;
destination.StackTrace = model.StackTrace;
destination.InnerException = model.InnerException;
}
else
{
destination = model;
}
return destination;
}
}
}

View file

@ -0,0 +1,14 @@
using System;
using AutoMapper;
using Elsa.Models;
namespace Elsa.Mapping
{
public class ExceptionProfile : Profile
{
public ExceptionProfile()
{
CreateMap<Exception, SimpleException>().ConvertUsing<ExceptionConverter>();
}
}
}

View file

@ -177,7 +177,8 @@ export class ElsaWorkflowInstanceJournal {
<div class="elsa-px-4 sm:elsa-px-6">
<div class="elsa-flex elsa-flex-col elsa-items-end">
<div class="elsa-ml-3 h-7 elsa-flex elsa-items-center">
<button type="button" onClick={e => this.onCloseClick()} class="elsa-bg-white elsa-rounded-md elsa-text-gray-400 hover:elsa-text-gray-500 focus:elsa-outline-none focus:elsa-ring-2 focus:elsa-ring-offset-2 focus:elsa-ring-blue-500">
<button type="button" onClick={e => this.onCloseClick()}
class="elsa-bg-white elsa-rounded-md elsa-text-gray-400 hover:elsa-text-gray-500 focus:elsa-outline-none focus:elsa-ring-2 focus:elsa-ring-offset-2 focus:elsa-ring-blue-500">
<span class="elsa-sr-only">Close panel</span>
<svg class="elsa-h-6 elsa-w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
@ -259,7 +260,7 @@ export class ElsaWorkflowInstanceJournal {
if (typeof value == 'string')
valueText = value;
else if (typeof value == 'object')
valueText = JSON.stringify(value);
valueText = JSON.stringify(value, null, 1);
else if (typeof value == 'undefined')
valueText = null;
else
@ -274,8 +275,8 @@ export class ElsaWorkflowInstanceJournal {
<li>
<div onClick={() => this.onRecordClick(record)} class={`${recordClass} elsa-border-2 elsa-cursor-pointer elsa-p-4 elsa-rounded`}>
<div class="elsa-relative elsa-pb-10">
{isLastItem ? undefined : <div class="elsa-flex elsa-absolute top-8 left-4 -elsa-ml-px elsa-h-full elsa-w-0.5 elsa-bg-gray-200">
<div class="elsa-flex elsa-flex-1 elsa-items-center elsa-relative -elsa-left-5">
{isLastItem ? undefined : <div class="elsa-flex elsa-absolute top-8 elsa-left-4 -elsa-ml-px elsa-h-full elsa-w-0.5 elsa-bg-gray-200">
<div class="elsa-flex elsa-flex-1 elsa-items-center elsa-relative elsa-right-10">
<span class="elsa-flex-1 elsa-text-sm elsa-text-gray-500 elsa-w-max elsa-bg-white elsa-p-1 elsa-rounded">{deltaTimeText}</span>
</div>
</div>}
@ -303,27 +304,31 @@ export class ElsaWorkflowInstanceJournal {
</div>
</div>
<div class="elsa-ml-12 elsa-mt-2">
<dl class="elsa-grid elsa-grid-cols-1 elsa-gap-x-4 elsa-gap-y-8 sm:elsa-grid-cols-2">
<div class="sm:elsa-col-span-1">
<dt class="elsa-text-sm elsa-font-medium elsa-text-gray-500">Activity ID</dt>
<dd class="elsa-mt-1 elsa-text-sm elsa-text-gray-900 elsa-mb-2">{record.activityId}</dd>
<dl class="sm:elsa-divide-y sm:elsa-divide-gray-200">
<div class="elsa-grid elsa-grid-cols-2 elsa-gap-x-4 elsa-gap-y-8 sm:elsa-grid-cols-2">
<div class="sm:elsa-col-span-2">
<dt class="elsa-text-sm elsa-font-medium elsa-text-gray-500">Activity ID</dt>
<dd class="elsa-mt-1 elsa-text-sm elsa-text-gray-900 elsa-mb-2">{record.activityId}</dd>
</div>
{collection.map(filteredRecordData, (v, k) => (
<div class="sm:elsa-col-span-2">
<dt class="elsa-text-sm elsa-font-medium elsa-text-gray-500">{k}</dt>
<dd class="elsa-mt-1 elsa-text-sm elsa-text-gray-900 elsa-mb-2">
<pre>{v}</pre>
</dd>
</div>
))}
{record.message ? (
<div class="sm:elsa-col-span-2">
<dt class="elsa-text-sm elsa-font-medium elsa-text-gray-500">
Message
</dt>
<dd class="elsa-mt-1 elsa-text-sm elsa-text-gray-900">
{record.message}
</dd>
</div>
) : undefined}
</div>
{collection.map(filteredRecordData, (v, k) => (
<div class="sm:elsa-col-span-1">
<dt class="elsa-text-sm elsa-font-medium elsa-text-gray-500">{k}</dt>
<dd class="elsa-mt-1 elsa-text-sm elsa-text-gray-900 elsa-mb-2"><pre>{v}</pre></dd>
</div>
))}
{record.message ? (
<div class="sm:elsa-col-span-1">
<dt class="elsa-text-sm elsa-font-medium elsa-text-gray-500">
Message
</dt>
<dd class="elsa-mt-1 elsa-text-sm elsa-text-gray-900">
{record.message}
</dd>
</div>
) : undefined}
</dl>
</div>
</div>

View file

@ -6,25 +6,26 @@ using Elsa.Services.Models;
namespace Elsa.Server.Api.Mapping
{
public class ActivityBlueprintConverter : ITypeConverter<IActivityBlueprint, ActivityBlueprintModel>
public class ActivityBlueprintConverter : ITypeConverter<IActivityBlueprint, ActivityBlueprintModel?>
{
public const string ActivityPropertiesKey = "ActivityProperties";
public ActivityBlueprintModel Convert(IActivityBlueprint source, ActivityBlueprintModel destination, ResolutionContext context)
public ActivityBlueprintModel Convert(IActivityBlueprint source, ActivityBlueprintModel? destination, ResolutionContext context)
{
return new()
{
Id = source.Id,
Name = source.Name,
DisplayName = source.DisplayName,
Description = source.Description,
Type = source.Type,
ParentId = source.Parent?.Id,
PersistWorkflow = source.PersistWorkflow,
LoadWorkflowContext = source.LoadWorkflowContext,
SaveWorkflowContext = source.SaveWorkflowContext,
Properties = GetProperties(source, context)
};
destination ??= new ActivityBlueprintModel();
destination.Id = source.Id;
destination. Name = source.Name;
destination.DisplayName = source.DisplayName;
destination.Description = source.Description;
destination.Type = source.Type;
destination.ParentId = source.Parent?.Id;
destination.PersistWorkflow = source.PersistWorkflow;
destination.LoadWorkflowContext = source.LoadWorkflowContext;
destination.SaveWorkflowContext = source.SaveWorkflowContext;
destination.Properties = GetProperties(source, context);
return destination;
}
private Variables GetProperties(IActivityBlueprint activityBlueprint, ResolutionContext context)

View file

@ -4,14 +4,17 @@ using Elsa.Services.Models;
namespace Elsa.Server.Api.Mapping
{
public class ConnectionConverter : ITypeConverter<IConnection, ConnectionModel>
public class ConnectionConverter : ITypeConverter<IConnection, ConnectionModel?>
{
public ConnectionModel Convert(IConnection source, ConnectionModel destination, ResolutionContext context) =>
new()
{
SourceActivityId = source.Source.Activity.Id,
TargetActivityId = source.Target.Activity.Id,
Outcome = source.Source.Outcome
};
public ConnectionModel Convert(IConnection source, ConnectionModel? destination, ResolutionContext context)
{
destination ??= new ConnectionModel();
destination.SourceActivityId = source.Source.Activity.Id;
destination.TargetActivityId = source.Target.Activity.Id;
destination.Outcome = source.Source.Outcome;
return destination;
}
}
}