@@ -10,10 +10,17 @@ import {
1010 ScheduleHandle ,
1111 ScheduleSummary ,
1212 ScheduleUpdateOptions ,
13+ ScheduleDescription ,
1314} from '@temporalio/client' ;
1415import { msToNumber } from '@temporalio/common/lib/time' ;
15- import { SearchAttributes , SearchAttributeType , 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' ;
23+ import { defaultSAKeys } from './helpers-integration' ;
1724
1825export interface Context {
1926 client : Client ;
@@ -168,9 +175,7 @@ if (RUN_INTEGRATION_TESTS) {
168175 searchAttributes : {
169176 CustomKeywordField : [ 'test-value2' ] ,
170177 } ,
171- typedSearchAttributes : new TypedSearchAttributes ( [
172- { key : { name : 'CustomIntField' , type : SearchAttributeType . INT } , value : 42 } ,
173- ] ) ,
178+ typedSearchAttributes : new TypedSearchAttributes ( [ { key : defaultSAKeys . CustomIntField , value : 42 } ] ) ,
174179 } ,
175180 } ) ;
176181
@@ -188,8 +193,8 @@ if (RUN_INTEGRATION_TESTS) {
188193 t . deepEqual (
189194 describedSchedule . action . typedSearchAttributes ,
190195 new TypedSearchAttributes ( [
191- { key : { name : 'CustomIntField' , type : SearchAttributeType . INT } , value : 42 } ,
192- { key : { name : 'CustomKeywordField' , type : SearchAttributeType . KEYWORD } , value : 'test-value2' } ,
196+ { key : defaultSAKeys . CustomIntField , value : 42 } ,
197+ { key : defaultSAKeys . CustomKeywordField , value : 'test-value2' } ,
193198 ] )
194199 ) ;
195200 } finally {
@@ -216,9 +221,7 @@ if (RUN_INTEGRATION_TESTS) {
216221 searchAttributes : {
217222 CustomKeywordField : [ 'test-value2' ] ,
218223 } ,
219- typedSearchAttributes : new TypedSearchAttributes ( [
220- { key : { name : 'CustomIntField' , type : SearchAttributeType . INT } , value : 42 } ,
221- ] ) ,
224+ typedSearchAttributes : new TypedSearchAttributes ( [ { key : defaultSAKeys . CustomIntField , value : 42 } ] ) ,
222225 } ,
223226 } ) ;
224227
@@ -237,8 +240,8 @@ if (RUN_INTEGRATION_TESTS) {
237240 t . deepEqual (
238241 describedSchedule . action . typedSearchAttributes ,
239242 new TypedSearchAttributes ( [
240- { key : { name : 'CustomIntField' , type : SearchAttributeType . INT } , value : 42 } ,
241- { key : { name : 'CustomKeywordField' , type : SearchAttributeType . KEYWORD } , value : 'test-value2' } ,
243+ { key : defaultSAKeys . CustomIntField , value : 42 } ,
244+ { key : defaultSAKeys . CustomKeywordField , value : 'test-value2' } ,
242245 ] )
243246 ) ;
244247 } finally {
@@ -351,9 +354,7 @@ if (RUN_INTEGRATION_TESTS) {
351354 searchAttributes : {
352355 CustomKeywordField : [ 'test-value2' ] ,
353356 } ,
354- typedSearchAttributes : new TypedSearchAttributes ( [
355- { key : { name : 'CustomIntField' , type : SearchAttributeType . INT } , value : 42 } ,
356- ] ) ,
357+ typedSearchAttributes : new TypedSearchAttributes ( [ { key : defaultSAKeys . CustomIntField , value : 42 } ] ) ,
357358 } ,
358359 } ) ;
359360
@@ -598,9 +599,7 @@ if (RUN_INTEGRATION_TESTS) {
598599 taskQueue,
599600 } ,
600601 searchAttributes,
601- typedSearchAttributes : new TypedSearchAttributes ( [
602- { key : { name : 'CustomIntField' , type : SearchAttributeType . INT } , value : 42 } ,
603- ] ) ,
602+ typedSearchAttributes : new TypedSearchAttributes ( [ { key : defaultSAKeys . CustomIntField , value : 42 } ] ) ,
604603 } )
605604 ) ;
606605 }
@@ -759,6 +758,131 @@ if (RUN_INTEGRATION_TESTS) {
759758 }
760759 } ) ;
761760
761+ = === ===
762+ test . serial ( 'Can update search attributes of a schedule' , async ( t ) => {
763+ const { client } = t . context ;
764+ const scheduleId = `can-update-search-attributes-of-schedule-${ randomUUID ( ) } ` ;
765+
766+ // Helper to wait for search attribute changes to propagate.
767+ const waitForAttributeChange = async (
768+ handle : ScheduleHandle ,
769+ attributeName : string ,
770+ shouldExist : boolean
771+ ) : Promise < ScheduleDescription > => {
772+ await waitUntil ( async ( ) => {
773+ const desc = await handle . describe ( ) ;
774+ const exists =
775+ desc . typedSearchAttributes . getAll ( ) . find ( ( pair ) => pair . key . name === attributeName ) !== undefined ;
776+ return exists === shouldExist ;
777+ } , 300 ) ;
778+ return await handle . describe ( ) ;
779+ } ;
780+
781+ // Create a schedule with search attributes.
782+ const handle = await client . schedule . create ( {
783+ scheduleId,
784+ spec : {
785+ calendars : [ { hour : { start : 2 , end : 7 , step : 1 } } ] ,
786+ } ,
787+ action : {
788+ type : 'startWorkflow' ,
789+ workflowType : dummyWorkflow ,
790+ taskQueue,
791+ } ,
792+ searchAttributes : {
793+ CustomKeywordField : [ 'keyword-one' ] ,
794+ } ,
795+ typedSearchAttributes : [ { key : defineSearchAttributeKey ( 'CustomIntField' , SearchAttributeType . INT ) , value : 1 } ] ,
796+ } ) ;
797+
798+ // Check the search attributes are part of the schedule description.
799+ const desc = await handle . describe ( ) ;
800+ // eslint-disable-next-line deprecation/deprecation
801+ t . deepEqual ( desc . searchAttributes , {
802+ CustomKeywordField : [ 'keyword-one' ] ,
803+ CustomIntField : [ 1 ] ,
804+ } ) ;
805+ t . deepEqual (
806+ desc . typedSearchAttributes ,
807+ new TypedSearchAttributes ( [
808+ { key : defineSearchAttributeKey ( 'CustomIntField' , SearchAttributeType . INT ) , value : 1 } ,
809+ { key : defineSearchAttributeKey ( 'CustomKeywordField' , SearchAttributeType . KEYWORD ) , value : 'keyword-one' } ,
810+ ] )
811+ ) ;
812+
813+ // Perform a series of updates to schedule's search attributes.
814+ try {
815+ // Update existing search attributes, add new ones.
816+ await handle . update ( ( desc ) => ( {
817+ ...desc ,
818+ searchAttributes : {
819+ CustomKeywordField : [ 'keyword-two' ] ,
820+ // Add a new search attribute.
821+ CustomDoubleField : [ 1.5 ] ,
822+ } ,
823+ typedSearchAttributes : [
824+ { key : defineSearchAttributeKey ( 'CustomIntField' , SearchAttributeType . INT ) , value : 2 } ,
825+ // Add a new typed search attribute.
826+ { key : defineSearchAttributeKey ( 'CustomTextField' , SearchAttributeType . TEXT ) , value : 'new-text' } ,
827+ ] ,
828+ } ) ) ;
829+
830+ let desc = await waitForAttributeChange ( handle , 'CustomTextField' , true ) ;
831+ // eslint-disable-next-line deprecation/deprecation
832+ t . deepEqual ( desc . searchAttributes , {
833+ CustomKeywordField : [ 'keyword-two' ] ,
834+ CustomIntField : [ 2 ] ,
835+ CustomDoubleField : [ 1.5 ] ,
836+ CustomTextField : [ 'new-text' ] ,
837+ } ) ;
838+ t . deepEqual (
839+ desc . typedSearchAttributes ,
840+ new TypedSearchAttributes ( [
841+ { key : defineSearchAttributeKey ( 'CustomIntField' , SearchAttributeType . INT ) , value : 2 } ,
842+ { key : defineSearchAttributeKey ( 'CustomKeywordField' , SearchAttributeType . KEYWORD ) , value : 'keyword-two' } ,
843+ { key : defineSearchAttributeKey ( 'CustomTextField' , SearchAttributeType . TEXT ) , value : 'new-text' } ,
844+ { key : defineSearchAttributeKey ( 'CustomDoubleField' , SearchAttributeType . DOUBLE ) , value : 1.5 } ,
845+ ] )
846+ ) ;
847+
848+ // Update and remove some search attributes. We remove a search attribute by omitting an existing key from the update.
849+ await handle . update ( ( desc ) => ( {
850+ ...desc ,
851+ searchAttributes : {
852+ CustomKeywordField : [ 'keyword-three' ] ,
853+ } ,
854+ typedSearchAttributes : [ { key : defineSearchAttributeKey ( 'CustomIntField' , SearchAttributeType . INT ) , value : 3 } ] ,
855+ } ) ) ;
856+
857+ desc = await waitForAttributeChange ( handle , 'CustomTextField' , false ) ;
858+ // eslint-disable-next-line deprecation/deprecation
859+ t . deepEqual ( desc . searchAttributes , {
860+ CustomKeywordField : [ 'keyword-three' ] ,
861+ CustomIntField : [ 3 ] ,
862+ } ) ;
863+ t . deepEqual (
864+ desc . typedSearchAttributes ,
865+ new TypedSearchAttributes ( [
866+ { key : defineSearchAttributeKey ( 'CustomIntField' , SearchAttributeType . INT ) , value : 3 } ,
867+ { key : defineSearchAttributeKey ( 'CustomKeywordField' , SearchAttributeType . KEYWORD ) , value : 'keyword-three' } ,
868+ ] )
869+ ) ;
870+
871+ // Remove all search attributes.
872+ await handle . update ( ( desc ) => ( {
873+ ...desc ,
874+ searchAttributes : { } ,
875+ typedSearchAttributes : [ ] ,
876+ } ) ) ;
877+
878+ desc = await waitForAttributeChange ( handle , 'CustomIntField' , false ) ;
879+ t . deepEqual ( desc . searchAttributes , { } ) ; // eslint-disable-line deprecation/deprecation
880+ t . deepEqual ( desc . typedSearchAttributes , new TypedSearchAttributes ( [ ] ) ) ;
881+ } finally {
882+ await handle . delete ( ) ;
883+ }
884+ } ) ;
885+
762886 test . serial ( 'User metadata on schedule' , async ( t ) => {
763887 const { client } = t . context ;
764888 const scheduleId = `schedule-with-user-metadata-${ randomUUID ( ) } ` ;
@@ -769,7 +893,7 @@ if (RUN_INTEGRATION_TESTS) {
769893 type : 'startWorkflow' ,
770894 workflowType : dummyWorkflow ,
771895 taskQueue,
772- staticSummary : 'schedule static summary' ,
896+ staticSummary : 'schedule static summary' ,
773897 staticDetails : 'schedule static details' ,
774898 } ,
775899 } ) ;
0 commit comments