Merge branch 'main' into 1487-display-data-most-used-templates-table

This commit is contained in:
Beverly Nguyen
2024-08-29 14:57:36 -07:00
68 changed files with 748 additions and 1146 deletions

View File

@@ -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" />

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 13.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="719px" height="720px" viewBox="0 0 129.996 130.238" enable-background="new 0 0 129.996 130.238"
xml:space="preserve">
<rect fill="#00538E" width="129.996" height="130.238"/>
<path fill="#FFFFFF" d="M78.611,96.076c0,9.138-7.175,12.953-15.451,12.688c-2.938-0.095-5.799-0.838-8.916-1.855l0.707-6.578
c2.638,1.086,6.057,2.263,8.92,2.219c3.74-0.058,7.477-1.794,7.477-6.124c0-3.335-2.67-4.976-5.252-6.283
c-5.709-2.896-12.275-4.907-12.275-12.998c0-7.65,6.084-11.64,13.625-11.64c2.741,0,6.014,0.477,9.299,1.641l-0.985,6.478
c-3.577-1.403-5.14-1.901-8.078-1.901c-3.253,0-6.597,1.143-6.597,4.896c0,2.933,3.01,4.456,5.248,5.573
C72.398,85.218,78.611,87.703,78.611,96.076"/>
<path fill="#FFFFFF" d="M44.561,106.8c-2.632,0.901-6.662,1.964-13.092,1.964c-13.195,0-22.027-8.529-22.027-21.805
c0-12.882,9.375-21.455,22.086-21.455c6.036,0,8.727,0.896,12.541,2.212l-0.574,7.216c-3.51-2.294-6.498-3.211-11.674-3.211
c-9.09,0-15.158,6.578-15.115,15.412c0.043,9.506,6.648,15.416,14.822,15.416c2.437,0,4.508-0.178,5.996-0.569V91.15h-7.861v-6.103
h14.898V106.8"/>
<polyline fill="#FFFFFF" points="102.041,64.587 112.063,86.918 105.08,86.918 102.041,79.497 98.996,86.918 92.014,86.918
102.041,64.587 "/>
<polyline fill="#FFFFFF" points="112.178,87.179 121.507,108.029 114.176,108.029 107.203,91.839 112.178,87.179 "/>
<polyline fill="#FFFFFF" points="91.9,87.183 96.873,91.841 93.199,100.24 102.027,95.611 107.109,99.525 89.727,108.031
82.57,108.031 91.9,87.183 "/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -26,6 +26,13 @@
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
let tooltip = d3.select('#tooltip');
if (tooltip.empty()) {
tooltip = d3.select('body').append('div')
.attr('id', 'tooltip')
.style('display', 'none');
}
// Create legend
const legendContainer = d3.select('.chart-legend');
legendContainer.selectAll('*').remove(); // Clear any existing legend
@@ -57,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]);
@@ -95,10 +102,6 @@
const color = d3.scaleOrdinal()
.domain(['delivered', 'failed'])
.range([COLORS.delivered, COLORS.failed]);
// Create tooltip
const tooltip = d3.select('body').append('div')
.attr('id', 'tooltip')
.style('display', 'none');
// Create bars with animation
const barGroups = svg.selectAll('.bar-group')
@@ -185,41 +188,64 @@
return;
}
var daily_stats = activityChartContainer.getAttribute('data-daily-stats');
var daily_stats_by_user = activityChartContainer.getAttribute('data-daily_stats_by_user');
var url = type === 'service' ? `/daily_stats.json` : `/daily_stats_by_user.json`;
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
labels = [];
deliveredData = [];
failedData = [];
try {
// Choose the correct JSON string based on the type ('service' or 'user'),
// replace single quotes with double quotes to ensure valid JSON format,
// then parse the JSON string into a JavaScript object.
var statsJson = type === 'service' ? daily_stats : daily_stats_by_user;
statsJson = statsJson.replace(/'/g, '"');
data = JSON.parse(statsJson);
} catch (error) {
console.error('Error parsing JSON data:', error);
return;
}
var labels = [];
var deliveredData = [];
var failedData = [];
let totalMessages = 0;
for (var dateString in data) {
if (data.hasOwnProperty(dateString)) {
const dateParts = dateString.split('-');
const formattedDate = `${dateParts[1]}/${dateParts[2]}/${dateParts[0].slice(2)}`;
for (var dateString in data) {
if (data.hasOwnProperty(dateString)) {
const dateParts = dateString.split('-');
const formattedDate = `${dateParts[1]}/${dateParts[2]}/${dateParts[0].slice(2)}`;
labels.push(formattedDate);
deliveredData.push(data[dateString].sms.delivered);
failedData.push(data[dateString].sms.failure);
}
}
labels.push(formattedDate);
deliveredData.push(data[dateString].sms.delivered);
failedData.push(data[dateString].sms.failure);
try {
createChart('#weeklyChart', labels, deliveredData, failedData);
createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData);
} catch (error) {
console.error('Error creating chart or table:', error);
}
// Calculate the total number of messages
totalMessages += data[dateString].sms.delivered + data[dateString].sms.failure;
}
}
// 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));
};
const handleDropdownChange = function(event) {
@@ -228,32 +254,40 @@
const selectElement = document.getElementById('options');
const selectedText = selectElement.options[selectElement.selectedIndex].text;
if (selectedValue === "individual") {
subTitle.textContent = selectedText + " - Last 7 Days";
fetchData('individual');
} else if (selectedValue === "service") {
subTitle.textContent = selectedText + " - Last 7 Days";
fetchData('service');
}
subTitle.textContent = `${selectedText} - last 7 days`;
fetchData(selectedValue);
// Update ARIA live region
const liveRegion = document.getElementById('aria-live-account');
liveRegion.textContent = `Data updated for ${selectedText} - Last 7 Days`;
};
liveRegion.textContent = `Data updated for ${selectedText} - last 7 days`;
document.addEventListener('DOMContentLoaded', function() {
// Initialize activityChart chart and table with service data by default
fetchData('service');
// Add event listener to the dropdown
const dropdown = document.getElementById('options');
dropdown.addEventListener('change', handleDropdownChange);
// Switch tables based on dropdown selection
const selectedTable = selectedValue === "individual" ? "table1" : "table2";
const tables = document.querySelectorAll('.table-overflow-x-auto');
tables.forEach(function(table) {
table.classList.add('hidden'); // Hide all tables by adding the hidden class
table.classList.remove('visible'); // Ensure they are not visible
});
const tableToShow = document.getElementById(selectedTable);
tableToShow.classList.remove('hidden'); // Remove hidden class
tableToShow.classList.add('visible'); // Add visible class
};
document.addEventListener('DOMContentLoaded', function() {
// Initialize activityChart chart and table with service data by default
fetchData('service');
// Add event listener to the dropdown
const dropdown = document.getElementById('options');
dropdown.addEventListener('change', handleDropdownChange);
});
// Resize chart on window resize
window.addEventListener('resize', function() {
const selectedValue = document.getElementById('options').value;
handleDropdownChange({ target: { value: selectedValue } });
if (labels.length > 0 && deliveredData.length > 0 && failedData.length > 0) {
createChart('#weeklyChart', labels, deliveredData, failedData);
createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData);
}
});
// Export functions for testing

