Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KeywordList search attribute support #2420

Merged
merged 9 commits into from
Nov 20, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { translate } from '$lib/i18n/translate';
import type { SearchAttribute } from '$lib/types';
import { decodePayloadAttributes } from '$lib/utilities/decode-payload';
import { payloadToString } from '$lib/utilities/payload-to-string';
import { pluralize } from '$lib/utilities/pluralize';

export let searchAttributes: SearchAttribute;
Expand All @@ -23,12 +24,13 @@
{#if searchAttributeCount}
<ul class="w-full">
{#each Object.entries(indexedFields) as [searchAttrName, searchAttrValue]}
{@const value = payloadToString(searchAttrValue)}
<li
class="flex flex-wrap items-center gap-2 border-b py-2 last-of-type:border-b-0"
>
<span class="break-all">{searchAttrName}</span>
<span class="surface-subtle select-all rounded-sm p-1 leading-4"
>{searchAttrValue}</span
>{value}</span
>
</li>
{/each}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,7 @@

<div class="flex flex-wrap gap-2" class:pt-2={visibleFilters.length}>
{#each visibleFilters as workflowFilter, i (`${workflowFilter.attribute}-${i}`)}
{@const { attribute, type, value, conditional, customDate } =
workflowFilter}
{@const { attribute, value, conditional, customDate } = workflowFilter}
{#if attribute}
<div in:fade data-testid="{workflowFilter.attribute}-{i}">
<Chip
Expand Down Expand Up @@ -119,7 +118,7 @@
{#if isNullConditional(conditional)}
{conditional}
{value}
{:else if isDateTimeFilter({ attribute, type })}
{:else if isDateTimeFilter(workflowFilter)}
{#if customDate}
{formatDateTimeRange(value, $timeFormat, $relativeTime)}
{:else}
Expand All @@ -133,7 +132,7 @@
{isStartsWith(conditional)
? translate('common.starts-with').toLocaleLowerCase()
: conditional}
{isTextFilter({ attribute, type }) ? `"${value}"` : value}
{isTextFilter(workflowFilter) ? `"${value}"` : value}
{/if}
</span>
{/if}
Expand Down
34 changes: 18 additions & 16 deletions src/lib/components/search-attribute-filter/index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
isBooleanFilter,
isDateTimeFilter,
isDurationFilter,
isListFilter,
isNumberFilter,
isStatusFilter,
isTextFilter,
Expand All @@ -45,6 +46,7 @@
import DateTimeFilter from './datetime-filter.svelte';
import DurationFilter from './duration-filter.svelte';
import FilterList from './filter-list.svelte';
import ListFilter from './list-filter.svelte';
import NumberFilter from './number-filter.svelte';
import SearchAttributeMenu from './search-attribute-menu.svelte';
import StatusFilter from './status-filter.svelte';
Expand All @@ -59,9 +61,8 @@
const activeQueryIndex = writable<number>(null);
const focusedElementId = writable<string>('');

$: ({ attribute, type } = $filter);
$: searchParamQuery = $page.url.searchParams.get('query');
$: showClearAllButton = showFilter && filters.length && !attribute;
$: showClearAllButton = showFilter && filters.length && !$filter.attribute;

setContext<FilterContext>(FILTER_CONTEXT, {
filter,
Expand Down Expand Up @@ -105,7 +106,7 @@

function updateFocusedElementId() {
if ($activeQueryIndex !== null) {
$focusedElementId = getFocusedElementId({ attribute, type });
$focusedElementId = getFocusedElementId($filter);
}
}

Expand Down Expand Up @@ -134,7 +135,7 @@
}

function handleKeyUp(event: KeyboardEvent) {
if (event.key === 'Escape' && !isTextFilter({ attribute, type })) {
if (event.key === 'Escape' && !isTextFilter($filter)) {
resetFilter();
}
}
Expand All @@ -145,56 +146,57 @@
<slot />
{#if showFilter}
<div
class="flex items-center"
class="flex"
class:filter={!showClearAllButton}
on:keyup={handleKeyUp}
role="none"
>
{#if isStatusFilter(attribute)}
{#if isStatusFilter($filter)}
<StatusFilter bind:filters />
{:else}
<SearchAttributeMenu {filters} {options} />
{/if}

{#if attribute}
{#if isTextFilter({ attribute, type })}
{#if $filter.attribute}
{#if isTextFilter($filter)}
<div
class="flex w-full items-center"
in:fly={{ x: -100, duration: 150 }}
>
<TextFilter />
<CloseFilter />
</div>
<!-- TODO: Add KeywordList support -->
<!-- {:else if isListFilter(attribute)}
{:else if isListFilter($filter)}
<div class="w-full" in:fly={{ x: -100, duration: 150 }}>
<ListFilter />
</div> -->
{:else if isDurationFilter(attribute)}
<ListFilter>
<CloseFilter />
</ListFilter>
</div>
{:else if isDurationFilter($filter)}
<div
class="flex w-full items-center"
in:fly={{ x: -100, duration: 150 }}
>
<DurationFilter />
<CloseFilter />
</div>
{:else if isNumberFilter({ attribute, type })}
{:else if isNumberFilter($filter)}
<div
class="flex w-full items-center"
in:fly={{ x: -100, duration: 150 }}
>
<NumberFilter />
<CloseFilter />
</div>
{:else if isDateTimeFilter({ attribute, type })}
{:else if isDateTimeFilter($filter)}
<div
class="flex w-full items-center"
in:fly={{ x: -100, duration: 150 }}
>
<DateTimeFilter />
<CloseFilter />
</div>
{:else if isBooleanFilter({ attribute, type })}
{:else if isBooleanFilter($filter)}
<div
class="flex w-full items-center"
in:fly={{ x: -100, duration: 150 }}
Expand Down
34 changes: 24 additions & 10 deletions src/lib/components/search-attribute-filter/list-filter.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@

const { filter, handleSubmit } = getContext<FilterContext>(FILTER_CONTEXT);

let list: string[] = [];
$: ({ value } = $filter);
$: list =
value.length > 0
? value
.slice(1, -1)
.split(', ')
.map((v) => v.slice(1, -1))
: [];

function onSubmit() {
$filter.conditional = 'IN';
Expand All @@ -19,21 +26,28 @@
}
</script>

<div class="flex">
<form class="flex grow" on:submit|preventDefault={onSubmit}>
<ChipInput
label={$filter.attribute}
labelHidden
id="list-filter"
bind:chips={list}
class="w-full rounded-none"
class="w-full"
removeChipButtonLabel={(chip) =>
translate('workflows.remove-keyword-label', { keyword: chip })}
placeholder="{translate('common.type-or-paste-in')} {$filter.attribute}"
unroundLeft
unroundRight
external
/>
<Button
variant="secondary"
borderRadiusModifier="square-left"
disabled={!list.length}
on:click={onSubmit}>{translate('common.submit')}</Button
>
</div>
<div class="flex h-fit items-center">
<Button
borderRadiusModifier="square-left"
disabled={!list.length}
type="submit"
>
{translate('common.search')}
</Button>
<slot />
</div>
</form>
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@
import type { SearchAttributeFilter } from '$lib/models/search-attribute-filters';
import type { SearchAttributeOption } from '$lib/stores/search-attributes';
import type { SearchAttributeType } from '$lib/types/workflows';
import {
getFocusedElementId,
isListFilter,
} from '$lib/utilities/query/search-attribute-filter';
import { getFocusedElementId } from '$lib/utilities/query/search-attribute-filter';
import { emptyFilter } from '$lib/utilities/query/to-list-workflow-filters';

import { FILTER_CONTEXT, type FilterContext } from './index.svelte';
Expand All @@ -38,18 +35,14 @@
function handleNewQuery(value: string, type: SearchAttributeType) {
searchAttributeValue = '';
filter.set({ ...emptyFilter(), attribute: value, conditional: '=', type });
$focusedElementId = getFocusedElementId({ attribute: value, type });
$focusedElementId = getFocusedElementId($filter);
}

let searchAttributeValue = '';
// TODO: Add KeywordList support
$: _options = options.filter(
({ value, type }) => !isListFilter({ attribute: value, type }),
);

$: filteredOptions = !searchAttributeValue
? _options
: _options.filter((option) =>
? options
: options.filter((option) =>
option.value.toLowerCase().includes(searchAttributeValue.toLowerCase()),
);
</script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@
const { filter, resetFilter } = getContext<FilterContext>(FILTER_CONTEXT);
const open = writable(true);
$: _filters = [...filters];
$: statusFilters = _filters.filter((filter) =>
isStatusFilter(filter.attribute),
);
$: statusFilters = _filters.filter((filter) => isStatusFilter(filter));

function apply() {
filters = _filters;
Expand Down Expand Up @@ -61,9 +59,7 @@
if (status === 'All') {
_filters = filters.filter((f) => f.attribute !== 'ExecutionStatus');
} else if (statusFilters.find((s) => s.value === status)) {
const nonStatusFilters = filters.filter(
(f) => !isStatusFilter(f.attribute),
);
const nonStatusFilters = filters.filter((f) => !isStatusFilter(f));
_filters = [
...nonStatusFilters,
...mapStatusesToFilters(
Expand All @@ -74,9 +70,7 @@
if (!statusFilters.length) {
_filters = [..._filters, mapStatusToFilter(status)];
} else {
const nonStatusFilters = _filters.filter(
(f) => !isStatusFilter(f.attribute),
);
const nonStatusFilters = _filters.filter((f) => !isStatusFilter(f));
_filters = [
...nonStatusFilters,
...mapStatusesToFilters([
Expand Down
8 changes: 6 additions & 2 deletions src/lib/components/workflow/add-search-attributes.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@
on:click={addSearchAttribute}
disabled={!searchAttributes.length ||
attributesToAdd.length === searchAttributes.length ||
attributesToAdd.filter((a) => a.value === '' || a.value === null).length >
0}>{translate('workflows.add-search-attribute')}</Button
attributesToAdd.filter(
(a) =>
a.value === '' ||
a.value === null ||
(Array.isArray(a.value) && a.value.length === 0),
).length > 0}>{translate('workflows.add-search-attribute')}</Button
>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
let second = '';

onMount(() => {
if (value) {
if (value && (typeof value === 'string' || typeof value === 'number')) {
const datetime = new Date(value);
const utcDate = new Date(
datetime.getUTCFullYear(),
Expand Down
22 changes: 9 additions & 13 deletions src/lib/components/workflow/search-attribute-input/index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,34 @@
import Select from '$lib/holocene/select/select.svelte';
import { translate } from '$lib/i18n/translate';
import {
customSearchAttributeOptions,
customSearchAttributes,
type SearchAttributeInput,
} from '$lib/stores/search-attributes';
import {
SEARCH_ATTRIBUTE_TYPE,
type SearchAttributes,
} from '$lib/types/workflows';
import { SEARCH_ATTRIBUTE_TYPE } from '$lib/types/workflows';

import DatetimeInput from './datetime-input.svelte';
import ListInput from './list-input.svelte';
import NumberInput from './number-input.svelte';
import TextInput from './text-input.svelte';

export let attributesToAdd: SearchAttributeInput[] = [];
export let searchAttributes: SearchAttributes = $customSearchAttributes;
export let attribute: SearchAttributeInput;
export let onRemove: (attribute: string) => void;

$: type = searchAttributes[attribute.attribute];
$: searchAttributesOptions = [...Object.entries(searchAttributes)]
.map(([key, value]) => ({ label: key, value: key, type: value }))
.filter(({ type }) => type !== 'KeywordList');

$: type = $customSearchAttributes[attribute.attribute];
$: isDisabled = (value: string) => {
return !!attributesToAdd.find((a) => a.attribute === value);
};

const handleAttributeChange = (attr: string) => {
if (type !== searchAttributes[attr]) {
if (type !== $customSearchAttributes[attr]) {
attribute.value = null;
}
};
</script>

<div class="flex items-start gap-2">
<div class="flex items-end gap-2">
<div class="min-w-fit">
<Select
id="search-attribute"
Expand All @@ -47,7 +41,7 @@
bind:value={attribute.attribute}
onChange={handleAttributeChange}
>
{#each searchAttributesOptions as { value, label, type }}
{#each $customSearchAttributeOptions as { value, label, type }}
<Option disabled={isDisabled(value)} {value} description={type}
>{label}</Option
>
Expand All @@ -67,6 +61,8 @@
<DatetimeInput bind:value={attribute.value} />
{:else if type === SEARCH_ATTRIBUTE_TYPE.INT || type === SEARCH_ATTRIBUTE_TYPE.DOUBLE}
<NumberInput bind:value={attribute.value} />
{:else if type === SEARCH_ATTRIBUTE_TYPE.KEYWORDLIST}
<ListInput bind:value={attribute.value} />
{:else}
<TextInput bind:value={attribute.value} />
{/if}
Expand Down
Loading
Loading