diff --git a/experimenter/experimenter/experiments/models.py b/experimenter/experimenter/experiments/models.py index 6e75a062fd..c10434c4b6 100644 --- a/experimenter/experimenter/experiments/models.py +++ b/experimenter/experimenter/experiments/models.py @@ -730,6 +730,28 @@ def should_show_remote_settings_pending(self, reviewer): self.PublishStatus.WAITING, ) and self.can_review(reviewer) + @property + def rejection_block(self): + rejection = self.changes.latest_rejection() + if not rejection: + return None + + flow_key = None + if rejection.old_status == self.Status.DRAFT: + flow_key = "LAUNCH_EXPERIMENT" + elif rejection.old_status == self.Status.LIVE: + if rejection.old_status_next == self.Status.LIVE: + flow_key = "END_ENROLLMENT" + else: + flow_key = "END_EXPERIMENT" + + return { + "action": NimbusUIConstants.REVIEW_REQUEST_MESSAGES[flow_key], + "email": rejection.changed_by.email, + "date": rejection.changed_on, + "message": rejection.message, + } + def review_messages(self): if self.status_next == self.Status.COMPLETE: return NimbusUIConstants.REVIEW_REQUEST_MESSAGES["END_EXPERIMENT"] diff --git a/experimenter/experimenter/experiments/tests/test_models.py b/experimenter/experimenter/experiments/tests/test_models.py index 2a8296a5a6..14ba5db56d 100644 --- a/experimenter/experimenter/experiments/tests/test_models.py +++ b/experimenter/experimenter/experiments/tests/test_models.py @@ -3758,6 +3758,57 @@ def test_review_messages_and_action_type(self, lifecycle, expected_message): experiment = NimbusExperimentFactory.create_with_lifecycle(lifecycle) self.assertEqual(experiment.review_messages(), expected_message) + @parameterized.expand( + [ + ( + NimbusExperiment.Status.DRAFT, + None, + "LAUNCH_EXPERIMENT", + ), + ( + NimbusExperiment.Status.LIVE, + NimbusExperiment.Status.LIVE, + "END_ENROLLMENT", + ), + ( + NimbusExperiment.Status.LIVE, + NimbusExperiment.Status.COMPLETE, + "END_EXPERIMENT", + ), + ] + ) + def test_rejection_block_from_rejection_changelog( + self, + status, + status_next, + expected_flow_key, + ): + experiment = NimbusExperimentFactory.create_with_lifecycle( + NimbusExperimentFactory.Lifecycles.CREATED + ) + + experiment.status = status + experiment.status_next = status_next + + for publish_status in ( + NimbusExperiment.PublishStatus.REVIEW, + NimbusExperiment.PublishStatus.IDLE, + ): + experiment.publish_status = publish_status + experiment.save() + generate_nimbus_changelog(experiment, experiment.owner, "test message") + + block = experiment.rejection_block + self.assertIsNotNone(block) + self.assertEqual( + block["action"], NimbusUIConstants.REVIEW_REQUEST_MESSAGES[expected_flow_key] + ) + self.assertEqual( + block["email"], experiment.changes.latest_rejection().changed_by.email + ) + self.assertEqual(block["date"], experiment.changes.latest_rejection().changed_on) + self.assertEqual(block["message"], "test message") + class TestNimbusBranch(TestCase): def test_str(self): diff --git a/experimenter/experimenter/nimbus_ui_new/forms.py b/experimenter/experimenter/nimbus_ui_new/forms.py index 25c9736d44..e9ef02a7ea 100644 --- a/experimenter/experimenter/nimbus_ui_new/forms.py +++ b/experimenter/experimenter/nimbus_ui_new/forms.py @@ -955,9 +955,21 @@ class ReviewToDraftForm(UpdateStatusForm): status = NimbusExperiment.Status.DRAFT status_next = NimbusExperiment.Status.DRAFT publish_status = NimbusExperiment.PublishStatus.IDLE + changelog_message = forms.CharField( + required=False, label="Changelog Message", max_length=1000 + ) + + cancel_message = forms.CharField( + required=False, label="Cancel Message", max_length=1000 + ) def get_changelog_message(self): - return f"{self.request.user} cancelled the review" + if self.cleaned_data.get("changelog_message"): + return ( + f"{self.request.user} rejected the review with reason: " + f"{self.cleaned_data['changelog_message']}" + ) + return f"{self.request.user} {self.cleaned_data['cancel_message']}" class ReviewToApproveForm(UpdateStatusForm): diff --git a/experimenter/experimenter/nimbus_ui_new/templates/nimbus_experiments/launch_controls.html b/experimenter/experimenter/nimbus_ui_new/templates/nimbus_experiments/launch_controls.html index f94434ea6a..9e03fc8902 100644 --- a/experimenter/experimenter/nimbus_ui_new/templates/nimbus_experiments/launch_controls.html +++ b/experimenter/experimenter/nimbus_ui_new/templates/nimbus_experiments/launch_controls.html @@ -1,6 +1,21 @@ {% load nimbus_extras %}
+ {% with rejection=experiment.rejection_block %} + {% if rejection %} +
+
+

