@@ -10,10 +10,16 @@ import {
1010 ScheduleHandle ,
1111 ScheduleSummary ,
1212 ScheduleUpdateOptions ,
13+ ScheduleDescription ,
1314} from '@temporalio/client' ;
1415import { 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' ;
1723import { defaultSAKeys } from './helpers-integration' ;
1824
1925export 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