removed socket io

This commit is contained in:
Beverly Nguyen
2024-08-09 14:29:14 -07:00
parent b426a17cf8
commit 9e6f87ace1
10 changed files with 148 additions and 505 deletions

View File

@@ -18,7 +18,6 @@ from flask import (
)
from flask.globals import request_ctx
from flask_login import LoginManager, current_user
from flask_socketio import SocketIO
from flask_talisman import Talisman
from flask_wtf import CSRFProtect
from flask_wtf.csrf import CSRFError
@@ -119,8 +118,6 @@ from notifications_utils.recipients import format_phone_number_human_readable
login_manager = LoginManager()
csrf = CSRFProtect()
talisman = Talisman()
socketio = SocketIO()
# The current service attached to the request stack.
current_service = LocalProxy(partial(getattr, request_ctx, "service"))
@@ -177,7 +174,6 @@ def create_app(application):
init_govuk_frontend(application)
init_jinja(application)
socketio.init_app(application, cors_allowed_origins=['http://localhost:6012'])
for client in (
csrf,

View File

@@ -185,41 +185,38 @@
return;
}
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';
var daily_stats = activityChartContainer.getAttribute('data-daily-stats');
var daily_stats_by_user = activityChartContainer.getAttribute('data-daily_stats_by_user');
socket.on('connect', function () {
socket.emit(eventType);
});
var data;
try {
data = type === 'service' ? eval("(" + daily_stats + ")") : eval("(" + daily_stats_by_user + ")");
} catch (error) {
console.error('Error parsing data:', error);
return;
}
socket.on('connect_error', function(error) {
console.error('WebSocket connection error:', error);
});
var labels = [];
var deliveredData = [];
var failedData = [];
socket.on(socketConnect, function(data) {
var labels = [];
var deliveredData = [];
var failedData = [];
for (var dateString in data) {
// Parse the date string (assuming format YYYY-MM-DD)
for (var dateString in data) {
if (data.hasOwnProperty(dateString)) {
const dateParts = dateString.split('-');
const formattedDate = `${dateParts[1]}/${dateParts[2]}/${dateParts[0].slice(2)}`; // Format to MM/DD/YY
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);
}
}
try {
createChart('#weeklyChart', labels, deliveredData, failedData);
createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData);
});
socket.on('error', function(data) {
console.log('Error:', data);
});
} catch (error) {
console.error('Error creating chart or table:', error);
}
};
const handleDropdownChange = function(event) {

View File

@@ -1,70 +0,0 @@
(function (window) {
function initializeChartAndSocket() {
var ctx = document.getElementById('myChart');
if (!ctx) {
return;
}
var myBarChart = new Chart(ctx.getContext('2d'), {
type: 'bar',
data: {
labels: [],
datasets: [
{
label: 'Delivered',
data: [],
backgroundColor: '#0076d6',
stack: 'Stack 0'
},
]
},
options: {
animation: false,
scales: {
y: {
beginAtZero: true
}
}
}
});
var socket = io();
socket.on('connect', function() {
socket.emit('fetch_daily_stats_by_user');
});
socket.on('daily_stats_by_user_update', function(data) {
// console.log('Data received:', data);
var labels = [];
var deliveredData = [];
var failedData = [];
for (var date in data) {
labels.push(date);
deliveredData.push(data[date].sms.delivered);
}
myBarChart.data.labels = labels;
myBarChart.data.datasets[0].data = deliveredData;
myBarChart.update();
});
socket.on('error', function(data) {
// console.log('Error:', data);
});
var sevenDaysButton = document.getElementById('sevenDaysButton');
if (sevenDaysButton) {
sevenDaysButton.addEventListener('click', function() {
socket.emit('fetch_daily_stats_by_user');
// console.log('clicked');
});
}
}
document.addEventListener('DOMContentLoaded', initializeChartAndSocket);
})(window);

View File

@@ -3,9 +3,16 @@ from datetime import datetime
from functools import partial
from itertools import groupby
from flask import Response, abort, jsonify, render_template, request, session, url_for
from flask import (
Response,
abort,
jsonify,
render_template,
request,
session,
url_for,
)
from flask_login import current_user
from flask_socketio import emit
from werkzeug.utils import redirect
from app import (
@@ -13,7 +20,6 @@ from app import (
current_service,
job_api_client,
service_api_client,
socketio,
template_statistics_client,
)
from app.formatters import format_date_numeric, format_datetime_numeric, get_time_left
@@ -32,38 +38,6 @@ from app.utils.user import user_has_permissions
from notifications_utils.recipients import format_phone_number_human_readable
@socketio.on("fetch_daily_stats", namespace="/services")
def handle_fetch_daily_stats():
service_id = session.get("service_id")
if service_id:
date_range = get_stats_date_range()
daily_stats = service_api_client.get_service_notification_statistics_by_day(
service_id, start_date=date_range["start_date"], days=date_range["days"]
)
emit("daily_stats_update", daily_stats)
else:
emit("error", {"error": "No service_id provided"})
@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")
if service_id and user_id:
date_range = get_stats_date_range()
daily_stats_by_user = (
service_api_client.get_user_service_notification_statistics_by_day(
service_id,
user_id,
start_date=date_range["start_date"],
days=date_range["days"],
)
)
emit("daily_stats_by_user_update", daily_stats_by_user)
else:
emit("error", {"error": "No service_id or user_id provided"})
@main.route("/services/<uuid:service_id>/dashboard")
@user_has_permissions("view_activity", "send_messages")
def old_service_dashboard(service_id):
@@ -87,12 +61,17 @@ 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 = [
{
@@ -123,6 +102,23 @@ 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(
service_id, start_date=date_range["start_date"], days=date_range["days"]
)
def get_daily_stats_by_user(service_id, user_id, date_range):
return service_api_client.get_user_service_notification_statistics_by_day(
service_id,
user_id,
start_date=date_range["start_date"],
days=date_range["days"],
)

View File

@@ -36,7 +36,7 @@
<h2 class="line-height-sans-2 margin-bottom-0 margin-top-4">
Activity snapshot
</h2>
<div id="activityChartContainer">
<div id="activityChartContainer" data-daily-stats="{{ daily_stats }}" data-daily_stats_by_user="{{ daily_stats_by_user }}">
<form class="usa-form">
<label class="usa-label" for="options">Account</label>
<select class="usa-select margin-bottom-2" name="options" id="options">

View File

@@ -95,7 +95,6 @@ const javascripts = () => {
paths.npm + 'timeago/jquery.timeago.js',
paths.npm + 'textarea-caret/index.js',
paths.npm + 'cbor-js/cbor.js',
paths.npm + 'socket.io-client/dist/socket.io.min.js',
paths.npm + 'd3/dist/d3.min.js'
]));

84
package-lock.json generated
View File

@@ -20,7 +20,6 @@
"python": "^0.0.4",
"query-command-supported": "1.0.0",
"sass-embedded": "^1.77.5",
"socket.io-client": "^4.2.0",
"textarea-caret": "3.1.0",
"timeago": "1.6.7"
},
@@ -2705,11 +2704,6 @@
"@sinonjs/commons": "^3.0.0"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="
},
"node_modules/@tootallnate/once": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
@@ -5999,6 +5993,7 @@
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
"dev": true,
"dependencies": {
"ms": "2.1.2"
},
@@ -6372,46 +6367,6 @@
"once": "^1.4.0"
}
},
"node_modules/engine.io-client": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz",
"integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1",
"xmlhttprequest-ssl": "~2.0.0"
}
},
"node_modules/engine.io-client/node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/engine.io-parser": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz",
"integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
@@ -11960,7 +11915,8 @@
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"node_modules/mute-stdout": {
"version": "2.0.0",
@@ -14325,32 +14281,6 @@
"urix": "^0.1.0"
}
},
"node_modules/socket.io-client": {
"version": "4.7.5",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz",
"integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.2",
"engine.io-client": "~6.5.2",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -15812,14 +15742,6 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/xtend": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz",

View File

@@ -36,7 +36,6 @@
"python": "^0.0.4",
"query-command-supported": "1.0.0",
"sass-embedded": "^1.77.5",
"socket.io-client": "^4.2.0",
"textarea-caret": "3.1.0",
"timeago": "1.6.7"
},

