Compare commits

..

100 Commits

Author SHA1 Message Date
Kenneth Kehl
96c2b7f419 fix 2025-02-20 15:03:48 -08:00
Kenneth Kehl
b77749415f try again with at cache 2025-02-20 14:35:33 -08:00
Kenneth Kehl
2dba0015e1 try cache 2025-02-20 14:07:55 -08:00
ccostino
f9fd580c58 Merge pull request #2350 from GSA/fix-terraform-actions
Update Terraform installation and configuration
2025-02-20 10:52:30 -05:00
Carlo Costino
fe6921e243 Update Terraform installation and configuration
This changeset accounts for having to explicitly install Terraform and updates our user configuration in several environments to account for team member changes.

Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
2025-02-19 21:41:32 -05:00
Beverly Nguyen
d20f887f0b Merge pull request #2346 from GSA/update-github-actions 2025-02-19 14:24:01 -08:00
Carlo Costino
eadd8dddb5 Update GitHub Action References
This changeset updates many of our GitHub Action references to point to the latest versions to ensure they are kept up-to-date.  This helps address any improvements and security patches that have been made to them.

Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
2025-02-19 16:56:09 -05:00
ccostino
77248b8bf0 Merge pull request #2345 from GSA/2344-reverting-back-100000-messages-to-250000-messages-in-front-of-login
reverting back to 250,000
2025-02-19 14:23:59 -05:00
Andrew Shumway
a8d036a3e5 Merge pull request #2340 from GSA/dependabot/pip/pyproj-3.7.1
Bump pyproj from 3.6.1 to 3.7.1
2025-02-19 12:22:49 -07:00
dependabot[bot]
05a746a042 Bump pyproj from 3.6.1 to 3.7.1
Bumps [pyproj](https://github.com/pyproj4/pyproj) from 3.6.1 to 3.7.1.
- [Release notes](https://github.com/pyproj4/pyproj/releases)
- [Changelog](https://github.com/pyproj4/pyproj/blob/main/docs/history.rst)
- [Commits](https://github.com/pyproj4/pyproj/compare/3.6.1...3.7.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-19 18:58:30 +00:00
Andrew Shumway
84acce48c4 Merge pull request #2335 from GSA/dependabot/pip/newrelic-10.6.0
Bump newrelic from 10.2.0 to 10.6.0
2025-02-19 11:55:21 -07:00
Beverly Nguyen
1bbc49c0ad reverting back to 250,000 2025-02-19 10:06:41 -08:00
Alex Janousek
bcd7ead5ec Merge pull request #2338 from GSA/dependabot/npm_and_yarn/babel/core-7.26.9
Bump @babel/core from 7.26.8 to 7.26.9
2025-02-19 12:52:53 -05:00
ccostino
0405c92f69 Merge pull request #2311 from GSA/2171-clean-up-titles
Pluralization around text, fixing conditionals, making bar chart more visible
2025-02-19 12:45:20 -05:00
Beverly Nguyen
ad1f83e80e Merge branch 'main' into 2171-clean-up-titles 2025-02-19 09:05:16 -08:00
dependabot[bot]
44778349f0 Bump @babel/core from 7.26.8 to 7.26.9
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.26.8 to 7.26.9.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.9/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-19 16:28:48 +00:00
Alex Janousek
5b46a4e1d7 Merge pull request #2337 from GSA/dependabot/npm_and_yarn/babel/preset-env-7.26.9
Bump @babel/preset-env from 7.26.7 to 7.26.9
2025-02-19 11:27:43 -05:00
Alex Janousek
91bea21607 Merge pull request #2339 from GSA/dependabot/npm_and_yarn/sass-embedded-1.85.0
Bump sass-embedded from 1.83.4 to 1.85.0
2025-02-19 11:27:33 -05:00
Alex Janousek
ab497be1f8 Merge pull request #2341 from GSA/dependabot/npm_and_yarn/rollup-4.34.8
Bump rollup from 4.34.6 to 4.34.8
2025-02-19 11:27:15 -05:00
dependabot[bot]
5b88eefe27 Bump rollup from 4.34.6 to 4.34.8
Bumps [rollup](https://github.com/rollup/rollup) from 4.34.6 to 4.34.8.
- [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.34.6...v4.34.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 23:50:56 +00:00
dependabot[bot]
34e8647fba Bump sass-embedded from 1.83.4 to 1.85.0
Bumps [sass-embedded](https://github.com/sass/embedded-host-node) from 1.83.4 to 1.85.0.
- [Changelog](https://github.com/sass/embedded-host-node/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/embedded-host-node/compare/1.83.4...1.85.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-14 23:48:09 +00:00
dependabot[bot]
2fdedc1b4f Bump @babel/preset-env from 7.26.7 to 7.26.9
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.26.7 to 7.26.9.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.9/packages/babel-preset-env)

---
updated-dependencies:
- dependency-name: "@babel/preset-env"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-14 23:47:50 +00:00
dependabot[bot]
db1f929dba Bump newrelic from 10.2.0 to 10.6.0
Bumps [newrelic](https://github.com/newrelic/newrelic-python-agent) from 10.2.0 to 10.6.0.
- [Release notes](https://github.com/newrelic/newrelic-python-agent/releases)
- [Commits](https://github.com/newrelic/newrelic-python-agent/compare/v10.2.0...v10.6.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-13 23:35:51 +00:00
Andrew Shumway
e7953262a3 Merge pull request #2329 from GSA/dependabot/pip/botocore-1.35.99
Bump botocore from 1.35.51 to 1.35.99
2025-02-13 10:44:03 -07:00
dependabot[bot]
89cfbd989d Bump botocore from 1.35.51 to 1.35.99
Bumps [botocore](https://github.com/boto/botocore) from 1.35.51 to 1.35.99.
- [Commits](https://github.com/boto/botocore/compare/1.35.51...1.35.99)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-12 21:41:11 +00:00
Andrew Shumway
479cca3adb Merge pull request #2330 from GSA/dependabot/pip/cryptography-44.0.1
Bump cryptography from 43.0.3 to 44.0.1
2025-02-12 14:39:05 -07:00
dependabot[bot]
cc6a66f790 Bump cryptography from 43.0.3 to 44.0.1
Bumps [cryptography](https://github.com/pyca/cryptography) from 43.0.3 to 44.0.1.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/43.0.3...44.0.1)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-11 19:17:18 +00:00
Alex Janousek
0c1dd129e0 Merge pull request #2326 from GSA/dependabot/npm_and_yarn/rollup-4.34.6
Bump rollup from 4.34.4 to 4.34.6
2025-02-11 11:41:40 -05:00
Alex Janousek
d70a80f4a6 Merge pull request #2328 from GSA/dependabot/npm_and_yarn/babel/core-7.26.8
Bump @babel/core from 7.26.7 to 7.26.8
2025-02-11 11:41:25 -05:00
dependabot[bot]
78cfafae94 Bump @babel/core from 7.26.7 to 7.26.8
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.26.7 to 7.26.8.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.8/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-10 23:18:16 +00:00
ccostino
3016aff185 Merge pull request #2261 from GSA/dependabot/pip/vulture-2.14
Bump vulture from 2.13 to 2.14
2025-02-10 16:13:02 -05:00
dependabot[bot]
4726143fb7 Bump vulture from 2.13 to 2.14
Bumps [vulture](https://github.com/jendrikseipp/vulture) from 2.13 to 2.14.
- [Release notes](https://github.com/jendrikseipp/vulture/releases)
- [Changelog](https://github.com/jendrikseipp/vulture/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jendrikseipp/vulture/compare/v2.13...v2.14)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-10 19:06:21 +00:00
ccostino
a4963b8263 Merge pull request #2321 from GSA/notify-admin-2315
dynamic scan warnings
2025-02-10 14:05:22 -05:00
ccostino
22f729d652 Merge pull request #2289 from GSA/notify-admin-2286
redis report
2025-02-10 12:45:44 -05:00
dependabot[bot]
38fad44a15 Bump rollup from 4.34.4 to 4.34.6
Bumps [rollup](https://github.com/rollup/rollup) from 4.34.4 to 4.34.6.
- [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.34.4...v4.34.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-07 23:58:26 +00:00
Kenneth Kehl
208891a71a ignore source code disclosure java as a false positive 2025-02-06 11:42:31 -08:00
Kenneth Kehl
afd3d94790 try again 2025-02-06 11:33:58 -08:00
Kenneth Kehl
515419e0a2 try to block serving node files 2025-02-06 11:26:44 -08:00
Kenneth Kehl
d16eb70765 try to fix svg content type 2025-02-06 10:52:53 -08:00
Kenneth Kehl
39fd87064b try to remove comments 2025-02-06 08:25:39 -08:00
Kenneth Kehl
5c6b4132a9 try to remove comments 2025-02-06 08:10:15 -08:00
Alex Janousek
b12f124b62 Merge pull request #2322 from GSA/dependabot/npm_and_yarn/rollup-4.34.4
Bump rollup from 4.34.1 to 4.34.4
2025-02-06 07:45:49 -05:00
dependabot[bot]
af8f8a9c17 Bump rollup from 4.34.1 to 4.34.4
Bumps [rollup](https://github.com/rollup/rollup) from 4.34.1 to 4.34.4.
- [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.34.1...v4.34.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-05 23:35:14 +00:00
Kenneth Kehl
23e10b1529 tuple 2025-02-05 10:56:32 -08:00
Kenneth Kehl
545bf10e0d tuple 2025-02-05 10:47:25 -08:00
Kenneth Kehl
12a2290ea9 dynamic scan warnings 2025-02-05 10:35:30 -08:00
Alex Janousek
5ffb4ddacf Merge pull request #2319 from GSA/dependabot/npm_and_yarn/rollup-4.34.1
Bump rollup from 4.32.0 to 4.34.1
2025-02-04 11:30:10 -05:00
Alex Janousek
0a538c4c1d Merge pull request #2317 from GSA/dependabot/npm_and_yarn/playwright-1.50.1
Bump playwright from 1.50.0 to 1.50.1
2025-02-04 11:29:56 -05:00
dependabot[bot]
d29260d7b6 Bump rollup from 4.32.0 to 4.34.1
Bumps [rollup](https://github.com/rollup/rollup) from 4.32.0 to 4.34.1.
- [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.32.0...v4.34.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 23:46:04 +00:00
Kenneth Kehl
bd619af421 merge from main 2025-02-03 09:01:12 -08:00
dependabot[bot]
91faaebcfd Bump playwright from 1.50.0 to 1.50.1
Bumps [playwright](https://github.com/microsoft/playwright) from 1.50.0 to 1.50.1.
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.50.0...v1.50.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-31 23:50:58 +00:00
Kenneth Kehl
9e1ec466f5 Merge pull request #2313 from GSA/enable-about-page-in-dynamic-scans
Update zaproxy-baseline GitHub Action reference
2025-01-31 08:44:38 -08:00
Carlo Costino
d627ba62de Update reference in daily check action
Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
2025-01-31 11:08:48 -05:00
Carlo Costino
0ba489df78 Update reference to zaproxy/action-baseline to 0.14.0
Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
2025-01-31 10:46:32 -05:00
Carlo Costino
4ae351b1f6 This changeset explicitly enables the FEATURE_ABOUT_PAGE_ENABLED feature flag for our dynamic scans to make sure that all pages are scanned, regardless if they are enabled on our production site or not.
Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
2025-01-30 16:32:12 -05:00
Beverly Nguyen
704fe322ba Merge pull request #2300 from GSA/2125-send-message-a11y-audit---unique-ids
Send Message A11Y Audit - Unique IDs and Required Field
2025-01-30 12:07:45 -08:00
Kenneth Kehl
41001b7f1b Merge pull request #2309 from GSA/clean_log_messages
clean log messages
2025-01-30 11:11:20 -08:00
Beverly Nguyen
2d896daa0e Merge branch 'main' into 2171-clean-up-titles 2025-01-30 10:24:54 -08:00
Beverly Nguyen
6ccf1bb983 fixed arrived from preview page 2025-01-30 10:24:11 -08:00
Beverly Nguyen
5020964fb1 height more visible for stacked bars 2025-01-30 09:56:53 -08:00
Carlo Costino
653f3f5644 Merge pull request #2293 from GSA/sheevdave-patch-3
Update issue_template.yml
2025-01-30 12:45:31 -05:00
Sheev Davé
057ed6f88c Update .github/ISSUE_TEMPLATE/issue_template.yml
Co-authored-by: Carlo Costino <ccostino@users.noreply.github.com>
2025-01-30 09:34:56 -08:00
Beverly Nguyen
5f8d5996aa cleaning up dup codes 2025-01-29 18:23:26 -08:00
Beverly Nguyen
229842b8c9 pluralization and fixing conditionals 2025-01-29 16:32:21 -08:00
Sheev Davé
3e379159a5 Merge pull request #2292 from GSA/sheevdave-patch-2
Update pull_request_template.md
2025-01-29 14:56:36 -08:00
Kenneth Kehl
4feace4cdc code review feedback 2025-01-29 13:51:17 -08:00
Kenneth Kehl
48b317ed52 format 2025-01-29 13:49:58 -08:00
Kenneth Kehl
709b7049c6 code review feedback 2025-01-29 13:22:22 -08:00
Kenneth Kehl
9630d085f4 Update .github/workflows/checks.yml
Co-authored-by: Carlo Costino <ccostino@users.noreply.github.com>
2025-01-29 13:21:28 -08:00
Kenneth Kehl
adf9c61002 clean log messages 2025-01-29 10:45:14 -08:00
Beverly Nguyen
6ea4da2617 Adding back classes 2025-01-28 16:11:07 -08:00
Beverly Nguyen
6c3d6ee77e Merge pull request #2250 from GSA/2199-add-pending-message-data-to-daily-and-user_daily-stats
Add pending to dashboard
2025-01-28 13:38:31 -08:00
Alex Janousek
088da55bb6 Merge pull request #2295 from GSA/dependabot/npm_and_yarn/babel/preset-env-7.26.7
Bump @babel/preset-env from 7.26.0 to 7.26.7
2025-01-28 12:45:16 -05:00
Beverly Nguyen
88b114f58b test 2025-01-27 19:19:41 -08:00
Beverly Nguyen
7776f9900a added id and required field 2025-01-27 19:15:37 -08:00
Sheev Davé
22fcc703f5 Update issue_template.yml 2025-01-27 12:53:47 -08:00
dependabot[bot]
03984bc10b Bump @babel/preset-env from 7.26.0 to 7.26.7
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.26.0 to 7.26.7.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.7/packages/babel-preset-env)

---
updated-dependencies:
- dependency-name: "@babel/preset-env"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 14:16:51 +00:00
Beverly Nguyen
fc755c1b85 test 2025-01-23 14:03:59 -08:00
Beverly Nguyen
933348baa5 Fix jest 2025-01-23 13:24:11 -08:00
Sheev Davé
4e335561e3 Update issue_template.yml
added "This includes items related to accessibility." to 3 sections
2025-01-23 11:57:37 -08:00
Beverly Nguyen
cae2ec1a6d refactor naming 2025-01-23 11:53:27 -08:00
Beverly Nguyen
8f30f9320a Merge branch 'main' into 2199-add-pending-message-data-to-daily-and-user_daily-stats 2025-01-23 11:26:53 -08:00
Kenneth Kehl
5d565ab88b fix black and isort forever 2025-01-22 13:45:42 -08:00
Kenneth Kehl
cfb966ce53 redis report 2025-01-22 13:34:34 -08:00
Sheev Davé
c47033af7f Update pull_request_template.md
added more a11y specific checks
2025-01-22 13:23:05 -08:00
Kenneth Kehl
a4f6dd7100 redis report 2025-01-22 08:44:34 -08:00
Beverly Nguyen
38675e88dd jest 2025-01-21 16:17:44 -08:00
Beverly Nguyen
fb1609e71c merge main 2025-01-21 16:12:21 -08:00
Beverly Nguyen
5cedf04269 Merge branch 'main' into 2199-add-pending-message-data-to-daily-and-user_daily-stats 2025-01-21 16:12:12 -08:00
Beverly Nguyen
607e112905 updated fetching interval 2025-01-15 12:07:40 -08:00
Beverly Nguyen
cd90682958 fixed drop down 2025-01-14 13:49:40 -08:00
Beverly Nguyen
5e61fb831d jest test 2025-01-14 13:10:48 -08:00
Beverly Nguyen
82179c0ad8 Merge branch 'main' into 2199-add-pending-message-data-to-daily-and-user_daily-stats 2025-01-14 12:20:13 -08:00
Beverly Nguyen
5c0eb91dd4 poetry 2025-01-14 12:14:35 -08:00
Beverly Nguyen
2159a9ddc5 clean up 2025-01-14 11:48:23 -08:00
Beverly Nguyen
ce87e2162b clean up 2025-01-13 22:40:58 -08:00
Beverly Nguyen
6d008d4e45 clean up 2025-01-13 15:54:04 -08:00
Beverly Nguyen
30293d9774 draft dash 2025-01-09 16:20:33 -08:00
Beverly Nguyen
9d09f75855 draft dash 2025-01-09 16:18:47 -08:00
Beverly Nguyen
a3db0a9fc0 first round 2025-01-06 16:31:52 -08:00
46 changed files with 1321 additions and 2182 deletions

View File

@@ -684,5 +684,5 @@
}
]
},
"generated_at": "2025-01-16T16:38:48Z"
"generated_at": "2025-02-03T17:01:06Z"
}

View File

@@ -68,7 +68,7 @@ body:
attributes:
value: '**Accessibility:**'
- type: textarea
id: notes
id: accessibility
attributes:
label: "List any specific accessibility guidance or tests that need to be considered for this user story."
description: "List what type of accessibility tests need to pass."

View File

@@ -23,5 +23,7 @@ Please enter a detailed description here.
## A11y Checks (if applicable)
* Conduct automated tests through [AxeDevTools](https://www.deque.com/axe/devtools/) and [WAVE](https://wave.webaim.org/)
* Double check work is getting picked up by the automated E2E tests
* Conduct browser-based tests through [AxeDevTools](https://www.deque.com/axe/devtools/) and [WAVE](https://wave.webaim.org/)
* Review the [Manual Checklist](https://docs.google.com/document/d/192bBXStebdXWtYhZQ73qaWMJhGcuSB1W6c9YBXhWZvc/edit?usp=sharing)
* Make sure there are no linting errors in VSCode or other IDE of choice

View File

@@ -10,7 +10,7 @@ env:
FLASK_APP: application.py
WERKZEUG_DEBUG_PIN: off
REDIS_ENABLED: 0
NODE_VERSION: 16.15.1
NODE_VERSION: 22.3.0
AWS_US_TOLL_FREE_NUMBER: "+18556438890"
ADMIN_BASE_URL: http://localhost:6012
@@ -38,10 +38,10 @@ jobs:
output: report-markdown
annotations: failed-tests
prnumber: ${{ steps.findPr.outputs.number }}
- name: Run style checks
run: poetry run flake8 .
- name: Check imports alphabetized
run: poetry run isort --check-only ./app ./tests
- name: Run style checks
run: poetry run flake8 .
- name: Check dead code
run: make dead-code
- name: Run js tests
@@ -139,7 +139,7 @@ jobs:
- uses: ./.github/actions/setup-project
- name: Create requirements.txt
run: poetry export --without-hashes --format=requirements.txt > requirements.txt
- uses: pypa/gh-action-pip-audit@v1.0.8
- uses: pypa/gh-action-pip-audit@v1.1.0
with:
inputs: requirements.txt
ignore-vulns: |
@@ -165,8 +165,9 @@ jobs:
run: make run-flask &
env:
NOTIFY_ENVIRONMENT: scanning
FEATURE_ABOUT_PAGE_ENABLED: true
- name: Run OWASP Baseline Scan
uses: zaproxy/action-baseline@v0.9.0
uses: zaproxy/action-baseline@v0.14.0
with:
docker_name: "ghcr.io/zaproxy/zaproxy:weekly"
target: "http://localhost:6012"

View File

@@ -16,7 +16,7 @@ env:
FLASK_APP: application.py
WERKZEUG_DEBUG_PIN: off
REDIS_ENABLED: 0
NODE_VERSION: 16.15.1
NODE_VERSION: 22.3.0
jobs:
dependency-audits:
@@ -26,7 +26,7 @@ jobs:
- uses: ./.github/actions/setup-project
- name: Create requirements.txt
run: poetry export --without-hashes --format=requirements.txt > requirements.txt
- uses: pypa/gh-action-pip-audit@v1.0.6
- uses: pypa/gh-action-pip-audit@v1.1.0
with:
inputs: requirements.txt
- name: Run npm audit
@@ -50,7 +50,7 @@ jobs:
env:
NOTIFY_ENVIRONMENT: scanning
- name: Run OWASP Full Scan
uses: zaproxy/action-full-scan@v0.7.0
uses: zaproxy/action-full-scan@v0.12.0
with:
docker_name: 'ghcr.io/zaproxy/zaproxy:weekly'
target: 'http://localhost:6012'

View File

@@ -16,9 +16,17 @@ jobs:
with:
fetch-depth: 2
# Looks like we need to install Terraform ourselves now!
# https://github.com/actions/runner-images/issues/10796#issuecomment-2417064348
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "^1.7.5"
terraform_wrapper: false
- name: Check for changes to Terraform
id: changed-terraform-files
uses: tj-actions/changed-files@v44
uses: tj-actions/changed-files@v45
with:
files: |
terraform/demo/**
@@ -93,7 +101,7 @@ jobs:
- name: Check for changes to egress config
id: changed-egress-config
uses: tj-actions/changed-files@v44
uses: tj-actions/changed-files@v45
with:
files: |
deploy-config/egress_proxy/notify-admin-demo.*.acl

View File

@@ -16,9 +16,17 @@ jobs:
with:
fetch-depth: 2
# Looks like we need to install Terraform ourselves now!
# https://github.com/actions/runner-images/issues/10796#issuecomment-2417064348
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "^1.7.5"
terraform_wrapper: false
- name: Check for changes to Terraform
id: changed-terraform-files
uses: tj-actions/changed-files@v44
uses: tj-actions/changed-files@v45
with:
files: |
terraform/production/**
@@ -93,7 +101,7 @@ jobs:
- name: Check for changes to egress config
id: changed-egress-config
uses: tj-actions/changed-files@v44
uses: tj-actions/changed-files@v45
with:
files: |
deploy-config/egress_proxy/notify-admin-production.*.acl

View File

@@ -21,9 +21,17 @@ jobs:
with:
fetch-depth: 2
# Looks like we need to install Terraform ourselves now!
# https://github.com/actions/runner-images/issues/10796#issuecomment-2417064348
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "^1.7.5"
terraform_wrapper: false
- name: Check for changes to Terraform
id: changed-terraform-files
uses: tj-actions/changed-files@v44
uses: tj-actions/changed-files@v45
with:
files: |
terraform/staging/**
@@ -100,7 +108,7 @@ jobs:
- name: Check for changes to egress config
id: changed-egress-config
uses: tj-actions/changed-files@v44
uses: tj-actions/changed-files@v45
with:
files: |
deploy-config/egress_proxy/notify-admin-staging.*.acl

View File

@@ -15,6 +15,14 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
# Looks like we need to install Terraform ourselves now!
# https://github.com/actions/runner-images/issues/10796#issuecomment-2417064348
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "^1.7.5"
terraform_wrapper: false
- name: Check for drift
uses: dflook/terraform-check@v1
env:
@@ -35,6 +43,14 @@ jobs:
with:
ref: 'production'
# Looks like we need to install Terraform ourselves now!
# https://github.com/actions/runner-images/issues/10796#issuecomment-2417064348
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "^1.7.5"
terraform_wrapper: false
- name: Check for drift
uses: dflook/terraform-check@v1
env:
@@ -55,6 +71,14 @@ jobs:
with:
ref: 'production'
# Looks like we need to install Terraform ourselves now!
# https://github.com/actions/runner-images/issues/10796#issuecomment-2417064348
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "^1.7.5"
terraform_wrapper: false
- name: Check for drift
uses: dflook/terraform-check@v1
env:

View File

@@ -18,6 +18,14 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
# Looks like we need to install Terraform ourselves now!
# https://github.com/actions/runner-images/issues/10796#issuecomment-2417064348
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "^1.7.5"
terraform_wrapper: false
- name: Terraform format
id: format
run: terraform fmt -check
@@ -51,7 +59,7 @@ jobs:
# inspiration: https://learn.hashicorp.com/tutorials/terraform/github-actions#review-actions-workflow
- name: Update PR
uses: actions/github-script@v6
uses: actions/github-script@v7
# we would like to update the PR even when a prior step failed
if: ${{ always() }}
with:

View File

@@ -18,6 +18,14 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
# Looks like we need to install Terraform ourselves now!
# https://github.com/actions/runner-images/issues/10796#issuecomment-2417064348
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "^1.7.5"
terraform_wrapper: false
- name: Terraform format
id: format
run: terraform fmt -check
@@ -51,7 +59,7 @@ jobs:
# inspiration: https://learn.hashicorp.com/tutorials/terraform/github-actions#review-actions-workflow
- name: Update PR
uses: actions/github-script@v6
uses: actions/github-script@v7
# we would like to update the PR even when a prior step failed
if: ${{ always() }}
with:

View File

@@ -18,6 +18,14 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
# Looks like we need to install Terraform ourselves now!
# https://github.com/actions/runner-images/issues/10796#issuecomment-2417064348
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "^1.7.5"
terraform_wrapper: false
- name: Terraform format
id: format
run: terraform fmt -check
@@ -51,7 +59,7 @@ jobs:
# inspiration: https://learn.hashicorp.com/tutorials/terraform/github-actions#review-actions-workflow
- name: Update PR
uses: actions/github-script@v6
uses: actions/github-script@v7
# we would like to update the PR even when a prior step failed
if: ${{ always() }}
with:

View File

@@ -62,6 +62,13 @@ py-lint: ## Run python linting scanners and black
poetry run flake8 .
poetry run isort --check-only ./app ./tests
.PHONY: tada
tada: ## Run python linting scanners and black
poetry run isort ./app ./tests
poetry run black .
poetry run flake8 .
.PHONY: avg-complexity
avg-complexity:
echo "*** Shows average complexity in radon of all code ***"

View File

@@ -1,11 +1,14 @@
(function (window) {
if (document.getElementById('activityChartContainer')) {
let currentType = 'service';
const tableContainer = document.getElementById('activityContainer');
const currentUserName = tableContainer.getAttribute('data-currentUserName');
const currentServiceId = tableContainer.getAttribute('data-currentServiceId');
const COLORS = {
delivered: '#0076d6',
failed: '#fa9441',
pending: '#C7CACE',
text: '#666'
};
@@ -13,7 +16,7 @@
const FONT_WEIGHT = 'bold';
const MAX_Y = 120;
const createChart = function(containerId, labels, deliveredData, failedData) {
const createChart = function(containerId, labels, deliveredData, failedData, pendingData) {
const container = d3.select(containerId);
container.selectAll('*').remove(); // Clear any existing content
@@ -36,7 +39,7 @@
}
// Calculate total messages
const totalMessages = d3.sum(deliveredData) + d3.sum(failedData);
const totalMessages = d3.sum(deliveredData) + d3.sum(failedData) + d3.sum(pendingData);
// Create legend only if there are messages
const legendContainer = d3.select('.chart-legend');
@@ -46,7 +49,8 @@
// Show legend if there are messages
const legendData = [
{ label: 'Delivered', color: COLORS.delivered },
{ label: 'Failed', color: COLORS.failed }
{ label: 'Failed', color: COLORS.failed },
{ label: 'Pending', color: COLORS.pending }
];
const legendItem = legendContainer.selectAll('.legend-item')
@@ -77,8 +81,9 @@
.range([0, width])
.padding(0.1);
// Adjust the y-axis domain to add some space above the tallest bar
const maxY = d3.max(deliveredData.map((d, i) => d + (failedData[i] || 0)));
const y = d3.scaleSqrt()
const maxY = d3.max(deliveredData.map((d, i) => d + (failedData[i] || 0) + (pendingData[i] || 0)));
const y = d3.scaleSymlog()
.domain([0, maxY + 2]) // Add 2 units of space at the top
.nice()
.range([height, 0]);
@@ -90,7 +95,7 @@
// Generate the y-axis with whole numbers
const yAxis = d3.axisLeft(y)
.ticks(Math.min(maxY + 2, 10)) // Generate up to 10 ticks based on the data
.ticks(Math.min(maxY + 2, 3))
.tickFormat(d3.format('d')); // Ensure whole numbers on the y-axis
svg.append('g')
@@ -101,12 +106,13 @@
const stackData = labels.map((label, i) => ({
label: label,
delivered: deliveredData[i],
failed: failedData[i] || 0 // Ensure there's a value for failed, even if it's 0
failed: failedData[i] || 0,
pending: pendingData[i] || 0
}));
// Stack the data
const stack = d3.stack()
.keys(['delivered', 'failed'])
.keys(['delivered', 'failed', 'pending'])
.order(d3.stackOrderNone)
.offset(d3.stackOffsetNone);
@@ -114,8 +120,8 @@
// Color scale
const color = d3.scaleOrdinal()
.domain(['delivered', 'failed'])
.range([COLORS.delivered, COLORS.failed]);
.domain(['delivered', 'failed', 'pending'])
.range([COLORS.delivered, COLORS.failed, COLORS.pending]);
// Create bars with animation
const barGroups = svg.selectAll('.bar-group')
@@ -124,11 +130,12 @@
.append('g')
.attr('class', 'bar-group')
.attr('fill', d => color(d.key));
const minBarHeight = 5;
barGroups.selectAll('rect')
.data(d => d)
.enter()
.append('rect')
.filter(d => d[1] - d[0] > 0)
.attr('x', d => x(d.data.label))
.attr('y', height)
.attr('height', 0)
@@ -149,11 +156,13 @@
.transition()
.duration(1000)
.attr('y', d => y(d[1]))
.attr('height', d => y(d[0]) - y(d[1]));
};
.attr('height', d => {
const calculatedHeight = y(d[0]) - y(d[1]);
return calculatedHeight < minBarHeight ? minBarHeight : calculatedHeight;
}); };
// Function to create an accessible table
const createTable = function(tableId, chartType, labels, deliveredData, failedData) {
const createTable = function(tableId, chartType, labels, deliveredData, failedData, pendingData) {
const table = document.getElementById(tableId);
table.innerHTML = ""; // Clear previous data
@@ -165,7 +174,7 @@
// Create table header
const headerRow = document.createElement('tr');
const headers = ['Day', 'Delivered', 'Failed'];
const headers = ['Day', 'Delivered', 'Failed', 'Pending'];
headers.forEach(headerText => {
const th = document.createElement('th');
th.textContent = headerText;
@@ -188,6 +197,10 @@
cellFailed.textContent = failedData[index];
row.appendChild(cellFailed);
const cellPending = document.createElement('td');
cellPending.textContent = pendingData[index];
row.appendChild(cellPending);
tbody.appendChild(row);
});
@@ -197,12 +210,13 @@
};
const fetchData = function(type) {
var ctx = document.getElementById('weeklyChart');
if (!ctx) {
return;
}
var url = type === 'service' ? `/daily_stats.json` : `/daily_stats_by_user.json`;
var url = type === 'service' ? `/services/${currentServiceId}/daily-stats.json` : `/services/${currentServiceId}/daily-stats-by-user.json`;
return fetch(url)
.then(response => {
if (!response.ok) {
@@ -214,7 +228,7 @@
labels = [];
deliveredData = [];
failedData = [];
pendingData = [];
let totalMessages = 0;
for (var dateString in data) {
@@ -225,9 +239,8 @@
labels.push(formattedDate);
deliveredData.push(data[dateString].sms.delivered);
failedData.push(data[dateString].sms.failure);
// Calculate the total number of messages
totalMessages += data[dateString].sms.delivered + data[dateString].sms.failure;
pendingData.push(data[dateString].sms.pending || 0);
totalMessages += data[dateString].sms.delivered + data[dateString].sms.failure + data[dateString].sms.pending;
}
}
@@ -253,17 +266,18 @@
}
} else {
// If there are messages, create the chart and table
createChart('#weeklyChart', labels, deliveredData, failedData);
createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData);
}
return data;
})
.catch(error => console.error('Error fetching daily stats:', error));
};
createChart('#weeklyChart', labels, deliveredData, failedData, pendingData);
createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData, pendingData);
}
return data;
})
.catch(error => console.error('Error fetching daily stats:', error));
};
setInterval(() => fetchData(currentType), 25000);
const handleDropdownChange = function(event) {
const selectedValue = event.target.value;
currentType = selectedValue;
const subTitle = document.querySelector(`#activityChartContainer .chart-subtitle`);
const selectElement = document.getElementById('options');
const selectedText = selectElement.options[selectElement.selectedIndex].text;
@@ -316,7 +330,7 @@
document.addEventListener('DOMContentLoaded', function() {
// Initialize activityChart chart and table with service data by default
fetchData('service');
fetchData(currentType);
const allRows = Array.from(document.querySelectorAll('#activity-table tbody tr'));
allRows.forEach((row, index) => {
@@ -329,9 +343,9 @@
// Resize chart on window resize
window.addEventListener('resize', function() {
if (labels.length > 0 && deliveredData.length > 0 && failedData.length > 0) {
createChart('#weeklyChart', labels, deliveredData, failedData);
createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData);
if (labels.length > 0 && deliveredData.length > 0 && failedData.length > 0 && pendingData.length > 0) {
createChart('#weeklyChart', labels, deliveredData, failedData, pendingData);
createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData, pendingData);
}
});

View File

@@ -1024,3 +1024,7 @@ nav.nav {
font-size: units(3);
font-weight: bold;
}
.form-control-error {
border: 4px solid #b10e1e
}

View File

@@ -62,39 +62,60 @@ def service_dashboard(service_id):
job_response = job_api_client.get_jobs(service_id)["data"]
service_data_retention_days = 7
filtered_jobs = [job for job in job_response if job["job_status"] != "cancelled"]
sorted_jobs = sorted(filtered_jobs, key=lambda job: job["created_at"], reverse=True)
active_jobs = [job for job in job_response if job["job_status"] != "cancelled"]
sorted_jobs = sorted(active_jobs, key=lambda job: job["created_at"], reverse=True)
job_lists = [
{**job_dict, "finished_processing": job_is_finished(job_dict)}
for job_dict in sorted_jobs
]
return render_template(
"views/dashboard/dashboard.html",
updates_url=url_for(".service_dashboard_updates", service_id=service_id),
partials=get_dashboard_partials(service_id),
jobs=sorted_jobs,
jobs=job_lists,
service_data_retention_days=service_data_retention_days,
sms_sent=sms_sent,
sms_allowance_remaining=sms_allowance_remaining,
)
@main.route("/daily_stats.json")
def get_daily_stats():
service_id = session.get("service_id")
date_range = get_stats_date_range()
def job_is_finished(job_dict):
done_statuses = [
"delivered",
"sent",
"failed",
"technical-failure",
"temporary-failure",
"permanent-failure",
"cancelled",
]
processed_count = sum(
stat["count"]
for stat in job_dict["statistics"]
if stat["status"] in done_statuses
)
return job_dict["notification_count"] == processed_count
@main.route("/services/<uuid:service_id>/daily-stats.json")
@user_has_permissions()
def get_daily_stats(service_id):
date_range = get_stats_date_range()
stats = service_api_client.get_service_notification_statistics_by_day(
service_id, start_date=date_range["start_date"], days=date_range["days"]
)
return jsonify(stats)
@main.route("/daily_stats_by_user.json")
def get_daily_stats_by_user():
service_id = session.get("service_id")
@main.route("/services/<uuid:service_id>/daily-stats-by-user.json")
@user_has_permissions()
def get_daily_stats_by_user(service_id):
date_range = get_stats_date_range()
user_id = current_user.id
stats = service_api_client.get_user_service_notification_statistics_by_day(
service_id,
user_id,
user_id=current_user.id,
start_date=date_range["start_date"],
days=date_range["days"],
)

View File

@@ -401,7 +401,9 @@ def get_job_partials(job):
)
if request.referrer is not None:
session["arrived_from_preview_page"] = ("check" in request.referrer) or ("help=0" in request.referrer)
session["arrived_from_preview_page"] = ("check" in request.referrer) or (
"help=0" in request.referrer
)
else:
session["arrived_from_preview_page"] = False

View File

@@ -116,6 +116,70 @@ def download_all_users():
return response
@main.route("/platform-admin/get-redis-report")
@user_is_platform_admin
def get_redis_report():
memory_info = redis_client.info("memory")
memory_used = memory_info.get("used_memory_human", "N/A")
max_memory = memory_info.get("maxmemory_human", "N/A")
if max_memory == "0B":
max_memory = "No set limit"
mem_fragmentation = memory_info.get("mem_fragmentation_ratio", "N/A")
frag_quality = "Swapping (bad)"
if mem_fragmentation >= 1.0:
frag_quality = "Healthy"
if mem_fragmentation > 1.5:
frag_quality = "Problematic"
if mem_fragmentation > 2.0:
frag_quality = "Severe fragmentation"
frag_note = ""
if mem_fragmentation > 2.0:
frag_note = "Use MEMORY PURGE.\nReplace multiple small keys with hashes.\nAvoid long keys.\nSet max_memory."
elif mem_fragmentation < 1.0:
frag_note = "Allocate more RAM.\nSet max_memory."
keys = redis_client.keys("*")
key_details = []
for key in keys:
key_type = redis_client.type(key).decode("utf-8")
ttl = redis_client.ttl(key)
ttl_str = "No Expiry" if ttl == -1 else f"{ttl} seconds"
key_details.append(
{"Key": key.decode("utf-8"), "Type": key_type, "TTL": ttl_str}
)
output = StringIO()
writer = csv.writer(
output,
)
writer.writerow(["Redis Report"])
writer.writerow([])
writer.writerow(["Memory"])
writer.writerow(["", "Memory Used", memory_used])
writer.writerow(["", "Max Memory", max_memory])
writer.writerow(["", "Memory Fragmentation Ratio", mem_fragmentation])
writer.writerow(["", "Memory Fragmentation Quality", frag_quality, frag_note])
writer.writerow([])
writer.writerow(["Keys Overview"])
writer.writerow(["", "TTL", "Type", "Key"])
for key_detail in key_details:
writer.writerow(
["", key_detail["TTL"], key_detail["Type"], key_detail["Key"][0:50]]
)
csv_data = output.getvalue()
# Create a direct download response with the CSV data and appropriate headers
response = Response(csv_data, content_type="text/csv; charset=utf-8")
response.headers["Content-Disposition"] = "attachment; filename=redis.csv"
return response
def is_over_threshold(number, total, threshold):
percentage = number / total * 100 if total else 0
return percentage > threshold

View File

@@ -68,11 +68,12 @@ def _get_access_token(code): # pragma: no cover
id_token = get_id_token(response_json)
nonce = id_token["nonce"]
nonce_key = f"login-nonce-{unquote(nonce)}"
stored_nonce = redis_client.get(nonce_key).decode("utf8")
if not os.getenv("NOTIFY_ENVIRONMENT") == "development":
stored_nonce = redis_client.get(nonce_key).decode("utf8")
if nonce != stored_nonce:
current_app.logger.error(f"Nonce Error: {nonce} != {stored_nonce}")
abort(403)
if nonce != stored_nonce:
current_app.logger.error(f"Nonce Error: {nonce} != {stored_nonce}")
abort(403)
try:
access_token = response_json["access_token"]
@@ -112,7 +113,7 @@ def _do_login_dot_gov(): # $ pragma: no cover
verify_key = f"login-verify_email-{unquote(state)}"
verify_path = bool(redis_client.get(verify_key))
if not verify_path:
if not verify_path and not os.getenv("NOTIFY_ENVIRONMENT") == "development":
state_key = f"login-state-{unquote(state)}"
stored_state = unquote(redis_client.get(state_key).decode("utf8"))
if state != stored_state:

View File

@@ -6,17 +6,36 @@ from app.notify_client import NotifyAdminAPIClient
class BillingAPIClient(NotifyAdminAPIClient):
def get_monthly_usage_for_service(self, service_id, year):
return self.get(
monthly_usage = redis_client.get(f"monthly-usage-summary-{service_id}-{year}")
if monthly_usage is not None:
return json.loads(monthly_usage.decode("utf-8"))
result = self.get(
"/service/{0}/billing/monthly-usage".format(service_id),
params=dict(year=year),
)
redis_client.set(
f"monthly-usage-summary-{service_id}-{year}",
json.dumps(result),
ex=30,
)
return result
def get_annual_usage_for_service(self, service_id, year=None):
return self.get(
annual_usage = redis_client.get(f"yearly-usage-summary-{service_id}-{year}")
if annual_usage is not None:
return json.loads(annual_usage.decode("utf-8"))
result = self.get(
"/service/{0}/billing/yearly-usage-summary".format(service_id),
params=dict(year=year),
)
redis_client.set(
f"yearly-usage-summary-{service_id}-{year}",
json.dumps(result),
ex=30,
)
return result
def get_free_sms_fragment_limit_for_year(self, service_id, year=None):
frag_limit = redis_client.get(f"free-sms-fragment-limit-{service_id}-{year}")
if frag_limit is not None:
@@ -48,13 +67,28 @@ class BillingAPIClient(NotifyAdminAPIClient):
)
def get_data_for_billing_report(self, start_date, end_date):
return self.get(
x_start_date = str(start_date)
x_start_date = x_start_date.replace(" ", "_")
x_end_date = str(end_date)
x_end_date = x_end_date.replace(" ", "_")
billing_data = redis_client.get(
f"get-data-for-billing-report-{x_start_date}-{x_end_date}"
)
if billing_data is not None:
return json.loads(billing_data.decode("utf-8"))
result = self.get(
url="/platform-stats/data-for-billing-report",
params={
"start_date": str(start_date),
"end_date": str(end_date),
},
)
redis_client.set(
f"get-data-for-billing-report-{x_start_date}-{x_end_date}",
json.dumps(result),
ex=30,
)
return result
def get_data_for_volumes_by_service_report(self, start_date, end_date):
return self.get(

View File

@@ -1,7 +1,15 @@
from app.notify_client import NotifyAdminAPIClient, _attach_current_user
import json
from app.extensions import redis_client
from app.notify_client import NotifyAdminAPIClient, _attach_current_user, cache
class NotificationApiClient(NotifyAdminAPIClient):
@cache.set(
"notifications-{service_id}-{job_id}-{status}-{page}-{limit_days}-{include_jobs}-{include_one_off}",
ttl_in_seconds=30,
)
def get_notifications_for_service(
self,
service_id,
@@ -41,7 +49,7 @@ class NotificationApiClient(NotifyAdminAPIClient):
if job_id:
return method(
url="/service/{}/job/{}/notifications".format(service_id, job_id),
**kwargs
**kwargs,
)
else:
if limit_days is not None:
@@ -96,9 +104,20 @@ class NotificationApiClient(NotifyAdminAPIClient):
)
def get_notification_count_for_job_id(self, *, service_id, job_id):
return self.get(
counts = redis_client.get(
f"notification-count-for-job-id-{service_id}-{job_id}"
)
if counts is not None:
return json.loads(counts.decode("utf-8"))
result = self.get(
url="/service/{}/job/{}/notification_count".format(service_id, job_id)
)["count"]
)
redis_client.set(
f"notification-count-for-job-id-{service_id}-{job_id}",
json.dumps(result["count"]),
ex=30,
)
return result["count"]
notification_api_client = NotificationApiClient()

View File

@@ -38,6 +38,7 @@ class ServiceAPIClient(NotifyAdminAPIClient):
"""
return self.get("/service/{0}".format(service_id))
@cache.set("service-stats-{service_id}-{limit_days}", ttl_in_seconds=30)
def get_service_statistics(self, service_id, limit_days=None):
return self.get(
"/service/{0}/statistics".format(service_id),

View File

@@ -14,10 +14,8 @@
<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 %}
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-WX5NGWF"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
{% endblock %}
{% endblock %}
@@ -145,10 +143,8 @@
{% block bodyEnd %}
{% block extra_javascripts %}
{% endblock %}
<!--[if gt IE 8]><!-->
<script type="text/javascript" src="{{ asset_url('javascripts/all.js') }}"></script>
<script type="text/javascript" src="{{ asset_url('js/uswds.min.js') }}"></script>
<!--<![endif]-->
{% endblock %}
</body>
</html>

View File

@@ -34,7 +34,7 @@
attributes: params.errorMessage.attributes,
html: params.errorMessage.html,
text: params.errorMessage.text,
visuallyHiddenText: params.errorMessage.visuallyHiddenText
visuallyHiddenText: params.errorMessage.visuallyHiddenText,
}) | indent(2) | trim }}
{% endif %}
<input class="usa-input {%- if params.classes %} {{ params.classes }}{% endif %} {%- if params.errorMessage %} usa-input--error{% endif %}" id="{{ params.id }}" name="{{ params.name }}" type="{{ params.type | default('text') }}"
@@ -42,5 +42,7 @@
{%- if describedBy %} aria-describedby="{{ describedBy }}"{% endif %}
{%- if params.autocomplete %} autocomplete="{{ params.autocomplete}}"{% endif %}
{%- if params.pattern %} pattern="{{ params.pattern }}"{% endif %}
{%- for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor -%}>
{%- for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor -%}
{%- if params.required %} required{% endif %}
/>
</div>

View File

@@ -16,19 +16,9 @@
placeholder=''
) %}
<div
class="form-group{% if field.errors %} form-group-error{% endif %} {{ extra_form_group_classes }}"
class="usa-form-group{% if field.errors %} usa-form-group--error{% endif %} {{ extra_form_group_classes }}"
data-module="{% if autofocus %}autofocus{% elif colour_preview %}colour-preview{% endif %}"
>
{% if field.errors %}
<div class="usa-alert usa-alert--error edit-textbox-error-mt" role="alert">
<div class="usa-alert__body">
<h4 class="usa-alert__heading">Error message</h4>
<p class="usa-alert__text" data-module="track-error" data-error-type="{{ field.errors[0] }}" data-error-label="{{ field.name }}">
{% if not safe_error_message %}{{ field.errors[0] }}{% else %}{{ field.errors[0]|safe }}{% endif %}
</p>
</div>
</div>
{% endif %}
<label class="usa-label" for="{{ field.name }}">
{% if label %}
{{ label }}
@@ -41,6 +31,12 @@
{{ hint }}
</div>
{% endif %}
{% if field.errors %}
<span id="{{ field.name}}-error" class="usa-error-message" data-module="track-error" data-error-type="{{ field.errors[0] }}" data-error-label="{{ field.name }}" tabindex="-1" aria-live="assertive" role="alert">
<span class="usa-sr-only">Error:</span>
{% if not safe_error_message %}{{ field.errors[0] }}{% else %}{{ field.errors[0]|safe }}{% endif %}
</span>
{% endif %}
{%
if highlight_placeholders or autosize
%}
@@ -59,6 +55,8 @@
data_highlight_placeholders='true' if highlight_placeholders else 'false',
rows=rows|string,
placeholder=placeholder,
aria_describedby=field.name+"-error",
required='required' if required else None,
**kwargs
) }}
{% if suffix %}

View File

@@ -25,7 +25,7 @@
{% if job.scheduled_for %}
<div class="usa-alert usa-alert--info">
<div class="usa-alert__body">
<h2 class="usa-alert__heading">Your message has been scheduled</h2>
<h2 class="usa-alert__heading">Your {{ 'message has' if job.notification_count == 1 else 'messages have' }} been scheduled</h2>
<p class="usa-alert__text">
{{ job.template_name }} - {{ current_service.name }} was scheduled on {{ job.scheduled_for|format_datetime_normal }} by {{ job.created_by.name }}
</p>
@@ -37,7 +37,7 @@
<div class="usa-alert usa-alert--success">
<div class="usa-alert__body">
<h2 class="usa-alert__heading">
Your message is sending
Your {{ 'message is' if job.notification_count == 1 else 'messages are' }} sending
</h2>
<p class="usa-alert__text">
{{ job.template_name }} - {{ current_service.name }}
@@ -49,7 +49,7 @@
<div class="usa-alert usa-alert--info">
<div class="usa-alert__body">
<h2 class="usa-alert__heading">
Your message is pending
Your {{ 'message is' if job.notification_count == 1 else 'messages are' }} pending
</h2>
<p class="usa-alert__text">
{{ job.template_name }} - {{ current_service.name }}
@@ -64,7 +64,7 @@
<div class="usa-alert usa-alert--success">
<div class="usa-alert__body">
<h2 class="usa-alert__heading">
Your message has been sent
Your {{ 'message has' if job.notification_count == 1 else 'messages have' }} been sent
</h2>
<p class="usa-alert__text">
{{ job.template_name }} - {{ current_service.name }}

View File

@@ -18,7 +18,7 @@
</div>
</div>
<div id="aria-live-account" class="usa-sr-only" aria-live="polite"></div>
<div class="table-container" id="activityContainer" data-currentUserName="{{ current_user.name }}">
<div class="table-container" id="activityContainer" data-currentUserName="{{ current_user.name }}" data-currentServiceId="{{current_service.id}}">
<div id="tableActivity" class="table-overflow-x-auto">
<h2 id="table-heading" class="margin-top-4 margin-bottom-1">Service activity</h2>
@@ -46,14 +46,16 @@
</td>
<td class="table-field template">{{ job.template_name }}</td>
<td class="table-field time-sent">
{% if job.scheduled_for and not job.processing_finished %}
Scheduled for {{ job.scheduled_for|format_datetime_table }}
{% elif job.processing_finished and not job.statistics|selectattr('status', 'equalto', 'sending')|list %}
Sent on {{ job.processing_finished|format_datetime_table }}
{% elif job.processing_started %}
Sending since {{ job.processing_started|format_datetime_table }}
{% if not job.finished_processing %}
{% if job.scheduled_for%}
Scheduled for {{ job.scheduled_for|format_datetime_table }}
{% elif job.processing_started %}
Sending since {{ job.processing_started|format_datetime_table }}
{% else %}
Pending since {{ job.created_at|format_datetime_table }}
{% endif %}
{% else %}
Pending since {{ job.created_at|format_datetime_table }}
Sent on {{ job.processing_started|format_datetime_table }}
{% endif %}
</td>
<td class="table-field sender sender-column">{{ job.created_by.name }}</td>

View File

@@ -32,6 +32,8 @@
<div class="tablet:grid-col-9 mobile-lg:grid-col-12">
{{ form.name(param_extensions={
"extra_form_group_classes": "margin-bottom-2",
"id": "name",
"required": True,
"hint": {"text": "Your recipients will not see this"}
}) }}
{{ textbox(
@@ -41,7 +43,8 @@
hint=content_hint,
rows=5,
extra_form_group_classes='margin-bottom-1',
placeholder='Edit me! Check out the Personalization section below for details on cool ((stuff)) you can do with your messages!'
placeholder='Edit me! Check out the Personalization section below for details on cool ((stuff)) you can do with your messages!',
required=True
) }}
{% if current_user.platform_admin %}
{{ form.process_type }}

View File

@@ -11,7 +11,7 @@
{% block maincolumn_content %}
{{ page_header("Message status") }}
{% if not job.processing_finished %}
{% if not job.finished_processing %}
<div
data-module="update-content"
data-resource="{{ updates_url }}"
@@ -20,7 +20,7 @@
>
{% endif %}
{{ partials['status']|safe }}
{% if not job.processing_finished %}
{% if not job.finished_processing %}
</div>
{% endif %}
{% if not finished %}

View File

@@ -34,7 +34,7 @@
{% set product_highlights = [
{
"svg_src": "#chat",
"card_heading": "Up to 100,000 messages to use over your first year*",
"card_heading": "Up to 250,000 messages to use over your first year*",
},
{
"svg_src": "#phone",

View File

@@ -34,5 +34,8 @@
<p>
<a class="usa-link" href="{{ url_for('main.download_all_users') }}">Download All Users</a>
</p>
<p>
<a class="usa-link" href="{{ url_for('main.get_redis_report') }}">Get Redis Report</a>
</p>
{% endblock %}

View File

@@ -37,8 +37,8 @@
data_kwargs={'force-focus': True}
) %}
<div class="grid-row">
<div class="grid-col-12 {% if form.placeholder_value.label.text == 'phone number' %}extra-tracking{% endif %}" aria-live="polite" role="alert">
{{ form.placeholder_value(param_extensions={"classes": ""}) }}
<div class="grid-col-12 {% if form.placeholder_value.label.text == 'phone number' %}extra-tracking{% endif %}">
{{ form.placeholder_value(param_extensions={"classes": "", "id": "phone-number"}) }}
</div>
{% if skip_link or link_to_upload %}
<div class="grid-col-12 margin-top-1">

View File

@@ -90,8 +90,4 @@
{% endif %}
</div>
<!--<div class="">
{{ copy_to_clipboard(template.id, name="Template ID", thing='template ID') }}
</div>-->
{% endblock %}

View File

@@ -1,7 +1,10 @@
from flask import Flask
from werkzeug.serving import WSGIRequestHandler
from app import create_app
WSGIRequestHandler.version_string = lambda self: "SecureServer"
application = Flask("app")
create_app(application)

View File

@@ -149,6 +149,22 @@ class RedisClient:
except Exception as e:
self.__handle_exception(e, raise_exception, "incr", key)
def info(self, key):
if self.active:
return self.redis_store.info(key)
def keys(self, pattern):
if self.active:
return self.redis_store.keys(pattern)
def type(self, key):
if self.active:
return self.redis_store.type(key)
def ttl(self, key):
if self.active:
return self.redis_store.ttl(key)
def get(self, key, raise_exception=False):
key = prepare_value(key)
if self.active:

View File

@@ -75,6 +75,24 @@ class ResponseHeaderMiddleware(object):
if SPAN_ID_HEADER.lower() not in lower_existing_header_names:
headers.append((SPAN_ID_HEADER, str(req.span_id)))
# Some dynamic scan findings
headers.append(("Cross-Origin-Opener-Policy", "same-origin"))
headers.append(("Cross-Origin-Embedder-Policy", "require-corp"))
headers.append(("Cross-Origin-Resource-Policy", "same-origin"))
headers.append(("Cross-Origin-Opener-Policy", "same-origin"))
# svg content type should not contain charset
found_svg = False
for _, v in headers:
if "svg+xml" in v:
found_svg = True
if found_svg:
new_headers = [
(k, v) for k, v in headers if k.lower() != "content-type"
]
new_headers.append(("Content-Type", "image/svg+xml"))
return start_response(status, new_headers, exc_info)
return start_response(status, headers, exc_info)
return self._app(environ, rewrite_response_headers)

2576
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -37,18 +37,18 @@
"hogan": "1.0.2",
"jquery": "3.7.1",
"morphdom": "^2.7.4",
"playwright": "^1.50.0",
"playwright": "^1.50.1",
"python": "^0.0.4",
"query-command-supported": "1.0.0",
"sass-embedded": "^1.83.4",
"sass-embedded": "^1.85.0",
"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.7",
"@babel/preset-env": "^7.26.0",
"@babel/core": "^7.26.9",
"@babel/preset-env": "^7.26.9",
"@uswds/compile": "^1.2.1",
"backstopjs": "^6.3.25",
"better-npm-audit": "^3.11.0",
@@ -61,12 +61,12 @@
"gulp-jshint": "2.1.0",
"gulp-prettyerror": "2.0.0",
"gulp-uglify": "3.0.2",
"jest": "29.7.0",
"jest": "^29.7.0",
"jest-each": "^29.2.1",
"jest-environment-jsdom": "^29.2.2",
"jshint": "2.13.6",
"jshint-stylish": "2.2.1",
"rollup": "^4.32.0",
"rollup": "^4.34.8",
"rollup-plugin-commonjs": "10.1.0",
"rollup-plugin-node-resolve": "5.2.0"
}

220
poetry.lock generated
View File

@@ -42,17 +42,17 @@ tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"]
[[package]]
name = "awscli"
version = "1.35.17"
version = "1.36.40"
description = "Universal Command Line Environment for AWS."
optional = false
python-versions = ">=3.8"
files = [
{file = "awscli-1.35.17-py3-none-any.whl", hash = "sha256:67906511b138bb6b241136c0ba6bee854f522b7b506887911e6ad34877a822c3"},
{file = "awscli-1.35.17.tar.gz", hash = "sha256:9469a1924c6987dd0553ad0d0fd2a37717d0f7b55104e99f5789d3678c9706c4"},
{file = "awscli-1.36.40-py3-none-any.whl", hash = "sha256:971c3b150c06068bc26867fe295753547780f63fcf8256d41cd38760e44d46ca"},
{file = "awscli-1.36.40.tar.gz", hash = "sha256:e2a88f88dc16d5c0f26379afd6f254097e53e8b34c82164e59f4165db6ee6dfa"},
]
[package.dependencies]
botocore = "1.35.51"
botocore = "1.35.99"
colorama = ">=0.2.5,<0.4.7"
docutils = ">=0.10,<0.17"
PyYAML = ">=3.10,<6.1"
@@ -219,13 +219,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
[[package]]
name = "botocore"
version = "1.35.51"
version = "1.35.99"
description = "Low-level, data-driven core of boto 3."
optional = false
python-versions = ">=3.8"
files = [
{file = "botocore-1.35.51-py3-none-any.whl", hash = "sha256:4d65b00111bd12b98e9f920ecab602cf619cc6a6d0be6e5dd53f517e4b92901c"},
{file = "botocore-1.35.51.tar.gz", hash = "sha256:a9b3d1da76b3e896ad74605c01d88f596324a3337393d4bfbfa0d6c35822ca9c"},
{file = "botocore-1.35.99-py3-none-any.whl", hash = "sha256:b22d27b6b617fc2d7342090d6129000af2efd20174215948c0d7ae2da0fab445"},
{file = "botocore-1.35.99.tar.gz", hash = "sha256:1eab44e969c39c5f3d9a3104a0836c24715579a455f12b3979a31d7cde51b3c3"},
]
[package.dependencies]
@@ -595,51 +595,55 @@ toml = ["tomli"]
[[package]]
name = "cryptography"
version = "43.0.3"
version = "44.0.1"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
optional = false
python-versions = ">=3.7"
python-versions = "!=3.9.0,!=3.9.1,>=3.7"
files = [
{file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"},
{file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"},
{file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"},
{file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"},
{file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"},
{file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"},
{file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"},
{file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"},
{file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"},
{file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"},
{file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"},
{file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"},
{file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"},
{file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"},
{file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"},
{file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"},
{file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"},
{file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"},
{file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"},
{file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"},
{file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"},
{file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"},
{file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"},
{file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"},
{file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"},
{file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"},
{file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"},
{file = "cryptography-44.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf688f615c29bfe9dfc44312ca470989279f0e94bb9f631f85e3459af8efc009"},
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd7c7e2d71d908dc0f8d2027e1604102140d84b155e658c20e8ad1304317691f"},
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887143b9ff6bad2b7570da75a7fe8bbf5f65276365ac259a5d2d5147a73775f2"},
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:322eb03ecc62784536bc173f1483e76747aafeb69c8728df48537eb431cd1911"},
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:21377472ca4ada2906bc313168c9dc7b1d7ca417b63c1c3011d0c74b7de9ae69"},
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:df978682c1504fc93b3209de21aeabf2375cb1571d4e61907b3e7a2540e83026"},
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:eb3889330f2a4a148abead555399ec9a32b13b7c8ba969b72d8e500eb7ef84cd"},
{file = "cryptography-44.0.1-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:8e6a85a93d0642bd774460a86513c5d9d80b5c002ca9693e63f6e540f1815ed0"},
{file = "cryptography-44.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6f76fdd6fd048576a04c5210d53aa04ca34d2ed63336d4abd306d0cbe298fddf"},
{file = "cryptography-44.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6c8acf6f3d1f47acb2248ec3ea261171a671f3d9428e34ad0357148d492c7864"},
{file = "cryptography-44.0.1-cp37-abi3-win32.whl", hash = "sha256:24979e9f2040c953a94bf3c6782e67795a4c260734e5264dceea65c8f4bae64a"},
{file = "cryptography-44.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:fd0ee90072861e276b0ff08bd627abec29e32a53b2be44e41dbcdf87cbee2b00"},
{file = "cryptography-44.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a2d8a7045e1ab9b9f803f0d9531ead85f90c5f2859e653b61497228b18452008"},
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8272f257cf1cbd3f2e120f14c68bff2b6bdfcc157fafdee84a1b795efd72862"},
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e8d181e90a777b63f3f0caa836844a1182f1f265687fac2115fcf245f5fbec3"},
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:436df4f203482f41aad60ed1813811ac4ab102765ecae7a2bbb1dbb66dcff5a7"},
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4f422e8c6a28cf8b7f883eb790695d6d45b0c385a2583073f3cec434cc705e1a"},
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:72198e2b5925155497a5a3e8c216c7fb3e64c16ccee11f0e7da272fa93b35c4c"},
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a46a89ad3e6176223b632056f321bc7de36b9f9b93b2cc1cccf935a3849dc62"},
{file = "cryptography-44.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:53f23339864b617a3dfc2b0ac8d5c432625c80014c25caac9082314e9de56f41"},
{file = "cryptography-44.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:888fcc3fce0c888785a4876ca55f9f43787f4c5c1cc1e2e0da71ad481ff82c5b"},
{file = "cryptography-44.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:00918d859aa4e57db8299607086f793fa7813ae2ff5a4637e318a25ef82730f7"},
{file = "cryptography-44.0.1-cp39-abi3-win32.whl", hash = "sha256:9b336599e2cb77b1008cb2ac264b290803ec5e8e89d618a5e978ff5eb6f715d9"},
{file = "cryptography-44.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:e403f7f766ded778ecdb790da786b418a9f2394f36e8cc8b796cc056ab05f44f"},
{file = "cryptography-44.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1f9a92144fa0c877117e9748c74501bea842f93d21ee00b0cf922846d9d0b183"},
{file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:610a83540765a8d8ce0f351ce42e26e53e1f774a6efb71eb1b41eb01d01c3d12"},
{file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5fed5cd6102bb4eb843e3315d2bf25fede494509bddadb81e03a859c1bc17b83"},
{file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:f4daefc971c2d1f82f03097dc6f216744a6cd2ac0f04c68fb935ea2ba2a0d420"},
{file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94f99f2b943b354a5b6307d7e8d19f5c423a794462bde2bf310c770ba052b1c4"},
{file = "cryptography-44.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d9c5b9f698a83c8bd71e0f4d3f9f839ef244798e5ffe96febfa9714717db7af7"},
{file = "cryptography-44.0.1.tar.gz", hash = "sha256:f51f5705ab27898afda1aaa430f34ad90dc117421057782022edf0600bec5f14"},
]
[package.dependencies]
cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""}
[package.extras]
docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"]
docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"]
nox = ["nox"]
pep8test = ["check-sdist", "click", "mypy", "ruff"]
sdist = ["build"]
docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"]
docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"]
nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"]
pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"]
sdist = ["build (>=1.0.0)"]
ssh = ["bcrypt (>=3.1.5)"]
test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
test = ["certifi (>=2024)", "cryptography-vectors (==44.0.1)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"]
test-randomorder = ["pytest-randomly"]
[[package]]
@@ -1746,40 +1750,40 @@ files = [
[[package]]
name = "newrelic"
version = "10.2.0"
version = "10.6.0"
description = "New Relic Python Agent"
optional = false
python-versions = ">=3.7"
files = [
{file = "newrelic-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6f822e6a43151af13a748fb2de6ff298aeb6eee03bf6512afba6aaa79211172"},
{file = "newrelic-10.2.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501cc575b3fd702a21542a0f5dac59b83f47e2806f5b7c0f4e4510b5474ed77f"},
{file = "newrelic-10.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3f15a940b6794b4008ab983e7ac3b4d179efe609e040ee96ed5744723fc580c8"},
{file = "newrelic-10.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:60d01303807228718c4099d8550f72d21ee8b61a33555d8974800f6868f2144a"},
{file = "newrelic-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5809b4111ef3b1d0b5fa66ad06a81de512842370707863d44888c9439f16c4c"},
{file = "newrelic-10.2.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3415b1c7cab5e586e72cca467dd80cd0507f23a3139c02911cf75892fdbb48a6"},
{file = "newrelic-10.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3d9c8297ba158ce4570fc48cfea7bdf3678b2054baaf0cad4debcbca33c2af3a"},
{file = "newrelic-10.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3bee0b9ce1eccf6ac63e51113781743853b1b84c98ae48ed17d0410c352ccb4d"},
{file = "newrelic-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6ff022c7556b61b067e8e6b729fe60f437a8356f319ae3b8342858792f3930d"},
{file = "newrelic-10.2.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3521d646c0032db53b7320fe6b6859eebd863f1b47d7c7dd480073727091e50e"},
{file = "newrelic-10.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b4220b97669d214e75d2039fea9e0505fde5bc450832210abbc76b9a635785ed"},
{file = "newrelic-10.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c6aa9cf936b16d13b65c1b7aa6c722a76a0702469f91bcfc3c5b39f3293181eb"},
{file = "newrelic-10.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36a2218c9e79897d9b5671cdeac30c467d7fbac10cda4f2d79062f2bd0fcaed8"},
{file = "newrelic-10.2.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f021eac4c2e3b14eab90c608d8bd25b4e3c6b0b0d40796ec1c1260cc47b5e83"},
{file = "newrelic-10.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:69aa68cae47c595bdeb95f275d78693ec27a9fd9353bf81257e21f8607134db6"},
{file = "newrelic-10.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ef5d27001d3b5ca53f19d150c60b570c1b0c774d082ab9bf8349f4430ed85b48"},
{file = "newrelic-10.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00df1aa613294cb592a52157f789e75166dbf439cfa9e6cf59f6cf4a265dada9"},
{file = "newrelic-10.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6d4094d19db924c51ca35da603344907bcbca030822f7a78d5d9c6ad361d419"},
{file = "newrelic-10.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:62d521a5d7269c8a5c5838c4ca3b757ef63a13257302c901223c75510cc6f9f5"},
{file = "newrelic-10.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3cd5aeade6462519328fc42f4e98948a45571f3d22360a0559e19a6525c723a6"},
{file = "newrelic-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6333aa7051544ddc7f8a85f344bc3f401ddd8635540878da34de7bfd91f5d95"},
{file = "newrelic-10.2.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b449546ebb89feaadfd36fda7735ce06023fc90979b838e244f98369aba5ccb"},
{file = "newrelic-10.2.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:fc3d34db12133b481636384663f45b9ccd7f0f41554a59c15ec37aeb4f77227d"},
{file = "newrelic-10.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a15df23effd09bb1d1f5c38866b75cc5f380b6aa953efbca9e95e79b72744db4"},
{file = "newrelic-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc693e0db87ab4cf6623847c3949debbcc991554edfb4dd8c02c136e0770b367"},
{file = "newrelic-10.2.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c57e79d37ed87e2790c5e66253f9a5d91ed8cc218f160d5a4d062fc759791a78"},
{file = "newrelic-10.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:23400846dad2283693eade90b6d3c3462301a4b7735c7f76009b1fa445660aeb"},
{file = "newrelic-10.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:32bd34e4cd73c2435472c0b67869fd2db914d6c99d3e1e404e09affe61a8551e"},
{file = "newrelic-10.2.0.tar.gz", hash = "sha256:03ab987eae0452aeb5aed8571c100d1735613a3a227387f99fe54ed38f1ae0e9"},
{file = "newrelic-10.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0192ae5b6d624b1dbd5bb5808e0b0846e109d614b38ddfab6328e87bb261d889"},
{file = "newrelic-10.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f2cfa6aee9668f6e19c3eba77c851dddbd0bc4c87af2551ad6a56f17a3d5fbe"},
{file = "newrelic-10.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c532a2ec39b1f8fbeba7b557ae2d0cb3440012a0ecbbd138b6a137ecd90224c9"},
{file = "newrelic-10.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b282a069984e3bf3bc810eaf988165eb0e8dccc99b3c34d7c0f34c7649093fbe"},
{file = "newrelic-10.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5682142c7f94ca1294b51e3c49cb3ef64580ce36ff223efa23fbc2f34e7fb392"},
{file = "newrelic-10.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6cc7d1df935239613582d592253fbe3a961f372c9f2568e4e8b0937caf8f072b"},
{file = "newrelic-10.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:659991fc2f5149b4f7711e7f51c0a76ac1bdef7ca0b5f1b4806c21ed05553963"},
{file = "newrelic-10.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c657a50cc831d80bff310022106e1086c224b50a114241cb61194921498bcd5c"},
{file = "newrelic-10.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8af1c6cea8ffdb185fcfe871f61ab2de8471c71b1d417f07a93e6fe7713f012a"},
{file = "newrelic-10.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3af6f16cfa95d187a37be26ef44f73b15c33b3600c14d4936fc77666a26192e6"},
{file = "newrelic-10.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a64e99fe3bb0a5bb9f942053291214659345064f79355f1cb1753b2ae3989792"},
{file = "newrelic-10.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:120bce9635c39e8f444e127132e08cf536a042138c5688c6bce27dbfd44caf6f"},
{file = "newrelic-10.6.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b539ebb8e624b797a4dd35ef742a11148f16d8d9b4da5b4b4c77c33a354e9206"},
{file = "newrelic-10.6.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abe82936cd60d9ebfb3573311c9ca4c22682f3ca5a27baa2bf5f9e26a7a4ed7e"},
{file = "newrelic-10.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f3a9c60cd1b4b76acab30306d30b95b90fd19eaa38c34ef84dcaade7a6894426"},
{file = "newrelic-10.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eb69b15f2f917fd7874e05f0444752ae1cc1eb63028814ab77033babfc6329e5"},
{file = "newrelic-10.6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8ca468375f5959867fda7bbe90f2d80ca32aae6c5d632e3234e32064013e439"},
{file = "newrelic-10.6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:319c4df59515d214ae91fd6c46cc7fc120217ffc38a080689fd9930b3b2a7530"},
{file = "newrelic-10.6.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:23fc4539240bb8a5c131d56f0d2d982c253ee2adf6e2dff945daaab07ef5e527"},
{file = "newrelic-10.6.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89b304474705254d94cfc82d58a505f18c5bba029aaec32fba15ba582cb2c849"},
{file = "newrelic-10.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:465f4f8be765121a6e1d215184b61b8c6c5bb24106f7e3f1443cffe14f647e8f"},
{file = "newrelic-10.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:579d146ff0309e93af712759df8732e7d23a7c676dc1e38c2ac32af46be15f5d"},
{file = "newrelic-10.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:07b31b0970ad4b160a9481f1bd908250aa39ee8d7f2a4fa1d88b13c360361241"},
{file = "newrelic-10.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2c9f39b6b1154dfa21831d7632b53cd1fa1a2b4069a273dead1a40186621688e"},
{file = "newrelic-10.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ba77efcbf1aaa902cdc546480fa05af6e907e4334798b53ab08f34be1d13a9c"},
{file = "newrelic-10.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaf6f29b82845355faa32e719d246a63b29d2b8cbd406cc31a4991dc213c4813"},
{file = "newrelic-10.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:275d930c60cb534e26dd2aaa61e5c25b6312d63fef011055203d3f6a26e20863"},
{file = "newrelic-10.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:43a8aafc4a5d844192872a94d9033f4721693a6c0950750f5a0b0e448821831b"},
{file = "newrelic-10.6.0.tar.gz", hash = "sha256:46d6f39a9cb89cca2440211e272e6a13bb89802865a4c2b2cd9d613c1488c3ba"},
]
[package.extras]
@@ -2310,38 +2314,44 @@ diagrams = ["jinja2", "railroad-diagrams"]
[[package]]
name = "pyproj"
version = "3.6.1"
version = "3.7.1"
description = "Python interface to PROJ (cartographic projections and coordinate transformations library)"
optional = false
python-versions = ">=3.9"
python-versions = ">=3.10"
files = [
{file = "pyproj-3.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ab7aa4d9ff3c3acf60d4b285ccec134167a948df02347585fdd934ebad8811b4"},
{file = "pyproj-3.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4bc0472302919e59114aa140fd7213c2370d848a7249d09704f10f5b062031fe"},
{file = "pyproj-3.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5279586013b8d6582e22b6f9e30c49796966770389a9d5b85e25a4223286cd3f"},
{file = "pyproj-3.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80fafd1f3eb421694857f254a9bdbacd1eb22fc6c24ca74b136679f376f97d35"},
{file = "pyproj-3.6.1-cp310-cp310-win32.whl", hash = "sha256:c41e80ddee130450dcb8829af7118f1ab69eaf8169c4bf0ee8d52b72f098dc2f"},
{file = "pyproj-3.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:db3aedd458e7f7f21d8176f0a1d924f1ae06d725228302b872885a1c34f3119e"},
{file = "pyproj-3.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ebfbdbd0936e178091309f6cd4fcb4decd9eab12aa513cdd9add89efa3ec2882"},
{file = "pyproj-3.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:447db19c7efad70ff161e5e46a54ab9cc2399acebb656b6ccf63e4bc4a04b97a"},
{file = "pyproj-3.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7e13c40183884ec7f94eb8e0f622f08f1d5716150b8d7a134de48c6110fee85"},
{file = "pyproj-3.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65ad699e0c830e2b8565afe42bd58cc972b47d829b2e0e48ad9638386d994915"},
{file = "pyproj-3.6.1-cp311-cp311-win32.whl", hash = "sha256:8b8acc31fb8702c54625f4d5a2a6543557bec3c28a0ef638778b7ab1d1772132"},
{file = "pyproj-3.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:38a3361941eb72b82bd9a18f60c78b0df8408416f9340521df442cebfc4306e2"},
{file = "pyproj-3.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1e9fbaf920f0f9b4ee62aab832be3ae3968f33f24e2e3f7fbb8c6728ef1d9746"},
{file = "pyproj-3.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d227a865356f225591b6732430b1d1781e946893789a609bb34f59d09b8b0f8"},
{file = "pyproj-3.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83039e5ae04e5afc974f7d25ee0870a80a6bd6b7957c3aca5613ccbe0d3e72bf"},
{file = "pyproj-3.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb059ba3bced6f6725961ba758649261d85ed6ce670d3e3b0a26e81cf1aa8d"},
{file = "pyproj-3.6.1-cp312-cp312-win32.whl", hash = "sha256:2d6ff73cc6dbbce3766b6c0bce70ce070193105d8de17aa2470009463682a8eb"},
{file = "pyproj-3.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:7a27151ddad8e1439ba70c9b4b2b617b290c39395fa9ddb7411ebb0eb86d6fb0"},
{file = "pyproj-3.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4ba1f9b03d04d8cab24d6375609070580a26ce76eaed54631f03bab00a9c737b"},
{file = "pyproj-3.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18faa54a3ca475bfe6255156f2f2874e9a1c8917b0004eee9f664b86ccc513d3"},
{file = "pyproj-3.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd43bd9a9b9239805f406fd82ba6b106bf4838d9ef37c167d3ed70383943ade1"},
{file = "pyproj-3.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50100b2726a3ca946906cbaa789dd0749f213abf0cbb877e6de72ca7aa50e1ae"},
{file = "pyproj-3.6.1-cp39-cp39-win32.whl", hash = "sha256:9274880263256f6292ff644ca92c46d96aa7e57a75c6df3f11d636ce845a1877"},
{file = "pyproj-3.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:36b64c2cb6ea1cc091f329c5bd34f9c01bb5da8c8e4492c709bda6a09f96808f"},
{file = "pyproj-3.6.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd93c1a0c6c4aedc77c0fe275a9f2aba4d59b8acf88cebfc19fe3c430cfabf4f"},
{file = "pyproj-3.6.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6420ea8e7d2a88cb148b124429fba8cd2e0fae700a2d96eab7083c0928a85110"},
{file = "pyproj-3.6.1.tar.gz", hash = "sha256:44aa7c704c2b7d8fb3d483bbf75af6cb2350d30a63b144279a09b75fead501bf"},
{file = "pyproj-3.7.1-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:bf09dbeb333c34e9c546364e7df1ff40474f9fddf9e70657ecb0e4f670ff0b0e"},
{file = "pyproj-3.7.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:6575b2e53cc9e3e461ad6f0692a5564b96e7782c28631c7771c668770915e169"},
{file = "pyproj-3.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cb516ee35ed57789b46b96080edf4e503fdb62dbb2e3c6581e0d6c83fca014b"},
{file = "pyproj-3.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e47c4e93b88d99dd118875ee3ca0171932444cdc0b52d493371b5d98d0f30ee"},
{file = "pyproj-3.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3e8d276caeae34fcbe4813855d0d97b9b825bab8d7a8b86d859c24a6213a5a0d"},
{file = "pyproj-3.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f173f851ee75e54acdaa053382b6825b400cb2085663a9bb073728a59c60aebb"},
{file = "pyproj-3.7.1-cp310-cp310-win32.whl", hash = "sha256:f550281ed6e5ea88fcf04a7c6154e246d5714be495c50c9e8e6b12d3fb63e158"},
{file = "pyproj-3.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:3537668992a709a2e7f068069192138618c00d0ba113572fdd5ee5ffde8222f3"},
{file = "pyproj-3.7.1-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:a94e26c1a4950cea40116775588a2ca7cf56f1f434ff54ee35a84718f3841a3d"},
{file = "pyproj-3.7.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:263b54ba5004b6b957d55757d846fc5081bc02980caa0279c4fc95fa0fff6067"},
{file = "pyproj-3.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6d6a2ccd5607cd15ef990c51e6f2dd27ec0a741e72069c387088bba3aab60fa"},
{file = "pyproj-3.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c5dcf24ede53d8abab7d8a77f69ff1936c6a8843ef4fcc574646e4be66e5739"},
{file = "pyproj-3.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3c2e7449840a44ce860d8bea2c6c1c4bc63fa07cba801dcce581d14dcb031a02"},
{file = "pyproj-3.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0829865c1d3a3543f918b3919dc601eea572d6091c0dd175e1a054db9c109274"},
{file = "pyproj-3.7.1-cp311-cp311-win32.whl", hash = "sha256:6181960b4b812e82e588407fe5c9c68ada267c3b084db078f248db5d7f45d18a"},
{file = "pyproj-3.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ad0ff443a785d84e2b380869fdd82e6bfc11eba6057d25b4409a9bbfa867970"},
{file = "pyproj-3.7.1-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:2781029d90df7f8d431e29562a3f2d8eafdf233c4010d6fc0381858dc7373217"},
{file = "pyproj-3.7.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:d61bf8ab04c73c1da08eedaf21a103b72fa5b0a9b854762905f65ff8b375d394"},
{file = "pyproj-3.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04abc517a8555d1b05fcee768db3280143fe42ec39fdd926a2feef31631a1f2f"},
{file = "pyproj-3.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084c0a475688f934d386c2ab3b6ce03398a473cd48adfda70d9ab8f87f2394a0"},
{file = "pyproj-3.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a20727a23b1e49c7dc7fe3c3df8e56a8a7acdade80ac2f5cca29d7ca5564c145"},
{file = "pyproj-3.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bf84d766646f1ebd706d883755df4370aaf02b48187cedaa7e4239f16bc8213d"},
{file = "pyproj-3.7.1-cp312-cp312-win32.whl", hash = "sha256:5f0da2711364d7cb9f115b52289d4a9b61e8bca0da57f44a3a9d6fc9bdeb7274"},
{file = "pyproj-3.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:aee664a9d806612af30a19dba49e55a7a78ebfec3e9d198f6a6176e1d140ec98"},
{file = "pyproj-3.7.1-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:5f8d02ef4431dee414d1753d13fa82a21a2f61494737b5f642ea668d76164d6d"},
{file = "pyproj-3.7.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:0b853ae99bda66cbe24b4ccfe26d70601d84375940a47f553413d9df570065e0"},
{file = "pyproj-3.7.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83db380c52087f9e9bdd8a527943b2e7324f275881125e39475c4f9277bdeec4"},
{file = "pyproj-3.7.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b35ed213892e211a3ce2bea002aa1183e1a2a9b79e51bb3c6b15549a831ae528"},
{file = "pyproj-3.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a8b15b0463d1303bab113d1a6af2860a0d79013c3a66fcc5475ce26ef717fd4f"},
{file = "pyproj-3.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:87229e42b75e89f4dad6459200f92988c5998dfb093c7c631fb48524c86cd5dc"},
{file = "pyproj-3.7.1-cp313-cp313-win32.whl", hash = "sha256:d666c3a3faaf3b1d7fc4a544059c4eab9d06f84a604b070b7aa2f318e227798e"},
{file = "pyproj-3.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:d3caac7473be22b6d6e102dde6c46de73b96bc98334e577dfaee9886f102ea2e"},
{file = "pyproj-3.7.1.tar.gz", hash = "sha256:60d72facd7b6b79853f19744779abcd3f804c4e0d4fa8815469db20c9f640a47"},
]
[package.dependencies]
@@ -3039,13 +3049,13 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess
[[package]]
name = "vulture"
version = "2.13"
version = "2.14"
description = "Find dead code"
optional = false
python-versions = ">=3.8"
files = [
{file = "vulture-2.13-py2.py3-none-any.whl", hash = "sha256:34793ba60488e7cccbecdef3a7fe151656372ef94fdac9fe004c52a4000a6d44"},
{file = "vulture-2.13.tar.gz", hash = "sha256:78248bf58f5eaffcc2ade306141ead73f437339950f80045dce7f8b078e5a1aa"},
{file = "vulture-2.14-py2.py3-none-any.whl", hash = "sha256:d9a90dba89607489548a49d557f8bac8112bd25d3cbc8aeef23e860811bd5ed9"},
{file = "vulture-2.14.tar.gz", hash = "sha256:cb8277902a1138deeab796ec5bef7076a6e0248ca3607a3f3dee0b6d9e9b8415"},
]
[[package]]
@@ -3134,4 +3144,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.12.2"
content-hash = "3035525f7fc44291ea02b011d9fa6c0e84501d567417144e4d8fd6beebb11928"
content-hash = "ad24005d65a2f8cff39fc4a1aa543f5aebb103c0748b3dadd9cb5ee5c692846c"

View File

@@ -30,7 +30,7 @@ pyexcel-ods3 = "==0.6.1"
pyexcel-xls = "==0.7.0"
pyexcel-xlsx = "==0.6.0"
openpyxl = "==3.0.10"
pyproj = "==3.6.1"
pyproj = "==3.7.1"
python-dotenv = "==1.0.1"
pytz = "^2024.1"
rtreelib = "==0.2.0"
@@ -40,10 +40,10 @@ markdown = "^3.5.2"
async-timeout = "^4.0.3"
bleach = "^6.1.0"
boto3 = "^1.34.156"
botocore = "^1.34.156"
botocore = "^1.35.99"
cachetools = "^5.4.0"
cffi = "^1.16.0"
cryptography = "^43.0.1"
cryptography = "^44.0.1"
flask-redis = "^0.4.0"
geojson = "^3.1.0"
jmespath = "^1.0.1"
@@ -94,7 +94,7 @@ pytest-playwright = "^0.5.1"
pytest-xdist = "^3.5.0"
radon = "^6.0.1"
requests-mock = "^1.11.0"
vulture = "^2.11"
vulture = "^2.14"
[build-system]

View File

@@ -1521,7 +1521,7 @@ def test_link_to_upload_not_offered_when_entering_personalisation(
# Were entering personalization
assert page.select_one("input[type=text]")["name"] == "placeholder_value"
assert page.select_one("label[for=placeholder_value]").text.strip() == "name"
assert page.select_one("label[for=phone-number]").text.strip() == "name"
# No Upload link shown
assert len(page.select("main a")) == 0
assert "Upload" not in page.select_one("main").text

View File

@@ -174,9 +174,8 @@ def test_should_show_empty_text_box(
# data-module=autofocus is set on a containing element so it
# shouldnt also be set on the textbox itself
assert "data-module" not in textbox
assert (
normalize_spaces(page.select_one("label[for=placeholder_value]").text) == "one"
)
assert normalize_spaces(page.select_one("label[for=phone-number]").text) == "one"
def test_should_prefill_answers_for_get_tour_step(

View File

@@ -55,8 +55,16 @@ from app.notify_client.notification_api_client import NotificationApiClient
def test_client_gets_notifications_for_service_and_job_by_page(
mocker, arguments, expected_call
):
mocker.patch(
"app.extensions.RedisClient.get",
return_value={},
)
mocker.patch("app.extensions.RedisClient.set", return_value={})
mock_get = mocker.patch(
"app.notify_client.notification_api_client.NotificationApiClient.get"
"app.notify_client.notification_api_client.NotificationApiClient.get",
return_value={},
)
NotificationApiClient().get_notifications_for_service("abcd1234", **arguments)
mock_get.assert_called_once_with(**expected_call)
@@ -102,8 +110,16 @@ def test_client_gets_notifications_for_service_and_job_by_page(
def test_client_gets_notifications_for_service_and_job_by_page_posts_for_to(
mocker, arguments, expected_call
):
mocker.patch(
"app.extensions.RedisClient.get",
return_value={},
)
mocker.patch("app.extensions.RedisClient.set", return_value={})
mock_post = mocker.patch(
"app.notify_client.notification_api_client.NotificationApiClient.post"
"app.notify_client.notification_api_client.NotificationApiClient.post",
return_value={},
)
NotificationApiClient().get_notifications_for_service("abcd1234", **arguments)
mock_post.assert_called_once_with(**expected_call)
@@ -141,8 +157,15 @@ def test_get_notification(mocker):
def test_get_notification_count_for_job_id(mocker):
mock_get = mocker.patch(
"app.notify_client.notification_api_client.NotificationApiClient.get"
"app.notify_client.notification_api_client.NotificationApiClient.get",
return_value={"count": 0},
)
mocker.patch(
"app.notify_client.notification_api_client.redis_client.get", return_value=None
)
mocker.patch("app.notify_client.billing_api_client.redis_client.set")
NotificationApiClient().get_notification_count_for_job_id(
service_id="foo", job_id="bar"
)

View File

@@ -107,6 +107,7 @@ EXCLUDED_ENDPOINTS = tuple(
"get_volumes_by_service",
"get_example_csv",
"get_notifications_as_json",
"get_redis_report",
"get_started",
"get_started_old",
"go_to_dashboard_after_tour",

View File

@@ -40,7 +40,7 @@ beforeAll(done => {
</div>
</div>
<div id="aria-live-account" class="usa-sr-only" aria-live="polite"></div>
<div id="activityContainer" data-currentUserName="Test User"></div>
<div id="activityContainer" data-currentUserName="Test User" data-currentServiceId="12345"></div>
<div id="aria-live-account" class="usa-sr-only" aria-live="polite"></div>
`;
@@ -64,13 +64,13 @@ test('D3 is loaded correctly', () => {
test('Populates the accessible table for activity chart correctly', () => {
const sampleData = {
'2024-07-01': { sms: { delivered: 50, failed: 5 } },
'2024-07-02': { sms: { delivered: 60, failed: 2 } },
'2024-07-03': { sms: { delivered: 70, failed: 1 } },
'2024-07-04': { sms: { delivered: 80, failed: 0 } },
'2024-07-05': { sms: { delivered: 90, failed: 3 } },
'2024-07-06': { sms: { delivered: 100, failed: 4 } },
'2024-07-07': { sms: { delivered: 110, failed: 2 } },
'2024-07-01': { sms: { delivered: 50, failed: 5, pending: 10 } },
'2024-07-02': { sms: { delivered: 60, failed: 2, pending: 5 } },
'2024-07-03': { sms: { delivered: 70, failed: 1, pending: 3 } },
'2024-07-04': { sms: { delivered: 80, failed: 0, pending: 0 } },
'2024-07-05': { sms: { delivered: 90, failed: 3, pending: 8 } },
'2024-07-06': { sms: { delivered: 100, failed: 4, pending: 7 } },
'2024-07-07': { sms: { delivered: 110, failed: 2, pending: 6 } },
};
const labels = Object.keys(sampleData).map(dateString => {
@@ -79,8 +79,9 @@ test('Populates the accessible table for activity chart correctly', () => {
});
const deliveredData = Object.values(sampleData).map(d => d.sms.delivered);
const failedData = Object.values(sampleData).map(d => d.sms.failed);
const pendingData = Object.values(sampleData).map(d => d.sms.pending);
window.createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData);
window.createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData, pendingData);
const table = document.getElementById('weeklyTable');
expect(table).toBeDefined();
@@ -92,6 +93,7 @@ test('Populates the accessible table for activity chart correctly', () => {
expect(headers[0].textContent).toBe('Day');
expect(headers[1].textContent).toBe('Delivered');
expect(headers[2].textContent).toBe('Failed');
expect(headers[3].textContent).toBe('Pending');
const firstRowCells = rows[1].getElementsByTagName('td');
expect(firstRowCells[0].textContent).toBe('07/01/24');
@@ -100,58 +102,73 @@ test('Populates the accessible table for activity chart correctly', () => {
});
test('SVG element is correctly set up', () => {
window.createChart('#weeklyChart', ['07/01/24', '07/02/24', '07/03/24', '07/04/24', '07/05/24', '07/06/24', '07/07/24'], [50, 60, 70, 80, 90, 100, 110], [5, 2, 1, 0, 3, 4, 2]);
window.createChart(
'#weeklyChart',
['07/01/24', '07/02/24', '07/03/24', '07/04/24', '07/05/24', '07/06/24', '07/07/24'],
[50, 60, 70, 80, 90, 100, 110],
[5, 2, 1, 0, 3, 4, 2],
[10, 5, 3, 0, 8, 7, 6]
);
const svg = document.getElementById('weeklyChart').querySelector('svg');
expect(svg).not.toBeNull();
expect(svg.getAttribute('width')).toBe('0');
expect(svg.getAttribute('height')).toBe('400');
expect(svg.querySelectorAll('.bar-group').length).toBe(3);
});
test('Check HTML content after chart creation', () => {
// Create sample data for the chart
const labels = ['07/01/24', '07/02/24', '07/03/24', '07/04/24', '07/05/24', '07/06/24', '07/07/24'];
const deliveredData = [50, 60, 70, 80, 90, 100, 110];
const failedData = [5, 2, 1, 0, 3, 4, 2];
const pendingData = [10, 5, 8, 3, 6, 7, 4];
// Ensure the container has the correct width
const container = document.getElementById('weeklyChart');
container.style.width = '600px'; // Force a specific width
container.style.width = '600px';
const containerWidth = container.clientWidth;
expect(containerWidth).toBeGreaterThan(0);
// Call the function to create the chart
window.createChart('#weeklyChart', labels, deliveredData, failedData);
window.createChart('#weeklyChart', labels, deliveredData, failedData, pendingData);
// Optionally, you can add assertions to check for specific elements
expect(container.querySelector('svg')).not.toBeNull();
expect(container.querySelectorAll('rect').length).toBeGreaterThan(0);
const svg = container.querySelector('svg');
expect(svg).not.toBeNull();
const bars = container.querySelectorAll('rect');
expect(bars.length).toBeGreaterThan(0);
const barGroups = svg.querySelectorAll('.bar-group');
expect(barGroups.length).toBe(3);
const pendingBars = Array.from(bars).filter(bar =>
bar.parentNode.getAttribute('fill') === '#C7CACE'
);
expect(pendingBars.length).toBe(labels.length);
});
test('Legend is visible when there are delivered or failed messages', () => {
// Example data with delivered and failed messages
test('Legend includes pending when data exists', () => {
const labels = ['Day 1', 'Day 2'];
const deliveredData = [10, 20]; // Mock delivered data
const failedData = [5, 0]; // Mock failed data
const deliveredData = [10, 20];
const failedData = [5, 0];
const pendingData = [3, 2];
// Call the createChart function
window.createChart('#weeklyChart', labels, deliveredData, failedData);
window.createChart('#weeklyChart', labels, deliveredData, failedData, pendingData);
// Check if the legend is displayed using computed style
const legendContainer = document.querySelector('.chart-legend');
const legendDisplayStyle = window.getComputedStyle(legendContainer).display;
expect(legendDisplayStyle).toBe('flex');
expect(legendContainer.querySelectorAll('.legend-item').length).toBe(2); // Ensure two legend items
const legendItems = legendContainer.querySelectorAll('.legend-item');
expect(legendItems.length).toBe(3);
const pendingLegend = Array.from(legendItems).find(item =>
item.textContent.includes('Pending')
);
expect(pendingLegend).not.toBeNull();
});
test('Legend is hidden when there are no delivered or failed messages', () => {
// Example data with no delivered and no failed messages
test('Legend is hidden when there are no delivered, failed, or pending messages', () => {
const labels = ['Day 1', 'Day 2'];
const deliveredData = [0, 0]; // No delivered messages
const failedData = [0, 0]; // No failed messages
const deliveredData = [0, 0];
const failedData = [0, 0];
const pendingData = [0, 0];
// Call the createChart function
window.createChart('#weeklyChart', labels, deliveredData, failedData);
window.createChart('#weeklyChart', labels, deliveredData, failedData, pendingData);
// Check if the legend is hidden using computed style
const legendContainer = document.querySelector('.chart-legend');
@@ -161,25 +178,48 @@ test('Legend is hidden when there are no delivered or failed messages', () => {
test('Fetches data and creates chart and table correctly', async () => {
const mockResponse = {
'2024-07-01': { sms: { delivered: 50, failed: 5 } },
'2024-07-02': { sms: { delivered: 60, failed: 2 } },
'2024-07-03': { sms: { delivered: 70, failed: 1 } },
'2024-07-04': { sms: { delivered: 80, failed: 0 } },
'2024-07-05': { sms: { delivered: 90, failed: 3 } },
'2024-07-06': { sms: { delivered: 100, failed: 4 } },
'2024-07-07': { sms: { delivered: 110, failed: 2 } },
'2024-07-01': { sms: { delivered: 50, failed: 5, pending: 10 } },
'2024-07-02': { sms: { delivered: 60, failed: 2, pending: 8 } },
'2024-07-03': { sms: { delivered: 70, failed: 1, pending: 6 } },
'2024-07-04': { sms: { delivered: 80, failed: 0, pending: 4 } },
'2024-07-05': { sms: { delivered: 90, failed: 3, pending: 7 } },
'2024-07-06': { sms: { delivered: 100, failed: 4, pending: 5 } },
'2024-07-07': { sms: { delivered: 110, failed: 2, pending: 3 } },
};
const tableContainer = document.getElementById('activityContainer');
const currentServiceId = tableContainer.getAttribute('data-currentServiceId');
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve(mockResponse),
ok: true,
json: () => Promise.resolve(mockResponse),
})
);
);
const data = await fetchData('service');
const data = await fetchData('service');
expect(global.fetch).toHaveBeenCalledWith('/daily_stats.json');
expect(data).toEqual(mockResponse);
expect(global.fetch).toHaveBeenCalledWith(`/services/${currentServiceId}/daily-stats.json`);
expect(data).toEqual(mockResponse);
const labels = Object.keys(mockResponse).map(dateString => {
const dateParts = dateString.split('-');
return `${dateParts[1]}/${dateParts[2]}/${dateParts[0].slice(2)}`;
});
const deliveredData = Object.values(mockResponse).map(d => d.sms.delivered);
const failedData = Object.values(mockResponse).map(d => d.sms.failed);
const pendingData = Object.values(mockResponse).map(d => d.sms.pending);
window.createChart('#weeklyChart', labels, deliveredData, failedData, pendingData);
window.createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData, pendingData);
const chart = document.getElementById('weeklyChart').querySelector('svg');
expect(chart).not.toBeNull();
const table = document.getElementById('weeklyTable');
expect(table).toBeDefined();
const rows = table.getElementsByTagName('tr');
expect(rows.length).toBe(8);
});
test('handleDropdownChange updates DOM for individual selection', () => {
@@ -188,7 +228,7 @@ test('handleDropdownChange updates DOM for individual selection', () => {
<div class="chart-subtitle"></div>
</div>
<div id="aria-live-account"></div>
<div id="activityContainer" data-currentUserName="Test User"></div>
<div id="activityContainer" data-currentUserName="Test User" data-currentServiceId="12345"></div>
<div id="tableActivity">
<h2 id="table-heading"></h2>
<table id="activity-table">

View File

@@ -53,6 +53,7 @@
10096 WARN (Timestamp Disclosure - Passive/release)
10097 WARN (Hash Disclosure - Passive/beta)
10098 WARN (Cross-Domain Misconfiguration - Passive/release)
10099 IGNORE (Source Code Disclosure - Java)
10104 WARN (User Agent Fuzzer - Active/beta)
10105 WARN (Weak Authentication Method - Passive/release)
10106 IGNORE (HTTP Only Site - Active/beta)