Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into remove-old-phonenumbe…
Browse files Browse the repository at this point in the history
…r-validation
  • Loading branch information
rparke committed Jan 30, 2025
2 parents 2187172 + 88f8ec5 commit 4b47593
Show file tree
Hide file tree
Showing 62 changed files with 298 additions and 323 deletions.
10 changes: 3 additions & 7 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# This file was automatically copied from [email protected].0
# This file was automatically copied from [email protected].1

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
Expand All @@ -9,12 +9,8 @@ repos:
- id: check-yaml
- id: debug-statements
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: 'v0.8.2'
rev: 'v0.9.2'
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- repo: https://github.com/psf/black
rev: 24.10.0
hooks:
- id: black
name: black (python)
- id: ruff-format
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ generate-version-file: ## Generates the app version file
.PHONY: test
test: ## Run tests
ruff check .
black --check .
ruff format --check .
npm test
py.test -n auto --maxfail=10 tests/

Expand All @@ -67,8 +67,10 @@ fix-imports: ## Fix imports using ruff
.PHONY: freeze-requirements
freeze-requirements: ## create static requirements.txt
uv pip compile requirements.in -o requirements.txt
uv pip sync requirements.txt
python -c "from notifications_utils.version_tools import copy_config; copy_config()"
uv pip compile requirements_for_test.in -o requirements_for_test.txt
uv pip sync requirements_for_test.txt

.PHONY: bump-utils
bump-utils: # Bump notifications-utils package to latest version
Expand Down
2 changes: 1 addition & 1 deletion app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ def useful_headers_after_request(response):
"Link",
(
"<{asset_url}>; rel=dns-prefetch, <{asset_url}>; rel=preconnect".format(
asset_url=f'https://{current_app.config["ASSET_DOMAIN"]}'
asset_url=f"https://{current_app.config['ASSET_DOMAIN']}"
)
),
)
Expand Down
8 changes: 8 additions & 0 deletions app/assets/javascripts/esm/all-esm.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ColourPreview from './colour-preview.mjs';
import FileUpload from './file-upload.mjs';
import Autofocus from './autofocus.mjs';
import Homepage from './homepage.mjs';
import PreviewPane from './preview-pane.mjs';

// Modules from 3rd party vendors
import morphdom from 'morphdom';
Expand Down Expand Up @@ -43,6 +44,13 @@ if ($homePage) {
new Homepage($homePage);
}

// this module doesn't currently use "data-notify-module" for initialisation
// should we change that?
const $previewPane = document.querySelector('.govuk-radios__item input[name="branding_style"]:checked');
if ($previewPane) {
new PreviewPane($previewPane);
}

const focusBanner = new FocusBanner();

// ES modules do not export to global so in order to
Expand Down
80 changes: 80 additions & 0 deletions app/assets/javascripts/esm/preview-pane.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { isSupported } from 'govuk-frontend';

// This new way of writing Javascript components is based on the GOV.UK Frontend skeleton Javascript coding standard
// that uses ES2015 Classes -
// https://github.com/alphagov/govuk-frontend/blob/main/docs/contributing/coding-standards/js.md#skeleton
//
// It replaces the previously used way of setting methods on the component's `prototype`.
// We use a class declaration way of defining classes -
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/class
//
// More on ES2015 Classes at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes

