Skip to content

Commit c6e7c88

Browse files
authored
Schedule update search attributes (#1625)
1 parent a0470c9 commit c6e7c88

File tree

3 files changed

+139
-3
lines changed

3 files changed

+139
-3
lines changed

packages/client/src/schedule-client.ts

+6
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,12 @@ export class ScheduleClient extends BaseClient {
307307
},
308308
identity: this.options.identity,
309309
requestId: uuid4(),
310+
searchAttributes:
311+
opts.searchAttributes || opts.typedSearchAttributes // eslint-disable-line deprecation/deprecation
312+
? {
313+
indexedFields: encodeUnifiedSearchAttributes(opts.searchAttributes, opts.typedSearchAttributes), // eslint-disable-line deprecation/deprecation
314+
}
315+
: undefined,
310316
};
311317
try {
312318
return await this.workflowService.updateSchedule(req);

packages/client/src/schedule-types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ export type CompiledScheduleOptions = Replace<
142142
* The specification of an updated Schedule, as expected by {@link ScheduleHandle.update}.
143143
*/
144144
export type ScheduleUpdateOptions<A extends ScheduleOptionsAction = ScheduleOptionsAction> = Replace<
145-
Omit<ScheduleOptions, 'scheduleId' | 'memo' | 'searchAttributes' | 'typedSearchAttributes'>,
145+
Omit<ScheduleOptions, 'scheduleId' | 'memo'>,
146146
{
147147
action: A;
148148
state: Omit<ScheduleOptions['state'], 'triggerImmediately' | 'backfill'>;

packages/test/src/test-schedules.ts

+132-2
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,16 @@ import {
1010
ScheduleHandle,
1111
ScheduleSummary,
1212
ScheduleUpdateOptions,
13+
ScheduleDescription,
1314
} from '@temporalio/client';
1415
import { msToNumber } from '@temporalio/common/lib/time';
15-
import { SearchAttributes, TypedSearchAttributes } from '@temporalio/common';
16-
import { registerDefaultCustomSearchAttributes, RUN_INTEGRATION_TESTS } from './helpers';
16+
import {
17+
SearchAttributeType,
18+
SearchAttributes,
19+
TypedSearchAttributes,
20+
defineSearchAttributeKey,
21+
} from '@temporalio/common';
22+
import { registerDefaultCustomSearchAttributes, RUN_INTEGRATION_TESTS, waitUntil } from './helpers';
1723
import { defaultSAKeys } from './helpers-integration';
1824

1925
export interface Context {
@@ -751,4 +757,128 @@ if (RUN_INTEGRATION_TESTS) {
751757
await handle.delete();
752758
}
753759
});
760+
761+
test.serial('Can update search attributes of a schedule', async (t) => {
762+
const { client } = t.context;
763+
const scheduleId = `can-update-search-attributes-of-schedule-${randomUUID()}`;
764+
765+
// Helper to wait for search attribute changes to propagate.
766+
const waitForAttributeChange = async (
767+
handle: ScheduleHandle,
768+
attributeName: string,
769+
shouldExist: boolean
770+
): Promise<ScheduleDescription> => {
771+
await waitUntil(async () => {
772+
const desc = await handle.describe();
773+
const exists =
774+
desc.typedSearchAttributes.getAll().find((pair) => pair.key.name === attributeName) !== undefined;
775+
return exists === shouldExist;
776+
}, 300);
777+
return await handle.describe();
778+
};
779+
780+
// Create a schedule with search attributes.
781+
const handle = await client.schedule.create({
782+
scheduleId,
783+
spec: {
784+
calendars: [{ hour: { start: 2, end: 7, step: 1 } }],
785+
},
786+
action: {
787+
type: 'startWorkflow',
788+
workflowType: dummyWorkflow,
789+
taskQueue,
790+
},
791+
searchAttributes: {
792+
CustomKeywordField: ['keyword-one'],
793+
},
794+
typedSearchAttributes: [{ key: defineSearchAttributeKey('CustomIntField', SearchAttributeType.INT), value: 1 }],
795+
});
796+
797+
// Check the search attributes are part of the schedule description.
798+
const desc = await handle.describe();
799+
// eslint-disable-next-line deprecation/deprecation
800+
t.deepEqual(desc.searchAttributes, {
801+
CustomKeywordField: ['keyword-one'],
802+
CustomIntField: [1],
803+
});
804+
t.deepEqual(
805+
desc.typedSearchAttributes,
806+
new TypedSearchAttributes([
807+
{ key: defineSearchAttributeKey('CustomIntField', SearchAttributeType.INT), value: 1 },
808+
{ key: defineSearchAttributeKey('CustomKeywordField', SearchAttributeType.KEYWORD), value: 'keyword-one' },
809+
])
810+
);
811+
812+
// Perform a series of updates to schedule's search attributes.
813+
try {
814+
// Update existing search attributes, add new ones.
815+
await handle.update((desc) => ({
816+
...desc,
817+
searchAttributes: {
818+
CustomKeywordField: ['keyword-two'],
819+
// Add a new search attribute.
820+
CustomDoubleField: [1.5],
821+
},
822+
typedSearchAttributes: [
823+
{ key: defineSearchAttributeKey('CustomIntField', SearchAttributeType.INT), value: 2 },
824+
// Add a new typed search attribute.
825+
{ key: defineSearchAttributeKey('CustomTextField', SearchAttributeType.TEXT), value: 'new-text' },
826+
],
827+
}));
828+
829+
let desc = await waitForAttributeChange(handle, 'CustomTextField', true);
830+
// eslint-disable-next-line deprecation/deprecation
831+
t.deepEqual(desc.searchAttributes, {
832+
CustomKeywordField: ['keyword-two'],
833+
CustomIntField: [2],
834+
CustomDoubleField: [1.5],
835+
CustomTextField: ['new-text'],
836+
});
837+
t.deepEqual(
838+
desc.typedSearchAttributes,
839+
new TypedSearchAttributes([
840+
{ key: defineSearchAttributeKey('CustomIntField', SearchAttributeType.INT), value: 2 },
841+
{ key: defineSearchAttributeKey('CustomKeywordField', SearchAttributeType.KEYWORD), value: 'keyword-two' },
842+
{ key: defineSearchAttributeKey('CustomTextField', SearchAttributeType.TEXT), value: 'new-text' },
843+
{ key: defineSearchAttributeKey('CustomDoubleField', SearchAttributeType.DOUBLE), value: 1.5 },
844+
])
845+
);
846+
847+
// Update and remove some search attributes. We remove a search attribute by omitting an existing key from the update.
848+
await handle.update((desc) => ({
849+
...desc,
850+
searchAttributes: {
851+
CustomKeywordField: ['keyword-three'],
852+
},
853+
typedSearchAttributes: [{ key: defineSearchAttributeKey('CustomIntField', SearchAttributeType.INT), value: 3 }],
854+
}));
855+
856+
desc = await waitForAttributeChange(handle, 'CustomTextField', false);
857+
// eslint-disable-next-line deprecation/deprecation
858+
t.deepEqual(desc.searchAttributes, {
859+
CustomKeywordField: ['keyword-three'],
860+
CustomIntField: [3],
861+
});
862+
t.deepEqual(
863+
desc.typedSearchAttributes,
864+
new TypedSearchAttributes([
865+
{ key: defineSearchAttributeKey('CustomIntField', SearchAttributeType.INT), value: 3 },
866+
{ key: defineSearchAttributeKey('CustomKeywordField', SearchAttributeType.KEYWORD), value: 'keyword-three' },
867+
])
868+
);
869+
870+
// Remove all search attributes.
871+
await handle.update((desc) => ({
872+
...desc,
873+
searchAttributes: {},
874+
typedSearchAttributes: [],
875+
}));
876+
877+
desc = await waitForAttributeChange(handle, 'CustomIntField', false);
878+
t.deepEqual(desc.searchAttributes, {}); // eslint-disable-line deprecation/deprecation
879+
t.deepEqual(desc.typedSearchAttributes, new TypedSearchAttributes([]));
880+
} finally {
881+
await handle.delete();
882+
}
883+
});
754884
}

0 commit comments

Comments
 (0)