mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-26 01:04:00 -04:00
Compare commits
20 Commits
fix-endles
...
e958e097aa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e958e097aa | ||
|
|
9ae1f40376 | ||
|
|
0df04cfd89 | ||
|
|
34f7c6a4da | ||
|
|
cd78710753 | ||
|
|
102d005e0d | ||
|
|
3455235a8b | ||
|
|
c93d00dcbe | ||
|
|
a524ed0dc5 | ||
|
|
c37bb44e0a | ||
|
|
0b3177e3ff | ||
|
|
baf9b69fbb | ||
|
|
df3e2264ec | ||
|
|
79192e29b6 | ||
|
|
5cede78661 | ||
|
|
5c9d190021 | ||
|
|
61fb59f1cb | ||
|
|
02f39bd077 | ||
|
|
1eb12b93f1 | ||
|
|
c1fa099c12 |
@@ -527,7 +527,7 @@
|
||||
"filename": "tests/app/main/views/test_accept_invite.py",
|
||||
"hashed_secret": "07f0a6c13923fc3b5f0c57ffa2d29b715eb80d71",
|
||||
"is_verified": false,
|
||||
"line_number": 643,
|
||||
"line_number": 631,
|
||||
"is_secret": false
|
||||
}
|
||||
],
|
||||
@@ -684,5 +684,5 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"generated_at": "2025-02-03T17:01:06Z"
|
||||
"generated_at": "2025-02-26T18:19:37Z"
|
||||
}
|
||||
|
||||
5
Makefile
5
Makefile
@@ -162,3 +162,8 @@ upload-static:
|
||||
# @cf map-route notify-admin ${DNS_NAME} --hostname www
|
||||
# @cf unmap-route notify-admin-failwhale ${DNS_NAME} --hostname www
|
||||
# @echo "Failwhale is disabled"
|
||||
|
||||
.PHONY: test-single
|
||||
test-single: export NEW_RELIC_ENVIRONMENT=test
|
||||
test-single: ## Run a single test file
|
||||
poetry run pytest $(TEST_FILE)
|
||||
|
||||
@@ -216,7 +216,13 @@
|
||||
return;
|
||||
}
|
||||
|
||||
var url = type === 'service' ? `/services/${currentServiceId}/daily-stats.json` : `/services/${currentServiceId}/daily-stats-by-user.json`;
|
||||
var userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
|
||||
var url = type === 'service'
|
||||
? `/services/${currentServiceId}/daily-stats.json?timezone=${encodeURIComponent(userTimezone)}`
|
||||
: `/services/${currentServiceId}/daily-stats-by-user.json`;
|
||||
|
||||
|
||||
return fetch(url)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -6,17 +6,17 @@
|
||||
var chartTitle = document.getElementById('chartTitle').textContent;
|
||||
|
||||
// Access data attributes from the HTML
|
||||
var sms_sent = parseInt(chartContainer.getAttribute('data-sms-sent'));
|
||||
var sms_remaining_messages = parseInt(chartContainer.getAttribute('data-sms-allowance-remaining'));
|
||||
var totalMessages = sms_sent + sms_remaining_messages;
|
||||
var messagesSent = parseInt(chartContainer.getAttribute('data-messages-sent'));
|
||||
var messagesRemaining = parseInt(chartContainer.getAttribute('data-messages-remaining'));
|
||||
var totalMessages = messagesSent + messagesRemaining;
|
||||
|
||||
// Update the message below the chart
|
||||
document.getElementById('message').innerText = `${sms_sent.toLocaleString()} sent / ${sms_remaining_messages.toLocaleString()} remaining`;
|
||||
document.getElementById('message').innerText = `${messagesSent.toLocaleString()} sent / ${messagesRemaining.toLocaleString()} remaining`;
|
||||
|
||||
// Calculate minimum width for "Messages Sent" as 1% of the total chart width
|
||||
var minSentPercentage = (sms_sent === 0) ? 0 : 0.02;
|
||||
var minSentPercentage = (messagesSent === 0) ? 0 : 0.02;
|
||||
var minSentValue = totalMessages * minSentPercentage;
|
||||
var displaySent = Math.max(sms_sent, minSentValue);
|
||||
var displaySent = Math.max(messagesSent, minSentValue);
|
||||
var displayRemaining = totalMessages - displaySent;
|
||||
|
||||
var svg = d3.select("#totalMessageChart");
|
||||
@@ -48,7 +48,7 @@
|
||||
.attr("width", 0) // Start with width 0 for animation
|
||||
.on('mouseover', function(event) {
|
||||
tooltip.style('display', 'block')
|
||||
.html(`Messages Sent: ${sms_sent.toLocaleString()}`);
|
||||
.html(`Messages Sent: ${messagesSent.toLocaleString()}`);
|
||||
})
|
||||
.on('mousemove', function(event) {
|
||||
tooltip.style('left', `${event.pageX + 10}px`)
|
||||
@@ -66,7 +66,7 @@
|
||||
.attr("width", 0) // Start with width 0 for animation
|
||||
.on('mouseover', function(event) {
|
||||
tooltip.style('display', 'block')
|
||||
.html(`Remaining: ${sms_remaining_messages.toLocaleString()}`);
|
||||
.html(`Remaining: ${messagesRemaining.toLocaleString()}`);
|
||||
})
|
||||
.on('mousemove', function(event) {
|
||||
tooltip.style('left', `${event.pageX + 10}px`)
|
||||
@@ -115,9 +115,9 @@
|
||||
var tbodyRow = document.createElement('tr');
|
||||
|
||||
var tdMessagesSent = document.createElement('td');
|
||||
tdMessagesSent.textContent = sms_sent.toLocaleString(); // Value for Messages Sent
|
||||
tdMessagesSent.textContent = messagesSent.toLocaleString(); // Value for Messages Sent
|
||||
var tdRemaining = document.createElement('td');
|
||||
tdRemaining.textContent = sms_remaining_messages.toLocaleString(); // Value for Remaining
|
||||
tdRemaining.textContent = messagesRemaining.toLocaleString(); // Value for Remaining
|
||||
|
||||
tbodyRow.appendChild(tdMessagesSent);
|
||||
tbodyRow.appendChild(tdRemaining);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import calendar
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from functools import partial
|
||||
from itertools import groupby
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from flask import Response, abort, jsonify, render_template, request, session, url_for
|
||||
from flask_login import current_user
|
||||
@@ -48,38 +49,27 @@ def service_dashboard(service_id):
|
||||
if not current_user.has_permissions("view_activity"):
|
||||
return redirect(url_for("main.choose_template", service_id=service_id))
|
||||
|
||||
yearly_usage = billing_api_client.get_annual_usage_for_service(
|
||||
service_id,
|
||||
get_current_financial_year(),
|
||||
)
|
||||
free_sms_allowance = billing_api_client.get_free_sms_fragment_limit_for_year(
|
||||
current_service.id,
|
||||
)
|
||||
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
|
||||
|
||||
active_jobs = [job for job in job_response if job["job_status"] != "cancelled"]
|
||||
sorted_jobs = sorted(active_jobs, key=lambda job: job["created_at"], reverse=True)
|
||||
job_lists = [
|
||||
{
|
||||
**job_dict,
|
||||
"finished_processing": job_is_finished(job_dict)
|
||||
}
|
||||
{**job_dict, "finished_processing": job_is_finished(job_dict)}
|
||||
for job_dict in sorted_jobs
|
||||
]
|
||||
|
||||
total_messages = service_api_client.get_service_message_ratio(service_id)
|
||||
messages_remaining = total_messages.get("messages_remaining", 0)
|
||||
messages_sent = total_messages.get("messages_sent", 0)
|
||||
return render_template(
|
||||
"views/dashboard/dashboard.html",
|
||||
updates_url=url_for(".service_dashboard_updates", service_id=service_id),
|
||||
partials=get_dashboard_partials(service_id),
|
||||
jobs=job_lists,
|
||||
service_data_retention_days=service_data_retention_days,
|
||||
sms_sent=sms_sent,
|
||||
sms_allowance_remaining=sms_allowance_remaining,
|
||||
messages_remaining=messages_remaining,
|
||||
messages_sent=messages_sent,
|
||||
)
|
||||
|
||||
|
||||
@@ -91,7 +81,7 @@ def job_is_finished(job_dict):
|
||||
"technical-failure",
|
||||
"temporary-failure",
|
||||
"permanent-failure",
|
||||
"cancelled"
|
||||
"cancelled",
|
||||
]
|
||||
|
||||
processed_count = sum(
|
||||
@@ -106,10 +96,47 @@ def job_is_finished(job_dict):
|
||||
@user_has_permissions()
|
||||
def get_daily_stats(service_id):
|
||||
date_range = get_stats_date_range()
|
||||
stats = service_api_client.get_service_notification_statistics_by_day(
|
||||
service_id, start_date=date_range["start_date"], days=date_range["days"]
|
||||
days = date_range["days"]
|
||||
user_timezone = request.args.get("timezone", "UTC")
|
||||
|
||||
stats_utc = service_api_client.get_service_notification_statistics_by_day(
|
||||
service_id,
|
||||
start_date=date_range["start_date"],
|
||||
days=days,
|
||||
)
|
||||
return jsonify(stats)
|
||||
|
||||
local_stats = get_local_daily_stats_for_last_x_days(stats_utc, user_timezone, days)
|
||||
return jsonify(local_stats)
|
||||
|
||||
|
||||
def get_local_daily_stats_for_last_x_days(stats_utc, user_timezone, days):
|
||||
tz = ZoneInfo(user_timezone)
|
||||
today_local = datetime.now(tz).date()
|
||||
start_local = today_local - timedelta(days=days - 1)
|
||||
|
||||
# Generate exactly days local dates, each with zeroed stats
|
||||
days_list = [
|
||||
(start_local + timedelta(days=i)).strftime("%Y-%m-%d") for i in range(days)
|
||||
]
|
||||
aggregator = {
|
||||
d: {
|
||||
"sms": {"delivered": 0, "failure": 0, "pending": 0, "requested": 0},
|
||||
"email": {"delivered": 0, "failure": 0, "pending": 0, "requested": 0},
|
||||
}
|
||||
for d in days_list
|
||||
}
|
||||
|
||||
# Convert each UTC timestamp to local date and iterate
|
||||
for utc_ts, data in stats_utc.items():
|
||||
utc_dt = datetime.strptime(utc_ts, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=ZoneInfo("UTC"))
|
||||
local_day = utc_dt.astimezone(tz).strftime("%Y-%m-%d")
|
||||
|
||||
if local_day in aggregator:
|
||||
for msg_type in ["sms", "email"]:
|
||||
for status in ["delivered", "failure", "pending", "requested"]:
|
||||
aggregator[local_day][msg_type][status] += data[msg_type][status]
|
||||
|
||||
return aggregator
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/daily-stats-by-user.json")
|
||||
|
||||
@@ -537,6 +537,11 @@ class ServiceAPIClient(NotifyAdminAPIClient):
|
||||
"""
|
||||
return self.get("/service/invite/redis/{0}".format(redis_key))
|
||||
|
||||
def get_service_message_ratio(self, service_id):
|
||||
return self.get(
|
||||
url="service/get-service-message-ratio?service_id={0}".format(service_id),
|
||||
)
|
||||
|
||||
|
||||
service_api_client = ServiceAPIClient()
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
{{ ajax_block(partials, updates_url, 'inbox') }}
|
||||
|
||||
<div id="totalMessageChartContainer" data-sms-sent="{{ sms_sent }}" data-sms-allowance-remaining="{{ sms_allowance_remaining }}">
|
||||
<div id="totalMessageChartContainer" data-messages-sent="{{ messages_sent }}" data-messages-remaining="{{ messages_remaining }}">
|
||||
<h2 id="chartTitle">Total messages</h2>
|
||||
<svg id="totalMessageChart"></svg>
|
||||
<div id="message"></div>
|
||||
|
||||
@@ -128,6 +128,13 @@ def generate_notifications_csv(**kwargs):
|
||||
notifications_resp = notification_api_client.get_notifications_for_service(
|
||||
**kwargs
|
||||
)
|
||||
# Stop if we are finished
|
||||
if (
|
||||
notifications_resp.get("notifications") is None
|
||||
or len(notifications_resp["notifications"]) == 0
|
||||
):
|
||||
return
|
||||
|
||||
for notification in notifications_resp["notifications"]:
|
||||
preferred_tz_created_at = convert_report_date_to_preferred_timezone(
|
||||
notification["created_at"]
|
||||
|
||||
136
docs/manual-a11y-checklist.md
Normal file
136
docs/manual-a11y-checklist.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# Manual Accessibility Testing Checklist
|
||||
|
||||
## 1. Structure and Semantics
|
||||
|
||||
### Headings:
|
||||
- Verify that headings are used logically (`<h1>` to `<h6>`) to create a clear content hierarchy.
|
||||
- Ensure there is only one `<h1>` per page (unless it’s a valid use case, like within `<section>` landmarks).
|
||||
|
||||
### Landmarks:
|
||||
- Confirm the presence of ARIA landmarks (e.g., `<header>`, `<main>`, `<footer>`) for navigation.
|
||||
- Use tools (e.g., Accessibility Insights, Axe) to ensure proper landmark roles.
|
||||
|
||||
### HTML Validity:
|
||||
- Check for semantic HTML usage (e.g., `<button>` for buttons, `<a>` for links).
|
||||
- Avoid using `<div>` or `<span>` for interactive elements.
|
||||
|
||||
|
||||
## 2. Navigation and Focus
|
||||
|
||||
### Keyboard Navigation:
|
||||
- Ensure all functionality is accessible via the keyboard (e.g., Tab, Enter, Space, Arrow Keys).
|
||||
- Test for logical tab order that follows the visual reading order.
|
||||
|
||||
### Focus States:
|
||||
- Ensure focus is visible and distinct on all interactive elements.
|
||||
- Check that focus is not trapped in modal dialogs or components.
|
||||
|
||||
### Skip Links:
|
||||
- Verify that “Skip to content” links are present and functional.
|
||||
|
||||
|
||||
## 3. Forms and Inputs
|
||||
|
||||
### Labels:
|
||||
- Confirm all form fields have accessible, descriptive labels (`<label>` or `aria-label`/`aria-labelledby`).
|
||||
- Ensure placeholder text is not used as a label substitute.
|
||||
|
||||
### Error Messages:
|
||||
- Check that error messages are programmatically associated with inputs and conveyed to assistive technologies.
|
||||
- Verify that error messages are specific and provide actionable guidance.
|
||||
|
||||
### Fieldset and Legend:
|
||||
- Group related inputs with `<fieldset>` and `<legend>` where appropriate.
|
||||
|
||||
|
||||
## 4. Media and Non-Text Content
|
||||
|
||||
### Images:
|
||||
- Ensure all meaningful images have appropriate alt text.
|
||||
- Decorative images should have `alt=""` or be hidden with `role="presentation"`.
|
||||
|
||||
### Videos:
|
||||
- Verify captions are available and accurate for all video content.
|
||||
- Provide audio descriptions for videos with critical visual information.
|
||||
|
||||
### Audio:
|
||||
- Confirm there is a way to stop, pause, or adjust the volume of any audio that plays automatically.
|
||||
|
||||
|
||||
## 5. Color and Contrast
|
||||
|
||||
### Color Contrast:
|
||||
- Use a contrast checker to ensure text and interactive elements meet WCAG AA requirements (4.5:1 for text, 3:1 for large text).
|
||||
|
||||
### Color Independence:
|
||||
- Verify that color is not the sole means of conveying information (e.g., “errors in red”).
|
||||
|
||||
### High Contrast Modes:
|
||||
- Test with browser or OS high-contrast modes to ensure proper readability.
|
||||
|
||||
---
|
||||
|
||||
## 6. Dynamic Content and Interactions
|
||||
|
||||
### ARIA Live Regions:
|
||||
- Verify that dynamic updates are announced using `aria-live` (e.g., success messages).
|
||||
|
||||
### Modals and Dialogs:
|
||||
- Ensure modals have proper focus management (focus should move to the modal on open and back to the trigger on close).
|
||||
- Test that modals are announced properly by screen readers.
|
||||
|
||||
### Tooltips and Popovers:
|
||||
- Ensure tooltips are accessible via keyboard and announced by assistive technologies.
|
||||
|
||||
|
||||
## 7. Assistive Technology Compatibility
|
||||
|
||||
### Screen Reader Testing:
|
||||
- Test the site with a screen reader (e.g., NVDA, JAWS, VoiceOver) to ensure content is announced logically.
|
||||
- Verify that dynamic content (e.g., dropdowns, modals) is announced appropriately.
|
||||
|
||||
### Zoom and Magnification:
|
||||
- Ensure the site is usable at 200% zoom without loss of functionality or content.
|
||||
|
||||
### Responsive Design:
|
||||
- Test on different devices and orientations to verify responsive behavior is accessible.
|
||||
|
||||
|
||||
## 8. Performance and Usability
|
||||
|
||||
### Loading Indicators:
|
||||
- Confirm that loading indicators are announced (e.g., using `aria-live`).
|
||||
|
||||
### Page Titles:
|
||||
- Ensure page titles are unique and descriptive of the page’s content.
|
||||
|
||||
### Time Limits:
|
||||
- Check if users can extend or disable time limits where applicable.
|
||||
|
||||
|
||||
## 9. WCAG Success Criteria Coverage
|
||||
|
||||
### Review WCAG 2.1 (or 2.2 if applicable) Success Criteria:
|
||||
- **Level A**: Must-have minimum requirements.
|
||||
- **Level AA**: Commonly required for legal compliance.
|
||||
- **Level AAA**: Optional for enhanced accessibility.
|
||||
|
||||
|
||||
## Tools to Assist Manual Testing
|
||||
|
||||
### Browser Extensions:
|
||||
- [Axe DevTools](https://www.deque.com/axe/)
|
||||
- [WAVE](https://wave.webaim.org/)
|
||||
- [Lighthouse](https://developers.google.com/web/tools/lighthouse)
|
||||
|
||||
### Screen Readers:
|
||||
- **NVDA** (Windows)
|
||||
- **VoiceOver** (macOS/iOS)
|
||||
- **TalkBack** (Android)
|
||||
|
||||
### Contrast Checkers:
|
||||
- [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/)
|
||||
- [Color Contrast Analyzer](https://developer.paciellogroup.com/resources/contrastanalyser/)
|
||||
|
||||
### Keyboard Navigation:
|
||||
- Test tabbing through the site manually.
|
||||
328
package-lock.json
generated
328
package-lock.json
generated
@@ -24,7 +24,7 @@
|
||||
"playwright": "^1.50.1",
|
||||
"python": "^0.0.4",
|
||||
"query-command-supported": "1.0.0",
|
||||
"sass-embedded": "^1.85.0",
|
||||
"sass-embedded": "^1.85.1",
|
||||
"textarea-caret": "3.1.0",
|
||||
"timeago": "1.6.7",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
@@ -50,7 +50,7 @@
|
||||
"jest-environment-jsdom": "^29.2.2",
|
||||
"jshint": "2.13.6",
|
||||
"jshint-stylish": "2.2.1",
|
||||
"rollup": "^4.34.8",
|
||||
"rollup": "^4.34.9",
|
||||
"rollup-plugin-commonjs": "10.1.0",
|
||||
"rollup-plugin-node-resolve": "5.2.0"
|
||||
},
|
||||
@@ -2340,9 +2340,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz",
|
||||
"integrity": "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.9.tgz",
|
||||
"integrity": "sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -2353,9 +2353,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz",
|
||||
"integrity": "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.9.tgz",
|
||||
"integrity": "sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2366,9 +2366,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz",
|
||||
"integrity": "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz",
|
||||
"integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2379,9 +2379,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz",
|
||||
"integrity": "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz",
|
||||
"integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2392,9 +2392,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz",
|
||||
"integrity": "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.9.tgz",
|
||||
"integrity": "sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2405,9 +2405,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz",
|
||||
"integrity": "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.9.tgz",
|
||||
"integrity": "sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2418,9 +2418,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz",
|
||||
"integrity": "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.9.tgz",
|
||||
"integrity": "sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -2431,9 +2431,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz",
|
||||
"integrity": "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.9.tgz",
|
||||
"integrity": "sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -2444,9 +2444,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz",
|
||||
"integrity": "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz",
|
||||
"integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2457,9 +2457,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz",
|
||||
"integrity": "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz",
|
||||
"integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2470,9 +2470,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz",
|
||||
"integrity": "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.9.tgz",
|
||||
"integrity": "sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -2483,9 +2483,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz",
|
||||
"integrity": "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.9.tgz",
|
||||
"integrity": "sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -2496,9 +2496,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz",
|
||||
"integrity": "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.9.tgz",
|
||||
"integrity": "sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -2509,9 +2509,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz",
|
||||
"integrity": "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.9.tgz",
|
||||
"integrity": "sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -2522,9 +2522,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz",
|
||||
"integrity": "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz",
|
||||
"integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2535,9 +2535,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz",
|
||||
"integrity": "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz",
|
||||
"integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2548,9 +2548,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz",
|
||||
"integrity": "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz",
|
||||
"integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2561,9 +2561,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz",
|
||||
"integrity": "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.9.tgz",
|
||||
"integrity": "sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -2574,9 +2574,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz",
|
||||
"integrity": "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz",
|
||||
"integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -11566,9 +11566,9 @@
|
||||
"license": "Unlicense"
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.34.8",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz",
|
||||
"integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.9.tgz",
|
||||
"integrity": "sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.6"
|
||||
@@ -11581,25 +11581,25 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.34.8",
|
||||
"@rollup/rollup-android-arm64": "4.34.8",
|
||||
"@rollup/rollup-darwin-arm64": "4.34.8",
|
||||
"@rollup/rollup-darwin-x64": "4.34.8",
|
||||
"@rollup/rollup-freebsd-arm64": "4.34.8",
|
||||
"@rollup/rollup-freebsd-x64": "4.34.8",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.34.8",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.34.8",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.34.8",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.34.8",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.34.8",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.34.8",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.34.8",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.34.8",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.34.8",
|
||||
"@rollup/rollup-linux-x64-musl": "4.34.8",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.34.8",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.34.8",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.34.8",
|
||||
"@rollup/rollup-android-arm-eabi": "4.34.9",
|
||||
"@rollup/rollup-android-arm64": "4.34.9",
|
||||
"@rollup/rollup-darwin-arm64": "4.34.9",
|
||||
"@rollup/rollup-darwin-x64": "4.34.9",
|
||||
"@rollup/rollup-freebsd-arm64": "4.34.9",
|
||||
"@rollup/rollup-freebsd-x64": "4.34.9",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.34.9",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.34.9",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.34.9",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.34.9",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.34.9",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.34.9",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.34.9",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.34.9",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.34.9",
|
||||
"@rollup/rollup-linux-x64-musl": "4.34.9",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.34.9",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.34.9",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.34.9",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
@@ -11708,9 +11708,9 @@
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/sass-embedded": {
|
||||
"version": "1.85.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.85.0.tgz",
|
||||
"integrity": "sha512-x3Vv54g0jv1aPSW8OTA/0GzQCs/HMQOjIkLtZJ3Xsn/I4vnyjKbVTQmFTax9bQjldqLEEkdbvy6ES/cOOnYNwA==",
|
||||
"version": "1.85.1",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.85.1.tgz",
|
||||
"integrity": "sha512-0i+3h2Df/c71afluxC1SXqyyMmJlnKWfu9ZGdzwuKRM1OftEa2XM2myt5tR36CF3PanYrMjFKtRIj8PfSf838w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^2.0.0",
|
||||
@@ -11729,32 +11729,32 @@
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"sass-embedded-android-arm": "1.85.0",
|
||||
"sass-embedded-android-arm64": "1.85.0",
|
||||
"sass-embedded-android-ia32": "1.85.0",
|
||||
"sass-embedded-android-riscv64": "1.85.0",
|
||||
"sass-embedded-android-x64": "1.85.0",
|
||||
"sass-embedded-darwin-arm64": "1.85.0",
|
||||
"sass-embedded-darwin-x64": "1.85.0",
|
||||
"sass-embedded-linux-arm": "1.85.0",
|
||||
"sass-embedded-linux-arm64": "1.85.0",
|
||||
"sass-embedded-linux-ia32": "1.85.0",
|
||||
"sass-embedded-linux-musl-arm": "1.85.0",
|
||||
"sass-embedded-linux-musl-arm64": "1.85.0",
|
||||
"sass-embedded-linux-musl-ia32": "1.85.0",
|
||||
"sass-embedded-linux-musl-riscv64": "1.85.0",
|
||||
"sass-embedded-linux-musl-x64": "1.85.0",
|
||||
"sass-embedded-linux-riscv64": "1.85.0",
|
||||
"sass-embedded-linux-x64": "1.85.0",
|
||||
"sass-embedded-win32-arm64": "1.85.0",
|
||||
"sass-embedded-win32-ia32": "1.85.0",
|
||||
"sass-embedded-win32-x64": "1.85.0"
|
||||
"sass-embedded-android-arm": "1.85.1",
|
||||
"sass-embedded-android-arm64": "1.85.1",
|
||||
"sass-embedded-android-ia32": "1.85.1",
|
||||
"sass-embedded-android-riscv64": "1.85.1",
|
||||
"sass-embedded-android-x64": "1.85.1",
|
||||
"sass-embedded-darwin-arm64": "1.85.1",
|
||||
"sass-embedded-darwin-x64": "1.85.1",
|
||||
"sass-embedded-linux-arm": "1.85.1",
|
||||
"sass-embedded-linux-arm64": "1.85.1",
|
||||
"sass-embedded-linux-ia32": "1.85.1",
|
||||
"sass-embedded-linux-musl-arm": "1.85.1",
|
||||
"sass-embedded-linux-musl-arm64": "1.85.1",
|
||||
"sass-embedded-linux-musl-ia32": "1.85.1",
|
||||
"sass-embedded-linux-musl-riscv64": "1.85.1",
|
||||
"sass-embedded-linux-musl-x64": "1.85.1",
|
||||
"sass-embedded-linux-riscv64": "1.85.1",
|
||||
"sass-embedded-linux-x64": "1.85.1",
|
||||
"sass-embedded-win32-arm64": "1.85.1",
|
||||
"sass-embedded-win32-ia32": "1.85.1",
|
||||
"sass-embedded-win32-x64": "1.85.1"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-android-arm": {
|
||||
"version": "1.85.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.85.0.tgz",
|
||||
"integrity": "sha512-pPBT7Ad6G8Mlao8ypVNXW2ya7I/Bhcny+RYZ/EmrunEXfhzCNp4PWV2VAweitPO9RnPIJwvUTkLc8Fu6K3nVmw==",
|
||||
"version": "1.85.1",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.85.1.tgz",
|
||||
"integrity": "sha512-GkcgUGMZtEF9gheuE1dxCU0ZSAifuaFXi/aX7ZXvjtdwmTl9Zc/OHR9oiUJkc8IW9UI7H8TuwlTAA8+SwgwIeQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -11768,9 +11768,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-android-arm64": {
|
||||
"version": "1.85.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.85.0.tgz",
|
||||
"integrity": "sha512-4itDzRwezwrW8+YzMLIwHtMeH+qrBNdBsRn9lTVI15K+cNLC8z5JWJi6UCZ8TNNZr9LDBfsh5jUdjSub0yF7jg==",
|
||||
"version": "1.85.1",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.85.1.tgz",
|
||||
"integrity": "sha512-27oRheqNA3SJM2hAxpVbs7mCKUwKPWmEEhyiNFpBINb5ELVLg+Ck5RsGg+SJmo130ul5YX0vinmVB5uPWc8X5w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -11784,9 +11784,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-android-ia32": {
|
||||
"version": "1.85.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-android-ia32/-/sass-embedded-android-ia32-1.85.0.tgz",
|
||||
"integrity": "sha512-bwqKq95hzbGbMTeXCMQhH7yEdc2xJVwIXj7rGdD3McvyFWbED6362XRFFPI5YyjfD2wRJd9yWLh/hn+6VyjcYA==",
|
||||
"version": "1.85.1",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-android-ia32/-/sass-embedded-android-ia32-1.85.1.tgz",
|
||||
"integrity": "sha512-f3x16NyRgtXFksIaO/xXKrUhttUBv8V0XsAR2Dhdb/yz4yrDrhzw9Wh8fmw7PlQqECcQvFaoDr3XIIM6lKzasw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -11800,9 +11800,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-android-riscv64": {
|
||||
"version": "1.85.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.85.0.tgz",
|
||||
"integrity": "sha512-Fgkgay+5EePJXZFHR5Vlkutnsmox2V6nX4U3mfGbSN1xjLRm8F5ST72V2s5Z0mnIFpGvEu/v7hfptgViqMvaxg==",
|
||||
"version": "1.85.1",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.85.1.tgz",
|
||||
"integrity": "sha512-IP6OijpJ8Mqo7XqCe0LsuZVbAxEFVboa0kXqqR5K55LebEplsTIA2GnmRyMay3Yr/2FVGsZbCb6Wlgkw23eCiA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -11816,9 +11816,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-android-x64": {
|
||||
"version": "1.85.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.85.0.tgz",
|
||||
"integrity": "sha512-/bG3JgTn3eoIDHCiJNVkLeJgUesat4ghxqYmKMZUJx++4e6iKCDj8XwQTJAgm+QDrsPKXHBacHEANJ9LEAuTqg==",
|
||||
"version": "1.85.1",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.85.1.tgz",
|
||||
"integrity": "sha512-Mh7CA53wR3ADvXAYipFc/R3vV4PVOzoKwWzPxmq+7i8UZrtsVjKONxGtqWe9JG1mna0C9CRZAx0sv/BzbOJxWg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -11832,9 +11832,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-darwin-arm64": {
|
||||
"version": "1.85.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.85.0.tgz",
|
||||
"integrity": "sha512-plp8TyMz97YFBCB3ndftEvoW29vyfsSBJILM5U84cGzr06SvLh/Npjj8psfUeRw+upEk1zkFtw5u61sRCdgwIw==",
|
||||
"version": "1.85.1",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.85.1.tgz",
|
||||
"integrity": "sha512-msWxzhvcP9hqGVegxVePVEfv9mVNTlUgGr6k7O7Ihji702mbtrH/lKwF4aRkkt4g1j7tv10+JtQXmTNi/pi9kA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -11848,9 +11848,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-darwin-x64": {
|
||||
"version": "1.85.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.85.0.tgz",
|
||||
"integrity": "sha512-LP8Zv8DG57Gn6PmSwWzC0gEZUsGdg36Ps3m0i1fVTOelql7N3HZIrlPYRjJvidL8ZlB3ISxNANebTREUHn/wkQ==",
|
||||
"version": "1.85.1",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.85.1.tgz",
|
||||
"integrity": "sha512-J4UFHUiyI9Z+mwYMwz11Ky9TYr3hY1fCxeQddjNGL/+ovldtb0yAIHvoVM0BGprQDm5JqhtUk8KyJ3RMJqpaAA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -11864,9 +11864,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-arm": {
|
||||
"version": "1.85.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.85.0.tgz",
|
||||
"integrity": "sha512-18xOAEfazJt1MMVS2TRHV94n81VyMnywOoJ7/S7I79qno/zx26OoqqP4XvH107xu8+mZ9Gg54LrUH6ZcgHk08g==",
|
||||
"version": "1.85.1",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.85.1.tgz",
|
||||
"integrity": "sha512-X0fDh95nNSw1wfRlnkE4oscoEA5Au4nnk785s9jghPFkTBg+A+5uB6trCjf0fM22+Iw6kiP4YYmDdw3BqxAKLQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -11880,9 +11880,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-arm64": {
|
||||
"version": "1.85.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.85.0.tgz",
|
||||
"integrity": "sha512-JRIRKVOY5Y8M1zlUOv9AQGju4P6lj8i5vLJZsVYVN/uY8Cd2dDJZPC8EOhjntp+IpF8AOGIHqCeCkHBceIyIjA==",
|
||||
"version": "1.85.1",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.85.1.tgz",
|
||||
"integrity": "sha512-jGadetB03BMFG2rq3OXub/uvC/lGpbQOiLGEz3NLb2nRZWyauRhzDtvZqkr6BEhxgIWtMtz2020yD8ZJSw/r2w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -11896,9 +11896,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-ia32": {
|
||||
"version": "1.85.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-ia32/-/sass-embedded-linux-ia32-1.85.0.tgz",
|
||||
"integrity": "sha512-4JH+h+gLt9So22nNPQtsKojEsLzjld9ol3zWcOtMGclv+HojZGbCuhJUrLUcK72F8adXYsULmWhJPKROLIwYMA==",
|
||||
"version": "1.85.1",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-ia32/-/sass-embedded-linux-ia32-1.85.1.tgz",
|
||||
"integrity": "sha512-7HlYY90d9mitDtNi5s+S+5wYZrTVbkBH2/kf7ixrzh2BFfT0YM81UHLJRnGX93y9aOMBL6DSZAIfkt1RsV9bkQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -11912,9 +11912,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-musl-arm": {
|
||||
"version": "1.85.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.85.0.tgz",
|
||||
"integrity": "sha512-Z1j4ageDVFihqNUBnm89fxY46pY0zD/Clp1D3ZdI7S+D280+AEpbm5vMoH8LLhBQfQLf2w7H++SZGpQwrisudQ==",
|
||||
"version": "1.85.1",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.85.1.tgz",
|
||||
"integrity": "sha512-5vcdEqE8QZnu6i6shZo7x2N36V7YUoFotWj2rGekII5ty7Nkaj+VtZhUEOp9tAzEOlaFuDp5CyO1kUCvweT64A==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -11928,9 +11928,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-musl-arm64": {
|
||||
"version": "1.85.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.85.0.tgz",
|
||||
"integrity": "sha512-aoQjUjK28bvdw9XKTjQeayn8oWQ2QqvoTD11myklGd3IHH7Jj0nwXUstI4NxDueCKt3wghuZoIQkjOheReQxlg==",
|
||||
"version": "1.85.1",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.85.1.tgz",
|
||||
"integrity": "sha512-FLkIT0p18XOkR6wryJ13LqGBDsrYev2dRk9dtiU18NCpNXruKsdBQ1ZnWHVKB3h1dA9lFyEEisC0sooKdNfeOQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -11944,9 +11944,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-musl-ia32": {
|
||||
"version": "1.85.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-ia32/-/sass-embedded-linux-musl-ia32-1.85.0.tgz",
|
||||
"integrity": "sha512-/cJCSXOfXmQFH8deE+3U9x+BSz8i0d1Tt9gKV/Gat1Xm43Oumw8pmZgno+cDuGjYQInr9ryW5121pTMlj/PBXQ==",
|
||||
"version": "1.85.1",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-ia32/-/sass-embedded-linux-musl-ia32-1.85.1.tgz",
|
||||
"integrity": "sha512-N1093T84zQJor1yyIAdYScB5eAuQarGK1tKgZ4uTnxVlgA7Xi1lXV8Eh7ox9sDqKCaWkVQ3MjqU26vYRBeRWyw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -11960,9 +11960,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-musl-riscv64": {
|
||||
"version": "1.85.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.85.0.tgz",
|
||||
"integrity": "sha512-l+FJxMXkmg42RZq5RFKXg4InX0IA7yEiPHe4kVSdrczP7z3NLxk+W9wVkPnoRKYIMe1qZPPQ25y0TgI4HNWouA==",
|
||||
"version": "1.85.1",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.85.1.tgz",
|
||||
"integrity": "sha512-WRsZS/7qlfYXsa93FBpSruieuURIu7ySfFhzYfF1IbKrNAGwmbduutkHZh2ddm5/vQMvQ0Rdosgv+CslaQHMcw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -11976,9 +11976,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-musl-x64": {
|
||||
"version": "1.85.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.85.0.tgz",
|
||||
"integrity": "sha512-M9ffjcYfFcRvkFA6V3DpOS955AyvmpvPAhL/xNK45d/ma1n1ehTWpd24tVeKiNK5CZkNjjMEfyw2fHa6MpqmEA==",
|
||||
"version": "1.85.1",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.85.1.tgz",
|
||||
"integrity": "sha512-+OlLIilA5TnP0YEqTQ8yZtkW+bJIQYvzoGoNLUEskeyeGuOiIyn2CwL6G4JQB4xZQFaxPHb7JD3EueFkQbH0Pw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -11992,9 +11992,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-riscv64": {
|
||||
"version": "1.85.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.85.0.tgz",
|
||||
"integrity": "sha512-yqPXQWfM+qiIPkfn++48GOlbmSvUZIyL9nwFstBk0k4x40UhbhilfknqeTUpxoHfQzylTGVhrm5JE7MjM+LNZA==",
|
||||
"version": "1.85.1",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.85.1.tgz",
|
||||
"integrity": "sha512-mKKlOwMGLN7yP1p0gB5yG/HX4fYLnpWaqstNuOOXH+fOzTaNg0+1hALg0H0CDIqypPO74M5MS9T6FAJZGdT6dQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -12008,9 +12008,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-linux-x64": {
|
||||
"version": "1.85.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.85.0.tgz",
|
||||
"integrity": "sha512-NTDeQFZcuVR7COoaRy8pZD6/+QznwBR8kVFsj7NpmvX9aJ7TX/q+OQZHX7Bfb3tsfKXhf1YZozegPuYxRnMKAQ==",
|
||||
"version": "1.85.1",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.85.1.tgz",
|
||||
"integrity": "sha512-uKRTv0z8NgtHV7xSren78+yoWB79sNi7TMqI7Bxd8fcRNIgHQSA8QBdF8led2ETC004hr8h71BrY60RPO+SSvA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -12024,9 +12024,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-win32-arm64": {
|
||||
"version": "1.85.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.85.0.tgz",
|
||||
"integrity": "sha512-gO0VAuxC4AdV+uZYJESRWVVHQWCGzNs0C3OKCAdH4r1vGRugooMi7J/5wbwUdXDA1MV9ICfhlKsph2n3GiPdqA==",
|
||||
"version": "1.85.1",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.85.1.tgz",
|
||||
"integrity": "sha512-/GMiZXBOc6AEMBC3g25Rp+x8fq9Z6Ql7037l5rajBPhZ+DdFwtdHY0Ou3oIU6XuWUwD06U3ii4XufXVFhsP6PA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -12040,9 +12040,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-win32-ia32": {
|
||||
"version": "1.85.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-win32-ia32/-/sass-embedded-win32-ia32-1.85.0.tgz",
|
||||
"integrity": "sha512-PCyn6xeFIBUgBceNypuf73/5DWF2VWPlPqPuBprPsTvpZOMUJeBtP+Lf4mnu3dNy1z76mYVnpaCnQmzZ0zHZaA==",
|
||||
"version": "1.85.1",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-win32-ia32/-/sass-embedded-win32-ia32-1.85.1.tgz",
|
||||
"integrity": "sha512-L+4BWkKKBGFOKVQ2PQ5HwFfkM5FvTf1Xx2VSRvEWt9HxPXp6SPDho6zC8fqNQ3hSjoaoASEIJcSvgfdQYO0gdg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -12056,9 +12056,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded-win32-x64": {
|
||||
"version": "1.85.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.85.0.tgz",
|
||||
"integrity": "sha512-AknE2jLp6OBwrR5hQ8pDsG94KhJCeSheFJ2xgbnk8RUjZX909JiNbgh2sNt9LG+RXf4xZa55dDL537gZoCx/iw==",
|
||||
"version": "1.85.1",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.85.1.tgz",
|
||||
"integrity": "sha512-/FO0AGKWxVfCk4GKsC0yXWBpUZdySe3YAAbQQL0lL6xUd1OiUY8Kow6g4Kc1TB/+z0iuQKKTqI/acJMEYl4iTQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
"playwright": "^1.50.1",
|
||||
"python": "^0.0.4",
|
||||
"query-command-supported": "1.0.0",
|
||||
"sass-embedded": "^1.85.0",
|
||||
"sass-embedded": "^1.85.1",
|
||||
"textarea-caret": "3.1.0",
|
||||
"timeago": "1.6.7",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
@@ -66,7 +66,7 @@
|
||||
"jest-environment-jsdom": "^29.2.2",
|
||||
"jshint": "2.13.6",
|
||||
"jshint-stylish": "2.2.1",
|
||||
"rollup": "^4.34.8",
|
||||
"rollup": "^4.34.9",
|
||||
"rollup-plugin-commonjs": "10.1.0",
|
||||
"rollup-plugin-node-resolve": "5.2.0"
|
||||
}
|
||||
|
||||
@@ -300,25 +300,13 @@ def test_accepting_invite_removes_invite_from_session(
|
||||
|
||||
client_request.login(user)
|
||||
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
|
||||
date_range = {"start_date": "2024-01-01", "days": 7}
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.dashboard.get_daily_stats",
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value={
|
||||
date_range["start_date"]: {
|
||||
"email": {"delivered": 0, "failure": 0, "requested": 0},
|
||||
"sms": {"delivered": 0, "failure": 1, "requested": 1},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.dashboard.get_daily_stats_by_user",
|
||||
return_value={
|
||||
date_range["start_date"]: {
|
||||
"email": {"delivered": 1, "failure": 0, "requested": 1},
|
||||
"sms": {"delivered": 1, "failure": 0, "requested": 1},
|
||||
},
|
||||
"messages_remaining": 71919,
|
||||
"messages_sent": 28081,
|
||||
"total_message_limit": 100000,
|
||||
},
|
||||
)
|
||||
page = client_request.get(
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import copy
|
||||
import json
|
||||
from datetime import datetime
|
||||
from unittest.mock import patch
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytest
|
||||
from flask import url_for
|
||||
@@ -12,6 +14,7 @@ from app.main.views.dashboard import (
|
||||
aggregate_template_usage,
|
||||
format_monthly_stats_to_list,
|
||||
get_dashboard_totals,
|
||||
get_local_daily_stats_for_last_x_days,
|
||||
get_tuples_of_financial_years,
|
||||
)
|
||||
from tests import (
|
||||
@@ -193,6 +196,12 @@ mock_daily_stats_by_user = {
|
||||
},
|
||||
}
|
||||
|
||||
mock_service_message_ratio = {
|
||||
"messages_remaining": 71919,
|
||||
"messages_sent": 28081,
|
||||
"total_message_limit": 100000,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"user",
|
||||
@@ -252,12 +261,8 @@ def test_get_started(
|
||||
)
|
||||
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,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
page = client_request.get(
|
||||
"main.service_dashboard",
|
||||
@@ -286,12 +291,8 @@ def test_get_started_is_hidden_once_templates_exist(
|
||||
)
|
||||
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,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
page = client_request.get(
|
||||
"main.service_dashboard",
|
||||
@@ -317,12 +318,8 @@ 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.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,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
page = client_request.get(
|
||||
"main.service_dashboard",
|
||||
@@ -348,12 +345,8 @@ def test_inbound_messages_shows_count_of_messages_when_there_are_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,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
page = client_request.get(
|
||||
"main.service_dashboard",
|
||||
@@ -384,12 +377,8 @@ def test_inbound_messages_shows_count_of_messages_when_there_are_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,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
page = client_request.get(
|
||||
"main.service_dashboard",
|
||||
@@ -638,12 +627,8 @@ def test_should_show_recent_templates_on_dashboard(
|
||||
)
|
||||
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,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
page = client_request.get(
|
||||
"main.service_dashboard",
|
||||
@@ -690,12 +675,8 @@ def test_should_not_show_recent_templates_on_dashboard_if_only_one_template_used
|
||||
)
|
||||
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,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
|
||||
main = page.select_one("main").text
|
||||
@@ -849,12 +830,8 @@ def test_should_show_upcoming_jobs_on_dashboard(
|
||||
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,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
page = client_request.get(
|
||||
"main.service_dashboard",
|
||||
@@ -896,12 +873,8 @@ 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,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
page = client_request.get(
|
||||
"main.service_dashboard",
|
||||
@@ -926,12 +899,8 @@ def test_should_not_show_upcoming_jobs_on_dashboard_if_service_has_no_jobs(
|
||||
):
|
||||
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,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
page = client_request.get(
|
||||
"main.service_dashboard",
|
||||
@@ -1016,12 +985,8 @@ def test_should_not_show_jobs_on_dashboard_for_users_with_uploads_page(
|
||||
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,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
page = client_request.get(
|
||||
"main.service_dashboard",
|
||||
@@ -1284,12 +1249,8 @@ def test_menu_send_messages(
|
||||
|
||||
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,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
page = _test_dashboard_menu(
|
||||
client_request,
|
||||
@@ -1325,12 +1286,8 @@ def test_menu_manage_service(
|
||||
):
|
||||
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,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
page = _test_dashboard_menu(
|
||||
client_request,
|
||||
@@ -1366,12 +1323,8 @@ def test_menu_main_settings(
|
||||
):
|
||||
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,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
page = _test_settings_menu(
|
||||
client_request,
|
||||
@@ -1406,12 +1359,8 @@ def test_menu_manage_api_keys(
|
||||
):
|
||||
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,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
page = _test_dashboard_menu(
|
||||
client_request,
|
||||
@@ -1447,12 +1396,8 @@ def test_menu_all_services_for_platform_admin_user(
|
||||
):
|
||||
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,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
page = _test_dashboard_menu(
|
||||
client_request, mocker, platform_admin_user, service_one, []
|
||||
@@ -1490,12 +1435,8 @@ def test_route_for_service_permissions(
|
||||
)
|
||||
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,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
validate_route_permission(
|
||||
mocker,
|
||||
@@ -1639,12 +1580,8 @@ def test_org_breadcrumbs_do_not_show_if_service_has_no_org(
|
||||
):
|
||||
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,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
|
||||
|
||||
@@ -1710,12 +1647,8 @@ 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.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,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
|
||||
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
|
||||
@@ -1750,12 +1683,8 @@ def test_org_breadcrumbs_do_not_show_if_user_is_a_member_of_the_services_org_but
|
||||
|
||||
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,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
|
||||
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
|
||||
@@ -1792,12 +1721,8 @@ 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.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,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
|
||||
client_request.login(platform_admin_user, service_one_json)
|
||||
@@ -1829,14 +1754,9 @@ def test_breadcrumb_shows_if_service_is_suspended(
|
||||
)
|
||||
|
||||
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,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
|
||||
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
|
||||
@@ -1869,12 +1789,8 @@ def test_service_dashboard_shows_usage(
|
||||
)
|
||||
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,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
|
||||
service_one["permissions"] = permissions
|
||||
@@ -1909,12 +1825,8 @@ 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.main.views.dashboard.get_daily_stats_by_user",
|
||||
return_value=mock_daily_stats_by_user,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
|
||||
|
||||
@@ -1930,12 +1842,8 @@ def test_service_dashboard_shows_batched_jobs(
|
||||
):
|
||||
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,
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value=mock_service_message_ratio,
|
||||
)
|
||||
|
||||
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
|
||||
@@ -1945,3 +1853,155 @@ def test_service_dashboard_shows_batched_jobs(
|
||||
assert len(rows) == 1
|
||||
|
||||
assert job_table_body is not None
|
||||
|
||||
|
||||
@patch("app.main.views.dashboard.datetime", wraps=datetime)
|
||||
def test_simple_local_conversion(mock_dt):
|
||||
stats_utc = {
|
||||
"2025-02-24T15:00:00Z": {
|
||||
"sms": {"delivered": 1, "failure": 0, "pending": 0, "requested": 1},
|
||||
"email": {"delivered": 0, "failure": 0, "pending": 0, "requested": 0},
|
||||
},
|
||||
"2025-02-25T07:00:00Z": {
|
||||
"sms": {"delivered": 2, "failure": 0, "pending": 0, "requested": 2},
|
||||
"email": {"delivered": 0, "failure": 0, "pending": 0, "requested": 0},
|
||||
},
|
||||
}
|
||||
|
||||
# Mock today's date in local time: 2025-02-25 at 10:00
|
||||
mock_dt.now.return_value = datetime(2025, 2, 25, 10, 0, 0, tzinfo=ZoneInfo("America/New_York"))
|
||||
|
||||
local_stats = get_local_daily_stats_for_last_x_days(
|
||||
stats_utc,
|
||||
"America/New_York",
|
||||
days=2
|
||||
)
|
||||
|
||||
assert len(local_stats) == 2
|
||||
assert "2025-02-24" in local_stats
|
||||
assert "2025-02-25" in local_stats
|
||||
|
||||
assert local_stats["2025-02-24"]["sms"]["delivered"] == 1
|
||||
assert local_stats["2025-02-24"]["sms"]["requested"] == 1
|
||||
assert local_stats["2025-02-25"]["sms"]["delivered"] == 2
|
||||
assert local_stats["2025-02-25"]["sms"]["requested"] == 2
|
||||
|
||||
|
||||
def test_no_timestamps_returns_zeroed_days():
|
||||
stats_utc = {}
|
||||
|
||||
class MockDateTime(datetime):
|
||||
@classmethod
|
||||
def now(cls, tz=None):
|
||||
return cls(2025, 2, 26, 8, 0, 0, tzinfo=tz)
|
||||
|
||||
with patch("app.main.views.dashboard.datetime", MockDateTime):
|
||||
local_stats = get_local_daily_stats_for_last_x_days(
|
||||
stats_utc, "America/New_York", days=3
|
||||
)
|
||||
assert list(local_stats.keys()) == ["2025-02-24", "2025-02-25", "2025-02-26"]
|
||||
for day in local_stats:
|
||||
for msg_type in ["sms", "email"]:
|
||||
for status in ["delivered", "failure", "pending", "requested"]:
|
||||
assert local_stats[day][msg_type][status] == 0
|
||||
|
||||
|
||||
def test_timestamp_in_future_time_zone():
|
||||
stats_utc = {
|
||||
"2025-02-25T01:00:00Z": {
|
||||
"sms": {"delivered": 5, "failure": 0, "pending": 0, "requested": 5},
|
||||
"email": {"delivered": 0, "failure": 0, "pending": 0, "requested": 0},
|
||||
}
|
||||
}
|
||||
|
||||
class MockDateTime(datetime):
|
||||
@classmethod
|
||||
def now(cls, tz=None):
|
||||
return cls(2025, 2, 25, 10, 0, 0, tzinfo=tz)
|
||||
|
||||
with patch("app.main.views.dashboard.datetime", MockDateTime):
|
||||
local_stats = get_local_daily_stats_for_last_x_days(
|
||||
stats_utc, "Asia/Shanghai", days=1
|
||||
)
|
||||
|
||||
assert list(local_stats.keys()) == ["2025-02-25"]
|
||||
assert local_stats["2025-02-25"]["sms"]["delivered"] == 5
|
||||
|
||||
|
||||
def test_many_timestamps_one_local_day():
|
||||
stats_utc = {
|
||||
"2025-02-24T05:00:00Z": {
|
||||
"sms": {"delivered": 2, "failure": 1, "pending": 0, "requested": 3},
|
||||
"email": {"delivered": 0, "failure": 0, "pending": 0, "requested": 0},
|
||||
},
|
||||
"2025-02-24T09:30:00Z": {
|
||||
"sms": {"delivered": 1, "failure": 0, "pending": 0, "requested": 1},
|
||||
"email": {"delivered": 2, "failure": 0, "pending": 0, "requested": 2},
|
||||
},
|
||||
"2025-02-24T16:59:59Z": {
|
||||
"sms": {"delivered": 4, "failure": 0, "pending": 0, "requested": 4},
|
||||
"email": {"delivered": 0, "failure": 0, "pending": 0, "requested": 0},
|
||||
},
|
||||
}
|
||||
|
||||
class MockDateTime(datetime):
|
||||
@classmethod
|
||||
def now(cls, tz=None):
|
||||
return cls(2025, 2, 24, 20, 0, 0, tzinfo=tz)
|
||||
|
||||
with patch("app.main.views.dashboard.datetime", MockDateTime):
|
||||
local_stats = get_local_daily_stats_for_last_x_days(
|
||||
stats_utc, "America/New_York", days=1
|
||||
)
|
||||
|
||||
assert list(local_stats.keys()) == ["2025-02-24"]
|
||||
assert local_stats["2025-02-24"]["sms"]["delivered"] == 7
|
||||
assert local_stats["2025-02-24"]["sms"]["requested"] == 8
|
||||
assert local_stats["2025-02-24"]["email"]["delivered"] == 2
|
||||
assert local_stats["2025-02-24"]["email"]["requested"] == 2
|
||||
|
||||
|
||||
def test_local_conversion_phoenix():
|
||||
"""Test aggregator logic in Mountain Time, no DST (America/Phoenix)."""
|
||||
stats_utc = {
|
||||
"2025-02-25T01:00:00Z": {
|
||||
"sms": {"delivered": 1, "failure": 0, "pending": 0, "requested": 1},
|
||||
"email": {"delivered": 0, "failure": 0, "pending": 0, "requested": 0},
|
||||
},
|
||||
"2025-02-25T12:00:00Z": {
|
||||
"sms": {"delivered": 2, "failure": 0, "pending": 0, "requested": 2},
|
||||
"email": {"delivered": 1, "failure": 0, "pending": 0, "requested": 1},
|
||||
},
|
||||
}
|
||||
|
||||
with patch("app.main.views.dashboard.datetime", wraps=datetime) as mock_dt:
|
||||
mock_dt.now.return_value = datetime(2025, 2, 25, 12, 0, 0, tzinfo=ZoneInfo("America/Phoenix"))
|
||||
local_stats = get_local_daily_stats_for_last_x_days(stats_utc, "America/Phoenix", days=1)
|
||||
|
||||
assert len(local_stats) == 1
|
||||
(day_key,) = local_stats.keys()
|
||||
sms_delivered = local_stats[day_key]["sms"]["delivered"]
|
||||
assert sms_delivered in (2, 3, 4)
|
||||
|
||||
|
||||
def test_local_conversion_honolulu():
|
||||
"""Test aggregator logic in Hawaii (Pacific/Honolulu)."""
|
||||
stats_utc = {
|
||||
"2025-02-25T03:00:00Z": {
|
||||
"sms": {"delivered": 2, "failure": 0, "pending": 0, "requested": 2},
|
||||
"email": {"delivered": 0, "failure": 0, "pending": 0, "requested": 0},
|
||||
},
|
||||
"2025-02-25T21:00:00Z": {
|
||||
"sms": {"delivered": 1, "failure": 0, "pending": 0, "requested": 1},
|
||||
"email": {"delivered": 2, "failure": 0, "pending": 0, "requested": 2},
|
||||
},
|
||||
}
|
||||
|
||||
with patch("app.main.views.dashboard.datetime", wraps=datetime) as mock_dt:
|
||||
mock_dt.now.return_value = datetime(2025, 2, 25, 12, 0, 0, tzinfo=ZoneInfo("Pacific/Honolulu"))
|
||||
local_stats = get_local_daily_stats_for_last_x_days(stats_utc, "Pacific/Honolulu", days=1)
|
||||
|
||||
assert len(local_stats) == 1
|
||||
(day_key,) = local_stats.keys()
|
||||
total_requested = local_stats[day_key]["sms"]["requested"]
|
||||
assert total_requested in (3, 1, 4)
|
||||
|
||||
@@ -128,25 +128,12 @@ def test_sign_out_user(
|
||||
# Check we are logged in
|
||||
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
|
||||
|
||||
date_range = {"start_date": "2024-01-01", "days": 7}
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.dashboard.get_daily_stats",
|
||||
"app.service_api_client.get_service_message_ratio",
|
||||
return_value={
|
||||
date_range["start_date"]: {
|
||||
"email": {"delivered": 0, "failure": 0, "requested": 0},
|
||||
"sms": {"delivered": 0, "failure": 1, "requested": 1},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
mocker.patch(
|
||||
"app.main.views.dashboard.get_daily_stats_by_user",
|
||||
return_value={
|
||||
date_range["start_date"]: {
|
||||
"email": {"delivered": 1, "failure": 0, "requested": 1},
|
||||
"sms": {"delivered": 1, "failure": 0, "requested": 1},
|
||||
},
|
||||
"messages_remaining": 71919,
|
||||
"messages_sent": 28081,
|
||||
"total_message_limit": 100000,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -199,7 +199,7 @@ test('Fetches data and creates chart and table correctly', async () => {
|
||||
|
||||
const data = await fetchData('service');
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledWith(`/services/${currentServiceId}/daily-stats.json`);
|
||||
expect(global.fetch).toHaveBeenCalledWith(`/services/${currentServiceId}/daily-stats.json?timezone=UTC`);
|
||||
expect(data).toEqual(mockResponse);
|
||||
|
||||
const labels = Object.keys(mockResponse).map(dateString => {
|
||||
|
||||
@@ -22,7 +22,7 @@ Object.defineProperty(HTMLElement.prototype, 'clientWidth', {
|
||||
beforeEach(() => {
|
||||
// Set up the DOM with the D3 script included
|
||||
document.body.innerHTML = `
|
||||
<div id="totalMessageChartContainer" data-sms-sent="100" data-sms-allowance-remaining="249900" style="width: 600px;">
|
||||
<div id="totalMessageChartContainer" data-messages-sent="28081" data-messages-remaining="71919" style="width: 600px;">
|
||||
<h1 id="chartTitle">Total Messages</h1>
|
||||
<svg id="totalMessageChart"></svg>
|
||||
</div>
|
||||
@@ -76,8 +76,8 @@ test('Populates the accessible table correctly', () => {
|
||||
expect(headers[1].textContent).toBe('Remaining');
|
||||
|
||||
const firstRowCells = rows[1].getElementsByTagName('td');
|
||||
expect(firstRowCells[0].textContent).toBe('100');
|
||||
expect(firstRowCells[1].textContent).toBe('249,900');
|
||||
expect(firstRowCells[0].textContent).toBe('28,081');
|
||||
expect(firstRowCells[1].textContent).toBe('71,919');
|
||||
});
|
||||
|
||||
// Test to check if the chart title is correctly set
|
||||
@@ -114,7 +114,7 @@ test('Chart resizes correctly on window resize', done => {
|
||||
// Testing the tooltip
|
||||
test('Tooltip displays on hover', () => {
|
||||
document.body.innerHTML = `
|
||||
<div id="totalMessageChartContainer" data-sms-sent="100" data-sms-allowance-remaining="249900" style="width: 600px;">
|
||||
<div id="totalMessageChartContainer" data-messages-sent="100" data-messages-remaining="249900" style="width: 600px;">
|
||||
<h1 id="chartTitle">Total Messages</h1>
|
||||
<svg id="totalMessageChart"></svg>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user