V3 search workflows definitions and instances (#4123)

* search workflowdefinition

* filter workflowinstance

* also add version options

* initiate search field

* add search to workflowinstance

* extend search with description and definitionversionid

* make everything work for workflow definition and instance search
This commit is contained in:
herblinernexxbizz 2023-06-12 10:44:33 +02:00 committed by GitHub
parent c35d16ee3e
commit 3aa05d1fed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 110 additions and 15 deletions

View file

@ -2,7 +2,8 @@ import {Component, Event, EventEmitter, h, Host, Method, Prop, State, Watch} fro
import {OrderBy, PagedList, VersionOptions} from '../../../models';
import {Container} from 'typedi';
import {DeleteIcon, EditIcon, PublishIcon, UnPublishIcon} from '../../../components/icons/tooling';
import {Filter, FilterProps} from './filter';
import { Search } from "./search";
import { Filter, FilterProps } from './filter';
import {PagerData} from '../../../components/shared/pager/pager';
import {updateSelectedWorkflowDefinitions} from '../services/utils';
import {WorkflowDefinitionSummary} from "../models/entities";
@ -43,6 +44,7 @@ export class WorkflowDefinitionBrowser {
@State() private orderBy?: WorkflowDefinitionsOrderBy;
@State() private labels?: string[];
@State() private selectAllChecked: boolean;
@State() private searchTerm?: string;
async componentWillLoad() {
const persistedRequest = getRequest();
@ -60,6 +62,13 @@ export class WorkflowDefinitionBrowser {
this.newWorkflowDefinitionSelected.emit();
};
private onSearch = async (term: string) => {
this.searchTerm = term;
this.resetPagination();
await this.loadWorkflowDefinitions();
};
private async onPublishClick(e: MouseEvent, workflowDefinition: WorkflowDefinitionSummary) {
await this.api.publish(workflowDefinition);
await this.loadWorkflowDefinitions();
@ -146,6 +155,7 @@ export class WorkflowDefinitionBrowser {
const materializerName = 'Json';
const request: ListWorkflowDefinitionsRequest = {
searchTerm: this.searchTerm,
materializerName,
page: this.currentPage,
pageSize: this.currentPageSize,
@ -252,6 +262,8 @@ export class WorkflowDefinitionBrowser {
<Host class="tw-block">
<div class="tw-pt-4">
<h2 class="tw-text-lg tw-font-medium tw-ml-4 tw-mb-2">Workflow Definitions</h2>
<Search onSearch={this.onSearch} />
<Filter {...filterProps} />
<div class="tw-align-middle tw-inline-block tw-min-w-full tw-border-b tw-border-gray-200">
<table class="default-table">

View file

@ -40,8 +40,6 @@ export const Filter: FunctionalComponent<FilterProps> = ({ pageSizeFilter, order
</div>
<div class="tw-flex-1">&nbsp;</div>
<elsa-label-picker {...labelFilter} />
<PageSizeFilter {...pageSizeFilter} />
<OrderByFilter {...orderByFilter} />

View file

@ -0,0 +1,49 @@
import {FunctionalComponent, h} from "@stencil/core";
import {debounce} from 'lodash';
export interface SearchProps {
onSearch: (term: string) => void;
}
export const Search: FunctionalComponent<SearchProps> = ({onSearch}) => {
const onSubmit = (e: Event) => {
e.preventDefault();
const form = e.target as HTMLFormElement;
const formData = new FormData(form);
const searchTerm: FormDataEntryValue = formData.get('searchTerm').toString();
onSearch(searchTerm);
}
const onSearchDebounced = debounce(onSearch, 200);
const onKeyUp = (e: KeyboardEvent) => {
const term = (e.target as HTMLInputElement).value;
onSearchDebounced(term);
};
return <div class="tw-relative tw-z-10 tw-flex-shrink-0 tw-flex tw-h-16 tw-bg-white tw-border-b tw-border-gray-200">
<div class="tw-flex-1 tw-px-4 tw-flex tw-justify-between sm:tw-px-6 lg:tw-px-8">
<div class="tw-flex-1 tw-flex">
<form class="tw-w-full tw-flex md:tw-ml-0" onSubmit={onSubmit}>
<label htmlFor="search_field" class="tw-sr-only">Search</label>
<div class="tw-relative tw-w-full tw-text-gray-400 focus-within:tw-text-gray-600">
<div
class="tw-absolute tw-inset-y-0 tw-left-0 tw-flex tw-items-center tw-pointer-events-none">
<svg class="tw-h-5 tw-w-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"/>
</svg>
</div>
<input name="searchTerm"
onKeyUp={onKeyUp}
class="tw-block tw-w-full tw-h-full tw-pl-8 tw-pr-3 tw-py-2 tw-rounded-md tw-text-gray-900 tw-placeholder-cool-gray-500 focus:tw-placeholder-cool-gray-400 sm:tw-text-sm tw-border-0 focus:tw-outline-none focus:tw-ring-0"
placeholder="Search"
type="search"/>
</div>
</form>
</div>
</div>
</div>;
}

View file

@ -91,6 +91,8 @@ export class WorkflowDefinitionsApi {
if (!!request.label) queryString.label = request.label;
if (!!request.searchTerm) queryString.searchTerm = request.searchTerm;
const queryStringText = serializeQueryString(queryString);
const httpClient = await this.getHttpClient();
const response = await httpClient.get<PagedList<WorkflowDefinitionSummary>>(`workflow-definitions${queryStringText}`);
@ -255,6 +257,7 @@ export enum WorkflowDefinitionsOrderBy {
}
export interface ListWorkflowDefinitionsRequest {
searchTerm?: string;
page?: number;
pageSize?: number;
definitionIds?: Array<string>;

View file

@ -57,7 +57,7 @@ export class WorkflowInstanceBrowser {
if (persistedRequest) {
// TODO: Persist search term, need to bind the value to the input
// this.searchTerm = persistedRequest.searchTerm
//this.searchTerm = persistedRequest.searchTerm
this.currentPage = persistedRequest.page
this.currentPageSize = persistedRequest.pageSize
this.orderBy = persistedRequest.orderBy

View file

@ -53,9 +53,11 @@ export const Filter: FunctionalComponent<FilterProps> = ({pageSizeFilter, workfl
<div class="tw-flex-1">
&nbsp;
</div>
<button onClick={resetFilter} type="button" class="tw-text-sm tw-text-blue-600 active:tw-text-blue-700 tw-px-3 active:ring tw-ring-blue-500 tw-rounded">
Reset
</button>
<PageSizeFilter {...pageSizeFilter}/>
<WorkflowFilter {...workflowFilter}/>
<StatusFilter {...statusFilter}/>

View file

@ -114,4 +114,4 @@ export interface GetWorkflowJournalRequest {
workflowInstanceId: string;
page?: number;
pageSize?: number;
}
}

View file

@ -96,6 +96,22 @@ public static class ParameterizedQueryBuilderExtensions
return query;
}
/// <summary>
/// Appends an search for workflowdfefinitions to the search
/// </summary>
/// <param name="query">The query.</param>
/// <param name="searchTerm">The search term.</param>
public static ParameterizedQuery AndWorkflowDefinitionSearchTerm(this ParameterizedQuery query, string? searchTerm)
{
if (string.IsNullOrWhiteSpace(searchTerm)) return query;
var searchTermLike = $"%{searchTerm}%";
query.Sql.AppendLine("and (Name like @SearchTermLike or Description like @SearchTermLike or Id = @SearchTerm or DefinitionId = @SearchTerm)");
query.Parameters.Add("@SearchTerm", searchTerm);
query.Parameters.Add("@SearchTermLike", searchTermLike);
return query;
}
/// <summary>
/// Appends an AND clause to the query if the value is not null.
/// </summary>
@ -153,8 +169,11 @@ public static class ParameterizedQueryBuilderExtensions
public static ParameterizedQuery AndWorkflowInstanceSearchTerm(this ParameterizedQuery query, string? searchTerm)
{
if (string.IsNullOrWhiteSpace(searchTerm)) return query;
query.Sql.AppendLine("and (Name like @SearchTerm or ID like @SearchTerm or DefinitionId like @SearchTerm or CorrelationId like @SearchTerm)");
var searchTermLike = $"%{searchTerm}%";
query.Sql.AppendLine("and (Name like @SearchTermLike or ID = @SearchTerm or DefinitionId = @SearchTerm or DefinitionVersionId = @SearchTerm or CorrelationId = @SearchTerm)");
query.Parameters.Add("@SearchTerm", searchTerm);
query.Parameters.Add("@SearchTermLike", searchTermLike);
return query;
}

View file

@ -152,7 +152,8 @@ public class DapperWorkflowDefinitionStore : IWorkflowDefinitionStore
.Equals(nameof(WorkflowDefinition.MaterializerName), filter.MaterializerName)
.Equals(nameof(WorkflowDefinition.Name), filter.Name)
.In(nameof(WorkflowDefinition.Name), filter.Names)
.Equals(nameof(WorkflowDefinition.Options.UsableAsActivity), filter.UsableAsActivity);
.Equals(nameof(WorkflowDefinition.Options.UsableAsActivity), filter.UsableAsActivity)
.AndWorkflowDefinitionSearchTerm(filter.SearchTerm);
}
private Page<WorkflowDefinition> Map(Page<WorkflowDefinitionRecord> source) => new(Map(source.Items).ToList(), source.TotalCount);

View file

@ -136,8 +136,7 @@ public class DapperWorkflowInstanceStore : IWorkflowInstanceStore
.Equals(nameof(WorkflowInstance.Name), filter.Version)
.Equals(nameof(WorkflowInstance.CorrelationId), filter.CorrelationId)
.In(nameof(WorkflowInstance.CorrelationId), filter.CorrelationIds)
.AndWorkflowInstanceSearchTerm(filter.SearchTerm)
;
.AndWorkflowInstanceSearchTerm(filter.SearchTerm);
}
private async Task<Page<WorkflowInstance>> MapAsync(Page<WorkflowInstanceRecord> source)

View file

@ -183,7 +183,7 @@ public class EFCoreWorkflowDefinitionStore : IWorkflowDefinitionStore
if (filter.Name != null) queryable = queryable.Where(x => x.Name == filter.Name);
if (filter.Names != null) queryable = queryable.Where(x => filter.Names.Contains(x.Name!));
if (filter.UsableAsActivity != null) queryable = queryable.Where(x => EF.Property<bool>(x, "UsableAsActivity") == filter.UsableAsActivity);
if (!string.IsNullOrWhiteSpace(filter.SearchTerm)) queryable = queryable.Where(x => x.Name.Contains(filter.SearchTerm) || x.Description.Contains(filter.SearchTerm) || x.Id == filter.SearchTerm || x.DefinitionId == filter.SearchTerm);
return queryable;
}

View file

@ -39,6 +39,10 @@ internal class List : ElsaEndpoint<Request, Response>
var versionOptions = request.VersionOptions != null ? VersionOptions.FromString(request.VersionOptions) : default(VersionOptions?);
var splitIds = request.DefinitionIds ?? Array.Empty<string>();
if (!string.IsNullOrWhiteSpace(request.SearchTerm)) {
return new WorkflowDefinitionFilter { SearchTerm = request.SearchTerm, VersionOptions = versionOptions };
}
return splitIds.Any()
? new WorkflowDefinitionFilter { DefinitionIds = splitIds, VersionOptions = versionOptions }
: new WorkflowDefinitionFilter { MaterializerName = request.MaterializerName, VersionOptions = versionOptions };

View file

@ -15,6 +15,7 @@ public class Request
public int? PageSize { get; set; }
public OrderByWorkflowDefinition? OrderBy { get; set; }
public OrderDirection? OrderDirection { get; set; }
public string? SearchTerm { get; set; }
}
public class Response

View file

@ -40,7 +40,12 @@ public class WorkflowDefinitionFilter
/// Filter by the name of the workflow definition.
/// </summary>
public string? Name { get; set; }
/// <summary>
/// Filter by the name or id of the workflow definition.
/// </summary>
public string? SearchTerm { get; set; }
/// <summary>
/// Filter by the names of the workflow definitions.
/// </summary>
@ -73,7 +78,8 @@ public class WorkflowDefinitionFilter
if (filter.Name != null) queryable = queryable.Where(x => x.Name == filter.Name);
if (filter.Names != null) queryable = queryable.Where(x => filter.Names.Contains(x.Name!));
if (filter.UsableAsActivity != null) queryable = queryable.Where(x => x.Options.UsableAsActivity == filter.UsableAsActivity);
if (!string.IsNullOrWhiteSpace(filter.SearchTerm)) queryable = queryable.Where(x => x.Name.Contains(filter.SearchTerm) || x.Description.Contains(filter.SearchTerm) || x.Id == filter.SearchTerm || x.DefinitionId == filter.SearchTerm);
return queryable;
}
}

View file

@ -94,9 +94,10 @@ public class WorkflowInstanceFilter
query =
from instance in query
where instance.Name!.Contains(searchTerm)
|| instance.Id.Contains(searchTerm)
|| instance.DefinitionId.Contains(searchTerm)
|| instance.CorrelationId!.Contains(searchTerm)
|| instance.DefinitionVersionId.Equals(searchTerm)
|| instance.Id.Equals(searchTerm)
|| instance.DefinitionId.Equals(searchTerm)
|| instance.CorrelationId!.Equals(searchTerm)
select instance;
}