From 3d32de602043b55d563226a5173cddb96cc0060e Mon Sep 17 00:00:00 2001 From: yashikakhurana Date: Fri, 19 Dec 2025 09:48:54 -0800 Subject: [PATCH 1/5] feat(nimbus): Disable slack notifications toggle --- ...ment_disable_launch_slack_notifications.py | 18 +++ .../experimenter/experiments/models.py | 4 + .../experiments/tests/test_changelog_utils.py | 2 + experimenter/experimenter/nimbus_ui/forms.py | 27 +++- .../nimbus_experiments/experiment_base.html | 25 +++ .../nimbus_ui/tests/test_forms.py | 143 ++++++++++++++++++ .../nimbus_ui/tests/test_views.py | 37 +++++ experimenter/experimenter/nimbus_ui/urls.py | 6 + experimenter/experimenter/nimbus_ui/views.py | 12 ++ 9 files changed, 268 insertions(+), 6 deletions(-) create mode 100644 experimenter/experimenter/experiments/migrations/0308_nimbusexperiment_disable_launch_slack_notifications.py diff --git a/experimenter/experimenter/experiments/migrations/0308_nimbusexperiment_disable_launch_slack_notifications.py b/experimenter/experimenter/experiments/migrations/0308_nimbusexperiment_disable_launch_slack_notifications.py new file mode 100644 index 0000000000..8a930657a0 --- /dev/null +++ b/experimenter/experimenter/experiments/migrations/0308_nimbusexperiment_disable_launch_slack_notifications.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.9 on 2025-12-18 21:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('experiments', '0307_nimbusexperiment_next_steps_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='nimbusexperiment', + name='disable_launch_slack_notifications', + field=models.BooleanField(default=False, verbose_name='Disable Launch Slack Notifications'), + ), + ] diff --git a/experimenter/experimenter/experiments/models.py b/experimenter/experimenter/experiments/models.py index 9f267d35eb..9c7c67fdf3 100644 --- a/experimenter/experimenter/experiments/models.py +++ b/experimenter/experimenter/experiments/models.py @@ -427,6 +427,10 @@ class NimbusExperiment(NimbusConstants, TargetingConstants, FilterMixin, models. blank=True, verbose_name="Subscribers", ) + disable_launch_slack_notifications = models.BooleanField( + "Disable Launch Slack Notifications", + default=False, + ) use_group_id = models.BooleanField(default=True) objects = NimbusExperimentManager() is_firefox_labs_opt_in = models.BooleanField( diff --git a/experimenter/experimenter/experiments/tests/test_changelog_utils.py b/experimenter/experimenter/experiments/tests/test_changelog_utils.py index d3d9280152..636f053d0c 100644 --- a/experimenter/experimenter/experiments/tests/test_changelog_utils.py +++ b/experimenter/experimenter/experiments/tests/test_changelog_utils.py @@ -57,6 +57,7 @@ def test_outputs_expected_schema_for_empty_experiment(self): "channels": [], "conclusion_recommendations": [], "countries": [], + "disable_launch_slack_notifications": False, "equal_branch_ratio": experiment.equal_branch_ratio, "excluded_experiments": [], "exclude_countries": experiment.exclude_countries, @@ -191,6 +192,7 @@ def test_outputs_expected_schema_for_complete_experiment(self): "channel": experiment.channel, "channels": experiment.channels, "conclusion_recommendations": [], + "disable_launch_slack_notifications": False, "equal_branch_ratio": experiment.equal_branch_ratio, "excluded_experiments": [], "exclude_countries": experiment.exclude_countries, diff --git a/experimenter/experimenter/nimbus_ui/forms.py b/experimenter/experimenter/nimbus_ui/forms.py index a3109c8ba3..a131e46d2d 100644 --- a/experimenter/experimenter/nimbus_ui/forms.py +++ b/experimenter/experimenter/nimbus_ui/forms.py @@ -1329,6 +1329,20 @@ def save(self, commit=True): return self.instance +class ToggleLaunchSlackNotificationsForm(NimbusChangeLogFormMixin, forms.ModelForm): + class Meta: + model = NimbusExperiment + fields = ["disable_launch_slack_notifications"] + + def get_changelog_message(self): + status = ( + "disabled" + if self.cleaned_data.get("disable_launch_slack_notifications") + else "enabled" + ) + return f"{self.request.user} {status} launch Slack notifications" + + class SlackNotificationMixin: slack_action = None @@ -1336,12 +1350,13 @@ class SlackNotificationMixin: def save(self, commit=True): experiment = super().save(commit=commit) if self.slack_action: - nimbus_send_slack_notification.delay( - experiment_id=experiment.id, - email_addresses=experiment.notification_emails, - action_text=NimbusConstants.SLACK_FORM_ACTIONS[self.slack_action], - requesting_user_email=self.request.user.email, - ) + if not experiment.disable_launch_slack_notifications: + nimbus_send_slack_notification.delay( + experiment_id=experiment.id, + email_addresses=experiment.notification_emails, + action_text=NimbusConstants.SLACK_FORM_ACTIONS[self.slack_action], + requesting_user_email=self.request.user.email, + ) return experiment diff --git a/experimenter/experimenter/nimbus_ui/templates/nimbus_experiments/experiment_base.html b/experimenter/experimenter/nimbus_ui/templates/nimbus_experiments/experiment_base.html index 6a74f8a848..cb41142f48 100644 --- a/experimenter/experimenter/nimbus_ui/templates/nimbus_experiments/experiment_base.html +++ b/experimenter/experimenter/nimbus_ui/templates/nimbus_experiments/experiment_base.html @@ -36,6 +36,31 @@

{{ experiment.name }}

+
+
+ {% csrf_token %} + +
+ + +
+
+
{% if experiment.parent %}

Cloned from diff --git a/experimenter/experimenter/nimbus_ui/tests/test_forms.py b/experimenter/experimenter/nimbus_ui/tests/test_forms.py index 82bb3d6ce6..4734ed9146 100644 --- a/experimenter/experimenter/nimbus_ui/tests/test_forms.py +++ b/experimenter/experimenter/nimbus_ui/tests/test_forms.py @@ -80,6 +80,7 @@ TagFormSet, TakeawaysForm, ToggleArchiveForm, + ToggleLaunchSlackNotificationsForm, UnsubscribeForm, ) from experimenter.openidc.tests.factories import UserFactory @@ -404,6 +405,54 @@ def test_toggle_unarchive(self): ) +class TestToggleLaunchSlackNotificationsForm(RequestFormTestCase): + @parameterized.expand( + [ + ( + "disable", + False, + True, + "disabled", + ), + ( + "enable", + True, + False, + "enabled", + ), + ] + ) + def test_toggle_slack_notifications( + self, _name, initial_value, new_value, expected_status + ): + experiment = NimbusExperiment.objects.create( + owner=self.user, + name="Test Experiment", + slug="test-experiment", + disable_launch_slack_notifications=initial_value, + ) + + data = { + "disable_launch_slack_notifications": new_value, + } + + form = ToggleLaunchSlackNotificationsForm( + data, instance=experiment, request=self.request + ) + self.assertTrue(form.is_valid()) + + updated_experiment = form.save() + + self.assertEqual(updated_experiment.disable_launch_slack_notifications, new_value) + + changelog_message = form.get_changelog_message() + + self.assertEqual( + changelog_message, + f"{self.user} {expected_status} launch Slack notifications", + ) + + class TestQAStatusForm(RequestFormTestCase): def test_form_updates_qa_fields_and_creates_changelog(self): experiment = NimbusExperimentFactory.create_with_lifecycle( @@ -1382,6 +1431,100 @@ def test_approve_update_rollout_form(self): self.mock_preview_task.assert_called_once_with(countdown=5) self.mock_allocate_bucket_range.assert_called_once() + @parameterized.expand( + [ + ( + "draft_to_review_skips_slack_when_disabled", + DraftToReviewForm, + NimbusExperiment.Status.DRAFT, + {}, + True, + False, + ), + ( + "draft_to_review_sends_slack_when_enabled", + DraftToReviewForm, + NimbusExperiment.Status.DRAFT, + {}, + False, + True, + ), + ( + "live_to_update_rollout_skips_slack_when_disabled", + LiveToUpdateRolloutForm, + NimbusExperiment.Status.LIVE, + {"is_rollout": True}, + True, + False, + ), + ( + "live_to_update_rollout_sends_slack_when_enabled", + LiveToUpdateRolloutForm, + NimbusExperiment.Status.LIVE, + {"is_rollout": True}, + False, + True, + ), + ( + "live_to_end_enrollment_skips_slack_when_disabled", + LiveToEndEnrollmentForm, + NimbusExperiment.Status.LIVE, + {}, + True, + False, + ), + ( + "live_to_end_enrollment_sends_slack_when_enabled", + LiveToEndEnrollmentForm, + NimbusExperiment.Status.LIVE, + {}, + False, + True, + ), + ( + "live_to_complete_skips_slack_when_disabled", + LiveToCompleteForm, + NimbusExperiment.Status.LIVE, + {}, + True, + False, + ), + ( + "live_to_complete_sends_slack_when_enabled", + LiveToCompleteForm, + NimbusExperiment.Status.LIVE, + {}, + False, + True, + ), + ] + ) + def test_slack_notification_behavior( + self, + _name, + form_class, + status, + extra_kwargs, + disable_slack, + should_call_slack, + ): + experiment = NimbusExperimentFactory.create( + status=status, + status_next=None, + publish_status=NimbusExperiment.PublishStatus.IDLE, + disable_launch_slack_notifications=disable_slack, + **extra_kwargs, + ) + form = form_class(data={}, instance=experiment, request=self.request) + self.assertTrue(form.is_valid(), form.errors) + + form.save() + + if should_call_slack: + self.mock_slack_task.assert_called_once() + else: + self.mock_slack_task.assert_not_called() + class TestOverviewForm(RequestFormTestCase): def test_valid_form_saves(self): diff --git a/experimenter/experimenter/nimbus_ui/tests/test_views.py b/experimenter/experimenter/nimbus_ui/tests/test_views.py index e683f23337..4163ee1af2 100644 --- a/experimenter/experimenter/nimbus_ui/tests/test_views.py +++ b/experimenter/experimenter/nimbus_ui/tests/test_views.py @@ -1610,6 +1610,43 @@ def test_toggle_archive_status_to_unarchive(self): self.assertFalse(updated_experiment.is_archived) +class TestToggleLaunchSlackNotificationsView(AuthTestCase): + def setUp(self): + super().setUp() + self.experiment = NimbusExperiment.objects.create( + slug="test-experiment", + name="Test Experiment", + owner=self.user, + disable_launch_slack_notifications=False, + ) + + @parameterized.expand( + [ + ("disable", False, True), + ("enable", True, False), + ] + ) + def test_toggle_slack_notifications(self, _name, initial_value, new_value): + self.experiment.disable_launch_slack_notifications = initial_value + self.experiment.save() + + response = self.client.post( + reverse( + "nimbus-ui-toggle-launch-slack-notifications", + kwargs={"slug": self.experiment.slug}, + ), + {"disable_launch_slack_notifications": new_value}, + ) + self.assertEqual(response.status_code, 302) + self.assertEqual( + response.url, + reverse("nimbus-ui-detail", kwargs={"slug": self.experiment.slug}), + ) + + updated_experiment = NimbusExperiment.objects.get(slug=self.experiment.slug) + self.assertEqual(updated_experiment.disable_launch_slack_notifications, new_value) + + class TestOverviewUpdateView(AuthTestCase): def test_get_renders_for_draft_experiment(self): experiment = NimbusExperimentFactory.create_with_lifecycle( diff --git a/experimenter/experimenter/nimbus_ui/urls.py b/experimenter/experimenter/nimbus_ui/urls.py index d3d6c233f0..0a3b54422a 100644 --- a/experimenter/experimenter/nimbus_ui/urls.py +++ b/experimenter/experimenter/nimbus_ui/urls.py @@ -51,6 +51,7 @@ TagsManageView, TakeawaysUpdateView, ToggleArchiveView, + ToggleLaunchSlackNotificationsView, UnsubscribeView, ) @@ -200,6 +201,11 @@ FeatureUnsubscribeView.as_view(), name="nimbus-ui-feature-unsubscribe", ), + re_path( + r"^(?P[\w-]+)/toggle_launch_slack_notifications/", + ToggleLaunchSlackNotificationsView.as_view(), + name="nimbus-ui-toggle-launch-slack-notifications", + ), re_path( r"^(?P[\w-]+)/update_collaborators/", CollaboratorsUpdateView.as_view(), diff --git a/experimenter/experimenter/nimbus_ui/views.py b/experimenter/experimenter/nimbus_ui/views.py index c882ff06b7..2186dbb93f 100644 --- a/experimenter/experimenter/nimbus_ui/views.py +++ b/experimenter/experimenter/nimbus_ui/views.py @@ -73,6 +73,7 @@ TagFormSet, TakeawaysForm, ToggleArchiveForm, + ToggleLaunchSlackNotificationsForm, UnsubscribeForm, ) @@ -591,6 +592,17 @@ class UnsubscribeView( form_class = UnsubscribeForm +class ToggleLaunchSlackNotificationsView( + NimbusExperimentViewMixin, + RequestFormMixin, + UpdateView, +): + form_class = ToggleLaunchSlackNotificationsForm + + def get_success_url(self): + return reverse("nimbus-ui-detail", kwargs={"slug": self.object.slug}) + + class FeatureSubscribeView(FeatureSubscriberViewMixin): form_class = FeatureSubscribeForm url_name = "nimbus-ui-feature-subscribe" From 6b944a5b1f687b03f63e65ab8f5ce743ae049f59 Mon Sep 17 00:00:00 2001 From: yashikakhurana Date: Fri, 19 Dec 2025 10:51:55 -0800 Subject: [PATCH 2/5] feat(nimbus): Disable slack notifications toggle --- ...riment_disable_review_slack_notifications.py} | 6 +++--- experimenter/experimenter/experiments/models.py | 4 ++-- .../experiments/tests/test_changelog_utils.py | 4 ++-- experimenter/experimenter/nimbus_ui/forms.py | 10 +++++----- .../nimbus_experiments/experiment_base.html | 10 +++++----- .../experimenter/nimbus_ui/tests/test_forms.py | 16 ++++++++-------- .../experimenter/nimbus_ui/tests/test_views.py | 12 ++++++------ experimenter/experimenter/nimbus_ui/urls.py | 8 ++++---- experimenter/experimenter/nimbus_ui/views.py | 6 +++--- 9 files changed, 38 insertions(+), 38 deletions(-) rename experimenter/experimenter/experiments/migrations/{0308_nimbusexperiment_disable_launch_slack_notifications.py => 0308_nimbusexperiment_disable_review_slack_notifications.py} (71%) diff --git a/experimenter/experimenter/experiments/migrations/0308_nimbusexperiment_disable_launch_slack_notifications.py b/experimenter/experimenter/experiments/migrations/0308_nimbusexperiment_disable_review_slack_notifications.py similarity index 71% rename from experimenter/experimenter/experiments/migrations/0308_nimbusexperiment_disable_launch_slack_notifications.py rename to experimenter/experimenter/experiments/migrations/0308_nimbusexperiment_disable_review_slack_notifications.py index 8a930657a0..4ecab03c46 100644 --- a/experimenter/experimenter/experiments/migrations/0308_nimbusexperiment_disable_launch_slack_notifications.py +++ b/experimenter/experimenter/experiments/migrations/0308_nimbusexperiment_disable_review_slack_notifications.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.9 on 2025-12-18 21:51 +# Generated by Django 5.2.9 on 2025-12-19 18:18 from django.db import migrations, models @@ -12,7 +12,7 @@ class Migration(migrations.Migration): operations = [ migrations.AddField( model_name='nimbusexperiment', - name='disable_launch_slack_notifications', - field=models.BooleanField(default=False, verbose_name='Disable Launch Slack Notifications'), + name='disable_review_slack_notifications', + field=models.BooleanField(default=False, verbose_name='Disable Review Slack Notifications'), ), ] diff --git a/experimenter/experimenter/experiments/models.py b/experimenter/experimenter/experiments/models.py index 9c7c67fdf3..03ed6c4a27 100644 --- a/experimenter/experimenter/experiments/models.py +++ b/experimenter/experimenter/experiments/models.py @@ -427,8 +427,8 @@ class NimbusExperiment(NimbusConstants, TargetingConstants, FilterMixin, models. blank=True, verbose_name="Subscribers", ) - disable_launch_slack_notifications = models.BooleanField( - "Disable Launch Slack Notifications", + disable_review_slack_notifications = models.BooleanField( + "Disable Review Slack Notifications", default=False, ) use_group_id = models.BooleanField(default=True) diff --git a/experimenter/experimenter/experiments/tests/test_changelog_utils.py b/experimenter/experimenter/experiments/tests/test_changelog_utils.py index 636f053d0c..108d2bd3a9 100644 --- a/experimenter/experimenter/experiments/tests/test_changelog_utils.py +++ b/experimenter/experimenter/experiments/tests/test_changelog_utils.py @@ -57,7 +57,7 @@ def test_outputs_expected_schema_for_empty_experiment(self): "channels": [], "conclusion_recommendations": [], "countries": [], - "disable_launch_slack_notifications": False, + "disable_review_slack_notifications": False, "equal_branch_ratio": experiment.equal_branch_ratio, "excluded_experiments": [], "exclude_countries": experiment.exclude_countries, @@ -192,7 +192,7 @@ def test_outputs_expected_schema_for_complete_experiment(self): "channel": experiment.channel, "channels": experiment.channels, "conclusion_recommendations": [], - "disable_launch_slack_notifications": False, + "disable_review_slack_notifications": False, "equal_branch_ratio": experiment.equal_branch_ratio, "excluded_experiments": [], "exclude_countries": experiment.exclude_countries, diff --git a/experimenter/experimenter/nimbus_ui/forms.py b/experimenter/experimenter/nimbus_ui/forms.py index a131e46d2d..a737eccd4c 100644 --- a/experimenter/experimenter/nimbus_ui/forms.py +++ b/experimenter/experimenter/nimbus_ui/forms.py @@ -1329,18 +1329,18 @@ def save(self, commit=True): return self.instance -class ToggleLaunchSlackNotificationsForm(NimbusChangeLogFormMixin, forms.ModelForm): +class ToggleReviewSlackNotificationsForm(NimbusChangeLogFormMixin, forms.ModelForm): class Meta: model = NimbusExperiment - fields = ["disable_launch_slack_notifications"] + fields = ["disable_review_slack_notifications"] def get_changelog_message(self): status = ( "disabled" - if self.cleaned_data.get("disable_launch_slack_notifications") + if self.cleaned_data.get("disable_review_slack_notifications") else "enabled" ) - return f"{self.request.user} {status} launch Slack notifications" + return f"{self.request.user} {status} review Slack notifications" class SlackNotificationMixin: @@ -1350,7 +1350,7 @@ class SlackNotificationMixin: def save(self, commit=True): experiment = super().save(commit=commit) if self.slack_action: - if not experiment.disable_launch_slack_notifications: + if not experiment.disable_review_slack_notifications: nimbus_send_slack_notification.delay( experiment_id=experiment.id, email_addresses=experiment.notification_emails, diff --git a/experimenter/experimenter/nimbus_ui/templates/nimbus_experiments/experiment_base.html b/experimenter/experimenter/nimbus_ui/templates/nimbus_experiments/experiment_base.html index cb41142f48..1f5b5c0cc5 100644 --- a/experimenter/experimenter/nimbus_ui/templates/nimbus_experiments/experiment_base.html +++ b/experimenter/experimenter/nimbus_ui/templates/nimbus_experiments/experiment_base.html @@ -38,16 +38,16 @@