View File

@@ -3,11 +3,9 @@ import json
from datetime import datetime
import pytest
from flask import Flask, url_for
from flask_socketio import SocketIO, SocketIOTestClient
from flask import url_for
from freezegun import freeze_time
from app import create_app
from app.main.views.dashboard import (
aggregate_notifications_stats,
aggregate_status_types,
@@ -15,8 +13,6 @@ from app.main.views.dashboard import (
format_monthly_stats_to_list,
get_dashboard_totals,
get_tuples_of_financial_years,
handle_fetch_daily_stats,
handle_fetch_daily_stats_by_user,
)
from tests import (
organization_json,
@@ -27,8 +23,6 @@ from tests import (
from tests.conftest import (
ORGANISATION_ID,
SERVICE_ONE_ID,
SERVICE_TWO_ID,
USER_ONE_ID,
create_active_caseworking_user,
create_active_user_view_permissions,
normalize_spaces,
@@ -163,6 +157,22 @@ stub_template_stats = [
},
]
date_range = {"start_date": "2024-01-01", "days": 7}
mock_daily_stats = {
date_range["start_date"]: {
"email": {"delivered": 0, "failure": 0, "requested": 0},
"sms": {"delivered": 0, "failure": 1, "requested": 1},
},
}
mock_daily_stats_by_user = {
date_range["start_date"]: {
"email": {"delivered": 1, "failure": 0, "requested": 1},
"sms": {"delivered": 1, "failure": 0, "requested": 1},
},
}
@pytest.mark.parametrize(
"user",
@@ -221,10 +231,9 @@ def test_get_started(
return_value=copy.deepcopy(stub_template_stats),
)
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
)
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -251,10 +260,9 @@ def test_get_started_is_hidden_once_templates_exist(
return_value=copy.deepcopy(stub_template_stats),
)
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
)
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -278,10 +286,9 @@ def test_inbound_messages_not_visible_to_service_without_permissions(
):
service_one["permissions"] = []
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
)
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -305,6 +312,9 @@ def test_inbound_messages_shows_count_of_messages_when_there_are_messages(
mock_get_inbound_sms_summary,
):
service_one["permissions"] = ["inbound_sms"]
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -333,6 +343,9 @@ def test_inbound_messages_shows_count_of_messages_when_there_are_no_messages(
mock_get_inbound_sms_summary_with_no_messages,
):
service_one["permissions"] = ["inbound_sms"]
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -579,10 +592,9 @@ def test_should_show_recent_templates_on_dashboard(
return_value=copy.deepcopy(stub_template_stats),
)
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
)
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -636,10 +648,9 @@ def test_should_not_show_recent_templates_on_dashboard_if_only_one_template_used
return_value=stats,
)
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
)
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
main = page.select_one("main").text
@@ -791,10 +802,9 @@ def test_should_show_upcoming_jobs_on_dashboard(
mock_get_free_sms_fragment_limit,
mock_get_inbound_sms_summary,
):
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
)
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -834,6 +844,9 @@ def test_should_not_show_upcoming_jobs_on_dashboard_if_count_is_0(
},
)
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -856,10 +869,9 @@ def test_should_not_show_upcoming_jobs_on_dashboard_if_service_has_no_jobs(
mock_get_inbound_sms_summary,
):
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
)
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -942,6 +954,9 @@ def test_should_not_show_jobs_on_dashboard_for_users_with_uploads_page(
mock_get_free_sms_fragment_limit,
mock_get_inbound_sms_summary,
):
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -1202,10 +1217,9 @@ def test_menu_send_messages(
service_one["permissions"] = ["email", "sms"]
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
)
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
page = _test_dashboard_menu(
client_request,
mocker,
@@ -1239,10 +1253,9 @@ def test_menu_manage_service(
mock_get_free_sms_fragment_limit,
):
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
)
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
page = _test_dashboard_menu(
client_request,
mocker,
@@ -1276,10 +1289,9 @@ def test_menu_main_settings(
mock_get_free_sms_fragment_limit,
):
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
)
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
page = _test_settings_menu(
client_request,
mocker,
@@ -1312,10 +1324,9 @@ def test_menu_manage_api_keys(
mock_get_free_sms_fragment_limit,
):
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
)
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
page = _test_dashboard_menu(
client_request,
mocker,
@@ -1352,10 +1363,9 @@ def test_menu_all_services_for_platform_admin_user(
mock_get_free_sms_fragment_limit,
):
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
)
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
page = _test_dashboard_menu(
client_request, mocker, platform_admin_user, service_one, []
)
@@ -1391,10 +1401,9 @@ def test_route_for_service_permissions(
"app.service_api_client.get_global_notification_count", side_effect=_get
)
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
)
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
validate_route_permission(
mocker,
notify_admin,
@@ -1536,10 +1545,9 @@ def test_org_breadcrumbs_do_not_show_if_service_has_no_org(
mock_get_free_sms_fragment_limit,
):
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
)
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
assert not page.select(".navigation-organization-link")
@@ -1603,10 +1611,9 @@ def test_org_breadcrumbs_show_if_user_is_a_member_of_the_services_org(
),
)
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
)
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
assert page.select_one(".navigation-organization-link")["href"] == url_for(
@@ -1639,11 +1646,9 @@ def test_org_breadcrumbs_do_not_show_if_user_is_a_member_of_the_services_org_but
mocker.patch("app.models.service.Organization")
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
@@ -1678,10 +1683,9 @@ def test_org_breadcrumbs_show_if_user_is_platform_admin(
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
)
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
client_request.login(platform_admin_user, service_one_json)
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
@@ -1714,10 +1718,10 @@ def test_breadcrumb_shows_if_service_is_suspended(
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
)
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
assert "Suspended" in page.select_one(".navigation-service-name").text
@@ -1747,10 +1751,9 @@ def test_service_dashboard_shows_usage(
},
)
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
)
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
service_one["permissions"] = permissions
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
@@ -1783,11 +1786,9 @@ def test_service_dashboard_shows_free_allowance(
],
)
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
def test_service_dashboard_shows_batched_jobs(
@@ -1801,13 +1802,11 @@ def test_service_dashboard_shows_batched_jobs(
mock_get_free_sms_fragment_limit,
):
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
)
mocker.patch("app.main.views.dashboard.get_daily_stats", return_value=mock_daily_stats)
mocker.patch("app.main.views.dashboard.get_daily_stats_by_user", return_value=mock_daily_stats_by_user)
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
job_table_body = page.find("table", class_="job-table")
rows = job_table_body.find_all("tbody")[0].find_all("tr")
@@ -1815,158 +1814,3 @@ def test_service_dashboard_shows_batched_jobs(
assert job_table_body is not None
assert len(rows) == 1
@pytest.fixture
def app_with_socketio():
app = Flask("app")
create_app(app)
socketio = SocketIO(app)
socketio.on_event("fetch_daily_stats", handle_fetch_daily_stats)
socketio.on_event("fetch_daily_stats_by_user", handle_fetch_daily_stats_by_user)
return app, socketio
@pytest.mark.parametrize(
("service_id", "date_range", "expected_call_args"),
[
(
SERVICE_ONE_ID,
{"start_date": "2024-01-01", "days": 7},
{"service_id": SERVICE_ONE_ID, "start_date": "2024-01-01", "days": 7},
),
(
SERVICE_TWO_ID,
{"start_date": "2023-06-01", "days": 7},
{"service_id": SERVICE_TWO_ID, "start_date": "2023-06-01", "days": 7},
),
],
)
def test_fetch_daily_stats(
app_with_socketio,
mocker,
service_id,
date_range,
expected_call_args,
):
app, socketio = app_with_socketio
mocker.patch(
"app.main.views.dashboard.get_stats_date_range", return_value=date_range
)
mock_service_api = mocker.patch(
"app.service_api_client.get_service_notification_statistics_by_day",
return_value={
date_range["start_date"]: {
"email": {"delivered": 0, "failure": 0, "requested": 0},
"sms": {"delivered": 0, "failure": 1, "requested": 1},
},
},
)
with app.test_client() as client:
with client.session_transaction() as sess:
sess["service_id"] = service_id
socketio_client = SocketIOTestClient(app, socketio, flask_test_client=client)
connected = socketio_client.is_connected()
assert connected, "Client should be connected"
socketio_client.emit("fetch_daily_stats")
received = socketio_client.get_received()
mock_service_api.assert_called_once_with(
expected_call_args["service_id"],
start_date=expected_call_args["start_date"],
days=expected_call_args["days"],
)
assert received, "Should receive a response message"
assert received[0]["name"] == "daily_stats_update"
assert received[0]["args"][0] == {
date_range["start_date"]: {
"email": {"delivered": 0, "failure": 0, "requested": 0},
"sms": {"delivered": 0, "failure": 1, "requested": 1},
},
}
socketio_client.disconnect()
disconnected = not socketio_client.is_connected()
assert disconnected, "Client should be disconnected"
@pytest.mark.parametrize(
("service_id", "user_id", "date_range", "expected_call_args", "user"),
[
(
SERVICE_ONE_ID,
USER_ONE_ID,
{"start_date": "2024-01-01", "days": 7},
{
"service_id": SERVICE_ONE_ID,
"user_id": USER_ONE_ID,
"start_date": "2024-01-01",
"days": 7,
},
{"id": USER_ONE_ID, "name": "Test User"},
),
],
)
def test_fetch_daily_stats_by_user(
app_with_socketio,
mocker,
service_id,
user_id,
date_range,
expected_call_args,
user,
):
app, socketio = app_with_socketio
mocker.patch(
"app.main.views.dashboard.get_stats_date_range", return_value=date_range
)
mock_service_api = mocker.patch(
"app.service_api_client.get_user_service_notification_statistics_by_day",
return_value={
date_range["start_date"]: {
"email": {"delivered": 0, "failure": 0, "requested": 0},
"sms": {"delivered": 0, "failure": 1, "requested": 1},
},
},
)
mocker.patch("app.user_api_client.get_user", return_value=user)
with app.test_client() as client:
with client.session_transaction() as sess:
sess["service_id"] = service_id
sess["user_id"] = user_id
socketio_client = SocketIOTestClient(app, socketio, flask_test_client=client)
connected = socketio_client.is_connected()
assert connected, "Client should be connected"
socketio_client.emit("fetch_daily_stats_by_user")
received = socketio_client.get_received()
mock_service_api.assert_called_once_with(
expected_call_args["service_id"],
expected_call_args["user_id"],
start_date=expected_call_args["start_date"],
days=expected_call_args["days"],
)
assert received, "Should receive a response message"
assert received[0]["name"] == "daily_stats_by_user_update"
assert received[0]["args"][0] == {
date_range["start_date"]: {
"email": {"delivered": 0, "failure": 0, "requested": 0},
"sms": {"delivered": 0, "failure": 1, "requested": 1},
},
}
socketio_client.disconnect()
disconnected = not socketio_client.is_connected()
assert disconnected, "Client should be disconnected"

View File

@@ -21,7 +21,7 @@ Object.defineProperty(HTMLElement.prototype, 'clientWidth', {
beforeAll(done => {
// Set up the DOM with the D3 script included
document.body.innerHTML = `
<div id="activityChartContainer">
<div id="activityChartContainer" data-daily-stats="{{ daily_stats }}" data-daily_stats_by_user="{{ daily_stats_by_user }}">
<form class="usa-form">
<label class="usa-label" for="options">Account</label>
<select class="usa-select margin-bottom-2" name="options" id="options">
@@ -30,7 +30,7 @@ beforeAll(done => {
<option value="individual">User Name</option>
</select>
</form>
<div id="activityChart">
<div id="activityChart" >
<div class="chart-header">
<div class="chart-subtitle">Service Name - Last 7 Days</div>
<div class="chart-legend" aria-label="Legend"></div>
@@ -124,43 +124,3 @@ 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);
});