Compare commits

...

69 Commits

Author SHA1 Message Date
Jonathan Bobel
53034b7e89 Merge branch 'main' of https://github.com/GSA/notifications-admin into 2489-clean-up-js-audit-and-cleanup 2025-04-16 11:24:22 -04:00
Alex Janousek
5464da155a Merge pull request #2501 from GSA/dependabot/pip/phonenumbers-9.0.3
Bump phonenumbers from 9.0.2 to 9.0.3
2025-04-15 12:47:57 -04:00
Alex Janousek
01678ce67a Merge pull request #2504 from GSA/dependabot/npm_and_yarn/rollup-4.40.0
Bump rollup from 4.39.0 to 4.40.0
2025-04-15 12:47:21 -04:00
dependabot[bot]
67b7dc28b4 Bump rollup from 4.39.0 to 4.40.0
Bumps [rollup](https://github.com/rollup/rollup) from 4.39.0 to 4.40.0.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.39.0...v4.40.0)

---
updated-dependencies:
- dependency-name: rollup
  dependency-version: 4.40.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 23:37:01 +00:00
Beverly Nguyen
fbd70b7c38 Merge pull request #2503 from GSA/update-makefile
Update Makefile to handle pre-existing git hooks
2025-04-14 13:28:46 -07:00
Carlo Costino
06ce617f0a Create new command instead of trying to be fancy
Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
2025-04-14 16:17:35 -04:00
Carlo Costino
1afe8121b8 Update Makefile to handle pre-existing git hooks
This changeset adds a bit of extra support to the bootstrap command to make sure that pre-existing git hooks do not interfere with the installation of the pre-commit git hooks.

Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
2025-04-14 15:47:12 -04:00
dependabot[bot]
9d7b6d3147 Bump phonenumbers from 9.0.2 to 9.0.3
Bumps [phonenumbers](https://github.com/daviddrysdale/python-phonenumbers) from 9.0.2 to 9.0.3.
- [Commits](https://github.com/daviddrysdale/python-phonenumbers/compare/v9.0.2...v9.0.3)

---
updated-dependencies:
- dependency-name: phonenumbers
  dependency-version: 9.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 18:55:06 +00:00
ccostino
7b2d031591 Merge pull request #2500 from GSA/dependabot/pip/newrelic-10.9.0
Bump newrelic from 10.8.1 to 10.9.0
2025-04-14 14:53:10 -04:00
Jonathan Bobel
81d4fc0b8c Merge branch 'main' of https://github.com/GSA/notifications-admin into 2489-clean-up-js-audit-and-cleanup 2025-04-14 14:27:36 -04:00
Jonathan Bobel
0b441e36f1 More js cleanup 2025-04-14 14:27:22 -04:00
Kenneth Kehl
ae3be157e6 Merge pull request #2479 from GSA/intl_nums
Intl nums
2025-04-14 10:24:25 -07:00
Jonathan Bobel
1bf7e58445 Merge branch 'main' of https://github.com/GSA/notifications-admin into 2489-clean-up-js-audit-and-cleanup 2025-04-14 11:41:20 -04:00
Kenneth Kehl
16b1429eb0 merge from main 2025-04-14 07:54:42 -07:00
dependabot[bot]
64ac382af9 Bump newrelic from 10.8.1 to 10.9.0
Bumps [newrelic](https://github.com/newrelic/newrelic-python-agent) from 10.8.1 to 10.9.0.
- [Release notes](https://github.com/newrelic/newrelic-python-agent/releases)
- [Commits](https://github.com/newrelic/newrelic-python-agent/compare/v10.8.1...v10.9.0)

---
updated-dependencies:
- dependency-name: newrelic
  dependency-version: 10.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-11 23:48:35 +00:00
Beverly Nguyen
d3e17c743b Merge pull request #2490 from GSA/sandbox_scan_fix 2025-04-11 09:39:39 -07:00
Jonathan Bobel
9eec7cdba5 Merge pull request #2486 from GSA/2041-csv-upload-more-instructions
2041 csv upload more instructions
2025-04-10 16:31:14 -04:00
alexjanousekGSA
e59ddfcae5 Update unit test 2025-04-10 15:58:26 -04:00
Jonathan Bobel
38d8bdea0a Small content updates 2025-04-10 15:41:27 -04:00
Jonathan Bobel
1967195da0 Merge branch 'main' of https://github.com/GSA/notifications-admin into 2041-csv-upload-more-instructions 2025-04-10 15:20:26 -04:00
alexjanousekGSA
fb11fbfba3 Merged in main 2025-04-10 15:11:28 -04:00
alexjanousekGSA
17a7080f46 Fixing unit tests 2025-04-10 15:10:07 -04:00
alexjanousekGSA
a962da9c7d Added existing csp header 2025-04-10 12:15:34 -04:00
alexjanousekGSA
3a468c77aa Fixed unit test 2025-04-10 08:20:47 -04:00
Kenneth Kehl
f3c3a60875 merge from main and fix test 2025-04-09 14:18:24 -07:00
Jonathan Bobel
487866d81c Updating tests for the table 2025-04-09 14:52:56 -04:00
Jonathan Bobel
b71a941634 Update _legacy-styles.scss 2025-04-09 12:07:32 -04:00
Jonathan Bobel
c599407850 Merge branch 'main' of https://github.com/GSA/notifications-admin into 2489-clean-up-js-audit-and-cleanup 2025-04-09 11:38:44 -04:00
Jonathan Bobel
42ac6912b0 Simplifying table js 2025-04-09 11:38:36 -04:00
Andrew Shumway
253934a495 Merge pull request #2472 from GSA/dependabot/pip/async-timeout-5.0.1
Bump async-timeout from 4.0.3 to 5.0.1
2025-04-08 11:06:07 -06:00
alexjanousekGSA
fff81870e5 Fixed header 2025-04-08 13:03:01 -04:00
Andrew Shumway
970483ef0b Merge pull request #2491 from GSA/dependabot/pip/pip-audit-2.9.0
Bump pip-audit from 2.8.0 to 2.9.0
2025-04-08 10:55:34 -06:00
ccostino
ba661a0c53 Merge pull request #2492 from GSA/dependabot/pip/moto-5.1.3
Bump moto from 5.1.2 to 5.1.3
2025-04-08 12:54:49 -04:00
dependabot[bot]
f5ce055079 Bump async-timeout from 4.0.3 to 5.0.1
Bumps [async-timeout](https://github.com/aio-libs/async-timeout) from 4.0.3 to 5.0.1.
- [Release notes](https://github.com/aio-libs/async-timeout/releases)
- [Changelog](https://github.com/aio-libs/async-timeout/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/async-timeout/compare/v4.0.3...v5.0.1)

---
updated-dependencies:
- dependency-name: async-timeout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 16:51:40 +00:00
Andrew Shumway
29c2eafbd7 Merge pull request #2470 from GSA/dependabot/pip/markupsafe-3.0.2
Bump markupsafe from 2.1.5 to 3.0.2
2025-04-08 10:49:46 -06:00
dependabot[bot]
6ecbc7ebab Bump markupsafe from 2.1.5 to 3.0.2
Bumps [markupsafe](https://github.com/pallets/markupsafe) from 2.1.5 to 3.0.2.
- [Release notes](https://github.com/pallets/markupsafe/releases)
- [Changelog](https://github.com/pallets/markupsafe/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/markupsafe/compare/2.1.5...3.0.2)

---
updated-dependencies:
- dependency-name: markupsafe
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 16:07:13 +00:00
Andrew Shumway
f3688d62d5 Merge pull request #2471 from GSA/dependabot/pip/ago-0.1.0
Bump ago from 0.0.95 to 0.1.0
2025-04-08 10:05:06 -06:00
Jonathan Bobel
6f6fd01ea2 Merge branch 'main' of https://github.com/GSA/notifications-admin into 2489-clean-up-js-audit-and-cleanup 2025-04-08 10:15:50 -04:00
dependabot[bot]
1f13176fa6 Bump moto from 5.1.2 to 5.1.3
Bumps [moto](https://github.com/getmoto/moto) from 5.1.2 to 5.1.3.
- [Release notes](https://github.com/getmoto/moto/releases)
- [Changelog](https://github.com/getmoto/moto/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getmoto/moto/compare/5.1.2...5.1.3)

---
updated-dependencies:
- dependency-name: moto
  dependency-version: 5.1.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 01:11:50 +00:00
dependabot[bot]
09c6d0445d Bump pip-audit from 2.8.0 to 2.9.0
Bumps [pip-audit](https://github.com/pypa/pip-audit) from 2.8.0 to 2.9.0.
- [Release notes](https://github.com/pypa/pip-audit/releases)
- [Changelog](https://github.com/pypa/pip-audit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pypa/pip-audit/compare/v2.8.0...v2.9.0)

---
updated-dependencies:
- dependency-name: pip-audit
  dependency-version: 2.9.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 01:04:34 +00:00
dependabot[bot]
c328353e18 Bump ago from 0.0.95 to 0.1.0
---
updated-dependencies:
- dependency-name: ago
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-07 21:27:13 +00:00
Alex Janousek
e9e0601237 Merge pull request #2473 from GSA/dependabot/pip/phonenumbers-9.0.2
Bump phonenumbers from 8.13.55 to 9.0.2
2025-04-07 17:25:24 -04:00
Jonathan Bobel
9921809f90 2489 - Clean up site js 2025-04-07 15:25:29 -04:00
dependabot[bot]
88f29a6727 Bump phonenumbers from 8.13.55 to 9.0.2
Bumps [phonenumbers](https://github.com/daviddrysdale/python-phonenumbers) from 8.13.55 to 9.0.2.
- [Commits](https://github.com/daviddrysdale/python-phonenumbers/compare/v8.13.55...v9.0.2)

---
updated-dependencies:
- dependency-name: phonenumbers
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-07 18:51:04 +00:00
Alex Janousek
9d4d55a670 Merge pull request #2474 from GSA/dependabot/pip/pyexcel-xlsx-0.6.1
Bump pyexcel-xlsx from 0.6.0 to 0.6.1
2025-04-07 14:49:07 -04:00
Alex Janousek
3211c5a16f Merge pull request #2476 from GSA/dependabot/npm_and_yarn/uswds/compile-1.2.2
Bump @uswds/compile from 1.2.1 to 1.2.2
2025-04-07 14:48:54 -04:00
Alex Janousek
9c8aa9c0e2 Merge pull request #2477 from GSA/dependabot/npm_and_yarn/rollup-4.39.0
Bump rollup from 4.38.0 to 4.39.0
2025-04-07 14:48:42 -04:00
Alex Janousek
c2508f7230 Merge pull request #2487 from GSA/dependabot/npm_and_yarn/sass-embedded-1.86.3
Bump sass-embedded from 1.86.1 to 1.86.3
2025-04-07 14:48:17 -04:00
alexjanousekGSA
b5675e586e Fixed security issue and noscript iframe 2025-04-07 14:45:28 -04:00
Jonathan Bobel
823b490331 Merge branch 'main' of https://github.com/GSA/notifications-admin into 2041-csv-upload-more-instructions 2025-04-07 11:22:12 -04:00
dependabot[bot]
e69d6f807f Bump sass-embedded from 1.86.1 to 1.86.3
Bumps [sass-embedded](https://github.com/sass/embedded-host-node) from 1.86.1 to 1.86.3.
- [Changelog](https://github.com/sass/embedded-host-node/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/embedded-host-node/compare/1.86.1...1.86.3)

---
updated-dependencies:
- dependency-name: sass-embedded
  dependency-version: 1.86.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-04 23:53:25 +00:00
ccostino
ee8213f670 Merge pull request #2485 from GSA/updating_assets_folder
updating asset folder
2025-04-04 14:57:47 -04:00
Beverly Nguyen
c43174be78 Merge pull request #2484 from GSA/corrupt_csv
fix blank lines at end of csv
2025-04-04 11:34:09 -07:00
Jonathan Bobel
548072fe66 Content and styling updates to CSV upload page 2025-04-04 14:33:11 -04:00
Beverly Nguyen
7f3e368427 updating asset folder 2025-04-04 11:29:53 -07:00
Beverly Nguyen
caed2ad5d6 updating asset folder 2025-04-04 11:27:20 -07:00
dependabot[bot]
3401b994cd Bump pyexcel-xlsx from 0.6.0 to 0.6.1
Bumps [pyexcel-xlsx](https://github.com/pyexcel/pyexcel-xlsx) from 0.6.0 to 0.6.1.
- [Release notes](https://github.com/pyexcel/pyexcel-xlsx/releases)
- [Changelog](https://github.com/pyexcel/pyexcel-xlsx/blob/dev/CHANGELOG.rst)
- [Commits](https://github.com/pyexcel/pyexcel-xlsx/compare/v0.6.0...v0.6.1)

---
updated-dependencies:
- dependency-name: pyexcel-xlsx
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-04 18:07:10 +00:00
Beverly Nguyen
d5798bb818 Merge pull request #2480 from GSA/2475-target-blank-error 2025-04-04 11:05:11 -07:00
Kenneth Kehl
0cf9abf5e8 add test 2025-04-04 09:35:01 -07:00
Kenneth Kehl
0bdf0cb073 fix blank lines at end of csv 2025-04-04 09:13:46 -07:00
Jonathan Bobel
b8b7c2df69 Merge branch 'main' of https://github.com/GSA/notifications-admin into 2041-csv-upload-more-instructions 2025-04-04 11:29:31 -04:00
Jonathan Bobel
db37a4de94 Updating all links with a target="_blank" 2025-04-03 11:57:21 -04:00
Kenneth Kehl
b3b31d0732 merge from main 2025-04-03 07:45:06 -07:00
Kenneth Kehl
ef779daec1 reenable intl numbers 2025-04-03 07:44:21 -07:00
Jonathan Bobel
50df9c79a2 2475 - Update target="_blank" links 2025-04-03 10:37:50 -04:00
dependabot[bot]
e4a4217dba Bump rollup from 4.38.0 to 4.39.0
Bumps [rollup](https://github.com/rollup/rollup) from 4.38.0 to 4.39.0.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.38.0...v4.39.0)

---
updated-dependencies:
- dependency-name: rollup
  dependency-version: 4.39.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-02 23:55:21 +00:00
dependabot[bot]
3179471122 Bump @uswds/compile from 1.2.1 to 1.2.2
Bumps [@uswds/compile](https://github.com/uswds/uswds-compile) from 1.2.1 to 1.2.2.
- [Release notes](https://github.com/uswds/uswds-compile/releases)
- [Commits](https://github.com/uswds/uswds-compile/compare/v1.2.1...v1.2.2)

---
updated-dependencies:
- dependency-name: "@uswds/compile"
  dependency-version: 1.2.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-02 23:55:01 +00:00
Jonathan Bobel
96890199f3 Style update 2025-03-27 09:19:09 -04:00
Jonathan Bobel
700c76807b Update send.html 2025-03-26 16:01:56 -04:00
45 changed files with 922 additions and 1453 deletions

4
.gitignore vendored
View File

@@ -14,6 +14,10 @@
*.XLSX
## Non user files allowed to be commited
!app/assets/pdf/best-practices-for-texting-the-public.pdf
!app/assets/pdf/investing-in-notifications-tts-public-benefits-studio-decision-memo.pdf
!app/assets/pdf/best-practices-section-outline.pdf
!app/assets/pdf/standing-up-your-own-notify.pdf
!app/assets/pdf/tcpa_overview.pdf
!app/assets/pdf/investing-notifications-tts-public-benefits-memo.pdf
!app/assets/pdf/out-of-pilot-announcement.pdf

View File

@@ -8,6 +8,7 @@ repos:
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
exclude: ^app/assets/pdf/.*\.pdf$
- id: debug-statements
- id: check-merge-conflict
- id: check-toml

View File

@@ -6,6 +6,7 @@ APP_VERSION_FILE = app/version.py
GIT_BRANCH ?= $(shell git symbolic-ref --short HEAD 2> /dev/null || echo "detached")
GIT_COMMIT ?= $(shell git rev-parse HEAD 2> /dev/null || echo "")
GIT_HOOKS_PATH ?= $(shell git config --global core.hooksPath || echo "")
VIRTUALENV_ROOT := $(shell [ -z $$VIRTUAL_ENV ] && echo $$(pwd)/venv || echo $$VIRTUAL_ENV)
@@ -14,7 +15,8 @@ NVMSH := $(shell [ -f "$(HOME)/.nvm/nvm.sh" ] && echo "$(HOME)/.nvm/nvm.sh" || e
## DEVELOPMENT
.PHONY: bootstrap
bootstrap: generate-version-file ## Set up everything to run the app
bootstrap: ## Set up everything to run the app
make generate-version-file
poetry self add poetry-dotenv-plugin
poetry lock --no-update
poetry install --sync --no-root
@@ -24,6 +26,20 @@ bootstrap: generate-version-file ## Set up everything to run the app
source $(NVMSH) && npm ci --no-audit
source $(NVMSH) && npm run build
.PHONY: bootstrap-with-git-hooks
bootstrap-with-git-hooks: ## Sets everything up and accounts for pre-existing git hooks
make generate-version-file
poetry self add poetry-dotenv-plugin
poetry lock --no-update
poetry install --sync --no-root
poetry run playwright install --with-deps
git config --global --unset-all core.hooksPath
poetry run pre-commit install
git config --global core.hookspath "${GIT_HOOKS_PATH}"
source $(NVMSH) --no-use && nvm install && npm install
source $(NVMSH) && npm ci --no-audit
source $(NVMSH) && npm run build
.PHONY: watch-frontend
watch-frontend: ## Build frontend and watch for changes
source $(NVMSH) && npm run watch

View File

@@ -145,6 +145,7 @@ def _csp(config):
"frame-src": [
"https://www.youtube.com",
"https://www.youtube-nocookie.com",
"https://www.googletagmanager.com",
],
"frame-ancestors": "'none'",
"form-action": "'self'",
@@ -169,6 +170,11 @@ def _csp(config):
def create_app(application):
@application.after_request
def add_csp_header(response):
existing_csp = response.headers.get("Content-Security-Policy", "")
response.headers["Content-Security-Policy"] = existing_csp + "; form-action 'self';"
return response
# @application.context_processor
# def inject_feature_flags():
# this is where feature flags can be easily added as a dictionary within context

View File

@@ -1,29 +0,0 @@
(function(Modules) {
"use strict";
let isSixDigitHex = value => value.match(/^#[0-9A-F]{6}$/i);
let colourOrWhite = value => isSixDigitHex(value) ? value : '#FFFFFF';
Modules.ColourPreview = function() {
this.start = component => {
this.$input = $(component);
this.$input.closest('.usa-form-group').append(
this.$preview = $('<span class="textbox-colour-preview"></span>')
);
this.$input
.on('input', this.update)
.trigger('input');
};
this.update = () => this.$preview.css(
'background', colourOrWhite(this.$input.val())
);
};
})(window.GOVUK.Modules);

View File

@@ -1,124 +1,41 @@
(function(Modules) {
"use strict";
(function () {
'use strict';
Modules.FullscreenTable = function() {
function initFullscreenTables() {
const tables = document.querySelectorAll('.fullscreen-table');
this.start = function(component) {
tables.forEach((component) => {
const table = component.querySelector('table');
const wrapper = component.querySelector('.table-wrapper');
const rightShadow = component.querySelector('.fullscreen-right-shadow');
this.$component = $(component);
this.$table = this.$component.find('table');
this.nativeHeight = this.$component.innerHeight() + 20; // 20px to allow room for scrollbar
this.topOffset = this.$component.offset().top;
if (!table || !wrapper) return;
this.insertShims();
this.maintainWidth();
this.maintainHeight();
this.toggleShadows();
const toggleShadows = () => {
const scrollLeft = wrapper.scrollLeft;
const maxScrollLeft = wrapper.scrollWidth - wrapper.clientWidth;
$(window)
.on('scroll resize', this.maintainHeight)
.on('resize', this.maintainWidth);
wrapper.classList.toggle('scrolled', scrollLeft > 0);
this.$scrollableTable
.on('scroll', this.toggleShadows)
.on('scroll', this.maintainHeight)
.on('focus blur', () => this.$component.toggleClass('js-focus-style'));
if (rightShadow) {
rightShadow.classList.toggle('visible', scrollLeft < maxScrollLeft);
}
};
if (
window.GOVUK.stickAtBottomWhenScrolling &&
window.GOVUK.stickAtBottomWhenScrolling.recalculate
) {
window.GOVUK.stickAtBottomWhenScrolling.recalculate();
}
wrapper.addEventListener('scroll', toggleShadows);
toggleShadows(); // init state
});
}
this.maintainWidth();
// Auto-init on page load in browser
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initFullscreenTables);
} else {
initFullscreenTables();
}
};
this.insertShims = () => {
const attributesForFocus = 'role aria-labelledby tabindex';
let captionId = this.$table.find('caption').text().toLowerCase().replace(/[^A-Za-z]+/g, '');
this.$table.find('caption').attr('id', captionId);
this.$table.wrap(`<div class="fullscreen-scrollable-table" role="region" aria-labelledby="${captionId}" tabindex="0"/>`);
this.$component
.append(
this.$component.find('.fullscreen-scrollable-table')
.clone()
.addClass('fullscreen-fixed-table')
.removeClass('fullscreen-scrollable-table')
.removeAttr(attributesForFocus)
.attr('aria-hidden', true)
.find('caption')
.removeAttr('id')
.closest('.fullscreen-fixed-table')
)
.append(
'<div class="fullscreen-right-shadow" />'
)
.after(
$("<div class='fullscreen-shim'/>").css({
'height': this.nativeHeight,
'top': this.topOffset
})
)
.css('position', 'absolute');
this.$scrollableTable = this.$component.find('.fullscreen-scrollable-table');
this.$fixedTable = this.$component.find('.fullscreen-fixed-table');
};
this.maintainHeight = () => {
let height = Math.min(
$(window).height() - this.topOffset + $(window).scrollTop(),
this.nativeHeight
);
this.$scrollableTable.outerHeight(height);
this.$fixedTable.outerHeight(height);
};
this.maintainWidth = () => {
let indexColumnWidth = this.$fixedTable.find('.table-field-index').outerWidth();
this.$scrollableTable
.css({
'width': this.$component.parent('main').width() - indexColumnWidth,
'margin-left': indexColumnWidth
});
this.$fixedTable
.width(indexColumnWidth + 4);
};
this.toggleShadows = () => {
this.$fixedTable
.toggleClass(
'fullscreen-scrolled-table',
this.$scrollableTable.scrollLeft() > 0
);
this.$component.find('.fullscreen-right-shadow')
.toggleClass(
'visible',
this.$scrollableTable.scrollLeft() < (this.$table.width() - this.$scrollableTable.width())
);
setTimeout(
() => this.$component.find('.fullscreen-right-shadow').addClass('with-transition'),
3000
);
};
};
})(window.GOVUK.Modules);
// Expose it globally in test environments
if (typeof window !== 'undefined') {
window.initFullscreenTables = initFullscreenTables;
}
})();

View File

@@ -1,24 +0,0 @@
(function(window) {
"use strict";
window.GOVUK.Modules.Homepage = function() {
this.start = function(component) {
let $component = $(component);
let iterations = 0;
let timeout = null;
$component.on('click', () => {
if (++iterations == 5) {
$component.toggleClass('product-page-intro-wrapper--alternative');
}
clearTimeout(timeout);
timeout = setTimeout(() => iterations = 0, 1500);
});
};
};
})(window);

View File

@@ -1,27 +1,38 @@
window.GOVUK.Frontend.initAll();
$(() => $("time.timeago").timeago());
// Initialize GOV.UK Frontend components (modern init)
window.GOVUKFrontend.initAll();
var showHideContent = new GOVUK.ShowHideContent();
showHideContent.init();
$(() => GOVUK.modules.start());
$(() => $('.error-message, .usa-error-message').eq(0).parent('label').next('input').trigger('focus'));
$(() => $('.govuk-header__container').on('click', function() {
$(this).css('border-color', '#005ea5');
}));
// Applies our expanded focus style to the siblings of links when that link is wrapped in a heading.
//
// This will be possible in CSS in the future, using the :has pseudo-class. When :has is available
// in the browsers we support, this code can be replaced with a CSS-only solution.
$('.js-mark-focus-on-parent').on('focus blur', '*', e => {
$target = $(e.target);
if (e.type === 'focusin') {
$target.parent().addClass('js-child-has-focus');
} else {
$target.parent().removeClass('js-child-has-focus');
document.addEventListener('DOMContentLoaded', () => {
// Autofocus the first error input
const errorEl = document.querySelector('.error-message, .usa-error-message');
if (errorEl) {
const label = errorEl.closest('label');
if (label) {
const input = label.nextElementSibling;
if (input && input.focus) {
input.focus();
}
}
}
// Optional: highlight header on click
const header = document.querySelector('.govuk-header__container');
if (header) {
header.addEventListener('click', () => {
header.style.borderColor = '#005ea5';
});
}
// Focus styling for heading-wrapped links
const containers = document.querySelectorAll('.js-mark-focus-on-parent');
containers.forEach(container => {
container.addEventListener('focusin', e => {
e.target.parentElement.classList.add('js-child-has-focus');
});
container.addEventListener('focusout', e => {
e.target.parentElement.classList.remove('js-child-has-focus');
});
});
});

Binary file not shown.

Binary file not shown.

View File

@@ -49,12 +49,12 @@
}
.sms-message-sender, .sms-message-file-name, .sms-message-scheduler, .sms-message-template, .sms-message-sender {
margin:0.25rem 0 0;
margin: units(0.5) 0 0;
}
.sms-message-recipient {
color: color('gray-cool-90');
margin: units(1) 0 units(1);
margin: units(0.5) 0 units(2);
}
.sms-message-status {
@@ -174,8 +174,10 @@ h2.recipient-list {
}
.spreadsheet {
margin-bottom: units (1);
margin-bottom: units(1);
overflow-x: auto;
position: relative;
padding-left: 0;
.table {
margin-bottom: 0;
@@ -186,27 +188,56 @@ h2.recipient-list {
}
th,
.table-field-index {
background: color('gray-cool-10');
td {
padding-inline: 10px;
border: 1px solid color('gray-cool-10');
background: white;
z-index: 1;
}
td {
min-width: calc(194px - 11px);
&:first-child {
min-width: auto;
}
}
th.table-field-heading {
background: color('gray-cool-10');
font-weight: bold;
text-align: center;
}
th, td {
padding-left: 10px;
padding-right: 10px;
border: 1px solid color('gray-cool-10');
}
.table-field-index,
.table-field-heading-first {
position: sticky;
left: 0;
z-index: 10;
background: white;
text-align: center;
width: units(2);
td {
border-top: 0;
// 194 is the width of the table * 1/3.5, so the overflow cuts off
// at 3.5 columns wide.
// 11 accounts for the padding of the table cell
min-width: 194px - 11px;
&:first-child {
min-width: auto;
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 0px;
z-index: 11;
outline: 1px solid color('gray-cool-10');
}
&::after {
content: "";
position: absolute;
top: 0;
right: 0;
height: 100%;
width: 1px;
background: color('gray-cool-10');
z-index: 11;
}
}

View File

@@ -722,7 +722,6 @@ details form {
.placeholder,
.placeholder-conditional {
background-color: #face00;
border: 1px solid #face00;
border-radius: 7px;
}

View File

@@ -28,8 +28,17 @@ def get_csv_upload(service_id, upload_id):
return get_s3_object(*get_csv_location(service_id, upload_id))
def remove_blank_lines(filedata):
# sometimes people upload files with hundreds of blank lines at the end
data = filedata["data"]
cleaned_data = "\n".join(line for line in data.splitlines() if line.strip())
filedata["data"] = cleaned_data
return filedata
def s3upload(service_id, filedata):
filedata = remove_blank_lines(filedata)
upload_id = str(uuid.uuid4())
bucket_name, file_location, access_key, secret_key, region = get_csv_location(
service_id, upload_id

View File

@@ -14,7 +14,7 @@
<script nonce="{{ csp_nonce() }}">document.body.className = ((document.body.className) ? document.body.className + ' js-enabled' : 'js-enabled');</script>
{% block bodyStart %}
{% block extra_javascripts_before_body %}
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-WX5NGWF"
<noscript><iframe sandbox src="https://www.googletagmanager.com/ns.html?id=GTM-WX5NGWF"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
{% endblock %}
{% endblock %}

View File

@@ -29,7 +29,7 @@
'class': 'file-upload-field',
'accept': allowed_file_extensions|format_list_items('.{item}')|join(',')|e
}) }}
<label class="file-upload-button" for="{{ field.name }}">
<label class="file-upload-button usa-button" for="{{ field.name }}">
{{ button_text }}
</label>
{% if alternate_link and alternate_link_text %}

View File

@@ -16,7 +16,10 @@
<li>More effectively deliver program outcomes</li>
<li>Save administrative costs</li>
<li>Implement <a class="use-link usa-link--external"
href="https://digital.gov/resources/delivering-digital-first-public-experience/" target="_blank">21st Century
href="https://digital.gov/resources/delivering-digital-first-public-experience/"
rel="noopener noreferrer"
target="_blank"
aria-label="21st Century IDEA (opens in a new tab)">21st Century
IDEA</a> and other directives</li>
</ul>
<p>Notify.gov is an easy-to-use, web-based platform. It requires no technical expertise or system integration — users

View File

@@ -22,16 +22,26 @@
</ul>
<p>
Notify.gov operates under a full three-year <a class="use-link usa-link--external"
href="https://digital.gov/resources/an-introduction-to-ato/" target="_blank">Authority-to-Operate (ATO)</a>. This
href="https://digital.gov/resources/an-introduction-to-ato/"
target="_blank"
rel="noopener noreferrer"
aria-label="Authority-to-Operate (ATO) (opens in a new tab)">Authority-to-Operate (ATO)</a>. This
federal security authorization process leverages security
controls provided by National Institute of Standards and Technology (NIST).
</p>
<p>
Our infrastructure runs on <a class="use-link usa-link--external" href="https://www.cloud.gov/"
target="_blank">cloud.gov</a> and utilizes several
Our infrastructure runs on <a class="use-link usa-link--external"
href="https://www.cloud.gov/"
target="_blank"
rel="noopener noreferrer"
aria-label="cloud dot gov (opens in a new tab)">cloud.gov</a> and utilizes several
services through Amazon Web
Services (AWS), including <a class="use-link usa-link--external" href="https://aws.amazon.com/sns/" target="_blank">
Services (AWS), including <a class="use-link usa-link--external"
href="https://aws.amazon.com/sns/"
rel="noopener noreferrer"
target="_blank"
aria-label="AWS SNS (opens in a new tab)">
AWS SNS </a> for sending SMS
messages.
</p>
@@ -53,15 +63,22 @@
</p>
<h2>Multi-Factor Authentication</h2>
<p>
Notify.gov uses <a class="use-link usa-link--external" href="https://login.gov/what-is-login/"
target="_blank">Login.gov</a> for enhanced security.
Notify.gov uses <a class="use-link usa-link--external"
href="https://login.gov/what-is-login/"
rel="noopener noreferrer"
target="_blank"
aria-label="Login dot gov (opens in a new tab)">Login.gov</a> for enhanced security.
Login.gov is an extra layer of security created by the government that uses multi-factor authentication and stronger
passwords to protect your account.
</p>
<p>
To access Notify.gov, users will use a Login.gov account associated with their agency (.gov) email with one of the
<a class="use-link usa-link--external" href="https://login.gov/help/get-started/authentication-methods/"
target="_blank">multi-factor authentication
<a class="use-link usa-link--external"
href="https://login.gov/help/get-started/authentication-methods/"
rel="noopener noreferrer"
target="_blank"
aria-label="login dot gov multi-factor authentication methods (opens in a new tab)"
>multi-factor authentication
methods</a> offered through Login.gov.
</p>
</section>

View File

@@ -16,7 +16,9 @@
<p>
Confusing or <a class="use-link usa-link--external"
href="https://kffhealthnews.org/news/tougher-returned-mail-policies-add-to-medicaid-enrollment-drop/"
target="_blank">unreceived notifications</a> are one of the largest barriers to people getting and keeping
target="_blank"
rel="noopener noreferrer"
aria-label="unreceived notifications (opens in a new tab)">unreceived notifications</a> are one of the largest barriers to people getting and keeping
benefits. The typical ways the government communicates with people often fall short. Low income households are more
likely to experience housing instability, which means paper mail, already slow, can easily be missed.
</p>
@@ -24,7 +26,9 @@
<p>
<a class="use-link usa-link--external"
href="https://www.pewresearch.org/internet/fact-sheet/mobile/?tabItem=64e32376-5a21-4b1d-8f8b-5f92406db984"
target="_blank">Pew Research shows that nearly all adults in the US have a cell phone.</a> Reliance on smartphones
target="_blank"
rel="noopener noreferrer"
aria-label="Pew Research shows that nearly all adults in the US have a cell phone (opens in a new tab)">Pew Research shows that nearly all adults in the US have a cell phone.</a> Reliance on smartphones
for online access is especially common among Americans with lower household incomes and those with lower levels of
formal education. Of those earning less than $30,000 a year, 28% say their mobile phone is the sole method to
digitally connect.

View File

@@ -57,7 +57,7 @@
{{notification.key_name}}
</span>
<span class="grid-col-6 api-notifications-item__meta-time">
<time class="timeago" datetime="{{ notification.created_at }}">
<time datetime="{{ notification.created_at }}">
{{ notification.created_at|format_delta }}
</time>
</span>

View File

@@ -1,7 +1,7 @@
{% extends "withnav_template.html" %}
{% from "components/banner.html" import banner_wrapper %}
{% from "components/radios.html" import radio_select %}
{% from "components/table.html" import list_table, field, text_field, index_field, hidden_field_heading %}
{% from "components/table.html" import list_table, field, text_field, index_field, index_field_heading %}
{% from "components/file-upload.html" import file_upload %}
{% from "components/components/back-link/macro.njk" import usaBackLink %}
@@ -161,18 +161,15 @@ recipients.column_headers %}
<h2 class="font-body-lg" id="file-preview">{{ original_file_name }}</h2>
<div class="fullscreen-content" data-module="fullscreen-table">
<div class="table-wrapper spreadsheet" data-module="fullscreen-table">
{% call(item, row_number) list_table(
recipients.displayed_rows,
caption=original_file_name,
caption_visible=False,
field_headings=[
'<span class="usa-sr-only">Row in file</span><span aria-hidden="true">1</span>'|safe
] + column_headers
) %}
field_headings=[''] + column_headers )%}
{% call index_field() %}
<span>
{% set displayed_index = item.index + 2 %}
{% set displayed_index = item.index + 1 %}
{{ displayed_index }}
</span>
{% endcall %}

View File

@@ -55,7 +55,7 @@
</div>
</div>
<div class="fullscreen-content" data-module="fullscreen-table">
<div class="table-wrapper spreadsheet" data-module="fullscreen-table">
{% call(item, row_number) mapping_table(
caption="Errors in " + original_file_name,
caption_visible=False,

View File

@@ -60,7 +60,7 @@
<p class="hint">This person has never logged in</p>
{% else %}
<p>Last logged in
<time class="timeago" datetime="{{ user.logged_in_at }}">
<time datetime="{{ user.logged_in_at }}">
{{ user.logged_in_at|format_delta }}
</time>
</p>

View File

@@ -23,7 +23,9 @@
<p>
When the <a class="use-link usa-link--external"
href="https://www.cbpp.org/research/food-assistance/targeted-text-message-outreach-can-increase-wic-enrollment-pilots-show"
target="_blank">Center on Budget and Policy Priorities</a> studied WIC, they found key learnings about the
target="_blank"
rel="noopener noreferrer"
aria-label="Center on Budget and Policy Priorities (opens in a new tab)">Center on Budget and Policy Priorities</a> studied WIC, they found key learnings about the
quantity of messages delivered, how people engage with messages, and how they take action.
</p>
@@ -68,8 +70,11 @@
</li>
</ul>
<p>
The <a class="use-link usa-link--external" href="https://codeforamerica.org/resources/texting-playbook/"
target="_blank">Code for Americas Texting
The <a class="use-link usa-link--external"
href="https://codeforamerica.org/resources/texting-playbook/"
target="_blank"
rel="noopener noreferrer"
aria-label="Code for America's Texting Playbook (opens in a new tab)">Code for Americas Texting
Playbook</a>
reported specific learnings around appointment reminders, completing
document submission, and maintenance reminders.

View File

@@ -142,7 +142,9 @@
<p>
The Department of Veterans Affairs provides a <a class="use-link usa-link--external"
href="https://github.com/department-of-veterans-affairs/va.gov-team/blob/master/products/va-notify/notification-guide.md#vanotify-notification-guide"
target="_blank">helpful flow-chart</a> that can help you decide if a text message is needed
target="_blank"
rel="noopener noreferrer"
aria-label="helpful flow-chart (opens in a new tab)">helpful flow-chart</a> that can help you decide if a text message is needed
for the communication problem you are trying to solve.
</p>
<h2>Use a hypothesis framework to plan your campaign</h2>

View File

@@ -101,8 +101,11 @@
<p class="sms-message-wrapper text-left">
{{circle_number(7) }}<b>Dept. of Social Services:</b> Hi {{circle_number(8) }}<b>Julie,</b> Your Medicaid
renewal is closing December 31, 2023. You can renew online at {{circle_number(9) }}<a
class="use-link usa-link--external" href="https://www.application.yourstate.gov" target="_blank"
rel="application.yourstate.gov">https://www.application.yourstate.gov</a> or {{circle_number(10) }}
class="use-link usa-link--external"
href="https://www.application.yourstate.gov"
target="_blank"
rel="application.yourstate.gov"
aria-label="Yourstate dot gov (opens in a new tab)">https://www.application.yourstate.gov</a> or {{circle_number(10) }}
<b>call the number on the back</b> of your Medicaid card.
</p>
</div>

View File

@@ -29,7 +29,10 @@
{% set links = [
{
"p_text": 'The <a class="use-link usa-link--external" href="https://www.fcc.gov/sites/default/files/tcpa-rules.pdf"
target="_blank">Telephone Consumer
target="_blank"
rel="noopener noreferrer"
aria-label="Telephone Consumer Protection Act (TCPA) (opens in a new tab)"
>Telephone Consumer
Protection Act (TCPA)</a> (47 USC § 227) is the federal law that impacts how organizations are allowed to
communicate
in bulk with the public via telephone (including text message or SMS).'
@@ -37,7 +40,9 @@
{
"p_text": 'The <a class="use-link usa-link--external"
href="https://www.fcc.gov/document/fcc-provides-best-practices-enable-critical-health-care-coverage-calls"
target="_blank">FCC has ruled</a> that <b>Federal</b> and <b>State</b> programs are exempt from the TCPA and can
target="_blank"
rel="noopener noreferrer"
aria-label="FCC has ruled (opens in a new tab)">FCC has ruled</a> that <b>Federal</b> and <b>State</b> programs are exempt from the TCPA and can
send
text messages to the public without consent if conducting official business. Without explicit mention in the ruling,
local governments, phone carriers, or any texting intermediaries might require it.'
@@ -52,7 +57,10 @@
case.
For additional questions about the enforcement of the TCPA, you can watch <a class="use-link usa-link--external"
href="https://www.performance.gov/cx/life-experiences/facing-a-financial-shock/outputs/fcc-training/"
target="_blank">a recorded training on public
target="_blank"
rel="noopener noreferrer"
aria-label="a recorded training on public
benefits texting (opens in a new tab)">a recorded training on public
benefits texting</a> provided by the FCC.'
},
] %}
@@ -65,7 +73,9 @@
<h4 class="usa-collection__heading">
<a class="usa-link {% if item.is_downloadable %}usa-link--downloadable{% else %}usa-link--external{% endif %}"
href="{% if item.is_downloadable %}{{ asset_url(item.url_link) }}{% else %}{{ item.url_link }}{% endif %}"
target="_blank">
target="_blank"
rel="noopener noreferrer"
aria-label="{{ item.url_text }} (opens in a new tab)">
{{ item.url_text }}
</a>
</h4>
@@ -119,6 +129,7 @@
process
to keep your phone number list up-to-date. The FCC offers a <a class="use-link usa-link--external"
href="https://www.fcc.gov/reassigned-numbers-database" target="_blank"
aria-label="Reassigned Numbers Database (opens in a new tab)"
rel="Reassigned Numbers Database">Reassigned
Numbers Database</a> to identify phone numbers that may have been reassigned since your agency obtained them.
</p>

View File

@@ -81,7 +81,10 @@
</ul>
<h2>What provoking action looks like</h2>
<p class="padding-bottom-2"><a class="use-link usa-link--external"
href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC10002044/" target="_blank">Evidence
href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC10002044/"
target="_blank"
rel="noopener noreferrer"
aria-label="Evidence shows (opens in a new tab)">Evidence
shows</a> that employing
behavioral science is an effective way to increase the
likelihood of a recipient

View File

@@ -11,7 +11,7 @@
</h1>
<p>
<a class="usa-link" target="_blank" href="{{ url_for('main.live_services_csv') }}">Download live services csv report</a>
<a class="usa-link" target="_blank" rel="noopener noreferrer" aria-label="Download live services CSV report (opens in a new tab)" href="{{ url_for('main.live_services_csv') }}">Download live services csv report</a>
</p>
<p>
<a class="usa-link" href="{{ url_for('main.notifications_sent_by_service') }}">Monthly notification statuses for live services</a>

View File

@@ -5,9 +5,10 @@
{% from "components/table.html" import list_table, text_field, index_field, index_field_heading %}
{% from "components/components/back-link/macro.njk" import usaBackLink %}
<script type="text/javascript" src="{{ asset_url('js/setTimezone.js') }}"></script>
import { FullscreenTable } from './fullscreenTable.js';
{% block service_page_title %}
Upload a list of {{ 999|recipient_count_label(template.template_type) }}
Upload your bulk-sending spreadsheet
{% endblock %}
@@ -15,22 +16,63 @@
{{ usaBackLink(params) }}
{% endblock %}
{% set phone_numbers = [
{
"svg_src": "#check_circle",
"card_heading": "Label column A (the first column) as Phone number",
},
{
"svg_src": "#check_circle",
"card_heading": "Double check it's the only column with Phone number as its label",
},
{
"svg_src": "#check_circle",
"card_heading": "Make sure no duplicate phone numbers are listed in Column A",
}
]
%}
{% set additional_data = [
{
"svg_src": "#check_circle",
"card_heading": "Match column labels one-to-one to the message template placeholders",
},
{
"svg_src": "#check_circle",
"card_heading": "Label each additional <a class=\"usa-link\" href=\"/using-notify/how-to#personalize-content\">personalized placeholder</a> separately",
},
{
"svg_src": "#check_circle",
"card_heading": "Separate each word in a column label with a space or dash, but no commas",
},
{
"svg_src": "#check_circle",
"card_heading": "Fill in each personalized placeholder with the appropriate data or information",
},
{
"svg_src": "#check_circle",
"card_heading": "Fill in each <a class=\"usa-link\" href=\"/using-notify/how-to#conditional-content\">conditional placeholder</a> column with a Yes (Y) or No (N) to “answer” whether the recipient meets its criteria",
}
]
%}
{% block maincolumn_content %}
{{ page_header('Upload a list of {}'.format(999|recipient_count_label(template.template_type))) }}
{{ page_header('Upload your bulk-sending spreadsheet')}}
<p class="font-sans-lg text-base">Organize phone numbers and information in a single spreadsheet and upload when you have multiple messages to send. Column headers in the spreadsheet will place the data in the right spots within the template that's selected.</p>
<div class="page-footer bottom-gutter">
<div class="page-footer bottom-gutter margin-top-3">
{{file_upload(
form.file,
allowed_file_extensions=allowed_file_extensions,
button_text='Choose a file',
button_text='Choose and upload a spreadsheet',
show_errors=False
)}}
</div>
<h2 class="font-body-lg">Your file needs to look like this example</h2>
<h2 class="font-body-lg">A spreadsheet is available to use</h2>
<div class="spreadsheet" data-module="fullscreen-table">
<div class="table-wrapper spreadsheet" data-module="fullscreen-table">
{% call(item, row_number) list_table(
example,
caption="Example",
@@ -44,14 +86,71 @@
{% endcall %}
</div>
<p class="hint">
Save your spreadsheet as a <abbr title="Comma Separated Values">CSV</abbr> file for bulk messaging. It is the most reliable when uploading your contact list. Start by downloading this example for your message template.
Each template saved in Notify is given an example <abbr title="Comma Separated Values">CSV</abbr> formatted spreadsheet like this. It's the most reliable file format for uploading your contact list. Use this example to populate your template with the right data items. Start by downloading this example for your message template. Then save it as a <abbr title="Comma Separated Values">CSV</abbr> file for bulk messaging.
</p>
<p class="table-show-more-link">
<a class="usa-link display-flex margin-top-1" href="{{ url_for('.get_example_csv', service_id=current_service.id, template_id=template.id) }}" download>Download this example (<abbr title="Comma separated values">CSV</abbr>)
<img class="margin-left-05" src="{{ asset_url('img/material-icons/download.svg') }}" alt="" />
</a>
</p>
<h2 class="font-body-lg margin-bottom-1">Your file will populate this template:<br><span class="font-body-lg">({{ template.name }})</span></h2>
<h2 class="font-body-lg">Your bulk-sending spreadsheet checklist</h2>
<div class="grid-container margin-top-2 padding-0">
<div class="grid-row">
<div class="grid-col-12">
<h3>Phone numbers</h3>
<ul class="usa-icon-list">
{% for item in phone_numbers %}
<li class="usa-icon-list__item">
<div class="usa-icon-list__icon text-green">
<svg aria-hidden="true" focusable="false" role="img" class="usa-icon">
<use xlink:href="{{ asset_url('img/sprite.svg') }}{{ item.svg_src }}"></use>
</svg>
</div>
<div class="usa-icon-list__content">
{{item.card_heading | safe }}
</div>
</li>
{% endfor %}
</ul>
</div>
<div class="grid-col-12">
<h3>Additional data</h3>
<ul class="usa-icon-list">
{% for item in additional_data %}
<li class="usa-icon-list__item">
<div class="usa-icon-list__icon text-green">
<svg aria-hidden="true" focusable="false" role="img" class="usa-icon">
<use xlink:href="{{ asset_url('img/sprite.svg') }}{{ item.svg_src }}"></use>
</svg>
</div>
<div class="usa-icon-list__content">
{{item.card_heading | safe }}
</div>
</li>
{% endfor %}
</ul>
</div>
<div class="grid-col-12">
<h3>If an error occurs</h3>
<ul class="usa-icon-list">
<li class="usa-icon-list__item">
<div class="usa-icon-list__icon text-red">
<svg aria-hidden="true" focusable="false" role="img" class="usa-icon">
<use xlink:href="{{ asset_url('img/sprite.svg') }}#error"></use>
</svg>
</div>
<div class="usa-icon-list__content">
If you're receiving any errors, double check your file format and make sure your data is pasted in with values only
</div>
</li>
</ul>
</div>
</div>
</div>
<h2 class="font-body-lg margin-bottom-2">You are filling in this template</h2>
<p>Template: {{ template.name }}</p>
{{ template|string }}
{% endblock %}

View File

@@ -47,7 +47,6 @@ const javascripts = () => {
paths.npm + 'hogan.js/dist/hogan-3.0.2.js',
paths.npm + 'jquery/dist/jquery.min.js',
paths.npm + 'query-command-supported/dist/queryCommandSupported.min.js',
paths.npm + 'timeago/jquery.timeago.js',
paths.npm + 'textarea-caret/index.js',
paths.npm + 'cbor-js/cbor.js',
paths.npm + 'd3/dist/d3.min.js',
@@ -68,13 +67,11 @@ const javascripts = () => {
paths.src + 'javascripts/errorTracking.js',
paths.src + 'javascripts/preventDuplicateFormSubmissions.js',
paths.src + 'javascripts/fullscreenTable.js',
paths.src + 'javascripts/colourPreview.js',
paths.src + 'javascripts/templateFolderForm.js',
paths.src + 'javascripts/collapsibleCheckboxes.js',
paths.src + 'javascripts/radioSlider.js',
paths.src + 'javascripts/updateStatus.js',
paths.src + 'javascripts/errorBanner.js',
paths.src + 'javascripts/homepage.js',
paths.src + 'javascripts/timeoutPopup.js',
paths.src + 'javascripts/date.js',
paths.src + 'javascripts/loginAlert.js',

View File

@@ -608,8 +608,6 @@ def validate_phone_number(number, international=False):
try:
parsed = phonenumbers.parse(number, None)
if parsed.country_code != 1:
raise InvalidPhoneError("Invalid country code")
number = f"{parsed.country_code}{parsed.national_number}"
if len(number) < 8:
raise InvalidPhoneError("Not enough digits")

780
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -40,16 +40,15 @@
"playwright": "^1.51.1",
"python": "^0.0.4",
"query-command-supported": "1.0.0",
"sass-embedded": "^1.86.1",
"sass-embedded": "^1.86.3",
"textarea-caret": "3.1.0",
"timeago": "1.6.7",
"vinyl-buffer": "^1.0.1",
"vinyl-source-stream": "^2.0.0"
},
"devDependencies": {
"@babel/core": "^7.26.10",
"@babel/preset-env": "^7.26.9",
"@uswds/compile": "^1.2.1",
"@uswds/compile": "^1.2.2",
"backstopjs": "^6.3.25",
"better-npm-audit": "^3.11.0",
"gulp": "^5.0.0",
@@ -66,7 +65,7 @@
"jest-environment-jsdom": "^29.2.2",
"jshint": "2.13.6",
"jshint-stylish": "2.2.1",
"rollup": "^4.38.0",
"rollup": "^4.40.0",
"rollup-plugin-commonjs": "10.1.0",
"rollup-plugin-node-resolve": "5.2.0"
}

228
poetry.lock generated
View File

@@ -2,25 +2,25 @@
[[package]]
name = "ago"
version = "0.0.95"
version = "0.1.0"
description = "ago: Human readable timedeltas"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "ago-0.0.95.tar.gz", hash = "sha256:d2010f5eac3df544ec48ec116102e068591a345b1a580f32973db8a505fca744"},
{file = "ago-0.1.0-py3-none-any.whl", hash = "sha256:c0c60d106fe333ac06e2ff2dd7fb5de2c4ec3183ee39708a35b6144ccb86dece"},
{file = "ago-0.1.0.tar.gz", hash = "sha256:15604159711c47e08f21251f28bfb4383942087535660dbe640b2f6193f92be4"},
]
[[package]]
name = "async-timeout"
version = "4.0.3"
version = "5.0.1"
description = "Timeout context manager for asyncio programs"
optional = false
python-versions = ">=3.7"
groups = ["main"]
python-versions = ">=3.8"
files = [
{file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
{file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
{file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"},
{file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"},
]
[[package]]
@@ -1546,72 +1546,72 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
[[package]]
name = "markupsafe"
version = "2.1.5"
version = "3.0.2"
description = "Safely add untrusted strings to HTML/XML markup."
optional = false
python-versions = ">=3.7"
groups = ["main", "dev"]
python-versions = ">=3.9"
files = [
{file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"},
{file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"},
{file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"},
{file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"},
{file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"},
{file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"},
{file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"},
{file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"},
{file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"},
{file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"},
{file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"},
{file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"},
{file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"},
{file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"},
{file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"},
{file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"},
{file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"},
{file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"},
{file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"},
{file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"},
{file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"},
{file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"},
{file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"},
{file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"},
{file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"},
{file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"},
{file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"},
{file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"},
{file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"},
{file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"},
{file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"},
{file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"},
{file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"},
{file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"},
{file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"},
{file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"},
{file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"},
{file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"},
{file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"},
{file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"},
{file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"},
{file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"},
{file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"},
{file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"},
{file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"},
{file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"},
{file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"},
{file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"},
{file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"},
{file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"},
{file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"},
{file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"},
{file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"},
{file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"},
{file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"},
{file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"},
{file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"},
{file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"},
{file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"},
{file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"},
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"},
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"},
{file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"},
{file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"},
{file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"},
{file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"},
{file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"},
{file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"},
{file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"},
{file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"},
{file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"},
{file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"},
{file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"},
{file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"},
{file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"},
{file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"},
{file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"},
{file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"},
{file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"},
{file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"},
{file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"},
{file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"},
{file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"},
{file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"},
{file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"},
{file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"},
{file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"},
{file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"},
{file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"},
{file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"},
{file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"},
{file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"},
{file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"},
{file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"},
{file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"},
{file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"},
{file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"},
{file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"},
{file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"},
{file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"},
{file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"},
{file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"},
{file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"},
{file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"},
{file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"},
{file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"},
{file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"},
{file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"},
{file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"},
{file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"},
{file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"},
{file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"},
]
[[package]]
@@ -1652,14 +1652,14 @@ files = [
[[package]]
name = "moto"
version = "5.1.2"
version = "5.1.3"
description = "A library that allows you to easily mock out tests based on AWS infrastructure"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "moto-5.1.2-py3-none-any.whl", hash = "sha256:3789084bb20052b6eb846fe6f4831ce6dfe8a3b197c8f63789b40281b5e1731d"},
{file = "moto-5.1.2.tar.gz", hash = "sha256:0e4c650d31eacfbe726c37e956efa04d36948e23f7d3228a7c3746aa839e66c2"},
{file = "moto-5.1.3-py3-none-any.whl", hash = "sha256:6355b4c7208bd8d884354127824989034f1979da7b96d6e9789a0f934c0f7d6c"},
{file = "moto-5.1.3.tar.gz", hash = "sha256:078e73f6fe27a76283f82c6c5507b9c32c0d5cfe32ad4a3d1434f62798da6166"},
]
[package.dependencies]
@@ -1784,41 +1784,41 @@ files = [
[[package]]
name = "newrelic"
version = "10.8.1"
version = "10.9.0"
description = "New Relic Python Agent"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "newrelic-10.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c1c8438138ca530ecbb4a8d52805520b6815e65e93856739494aa657e1b464a"},
{file = "newrelic-10.8.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:824d36a9be37c43f7d66bed46689c1c2168eba60c591299e932684e03eb10e42"},
{file = "newrelic-10.8.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e2d02e6edd5a9c0e2229e6ca1fa114d9ee95e7c137d3eaa9de3ce8b1ce73b62b"},
{file = "newrelic-10.8.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0c5f96c2ec732258980ea1c2ae8e66ec834ff4c40a815ffdacf3154f1e53d3d5"},
{file = "newrelic-10.8.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03552deef7ee3d9d40b579beca702ac65105d7b5ebd603da0520961b96acadfe"},
{file = "newrelic-10.8.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:600aa9b4dafcc4e0d16cc9c0bd40e2fe07a5fa3aa03f4293ba69045738885473"},
{file = "newrelic-10.8.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:75dbdceb1601368becaf0e37af98eadcd5c41d0fb362b906d11eb6697528b52b"},
{file = "newrelic-10.8.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a928b17f4c4d729f0522b3029da5ece1d32d2c5f0a89d500a037619bc53a0b06"},
{file = "newrelic-10.8.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:055a8316e3eb1ddb230495928c5d4ad71ad18d11a295092123a1de9f01efa385"},
{file = "newrelic-10.8.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a25ce0228eb104274b71eadf110971c51c90f80c7c6f31f62b3fda4d732eb0a3"},
{file = "newrelic-10.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3c025d52c45b17abaaeb1907e18104567e65c0d36f855a8fe9834cf105e0ca46"},
{file = "newrelic-10.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:28c26c54b6be6a4827ce0cbd056bbb12c5aec148a7de0b664438cc35bb93afdb"},
{file = "newrelic-10.8.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1688c9dcfb101b30acb4baf2e2f45159c3d298cdd585204eedf4642e2850fe3"},
{file = "newrelic-10.8.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b68067a89eca771e766370fdf3a7f4dd5e02372957051e54fad23096b93e1819"},
{file = "newrelic-10.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59055c2265183f7ec9fff9c657451accba6f84eb0ceb5dca9a6dcaa012f2df6b"},
{file = "newrelic-10.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b3c70ef477e91e9c4419905940769c8223a9d21bf388473687aa500998f23543"},
{file = "newrelic-10.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1ce54252d6e3c0b02887b284b58011c9b5a0d82deafc2dc9313be9cb757a720"},
{file = "newrelic-10.8.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9a053f1ba62b89c2a1543a8a55e36bc1cd3c77a39a98744927ba298ce4e3af3"},
{file = "newrelic-10.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5444474ba9aa6a08e0235b931c4506c9d78911d9bdbb3a68132107186bd53b35"},
{file = "newrelic-10.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89d75646853ce429ba7fe6d7a0d50100107040525094d8f14b965660a6ea41f7"},
{file = "newrelic-10.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9a3410ea09e70af92181db485b7772ae7c9243d4605dba20e9dd6cea08e0a1"},
{file = "newrelic-10.8.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7396a8e0b52ce7cb8a6f5cbef5196da7356b3197421617648b9c6d861aa9fe3"},
{file = "newrelic-10.8.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:457c74a3fbc4484111f0e5473dc8186da5cd371ced820fb28107fed9790ddc55"},
{file = "newrelic-10.8.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:935fdb99883636da4f8aca3d44646c0262c74e1322342e86f0ab0016ecd4cb37"},
{file = "newrelic-10.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b823bd4ef787e47f4a3c39a03ba9634dfadee609bb11d050634316f376a9368b"},
{file = "newrelic-10.8.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17a696256c76af9f186af93aa4ef97e9b3e2bd832bec263f5e0b69f47e7ad2f6"},
{file = "newrelic-10.8.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c63924d7748555af011de1d04c0f51069d78fb45112ded407cec39d171763cd3"},
{file = "newrelic-10.8.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d0b01b8a7ff3596825d99ef88b5c04ad6a312d0c3969e97651fda3a35b6212c2"},
{file = "newrelic-10.8.1.tar.gz", hash = "sha256:4eac63a95a4565198c36a4ffacb3798bd53affb6fb188531e201f8eb239ee86b"},
{file = "newrelic-10.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c35e1405a0e0bbe2f6a2d48c54155a5d826f9d06822e6aed76e1bd4e3dd19a22"},
{file = "newrelic-10.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:113db07e79bd0583420793a7db32639038ad04b20b0c73f9d06888a29ece2a6d"},
{file = "newrelic-10.9.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:28775472638d837e1aef0dc462526e4d75e4a187317fccffc215ecc6066c8a6d"},
{file = "newrelic-10.9.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f955f997a6581d604115123f4a35a6cbfab9a0e0540f9341c61998d8a6302900"},
{file = "newrelic-10.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeffadc14bd0c2985d2f1872039969a4ddad217e772b881b2d2938f65da894d9"},
{file = "newrelic-10.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a841884b1f51b13d95ee119fee69fb7b6191abbb3ecfeab5f3d5cb51b633e735"},
{file = "newrelic-10.9.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0b72834b5634116c258caf1ab0dd0d7157648596ed2c98a1128849a4689e92d0"},
{file = "newrelic-10.9.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:95263e182be8ccef73e847136c0fa8d70d728c9dab2be3402f6e7e174ff14fa6"},
{file = "newrelic-10.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e5e4894a6fdb7d91ad411d201bdf4b44d9e1b8652c5174bb992086ed2e5d099"},
{file = "newrelic-10.9.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7039dcaced71a59ba5cb5007f303ac02dd0e5ae6eabbe1d836987d563a55d4b"},
{file = "newrelic-10.9.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9a2b8b2e76d7db53f4152843cc13ae2db6bc7689f496e5db5643e68a4820332d"},
{file = "newrelic-10.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:25e500c001d2ab29cffb012a60a2e2638343f199931706ffc302208a92b20b24"},
{file = "newrelic-10.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7906b7631e58c8ad71e1eb4f2538f92a02b15678687045c92cfaf89ed88ff608"},
{file = "newrelic-10.9.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19a6ff878e1b611cf3b0c407c91702863a5233b38ebb16be5d8767df54a355b9"},
{file = "newrelic-10.9.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:992a67711e9946ed48856450d68d5764c809a0d8970bd4eb84ef03ab4faf4e76"},
{file = "newrelic-10.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:32feb34886284c73d16a6b44a349af8b2a3d01e6ae63f0579d82226b5f9dc0da"},
{file = "newrelic-10.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6de880814d2eaa9aa68a046666dfec90880c1af8b949263471c9e40a9476a00e"},
{file = "newrelic-10.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01b06ef9e3725e92d92978fe3526188864ae912569d63728fd0c7cb76914fff9"},
{file = "newrelic-10.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0ff3dd40fbdbc93886be217d4430a3d6352d7456e92cfd59f463865d3f76d704"},
{file = "newrelic-10.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4be2e2bc1cdb129c130a31378c1b031b544e332d7f085adcdb1f50911d88b782"},
{file = "newrelic-10.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6e9049bc6f72a90911c3ea79a5559b41c83e136b8eea07db622405450fa09fe"},
{file = "newrelic-10.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:227ef495e64544d8ed87d4829bb64246ebcba5c192eb58b5e8b009bc761fe06a"},
{file = "newrelic-10.9.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4901baf6f3235945f4238465b5f9e07c52f7eb81d283d23252349265e21ab6f8"},
{file = "newrelic-10.9.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c7e8764d126b8f905a889ce84792ec220a991ed639068a4278ff939f8147c218"},
{file = "newrelic-10.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fbdc89951bd13f134984e475fd47ee9a03e1f8d5ee5992c0988654cc0675252"},
{file = "newrelic-10.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb0529857393ae54c8f5c36c4360d8a5bc30c326c37e240af3e39823f4c974f7"},
{file = "newrelic-10.9.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a95d6dd7f02d1f00df030e222115ece8f1fe3153d7670e3f619b16f0923ccbc1"},
{file = "newrelic-10.9.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ddf43e2fbad8500df10a59d4638902c8b1781dbecd28ad97a3df5bf923eee0a9"},
{file = "newrelic-10.9.0.tar.gz", hash = "sha256:0741de2138b41a1ae1cfad397878774de4131196d66f1443a23b055d9f47e706"},
]
[package.extras]
@@ -2006,14 +2006,14 @@ setuptools = "*"
[[package]]
name = "phonenumbers"
version = "8.13.55"
version = "9.0.3"
description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers."
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "phonenumbers-8.13.55-py2.py3-none-any.whl", hash = "sha256:25feaf46135f0fb1e61b69513dc97c477285ba98a69204bf5a8cf241a844a718"},
{file = "phonenumbers-8.13.55.tar.gz", hash = "sha256:57c989dda3eabab1b5a9e3d24438a39ebd032fa0172bf68bfd90ab70b3d5e08b"},
{file = "phonenumbers-9.0.3-py2.py3-none-any.whl", hash = "sha256:0509d9b70ef66c04a1538f0b31c3e0c0b023127dad0eb1199adef0bc1841ff34"},
{file = "phonenumbers-9.0.3.tar.gz", hash = "sha256:c5084e6c6a77e0e8d725086c7f69002ef8e39529155b8cd31cbf8c987f344a33"},
]
[[package]]
@@ -2045,19 +2045,19 @@ pip = "*"
[[package]]
name = "pip-audit"
version = "2.8.0"
version = "2.9.0"
description = "A tool for scanning Python environments for known vulnerabilities"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "pip_audit-2.8.0-py3-none-any.whl", hash = "sha256:200f50d56cb6fba3a9189c20d53250354f72f161d63b6ef77ae5de2b53044566"},
{file = "pip_audit-2.8.0.tar.gz", hash = "sha256:9816cbd94de6f618a8965c117433006b3d565a708dc05d5a7be47ab65b66fa05"},
{file = "pip_audit-2.9.0-py3-none-any.whl", hash = "sha256:348b16e60895749a0839875d7cc27ebd692e1584ebe5d5cb145941c8e25a80bd"},
{file = "pip_audit-2.9.0.tar.gz", hash = "sha256:0b998410b58339d7a231e5aa004326a294e4c7c6295289cdc9d5e1ef07b1f44d"},
]
[package.dependencies]
CacheControl = {version = ">=0.13.0", extras = ["filecache"]}
cyclonedx-python-lib = ">=5,<9"
cyclonedx-python-lib = ">=5,<10"
packaging = ">=23.0.0"
pip-api = ">=0.0.28"
pip-requirements-parser = ">=32.0.0"
@@ -2328,14 +2328,14 @@ xlwt = "*"
[[package]]
name = "pyexcel-xlsx"
version = "0.6.0"
version = "0.6.1"
description = "A wrapper library to read, manipulate and write data in xlsx and xlsmformat"
optional = false
python-versions = ">=3.6"
groups = ["main"]
files = [
{file = "pyexcel-xlsx-0.6.0.tar.gz", hash = "sha256:55754f764252461aca6871db203f4bd1370ec877828e305e6be1de5f9aa6a79d"},
{file = "pyexcel_xlsx-0.6.0-py2.py3-none-any.whl", hash = "sha256:16530f96a77c97ebcba7941517d2756ac52d3ce2903d81eecd7f300778d5242a"},
{file = "pyexcel_xlsx-0.6.1-py2.py3-none-any.whl", hash = "sha256:c17d8c90ae6c2ae5672e469e6afe4336597e4ff479e5a93ba846ae46b7b80769"},
{file = "pyexcel_xlsx-0.6.1.tar.gz", hash = "sha256:80b7985d394b73abf5c52212027bbbc0c7f6090192bcbc76403f8672d74c7960"},
]
[package.dependencies]
@@ -3300,4 +3300,4 @@ files = [
[metadata]
lock-version = "2.1"
python-versions = "^3.12.2"
content-hash = "fc363e188984011a288603a65ba33b2f8c7658c3e0ade8e87ba986bd7ff65620"
content-hash = "418596e226a87272f31ee6db71b26e45754dad12d29761c9cff83cd86363c0a5"

View File

@@ -9,7 +9,7 @@ package-mode = false
[tool.poetry.dependencies]
axe-core-python = "^0.1.0"
python = "^3.12.2"
ago = "~=0.0.95"
ago = "~=0.1.0"
beautifulsoup4 = "^4.13.3"
blinker = "~=1.8"
exceptiongroup = "==1.2.2"
@@ -28,7 +28,7 @@ pyexcel = "==0.7.2"
pyexcel-io = "==0.6.7"
pyexcel-ods3 = "==0.6.1"
pyexcel-xls = "==0.7.1"
pyexcel-xlsx = "==0.6.0"
pyexcel-xlsx = "==0.6.1"
openpyxl = "==3.0.10"
pyproj = "==3.7.1"
python-dotenv = "==1.1.0"
@@ -38,7 +38,7 @@ werkzeug = "^3.1.3"
wtforms = "~=3.1"
markdown = "^3.7"
mistune = "^3.1.3"
async-timeout = "^4.0.3"
async-timeout = "^5.0.1"
bleach = "^6.1.0"
boto3 = "^1.35.99"
botocore = "^1.35.99"
@@ -50,7 +50,7 @@ geojson = "^3.1.0"
jmespath = "^1.0.1"
numpy = "^2.2.4"
ordered-set = "^4.1.0"
phonenumbers = "^8.13.52"
phonenumbers = "^9.0.3"
pycparser = "^2.22"
python-json-logger = "^3.3.0"
redis = "^5.2.1"
@@ -62,7 +62,7 @@ certifi = "^2025.1.31"
charset-normalizer = "^3.4.1"
click = "^8.1.8"
idna = "^3.7"
markupsafe = "^2.1.5"
markupsafe = "^3.0.2"
python-dateutil = "^2.9.0.post0"
pyyaml = "^6.0.1"
requests = "^2.32.3"

View File

@@ -14,7 +14,7 @@ def test_owasp_useful_headers_set(
assert response.headers["X-Frame-Options"] == "deny"
assert response.headers["X-Content-Type-Options"] == "nosniff"
csp = response.headers["Content-Security-Policy"]
assert search(r"default-src 'self' static\.example\.com;", csp)
assert search(r"frame-src.*https://www\.googletagmanager\.com", csp)
assert search(r"frame-ancestors 'none';", csp)
assert search(r"form-action 'self';", csp)
assert search(

View File

@@ -1,6 +1,6 @@
from unittest.mock import Mock
from app.s3_client.s3_csv_client import set_metadata_on_csv_upload
from app.s3_client.s3_csv_client import remove_blank_lines, set_metadata_on_csv_upload
def test_sets_metadata(client_request, mocker):
@@ -21,3 +21,11 @@ def test_sets_metadata(client_request, mocker):
MetadataDirective="REPLACE",
ServerSideEncryption="AES256",
)
def test_removes_blank_lines():
filedata = {
"data": "phone number\r\n15555555555\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"
}
file_data = remove_blank_lines(filedata)
assert file_data == {"data": "phone number\n15555555555"}

View File

@@ -18,6 +18,12 @@ Object.defineProperty(HTMLElement.prototype, 'clientWidth', {
});
// beforeAll hook to set up the DOM and load D3.js script
beforeAll(() => {
jest.spyOn(Intl, 'DateTimeFormat').mockImplementation(() => ({
resolvedOptions: () => ({ timeZone: 'UTC' })
}));
});
beforeAll(done => {
// Set up the DOM with the D3 script included
document.body.innerHTML = `

View File

@@ -1,139 +0,0 @@
const helpers = require('./support/helpers.js');
beforeAll(() => {
require('../../app/assets/javascripts/colourPreview.js');
});
afterAll(() => {
require('./support/teardown.js');
});
describe('Colour preview', () => {
let field;
let textbox;
let swatchEl;
beforeEach(() => {
// set up DOM
document.body.innerHTML = `
<div class="usa-form-group">
<label class="govuk-form-label" for="colour">
Colour
</label>
<input class="govuk-input govuk-input--width-6" id="colour" name="colour" rows="8" type="text" value="" data-module="colour-preview">
</div>`;
field = document.querySelector('.usa-form-group');
textbox = document.querySelector('input[type=text]');
});
afterEach(() => {
document.body.innerHTML = '';
});
describe("When the page loads", () => {
test("It should add a swatch element for the preview", () => {
// start the module
window.GOVUK.modules.start();
swatchEl = document.querySelector('.textbox-colour-preview');
expect(swatchEl).not.toBeNull();
});
test("If the textbox is empty it should make the swatch white", () => {
// start the module
window.GOVUK.modules.start();
swatchEl = document.querySelector('.textbox-colour-preview');
// textbox defaults to empty
// colours are output in RGB
expect(swatchEl.style.background).toEqual('rgb(255, 255, 255)');
});
test("If the textbox has a value which is a hex code it should add that colour to the swatch", () => {
textbox.setAttribute('value', '#00FF00');
// start the module
window.GOVUK.modules.start();
swatchEl = document.querySelector('.textbox-colour-preview');
// colours are output in RGB
expect(swatchEl.style.background).toEqual('rgb(0, 255, 0)');
});
test("If the textbox has a value which isn't a hex code it should make the swatch white", () => {
textbox.setAttribute('value', 'green');
// start the module
window.GOVUK.modules.start();
swatchEl = document.querySelector('.textbox-colour-preview');
// colours are output in RGB
expect(swatchEl.style.background).toEqual('rgb(255, 255, 255)');
});
});
describe("When input is added to the textbox", () => {
beforeEach(() => {
// start the module
window.GOVUK.modules.start();
swatchEl = document.querySelector('.textbox-colour-preview');
});
test("If the textbox is empty it should make the swatch white", () => {
helpers.triggerEvent(document.querySelector('input[type=text]'), 'input');
// textbox defaults to empty
expect(swatchEl.style.background).toEqual('rgb(255, 255, 255)');
});
test("If the textbox has a value which is a hex code it should add that colour to the swatch", () => {
textbox.setAttribute('value', '#00FF00');
helpers.triggerEvent(document.querySelector('input[type=text]'), 'input');
// textbox defaults to empty
expect(swatchEl.style.background).toEqual('rgb(0, 255, 0)');
});
test("If the textbox has a value which isn't a hex code it should make the swatch white", () => {
textbox.setAttribute('value', 'green');
helpers.triggerEvent(document.querySelector('input[type=text]'), 'input');
// textbox defaults to empty
expect(swatchEl.style.background).toEqual('rgb(255, 255, 255)');
});
});
});

View File

@@ -1,370 +1,88 @@
const helpers = require('./support/helpers');
require('../../app/assets/javascripts/fullscreenTable.js');
beforeAll(() => {
// TODO: remove this when tests for sticky JS are written
require('../../app/assets/javascripts/stick-to-window-when-scrolling.js');
describe('fullscreenTable', () => {
let wrapper, rightShadow;
require('../../app/assets/javascripts/fullscreenTable.js');
});
afterAll(() => {
require('./support/teardown.js');
});
describe('FullscreenTable', () => {
let screenMock;
let container;
let tableFrame;
let table;
let numberColumnFrame;
beforeEach(() => {
const tableHeadings = () => {
let result = '';
const headings = ['1', 'name', 'email address', 'age', 'building number', 'address line 1', 'address line 2', 'postcode'];
headings.forEach((heading, idx) => {
if (idx === 0) {
result += `<th scope="col" class="table-field-heading-first">
<span class="visually-hidden">Row in file</span><span aria-hidden="true" class="table-field-invisible-error">${heading}</span>
</th>`;
} else {
result += `<th scope="col" class="table-field-heading">
${heading}
</th>`;
}
});
return result;
}
const rowCells = (cells) => {
let result = '';
Object.keys(cells).forEach((key, idx) => {
if (idx === 0) {
result += `<td class="table-field-index">
<span class="table-field-error">
${key}
</span>
</td>`;
} else {
result += `<td class="table-field-left-aligned ">
<div class="table-field-status-default">
${key}
</div>
</td>`;
}
});
return result;
};
const tableRows = () => {
let result = '';
const rows = [
['John Logie Baird', 'johnlbaird@gmail.com', '37', '22', 'Frith Street', 'Soho, London', 'W1D 4RF'],
['Guglielmo Marconi', 'gmarconi@hotmail.com', '21', 'Pontecchio Marconi', 'Via Celestini 1', 'Bologna', ''],
['Louis Braille', 'louisbraille@yahoo.co.uk', '', '56', 'Boulevard des Invalides', 'Paris', '75007'],
['Ray Tomlinson', 'hedy.lamarr@msn.com', '25', '870', '870 Winter Street', 'Waltham', 'MA 02451']
];
rows.forEach(row => {
result += `<tr class="table-row">${rowCells(row)}</tr>`;
});
return result;
}
screenMock = new helpers.ScreenMock(jest);
screenMock.setWindow({
width: 1990,
height: 940,
scrollTop: 0
});
// set up DOM
document.body.innerHTML =
`<main>
<div class="fullscreen-content" data-module="fullscreen-table">
<table class="table table-font-xsmall">
<caption class="heading-medium table-heading visuallyhidden">
people.csv
</caption>
<thead class="table-field-headings-visible">
<tr>
${tableHeadings()}
</tr>
</thead>
<tbody>
${tableRows()}
</tbody>
const setupDOM = (scrollLeft = 0, scrollWidth = 1000, clientWidth = 500) => {
document.body.innerHTML = `
<div class="fullscreen-table">
<div class="table-wrapper" style="overflow-x: scroll;">
<table>
<tr><td>Test</td></tr>
</table>
</div>
</main>`;
<div class="fullscreen-right-shadow"></div>
</div>
`;
container = document.querySelector('.fullscreen-content');
wrapper = document.querySelector('.table-wrapper');
rightShadow = document.querySelector('.fullscreen-right-shadow');
});
// Mock scroll values
Object.defineProperty(wrapper, 'scrollLeft', {
get: () => scrollLeft,
configurable: true
});
Object.defineProperty(wrapper, 'scrollWidth', {
get: () => scrollWidth,
configurable: true
});
Object.defineProperty(wrapper, 'clientWidth', {
get: () => clientWidth,
configurable: true
});
// 👇 Now that DOM is ready, initialize the logic
window.initFullscreenTables();
// Force trigger scroll event if needed
wrapper.dispatchEvent(new Event('scroll'));
};
afterEach(() => {
document.body.innerHTML = '';
});
describe("when it loads", () => {
test("it fixes the number column for each row without changing the semantics", () => {
// start module
window.GOVUK.modules.start();
tableFrame = document.querySelector('.fullscreen-scrollable-table');
numberColumnFrame = document.querySelector('.fullscreen-fixed-table');
expect(tableFrame).not.toBeNull();
expect(numberColumnFrame).not.toBeNull();
expect(numberColumnFrame.getAttribute('aria-hidden')).toEqual('true');
});
test("it calls the sticky JS to update any cached dimensions", () => {
const stickyJSSpy = jest.spyOn(window.GOVUK.stickAtBottomWhenScrolling, 'recalculate');
// start module
window.GOVUK.modules.start();
expect(stickyJSSpy.mock.calls.length).toBe(1);
stickyJSSpy.mockClear();
});
test("the scrolling section is focusable and has an accessible name matching the table caption", () => {
// start module
window.GOVUK.modules.start();
tableFrame = document.querySelector('.fullscreen-scrollable-table');
caption = tableFrame.querySelector('caption');
expect(tableFrame.hasAttribute('role')).toBe(true);
expect(tableFrame.getAttribute('role')).toEqual('region');
expect(tableFrame.hasAttribute('aria-labelledby')).toBe(true);
expect(tableFrame.getAttribute('aria-labelledby')).toEqual(caption.getAttribute('id'));
});
test("the section providing the fixed row headers is not focusable and is hidden from assistive tech'", () => {
// start module
window.GOVUK.modules.start();
fixedRowHeaders = document.querySelector('.fullscreen-fixed-table');
expect(fixedRowHeaders.hasAttribute('role')).toBe(false);
expect(fixedRowHeaders.hasAttribute('aria-labelledby')).toBe(false);
expect(fixedRowHeaders.hasAttribute('tabindex')).toBe(false);
expect(fixedRowHeaders.hasAttribute('aria-hidden')).toBe(true);
expect(fixedRowHeaders.getAttribute('aria-hidden')).toEqual('true');
});
test('adds right shadow on initial load if scrollable', () => {
setupDOM(0, 1000, 500);
expect(wrapper.classList.contains('scrolled')).toBe(false);
expect(rightShadow.classList.contains('visible')).toBe(true);
});
describe("the height of the table should fit the vertical space available to it", () => {
let containerBoundingClientRectSpy;
let containerClientRectsSpy;
beforeEach(() => {
// set the height and offset of the window and table container from the top of the document
// so just the top 268px of it appears on-screen
screenMock.window.setHeightTo(768);
screenMock.mockPositionAndDimension('container', container, {
'offsetHeight': 1000,
'offsetWidth': 641,
'offsetTop': 500
});
// start module
window.GOVUK.modules.start();
tableFrame = document.querySelector('.fullscreen-scrollable-table');
numberColumnFrame = document.querySelector('.fullscreen-fixed-table');
});
afterEach(() => {
screenMock.reset();
});
test("when the page has loaded", () => {
// the frames should crop to the top 268px of the table that is visible
expect(window.getComputedStyle(tableFrame)['height']).toEqual('268px');
expect(window.getComputedStyle(numberColumnFrame)['height']).toEqual('268px');
});
test("when the page has scrolled", () => {
// scroll the window so the table fills the height of the window (768px)
screenMock.window.scrollTo(500);
// the frames should crop to the window height
expect(window.getComputedStyle(tableFrame)['height']).toEqual('768px');
expect(window.getComputedStyle(numberColumnFrame)['height']).toEqual('768px');
});
test("when the page has resized", () => {
// resize the window by 232px (from 768px to 1000px)
screenMock.window.resizeTo({ height: 1000, width: 1024 });
// the frames should crop to the top 500px of the table now visible
expect(window.getComputedStyle(tableFrame)['height']).toEqual('500px');
expect(window.getComputedStyle(numberColumnFrame)['height']).toEqual('500px');
});
test('hides right shadow when scrolled to max', () => {
setupDOM(500, 1000, 500);
expect(wrapper.classList.contains('scrolled')).toBe(true);
expect(rightShadow.classList.contains('visible')).toBe(false);
});
describe("the width of the table should fit the horizontal space available to it", () => {
let rowNumberColumnCell;
beforeEach(() => {
rowNumberColumnCell = container.querySelector('.table-field-index');
// set main content column width (used as module as gauge for table width)
screenMock.window.setWidthTo(1024);
document.querySelector('main').setAttribute('style', 'width: 742px');
// set total width of column for row numbers in table to 40px
rowNumberColumnCell.setAttribute('style', 'width: 40px');
// start module
window.GOVUK.modules.start();
tableFrame = document.querySelector('.fullscreen-scrollable-table');
numberColumnFrame = document.querySelector('.fullscreen-fixed-table');
});
afterEach(() => {
screenMock.reset();
});
test("when the page has loaded", () => {
// table should set its width to be that of `<main>`, minus margin-left for the row numbers column
expect(window.getComputedStyle(tableFrame)['width']).toEqual('702px'); // width of content column - numbers column
expect(window.getComputedStyle(tableFrame)['margin-left']).toEqual('40px'); // width of numbers column
// table for number column has 4px extra to allow space for drop shadow
expect(window.getComputedStyle(numberColumnFrame)['width']).toEqual('44px');
});
test("when the page has resized", () => {
// resize window and content column
document.querySelector('main').setAttribute('style', 'width: 720px');
screenMock.window.resizeTo({ height: 768, width: 960 });
// table should set its width to be that of `<main>`, minus margin-left for the row numbers column
expect(window.getComputedStyle(tableFrame)['width']).toEqual('680px'); // width of content column - numbers column
expect(window.getComputedStyle(tableFrame)['margin-left']).toEqual('40px'); // width of numbers column
// table for number column has 4px extra to allow space for drop shadow
expect(window.getComputedStyle(numberColumnFrame)['width']).toEqual('44px');
});
test('handles mid-scroll: left + right shadows active', () => {
setupDOM(250, 1000, 500);
expect(wrapper.classList.contains('scrolled')).toBe(true);
expect(rightShadow.classList.contains('visible')).toBe(true);
});
describe("when the table scrolls horizontally", () => {
let rightEdgeShadow;
test('gracefully handles missing right shadow', () => {
document.body.innerHTML = `
<div class="fullscreen-table">
<div class="table-wrapper">
<table>
<tr><td>Only content</td></tr>
</table>
</div>
</div>
`;
beforeEach(() => {
wrapper = document.querySelector('.table-wrapper');
// start module
window.GOVUK.modules.start();
Object.defineProperty(wrapper, 'scrollLeft', { get: () => 100, configurable: true });
Object.defineProperty(wrapper, 'scrollWidth', { get: () => 500, configurable: true });
Object.defineProperty(wrapper, 'clientWidth', { get: () => 200, configurable: true });
tableFrame = document.querySelector('.fullscreen-scrollable-table');
table = tableFrame.querySelector('table');
numberColumnFrame = document.querySelector('.fullscreen-fixed-table');
rightEdgeShadow = container.querySelector('.fullscreen-right-shadow');
tableFrame.setAttribute('style', 'width: 742px');
table.setAttribute('style', 'width: 1000px');
});
test("the right edge of the table scroll area should have a drop-shadow if it isn't scrolled", () => {
tableFrame.scrollLeft = 0;
helpers.triggerEvent(tableFrame, 'scroll');
expect(numberColumnFrame.classList.contains('fullscreen-scrolled-table')).toBe(false);
expect(rightEdgeShadow.classList.contains('visible')).toBe(true);
});
test("the left edge of the table scroll area should have a drop-shadow if the table is scrolled to 100%", () => {
// scroll to end of table
tableFrame.scrollLeft = 258;
helpers.triggerEvent(tableFrame, 'scroll');
expect(numberColumnFrame.classList.contains('fullscreen-scrolled-table')).toBe(true);
expect(rightEdgeShadow.classList.contains('visible')).toBe(false);
});
test("both edges of the table scroll area should have a drop-shadow if the table is scrolled between 0% and 100%", () => {
// scroll to middle of table
tableFrame.scrollLeft = 129;
helpers.triggerEvent(tableFrame, 'scroll');
expect(numberColumnFrame.classList.contains('fullscreen-scrolled-table')).toBe(true);
expect(rightEdgeShadow.classList.contains('visible')).toBe(true);
});
// 👇 Call after building DOM
window.initFullscreenTables();
wrapper.dispatchEvent(new Event('scroll'));
expect(wrapper.classList.contains('scrolled')).toBe(true);
// No error thrown for missing .fullscreen-right-shadow
});
describe("when the table is focused", () => {
beforeEach(() => {
// start module
window.GOVUK.modules.start();
tableFrame = document.querySelector('.fullscreen-scrollable-table');
tableFrame.focus();
});
test("it should make the parent frame a focus style", () => {
expect(container.classList.contains('js-focus-style')).toBe(true);
});
});
});

View File

View File

@@ -765,6 +765,10 @@ def test_bad_or_missing_data(
assert recipients.has_errors is True
# TODO in the first test where the expected result is {0,1}, the original
# expected result was {0,1,2}. Why? In restoring international capability
# for some reason +447900123 now looks legit. It may have more to do with
# formatting than the actually validity of the number, not sure.
@pytest.mark.parametrize(
("file_contents", "rows_with_bad_recipients"),
[
@@ -775,7 +779,7 @@ def test_bad_or_missing_data(
1234
+447900123
""",
{0, 1, 2},
{0, 1},
),
(
"""
@@ -784,7 +788,7 @@ def test_bad_or_missing_data(
+12022340104, USA
+23051234567, Mauritius
""",
{2},
set(),
),
],
)

View File

@@ -23,21 +23,17 @@ valid_us_phone_numbers = [
"(202) 555-0104",
]
# TODO
# International phone number tests are commented out as a result of issue #943 in notifications-admin. We are
# deliberately eliminating the ability to send to numbers outside of country code 1. These tests should
# be removed at some point when we are sure we are never going to support international numbers
valid_international_phone_numbers = [
# "+71234567890", # Russia
# "+447123456789", # UK
# "+4407123456789", # UK
# "+4407123 456789", # UK
# "+4407123-456-789", # UK
# "+23051234567", # Mauritius,
# "+682 12345", # Cook islands
# "+3312345678",
# "+9-2345-12345-12345", # 15 digits
"+71234567890", # Russia
"+447123456789", # UK
"+4407123456789", # UK
"+4407123 456789", # UK
"+4407123-456-789", # UK
"+23051234567", # Mauritius,
"+682 12345", # Cook islands
"+3312345678",
"+9-2345-12345-12345", # 15 digits
]
@@ -84,7 +80,7 @@ invalid_us_phone_numbers = sum(
invalid_phone_numbers = [
("+80233456789", "Not a valid country prefix"),
("1234567", "Not enough digits"),
("+682 1234", "Invalid country code"), # Cook Islands phone numbers can be 5 digits
("+682 1234", "Not enough digits"),
("+12345 12345 12345 6", "Too many digits"),
]
@@ -155,46 +151,46 @@ def test_detect_us_phone_numbers(phone_number):
@pytest.mark.parametrize(
("phone_number", "expected_info"),
[
# (
# "+4407900900123",
# international_phone_info(
# international=True,
# country_prefix="44", # UK
# billable_units=1,
# ),
# ),
# (
# "+4407700900123",
# international_phone_info(
# international=True,
# country_prefix="44", # Number in TV range
# billable_units=1,
# ),
# ),
# (
# "+4407700800123",
# international_phone_info(
# international=True,
# country_prefix="44", # UK Crown dependency, so prefix same as UK
# billable_units=1,
# ),
# ),
# ( #
# "+20-12-1234-1234",
# international_phone_info(
# international=True,
# country_prefix="20", # Egypt
# billable_units=1,
# ),
# ),
# (
# "+201212341234",
# international_phone_info(
# international=True,
# country_prefix="20", # Egypt
# billable_units=1,
# ),
# ),
(
"+4407900900123",
international_phone_info(
international=True,
country_prefix="44", # UK
billable_units=1,
),
),
(
"+4407700900123",
international_phone_info(
international=True,
country_prefix="44", # Number in TV range
billable_units=1,
),
),
(
"+4407700800123",
international_phone_info(
international=True,
country_prefix="44", # UK Crown dependency, so prefix same as UK
billable_units=1,
),
),
( #
"+20-12-1234-1234",
international_phone_info(
international=True,
country_prefix="20", # Egypt
billable_units=1,
),
),
(
"+201212341234",
international_phone_info(
international=True,
country_prefix="20", # Egypt
billable_units=1,
),
),
(
"+1 664-491-3434",
international_phone_info(
@@ -203,14 +199,14 @@ def test_detect_us_phone_numbers(phone_number):
billable_units=1,
),
),
# (
# "+71234567890",
# international_phone_info(
# international=True,
# country_prefix="7", # Russia
# billable_units=1,
# ),
# ),
(
"+71234567890",
international_phone_info(
international=True,
country_prefix="7", # Russia
billable_units=1,
),
),
(
"1-202-555-0104",
international_phone_info(
@@ -227,14 +223,14 @@ def test_detect_us_phone_numbers(phone_number):
billable_units=1,
),
),
# (
# "+23051234567",
# international_phone_info(
# international=True,
# country_prefix="230", # Mauritius
# billable_units=1,
# ),
# ),
(
"+23051234567",
international_phone_info(
international=True,
country_prefix="230", # Mauritius
billable_units=1,
),
),
],
)
def test_get_international_info(phone_number, expected_info):
@@ -287,11 +283,11 @@ def test_valid_us_phone_number_can_be_formatted_consistently(phone_number):
@pytest.mark.parametrize(
("phone_number", "expected_formatted"),
[
# ("+44071234567890", "+4471234567890"),
("+44071234567890", "+4471234567890"),
("1-202-555-0104", "+12025550104"),
("+12025550104", "+12025550104"),
("12025550104", "+12025550104"),
# ("+23051234567", "+23051234567"),
("+23051234567", "+23051234567"),
],
)
def test_valid_international_phone_number_can_be_formatted_consistently(
@@ -362,17 +358,17 @@ def test_validates_against_guestlist_of_phone_numbers(phone_number):
)
# @pytest.mark.parametrize(
# "recipient_number, allowlist_number",
# [
# ["+4407123-456-789", "+4407123456789"],
# ["+4407123456789", "+4407123-456-789"],
# ],
# )
# def test_validates_against_guestlist_of_international_phone_numbers(
# recipient_number, allowlist_number
# ):
# assert allowed_to_send_to(recipient_number, [allowlist_number])
@pytest.mark.parametrize(
("recipient_number", "allowlist_number"),
[
("+4407123-456-789", "+4407123456789"),
("+4407123456789", "+4407123-456-789"),
],
)
def test_validates_against_guestlist_of_international_phone_numbers(
recipient_number, allowlist_number
):
assert allowed_to_send_to(recipient_number, [allowlist_number])
@pytest.mark.parametrize("email_address", valid_email_addresses)
@@ -385,16 +381,17 @@ def test_validates_against_guestlist_of_email_addresses(email_address):
@pytest.mark.parametrize(
("phone_number", "expected_formatted"),
[
# ("+4407900900123", "+44 7900 900123"), # UK
# ("+44(0)7900900123", "+44 7900 900123"), # UK
# ("+447900900123", "+44 7900 900123"), # UK
("+4407900900123", "+44 7900 900123"), # UK
("+44(0)7900900123", "+44 7900 900123"), # UK
("+447900900123", "+44 7900 900123"), # UK
# TODO these should be fixed, but affect readability, not sendability
# ("+20-12-1234-1234", "+20 121 234 1234"), # Egypt
# ("+201212341234", "+20 121 234 1234"), # Egypt
("+1 664 491-3434", "+1 664-491-3434"), # Montserrat
# ("+7 499 1231212", "+7 499 123-12-12"), # Moscow (Russia)
("+7 499 1231212", "+7 499 123-12-12"), # Moscow (Russia)
("1-202-555-0104", "(202) 555-0104"), # Washington DC (USA)
# ("+23051234567", "+230 5123 4567"), # Mauritius
# ("+33(0)1 12345678", "+33 1 12 34 56 78"), # Paris (France)
("+23051234567", "+230 5123 4567"), # Mauritius
("+33(0)1 12345678", "+33 1 12 34 56 78"), # Paris (France)
],
)
def test_format_us_and_international_phone_numbers(phone_number, expected_formatted):
@@ -411,7 +408,7 @@ def test_format_us_and_international_phone_numbers(phone_number, expected_format
(None, ""),
("foo", "foo"),
("TeSt@ExAmPl3.com", "test@exampl3.com"),
# ("+4407900 900 123", "+447900900123"),
("+4407900 900 123", "+447900900123"),
("+1 800 555 5555", "+18005555555"),
],
)