diff --git a/experimenter/experimenter/nimbus_ui/forms.py b/experimenter/experimenter/nimbus_ui/forms.py index 4ae6baa7fc..2114139ea2 100644 --- a/experimenter/experimenter/nimbus_ui/forms.py +++ b/experimenter/experimenter/nimbus_ui/forms.py @@ -1479,33 +1479,31 @@ def save(self, commit=True): class FeaturesForm(forms.ModelForm): + def get_feature_config_choices(self, application, qs): + choices = [] + choices.extend( + sorted( + [ + (feature.pk, f"{feature.name} - {feature.description}") + for feature in qs + ], + key=lambda choice: choice[1].lower(), + ) + ) + return choices + application = forms.ChoiceField( - label="", - choices=NimbusExperiment.Application.choices, - widget=forms.widgets.Select( - attrs={ - "class": "form-select", - }, - ), - initial=NimbusExperiment.Application.DESKTOP.value, + required=False, + choices=[("", "Nothing selected"), *list(NimbusExperiment.Application.choices)], + widget=SingleSelectWidget(), ) feature_configs = forms.ChoiceField( - label="", - choices=[], + required=False, + choices=[("", "Nothing selected")], widget=SingleSelectWidget(), ) update_on_change_fields = ("application", "feature_configs") - def get_feature_config_choices(self, application, qs): - return sorted( - [ - (application.pk, f"{application.name} - {application.description}") - for application in NimbusFeatureConfig.objects.all() - if application in qs - ], - key=lambda choice: choice[1].lower(), - ) - class Meta: model = NimbusFeatureConfig fields = ["application", "feature_configs"] @@ -1513,15 +1511,18 @@ class Meta: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - selected_app = self.data.get("application") or self.get_initial_for_field( - self.fields["application"], "application" - ) - features = NimbusFeatureConfig.objects.filter(application=selected_app).order_by( - "slug" - ) - self.fields["feature_configs"].choices = self.get_feature_config_choices( - selected_app, features - ) + # Default: nothing selected for application and features + selected_app = self.data.get("application") or self.initial.get("application") + if selected_app: + features = NimbusFeatureConfig.objects.filter( + application=selected_app + ).order_by("slug") + self.fields["feature_configs"].choices = self.get_feature_config_choices( + selected_app, features + ) + + self.fields["application"].initial = "" + self.fields["feature_configs"].initial = "" base_url = reverse("nimbus-ui-features") htmx_attrs = { @@ -1531,6 +1532,7 @@ def __init__(self, *args, **kwargs): "hx-select": "#features-form", "hx-target": "#features-form", "hx-swap": "outerHTML", + "hx-select-oob": "#deliveries-table", } self.fields["application"].widget.attrs.update(htmx_attrs) self.fields["feature_configs"].widget.attrs.update(htmx_attrs) diff --git a/experimenter/experimenter/nimbus_ui/templates/nimbus_experiments/features.html b/experimenter/experimenter/nimbus_ui/templates/nimbus_experiments/features.html index d5fe9e03b6..4792fac0c0 100644 --- a/experimenter/experimenter/nimbus_ui/templates/nimbus_experiments/features.html +++ b/experimenter/experimenter/nimbus_ui/templates/nimbus_experiments/features.html @@ -17,24 +17,73 @@ hx-swap="outerHTML"> {% csrf_token %}
-
+
- +
Application
{{ form.application|add_class:"form-control"|add_error_class:"is-invalid" }} {% for error in form.application.errors %}
{{ error }}
{% endfor %}
- +
Feature Config
{{ form.feature_configs|add_class:"form-control" }} {% for error in form.feature_configs.errors %}
{{ error }}
{% endfor %}
-
{{ application }}
-
{{ feature_configs }}
+
+
+ Hugging Foxes + Deliveries +
+
+
+
+ + + + + + + + + + + + + + {% for experiment in experiments %} + + + + + + + + + {% empty %} + + + + {% endfor %} + + +
Recipe NameLaunch DateType Of DeliveryChannel(s)Minimum VersionPopulation SizeDelivery Brief
+ {{ experiment.name }} + {{ experiment.published_date|format_not_set }}{{ experiment.home_type_choice }}{{ experiment.get_channel_display }}{{ experiment.firefox_min_version|parse_version }}{{ experiment.population_percent|floatformat:"-1" }}{{ experiment.delivery_brief|format_not_set }}
+

No Deliveries

