diff --git a/app/experimenter/experiments/admin.py b/app/experimenter/experiments/admin.py index b140e3baa6..fd436ca0d3 100644 --- a/app/experimenter/experiments/admin.py +++ b/app/experimenter/experiments/admin.py @@ -51,6 +51,7 @@ class ExperimentAdmin(admin.ModelAdmin): "proposed_enrollment", "proposed_duration", "bugzilla_id", + "normandy_slug", "data_science_bugzilla_url", "feature_bugzilla_url", "related_work", diff --git a/app/experimenter/experiments/forms.py b/app/experimenter/experiments/forms.py index bf5d450933..7dd4f9006a 100644 --- a/app/experimenter/experiments/forms.py +++ b/app/experimenter/experiments/forms.py @@ -812,6 +812,9 @@ def save(self, *args, **kwargs): and self.new_status == Experiment.STATUS_SHIP and experiment.bugzilla_id ): + experiment.normandy_slug = experiment.generate_normandy_slug() + experiment.save() + tasks.add_experiment_comment_task.delay( self.request.user.id, experiment.id ) diff --git a/app/experimenter/experiments/migrations/0031_experiment_normandy_slug.py b/app/experimenter/experiments/migrations/0031_experiment_normandy_slug.py new file mode 100644 index 0000000000..80720129a7 --- /dev/null +++ b/app/experimenter/experiments/migrations/0031_experiment_normandy_slug.py @@ -0,0 +1,16 @@ +# Generated by Django 2.1.7 on 2019-03-13 16:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [("experiments", "0030_experiment_engineering_owner")] + + operations = [ + migrations.AddField( + model_name="experiment", + name="normandy_slug", + field=models.CharField(blank=True, max_length=255, null=True), + ) + ] diff --git a/app/experimenter/experiments/models.py b/app/experimenter/experiments/models.py index 1823ff4d6f..9c69bb7e53 100644 --- a/app/experimenter/experiments/models.py +++ b/app/experimenter/experiments/models.py @@ -115,6 +115,7 @@ class Experiment(ExperimentConstants, models.Model): dashboard_image_url = models.URLField(blank=True, null=True) bugzilla_id = models.CharField(max_length=255, blank=True, null=True) + normandy_slug = models.CharField(max_length=255, blank=True, null=True) data_science_bugzilla_url = models.URLField(blank=True, null=True) feature_bugzilla_url = models.URLField(blank=True, null=True) @@ -236,6 +237,25 @@ def test_tube_url(self): "https://firefox-test-tube.herokuapp.com/experiments/{slug}/" ).format(slug=self.slug) + def generate_normandy_slug(self): + error_msg = ( + "The {field} must be set before a Normandy slug can be generated" + ) + + if not self.firefox_version: + raise ValueError(error_msg.format(field="Firefox version")) + + if not self.firefox_channel: + raise ValueError(error_msg.format(field="Firefox channel")) + + if not self.bugzilla_id: + raise ValueError(error_msg.format(field="Bugzilla ID")) + + return ( + f"{self.type}-{self.slug}-{self.firefox_channel}" + f"-{self.firefox_version}-bug-{self.bugzilla_id}" + ).lower() + @property def has_external_urls(self): return ( diff --git a/app/experimenter/experiments/tests/test_forms.py b/app/experimenter/experiments/tests/test_forms.py index 0b1e953c5a..67417eaae5 100644 --- a/app/experimenter/experiments/tests/test_forms.py +++ b/app/experimenter/experiments/tests/test_forms.py @@ -1047,17 +1047,31 @@ def test_sets_bugzilla_id_when_draft_becomes_review(self): self.user.id, experiment.id ) - def test_adds_bugzilla_comment_when_review_becomes_ship(self): + def test_adds_bugzilla_comment_and_normandy_slug_when_becomes_ship(self): experiment = ExperimentFactory.create_with_status( - Experiment.STATUS_REVIEW, bugzilla_id="12345" + target_status=Experiment.STATUS_REVIEW, + type=Experiment.TYPE_PREF, + name="Experiment Name", + slug="experiment-slug", + firefox_version="57.0", + firefox_channel=Experiment.CHANNEL_NIGHTLY, + bugzilla_id="12345", ) + self.assertEqual(experiment.normandy_slug, None) + form = ExperimentStatusForm( request=self.request, data={"status": experiment.STATUS_SHIP}, instance=experiment, ) + self.assertTrue(form.is_valid()) experiment = form.save() + + self.assertEqual( + experiment.normandy_slug, + "pref-experiment-slug-nightly-57.0-bug-12345", + ) self.mock_tasks_add_comment.delay.assert_called_with( self.user.id, experiment.id ) diff --git a/app/experimenter/experiments/tests/test_models.py b/app/experimenter/experiments/tests/test_models.py index f306405123..d57fffecde 100644 --- a/app/experimenter/experiments/tests/test_models.py +++ b/app/experimenter/experiments/tests/test_models.py @@ -160,6 +160,56 @@ def test_has_external_urls_is_true_when_bugzilla_and_test_tube_urls(self): ) self.assertTrue(experiment.has_external_urls) + def test_generate_normandy_slug_raises_valueerror_without_version(self): + experiment = ExperimentFactory.create( + type=Experiment.TYPE_PREF, + slug="experiment-slug", + firefox_version="", + firefox_channel="Nightly", + bugzilla_id="12345", + ) + + with self.assertRaises(ValueError): + experiment.generate_normandy_slug() + + def test_generate_normandy_slug_raises_valueerror_without_channel(self): + experiment = ExperimentFactory.create( + type=Experiment.TYPE_PREF, + slug="experiment-slug", + firefox_version="57.0", + firefox_channel="", + bugzilla_id="12345", + ) + + with self.assertRaises(ValueError): + experiment.generate_normandy_slug() + + def test_generate_normandy_slug_raises_valueerror_without_bugzilla(self): + experiment = ExperimentFactory.create( + type=Experiment.TYPE_PREF, + slug="experiment-slug", + firefox_version="57.0", + firefox_channel="Nightly", + bugzilla_id="", + ) + + with self.assertRaises(ValueError): + experiment.generate_normandy_slug() + + def test_generate_normandy_slug_returns_expected_slug(self): + experiment = ExperimentFactory.create( + type=Experiment.TYPE_PREF, + slug="experiment-slug", + firefox_version="57.0", + firefox_channel="Nightly", + bugzilla_id="12345", + ) + + self.assertEqual( + experiment.generate_normandy_slug(), + "pref-experiment-slug-nightly-57.0-bug-12345", + ) + def test_start_date_returns_proposed_start_date_if_change_is_missing(self): experiment = ExperimentFactory.create_with_variants() self.assertEqual(experiment.start_date, experiment.proposed_start_date)