diff --git a/.gitignore b/.gitignore index 62f46925c..7e07fc102 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,10 @@ *.XLSX ## Non user files allowed to be commited +!app/assets/pdf/best-practices-for-texting-the-public.pdf +!app/assets/pdf/investing-in-notifications-tts-public-benefits-studio-decision-memo.pdf +!app/assets/pdf/best-practices-section-outline.pdf +!app/assets/pdf/standing-up-your-own-notify.pdf !app/assets/pdf/tcpa_overview.pdf !app/assets/pdf/investing-notifications-tts-public-benefits-memo.pdf !app/assets/pdf/out-of-pilot-announcement.pdf diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cb3c48cae..579650135 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,6 +8,7 @@ repos: - id: end-of-file-fixer - id: check-yaml - id: check-added-large-files + exclude: ^app/assets/pdf/.*\.pdf$ - id: debug-statements - id: check-merge-conflict - id: check-toml diff --git a/Makefile b/Makefile index 8fa5364a0..30eed146d 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ APP_VERSION_FILE = app/version.py GIT_BRANCH ?= $(shell git symbolic-ref --short HEAD 2> /dev/null || echo "detached") GIT_COMMIT ?= $(shell git rev-parse HEAD 2> /dev/null || echo "") +GIT_HOOKS_PATH ?= $(shell git config --global core.hooksPath || echo "") VIRTUALENV_ROOT := $(shell [ -z $$VIRTUAL_ENV ] && echo $$(pwd)/venv || echo $$VIRTUAL_ENV) @@ -14,7 +15,8 @@ NVMSH := $(shell [ -f "$(HOME)/.nvm/nvm.sh" ] && echo "$(HOME)/.nvm/nvm.sh" || e ## DEVELOPMENT .PHONY: bootstrap -bootstrap: generate-version-file ## Set up everything to run the app +bootstrap: ## Set up everything to run the app + make generate-version-file poetry self add poetry-dotenv-plugin poetry lock --no-update poetry install --sync --no-root @@ -24,6 +26,20 @@ bootstrap: generate-version-file ## Set up everything to run the app source $(NVMSH) && npm ci --no-audit source $(NVMSH) && npm run build +.PHONY: bootstrap-with-git-hooks +bootstrap-with-git-hooks: ## Sets everything up and accounts for pre-existing git hooks + make generate-version-file + poetry self add poetry-dotenv-plugin + poetry lock --no-update + poetry install --sync --no-root + poetry run playwright install --with-deps + git config --global --unset-all core.hooksPath + poetry run pre-commit install + git config --global core.hookspath "${GIT_HOOKS_PATH}" + source $(NVMSH) --no-use && nvm install && npm install + source $(NVMSH) && npm ci --no-audit + source $(NVMSH) && npm run build + .PHONY: watch-frontend watch-frontend: ## Build frontend and watch for changes source $(NVMSH) && npm run watch diff --git a/app/__init__.py b/app/__init__.py index 2ae627f10..f1c1d5fe4 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -145,6 +145,7 @@ def _csp(config): "frame-src": [ "https://www.youtube.com", "https://www.youtube-nocookie.com", + "https://www.googletagmanager.com", ], "frame-ancestors": "'none'", "form-action": "'self'", @@ -169,6 +170,11 @@ def _csp(config): def create_app(application): + @application.after_request + def add_csp_header(response): + existing_csp = response.headers.get("Content-Security-Policy", "") + response.headers["Content-Security-Policy"] = existing_csp + "; form-action 'self';" + return response # @application.context_processor # def inject_feature_flags(): # this is where feature flags can be easily added as a dictionary within context diff --git a/app/assets/javascripts/validation.js b/app/assets/javascripts/validation.js index 0f60fc299..ade16c0e3 100644 --- a/app/assets/javascripts/validation.js +++ b/app/assets/javascripts/validation.js @@ -38,6 +38,7 @@ function attachValidation() { const validatedRadioNames = new Set(); inputs.forEach((input) => { + if (input.type === "hidden") return; const errorId = input.type === "radio" ? `${input.name}-error` : `${input.id}-error`; let errorElement = document.getElementById(errorId); @@ -85,6 +86,7 @@ function attachValidation() { }); inputs.forEach((input) => { + if (input.type === "hidden") return; input.addEventListener("input", function () { const errorId = input.type === "radio" ? `${input.name}-error` : `${input.id}-error`; const errorElement = document.getElementById(errorId); diff --git a/app/assets/pdf/best-practices-for-texting-the-public.pdf b/app/assets/pdf/best-practices-for-texting-the-public.pdf new file mode 100644 index 000000000..04e9cc99c Binary files /dev/null and b/app/assets/pdf/best-practices-for-texting-the-public.pdf differ diff --git a/app/assets/pdf/best-practices-section-outline.pdf b/app/assets/pdf/best-practices-section-outline.pdf new file mode 100644 index 000000000..4c19613b0 Binary files /dev/null and b/app/assets/pdf/best-practices-section-outline.pdf differ diff --git a/app/assets/pdf/investing-in-notifications-tts-public-benefits-studio-decision-memo.pdf b/app/assets/pdf/investing-in-notifications-tts-public-benefits-studio-decision-memo.pdf new file mode 100644 index 000000000..727d6a122 Binary files /dev/null and b/app/assets/pdf/investing-in-notifications-tts-public-benefits-studio-decision-memo.pdf differ diff --git a/app/assets/pdf/standing-up-your-own-notify.pdf b/app/assets/pdf/standing-up-your-own-notify.pdf new file mode 100644 index 000000000..f76c9d094 Binary files /dev/null and b/app/assets/pdf/standing-up-your-own-notify.pdf differ diff --git a/app/assets/sass/uswds/_main.scss b/app/assets/sass/uswds/_main.scss index db6b166ff..983a72c8c 100644 --- a/app/assets/sass/uswds/_main.scss +++ b/app/assets/sass/uswds/_main.scss @@ -49,12 +49,12 @@ } .sms-message-sender, .sms-message-file-name, .sms-message-scheduler, .sms-message-template, .sms-message-sender { - margin:0.25rem 0 0; + margin: units(0.5) 0 0; } .sms-message-recipient { color: color('gray-cool-90'); - margin: units(1) 0 units(1); + margin: units(0.5) 0 units(2); } .sms-message-status { diff --git a/app/s3_client/s3_csv_client.py b/app/s3_client/s3_csv_client.py index 195ea3032..4d8f33a07 100644 --- a/app/s3_client/s3_csv_client.py +++ b/app/s3_client/s3_csv_client.py @@ -28,8 +28,17 @@ def get_csv_upload(service_id, upload_id): return get_s3_object(*get_csv_location(service_id, upload_id)) +def remove_blank_lines(filedata): + # sometimes people upload files with hundreds of blank lines at the end + data = filedata["data"] + cleaned_data = "\n".join(line for line in data.splitlines() if line.strip()) + filedata["data"] = cleaned_data + return filedata + + def s3upload(service_id, filedata): + filedata = remove_blank_lines(filedata) upload_id = str(uuid.uuid4()) bucket_name, file_location, access_key, secret_key, region = get_csv_location( service_id, upload_id diff --git a/app/templates/base.html b/app/templates/base.html index c5ee1ab42..2dc0ae9e0 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -20,7 +20,7 @@ {% block bodyStart %} {% block extra_javascripts_before_body %} - {% endblock %} {% endblock %} diff --git a/app/templates/views/about/about.html b/app/templates/views/about/about.html index 5b034d34f..51272b5e4 100644 --- a/app/templates/views/about/about.html +++ b/app/templates/views/about/about.html @@ -16,7 +16,10 @@
  • More effectively deliver program outcomes
  • Save administrative costs
  • Implement 21st Century + href="https://digital.gov/resources/delivering-digital-first-public-experience/" + rel="noopener noreferrer" + target="_blank" + aria-label="21st Century IDEA (opens in a new tab)">21st Century IDEA and other directives
  • Notify.gov is an easy-to-use, web-based platform. It requires no technical expertise or system integration — users diff --git a/app/templates/views/about/security.html b/app/templates/views/about/security.html index 0a916035e..8a32e3fc7 100644 --- a/app/templates/views/about/security.html +++ b/app/templates/views/about/security.html @@ -22,16 +22,26 @@

    Notify.gov operates under a full three-year Authority-to-Operate (ATO). This + href="https://digital.gov/resources/an-introduction-to-ato/" + target="_blank" + rel="noopener noreferrer" + aria-label="Authority-to-Operate (ATO) (opens in a new tab)">Authority-to-Operate (ATO). This federal security authorization process leverages security controls provided by National Institute of Standards and Technology (NIST).

    - Our infrastructure runs on cloud.gov and utilizes several + Our infrastructure runs on cloud.gov and utilizes several services through Amazon Web - Services (AWS), including + Services (AWS), including AWS SNS for sending SMS messages.

    @@ -53,15 +63,22 @@

    Multi-Factor Authentication

    - Notify.gov uses Login.gov for enhanced security. + Notify.gov uses Login.gov for enhanced security. Login.gov is an extra layer of security created by the government that uses multi-factor authentication and stronger passwords to protect your account.

    To access Notify.gov, users will use a Login.gov account associated with their agency (.gov) email with one of the - multi-factor authentication + multi-factor authentication methods offered through Login.gov.

    diff --git a/app/templates/views/about/why-text-messaging.html b/app/templates/views/about/why-text-messaging.html index d9ec2635a..b59acf3ca 100644 --- a/app/templates/views/about/why-text-messaging.html +++ b/app/templates/views/about/why-text-messaging.html @@ -16,7 +16,9 @@

    Confusing or unreceived notifications are one of the largest barriers to people getting and keeping + target="_blank" + rel="noopener noreferrer" + aria-label="unreceived notifications (opens in a new tab)">unreceived notifications are one of the largest barriers to people getting and keeping benefits. The typical ways the government communicates with people often fall short. Low income households are more likely to experience housing instability, which means paper mail, already slow, can easily be missed.

    @@ -24,7 +26,9 @@

    Pew Research shows that nearly all adults in the US have a cell phone. Reliance on smartphones + target="_blank" + rel="noopener noreferrer" + aria-label="Pew Research shows that nearly all adults in the US have a cell phone (opens in a new tab)">Pew Research shows that nearly all adults in the US have a cell phone. Reliance on smartphones for online access is especially common among Americans with lower household incomes and those with lower levels of formal education. Of those earning less than $30,000 a year, 28% say their mobile phone is the sole method to digitally connect. diff --git a/app/templates/views/guides/benchmark-performance.html b/app/templates/views/guides/benchmark-performance.html index c302fc5b8..39ca69700 100644 --- a/app/templates/views/guides/benchmark-performance.html +++ b/app/templates/views/guides/benchmark-performance.html @@ -23,7 +23,9 @@

    When the Center on Budget and Policy Priorities studied WIC, they found key learnings about the + target="_blank" + rel="noopener noreferrer" + aria-label="Center on Budget and Policy Priorities (opens in a new tab)">Center on Budget and Policy Priorities studied WIC, they found key learnings about the quantity of messages delivered, how people engage with messages, and how they take action.

    @@ -68,8 +70,11 @@

    - The Code for America’s Texting + The Code for America’s Texting Playbook reported specific learnings around appointment reminders, completing document submission, and maintenance reminders. diff --git a/app/templates/views/guides/clear-goals.html b/app/templates/views/guides/clear-goals.html index a6e3a742b..f3a188766 100644 --- a/app/templates/views/guides/clear-goals.html +++ b/app/templates/views/guides/clear-goals.html @@ -142,7 +142,9 @@

    The Department of Veterans Affairs provides a helpful flow-chart that can help you decide if a text message is needed + target="_blank" + rel="noopener noreferrer" + aria-label="helpful flow-chart (opens in a new tab)">helpful flow-chart that can help you decide if a text message is needed for the communication problem you are trying to solve.

    Use a hypothesis framework to plan your campaign

    diff --git a/app/templates/views/guides/establish-trust.html b/app/templates/views/guides/establish-trust.html index 026e9da81..5d72f66b4 100644 --- a/app/templates/views/guides/establish-trust.html +++ b/app/templates/views/guides/establish-trust.html @@ -101,8 +101,11 @@

    {{circle_number(7) }}Dept. of Social Services: Hi {{circle_number(8) }}Julie, Your Medicaid renewal is closing December 31, 2023. You can renew online at {{circle_number(9) }}https://www.application.yourstate.gov or {{circle_number(10) }} + class="use-link usa-link--external" + href="https://www.application.yourstate.gov" + target="_blank" + rel="application.yourstate.gov" + aria-label="Yourstate dot gov (opens in a new tab)">https://www.application.yourstate.gov or {{circle_number(10) }} call the number on the back of your Medicaid card.

    diff --git a/app/templates/views/guides/rules-and-regulations.html b/app/templates/views/guides/rules-and-regulations.html index 8ad6d000b..7f86248ea 100644 --- a/app/templates/views/guides/rules-and-regulations.html +++ b/app/templates/views/guides/rules-and-regulations.html @@ -29,7 +29,10 @@ {% set links = [ { "p_text": 'The Telephone Consumer + target="_blank" + rel="noopener noreferrer" + aria-label="Telephone Consumer Protection Act (TCPA) (opens in a new tab)" + >Telephone Consumer Protection Act (TCPA) (47 USC § 227) is the federal law that impacts how organizations are allowed to communicate in bulk with the public via telephone (including text message or SMS).' @@ -37,7 +40,9 @@ { "p_text": 'The FCC has ruled that Federal and State programs are exempt from the TCPA and can + target="_blank" + rel="noopener noreferrer" + aria-label="FCC has ruled (opens in a new tab)">FCC has ruled that Federal and State programs are exempt from the TCPA and can send text messages to the public without consent if conducting official business. Without explicit mention in the ruling, local governments, phone carriers, or any texting intermediaries might require it.' @@ -52,7 +57,10 @@ case. For additional questions about the enforcement of the TCPA, you can watch a recorded training on public + target="_blank" + rel="noopener noreferrer" + aria-label="a recorded training on public + benefits texting (opens in a new tab)">a recorded training on public benefits texting provided by the FCC.' }, ] %} @@ -65,7 +73,9 @@

    + target="_blank" + rel="noopener noreferrer" + aria-label="{{ item.url_text }} (opens in a new tab)"> {{ item.url_text }}

    @@ -119,6 +129,7 @@ process to keep your phone number list up-to-date. The FCC offers a Reassigned Numbers Database to identify phone numbers that may have been reassigned since your agency obtained them.

    diff --git a/app/templates/views/guides/write-for-action.html b/app/templates/views/guides/write-for-action.html index 31ac2aa84..f7574def5 100644 --- a/app/templates/views/guides/write-for-action.html +++ b/app/templates/views/guides/write-for-action.html @@ -81,7 +81,10 @@

    What provoking action looks like

    Evidence + href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC10002044/" + target="_blank" + rel="noopener noreferrer" + aria-label="Evidence shows (opens in a new tab)">Evidence shows that employing behavioral science is an effective way to increase the likelihood of a recipient diff --git a/app/templates/views/platform-admin/reports.html b/app/templates/views/platform-admin/reports.html index 23dccc41c..02157c2a2 100644 --- a/app/templates/views/platform-admin/reports.html +++ b/app/templates/views/platform-admin/reports.html @@ -11,7 +11,7 @@

    - Download live services csv report + Download live services csv report

    Monthly notification statuses for live services diff --git a/app/templates/views/send.html b/app/templates/views/send.html index b130db098..98ab1f505 100644 --- a/app/templates/views/send.html +++ b/app/templates/views/send.html @@ -7,7 +7,7 @@ {% block service_page_title %} - Upload a list of {{ 999|recipient_count_label(template.template_type) }} + Upload your bulk-sending spreadsheet {% endblock %} @@ -17,21 +17,62 @@ }) }} {% endblock %} +{% set phone_numbers = [ + { + "svg_src": "#check_circle", + "card_heading": "Label column A (the first column) as Phone number", + }, + { + "svg_src": "#check_circle", + "card_heading": "Double check it's the only column with Phone number as its label", + }, + { + "svg_src": "#check_circle", + "card_heading": "Make sure no duplicate phone numbers are listed in Column A", + } + ] +%} + +{% set additional_data = [ + { + "svg_src": "#check_circle", + "card_heading": "Match column labels one-to-one to the message template placeholders", + }, + { + "svg_src": "#check_circle", + "card_heading": "Label each additional personalized placeholder separately", + }, + { + "svg_src": "#check_circle", + "card_heading": "Separate each word in a column label with a space or dash, but no commas", + }, + { + "svg_src": "#check_circle", + "card_heading": "Fill in each personalized placeholder with the appropriate data or information", + }, + { + "svg_src": "#check_circle", + "card_heading": "Fill in each conditional placeholder column with a Yes (Y) or No (N) to “answer” whether the recipient meets its criteria", + } + ] +%} + {% block maincolumn_content %} - {{ page_header('Upload a list of {}'.format(999|recipient_count_label(template.template_type))) }} + {{ page_header('Upload your bulk-sending spreadsheet')}} +

    Organize phone numbers and information in a single spreadsheet and upload when you have multiple messages to send. Column headers in the spreadsheet will place the data in the right spots within the template that's selected.

    -