Skip to content

Commit 25b4795

Browse files
Schedule search attributes (#2275)
* Schedule search attributes (#2241) * Add search attributes to create schedule UI * Add search attributes to edit schedule UI * Fix search attribute inputs based on search attribute type * Add search attributes to schedule view UI * Use custom datetime input with UTC * Small UI and copy fixes * Fix attribute value to always update on attribute change * Fix label spacing and update custom search attribute table to be list instead * Update schedule advanced settings to be list * Fix search attribute view/create/update * Fix type * Add expandable prop to search attribute Accordion --------- Co-authored-by: Alex Tideman <[email protected]> * Add ability to query schedules (#2272) * Refactor FilterSearch to component for reuse on Schedules page * Rename filter search to search attribute filter * Make schedules table headers configurable * Add back schedule name search input * Fix responsiveness * Only add starts with option to TextFilter if attribute is a Keyword * Fix empty state * update testId * Add onError callback * Remove schedule name search input * Add search attribute type map * Add ScheduleId filter option and rename table header * Use Input required prop on schedule form * Add isCloud check for ScheduleId * Add unspecified search attribute type --------- Co-authored-by: Alex Tideman <[email protected]>
1 parent 6f842c2 commit 25b4795

File tree

73 files changed

+1543
-971
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+1543
-971
lines changed

Diff for: src/lib/components/schedule/schedule-advanced-settings.svelte

+43-24
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,47 @@
1414

1515
<ScheduleNotes {notes} />
1616
<Accordion title={translate('schedules.advanced-settings')}>
17-
<p>
18-
{translate('schedules.start-time')}
19-
{spec?.startTime ?? translate('common.none')}
20-
</p>
21-
<p>
22-
{translate('schedules.end-time')}{spec?.endTime ?? translate('common.none')}
23-
</p>
24-
<p>
25-
{translate('schedules.jitter')}{spec?.jitter ?? translate('common.none')}
26-
</p>
27-
<p>
28-
{translate('schedules.exclusion-calendar')}{spec?.excludeCalendar?.[0] ??
29-
translate('common.none')}
30-
</p>
31-
{#if state?.limitedActions}
32-
<p>
33-
{translate('schedules.remaining-actions')}{state?.remainingActions ??
34-
translate('common.none')}
35-
</p>
36-
{/if}
37-
<p>
38-
{translate('schedules.overlap-policy')}{policies?.overlapPolicy ??
39-
translate('common.none')}
40-
</p>
17+
<ul class="settings-list">
18+
<li>
19+
{translate('schedules.start-time')}
20+
<span> {spec?.startTime ?? translate('common.none')}</span>
21+
</li>
22+
<li>
23+
{translate('schedules.end-time')}
24+
<span>{spec?.endTime ?? translate('common.none')} </span>
25+
</li>
26+
<li>
27+
{translate('schedules.jitter')}
28+
<span>{spec?.jitter ?? translate('common.none')} </span>
29+
</li>
30+
31+
<li>
32+
{translate('schedules.exclusion-calendar')}
33+
<span> {spec?.excludeCalendar?.[0] ?? translate('common.none')}</span>
34+
</li>
35+
{#if state?.limitedActions}
36+
<li>
37+
{translate('schedules.remaining-actions')}
38+
<span>{state?.remainingActions ?? translate('common.none')} </span>
39+
</li>
40+
{/if}
41+
<li>
42+
{translate('schedules.overlap-policy')}
43+
<span>{policies?.overlapPolicy ?? translate('common.none')} </span>
44+
</li>
45+
</ul>
4146
</Accordion>
47+
48+
<style lang="postcss">
49+
.settings-list {
50+
@apply w-full;
51+
52+
li {
53+
@apply flex flex-wrap items-center gap-2 border-b py-2 last-of-type:border-b-0;
54+
55+
span {
56+
@apply surface-subtle select-all rounded-sm p-1 leading-4;
57+
}
58+
}
59+
}
60+
</style>

Diff for: src/lib/components/schedule/schedule-form-view.svelte

+31-7
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,28 @@
1111
import Loading from '$lib/holocene/loading.svelte';
1212
import { translate } from '$lib/i18n/translate';
1313
import { error, loading } from '$lib/stores/schedules';
14+
import type { SearchAttributeInput } from '$lib/stores/search-attributes';
1415
import type {
1516
FullSchedule,
1617
ScheduleParameters,
1718
SchedulePreset,
1819
} from '$lib/types/schedule';
20+
import { decodePayloadAttributes } from '$lib/utilities/decode-payload';
1921
import {
2022
routeForSchedule,
2123
routeForSchedules,
2224
} from '$lib/utilities/route-for';
2325
import { writeActionsAreAllowed } from '$lib/utilities/write-actions-are-allowed';
2426
27+
import AddSearchAttributes from '../workflow/add-search-attributes.svelte';
28+
2529
import ScheduleInputPayload from './schedule-input-payload.svelte';
2630
27-
import type { Schedule } from '$types';
31+
import type { Schedule, SearchAttribute } from '$types';
2832
2933
export let schedule: FullSchedule | null = null;
34+
export let searchAttributes: SearchAttribute = {};
35+
3036
export let onConfirm: (
3137
preset: SchedulePreset,
3238
args: Partial<ScheduleParameters>,
@@ -52,9 +58,17 @@
5258
5359
let errors = {};
5460
let name = scheduleId ?? '';
55-
let workflowType = schedule?.action?.startWorkflow?.workflowType?.name ?? '';
56-
let workflowId = schedule?.action?.startWorkflow?.workflowId ?? '';
57-
let taskQueue = schedule?.action?.startWorkflow?.taskQueue?.name ?? '';
61+
const decodedWorkflow = decodePayloadAttributes(
62+
schedule?.action?.startWorkflow,
63+
);
64+
const decodedSearchAttributes = decodePayloadAttributes({ searchAttributes });
65+
const indexedFields =
66+
decodedSearchAttributes?.searchAttributes.indexedFields ??
67+
({} as { [k: string]: string });
68+
69+
let workflowType = decodedWorkflow?.workflowType?.name ?? '';
70+
let workflowId = decodedWorkflow?.workflowId ?? '';
71+
let taskQueue = decodedWorkflow?.taskQueue?.name ?? '';
5872
let input = '';
5973
let daysOfWeek: string[] = [];
6074
let daysOfMonth: number[] = [];
@@ -65,6 +79,9 @@
6579
let second = '';
6680
let phase = '';
6781
let cronString = '';
82+
let searchAttributesInput: SearchAttributeInput[] = Object.entries(
83+
indexedFields,
84+
).map(([attribute, value]) => ({ attribute, value }));
6885
6986
const handleConfirm = (preset: SchedulePreset, schedule?: Schedule) => {
7087
const args: Partial<ScheduleParameters> = {
@@ -82,6 +99,7 @@
8299
daysOfMonth,
83100
days,
84101
months,
102+
searchAttributes: searchAttributesInput,
85103
};
86104
87105
onConfirm(preset, args, schedule);
@@ -145,9 +163,7 @@
145163
<h1>{title}</h1>
146164
</header>
147165
<form class="mb-4 flex w-full flex-col gap-4 md:w-2/3 xl:w-1/2">
148-
<Alert intent="error" title="" hidden={!$error}>
149-
{$error}
150-
</Alert>
166+
<Alert intent="error" title={$error} hidden={!$error} />
151167
<div class="w-full">
152168
<Input
153169
id="name"
@@ -158,6 +174,7 @@
158174
disabled={Boolean(scheduleId)}
159175
on:input={onInput}
160176
on:blur={onBlur}
177+
required
161178
/>
162179
</div>
163180
<div class="w-full">
@@ -168,6 +185,7 @@
168185
error={errors['workflowType']}
169186
on:input={onInput}
170187
on:blur={onBlur}
188+
required
171189
/>
172190
</div>
173191
<div class="w-full">
@@ -178,6 +196,7 @@
178196
error={errors['workflowId']}
179197
on:input={onInput}
180198
on:blur={onBlur}
199+
required
181200
/>
182201
</div>
183202
<div class="w-full">
@@ -188,13 +207,18 @@
188207
error={errors['taskQueue']}
189208
on:input={onInput}
190209
on:blur={onBlur}
210+
required
191211
/>
192212
</div>
193213
<ScheduleInputPayload
194214
bind:input
195215
payloads={schedule?.action?.startWorkflow?.input}
196216
error={errors['input']}
197217
/>
218+
<AddSearchAttributes
219+
bind:attributesToAdd={searchAttributesInput}
220+
class="w-full"
221+
/>
198222
<SchedulesCalendarView
199223
let:preset
200224
{schedule}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<script lang="ts">
2+
import Accordion from '$lib/holocene/accordion.svelte';
3+
import { translate } from '$lib/i18n/translate';
4+
import type { SearchAttribute } from '$lib/types';
5+
import { decodePayloadAttributes } from '$lib/utilities/decode-payload';
6+
import { pluralize } from '$lib/utilities/pluralize';
7+
8+
export let searchAttributes: SearchAttribute;
9+
10+
$: decodedSearchAttributes = decodePayloadAttributes({ searchAttributes });
11+
$: indexedFields =
12+
decodedSearchAttributes?.searchAttributes.indexedFields ?? {};
13+
$: searchAttributeCount = Object.keys(indexedFields).length;
14+
</script>
15+
16+
<Accordion
17+
title={translate('events.custom-search-attributes')}
18+
subtitle={`${searchAttributeCount} ${translate(
19+
'events.custom-search',
20+
)} ${pluralize(translate('events.attribute'), searchAttributeCount)}`}
21+
expandable={searchAttributeCount > 0}
22+
>
23+
{#if searchAttributeCount}
24+
<ul class="w-full">
25+
{#each Object.entries(indexedFields) as [searchAttrName, searchAttrValue]}
26+
<li
27+
class="flex flex-wrap items-center gap-2 border-b py-2 last-of-type:border-b-0"
28+
>
29+
<span class="break-all">{searchAttrName}</span>
30+
<span class="surface-subtle select-all rounded-sm p-1 leading-4"
31+
>{searchAttrValue}</span
32+
>
33+
</li>
34+
{/each}
35+
</ul>
36+
{/if}
37+
</Accordion>

Diff for: src/lib/components/schedule/schedules-table-row.svelte

+59-39
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import WorkflowStatus from '$lib/components/workflow-status.svelte';
55
import Link from '$lib/holocene/link.svelte';
66
import { translate } from '$lib/i18n/translate';
7+
import type { ConfigurableTableHeader } from '$lib/stores/configurable-table-columns';
78
import { relativeTime, timeFormat } from '$lib/stores/time-format';
9+
import { decodePayloadAttributes } from '$lib/utilities/decode-payload';
810
import { formatDate } from '$lib/utilities/format-date';
911
import {
1012
routeForEventHistory,
@@ -18,11 +20,14 @@
1820
let { namespace } = $page.params;
1921
2022
export let schedule: ScheduleListEntry;
23+
export let columns: ConfigurableTableHeader[];
2124
2225
$: spec = schedule?.info?.spec;
2326
$: calendar = spec?.structuredCalendar?.[0];
2427
$: interval = spec?.interval?.[0];
2528
$: timezoneName = spec?.timezoneName || 'UTC';
29+
$: searchAttributes = schedule?.searchAttributes ?? {};
30+
$: decodedAttributes = decodePayloadAttributes({ searchAttributes });
2631
2732
const sortRecentActions = (recentActions: ScheduleActionResult[]) => {
2833
return (
@@ -43,45 +48,60 @@
4348
</script>
4449

4550
<tr>
46-
<td class="cell">
47-
<WorkflowStatus status={schedule?.info?.paused ? 'Paused' : 'Running'} />
48-
</td>
49-
<td class="cell whitespace-pre-line break-words">
50-
<Link href={route}>{schedule.scheduleId}</Link>
51-
</td>
52-
<td class="cell whitespace-pre-line break-words max-md:hidden">
53-
{schedule?.info?.workflowType?.name ?? ''}
54-
</td>
55-
<td class="cell truncate">
56-
{#each sortRecentActions(schedule?.info?.recentActions) as run}
57-
<p>
58-
<Link
59-
href={routeForEventHistory({
60-
namespace,
61-
workflow: run?.startWorkflowResult?.workflowId,
62-
run: run?.startWorkflowResult?.runId,
63-
})}
64-
>{formatDate(run?.actualTime, $timeFormat, {
65-
relative: $relativeTime,
66-
})}</Link
67-
>
68-
</p>
69-
{/each}
70-
</td>
71-
<td class="cell truncate">
72-
{#each schedule?.info?.futureActionTimes?.slice(0, 5) ?? [] as run}
73-
<div>
74-
{formatDate(run, $timeFormat, {
75-
relative: $relativeTime,
76-
relativeLabel: translate('common.from-now'),
77-
})}
78-
</div>
79-
{/each}
80-
</td>
81-
<td class="cell">
82-
<p>{@html translate('common.timezone', { timezone: timezoneName })}</p>
83-
<ScheduleBasicFrequency {calendar} {interval} />
84-
</td>
51+
{#each columns as { label } (label)}
52+
{#if label === translate('common.status')}
53+
<td class="cell">
54+
<WorkflowStatus
55+
status={schedule?.info?.paused ? 'Paused' : 'Running'}
56+
/>
57+
</td>
58+
{:else if label === translate('schedules.id')}
59+
<td class="cell whitespace-pre-line break-words">
60+
<Link href={route}>{schedule.scheduleId}</Link>
61+
</td>
62+
{:else if label === translate('common.workflow-type')}
63+
<td class="cell whitespace-pre-line break-words">
64+
{schedule?.info?.workflowType?.name ?? ''}
65+
</td>
66+
{:else if label === translate('schedules.recent-runs')}
67+
<td class="cell truncate">
68+
{#each sortRecentActions(schedule?.info?.recentActions) as run}
69+
<p>
70+
<Link
71+
href={routeForEventHistory({
72+
namespace,
73+
workflow: run?.startWorkflowResult?.workflowId,
74+
run: run?.startWorkflowResult?.runId,
75+
})}
76+
>{formatDate(run?.actualTime, $timeFormat, {
77+
relative: $relativeTime,
78+
})}</Link
79+
>
80+
</p>
81+
{/each}
82+
</td>
83+
{:else if label === translate('schedules.upcoming-runs')}
84+
<td class="cell truncate">
85+
{#each schedule?.info?.futureActionTimes?.slice(0, 5) ?? [] as run}
86+
<div>
87+
{formatDate(run, $timeFormat, {
88+
relative: $relativeTime,
89+
relativeLabel: translate('common.from-now'),
90+
})}
91+
</div>
92+
{/each}
93+
</td>
94+
{:else if label === translate('schedules.schedule-spec')}
95+
<td class="cell">
96+
<p>{@html translate('common.timezone', { timezone: timezoneName })}</p>
97+
<ScheduleBasicFrequency {calendar} {interval} />
98+
</td>
99+
{:else}
100+
<td class="cell">
101+
{decodedAttributes?.searchAttributes?.indexedFields?.[label] ?? ''}
102+
</td>
103+
{/if}
104+
{/each}
85105
</tr>
86106

87107
<style lang="postcss">

Diff for: src/lib/components/workflow/filter-search/duration-filter.svelte renamed to src/lib/components/search-attribute-filter/duration-filter.svelte

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@
3333
};
3434
</script>
3535

36-
<ConditionalMenu inputId="duration-filter-search" noBorderLeft>
36+
<ConditionalMenu inputId="duration-filter" noBorderLeft>
3737
<Input
3838
label={$filter.attribute}
3939
labelHidden
40-
id="duration-filter-search"
40+
id="duration-filter"
4141
type="search"
4242
placeholder={`${translate('common.enter')} ${
4343
$filter.attribute

0 commit comments

Comments
 (0)