mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-24 08:16:51 -04:00
Compare commits
1 Commits
2282-p2-ho
...
sheevdave-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f384ba00bb |
2
.github/ISSUE_TEMPLATE/issue_template.yml
vendored
2
.github/ISSUE_TEMPLATE/issue_template.yml
vendored
@@ -68,7 +68,7 @@ body:
|
||||
attributes:
|
||||
value: '**Accessibility:**'
|
||||
- type: textarea
|
||||
id: accessibility
|
||||
id: notes
|
||||
attributes:
|
||||
label: "List any specific accessibility guidance or tests that need to be considered for this user story."
|
||||
description: "List what type of accessibility tests need to pass."
|
||||
|
||||
4
.github/pull_request_template.md
vendored
4
.github/pull_request_template.md
vendored
@@ -23,7 +23,5 @@ Please enter a detailed description here.
|
||||
|
||||
## A11y Checks (if applicable)
|
||||
|
||||
* Double check work is getting picked up by the automated E2E tests
|
||||
* Conduct browser-based tests through [AxeDevTools](https://www.deque.com/axe/devtools/) and [WAVE](https://wave.webaim.org/)
|
||||
* Conduct automated tests through [AxeDevTools](https://www.deque.com/axe/devtools/) and [WAVE](https://wave.webaim.org/)
|
||||
* Review the [Manual Checklist](https://docs.google.com/document/d/192bBXStebdXWtYhZQ73qaWMJhGcuSB1W6c9YBXhWZvc/edit?usp=sharing)
|
||||
* Make sure there are no linting errors in VSCode or other IDE of choice
|
||||
|
||||
3
.github/workflows/checks.yml
vendored
3
.github/workflows/checks.yml
vendored
@@ -165,9 +165,8 @@ jobs:
|
||||
run: make run-flask &
|
||||
env:
|
||||
NOTIFY_ENVIRONMENT: scanning
|
||||
FEATURE_ABOUT_PAGE_ENABLED: true
|
||||
- name: Run OWASP Baseline Scan
|
||||
uses: zaproxy/action-baseline@v0.14.0
|
||||
uses: zaproxy/action-baseline@v0.9.0
|
||||
with:
|
||||
docker_name: "ghcr.io/zaproxy/zaproxy:weekly"
|
||||
target: "http://localhost:6012"
|
||||
|
||||
2
.github/workflows/daily_checks.yml
vendored
2
.github/workflows/daily_checks.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
||||
env:
|
||||
NOTIFY_ENVIRONMENT: scanning
|
||||
- name: Run OWASP Full Scan
|
||||
uses: zaproxy/action-full-scan@v0.12.0
|
||||
uses: zaproxy/action-full-scan@v0.7.0
|
||||
with:
|
||||
docker_name: 'ghcr.io/zaproxy/zaproxy:weekly'
|
||||
target: 'http://localhost:6012'
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/static/images/favicon-16x16.png">
|
||||
<link rel="manifest" href="{{ asset_url('images/site.webmanifest') }}">
|
||||
|
||||
<link rel="stylesheet" media="screen" href="/static/stylesheets/legacy/uk.css?d077f86473501ab244de0b30600536ee" />
|
||||
<link rel="stylesheet" media="screen" href="/static/stylesheets/main.css?d077f86473501ab244de0b30600536ee" />
|
||||
<link rel="stylesheet" media="print" href="/static/stylesheets/print.css?28010888ca5719dc83d7c2c80f6ed2b2" />
|
||||
|
||||
<meta property="og:image" content="/static/images/notify-og-image.png">
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 474 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 28 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 362 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 292 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 373 KiB |
@@ -1,14 +1,11 @@
|
||||
(function (window) {
|
||||
|
||||
if (document.getElementById('activityChartContainer')) {
|
||||
let currentType = 'service';
|
||||
const tableContainer = document.getElementById('activityContainer');
|
||||
const currentUserName = tableContainer.getAttribute('data-currentUserName');
|
||||
const currentServiceId = tableContainer.getAttribute('data-currentServiceId');
|
||||
const COLORS = {
|
||||
delivered: '#0076d6',
|
||||
failed: '#fa9441',
|
||||
pending: '#C7CACE',
|
||||
text: '#666'
|
||||
};
|
||||
|
||||
@@ -16,7 +13,7 @@
|
||||
const FONT_WEIGHT = 'bold';
|
||||
const MAX_Y = 120;
|
||||
|
||||
const createChart = function(containerId, labels, deliveredData, failedData, pendingData) {
|
||||
const createChart = function(containerId, labels, deliveredData, failedData) {
|
||||
const container = d3.select(containerId);
|
||||
container.selectAll('*').remove(); // Clear any existing content
|
||||
|
||||
@@ -39,7 +36,7 @@
|
||||
}
|
||||
|
||||
// Calculate total messages
|
||||
const totalMessages = d3.sum(deliveredData) + d3.sum(failedData) + d3.sum(pendingData);
|
||||
const totalMessages = d3.sum(deliveredData) + d3.sum(failedData);
|
||||
|
||||
// Create legend only if there are messages
|
||||
const legendContainer = d3.select('.chart-legend');
|
||||
@@ -49,8 +46,7 @@
|
||||
// Show legend if there are messages
|
||||
const legendData = [
|
||||
{ label: 'Delivered', color: COLORS.delivered },
|
||||
{ label: 'Failed', color: COLORS.failed },
|
||||
{ label: 'Pending', color: COLORS.pending }
|
||||
{ label: 'Failed', color: COLORS.failed }
|
||||
];
|
||||
|
||||
const legendItem = legendContainer.selectAll('.legend-item')
|
||||
@@ -81,9 +77,8 @@
|
||||
.range([0, width])
|
||||
.padding(0.1);
|
||||
// Adjust the y-axis domain to add some space above the tallest bar
|
||||
const maxY = d3.max(deliveredData.map((d, i) => d + (failedData[i] || 0) + (pendingData[i] || 0)));
|
||||
|
||||
const y = d3.scaleSymlog()
|
||||
const maxY = d3.max(deliveredData.map((d, i) => d + (failedData[i] || 0)));
|
||||
const y = d3.scaleSqrt()
|
||||
.domain([0, maxY + 2]) // Add 2 units of space at the top
|
||||
.nice()
|
||||
.range([height, 0]);
|
||||
@@ -95,7 +90,7 @@
|
||||
|
||||
// Generate the y-axis with whole numbers
|
||||
const yAxis = d3.axisLeft(y)
|
||||
.ticks(Math.min(maxY + 2, 3))
|
||||
.ticks(Math.min(maxY + 2, 10)) // Generate up to 10 ticks based on the data
|
||||
.tickFormat(d3.format('d')); // Ensure whole numbers on the y-axis
|
||||
|
||||
svg.append('g')
|
||||
@@ -106,13 +101,12 @@
|
||||
const stackData = labels.map((label, i) => ({
|
||||
label: label,
|
||||
delivered: deliveredData[i],
|
||||
failed: failedData[i] || 0,
|
||||
pending: pendingData[i] || 0
|
||||
failed: failedData[i] || 0 // Ensure there's a value for failed, even if it's 0
|
||||
}));
|
||||
|
||||
// Stack the data
|
||||
const stack = d3.stack()
|
||||
.keys(['delivered', 'failed', 'pending'])
|
||||
.keys(['delivered', 'failed'])
|
||||
.order(d3.stackOrderNone)
|
||||
.offset(d3.stackOffsetNone);
|
||||
|
||||
@@ -120,8 +114,8 @@
|
||||
|
||||
// Color scale
|
||||
const color = d3.scaleOrdinal()
|
||||
.domain(['delivered', 'failed', 'pending'])
|
||||
.range([COLORS.delivered, COLORS.failed, COLORS.pending]);
|
||||
.domain(['delivered', 'failed'])
|
||||
.range([COLORS.delivered, COLORS.failed]);
|
||||
|
||||
// Create bars with animation
|
||||
const barGroups = svg.selectAll('.bar-group')
|
||||
@@ -159,7 +153,7 @@
|
||||
};
|
||||
|
||||
// Function to create an accessible table
|
||||
const createTable = function(tableId, chartType, labels, deliveredData, failedData, pendingData) {
|
||||
const createTable = function(tableId, chartType, labels, deliveredData, failedData) {
|
||||
const table = document.getElementById(tableId);
|
||||
table.innerHTML = ""; // Clear previous data
|
||||
|
||||
@@ -171,7 +165,7 @@
|
||||
|
||||
// Create table header
|
||||
const headerRow = document.createElement('tr');
|
||||
const headers = ['Day', 'Delivered', 'Failed', 'Pending'];
|
||||
const headers = ['Day', 'Delivered', 'Failed'];
|
||||
headers.forEach(headerText => {
|
||||
const th = document.createElement('th');
|
||||
th.textContent = headerText;
|
||||
@@ -194,10 +188,6 @@
|
||||
cellFailed.textContent = failedData[index];
|
||||
row.appendChild(cellFailed);
|
||||
|
||||
const cellPending = document.createElement('td');
|
||||
cellPending.textContent = pendingData[index];
|
||||
row.appendChild(cellPending);
|
||||
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
@@ -207,13 +197,12 @@
|
||||
};
|
||||
|
||||
const fetchData = function(type) {
|
||||
|
||||
var ctx = document.getElementById('weeklyChart');
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
var url = type === 'service' ? `/services/${currentServiceId}/daily-stats.json` : `/services/${currentServiceId}/daily-stats-by-user.json`;
|
||||
var url = type === 'service' ? `/daily_stats.json` : `/daily_stats_by_user.json`;
|
||||
return fetch(url)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
@@ -225,7 +214,7 @@
|
||||
labels = [];
|
||||
deliveredData = [];
|
||||
failedData = [];
|
||||
pendingData = [];
|
||||
|
||||
let totalMessages = 0;
|
||||
|
||||
for (var dateString in data) {
|
||||
@@ -236,8 +225,6 @@
|
||||
labels.push(formattedDate);
|
||||
deliveredData.push(data[dateString].sms.delivered);
|
||||
failedData.push(data[dateString].sms.failure);
|
||||
pendingData.push(data[dateString].sms.pending || 0);
|
||||
totalMessages += data[dateString].sms.delivered + data[dateString].sms.failure + data[dateString].sms.pending;
|
||||
|
||||
// Calculate the total number of messages
|
||||
totalMessages += data[dateString].sms.delivered + data[dateString].sms.failure;
|
||||
@@ -266,18 +253,17 @@
|
||||
}
|
||||
} else {
|
||||
// If there are messages, create the chart and table
|
||||
createChart('#weeklyChart', labels, deliveredData, failedData, pendingData);
|
||||
createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData, pendingData);
|
||||
}
|
||||
createChart('#weeklyChart', labels, deliveredData, failedData);
|
||||
createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData);
|
||||
}
|
||||
|
||||
return data;
|
||||
})
|
||||
.catch(error => console.error('Error fetching daily stats:', error));
|
||||
};
|
||||
|
||||
return data;
|
||||
})
|
||||
.catch(error => console.error('Error fetching daily stats:', error));
|
||||
};
|
||||
setInterval(() => fetchData(currentType), 25000);
|
||||
const handleDropdownChange = function(event) {
|
||||
const selectedValue = event.target.value;
|
||||
currentType = selectedValue;
|
||||
const subTitle = document.querySelector(`#activityChartContainer .chart-subtitle`);
|
||||
const selectElement = document.getElementById('options');
|
||||
const selectedText = selectElement.options[selectElement.selectedIndex].text;
|
||||
@@ -330,7 +316,7 @@
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize activityChart chart and table with service data by default
|
||||
fetchData(currentType);
|
||||
fetchData('service');
|
||||
|
||||
const allRows = Array.from(document.querySelectorAll('#activity-table tbody tr'));
|
||||
allRows.forEach((row, index) => {
|
||||
@@ -343,9 +329,9 @@
|
||||
|
||||
// Resize chart on window resize
|
||||
window.addEventListener('resize', function() {
|
||||
if (labels.length > 0 && deliveredData.length > 0 && failedData.length > 0 && pendingData.length > 0) {
|
||||
createChart('#weeklyChart', labels, deliveredData, failedData, pendingData);
|
||||
createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData, pendingData);
|
||||
if (labels.length > 0 && deliveredData.length > 0 && failedData.length > 0) {
|
||||
createChart('#weeklyChart', labels, deliveredData, failedData);
|
||||
createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,186 +0,0 @@
|
||||
@use "uswds-core" as *;
|
||||
|
||||
.dashboard {
|
||||
.big-number-with-status {
|
||||
.big-number-smaller {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.big-number-number {
|
||||
font-size: units(5);
|
||||
line-height: units(5);
|
||||
}
|
||||
.big-number-label {
|
||||
font-size: units(2.5);
|
||||
}
|
||||
}
|
||||
.big-number-status {
|
||||
background: color('green-cool-40v');
|
||||
display: flex;
|
||||
padding: units(1) units(2);
|
||||
&--failing {
|
||||
padding: 0;
|
||||
a.usa-link {
|
||||
color: white;
|
||||
background: color('red-warm-50v');
|
||||
padding: units(1) units(2);
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
&:hover {
|
||||
background: color('red-warm-60v');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.usa-table {
|
||||
width: 100%;
|
||||
caption {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.table-field-center-aligned {
|
||||
text-align: center;
|
||||
}
|
||||
.template-statistics-table-template-name {
|
||||
padding-left: units(4);
|
||||
display: inline-block;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url(../img/material-icons/description.svg);
|
||||
}
|
||||
}
|
||||
.get-started {
|
||||
border: 1px solid color('gray-90');
|
||||
padding: units(2);
|
||||
margin-bottom: units(4);
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-table {
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
.file-list-filename {
|
||||
font-weight: bold;
|
||||
}
|
||||
.file-list-hint {
|
||||
margin: 0;
|
||||
word-break: break-word;
|
||||
}
|
||||
.table-field,
|
||||
.table-field-right-aligned {
|
||||
width: 50%;
|
||||
}
|
||||
&.usage-table {
|
||||
.table-field,
|
||||
.table-field-left-aligned,
|
||||
.table-field-right-aligned {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.job-status-table {
|
||||
table-layout: fixed;
|
||||
|
||||
thead tr th {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
tr {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
th:first-child,
|
||||
td:first-child {
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
th:nth-child(2),
|
||||
R td:nth-child(2) {
|
||||
width: 25%;
|
||||
}
|
||||
}
|
||||
|
||||
.usage-table {
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.big-number-smallest {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.job-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
td.file-name {
|
||||
width: 25%;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
td.jobid {
|
||||
width: 5%;
|
||||
}
|
||||
td.template {
|
||||
width: 25%;
|
||||
}
|
||||
td.time-sent {
|
||||
width: 15%;
|
||||
}
|
||||
td.sender {
|
||||
width: 20%;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
td.count-of-recipients {
|
||||
width: 5%;
|
||||
}
|
||||
td.report {
|
||||
width: 2%;
|
||||
text-align: center;
|
||||
}
|
||||
td.delivered {
|
||||
width: 2%;
|
||||
text-align: center;
|
||||
}
|
||||
td.failed {
|
||||
width: 2%;
|
||||
text-align: center;
|
||||
}
|
||||
td.report img {
|
||||
padding-top: 5px;
|
||||
}
|
||||
th {
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.usa-table-container--scrollable-mobile {
|
||||
margin: 0;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.usa-table th[data-sortable][aria-sort='ascending'],
|
||||
.usa-table th[data-sortable][aria-sort='descending'] {
|
||||
background-color: #a1d3ff;
|
||||
}
|
||||
|
||||
#template-list {
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
padding: units(1) 0 units(1) units(1);
|
||||
margin: units(2) 0 units(5);
|
||||
ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
}
|
||||
|
||||
.usa-prose > p.max-width-full {
|
||||
max-width: 100%;
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
@use 'uswds-core' as *;
|
||||
|
||||
$do-dont-color-do: 'green-cool-50v';
|
||||
$do-dont-color-dont: 'red-cool-50v';
|
||||
$do-dont-color-border: 'gray-cool-20';
|
||||
$do-dont-padding: 3;
|
||||
$do-dont-top-bar-width: 1;
|
||||
|
||||
.do-dont {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding-top: units(2);
|
||||
}
|
||||
|
||||
.do-dont__do,
|
||||
.do-dont__dont {
|
||||
background-color: color('white');
|
||||
border: 1px solid color($do-dont-color-border);
|
||||
flex: 1 0 0;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.do-dont__do {
|
||||
padding: 0rem;
|
||||
}
|
||||
|
||||
.do-dont__do:before,
|
||||
.do-dont__dont:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: units($do-dont-top-bar-width);
|
||||
width: calc(100% + 2px);
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
}
|
||||
|
||||
.do-dont__do:before {
|
||||
background-color: color($do-dont-color-do);
|
||||
}
|
||||
|
||||
.do-dont__dont:before {
|
||||
background-color: color($do-dont-color-dont);
|
||||
}
|
||||
|
||||
.do-dont__heading {
|
||||
// @include typeset("lang", "lg", 2);
|
||||
align-items: center;
|
||||
border-bottom: 1px solid color($do-dont-color-border);
|
||||
display: flex;
|
||||
font-weight: fw('bold');
|
||||
margin: 0;
|
||||
padding: units(4) units($do-dont-padding) units($do-dont-padding);
|
||||
|
||||
.usa-icon {
|
||||
@include u-square(3);
|
||||
margin-right: units(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.do-dont__do .do-dont__heading {
|
||||
color: color($do-dont-color-do);
|
||||
}
|
||||
|
||||
.do-dont__dont .do-dont__heading {
|
||||
color: color($do-dont-color-dont);
|
||||
}
|
||||
|
||||
.do-dont__content {
|
||||
padding: 0 units($do-dont-padding) units($do-dont-padding)
|
||||
units($do-dont-padding);
|
||||
|
||||
ul {
|
||||
margin-bottom: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
li + li {
|
||||
margin-top: units(1);
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
@use "uswds-core" as *;
|
||||
@use "tokens" as vars;
|
||||
|
||||
.usa-section--dark {
|
||||
background-color: color('gray-90');
|
||||
h1 {
|
||||
font-family: family('sans');
|
||||
font-size: units(7);
|
||||
color: vars.$notify-light-blue;
|
||||
@media (max-width: units('desktop')) {
|
||||
font-size: units(5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.usa-hero > .grid-container {
|
||||
@media (max-width: units('tablet')) {
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.usa-section {
|
||||
&.key-features {
|
||||
background-color: color('gray-cool-2');
|
||||
}
|
||||
&.security-and-privacy {
|
||||
img {
|
||||
filter: brightness(0) saturate(100%) invert(22%) sepia(98%) saturate(1829%) hue-rotate(187deg) brightness(88%) contrast(101%);
|
||||
}
|
||||
}
|
||||
&__home {
|
||||
h2 {
|
||||
font-size: units(6);
|
||||
line-height: 1.2;
|
||||
@media (max-width: units('desktop')) {
|
||||
font-size: units(4);
|
||||
}
|
||||
}
|
||||
p {
|
||||
font-size: 20px;
|
||||
}
|
||||
img {
|
||||
@media (max-width: units('tablet')) {
|
||||
max-width: 60%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.home-cards {
|
||||
justify-content: space-between;
|
||||
.usa-card__container {
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
.img-container {
|
||||
width: units('card');
|
||||
height: units('card');
|
||||
margin: units(3) 0;
|
||||
border-radius: 50%;
|
||||
background: color('gray-cool-10');
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
img {
|
||||
width: units(10);
|
||||
height: units(10);
|
||||
filter: brightness(0) saturate(100%) invert(22%) sepia(98%) saturate(1829%) hue-rotate(187deg) brightness(88%) contrast(101%);
|
||||
}
|
||||
}
|
||||
h3, p {
|
||||
font-size: units(3);
|
||||
line-height: 1.3;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
@use "uswds-core" as *;
|
||||
|
||||
// Tabs
|
||||
|
||||
.tabs {
|
||||
.pill {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
.pill-item__container {
|
||||
border: 1px solid color('gray-cool-10');
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
font-size: units(2);
|
||||
a {
|
||||
padding: units(4);
|
||||
.big-number-smaller {
|
||||
font-size: units(5);
|
||||
line-height: units(6);
|
||||
}
|
||||
.big-number-smallest {
|
||||
font-size: units(3);
|
||||
}
|
||||
&:not(.pill-item--selected):hover {
|
||||
background: color('blue-warm-70v');
|
||||
}
|
||||
&.pill-item--selected:hover {
|
||||
color: color('blue-60v');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
@use "uswds-core" as *;
|
||||
|
||||
// Colors
|
||||
$notify-primary: color('blue-60v');
|
||||
$notify-secondary: color('red-warm-50v');
|
||||
$notify-light-blue: color('blue-40v');
|
||||
$notify-tertiary: color('gold-20v');
|
||||
$notify-text: color('ink');
|
||||
@@ -1,3 +1,25 @@
|
||||
/*
|
||||
* * * * * ==============================
|
||||
* * * * * ==============================
|
||||
* * * * * ==============================
|
||||
* * * * * ==============================
|
||||
========================================
|
||||
========================================
|
||||
========================================
|
||||
----------------------------------------
|
||||
USWDS THEME CUSTOM STYLES
|
||||
----------------------------------------
|
||||
!! Copy this file to your project's
|
||||
sass root. Don't edit the version
|
||||
in node_modules.
|
||||
----------------------------------------
|
||||
Custom project SASS goes here.
|
||||
|
||||
i.e.
|
||||
@include u-padding-right('05');
|
||||
----------------------------------------
|
||||
*/
|
||||
|
||||
@use 'uswds-core' as *;
|
||||
|
||||
iframe:focus,
|
||||
@@ -34,10 +56,7 @@ button:not([disabled]):focus {
|
||||
}
|
||||
}
|
||||
@include at-media-max('desktop') {
|
||||
.usa-nav__secondary-links
|
||||
{
|
||||
padding-left: units(2);
|
||||
}
|
||||
padding: 0 units(2);
|
||||
ul li {
|
||||
padding-bottom: units(1);
|
||||
}
|
||||
@@ -141,17 +160,9 @@ td.table-empty-message {
|
||||
}
|
||||
}
|
||||
|
||||
.login-button {
|
||||
margin-right: 0;
|
||||
img {
|
||||
height: 1rem;
|
||||
}
|
||||
@media (max-width: units('desktop')) {
|
||||
img {
|
||||
height: 14px;
|
||||
}
|
||||
font-size: 14px;
|
||||
}
|
||||
.usa-button img {
|
||||
margin-left: 0.5rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.usa-button.login-button.login-button--primary,
|
||||
@@ -354,6 +365,227 @@ td.table-empty-message {
|
||||
}
|
||||
}
|
||||
|
||||
// Dashboard
|
||||
|
||||
.dashboard {
|
||||
.big-number-with-status {
|
||||
.big-number-smaller {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.big-number-number {
|
||||
font-size: units(5);
|
||||
line-height: units(5);
|
||||
}
|
||||
.big-number-label {
|
||||
font-size: units(2.5);
|
||||
}
|
||||
}
|
||||
.big-number-status {
|
||||
background: color('green-cool-40v');
|
||||
display: flex;
|
||||
padding: units(1) units(2);
|
||||
&--failing {
|
||||
padding: 0;
|
||||
a.usa-link {
|
||||
color: white;
|
||||
background: color('red-warm-50v');
|
||||
padding: units(1) units(2);
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
&:hover {
|
||||
background: color('red-warm-60v');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.usa-table {
|
||||
width: 100%;
|
||||
caption {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.table-field-center-aligned {
|
||||
text-align: center;
|
||||
}
|
||||
.template-statistics-table-template-name {
|
||||
padding-left: units(4);
|
||||
display: inline-block;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url(../img/material-icons/description.svg);
|
||||
}
|
||||
}
|
||||
.get-started {
|
||||
border: 1px solid color('gray-90');
|
||||
padding: units(2);
|
||||
margin-bottom: units(4);
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-table {
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
.file-list-filename {
|
||||
font-weight: bold;
|
||||
}
|
||||
.file-list-hint {
|
||||
margin: 0;
|
||||
word-break: break-word;
|
||||
}
|
||||
.table-field,
|
||||
.table-field-right-aligned {
|
||||
width: 50%;
|
||||
}
|
||||
&.usage-table {
|
||||
.table-field,
|
||||
.table-field-left-aligned,
|
||||
.table-field-right-aligned {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.job-status-table {
|
||||
table-layout: fixed;
|
||||
|
||||
thead tr th {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
tr {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
th:first-child,
|
||||
td:first-child {
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
th:nth-child(2),
|
||||
R td:nth-child(2) {
|
||||
width: 25%;
|
||||
}
|
||||
}
|
||||
|
||||
.usage-table {
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.big-number-smallest {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.job-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
td.file-name {
|
||||
width: 25%;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
td.jobid {
|
||||
width: 5%;
|
||||
}
|
||||
td.template {
|
||||
width: 25%;
|
||||
}
|
||||
td.time-sent {
|
||||
width: 15%;
|
||||
}
|
||||
td.sender {
|
||||
width: 20%;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
td.count-of-recipients {
|
||||
width: 5%;
|
||||
}
|
||||
td.report {
|
||||
width: 2%;
|
||||
text-align: center;
|
||||
}
|
||||
td.delivered {
|
||||
width: 2%;
|
||||
text-align: center;
|
||||
}
|
||||
td.failed {
|
||||
width: 2%;
|
||||
text-align: center;
|
||||
}
|
||||
td.report img {
|
||||
padding-top: 5px;
|
||||
}
|
||||
th {
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.usa-table-container--scrollable-mobile {
|
||||
margin: 0;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.usa-table th[data-sortable][aria-sort='ascending'],
|
||||
.usa-table th[data-sortable][aria-sort='descending'] {
|
||||
background-color: #a1d3ff;
|
||||
}
|
||||
|
||||
#template-list {
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
padding: units(1) 0 units(1) units(1);
|
||||
margin: units(2) 0 units(5);
|
||||
ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
}
|
||||
|
||||
.usa-prose > p.max-width-full {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
// Tabs
|
||||
|
||||
.tabs {
|
||||
.pill {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
.pill-item__container {
|
||||
border: 1px solid color('gray-cool-10');
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
font-size: units(2);
|
||||
a {
|
||||
padding: units(4);
|
||||
.big-number-smaller {
|
||||
font-size: units(5);
|
||||
line-height: units(6);
|
||||
}
|
||||
.big-number-smallest {
|
||||
font-size: units(3);
|
||||
}
|
||||
&:not(.pill-item--selected):hover {
|
||||
background: color('blue-warm-70v');
|
||||
}
|
||||
&.pill-item--selected:hover {
|
||||
color: color('blue-60v');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Etc
|
||||
|
||||
.email-brand,
|
||||
@@ -515,6 +747,86 @@ div.guides {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
$do-dont-color-do: 'green-cool-50v';
|
||||
$do-dont-color-dont: 'red-cool-50v';
|
||||
$do-dont-color-border: 'gray-cool-20';
|
||||
$do-dont-padding: 3;
|
||||
$do-dont-top-bar-width: 1;
|
||||
|
||||
.do-dont {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding-top: units(2);
|
||||
}
|
||||
|
||||
.do-dont__do,
|
||||
.do-dont__dont {
|
||||
background-color: color('white');
|
||||
border: 1px solid color($do-dont-color-border);
|
||||
flex: 1 0 0;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.do-dont__do {
|
||||
padding: 0rem;
|
||||
}
|
||||
|
||||
.do-dont__do:before,
|
||||
.do-dont__dont:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: units($do-dont-top-bar-width);
|
||||
width: calc(100% + 2px);
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
}
|
||||
|
||||
.do-dont__do:before {
|
||||
background-color: color($do-dont-color-do);
|
||||
}
|
||||
|
||||
.do-dont__dont:before {
|
||||
background-color: color($do-dont-color-dont);
|
||||
}
|
||||
|
||||
.do-dont__heading {
|
||||
// @include typeset("lang", "lg", 2);
|
||||
align-items: center;
|
||||
border-bottom: 1px solid color($do-dont-color-border);
|
||||
display: flex;
|
||||
font-weight: fw('bold');
|
||||
margin: 0;
|
||||
padding: units(4) units($do-dont-padding) units($do-dont-padding);
|
||||
|
||||
.usa-icon {
|
||||
@include u-square(3);
|
||||
margin-right: units(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.do-dont__do .do-dont__heading {
|
||||
color: color($do-dont-color-do);
|
||||
}
|
||||
|
||||
.do-dont__dont .do-dont__heading {
|
||||
color: color($do-dont-color-dont);
|
||||
}
|
||||
|
||||
.do-dont__content {
|
||||
padding: 0 units($do-dont-padding) units($do-dont-padding)
|
||||
units($do-dont-padding);
|
||||
|
||||
ul {
|
||||
margin-bottom: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
li + li {
|
||||
margin-top: units(1);
|
||||
}
|
||||
}
|
||||
|
||||
.site-note {
|
||||
background-color: #dfe1e2;
|
||||
max-width: 72ex;
|
||||
@@ -659,6 +971,32 @@ nav.nav {
|
||||
}
|
||||
}
|
||||
|
||||
.home-cards {
|
||||
justify-content: space-between;
|
||||
.usa-card__container {
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
img {
|
||||
margin: units(4) auto 0;
|
||||
width: units(15);
|
||||
height: units(15);
|
||||
}
|
||||
.usa-card__body {
|
||||
margin-bottom: units(2);
|
||||
}
|
||||
.blue-bar {
|
||||
background-color: #005eb8;
|
||||
height: 1.3em;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.contact-us-card {
|
||||
border: 2px solid color("ink");
|
||||
@@ -686,7 +1024,3 @@ nav.nav {
|
||||
font-size: units(3);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-control-error {
|
||||
border: 4px solid #b10e1e
|
||||
}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
@forward "uswds-theme";
|
||||
@forward "uswds";
|
||||
@forward "uswds-theme-custom-styles";
|
||||
@use "tokens" as vars;
|
||||
@forward "legacy-styles";
|
||||
@forward "home";
|
||||
@forward "dashboard";
|
||||
@forward "tabs";
|
||||
@forward "do-dont";
|
||||
@forward "data-visualization";
|
||||
|
||||
90
app/assets/stylesheets/main.scss
Normal file
90
app/assets/stylesheets/main.scss
Normal file
@@ -0,0 +1,90 @@
|
||||
// Path to assets for use with file-url()
|
||||
$path: '/static/images/';
|
||||
|
||||
// Dependencies from GOV.UK Frontend Toolkit
|
||||
// https://github.com/alphagov/govuk_frontend_toolkit/
|
||||
@import 'conditionals';
|
||||
@import 'shims';
|
||||
@import 'measurements';
|
||||
@import 'css3';
|
||||
@import 'colours';
|
||||
@import 'typography';
|
||||
@import 'grid_layout';
|
||||
@import 'helpers';
|
||||
|
||||
// Dependencies from GOVU.UK Frontend Toolkit, rewritten for this application
|
||||
@import 'url-helpers';
|
||||
|
||||
// Specific to this application, needs to go at the top of the cascade
|
||||
@import 'globals';
|
||||
|
||||
// Dependencies from GOV.UK Elements
|
||||
// https://github.com/alphagov/govuk_elements
|
||||
@import 'elements/helpers';
|
||||
@import 'elements/reset';
|
||||
@import 'elements/details';
|
||||
@import 'elements/elements-typography';
|
||||
@import 'elements/forms';
|
||||
@import 'elements/forms/form-multiple-choice';
|
||||
@import 'elements/forms/form-validation';
|
||||
@import 'elements/lists';
|
||||
@import 'elements/panels';
|
||||
@import 'elements/tables';
|
||||
|
||||
// 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';
|
||||
@import 'components/site-footer';
|
||||
@import 'components/placeholder';
|
||||
@import 'components/sms-message';
|
||||
@import 'components/page-footer';
|
||||
@import 'components/table';
|
||||
@import 'components/navigation';
|
||||
@import 'components/big-number';
|
||||
@import 'components/banner';
|
||||
@import 'components/textbox';
|
||||
@import 'components/file-upload';
|
||||
@import 'components/browse-list';
|
||||
@import 'components/email-message';
|
||||
@import 'components/copy-to-clipboard';
|
||||
@import 'components/vendor/previous-next-navigation';
|
||||
@import 'components/radios';
|
||||
@import 'components/checkboxes';
|
||||
@import 'components/pill';
|
||||
@import 'components/show-more';
|
||||
@import 'components/message';
|
||||
@import 'components/research-mode';
|
||||
@import 'components/tick-cross';
|
||||
@import 'components/list-entry';
|
||||
@import 'components/live-search';
|
||||
@import 'components/stick-at-top-when-scrolling';
|
||||
@import 'components/fullscreen-table';
|
||||
@import 'components/conditional-radios';
|
||||
@import 'components/vendor/breadcrumbs';
|
||||
@import 'components/vendor/responsive-embed';
|
||||
@import 'components/preview-pane';
|
||||
@import 'components/task-list';
|
||||
@import 'components/loading-indicator';
|
||||
@import 'components/area-list';
|
||||
@import 'components/content-metadata';
|
||||
|
||||
@import 'views/dashboard';
|
||||
@import 'views/users';
|
||||
@import 'views/api';
|
||||
@import 'views/product-page';
|
||||
@import 'views/template';
|
||||
@import 'views/notification';
|
||||
@import 'views/send';
|
||||
@import 'views/get_started';
|
||||
@import 'views/history';
|
||||
|
||||
// TODO: break this up
|
||||
@import 'app';
|
||||
@@ -76,24 +76,25 @@ def service_dashboard(service_id):
|
||||
)
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/daily-stats.json")
|
||||
@user_has_permissions()
|
||||
def get_daily_stats(service_id):
|
||||
@main.route("/daily_stats.json")
|
||||
def get_daily_stats():
|
||||
service_id = session.get("service_id")
|
||||
date_range = get_stats_date_range()
|
||||
|
||||
stats = service_api_client.get_service_notification_statistics_by_day(
|
||||
service_id, start_date=date_range["start_date"], days=date_range["days"]
|
||||
)
|
||||
return jsonify(stats)
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/daily-stats-by-user.json")
|
||||
@user_has_permissions()
|
||||
def get_daily_stats_by_user(service_id):
|
||||
@main.route("/daily_stats_by_user.json")
|
||||
def get_daily_stats_by_user():
|
||||
service_id = session.get("service_id")
|
||||
date_range = get_stats_date_range()
|
||||
user_id = current_user.id
|
||||
stats = service_api_client.get_user_service_notification_statistics_by_day(
|
||||
service_id,
|
||||
user_id=current_user.id,
|
||||
user_id,
|
||||
start_date=date_range["start_date"],
|
||||
days=date_range["days"],
|
||||
)
|
||||
|
||||
@@ -401,9 +401,7 @@ def get_job_partials(job):
|
||||
)
|
||||
|
||||
if request.referrer is not None:
|
||||
session["arrived_from_preview_page"] = ("check" in request.referrer) or (
|
||||
"help=0" in request.referrer
|
||||
)
|
||||
session["arrived_from_preview_page"] = ("check" in request.referrer) or ("help=0" in request.referrer)
|
||||
else:
|
||||
session["arrived_from_preview_page"] = False
|
||||
|
||||
|
||||
@@ -6,36 +6,17 @@ from app.notify_client import NotifyAdminAPIClient
|
||||
|
||||
class BillingAPIClient(NotifyAdminAPIClient):
|
||||
def get_monthly_usage_for_service(self, service_id, year):
|
||||
monthly_usage = redis_client.get(f"monthly-usage-summary-{service_id}-{year}")
|
||||
if monthly_usage is not None:
|
||||
return json.loads(monthly_usage.decode("utf-8"))
|
||||
result = self.get(
|
||||
return self.get(
|
||||
"/service/{0}/billing/monthly-usage".format(service_id),
|
||||
params=dict(year=year),
|
||||
)
|
||||
redis_client.set(
|
||||
f"monthly-usage-summary-{service_id}-{year}",
|
||||
json.dumps(result),
|
||||
ex=30,
|
||||
)
|
||||
return result
|
||||
|
||||
def get_annual_usage_for_service(self, service_id, year=None):
|
||||
annual_usage = redis_client.get(f"yearly-usage-summary-{service_id}-{year}")
|
||||
if annual_usage is not None:
|
||||
return json.loads(annual_usage.decode("utf-8"))
|
||||
result = self.get(
|
||||
return self.get(
|
||||
"/service/{0}/billing/yearly-usage-summary".format(service_id),
|
||||
params=dict(year=year),
|
||||
)
|
||||
|
||||
redis_client.set(
|
||||
f"yearly-usage-summary-{service_id}-{year}",
|
||||
json.dumps(result),
|
||||
ex=30,
|
||||
)
|
||||
return result
|
||||
|
||||
def get_free_sms_fragment_limit_for_year(self, service_id, year=None):
|
||||
frag_limit = redis_client.get(f"free-sms-fragment-limit-{service_id}-{year}")
|
||||
if frag_limit is not None:
|
||||
@@ -67,28 +48,13 @@ class BillingAPIClient(NotifyAdminAPIClient):
|
||||
)
|
||||
|
||||
def get_data_for_billing_report(self, start_date, end_date):
|
||||
x_start_date = str(start_date)
|
||||
x_start_date = x_start_date.replace(" ", "_")
|
||||
x_end_date = str(end_date)
|
||||
x_end_date = x_end_date.replace(" ", "_")
|
||||
billing_data = redis_client.get(
|
||||
f"get-data-for-billing-report-{x_start_date}-{x_end_date}"
|
||||
)
|
||||
if billing_data is not None:
|
||||
return json.loads(billing_data.decode("utf-8"))
|
||||
result = self.get(
|
||||
return self.get(
|
||||
url="/platform-stats/data-for-billing-report",
|
||||
params={
|
||||
"start_date": str(start_date),
|
||||
"end_date": str(end_date),
|
||||
},
|
||||
)
|
||||
redis_client.set(
|
||||
f"get-data-for-billing-report-{x_start_date}-{x_end_date}",
|
||||
json.dumps(result),
|
||||
ex=30,
|
||||
)
|
||||
return result
|
||||
|
||||
def get_data_for_volumes_by_service_report(self, start_date, end_date):
|
||||
return self.get(
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import json
|
||||
|
||||
from app.extensions import redis_client
|
||||
from app.notify_client import NotifyAdminAPIClient, _attach_current_user
|
||||
|
||||
|
||||
@@ -44,7 +41,7 @@ class NotificationApiClient(NotifyAdminAPIClient):
|
||||
if job_id:
|
||||
return method(
|
||||
url="/service/{}/job/{}/notifications".format(service_id, job_id),
|
||||
**kwargs,
|
||||
**kwargs
|
||||
)
|
||||
else:
|
||||
if limit_days is not None:
|
||||
@@ -99,20 +96,9 @@ class NotificationApiClient(NotifyAdminAPIClient):
|
||||
)
|
||||
|
||||
def get_notification_count_for_job_id(self, *, service_id, job_id):
|
||||
counts = redis_client.get(
|
||||
f"notification-count-for-job-id-{service_id}-{job_id}"
|
||||
)
|
||||
if counts is not None:
|
||||
return json.loads(counts.decode("utf-8"))
|
||||
result = self.get(
|
||||
return self.get(
|
||||
url="/service/{}/job/{}/notification_count".format(service_id, job_id)
|
||||
)
|
||||
redis_client.set(
|
||||
f"notification-count-for-job-id-{service_id}-{job_id}",
|
||||
json.dumps(result["count"]),
|
||||
ex=30,
|
||||
)
|
||||
return result["count"]
|
||||
)["count"]
|
||||
|
||||
|
||||
notification_api_client = NotificationApiClient()
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
attributes: params.errorMessage.attributes,
|
||||
html: params.errorMessage.html,
|
||||
text: params.errorMessage.text,
|
||||
visuallyHiddenText: params.errorMessage.visuallyHiddenText,
|
||||
visuallyHiddenText: params.errorMessage.visuallyHiddenText
|
||||
}) | indent(2) | trim }}
|
||||
{% endif %}
|
||||
<input class="usa-input {%- if params.classes %} {{ params.classes }}{% endif %} {%- if params.errorMessage %} usa-input--error{% endif %}" id="{{ params.id }}" name="{{ params.name }}" type="{{ params.type | default('text') }}"
|
||||
@@ -42,7 +42,5 @@
|
||||
{%- if describedBy %} aria-describedby="{{ describedBy }}"{% endif %}
|
||||
{%- if params.autocomplete %} autocomplete="{{ params.autocomplete}}"{% endif %}
|
||||
{%- if params.pattern %} pattern="{{ params.pattern }}"{% endif %}
|
||||
{%- for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor -%}
|
||||
{%- if params.required %} required{% endif %}
|
||||
/>
|
||||
{%- for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor -%}>
|
||||
</div>
|
||||
|
||||
@@ -39,6 +39,12 @@
|
||||
] %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<header class="usa-header usa-header--extended">
|
||||
<div class="usa-nav-container">
|
||||
<div class="usa-navbar">
|
||||
@@ -47,7 +53,7 @@
|
||||
<div class="logo-img display-flex">
|
||||
<a href="/">
|
||||
<span class="usa-sr-only">Notify.gov logo</span>
|
||||
<img src="{{ (asset_path | default('/static')) + 'images/notify-logo.svg' }}" alt=""
|
||||
<img src="{{ (asset_path | default('/static')) + 'images/notify-logo.svg' }}" alt="Notify.gov logo"
|
||||
class="usa-flag-logo margin-right-1">
|
||||
</a>
|
||||
</div>
|
||||
@@ -55,6 +61,14 @@
|
||||
<button type="button" class="usa-menu-btn">Menu</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if not current_user.is_authenticated and is_information_section%}
|
||||
<div class="usa-nav__login">
|
||||
<a class="usa-button usa-button login-button login-button--primary margin-right-2"
|
||||
href="{{ initial_signin_url }}">Sign
|
||||
in with <img src="{{ asset_url('images/logo-login.svg') }}" alt="Login.gov logo">
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -75,11 +89,7 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% if not current_user.is_authenticated %}
|
||||
<div class="usa-nav__secondary margin-bottom-2">
|
||||
{% else %}
|
||||
<div class="usa-nav__secondary margin-bottom-6">
|
||||
{% endif %}
|
||||
<ul class="usa-nav__secondary-links">
|
||||
{% if secondaryNavigation %}
|
||||
{% for item in secondaryNavigation %}
|
||||
@@ -94,19 +104,8 @@
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% if not current_user.is_authenticated %}
|
||||
<div class="usa-nav__login display-flex flex-align-center">
|
||||
<span class="margin-right-2 display-none desktop:display-block">
|
||||
If you are an existing partner
|
||||
</span>
|
||||
<a id="header_login_button" class="usa-button login-button login-button--primary desktop:padding-2 padding-1"
|
||||
href="{{ initial_signin_url }}">
|
||||
Sign in with
|
||||
<img src="{{ asset_url('images/logo-login.svg') }}" alt="Login.gov logo">
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -16,9 +16,19 @@
|
||||
placeholder=''
|
||||
) %}
|
||||
<div
|
||||
class="usa-form-group{% if field.errors %} usa-form-group--error{% endif %} {{ extra_form_group_classes }}"
|
||||
class="form-group{% if field.errors %} form-group-error{% endif %} {{ extra_form_group_classes }}"
|
||||
data-module="{% if autofocus %}autofocus{% elif colour_preview %}colour-preview{% endif %}"
|
||||
>
|
||||
{% if field.errors %}
|
||||
<div class="usa-alert usa-alert--error edit-textbox-error-mt" role="alert">
|
||||
<div class="usa-alert__body">
|
||||
<h4 class="usa-alert__heading">Error message</h4>
|
||||
<p class="usa-alert__text" data-module="track-error" data-error-type="{{ field.errors[0] }}" data-error-label="{{ field.name }}">
|
||||
{% if not safe_error_message %}{{ field.errors[0] }}{% else %}{{ field.errors[0]|safe }}{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<label class="usa-label" for="{{ field.name }}">
|
||||
{% if label %}
|
||||
{{ label }}
|
||||
@@ -31,12 +41,6 @@
|
||||
{{ hint }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if field.errors %}
|
||||
<span id="{{ field.name}}-error" class="usa-error-message" data-module="track-error" data-error-type="{{ field.errors[0] }}" data-error-label="{{ field.name }}" tabindex="-1" aria-live="assertive" role="alert">
|
||||
<span class="usa-sr-only">Error:</span>
|
||||
{% if not safe_error_message %}{{ field.errors[0] }}{% else %}{{ field.errors[0]|safe }}{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
{%
|
||||
if highlight_placeholders or autosize
|
||||
%}
|
||||
@@ -55,8 +59,6 @@
|
||||
data_highlight_placeholders='true' if highlight_placeholders else 'false',
|
||||
rows=rows|string,
|
||||
placeholder=placeholder,
|
||||
aria_describedby=field.name+"-error",
|
||||
required='required' if required else None,
|
||||
**kwargs
|
||||
) }}
|
||||
{% if suffix %}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="aria-live-account" class="usa-sr-only" aria-live="polite"></div>
|
||||
<div class="table-container" id="activityContainer" data-currentUserName="{{ current_user.name }}" data-currentServiceId="{{current_service.id}}">
|
||||
<div class="table-container" id="activityContainer" data-currentUserName="{{ current_user.name }}">
|
||||
<div id="tableActivity" class="table-overflow-x-auto">
|
||||
<h2 id="table-heading" class="margin-top-4 margin-bottom-1">Service activity</h2>
|
||||
|
||||
|
||||
@@ -32,8 +32,6 @@
|
||||
<div class="tablet:grid-col-9 mobile-lg:grid-col-12">
|
||||
{{ form.name(param_extensions={
|
||||
"extra_form_group_classes": "margin-bottom-2",
|
||||
"id": "name",
|
||||
"required": True,
|
||||
"hint": {"text": "Your recipients will not see this"}
|
||||
}) }}
|
||||
{{ textbox(
|
||||
@@ -43,8 +41,7 @@
|
||||
hint=content_hint,
|
||||
rows=5,
|
||||
extra_form_group_classes='margin-bottom-1',
|
||||
placeholder='Edit me! Check out the Personalization section below for details on cool ((stuff)) you can do with your messages!',
|
||||
required=True
|
||||
placeholder='Edit me! Check out the Personalization section below for details on cool ((stuff)) you can do with your messages!'
|
||||
) }}
|
||||
{% if current_user.platform_admin %}
|
||||
{{ form.process_type }}
|
||||
|
||||
@@ -37,8 +37,8 @@
|
||||
data_kwargs={'force-focus': True}
|
||||
) %}
|
||||
<div class="grid-row">
|
||||
<div class="grid-col-12 {% if form.placeholder_value.label.text == 'phone number' %}extra-tracking{% endif %}">
|
||||
{{ form.placeholder_value(param_extensions={"classes": "", "id": "phone-number"}) }}
|
||||
<div class="grid-col-12 {% if form.placeholder_value.label.text == 'phone number' %}extra-tracking{% endif %}" aria-live="polite" role="alert">
|
||||
{{ form.placeholder_value(param_extensions={"classes": ""}) }}
|
||||
</div>
|
||||
{% if skip_link or link_to_upload %}
|
||||
<div class="grid-col-12 margin-top-1">
|
||||
|
||||
@@ -9,65 +9,70 @@ import usaButton %} {% block meta %}
|
||||
|
||||
<main id="main-content" role="main">
|
||||
{% block content %}
|
||||
<section class="usa-section--dark usa-hero padding-y-2" aria-label="Introduction">
|
||||
<section class="usa-section--dark usa-hero" aria-label="Introduction">
|
||||
<div class="grid-container padding-y-4">
|
||||
<div class="grid-row grid-gap display-flex flex-align-center">
|
||||
<div class="desktop:grid-col-8 tablet:grid-col-12">
|
||||
<h1 class="usa-hero__heading">
|
||||
<h1 class="font-sans-2xl usa-hero__heading">
|
||||
Reach people where they are with government-powered text messages
|
||||
</h1>
|
||||
<p class="desktop:font-sans-lg font-sans-md">
|
||||
<p class="font-sans-lg">
|
||||
Notify.gov is a text messaging service that helps federal, state,
|
||||
local, tribal and territorial governments more effectively
|
||||
communicate with the people they serve.
|
||||
</p>
|
||||
<div class="usa-button-group margin-bottom-5 flex-align-center">
|
||||
<a
|
||||
class="usa-button usa-button login-button login-button--primary margin-right-2"
|
||||
href="{{ initial_signin_url }}"
|
||||
>Sign in with
|
||||
<img
|
||||
src="{{ asset_url('images/logo-login.svg') }}"
|
||||
alt="Login.gov logo"
|
||||
/>
|
||||
</a>
|
||||
if you are an existing partner
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="desktop:grid-col-4 desktop:display-block display-none"
|
||||
class="desktop:grid-col-3 grid-offset-1 desktop:display-block display-none margin-x-5"
|
||||
>
|
||||
<img
|
||||
src="{{ asset_url('images/product/hero-icons.svg') }}"
|
||||
alt=""
|
||||
src="{{ asset_url('images/product/phone-text.png') }}"
|
||||
alt="Illustration of a mobile phone displaying a text message and number on its screen, with a speech bubble coming out from outside the phone, symbolizing communication or notification."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="grid-container usa-section usa-section__home border-top border-base-lighter usa-prose padding-bottom-4">
|
||||
<div class="grid-row flex-align-center flex-justify-center">
|
||||
<div class="tablet:grid-col-4 mobile-lg:grid-col-12 display-flex flex-justify-center margin-bottom-6">
|
||||
<img
|
||||
src="{{ asset_url('images/product/couple-looking-at-phone.png') }}"
|
||||
alt="A couple smiling and looking at a cell phone together"/>
|
||||
</div>
|
||||
<div class="tablet:grid-col-6 tablet:margin-left-8 mobile-lg:grid-col-12">
|
||||
<h2 class="font-body-xl margin-top-0 margin-bottom-3">
|
||||
Government texting made easy
|
||||
</h2>
|
||||
<p class="usa-body margin-bottom-5">
|
||||
Notify.gov is a text messaging platform built for government agencies.
|
||||
With minimal set-up and secure, personalized messaging, you can make
|
||||
one-way texting a part of your outreach program.
|
||||
</p>
|
||||
<a class="usa-button usa-button--big" href="/about">Learn more about Notify.gov</a>
|
||||
</div>
|
||||
</div>
|
||||
<section
|
||||
class="grid-container usa-section usa-section__home border-top border-base-lighter usa-prose padding-bottom-1"
|
||||
>
|
||||
<h2 class="font-body-xl margin-top-0 margin-bottom-3">
|
||||
Government texting made easy
|
||||
</h2>
|
||||
<p class="usa-body">
|
||||
Notify.gov is a text messaging platform built for government agencies.
|
||||
With minimal set-up and secure, personalized messaging, you can make
|
||||
one-way texting a part of your outreach program.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="usa-section usa-section__home usa-prose key-features padding-bottom-4">
|
||||
<h2 class="text-center margin-top-0">Key features</h2>
|
||||
<div class="grid-container home-cards margin-top-5">
|
||||
<section
|
||||
class="grid-container usa-section usa-section__home usa-prose padding-bottom-1"
|
||||
>
|
||||
<h2 class="font-sans-xl margin-top-0">Key features</h2>
|
||||
<div class="home-cards margin-top-5">
|
||||
<ul class="usa-card-group display-flex margin-bottom-4">
|
||||
<li class="usa-card tablet:grid-col-4 mobile-lg:grid-col-12">
|
||||
<div class="usa-card__container">
|
||||
<div class="img-container">
|
||||
<img
|
||||
src="{{ asset_url('images/internet.svg') }}"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div class="usa-card__header padding-bottom-0">
|
||||
<h3>Web-based</h3>
|
||||
<div class="blue-bar"></div>
|
||||
<img
|
||||
src="{{ asset_url('images/internet.svg') }}"
|
||||
alt="Globe on top of a web browser"
|
||||
/>
|
||||
<div class="usa-card__header">
|
||||
<h3 class="font-heading-md">Web-based</h3>
|
||||
</div>
|
||||
<div class="usa-card__body">
|
||||
<p>Nothing to download or install</p>
|
||||
@@ -76,14 +81,13 @@ import usaButton %} {% block meta %}
|
||||
</li>
|
||||
<li class="usa-card tablet:grid-col-4 mobile-lg:grid-col-12">
|
||||
<div class="usa-card__container">
|
||||
<div class="img-container">
|
||||
<img
|
||||
src="{{ asset_url('images/fast.svg') }}"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div class="usa-card__header padding-bottom-0">
|
||||
<h3>Fast and easy</h3>
|
||||
<div class="blue-bar"></div>
|
||||
<img
|
||||
src="{{ asset_url('images/fast.svg') }}"
|
||||
alt="Stopwatch with a notification speech bubble with a star inside"
|
||||
/>
|
||||
<div class="usa-card__header">
|
||||
<h3 class="font-heading-md">Fast and easy</h3>
|
||||
</div>
|
||||
<div class="usa-card__body">
|
||||
<p>No technical expertise required</p>
|
||||
@@ -92,14 +96,13 @@ import usaButton %} {% block meta %}
|
||||
</li>
|
||||
<li class="usa-card tablet:grid-col-4 mobile-lg:grid-col-12">
|
||||
<div class="usa-card__container">
|
||||
<div class="img-container">
|
||||
<img
|
||||
src="{{ asset_url('images/status.svg') }}"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div class="usa-card__header padding-bottom-0">
|
||||
<h3>Track message delivery</h3>
|
||||
<div class="blue-bar"></div>
|
||||
<img
|
||||
src="{{ asset_url('images/status.svg') }}"
|
||||
alt="3 status messages, 2 successes and one failure"
|
||||
/>
|
||||
<div class="usa-card__header">
|
||||
<h3 class="font-heading-md">Track message delivery</h3>
|
||||
</div>
|
||||
<div class="usa-card__body">
|
||||
<p>See which messages were received</p>
|
||||
@@ -108,16 +111,17 @@ import usaButton %} {% block meta %}
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="usa-card-group">
|
||||
<li class="usa-card tablet:grid-offset-2 tablet:grid-col-4 mobile-lg:grid-col-12">
|
||||
<li class="usa-card tablet:grid-col-4 mobile-lg:grid-col-12">
|
||||
<div class="usa-card__container">
|
||||
<div class="img-container">
|
||||
<img
|
||||
src="{{ asset_url('images/translation.svg') }}"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div class="usa-card__header padding-bottom-0">
|
||||
<h3>Send in recipients' preferred language</h3>
|
||||
<div class="blue-bar"></div>
|
||||
<img
|
||||
src="{{ asset_url('images/translation.svg') }}"
|
||||
alt="Speech bubbles with the letter A and the Chinese character for language"
|
||||
/>
|
||||
<div class="usa-card__header">
|
||||
<h3 class="font-heading-md">
|
||||
Send in recipients' preferred language
|
||||
</h3>
|
||||
</div>
|
||||
<div class="usa-card__body">
|
||||
<p>Notify.gov has support for more than 30 character sets</p>
|
||||
@@ -126,14 +130,31 @@ import usaButton %} {% block meta %}
|
||||
</li>
|
||||
<li class="usa-card tablet:grid-col-4 mobile-lg:grid-col-12">
|
||||
<div class="usa-card__container">
|
||||
<div class="img-container">
|
||||
<img
|
||||
src="{{ asset_url('images/send.svg') }}"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div class="blue-bar"></div>
|
||||
<img
|
||||
src="{{ asset_url('images/security.svg') }}"
|
||||
alt="Lock with code icon inside on top of a web browser"
|
||||
/>
|
||||
<div class="usa-card__header">
|
||||
<h3 class="padding-bottom-0">
|
||||
<h3 class="font-heading-md">Security and privacy</h3>
|
||||
</div>
|
||||
<div class="usa-card__body">
|
||||
<p>
|
||||
Limited data retention, encryption, and multi-factor
|
||||
authentication protect user data and manage risk with <br><a href="/about/security">our security efforts</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="usa-card tablet:grid-col-4 mobile-lg:grid-col-12">
|
||||
<div class="usa-card__container">
|
||||
<div class="blue-bar"></div>
|
||||
<img
|
||||
src="{{ asset_url('images/send.svg') }}"
|
||||
alt="Paper airplane and a notification icon with the number 1 inside"
|
||||
/>
|
||||
<div class="usa-card__header">
|
||||
<h3 class="font-heading-md">
|
||||
Send bulk, customized, one-way messages
|
||||
</h3>
|
||||
</div>
|
||||
@@ -149,52 +170,51 @@ import usaButton %} {% block meta %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="usa-section usa-section__home usa-prose security-and-privacy padding-y-6 bg-base-lighter">
|
||||
<div class="grid-container">
|
||||
<div class="grid-row flex-align-center flex-justify-center">
|
||||
<div class="tablet:grid-col-7 mobile-lg:grid-col-12 margin-bottom-4">
|
||||
<h2 class="font-heading-xl margin-top-0 margin-bottom-3">
|
||||
Security and privacy
|
||||
</h2>
|
||||
<p class="usa-body margin-bottom-5">
|
||||
Limited data retention, encryption, and multi-factor authentication protect user data and manage risk.
|
||||
</p>
|
||||
<a class="usa-button usa-button--big" href="/join-notify">Our security efforts</a>
|
||||
</div>
|
||||
<div class="tablet:grid-col-3 mobile-lg:grid-col-12 display-flex flex-justify-center">
|
||||
<img
|
||||
class="width-card-lg"
|
||||
src="{{ asset_url('images/security.svg') }}"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<section
|
||||
class="grid-container usa-section usa-section__home usa-prose grid-container padding-bottom-10"
|
||||
>
|
||||
<h2 class="font-heading-xl margin-top-0 margin-bottom-3">
|
||||
Who can use Notify.gov?
|
||||
</h2>
|
||||
<p class="usa-body">
|
||||
All <span class="text-bold">federal</span> agencies and programs are
|
||||
eligible to use Notify.gov.
|
||||
</p>
|
||||
<p class="usa-body">
|
||||
US
|
||||
<span class="text-bold">state, local, territorial, or tribal</span>
|
||||
governments that administer or deliver federally-funded programs or
|
||||
services may qualify to use Notify.gov to communicate with applicants and
|
||||
participants in these programs.
|
||||
</p>
|
||||
<div class="grid-container margin-top-4 padding-left-0 padding-right-0">
|
||||
<div class="grid-row grid-gap-3">
|
||||
<a
|
||||
class="text-no-underline tablet:grid-col-4 mobile-lg:grid-col-12"
|
||||
href="mailto:tts-notify@gsa.gov"
|
||||
>
|
||||
<div class="contact-us-card">
|
||||
<div class="grid-row flex-align-center grid-gap-2">
|
||||
<div class="grid-col-auto">
|
||||
<img
|
||||
src="{{ asset_url('images/contact.svg') }}"
|
||||
alt=""
|
||||
class="height-7 width-7"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid-col">
|
||||
<p class="margin-0">
|
||||
<strong>Contact us</strong><br />
|
||||
To learn more about becoming a partner!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="grid-container usa-section usa-section__home usa-prose margin-bottom-15">
|
||||
<div class="grid-row flex-align-center flex-justify-center">
|
||||
<div class="tablet:grid-col-4 mobile-lg:grid-col-12 display-flex flex-justify-center margin-right-8 margin-bottom-5">
|
||||
<img src="{{ asset_url('images/product/who-can-use-notify.png') }}" alt="Woman smiling behind two computer screens"/>
|
||||
</div>
|
||||
<div class="tablet:grid-col-6 mobile-lg:grid-col-12">
|
||||
<h2 class="font-heading-xl margin-top-0 margin-bottom-3">
|
||||
Who can use Notify.gov?
|
||||
</h2>
|
||||
<p class="usa-body">
|
||||
All <span class="text-bold">federal</span> agencies and programs are
|
||||
eligible to use Notify.gov.
|
||||
</p>
|
||||
<p class="usa-body margin-bottom-5">
|
||||
US
|
||||
<span class="text-bold">state, local, territorial, or tribal</span>
|
||||
governments that administer or deliver federally-funded programs or
|
||||
services may qualify to use Notify.gov to communicate with applicants and
|
||||
participants in these programs.
|
||||
</p>
|
||||
<a class="usa-button usa-button--big" href="/join-notify">Become a Notify.gov partner</a>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
||||
@@ -90,4 +90,8 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!--<div class="">
|
||||
{{ copy_to_clipboard(template.id, name="Template ID", thing='template ID') }}
|
||||
</div>-->
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<link rel="apple-touch-icon" sizes="180x180"
|
||||
href="/static_503/images/apple-touch-icon.png?a0f7e1b728a42016b247dc54ee40d055">
|
||||
|
||||
<link rel="stylesheet" media="screen" href="/static_503/stylesheets/legacy/uk.css?d077f86473501ab244de0b30600536ee" />
|
||||
<link rel="stylesheet" media="screen" href="/static_503/stylesheets/main.css?d077f86473501ab244de0b30600536ee" />
|
||||
<link rel="stylesheet" media="print" href="/static_503/stylesheets/print.css?28010888ca5719dc83d7c2c80f6ed2b2" />
|
||||
<style>
|
||||
.govuk-header__container {
|
||||
@@ -127,4 +127,4 @@
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
2176
package-lock.json
generated
2176
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -37,7 +37,7 @@
|
||||
"hogan": "1.0.2",
|
||||
"jquery": "3.7.1",
|
||||
"morphdom": "^2.7.4",
|
||||
"playwright": "^1.50.1",
|
||||
"playwright": "^1.50.0",
|
||||
"python": "^0.0.4",
|
||||
"query-command-supported": "1.0.0",
|
||||
"sass-embedded": "^1.83.4",
|
||||
@@ -48,7 +48,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.26.7",
|
||||
"@babel/preset-env": "^7.26.7",
|
||||
"@babel/preset-env": "^7.26.0",
|
||||
"@uswds/compile": "^1.2.1",
|
||||
"backstopjs": "^6.3.25",
|
||||
"better-npm-audit": "^3.11.0",
|
||||
@@ -61,12 +61,12 @@
|
||||
"gulp-jshint": "2.1.0",
|
||||
"gulp-prettyerror": "2.0.0",
|
||||
"gulp-uglify": "3.0.2",
|
||||
"jest": "^29.7.0",
|
||||
"jest": "29.7.0",
|
||||
"jest-each": "^29.2.1",
|
||||
"jest-environment-jsdom": "^29.2.2",
|
||||
"jshint": "2.13.6",
|
||||
"jshint-stylish": "2.2.1",
|
||||
"rollup": "^4.34.4",
|
||||
"rollup": "^4.32.0",
|
||||
"rollup-plugin-commonjs": "10.1.0",
|
||||
"rollup-plugin-node-resolve": "5.2.0"
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ def test_non_logged_in_user_can_see_homepage(
|
||||
|
||||
# Assert the entire HTML of the button to include the image
|
||||
button = page.select_one(
|
||||
"#header_login_button"
|
||||
"a.usa-button.login-button.login-button--primary.margin-right-2"
|
||||
)
|
||||
assert "Sign in with" in button.text.strip() # Assert button text
|
||||
assert button.find("img")["alt"] == "Login.gov logo" # Assert image presence
|
||||
|
||||
@@ -1521,7 +1521,7 @@ def test_link_to_upload_not_offered_when_entering_personalisation(
|
||||
|
||||
# We’re entering personalization
|
||||
assert page.select_one("input[type=text]")["name"] == "placeholder_value"
|
||||
assert page.select_one("label[for=phone-number]").text.strip() == "name"
|
||||
assert page.select_one("label[for=placeholder_value]").text.strip() == "name"
|
||||
# No ‘Upload’ link shown
|
||||
assert len(page.select("main a")) == 0
|
||||
assert "Upload" not in page.select_one("main").text
|
||||
|
||||
@@ -174,7 +174,9 @@ def test_should_show_empty_text_box(
|
||||
# data-module=autofocus is set on a containing element so it
|
||||
# shouldn’t also be set on the textbox itself
|
||||
assert "data-module" not in textbox
|
||||
assert normalize_spaces(page.select_one("label[for=phone-number]").text) == "one"
|
||||
assert (
|
||||
normalize_spaces(page.select_one("label[for=placeholder_value]").text) == "one"
|
||||
)
|
||||
|
||||
|
||||
def test_should_prefill_answers_for_get_tour_step(
|
||||
|
||||
@@ -141,15 +141,8 @@ def test_get_notification(mocker):
|
||||
|
||||
def test_get_notification_count_for_job_id(mocker):
|
||||
mock_get = mocker.patch(
|
||||
"app.notify_client.notification_api_client.NotificationApiClient.get",
|
||||
return_value={"count": 0},
|
||||
"app.notify_client.notification_api_client.NotificationApiClient.get"
|
||||
)
|
||||
|
||||
mocker.patch(
|
||||
"app.notify_client.notification_api_client.redis_client.get", return_value=None
|
||||
)
|
||||
|
||||
mocker.patch("app.notify_client.billing_api_client.redis_client.set")
|
||||
NotificationApiClient().get_notification_count_for_job_id(
|
||||
service_id="foo", job_id="bar"
|
||||
)
|
||||
|
||||
@@ -40,7 +40,7 @@ beforeAll(done => {
|
||||
</div>
|
||||
</div>
|
||||
<div id="aria-live-account" class="usa-sr-only" aria-live="polite"></div>
|
||||
<div id="activityContainer" data-currentUserName="Test User" data-currentServiceId="12345"></div>
|
||||
<div id="activityContainer" data-currentUserName="Test User"></div>
|
||||
|
||||
<div id="aria-live-account" class="usa-sr-only" aria-live="polite"></div>
|
||||
`;
|
||||
@@ -64,13 +64,13 @@ test('D3 is loaded correctly', () => {
|
||||
|
||||
test('Populates the accessible table for activity chart correctly', () => {
|
||||
const sampleData = {
|
||||
'2024-07-01': { sms: { delivered: 50, failed: 5, pending: 10 } },
|
||||
'2024-07-02': { sms: { delivered: 60, failed: 2, pending: 5 } },
|
||||
'2024-07-03': { sms: { delivered: 70, failed: 1, pending: 3 } },
|
||||
'2024-07-04': { sms: { delivered: 80, failed: 0, pending: 0 } },
|
||||
'2024-07-05': { sms: { delivered: 90, failed: 3, pending: 8 } },
|
||||
'2024-07-06': { sms: { delivered: 100, failed: 4, pending: 7 } },
|
||||
'2024-07-07': { sms: { delivered: 110, failed: 2, pending: 6 } },
|
||||
'2024-07-01': { sms: { delivered: 50, failed: 5 } },
|
||||
'2024-07-02': { sms: { delivered: 60, failed: 2 } },
|
||||
'2024-07-03': { sms: { delivered: 70, failed: 1 } },
|
||||
'2024-07-04': { sms: { delivered: 80, failed: 0 } },
|
||||
'2024-07-05': { sms: { delivered: 90, failed: 3 } },
|
||||
'2024-07-06': { sms: { delivered: 100, failed: 4 } },
|
||||
'2024-07-07': { sms: { delivered: 110, failed: 2 } },
|
||||
};
|
||||
|
||||
const labels = Object.keys(sampleData).map(dateString => {
|
||||
@@ -79,9 +79,8 @@ test('Populates the accessible table for activity chart correctly', () => {
|
||||
});
|
||||
const deliveredData = Object.values(sampleData).map(d => d.sms.delivered);
|
||||
const failedData = Object.values(sampleData).map(d => d.sms.failed);
|
||||
const pendingData = Object.values(sampleData).map(d => d.sms.pending);
|
||||
|
||||
window.createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData, pendingData);
|
||||
window.createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData);
|
||||
|
||||
const table = document.getElementById('weeklyTable');
|
||||
expect(table).toBeDefined();
|
||||
@@ -93,7 +92,6 @@ test('Populates the accessible table for activity chart correctly', () => {
|
||||
expect(headers[0].textContent).toBe('Day');
|
||||
expect(headers[1].textContent).toBe('Delivered');
|
||||
expect(headers[2].textContent).toBe('Failed');
|
||||
expect(headers[3].textContent).toBe('Pending');
|
||||
|
||||
const firstRowCells = rows[1].getElementsByTagName('td');
|
||||
expect(firstRowCells[0].textContent).toBe('07/01/24');
|
||||
@@ -102,73 +100,58 @@ test('Populates the accessible table for activity chart correctly', () => {
|
||||
});
|
||||
|
||||
test('SVG element is correctly set up', () => {
|
||||
window.createChart(
|
||||
'#weeklyChart',
|
||||
['07/01/24', '07/02/24', '07/03/24', '07/04/24', '07/05/24', '07/06/24', '07/07/24'],
|
||||
[50, 60, 70, 80, 90, 100, 110],
|
||||
[5, 2, 1, 0, 3, 4, 2],
|
||||
[10, 5, 3, 0, 8, 7, 6]
|
||||
);
|
||||
window.createChart('#weeklyChart', ['07/01/24', '07/02/24', '07/03/24', '07/04/24', '07/05/24', '07/06/24', '07/07/24'], [50, 60, 70, 80, 90, 100, 110], [5, 2, 1, 0, 3, 4, 2]);
|
||||
|
||||
const svg = document.getElementById('weeklyChart').querySelector('svg');
|
||||
expect(svg).not.toBeNull();
|
||||
expect(svg.querySelectorAll('.bar-group').length).toBe(3);
|
||||
expect(svg.getAttribute('width')).toBe('0');
|
||||
expect(svg.getAttribute('height')).toBe('400');
|
||||
});
|
||||
|
||||
test('Check HTML content after chart creation', () => {
|
||||
// Create sample data for the chart
|
||||
const labels = ['07/01/24', '07/02/24', '07/03/24', '07/04/24', '07/05/24', '07/06/24', '07/07/24'];
|
||||
const deliveredData = [50, 60, 70, 80, 90, 100, 110];
|
||||
const failedData = [5, 2, 1, 0, 3, 4, 2];
|
||||
const pendingData = [10, 5, 8, 3, 6, 7, 4];
|
||||
|
||||
// Ensure the container has the correct width
|
||||
const container = document.getElementById('weeklyChart');
|
||||
container.style.width = '600px';
|
||||
container.style.width = '600px'; // Force a specific width
|
||||
const containerWidth = container.clientWidth;
|
||||
expect(containerWidth).toBeGreaterThan(0);
|
||||
|
||||
window.createChart('#weeklyChart', labels, deliveredData, failedData, pendingData);
|
||||
// Call the function to create the chart
|
||||
window.createChart('#weeklyChart', labels, deliveredData, failedData);
|
||||
|
||||
const svg = container.querySelector('svg');
|
||||
expect(svg).not.toBeNull();
|
||||
|
||||
const bars = container.querySelectorAll('rect');
|
||||
expect(bars.length).toBeGreaterThan(0);
|
||||
|
||||
const barGroups = svg.querySelectorAll('.bar-group');
|
||||
expect(barGroups.length).toBe(3);
|
||||
|
||||
const pendingBars = Array.from(bars).filter(bar =>
|
||||
bar.parentNode.getAttribute('fill') === '#C7CACE'
|
||||
);
|
||||
expect(pendingBars.length).toBe(labels.length);
|
||||
// Optionally, you can add assertions to check for specific elements
|
||||
expect(container.querySelector('svg')).not.toBeNull();
|
||||
expect(container.querySelectorAll('rect').length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('Legend includes pending when data exists', () => {
|
||||
test('Legend is visible when there are delivered or failed messages', () => {
|
||||
// Example data with delivered and failed messages
|
||||
const labels = ['Day 1', 'Day 2'];
|
||||
const deliveredData = [10, 20];
|
||||
const failedData = [5, 0];
|
||||
const pendingData = [3, 2];
|
||||
|
||||
window.createChart('#weeklyChart', labels, deliveredData, failedData, pendingData);
|
||||
|
||||
const legendContainer = document.querySelector('.chart-legend');
|
||||
const legendItems = legendContainer.querySelectorAll('.legend-item');
|
||||
expect(legendItems.length).toBe(3);
|
||||
|
||||
const pendingLegend = Array.from(legendItems).find(item =>
|
||||
item.textContent.includes('Pending')
|
||||
);
|
||||
expect(pendingLegend).not.toBeNull();
|
||||
});
|
||||
|
||||
test('Legend is hidden when there are no delivered, failed, or pending messages', () => {
|
||||
const labels = ['Day 1', 'Day 2'];
|
||||
const deliveredData = [0, 0];
|
||||
const failedData = [0, 0];
|
||||
const pendingData = [0, 0];
|
||||
const deliveredData = [10, 20]; // Mock delivered data
|
||||
const failedData = [5, 0]; // Mock failed data
|
||||
|
||||
// Call the createChart function
|
||||
window.createChart('#weeklyChart', labels, deliveredData, failedData, pendingData);
|
||||
window.createChart('#weeklyChart', labels, deliveredData, failedData);
|
||||
|
||||
// Check if the legend is displayed using computed style
|
||||
const legendContainer = document.querySelector('.chart-legend');
|
||||
const legendDisplayStyle = window.getComputedStyle(legendContainer).display;
|
||||
expect(legendDisplayStyle).toBe('flex');
|
||||
expect(legendContainer.querySelectorAll('.legend-item').length).toBe(2); // Ensure two legend items
|
||||
});
|
||||
|
||||
test('Legend is hidden when there are no delivered or failed messages', () => {
|
||||
// Example data with no delivered and no failed messages
|
||||
const labels = ['Day 1', 'Day 2'];
|
||||
const deliveredData = [0, 0]; // No delivered messages
|
||||
const failedData = [0, 0]; // No failed messages
|
||||
|
||||
// Call the createChart function
|
||||
window.createChart('#weeklyChart', labels, deliveredData, failedData);
|
||||
|
||||
// Check if the legend is hidden using computed style
|
||||
const legendContainer = document.querySelector('.chart-legend');
|
||||
@@ -178,48 +161,25 @@ test('Legend is hidden when there are no delivered, failed, or pending messages'
|
||||
|
||||
test('Fetches data and creates chart and table correctly', async () => {
|
||||
const mockResponse = {
|
||||
'2024-07-01': { sms: { delivered: 50, failed: 5, pending: 10 } },
|
||||
'2024-07-02': { sms: { delivered: 60, failed: 2, pending: 8 } },
|
||||
'2024-07-03': { sms: { delivered: 70, failed: 1, pending: 6 } },
|
||||
'2024-07-04': { sms: { delivered: 80, failed: 0, pending: 4 } },
|
||||
'2024-07-05': { sms: { delivered: 90, failed: 3, pending: 7 } },
|
||||
'2024-07-06': { sms: { delivered: 100, failed: 4, pending: 5 } },
|
||||
'2024-07-07': { sms: { delivered: 110, failed: 2, pending: 3 } },
|
||||
'2024-07-01': { sms: { delivered: 50, failed: 5 } },
|
||||
'2024-07-02': { sms: { delivered: 60, failed: 2 } },
|
||||
'2024-07-03': { sms: { delivered: 70, failed: 1 } },
|
||||
'2024-07-04': { sms: { delivered: 80, failed: 0 } },
|
||||
'2024-07-05': { sms: { delivered: 90, failed: 3 } },
|
||||
'2024-07-06': { sms: { delivered: 100, failed: 4 } },
|
||||
'2024-07-07': { sms: { delivered: 110, failed: 2 } },
|
||||
};
|
||||
const tableContainer = document.getElementById('activityContainer');
|
||||
const currentServiceId = tableContainer.getAttribute('data-currentServiceId');
|
||||
|
||||
|
||||
global.fetch = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve(mockResponse),
|
||||
ok: true,
|
||||
json: () => Promise.resolve(mockResponse),
|
||||
})
|
||||
);
|
||||
);
|
||||
|
||||
const data = await fetchData('service');
|
||||
const data = await fetchData('service');
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledWith(`/services/${currentServiceId}/daily-stats.json`);
|
||||
expect(data).toEqual(mockResponse);
|
||||
|
||||
const labels = Object.keys(mockResponse).map(dateString => {
|
||||
const dateParts = dateString.split('-');
|
||||
return `${dateParts[1]}/${dateParts[2]}/${dateParts[0].slice(2)}`;
|
||||
});
|
||||
const deliveredData = Object.values(mockResponse).map(d => d.sms.delivered);
|
||||
const failedData = Object.values(mockResponse).map(d => d.sms.failed);
|
||||
const pendingData = Object.values(mockResponse).map(d => d.sms.pending);
|
||||
|
||||
window.createChart('#weeklyChart', labels, deliveredData, failedData, pendingData);
|
||||
window.createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData, pendingData);
|
||||
|
||||
const chart = document.getElementById('weeklyChart').querySelector('svg');
|
||||
expect(chart).not.toBeNull();
|
||||
|
||||
const table = document.getElementById('weeklyTable');
|
||||
expect(table).toBeDefined();
|
||||
const rows = table.getElementsByTagName('tr');
|
||||
expect(rows.length).toBe(8);
|
||||
expect(global.fetch).toHaveBeenCalledWith('/daily_stats.json');
|
||||
expect(data).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
test('handleDropdownChange updates DOM for individual selection', () => {
|
||||
@@ -228,7 +188,7 @@ test('handleDropdownChange updates DOM for individual selection', () => {
|
||||
<div class="chart-subtitle"></div>
|
||||
</div>
|
||||
<div id="aria-live-account"></div>
|
||||
<div id="activityContainer" data-currentUserName="Test User" data-currentServiceId="12345"></div>
|
||||
<div id="activityContainer" data-currentUserName="Test User"></div>
|
||||
<div id="tableActivity">
|
||||
<h2 id="table-heading"></h2>
|
||||
<table id="activity-table">
|
||||
|
||||
Reference in New Issue
Block a user