From 812fd255714e4eb1351237a6923c32e5a36c6bff Mon Sep 17 00:00:00 2001 From: Pea Tyczynska Date: Tue, 1 Oct 2024 14:04:51 +0100 Subject: [PATCH 1/9] Add "Send user API docs section" page It allows user to enter an URL. --- app/main/forms.py | 13 +++++++++ app/main/views/index.py | 13 ++++++++- app/navigation.py | 1 + .../api-documentation-section.html | 27 +++++++++++++++++++ tests/app/main/views/test_index.py | 7 +++++ 5 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 app/templates/views/guidance/using-notify/api-documentation-section.html diff --git a/app/main/forms.py b/app/main/forms.py index f483e042a7..79b2cf8b87 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -2280,6 +2280,19 @@ def validate(self, *args, **kwargs): return super().validate(*args, **kwargs) or self.url.data == "" +class UrlForm(StripWhitespaceForm): + url = GovukTextInputField( + "URL", + validators=[ + DataRequired(message="Cannot be empty"), + Regexp(regex="^https.*", message="Must be a valid https URL"), + ], + ) + + def validate(self, *args, **kwargs): + return super().validate(*args, **kwargs) or self.url.data == "" + + class SMSPrefixForm(StripWhitespaceForm): enabled = OnOffField("") # label is assigned on instantiation diff --git a/app/main/views/index.py b/app/main/views/index.py index 4bfad425a5..f1c074634c 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -15,7 +15,7 @@ from app import status_api_client from app.formatters import message_count from app.main import main -from app.main.forms import FieldWithNoneOption +from app.main.forms import FieldWithNoneOption, UrlForm from app.main.views.sub_navigation_dictionaries import features_nav, using_notify_nav from app.models.branding import EmailBranding from app.models.letter_rates import LetterRates @@ -159,6 +159,17 @@ def guidance_api_documentation(): ) +@main.route("/using-notify/api-documentation/section") +def guidance_api_documentation_section(): + + form = UrlForm() + return render_template( + "views/guidance/using-notify/api-documentation-section.html", + navigation_links=using_notify_nav(), + form=form, + ) + + @main.route("/using-notify/attach-pages") def guidance_attach_pages(): return render_template( diff --git a/app/navigation.py b/app/navigation.py index 6e07885937..06f963c05c 100644 --- a/app/navigation.py +++ b/app/navigation.py @@ -59,6 +59,7 @@ class HeaderNavigation(Navigation): "using-notify": { "guidance_using_notify", "guidance_api_documentation", + "guidance_api_documentation_section", "guidance_attach_pages", "guidance_bulk_sending", "guidance_data_retention_period", diff --git a/app/templates/views/guidance/using-notify/api-documentation-section.html b/app/templates/views/guidance/using-notify/api-documentation-section.html new file mode 100644 index 0000000000..4dfbdfab01 --- /dev/null +++ b/app/templates/views/guidance/using-notify/api-documentation-section.html @@ -0,0 +1,27 @@ +{% extends "content_template.html" %} +{% from "components/form.html" import form_wrapper %} +{% from "components/page-footer.html" import page_footer %} + +{# Used by the content_template.html layout, prefixes the "navigation" accessible name #} +{% set navigation_label_prefix = 'Using Notify' %} +{% set page_title = "Send a link to an API docs section" %} + + +{% block per_page_title %} +{{page_title}} +{% endblock %} + +{% block content_column_content %} + +

{{page_title}}

+

Below, copy paste the full URL for docs section you would like to send to a service user, and click "Submit".

+ +

You will be taken to +a landing page for that section. Send link to that page to your user, and they will be able to choose which language version they want to see.