class PreviewPane {
constructor($module) {
if (!isSupported()) {
return this;
}

this.branding_style = $module.value;
this.$form = $module.form;
this.$paneWrapper = this.$form.querySelector('.govuk-grid-column-full');
this.previewType = this.$form.dataset.previewType;
this.letterBrandingPreviewRootPath = `templates/${this.previewType}-preview-image`;

this.applyPreviewPane(this.previewType);

this.$form.setAttribute('action', location.pathname.replace(new RegExp(`set-${this.previewType}-branding$`), `preview-${this.previewType}-branding`));
this.$form.querySelector('button').textContent = 'Save';

this.$form.querySelector('fieldset').addEventListener("change", (e) => {
if (e.target.matches('input[name="branding_style"]')) {
this.setPreviewPane(e.target);
}
});
}

applyPreviewPane(previewType) {
previewType === 'letter' ? this.generateImagePreview() : this.generateIframePreview();
}

// we want to generate this just-in-time as otherwise
// once the image is appended, src does a http request
// even if the previewType is letter
generateImagePreview() {
let imagePreviewPane = document.createElement('div');
imagePreviewPane.setAttribute('class','branding-preview-image');
let imagePreviewPaneImage = document.createElement('img');
imagePreviewPaneImage.setAttribute('alt','Preview of selected letter branding');
imagePreviewPaneImage.setAttribute('src', `/${this.letterBrandingPreviewRootPath}?${this.buildQueryString(["branding_style", this.branding_style])}`);
imagePreviewPane.appendChild(imagePreviewPaneImage);
this.$paneWrapper.append(imagePreviewPane);
}

generateIframePreview() {
let iframePreviewPane = document.createElement('iframe');
iframePreviewPane.setAttribute('class','branding-preview');
iframePreviewPane.setAttribute('scrolling','no');
iframePreviewPane.setAttribute('src',`/_${this.previewType}?${this.buildQueryString(['branding_style', this.branding_style])}`);
this.$paneWrapper.append(iframePreviewPane);
}

buildQueryString () {
// we can accept multiple arrays of parameters
// but here we only have 2
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments
const data = Array.from(arguments);
return data.map((val, _idx) => `${encodeURI(val[0])}=${encodeURI(val[1])}`).join('&');
}

setPreviewPane ($target) {
this.branding_style = $target.value;
this.previewType === 'letter' ?
this.$paneWrapper.querySelector('img').setAttribute('src', `/${this.letterBrandingPreviewRootPath}?${this.buildQueryString(['branding_style', this.branding_style])}`)
:
this.$paneWrapper.querySelector('iframe').setAttribute('src', `/_${this.previewType}?${this.buildQueryString(['branding_style', this.branding_style])}`);
;
}
}

export default PreviewPane;
52 changes: 0 additions & 52 deletions app/assets/javascripts/previewPane.js

This file was deleted.

76 changes: 34 additions & 42 deletions app/formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,10 @@ def get_human_day(time, date_prefix=""):


def format_time(date):
return (
{"12:00AM": "Midnight", "12:00PM": "Midday"}
.get(
utc_string_to_aware_gmt_datetime(date).strftime("%-I:%M%p"),
utc_string_to_aware_gmt_datetime(date).strftime("%-I:%M%p"),
)
.lower()
)
return {"12:00AM": "Midnight", "12:00PM": "Midday"}.get(
utc_string_to_aware_gmt_datetime(date).strftime("%-I:%M%p"),
utc_string_to_aware_gmt_datetime(date).strftime("%-I:%M%p"),
).lower()


def format_date(date):
Expand Down Expand Up @@ -201,40 +197,36 @@ def format_notification_status_as_time(status, created, updated):


def format_notification_status_as_field_status(status, notification_type):
return (
return {
"letter": {
"failed": "error",
"technical-failure": "error",
"temporary-failure": "error",
"permanent-failure": "error",
"delivered": None,
"sent": None,
"sending": None,
"created": None,
"accepted": None,
"pending-virus-check": None,
"virus-scan-failed": "error",
"returned-letter": None,
"cancelled": "error",
},
}.get(
notification_type,
{
"letter": {
"failed": "error",
"technical-failure": "error",
"temporary-failure": "error",
"permanent-failure": "error",
"delivered": None,
"sent": None,
"sending": None,
"created": None,
"accepted": None,
"pending-virus-check": None,
"virus-scan-failed": "error",
"returned-letter": None,
"cancelled": "error",
},
}
.get(
notification_type,
{
"failed": "error",
"technical-failure": "error",
"temporary-failure": "error",
"permanent-failure": "error",
"delivered": None,
"sent": "sent-international" if notification_type == "sms" else None,
"sending": "default",
"created": "default",
"pending": "default",
},
)
.get(status, "error")
)
"failed": "error",
"technical-failure": "error",
"temporary-failure": "error",
"permanent-failure": "error",
"delivered": None,
"sent": "sent-international" if notification_type == "sms" else None,
"sending": "default",
"created": "default",
"pending": "default",
},
).get(status, "error")