View File

@@ -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;

View File

@@ -0,0 +1,4 @@
document.addEventListener('DOMContentLoaded', function() {
var timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
document.cookie = `timezone=${timeZone}; path=/`;
})

View File

@@ -9,6 +9,9 @@ $failed: color('gray-cool-20');
&.usage {
height: units(4);
}
svg {
overflow: visible;
}
}
#totalMessageChartContainer {

View File

@@ -332,6 +332,12 @@ td.table-empty-message {
bottom: 0;
}
.table-overflow-x-auto {
&.hidden {
display: none;
}
}
@media (max-width: units('desktop-lg')) {
.table-overflow-x-auto {
overflow-x: auto;
@@ -464,7 +470,7 @@ td.table-empty-message {
width: 25%;
}
td.time-sent {
width: 30%;
width: 15%;
}
td.sender {
width: 20%;
@@ -474,12 +480,20 @@ td.table-empty-message {
width: 5%;
}
td.report {
width: 5%;
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
}

View File

@@ -13,5 +13,6 @@ in the form $setting: value,
$theme-banner-max-width: "desktop-lg",
$theme-grid-container-max-width: "desktop-lg",
$theme-footer-max-width: "desktop-lg",
$theme-header-max-width: "desktop-lg"
$theme-header-max-width: "desktop-lg",
$theme-identifier-max-width: "desktop-lg"
);

View File

@@ -13,14 +13,14 @@ from app.utils.user import user_has_permissions
@main.route("/activity/services/<uuid:service_id>")
@user_has_permissions()
@user_has_permissions("view_activity")
def all_jobs_activity(service_id):
service_data_retention_days = 7
page = get_page_from_request()
jobs = job_api_client.get_page_of_jobs(service_id, page=page)
all_jobs_dict = generate_job_dict(jobs)
prev_page, next_page, pagination = handle_pagination(jobs, service_id, page)
message_type = ("sms",)
return render_template(
"views/activity/all-activity.html",
all_jobs_dict=all_jobs_dict,
@@ -28,6 +28,34 @@ def all_jobs_activity(service_id):
next_page=next_page,
prev_page=prev_page,
pagination=pagination,
download_link_one_day=url_for(
".download_notifications_csv",
service_id=current_service.id,
message_type=message_type,
status=request.args.get("status"),
number_of_days="one_day",
),
download_link_three_day=url_for(
".download_notifications_csv",
service_id=current_service.id,
message_type=message_type,
status=request.args.get("status"),
number_of_days="three_day",
),
download_link_five_day=url_for(
".download_notifications_csv",
service_id=current_service.id,
message_type=message_type,
status=request.args.get("status"),
number_of_days="five_day",
),
download_link_seven_day=url_for(
".download_notifications_csv",
service_id=current_service.id,
message_type=message_type,
status=request.args.get("status"),
number_of_days="seven_day",
),
)
@@ -75,6 +103,22 @@ def generate_job_dict(jobs):
"processing_started": job["processing_started"],
"created_by": job["created_by"],
"template_name": job["template_name"],
"delivered_count": next(
(
stat["count"]
for stat in job.get("statistics", [])
if stat["status"] == "delivered"
),
None,
),
"failed_count": next(
(
stat["count"]
for stat in job.get("statistics", [])
if stat["status"] == "failed"
),
None,
),
}
for job in jobs["data"]
]

