From 6d559c38638bf29eb7561d7bdfb397a234c3ee87 Mon Sep 17 00:00:00 2001 From: elisa-av Date: Mon, 21 Oct 2024 13:56:39 -0300 Subject: [PATCH 1/7] feat(contact_form): Enable pre-selection of issue type via URL query parameters This update enhances the contact form by allowing users to select the "Issue Type" and also pre-select it via URL query parameters, improving UX. Additionally, the contact form now sends submissions to support@freelawproject.atlassian.net, integrating with Jira for better issue tracking. The email body includes: User Email: Displayed as User Email: to facilitate easy parsing in Jira. Issue Type: Clearly stated to assist in automatic categorization within Jira. Subject and Message: Retained as before for detailed context. This change enables automatic ticket creation to relieve some of the overhead of managing requests manually by email. --- cl/simple_pages/forms.py | 15 +++++++++++++++ cl/simple_pages/templates/contact_form.html | 12 ++++++++++++ cl/simple_pages/views.py | 17 +++++++++++++++-- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/cl/simple_pages/forms.py b/cl/simple_pages/forms.py index 57e2e37346..bedd30ac81 100644 --- a/cl/simple_pages/forms.py +++ b/cl/simple_pages/forms.py @@ -6,6 +6,12 @@ class ContactForm(forms.Form): + ISSUE_TYPE_CHOICES = [ + ("removal", "Case Removal Request"), + ("bug", "RECAP Extension Bug"), + ("support", "Support"), + ] + name = forms.CharField( widget=forms.TextInput(attrs={"class": "form-control"}) ) @@ -14,6 +20,11 @@ class ContactForm(forms.Form): widget=forms.TextInput(attrs={"class": "form-control"}) ) + issue_type = forms.ChoiceField( + choices=ISSUE_TYPE_CHOICES, + widget=forms.Select(attrs={"class": "form-control"}) + ) + # This is actually the "Subject" field, but we call it the phone_number # field to defeat spam. phone_number = forms.CharField( @@ -49,3 +60,7 @@ def clean(self) -> Dict[str, Any] | None: ) self.add_error("message", msg) return cleaned_data + + def get_issue_type_display(self) -> str: + value = self.cleaned_data.get("issue_type") + return dict(self.fields["issue_type"].choices).get(value, "Unidentified Type") diff --git a/cl/simple_pages/templates/contact_form.html b/cl/simple_pages/templates/contact_form.html index a301202522..70d812a2c5 100644 --- a/cl/simple_pages/templates/contact_form.html +++ b/cl/simple_pages/templates/contact_form.html @@ -45,6 +45,18 @@

Contact Us

{% endif %} +
+ + {{ form.issue_type }} + {% if form.issue_type.errors %} +

+ {% for error in form.issue_type.errors %} + {{ error|escape }} + {% endfor %} +