def format_notification_status_as_url(status, notification_type):
Expand Down Expand Up @@ -276,7 +268,7 @@ def format_pennies_as_currency(pennies: int | float, long: bool) -> str:
pennies = decimal.Decimal(str(pennies))
if pennies >= 100:
pennies = round(pennies)
return f"£{pennies//100:,}.{pennies%100:02}"
return f"£{pennies // 100:,}.{pennies % 100:02}"
elif long:
return f"{pennies} pence"

Expand Down
3 changes: 1 addition & 2 deletions app/main/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2976,8 +2976,7 @@ def __init__(self, is_a_batched_report, report_completed, *args, **kwargs):
def validate_report_has_been_processed(self, field):
if not field.data and not self.report_completed:
raise ValidationError(
"There is a problem. "
"You must confirm that you have removed the email addresses from your mailing list."
"There is a problem. You must confirm that you have removed the email addresses from your mailing list."
)

if field.data and self.report_completed:
Expand Down
14 changes: 4 additions & 10 deletions app/main/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@ def __call__(self, form, field):
message = """
Enter a public sector email address or
<a class="govuk-link govuk-link--no-visited-state" href="{}">find out who can use Notify</a>
""".format(
url_for("main.guidance_who_can_use_notify")
)
""".format(url_for("main.guidance_who_can_use_notify"))
if not is_gov_user(field.data.lower()):
raise ValidationError(message)

Expand Down Expand Up @@ -185,8 +183,7 @@ class IsNotAGenericSenderID:

def __init__(
self,
message="Text message sender ID cannot be Alert, Info or Verify as those are prohibited due to "
"usage by spam",
message="Text message sender ID cannot be Alert, Info or Verify as those are prohibited due to usage by spam",
):
self.message = message

Expand All @@ -196,7 +193,6 @@ def __call__(self, form, field):


class IsNotLikeNHSNoReply:

def __call__(self, form, field):
lower_cased_data = field.data.lower()
if (
Expand All @@ -223,10 +219,8 @@ def create_phishing_senderid_zendesk_ticket(senderID=None):


class IsNotAPotentiallyMaliciousSenderID:

def __call__(self, form, field):
if protected_sender_id_api_client.get_check_sender_id(sender_id=field.data):

create_phishing_senderid_zendesk_ticket(senderID=field.data)
current_app.logger.warning("User tried to set sender id to potentially malicious one: %s", field.data)
raise ValidationError(
Expand Down Expand Up @@ -284,7 +278,7 @@ def __call__(self, form, field):
else:
error_message = (
f"Cannot contain "
f'{formatted_list(illegal_characters, conjunction="or", before_each="", after_each="")}'
f"{formatted_list(illegal_characters, conjunction='or', before_each='', after_each='')}"
)

if hasattr(field, "error_summary_messages"):
Expand All @@ -293,7 +287,7 @@ def __call__(self, form, field):
else:
error_summary_message = (
f"%s cannot contain "
f'{formatted_list(illegal_characters, conjunction="or", before_each="", after_each="")}'
f"{formatted_list(illegal_characters, conjunction='or', before_each='', after_each='')}"
)
field.error_summary_messages.append(error_summary_message)

Expand Down
5 changes: 2 additions & 3 deletions app/main/views/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,8 +411,7 @@ def inbox_download(service_id):
mimetype="text/csv",
headers={
"Content-Disposition": (
f"inline; "
f'filename="Received text messages {format_date_numeric(datetime.utcnow().isoformat())}.csv"'
f'inline; filename="Received text messages {format_date_numeric(datetime.utcnow().isoformat())}.csv"'
)
},
)
Expand Down Expand Up @@ -650,7 +649,7 @@ def get_monthly_usage_breakdown_for_letters(monthly_letters):

def get_monthly_usage_postage_description(row):
if row["postage"] in ("first", "second"):
return f'{row["postage"]} class'
return f"{row['postage']} class"
return "international"


Expand Down
Loading

0 comments on commit 4b47593

Please sign in to comment.