Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nimbus): add overview form to new nimbus ui #11926

Merged
merged 1 commit into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions experimenter/experimenter/experiments/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,9 @@ def get_detail_url(self):
def get_history_url(self):
return reverse("nimbus-new-history", kwargs={"slug": self.slug})

def get_update_overview_url(self):
return reverse("nimbus-new-update-overview", kwargs={"slug": self.slug})

def get_update_metrics_url(self):
return reverse("nimbus-new-update-metrics", kwargs={"slug": self.slug})

Expand Down
136 changes: 135 additions & 1 deletion experimenter/experimenter/nimbus_ui_new/forms.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from django import forms
from django.contrib.auth.models import User
from django.forms import inlineformset_factory
from django.http import HttpRequest
from django.utils.text import slugify

from experimenter.experiments.changelog_utils import generate_nimbus_changelog
from experimenter.experiments.models import NimbusExperiment
from experimenter.experiments.models import NimbusDocumentationLink, NimbusExperiment
from experimenter.nimbus_ui_new.constants import NimbusUIConstants
from experimenter.outcomes import Outcomes
from experimenter.projects.models import Project
from experimenter.segments import Segments


Expand Down Expand Up @@ -153,6 +155,138 @@ def __init__(self, *args, attrs=None, **kwargs):
super().__init__(*args, attrs=attrs, **kwargs)


class InlineRadioSelect(forms.RadioSelect):
template_name = "common/widgets/inline_radio.html"
option_template_name = "common/widgets/inline_radio_option.html"


class NimbusDocumentationLinkForm(forms.ModelForm):
title = forms.ChoiceField(
choices=NimbusExperiment.DocumentationLink.choices,
required=False,
widget=forms.Select(attrs={"class": "form-select"}),
)
link = forms.CharField(
required=False, widget=forms.TextInput(attrs={"class": "form-control"})
)

class Meta:
model = NimbusDocumentationLink
fields = ("title", "link")


class OverviewForm(NimbusChangeLogFormMixin, forms.ModelForm):
YES_NO_CHOICES = (
(True, "Yes"),
(False, "No"),
)

name = forms.CharField(
required=False, widget=forms.TextInput(attrs={"class": "form-control"})
)
hypothesis = forms.CharField(
required=False, widget=forms.Textarea(attrs={"class": "form-control"})
)
risk_brand = forms.TypedChoiceField(
required=False,
choices=YES_NO_CHOICES,
widget=InlineRadioSelect,
coerce=lambda x: x == "True",
)
risk_message = forms.TypedChoiceField(
required=False,
choices=YES_NO_CHOICES,
widget=InlineRadioSelect,
coerce=lambda x: x == "True",
)
projects = forms.ModelMultipleChoiceField(
required=False, queryset=Project.objects.all(), widget=MultiSelectWidget()
)
public_description = forms.CharField(
required=False, widget=forms.Textarea(attrs={"class": "form-control", "rows": 3})
)
risk_revenue = forms.TypedChoiceField(
required=False,
choices=YES_NO_CHOICES,
widget=InlineRadioSelect,
coerce=lambda x: x == "True",
)
risk_partner_related = forms.TypedChoiceField(
required=False,
choices=YES_NO_CHOICES,
widget=InlineRadioSelect,
coerce=lambda x: x == "True",
)

class Meta:
model = NimbusExperiment
fields = [
"name",
"hypothesis",
"projects",
"public_description",
"risk_partner_related",
"risk_revenue",
"risk_brand",
"risk_message",
]

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.NimbusDocumentationLinkFormSet = inlineformset_factory(
NimbusExperiment,
NimbusDocumentationLink,
form=NimbusDocumentationLinkForm,
extra=0, # Number of empty forms to display initially
)
self.documentation_links = self.NimbusDocumentationLinkFormSet(
data=self.data or None,
instance=self.instance,
)

def is_valid(self):
return super().is_valid() and self.documentation_links.is_valid()

