Compare commits
406 Commits
08-15-2024
...
10-16-2024
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31076781a3 | ||
|
|
e54d18170e | ||
|
|
c3f4482677 | ||
|
|
0183f37c7b | ||
|
|
fc6adc6771 | ||
|
|
34ccfb5bd7 | ||
|
|
db5626f5b0 | ||
|
|
2624bd885d | ||
|
|
a5ff9af1eb | ||
|
|
8e29229d8b | ||
|
|
c30dbec6ce | ||
|
|
29854c6164 | ||
|
|
978031744f | ||
|
|
f27703112a | ||
|
|
c47144a8b0 | ||
|
|
fd447e9a75 | ||
|
|
9ee0da0b32 | ||
|
|
2fbce24f97 | ||
|
|
10da94dbf8 | ||
|
|
d86b1afb9a | ||
|
|
10a8d1b938 | ||
|
|
b7b23ba6c8 | ||
|
|
1cc760c153 | ||
|
|
338fbfc407 | ||
|
|
5adcfae5d8 | ||
|
|
b8b4a707a3 | ||
|
|
61ea8e0b03 | ||
|
|
3ad6f9daae | ||
|
|
947f85ba51 | ||
|
|
65c99bf8e8 | ||
|
|
d8feea6bd5 | ||
|
|
c89a6089a0 | ||
|
|
971486be20 | ||
|
|
cd6c9af0bc | ||
|
|
7eb7ed1855 | ||
|
|
ea93b57190 | ||
|
|
3865c5b952 | ||
|
|
f37b89849e | ||
|
|
397bbeabde | ||
|
|
a11c567de3 | ||
|
|
bdc27c10d0 | ||
|
|
f7f449ff07 | ||
|
|
b133def7fc | ||
|
|
b4b15bc209 | ||
|
|
44524eb9e2 | ||
|
|
4b8b078476 | ||
|
|
22f9ff603a | ||
|
|
3f7e600ac1 | ||
|
|
5a5b0649e0 | ||
|
|
a23fe5c046 | ||
|
|
306391c958 | ||
|
|
e41ad9fab9 | ||
|
|
fb83e8610c | ||
|
|
d0a435becd | ||
|
|
62f58c6e27 | ||
|
|
0db68453b9 | ||
|
|
fbba1a735f | ||
|
|
35eb2a9013 | ||
|
|
108e93d06a | ||
|
|
4d4288d3c7 | ||
|
|
c0ffa63f88 | ||
|
|
89413c56ba | ||
|
|
e6a3c0d4b7 | ||
|
|
7d2f7d1c8a | ||
|
|
f118b1c4bf | ||
|
|
85a5e3075a | ||
|
|
59959cfab4 | ||
|
|
68cf422ca8 | ||
|
|
a9999aa8ec | ||
|
|
3a50fff6ea | ||
|
|
c15d388fc2 | ||
|
|
ec3ebe5b90 | ||
|
|
fbdc01b109 | ||
|
|
60e3c5291e | ||
|
|
c178a3e77e | ||
|
|
4e3eb03040 | ||
|
|
29b9e122ba | ||
|
|
63c1652c40 | ||
|
|
f28a618e78 | ||
|
|
aa7c7b7d9a | ||
|
|
4c0221e295 | ||
|
|
21d1b69c22 | ||
|
|
5aac3130b3 | ||
|
|
75ce12f5da | ||
|
|
21bd20f607 | ||
|
|
371eb58392 | ||
|
|
a27b9a701b | ||
|
|
c328044636 | ||
|
|
77f44d4d5e | ||
|
|
73b3a61746 | ||
|
|
c5b2535b8e | ||
|
|
9449b80975 | ||
|
|
cc3d70303c | ||
|
|
ef528360be | ||
|
|
ae1a51ff79 | ||
|
|
e9b364010a | ||
|
|
a8bf2414b4 | ||
|
|
ced229167f | ||
|
|
8deed320f8 | ||
|
|
114814b0b5 | ||
|
|
88262302e6 | ||
|
|
2e8f82be3b | ||
|
|
d63cfb3cb0 | ||
|
|
6980e28786 | ||
|
|
c9acf937cf | ||
|
|
37bb89402e | ||
|
|
f20803ecae | ||
|
|
addf4f0959 | ||
|
|
b7f26454ff | ||
|
|
a669477f48 | ||
|
|
8c932a6072 | ||
|
|
d2c654214b | ||
|
|
3120f82b1e | ||
|
|
0e45588e5a | ||
|
|
d4223429ec | ||
|
|
1ea15d5ba1 | ||
|
|
915b18bbbd | ||
|
|
f2fc6da0ee | ||
|
|
e96aec9898 | ||
|
|
e11b6376c2 | ||
|
|
4e2630d675 | ||
|
|
0a5ca7d0d6 | ||
|
|
64a01e70b7 | ||
|
|
466137ba07 | ||
|
|
0d04524525 | ||
|
|
08c97a8066 | ||
|
|
6815493f4a | ||
|
|
f578a52010 | ||
|
|
40a0418731 | ||
|
|
b7d699feae | ||
|
|
8b9293db0e | ||
|
|
6ff8be5d3e | ||
|
|
475bd0c1b4 | ||
|
|
2c7d090b82 | ||
|
|
4f193e362c | ||
|
|
687372fd6c | ||
|
|
302a79b41f | ||
|
|
f0299c4042 | ||
|
|
bc5b980431 | ||
|
|
08384dd1fa | ||
|
|
bdc5614fff | ||
|
|
060db76640 | ||
|
|
b2b758ca8d | ||
|
|
584ccce53d | ||
|
|
dda788b073 | ||
|
|
feff8ec1be | ||
|
|
cfc9acc6f2 | ||
|
|
959a680664 | ||
|
|
a5e72ea4eb | ||
|
|
122343e46e | ||
|
|
86b217b1df | ||
|
|
982f77413e | ||
|
|
3967612eb1 | ||
|
|
b0f0a8d8db | ||
|
|
fd2de9bf9b | ||
|
|
cd24c2b029 | ||
|
|
63747515db | ||
|
|
55f588f13d | ||
|
|
81a629935c | ||
|
|
49ec3dc559 | ||
|
|
647b37ae61 | ||
|
|
7b2d495c1b | ||
|
|
47f0952125 | ||
|
|
e0fb031cf5 | ||
|
|
72eff3235e | ||
|
|
67ad9148d6 | ||
|
|
2f40be8cd0 | ||
|
|
a088409dfe | ||
|
|
ddd413f299 | ||
|
|
250a36836d | ||
|
|
f8fd0422bf | ||
|
|
4cdac9eba3 | ||
|
|
76863cd4dc | ||
|
|
3d00b0c94f | ||
|
|
4594dc317f | ||
|
|
5e93f2d572 | ||
|
|
a2dbb6c9e6 | ||
|
|
d097a0e2a6 | ||
|
|
d3855f47ac | ||
|
|
6f91d60030 | ||
|
|
f4d8816eac | ||
|
|
99ce42fc4a | ||
|
|
899347d349 | ||
|
|
14b37524ae | ||
|
|
5caa26e93c | ||
|
|
5ee559da10 | ||
|
|
73852a54f2 | ||
|
|
ec806ce385 | ||
|
|
8c3cdabb92 | ||
|
|
4e52d60a7c | ||
|
|
a5889d3448 | ||
|
|
5234706ae1 | ||
|
|
70c9801f1d | ||
|
|
df22530fa4 | ||
|
|
df630b58d8 | ||
|
|
f9337b614f | ||
|
|
09a775fdad | ||
|
|
62cfa2c39c | ||
|
|
8b6f719510 | ||
|
|
b953d316c4 | ||
|
|
5c3b5790ae | ||
|
|
d0d3878881 | ||
|
|
b6e0249adb | ||
|
|
4a8d768b6f | ||
|
|
876db282d5 | ||
|
|
028423adc1 | ||
|
|
56f7ad47e5 | ||
|
|
c44084f325 | ||
|
|
cf39b32808 | ||
|
|
84f3c61bc0 | ||
|
|
892705b125 | ||
|
|
ef9e3aecbe | ||
|
|
6da52f91b3 | ||
|
|
5eadcc0319 | ||
|
|
8d6478c3c1 | ||
|
|
a267cf3d4b | ||
|
|
f82bcb0509 | ||
|
|
c3a2441ade | ||
|
|
8ef357ea61 | ||
|
|
286166c2ad | ||
|
|
7b9e5288f8 | ||
|
|
df85f66a43 | ||
|
|
a91d6d25c8 | ||
|
|
88c47d15f6 | ||
|
|
6537eb2b1d | ||
|
|
75727520c0 | ||
|
|
d6c8ee90e8 | ||
|
|
a3ef868924 | ||
|
|
bdf4c9597b | ||
|
|
6e3db9d59a | ||
|
|
fc88ca55e7 | ||
|
|
b8edfa8175 | ||
|
|
1a247260ff | ||
|
|
92229707e2 | ||
|
|
5db3d4878f | ||
|
|
b1f1005441 | ||
|
|
6223341826 | ||
|
|
6b3fde3356 | ||
|
|
749e4cc708 | ||
|
|
a2d542c726 | ||
|
|
b67b15b6c9 | ||
|
|
d94a35cd30 | ||
|
|
26e53e76c5 | ||
|
|
6cf540be04 | ||
|
|
e3007f273e | ||
|
|
46a980c505 | ||
|
|
b32dde1a07 | ||
|
|
87581705c6 | ||
|
|
3aa68a3d25 | ||
|
|
1308894191 | ||
|
|
35dcfccd5f | ||
|
|
ddf9c6d3ec | ||
|
|
9e9ba9186a | ||
|
|
b5e2bf705d | ||
|
|
5ada9914b4 | ||
|
|
c075cacae9 | ||
|
|
da7d618460 | ||
|
|
9340069454 | ||
|
|
6621144061 | ||
|
|
f6a1f1af5b | ||
|
|
8b81c5ce7d | ||
|
|
ce74175be1 | ||
|
|
f74bf271a5 | ||
|
|
e8b12de581 | ||
|
|
dcc8055377 | ||
|
|
ab96955982 | ||
|
|
c36561ee4d | ||
|
|
c51a5593a7 | ||
|
|
be14a4a0c4 | ||
|
|
65ef22f26e | ||
|
|
e704cb1b78 | ||
|
|
2cae3462d3 | ||
|
|
5beb9fcce1 | ||
|
|
7fea566201 | ||
|
|
3158b91378 | ||
|
|
6f2c7be4ce | ||
|
|
b15b3a67f6 | ||
|
|
8cc14ec4bf | ||
|
|
4dbd9ede92 | ||
|
|
38fde60348 | ||
|
|
b1ad888b65 | ||
|
|
43bafbb617 | ||
|
|
c5060d3f14 | ||
|
|
15a87ec026 | ||
|
|
2690a059d2 | ||
|
|
5cf80ec5dc | ||
|
|
6fffa3cf43 | ||
|
|
e4c99dc15b | ||
|
|
33e616a8f1 | ||
|
|
d4b9a95b29 | ||
|
|
ed1ef5cd4a | ||
|
|
caefc958df | ||
|
|
981281f725 | ||
|
|
4cacd108b1 | ||
|
|
68c0763dec | ||
|
|
3f7d1ffffe | ||
|
|
7e38e28a5e | ||
|
|
f5baf1cb79 | ||
|
|
67f128c26e | ||
|
|
88af5de89d | ||
|
|
7d1a50a5b0 | ||
|
|
758efa8215 | ||
|
|
e9a8e2769a | ||
|
|
f27d53116e | ||
|
|
b20c8ec909 | ||
|
|
664d07b414 | ||
|
|
da6e9f4e8c | ||
|
|
549855c957 | ||
|
|
20428f7485 | ||
|
|
d00f4b5f46 | ||
|
|
d756b2289f | ||
|
|
8f15a83415 | ||
|
|
bb106c04f1 | ||
|
|
5a4402fd8b | ||
|
|
3d808cd3e5 | ||
|
|
87d4911f89 | ||
|
|
407f049360 | ||
|
|
e15c89703b | ||
|
|
9907409efd | ||
|
|
7b6b363532 | ||
|
|
f78fe350b0 | ||
|
|
0c612581cd | ||
|
|
ca7c2e0c93 | ||
|
|
1768a4bbd9 | ||
|
|
3456bb47a1 | ||
|
|
8e3cd6324f | ||
|
|
c67407f090 | ||
|
|
50e67d2308 | ||
|
|
85bafde78d | ||
|
|
c7b83596ba | ||
|
|
77d49754c6 | ||
|
|
1361c2f836 | ||
|
|
5b14a4ea90 | ||
|
|
769e63ebc7 | ||
|
|
b40cb5a9ad | ||
|
|
6ae00cabfe | ||
|
|
13cdb10d88 | ||
|
|
8c0fd48e06 | ||
|
|
4f9f546f23 | ||
|
|
4ca7087218 | ||
|
|
75f6ee6837 | ||
|
|
25722deb68 | ||
|
|
27c6098cc8 | ||
|
|
f553b863c6 | ||
|
|
5243878ef7 | ||
|
|
3b1bd05a10 | ||
|
|
10b65efe1f | ||
|
|
91f79f694e | ||
|
|
1bfd619057 | ||
|
|
03fae4d08f | ||
|
|
67b928085c | ||
|
|
cd98439ac3 | ||
|
|
f0ec9e3da9 | ||
|
|
66a555a2d4 | ||
|
|
339797558a | ||
|
|
672fed7e4b | ||
|
|
5a3f3e5ca0 | ||
|
|
6b236bfa0f | ||
|
|
0c1fc478da | ||
|
|
01eb194a08 | ||
|
|
2c7d9d48dc | ||
|
|
1d03d73642 | ||
|
|
b61329f86d | ||
|
|
062eefcf7a | ||
|
|
8b2a3c26fa | ||
|
|
b7a0dc2225 | ||
|
|
6dc9efd13d | ||
|
|
1eb54f8252 | ||
|
|
eca30e5b20 | ||
|
|
0b75486210 | ||
|
|
5adc45d591 | ||
|
|
4cdcdc075c | ||
|
|
932963beb5 | ||
|
|
136e82102b | ||
|
|
b84d00bfb1 | ||
|
|
0ea4d8897e | ||
|
|
8a3bdd181d | ||
|
|
a81dc83133 | ||
|
|
cc24604aa4 | ||
|
|
ea8cfbdadb | ||
|
|
d1ed7e1d0c | ||
|
|
f9169fcbcb | ||
|
|
17b21b2382 | ||
|
|
170527cfdf | ||
|
|
7b570bfbed | ||
|
|
bc72288d4d | ||
|
|
e50b4bb5f7 | ||
|
|
32f1bb4f87 | ||
|
|
594ad74006 | ||
|
|
3b9664f220 | ||
|
|
9897385837 | ||
|
|
4ec3bc5892 | ||
|
|
358971164e | ||
|
|
282611ec9c | ||
|
|
4a6539197f | ||
|
|
e92fd011ca | ||
|
|
9e0fe69d2b | ||
|
|
0ce3d5033c | ||
|
|
ff110de97f | ||
|
|
d54c339994 | ||
|
|
3dc73ab92e | ||
|
|
42570416da | ||
|
|
e934e49137 | ||
|
|
cec1e4cff0 | ||
|
|
31762239dd | ||
|
|
017198754f |
18
.ds.baseline
@@ -133,15 +133,7 @@
|
||||
"filename": ".github/workflows/checks.yml",
|
||||
"hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8",
|
||||
"is_verified": false,
|
||||
"line_number": 65,
|
||||
"is_secret": false
|
||||
},
|
||||
{
|
||||
"type": "Basic Auth Credentials",
|
||||
"filename": ".github/workflows/checks.yml",
|
||||
"hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8",
|
||||
"is_verified": false,
|
||||
"line_number": 99,
|
||||
"line_number": 68,
|
||||
"is_secret": false
|
||||
}
|
||||
],
|
||||
@@ -169,7 +161,7 @@
|
||||
"filename": "app/config.py",
|
||||
"hashed_secret": "577a4c667e4af8682ca431857214b3a920883efc",
|
||||
"is_verified": false,
|
||||
"line_number": 117,
|
||||
"line_number": 118,
|
||||
"is_secret": false
|
||||
}
|
||||
],
|
||||
@@ -601,7 +593,7 @@
|
||||
"filename": "tests/app/main/views/test_user_profile.py",
|
||||
"hashed_secret": "8072d7aad32964ec43fbcb699c75dc38890792f7",
|
||||
"is_verified": false,
|
||||
"line_number": 350,
|
||||
"line_number": 336,
|
||||
"is_secret": false
|
||||
},
|
||||
{
|
||||
@@ -609,7 +601,7 @@
|
||||
"filename": "tests/app/main/views/test_user_profile.py",
|
||||
"hashed_secret": "4c9dbb972da179e4f66f023eaa5fb9451d835030",
|
||||
"is_verified": false,
|
||||
"line_number": 351,
|
||||
"line_number": 337,
|
||||
"is_secret": false
|
||||
}
|
||||
],
|
||||
@@ -692,5 +684,5 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"generated_at": "2024-08-15T16:29:15Z"
|
||||
"generated_at": "2024-10-07T20:44:40Z"
|
||||
}
|
||||
|
||||
16
.github/actions/deploy-proxy/action.yml
vendored
@@ -1,6 +1,9 @@
|
||||
name: Deploy egress proxy
|
||||
description: Set egress space security groups and deploy proxy
|
||||
inputs:
|
||||
cf_org:
|
||||
description: The org the target app exists in.
|
||||
required: true
|
||||
cf_space:
|
||||
description: The space the target app exists in.
|
||||
required: true
|
||||
@@ -16,6 +19,19 @@ inputs:
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Install cf-cli
|
||||
shell: bash
|
||||
run: |
|
||||
curl -A "cg-deploy-action" -v -L -o cf-cli_amd64.deb 'https://packages.cloudfoundry.org/stable?release=debian64&version=v8&source=github'
|
||||
sudo dpkg -i cf-cli_amd64.deb
|
||||
- name: Login to cf-cli
|
||||
shell: bash
|
||||
run: |
|
||||
cf api api.fr.cloud.gov
|
||||
cf auth
|
||||
- name: Target org and space
|
||||
shell: bash
|
||||
run: cf target -o ${{ inputs.cf_org }} -s ${{ inputs.cf_space }}
|
||||
- name: Set restricted space egress
|
||||
shell: bash
|
||||
run: ./terraform/set_space_egress.sh -t -s ${{ inputs.cf_space }}
|
||||
|
||||
8
.github/dependabot.yml
vendored
@@ -9,8 +9,16 @@ updates:
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
||||
assignees:
|
||||
- "A-Shumway42"
|
||||
reviewers:
|
||||
- "A-Shumway42"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
versioning-strategy: increase
|
||||
assignees:
|
||||
- "alexjanousekGSA"
|
||||
reviewers:
|
||||
- "alexjanousekGSA"
|
||||
|
||||
70
.github/workflows/checks.yml
vendored
@@ -53,11 +53,13 @@ jobs:
|
||||
|
||||
end-to-end-tests:
|
||||
if: ${{ github.actor != 'dependabot[bot]' }}
|
||||
|
||||
permissions:
|
||||
checks: write
|
||||
pull-requests: write
|
||||
contents: write
|
||||
runs-on: ubuntu-latest
|
||||
environment: staging
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
@@ -89,65 +91,33 @@ jobs:
|
||||
- uses: ./.github/actions/setup-project
|
||||
- uses: jwalton/gh-find-current-pr@v1
|
||||
id: findPr
|
||||
- name: Clone API
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: GSA/notifications-api
|
||||
path: "notifications-api"
|
||||
- name: Install API dependencies
|
||||
working-directory: "notifications-api"
|
||||
run: make bootstrap
|
||||
env:
|
||||
DATABASE_URL: postgresql://user:password@localhost:5432/test_notification_api
|
||||
SQLALCHEMY_DATABASE_TEST_URI: postgresql://user:password@localhost:5432/test_notification_api
|
||||
REDIS_URL: redis://localhost:6379
|
||||
NOTIFY_E2E_TEST_EMAIL: ${{ secrets.NOTIFY_E2E_TEST_EMAIL }}
|
||||
NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }}
|
||||
NOTIFY_ENVIRONMENT: development
|
||||
- name: Run API server
|
||||
working-directory: "notifications-api"
|
||||
run: make run-procfile &
|
||||
env:
|
||||
DATABASE_URL: postgresql://user:password@localhost:5432/test_notification_api
|
||||
SQLALCHEMY_DATABASE_TEST_URI: postgresql://user:password@localhost:5432/test_notification_api
|
||||
REDIS_URL: redis://localhost:6379
|
||||
NOTIFY_E2E_TEST_EMAIL: ${{ secrets.NOTIFY_E2E_TEST_EMAIL }}
|
||||
NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }}
|
||||
NOTIFY_ENVIRONMENT: development
|
||||
- name: Check API Server availability
|
||||
run: |
|
||||
curl --fail -v https://notify-api-staging.app.cloud.gov || exit 1
|
||||
- name: Run Admin server
|
||||
run: make run-flask &
|
||||
# If we want to log stuff and see what's broken,
|
||||
# insert this line:
|
||||
# tail -f admin-server.log &
|
||||
# above make e2e-test
|
||||
|
||||
|
||||
run: |
|
||||
make run-flask > admin-server.log 2>&1 &
|
||||
tail -f admin-server.log &
|
||||
make e2e-test
|
||||
|
||||
env:
|
||||
# API_HOST_NAME: https://notify-api-staging.app.cloud.gov
|
||||
API_HOST_NAME: http://localhost:6011
|
||||
DANGEROUS_SALT: ${{ secrets.DANGEROUS_SALT }}
|
||||
API_HOST_NAME: https://notify-api-staging.app.cloud.gov/
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
DANGEROUS_SALT: ${{ secrets.DANGEROUS_SALT }}
|
||||
ADMIN_CLIENT_SECRET: ${{ secrets.ADMIN_CLIENT_SECRET }}
|
||||
ADMIN_CLIENT_USERNAME: notify-admin
|
||||
NOTIFY_ENVIRONMENT: e2etest
|
||||
NOTIFY_E2E_AUTH_STATE_PATH: ${{ secrets.NOTIFY_E2E_AUTH_STATE_PATH }}
|
||||
NOTIFY_E2E_TEST_EMAIL: ${{ secrets.NOTIFY_E2E_TEST_EMAIL }}
|
||||
NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }}
|
||||
NOTIFY_E2E_TEST_URI: http://localhost:6012
|
||||
- name: Run E2E tests
|
||||
# Run the E2E tests against the code found in this PR.
|
||||
# run: poetry run pytest -v --browser chromium --browser firefox --browser webkit tests/end_to_end
|
||||
# --browser webkit doesn't work at this time.
|
||||
run: make e2e-test
|
||||
# Debugging for now to troubleshoot a connectivity issue to the local servers
|
||||
# run: curl --request GET --url "http://localhost:6012"
|
||||
env:
|
||||
API_HOST_NAME: http://localhost:6011
|
||||
DANGEROUS_SALT: ${{ secrets.DANGEROUS_SALT }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
ADMIN_CLIENT_SECRET: ${{ secrets.ADMIN_CLIENT_SECRET }}
|
||||
ADMIN_CLIENT_USERNAME: notify-admin
|
||||
|
||||
NOTIFY_ENVIRONMENT: e2etest
|
||||
NOTIFY_E2E_AUTH_STATE_PATH: ${{ secrets.NOTIFY_E2E_AUTH_STATE_PATH }}
|
||||
NOTIFY_E2E_TEST_EMAIL: ${{ secrets.NOTIFY_E2E_TEST_EMAIL }}
|
||||
NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }}
|
||||
NOTIFY_E2E_TEST_URI: http://localhost:6012
|
||||
|
||||
NOTIFY_E2E_TEST_URI: http://localhost:6012/
|
||||
VCAP_SERVICES: ${{ secrets.VCAP_SERVICES }}
|
||||
validate-new-relic-config:
|
||||
runs-on: ubuntu-latest
|
||||
environment: staging
|
||||
|
||||
15
.github/workflows/deploy-demo.yml
vendored
@@ -47,11 +47,12 @@ jobs:
|
||||
run: poetry export --without-hashes --format=requirements.txt > requirements.txt
|
||||
|
||||
- name: Deploy to cloud.gov
|
||||
uses: 18f/cg-deploy-action@main
|
||||
uses: cloud-gov/cg-cli-tools@main
|
||||
env:
|
||||
DANGEROUS_SALT: ${{ secrets.DANGEROUS_SALT }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
ADMIN_CLIENT_SECRET: ${{ secrets.ADMIN_CLIENT_SECRET }}
|
||||
ADMIN_CLIENT_USERNAME: "notify-admin"
|
||||
NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }}
|
||||
NR_BROWSER_KEY: ${{ secrets.NR_BROWSER_KEY }}
|
||||
COMMIT_HASH: ${{ github.sha }}
|
||||
@@ -63,21 +64,22 @@ jobs:
|
||||
LOGIN_DOT_GOV_BASE_LOGOUT_URL: "https://secure.login.gov/openid_connect/logout?"
|
||||
LOGIN_DOT_GOV_SIGNOUT_REDIRECT: "https://notify-demo.app.cloud.gov/sign-out"
|
||||
LOGIN_DOT_GOV_INITIAL_SIGNIN_URL: "https://secure.login.gov/openid_connect/authorize?acr_values=http%3A%2F%2Fidmanagement.gov%2Fns%2Fassurance%2Fial%2F1&client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov&nonce=NONCE&prompt=select_account&redirect_uri=https://notify-demo.app.cloud.gov/sign-in&response_type=code&scope=openid+email&state=STATE"
|
||||
LOGIN_DOT_GOV_CERTS_URL: "https://secure.login.gov/api/openid_connect/certs"
|
||||
with:
|
||||
cf_username: ${{ secrets.CLOUDGOV_USERNAME }}
|
||||
cf_password: ${{ secrets.CLOUDGOV_PASSWORD }}
|
||||
cf_org: gsa-tts-benefits-studio
|
||||
cf_space: notify-demo
|
||||
push_arguments: >-
|
||||
push -f manifest.yml
|
||||
--vars-file deploy-config/demo.yml
|
||||
--var DANGEROUS_SALT="$DANGEROUS_SALT"
|
||||
--var SECRET_KEY="$SECRET_KEY"
|
||||
--var ADMIN_CLIENT_USERNAME="notify-admin"
|
||||
--var ADMIN_CLIENT_SECRET="$ADMIN_CLIENT_SECRET"
|
||||
--var ADMIN_CLIENT_USERNAME="$ADMIN_CLIENT_USERNAME"
|
||||
--var NEW_RELIC_LICENSE_KEY="$NEW_RELIC_LICENSE_KEY"
|
||||
--var NR_BROWSER_KEY="$NR_BROWSER_KEY"
|
||||
--var COMMIT_HASH="$COMMIT_HASH"
|
||||
--var LOGIN_PEM="$LOGIN_PEM"
|
||||
--var LOGIN_DOT_GOV_CLIENT_ID="$LOGIN_DOT_GOV_CLIENT_ID"
|
||||
--var LOGIN_DOT_GOV_USER_INFO_URL="$LOGIN_DOT_GOV_USER_INFO_URL"
|
||||
--var LOGIN_DOT_GOV_ACCESS_TOKEN_URL="$LOGIN_DOT_GOV_ACCESS_TOKEN_URL"
|
||||
@@ -85,6 +87,9 @@ jobs:
|
||||
--var LOGIN_DOT_GOV_BASE_LOGOUT_URL="$LOGIN_DOT_GOV_BASE_LOGOUT_URL"
|
||||
--var LOGIN_DOT_GOV_SIGNOUT_REDIRECT="$LOGIN_DOT_GOV_SIGNOUT_REDIRECT"
|
||||
--var LOGIN_DOT_GOV_INITIAL_SIGNIN_URL="$LOGIN_DOT_GOV_INITIAL_SIGNIN_URL"
|
||||
--var LOGIN_DOT_GOV_CERTS_URL="$LOGIN_DOT_GOV_CERTS_URL"
|
||||
--var LOGIN_PEM="$LOGIN_PEM"
|
||||
--strategy rolling
|
||||
|
||||
- name: Check for changes to egress config
|
||||
id: changed-egress-config
|
||||
@@ -97,6 +102,10 @@ jobs:
|
||||
- name: Deploy egress proxy
|
||||
if: steps.changed-egress-config.outputs.any_changed == 'true'
|
||||
uses: ./.github/actions/deploy-proxy
|
||||
env:
|
||||
CF_USERNAME: ${{ secrets.CLOUDGOV_USERNAME }}
|
||||
CF_PASSWORD: ${{ secrets.CLOUDGOV_PASSWORD }}
|
||||
with:
|
||||
cf_org: gsa-tts-benefits-studio
|
||||
cf_space: notify-demo
|
||||
app: notify-admin-demo
|
||||
|
||||
19
.github/workflows/deploy-prod.yml
vendored
@@ -47,11 +47,12 @@ jobs:
|
||||
run: poetry export --without-hashes --format=requirements.txt > requirements.txt
|
||||
|
||||
- name: Deploy to cloud.gov
|
||||
uses: 18f/cg-deploy-action@main
|
||||
uses: cloud-gov/cg-cli-tools@main
|
||||
env:
|
||||
DANGEROUS_SALT: ${{ secrets.DANGEROUS_SALT }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
ADMIN_CLIENT_SECRET: ${{ secrets.ADMIN_CLIENT_SECRET }}
|
||||
ADMIN_CLIENT_USERNAME: "notify-admin"
|
||||
NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }}
|
||||
NR_BROWSER_KEY: ${{ secrets.NR_BROWSER_KEY }}
|
||||
COMMIT_HASH: ${{ github.sha }}
|
||||
@@ -63,21 +64,22 @@ jobs:
|
||||
LOGIN_DOT_GOV_BASE_LOGOUT_URL: "https://secure.login.gov/openid_connect/logout?"
|
||||
LOGIN_DOT_GOV_SIGNOUT_REDIRECT: "https://beta.notify.gov/sign-out"
|
||||
LOGIN_DOT_GOV_INITIAL_SIGNIN_URL: "https://secure.login.gov/openid_connect/authorize?acr_values=http%3A%2F%2Fidmanagement.gov%2Fns%2Fassurance%2Fial%2F1&client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov&nonce=NONCE&prompt=select_account&redirect_uri=https://beta.notify.gov/sign-in&response_type=code&scope=openid+email&state=STATE"
|
||||
LOGIN_DOT_GOV_CERTS_URL: "https://secure.login.gov/api/openid_connect/certs"
|
||||
with:
|
||||
cf_username: ${{ secrets.CLOUDGOV_USERNAME }}
|
||||
cf_password: ${{ secrets.CLOUDGOV_PASSWORD }}
|
||||
cf_org: gsa-tts-benefits-studio
|
||||
cf_space: notify-production
|
||||
push_arguments: >-
|
||||
push -f manifest.yml
|
||||
--vars-file deploy-config/production.yml
|
||||
--var DANGEROUS_SALT="$DANGEROUS_SALT"
|
||||
--var SECRET_KEY="$SECRET_KEY"
|
||||
--var ADMIN_CLIENT_USERNAME="notify-admin"
|
||||
--var ADMIN_CLIENT_SECRET="$ADMIN_CLIENT_SECRET"
|
||||
--var ADMIN_CLIENT_USERNAME="$ADMIN_CLIENT_USERNAME"
|
||||
--var NEW_RELIC_LICENSE_KEY="$NEW_RELIC_LICENSE_KEY"
|
||||
--var NR_BROWSER_KEY="$NR_BROWSER_KEY"
|
||||
--var COMMIT_HASH="$COMMIT_HASH"
|
||||
--var LOGIN_PEM="$LOGIN_PEM"
|
||||
--var LOGIN_DOT_GOV_CLIENT_ID="$LOGIN_DOT_GOV_CLIENT_ID"
|
||||
--var LOGIN_DOT_GOV_USER_INFO_URL="$LOGIN_DOT_GOV_USER_INFO_URL"
|
||||
--var LOGIN_DOT_GOV_ACCESS_TOKEN_URL="$LOGIN_DOT_GOV_ACCESS_TOKEN_URL"
|
||||
@@ -85,6 +87,9 @@ jobs:
|
||||
--var LOGIN_DOT_GOV_BASE_LOGOUT_URL="$LOGIN_DOT_GOV_BASE_LOGOUT_URL"
|
||||
--var LOGIN_DOT_GOV_SIGNOUT_REDIRECT="$LOGIN_DOT_GOV_SIGNOUT_REDIRECT"
|
||||
--var LOGIN_DOT_GOV_INITIAL_SIGNIN_URL="$LOGIN_DOT_GOV_INITIAL_SIGNIN_URL"
|
||||
--var LOGIN_DOT_GOV_CERTS_URL="$LOGIN_DOT_GOV_CERTS_URL"
|
||||
--var LOGIN_PEM="$LOGIN_PEM"
|
||||
--strategy rolling
|
||||
|
||||
- name: Check for changes to egress config
|
||||
id: changed-egress-config
|
||||
@@ -97,6 +102,10 @@ jobs:
|
||||
- name: Deploy egress proxy
|
||||
if: steps.changed-egress-config.outputs.any_changed == 'true'
|
||||
uses: ./.github/actions/deploy-proxy
|
||||
env:
|
||||
CF_USERNAME: ${{ secrets.CLOUDGOV_USERNAME }}
|
||||
CF_PASSWORD: ${{ secrets.CLOUDGOV_PASSWORD }}
|
||||
with:
|
||||
cf_space: notify-production
|
||||
app: notify-admin-production
|
||||
cf_org: gsa-tts-benefits-studio
|
||||
cf_space: notify-prod
|
||||
app: notify-admin-prod
|
||||
|
||||
17
.github/workflows/deploy.yml
vendored
@@ -53,11 +53,12 @@ jobs:
|
||||
|
||||
|
||||
- name: Deploy to cloud.gov
|
||||
uses: 18f/cg-deploy-action@main
|
||||
uses: cloud-gov/cg-cli-tools@main
|
||||
env:
|
||||
DANGEROUS_SALT: ${{ secrets.DANGEROUS_SALT }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
ADMIN_CLIENT_SECRET: ${{ secrets.ADMIN_CLIENT_SECRET }}
|
||||
ADMIN_CLIENT_USERNAME: "notify-admin"
|
||||
NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }}
|
||||
NR_BROWSER_KEY: ${{ secrets.NR_BROWSER_KEY }}
|
||||
COMMIT_HASH: ${{ github.sha }}
|
||||
@@ -69,21 +70,22 @@ jobs:
|
||||
LOGIN_DOT_GOV_BASE_LOGOUT_URL: "https://secure.login.gov/openid_connect/logout?"
|
||||
LOGIN_DOT_GOV_SIGNOUT_REDIRECT: "https://notify-staging.app.cloud.gov/sign-out"
|
||||
LOGIN_DOT_GOV_INITIAL_SIGNIN_URL: "https://secure.login.gov/openid_connect/authorize?acr_values=http%3A%2F%2Fidmanagement.gov%2Fns%2Fassurance%2Fial%2F1&client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov&nonce=NONCE&prompt=select_account&redirect_uri=https://notify-staging.app.cloud.gov/sign-in&response_type=code&scope=openid+email&state=STATEE"
|
||||
LOGIN_DOT_GOV_CERTS_URL: "https://secure.login.gov/api/openid_connect/certs"
|
||||
with:
|
||||
cf_username: ${{ secrets.CLOUDGOV_USERNAME }}
|
||||
cf_password: ${{ secrets.CLOUDGOV_PASSWORD }}
|
||||
cf_org: gsa-tts-benefits-studio
|
||||
cf_space: notify-staging
|
||||
push_arguments: >-
|
||||
cf_command: >-
|
||||
push -f manifest.yml
|
||||
--vars-file deploy-config/staging.yml
|
||||
--var DANGEROUS_SALT="$DANGEROUS_SALT"
|
||||
--var SECRET_KEY="$SECRET_KEY"
|
||||
--var ADMIN_CLIENT_USERNAME="notify-admin"
|
||||
--var ADMIN_CLIENT_SECRET="$ADMIN_CLIENT_SECRET"
|
||||
--var ADMIN_CLIENT_USERNAME="$ADMIN_CLIENT_USERNAME"
|
||||
--var NEW_RELIC_LICENSE_KEY="$NEW_RELIC_LICENSE_KEY"
|
||||
--var NR_BROWSER_KEY="$NR_BROWSER_KEY"
|
||||
--var COMMIT_HASH="$COMMIT_HASH"
|
||||
--var LOGIN_PEM="$LOGIN_PEM"
|
||||
--var LOGIN_DOT_GOV_CLIENT_ID="$LOGIN_DOT_GOV_CLIENT_ID"
|
||||
--var LOGIN_DOT_GOV_USER_INFO_URL="$LOGIN_DOT_GOV_USER_INFO_URL"
|
||||
--var LOGIN_DOT_GOV_ACCESS_TOKEN_URL="$LOGIN_DOT_GOV_ACCESS_TOKEN_URL"
|
||||
@@ -91,6 +93,9 @@ jobs:
|
||||
--var LOGIN_DOT_GOV_BASE_LOGOUT_URL="$LOGIN_DOT_GOV_BASE_LOGOUT_URL"
|
||||
--var LOGIN_DOT_GOV_SIGNOUT_REDIRECT="$LOGIN_DOT_GOV_SIGNOUT_REDIRECT"
|
||||
--var LOGIN_DOT_GOV_INITIAL_SIGNIN_URL="$LOGIN_DOT_GOV_INITIAL_SIGNIN_URL"
|
||||
--var LOGIN_DOT_GOV_CERTS_URL="$LOGIN_DOT_GOV_CERTS_URL"
|
||||
--var LOGIN_PEM="$LOGIN_PEM"
|
||||
--strategy rolling
|
||||
|
||||
|
||||
- name: Check for changes to egress config
|
||||
@@ -104,7 +109,11 @@ jobs:
|
||||
- name: Deploy egress proxy
|
||||
if: steps.changed-egress-config.outputs.any_changed == 'true'
|
||||
uses: ./.github/actions/deploy-proxy
|
||||
env:
|
||||
CF_USERNAME: ${{ secrets.CLOUDGOV_USERNAME }}
|
||||
CF_PASSWORD: ${{ secrets.CLOUDGOV_PASSWORD }}
|
||||
with:
|
||||
cf_org: gsa-tts-benefits-studio
|
||||
cf_space: notify-staging
|
||||
app: notify-admin-staging
|
||||
|
||||
|
||||
5
.gitignore
vendored
@@ -134,3 +134,8 @@ playwright/
|
||||
|
||||
# Nodenv
|
||||
.node-version
|
||||
|
||||
# BackstopJS
|
||||
backstop_data/bitmaps_test/
|
||||
backstop_data/html_report/
|
||||
backstop_data/engine_scripts/playwright/
|
||||
|
||||
3
.prettierrc
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"singleQuote": true
|
||||
}
|
||||
2
Makefile
@@ -88,7 +88,7 @@ dead-code: ## 60% is our aspirational goal, but currently breaks the build
|
||||
.PHONY: e2e-test
|
||||
e2e-test: export NEW_RELIC_ENVIRONMENT=test
|
||||
e2e-test: ## Run end-to-end integration tests; note that --browser webkit isn't currently working
|
||||
poetry run pytest -vv --browser chromium --browser firefox tests/end_to_end
|
||||
DEBUG=pw:api,pw:browser poetry run pytest -vv --browser chromium --browser firefox tests/end_to_end
|
||||
|
||||
.PHONY: js-lint
|
||||
js-lint: ## Run javascript linting scanners
|
||||
|
||||
10
README.md
@@ -507,3 +507,13 @@ insurance.
|
||||
For more information on what we're working on, the Notify tool, and how to get
|
||||
involved with our team,
|
||||
[see our flyer.](https://github.com/GSA/notifications-admin/blob/main/docs/notify-pilot-flyer.md)
|
||||
|
||||
## Updating secrets for the E2E tests
|
||||
|
||||
At some point, E2E tests will fail because the secrets held in VCAP_SERVICES have expired. To refresh
|
||||
them, you will need to do the following:
|
||||
|
||||
1. Log in the normal way to access cloudfoundry command line options
|
||||
2. In your terminal, run `chmod +x print_vcap.sh`
|
||||
3. In your terminal, run `./print_vcap.sh`
|
||||
4. Copy the value in your terminal and paste it into the VCAP_SERVICES secret in Github on the staging tier.
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
href="/static/images/apple-touch-icon.png?a0f7e1b728a42016b247dc54ee40d055">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/static/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/static/images/favicon-16x16.png">
|
||||
<link rel="manifest" href="/static/images/site.webmanifest">
|
||||
<link rel="manifest" href="{{ asset_url('images/site.webmanifest') }}">
|
||||
|
||||
<link rel="stylesheet" media="screen" href="/static/stylesheets/main.css?d077f86473501ab244de0b30600536ee" />
|
||||
<link rel="stylesheet" media="print" href="/static/stylesheets/print.css?28010888ca5719dc83d7c2c80f6ed2b2" />
|
||||
@@ -68,11 +68,8 @@
|
||||
<p class="usa-body">
|
||||
Try again later.
|
||||
</p>
|
||||
<p class="usa-body">
|
||||
You can check our <a class="usa-link"
|
||||
href="https://status.notifications.service.gov.uk/">system status</a> page to see if there are any known
|
||||
issues.<br />To report a problem, email <a
|
||||
href="mailto:gov-uk-notify-support@digital.cabinet-office.gov.uk">gov-uk-notify-support@digital.cabinet-office.gov.uk</a>
|
||||
<p>To report a problem you can email us at <a class="usa-link" href="mailto:notify-support@gsa.gov">notify-support@gsa.gov</a>.</p>
|
||||
<p>You can expect a response within one business day.</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
.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.scaleLinear()
|
||||
const y = d3.scaleSqrt()
|
||||
.domain([0, maxY + 2]) // Add 2 units of space at the top
|
||||
.nice()
|
||||
.range([height, 0]);
|
||||
@@ -201,6 +201,8 @@
|
||||
deliveredData = [];
|
||||
failedData = [];
|
||||
|
||||
let totalMessages = 0;
|
||||
|
||||
for (var dateString in data) {
|
||||
if (data.hasOwnProperty(dateString)) {
|
||||
const dateParts = dateString.split('-');
|
||||
@@ -209,11 +211,38 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
createChart('#weeklyChart', labels, deliveredData, failedData);
|
||||
createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData);
|
||||
// Check if there are no messages sent
|
||||
const subTitle = document.querySelector(`#activityChartContainer .chart-subtitle`);
|
||||
if (totalMessages === 0) {
|
||||
// Remove existing chart and render the alert message
|
||||
d3.select('#weeklyChart').selectAll('*').remove();
|
||||
d3.select('#weeklyChart')
|
||||
.append('div')
|
||||
.html(`
|
||||
<div class="usa-alert usa-alert--info usa-alert--slim">
|
||||
<div class="usa-alert__body">
|
||||
<p class="usa-alert__text">
|
||||
No messages sent in the last 7 days
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
// Hide the subtitle
|
||||
if (subTitle) {
|
||||
subTitle.style.display = 'none';
|
||||
}
|
||||
} 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));
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
document.getElementById('message').innerText = `${sms_sent.toLocaleString()} sent / ${sms_remaining_messages.toLocaleString()} remaining`;
|
||||
|
||||
// Calculate minimum width for "Messages Sent" as 1% of the total chart width
|
||||
var minSentPercentage = 0.02; // Minimum width as a percentage of total messages (1% in this case)
|
||||
var minSentPercentage = (sms_sent === 0) ? 0 : 0.02;
|
||||
var minSentValue = totalMessages * minSentPercentage;
|
||||
var displaySent = Math.max(sms_sent, minSentValue);
|
||||
var displayRemaining = totalMessages - displaySent;
|
||||
|
||||
4
app/assets/js/setTimezone.js
Normal file
@@ -0,0 +1,4 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
document.cookie = `timezone=${timeZone}; path=/`;
|
||||
})
|
||||
@@ -9,6 +9,9 @@ $failed: color('gray-cool-20');
|
||||
&.usage {
|
||||
height: units(4);
|
||||
}
|
||||
svg {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
#totalMessageChartContainer {
|
||||
|
||||
@@ -34,6 +34,11 @@ $path: '/static/images/';
|
||||
// Dependencies from GOV.UK Frontend, packaged to be specific to this application
|
||||
@import './govuk-frontend/all';
|
||||
|
||||
// Custom overrides
|
||||
.govuk-link {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
// Specific to this application
|
||||
@import 'local/typography';
|
||||
@import 'grids';
|
||||
|
||||
@@ -8,6 +8,7 @@ from notifications_utils import DAILY_MESSAGE_LIMIT
|
||||
|
||||
|
||||
class Config(object):
|
||||
SIMULATED_SMS_NUMBERS = ("+14254147755", "+14254147167")
|
||||
NOTIFY_APP_NAME = "admin"
|
||||
NOTIFY_ENVIRONMENT = getenv("NOTIFY_ENVIRONMENT", "development")
|
||||
API_HOST_NAME = getenv("API_HOST_NAME", "localhost")
|
||||
|
||||
@@ -14,7 +14,7 @@ Learn how to [personalize messages](/using-notify/guidance) to increase response
|
||||
Learn about message _parts_ and [how limits are calculated](/using-notify/pricing).
|
||||
|
||||
5. ## Start sending messages
|
||||
To remove the restrictions of Trial Mode and begin sending messages to clients complete the [Live Campaign Form](https://airtable.com/appe4n7jYOALPLcyU/shrIPWnLTw9U1fclL).
|
||||
To remove the restrictions of Trial Mode and begin sending messages to clients complete the <a class="usa-link usa-link--external" href="https://docs.google.com/forms/d/1fnaBtxuGf3q-OdGVyt2LqBKvp9_P21kmKJa0yIK8rWM/edit">Go-Live Form</a>.
|
||||
We’ll respond within one business day.
|
||||
|
||||
### Questions?
|
||||
|
||||
@@ -16,6 +16,7 @@ from app import (
|
||||
)
|
||||
from app.formatters import format_date_numeric, format_datetime_numeric, get_time_left
|
||||
from app.main import main
|
||||
from app.main.views.user_profile import set_timezone
|
||||
from app.statistics_utils import get_formatted_percentage
|
||||
from app.utils import (
|
||||
DELIVERED_STATUSES,
|
||||
@@ -39,6 +40,7 @@ def old_service_dashboard(service_id):
|
||||
@main.route("/services/<uuid:service_id>")
|
||||
@user_has_permissions()
|
||||
def service_dashboard(service_id):
|
||||
|
||||
if session.get("invited_user_id"):
|
||||
session.pop("invited_user_id", None)
|
||||
session["service_id"] = service_id
|
||||
@@ -323,13 +325,19 @@ def aggregate_template_usage(template_statistics, sort_key="count"):
|
||||
key=lambda x: x["template_id"],
|
||||
):
|
||||
template_stats = list(v)
|
||||
|
||||
first_stat = template_stats[0] if template_stats else None
|
||||
templates.append(
|
||||
{
|
||||
"template_id": k,
|
||||
"template_name": template_stats[0]["template_name"],
|
||||
"template_type": template_stats[0]["template_type"],
|
||||
"template_name": first_stat.get("template_name"),
|
||||
"template_type": first_stat.get("template_type"),
|
||||
"count": sum(s["count"] for s in template_stats),
|
||||
"created_by": first_stat.get("created_by"),
|
||||
"created_by_id": first_stat.get("created_by_id"),
|
||||
"last_used": first_stat.get("last_used"),
|
||||
"status": first_stat.get("status"),
|
||||
"template_folder": first_stat.get("template_folder"),
|
||||
"template_folder_id": first_stat.get("template_folder_id"),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -402,11 +410,13 @@ def get_dashboard_partials(service_id):
|
||||
|
||||
|
||||
def get_dashboard_totals(statistics):
|
||||
|
||||
for msg_type in statistics.values():
|
||||
msg_type["failed_percentage"] = get_formatted_percentage(
|
||||
msg_type["failed"], msg_type["requested"]
|
||||
)
|
||||
msg_type["show_warning"] = float(msg_type["failed_percentage"]) > 3
|
||||
|
||||
return statistics
|
||||
|
||||
|
||||
@@ -465,6 +475,8 @@ def get_months_for_financial_year(year, time_format="%B"):
|
||||
|
||||
|
||||
def get_current_month_for_financial_year(year):
|
||||
# Setting the timezone here because we need to set it somewhere.
|
||||
set_timezone()
|
||||
current_month = datetime.now().month
|
||||
return current_month
|
||||
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import os
|
||||
import secrets
|
||||
|
||||
from flask import abort, current_app, redirect, render_template, request, url_for
|
||||
from flask import (
|
||||
abort,
|
||||
current_app,
|
||||
redirect,
|
||||
render_template,
|
||||
request,
|
||||
session,
|
||||
url_for,
|
||||
)
|
||||
from flask_login import current_user
|
||||
|
||||
from app import status_api_client
|
||||
@@ -23,8 +32,12 @@ def index():
|
||||
)
|
||||
url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL")
|
||||
# handle unit tests
|
||||
|
||||
nonce = secrets.token_urlsafe()
|
||||
session["nonce"] = nonce
|
||||
|
||||
if url is not None:
|
||||
url = url.replace("NONCE", token)
|
||||
url = url.replace("NONCE", nonce)
|
||||
url = url.replace("STATE", token)
|
||||
return render_template(
|
||||
"views/signedout.html",
|
||||
|
||||
@@ -5,7 +5,16 @@ from collections import OrderedDict
|
||||
from datetime import datetime
|
||||
from io import StringIO
|
||||
|
||||
from flask import Response, abort, flash, render_template, request, url_for
|
||||
from flask import (
|
||||
Response,
|
||||
abort,
|
||||
current_app,
|
||||
flash,
|
||||
render_template,
|
||||
request,
|
||||
session,
|
||||
url_for,
|
||||
)
|
||||
from notifications_python_client.errors import HTTPError
|
||||
|
||||
from app import (
|
||||
@@ -25,6 +34,7 @@ from app.main.forms import (
|
||||
DateFilterForm,
|
||||
RequiredDateFilterForm,
|
||||
)
|
||||
from app.main.views.send import _send_notification
|
||||
from app.statistics_utils import (
|
||||
get_formatted_percentage,
|
||||
get_formatted_percentage_two_dp,
|
||||
@@ -771,3 +781,80 @@ def _get_user_row(r):
|
||||
row.append(r["password_changed_at"])
|
||||
row.append(r["state"])
|
||||
return row
|
||||
|
||||
|
||||
@main.route(
|
||||
"/platform-admin/load-test",
|
||||
methods=["POST", "GET"],
|
||||
)
|
||||
@user_is_platform_admin
|
||||
def load_test():
|
||||
"""
|
||||
The load test assumes that a service called 'Test service' exists. It will make
|
||||
the platform admin a member of this service if the platform is not already. All
|
||||
messagese will be sent in this service.
|
||||
"""
|
||||
service = _find_load_test_service()
|
||||
_prepare_load_test_service(service)
|
||||
example_template = _find_example_template(service)
|
||||
|
||||
# Simulated success
|
||||
for _ in range(0, 250):
|
||||
session["recipient"] = current_app.config["SIMULATED_SMS_NUMBERS"][0]
|
||||
session["placeholders"] = {
|
||||
"day of week": "Monday",
|
||||
"color": "blue",
|
||||
"phone number": current_app.config["SIMULATED_SMS_NUMBERS"][0],
|
||||
}
|
||||
_send_notification(service["id"], example_template["id"])
|
||||
# Simulated failure
|
||||
for _ in range(0, 250):
|
||||
session["recipient"] = current_app.config["SIMULATED_SMS_NUMBERS"][1]
|
||||
session["placeholders"] = {
|
||||
"day of week": "Wednesday",
|
||||
"color": "orange",
|
||||
"phone number": current_app.config["SIMULATED_SMS_NUMBERS"][1],
|
||||
}
|
||||
_send_notification(service["id"], example_template["id"])
|
||||
|
||||
# For now, just redirect to the splash page so we know it's done
|
||||
return render_template(
|
||||
"views/platform-admin/splash-page.html",
|
||||
)
|
||||
|
||||
|
||||
def _find_example_template(service):
|
||||
templates = service_api_client.get_service_templates(service["id"])
|
||||
templates = templates["data"]
|
||||
for template in templates:
|
||||
# template = json.loads(template)
|
||||
if template["name"] == "Example text message template":
|
||||
return template
|
||||
|
||||
raise Exception("Could not find example template for load test")
|
||||
|
||||
|
||||
def _find_load_test_service():
|
||||
services = service_api_client.find_services_by_name("Test service")
|
||||
services = services["data"]
|
||||
|
||||
for service in services:
|
||||
if service["name"] == "Test service":
|
||||
return service
|
||||
|
||||
raise Exception("Could not find 'Test service' for load test")
|
||||
|
||||
|
||||
def _prepare_load_test_service(service):
|
||||
users = user_api_client.get_all_users()
|
||||
for user in users:
|
||||
if user["platform_admin"] == "t":
|
||||
try:
|
||||
user_api_client.add_user_to_service(
|
||||
service["id"], user["id"], ["send messages"]
|
||||
)
|
||||
except Exception:
|
||||
current_app.logger.exception(
|
||||
"Couldnt add user, may already be part of service"
|
||||
)
|
||||
pass
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import os
|
||||
import time
|
||||
import uuid
|
||||
from string import ascii_uppercase
|
||||
@@ -33,6 +34,7 @@ from app.main.forms import (
|
||||
SetSenderForm,
|
||||
get_placeholder_form_instance,
|
||||
)
|
||||
from app.main.views.user_profile import set_timezone
|
||||
from app.models.user import Users
|
||||
from app.s3_client.s3_csv_client import (
|
||||
get_csv_metadata,
|
||||
@@ -800,9 +802,15 @@ def get_skip_link(step_index, template):
|
||||
)
|
||||
@user_has_permissions("send_messages", restrict_admin_usage=True)
|
||||
def send_one_off_to_myself(service_id, template_id):
|
||||
db_template = current_service.get_template_with_user_permission_or_403(
|
||||
template_id, current_user
|
||||
)
|
||||
current_app.logger.info("Send one off to myself")
|
||||
try:
|
||||
db_template = current_service.get_template_with_user_permission_or_403(
|
||||
template_id, current_user
|
||||
)
|
||||
except Exception:
|
||||
current_app.logger.exception("Couldnt get template for one off")
|
||||
# Use 406 just because we're limited to certain codes here and it will point us back to a problem here
|
||||
abort(406)
|
||||
|
||||
if db_template["template_type"] not in ("sms", "email"):
|
||||
abort(404)
|
||||
@@ -941,8 +949,8 @@ def preview_notification(service_id, template_id):
|
||||
)
|
||||
@user_has_permissions("send_messages", restrict_admin_usage=True)
|
||||
def send_notification(service_id, template_id):
|
||||
scheduled_for = session.pop("scheduled_for", "")
|
||||
recipient = get_recipient()
|
||||
|
||||
if not recipient:
|
||||
return redirect(
|
||||
url_for(
|
||||
@@ -951,53 +959,10 @@ def send_notification(service_id, template_id):
|
||||
template_id=template_id,
|
||||
)
|
||||
)
|
||||
upload_id = _send_notification(service_id, template_id)
|
||||
|
||||
keys = []
|
||||
values = []
|
||||
for k, v in session["placeholders"].items():
|
||||
keys.append(k)
|
||||
values.append(v)
|
||||
|
||||
data = ",".join(keys)
|
||||
vals = ",".join(values)
|
||||
data = f"{data}\r\n{vals}"
|
||||
|
||||
filename = (
|
||||
f"one-off-{uuid.uuid4()}.csv" # {current_user.name} removed from filename
|
||||
)
|
||||
my_data = {"filename": filename, "template_id": template_id, "data": data}
|
||||
upload_id = s3upload(service_id, my_data)
|
||||
|
||||
# To debug messages that the user reports have not been sent, we log
|
||||
# the csv filename and the job id. The user will give us the file name,
|
||||
# so we can search on that to obtain the job id, which we can use elsewhere
|
||||
# on the API side to find out what happens to the message.
|
||||
current_app.logger.info(
|
||||
hilite(
|
||||
f"One-off file: {filename} job_id: {upload_id} s3 location: service-{service_id}-notify/{upload_id}.csv"
|
||||
)
|
||||
)
|
||||
|
||||
form = CsvUploadForm()
|
||||
form.file.data = my_data
|
||||
form.file.name = filename
|
||||
|
||||
check_message_output = check_messages(service_id, template_id, upload_id, 2)
|
||||
if "You cannot send to" in check_message_output:
|
||||
return check_messages(service_id, template_id, upload_id, 2)
|
||||
|
||||
job_api_client.create_job(
|
||||
upload_id,
|
||||
service_id,
|
||||
scheduled_for=scheduled_for,
|
||||
template_id=template_id,
|
||||
original_file_name=filename,
|
||||
notification_count=1,
|
||||
valid="True",
|
||||
)
|
||||
|
||||
session.pop("recipient")
|
||||
session.pop("placeholders")
|
||||
session.pop("recipient", "")
|
||||
session.pop("placeholders", "")
|
||||
|
||||
# We have to wait for the job to run and create the notification in the database
|
||||
time.sleep(0.1)
|
||||
@@ -1045,6 +1010,63 @@ def send_notification(service_id, template_id):
|
||||
)
|
||||
|
||||
|
||||
def _send_notification(service_id, template_id):
|
||||
scheduled_for = session.pop("scheduled_for", "")
|
||||
|
||||
keys = []
|
||||
values = []
|
||||
# Guarantee that the real phone number comes last, because some
|
||||
# users will have placeholders like "add your second phone number"
|
||||
# or something like as custom placeholders.
|
||||
for k, v in session["placeholders"].items():
|
||||
if k != "phone number":
|
||||
keys.append(k)
|
||||
values.append(v)
|
||||
if "phone number" in session["placeholders"].keys():
|
||||
keys.append("phone number")
|
||||
values.append(session["placeholders"]["phone number"])
|
||||
|
||||
data = ",".join(keys)
|
||||
vals = ",".join(values)
|
||||
data = f"{data}\r\n{vals}"
|
||||
filename = (
|
||||
f"one-off-{uuid.uuid4()}.csv" # {current_user.name} removed from filename
|
||||
)
|
||||
my_data = {"filename": filename, "template_id": template_id, "data": data}
|
||||
upload_id = s3upload(service_id, my_data)
|
||||
# To debug messages that the user reports have not been sent, we log
|
||||
# the csv filename and the job id. The user will give us the file name,
|
||||
# so we can search on that to obtain the job id, which we can use elsewhere
|
||||
# on the API side to find out what happens to the message.
|
||||
current_app.logger.info(
|
||||
hilite(
|
||||
f"One-off file: {filename} job_id: {upload_id} s3 location: {service_id}-service-notify/{upload_id}.csv"
|
||||
)
|
||||
)
|
||||
|
||||
# For load testing we want to skip these checks. They are doing some fine-grained
|
||||
# comparison about what is in the preview, but the load test just blast messages
|
||||
# and doesn't care about the preview.
|
||||
if os.getenv("NOTIFY_ENVIRONMENT") not in ("development", "staging", "demo"):
|
||||
form = CsvUploadForm()
|
||||
form.file.data = my_data
|
||||
form.file.name = filename
|
||||
check_message_output = check_messages(service_id, template_id, upload_id, 2)
|
||||
if "You cannot send to" in check_message_output:
|
||||
return check_messages(service_id, template_id, upload_id, 2)
|
||||
|
||||
job_api_client.create_job(
|
||||
upload_id,
|
||||
service_id,
|
||||
scheduled_for=scheduled_for,
|
||||
template_id=template_id,
|
||||
original_file_name=filename,
|
||||
notification_count=1,
|
||||
valid="True",
|
||||
)
|
||||
return upload_id
|
||||
|
||||
|
||||
def get_email_reply_to_address_from_session():
|
||||
if session.get("sender_id"):
|
||||
return current_service.get_email_reply_to_address(session["sender_id"])[
|
||||
@@ -1075,6 +1097,7 @@ def get_spreadsheet_column_headings_from_template(template):
|
||||
|
||||
|
||||
def get_recipient():
|
||||
set_timezone()
|
||||
if {"recipient", "placeholders"} - set(session.keys()):
|
||||
return None
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import json
|
||||
import os
|
||||
import secrets
|
||||
import time
|
||||
import uuid
|
||||
|
||||
@@ -12,6 +14,7 @@ from flask import (
|
||||
redirect,
|
||||
render_template,
|
||||
request,
|
||||
session,
|
||||
url_for,
|
||||
)
|
||||
from flask_login import current_user
|
||||
@@ -28,7 +31,7 @@ from app.utils.user import is_gov_user
|
||||
from notifications_utils.url_safe_token import generate_token
|
||||
|
||||
|
||||
def _reformat_keystring(orig):
|
||||
def _reformat_keystring(orig): # pragma: no cover
|
||||
arr = orig.split("-----")
|
||||
begin = arr[1]
|
||||
end = arr[3]
|
||||
@@ -37,9 +40,10 @@ def _reformat_keystring(orig):
|
||||
return new_keystring
|
||||
|
||||
|
||||
def _get_access_token(code, state):
|
||||
def _get_access_token(code, state): # pragma: no cover
|
||||
client_id = os.getenv("LOGIN_DOT_GOV_CLIENT_ID")
|
||||
access_token_url = os.getenv("LOGIN_DOT_GOV_ACCESS_TOKEN_URL")
|
||||
certs_url = os.getenv("LOGIN_DOT_GOV_CERTS_URL")
|
||||
keystring = os.getenv("LOGIN_PEM")
|
||||
if " " in keystring:
|
||||
keystring = _reformat_keystring(keystring)
|
||||
@@ -60,17 +64,47 @@ def _get_access_token(code, state):
|
||||
url = f"{base_url}{cli_assert}&{cli_assert_type}&{code_param}&grant_type=authorization_code"
|
||||
headers = {"Authorization": "Bearer %s" % token}
|
||||
response = requests.post(url, headers=headers)
|
||||
if response.json().get("access_token") is None:
|
||||
# Capture the response json here so it hopefully shows up in error reports
|
||||
current_app.logger.error(
|
||||
|
||||
response_json = response.json()
|
||||
try:
|
||||
encoded_id_token = response_json["id_token"]
|
||||
except KeyError as e:
|
||||
current_app.logger.exception(f"Error when getting id token {response_json}")
|
||||
raise KeyError(f"'access_token' {response.json()}") from e
|
||||
|
||||
# Getting Login.gov signing keys for unpacking the id_token correctly.
|
||||
jwks = requests.get(certs_url).json()
|
||||
public_keys = {
|
||||
jwk["kid"]: {
|
||||
"key": jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(jwk)),
|
||||
"algo": jwk["alg"],
|
||||
}
|
||||
for jwk in jwks["keys"]
|
||||
}
|
||||
kid = jwt.get_unverified_header(encoded_id_token)["kid"]
|
||||
pub_key = public_keys[kid]["key"]
|
||||
algo = public_keys[kid]["algo"]
|
||||
id_token = jwt.decode(
|
||||
encoded_id_token, pub_key, audience=client_id, algorithms=[algo]
|
||||
)
|
||||
|
||||
nonce = id_token["nonce"]
|
||||
saved_nonce = session.pop("nonce")
|
||||
if nonce != saved_nonce:
|
||||
current_app.logger.error(f"Nonce Error: {nonce} != {saved_nonce}")
|
||||
abort(403)
|
||||
|
||||
try:
|
||||
access_token = response_json["access_token"]
|
||||
except KeyError as e:
|
||||
current_app.logger.exception(
|
||||
f"Error when getting access token {response.json()} #notify-admin-1505"
|
||||
)
|
||||
raise KeyError(f"'access_token' {response.json()}")
|
||||
access_token = response.json()["access_token"]
|
||||
raise KeyError(f"'access_token' {response.json()}") from e
|
||||
return access_token
|
||||
|
||||
|
||||
def _get_user_email_and_uuid(access_token):
|
||||
def _get_user_email_and_uuid(access_token): # pragma: no cover
|
||||
headers = {"Authorization": "Bearer %s" % access_token}
|
||||
user_info_url = os.getenv("LOGIN_DOT_GOV_USER_INFO_URL")
|
||||
user_attributes = requests.get(
|
||||
@@ -82,7 +116,7 @@ def _get_user_email_and_uuid(access_token):
|
||||
return user_email, user_uuid
|
||||
|
||||
|
||||
def _do_login_dot_gov():
|
||||
def _do_login_dot_gov(): # $ pragma: no cover
|
||||
# start login.gov
|
||||
code = request.args.get("code")
|
||||
state = request.args.get("state")
|
||||
@@ -124,12 +158,13 @@ def _do_login_dot_gov():
|
||||
except BaseException as be: # noqa B036
|
||||
current_app.logger.error(f"Error signing in: {be} #notify-admin-1505 ")
|
||||
error(401)
|
||||
|
||||
return redirect(url_for("main.show_accounts_or_dashboard", next=redirect_url))
|
||||
|
||||
# end login.gov
|
||||
|
||||
|
||||
def verify_email(user, redirect_url):
|
||||
def verify_email(user, redirect_url): # pragma: no cover
|
||||
user_api_client.send_verify_code(user["id"], "email", None, redirect_url)
|
||||
title = "Email resent" if request.args.get("email_resent") else "Check your email"
|
||||
redirect_url = request.args.get("next")
|
||||
@@ -138,19 +173,41 @@ def verify_email(user, redirect_url):
|
||||
)
|
||||
|
||||
|
||||
def _handle_e2e_tests(redirect_url):
|
||||
current_app.logger.warning("E2E TESTS ARE ENABLED.")
|
||||
current_app.logger.warning(
|
||||
"If you are getting a 404 on signin, comment out E2E vars in .env file!"
|
||||
)
|
||||
user = user_api_client.get_user_by_email(os.getenv("NOTIFY_E2E_TEST_EMAIL"))
|
||||
activate_user(user["id"])
|
||||
return redirect(url_for("main.show_accounts_or_dashboard", next=redirect_url))
|
||||
def _handle_e2e_tests(redirect_url): # pragma: no cover
|
||||
try:
|
||||
current_app.logger.warning("E2E TESTS ARE ENABLED.")
|
||||
current_app.logger.warning(
|
||||
"If you are getting a 404 on signin, comment out E2E vars in .env file!"
|
||||
)
|
||||
user = user_api_client.get_user_by_email(os.getenv("NOTIFY_E2E_TEST_EMAIL"))
|
||||
activate_user(user["id"])
|
||||
|
||||
# Check if the redirect URL is present and safe before proceeding further
|
||||
if redirect_url and is_safe_redirect_url(redirect_url):
|
||||
return redirect(redirect_url)
|
||||
|
||||
return redirect(
|
||||
url_for(
|
||||
"main.show_accounts_or_dashboard",
|
||||
next="EMAIL_IS_OK",
|
||||
)
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
stre = str(e)
|
||||
stre = stre.replace(" ", "_")
|
||||
# Trying to get a message back to playwright somehow since we can't raise an error
|
||||
return redirect(url_for(f"https://{stre}"))
|
||||
|
||||
|
||||
@main.route("/sign-in", methods=(["GET", "POST"]))
|
||||
@hide_from_search_engines
|
||||
def sign_in():
|
||||
def sign_in(): # pragma: no cover
|
||||
redirect_url = request.args.get("next")
|
||||
|
||||
if os.getenv("NOTIFY_E2E_TEST_EMAIL"):
|
||||
return _handle_e2e_tests(redirect_url)
|
||||
|
||||
# If we have to revalidated the email, send the message
|
||||
# via email and redirect to the "verify your email page"
|
||||
# and don't proceed further with login
|
||||
@@ -162,11 +219,6 @@ def sign_in():
|
||||
):
|
||||
return email_verify_template
|
||||
|
||||
redirect_url = request.args.get("next")
|
||||
|
||||
if os.getenv("NOTIFY_E2E_TEST_EMAIL"):
|
||||
return _handle_e2e_tests(redirect_url)
|
||||
|
||||
if current_user and current_user.is_authenticated:
|
||||
if redirect_url and is_safe_redirect_url(redirect_url):
|
||||
return redirect(redirect_url)
|
||||
@@ -178,10 +230,15 @@ def sign_in():
|
||||
current_app.config["DANGEROUS_SALT"],
|
||||
)
|
||||
url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL")
|
||||
|
||||
nonce = secrets.token_urlsafe()
|
||||
session["nonce"] = nonce
|
||||
|
||||
# handle unit tests
|
||||
if url is not None:
|
||||
url = url.replace("NONCE", token)
|
||||
url = url.replace("NONCE", nonce)
|
||||
url = url.replace("STATE", token)
|
||||
|
||||
return render_template(
|
||||
"views/signin.html",
|
||||
again=bool(redirect_url),
|
||||
@@ -190,5 +247,5 @@ def sign_in():
|
||||
|
||||
|
||||
@login_manager.unauthorized_handler
|
||||
def sign_in_again():
|
||||
def sign_in_again(): # pragma: no cover
|
||||
return redirect(url_for("main.sign_in", next=request.path))
|
||||
|
||||
@@ -29,6 +29,7 @@ from app.main.forms import (
|
||||
TwoFactorForm,
|
||||
)
|
||||
from app.models.user import User
|
||||
from app.utils import hilite
|
||||
from app.utils.user import user_is_gov_user, user_is_logged_in
|
||||
from notifications_utils.url_safe_token import check_token
|
||||
|
||||
@@ -189,32 +190,19 @@ def user_profile_mobile_number_delete():
|
||||
@main.route("/user-profile/mobile-number/authenticate", methods=["GET", "POST"])
|
||||
@user_is_logged_in
|
||||
def user_profile_mobile_number_authenticate():
|
||||
# Validate password for form
|
||||
def _check_password(pwd):
|
||||
return user_api_client.verify_password(current_user.id, pwd)
|
||||
|
||||
form = ConfirmPasswordForm(_check_password)
|
||||
|
||||
if NEW_MOBILE not in session:
|
||||
return redirect(url_for(".user_profile_mobile_number"))
|
||||
|
||||
if form.validate_on_submit():
|
||||
session[NEW_MOBILE_PASSWORD_CONFIRMED] = True
|
||||
current_user.send_verify_code(to=session[NEW_MOBILE])
|
||||
create_mobile_number_change_event(
|
||||
user_id=current_user.id,
|
||||
updated_by_id=current_user.id,
|
||||
original_mobile_number=current_user.mobile_number,
|
||||
new_mobile_number=session[NEW_MOBILE],
|
||||
)
|
||||
return redirect(url_for(".user_profile_mobile_number_confirm"))
|
||||
|
||||
return render_template(
|
||||
"views/user-profile/authenticate.html",
|
||||
thing="mobile number",
|
||||
form=form,
|
||||
back_link=url_for(".user_profile_mobile_number_confirm"),
|
||||
session[NEW_MOBILE_PASSWORD_CONFIRMED] = True
|
||||
current_user.send_verify_code(to=session[NEW_MOBILE])
|
||||
create_mobile_number_change_event(
|
||||
user_id=current_user.id,
|
||||
updated_by_id=current_user.id,
|
||||
original_mobile_number=current_user.mobile_number,
|
||||
new_mobile_number=session[NEW_MOBILE],
|
||||
)
|
||||
return redirect(url_for(".user_profile_mobile_number_confirm"))
|
||||
|
||||
|
||||
@main.route("/user-profile/mobile-number/confirm", methods=["GET", "POST"])
|
||||
@@ -288,3 +276,15 @@ def user_profile_disable_platform_admin_view():
|
||||
return render_template(
|
||||
"views/user-profile/disable-platform-admin-view.html", form=form
|
||||
)
|
||||
|
||||
|
||||
def set_timezone():
|
||||
# Cookie is set in dashboard.html on page load
|
||||
try:
|
||||
timezone = request.cookies.get("timezone", "US/Eastern")
|
||||
current_app.logger.debug(hilite(f"User's timezone is {timezone}"))
|
||||
serialized_user = current_user.serialize()
|
||||
if serialized_user["preferred_timezone"] is not timezone:
|
||||
current_user.update(preferred_timezone=timezone)
|
||||
except Exception:
|
||||
current_app.logger.exception(hilite("Can't get timezone"))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
from flask import abort, current_app, request, session
|
||||
@@ -58,6 +59,7 @@ class User(JSONModel, UserMixin):
|
||||
super().__init__(_dict)
|
||||
self.permissions = _dict.get("permissions", {})
|
||||
self._platform_admin = _dict["platform_admin"]
|
||||
self.preferred_timezone = _dict.get("preferred_timezone", "America/New_York")
|
||||
|
||||
@classmethod
|
||||
def from_id(cls, user_id):
|
||||
@@ -199,7 +201,7 @@ class User(JSONModel, UserMixin):
|
||||
@property
|
||||
def is_gov_user(self):
|
||||
is_gov = is_gov_user(self.email_address)
|
||||
current_app.logger.info(f"User {self.id} is_gov_user: {is_gov}")
|
||||
# current_app.logger.info(f"User {self.id} is_gov_user: {is_gov}")
|
||||
return is_gov
|
||||
|
||||
@property
|
||||
@@ -208,9 +210,9 @@ class User(JSONModel, UserMixin):
|
||||
|
||||
@property
|
||||
def platform_admin(self):
|
||||
current_app.logger.warn(
|
||||
f"Checking User {self.id} for platform admin: {self._platform_admin}"
|
||||
)
|
||||
# current_app.logger.warning(
|
||||
# f"Checking User {self.id} for platform admin: {self._platform_admin}"
|
||||
# )
|
||||
return self._platform_admin and not session.get(
|
||||
"disable_platform_admin_view", False
|
||||
)
|
||||
@@ -218,6 +220,14 @@ class User(JSONModel, UserMixin):
|
||||
def has_permissions(
|
||||
self, *permissions, restrict_admin_usage=False, allow_org_user=False
|
||||
):
|
||||
# TODO need this for load test, but breaks unit tests
|
||||
if self.platform_admin and os.getenv("NOTIFY_ENVIRONMENT") in (
|
||||
"development",
|
||||
"staging",
|
||||
"demo",
|
||||
):
|
||||
return True
|
||||
|
||||
unknown_permissions = set(permissions) - all_ui_permissions
|
||||
if unknown_permissions:
|
||||
raise TypeError(
|
||||
@@ -232,26 +242,31 @@ class User(JSONModel, UserMixin):
|
||||
# we shouldn't have any pages that require permissions, but don't specify a service or organization.
|
||||
# use @user_is_platform_admin for platform admin only pages
|
||||
# raise NotImplementedError
|
||||
current_app.logger.warn(f"VIEW ARGS ARE {request.view_args}")
|
||||
# current_app.logger.warning(f"VIEW ARGS ARE {request.view_args}")
|
||||
pass
|
||||
|
||||
log_msg = f"has_permissions user: {self.id} service: {service_id}"
|
||||
# platform admins should be able to do most things (except eg send messages, or create api keys)
|
||||
if self.platform_admin and not restrict_admin_usage:
|
||||
current_app.logger.warn(f"{log_msg} true because user is platform_admin")
|
||||
current_app.logger.debug(
|
||||
"has_permissions is true because user is platform_admin"
|
||||
)
|
||||
return True
|
||||
|
||||
if org_id:
|
||||
value = self.belongs_to_organization(org_id)
|
||||
current_app.logger.warn(f"{log_msg} org: {org_id} returning {value}")
|
||||
current_app.logger.debug(
|
||||
f"has_permissions returns org: {org_id} returning {value}"
|
||||
)
|
||||
return value
|
||||
|
||||
if not permissions and self.belongs_to_service(service_id):
|
||||
current_app.logger.warn(f"{log_msg} True because belongs_to_service")
|
||||
current_app.logger.debug("has_permissions True because belongs_to_service")
|
||||
return True
|
||||
|
||||
if any(self.permissions_for_service(service_id) & set(permissions)):
|
||||
current_app.logger.warn(f"{log_msg} permissions valid")
|
||||
current_app.logger.debug(
|
||||
"has_permissions returns True because permissions valid"
|
||||
)
|
||||
return True
|
||||
|
||||
from app.models.service import Service
|
||||
@@ -259,7 +274,7 @@ class User(JSONModel, UserMixin):
|
||||
org_value = allow_org_user and self.belongs_to_organization(
|
||||
Service.from_id(service_id).organization_id
|
||||
)
|
||||
current_app.logger.warn(f"{log_msg} returning {org_value}")
|
||||
current_app.logger.debug(f"has_permissions returning {org_value}")
|
||||
return org_value
|
||||
|
||||
def permissions_for_service(self, service_id):
|
||||
@@ -267,7 +282,7 @@ class User(JSONModel, UserMixin):
|
||||
|
||||
def has_permission_for_service(self, service_id, permission):
|
||||
has_permission = permission in self.permissions_for_service(service_id)
|
||||
current_app.logger.warn(
|
||||
current_app.logger.debug(
|
||||
f"has_permission_for_service user: {self.id} service: {service_id} "
|
||||
f"permission: {permission} retuning {has_permission}"
|
||||
)
|
||||
@@ -548,17 +563,17 @@ class InvitedUser(JSONModel):
|
||||
return cls.by_id(invited_user_id) if invited_user_id else None
|
||||
|
||||
def has_permissions(self, *permissions):
|
||||
current_app.logger.warn(
|
||||
f"Checking invited user {self.id} for permissions: {permissions}"
|
||||
)
|
||||
# current_app.logger.warning(
|
||||
# f"Checking invited user {self.id} for permissions: {permissions}"
|
||||
# )
|
||||
if self.status == "cancelled":
|
||||
return False
|
||||
return set(self.permissions) > set(permissions)
|
||||
|
||||
def has_permission_for_service(self, service_id, permission):
|
||||
current_app.logger.warn(
|
||||
f"Checking invited user {self.id} for permission: {permission} on service {service_id}"
|
||||
)
|
||||
# current_app.logger.warn(
|
||||
# f"Checking invited user {self.id} for permission: {permission} on service {service_id}"
|
||||
# )
|
||||
if self.status == "cancelled":
|
||||
return False
|
||||
return self.service == service_id and permission in self.permissions
|
||||
|
||||
@@ -2,6 +2,7 @@ from flask import current_app
|
||||
from notifications_python_client.errors import HTTPError
|
||||
|
||||
from app.notify_client import NotifyAdminAPIClient, cache
|
||||
from app.utils import hilite
|
||||
from app.utils.user_permissions import translate_permissions_from_ui_to_db
|
||||
|
||||
ALLOWED_ATTRIBUTES = {
|
||||
@@ -109,13 +110,14 @@ class UserApiClient(NotifyAdminAPIClient):
|
||||
raise
|
||||
|
||||
def send_verify_code(self, user_id, code_type, to, next_string=None):
|
||||
|
||||
data = {"to": to}
|
||||
if next_string:
|
||||
data["next"] = next_string
|
||||
if code_type == "email":
|
||||
data["email_auth_link_host"] = self.admin_url
|
||||
endpoint = "/user/{0}/{1}-code".format(user_id, code_type)
|
||||
current_app.logger.warn(f"Sending verify_code {code_type} to {user_id}")
|
||||
current_app.logger.warn(hilite(f"Sending verify_code {code_type} to {user_id}"))
|
||||
self.post(endpoint, data=data)
|
||||
|
||||
def send_verify_email(self, user_id, to):
|
||||
|
||||
@@ -29,7 +29,10 @@ def get_s3_object(
|
||||
aws_secret_access_key=secret_key,
|
||||
region_name=region,
|
||||
)
|
||||
s3 = session.resource("s3", config=AWS_CLIENT_CONFIG)
|
||||
s3 = session.resource(
|
||||
"s3",
|
||||
config=AWS_CLIENT_CONFIG,
|
||||
)
|
||||
obj = s3.Object(bucket_name, filename)
|
||||
# This 'proves' that use of moto in the relevant tests in test_send.py
|
||||
# mocks everything related to S3. What you will see in the logs is:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import os
|
||||
import uuid
|
||||
|
||||
from flask import current_app
|
||||
@@ -10,13 +11,13 @@ from app.s3_client import (
|
||||
)
|
||||
from notifications_utils.s3 import s3upload as utils_s3upload
|
||||
|
||||
FILE_LOCATION_STRUCTURE = "service-{}-notify/{}.csv"
|
||||
NEW_FILE_LOCATION_STRUCTURE = "{}-service-notify/{}.csv"
|
||||
|
||||
|
||||
def get_csv_location(service_id, upload_id):
|
||||
return (
|
||||
current_app.config["CSV_UPLOAD_BUCKET"]["bucket"],
|
||||
FILE_LOCATION_STRUCTURE.format(service_id, upload_id),
|
||||
NEW_FILE_LOCATION_STRUCTURE.format(service_id, upload_id),
|
||||
current_app.config["CSV_UPLOAD_BUCKET"]["access_key_id"],
|
||||
current_app.config["CSV_UPLOAD_BUCKET"]["secret_access_key"],
|
||||
current_app.config["CSV_UPLOAD_BUCKET"]["region"],
|
||||
@@ -33,6 +34,14 @@ def s3upload(service_id, filedata):
|
||||
bucket_name, file_location, access_key, secret_key, region = get_csv_location(
|
||||
service_id, upload_id
|
||||
)
|
||||
if bucket_name == "":
|
||||
exp_bucket = current_app.config["CSV_UPLOAD_BUCKET"]["bucket"]
|
||||
exp_region = current_app.config["CSV_UPLOAD_BUCKET"]["region"]
|
||||
tier = os.getenv("NOTIFY_ENVIRONMENT")
|
||||
raise Exception(
|
||||
f"NO BUCKET NAME SHOULD BE: {exp_bucket} WITH REGION {exp_region} TIER {tier}"
|
||||
)
|
||||
|
||||
utils_s3upload(
|
||||
filedata=filedata["data"],
|
||||
region=region,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
{% block per_page_title %}{% endblock %} – Notify.gov
|
||||
{% endblock %}
|
||||
</title>
|
||||
{% include "new/components/head.html" %}
|
||||
{% include "components/head.html" %}
|
||||
</head>
|
||||
<body class="usa-template__body {{ bodyClasses }}">
|
||||
<script nonce="{{ csp_nonce() }}">document.body.className = ((document.body.className) ? document.body.className + ' js-enabled' : 'js-enabled');</script>
|
||||
@@ -32,8 +32,8 @@
|
||||
|
||||
|
||||
{% block header %}
|
||||
{% include 'new/components/usa_banner.html' %}
|
||||
{% include 'new/components/header.html' %}
|
||||
{% include 'components/usa_banner.html' %}
|
||||
{% include 'components/header.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
@@ -47,7 +47,7 @@
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
{% block flash_messages %}
|
||||
{% include 'new/components/flash_messages.html' %}
|
||||
{% include 'components/flash_messages.html' %}
|
||||
{% endblock %}
|
||||
{% block maincolumn_content %}
|
||||
<div class="grid-row">
|
||||
@@ -78,7 +78,7 @@
|
||||
{% set meta_suffix = "Built by the " + long_link + commit_hash %}
|
||||
{% endif %}
|
||||
|
||||
{% include "new/components/footer.html" %}
|
||||
{% include "components/footer.html" %}
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
{% block sessionUserWarning %}
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
<a href="{%- if params.href %}{{ params.href }}{% else %}#{% endif -%}" class="usa-link usa-back-link display-inline-flex margin-bottom-3 {%- if params.classes %} {{ params.classes }}{% endif -%}"
|
||||
{%- for attribute, value in params.attributes %} {{attribute}}="{{value}}"{% endfor %}>{{ (params.html | safe if params.html else (params.text if params.text else 'Back')) }}</a>
|
||||
<nav class="usa-breadcrumb" aria-label="Breadcrumb">
|
||||
<a href="{{ params.href or '#' }}" class="usa-link usa-back-link display-inline-flex {{ params.classes or '' }}"
|
||||
{%- for attribute, value in params.attributes %} {{ attribute }}="{{ value }}" {% endfor %}>{{ params.html | safe or params.text or 'Back' }}</a>
|
||||
</nav>
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# Footer
|
||||
|
||||
## Installation
|
||||
|
||||
See the [main README quick start guide](https://github.com/alphagov/govuk-frontend#quick-start) for how to install this component.
|
||||
|
||||
## Guidance and Examples
|
||||
|
||||
Find out when to use the footer component in your service in the [GOV.UK Design System](https://design-system.service.gov.uk/components/footer).
|
||||
|
||||
## Component options
|
||||
|
||||
Use options to customize the appearance, content and behavior of a component when using a macro, for example, changing the text.
|
||||
|
||||
See [options table](https://design-system.service.gov.uk/components/footer/#options-example-default) for details.
|
||||
@@ -1,115 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "meta",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"description": "Object containing options for the meta navigation.",
|
||||
"params": [
|
||||
{
|
||||
"name": "visuallyHiddenTitle",
|
||||
"type": "string",
|
||||
"description": "Title for a meta item section, which defaults to Support links"
|
||||
},
|
||||
{
|
||||
"name": "html",
|
||||
"type": "string",
|
||||
"description": "HTML to add to the meta section of the footer, which will appear below any links specified using meta.items."
|
||||
},
|
||||
{
|
||||
"name": "text",
|
||||
"type": "string",
|
||||
"description": "Text to add to the meta section of the footer, which will appear below any links specified using meta.items. If meta.html is specified, this option is ignored."
|
||||
},
|
||||
{
|
||||
"name": "items",
|
||||
"type": "array",
|
||||
"required": false,
|
||||
"description": "Array of items for use in the meta section of the footer.",
|
||||
"params": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "List item text in the meta section of the footer."
|
||||
},
|
||||
{
|
||||
"name": "href",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "List item href attribute in the meta section of the footer."
|
||||
},
|
||||
{
|
||||
"name": "attributes",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"description": "HTML attributes (for example data attributes) to add to the anchor in the footer meta section."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "navigation",
|
||||
"type": "array",
|
||||
"required": false,
|
||||
"description": "Array of items for use in the navigation section of the footer.",
|
||||
"params": [
|
||||
{
|
||||
"name": "title",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Title for a section"
|
||||
},
|
||||
{
|
||||
"name": "columns",
|
||||
"type": "integer",
|
||||
"required": false,
|
||||
"description": "Amount of columns to display items in navigation section of the footer."
|
||||
},
|
||||
{
|
||||
"name": "items",
|
||||
"type": "array",
|
||||
"required": false,
|
||||
"description": "Array of items to display in the list in navigation section of the footer.",
|
||||
"params": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "List item text in the navigation section of the footer."
|
||||
},
|
||||
{
|
||||
"name": "href",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "List item href attribute in the navigation section of the footer. Both `text` and `href` attributes need to be present to create a link."
|
||||
},
|
||||
{
|
||||
"name": "attributes",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"description": "HTML attributes (for example data attributes) to add to the anchor in the footer navigation section."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "containerClasses",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Classes that can be added to the inner container, useful if you want to make the footer full width."
|
||||
},
|
||||
{
|
||||
"name": "classes",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Classes to add to the footer component container."
|
||||
},
|
||||
{
|
||||
"name": "attributes",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"description": "HTML attributes (for example data attributes) to add to the footer component container."
|
||||
}
|
||||
]
|
||||
@@ -1,3 +0,0 @@
|
||||
{% macro usaFooter(params) %}
|
||||
{%- include "./template.njk" -%}
|
||||
{% endmacro %}
|
||||
@@ -1,85 +0,0 @@
|
||||
<footer class="usa-footer usa-footer--big">
|
||||
{# <div class="grid-container usa-footer__return-to-top">
|
||||
<a href="#">Return to top</a>
|
||||
</div> #}
|
||||
<div class="usa-footer__primary-section">
|
||||
<div class="grid-container">
|
||||
<div class="padding-y-1">
|
||||
{% if params.meta %}
|
||||
<h2 class="usa-sr-only">{{ params.meta.visuallyHiddenTitle | default("Support links") }}</h2>
|
||||
{% if params.meta.items %}
|
||||
{% for item in params.meta.items %}
|
||||
<a class="usa-footer__link" href="{{ item.href }}" {% for attribute, value in item.attributes %}
|
||||
{{attribute}}="{{value}}" {% endfor %}>
|
||||
{{ item.text }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if params.meta.text or params.meta.html %}
|
||||
<div>
|
||||
{{ params.meta.html | safe if params.meta.html else params.meta.text }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<div class="usa-identifier site-identifier">
|
||||
<section class="usa-identifier__section usa-identifier__section--masthead" aria-label="Agency identifier,,,,,,">
|
||||
<div class="usa-identifier__container">
|
||||
<div class="usa-identifier__logos"><a href="javascript:void(0);" class="usa-identifier__logo">
|
||||
<img class="usa-identifier__logo-img" src="{{ params.assetsPath | default('/static/images') }}/gsa-logo.svg" alt="GSA Logo" role="img" width="48"
|
||||
height="48">
|
||||
</a></div>
|
||||
<section class="usa-identifier__identity" aria-label="Agency description">
|
||||
<p class="usa-identifier__identity-domain">beta.notify.gov</p>
|
||||
<p class="usa-identifier__identity-disclaimer">An official website of the <a href="https://gsa.gov">General
|
||||
Services Administration</a></p>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
<nav class="usa-identifier__section usa-identifier__section--required-links" aria-label="Important links">
|
||||
<div class="usa-identifier__container">
|
||||
<ul class="usa-identifier__required-links-list">
|
||||
<li class="usa-identifier__required-links-item">
|
||||
<a href="https://www.gsa.gov/about-us" class="usa-identifier__required-link">About GSA</a>
|
||||
</li>
|
||||
<li class="usa-identifier__required-links-item">
|
||||
<a href="https://www.gsa.gov/website-information/accessibility-aids"
|
||||
class="usa-identifier__required-link">Accessibility support</a>
|
||||
</li>
|
||||
<li class="usa-identifier__required-links-item">
|
||||
<a href="https://www.gsa.gov/reference/freedom-of-information-act-foia"
|
||||
class="usa-identifier__required-link">FOIA requests</a>
|
||||
</li>
|
||||
<li class="usa-identifier__required-links-item">
|
||||
<a href="https://www.gsa.gov/reference/civil-rights-programs/notification-and-federal-employee-antidiscrimination-and-retaliation-act-of-2002"
|
||||
class="usa-identifier__required-link">No FEAR Act data</a>
|
||||
</li>
|
||||
<li class="usa-identifier__required-links-item">
|
||||
<a href="https://www.gsaig.gov/" class="usa-identifier__required-link">Office of the Inspector General</a>
|
||||
</li>
|
||||
<li class="usa-identifier__required-links-item">
|
||||
<a href="https://www.gsa.gov/reference/reports/budget-performance"
|
||||
class="usa-identifier__required-link">Performance reports</a>
|
||||
</li>
|
||||
<li class="usa-identifier__required-links-item">
|
||||
<a href="https://www.gsa.gov/website-information/website-policies"
|
||||
class="usa-identifier__required-link">Privacy policy</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="usa-identifier__section usa-identifier__section--usagov"
|
||||
aria-label="Government information and services">
|
||||
<div class="usa-identifier__container">
|
||||
<div class="usa-identifier__usagov-description">
|
||||
Looking for U.S. government information and services?
|
||||
</div>
|
||||
<a href="https://www.usa.gov/" class="usa-link">Visit USA.gov</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -1,15 +0,0 @@
|
||||
# Header
|
||||
|
||||
## Installation
|
||||
|
||||
See the [main README quick start guide](https://github.com/alphagov/govuk-frontend#quick-start) for how to install this component.
|
||||
|
||||
## Guidance and Examples
|
||||
|
||||
Find out when to use the header component in your service in the [GOV.UK Design System](https://design-system.service.gov.uk/components/header).
|
||||
|
||||
## Component options
|
||||
|
||||
Use options to customize the appearance, content and behavior of a component when using a macro, for example, changing the text.
|
||||
|
||||
See [options table](https://design-system.service.gov.uk/components/header/#options-example-default) for details.
|
||||
@@ -1,88 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "homepageUrl",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "The url of the homepage. Defaults to /"
|
||||
},
|
||||
{
|
||||
"name": "assetsPath",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "The public path for the assets folder. If not provided it defaults to /assets/images"
|
||||
},
|
||||
{
|
||||
"name": "productName",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Header title that is placed next to GOV.UK. Used for product names (i.e. Pay, Verify)"
|
||||
},
|
||||
{
|
||||
"name": "serviceName",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Header title that is placed next to GOV.UK. Used for product names (i.e. Pay, Verify)"
|
||||
},
|
||||
{
|
||||
"name": "serviceUrl",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Url for the service name anchor."
|
||||
},
|
||||
{
|
||||
"name": "navigation",
|
||||
"type": "array",
|
||||
"required": false,
|
||||
"description": "An array of navigation item objects.",
|
||||
"params": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Text of the navigation item."
|
||||
},
|
||||
{
|
||||
"name": "href",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Url of the navigation item anchor. Both `href` and `text` attributes for navigation items need to be provided to create an item."
|
||||
},
|
||||
{
|
||||
"name": "active",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"description": "Flag to mark the navigation item as active or not."
|
||||
},
|
||||
{
|
||||
"name": "attributes",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"description": "HTML attributes (for example data attributes) to add to the navigation item anchor."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "navigationClasses",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Classes for the navigation section of the header."
|
||||
},
|
||||
{
|
||||
"name": "containerClasses",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Classes for the container, useful if you want to make the header fixed width."
|
||||
},
|
||||
{
|
||||
"name": "classes",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"description": "Classes to add to the header container."
|
||||
},
|
||||
{
|
||||
"name": "attributes",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"description": "HTML attributes (for example data attributes) to add to the header container."
|
||||
}
|
||||
]
|
||||
@@ -1,3 +0,0 @@
|
||||
{% macro usaHeader(params) %}
|
||||
{%- include "./template.njk" -%}
|
||||
{% endmacro %}
|
||||
@@ -1,105 +0,0 @@
|
||||
<!-- usa banner -->
|
||||
<section class="usa-banner site-banner" aria-label="Official website of the United States government,,,,,,">
|
||||
<div class="usa-accordion">
|
||||
<header class="usa-banner__header">
|
||||
<div class="usa-banner__inner">
|
||||
<div class="grid-col-auto">
|
||||
<img aria-hidden="true" class="usa-banner__header-flag" src="/static/img/us_flag_small.png" alt="" />
|
||||
</div>
|
||||
<div class="grid-col-fill tablet:grid-col-auto" aria-hidden="true">
|
||||
<p class="usa-banner__header-text">An official website of the United States government</p>
|
||||
<p class="usa-banner__header-action">Here’s how you know</p>
|
||||
</div>
|
||||
<button type="button" class="usa-accordion__button usa-banner__button" aria-expanded="false" aria-controls="gov-banner">
|
||||
<span class="usa-banner__button-text">Here’s how you know</span>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="usa-banner__content usa-accordion__content" id="gov-banner" hidden="">
|
||||
<div class="grid-row grid-gap-lg">
|
||||
<div class="usa-banner__guidance tablet:grid-col-6">
|
||||
<img class="usa-banner__icon usa-media-block__img" src="/static/img/icon-dot-gov.svg" role="img" alt="" aria-hidden="true">
|
||||
<div class="usa-media-block__body">
|
||||
<p>
|
||||
<strong>Official websites use .gov</strong>
|
||||
<br>
|
||||
A <strong>.gov</strong> website belongs to an official government organization in the United States.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="usa-banner__guidance tablet:grid-col-6">
|
||||
<img class="usa-banner__icon usa-media-block__img" src="/static/img/icon-https.svg" role="img" alt="" aria-hidden="true">
|
||||
<div class="usa-media-block__body">
|
||||
<p>
|
||||
<strong>Secure .gov websites use HTTPS</strong>
|
||||
<br>
|
||||
A <strong>lock</strong> ( <span class="icon-lock"><svg xmlns="http://www.w3.org/2000/svg" width="52" height="64" viewBox="0 0 52 64" class="usa-banner__lock-image" role="img" aria-labelledby="banner-lock-description" focusable="false"><title id="banner-lock-title">Lock</title><desc id="banner-lock-description">Locked padlock</desc><path fill="#000000" fill-rule="evenodd" d="M26 0c10.493 0 19 8.507 19 19v9h3a4 4 0 0 1 4 4v28a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V32a4 4 0 0 1 4-4h3v-9C7 8.507 15.507 0 26 0zm0 8c-5.979 0-10.843 4.77-10.996 10.712L15 19v9h22v-9c0-6.075-4.925-11-11-11z"></path></svg></span> ) or <strong>https://</strong> means you’ve safely connected to the .gov website. Share sensitive information only on official, secure websites.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- usa header -->
|
||||
<header class="usa-header usa-header--extended">
|
||||
<div class="usa-nav-container">
|
||||
<div class="usa-navbar">
|
||||
<div class="usa-logo display-flex flex-align-center flex-justify" id="-logo">
|
||||
<div class="logo-img display-flex">
|
||||
<a href="/">
|
||||
<span class="usa-sr-only">Notify.gov logo</span>
|
||||
<image src="{{ params.assetsPath | default('/static/images') }}/notify-logo.png" alt="Notify.gov logo" xlink:href=""
|
||||
class="usa-flag-logo margin-right-1"></image>
|
||||
</a>
|
||||
</div>
|
||||
{% if params.navigation %}
|
||||
<button type="button" class="usa-menu-btn">Menu</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<nav aria-label="Primary navigation" class="usa-nav">
|
||||
<div class="usa-nav__inner">
|
||||
<button type="button" class="usa-nav__close">
|
||||
<img src="/static/images/usa-icons/close.svg" role="img" alt="Close" />
|
||||
</button>
|
||||
<ul class="usa-nav__primary usa-accordion margin-right-1">
|
||||
{% for item in params.navigation %}
|
||||
{% if item.href and item.text %}
|
||||
<li class="usa-nav__primary-item{{ ' is-current' if item.active }}">
|
||||
<a class="usa-nav__link {{ ' usa-current' if item.active }}" href="{{ item.href }}" {% for attribute, value in
|
||||
item.attributes %} {{attribute}}="{{value}}" {% endfor %}>
|
||||
<span>{{ item.text }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div class="usa-nav__secondary margin-bottom-6">
|
||||
<ul class="usa-nav__secondary-links">
|
||||
{% for item in params.secondaryNavigation %}
|
||||
{% if item.href and item.text %}
|
||||
<li class="usa-nav__secondary-item{{ ' is-current' if item.active }}">
|
||||
<a class="usa-nav__link {{ ' usa-current' if item.active }}" href="{{ item.href }}" {% for attribute, value in
|
||||
item.attributes %} {{attribute}}="{{value}}" {% endfor %}>
|
||||
<span>{{ item.text }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<!-- <section aria-label="Search component">
|
||||
<form class="usa-search usa-search--small margin-bottom-2" role="search">
|
||||
<label class="usa-sr-only" for="search-field">Search</label>
|
||||
<input class="usa-input" id="search-field" type="search" name="search" />
|
||||
<button class="usa-button" type="submit">
|
||||
<img src="/static/images/usa-icons-bg/search--white.svg" class="usa-search__submit-icon" alt="Search" />
|
||||
</button>
|
||||
</form>
|
||||
</section> -->
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
@@ -1,7 +1,3 @@
|
||||
<a href="{{ params.href | default('#content') }}" class="usa-skipnav{%- if params.classes %} {{ params.classes }}{% endif -%}"{%- for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor %}>
|
||||
{{- params.html | safe if params.html else params.text -}}
|
||||
</a>
|
||||
|
||||
<a class="usa-skipnav " href="#main-content">
|
||||
Skip to main content
|
||||
</a>
|
||||
@@ -16,7 +16,7 @@
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ assetPath | default('/static') }}/images/favicon-16x16.png" />
|
||||
<link rel="mask-icon" href="{{ assetPath | default('/static') }}/images/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ assetPath | default('/static') }}/images/apple-touch-icon.png">
|
||||
<link rel="manifest" href="/site.webmanifest">
|
||||
<link rel="manifest" href="{{ asset_url('images/site.webmanifest') }}">
|
||||
<meta name="msapplication-TileColor" content="#da532c">
|
||||
<link href="{{ assetPath | default('/static') }}/images/notify-dark-favicon.png" rel="icon" media="(prefers-color-scheme: dark)">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
@@ -30,7 +30,7 @@
|
||||
<meta property="og:image" content="/static/images/notify-og-image.png">
|
||||
{# google #}
|
||||
<script type="text/javascript" src="{{ asset_url('js/gtm_head.js') }}"></script>
|
||||
<meta name="google-site-verification" content="niWnSqImOWz6mVQTYqNb5tFK8HaKSB4b3ED4Z9gtUQ0" />
|
||||
<meta name="google-site-verification" content="niWnSqImOWz6mVQTYqNb5tFK8HaKSB4b3ED4Z9gtUQ0" /> {# pragma: allowlist secret #}
|
||||
{% if g.hide_from_search_engines %}
|
||||
<meta name="robots" content="noindex" />
|
||||
{% endif %}
|
||||
@@ -1,4 +1,3 @@
|
||||
{# setting navigation and secondarynavigation #}
|
||||
{% if current_user.is_authenticated %}
|
||||
{% set navigation = [
|
||||
{"href": url_for("main.show_accounts_or_dashboard"), "text": "Current service", "active": header_navigation.is_selected('accounts-or-dashboard')},
|
||||
@@ -30,7 +29,6 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{# usa header #}
|
||||
<header class="usa-header usa-header--extended">
|
||||
<div class="usa-nav-container">
|
||||
<div class="usa-navbar">
|
||||
@@ -50,7 +48,7 @@
|
||||
<nav aria-label="Primary navigation" class="usa-nav">
|
||||
<div class="usa-nav__inner">
|
||||
<button type="button" class="usa-nav__close">
|
||||
<img src="/static/images/usa-icons/close.svg" role="img" alt="Close" />
|
||||
<img src="{{ asset_url('img/usa-icons/close.svg') }}" role="img" alt="Close" />
|
||||
</button>
|
||||
<ul class="usa-nav__primary usa-accordion margin-right-1">
|
||||
{% for item in navigation %}
|
||||
@@ -1,7 +1,7 @@
|
||||
{% if help %}
|
||||
{% include 'partials/tour.html' %}
|
||||
{% else %}
|
||||
<nav class="nav margin-bottom-4">
|
||||
<nav id="nav-main-nav" aria-label="Main navigation" class="nav margin-bottom-4">
|
||||
<a class="usa-button margin-top-1 margin-bottom-5 width-full"
|
||||
href="{{ url_for('.choose_template', service_id=current_service.id) }}">Send messages</a>
|
||||
<ul class="usa-sidenav">
|
||||
@@ -13,15 +13,6 @@
|
||||
{% if not current_user.has_permissions('view_activity') %}
|
||||
<li class="usa-sidenav__item"><a class="{{ casework_navigation.is_selected('sent-messages') }}" href="{{ url_for('.view_notifications', service_id=current_service.id, status='sending,delivered,failed') }}">Sent messages</a></li>
|
||||
{% endif %}
|
||||
{% if current_user.has_permissions('manage_service', allow_org_user=True) %}
|
||||
{# <li class="usa-sidenav__item"><a class="{{ main_navigation.is_selected('usage') }}" href="{{ url_for('.usage', service_id=current_service.id) }}">Usage</a></li> #}
|
||||
{% endif %}
|
||||
<!-- {% if current_user.has_permissions('manage_api_keys', 'manage_service') %}
|
||||
<li class="usa-sidenav__item"><a class="{{ main_navigation.is_selected('settings') }}" href="{{ url_for('.service_settings', service_id=current_service.id) }}">Settings</a></li>
|
||||
{% endif %} -->
|
||||
{% if current_user.has_permissions('manage_api_keys') %}
|
||||
<!-- <li><a class="usa-link{{ main_navigation.is_selected('api-integration') }}" href="{{ url_for('.api_integration', service_id=current_service.id) }}">API integration</a></li> -->
|
||||
{% endif %}
|
||||
{% elif current_user.has_permissions(allow_org_user=True) %}
|
||||
<li class="usa-sidenav__item"><a class="usa-link{{ main_navigation.is_selected('usage') }}" href="{{ url_for('.usage', service_id=current_service.id) }}">Usage</a></li>
|
||||
<li class="usa-sidenav__item"><a class="usa-link{{ main_navigation.is_selected('team-members') }}" href="{{ url_for('.manage_users', service_id=current_service.id) }}">Team members</a></li>
|
||||
@@ -1,4 +1,4 @@
|
||||
<nav class="nav margin-bottom-4">
|
||||
<nav id="nav-org-nav" aria-label="Organization navigation" class="nav margin-bottom-4">
|
||||
<ul class="usa-sidenav">
|
||||
<li class="usa-sidenav__item"><a class="usa-link{{ org_navigation.is_selected('dashboard') }}" href="{{ url_for('.organization_dashboard', org_id=current_org.id) }}">Usage</a></li>
|
||||
<li class="usa-sidenav__item"><a class="usa-link{{ org_navigation.is_selected('team-members') }}" href="{{ url_for('.manage_org_users', org_id=current_org.id) }}">Team members</a></li>
|
||||
@@ -1,3 +1,4 @@
|
||||
<nav id="nav-service-nav" aria-label="Service navigation">
|
||||
<div class="navigation-service margin-top-5 display-flex flex-align-end flex-justify border-bottom padding-bottom-1">
|
||||
{% if current_service.organization_id %}
|
||||
{% if current_user.platform_admin or
|
||||
@@ -13,3 +14,4 @@
|
||||
</div>
|
||||
<a href="{{ url_for('main.choose_account') }}" class="usa-link">Switch service</a>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -1,5 +1,3 @@
|
||||
|
||||
{# usa banner #}
|
||||
<section class="usa-banner site-banner" aria-label="Official website of the United States government,,,,,,">
|
||||
<div class="usa-accordion">
|
||||
<header class="usa-banner__header">
|
||||
@@ -1,302 +0,0 @@
|
||||
{% extends "main_template.html" %}
|
||||
{% from "components/banner.html" import banner %}
|
||||
|
||||
{% block headIcons %}
|
||||
<link rel="shortcut icon" sizes="16x16 32x32 48x48" href="{{ asset_url('images/favicon.ico') }}" type="image/x-icon" />
|
||||
<link rel="mask-icon" href="{{ asset_url('images/usa-mask-icon.svg') }}" color="{{ themeColor | default('#F0F0F0') }}">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ asset_url('images/apple-touch-icon.png') }}">
|
||||
<link rel="apple-touch-icon" href="{{ asset_url('images/apple-touch-icon.png') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<link rel="stylesheet" media="screen" href="{{ asset_url('css/styles.css') }}" />
|
||||
{% block extra_stylesheets %}
|
||||
{% endblock %}
|
||||
{% if g.hide_from_search_engines %}
|
||||
<meta name="robots" content="noindex" />
|
||||
{% endif %}
|
||||
<meta name="google-site-verification" content="niWnSqImOWz6mVQTYqNb5tFK8HaKSB4b3ED4Z9gtUQ0" />
|
||||
{% block meta_format_detection %}
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
{% endblock %}
|
||||
{% block meta %}
|
||||
<meta property="og:site_name" content="Notify.gov">
|
||||
<meta property="og:image" content="{{ asset_url('images/notify-og-image.png') }}">
|
||||
{% endblock %}
|
||||
<script type="text/javascript" src="{{ asset_url('js/gtm_head.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block pageTitle %}
|
||||
{% block per_page_title %}{% endblock %} – Notify.gov
|
||||
{% endblock %}
|
||||
|
||||
{% 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 %}
|
||||
|
||||
{% block header %}
|
||||
{% if current_user.is_authenticated %}
|
||||
{% if current_user.platform_admin %}
|
||||
{% set navigation = [
|
||||
{
|
||||
"href": url_for("main.show_accounts_or_dashboard"),
|
||||
"text": "Current service",
|
||||
"active": header_navigation.is_selected('accounts-or-dashboard')
|
||||
},
|
||||
{
|
||||
"href": url_for('main.get_started'),
|
||||
"text": "Using Notify",
|
||||
"active": header_navigation.is_selected('using_notify')
|
||||
},
|
||||
{
|
||||
"href": url_for('main.features'),
|
||||
"text": "Features",
|
||||
"active": header_navigation.is_selected('features')
|
||||
},
|
||||
{
|
||||
"href": url_for('main.platform_admin_splash_page'),
|
||||
"text": "Platform admin",
|
||||
"active": header_navigation.is_selected('platform-admin')
|
||||
},
|
||||
{
|
||||
"href": url_for('main.support'),
|
||||
"text": "Contact us",
|
||||
"active": header_navigation.is_selected('support')
|
||||
}
|
||||
] %}
|
||||
{% if current_service %}
|
||||
{% set secondaryNavigation = [
|
||||
{
|
||||
"href": url_for('main.service_settings', service_id=current_service.id),
|
||||
"text": "Settings",
|
||||
"active": secondary_navigation.is_selected('settings')
|
||||
},
|
||||
{
|
||||
"href": url_for('main.sign_out'),
|
||||
"text": "Sign out"
|
||||
}
|
||||
] %}
|
||||
{% else %}
|
||||
{% set secondaryNavigation = [
|
||||
{
|
||||
"href": url_for('main.sign_out'),
|
||||
"text": "Sign out"
|
||||
}
|
||||
] %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% set navigation = [
|
||||
{
|
||||
"href": url_for("main.show_accounts_or_dashboard"),
|
||||
"text": "Current service",
|
||||
"active": header_navigation.is_selected('accounts-or-dashboard')
|
||||
},
|
||||
{
|
||||
"href": url_for('main.get_started'),
|
||||
"text": "Using Notify",
|
||||
"active": header_navigation.is_selected('using_notify')
|
||||
},
|
||||
{
|
||||
"href": url_for('main.features'),
|
||||
"text": "Features",
|
||||
"active": header_navigation.is_selected('features')
|
||||
},
|
||||
{
|
||||
"href": url_for('main.support'),
|
||||
"text": "Contact us",
|
||||
"active": header_navigation.is_selected('support')
|
||||
},
|
||||
{
|
||||
"href": url_for('main.user_profile'),
|
||||
"text": "User profile",
|
||||
"active": header_navigation.is_selected('user-profile')
|
||||
}
|
||||
] %}
|
||||
{% if current_service %}
|
||||
{% set secondaryNavigation = [
|
||||
{
|
||||
"href": url_for('main.service_settings', service_id=current_service.id),
|
||||
"text": "Settings",
|
||||
"active": secondary_navigation.is_selected('settings')
|
||||
},
|
||||
{
|
||||
"href": url_for('main.sign_out'),
|
||||
"text": "Sign out"
|
||||
}
|
||||
] %}
|
||||
{% else %}
|
||||
{% set secondaryNavigation = [
|
||||
{
|
||||
"href": url_for('main.sign_out'),
|
||||
"text": "Sign out"
|
||||
}
|
||||
] %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<!-- Add navigation back after pilot -->
|
||||
{# {% set navigation = [
|
||||
{
|
||||
"href": url_for('main.get_started'),
|
||||
"text": "Using Notify",
|
||||
"active": header_navigation.is_selected('using_notify')
|
||||
},
|
||||
{
|
||||
"href": url_for('main.features'),
|
||||
"text": "Features",
|
||||
"active": header_navigation.is_selected('features')
|
||||
},
|
||||
{
|
||||
"href": url_for('main.support'),
|
||||
"text": "Contact us",
|
||||
"active": header_navigation.is_selected('support')
|
||||
},
|
||||
{
|
||||
"href": url_for('main.sign_in'),
|
||||
"text": "Sign in",
|
||||
"active": header_navigation.is_selected('sign-in')
|
||||
}
|
||||
] %} #}
|
||||
{% endif %}
|
||||
|
||||
{{ usaHeader({
|
||||
"homepageUrl": url_for('main.show_accounts_or_dashboard'),
|
||||
"productName": "Notify",
|
||||
"navigation": navigation,
|
||||
"navigationClasses": "govuk-header__navigation--end",
|
||||
"secondaryNavigation": secondaryNavigation,
|
||||
"assetsPath": asset_path + "images"
|
||||
}) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}
|
||||
<!-- lines 179 - 251 can all be deleted and replaced with {% include "new/components/footer.html" %} -->
|
||||
{% if current_service and current_service.research_mode %}
|
||||
{% set meta_suffix = 'Built by the <a href="https://www.gsa.gov/about-us/organization/federal-acquisition-service/technology-transformation-services/tts-solutions" class="usa-link">Technology Transformation Services</a><span id="research-mode" class="research-mode">research mode</span>' %}
|
||||
{% else %}
|
||||
{% set commit_hash = ", Latest version: " + config['COMMIT_HASH'] %}
|
||||
{% set long_link = '<a href="https://www.gsa.gov/about-us/organization/federal-acquisition-service/technology-transformation-services/tts-solutions" class="usa-link">Technology Transformation Services</a>' %}
|
||||
{% set meta_suffix = "Built by the " + long_link + commit_hash %}
|
||||
{% endif %}
|
||||
|
||||
{{ usaFooter({
|
||||
"classes": "js-footer",
|
||||
"navigation": [
|
||||
{
|
||||
"title": "About Notify",
|
||||
"columns": 1,
|
||||
"items": [
|
||||
{
|
||||
"href": url_for("main.features"),
|
||||
"text": "Features"
|
||||
},
|
||||
{
|
||||
"href": url_for("main.roadmap"),
|
||||
"text": "Roadmap"
|
||||
},
|
||||
{
|
||||
"href": url_for("main.security"),
|
||||
"text": "Security"
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Using Notify",
|
||||
"columns": 1,
|
||||
"items": [
|
||||
{
|
||||
"href": url_for("main.get_started"),
|
||||
"text": "Get started"
|
||||
},
|
||||
{
|
||||
"href": url_for("main.pricing"),
|
||||
"text": "Pricing"
|
||||
},
|
||||
{
|
||||
"href": url_for("main.trial_mode_new"),
|
||||
"text": "Trial mode"
|
||||
},
|
||||
{
|
||||
"href": url_for("main.message_status"),
|
||||
"text": "Delivery status"
|
||||
},
|
||||
{
|
||||
"href": url_for("main.guidance_index"),
|
||||
"text": "Guidance"
|
||||
},
|
||||
{
|
||||
"href": url_for("main.documentation"),
|
||||
"text": "API documentation"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Support",
|
||||
"columns": 1,
|
||||
"items": [
|
||||
{
|
||||
"href": url_for('main.support'),
|
||||
"text": "Contact us"
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
"meta": {
|
||||
"items": meta_items,
|
||||
"html": meta_suffix
|
||||
}
|
||||
}) }}
|
||||
{% if current_user.is_authenticated %}
|
||||
{% block sessionUserWarning %}
|
||||
<dialog class="usa-modal" id="sessionTimer" aria-labelledby="sessionTimerHeading" aria-describedby="timerWarning">
|
||||
<div class="usa-modal__content">
|
||||
<div class="usa-modal__main">
|
||||
<h2 class="usa-modal__heading" id="sessionTimerHeading">
|
||||
Your session will end soon.
|
||||
<span class="usa-sr-only">Please choose to extend your session or sign out. Your session will expire in 5 minutes or less.</span>
|
||||
</h2>
|
||||
<div class="usa-prose">
|
||||
<p>You have been inactive for too long.
|
||||
Your session will expire in <span id="timeLeft" role="timer"></span>.
|
||||
</p>
|
||||
</div>
|
||||
<div class="usa-modal__footer">
|
||||
<ul class="usa-button-group">
|
||||
<li class="usa-button-group__item">
|
||||
<button type="button" class="usa-button" id="extendSessionTimer" data-close-modal>
|
||||
Extend Session
|
||||
</button>
|
||||
</li>
|
||||
<li class="usa-button-group__item">
|
||||
<button type="button" class="usa-button usa-button--unstyled padding-105 text-center" id="logOutTimer"
|
||||
data-close-modal>
|
||||
Sign out
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% 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 %}
|
||||
@@ -1,28 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% from "components/sub-navigation.html" import sub_navigation %}
|
||||
{% from "components/page-header.html" import page_header %}
|
||||
|
||||
{% block per_page_title %}
|
||||
{{ content_page_title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
<div class="grid-row">
|
||||
|
||||
{% if navigation_links %}
|
||||
<div class="tablet:grid-col-2 margin-bottom-4">
|
||||
{{ sub_navigation(navigation_links) }}
|
||||
</div>
|
||||
<div class="tablet:grid-col-10 tablet:padding-left-4 usa-prose site-prose">
|
||||
{% else %}
|
||||
<div class="tablet:grid-col-10">
|
||||
{% endif %}
|
||||
|
||||
{% block content_column_content %}
|
||||
{% endblock %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,32 +0,0 @@
|
||||
{# This template is an old version #}
|
||||
{% if help %}
|
||||
{% include 'partials/tour.html' %}
|
||||
{% else %}
|
||||
<nav class="nav margin-bottom-4">
|
||||
<a class="usa-button margin-top-1 margin-bottom-5 width-full"
|
||||
href="{{ url_for('.choose_template', service_id=current_service.id) }}">Send messages</a>
|
||||
<ul class="usa-sidenav">
|
||||
{% if current_user.has_permissions() %}
|
||||
{% if current_user.has_permissions('view_activity') %}
|
||||
<li class="usa-sidenav__item"><a class="{{ main_navigation.is_selected('dashboard') }}" href="{{ url_for('.service_dashboard', service_id=current_service.id) }}">Dashboard</a></li>
|
||||
{% endif %}
|
||||
{# <li class="usa-sidenav__item"><a class="{{ main_navigation.is_selected('templates') }}" href="{{ url_for('.choose_template', service_id=current_service.id) }}">Send messages</a></li> #}
|
||||
{% if not current_user.has_permissions('view_activity') %}
|
||||
<li class="usa-sidenav__item"><a class="{{ casework_navigation.is_selected('sent-messages') }}" href="{{ url_for('.view_notifications', service_id=current_service.id, status='sending,delivered,failed') }}">Sent messages</a></li>
|
||||
{% endif %}
|
||||
{% if current_user.has_permissions('manage_service', allow_org_user=True) %}
|
||||
{# <li class="usa-sidenav__item"><a class="{{ main_navigation.is_selected('usage') }}" href="{{ url_for('.usage', service_id=current_service.id) }}">Usage</a></li> #}
|
||||
{% endif %}
|
||||
<!-- {% if current_user.has_permissions('manage_api_keys', 'manage_service') %}
|
||||
<li class="usa-sidenav__item"><a class="{{ main_navigation.is_selected('settings') }}" href="{{ url_for('.service_settings', service_id=current_service.id) }}">Settings</a></li>
|
||||
{% endif %} -->
|
||||
{% if current_user.has_permissions('manage_api_keys') %}
|
||||
<!-- <li><a class="usa-link{{ main_navigation.is_selected('api-integration') }}" href="{{ url_for('.api_integration', service_id=current_service.id) }}">API integration</a></li> -->
|
||||
{% endif %}
|
||||
{% elif current_user.has_permissions(allow_org_user=True) %}
|
||||
<li class="usa-sidenav__item"><a class="usa-link{{ main_navigation.is_selected('usage') }}" href="{{ url_for('.usage', service_id=current_service.id) }}">Usage</a></li>
|
||||
<li class="usa-sidenav__item"><a class="usa-link{{ main_navigation.is_selected('team-members') }}" href="{{ url_for('.manage_users', service_id=current_service.id) }}">Team members</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
@@ -1,70 +0,0 @@
|
||||
{% from "./components/components/skip-link/macro.njk" import usaSkipLink -%}
|
||||
{% from "./components/components/header/macro.njk" import usaHeader -%}
|
||||
{% from "./components/components/footer/macro.njk" import usaFooter -%}
|
||||
{# specify absolute url for the static assets folder e.g. http://wwww.domain.com/assets #}
|
||||
{%- set assetUrl = assetUrl | default(assetPath) -%}
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ htmlLang | default('en') }}" class="{{ htmlClasses }}">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>{% block pageTitle %}Notify.gov{% endblock %}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<meta name="theme-color" media="(prefers-color-scheme: light)" content="f0f0f0" />
|
||||
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="1b1b1b" />
|
||||
{% if config['NR_MONITOR_ON'] %}
|
||||
{% include "partials/newrelic.html" -%}
|
||||
{% endif %}
|
||||
|
||||
{# Ensure that older IE versions always render with the correct rendering engine #}
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
|
||||
{% block headIcons %}
|
||||
<link rel="shortcut icon" href="{{ assetPath | default('/static') }}/images/favicon.ico" />
|
||||
<link rel="icon" type="image/png" sizes="32x32"href="{{ assetPath | default('/static') }}/images/favicon-32x32.png"/>
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ assetPath | default('/static') }}/images/favicon-16x16.png" />
|
||||
<link rel="mask-icon" href="{{ assetPath | default('/static') }}/images/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ assetPath | default('/static') }}/images/apple-touch-icon.png">
|
||||
<link rel="manifest" href="/site.webmanifest">
|
||||
<meta name="msapplication-TileColor" content="#da532c">
|
||||
<link href="{{ assetPath | default('/static') }}/images/notify-dark-favicon.png" rel="icon" media="(prefers-color-scheme: dark)">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
{% endblock %}
|
||||
|
||||
{% block head %}{% endblock %}
|
||||
{# The default og:image is added below head so that scrapers see any custom metatags first, and this is just a fallback #}
|
||||
{% block meta %}
|
||||
<meta property="og:site_name" content="Notify.gov">
|
||||
<meta property="og:image" content="{{ asset_url('images/notify-og-image.png') }}">
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body class="usa-template__body {{ bodyClasses }}">
|
||||
<script nonce="{{ csp_nonce() }}">document.body.className = ((document.body.className) ? document.body.className + ' js-enabled' : 'js-enabled');</script>
|
||||
{% block bodyStart %}{% endblock %}
|
||||
|
||||
{% block skipLink %}
|
||||
{{ usaSkipLink({
|
||||
"href": '#main-content',
|
||||
"text": 'Skip to main content'
|
||||
}) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
{{ usaHeader({}) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="grid-container">
|
||||
{% block beforeContent %}{% endblock %}
|
||||
<main class="{{ mainClasses }}" id="main-content" role="main">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}
|
||||
{{ usaFooter({}) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block bodyEnd %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,12 +0,0 @@
|
||||
{# This template is an old version #}
|
||||
<nav class="navigation">
|
||||
<ul>
|
||||
<li><a class="usa-link{{ org_navigation.is_selected('dashboard') }}" href="{{ url_for('.organization_dashboard', org_id=current_org.id) }}">Usage</a></li>
|
||||
<li><a class="usa-link{{ org_navigation.is_selected('team-members') }}" href="{{ url_for('.manage_org_users', org_id=current_org.id) }}">Team members</a></li>
|
||||
{% if current_user.platform_admin %}
|
||||
<li><a class="usa-link{{ org_navigation.is_selected('settings') }}" href="{{ url_for('.organization_settings', org_id=current_org.id) }}">Settings</a></li>
|
||||
<li><a class="usa-link{{ org_navigation.is_selected('trial-services') }}" href="{{ url_for('.organization_trial_mode_services', org_id=current_org.id) }}">Trial mode services</a></li>
|
||||
<li><a class="usa-link{{ org_navigation.is_selected('billing') }}" href="{{ url_for('.organization_billing', org_id=current_org.id) }}">Billing</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
@@ -1,35 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block per_page_title %}
|
||||
{% block org_page_title %}{% endblock %} – {{ current_org.name }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="grid-container">
|
||||
<div class="navigation-service usa-breadcrumb">
|
||||
{% if current_user.platform_admin %}
|
||||
<a href="{{ url_for('.organizations') }}" class="usa-link navigation-organization-link">Organizations</a>
|
||||
{% endif %}
|
||||
<div class="navigation-service">
|
||||
{{ current_org.name }}
|
||||
</div>
|
||||
<a href="{{ url_for('main.choose_account') }}" class="usa-link navigation-service">Switch service</a>
|
||||
</div>
|
||||
<div class="grid-row">
|
||||
<div class="grid-col-3">
|
||||
{% include "org_nav.html" %}
|
||||
</div>
|
||||
<div class="grid-col-9">
|
||||
{% block beforeContent %}
|
||||
{% block backLink %}{% endblock %}
|
||||
{% endblock %}
|
||||
<main class="main" id="main-content" role="main" >
|
||||
{% block content %}
|
||||
{% include 'flash_messages.html' %}
|
||||
{% block maincolumn_content %}{% endblock %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,20 +0,0 @@
|
||||
{# This template is an old version #}
|
||||
{% macro navigation_service_name(service) %}
|
||||
<div class="font-body-2xl text-bold">
|
||||
{{ service.name }}
|
||||
{% if not service.active %}
|
||||
<span class="navigation-service-name navigation-service-type--suspended">Suspended</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
<div class="navigation-service margin-top-5 display-flex flex-align-end flex-justify border-bottom padding-bottom-1">
|
||||
{% if current_service.organization_id %}
|
||||
{% if current_user.platform_admin or
|
||||
(current_user.belongs_to_organization(current_service.organization_id) and current_service.live) %}
|
||||
<a href="{{ url_for('.organization_dashboard', org_id=current_service.organization_id) }}" class="usa-link navigation-organization-link">{{ current_service.organization_name }}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{{ navigation_service_name(current_service) }}
|
||||
<a href="{{ url_for('main.choose_account') }}" class="usa-link">Switch service</a>
|
||||
</div>
|
||||
@@ -1,17 +0,0 @@
|
||||
{# This template is an old version #}
|
||||
{% if help %}
|
||||
{% include 'partials/tour.html' %}
|
||||
{% else %}
|
||||
<nav class="nav">
|
||||
<ul class="usa-sidenav">
|
||||
{# {% if current_user.has_permissions() %} #}
|
||||
<li class="usa-sidenav__item"><a class="{{ main_navigation.is_selected('settings') }}"
|
||||
href="{{ url_for('main.service_settings', service_id=current_service.id) }}">General</a></li>
|
||||
<li class="usa-sidenav__item"><a class="{{ main_navigation.is_selected('user-profile') }}"
|
||||
href="{{ url_for('main.user_profile', service_id=current_service.id) }}">User profile</a></li>
|
||||
<li class="usa-sidenav__item"><a class="{{ main_navigation.is_selected('team-members') }}"
|
||||
href="{{ url_for('main.manage_users', service_id=current_service.id) }}">Team members</a></li>
|
||||
{# {% endif %} #}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
@@ -1,35 +0,0 @@
|
||||
{# This template is an old version #}
|
||||
{% extends "admin_template.html" %}
|
||||
|
||||
{% block per_page_title %}
|
||||
{% block service_page_title %}{% endblock %} – {{ current_service.name }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="grid-container">
|
||||
<div class="grid-row margin-top-5">
|
||||
{% if help %}
|
||||
<div class="tablet:grid-col-3">
|
||||
{% else %}
|
||||
<div class="tablet:grid-col-3 margin-bottom-4">
|
||||
{% endif %}
|
||||
{% include "settings_nav.html" %}
|
||||
</div>
|
||||
{% if help %}
|
||||
<div class="tablet:grid-col-8">
|
||||
{% else %}
|
||||
<div class="tablet:grid-col-9 tablet:padding-left-4">
|
||||
{% endif %}
|
||||
{% block beforeContent %}
|
||||
{% block backLink %}{% endblock %}
|
||||
{% endblock %}
|
||||
<main id="main-content" role="main" class="usa-prose site-prose margin-bottom-10">
|
||||
{% block content %}
|
||||
{% include 'flash_messages.html' %}
|
||||
{% block maincolumn_content %}{% endblock %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,36 +0,0 @@
|
||||
{# This template is an old version #}
|
||||
{% extends "admin_template.html" %}
|
||||
|
||||
{% block per_page_title %}
|
||||
{% block service_page_title %}{% endblock %} – {{ current_service.name }}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="grid-container">
|
||||
{% include "service_navigation.html" %}
|
||||
<div class="grid-row margin-top-5">
|
||||
{% if help %}
|
||||
<div class="tablet:grid-col-3">
|
||||
{% else %}
|
||||
<div class="tablet:grid-col-3">
|
||||
{% endif %}
|
||||
{% include "main_nav.html" %}
|
||||
</div>
|
||||
{% if help %}
|
||||
<div class="grid-col-8">
|
||||
{% else %}
|
||||
<div class="tablet:grid-col-9 tablet:padding-left-4">
|
||||
{% endif %}
|
||||
{% block beforeContent %}
|
||||
{% block backLink %}{% endblock %}
|
||||
{% endblock %}
|
||||
<main id="main-content" role="main" class="usa-prose site-prose margin-bottom-10">
|
||||
{% block content %}
|
||||
{% include 'flash_messages.html' %}
|
||||
{% block maincolumn_content %}{% endblock %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,17 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% set mainClasses = "margin-top-5 padding-bottom-5" %}
|
||||
|
||||
{% block beforeContent %}
|
||||
{% if current_service and current_service.active and current_user.is_authenticated and current_user.belongs_to_service(current_service.id) %}
|
||||
<!-- <div class="navigation-service usa-breadcrumb">
|
||||
<a href="{{ url_for('main.show_accounts_or_dashboard') }}" class="usa-link usa-breadcrumb__link usa-breadcrumb-back-to">Back to {{ current_service.name }}</a>
|
||||
</div> -->
|
||||
{% endif %}
|
||||
{% block backLink %}{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'flash_messages.html' %}
|
||||
{% block maincolumn_content %}{% endblock %}
|
||||
{% endblock %}
|
||||
@@ -25,7 +25,7 @@
|
||||
{% if job.scheduled_for %}
|
||||
<div class="usa-alert usa-alert--info">
|
||||
<div class="usa-alert__body">
|
||||
<h4 class="usa-alert__heading">Your text has been scheduled</h4>
|
||||
<h2 class="usa-alert__heading">Your text has 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>
|
||||
@@ -35,7 +35,7 @@
|
||||
{% else %}
|
||||
<div class="usa-alert usa-alert--success">
|
||||
<div class="usa-alert__body">
|
||||
<h4 class="usa-alert__heading">Your text has been sent</h4>
|
||||
<h2 class="usa-alert__heading">Your text has been sent</h2>
|
||||
<p class="usa-alert__text">
|
||||
{{ job.template_name }} - {{ current_service.name }} was sent on {% if job.processing_started %}
|
||||
{{ job.processing_started|format_datetime_table }} {% else %}
|
||||
|
||||
@@ -35,11 +35,6 @@
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if page == 3 and pagination.last > 4 %}
|
||||
<li class="usa-pagination__item usa-pagination__overflow" aria-label="ellipsis indicating non-visible pages">
|
||||
<span>…</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if next_page %}
|
||||
@@ -58,7 +53,6 @@
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% endset %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
<div class="margin-bottom-8">
|
||||
<h1 class="usa-sr-only">All activity</h1>
|
||||
@@ -110,7 +104,7 @@
|
||||
<td class="table-field report">
|
||||
{% if job.time_left != "Data no longer available" %}
|
||||
<a href="{{ job.download_link }}"><img src="{{ url_for('static', filename='img/material-icons/file_download.svg') }}" alt="File Download Icon"></a>
|
||||
{% elif job %}
|
||||
{% else %}
|
||||
<span>N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
@@ -120,9 +114,9 @@
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr class="table-row">
|
||||
<td class="table-empty-message" colspan="10">No batched job messages found (messages are kept for {{ service_data_retention_days }} days).</td>
|
||||
<td class="table-empty-message" colspan="10">No messages found</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="usa-sr-only usa-table__announcement-region" aria-live="polite"></div>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
|
||||
|
||||
{% extends "withnav_template.html" %}
|
||||
|
||||
{% from "components/table.html" import list_table, field, text_field, link_field, right_aligned_field_heading, hidden_field_heading, row_heading, notification_status_field, notification_carrier_field, notification_carrier_message_field %}
|
||||
@@ -8,6 +10,8 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
<script type="text/javascript" src="{{ asset_url('js/setTimezone.js') }}"></script>
|
||||
|
||||
|
||||
<div class="dashboard margin-bottom-2">
|
||||
|
||||
@@ -29,9 +33,7 @@
|
||||
</div>
|
||||
<div id="totalMessageTable" class="margin-0"></div>
|
||||
|
||||
<h2 class="line-height-sans-2 margin-bottom-0 margin-top-4">
|
||||
Activity snapshot
|
||||
</h2>
|
||||
<h2 class="line-height-sans-2 margin-bottom-0 margin-top-4">Recent activity</h2>
|
||||
<div id="activityChartContainer">
|
||||
<form class="usa-form">
|
||||
<label class="usa-label" for="options">Account</label>
|
||||
@@ -44,7 +46,7 @@
|
||||
<div id="activityChart">
|
||||
<div class="chart-header">
|
||||
<div class="chart-subtitle">{{ current_service.name }} - last 7 days</div>
|
||||
<div class="chart-legend" aria-label="Legend"></div>
|
||||
<div class="chart-legend" role="region" aria-label="Legend"></div>
|
||||
</div>
|
||||
<div class="chart-container" id="weeklyChart"></div>
|
||||
<table id="weeklyTable" class="usa-sr-only usa-table"></table>
|
||||
|
||||
@@ -1,35 +1,45 @@
|
||||
{% from "components/table.html" import list_table, field, right_aligned_field_heading, row_heading, spark_bar_field %}
|
||||
|
||||
<div class="ajax-block-container">
|
||||
{% if template_statistics|length > 0 %}
|
||||
<h2 class="margin-top-4 margin-bottom-1">Recent templates</h2>
|
||||
<div class='template-statistics-table table-overflow-x-auto'>
|
||||
{% call(item, row_number) list_table(
|
||||
template_statistics,
|
||||
caption="Messages sent by template",
|
||||
caption_visible=False,
|
||||
border_visible=True,
|
||||
empty_message='',
|
||||
field_headings=[
|
||||
'Template',
|
||||
'Messages sent'
|
||||
],
|
||||
field_headings_visible=False
|
||||
) %}
|
||||
|
||||
{% call row_heading() %}
|
||||
<a class="usa-link template-statistics-table-template-name" href="{{ url_for('.view_template', service_id=current_service.id, template_id=item.template_id) }}">{{ item.template_name }}</a>
|
||||
<span class="template-statistics-table-hint">
|
||||
{{ 1|message_count_label(item.template_type, suffix='template')|capitalize }}
|
||||
</span>
|
||||
{% endcall %}
|
||||
|
||||
{{ spark_bar_field(item.count, most_used_template_count, id=item.template_id) }}
|
||||
{% endcall %}
|
||||
<a
|
||||
href="{{ url_for('.template_usage', service_id=current_service.id) }}"
|
||||
class="usa-link show-more-no-border"
|
||||
><span>See templates used by month</span></a>
|
||||
{% if template_statistics|length > 1 %}
|
||||
<h2 class="margin-top-4 margin-bottom-1">Most Used Templates</h2>
|
||||
<div class="template-statistics-table table-overflow-x-auto">
|
||||
<table class="usa-table width-full">
|
||||
<caption class="font-body-lg table-heading usa-sr-only">
|
||||
Messages sent by template
|
||||
</caption>
|
||||
<thead class="table-field-headings">
|
||||
<tr>
|
||||
<th class="table-field-heading-first" width="">
|
||||
<span>Template name</span>
|
||||
</th>
|
||||
<th class="table-field-heading" width="">
|
||||
<span>Folder</span>
|
||||
</th>
|
||||
<th class="table-field-heading" width="">
|
||||
<span>Last used</span>
|
||||
</th>
|
||||
<th class="table-field-heading" width="">
|
||||
<span>Created by</span>
|
||||
</th>
|
||||
<th class="table-field-heading" width="">
|
||||
<span># Times used</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in template_statistics[:8] %}
|
||||
<tr class="table-row">
|
||||
<td>
|
||||
<a class="usa-link template-statistics-table-template-name" href="{{ url_for('.view_template', service_id=current_service.id, template_id=item.template_id) }}">{{ item.template_name }}</a>
|
||||
</td>
|
||||
<td><p>{{ item.template_folder }}</p></td>
|
||||
<td><p>{{ item.last_used|format_datetime_table}}</p></td>
|
||||
<td><p>{{ item.created_by }}</p></td>
|
||||
<td><p>{{ item.count }}</p></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<a href="/services/78409625-0c0a-485e-b82c-b19c8f4b1bdb/template-usage" class="usa-link show-more-no-border"><span>See templates by month</span></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
{% block serviceNavigation %}{% endblock %}
|
||||
|
||||
{% block sideNavigation %}
|
||||
{% include "/new/components/settings_nav.html" %}
|
||||
{% include "components/settings_nav.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
{% block backLink %}{% endblock %}
|
||||
<main id="main-content" role="main">
|
||||
{% block content %}
|
||||
{% include 'new/components/flash_messages.html' %}
|
||||
{% include 'components/flash_messages.html' %}
|
||||
{% block platform_admin_content %}{% endblock %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
|
||||
@@ -28,6 +28,10 @@
|
||||
{% if not service['active'] %}
|
||||
<span class="heading-medium hint"> Archived</span>
|
||||
{% endif %}
|
||||
{% if service['name'] == 'Test service' %}
|
||||
<a class="usa-link" href="{{ url_for('main.load_test') }}">Load Test</a>
|
||||
{% endif %}
|
||||
|
||||
{% endcall %}
|
||||
|
||||
{% endcall %}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
{% from "components/file-upload.html" import file_upload %}
|
||||
{% from "components/table.html" import list_table, text_field, index_field, index_field_heading %}
|
||||
{% from "components/components/back-link/macro.njk" import usaBackLink %}
|
||||
<script type="text/javascript" src="{{ asset_url('js/setTimezone.js') }}"></script>
|
||||
|
||||
{% block service_page_title %}
|
||||
Upload a list of {{ 999|recipient_count_label(template.template_type) }}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
{% block serviceNavigation %}{% endblock %}
|
||||
|
||||
{% block sideNavigation %}
|
||||
{% include "/new/components/settings_nav.html" %}
|
||||
{% include "components/settings_nav.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
{% endif %}
|
||||
</p>
|
||||
{% else %}
|
||||
<nav id="template-list">
|
||||
<nav id="template-list" aria-label="Template list">
|
||||
{% set checkboxes_data = [] %}
|
||||
|
||||
{% if not current_user.has_permissions('manage_templates') %}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
{{ live_search(target_selector='#template-list .template-list-item', show=True, form=search_form) }}
|
||||
|
||||
<nav id="template-list">
|
||||
<nav id="template-list" aria-label="Choose reply">
|
||||
<ul>
|
||||
{% for item in templates_and_folders %}
|
||||
<li class="template-list-item {% if item.ancestors %}template-list-item-hidden-by-default{% endif %} {% if not item.ancestors %}template-list-item-without-ancestors{% endif %}">
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
form=search_form,
|
||||
autofocus=True
|
||||
) }}
|
||||
<nav id="template-list">
|
||||
<nav id="template-list" aria-label="Copy template list">
|
||||
<ul>
|
||||
{% for item in services_templates_and_folders %}
|
||||
|
||||
|
||||
@@ -63,24 +63,29 @@
|
||||
href="{{ url_for('.view_template_versions', service_id=current_service.id, template_id=template.id) }}">See previous
|
||||
versions</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if current_user.has_permissions('manage_templates') and user_has_template_permission %}
|
||||
{% if not template._template.archived %}
|
||||
<ul class="usa-list usa-list--unstyled">
|
||||
<li>
|
||||
<a class="usa-link margin-right-3"
|
||||
href="{{ url_for('.delete_service_template', service_id=current_service.id, template_id=template.id) }}">Delete this
|
||||
template</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if not template._template.redact_personalisation %}
|
||||
<ul class="usa-list usa-list--unstyled">
|
||||
<li>
|
||||
<a class="usa-link" href="{{ url_for('.confirm_redact_template', service_id=current_service.id, template_id=template.id) }}">Hide all personalized and conditional content after sending</a> for increased privacy protection
|
||||
</li>
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="hint">Personalization is hidden after sending</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<!--<div class="">
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
<h3 class="font-body-lg">Going Live</h3>
|
||||
|
||||
<p>To remove the restrictions of Trial Mode and begin sending messages to clients complete the <a href="https://airtable.com/appe4n7jYOALPLcyU/shrIPWnLTw9U1fclL">Live Campaign Form</a>.</p>
|
||||
<p>To remove the restrictions of Trial Mode and begin sending messages to clients complete the <a class="usa-link usa-link--external" href="https://docs.google.com/forms/d/1fnaBtxuGf3q-OdGVyt2LqBKvp9_P21kmKJa0yIK8rWM/edit">Go-Live Form</a>.</p>
|
||||
<p>We'll get back to you within one working day. </p>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
{% block serviceNavigation %}{% endblock %}
|
||||
|
||||
{% block sideNavigation %}
|
||||
{% include "/new/components/settings_nav.html" %}
|
||||
{% include "components/settings_nav.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
@@ -45,14 +45,9 @@
|
||||
{% endcall %}
|
||||
|
||||
{% call row() %}
|
||||
{{ text_field('Preferred Timezone') }}
|
||||
{{ text_field('Preferred timezone') }}
|
||||
{{ optional_text_field(current_user.preferred_timezone) }}
|
||||
{{ edit_field(
|
||||
'Change',
|
||||
url_for('.user_profile_preferred_timezone'),
|
||||
suffix='preferred timezone'
|
||||
)
|
||||
}}
|
||||
{{ text_field('Set automatically') }}
|
||||
{% endcall %}
|
||||
|
||||
{% if current_user.platform_admin or session.get('disable_platform_admin_view') %}
|
||||
|
||||
@@ -13,35 +13,20 @@
|
||||
{% block serviceNavigation %}
|
||||
{% if current_org.name %}
|
||||
{% else %}
|
||||
{% include "new/components/service_nav.html" %}
|
||||
{% include "components/service_nav.html" %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{#
|
||||
The withnav_template can serve as a replacement for both settings_template and org_template.html.
|
||||
|
||||
The file service_navigation.html is included only in withnav_template. It's not used in settings_template. That is one out of the two differences between settings template and withnav template. As a result, when other templates extend settings_template, they include the serviceNavigation block but keep it empty. The settings_template.html is specifically used for these pages in the app: manage-users.html, service-settings.html, and user-profile.html.
|
||||
|
||||
In addition, serviceNavigation should be empty on templates that previously extended org_template. For templates that previously extended org_template.html, there's an addition of the orgNavBreadcrumb block.
|
||||
{% block orgNavBreadcrumb %}
|
||||
{% include "/new/components/org_nav_breadcrumb.html" %}
|
||||
{% endblock %}
|
||||
#}
|
||||
{% if current_org.name %}
|
||||
{% block orgNavBreadcrumb %}{% include "/new/components/org_nav_breadcrumb.html" %}{% endblock %}
|
||||
{% block orgNavBreadcrumb %}{% include "components/org_nav_breadcrumb.html" %}{% endblock %}
|
||||
{% endif %}
|
||||
<div class="grid-row margin-top-5">
|
||||
<div class="tablet:grid-col-2">
|
||||
{% block sideNavigation %}
|
||||
{% if current_org.name %}
|
||||
{% include "/new/components/org_nav.html" %}
|
||||
{% include "components/org_nav.html" %}
|
||||
{% else %}
|
||||
{% include "/new/components/main_nav.html" %}
|
||||
{% include "components/main_nav.html" %}
|
||||
{% endif %}
|
||||
{#
|
||||
Include settings_nav.html for child templates that previously extended settings_template.
|
||||
|
||||
Include "org_nav.html" for child templates that previously extended org_template html
|
||||
#}
|
||||
{% endblock %}
|
||||
</div>
|
||||
<div class="tablet:grid-col-10 tablet:padding-left-4">
|
||||
@@ -50,7 +35,7 @@
|
||||
{% endblock %}
|
||||
<main id="main-content" role="main" class="usa-prose site-prose margin-bottom-10">
|
||||
{% block content %}
|
||||
{% include 'new/components/flash_messages.html' %}
|
||||
{% include 'components/flash_messages.html' %}
|
||||
{% block maincolumn_content %}{% endblock %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
|
||||
@@ -9,9 +9,8 @@ from app.utils.csv import get_user_preferred_timezone
|
||||
def get_current_financial_year():
|
||||
preferred_tz = pytz.timezone(get_user_preferred_timezone())
|
||||
now = datetime.now(preferred_tz)
|
||||
current_month = int(now.strftime("%-m"))
|
||||
current_year = int(now.strftime("%Y"))
|
||||
return current_year if current_month < 10 else current_year + 1
|
||||
return current_year
|
||||
|
||||
|
||||
def is_less_than_days_ago(date_from_db, number_of_days):
|
||||
|
||||
61
backstop.config.js
Normal file
@@ -0,0 +1,61 @@
|
||||
const { urls, baseUrl } = require('./urls');
|
||||
|
||||
const MISMATCH_THRESHOLD = 0.2;
|
||||
const SCREENSHOT_DELAY = 2000;
|
||||
|
||||
const createScenariosFromUrls = (urls, delay = SCREENSHOT_DELAY) => {
|
||||
return Object.keys(urls).map((label) => ({
|
||||
label,
|
||||
url: urls[label],
|
||||
selectors: ['document'],
|
||||
misMatchThreshold: MISMATCH_THRESHOLD,
|
||||
requireSameDimensions: true,
|
||||
delay,
|
||||
}));
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
id: 'backstop_test',
|
||||
viewports: [
|
||||
{
|
||||
label: 'desktop',
|
||||
width: 1024,
|
||||
height: 768,
|
||||
},
|
||||
],
|
||||
scenarios: [
|
||||
...createScenariosFromUrls(urls),
|
||||
{
|
||||
label: 'Choose Service - Accounts',
|
||||
url: `${baseUrl}/accounts`,
|
||||
selectors: ['h1.heading-large', 'a.usa-button[href="/add-service"]'],
|
||||
misMatchThreshold: MISMATCH_THRESHOLD,
|
||||
requireSameDimensions: true,
|
||||
delay: SCREENSHOT_DELAY,
|
||||
},
|
||||
// example page with script
|
||||
{
|
||||
label: 'Get Started Page - Highlight Trial Mode',
|
||||
url: `${baseUrl}/using-notify/get-started`,
|
||||
selectors: ['document'],
|
||||
misMatchThreshold: MISMATCH_THRESHOLD,
|
||||
requireSameDimensions: true,
|
||||
onBeforeScript: 'puppeteer/countFeatureLinks.js',
|
||||
delay: SCREENSHOT_DELAY,
|
||||
},
|
||||
],
|
||||
paths: {
|
||||
bitmaps_reference: 'backstop_data/bitmaps_reference',
|
||||
bitmaps_test: 'backstop_data/bitmaps_test',
|
||||
engine_scripts: 'backstop_data/engine_scripts',
|
||||
html_report: 'backstop_data/html_report',
|
||||
ci_report: 'backstop_data/ci_report',
|
||||
},
|
||||
engine: 'puppeteer',
|
||||
engineOptions: {
|
||||
browser: 'chromium',
|
||||
timeout: 30000,
|
||||
},
|
||||
report: ['browser'],
|
||||
debug: false,
|
||||
};
|
||||
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 349 KiB |
|
After Width: | Height: | Size: 325 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 908 KiB |
|
After Width: | Height: | Size: 418 KiB |
|
After Width: | Height: | Size: 376 KiB |
|
After Width: | Height: | Size: 282 KiB |
|
After Width: | Height: | Size: 558 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 190 KiB |
18
backstop_data/engine_scripts/puppeteer/countFeatureLinks.js
Normal file
@@ -0,0 +1,18 @@
|
||||
module.exports = async (page, scenario) => {
|
||||
await page.goto(scenario.url, { waitUntil: 'networkidle2' });
|
||||
|
||||
console.log('Page loaded.');
|
||||
|
||||
// Count the number of items in the side navigation and log their text
|
||||
const navItems = await page.$$eval('nav ul li.usa-sidenav__item a', (items) =>
|
||||
items.map((item) => item.textContent.trim())
|
||||
);
|
||||
|
||||
console.log(`Found ${navItems.length} navigation items:`);
|
||||
navItems.forEach((itemText, index) => {
|
||||
console.log(`${index + 1}: ${itemText}`);
|
||||
});
|
||||
|
||||
// Wait a moment for the logging to complete
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
};
|
||||
65
docs/backstop.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# BackstopJS Getting Started Guide
|
||||
|
||||
## Overview
|
||||
|
||||
BackstopJS is currently optional in this project and is used for visual regression testing to ensure consistency in the UI. To get started with BackstopJS, follow the steps below. The official guide can be found at the [BackstopJS GitHub Repository](https://github.com/garris/BackstopJS).
|
||||
|
||||
## Goal
|
||||
|
||||
Eventually BackstopJS will be implemented in the pipeline process to help catch errors before they make their way to higher environments.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before running BackstopJS, ensure the following:
|
||||
|
||||
- Both the API and Admin projects must be running.
|
||||
- In the .env file of both the API and Admin projects, make sure the following environment variables are uncommented and set to matching values:
|
||||
- `NOTIFY_E2E_TEST_EMAIL`
|
||||
- `NOTIFY_E2E_TEST_PASSWORD`
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
NOTIFY_E2E_TEST_EMAIL=your-email@example.com
|
||||
NOTIFY_E2E_TEST_PASSWORD=your-password
|
||||
```
|
||||
|
||||
### How to Run BackstopJS
|
||||
|
||||
#### Step 1: Install Gulp Globally
|
||||
|
||||
First, make sure all dependencies are installed and updated:
|
||||
|
||||
```
|
||||
make bootstrap
|
||||
```
|
||||
|
||||
#### Step 2: Run the Gulp Test Task
|
||||
|
||||
To run the visual regression tests, use the following command:
|
||||
|
||||
```
|
||||
gulp backstopTest
|
||||
```
|
||||
|
||||
If there are new expected changes in the UI, one will need to update the reference images before testing. To do this, run the following command:
|
||||
|
||||
```
|
||||
gulp backstopReference
|
||||
```
|
||||
|
||||
**_Note: After running the `gulp backstopReference` command, immediately running the test (`gulp backstopTest`) should pass all tests, assuming the changes are expected._**
|
||||
|
||||
### Adding New Tests
|
||||
|
||||
The entry point for BackstopJS configuration can be found in the `backstop.config.js` file.
|
||||
|
||||
To add a new feature or page to be tested, modify the `scenarios` array. Refer to the [official documentation](https://github.com/garris/BackstopJS) for details on the structure of the object.
|
||||
|
||||
In most cases, one will be testing entire pages instead of individual components. To reduce redundancy, helper functions were created with our default settings that will be applied to every URL in the `urls.js` file. To add a new URL, one would add an object like such:
|
||||
|
||||
```
|
||||
{ label: 'Support', path: '/support' }
|
||||
```
|
||||
|
||||
For unique cases that involve UI interaction, scripts linked to the `engine_scripts/puppeteer` directory can be used to simulate user interactions during tests. An example can be found with `countFeatureLinks.js`
|
||||
@@ -100,7 +100,8 @@ To get involved, email us at [tts-benefits-studio@gsa.gov](mailto:tts-benefits-s
|
||||
|
||||
## Notify.gov Demo
|
||||
|
||||
https://github.com/GSA/notifications-admin/assets/6556888/b87e12a3-6963-4fab-8124-7d0d2bb59901
|
||||
https://github.com/user-attachments/assets/e0bc4c61-6154-46b5-8b40-380ecd868b58
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
96
gulpfile.js
@@ -5,8 +5,8 @@ const rollupPluginNodeResolve = require('@rollup/plugin-node-resolve');
|
||||
const source = require('vinyl-source-stream');
|
||||
const buffer = require('vinyl-buffer');
|
||||
const gulpMerge = require('gulp-merge');
|
||||
const uswds = require("@uswds/compile");
|
||||
|
||||
const uswds = require('@uswds/compile');
|
||||
const { exec } = require('child_process');
|
||||
const plugins = {};
|
||||
plugins.addSrc = require('gulp-add-src');
|
||||
plugins.babel = require('gulp-babel');
|
||||
@@ -21,7 +21,7 @@ const paths = {
|
||||
dist: 'app/static/',
|
||||
npm: 'node_modules/',
|
||||
toolkit: 'node_modules/govuk_frontend_toolkit/',
|
||||
govuk_frontend: 'node_modules/govuk-frontend/'
|
||||
govuk_frontend: 'node_modules/govuk-frontend/',
|
||||
};
|
||||
|
||||
const javascripts = () => {
|
||||
@@ -29,28 +29,30 @@ const javascripts = () => {
|
||||
input: paths.src + 'javascripts/modules/all.mjs',
|
||||
plugins: [
|
||||
rollupPluginNodeResolve({
|
||||
mainFields: ['module', 'main']
|
||||
mainFields: ['module', 'main'],
|
||||
}),
|
||||
rollupPluginCommonjs({
|
||||
include: 'node_modules/**'
|
||||
})
|
||||
include: 'node_modules/**',
|
||||
}),
|
||||
],
|
||||
output: {
|
||||
format: 'iife',
|
||||
name: 'GOVUK'
|
||||
}
|
||||
name: 'GOVUK',
|
||||
},
|
||||
})
|
||||
.pipe(source('all.mjs'))
|
||||
.pipe(buffer())
|
||||
.pipe(plugins.addSrc.prepend([
|
||||
paths.npm + 'hogan.js/dist/hogan-3.0.2.js',
|
||||
paths.npm + 'jquery/dist/jquery.min.js',
|
||||
paths.npm + 'query-command-supported/dist/queryCommandSupported.min.js',
|
||||
paths.npm + 'timeago/jquery.timeago.js',
|
||||
paths.npm + 'textarea-caret/index.js',
|
||||
paths.npm + 'cbor-js/cbor.js',
|
||||
paths.npm + 'd3/dist/d3.min.js'
|
||||
]));
|
||||
.pipe(
|
||||
plugins.addSrc.prepend([
|
||||
paths.npm + 'hogan.js/dist/hogan-3.0.2.js',
|
||||
paths.npm + 'jquery/dist/jquery.min.js',
|
||||
paths.npm + 'query-command-supported/dist/queryCommandSupported.min.js',
|
||||
paths.npm + 'timeago/jquery.timeago.js',
|
||||
paths.npm + 'textarea-caret/index.js',
|
||||
paths.npm + 'cbor-js/cbor.js',
|
||||
paths.npm + 'd3/dist/d3.min.js',
|
||||
])
|
||||
);
|
||||
|
||||
const local = src([
|
||||
paths.toolkit + 'javascripts/govuk/modules.js',
|
||||
@@ -81,9 +83,11 @@ const javascripts = () => {
|
||||
paths.src + 'javascripts/activityChart.js',
|
||||
])
|
||||
.pipe(plugins.prettyerror())
|
||||
.pipe(plugins.babel({
|
||||
presets: ['@babel/preset-env']
|
||||
}));
|
||||
.pipe(
|
||||
plugins.babel({
|
||||
presets: ['@babel/preset-env'],
|
||||
})
|
||||
);
|
||||
|
||||
return gulpMerge(vendored, local)
|
||||
.pipe(plugins.uglify())
|
||||
@@ -93,17 +97,21 @@ const javascripts = () => {
|
||||
|
||||
// Task to copy `gtm_head.js`
|
||||
const copyGtmHead = () => {
|
||||
return src(paths.src + 'js/gtm_head.js')
|
||||
.pipe(dest(paths.dist + 'js/'));
|
||||
return src(paths.src + 'js/gtm_head.js').pipe(dest(paths.dist + 'js/'));
|
||||
};
|
||||
|
||||
// Task to copy `setTimezone.js`
|
||||
const copySetTimezone = () => {
|
||||
return src(paths.src + 'js/setTimezone.js').pipe(dest(paths.dist + 'js/'));
|
||||
};
|
||||
|
||||
// Task to copy images
|
||||
const copyImages = () => {
|
||||
return src(paths.src + 'images/**/*', { encoding: false })
|
||||
.pipe(dest(paths.dist + 'images/'));
|
||||
return src(paths.src + 'images/**/*', { encoding: false }).pipe(
|
||||
dest(paths.dist + 'images/')
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// Configure USWDS paths
|
||||
uswds.settings.version = 3;
|
||||
uswds.paths.dist.css = paths.dist + 'css';
|
||||
@@ -122,4 +130,40 @@ const copyAssets = async () => {
|
||||
await uswds.copyAssets();
|
||||
};
|
||||
|
||||
exports.default = series(styles, javascripts, copyGtmHead, copyImages, copyAssets);
|
||||
// Optional backstopJS task
|
||||
// Install gulp globally and run `gulp backstopTest`
|
||||
const backstopTest = (done) => {
|
||||
exec(
|
||||
'npx backstop test --configPath=backstop.config.js',
|
||||
(err, stdout, stderr) => {
|
||||
console.log(stdout);
|
||||
console.error(stderr);
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Optional backstopJS reference task
|
||||
// Install gulp globally and run `gulp backstopReference`
|
||||
const backstopReference = (done) => {
|
||||
exec(
|
||||
'npx backstop reference --configPath=backstop.config.js',
|
||||
(err, stdout, stderr) => {
|
||||
console.log(stdout);
|
||||
console.error(stderr);
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Export tasks
|
||||
exports.default = series(
|
||||
styles,
|
||||
javascripts,
|
||||
copyGtmHead,
|
||||
copySetTimezone,
|
||||
copyImages,
|
||||
copyAssets
|
||||
);
|
||||
exports.backstopTest = backstopTest;
|
||||
exports.backstopReference = backstopReference;
|
||||
|
||||
@@ -36,7 +36,6 @@ applications:
|
||||
API_HOST_NAME: https://notify-api-((env)).apps.internal:61443
|
||||
|
||||
# Credentials variables
|
||||
|
||||
ADMIN_CLIENT_SECRET: ((ADMIN_CLIENT_SECRET))
|
||||
ADMIN_CLIENT_USERNAME: ((ADMIN_CLIENT_USERNAME))
|
||||
DANGEROUS_SALT: ((DANGEROUS_SALT))
|
||||
@@ -60,3 +59,4 @@ applications:
|
||||
LOGIN_DOT_GOV_BASE_LOGOUT_URL: ((LOGIN_DOT_GOV_BASE_LOGOUT_URL))
|
||||
LOGIN_DOT_GOV_SIGNOUT_REDIRECT: ((LOGIN_DOT_GOV_SIGNOUT_REDIRECT))
|
||||
LOGIN_DOT_GOV_INITIAL_SIGNIN_URL: ((LOGIN_DOT_GOV_INITIAL_SIGNIN_URL))
|
||||
LOGIN_DOT_GOV_CERTS_URL: ((LOGIN_DOT_GOV_CERTS_URL))
|
||||
|
||||
@@ -37,7 +37,10 @@ def s3upload(
|
||||
aws_secret_access_key=secret_key,
|
||||
region_name=region,
|
||||
)
|
||||
_s3 = session.resource("s3", config=AWS_CLIENT_CONFIG)
|
||||
_s3 = session.resource(
|
||||
"s3",
|
||||
config=AWS_CLIENT_CONFIG,
|
||||
)
|
||||
# This 'proves' that use of moto in the relevant tests in test_send.py
|
||||
# mocks everything related to S3. What you will see in the logs is:
|
||||
# Exception: CREATED AT <MagicMock name='resource().Bucket().creation_date' id='4665562448'>
|
||||
|
||||