diff --git a/.env.example b/.env.example index 9126b59c0c..ad228c29bd 100644 --- a/.env.example +++ b/.env.example @@ -16,7 +16,5 @@ AWS_SECRET_ACCESS_KEY= REDIS_ENABLED=False -MIXPANEL_PROJECT_TOKEN= - ALLOW_DEBUG_ROUTE=False DEBUG_KEY= \ No newline at end of file diff --git a/.github/workflows/export_github_data.yml b/.github/workflows/export_github_data.yml index 2cd1112120..fe9bd96912 100644 --- a/.github/workflows/export_github_data.yml +++ b/.github/workflows/export_github_data.yml @@ -14,7 +14,7 @@ jobs: DNS_PROXY_FORWARDTOSENTINEL: "true" DNS_PROXY_LOGANALYTICSWORKSPACEID: ${{ secrets.LOG_ANALYTICS_WORKSPACE_ID }} DNS_PROXY_LOGANALYTICSSHAREDKEY: ${{ secrets.LOG_ANALYTICS_WORKSPACE_KEY }} - - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Export Data uses: cds-snc/github-repository-metadata-exporter@main with: diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 556ccb91f8..4c4fa192d0 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -20,12 +20,12 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@72803a12483ed6f4f7c34f804818169f50162e37 + uses: ossf/scorecard-action@e48dbb732fab761267783321dfa71e2a5c6e263d with: results_file: ossf-results.json results_format: json diff --git a/.github/workflows/s3-backup.yml b/.github/workflows/s3-backup.yml index d850d5e084..28fa505ba4 100644 --- a/.github/workflows/s3-backup.yml +++ b/.github/workflows/s3-backup.yml @@ -10,7 +10,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 # retrieve all history diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index f2e6f1ba01..2e72c97d29 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -38,7 +38,6 @@ jobs: - run: /bin/bash -c "scripts/bootstrap.sh && poetry run make test" env: A11Y_TRACKER_KEY: ${{ secrets.A11Y_TRACKER_KEY }} - MIXPANEL_PROJECT_TOKEN: - name: Notify Slack channel if this job failed in default branch if: ${{ failure() && github.ref == 'refs/heads/main' }} run: | diff --git a/.github/workflows/test_prod_config.yaml b/.github/workflows/test_prod_config.yaml index 8e7ba34d00..39af044740 100644 --- a/.github/workflows/test_prod_config.yaml +++ b/.github/workflows/test_prod_config.yaml @@ -41,7 +41,6 @@ jobs: - run: /bin/bash -c "scripts/bootstrap.sh && poetry run make test" env: A11Y_TRACKER_KEY: ${{ secrets.A11Y_TRACKER_KEY }} - MIXPANEL_PROJECT_TOKEN: - name: Notify Slack channel if this job failed in default branch if: ${{ failure() && github.ref == 'refs/heads/main' }} run: | diff --git a/app/main/validators.py b/app/main/validators.py index 77ad8365b4..a0d7b4b49d 100644 --- a/app/main/validators.py +++ b/app/main/validators.py @@ -175,7 +175,7 @@ def validate_callback_url(service_callback_url, bearer_token): response = requests.post( url=service_callback_url, allow_redirects=True, - data={"health_check": "true"}, + json={"health_check": True}, headers={"Content-Type": "application/json", "Authorization": f"Bearer {bearer_token}"}, timeout=2, ) diff --git a/app/main/views/api_keys.py b/app/main/views/api_keys.py index 8b5100d578..efeb907317 100644 --- a/app/main/views/api_keys.py +++ b/app/main/views/api_keys.py @@ -192,7 +192,7 @@ def delete_delivery_status_callback(service_id): form = ServiceDeliveryStatusCallbackForm( url=delivery_status_callback.get("url") if delivery_status_callback else "", - bearer_token=dummy_bearer_token if delivery_status_callback else "", + bearer_token=delivery_status_callback.get("bearer") if delivery_status_callback else "", ) return render_template( @@ -216,7 +216,7 @@ def delivery_status_callback(service_id): form = ServiceDeliveryStatusCallbackForm( url=delivery_status_callback.get("url") if delivery_status_callback else "", - bearer_token=dummy_bearer_token if delivery_status_callback else "", + bearer_token=delivery_status_callback.get("bearer") if delivery_status_callback else "", ) if form.validate_on_submit(): diff --git a/app/main/views/authenticator.py b/app/main/views/authenticator.py index 3f5926df97..ca1e4d3161 100644 --- a/app/main/views/authenticator.py +++ b/app/main/views/authenticator.py @@ -1,13 +1,11 @@ from flask import session from app.models.user import User -from config.mixpanel import NotifyMixpanel class Authenticator: def __init__(self, user_id): self.user = User.from_id(user_id) - self.mixpanel = NotifyMixpanel() def __enter__(self) -> User: # the user will have a new current_session_id set by the API - store it in the cookie for future requests @@ -23,9 +21,6 @@ def __enter__(self) -> User: return self.user def __exit__(self, _exec_type, _exec_value, _traceback) -> None: - self.mixpanel.track_user_profile(self.user) - self.mixpanel.track_event(self.user, "Notify: Logged in") - # get rid of anything in the session that we don't expect to have been set during register/sign in flow session.pop("user_details", None) session.pop("file_uploads", None) diff --git a/app/templates/partials/jobs/status.html b/app/templates/partials/jobs/status.html index 07a5fa6967..6572ea5fad 100644 --- a/app/templates/partials/jobs/status.html +++ b/app/templates/partials/jobs/status.html @@ -1,34 +1,52 @@ +{% from "components/table.html" import mapping_table, row, row_heading, text_field, link_field, with context %} +
-

