diff --git a/.ds.baseline b/.ds.baseline index 71169119f..999298014 100644 --- a/.ds.baseline +++ b/.ds.baseline @@ -133,7 +133,7 @@ "filename": ".github/workflows/checks.yml", "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", "is_verified": false, - "line_number": 70, + "line_number": 68, "is_secret": false } ], @@ -684,5 +684,5 @@ } ] }, - "generated_at": "2025-01-22T21:45:39Z" + "generated_at": "2025-02-03T17:01:06Z" } diff --git a/.github/ISSUE_TEMPLATE/issue_template.yml b/.github/ISSUE_TEMPLATE/issue_template.yml index 576af0095..f9cabaa72 100644 --- a/.github/ISSUE_TEMPLATE/issue_template.yml +++ b/.github/ISSUE_TEMPLATE/issue_template.yml @@ -64,6 +64,17 @@ body: validations: required: false + - type: markdown + attributes: + value: '**Accessibility:**' + - type: textarea + id: accessibility + attributes: + label: "List any specific accessibility guidance or tests that need to be considered for this user story." + description: "List what type of accessibility tests need to pass." + validations: + required: false + - type: markdown attributes: value: '**Notes:**' diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 7ffbbc290..a659829b2 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -23,5 +23,7 @@ Please enter a detailed description here. ## A11y Checks (if applicable) -* Conduct automated tests through [AxeDevTools](https://www.deque.com/axe/devtools/) and [WAVE](https://wave.webaim.org/) +* Double check work is getting picked up by the automated E2E tests +* Conduct browser-based tests through [AxeDevTools](https://www.deque.com/axe/devtools/) and [WAVE](https://wave.webaim.org/) * Review the [Manual Checklist](https://docs.google.com/document/d/192bBXStebdXWtYhZQ73qaWMJhGcuSB1W6c9YBXhWZvc/edit?usp=sharing) +* Make sure there are no linting errors in VSCode or other IDE of choice diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 49e24566a..a75144642 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -165,8 +165,9 @@ jobs: run: make run-flask & env: NOTIFY_ENVIRONMENT: scanning + FEATURE_ABOUT_PAGE_ENABLED: true - name: Run OWASP Baseline Scan - uses: zaproxy/action-baseline@v0.9.0 + uses: zaproxy/action-baseline@v0.14.0 with: docker_name: "ghcr.io/zaproxy/zaproxy:weekly" target: "http://localhost:6012" diff --git a/.github/workflows/daily_checks.yml b/.github/workflows/daily_checks.yml index a5e81a137..b24a71738 100644 --- a/.github/workflows/daily_checks.yml +++ b/.github/workflows/daily_checks.yml @@ -50,7 +50,7 @@ jobs: env: NOTIFY_ENVIRONMENT: scanning - name: Run OWASP Full Scan - uses: zaproxy/action-full-scan@v0.7.0 + uses: zaproxy/action-full-scan@v0.12.0 with: docker_name: 'ghcr.io/zaproxy/zaproxy:weekly' target: 'http://localhost:6012' diff --git a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss index 0bb1aab7e..ebce95061 100644 --- a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss +++ b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss @@ -1024,3 +1024,7 @@ nav.nav { font-size: units(3); font-weight: bold; } + +.form-control-error { + border: 4px solid #b10e1e +} diff --git a/app/notify_client/billing_api_client.py b/app/notify_client/billing_api_client.py index b1ffc19f0..363764907 100644 --- a/app/notify_client/billing_api_client.py +++ b/app/notify_client/billing_api_client.py @@ -6,17 +6,36 @@ from app.notify_client import NotifyAdminAPIClient class BillingAPIClient(NotifyAdminAPIClient): def get_monthly_usage_for_service(self, service_id, year): - return self.get( + monthly_usage = redis_client.get(f"monthly-usage-summary-{service_id}-{year}") + if monthly_usage is not None: + return json.loads(monthly_usage.decode("utf-8")) + result = self.get( "/service/{0}/billing/monthly-usage".format(service_id), params=dict(year=year), ) + redis_client.set( + f"monthly-usage-summary-{service_id}-{year}", + json.dumps(result), + ex=30, + ) + return result def get_annual_usage_for_service(self, service_id, year=None): - return self.get( + annual_usage = redis_client.get(f"yearly-usage-summary-{service_id}-{year}") + if annual_usage is not None: + return json.loads(annual_usage.decode("utf-8")) + result = self.get( "/service/{0}/billing/yearly-usage-summary".format(service_id), params=dict(year=year), ) + redis_client.set( + f"yearly-usage-summary-{service_id}-{year}", + json.dumps(result), + ex=30, + ) + return result + def get_free_sms_fragment_limit_for_year(self, service_id, year=None): frag_limit = redis_client.get(f"free-sms-fragment-limit-{service_id}-{year}") if frag_limit is not None: @@ -48,13 +67,28 @@ class BillingAPIClient(NotifyAdminAPIClient): ) def get_data_for_billing_report(self, start_date, end_date): - return self.get( + x_start_date = str(start_date) + x_start_date = x_start_date.replace(" ", "_") + x_end_date = str(end_date) + x_end_date = x_end_date.replace(" ", "_") + billing_data = redis_client.get( + f"get-data-for-billing-report-{x_start_date}-{x_end_date}" + ) + if billing_data is not None: + return json.loads(billing_data.decode("utf-8")) + result = self.get( url="/platform-stats/data-for-billing-report", params={ "start_date": str(start_date), "end_date": str(end_date), }, ) + redis_client.set( + f"get-data-for-billing-report-{x_start_date}-{x_end_date}", + json.dumps(result), + ex=30, + ) + return result def get_data_for_volumes_by_service_report(self, start_date, end_date): return self.get( diff --git a/app/notify_client/notification_api_client.py b/app/notify_client/notification_api_client.py index 95ac96a04..89e786079 100644 --- a/app/notify_client/notification_api_client.py +++ b/app/notify_client/notification_api_client.py @@ -1,3 +1,6 @@ +import json + +from app.extensions import redis_client from app.notify_client import NotifyAdminAPIClient, _attach_current_user @@ -41,7 +44,7 @@ class NotificationApiClient(NotifyAdminAPIClient): if job_id: return method( url="/service/{}/job/{}/notifications".format(service_id, job_id), - **kwargs + **kwargs, ) else: if limit_days is not None: @@ -96,9 +99,20 @@ class NotificationApiClient(NotifyAdminAPIClient): ) def get_notification_count_for_job_id(self, *, service_id, job_id): - return self.get( + counts = redis_client.get( + f"notification-count-for-job-id-{service_id}-{job_id}" + ) + if counts is not None: + return json.loads(counts.decode("utf-8")) + result = self.get( url="/service/{}/job/{}/notification_count".format(service_id, job_id) - )["count"] + ) + redis_client.set( + f"notification-count-for-job-id-{service_id}-{job_id}", + json.dumps(result["count"]), + ex=30, + ) + return result["count"] notification_api_client = NotificationApiClient() diff --git a/app/templates/components/components/input/template.njk b/app/templates/components/components/input/template.njk index 7f5634651..4ea649dce 100644 --- a/app/templates/components/components/input/template.njk +++ b/app/templates/components/components/input/template.njk @@ -34,7 +34,7 @@ attributes: params.errorMessage.attributes, html: params.errorMessage.html, text: params.errorMessage.text, - visuallyHiddenText: params.errorMessage.visuallyHiddenText + visuallyHiddenText: params.errorMessage.visuallyHiddenText, }) | indent(2) | trim }} {% endif %} + {%- for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor -%} + {%- if params.required %} required{% endif %} + /> diff --git a/app/templates/components/textbox.html b/app/templates/components/textbox.html index 3e479cbce..fa92d0cf8 100644 --- a/app/templates/components/textbox.html +++ b/app/templates/components/textbox.html @@ -16,19 +16,9 @@ placeholder='' ) %}
- {% if field.errors %} - - {% endif %}
{% endif %} + {% if field.errors %} + + Error: + {% if not safe_error_message %}{{ field.errors[0] }}{% else %}{{ field.errors[0]|safe }}{% endif %} + + {% endif %} {% if highlight_placeholders or autosize %} @@ -59,6 +55,8 @@ data_highlight_placeholders='true' if highlight_placeholders else 'false', rows=rows|string, placeholder=placeholder, + aria_describedby=field.name+"-error", + required='required' if required else None, **kwargs ) }} {% if suffix %} diff --git a/app/templates/views/edit-sms-template.html b/app/templates/views/edit-sms-template.html index 97eac73dc..8ef41bdb2 100644 --- a/app/templates/views/edit-sms-template.html +++ b/app/templates/views/edit-sms-template.html @@ -32,6 +32,8 @@
{{ form.name(param_extensions={ "extra_form_group_classes": "margin-bottom-2", + "id": "name", + "required": True, "hint": {"text": "Your recipients will not see this"} }) }} {{ textbox( @@ -41,7 +43,8 @@ hint=content_hint, rows=5, extra_form_group_classes='margin-bottom-1', - placeholder='Edit me! Check out the Personalization section below for details on cool ((stuff)) you can do with your messages!' + placeholder='Edit me! Check out the Personalization section below for details on cool ((stuff)) you can do with your messages!', + required=True ) }} {% if current_user.platform_admin %} {{ form.process_type }} diff --git a/app/templates/views/send-test.html b/app/templates/views/send-test.html index 720849ae6..fd5eb63db 100644 --- a/app/templates/views/send-test.html +++ b/app/templates/views/send-test.html @@ -37,8 +37,8 @@ data_kwargs={'force-focus': True} ) %}
-