{{ experiment.name }}

{% csrf_token %} - +
{{ experiment.name }} class="form-check-label text-secondary small" style="cursor: pointer"> - Disable launch Slack notifications + Disable review Slack notifications
diff --git a/experimenter/experimenter/nimbus_ui/tests/test_forms.py b/experimenter/experimenter/nimbus_ui/tests/test_forms.py index 4734ed9146..366a1b3235 100644 --- a/experimenter/experimenter/nimbus_ui/tests/test_forms.py +++ b/experimenter/experimenter/nimbus_ui/tests/test_forms.py @@ -80,7 +80,7 @@ TagFormSet, TakeawaysForm, ToggleArchiveForm, - ToggleLaunchSlackNotificationsForm, + ToggleReviewSlackNotificationsForm, UnsubscribeForm, ) from experimenter.openidc.tests.factories import UserFactory @@ -405,7 +405,7 @@ def test_toggle_unarchive(self): ) -class TestToggleLaunchSlackNotificationsForm(RequestFormTestCase): +class TestToggleReviewSlackNotificationsForm(RequestFormTestCase): @parameterized.expand( [ ( @@ -429,27 +429,27 @@ def test_toggle_slack_notifications( owner=self.user, name="Test Experiment", slug="test-experiment", - disable_launch_slack_notifications=initial_value, + disable_review_slack_notifications=initial_value, ) data = { - "disable_launch_slack_notifications": new_value, + "disable_review_slack_notifications": new_value, } - form = ToggleLaunchSlackNotificationsForm( + form = ToggleReviewSlackNotificationsForm( data, instance=experiment, request=self.request ) self.assertTrue(form.is_valid()) updated_experiment = form.save() - self.assertEqual(updated_experiment.disable_launch_slack_notifications, new_value) + self.assertEqual(updated_experiment.disable_review_slack_notifications, new_value) changelog_message = form.get_changelog_message() self.assertEqual( changelog_message, - f"{self.user} {expected_status} launch Slack notifications", + f"{self.user} {expected_status} review Slack notifications", ) @@ -1512,7 +1512,7 @@ def test_slack_notification_behavior( status=status, status_next=None, publish_status=NimbusExperiment.PublishStatus.IDLE, - disable_launch_slack_notifications=disable_slack, + disable_review_slack_notifications=disable_slack, **extra_kwargs, ) form = form_class(data={}, instance=experiment, request=self.request) diff --git a/experimenter/experimenter/nimbus_ui/tests/test_views.py b/experimenter/experimenter/nimbus_ui/tests/test_views.py index 4163ee1af2..7c73bbe523 100644 --- a/experimenter/experimenter/nimbus_ui/tests/test_views.py +++ b/experimenter/experimenter/nimbus_ui/tests/test_views.py @@ -1610,14 +1610,14 @@ def test_toggle_archive_status_to_unarchive(self): self.assertFalse(updated_experiment.is_archived) -class TestToggleLaunchSlackNotificationsView(AuthTestCase): +class TestToggleReviewSlackNotificationsView(AuthTestCase): def setUp(self): super().setUp() self.experiment = NimbusExperiment.objects.create( slug="test-experiment", name="Test Experiment", owner=self.user, - disable_launch_slack_notifications=False, + disable_review_slack_notifications=False, ) @parameterized.expand( @@ -1627,15 +1627,15 @@ def setUp(self): ] ) def test_toggle_slack_notifications(self, _name, initial_value, new_value): - self.experiment.disable_launch_slack_notifications = initial_value + self.experiment.disable_review_slack_notifications = initial_value self.experiment.save() response = self.client.post( reverse( - "nimbus-ui-toggle-launch-slack-notifications", + "nimbus-ui-toggle-review-slack-notifications", kwargs={"slug": self.experiment.slug}, ), - {"disable_launch_slack_notifications": new_value}, + {"disable_review_slack_notifications": new_value}, ) self.assertEqual(response.status_code, 302) self.assertEqual( @@ -1644,7 +1644,7 @@ def test_toggle_slack_notifications(self, _name, initial_value, new_value): ) updated_experiment = NimbusExperiment.objects.get(slug=self.experiment.slug) - self.assertEqual(updated_experiment.disable_launch_slack_notifications, new_value) + self.assertEqual(updated_experiment.disable_review_slack_notifications, new_value) class TestOverviewUpdateView(AuthTestCase): diff --git a/experimenter/experimenter/nimbus_ui/urls.py b/experimenter/experimenter/nimbus_ui/urls.py index 0a3b54422a..8a3129ff5e 100644 --- a/experimenter/experimenter/nimbus_ui/urls.py +++ b/experimenter/experimenter/nimbus_ui/urls.py @@ -51,7 +51,7 @@ TagsManageView, TakeawaysUpdateView, ToggleArchiveView, - ToggleLaunchSlackNotificationsView, + ToggleReviewSlackNotificationsView, UnsubscribeView, ) @@ -202,9 +202,9 @@ name="nimbus-ui-feature-unsubscribe", ), re_path( - r"^(?P[\w-]+)/toggle_launch_slack_notifications/", - ToggleLaunchSlackNotificationsView.as_view(), - name="nimbus-ui-toggle-launch-slack-notifications", + r"^(?P[\w-]+)/toggle_review_slack_notifications/", + ToggleReviewSlackNotificationsView.as_view(), + name="nimbus-ui-toggle-review-slack-notifications", ), re_path( r"^(?P[\w-]+)/update_collaborators/", diff --git a/experimenter/experimenter/nimbus_ui/views.py b/experimenter/experimenter/nimbus_ui/views.py index 2186dbb93f..0828546fe0 100644 --- a/experimenter/experimenter/nimbus_ui/views.py +++ b/experimenter/experimenter/nimbus_ui/views.py @@ -73,7 +73,7 @@ TagFormSet, TakeawaysForm, ToggleArchiveForm, - ToggleLaunchSlackNotificationsForm, + ToggleReviewSlackNotificationsForm, UnsubscribeForm, ) @@ -592,12 +592,12 @@ class UnsubscribeView( form_class = UnsubscribeForm -class ToggleLaunchSlackNotificationsView( +class ToggleReviewSlackNotificationsView( NimbusExperimentViewMixin, RequestFormMixin, UpdateView, ): - form_class = ToggleLaunchSlackNotificationsForm + form_class = ToggleReviewSlackNotificationsForm def get_success_url(self): return reverse("nimbus-ui-detail", kwargs={"slug": self.object.slug}) From 8e46e7b1efd27b395789a1c92853481dd59b3296 Mon Sep 17 00:00:00 2001 From: yashikakhurana Date: Fri, 19 Dec 2025 11:35:59 -0800 Subject: [PATCH 3/5] feat(nimbus): Disable slack notifications toggle --- ...ble_review_slack_notifications_and_more.py | 22 ++++++++++++ .../experimenter/experiments/models.py | 6 ++-- .../experiments/tests/test_changelog_utils.py | 4 +-- experimenter/experimenter/nimbus_ui/forms.py | 10 +++--- .../nimbus_experiments/experiment_base.html | 10 +++--- .../nimbus_ui/tests/test_forms.py | 34 +++++++++---------- .../nimbus_ui/tests/test_views.py | 12 +++---- 7 files changed, 60 insertions(+), 38 deletions(-) create mode 100644 experimenter/experimenter/experiments/migrations/0309_remove_nimbusexperiment_disable_review_slack_notifications_and_more.py diff --git a/experimenter/experimenter/experiments/migrations/0309_remove_nimbusexperiment_disable_review_slack_notifications_and_more.py b/experimenter/experimenter/experiments/migrations/0309_remove_nimbusexperiment_disable_review_slack_notifications_and_more.py new file mode 100644 index 0000000000..5a00b76582 --- /dev/null +++ b/experimenter/experimenter/experiments/migrations/0309_remove_nimbusexperiment_disable_review_slack_notifications_and_more.py @@ -0,0 +1,22 @@ +# Generated by Django 5.2.9 on 2025-12-19 19:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('experiments', '0308_nimbusexperiment_disable_review_slack_notifications'), + ] + + operations = [ + migrations.RemoveField( + model_name='nimbusexperiment', + name='disable_review_slack_notifications', + ), + migrations.AddField( + model_name='nimbusexperiment', + name='enable_review_slack_notifications', + field=models.BooleanField(default=True, verbose_name='Enable Review Slack Notifications'), + ), + ] diff --git a/experimenter/experimenter/experiments/models.py b/experimenter/experimenter/experiments/models.py index 03ed6c4a27..eeed47d37f 100644 --- a/experimenter/experimenter/experiments/models.py +++ b/experimenter/experimenter/experiments/models.py @@ -427,9 +427,9 @@ class NimbusExperiment(NimbusConstants, TargetingConstants, FilterMixin, models. blank=True, verbose_name="Subscribers", ) - disable_review_slack_notifications = models.BooleanField( - "Disable Review Slack Notifications", - default=False, + enable_review_slack_notifications = models.BooleanField( + "Enable Review Slack Notifications", + default=True, ) use_group_id = models.BooleanField(default=True) objects = NimbusExperimentManager() diff --git a/experimenter/experimenter/experiments/tests/test_changelog_utils.py b/experimenter/experimenter/experiments/tests/test_changelog_utils.py index 108d2bd3a9..92bb683cfe 100644 --- a/experimenter/experimenter/experiments/tests/test_changelog_utils.py +++ b/experimenter/experimenter/experiments/tests/test_changelog_utils.py @@ -57,7 +57,7 @@ def test_outputs_expected_schema_for_empty_experiment(self): "channels": [], "conclusion_recommendations": [], "countries": [], - "disable_review_slack_notifications": False, + "enable_review_slack_notifications": True, "equal_branch_ratio": experiment.equal_branch_ratio, "excluded_experiments": [], "exclude_countries": experiment.exclude_countries, @@ -192,7 +192,7 @@ def test_outputs_expected_schema_for_complete_experiment(self): "channel": experiment.channel, "channels": experiment.channels, "conclusion_recommendations": [], - "disable_review_slack_notifications": False, + "enable_review_slack_notifications": True, "equal_branch_ratio": experiment.equal_branch_ratio, "excluded_experiments": [], "exclude_countries": experiment.exclude_countries, diff --git a/experimenter/experimenter/nimbus_ui/forms.py b/experimenter/experimenter/nimbus_ui/forms.py index a737eccd4c..5c7fd0b632 100644 --- a/experimenter/experimenter/nimbus_ui/forms.py +++ b/experimenter/experimenter/nimbus_ui/forms.py @@ -1332,13 +1332,13 @@ def save(self, commit=True): class ToggleReviewSlackNotificationsForm(NimbusChangeLogFormMixin, forms.ModelForm): class Meta: model = NimbusExperiment - fields = ["disable_review_slack_notifications"] + fields = ["enable_review_slack_notifications"] def get_changelog_message(self): status = ( - "disabled" - if self.cleaned_data.get("disable_review_slack_notifications") - else "enabled" + "enabled" + if self.cleaned_data.get("enable_review_slack_notifications") + else "disabled" ) return f"{self.request.user} {status} review Slack notifications" @@ -1350,7 +1350,7 @@ class SlackNotificationMixin: def save(self, commit=True): experiment = super().save(commit=commit) if self.slack_action: - if not experiment.disable_review_slack_notifications: + if experiment.enable_review_slack_notifications: nimbus_send_slack_notification.delay( experiment_id=experiment.id, email_addresses=experiment.notification_emails, diff --git a/experimenter/experimenter/nimbus_ui/templates/nimbus_experiments/experiment_base.html b/experimenter/experimenter/nimbus_ui/templates/nimbus_experiments/experiment_base.html index 1f5b5c0cc5..9362cc2c8e 100644 --- a/experimenter/experimenter/nimbus_ui/templates/nimbus_experiments/experiment_base.html +++ b/experimenter/experimenter/nimbus_ui/templates/nimbus_experiments/experiment_base.html @@ -41,13 +41,13 @@

{{ experiment.name }}

action="{% url 'nimbus-ui-toggle-review-slack-notifications' slug=experiment.slug %}" class="d-flex align-items-center"> {% csrf_token %} - +
{{ experiment.name }}
diff --git a/experimenter/experimenter/nimbus_ui/tests/test_forms.py b/experimenter/experimenter/nimbus_ui/tests/test_forms.py index 366a1b3235..d4bfd91e4d 100644 --- a/experimenter/experimenter/nimbus_ui/tests/test_forms.py +++ b/experimenter/experimenter/nimbus_ui/tests/test_forms.py @@ -409,16 +409,16 @@ class TestToggleReviewSlackNotificationsForm(RequestFormTestCase): @parameterized.expand( [ ( - "disable", + "enable", False, True, - "disabled", + "enabled", ), ( - "enable", + "disable", True, False, - "enabled", + "disabled", ), ] ) @@ -429,11 +429,11 @@ def test_toggle_slack_notifications( owner=self.user, name="Test Experiment", slug="test-experiment", - disable_review_slack_notifications=initial_value, + enable_review_slack_notifications=initial_value, ) data = { - "disable_review_slack_notifications": new_value, + "enable_review_slack_notifications": new_value, } form = ToggleReviewSlackNotificationsForm( @@ -443,7 +443,7 @@ def test_toggle_slack_notifications( updated_experiment = form.save() - self.assertEqual(updated_experiment.disable_review_slack_notifications, new_value) + self.assertEqual(updated_experiment.enable_review_slack_notifications, new_value) changelog_message = form.get_changelog_message() @@ -1438,7 +1438,7 @@ def test_approve_update_rollout_form(self): DraftToReviewForm, NimbusExperiment.Status.DRAFT, {}, - True, + False, False, ), ( @@ -1446,7 +1446,7 @@ def test_approve_update_rollout_form(self): DraftToReviewForm, NimbusExperiment.Status.DRAFT, {}, - False, + True, True, ), ( @@ -1454,7 +1454,7 @@ def test_approve_update_rollout_form(self): LiveToUpdateRolloutForm, NimbusExperiment.Status.LIVE, {"is_rollout": True}, - True, + False, False, ), ( @@ -1462,7 +1462,7 @@ def test_approve_update_rollout_form(self): LiveToUpdateRolloutForm, NimbusExperiment.Status.LIVE, {"is_rollout": True}, - False, + True, True, ), ( @@ -1470,7 +1470,7 @@ def test_approve_update_rollout_form(self): LiveToEndEnrollmentForm, NimbusExperiment.Status.LIVE, {}, - True, + False, False, ), ( @@ -1478,7 +1478,7 @@ def test_approve_update_rollout_form(self): LiveToEndEnrollmentForm, NimbusExperiment.Status.LIVE, {}, - False, + True, True, ), ( @@ -1486,7 +1486,7 @@ def test_approve_update_rollout_form(self): LiveToCompleteForm, NimbusExperiment.Status.LIVE, {}, - True, + False, False, ), ( @@ -1494,7 +1494,7 @@ def test_approve_update_rollout_form(self): LiveToCompleteForm, NimbusExperiment.Status.LIVE, {}, - False, + True, True, ), ] @@ -1505,14 +1505,14 @@ def test_slack_notification_behavior( form_class, status, extra_kwargs, - disable_slack, + enable_slack, should_call_slack, ): experiment = NimbusExperimentFactory.create( status=status, status_next=None, publish_status=NimbusExperiment.PublishStatus.IDLE, - disable_review_slack_notifications=disable_slack, + enable_review_slack_notifications=enable_slack, **extra_kwargs, ) form = form_class(data={}, instance=experiment, request=self.request) diff --git a/experimenter/experimenter/nimbus_ui/tests/test_views.py b/experimenter/experimenter/nimbus_ui/tests/test_views.py index 7c73bbe523..b99fa4f973 100644 --- a/experimenter/experimenter/nimbus_ui/tests/test_views.py +++ b/experimenter/experimenter/nimbus_ui/tests/test_views.py @@ -1617,17 +1617,17 @@ def setUp(self): slug="test-experiment", name="Test Experiment", owner=self.user, - disable_review_slack_notifications=False, + enable_review_slack_notifications=True, ) @parameterized.expand( [ - ("disable", False, True), - ("enable", True, False), + ("disable", True, False), + ("enable", False, True), ] ) def test_toggle_slack_notifications(self, _name, initial_value, new_value): - self.experiment.disable_review_slack_notifications = initial_value + self.experiment.enable_review_slack_notifications = initial_value self.experiment.save() response = self.client.post( @@ -1635,7 +1635,7 @@ def test_toggle_slack_notifications(self, _name, initial_value, new_value): "nimbus-ui-toggle-review-slack-notifications", kwargs={"slug": self.experiment.slug}, ), - {"disable_review_slack_notifications": new_value}, + {"enable_review_slack_notifications": new_value}, ) self.assertEqual(response.status_code, 302) self.assertEqual( @@ -1644,7 +1644,7 @@ def test_toggle_slack_notifications(self, _name, initial_value, new_value): ) updated_experiment = NimbusExperiment.objects.get(slug=self.experiment.slug) - self.assertEqual(updated_experiment.disable_review_slack_notifications, new_value) + self.assertEqual(updated_experiment.enable_review_slack_notifications, new_value) class TestOverviewUpdateView(AuthTestCase): From d73b1bc1f46dd71648fe493b63e27f3742e1a71f Mon Sep 17 00:00:00 2001 From: yashikakhurana Date: Mon, 5 Jan 2026 13:41:17 -0800 Subject: [PATCH 4/5] chore(nimbus): Merge migrations --- ...ment_enable_review_slack_notifications.py} | 6 ++--- ...ble_review_slack_notifications_and_more.py | 22 ------------------- 2 files changed, 3 insertions(+), 25 deletions(-) rename experimenter/experimenter/experiments/migrations/{0308_nimbusexperiment_disable_review_slack_notifications.py => 0308_nimbusexperiment_enable_review_slack_notifications.py} (58%) delete mode 100644 experimenter/experimenter/experiments/migrations/0309_remove_nimbusexperiment_disable_review_slack_notifications_and_more.py diff --git a/experimenter/experimenter/experiments/migrations/0308_nimbusexperiment_disable_review_slack_notifications.py b/experimenter/experimenter/experiments/migrations/0308_nimbusexperiment_enable_review_slack_notifications.py similarity index 58% rename from experimenter/experimenter/experiments/migrations/0308_nimbusexperiment_disable_review_slack_notifications.py rename to experimenter/experimenter/experiments/migrations/0308_nimbusexperiment_enable_review_slack_notifications.py index 4ecab03c46..5210a2479c 100644 --- a/experimenter/experimenter/experiments/migrations/0308_nimbusexperiment_disable_review_slack_notifications.py +++ b/experimenter/experimenter/experiments/migrations/0308_nimbusexperiment_enable_review_slack_notifications.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.9 on 2025-12-19 18:18 +# Generated by Django 5.2.9 on 2026-01-05 21:39 from django.db import migrations, models @@ -12,7 +12,7 @@ class Migration(migrations.Migration): operations = [ migrations.AddField( model_name='nimbusexperiment', - name='disable_review_slack_notifications', - field=models.BooleanField(default=False, verbose_name='Disable Review Slack Notifications'), + name='enable_review_slack_notifications', + field=models.BooleanField(default=True, verbose_name='Enable Review Slack Notifications'), ), ] diff --git a/experimenter/experimenter/experiments/migrations/0309_remove_nimbusexperiment_disable_review_slack_notifications_and_more.py b/experimenter/experimenter/experiments/migrations/0309_remove_nimbusexperiment_disable_review_slack_notifications_and_more.py deleted file mode 100644 index 5a00b76582..0000000000 --- a/experimenter/experimenter/experiments/migrations/0309_remove_nimbusexperiment_disable_review_slack_notifications_and_more.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 5.2.9 on 2025-12-19 19:23 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('experiments', '0308_nimbusexperiment_disable_review_slack_notifications'), - ] - - operations = [ - migrations.RemoveField( - model_name='nimbusexperiment', - name='disable_review_slack_notifications', - ), - migrations.AddField( - model_name='nimbusexperiment', - name='enable_review_slack_notifications', - field=models.BooleanField(default=True, verbose_name='Enable Review Slack Notifications'), - ), - ] From ec66cbf621a1d9272affe438cc8ebfccda8239da Mon Sep 17 00:00:00 2001 From: yashikakhurana Date: Tue, 6 Jan 2026 09:16:43 -0800 Subject: [PATCH 5/5] chore(nimbus): Merge migrations --- ...309_nimbusexperiment_enable_review_slack_notifications.py} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename experimenter/experimenter/experiments/migrations/{0308_nimbusexperiment_enable_review_slack_notifications.py => 0309_nimbusexperiment_enable_review_slack_notifications.py} (76%) diff --git a/experimenter/experimenter/experiments/migrations/0308_nimbusexperiment_enable_review_slack_notifications.py b/experimenter/experimenter/experiments/migrations/0309_nimbusexperiment_enable_review_slack_notifications.py similarity index 76% rename from experimenter/experimenter/experiments/migrations/0308_nimbusexperiment_enable_review_slack_notifications.py rename to experimenter/experimenter/experiments/migrations/0309_nimbusexperiment_enable_review_slack_notifications.py index 5210a2479c..03a7f5bb9a 100644 --- a/experimenter/experimenter/experiments/migrations/0308_nimbusexperiment_enable_review_slack_notifications.py +++ b/experimenter/experimenter/experiments/migrations/0309_nimbusexperiment_enable_review_slack_notifications.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.9 on 2026-01-05 21:39 +# Generated by Django 5.2.9 on 2026-01-06 17:16 from django.db import migrations, models @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ('experiments', '0307_nimbusexperiment_next_steps_and_more'), + ('experiments', '0308_delete_duplicate_results_changes'), ] operations = [