Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nimbus): Enrollment and complete days #11711

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion experimenter/experimenter/experiments/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,8 @@ class EmailType(models.TextChoices):
WARNING_PREF_FLIPS_PREF_CONTROLLED_BY_FEATURE = (
"Pref '{pref}' is controlled by a variable in feature {feature_config_slug}'"
)
ENROLLMENT_END = "Enrollment End"
OBSERVATION = "Observation"
ENROLLMENT = "Enrollment"


RISK_QUESTIONS = {
Expand Down
42 changes: 34 additions & 8 deletions experimenter/experimenter/experiments/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -628,24 +628,34 @@ def treatment_branches(self):

@property
def is_draft(self):
return self.status == self.Status.DRAFT
return (
self.status == self.Status.DRAFT
and self.publish_status == self.PublishStatus.IDLE
)

@property
def is_review(self):
return self.is_draft and self.publish_status == self.PublishStatus.REVIEW
return self.status == self.Status.DRAFT and self.publish_status in [
self.PublishStatus.REVIEW,
self.PublishStatus.WAITING,
]

@property
def is_preview(self):
return self.status == self.Status.PREVIEW

@property
def is_live(self):
return self.status == self.Status.LIVE
def is_enrollment(self):
return self.status == self.Status.LIVE and not self.is_paused_published

@property
def is_complete(self):
return self.status == self.Status.COMPLETE

@property
def is_observation(self):
return self._enrollment_end_date is not None and not self.is_complete

@property
def is_started(self):
return self.status in (self.Status.LIVE, self.Status.COMPLETE)
Expand Down Expand Up @@ -806,41 +816,57 @@ def computed_duration_days(self):
return (self.computed_end_date - self.enrollment_start_date).days
return self.proposed_duration

@property
def computed_observations_days(self):
if (
enrollment_end_date := (
self.actual_enrollment_end_date or self.computed_enrollment_end_date
)
) and self.computed_end_date:
return (self.computed_end_date - enrollment_end_date).days
return None

def timeline(self):
timeline_entries = [
{
"label": self.Status.DRAFT,
"date": self.draft_date,
"is_active": self.is_draft,
"days": None,
},
{
"label": self.Status.PREVIEW,
"date": self.preview_date,
"is_active": self.is_preview,
"days": None,
},
{
"label": self.PublishStatus.REVIEW,
"date": self.review_date,
"is_active": self.is_review,
"days": None,
},
{
"label": self.Status.LIVE,
"label": NimbusConstants.ENROLLMENT,
"date": self.start_date,
"is_active": self.is_live,
"is_active": self.is_enrollment,
"days": self.computed_enrollment_days,
},
{
"label": self.Status.COMPLETE,
"date": self.computed_end_date,
"is_active": self.is_complete,
"days": self.computed_duration_days,
},
]
if not self.is_rollout:
timeline_entries.insert(
4,
{
"label": NimbusConstants.ENROLLMENT_END,
"label": NimbusConstants.OBSERVATION,
"date": self._enrollment_end_date,
"is_active": self._enrollment_end_date is not None,
"is_active": self.is_observation,
"days": self.computed_observations_days,
},
)

Expand Down
28 changes: 22 additions & 6 deletions experimenter/experimenter/experiments/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1689,7 +1689,11 @@ def test_review_date_returns_none_if_no_review_status(self):

def test_timeline_dates_includes_correct_status_dates_and_flags(self):
experiment = NimbusExperimentFactory.create_with_lifecycle(
lifecycle=NimbusExperimentFactory.Lifecycles.LIVE_APPROVE,
lifecycle=NimbusExperimentFactory.Lifecycles.ENDING_APPROVE_APPROVE,
proposed_enrollment=2,
start_date=datetime.date(2023, 1, 4),
_enrollment_end_date=datetime.date(2023, 1, 6),
end_date=datetime.date(2023, 1, 8),
)
NimbusChangeLogFactory.create(
experiment=experiment,
Expand All @@ -1701,48 +1705,60 @@ def test_timeline_dates_includes_correct_status_dates_and_flags(self):
experiment=experiment,
old_status=NimbusExperiment.Status.DRAFT,
new_status=NimbusExperiment.Status.PREVIEW,
changed_on=datetime.datetime(2023, 3, 1),
changed_on=datetime.datetime(2023, 1, 2),
)

NimbusChangeLogFactory.create(
experiment=experiment,
old_publish_status=NimbusExperiment.Status.PREVIEW,
new_publish_status=NimbusExperiment.PublishStatus.REVIEW,
changed_on=datetime.datetime(2023, 4, 1),
changed_on=datetime.datetime(2023, 1, 3),
)
timeline = experiment.timeline()
expected_timeline = [
{
"label": "Draft",
"date": experiment.draft_date,
"is_active": False,
"days": None,
},
{
"label": "Preview",
"date": experiment.preview_date,
"is_active": False,
"days": None,
},
{
"label": "Review",
"date": experiment.review_date,
"is_active": False,
"days": None,
},
{"label": "Live", "date": experiment.start_date, "is_active": True},
{
"label": NimbusConstants.ENROLLMENT_END,
"label": NimbusConstants.ENROLLMENT,
"date": experiment.start_date,
"is_active": False,
"days": 2,
},
{
"label": NimbusConstants.OBSERVATION,
"date": experiment._enrollment_end_date,
"is_active": False,
"days": 2,
},
{
"label": "Complete",
"date": experiment.computed_end_date,
"is_active": False,
"is_active": True,
"days": 4,
},
]

for i, expected in enumerate(expected_timeline):
self.assertEqual(timeline[i]["label"], expected["label"])
self.assertEqual(timeline[i]["date"], expected["date"])
self.assertEqual(timeline[i]["is_active"], expected["is_active"])
self.assertEqual(timeline[i].get("days"), expected["days"])

def test_timeline_dates_complete_is_active_when_status_is_complete(self):
experiment = NimbusExperimentFactory.create_with_lifecycle(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<li class="list-group-item flex-fill text-center d-flex flex-column justify-content-center {% if status.is_active %}bg-primary text-white{% endif %}">
<strong>{{ status.label }}</strong>
<small>{{ status.date|default:'---' }}</small>
{% if status.days is not None %}<small>{{ status.days }} day{{ status.days|pluralize }}</small>{% endif %}
</li>
{% endfor %}
</ul>
Expand Down