View File

@@ -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
@@ -53,17 +55,12 @@ def service_dashboard(service_id):
free_sms_allowance = billing_api_client.get_free_sms_fragment_limit_for_year(
current_service.id,
)
date_range = get_stats_date_range()
usage_data = get_annual_usage_breakdown(yearly_usage, free_sms_allowance)
sms_sent = usage_data["sms_sent"]
sms_allowance_remaining = usage_data["sms_allowance_remaining"]
job_response = job_api_client.get_jobs(service_id)["data"]
service_data_retention_days = 7
daily_stats = get_daily_stats(service_id, date_range)
daily_stats_by_user = get_daily_stats_by_user(
service_id, current_user.id, date_range
)
jobs = [
{
@@ -94,24 +91,32 @@ def service_dashboard(service_id):
service_data_retention_days=service_data_retention_days,
sms_sent=sms_sent,
sms_allowance_remaining=sms_allowance_remaining,
daily_stats=daily_stats,
daily_stats_by_user=daily_stats_by_user,
)
def get_daily_stats(service_id, date_range):
return service_api_client.get_service_notification_statistics_by_day(
@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)
def get_daily_stats_by_user(service_id, user_id, date_range):
return service_api_client.get_user_service_notification_statistics_by_day(
@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,
start_date=date_range["start_date"],
days=date_range["days"],
)
return jsonify(stats)
@main.route("/services/<uuid:service_id>/dashboard.json")
@@ -405,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
@@ -468,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

View File

@@ -1,9 +1,11 @@
import csv
import itertools
import json
from collections import OrderedDict
from datetime import datetime
from io import StringIO
from flask import abort, flash, render_template, request, url_for
from flask import Response, abort, flash, render_template, request, url_for
from notifications_python_client.errors import HTTPError
from app import (
@@ -70,6 +72,40 @@ def platform_admin():
)
@main.route("/platform-admin/download-all-users")
@user_is_platform_admin
def download_all_users():
# Create a CSV string from the user data
users = user_api_client.get_all_users_detailed()
if len(users) == 0:
return "No data to download."
output = StringIO()
header = ["Name", "Email Address", "Phone Number", "Service"]
fieldnames = ["name", "email_address", "mobile_number", "service"]
writer = csv.DictWriter(
output,
fieldnames=fieldnames,
delimiter=",",
)
# Write custom header
writer.writerow(dict(zip(fieldnames, header)))
for user in users:
user_no_commas = {key: value.replace(",", "") for key, value in user.items()}
if user_no_commas["name"].startswith("e2e"):
continue
writer.writerow(user_no_commas)
csv_data = output.getvalue()
# Create a direct download response with the CSV data and appropriate headers
response = Response(csv_data, content_type="text/csv; charset=utf-8")
response.headers["Content-Disposition"] = "attachment; filename=users.csv"
return response
def is_over_threshold(number, total, threshold):
percentage = number / total * 100 if total else 0
return percentage > threshold

View File

@@ -33,6 +33,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,
@@ -1075,6 +1076,7 @@ def get_spreadsheet_column_headings_from_template(template):
def get_recipient():
set_timezone()
if {"recipient", "placeholders"} - set(session.keys()):
return None

View File

@@ -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"))

View File

@@ -58,6 +58,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):