- -

-
- {{ _('Sent by:') }} - {% if job.api_key %} - {{ _("API key '{}'").format(url_for('.api_keys', service_id=current_service.id), job.api_key.name) }} - {% else %} - {{ job.created_by.name }} - {% endif %} -
-
- {{ _('Started:') }} - -
-
- {% if job.api_key %} - {{ _('API request name:')}} - {% else %} - {{ _('Spreadsheet:') }} - {% endif %} - {{ job.original_file_name }} -
-
- - - {% if template_type == "letter" %} -

- {{ letter_print_day }} -

+
+ {% set caption = _("Scheduled messages") %} + + {% call mapping_table( + caption=caption, + field_headings_visible=False, + caption_visible=False, + font_size='table-font-small', + ) %} + + {% call row() %} + {% call row_heading(cell_width="sm:w-1/3") %} + {{ _('Sent by') }} + {% endcall %} + {% if job.api_key %} + {{ text_field(_("API key '{}'").format(url_for('.api_keys', service_id=current_service.id), job.api_key.name)) }} + {% else %} + {{ text_field(job.created_by.name) }} {% endif %} -

+ {% endcall %} + + {% call row() %} + {% call row_heading(cell_width="sm:w-1/3") %} + {{ _('Started') }} + {% endcall %} + {{ text_field(text=job.created_at, date_format="local-datetime-short") }} + {% endcall %} + + {% call row() %} + {% call row_heading(cell_width="sm:w-1/3") %} + {% if job.api_key %} + {{ _('API request name')}} + {% else %} + {{ _('Spreadsheet') }} + {% endif %} + {% endcall %} + {{ text_field(job.original_file_name) }} + {% endcall %} + + {% call row() %} + {% call row_heading(cell_width="sm:w-1/3") %} + {{ _('Template') }} + {% endcall %} + {{ link_field(template.name, url_for('.view_template', service_id=job.service, template_id=template.id)) }} + {% endcall %} + {% endcall %} + +
diff --git a/app/translations/csv/fr.csv b/app/translations/csv/fr.csv index 86e5e83525..0bb35cb814 100644 --- a/app/translations/csv/fr.csv +++ b/app/translations/csv/fr.csv @@ -1823,6 +1823,7 @@ "Try creating {} in a different application","Essayez de créer {} dans une application différente." "Sent by:","Envoyé par :" "Started:","Début :" +"Started","Début" "Spreadsheet:","Feuille de calcul :" "API request name:","Nom de la requête API :" "GC Notify disposed of the information in this report on ","Notification GC a supprimé les renseignements de ce rapport le " diff --git a/ci/cloudbuild-test.yaml b/ci/cloudbuild-test.yaml index 84f25b464a..6c970f921d 100644 --- a/ci/cloudbuild-test.yaml +++ b/ci/cloudbuild-test.yaml @@ -4,6 +4,5 @@ steps: args: ["-c", "/workspace/scripts/bootstrap.sh && make test"] env: - 'CHROMEDRIVER_PATH=/usr/bin/chromedriver' - - 'MIXPANEL_PROJECT_TOKEN=' options: machineType: 'N1_HIGHCPU_32' diff --git a/config/mixpanel.py b/config/mixpanel.py deleted file mode 100644 index 453d058a6f..0000000000 --- a/config/mixpanel.py +++ /dev/null @@ -1,45 +0,0 @@ -import os - -from flask import current_app -from mixpanel import Mixpanel # type: ignore - -from app.models.user import User - - -class NotifyMixpanel: - enabled = False - - def __init__(self) -> None: - super().__init__() - self.token = os.environ.get("MIXPANEL_PROJECT_TOKEN") - self.mixpanel = Mixpanel(self.token) - if not self.token: - current_app.logger.warning( - "MIXPANEL_PROJECT_TOKEN is not set. Mixpanel features will not be supported." - "In order to enable Mixpanel, set MIXPANEL_PROJECT_TOKEN environment variable." - ) - NotifyMixpanel.enabled = False - else: - NotifyMixpanel.enabled = True - - def __mixpanel_enabled(callable): - def wrapper(*args, **kwargs): - if NotifyMixpanel.enabled: - callable(*args, **kwargs) - - return wrapper - - @__mixpanel_enabled # type: ignore - def track_event(self, user: User, msg="Notify: Sent message") -> None: - if user: - self.mixpanel.track(user.email_address, msg, {"product": "Notify"}) - - @__mixpanel_enabled # type: ignore - def track_user_profile(self, user: User) -> None: - if user: - profile = { - "$first_name": user.name.split()[0], - "$last_name": user.name.split()[-1], - "$email": user.email_address, - } - self.mixpanel.people_set(user.email_address, profile, meta={"product": "Notify"}) diff --git a/poetry.lock b/poetry.lock index dc442dbfa8..c1156b1c30 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1386,22 +1386,6 @@ files = [ {file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"}, ] -[[package]] -name = "mixpanel" -version = "4.10.1" -description = "Official Mixpanel library for Python" -optional = false -python-versions = ">=2.7, !=3.4.*" -files = [ - {file = "mixpanel-4.10.1-py2.py3-none-any.whl", hash = "sha256:a7a338b7197327e36356dbc1903086e7626db6d88367ccdd732b3f3c60d3b3ed"}, - {file = "mixpanel-4.10.1.tar.gz", hash = "sha256:29a6b5773dd34f05cf8e249f4e1d16e7b6280d6b58894551ce9a5aad7700a115"}, -] - -[package.dependencies] -requests = ">=2.4.2" -six = ">=1.9.0" -urllib3 = "*" - [[package]] name = "monkeytype" version = "23.3.0" @@ -2750,4 +2734,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "~3.12.7" -content-hash = "7bbb976b25e6bd92be3bdc2718323d5923fd79c834e1fca0a2fc8b0b569fc4e4" +content-hash = "a27d5771cdbf73ac0bca3cb9f4700ac2b5378960ec43b88fa76d289195a25806" diff --git a/pyproject.toml b/pyproject.toml index 9f5c92e6b2..f033a9f076 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,6 @@ WTForms = "3.1.2" email-validator = "1.3.1" Werkzeug = "3.0.4" greenlet = "3.1.1" -mixpanel = "4.10.1" unidecode = "^1.3.8" # PaaS diff --git a/tests/app/main/views/test_jobs.py b/tests/app/main/views/test_jobs.py index e008280b04..4f2eb3b95a 100644 --- a/tests/app/main/views/test_jobs.py +++ b/tests/app/main/views/test_jobs.py @@ -186,7 +186,10 @@ def test_should_show_page_for_one_job( ) assert page.h1.text.strip() == "Delivery report" - assert " ".join(page.find("tbody").find("tr").text.split()) == ("6502532222 template content No Delivered 11:10:00.061258") + dashboard_table = page.find_all("table")[1] + assert " ".join(dashboard_table.find("tbody").find("tr").text.split()) == ( + "6502532222 template content No Delivered 11:10:00.061258" + ) assert page.find("div", {"data-key": "notifications"})["data-resource"] == url_for( "main.view_job_updates", service_id=SERVICE_ONE_ID, @@ -204,10 +207,10 @@ def test_should_show_page_for_one_job( assert csv_link.text == "Download this report" assert page.find("time", {"id": "time-left"}).text.split(" ")[0] == "2016-01-09" - assert normalize_spaces(page.select_one("tbody tr").text) == normalize_spaces( + assert normalize_spaces(dashboard_table.select_one("tbody tr").text) == normalize_spaces( "6502532222 " "template content " "No " "Delivered 11:10:00.061258" ) - assert page.select_one("tbody tr a")["href"] == url_for( + assert dashboard_table.select_one("tbody tr a")["href"] == url_for( "main.view_notification", service_id=SERVICE_ONE_ID, notification_id=sample_uuid(), @@ -434,15 +437,13 @@ def test_should_show_job_from_api( service_id=SERVICE_ONE_ID, job_id=fake_uuid, ) + job_info_table = page.find_all("table")[0] - assert ( - normalize_spaces(page.select("div[data-test-id='dr_header'] >div:nth-child(1)")[0].text) - == f"Sent by: API key '{JOB_API_KEY_NAME}'" - ) - assert ( - normalize_spaces(page.select("div[data-test-id='dr_header'] >div:nth-child(2)")[0].text) - == "Started: 2016-01-01T00:00:00.061258+0000" - ) + assert normalize_spaces(job_info_table.select("th")[0].text) == "Sent by" + assert normalize_spaces(job_info_table.select("td")[0].text) == f"API key '{JOB_API_KEY_NAME}'" + + assert normalize_spaces(job_info_table.select("th")[1].text) == "Started" + assert normalize_spaces(job_info_table.select("td")[1].text) == "2016-01-01T00:00:00.061258+0000" # TODO: This test could be migrated to Cypress instead @@ -579,6 +580,7 @@ def test_should_not_show_cancel_link_for_letter_job_if_too_late( @freeze_time("2019-06-20 15:32:00.000001") +@pytest.mark.skip(reason="feature not in use") @pytest.mark.parametrize(" job_status", ["finished", "in progress"]) def test_should_show_cancel_link_for_letter_job( client_request, diff --git a/tests/app/main/views/test_new_password.py b/tests/app/main/views/test_new_password.py index 74f0d779a6..9c4334f2da 100644 --- a/tests/app/main/views/test_new_password.py +++ b/tests/app/main/views/test_new_password.py @@ -1,5 +1,4 @@ import json -import os from datetime import datetime from unittest import mock @@ -11,17 +10,6 @@ from tests.conftest import url_for_endpoint_with_token -@pytest.fixture(autouse=True) -def stub_mixpanel(mocker): - environment_vars = os.environ.copy() - os.environ["MIXPANEL_PROJECT_TOKEN"] = "project_token_from_mixpanel" - mocker.patch("mixpanel.Mixpanel") - - yield - - os.environ = environment_vars - - def test_should_render_new_password_template( app_, client, diff --git a/tests/app/main/views/test_two_factor.py b/tests/app/main/views/test_two_factor.py index 7593031e70..b357e343e6 100644 --- a/tests/app/main/views/test_two_factor.py +++ b/tests/app/main/views/test_two_factor.py @@ -1,5 +1,3 @@ -import os - import pytest from bs4 import BeautifulSoup from flask import url_for @@ -7,17 +5,6 @@ from tests.conftest import SERVICE_ONE_ID, captured_templates -@pytest.fixture(autouse=True) -def stub_mixpanel(mocker): - environment_vars = os.environ.copy() - os.environ["MIXPANEL_PROJECT_TOKEN"] = "project_token_from_mixpanel" - mocker.patch("mixpanel.Mixpanel") - - yield - - os.environ = environment_vars - - def test_should_render_sms_two_factor_page( client, api_user_active, diff --git a/tests/config/test_mixpanel.py b/tests/config/test_mixpanel.py deleted file mode 100644 index c144757fd4..0000000000 --- a/tests/config/test_mixpanel.py +++ /dev/null @@ -1,62 +0,0 @@ -import os - -import pytest - -from app.models.user import User -from config.mixpanel import NotifyMixpanel - - -@pytest.fixture(autouse=True) -def environment_vars_fixtures(app_): - with app_.app_context(): - environment_vars = os.environ.copy() - os.environ["MIXPANEL_PROJECT_TOKEN"] = "project_token_from_mixpanel" - - yield - - os.environ = environment_vars - - -def test_when_mixpanel_project_token_is_not_set(mocker, environment_vars_fixtures): - os.environ["MIXPANEL_PROJECT_TOKEN"] = "" - - mocked_current_app_logger_warning_fxn = mocker.patch("flask.current_app.logger.warning") - NotifyMixpanel() - mocked_current_app_logger_warning_fxn.assert_called_once() - - -def test_when_mixpanel_project_token_is_set(mocker, environment_vars_fixtures): - mocked_current_app_logger_warning_fxn = mocker.patch("flask.current_app.logger.warning") - NotifyMixpanel() - - mocked_current_app_logger_warning_fxn.assert_not_called() - - -def test_track_mixpanel_user_profile_when_user_is_not_present(mocker, environment_vars_fixtures): - mocked_mixpanel_people_set_fxn = mocker.patch("mixpanel.Mixpanel.people_set") - NotifyMixpanel().track_user_profile(None) - - mocked_mixpanel_people_set_fxn.assert_not_called() - - -def test_track_mixpanel_user_profile(mocker, active_user_with_permissions, environment_vars_fixtures): - mocked_mixpanel_people_set_fxn = mocker.patch("mixpanel.Mixpanel.people_set") - user = User(active_user_with_permissions) - NotifyMixpanel().track_user_profile(user) - - mocked_mixpanel_people_set_fxn.assert_called_once() - - -def test_track_mixpanel_event_when_user_is_not_present(mocker, environment_vars_fixtures): - mocked_mixpanel_track_fxn = mocker.patch("mixpanel.Mixpanel.track") - NotifyMixpanel().track_event(None) - - mocked_mixpanel_track_fxn.assert_not_called() - - -def test_track_mixpanel_event(mocker, active_user_with_permissions, environment_vars_fixtures): - mocked_mixpanel_track_fxn = mocker.patch("mixpanel.Mixpanel.track") - user = User(active_user_with_permissions) - NotifyMixpanel().track_event(user) - - mocked_mixpanel_track_fxn.assert_called_once()