def save(self):
experiment = super().save()
self.documentation_links.save()
return experiment

def get_changelog_message(self):
return f"{self.request.user} updated overview"


class DocumentationLinkCreateForm(NimbusChangeLogFormMixin, forms.ModelForm):
class Meta:
model = NimbusExperiment
fields = []

def save(self):
super().save(commit=False)
self.instance.documentation_links.create()
return self.instance

def get_changelog_message(self):
return f"{self.request.user} added a documentation link"


class DocumentationLinkDeleteForm(NimbusChangeLogFormMixin, forms.ModelForm):
link_id = forms.ModelChoiceField(queryset=NimbusDocumentationLink.objects.all())

class Meta:
model = NimbusExperiment
fields = ["link_id"]

def save(self):
super().save(commit=False)
documentation_link = self.cleaned_data["link_id"]
documentation_link.delete()
return self.instance

def get_changelog_message(self):
return f"{self.request.user} removed a documentation link"


class MetricsForm(NimbusChangeLogFormMixin, forms.ModelForm):
primary_outcomes = forms.MultipleChoiceField(
required=False, widget=MultiSelectWidget(attrs={"data-max-options": 2})
Expand Down
36 changes: 36 additions & 0 deletions experimenter/experimenter/nimbus_ui_new/static/css/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,42 @@
@import "~bootstrap/scss/variables";
@import "~bootstrap/scss/mixins";

.bootstrap-select {
border-radius: var(--bs-border-radius) !important;
border-width: 1px !important;
border: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;
--bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 16 16%27%3e%3cpath fill=%27none%27 stroke=%27%23343a40%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27 stroke-width=%272%27 d=%27m2 5 6 6 6-6%27/%3e%3c/svg%3e");
background-image: var(--bs-form-select-bg-img),
var(--bs-form-select-bg-icon, none);
background-repeat: no-repeat;
background-position: right 0.75rem center;
background-size:
16px 12px,
calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);

.dropdown-toggle::after {
content: none;
}
}

.was-validated .bootstrap-select {
border-radius: var(--bs-border-radius) !important;
border-width: 1px !important;
border: var(--bs-border-width) var(--bs-border-style)
var(--bs-form-valid-border-color) !important;
--bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 16 16%27%3e%3cpath fill=%27none%27 stroke=%27%23343a40%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27 stroke-width=%272%27 d=%27m2 5 6 6 6-6%27/%3e%3c/svg%3e");
--bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 8 8%27%3e%3cpath fill=%27%23198754%27 d=%27M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z%27/%3e%3c/svg%3e");
background-image: var(--bs-form-select-bg-img),
var(--bs-form-select-bg-icon, none);
background-repeat: no-repeat;
background-position:
right 0.75rem center,
center right 2.25rem;
background-size:
16px 12px,
calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
}

@include color-mode(light) {
.bootstrap-select {
.dropdown-menu {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{% with id=widget.attrs.id %}
<div {% if id %}id="{{ id }}"{% endif %}
{% if widget.attrs.class %}class="{{ widget.attrs.class }}"{% endif %}>
{% for group, options, index in widget.optgroups %}
{% if group %}
<div>
<label>{{ group }}</label>
{% endif %}
{% for option in options %}
<div class="form-check form-check-inline ms-3 me-0">
{% include option.template_name with widget=option %}

</div>
{% endfor %}
{% if group %}</div>{% endif %}
{% endfor %}
</div>
{% endwith %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{% if widget.wrap_label %}
<label class="form-check-label"
{% if widget.attrs.id %}for="{{ widget.attrs.id }}"{% endif %}>
{% endif %}
{% include "common/widgets/inline_radio_option_input.html" %}

{% if widget.wrap_label %}
{{ widget.label }}
</label>
{% endif %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<input type="{{ widget.type }}" class="form-check-input" name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}{% include "django/forms/widgets/attrs.html" %}
>
Loading