+ +{% call form_wrapper() %} + {{ form.url }} + {{ page_footer('Submit') }} +{% endcall %} + +{% endblock %} diff --git a/tests/app/main/views/test_index.py b/tests/app/main/views/test_index.py index e302b15dc1..aee491d9e0 100644 --- a/tests/app/main/views/test_index.py +++ b/tests/app/main/views/test_index.py @@ -384,3 +384,10 @@ def test_trial_mode_sending_limits(client_request): "send 50 text messages per day", "create letter templates, but not send them", ] + + +def test_GET_guidance_api_documentation_section(client_request): + page = client_request.get("main.guidance_api_documentation_section") + + assert page.select_one("h1").text == "Send a link to an API docs section" + assert page.select_one("input", attrs={"type": "text"})["name"] == "url" From 78a2dfaf78a3f1434c61da2f0b2bb9b6fe8a5863 Mon Sep 17 00:00:00 2001 From: Pea Tyczynska Date: Tue, 1 Oct 2024 16:34:20 +0100 Subject: [PATCH 2/9] On POST, redirect to Choose Guidance Version page with section_tag attribute. --- app/main/views/index.py | 12 +++++++++++- tests/app/main/views/test_index.py | 11 +++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/main/views/index.py b/app/main/views/index.py index f1c074634c..1137104abb 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -159,10 +159,15 @@ def guidance_api_documentation(): ) -@main.route("/using-notify/api-documentation/section") +@main.route("/using-notify/api-documentation/section", methods=["GET", "POST"]) def guidance_api_documentation_section(): form = UrlForm() + + if form.validate_on_submit(): + section_tag = form.url.data.split("#")[-1] + return redirect(url_for(".guidance_api_documentation_choose", section_tag=section_tag)) + return render_template( "views/guidance/using-notify/api-documentation-section.html", navigation_links=using_notify_nav(), @@ -170,6 +175,11 @@ def guidance_api_documentation_section(): ) +@main.route("/using-notify/api-documentation/choose") +def guidance_api_documentation_choose(): + pass + + @main.route("/using-notify/attach-pages") def guidance_attach_pages(): return render_template( diff --git a/tests/app/main/views/test_index.py b/tests/app/main/views/test_index.py index aee491d9e0..be356c23b7 100644 --- a/tests/app/main/views/test_index.py +++ b/tests/app/main/views/test_index.py @@ -391,3 +391,14 @@ def test_GET_guidance_api_documentation_section(client_request): assert page.select_one("h1").text == "Send a link to an API docs section" assert page.select_one("input", attrs={"type": "text"})["name"] == "url" + + +def test_POST_guidance_api_documentation_section(client_request): + client_request.post( + "main.guidance_api_documentation_section", + _data={"url": "https://docs.notifications.service.gov.uk/python.html#send-a-file-by-email"}, + _expected_redirect=url_for( + "main.guidance_api_documentation_choose", + section_tag="send-a-file-by-email", + ), + ) From 3f77febfc2b0b823cdee1fd7e91085557ea46e06 Mon Sep 17 00:00:00 2001 From: Pea Tyczynska Date: Tue, 1 Oct 2024 18:09:59 +0100 Subject: [PATCH 3/9] Validate URL - must be a link to Notify docs taht goes to a section in those docs. --- app/main/forms.py | 12 +++++++++++- tests/app/main/views/test_index.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/app/main/forms.py b/app/main/forms.py index 79b2cf8b87..2868800402 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -5,6 +5,7 @@ from functools import partial from itertools import chain from numbers import Number +from urllib.parse import urlparse import pytz from flask import request @@ -2290,7 +2291,16 @@ class UrlForm(StripWhitespaceForm): ) def validate(self, *args, **kwargs): - return super().validate(*args, **kwargs) or self.url.data == "" + self.url.validators.append(self.check_url) + return super().validate(*args, **kwargs) + + def check_url(self, *args, **kwargs): + parsed_url = urlparse(self.url.data) + + if parsed_url.hostname == "docs.notifications.service.gov.uk" and parsed_url.fragment: + return parsed_url + else: + raise ValidationError("Must be a valid https URL, pointing to a section within the GOV.UK Notify API docs.") class SMSPrefixForm(StripWhitespaceForm): diff --git a/tests/app/main/views/test_index.py b/tests/app/main/views/test_index.py index be356c23b7..1ad901c288 100644 --- a/tests/app/main/views/test_index.py +++ b/tests/app/main/views/test_index.py @@ -402,3 +402,31 @@ def test_POST_guidance_api_documentation_section(client_request): section_tag="send-a-file-by-email", ), ) + + +@pytest.mark.parametrize( + "url, expected_error_message", + [ + ["", "Cannot be empty"], # empty string + [ + "https://docs.notifications.service.gov.uk/python.html", + "Must be a valid https URL, pointing to a section within the GOV.UK Notify API docs.", + ], # no section + [ + "https://docs.payments.service.gov.uk/making_payments/#creating-a-payment", + "Must be a valid https URL, pointing to a section within the GOV.UK Notify API docs.", + ], # URL is notfor Notify's docs + [ + "http://docs.notifications.service.gov.uk/python.html#send-a-file-by-email", + "Must be a valid https URL", + ], # http instead of https + ], +) +def test_POST_guidance_api_documentation_section_with_incorrect_url(client_request, url, expected_error_message): + page = client_request.post( + "main.guidance_api_documentation_section", + _data={"url": url}, + _expected_status=200, + ) + + assert expected_error_message in page.select_one(".govuk-error-message").text From 720318df8d7940709357893cb5aedc5e67705464 Mon Sep 17 00:00:00 2001 From: Pea Tyczynska Date: Thu, 3 Oct 2024 11:23:24 +0100 Subject: [PATCH 4/9] Add page where user can choose version of docs to view section in --- app/main/forms.py | 17 ++++++++++++ app/main/views/index.py | 15 +++++++---- ...api-documentation-section-choose-docs.html | 23 ++++++++++++++++ tests/app/main/views/test_index.py | 26 ++++++++++++++++++- 4 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 app/templates/views/guidance/using-notify/api-documentation-section-choose-docs.html diff --git a/app/main/forms.py b/app/main/forms.py index 2868800402..e201b630b5 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -2303,6 +2303,23 @@ def check_url(self, *args, **kwargs): raise ValidationError("Must be a valid https URL, pointing to a section within the GOV.UK Notify API docs.") +class ChooseDocsForm(StripWhitespaceForm): + docs_version = GovukRadiosField( + "Which version of the docs would you like to view?", + choices=[ + ("python", "Python"), + ("ruby", "Ruby"), + ("java", "Java"), + ("node", "Node JS"), + ("net", ".Net"), + ("php", "PHP"), + ("rest-api", "Rest API"), + ("rest-api", "Any is fine"), + ], + thing="a language version of GOV.UK Notify's API docs", + ) + + class SMSPrefixForm(StripWhitespaceForm): enabled = OnOffField("") # label is assigned on instantiation diff --git a/app/main/views/index.py b/app/main/views/index.py index 1137104abb..895674cc26 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -15,7 +15,7 @@ from app import status_api_client from app.formatters import message_count from app.main import main -from app.main.forms import FieldWithNoneOption, UrlForm +from app.main.forms import ChooseDocsForm, FieldWithNoneOption, UrlForm from app.main.views.sub_navigation_dictionaries import features_nav, using_notify_nav from app.models.branding import EmailBranding from app.models.letter_rates import LetterRates @@ -166,7 +166,7 @@ def guidance_api_documentation_section(): if form.validate_on_submit(): section_tag = form.url.data.split("#")[-1] - return redirect(url_for(".guidance_api_documentation_choose", section_tag=section_tag)) + return redirect(url_for(".guidance_api_documentation_section_choose_docs", section_tag=section_tag)) return render_template( "views/guidance/using-notify/api-documentation-section.html", @@ -175,9 +175,14 @@ def guidance_api_documentation_section(): ) -@main.route("/using-notify/api-documentation/choose") -def guidance_api_documentation_choose(): - pass +@main.route("/using-notify/api-documentation/section/choose-docs") +def guidance_api_documentation_section_choose_docs(): + form = ChooseDocsForm() + return render_template( + "views/guidance/using-notify/api-documentation-section-choose-docs.html", + navigation_links=using_notify_nav(), + form=form, + ) @main.route("/using-notify/attach-pages") diff --git a/app/templates/views/guidance/using-notify/api-documentation-section-choose-docs.html b/app/templates/views/guidance/using-notify/api-documentation-section-choose-docs.html new file mode 100644 index 0000000000..ed97bd22fc --- /dev/null +++ b/app/templates/views/guidance/using-notify/api-documentation-section-choose-docs.html @@ -0,0 +1,23 @@ +{% extends "content_template.html" %} +{% from "components/form.html" import form_wrapper %} +{% from "components/page-footer.html" import page_footer %} + +{# Used by the content_template.html layout, prefixes the "navigation" accessible name #} +{% set navigation_label_prefix = 'Using Notify' %} +{% set page_title = "You have been sent a link to GOV.UK Notify API documentation" %} + + +{% block per_page_title %} +{{page_title}} +{% endblock %} + +{% block content_column_content %} + +

{{page_title}}

+ +{% call form_wrapper() %} +{{ form.docs_version }} +{{ page_footer("Let's go!") }} +{% endcall %} + +{% endblock %} diff --git a/tests/app/main/views/test_index.py b/tests/app/main/views/test_index.py index 1ad901c288..b1ab0e716e 100644 --- a/tests/app/main/views/test_index.py +++ b/tests/app/main/views/test_index.py @@ -386,6 +386,20 @@ def test_trial_mode_sending_limits(client_request): ] +def test_guidance_api_documentation_links_to_section_flow_for_platform_admins(client_request, platform_admin_user): + client_request.login(platform_admin_user) + + page = client_request.get("main.guidance_api_documentation") + + assert len(page.select('a[href^="{link}"]'.format(link=url_for(".guidance_api_documentation_section")))) == 1 + + +def test_guidance_api_documentation_does_not_link_to_section_flow_for_non_platform_admins(client_request): + page = client_request.get("main.guidance_api_documentation") + + assert len(page.select('a[href^="{link}"]'.format(link=url_for(".guidance_api_documentation_section")))) == 0 + + def test_GET_guidance_api_documentation_section(client_request): page = client_request.get("main.guidance_api_documentation_section") @@ -398,7 +412,7 @@ def test_POST_guidance_api_documentation_section(client_request): "main.guidance_api_documentation_section", _data={"url": "https://docs.notifications.service.gov.uk/python.html#send-a-file-by-email"}, _expected_redirect=url_for( - "main.guidance_api_documentation_choose", + "main.guidance_api_documentation_section_choose_docs", section_tag="send-a-file-by-email", ), ) @@ -430,3 +444,13 @@ def test_POST_guidance_api_documentation_section_with_incorrect_url(client_reque ) assert expected_error_message in page.select_one(".govuk-error-message").text + + +def test_GET_guidance_api_documentation_section_choose_docs(client_request): + page = client_request.get("main.guidance_api_documentation_section_choose_docs", section_tag="send-a-file-by-email") + + assert page.select_one("h1").text == "You have been sent a link to GOV.UK Notify API documentation" + + assert ["python", "ruby", "java", "node", "net", "php", "rest-api", "rest-api"] == [ + radio["value"] for radio in page.select("input[type=radio]") + ] From cde01bfcca08b643abb06d79d94deab9dab3602a Mon Sep 17 00:00:00 2001 From: Pea Tyczynska Date: Thu, 3 Oct 2024 11:41:23 +0100 Subject: [PATCH 5/9] Pass the section tag in form's hidden field - so it's available for POST request --- app/main/forms.py | 5 +++++ app/main/views/index.py | 2 +- tests/app/main/views/test_index.py | 5 +++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/main/forms.py b/app/main/forms.py index e201b630b5..464732cfe4 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -2304,6 +2304,10 @@ def check_url(self, *args, **kwargs): class ChooseDocsForm(StripWhitespaceForm): + + def __init__(self, section_tag): + super().__init__(section_tag=section_tag) + docs_version = GovukRadiosField( "Which version of the docs would you like to view?", choices=[ @@ -2318,6 +2322,7 @@ class ChooseDocsForm(StripWhitespaceForm): ], thing="a language version of GOV.UK Notify's API docs", ) + section_tag = HiddenField("section tag") class SMSPrefixForm(StripWhitespaceForm): diff --git a/app/main/views/index.py b/app/main/views/index.py index 895674cc26..6f8c864d5c 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -177,7 +177,7 @@ def guidance_api_documentation_section(): @main.route("/using-notify/api-documentation/section/choose-docs") def guidance_api_documentation_section_choose_docs(): - form = ChooseDocsForm() + form = ChooseDocsForm(section_tag=request.args.get("section_tag")) return render_template( "views/guidance/using-notify/api-documentation-section-choose-docs.html", navigation_links=using_notify_nav(), diff --git a/tests/app/main/views/test_index.py b/tests/app/main/views/test_index.py index b1ab0e716e..29c45f80ea 100644 --- a/tests/app/main/views/test_index.py +++ b/tests/app/main/views/test_index.py @@ -454,3 +454,8 @@ def test_GET_guidance_api_documentation_section_choose_docs(client_request): assert ["python", "ruby", "java", "node", "net", "php", "rest-api", "rest-api"] == [ radio["value"] for radio in page.select("input[type=radio]") ] + form = page.select_one("form") + assert form["action"] == url_for( + "main.guidance_api_documentation_section_choose_docs", + section_tag="send-a-file-by-email", + ) From 4fecb24a0931047f7b484a41515043ee01084bbf Mon Sep 17 00:00:00 2001 From: Pea Tyczynska Date: Thu, 3 Oct 2024 11:47:23 +0100 Subject: [PATCH 6/9] Let user choose which version of Notify docs they want to view When sending them a link to a particular section in the docs. We do this as often when sending service users links to a section in the docs, we don't know which of our clients they use, if any. I also included "Any is fine" option to choose, for users who aren't very technical, but would still like to see the docs. It might be hard for these users to choose from a list of options they are not familiar with. Hence, I provided a "comfort" option for them. Selecting this will take them to rest API docs. --- app/main/views/index.py | 9 ++++++++- tests/app/main/views/test_index.py | 8 ++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/main/views/index.py b/app/main/views/index.py index 6f8c864d5c..13e5cf2b01 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -175,9 +175,16 @@ def guidance_api_documentation_section(): ) -@main.route("/using-notify/api-documentation/section/choose-docs") +@main.route("/using-notify/api-documentation/section/choose-docs", methods=["GET", "POST"]) def guidance_api_documentation_section_choose_docs(): form = ChooseDocsForm(section_tag=request.args.get("section_tag")) + + if form.validate_on_submit(): + redirect_url = ( + f"https://docs.notifications.service.gov.uk/{form.docs_version.data}.html#{form.section_tag.data}" + ) + return redirect(redirect_url) + return render_template( "views/guidance/using-notify/api-documentation-section-choose-docs.html", navigation_links=using_notify_nav(), diff --git a/tests/app/main/views/test_index.py b/tests/app/main/views/test_index.py index 29c45f80ea..2442d15a0d 100644 --- a/tests/app/main/views/test_index.py +++ b/tests/app/main/views/test_index.py @@ -459,3 +459,11 @@ def test_GET_guidance_api_documentation_section_choose_docs(client_request): "main.guidance_api_documentation_section_choose_docs", section_tag="send-a-file-by-email", ) + + +def test_POST_guidance_api_documentation_section_choose_docs(client_request): + client_request.post( + "main.guidance_api_documentation_section_choose_docs", + _data={"docs_version": "python", "section_tag": "send-a-file-by-email"}, + _expected_redirect="https://docs.notifications.service.gov.uk/python.html#send-a-file-by-email", + ) From 7c0e5a22a4bec7b3649508103c61a7bc906e4057 Mon Sep 17 00:00:00 2001 From: Pea Tyczynska Date: Thu, 3 Oct 2024 14:45:41 +0100 Subject: [PATCH 7/9] Link to send section flow from API Documentation guidance page The link is only visible to platform admins. --- .../views/guidance/using-notify/api-documentation.html | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/templates/views/guidance/using-notify/api-documentation.html b/app/templates/views/guidance/using-notify/api-documentation.html index 764be8d091..c33789d2df 100644 --- a/app/templates/views/guidance/using-notify/api-documentation.html +++ b/app/templates/views/guidance/using-notify/api-documentation.html @@ -10,6 +10,16 @@ {% block content_column_content %}

