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:
parent
c35d16ee3e
commit
3aa05d1fed
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -40,8 +40,6 @@ export const Filter: FunctionalComponent<FilterProps> = ({ pageSizeFilter, order
|
|||
</div>
|
||||
<div class="tw-flex-1"> </div>
|
||||
|
||||
<elsa-label-picker {...labelFilter} />
|
||||
|
||||
<PageSizeFilter {...pageSizeFilter} />
|
||||
|
||||
<OrderByFilter {...orderByFilter} />
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
}
|
||||
|
|
@ -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>;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -53,9 +53,11 @@ export const Filter: FunctionalComponent<FilterProps> = ({pageSizeFilter, workfl
|
|||
<div class="tw-flex-1">
|
||||
|
||||
</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}/>
|
||||
|
|
|
|||
|
|
@ -114,4 +114,4 @@ export interface GetWorkflowJournalRequest {
|
|||
workflowInstanceId: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue