Skip to content

Commit b4e079d

Browse files
authored
Merge pull request #2447 from GSA/main
3/28/2025 Production Deploy
2 parents b14ed32 + 56ee7ec commit b4e079d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+887
-789
lines changed

.ds.baseline

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@
151151
"filename": "app/config.py",
152152
"hashed_secret": "577a4c667e4af8682ca431857214b3a920883efc",
153153
"is_verified": false,
154-
"line_number": 120,
154+
"line_number": 118,
155155
"is_secret": false
156156
}
157157
],
@@ -674,5 +674,5 @@
674674
}
675675
]
676676
},
677-
"generated_at": "2025-03-17T23:26:44Z"
677+
"generated_at": "2025-03-20T18:22:36Z"
678678
}

.github/workflows/checks.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,6 @@ jobs:
167167
run: make run-flask &
168168
env:
169169
NOTIFY_ENVIRONMENT: scanning
170-
FEATURE_ABOUT_PAGE_ENABLED: true
171170
- name: Run OWASP Baseline Scan
172171
uses: zaproxy/action-baseline@v0.14.0
173172
with:

app/__init__.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,15 @@ def _csp(config):
169169

170170

171171
def create_app(application):
172-
@application.context_processor
173-
def inject_feature_flags():
174-
feature_about_page_enabled = application.config.get(
175-
"FEATURE_ABOUT_PAGE_ENABLED", False
176-
)
177-
return dict(
178-
FEATURE_ABOUT_PAGE_ENABLED=feature_about_page_enabled,
179-
)
172+
# @application.context_processor
173+
# def inject_feature_flags():
174+
# this is where feature flags can be easily added as a dictionary within context
175+
# feature_about_page_enabled = application.config.get(
176+
# "FEATURE_ABOUT_PAGE_ENABLED", False
177+
# )
178+
# return dict(
179+
# FEATURE_ABOUT_PAGE_ENABLED=feature_about_page_enabled,
180+
# )
180181

181182
@application.context_processor
182183
def inject_initial_signin_url():
@@ -682,4 +683,4 @@ def slugify(text):
682683
"""
683684
Converts text to lowercase, replaces spaces with hyphens, and removes invalid characters.
684685
"""
685-
return re.sub(r'[^a-z0-9-]', '', re.sub(r'\s+', '-', text.lower()))
686+
return re.sub(r"[^a-z0-9-]", "", re.sub(r"\s+", "-", text.lower()))

app/assets/javascripts/activityChart.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@
220220

221221
var url = type === 'service'
222222
? `/services/${currentServiceId}/daily-stats.json?timezone=${encodeURIComponent(userTimezone)}`
223-
: `/services/${currentServiceId}/daily-stats-by-user.json`;
223+
: `/services/${currentServiceId}/daily-stats-by-user.json?timezone=${encodeURIComponent(userTimezone)}`;
224224

225225

226226
return fetch(url)

app/assets/javascripts/preventDuplicateFormSubmissions.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,23 @@
1313
} else {
1414

1515
$submitButton.data('clicked', 'true');
16-
setTimeout(renableSubmitButton($submitButton), 1500);
1716

18-
}
17+
if ($submitButton.is('[name="Send"], [name="Schedule"]')) {
18+
$submitButton.prop('disabled', true);
1919

20+
setTimeout(() => {
21+
renableSubmitButton($submitButton);
22+
}, 10000);
23+
} else {
24+
setTimeout(renableSubmitButton($submitButton), 1500);
25+
}
26+
}
2027
};
2128

2229
let renableSubmitButton = $submitButton => () => {
2330

2431
$submitButton.data('clicked', '');
25-
32+
$submitButton.prop('disabled', false);
2633
};
2734

2835
$('form').on('submit', disableSubmitButtons);

app/assets/javascripts/validation.js

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@ function showError(input, errorElement, message) {
77
errorElement.textContent = message;
88
}, 10);
99

10-
input.classList.add("usa-input--error");
10+
if (input.type !== "radio" && input.type !== "checkbox") {
11+
input.classList.add("usa-input--error");
12+
}
1113
input.setAttribute("aria-describedby", errorElement.id);
1214
}
1315

1416
function hideError(input, errorElement) {
1517
errorElement.style.display = "none";
16-
input.classList.remove("usa-input--error");
18+
if (input.type !== "radio" && input.type !== "checkbox") {
19+
input.classList.remove("usa-input--error");
20+
}
1721
input.removeAttribute("aria-describedby");
1822
}
1923

@@ -24,33 +28,42 @@ function getFieldLabel(input) {
2428

2529
// Attach validation logic to forms
2630
function attachValidation() {
27-
const forms = document.querySelectorAll("form.send-one-off-form");
31+
const forms = document.querySelectorAll('form[data-force-focus="True"]');
2832
forms.forEach((form) => {
2933
const inputs = form.querySelectorAll("input, textarea, select");
3034

3135
form.addEventListener("submit", function (event) {
3236
let isValid = true;
3337
let firstInvalidInput = null;
38+
const validatedRadioNames = new Set();
3439

3540
inputs.forEach((input) => {
36-
const errorId = input.id ? `${input.id}-error` : `${input.name}-error`;
41+
const errorId = input.type === "radio" ? `${input.name}-error` : `${input.id}-error`;
3742
let errorElement = document.getElementById(errorId);
3843

3944
if (!errorElement) {
4045
errorElement = document.createElement("span");
4146
errorElement.id = errorId;
4247
errorElement.classList.add("usa-error-message");
4348
errorElement.setAttribute("aria-live", "polite");
44-
input.insertAdjacentElement("afterend", errorElement);
49+
errorElement.style.display = "none";
50+
if (input.type === "radio") {
51+
const group = form.querySelectorAll(`input[name="${input.name}"]`);
52+
const lastRadio = group[group.length - 1];
53+
lastRadio.parentElement.insertAdjacentElement("afterend", errorElement);
54+
} else {
55+
input.insertAdjacentElement("afterend", errorElement);
56+
}
4557
}
4658

4759
if (input.type === "radio") {
48-
// Find all radio buttons with the same name
49-
const radioGroup = document.querySelectorAll(`input[name="${input.name}"]`);
60+
if (validatedRadioNames.has(input.name)) return;
61+
validatedRadioNames.add(input.name);
62+
const radioGroup = form.querySelectorAll(`input[name="${input.name}"]`);
5063
const isChecked = Array.from(radioGroup).some(radio => radio.checked);
5164

5265
if (!isChecked) {
53-
showError(input, errorElement, `Error: ${getFieldLabel(input)} must be selected.`);
66+
showError(input, errorElement, `Error: A selection must be made.`);
5467
isValid = false;
5568
if (!firstInvalidInput) {
5669
firstInvalidInput = input;
@@ -73,11 +86,20 @@ function attachValidation() {
7386

7487
inputs.forEach((input) => {
7588
input.addEventListener("input", function () {
76-
const errorElement = document.getElementById(`${input.id}-error`);
77-
if (input.value.trim() !== "" && errorElement) {
89+
const errorId = input.type === "radio" ? `${input.name}-error` : `${input.id}-error`;
90+
const errorElement = document.getElementById(errorId);
91+
if (errorElement && input.value.trim() !== "") {
7892
hideError(input, errorElement);
7993
}
8094
});
95+
if (input.type === "radio") {
96+
input.addEventListener("change", function () {
97+
const errorElement = document.getElementById(`${input.name}-error`);
98+
if (errorElement) {
99+
hideError(input, errorElement);
100+
}
101+
});
102+
}
81103
});
82104
});
83105
}

app/assets/sass/uswds/_uswds-theme-custom-styles.scss

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,6 @@ td.table-empty-message {
139139
font-family: family('sans');
140140
}
141141

142-
.usa-dark-background .pill-item__label {
143-
color: white;
144-
text-decoration: none !important;
145-
}
146-
147142
.pill-item.usa-link {
148143
text-decoration: none !important;
149144
}
@@ -198,9 +193,6 @@ td.table-empty-message {
198193
word-wrap: break-word;
199194
}
200195

201-
// border: 1px solid color('gray-cool-10');
202-
// padding: units(2);
203-
204196
.tick-cross-list-permissions {
205197
margin: units(1) 0;
206198
padding-left: units(2);
@@ -257,6 +249,11 @@ td.table-empty-message {
257249
}
258250
}
259251

252+
.usa-checkbox.template-list-item.template-list-item-with-checkbox.template-list-item-without-ancestors {
253+
display: flex;
254+
flex-direction: column;
255+
}
256+
260257
.usa-checkbox__label-description {
261258
margin: units(2px) 0 units(1) units(4);
262259
}
@@ -594,19 +591,31 @@ td.table-empty-message {
594591
.big-number-smallest {
595592
font-size: units(3);
596593
}
594+
.pill-item__label {
595+
color: white;
596+
}
597597
&:not(.pill-item--selected):hover {
598598
background: color('blue-warm-70v');
599+
600+
.pill-item__label {
601+
color: white;
602+
}
599603
}
600-
&.pill-item--selected:hover {
601-
color: color('blue-60v');
604+
&.pill-item--selected {
605+
.pill-item__label {
606+
color: color('blue-60v');
607+
}
608+
&:hover {
609+
.pill-item__label {
610+
color: color('blue-60v');
611+
}
612+
}
602613
}
603614
}
604615
}
605616
}
606617
}
607618

608-
// Etc
609-
610619
.email-brand,
611620
.browse-list {
612621
padding: 0;

app/config.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,6 @@ class Config(object):
8888
],
8989
}
9090

91-
FEATURE_ABOUT_PAGE_ENABLED = getenv("FEATURE_ABOUT_PAGE_ENABLED", "false") == "true"
92-
9391

9492
def _s3_credentials_from_env(bucket_prefix):
9593
return {

app/main/validators.py

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def __call__(self, form, field):
6161

6262

6363
class NoCommasInPlaceHolders:
64-
def __init__(self, message="You cannot put commas between double brackets"):
64+
def __init__(self, message="You cannot put commas between double parenthesis"):
6565
self.message = message
6666

6767
def __call__(self, form, field):
@@ -108,33 +108,17 @@ def __call__(self, form, field):
108108
)
109109
if non_sms_characters:
110110
raise ValidationError(
111-
"You cannot use {} in {}. {} will not show up properly on everyone’s phones.".format(
111+
"Please remove the unaccepted character {} in your message, then save again".format(
112112
formatted_list(
113113
non_sms_characters,
114-
conjunction="or",
114+
conjunction="and",
115115
before_each="",
116116
after_each="",
117117
),
118-
{
119-
"sms": "text messages",
120-
}.get(self._template_type),
121-
("It" if len(non_sms_characters) == 1 else "They"),
122118
)
123119
)
124120

125121

126-
# class NoPlaceholders:
127-
128-
# def __init__(self, message=None):
129-
# self.message = message or (
130-
# 'You can’t use ((double brackets)) to personalize this message'
131-
# )
132-
133-
# def __call__(self, form, field):
134-
# if Field(field.data).placeholders:
135-
# raise ValidationError(self.message)
136-
137-
138122
class LettersNumbersSingleQuotesFullStopsAndUnderscoresOnly:
139123
regex = re.compile(r"^[a-zA-Z0-9\s\._']+$")
140124

app/main/views/dashboard.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,13 +132,18 @@ def get_local_daily_stats_for_last_x_days(stats_utc, user_timezone, days):
132132
@user_has_permissions()
133133
def get_daily_stats_by_user(service_id):
134134
date_range = get_stats_date_range()
135-
stats = service_api_client.get_user_service_notification_statistics_by_day(
135+
days = date_range["days"]
136+
user_timezone = request.args.get("timezone", "UTC")
137+
138+
stats_utc = service_api_client.get_user_service_notification_statistics_by_day(
136139
service_id,
137140
user_id=current_user.id,
138141
start_date=date_range["start_date"],
139-
days=date_range["days"],
142+
days=days,
140143
)
141-
return jsonify(stats)
144+
145+
local_stats = get_local_daily_stats_for_last_x_days(stats_utc, user_timezone, days)
146+
return jsonify(local_stats)
142147

143148

144149
@main.route("/services/<uuid:service_id>/template-usage")

0 commit comments

Comments
 (0)