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): Subscribe/Unsubscribe to experiment on new summary page #11639

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
32 changes: 32 additions & 0 deletions experimenter/experimenter/nimbus_ui_new/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,35 @@ def __init__(self, *args, **kwargs):

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


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

def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user", None)
super().__init__(*args, **kwargs)

def save(self, commit=True):
self.instance.subscribers.add(self.user)
if commit:
self.instance.save()
return self.instance


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

def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user", None)
super().__init__(*args, **kwargs)

def save(self, commit=True):
self.instance.subscribers.remove(self.user)
if commit:
self.instance.save()
return self.instance
Original file line number Diff line number Diff line change
Expand Up @@ -90,22 +90,16 @@ <h4>Overview</h4>
</tr>
<tr>
<th>Team projects</th>
<td>
<td colspan="2">
{% for project in experiment.projects.all %}
<p>{{ project }}</p>
{% empty %}
<span class="text-danger">Not set</span>
{% endfor %}
</td>
<th>Subscribers</th>
<td>
{% for subscriber in experiment.subscribers.all %}
<p>{{ subscriber }}</p>
{% empty %}
<span class="text-danger">Not set</span>
{% endfor %}
</td>
</tr>
{% include 'nimbus_experiments/subscribers_list.html' %}

</tbody>
</table>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<tr class="subscribers-section">
<th>Subscribers</th>
<td id="subscribers-list"
colspan="2"
style="max-height: 150px;
overflow-y: auto;
overflow-x: hidden">
{% for subscriber in experiment.subscribers.all %}
<p>{{ subscriber.email }}</p>
{% empty %}
<span class="text-danger">Not Set</span>
{% endfor %}
</td>
<td style="text-align: right;">
{% if request.user in experiment.subscribers.all %}
<form method="post"
action="{% url 'unsubscribe' slug=experiment.slug %}"
hx-post="{% url 'unsubscribe' slug=experiment.slug %}"
hx-target=".subscribers-section"
hx-swap="outerHTML">
{% csrf_token %}
<button type="submit" class="btn btn-danger">Unsubscribe</button>
</form>
{% else %}
<form method="post"
action="{% url 'subscribe' slug=experiment.slug %}"
hx-post="{% url 'subscribe' slug=experiment.slug %}"
hx-target=".subscribers-section"
hx-swap="outerHTML">
{% csrf_token %}
<button type="submit" class="btn btn-success">Subscribe</button>
</form>
{% endif %}
</td>
</tr>
27 changes: 27 additions & 0 deletions experimenter/experimenter/nimbus_ui_new/tests/test_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
NimbusExperimentCreateForm,
QAStatusForm,
SignoffForm,
SubscribeForm,
TakeawaysForm,
UnsubscribeForm,
)
from experimenter.openidc.tests.factories import UserFactory
from experimenter.outcomes import Outcomes
Expand Down Expand Up @@ -274,3 +276,28 @@ def test_invalid_form_with_wrong_application_outcomes_and_segments(self):
self.assertIn("primary_outcomes", form.errors)
self.assertIn("secondary_outcomes", form.errors)
self.assertIn("segments", form.errors)


class SubscriptionFormTests(RequestFormTestCase):
def setUp(self):
super().setUp()
self.experiment = NimbusExperimentFactory.create(
name="Test Experiment",
owner=self.user,
qa_signoff=False,
vp_signoff=False,
legal_signoff=False,
)

def test_subscribe_form_adds_subscriber(self):
form = SubscribeForm(instance=self.experiment, data={}, user=self.user)
self.assertTrue(form.is_valid())
form.save()
self.assertIn(self.user, self.experiment.subscribers.all())

def test_unsubscribe_form_removes_subscriber(self):
self.experiment.subscribers.add(self.user)
form = UnsubscribeForm(instance=self.experiment, data={}, user=self.user)
self.assertTrue(form.is_valid())
form.save()
self.assertNotIn(self.user, self.experiment.subscribers.all())
27 changes: 27 additions & 0 deletions experimenter/experimenter/nimbus_ui_new/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1090,6 +1090,33 @@ def test_signoff_edit_mode_post_valid_form(self):
self.assertTrue(self.experiment.vp_signoff)
self.assertTrue(self.experiment.legal_signoff)

def test_subscribe_to_experiment(self):
self.assertNotIn(self.user, self.experiment.subscribers.all())

response = self.client.post(
reverse("subscribe", kwargs={"slug": self.experiment.slug})
)

self.experiment.refresh_from_db()

self.assertIn(self.user, self.experiment.subscribers.all())
self.assertEqual(response.status_code, 200)

def test_unsubscribe_from_experiment(self):
self.experiment.subscribers.add(self.user)
self.experiment.save()

self.assertIn(self.user, self.experiment.subscribers.all())

response = self.client.post(
reverse("unsubscribe", kwargs={"slug": self.experiment.slug})
)

self.experiment.refresh_from_db()

self.assertNotIn(self.user, self.experiment.subscribers.all())
self.assertEqual(response.status_code, 200)


class TestNimbusExperimentsCreateView(AuthTestCase):
def test_post_creates_experiment(self):
Expand Down
8 changes: 8 additions & 0 deletions experimenter/experimenter/nimbus_ui_new/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
NimbusExperimentsListTableView,
QAStatusUpdateView,
SignoffUpdateView,
SubscribeView,
TakeawaysUpdateView,
UnsubscribeView,
)

urlpatterns = [
Expand Down Expand Up @@ -52,4 +54,10 @@
NimbusExperimentsCreateView.as_view(),
name="nimbus-new-create",
),
re_path(r"^(?P<slug>[\w-]+)/subscribe/", SubscribeView.as_view(), name="subscribe"),
re_path(
r"^(?P<slug>[\w-]+)/unsubscribe/",
UnsubscribeView.as_view(),
name="unsubscribe",
),
]
27 changes: 27 additions & 0 deletions experimenter/experimenter/nimbus_ui_new/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.conf import settings
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views.generic import CreateView, DetailView
from django.views.generic.edit import UpdateView
Expand All @@ -18,7 +19,9 @@
NimbusExperimentCreateForm,
QAStatusForm,
SignoffForm,
SubscribeForm,
TakeawaysForm,
UnsubscribeForm,
)


Expand Down Expand Up @@ -215,3 +218,27 @@ class MetricsUpdateView(NimbusExperimentViewMixin, RequestFormMixin, UpdateView)
def form_valid(self, form):
super().form_valid(form)
return self.render_to_response(self.get_context_data(form=form))


class SubscribeView(NimbusExperimentViewMixin, RequestFormMixin, UpdateView):
form_class = SubscribeForm
template_name = "nimbus_experiments/subscribers_list.html"

def form_valid(self, form):
experiment = get_object_or_404(NimbusExperiment, slug=self.kwargs["slug"])
form.instance = experiment
form.user = self.request.user
form.save()
return render(self.request, self.template_name, {"experiment": experiment})


class UnsubscribeView(NimbusExperimentViewMixin, RequestFormMixin, UpdateView):
form_class = UnsubscribeForm
template_name = "nimbus_experiments/subscribers_list.html"

def form_valid(self, form):
experiment = get_object_or_404(NimbusExperiment, slug=self.kwargs["slug"])
form.instance = experiment
form.user = self.request.user
form.save()
return render(self.request, self.template_name, {"experiment": experiment})