+ {% endif %} +
+ {# We use the phone_number field as the subject field to defeat spam #}
diff --git a/cl/simple_pages/views.py b/cl/simple_pages/views.py index 9ef5cc988d..5ab73d9c1f 100644 --- a/cl/simple_pages/views.py +++ b/cl/simple_pages/views.py @@ -378,24 +378,37 @@ async def contact( logger.info("Detected spam message. Not sending email.") return HttpResponseRedirect(reverse("contact_thanks")) + issue_type_label = form.get_issue_type_display() + default_from = settings.DEFAULT_FROM_EMAIL message = EmailMessage( subject="[CourtListener] Contact: " "{phone_number}".format(**cd), body="Subject: {phone_number}\n" - "From: {name} ({email})\n\n\n" + "From: {name}\n" + "User Email: <{email}>\n" + "Issue Type: {issue_type_label}\n\n" "{message}\n\n" "Browser: {browser}".format( browser=request.META.get("HTTP_USER_AGENT", "Unknown"), + issue_type_label=issue_type_label, **cd, ), - to=["info@free.law"], + to=["support@freelawproject.atlassian.net"], reply_to=[cd.get("email", default_from) or default_from], ) await sync_to_async(message.send)() return HttpResponseRedirect(reverse("contact_thanks")) else: # the form is loading for the first time + issue_type = request.GET.get("issue_type") + if issue_type: + issue_type_lower = issue_type.lower() + valid_issue_types = [ + choice[0] for choice in ContactForm.ISSUE_TYPE_CHOICES + ] + if issue_type_lower in valid_issue_types: + initial["issue_type"] = issue_type_lower user = await request.auser() # type: ignore[attr-defined] if isinstance(user, User): initial["email"] = user.email From 07ceda27883570632da5d59f39e0a60148419ea9 Mon Sep 17 00:00:00 2001 From: elisa-av Date: Mon, 21 Oct 2024 14:13:13 -0300 Subject: [PATCH 2/7] fix(contact_form): use ISSUE_TYPE_CHOICES directly to resolve linter error --- cl/simple_pages/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cl/simple_pages/forms.py b/cl/simple_pages/forms.py index bedd30ac81..fd9a134a3d 100644 --- a/cl/simple_pages/forms.py +++ b/cl/simple_pages/forms.py @@ -63,4 +63,4 @@ def clean(self) -> Dict[str, Any] | None: def get_issue_type_display(self) -> str: value = self.cleaned_data.get("issue_type") - return dict(self.fields["issue_type"].choices).get(value, "Unidentified Type") + return dict(self.ISSUE_TYPE_CHOICES).get(value, "Unidentified Type") From 9deeb9abe7d7b619065fe2718fa10b27420d624c Mon Sep 17 00:00:00 2001 From: elisa-av Date: Mon, 21 Oct 2024 14:41:29 -0300 Subject: [PATCH 3/7] fix(contact_form): add default value to get_issue_type_display to resolve linter error --- cl/simple_pages/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cl/simple_pages/forms.py b/cl/simple_pages/forms.py index fd9a134a3d..6d5bac9eef 100644 --- a/cl/simple_pages/forms.py +++ b/cl/simple_pages/forms.py @@ -62,5 +62,5 @@ def clean(self) -> Dict[str, Any] | None: return cleaned_data def get_issue_type_display(self) -> str: - value = self.cleaned_data.get("issue_type") + value = self.cleaned_data.get("issue_type", "") return dict(self.ISSUE_TYPE_CHOICES).get(value, "Unidentified Type") From 13c0646098abac17c7c241ffe25bf2ad28ca9480 Mon Sep 17 00:00:00 2001 From: elisa-av Date: Mon, 21 Oct 2024 15:29:39 -0300 Subject: [PATCH 4/7] fix(contact_form): add dangling comma to resolve linter issue --- cl/simple_pages/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cl/simple_pages/forms.py b/cl/simple_pages/forms.py index 6d5bac9eef..96519c783f 100644 --- a/cl/simple_pages/forms.py +++ b/cl/simple_pages/forms.py @@ -22,7 +22,7 @@ class ContactForm(forms.Form): issue_type = forms.ChoiceField( choices=ISSUE_TYPE_CHOICES, - widget=forms.Select(attrs={"class": "form-control"}) + widget=forms.Select(attrs={"class": "form-control"}), ) # This is actually the "Subject" field, but we call it the phone_number From 600ef8df8f9f4889f4dec6b7709423116cee5604 Mon Sep 17 00:00:00 2001 From: elisa-av Date: Mon, 21 Oct 2024 18:22:20 -0300 Subject: [PATCH 5/7] fix(tests): Add new required form field to ContactTest --- cl/simple_pages/tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cl/simple_pages/tests.py b/cl/simple_pages/tests.py index f1a03157af..fa83c36612 100644 --- a/cl/simple_pages/tests.py +++ b/cl/simple_pages/tests.py @@ -19,6 +19,7 @@ class ContactTest(SimpleUserDataMixin, TestCase): test_msg = { "name": "pandora", "phone_number": "asdf", + "issue_type": "support", "message": "123456789012345678901", "email": "pandora@box.com", "hcaptcha": "xxx", From 74ad6b396faaa3f510843d5a658b3c956965876f Mon Sep 17 00:00:00 2001 From: elisa-a-v Date: Tue, 22 Oct 2024 13:00:05 -0300 Subject: [PATCH 6/7] refactor(contact_form): Use named constants for each value of the ISSUE_TYPE_CHOICES in ContactForm --- cl/simple_pages/forms.py | 12 +++++++++--- cl/simple_pages/views.py | 9 ++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/cl/simple_pages/forms.py b/cl/simple_pages/forms.py index 96519c783f..d541e2167e 100644 --- a/cl/simple_pages/forms.py +++ b/cl/simple_pages/forms.py @@ -6,12 +6,18 @@ class ContactForm(forms.Form): + REMOVAL_REQUEST = "removal" + RECAP_BUG = "recap" + SUPPORT_REQUEST = "support" + ISSUE_TYPE_CHOICES = [ - ("removal", "Case Removal Request"), - ("bug", "RECAP Extension Bug"), - ("support", "Support"), + (REMOVAL_REQUEST, "Case Removal Request"), + (RECAP_BUG, "RECAP Extension Bug"), + (SUPPORT_REQUEST, "Support"), ] + VALID_ISSUE_TYPES = [REMOVAL_REQUEST, RECAP_BUG, SUPPORT_REQUEST] + name = forms.CharField( widget=forms.TextInput(attrs={"class": "form-control"}) ) diff --git a/cl/simple_pages/views.py b/cl/simple_pages/views.py index 5ab73d9c1f..98616a906c 100644 --- a/cl/simple_pages/views.py +++ b/cl/simple_pages/views.py @@ -402,13 +402,8 @@ async def contact( else: # the form is loading for the first time issue_type = request.GET.get("issue_type") - if issue_type: - issue_type_lower = issue_type.lower() - valid_issue_types = [ - choice[0] for choice in ContactForm.ISSUE_TYPE_CHOICES - ] - if issue_type_lower in valid_issue_types: - initial["issue_type"] = issue_type_lower + if issue_type and issue_type.lower() in ContactForm.VALID_ISSUE_TYPES: + initial["issue_type"] = issue_type.lower() user = await request.auser() # type: ignore[attr-defined] if isinstance(user, User): initial["email"] = user.email From f09301508f8ce2e64433c6a0f19aad1400f11e5b Mon Sep 17 00:00:00 2001 From: elisa-a-v Date: Tue, 22 Oct 2024 13:05:55 -0300 Subject: [PATCH 7/7] fix(contact_form): Account for selected issue type when identifying removal requests --- cl/simple_pages/forms.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cl/simple_pages/forms.py b/cl/simple_pages/forms.py index d541e2167e..3f650f31b1 100644 --- a/cl/simple_pages/forms.py +++ b/cl/simple_pages/forms.py @@ -58,7 +58,11 @@ def clean(self) -> Dict[str, Any] | None: r"remov(e|al)|take down", re.I, ) - if re.search(regex, subject) and "http" not in message.lower(): + is_removal_request = ( + re.search(regex, subject) + or cleaned_data.get("issue_type", "") == self.REMOVAL_REQUEST + ) + if is_removal_request and "http" not in message.lower(): msg = ( "This appears to be a removal request, but you did not " "include a link. You must include a link for a request to be "