@@ -545,13 +545,27 @@ class Meta:
545545
546546 class Filters :
547547 IS_LAUNCH_QUEUED = Q (
548- status = NimbusConstants .Status .DRAFT ,
549- status_next = NimbusConstants .Status .LIVE ,
548+ Q (
549+ status = NimbusConstants .Status .DRAFT ,
550+ status_next = NimbusConstants .Status .LIVE ,
551+ )
552+ | Q (
553+ is_rollout = True ,
554+ status = NimbusConstants .Status .DISABLED ,
555+ status_next = NimbusConstants .Status .LIVE ,
556+ ),
550557 publish_status = NimbusConstants .PublishStatus .APPROVED ,
551558 )
552559 IS_LAUNCHING = Q (
553- status = NimbusConstants .Status .DRAFT ,
554- status_next = NimbusConstants .Status .LIVE ,
560+ Q (
561+ status = NimbusConstants .Status .DRAFT ,
562+ status_next = NimbusConstants .Status .LIVE ,
563+ )
564+ | Q (
565+ is_rollout = True ,
566+ status = NimbusConstants .Status .DISABLED ,
567+ status_next = NimbusConstants .Status .LIVE ,
568+ ),
555569 publish_status = NimbusConstants .PublishStatus .WAITING ,
556570 )
557571 IS_UPDATE_QUEUED = Q (
@@ -565,13 +579,27 @@ class Filters:
565579 publish_status = NimbusConstants .PublishStatus .WAITING ,
566580 )
567581 IS_END_QUEUED = Q (
568- status = NimbusConstants .Status .LIVE ,
569- status_next = NimbusConstants .Status .COMPLETE ,
582+ Q (
583+ status = NimbusConstants .Status .LIVE ,
584+ status_next = NimbusConstants .Status .COMPLETE ,
585+ )
586+ | Q (
587+ is_rollout = True ,
588+ status = NimbusConstants .Status .LIVE ,
589+ status_next = NimbusConstants .Status .DISABLED ,
590+ ),
570591 publish_status = NimbusConstants .PublishStatus .APPROVED ,
571592 )
572593 IS_ENDING = Q (
573- status = NimbusConstants .Status .LIVE ,
574- status_next = NimbusConstants .Status .COMPLETE ,
594+ Q (
595+ status = NimbusConstants .Status .LIVE ,
596+ status_next = NimbusConstants .Status .COMPLETE ,
597+ )
598+ | Q (
599+ is_rollout = True ,
600+ status = NimbusConstants .Status .LIVE ,
601+ status_next = NimbusConstants .Status .DISABLED ,
602+ ),
575603 publish_status = NimbusConstants .PublishStatus .WAITING ,
576604 )
577605 SHOULD_ALLOCATE_BUCKETS = Q (
@@ -1285,7 +1313,10 @@ def advance_rollout_phase(self):
12851313 today = timezone .now ().date ()
12861314 phase_ids = [phase .id for phase in phases ]
12871315
1288- if self .rollout_phase_id is None :
1316+ if self .rollout_phase_next_id is not None :
1317+ current_phase = self .rollout_phase
1318+ next_phase = self .rollout_phase_next
1319+ elif self .rollout_phase_id is None :
12891320 current_phase = None
12901321 next_phase = phases [0 ]
12911322 else :
@@ -1297,7 +1328,9 @@ def advance_rollout_phase(self):
12971328 if next_phase is not None and not next_phase .population_percent :
12981329 return
12991330
1300- if current_phase is not None :
1331+ if current_phase is not None and (
1332+ current_phase .end_date is None or current_phase .end_date > today
1333+ ):
13011334 current_phase .end_date = today
13021335 if current_phase .actual_start_date :
13031336 current_phase .start_date = current_phase .actual_start_date
@@ -1312,8 +1345,52 @@ def advance_rollout_phase(self):
13121345 next_phase .save ()
13131346 self .rollout_phase = next_phase
13141347 self .rollout_phase_next = None
1348+ self .population_percent = next_phase .population_percent
13151349 self .save ()
13161350
1351+ def stage_rollout_phase_advance (self , copy_current_if_missing = False ):
1352+ phases = list (self .rollout_phases .all ())
1353+ if not phases :
1354+ return None
1355+
1356+ current_phase = self .rollout_phase
1357+ if self .rollout_phase_next_id is not None :
1358+ next_phase = self .rollout_phase_next
1359+ elif self .rollout_phase_id is None :
1360+ next_phase = phases [0 ]
1361+ else :
1362+ phase_ids = [phase .id for phase in phases ]
1363+ current_index = phase_ids .index (self .rollout_phase_id )
1364+ next_index = current_index + 1
1365+ next_phase = phases [next_index ] if next_index < len (phases ) else None
1366+
1367+ if next_phase is None and copy_current_if_missing and current_phase is not None :
1368+ next_phase = self .rollout_phases .create (
1369+ population_percent = current_phase .population_percent
1370+ )
1371+
1372+ if next_phase is None or not next_phase .population_percent :
1373+ return None
1374+
1375+ self .rollout_phase_next = next_phase
1376+ self .population_percent = next_phase .population_percent
1377+ self .save (update_fields = ["rollout_phase_next" , "population_percent" ])
1378+ return next_phase
1379+
1380+ def end_current_rollout_phase (self ):
1381+ current_phase = self .rollout_phase
1382+ if current_phase is None :
1383+ return
1384+
1385+ today = timezone .now ().date ()
1386+ if current_phase .end_date is not None and current_phase .end_date <= today :
1387+ return
1388+
1389+ current_phase .end_date = today
1390+ if current_phase .actual_start_date :
1391+ current_phase .start_date = current_phase .actual_start_date
1392+ current_phase .save ()
1393+
13171394 @property
13181395 def is_missing_takeaway_info (self ):
13191396 return (
@@ -3457,6 +3534,7 @@ class Messages:
34573534 REJECTED_FROM_KINTO = "Rejected from Remote Settings"
34583535 LIVE = "Experiment is live"
34593536 COMPLETED = "Experiment is complete"
3537+ DISABLED = "Rollout is disabled"
34603538 RESULTS_UPDATED = "Experiment results updated"
34613539 MONITORING_DATA_UPDATED = "Experiment monitoring data updated"
34623540 HOLDBACK_ENROLLMENT_UPDATED = "Holdback enrollment period updated"
0 commit comments