diff --git a/src/core/Elsa.Core/Persistence/Specifications/WorkflowExecutionLogRecords/WorkflowInstanceIdSpecification.cs b/src/core/Elsa.Core/Persistence/Specifications/WorkflowExecutionLogRecords/WorkflowInstanceIdSpecification.cs new file mode 100644 index 000000000..4dcd6cb60 --- /dev/null +++ b/src/core/Elsa.Core/Persistence/Specifications/WorkflowExecutionLogRecords/WorkflowInstanceIdSpecification.cs @@ -0,0 +1,13 @@ +using System; +using System.Linq.Expressions; +using Elsa.Models; + +namespace Elsa.Persistence.Specifications.WorkflowExecutionLogRecords +{ + public class WorkflowInstanceIdSpecification : Specification + { + public string WorkflowInstanceId { get; set; } + public WorkflowInstanceIdSpecification(string workflowInstanceId) => WorkflowInstanceId = workflowInstanceId; + public override Expression> ToExpression() => x => x.WorkflowInstanceId == WorkflowInstanceId; + } +} \ No newline at end of file diff --git a/src/designer/elsa-workflows-studio/src/components.d.ts b/src/designer/elsa-workflows-studio/src/components.d.ts index ce4423511..6cbc45878 100644 --- a/src/designer/elsa-workflows-studio/src/components.d.ts +++ b/src/designer/elsa-workflows-studio/src/components.d.ts @@ -5,7 +5,7 @@ * It contains typing information for all components that exist in this project. */ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; -import { ActivityDefinitionProperty, ActivityDesignDisplayContext, ActivityModel, ActivityPropertyDescriptor, VersionOptions, WorkflowDefinition, WorkflowModel } from "./models"; +import { ActivityDefinitionProperty, ActivityDescriptor, ActivityDesignDisplayContext, ActivityModel, ActivityPropertyDescriptor, VersionOptions, WorkflowDefinition, WorkflowModel } from "./models"; import { MatchResults, RouterHistory } from "@stencil/router"; import { MenuItem } from "./components/controls/elsa-context-menu/models"; import { DropdownButtonItem, DropdownButtonOrigin } from "./components/controls/elsa-dropdown-button/models"; @@ -172,6 +172,12 @@ export namespace Components { "history"?: RouterHistory; "serverUrl": string; } + interface ElsaWorkflowInstanceJournal { + "activityDescriptors": Array; + "getServerUrl": () => Promise; + "serverUrl": string; + "workflowInstanceId": string; + } interface ElsaWorkflowInstanceViewerScreen { "getServerUrl": () => Promise; "serverUrl": string; @@ -382,6 +388,12 @@ declare global { prototype: HTMLElsaWorkflowDefinitionsListScreenElement; new (): HTMLElsaWorkflowDefinitionsListScreenElement; }; + interface HTMLElsaWorkflowInstanceJournalElement extends Components.ElsaWorkflowInstanceJournal, HTMLStencilElement { + } + var HTMLElsaWorkflowInstanceJournalElement: { + prototype: HTMLElsaWorkflowInstanceJournalElement; + new (): HTMLElsaWorkflowInstanceJournalElement; + }; interface HTMLElsaWorkflowInstanceViewerScreenElement extends Components.ElsaWorkflowInstanceViewerScreen, HTMLStencilElement { } var HTMLElsaWorkflowInstanceViewerScreenElement: { @@ -439,6 +451,7 @@ declare global { "elsa-workflow-definition-editor-notifications": HTMLElsaWorkflowDefinitionEditorNotificationsElement; "elsa-workflow-definition-editor-screen": HTMLElsaWorkflowDefinitionEditorScreenElement; "elsa-workflow-definitions-list-screen": HTMLElsaWorkflowDefinitionsListScreenElement; + "elsa-workflow-instance-journal": HTMLElsaWorkflowInstanceJournalElement; "elsa-workflow-instance-viewer-screen": HTMLElsaWorkflowInstanceViewerScreenElement; "elsa-workflow-instances-list-screen": HTMLElsaWorkflowInstancesListScreenElement; "elsa-workflow-publish-button": HTMLElsaWorkflowPublishButtonElement; @@ -602,6 +615,11 @@ declare namespace LocalJSX { "history"?: RouterHistory; "serverUrl"?: string; } + interface ElsaWorkflowInstanceJournal { + "activityDescriptors"?: Array; + "serverUrl"?: string; + "workflowInstanceId"?: string; + } interface ElsaWorkflowInstanceViewerScreen { "serverUrl"?: string; "workflowInstanceId"?: string; @@ -654,6 +672,7 @@ declare namespace LocalJSX { "elsa-workflow-definition-editor-notifications": ElsaWorkflowDefinitionEditorNotifications; "elsa-workflow-definition-editor-screen": ElsaWorkflowDefinitionEditorScreen; "elsa-workflow-definitions-list-screen": ElsaWorkflowDefinitionsListScreen; + "elsa-workflow-instance-journal": ElsaWorkflowInstanceJournal; "elsa-workflow-instance-viewer-screen": ElsaWorkflowInstanceViewerScreen; "elsa-workflow-instances-list-screen": ElsaWorkflowInstancesListScreen; "elsa-workflow-publish-button": ElsaWorkflowPublishButton; @@ -696,6 +715,7 @@ declare module "@stencil/core" { "elsa-workflow-definition-editor-notifications": LocalJSX.ElsaWorkflowDefinitionEditorNotifications & JSXBase.HTMLAttributes; "elsa-workflow-definition-editor-screen": LocalJSX.ElsaWorkflowDefinitionEditorScreen & JSXBase.HTMLAttributes; "elsa-workflow-definitions-list-screen": LocalJSX.ElsaWorkflowDefinitionsListScreen & JSXBase.HTMLAttributes; + "elsa-workflow-instance-journal": LocalJSX.ElsaWorkflowInstanceJournal & JSXBase.HTMLAttributes; "elsa-workflow-instance-viewer-screen": LocalJSX.ElsaWorkflowInstanceViewerScreen & JSXBase.HTMLAttributes; "elsa-workflow-instances-list-screen": LocalJSX.ElsaWorkflowInstancesListScreen & JSXBase.HTMLAttributes; "elsa-workflow-publish-button": LocalJSX.ElsaWorkflowPublishButton & JSXBase.HTMLAttributes; diff --git a/src/designer/elsa-workflows-studio/src/components/screens/workflow-instance-viewer/elsa-workflow-instance-viewer-screen/elsa-workflow-instance-journal.tsx b/src/designer/elsa-workflows-studio/src/components/screens/workflow-instance-viewer/elsa-workflow-instance-viewer-screen/elsa-workflow-instance-journal.tsx new file mode 100644 index 000000000..01f9cdcbc --- /dev/null +++ b/src/designer/elsa-workflows-studio/src/components/screens/workflow-instance-viewer/elsa-workflow-instance-viewer-screen/elsa-workflow-instance-journal.tsx @@ -0,0 +1,120 @@ +import {Component, h, Host, Method, Prop, State, Watch} from '@stencil/core'; +import { + ActivityDescriptor, PagedList, WorkflowExecutionLogRecord, +} from "../../../../models"; +import {createElsaClient} from "../../../../services/elsa-client"; + +@Component({ + tag: 'elsa-workflow-instance-journal', + shadow: false, +}) +export class ElsaWorkflowInstanceJournal { + + @Prop() workflowInstanceId: string; + @Prop() serverUrl: string; + @Prop() activityDescriptors: Array = []; + @State() records: PagedList = {items: [], totalCount: 0}; + + @Method() + async getServerUrl(): Promise { + return this.serverUrl; + } + + @Watch('workflowInstanceId') + async workflowInstanceIdChangedHandler(newValue: string) { + const workflowInstanceId = newValue; + const client = createElsaClient(this.serverUrl); + + if (workflowInstanceId && workflowInstanceId.length > 0) { + try { + this.records = await client.workflowExecutionLogApi.get(workflowInstanceId); + } catch { + console.warn(`The specified workflow definition does not exist. Creating a new one.`); + } + } + } + + async componentWillLoad() { + await this.workflowInstanceIdChangedHandler(this.workflowInstanceId); + } + + render() { + const records = this.records; + const items = records.items; + + const renderRecord = (record: WorkflowExecutionLogRecord, index: number) => { + const isLastItem = index == items.length - 1; + return ( +
  • +
    + {isLastItem ? undefined :
    +
  • + ); + }; + + return ( + + ); + } +} diff --git a/src/designer/elsa-workflows-studio/src/components/screens/workflow-instance-viewer/elsa-workflow-instance-viewer-screen/elsa-workflow-instance-viewer-screen.tsx b/src/designer/elsa-workflows-studio/src/components/screens/workflow-instance-viewer/elsa-workflow-instance-viewer-screen/elsa-workflow-instance-viewer-screen.tsx index b65184743..0d017dd28 100644 --- a/src/designer/elsa-workflows-studio/src/components/screens/workflow-instance-viewer/elsa-workflow-instance-viewer-screen/elsa-workflow-instance-viewer-screen.tsx +++ b/src/designer/elsa-workflows-studio/src/components/screens/workflow-instance-viewer/elsa-workflow-instance-viewer-screen/elsa-workflow-instance-viewer-screen.tsx @@ -158,16 +158,18 @@ export class ElsaWorkflowInstanceViewerScreen { } render() { + const descriptors: Array = state.activityDescriptors; return ( - this.el = el}> + this.el = el}> {this.renderCanvas()} + ); } renderCanvas() { return ( -
    +
    this.designer = el}/>
    ); diff --git a/src/designer/elsa-workflows-studio/src/models/domain.ts b/src/designer/elsa-workflows-studio/src/models/domain.ts index fe77bdbfc..b9b4cd3db 100644 --- a/src/designer/elsa-workflows-studio/src/models/domain.ts +++ b/src/designer/elsa-workflows-studio/src/models/domain.ts @@ -138,6 +138,18 @@ export interface ActivityDefinition { properties: Array; } +export interface WorkflowExecutionLogRecord { + id: string; + workflowInstanceId: string; + activityId: string; + activityType: string; + timestamp: Date; + eventName: string; + message?: string; + source?: string; + data?: any; +} + export interface ConnectionDefinition { sourceActivityId?: string; targetActivityId?: string; @@ -259,8 +271,8 @@ export interface ActivityPropertyDescriptor { export interface PagedList { items: Array; - page: number; - pageSize: number; + page?: number; + pageSize?: number; totalCount: number; } diff --git a/src/designer/elsa-workflows-studio/src/services/elsa-client.ts b/src/designer/elsa-workflows-studio/src/services/elsa-client.ts index 8f1da4935..c55fdbb16 100644 --- a/src/designer/elsa-workflows-studio/src/services/elsa-client.ts +++ b/src/designer/elsa-workflows-studio/src/services/elsa-client.ts @@ -10,7 +10,7 @@ import { VersionOptions, WorkflowBlueprint, WorkflowBlueprintSummary, WorkflowContextOptions, WorkflowDefinition, - WorkflowDefinitionSummary, WorkflowInstance, WorkflowInstanceSummary, + WorkflowDefinitionSummary, WorkflowExecutionLogRecord, WorkflowInstance, WorkflowInstanceSummary, WorkflowPersistenceBehavior, WorkflowStatus } from "../models"; @@ -130,6 +130,22 @@ export const createElsaClient = function (serverUrl: string): ElsaClient { return response.data; } }, + workflowExecutionLogApi: { + get: async (workflowInstanceId: string, page?: number, pageSize?: number): Promise> => { + const queryString = {}; + + if (!!page) + queryString['page'] = page; + + if (!!pageSize) + queryString['pageSize'] = pageSize; + + const queryStringItems = collection.map(queryString, (v, k) => `${k}=${v}`); + const queryStringText = queryStringItems.length > 0 ? `?${queryStringItems.join('&')}` : ''; + const response = await httpClient.get(`v1/workflow-instances/${workflowInstanceId}/execution-log${queryStringText}`); + return response.data; + } + }, scriptingApi: { getJavaScriptTypeDefinitions: async (workflowDefinitionId: string, context?: string): Promise => { context = context || ''; @@ -145,6 +161,7 @@ export interface ElsaClient { workflowDefinitionsApi: WorkflowDefinitionsApi; workflowRegistryApi: WorkflowRegistryApi; workflowInstancesApi: WorkflowInstancesApi; + workflowExecutionLogApi: WorkflowExecutionLogApi; scriptingApi: ScriptingApi; } @@ -185,6 +202,12 @@ export interface WorkflowInstancesApi { bulkDelete(request: BulkDeleteWorkflowsRequest): Promise; } +export interface WorkflowExecutionLogApi { + + get(workflowInstanceId: string, page?: number, pageSize?: number): Promise>; + +} + export interface BulkDeleteWorkflowsRequest { workflowInstanceIds: Array; } diff --git a/src/server/Elsa.Server.Api/Endpoints/WebhookDefinitions/List.cs b/src/server/Elsa.Server.Api/Endpoints/WebhookDefinitions/List.cs index 3be7efaa8..48f358329 100644 --- a/src/server/Elsa.Server.Api/Endpoints/WebhookDefinitions/List.cs +++ b/src/server/Elsa.Server.Api/Endpoints/WebhookDefinitions/List.cs @@ -42,7 +42,7 @@ namespace Elsa.Server.Api.Endpoints.WebhookDefinitions ] public async Task>> Handle(CancellationToken cancellationToken = default) { - var specification = Specification.All; + var specification = Specification.Identity; var items = await _webhookDefinitionStore.FindManyAsync(specification, cancellationToken: cancellationToken); return Json(items, _serializer.GetSettings()); diff --git a/src/server/Elsa.Server.Api/Endpoints/WorkflowExecutionLog/Get.cs b/src/server/Elsa.Server.Api/Endpoints/WorkflowExecutionLog/Get.cs new file mode 100644 index 000000000..1fcbcbd39 --- /dev/null +++ b/src/server/Elsa.Server.Api/Endpoints/WorkflowExecutionLog/Get.cs @@ -0,0 +1,46 @@ +using System.Threading; +using System.Threading.Tasks; +using Elsa.Models; +using Elsa.Persistence; +using Elsa.Persistence.Specifications; +using Elsa.Persistence.Specifications.WorkflowExecutionLogRecords; +using Elsa.Server.Api.Models; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Open.Linq.AsyncExtensions; +using Swashbuckle.AspNetCore.Annotations; + +namespace Elsa.Server.Api.Endpoints.WorkflowExecutionLog +{ + [ApiController] + [ApiVersion("1")] + [Route("v{apiVersion:apiVersion}/workflow-instances/{id}/execution-log")] + [Produces("application/json")] + public class Get : Controller + { + private readonly IWorkflowExecutionLogStore _workflowExecutionLogStore; + + public Get(IWorkflowExecutionLogStore workflowExecutionLogStore) + { + _workflowExecutionLogStore = workflowExecutionLogStore; + } + + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(PagedList))] + [SwaggerOperation( + Summary = "Returns the workflow's execution log.", + Description = "Returns the workflow's execution log.", + OperationId = "WorkflowExecutionLog.Get", + Tags = new[] { "WorkflowExecutionLog" }) + ] + public async Task>> Handle(string id, int? page = default, int? pageSize = default, CancellationToken cancellationToken = default) + { + var specification = new WorkflowInstanceIdSpecification(id); + var totalCount = await _workflowExecutionLogStore.CountAsync(specification, cancellationToken); + var paging = page != null ? Paging.Page(page.Value, pageSize ?? 100) : default; + var orderBy = OrderBySpecification.OrderBy(x => x.Timestamp); + var records = await _workflowExecutionLogStore.FindManyAsync(specification, orderBy, paging, cancellationToken).ToList(); + return new PagedList(records, page, pageSize, totalCount); + } + } +} \ No newline at end of file diff --git a/src/server/Elsa.Server.Api/Endpoints/WorkflowInstances/List.cs b/src/server/Elsa.Server.Api/Endpoints/WorkflowInstances/List.cs index 08a254b00..b11f0d6ca 100644 --- a/src/server/Elsa.Server.Api/Endpoints/WorkflowInstances/List.cs +++ b/src/server/Elsa.Server.Api/Endpoints/WorkflowInstances/List.cs @@ -53,7 +53,7 @@ namespace Elsa.Server.Api.Endpoints.WorkflowInstances CancellationToken cancellationToken = default) { _stopwatch.Restart(); - var specification = Specification.All; + var specification = Specification.Identity; if (!string.IsNullOrWhiteSpace(workflowDefinitionId)) specification = specification.WithWorkflowDefinition(workflowDefinitionId);