Optimize workflow definitions loading
Improves the Workflow Definition list view by instead of loading ALL versions of a page of workflow definitions, it instead only loads the LATEST and their PUBLISHED versions only. Over time, a workflow definition can have many versions, which would decrease page load performance over time
This commit is contained in:
parent
40b53b24a6
commit
6f8990def2
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using Elsa.Models;
|
||||
|
||||
namespace Elsa.Persistence.Specifications.WorkflowDefinitions
|
||||
{
|
||||
public class ManyWorkflowDefinitionIdsSpecification : Specification<WorkflowDefinition>
|
||||
{
|
||||
public IEnumerable<string> Ids { get; set; }
|
||||
public ManyWorkflowDefinitionIdsSpecification(IEnumerable<string> ids) => Ids = ids;
|
||||
public override Expression<Func<WorkflowDefinition, bool>> ToExpression() => x => Ids.Contains(x.DefinitionId);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import {Component, h, Prop, State} from '@stencil/core';
|
||||
import * as collection from 'lodash/collection';
|
||||
import {createElsaClient} from "../../../../services/elsa-client";
|
||||
import {PagedList, VersionOptions, WorkflowDefinitionSummary} from "../../../../models";
|
||||
import {RouterHistory} from "@stencil/router";
|
||||
|
|
@ -12,6 +11,7 @@ export class ElsaWorkflowDefinitionsListScreen {
|
|||
@Prop() history?: RouterHistory;
|
||||
@Prop() serverUrl: string;
|
||||
@State() workflowDefinitions: PagedList<WorkflowDefinitionSummary> = {items: [], page: 1, pageSize: 50, totalCount: 0};
|
||||
@State() publishedWorkflowDefinitions: WorkflowDefinitionSummary[] = [];
|
||||
|
||||
confirmDialog: HTMLElsaConfirmDialogElement;
|
||||
|
||||
|
|
@ -34,8 +34,12 @@ export class ElsaWorkflowDefinitionsListScreen {
|
|||
const elsaClient = this.createClient();
|
||||
const page = 0;
|
||||
const pageSize = 50;
|
||||
const versionOptions: VersionOptions = {allVersions: true};
|
||||
this.workflowDefinitions = await elsaClient.workflowDefinitionsApi.list(page, pageSize, versionOptions);
|
||||
const latestVersionOptions: VersionOptions = {isLatest: true};
|
||||
const publishedVersionOptions: VersionOptions = {isPublished: true};
|
||||
const latestWorkflowDefinitions = await elsaClient.workflowDefinitionsApi.list(page, pageSize, latestVersionOptions);
|
||||
const unpublishedWorkflowDefinitionIds = latestWorkflowDefinitions.items.filter(x => !x.isPublished).map(x => x.definitionId);
|
||||
this.publishedWorkflowDefinitions = await elsaClient.workflowDefinitionsApi.getMany(unpublishedWorkflowDefinitionIds, publishedVersionOptions);
|
||||
this.workflowDefinitions = latestWorkflowDefinitions;
|
||||
}
|
||||
|
||||
createClient() {
|
||||
|
|
@ -44,7 +48,6 @@ export class ElsaWorkflowDefinitionsListScreen {
|
|||
|
||||
render() {
|
||||
const workflowDefinitions = this.workflowDefinitions.items;
|
||||
const groupings = collection.groupBy(workflowDefinitions, 'definitionId');
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
|
@ -66,11 +69,9 @@ export class ElsaWorkflowDefinitionsListScreen {
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody class="elsa-bg-white elsa-divide-y elsa-divide-gray-100">
|
||||
{collection.map(groupings, group => {
|
||||
const versions = collection.orderBy(group, 'version', 'desc');
|
||||
const workflowDefinition: WorkflowDefinitionSummary = versions[0];
|
||||
{workflowDefinitions.map(workflowDefinition => {
|
||||
const latestVersionNumber = workflowDefinition.version;
|
||||
const publishedVersion: WorkflowDefinitionSummary = versions.find(x => x.isPublished);
|
||||
const publishedVersion: WorkflowDefinitionSummary = workflowDefinition.isPublished ? workflowDefinition : this.publishedWorkflowDefinitions.find(x => x.definitionId == workflowDefinition.definitionId);
|
||||
const publishedVersionNumber = !!publishedVersion ? publishedVersion.version : '-';
|
||||
let workflowDisplayName = workflowDefinition.displayName;
|
||||
|
||||
|
|
|
|||
|
|
@ -291,6 +291,10 @@ export interface PagedList<T> {
|
|||
totalCount: number;
|
||||
}
|
||||
|
||||
export interface ListModel<T> {
|
||||
items: Array<T>;
|
||||
}
|
||||
|
||||
export enum ActivityTraits {
|
||||
Action = 1,
|
||||
Trigger = 2,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
ActivityDescriptor,
|
||||
ConnectionDefinition,
|
||||
EventTypes,
|
||||
getVersionOptionsString,
|
||||
getVersionOptionsString, ListModel,
|
||||
OrderBy,
|
||||
PagedList,
|
||||
SelectListItem,
|
||||
|
|
@ -58,6 +58,11 @@ export const createElsaClient = function (serverUrl: string): ElsaClient {
|
|||
const response = await httpClient.get<PagedList<WorkflowDefinitionSummary>>(`v1/workflow-definitions?version=${versionOptionsString}`);
|
||||
return response.data;
|
||||
},
|
||||
getMany: async (ids: Array<string>, versionOptions?: VersionOptions) => {
|
||||
const versionOptionsString = getVersionOptionsString(versionOptions);
|
||||
const response = await httpClient.get<ListModel<WorkflowDefinitionSummary>>(`v1/workflow-definitions?ids=${ids.join(',')}&version=${versionOptionsString}`);
|
||||
return response.data.items;
|
||||
},
|
||||
getByDefinitionAndVersion: async (definitionId: string, versionOptions: VersionOptions) => {
|
||||
const versionOptionsString = getVersionOptionsString(versionOptions);
|
||||
const response = await httpClient.get<WorkflowDefinition>(`v1/workflow-definitions/${definitionId}/${versionOptionsString}`);
|
||||
|
|
@ -251,6 +256,8 @@ export interface WorkflowDefinitionsApi {
|
|||
|
||||
list(page?: number, pageSize?: number, versionOptions?: VersionOptions): Promise<PagedList<WorkflowDefinitionSummary>>;
|
||||
|
||||
getMany(ids: Array<string>, versionOptions?: VersionOptions): Promise<Array<WorkflowDefinitionSummary>>;
|
||||
|
||||
getByDefinitionAndVersion(definitionId: string, versionOptions: VersionOptions): Promise<WorkflowDefinition>;
|
||||
|
||||
save(request: SaveWorkflowDefinitionRequest): Promise<WorkflowDefinition>;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
using Microsoft.AspNetCore.Mvc.ActionConstraints;
|
||||
|
||||
namespace Elsa.Server.Api.ActionConstraints
|
||||
{
|
||||
public class RequiredFromQueryActionConstraint : IActionConstraint
|
||||
{
|
||||
private readonly string _parameter;
|
||||
public RequiredFromQueryActionConstraint(string parameter) => _parameter = parameter;
|
||||
public int Order => 999;
|
||||
public bool Accept(ActionConstraintContext context) => context.RouteContext.HttpContext.Request.Query.ContainsKey(_parameter);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
using System.Linq;
|
||||
using Elsa.Server.Api.ActionConstraints;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
|
||||
namespace Elsa.Server.Api.Attributes
|
||||
{
|
||||
public class RequiredFromQueryAttribute : FromQueryAttribute, IParameterModelConvention
|
||||
{
|
||||
public void Apply(ParameterModel parameter)
|
||||
{
|
||||
if (parameter.Action.Selectors != null && parameter.Action.Selectors.Any())
|
||||
{
|
||||
parameter.Action.Selectors.Last().ActionConstraints.Add(new RequiredFromQueryActionConstraint(parameter.BindingInfo?.BinderModelName ?? parameter.ParameterName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AutoMapper;
|
||||
using Elsa.Models;
|
||||
using Elsa.Persistence;
|
||||
using Elsa.Persistence.Specifications;
|
||||
using Elsa.Persistence.Specifications.WorkflowDefinitions;
|
||||
using Elsa.Serialization;
|
||||
using Elsa.Server.Api.Attributes;
|
||||
using Elsa.Server.Api.Models;
|
||||
using Elsa.Server.Api.Swagger.Examples;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
using Swashbuckle.AspNetCore.Filters;
|
||||
|
||||
namespace Elsa.Server.Api.Endpoints.WorkflowDefinitions
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1")]
|
||||
[Route("v{apiVersion:apiVersion}/workflow-definitions")]
|
||||
[Produces("application/json")]
|
||||
public class GetMany : Controller
|
||||
{
|
||||
private readonly IWorkflowDefinitionStore _workflowDefinitionStore;
|
||||
private readonly IContentSerializer _serializer;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public GetMany(IWorkflowDefinitionStore workflowDefinitionStore, IContentSerializer serializer, IMapper mapper)
|
||||
{
|
||||
_workflowDefinitionStore = workflowDefinitionStore;
|
||||
_serializer = serializer;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ListModel<WorkflowDefinitionSummaryModel>))]
|
||||
[SwaggerResponseExample(StatusCodes.Status200OK, typeof(WorkflowDefinitionPagedListExample))]
|
||||
[SwaggerOperation(
|
||||
Summary = "Returns a list of workflow definitions for the specified ids.",
|
||||
Description = "Returns a list of workflow definition summaries. When no version options are specified, the latest versions are returned.",
|
||||
OperationId = "WorkflowDefinitions.GetMany",
|
||||
Tags = new[] { "WorkflowDefinitions" })
|
||||
]
|
||||
public async Task<ActionResult<WorkflowDefinitionSummaryModel[]>> Handle([RequiredFromQuery] string? ids, VersionOptions? version = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
IList<WorkflowDefinitionSummaryModel> summaries = new List<WorkflowDefinitionSummaryModel>();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(ids))
|
||||
{
|
||||
version ??= VersionOptions.Latest;
|
||||
var splitIds = ids.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||
var specification = new VersionOptionsSpecification(version.Value).And(new ManyWorkflowDefinitionIdsSpecification(splitIds));
|
||||
var items = await _workflowDefinitionStore.FindManyAsync(specification, cancellationToken: cancellationToken);
|
||||
summaries = _mapper.Map<IList<WorkflowDefinitionSummaryModel>>(items);
|
||||
}
|
||||
|
||||
return Json(new ListModel<WorkflowDefinitionSummaryModel>(summaries), _serializer.GetSettings());
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/server/Elsa.Server.Api/Models/ListModel.cs
Normal file
11
src/server/Elsa.Server.Api/Models/ListModel.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Elsa.Server.Api.Models
|
||||
{
|
||||
public record ListModel<T>
|
||||
{
|
||||
public ListModel(IEnumerable<T> items) => Items = items.ToArray();
|
||||
public T[] Items { get; set; }
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue