From 13a1048d7212f6c23fdbe2695575f0e915713970 Mon Sep 17 00:00:00 2001 From: israr-ulhaq Date: Fri, 12 Jul 2024 12:39:37 +0100 Subject: [PATCH 1/2] updated home page content --- app/main/forms.py | 11 +-- app/static/src/css/custom.css | 3 + app/templates/main/download.html | 129 +++++++++++++++---------------- 3 files changed, 72 insertions(+), 71 deletions(-) diff --git a/app/main/forms.py b/app/main/forms.py index 5683149..a5c08ba 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -1,5 +1,5 @@ from flask_wtf import FlaskForm -from govuk_frontend_wtf.wtforms_widgets import GovRadioInput, GovSelect, GovSubmitInput +from govuk_frontend_wtf.wtforms_widgets import GovRadioInput, GovSubmitInput from wtforms.fields import RadioField, SelectField, SubmitField from wtforms.validators import AnyOf, InputRequired @@ -24,16 +24,17 @@ class CookiesForm(FlaskForm): class DownloadForm(FlaskForm): file_format = SelectField( - "File type", - widget=GovSelect(), + "Which format do you need?", + widget=GovRadioInput(), validators=[AnyOf(["json", "xlsx"])], choices=[ - ("xlsx", "XSLX (Excel)"), + ("xlsx", "XLSX (Microsoft Excel)"), ("json", "JSON"), ], default=None, + render_kw={"class": "govuk-fieldset__legend"}, ) - download = SubmitField("Download", widget=GovSubmitInput()) + download = SubmitField("Confirm and request data", widget=GovSubmitInput()) class RetrieveForm(FlaskForm): diff --git a/app/static/src/css/custom.css b/app/static/src/css/custom.css index 30da754..68eae0c 100644 --- a/app/static/src/css/custom.css +++ b/app/static/src/css/custom.css @@ -15,6 +15,9 @@ justify-content: center; } +.govuk-fieldset__legend { + font-weight: bold; +} .global-actions { display: flex; diff --git a/app/templates/main/download.html b/app/templates/main/download.html index d8f4933..daa0065 100644 --- a/app/templates/main/download.html +++ b/app/templates/main/download.html @@ -14,74 +14,71 @@ {% endblock beforeContent %} {% block content %} -

Download monitoring and evaluation data

+
+
+

Get monitoring and evaluation data

+

Use the filters to select the data you need.

+

You will get all of the data from all projects and programmes if you do not select any filters.

+
-

Use the following filters to select the projects you need.

+ {{ form.csrf_token }} -

The filters you select will be applied at project level. You will be able to download this as a file to your desktop.

+ {{ govukAccordion({ + "id": "accordion-download", + "headingLevel": 2, + "showAllSectionsText": "", + "items": [ + { + "heading": { + "text": "Filter by fund" + }, + "content": { + "html": checkboxItems(funds["name"], funds["items"]) + }, + }, + { + "heading": { + "text": "Filter by region" + }, + "content": { + "html": checkboxItems(regions["name"], regions["items"]) + } + }, + { + "heading": { + "text": "Filter by funded organisation" + }, + "content": { + "html": checkboxItems(orgs["name"], orgs["items"]) + } + }, + { + "heading": { + "text": "Filter by outcomes" + }, + "content": { + "html": checkboxItems(outcomes["name"], outcomes["items"]) + } + }, + { + "heading": { + "text": "Filter by returns period" + }, + "content": { + "html": selectItems(returnsParams) + } + } + ] + }) + }} -

- If you don't select any filters you will get all the data from all of the projects that are in the system. -

+ {{ form.file_format }} + {{ form.download }} - - {{ form.csrf_token }} - - {{ govukAccordion({ - "id": "accordion-download", - "headingLevel": 2, - "showAllSectionsText": "", - "items": [ - { - "heading": { - "text": "Filter by fund" - }, - "content": { - "html": checkboxItems(funds["name"], funds["items"]) - }, - }, - { - "heading": { - "text": "Filter by region" - }, - "content": { - "html": checkboxItems(regions["name"], regions["items"]) - } - }, - { - "heading": { - "text": "Filter by funded organisation" - }, - "content": { - "html": checkboxItems(orgs["name"], orgs["items"]) - } - }, - { - "heading": { - "text": "Filter by outcomes" - }, - "content": { - "html": checkboxItems(outcomes["name"], outcomes["items"]) - } - }, - { - "heading": { - "text": "Filter by returns period" - }, - "content": { - "html": selectItems(returnsParams) - } - } - ] - }) - }} - - {{ form.file_format }} - {{ form.download }} - -
- -

- Access the funding glossary -

+ +

+ Access the funding glossary +

+
+
{% endblock content %} From cc31b6c5830e6ed03d0e3c3e868fbd74afd11459 Mon Sep 17 00:00:00 2001 From: israr-ulhaq Date: Mon, 15 Jul 2024 16:37:13 +0100 Subject: [PATCH 2/2] added error summary added unit tests Unit tests updated --- app/main/forms.py | 4 +- app/main/routes.py | 119 +++++++++++++++++-------------- app/static/src/css/custom.css | 3 - app/templates/main/download.html | 40 +++++++++-- tests/test_routes.py | 42 +++++++---- 5 files changed, 132 insertions(+), 76 deletions(-) diff --git a/app/main/forms.py b/app/main/forms.py index a5c08ba..85240fa 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -1,7 +1,7 @@ from flask_wtf import FlaskForm from govuk_frontend_wtf.wtforms_widgets import GovRadioInput, GovSubmitInput from wtforms.fields import RadioField, SelectField, SubmitField -from wtforms.validators import AnyOf, InputRequired +from wtforms.validators import InputRequired class CookiesForm(FlaskForm): @@ -26,13 +26,11 @@ class DownloadForm(FlaskForm): file_format = SelectField( "Which format do you need?", widget=GovRadioInput(), - validators=[AnyOf(["json", "xlsx"])], choices=[ ("xlsx", "XLSX (Microsoft Excel)"), ("json", "JSON"), ], default=None, - render_kw={"class": "govuk-fieldset__legend"}, ) download = SubmitField("Confirm and request data", widget=GovSubmitInput()) diff --git a/app/main/routes.py b/app/main/routes.py index d46ca73..4636d67 100644 --- a/app/main/routes.py +++ b/app/main/routes.py @@ -69,65 +69,76 @@ def download(): ) if request.method == "POST": - file_format = form.file_format.data - if file_format not in ["json", "xlsx"]: - current_app.logger.error( - "Unexpected file format requested from /download: {file_format}", - extra=dict(file_format=file_format), + if form.validate_on_submit(): + file_format = form.file_format.data + orgs = request.form.getlist(FormNames.ORGS) + regions = request.form.getlist(FormNames.REGIONS) + funds = request.form.getlist(FormNames.FUNDS) + outcome_categories = request.form.getlist(FormNames.OUTCOMES) + from_quarter = request.form.get("from-quarter") + from_year = request.form.get("from-year") + to_quarter = request.form.get("to-quarter") + to_year = request.form.get("to-year") + + reporting_period_start = ( + financial_quarter_from_mapping(quarter=from_quarter, year=from_year) if to_quarter and to_year else None ) - return abort(500), f"Unknown file format: {file_format}" - - orgs = request.form.getlist(FormNames.ORGS) - regions = request.form.getlist(FormNames.REGIONS) - funds = request.form.getlist(FormNames.FUNDS) - outcome_categories = request.form.getlist(FormNames.OUTCOMES) - from_quarter = request.form.get("from-quarter") - from_year = request.form.get("from-year") - to_quarter = request.form.get("to-quarter") - to_year = request.form.get("to-year") - - reporting_period_start = ( - financial_quarter_from_mapping(quarter=from_quarter, year=from_year) if to_quarter and to_year else None - ) - reporting_period_end = ( - financial_quarter_to_mapping(quarter=to_quarter, year=to_year) if to_quarter and to_year else None - ) + reporting_period_end = ( + financial_quarter_to_mapping(quarter=to_quarter, year=to_year) if to_quarter and to_year else None + ) + + query_params = {"email_address": g.user.email, "file_format": file_format} + if orgs: + query_params["organisations"] = orgs + if regions: + query_params["regions"] = regions + if funds: + query_params["funds"] = funds + if outcome_categories: + query_params["outcome_categories"] = outcome_categories + if reporting_period_start: + query_params["rp_start"] = reporting_period_start + if reporting_period_end: + query_params["rp_end"] = reporting_period_end + + status_code = process_async_download(query_params) + + current_app.logger.info( + "Request for download by {user_id} with {query_params}", + extra={ + "user_id": g.account_id, + "email": g.user.email, + "query_params": query_params, + "request_type": "download", + }, + ) - query_params = {"email_address": g.user.email, "file_format": file_format} - if orgs: - query_params["organisations"] = orgs - if regions: - query_params["regions"] = regions - if funds: - query_params["funds"] = funds - if outcome_categories: - query_params["outcome_categories"] = outcome_categories - if reporting_period_start: - query_params["rp_start"] = reporting_period_start - if reporting_period_end: - query_params["rp_end"] = reporting_period_end - - status_code = process_async_download(query_params) - - current_app.logger.info( - "Request for download by {user_id} with {query_params}", - extra={ - "user_id": g.account_id, - "email": g.user.email, - "query_params": query_params, - "request_type": "download", - }, + if status_code == 204: + return redirect(url_for("main.request_received")) + else: + current_app.logger.error( + "Response status code from data-store/trigger_async_download: {content_type}", + extra=dict(content_type=status_code), + ) + return render_template("500.html") + + current_app.logger.error( + "Unexpected file format requested from /download: {file_format}", + extra=dict(file_format=form.file_format), ) + if form.file_format.errors: + form.file_format.errors = ["Select a file format"] - if status_code == 204: - return redirect(url_for("main.request_received")) - else: - current_app.logger.error( - "Response status code from data-store/trigger_async_download: {content_type}", - extra=dict(content_type=status_code), - ) - return render_template("500.html") + return render_template( + "download.html", + form=form, + funds=get_fund_checkboxes(), + regions=get_region_checkboxes(), + orgs=get_org_checkboxes(), + outcomes=get_outcome_checkboxes(), + returnsParams=get_returns(), + ) @bp.route("/request-received", methods=["GET"]) diff --git a/app/static/src/css/custom.css b/app/static/src/css/custom.css index 68eae0c..30da754 100644 --- a/app/static/src/css/custom.css +++ b/app/static/src/css/custom.css @@ -15,9 +15,6 @@ justify-content: center; } -.govuk-fieldset__legend { - font-weight: bold; -} .global-actions { display: flex; diff --git a/app/templates/main/download.html b/app/templates/main/download.html index daa0065..9d4613e 100644 --- a/app/templates/main/download.html +++ b/app/templates/main/download.html @@ -5,6 +5,7 @@ {%- from 'govuk_frontend_jinja/components/select/macro.html' import govukSelect -%} {%- from 'govuk_frontend_jinja/components/button/macro.html' import govukButton -%} {%- from 'govuk_frontend_jinja/components/label/macro.html' import govukLabel -%} +{%- from 'govuk_frontend_jinja/components/error-summary/macro.html' import govukErrorSummary -%} {%- from "outcomeCheckboxes.html" import outcomeCheckboxItems -%} {%- from "checkboxes.html" import checkboxItems -%} {%- from "select.html" import selectItems -%} @@ -16,10 +17,24 @@ {% block content %}
+ {% if form.errors %} + {{ govukErrorSummary ({ + "titleText": "There is a problem", + "errorList": [ + { + "text": "Select a file format", + "href": "#"+form.file_format.id + }, + + ] + }) + }} + + {% endif %}

Get monitoring and evaluation data

Use the filters to select the data you need.

You will get all of the data from all projects and programmes if you do not select any filters.

-
+ {{ form.csrf_token }} @@ -71,14 +86,31 @@

Get monitoring and evaluation data

] }) }} - - {{ form.file_format }} +
+