+
+
+
+
{% endblock %} diff --git a/experimenter/experimenter/nimbus_ui/tests/test_forms.py b/experimenter/experimenter/nimbus_ui/tests/test_forms.py index 8b0fa7aefe..d53b2a1c5a 100644 --- a/experimenter/experimenter/nimbus_ui/tests/test_forms.py +++ b/experimenter/experimenter/nimbus_ui/tests/test_forms.py @@ -3835,8 +3835,8 @@ def test_features_view_default_fields_are_firefox_desktop(self): form = FeaturesForm() application = form.fields["application"] feature_configs = form.fields["feature_configs"] - self.assertEqual(application.initial, NimbusExperiment.Application.DESKTOP.value) - self.assertIsNone(feature_configs.initial) + self.assertEqual(application.initial, "") + self.assertEqual(feature_configs.initial, "") @parameterized.expand( [ diff --git a/experimenter/experimenter/nimbus_ui/tests/test_views.py b/experimenter/experimenter/nimbus_ui/tests/test_views.py index 14a14944cb..1e80b40a58 100644 --- a/experimenter/experimenter/nimbus_ui/tests/test_views.py +++ b/experimenter/experimenter/nimbus_ui/tests/test_views.py @@ -3366,8 +3366,9 @@ def setUp(self): "feature-mobile": NimbusExperiment.Application.IOS, "feature-web": NimbusExperiment.Application.EXPERIMENTER, } + self.feature_configs = {} for item, value in self.features.items(): - NimbusFeatureConfigFactory.create( + self.feature_configs[item] = NimbusFeatureConfigFactory.create( slug=item, name=item.replace("-", " "), application=value ) @@ -3383,11 +3384,9 @@ def test_features_view_dropdown_loads_correct_default(self): form = response.context["form"] self.assertTrue(form.fields["application"]) - self.assertEqual( - form.fields["application"].initial, NimbusExperiment.Application.DESKTOP.value - ) + self.assertEqual(form.fields["application"].initial, "") self.assertTrue(form.fields["feature_configs"]) - self.assertEqual(form.fields["feature_configs"].initial, None) + self.assertEqual(form.fields["feature_configs"].initial, "") @parameterized.expand( [ @@ -3434,3 +3433,30 @@ def test_features_view_multiapplication_loads_in_feature_config(self): self.assertTrue(form.fields["application"]) self.assertEqual(form["application"].value(), applications[1].value) self.assertEqual(form["feature_configs"].value(), str(feature_config_multi.id)) + + @parameterized.expand( + [ + (NimbusExperiment.Application.DESKTOP, "feature-desktop"), + (NimbusExperiment.Application.IOS, "feature-mobile"), + (NimbusExperiment.Application.EXPERIMENTER, "feature-web"), + ] + ) + def test_features_view_renders_table_with_correct_elements( + self, application, feature_config + ): + experiment = f"Experiment {feature_config.replace('-', ' ')}" + NimbusExperimentFactory.create( + name=experiment, + application=application, + feature_configs=[self.feature_configs[feature_config]], + ) + + feature_id = self.feature_configs[feature_config].id + url = reverse("nimbus-ui-features") + response = self.client.get( + f"{url}?application={application.value}&feature_configs={feature_id}" + ) + + self.assertEqual(response.status_code, 200) + self.assertContains(response, "#deliveries-table") + self.assertContains(response, experiment) diff --git a/experimenter/experimenter/nimbus_ui/views.py b/experimenter/experimenter/nimbus_ui/views.py index a91c15d016..fd52bad3e3 100644 --- a/experimenter/experimenter/nimbus_ui/views.py +++ b/experimenter/experimenter/nimbus_ui/views.py @@ -644,6 +644,25 @@ class NimbusFeaturesView(TemplateView): def get_form(self): return FeaturesForm(self.request.GET or None) + def get_queryset(self): + qs = ( + NimbusExperiment.objects.with_merged_channel() + .filter(is_archived=False) + .order_by("-_updated_date_time") + ) + + app = self.request.GET.get("application") + if app: + qs = qs.filter(application=app) + + feature_id = self.request.GET.get("feature_configs") + if feature_id: + qs = qs.filter(feature_configs=feature_id).distinct() + else: + return None + + return qs + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) form = self.get_form() @@ -651,6 +670,7 @@ def get_context_data(self, **kwargs): context["links"] = NimbusUIConstants.FEATURE_PAGE_LINKS context["application"] = self.request.GET.get("application") context["feature_configs"] = self.request.GET.get("feature_configs") + context["experiments"] = self.get_queryset() return context