View File

@@ -123,6 +123,7 @@ class HeaderNavigation(Navigation):
"get_billing_report",
"get_users_report",
"get_daily_volumes",
"download_all_users",
"get_daily_sms_provider_volumes",
"get_volumes_by_service",
"organizations",

View File

@@ -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):
@@ -157,6 +159,10 @@ class UserApiClient(NotifyAdminAPIClient):
endpoint = "/user"
return self.get(endpoint)["data"]
def get_all_users_detailed(self):
endpoint = "/user/report-all-users"
return self.get(endpoint)["data"]
@cache.delete("service-{service_id}")
@cache.delete("service-{service_id}-template-folders")
@cache.delete("user-{user_id}")

View File

@@ -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 %}

View File

@@ -11,17 +11,16 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<link rel="shortcut icon" href="{{ assetPath | default('/assets') }}/images/favicon.ico" />
<link rel="icon" type="image/png" sizes="32x32" href="{{ assetPath | default('/assets') }}/images/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="{{ assetPath | default('/assets') }}/images/favicon-16x16.png" />
<link rel="mask-icon" href="{{ assetPath | default('/assets') }}/images/safari-pinned-tab.svg" color="#5bbad5">
<link rel="apple-touch-icon" sizes="180x180" href="{{ assetPath | default('/assets') }}/images/apple-touch-icon.png">
<link rel="manifest" href="/site.webmanifest">
<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="{{ asset_url('images/site.webmanifest') }}">
<meta name="msapplication-TileColor" content="#da532c">
<link href="{{ assetPath | default('/assets') }}/images/notify-dark-favicon.png" rel="icon" media="(prefers-color-scheme: dark)">
<link href="{{ assetPath | default('/static') }}/images/notify-dark-favicon.png" rel="icon" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#ffffff">
<link rel="stylesheet" media="screen" href="{{ asset_url('css/styles.css') }}" />
{% block extra_stylesheets %}{% endblock %}
@@ -31,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 %}