Documentation

+ + {% if current_user.platform_admin %} + + Send a link to docs section + +

+ {% endif %} +

This documentation is for developers who want to integrate the GOV.UK Notify API with a web application or back office system.

Client libraries

Links to documentation open in a new tab.

From bfe101b17bacb6f0ce72c8637cceb44c0875a8e6 Mon Sep 17 00:00:00 2001 From: Pea Tyczynska Date: Thu, 3 Oct 2024 17:19:41 +0100 Subject: [PATCH 8/9] Fix navigation-related tests --- app/navigation.py | 1 + tests/app/test_navigation.py | 2 ++ tests/route-list.json | 2 ++ 3 files changed, 5 insertions(+) diff --git a/app/navigation.py b/app/navigation.py index 06f963c05c..1114501bdd 100644 --- a/app/navigation.py +++ b/app/navigation.py @@ -60,6 +60,7 @@ class HeaderNavigation(Navigation): "guidance_using_notify", "guidance_api_documentation", "guidance_api_documentation_section", + "guidance_api_documentation_section_choose_docs", "guidance_attach_pages", "guidance_bulk_sending", "guidance_data_retention_period", diff --git a/tests/app/test_navigation.py b/tests/app/test_navigation.py index 3927cb1010..7e0feef642 100644 --- a/tests/app/test_navigation.py +++ b/tests/app/test_navigation.py @@ -130,6 +130,8 @@ "go_to_dashboard_after_tour", "guest_list", "guidance_api_documentation", + "guidance_api_documentation_section", + "guidance_api_documentation_section_choose_docs", "guidance_attach_pages", "guidance_billing_details", "guidance_bulk_sending", diff --git a/tests/route-list.json b/tests/route-list.json index 7c72a22848..e9c41f9a2a 100644 --- a/tests/route-list.json +++ b/tests/route-list.json @@ -379,6 +379,8 @@ "/users//change_auth", "/using-notify", "/using-notify/api-documentation", + "/using-notify/api-documentation/section", + "/using-notify/api-documentation/section/choose-docs", "/using-notify/attach-pages", "/using-notify/bulk-sending", "/using-notify/data-retention-period", From 585b7951ea1a1d7332a9c0939b6c1cf0fa3b225f Mon Sep 17 00:00:00 2001 From: karlchillmaid Date: Wed, 16 Oct 2024 21:24:00 +0100 Subject: [PATCH 9/9] Update page title and h1 --- .../views/guidance/using-notify/api-documentation.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/views/guidance/using-notify/api-documentation.html b/app/templates/views/guidance/using-notify/api-documentation.html index c33789d2df..33f1bc03b2 100644 --- a/app/templates/views/guidance/using-notify/api-documentation.html +++ b/app/templates/views/guidance/using-notify/api-documentation.html @@ -4,12 +4,12 @@ {% set navigation_label_prefix = 'Using Notify' %} {% block per_page_title %} - Documentation + API documentation {% endblock %} {% block content_column_content %} -

Documentation

+

API documentation

{% if current_user.platform_admin %}