diff --git a/.ds.baseline b/.ds.baseline index 6d2034588..f7f116b20 100644 --- a/.ds.baseline +++ b/.ds.baseline @@ -133,7 +133,7 @@ "filename": ".github/workflows/checks.yml", "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", "is_verified": false, - "line_number": 67, + "line_number": 68, "is_secret": false }, { @@ -141,7 +141,7 @@ "filename": ".github/workflows/checks.yml", "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", "is_verified": false, - "line_number": 101, + "line_number": 103, "is_secret": false } ], @@ -692,5 +692,5 @@ } ] }, - "generated_at": "2024-07-24T14:13:02Z" + "generated_at": "2024-08-07T14:36:40Z" } diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1be311a7e..3e3ce01d3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,14 +3,14 @@ # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates -version: 3 +version: 2 updates: - package-ecosystem: "pip" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "daily" - - package-ecosystem: 'npm' - directory: '/' + - package-ecosystem: "npm" + directory: "/" schedule: - interval: 'daily' - versioning-strategy: 'increase' + interval: "daily" + versioning-strategy: increase diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index edd14c6ea..efeefdbd6 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -54,6 +54,7 @@ jobs: run: poetry run coverage report --fail-under=90 end-to-end-tests: + if: ${{ github.actor != 'dependabot[bot]' }} permissions: checks: write pull-requests: write @@ -84,6 +85,7 @@ jobs: ports: # Maps tcp port 6379 on service container to the host - 6379:6379 + steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-project @@ -136,6 +138,12 @@ jobs: # Debugging for now to troubleshoot a connectivity issue to the local servers # run: curl --request GET --url "http://localhost:6012" env: + API_HOST_NAME: http://localhost:6011 + DANGEROUS_SALT: ${{ secrets.DANGEROUS_SALT }} + SECRET_KEY: ${{ secrets.SECRET_KEY }} + ADMIN_CLIENT_SECRET: ${{ secrets.ADMIN_CLIENT_SECRET }} + ADMIN_CLIENT_USERNAME: notify-admin + NOTIFY_ENVIRONMENT: e2etest NOTIFY_E2E_AUTH_STATE_PATH: ${{ secrets.NOTIFY_E2E_AUTH_STATE_PATH }} NOTIFY_E2E_TEST_EMAIL: ${{ secrets.NOTIFY_E2E_TEST_EMAIL }} diff --git a/app/__init__.py b/app/__init__.py index f32a98b6c..55942769c 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -177,7 +177,7 @@ def create_app(application): init_govuk_frontend(application) init_jinja(application) - socketio.init_app(application) + socketio.init_app(application, cors_allowed_origins=['http://localhost:6012']) for client in ( csrf, diff --git a/app/assets/javascripts/activityChart.js b/app/assets/javascripts/activityChart.js index 84e5e3d51..75913c145 100644 --- a/app/assets/javascripts/activityChart.js +++ b/app/assets/javascripts/activityChart.js @@ -185,7 +185,7 @@ return; } - var socket = io(); + var socket = io("/services"); var eventType = type === 'service' ? 'fetch_daily_stats' : 'fetch_daily_stats_by_user'; var socketConnect = type === 'service' ? 'daily_stats_update' : 'daily_stats_by_user_update'; @@ -193,6 +193,10 @@ socket.emit(eventType); }); + socket.on('connect_error', function(error) { + console.error('WebSocket connection error:', error); + }); + socket.on(socketConnect, function(data) { var labels = []; diff --git a/app/main/views/dashboard.py b/app/main/views/dashboard.py index 366182a82..9c3eb6527 100644 --- a/app/main/views/dashboard.py +++ b/app/main/views/dashboard.py @@ -32,7 +32,7 @@ from app.utils.user import user_has_permissions from notifications_utils.recipients import format_phone_number_human_readable -@socketio.on("fetch_daily_stats") +@socketio.on("fetch_daily_stats", namespace="/services") def handle_fetch_daily_stats(): service_id = session.get("service_id") if service_id: @@ -45,7 +45,7 @@ def handle_fetch_daily_stats(): emit("error", {"error": "No service_id provided"}) -@socketio.on("fetch_daily_stats_by_user") +@socketio.on("fetch_daily_stats_by_user", namespace="/services") def handle_fetch_daily_stats_by_user(): service_id = session.get("service_id") user_id = session.get("user_id") @@ -113,6 +113,7 @@ def service_dashboard(service_id): "original_file_name": job["original_file_name"], } for job in job_response + if job["job_status"] != "cancelled" ] return render_template( "views/dashboard/dashboard.html", diff --git a/app/notify_client/job_api_client.py b/app/notify_client/job_api_client.py index dfc4fe814..9a06e16bf 100644 --- a/app/notify_client/job_api_client.py +++ b/app/notify_client/job_api_client.py @@ -1,8 +1,6 @@ import datetime from zoneinfo import ZoneInfo -from flask import current_app - from app.extensions import redis_client from app.notify_client import NotifyAdminAPIClient, _attach_current_user, cache from app.utils.csv import get_user_preferred_timezone @@ -41,8 +39,6 @@ class JobApiClient(NotifyAdminAPIClient): params["statuses"] = ",".join(statuses) job = self.get(url=f"/service/{service_id}/job", params=params) - from pprint import pformat - current_app.logger.info(pformat(job)) return job def get_uploads(self, service_id, limit_days=None, page=1): diff --git a/docs/debug-issues-with-staging-api.md b/docs/debug-issues-with-staging-api.md new file mode 100644 index 000000000..517eab4d9 --- /dev/null +++ b/docs/debug-issues-with-staging-api.md @@ -0,0 +1,26 @@ +### Setting Up Environment Variables for Local Development to the Staging API + +When you’re working locally, you can point your local admin repo to the staging API and use that to help debug +issues with the staging data set. To do this, you’ll need to modify your .env file for the admin project and +include the following new environment variables: + +- `ADMIN_CLIENT_SECRET` +- `ADMIN_CLIENT_USERNAME` +- `DANGEROUS_SALT` +- `SECRET_KEY` + +Additionally, update `API_HOST_NAME` and `NOTIFY_ENVIRONMENT`: + +1. Change `API_HOST_NAME` to `API_HOST_NAME=https://notify-api-staging.app.cloud.gov` +2. Change `NOTIFY_ENVIRONMENT` to `NOTIFY_ENVIRONMENT=staging` + +### Retrieving Environment Variables for Staging + +You can retrieve the values needed for these by using the `cf` CLI (Cloud Foundry CLI tool) and making sure +you’re targeting the `notify-staging` space. + +1. `cf login -a [api.fr.cloud.gov](http://api.fr.cloud.gov/) --sso` +2. select `notify-staging` +3. `cf env notify-admin-staging` + +By pointing your local environment to staging, it should mirror what's in staging. diff --git a/poetry.lock b/poetry.lock index a636a17e7..88cec6cce 100644 --- a/poetry.lock +++ b/poetry.lock @@ -201,13 +201,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.34.150" +version = "1.34.156" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" files = [ - {file = "botocore-1.34.150-py3-none-any.whl", hash = "sha256:b988d47f4d502df85befce11a48002421e4e6ea4289997b5e0261bac5fa76ce6"}, - {file = "botocore-1.34.150.tar.gz", hash = "sha256:4d23387e0f076d87b637a2a35c0ff2b8daca16eace36b63ce27f65630c6b375a"}, + {file = "botocore-1.34.156-py3-none-any.whl", hash = "sha256:c48f8c8996216dfdeeb0aa6d3c0f2c7ae25234766434a2ea3e57bdc08494bdda"}, + {file = "botocore-1.34.156.tar.gz", hash = "sha256:5d1478c41ab9681e660b3322432fe09c4055759c317984b7b8d3af9557ff769a"}, ] [package.dependencies] @@ -216,7 +216,7 @@ python-dateutil = ">=2.1,<3.0.0" urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} [package.extras] -crt = ["awscrt (==0.20.11)"] +crt = ["awscrt (==0.21.2)"] [[package]] name = "cachecontrol" @@ -3088,4 +3088,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "b271104f669ce0a8e78fb09299b61cf0502cc81a18213dda00f77c759b6e0209" +content-hash = "9d6309a76755b2639d787f99944c1ead0bd93ab9f2f13208f88c51cecb5e0081" diff --git a/pyproject.toml b/pyproject.toml index 5a9dc8727..915f23610 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ markdown = "^3.5.2" async-timeout = "^4.0.3" bleach = "^6.1.0" boto3 = "^1.34.150" -botocore = "^1.34.150" +botocore = "^1.34.156" cachetools = "^5.4.0" cffi = "^1.16.0" cryptography = "^43.0.0" diff --git a/tests/javascripts/activityChart.test.js b/tests/javascripts/activityChart.test.js index a8b520aa7..2bca46d0f 100644 --- a/tests/javascripts/activityChart.test.js +++ b/tests/javascripts/activityChart.test.js @@ -124,3 +124,43 @@ test('Check HTML content after chart creation', () => { expect(container.querySelector('svg')).not.toBeNull(); expect(container.querySelectorAll('rect').length).toBeGreaterThan(0); }); + + +test('Initial fetch data populates chart and table', done => { + const mockData = { + '2024-07-01': { sms: { delivered: 50, failed: 5 } }, + '2024-07-02': { sms: { delivered: 60, failed: 2 } }, + '2024-07-03': { sms: { delivered: 70, failed: 1 } }, + '2024-07-04': { sms: { delivered: 80, failed: 0 } }, + '2024-07-05': { sms: { delivered: 90, failed: 3 } }, + '2024-07-06': { sms: { delivered: 100, failed: 4 } }, + '2024-07-07': { sms: { delivered: 110, failed: 2 } }, + }; + + const socket = { + on: jest.fn((event, callback) => { + if (event === 'daily_stats_update') { + callback(mockData); + done(); + } + }), + emit: jest.fn(), + }; + window.io = jest.fn(() => socket); + + document.dispatchEvent(new Event('DOMContentLoaded')); + + setTimeout(() => { + const table = document.getElementById('weeklyTable'); + expect(table).toBeDefined(); + + const rows = table.getElementsByTagName('tr'); + expect(rows.length).toBe(8); + + const firstRowCells = rows[1].getElementsByTagName('td'); + console.log('First row cells:', firstRowCells); + expect(firstRowCells[0].textContent).toBe('07/01/24'); + expect(firstRowCells[1].textContent).toBe('50'); + expect(firstRowCells[2].textContent).toBe('5'); + }, 100); +}); diff --git a/tests/javascripts/totalMessagesChart.test.js b/tests/javascripts/totalMessagesChart.test.js index f0d7ee55d..22bc39500 100644 --- a/tests/javascripts/totalMessagesChart.test.js +++ b/tests/javascripts/totalMessagesChart.test.js @@ -15,10 +15,11 @@ function loadScript(scriptContent) { Object.defineProperty(HTMLElement.prototype, 'clientWidth', { value: 600, writable: true, + configurable: true, }); // beforeAll hook to set up the DOM and load D3.js script -beforeAll(done => { +beforeEach(() => { // Set up the DOM with the D3 script included document.body.innerHTML = `