View File

@@ -50,7 +50,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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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('/assets') }}/images/favicon.ico" />
<link rel="icon" type="image/png" sizes="32x32"href="{{ assetPath | default('/assets') }}/images/favicon-32x32.png"/>
<link rel="icon" type="image/png" sizes="16x16" href="{{ assetPath | default('/assets') }}/images/favicon-16x16.png" />
<link rel="mask-icon" href="{{ assetPath | default('/assets') }}/images/safari-pinned-tab.svg" color="#5bbad5">
<link rel="apple-touch-icon" sizes="180x180" href="{{ assetPath | default('/assets') }}/images/apple-touch-icon.png">
<link rel="manifest" href="/site.webmanifest">
<meta name="msapplication-TileColor" content="#da532c">
<link href="{{ assetPath | default('/assets') }}/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>

View File

@@ -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>

View File

@@ -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 %}

View File

@@ -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>

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -15,7 +15,7 @@
class="usa-pagination__link usa-pagination__previous-page"
aria-label="Previous page"
>
<img src="{{ url_for('static', filename='/img/usa-icons/navigate_before.svg') }}" alt="arrow">
<img src="{{ asset_url('img/usa-icons/navigate_before.svg') }}" alt="arrow">
<span class="usa-pagination__link-text">Previous</span></a
>
</li>
@@ -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 %}
@@ -50,7 +45,7 @@
aria-label="Next page"
>
<span class="usa-pagination__link-text">Next </span>
<img src="{{ url_for('static', filename='/img/usa-icons/navigate_next.svg') }}" alt="arrow">
<img src="{{ asset_url('img/usa-icons/navigate_next.svg') }}" alt="arrow">
</a>
</li>
{% endif %}
@@ -58,7 +53,6 @@
</nav>
{% endif %}
{% endset %}
{% block maincolumn_content %}
<div class="margin-bottom-8">
<h1 class="usa-sr-only">All activity</h1>
@@ -84,6 +78,12 @@
<th data-sortable scope="col" role="columnheader" class="table-field-heading">
<span>Report</span>
</th>
<th data-sortable scope="col" role="columnheader" class="table-field-heading">
<span>Delivered</span>
</th>
<th data-sortable scope="col" role="columnheader" class="table-field-heading">
<span>Failed</span>
</th>
</tr>
</thead>
<tbody>
@@ -104,23 +104,40 @@
<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>
<td class="table-field delivered">{{ job.delivered_count if job.delivered_count is not none else '0' }}</td>
<td class="table-field failed">{{ job.failed_count if job.failed_count is not none else '0' }}</td>
</tr>
{% 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>
<p><b>Note: </b>Report data is only available for 7 days after your message has been sent</p>
</div>
{{show_pagination}}
{% if current_user.has_permissions('view_activity') %}
<h2 class="line-height-sans-2 margin-bottom-0 margin-top-4">Download recent reports</h2>
<p class="font-body-sm">
<a href="{{ download_link_one_day }}" download="download" class="usa-link">Download all data last 24 hours (<abbr title="Comma separated values">CSV</abbr>)</a>
</p>
<p class="font-body-sm">
<a href="{{ download_link_three_day }}" download="download" class="usa-link">Download all data last 3 days (<abbr title="Comma separated values">CSV</abbr>)</a>
&emsp;
</p>
<p class="font-body-sm">
<a href="{{ download_link_five_day }}" download="download" class="usa-link">Download all data last 5 days (<abbr title="Comma separated values">CSV</abbr>)</a>
</p>
<p class="font-body-sm">
<a href="{{ download_link_seven_day }}" download="download" class="usa-link">Download all data last 7 days (<abbr title="Comma separated values">CSV</abbr>)</a>
</p>
{% endif %}
</div>
{% endblock %}

View File

@@ -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">
@@ -23,16 +27,14 @@
{{ ajax_block(partials, updates_url, 'inbox') }}
<div id="totalMessageChartContainer" data-sms-sent="{{ sms_sent }}" data-sms-allowance-remaining="{{ sms_allowance_remaining }}">
<h2 id="chartTitle">Total Messages</h2>
<h2 id="chartTitle">Total messages</h2>
<svg id="totalMessageChart"></svg>
<div id="message"></div>
</div>
<div id="totalMessageTable" class="margin-0"></div>
<h2 class="line-height-sans-2 margin-bottom-0 margin-top-4">
Activity snapshot
</h2>
<div id="activityChartContainer" data-daily-stats="{{ daily_stats }}" data-daily_stats_by_user="{{ daily_stats_by_user }}">
<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>
<select class="usa-select margin-bottom-2" name="options" id="options">
@@ -43,7 +45,7 @@
</form>
<div id="activityChart">
<div class="chart-header">
<div class="chart-subtitle">{{ current_service.name }} - Last 7 Days</div>
<div class="chart-subtitle">{{ current_service.name }} - last 7 days</div>
<div class="chart-legend" aria-label="Legend"></div>
</div>
<div class="chart-container" id="weeklyChart"></div>
@@ -54,75 +56,87 @@
{% if current_user.has_permissions('manage_service') %}{% endif %}
{{ ajax_block(partials, updates_url, 'template-statistics') }}
<h2 class="margin-top-4 margin-bottom-1">Recent Batches</h2>
<div class="table-overflow-x-auto">
<table class="usa-table usa-table--borderless job-table">
<thead class="table-field-headings">
<tr>
<th scope="col" class="table-field-heading-first">
<span>File name</span>
</th>
<th scope="col" class="table-field-heading">
<span>Template</span>
</th>
<th scope="col" class="table-field-heading">
<span>Job status</span>
</th>
<th scope="col" class="table-field-heading">
<span>Sender</span>
</th>
<th scope="col" class="table-field-heading">
<span># of Recipients</span>
</th>
<th scope="col" class="table-field-heading">
<span>Report</span>
</th>
</tr>
</thead>
<tbody>
{% if jobs %}
{% for job in jobs[:5] %}
<div class="table-container">
<div id="table1" class="table-overflow-x-auto hidden">
<h2 class="margin-top-4 margin-bottom-1">My activity</h2>
<table class="usa-table job-table">
<thead class="table-field-headings">
<tr>
<th scope="col" class="table-field-heading-first" id="jobId"><span>Job ID#</span></th>
<th data-sortable scope="col" class="table-field-heading"><span>Template</span></th>
<th data-sortable scope="col" class="table-field-heading"><span>Job status</span></th>
<th data-sortable scope="col" class="table-field-heading"><span># of Recipients</span></th>
</tr>
</thead>
<tbody>
{% if jobs %}
{% for job in jobs[:5] %}
{% if job.created_by.name == current_user.name %}
{% set notification = job.notifications[0] %}
<tr id="{{ job.job_id }}">
<td class="table-field jobid" scope="row" role="rowheader">
<a class="usa-link" href="{{ job.view_job_link }}">
{{ job.job_id[:8] if job.job_id else 'Manually entered number' }}
</a>
</td>
<td class="table-field template">{{ job.template_name }}</td>
<td class="table-field time-sent">Sent on
{{ (job.processing_finished if job.processing_finished else job.processing_started
if job.processing_started else job.created_at)|format_datetime_table }}
</td>
<td class="table-field count-of-recipients">{{ job.notification_count }}</td>
</tr>
{% endif %}
{% endfor %}
{% else %}
<tr class="table-row">
<td class="table-empty-message" colspan="10">No batched job messages found &thinsp;(messages are kept for {{ service_data_retention_days }} days).</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
<div id="table2" class="table-overflow-x-auto visible">
<h2 class="margin-top-4 margin-bottom-1">Service activity</h2>
<table class="usa-table job-table">
<thead class="table-field-headings">
<tr>
<th scope="col" role="columnheader" class="table-field-heading-first" id="jobId"><span>Job ID#</span></th>
<th data-sortable scope="col" role="columnheader" class="table-field-heading"><span>Template</span></th>
<th data-sortable scope="col" role="columnheader" class="table-field-heading"><span>Job status</span></th>
<th data-sortable scope="col" role="columnheader" class="table-field-heading"><span>Sender</span></th>
<th data-sortable scope="col" role="columnheader" class="table-field-heading"><span># of Recipients</span></th>
</tr>
</thead>
<tbody>
{% if jobs %}
{% for job in jobs[:5] %}
{% set notification = job.notifications[0] %}
<tr id="{{ job.job_id }}">
<td class="table-field file-name">
{{ job.original_file_name[:12] if job.original_file_name else 'Manually entered number'}}
<br>
<a class="usa-link file-list-filename" href="{{ job.view_job_link }}">View Batch</a>
<td class="table-field jobid" scope="row" role="rowheader">
<a class="usa-link" href="{{ job.view_job_link }}">
{{ job.job_id[:8] if job.job_id else 'Manually entered number' }}
</a>
</td>
<td class="table-field template">
{{ job.template_name }}
</td>
<td class="table-field time-sent">
<td class="table-field template">{{ job.template_name }}</td>
<td class="table-field time-sent">Sent on
{{ (job.processing_finished if job.processing_finished else job.processing_started
if job.processing_started else job.created_at)|format_datetime_table }}
</td>
<td class="table-field sender">
{{ job.created_by.name }}
</td>
<td class="table-field count-of-recipients">
{{ job.notification_count}}
</td>
<td class="table-field report">
{% if job.time_left != "Data no longer available" %}
<a class="usa-link file-list-filename" href="{{ job.download_link }}">Download</a>
<span class="usa-hint">{{ job.time_left }}</span>
{% elif job %}
<span>{{ job.time_left }}</span>
{% endif %}
</td>
<td class="table-field sender">{{ job.created_by.name }}</td>
<td class="table-field count-of-recipients">{{ job.notification_count }}</td>
</tr>
{% endfor %}
{% else %}
<tr class="table-row">
<td class="table-empty-message" colspan="10">No batched job messages found &thinsp;(messages are kept for {{ service_data_retention_days }} days).</td>
</tr>
{% endif %}
</tbody>
</table>
{% endfor %}
{% else %}
<tr class="table-row">
<td class="table-empty-message" colspan="10">No batched job messages found &thinsp;(messages are kept for {{ service_data_retention_days }} days).</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
<h2>Recent templates</h2>
{{ ajax_block(partials, updates_url, 'template-statistics') }}
</div>

View File

@@ -1,7 +1,8 @@
{% 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 > 1 %}
{% 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,

View File

@@ -10,7 +10,7 @@
{% block serviceNavigation %}{% endblock %}
{% block sideNavigation %}
{% include "/new/components/settings_nav.html" %}
{% include "components/settings_nav.html" %}
{% endblock %}
{% block maincolumn_content %}

View File

@@ -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>

View File

@@ -31,4 +31,8 @@
<p>
<a class="usa-link" href="{{ url_for('main.get_users_report') }}">Users Report</a>
</p>
<p>
<a class="usa-link" href="{{ url_for('main.download_all_users') }}">Download All Users</a>
</p>
{% endblock %}

View File

@@ -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) }}

View File

@@ -9,7 +9,7 @@
{% block serviceNavigation %}{% endblock %}
{% block sideNavigation %}
{% include "/new/components/settings_nav.html" %}
{% include "components/settings_nav.html" %}
{% endblock %}
{% block maincolumn_content %}

View File

@@ -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') %}

View File

@@ -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>