+ The request to {{ rejection.action }} was Rejected due to: +

+

{{ rejection.email }} on {{ rejection.date|date:"F j, Y" }}:

+

{{ rejection.message }}

+
+
+ {% endif %} + {% endwith %}
{% csrf_token %} diff --git a/experimenter/experimenter/nimbus_ui_new/tests/test_forms.py b/experimenter/experimenter/nimbus_ui_new/tests/test_forms.py index ccb89ec8ea..5ba665a83c 100644 --- a/experimenter/experimenter/nimbus_ui_new/tests/test_forms.py +++ b/experimenter/experimenter/nimbus_ui_new/tests/test_forms.py @@ -672,13 +672,41 @@ def test_preview_to_draft_form(self): self.assertIn("moved the experiment back to Draft", changelog.message) self.mock_preview_task.assert_called_once_with(countdown=5) - def test_review_to_draft_form(self): + def test_review_to_draft_form_with_changelog_message(self): self.experiment.status = NimbusExperiment.Status.DRAFT self.experiment.status_next = NimbusExperiment.Status.LIVE self.experiment.publish_status = NimbusExperiment.PublishStatus.REVIEW self.experiment.save() - form = ReviewToDraftForm(data={}, instance=self.experiment, request=self.request) + form = ReviewToDraftForm( + data={"changelog_message": "Needs further updates."}, + instance=self.experiment, + request=self.request, + ) + self.assertTrue(form.is_valid(), form.errors) + + experiment = form.save() + self.assertEqual(experiment.status, NimbusExperiment.Status.DRAFT) + self.assertEqual(experiment.status_next, NimbusExperiment.Status.DRAFT) + self.assertEqual(experiment.publish_status, NimbusExperiment.PublishStatus.IDLE) + + changelog = experiment.changes.latest("changed_on") + self.assertEqual(changelog.changed_by, self.user) + self.assertIn( + "rejected the review with reason: Needs further updates.", changelog.message + ) + + def test_review_to_draft_form_with_cancel_message(self): + self.experiment.status = NimbusExperiment.Status.DRAFT + self.experiment.status_next = NimbusExperiment.Status.LIVE + self.experiment.publish_status = NimbusExperiment.PublishStatus.REVIEW + self.experiment.save() + + form = ReviewToDraftForm( + data={"cancel_message": "Review was withdrawn by the user."}, + instance=self.experiment, + request=self.request, + ) self.assertTrue(form.is_valid(), form.errors) experiment = form.save() @@ -688,7 +716,7 @@ def test_review_to_draft_form(self): changelog = experiment.changes.latest("changed_on") self.assertEqual(changelog.changed_by, self.user) - self.assertIn("cancelled the review", changelog.message) + self.assertIn(f"{self.user} Review was withdrawn by the user.", changelog.message) def test_review_to_approve_form(self): self.experiment.status = NimbusExperiment.Status.DRAFT