Skip to content

Commit 303d7f1

Browse files
authored
KeywordList search attribute support (#2420)
* Account for KeywordList in query and filter formatting utils and add tests * Add ListFilter to filter search * Add unround and external content options to ChipInput * Add KeywordList option to SearchAttributeInput * Add payloadToString util * Derive customSearchAttributeOptions * Remove onMount in ListFilter
1 parent 49063e4 commit 303d7f1

26 files changed

+360
-129
lines changed

Diff for: src/lib/components/schedule/schedule-search-attributes.svelte

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { translate } from '$lib/i18n/translate';
44
import type { SearchAttribute } from '$lib/types';
55
import { decodePayloadAttributes } from '$lib/utilities/decode-payload';
6+
import { payloadToString } from '$lib/utilities/payload-to-string';
67
import { pluralize } from '$lib/utilities/pluralize';
78
89
export let searchAttributes: SearchAttribute;
@@ -23,12 +24,13 @@
2324
{#if searchAttributeCount}
2425
<ul class="w-full">
2526
{#each Object.entries(indexedFields) as [searchAttrName, searchAttrValue]}
27+
{@const value = payloadToString(searchAttrValue)}
2628
<li
2729
class="flex flex-wrap items-center gap-2 border-b py-2 last-of-type:border-b-0"
2830
>
2931
<span class="break-all">{searchAttrName}</span>
3032
<span class="surface-subtle select-all rounded-sm p-1 leading-4"
31-
>{searchAttrValue}</span
33+
>{value}</span
3234
>
3335
</li>
3436
{/each}

Diff for: src/lib/components/search-attribute-filter/filter-list.svelte

+3-4
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,7 @@
8989

9090
<div class="flex flex-wrap gap-2" class:pt-2={visibleFilters.length}>
9191
{#each visibleFilters as workflowFilter, i (`${workflowFilter.attribute}-${i}`)}
92-
{@const { attribute, type, value, conditional, customDate } =
93-
workflowFilter}
92+
{@const { attribute, value, conditional, customDate } = workflowFilter}
9493
{#if attribute}
9594
<div in:fade data-testid="{workflowFilter.attribute}-{i}">
9695
<Chip
@@ -119,7 +118,7 @@
119118
{#if isNullConditional(conditional)}
120119
{conditional}
121120
{value}
122-
{:else if isDateTimeFilter({ attribute, type })}
121+
{:else if isDateTimeFilter(workflowFilter)}
123122
{#if customDate}
124123
{formatDateTimeRange(value, $timeFormat, $relativeTime)}
125124
{:else}
@@ -133,7 +132,7 @@
133132
{isStartsWith(conditional)
134133
? translate('common.starts-with').toLocaleLowerCase()
135134
: conditional}
136-
{isTextFilter({ attribute, type }) ? `"${value}"` : value}
135+
{isTextFilter(workflowFilter) ? `"${value}"` : value}
137136
{/if}
138137
</span>
139138
{/if}

Diff for: src/lib/components/search-attribute-filter/index.svelte

+18-16
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
isBooleanFilter,
3131
isDateTimeFilter,
3232
isDurationFilter,
33+
isListFilter,
3334
isNumberFilter,
3435
isStatusFilter,
3536
isTextFilter,
@@ -45,6 +46,7 @@
4546
import DateTimeFilter from './datetime-filter.svelte';
4647
import DurationFilter from './duration-filter.svelte';
4748
import FilterList from './filter-list.svelte';
49+
import ListFilter from './list-filter.svelte';
4850
import NumberFilter from './number-filter.svelte';
4951
import SearchAttributeMenu from './search-attribute-menu.svelte';
5052
import StatusFilter from './status-filter.svelte';
@@ -59,9 +61,8 @@
5961
const activeQueryIndex = writable<number>(null);
6062
const focusedElementId = writable<string>('');
6163
62-
$: ({ attribute, type } = $filter);
6364
$: searchParamQuery = $page.url.searchParams.get('query');
64-
$: showClearAllButton = showFilter && filters.length && !attribute;
65+
$: showClearAllButton = showFilter && filters.length && !$filter.attribute;
6566
6667
setContext<FilterContext>(FILTER_CONTEXT, {
6768
filter,
@@ -105,7 +106,7 @@
105106
106107
function updateFocusedElementId() {
107108
if ($activeQueryIndex !== null) {
108-
$focusedElementId = getFocusedElementId({ attribute, type });
109+
$focusedElementId = getFocusedElementId($filter);
109110
}
110111
}
111112
@@ -134,7 +135,7 @@
134135
}
135136
136137
function handleKeyUp(event: KeyboardEvent) {
137-
if (event.key === 'Escape' && !isTextFilter({ attribute, type })) {
138+
if (event.key === 'Escape' && !isTextFilter($filter)) {
138139
resetFilter();
139140
}
140141
}
@@ -145,56 +146,57 @@
145146
<slot />
146147
{#if showFilter}
147148
<div
148-
class="flex items-center"
149+
class="flex"
149150
class:filter={!showClearAllButton}
150151
on:keyup={handleKeyUp}
151152
role="none"
152153
>
153-
{#if isStatusFilter(attribute)}
154+
{#if isStatusFilter($filter)}
154155
<StatusFilter bind:filters />
155156
{:else}
156157
<SearchAttributeMenu {filters} {options} />
157158
{/if}
158159

159-
{#if attribute}
160-
{#if isTextFilter({ attribute, type })}
160+
{#if $filter.attribute}
161+
{#if isTextFilter($filter)}
161162
<div
162163
class="flex w-full items-center"
163164
in:fly={{ x: -100, duration: 150 }}
164165
>
165166
<TextFilter />
166167
<CloseFilter />
167168
</div>
168-
<!-- TODO: Add KeywordList support -->
169-
<!-- {:else if isListFilter(attribute)}
169+
{:else if isListFilter($filter)}
170170
<div class="w-full" in:fly={{ x: -100, duration: 150 }}>
171-
<ListFilter />
172-
</div> -->
173-
{:else if isDurationFilter(attribute)}
171+
<ListFilter>
172+
<CloseFilter />
173+
</ListFilter>
174+
</div>
175+
{:else if isDurationFilter($filter)}
174176
<div
175177
class="flex w-full items-center"
176178
in:fly={{ x: -100, duration: 150 }}
177179
>
178180
<DurationFilter />
179181
<CloseFilter />
180182
</div>
181-
{:else if isNumberFilter({ attribute, type })}
183+
{:else if isNumberFilter($filter)}
182184
<div
183185
class="flex w-full items-center"
184186
in:fly={{ x: -100, duration: 150 }}
185187
>
186188
<NumberFilter />
187189
<CloseFilter />
188190
</div>
189-
{:else if isDateTimeFilter({ attribute, type })}
191+
{:else if isDateTimeFilter($filter)}
190192
<div
191193
class="flex w-full items-center"
192194
in:fly={{ x: -100, duration: 150 }}
193195
>
194196
<DateTimeFilter />
195197
<CloseFilter />
196198
</div>
197-
{:else if isBooleanFilter({ attribute, type })}
199+
{:else if isBooleanFilter($filter)}
198200
<div
199201
class="flex w-full items-center"
200202
in:fly={{ x: -100, duration: 150 }}

Diff for: src/lib/components/search-attribute-filter/list-filter.svelte

+24-10
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,14 @@
99
1010
const { filter, handleSubmit } = getContext<FilterContext>(FILTER_CONTEXT);
1111
12-
let list: string[] = [];
12+
$: ({ value } = $filter);
13+
$: list =
14+
value.length > 0
15+
? value
16+
.slice(1, -1)
17+
.split(', ')
18+
.map((v) => v.slice(1, -1))
19+
: [];
1320
1421
function onSubmit() {
1522
$filter.conditional = 'IN';
@@ -19,21 +26,28 @@
1926
}
2027
</script>
2128

22-
<div class="flex">
29+
<form class="flex grow" on:submit|preventDefault={onSubmit}>
2330
<ChipInput
2431
label={$filter.attribute}
2532
labelHidden
2633
id="list-filter"
2734
bind:chips={list}
28-
class="w-full rounded-none"
35+
class="w-full"
2936
removeChipButtonLabel={(chip) =>
3037
translate('workflows.remove-keyword-label', { keyword: chip })}
3138
placeholder="{translate('common.type-or-paste-in')} {$filter.attribute}"
39+
unroundLeft
40+
unroundRight
41+
external
3242
/>
33-
<Button
34-
variant="secondary"
35-
borderRadiusModifier="square-left"
36-
disabled={!list.length}
37-
on:click={onSubmit}>{translate('common.submit')}</Button
38-
>
39-
</div>
43+
<div class="flex h-fit items-center">
44+
<Button
45+
borderRadiusModifier="square-left"
46+
disabled={!list.length}
47+
type="submit"
48+
>
49+
{translate('common.search')}
50+
</Button>
51+
<slot />
52+
</div>
53+
</form>

Diff for: src/lib/components/search-attribute-filter/search-attribute-menu.svelte

+4-11
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@
1313
import type { SearchAttributeFilter } from '$lib/models/search-attribute-filters';
1414
import type { SearchAttributeOption } from '$lib/stores/search-attributes';
1515
import type { SearchAttributeType } from '$lib/types/workflows';
16-
import {
17-
getFocusedElementId,
18-
isListFilter,
19-
} from '$lib/utilities/query/search-attribute-filter';
16+
import { getFocusedElementId } from '$lib/utilities/query/search-attribute-filter';
2017
import { emptyFilter } from '$lib/utilities/query/to-list-workflow-filters';
2118
2219
import { FILTER_CONTEXT, type FilterContext } from './index.svelte';
@@ -38,18 +35,14 @@
3835
function handleNewQuery(value: string, type: SearchAttributeType) {
3936
searchAttributeValue = '';
4037
filter.set({ ...emptyFilter(), attribute: value, conditional: '=', type });
41-
$focusedElementId = getFocusedElementId({ attribute: value, type });
38+
$focusedElementId = getFocusedElementId($filter);
4239
}
4340
4441
let searchAttributeValue = '';
45-
// TODO: Add KeywordList support
46-
$: _options = options.filter(
47-
({ value, type }) => !isListFilter({ attribute: value, type }),
48-
);
4942
5043
$: filteredOptions = !searchAttributeValue
51-
? _options
52-
: _options.filter((option) =>
44+
? options
45+
: options.filter((option) =>
5346
option.value.toLowerCase().includes(searchAttributeValue.toLowerCase()),
5447
);
5548
</script>

Diff for: src/lib/components/search-attribute-filter/status-filter.svelte

+3-9
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,7 @@
2727
const { filter, resetFilter } = getContext<FilterContext>(FILTER_CONTEXT);
2828
const open = writable(true);
2929
$: _filters = [...filters];
30-
$: statusFilters = _filters.filter((filter) =>
31-
isStatusFilter(filter.attribute),
32-
);
30+
$: statusFilters = _filters.filter((filter) => isStatusFilter(filter));
3331
3432
function apply() {
3533
filters = _filters;
@@ -61,9 +59,7 @@
6159
if (status === 'All') {
6260
_filters = filters.filter((f) => f.attribute !== 'ExecutionStatus');
6361
} else if (statusFilters.find((s) => s.value === status)) {
64-
const nonStatusFilters = filters.filter(
65-
(f) => !isStatusFilter(f.attribute),
66-
);
62+
const nonStatusFilters = filters.filter((f) => !isStatusFilter(f));
6763
_filters = [
6864
...nonStatusFilters,
6965
...mapStatusesToFilters(
@@ -74,9 +70,7 @@
7470
if (!statusFilters.length) {
7571
_filters = [..._filters, mapStatusToFilter(status)];
7672
} else {
77-
const nonStatusFilters = _filters.filter(
78-
(f) => !isStatusFilter(f.attribute),
79-
);
73+
const nonStatusFilters = _filters.filter((f) => !isStatusFilter(f));
8074
_filters = [
8175
...nonStatusFilters,
8276
...mapStatusesToFilters([

Diff for: src/lib/components/workflow/add-search-attributes.svelte

+6-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@
3333
on:click={addSearchAttribute}
3434
disabled={!searchAttributes.length ||
3535
attributesToAdd.length === searchAttributes.length ||
36-
attributesToAdd.filter((a) => a.value === '' || a.value === null).length >
37-
0}>{translate('workflows.add-search-attribute')}</Button
36+
attributesToAdd.filter(
37+
(a) =>
38+
a.value === '' ||
39+
a.value === null ||
40+
(Array.isArray(a.value) && a.value.length === 0),
41+
).length > 0}>{translate('workflows.add-search-attribute')}</Button
3842
>
3943
</div>

Diff for: src/lib/components/workflow/search-attribute-input/datetime-input.svelte

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
let second = '';
1717
1818
onMount(() => {
19-
if (value) {
19+
if (value && (typeof value === 'string' || typeof value === 'number')) {
2020
const datetime = new Date(value);
2121
const utcDate = new Date(
2222
datetime.getUTCFullYear(),

Diff for: src/lib/components/workflow/search-attribute-input/index.svelte

+9-13
Original file line numberDiff line numberDiff line change
@@ -4,40 +4,34 @@
44
import Select from '$lib/holocene/select/select.svelte';
55
import { translate } from '$lib/i18n/translate';
66
import {
7+
customSearchAttributeOptions,
78
customSearchAttributes,
89
type SearchAttributeInput,
910
} from '$lib/stores/search-attributes';
10-
import {
11-
SEARCH_ATTRIBUTE_TYPE,
12-
type SearchAttributes,
13-
} from '$lib/types/workflows';
11+
import { SEARCH_ATTRIBUTE_TYPE } from '$lib/types/workflows';
1412
1513
import DatetimeInput from './datetime-input.svelte';
14+
import ListInput from './list-input.svelte';
1615
import NumberInput from './number-input.svelte';
1716
import TextInput from './text-input.svelte';
1817
1918
export let attributesToAdd: SearchAttributeInput[] = [];
20-
export let searchAttributes: SearchAttributes = $customSearchAttributes;
2119
export let attribute: SearchAttributeInput;
2220
export let onRemove: (attribute: string) => void;
2321
24-
$: type = searchAttributes[attribute.attribute];
25-
$: searchAttributesOptions = [...Object.entries(searchAttributes)]
26-
.map(([key, value]) => ({ label: key, value: key, type: value }))
27-
.filter(({ type }) => type !== 'KeywordList');
28-
22+
$: type = $customSearchAttributes[attribute.attribute];
2923
$: isDisabled = (value: string) => {
3024
return !!attributesToAdd.find((a) => a.attribute === value);
3125
};
3226
3327
const handleAttributeChange = (attr: string) => {
34-
if (type !== searchAttributes[attr]) {
28+
if (type !== $customSearchAttributes[attr]) {
3529
attribute.value = null;
3630
}
3731
};
3832
</script>
3933

40-
<div class="flex items-start gap-2">
34+
<div class="flex items-end gap-2">
4135
<div class="min-w-fit">
4236
<Select
4337
id="search-attribute"
@@ -47,7 +41,7 @@
4741
bind:value={attribute.attribute}
4842
onChange={handleAttributeChange}
4943
>
50-
{#each searchAttributesOptions as { value, label, type }}
44+
{#each $customSearchAttributeOptions as { value, label, type }}
5145
<Option disabled={isDisabled(value)} {value} description={type}
5246
>{label}</Option
5347
>
@@ -67,6 +61,8 @@
6761
<DatetimeInput bind:value={attribute.value} />
6862
{:else if type === SEARCH_ATTRIBUTE_TYPE.INT || type === SEARCH_ATTRIBUTE_TYPE.DOUBLE}
6963
<NumberInput bind:value={attribute.value} />
64+
{:else if type === SEARCH_ATTRIBUTE_TYPE.KEYWORDLIST}
65+
<ListInput bind:value={attribute.value} />
7066
{:else}
7167
<TextInput bind:value={attribute.value} />
7268
{/if}

0 commit comments

Comments
 (0)