diff --git a/.gitignore b/.gitignore index c06ec47a78..c98c2698b3 100644 --- a/.gitignore +++ b/.gitignore @@ -95,6 +95,7 @@ experimenter/experimenter/reporting/reporting-ui/assets/ .tmontmp experimenter/coverage_html_report/ experimenter/tests/integration/test-reports/ +experimenter/tests/integration/nimbus/fixtures/targeting_configs.json junit.xml **/coverage_report/ diff --git a/Makefile b/Makefile index 1e3941fd3c..622852a1ac 100644 --- a/Makefile +++ b/Makefile @@ -60,6 +60,7 @@ LOAD_COUNTRIES = python manage.py loaddata ./experimenter/base/fixtures/countrie LOAD_LOCALES = python manage.py loaddata ./experimenter/base/fixtures/locales.json LOAD_LANGUAGES = python manage.py loaddata ./experimenter/base/fixtures/languages.json LOAD_FEATURES = python manage.py load_feature_configs +GENERATE_TARGETING_CONFIGS = python manage.py generate_targeting_configs LOAD_DUMMY_EXPERIMENTS = [[ -z $$SKIP_DUMMY ]] && python manage.py load_dummy_experiments || python manage.py load_dummy_tags JETSTREAM_CONFIG_URL = https://github.com/mozilla/metric-hub/archive/main.zip @@ -226,7 +227,7 @@ bash: build_dev cirrus_build refresh: kill build_dev cirrus_build compose_build refresh_db ## Rebuild all containers and the database refresh_db: # Rebuild the database - $(COMPOSE_RUN) -e SKIP_DUMMY=$$SKIP_DUMMY experimenter bash -c '$(WAIT_FOR_DB) $(PYTHON_MIGRATE)&&$(LOAD_LOCALES)&&$(LOAD_COUNTRIES)&&$(LOAD_LANGUAGES)&&$(LOAD_FEATURES)&&$(LOAD_DUMMY_EXPERIMENTS)' + $(COMPOSE_RUN) -e SKIP_DUMMY=$$SKIP_DUMMY experimenter bash -c '$(WAIT_FOR_DB) $(PYTHON_MIGRATE)&&$(LOAD_LOCALES)&&$(LOAD_COUNTRIES)&&$(LOAD_LANGUAGES)&&$(LOAD_FEATURES)&&$(GENERATE_TARGETING_CONFIGS)&&$(LOAD_DUMMY_EXPERIMENTS)' dependabot_approve: echo "Install and configure the Github CLI https://github.com/cli/cli" diff --git a/experimenter/experimenter/base/management/commands/generate_targeting_configs.py b/experimenter/experimenter/base/management/commands/generate_targeting_configs.py new file mode 100644 index 0000000000..d5f0efb7cd --- /dev/null +++ b/experimenter/experimenter/base/management/commands/generate_targeting_configs.py @@ -0,0 +1,40 @@ +import json +import logging +from pathlib import Path + +from django.core.management.base import BaseCommand + +from experimenter.targeting.constants import NimbusTargetingConfig + +logger = logging.getLogger() + +OUTPUT_PATH = ( + Path(__file__).resolve().parents[4] + / "tests" + / "integration" + / "nimbus" + / "fixtures" + / "targeting_configs.json" +) + + +class Command(BaseCommand): + help = "Generate targeting configs JSON for integration tests" + + def handle(self, *args, **options): + configs = [ + { + "label": config.name, + "value": config.slug, + "applicationValues": list(config.application_choice_names), + "description": config.description, + "stickyRequired": config.sticky_required, + "isFirstRunRequired": config.is_first_run_required, + } + for config in NimbusTargetingConfig.targeting_configs + ] + + OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True) + OUTPUT_PATH.write_text(json.dumps(configs, indent=2) + "\n") + + logger.info(f"Generated {len(configs)} targeting configs to {OUTPUT_PATH}") diff --git a/experimenter/experimenter/targeting/tests/test_generate_targeting_configs.py b/experimenter/experimenter/targeting/tests/test_generate_targeting_configs.py new file mode 100644 index 0000000000..e591bdd30d --- /dev/null +++ b/experimenter/experimenter/targeting/tests/test_generate_targeting_configs.py @@ -0,0 +1,43 @@ +import json +from unittest.mock import patch + +from django.core.management import call_command +from django.test import TestCase + +from experimenter.targeting.constants import NimbusTargetingConfig + + +class TestGenerateTargetingConfigsCommand(TestCase): + def test_generates_json_file(self): + with patch( + "experimenter.base.management.commands.generate_targeting_configs.OUTPUT_PATH" + ) as mock_path: + mock_path.parent.mkdir.return_value = None + + call_command("generate_targeting_configs") + + mock_path.parent.mkdir.assert_called_once_with(parents=True, exist_ok=True) + mock_path.write_text.assert_called_once() + + written_json = mock_path.write_text.call_args[0][0] + configs = json.loads(written_json) + + self.assertEqual(len(configs), len(NimbusTargetingConfig.targeting_configs)) + + for config in configs: + self.assertIn("label", config) + self.assertIn("value", config) + self.assertIn("applicationValues", config) + self.assertIn("description", config) + self.assertIn("stickyRequired", config) + self.assertIn("isFirstRunRequired", config) + + first = configs[0] + source = NimbusTargetingConfig.targeting_configs[0] + self.assertEqual(first["label"], source.name) + self.assertEqual(first["value"], source.slug) + self.assertEqual( + first["applicationValues"], list(source.application_choice_names) + ) + self.assertEqual(first["stickyRequired"], source.sticky_required) + self.assertEqual(first["isFirstRunRequired"], source.is_first_run_required) diff --git a/experimenter/tests/integration/nimbus/utils/helpers.py b/experimenter/tests/integration/nimbus/utils/helpers.py index d878a32a78..3721c63e5d 100755 --- a/experimenter/tests/integration/nimbus/utils/helpers.py +++ b/experimenter/tests/integration/nimbus/utils/helpers.py @@ -2,6 +2,7 @@ import os import time from functools import cache +from pathlib import Path import requests @@ -11,6 +12,9 @@ LOAD_DATA_RETRIES = 60 LOAD_DATA_RETRY_DELAY = 1.0 +TARGETING_CONFIGS_PATH = ( + Path(__file__).resolve().parents[1] / "fixtures" / "targeting_configs.json" +) def load_graphql_data(query): @@ -142,10 +146,10 @@ def load_config_data(): def load_targeting_configs(app=BaseExperimentApplications.FIREFOX_DESKTOP.value): - config_data = load_config_data() + targeting_configs = json.loads(TARGETING_CONFIGS_PATH.read_text()) return [ item["value"] - for item in config_data["targetingConfigs"] + for item in targeting_configs if ( BaseExperimentApplications.FIREFOX_DESKTOP.value in app and BaseExperimentApplications.FIREFOX_DESKTOP.value