We will:

+
    +
  • send you an email to the address you’ve signed in with
  • +
  • include a link in the email for you to download your data
  • +
+
+ {{ + form.file_format( + params={ + "fieldset": { + "legend": { + "text": form.file_format.label.text, + "classes": "govuk-!-font-weight-bold" + }, + } + } + ) + }} {{ form.download }} -

Access the funding glossary

+ {% endblock content %} diff --git a/tests/test_routes.py b/tests/test_routes.py index 3b98736..5630a05 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -76,23 +76,41 @@ def test_async_download_redirect_error(flask_test_client, mocked_failing_downloa assert b"Try again later." in response.data -def test_download_post_unknown_format(flask_test_client): +def test_download_post_unknown_format(flask_test_client, requests_mock): + requests_mock.get("http://data-store/organisations", json=[]) + requests_mock.get("http://data-store/regions", json=[]) + requests_mock.get("http://data-store/funds", json=[]) + requests_mock.get("http://data-store/outcome-categories", json=[]) + requests_mock.get( + "http://data-store/reporting-period-range", + json={"end_date": "2023-02-01T00:00:00Z", "start_date": "2023-02-12T00:00:00Z"}, + ) response = flask_test_client.post("/download", data={"file_format": "foobar"}) - assert response.status_code == 500 - + assert response.status_code == 200 -def test_download_post_no_format(flask_test_client): - response = flask_test_client.post("/download") - assert response.status_code == 500 + soup = BeautifulSoup(response.text, "html.parser") + error_summary = soup.find("p", {"class": "govuk-error-message"}) + assert error_summary is not None + assert "Select a file format" in error_summary.text -@patch("app.main.download_data.get_response") -def test_download_post_unknown_format_from_api(mock_get_response, flask_test_client): - mock_response = mock_get_response.return_value - mock_response.headers = {"content-type": "InvalidType"} +def test_download_post_no_format(flask_test_client, requests_mock): + requests_mock.get("http://data-store/organisations", json=[]) + requests_mock.get("http://data-store/regions", json=[]) + requests_mock.get("http://data-store/funds", json=[]) + requests_mock.get("http://data-store/outcome-categories", json=[]) + requests_mock.get( + "http://data-store/reporting-period-range", + json={"end_date": "2023-02-01T00:00:00Z", "start_date": "2023-02-12T00:00:00Z"}, + ) + response = flask_test_client.post("/download", data={"file_format": " "}) + assert response.headers["Content-Type"] == "text/html; charset=utf-8" + assert response.status_code == 200 - response = flask_test_client.post("/download?file_format=anything") - assert response.status_code == 500 + soup = BeautifulSoup(response.text, "html.parser") + error_summary = soup.find("p", {"class": "govuk-error-message"}) + assert error_summary is not None + assert "Select a file format" in error_summary.text def test_known_http_error_redirect(flask_test_client):