mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-26 09:13:58 -04:00
Compare commits
46 Commits
811f0e6df0
...
12-8-25
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc14da9638 | ||
|
|
957c3ee1e9 | ||
|
|
a81c9d0ff0 | ||
|
|
cd5aeceb43 | ||
|
|
b2235d6490 | ||
|
|
22af71718b | ||
|
|
0f9e61b831 | ||
|
|
612f585cbf | ||
|
|
0c9626ff4d | ||
|
|
9bc6290944 | ||
|
|
0d9061287c | ||
|
|
c5b3896b8d | ||
|
|
f887a19733 | ||
|
|
2b5d00ee23 | ||
|
|
62b8aa26cc | ||
|
|
149258347f | ||
|
|
325d9acc8f | ||
|
|
2c51e476ea | ||
|
|
31a46b7485 | ||
|
|
8b203666df | ||
|
|
0374e46e8c | ||
|
|
770f0203cf | ||
|
|
719aa01f0c | ||
|
|
23a55bcf96 | ||
|
|
4d8cc358b4 | ||
|
|
766a656311 | ||
|
|
567f80ce7d | ||
|
|
d603f8e8fd | ||
|
|
227839e7ca | ||
|
|
b2ab3f6361 | ||
|
|
f63103832e | ||
|
|
f315bd4f47 | ||
|
|
a552d2d046 | ||
|
|
0e73bef2ae | ||
|
|
39f1aa5b6d | ||
|
|
5bfb46dcfd | ||
|
|
d900264678 | ||
|
|
fe6e7d8fba | ||
|
|
04b9edf9c2 | ||
|
|
305db60c07 | ||
|
|
66379f7966 | ||
|
|
a11d47d0c3 | ||
|
|
8c01c39e7c | ||
|
|
0f995ce392 | ||
|
|
77d9bfb870 | ||
|
|
63d65c0305 |
19
.github/ISSUE_TEMPLATE/manual_qa_template.yml
vendored
19
.github/ISSUE_TEMPLATE/manual_qa_template.yml
vendored
@@ -83,6 +83,25 @@ body:
|
||||
- label: Clicking on “Remove this team member” prompts a confirmation
|
||||
- label: Clicking the confirmation removes the team member
|
||||
|
||||
- type: checkboxes
|
||||
id: org_dashboard
|
||||
attributes:
|
||||
label: Organization Dashboard
|
||||
options:
|
||||
- label: Dashboard navigation item is highlighted when viewing organization dashboard
|
||||
- label: Clicking "Create new service" button shows service creation form
|
||||
- label: Clicking "Add org admin" button shows user invitation form
|
||||
- label: Clicking "Manage" on a service shows the manage service form
|
||||
- label: In manage service form, service name, primary contact, and status can be edited
|
||||
- label: Clicking "Save Changes" shows confirmation modal
|
||||
- label: Confirming changes updates the service and shows success message
|
||||
- label: Updated service row is highlighted in the services table
|
||||
- label: The "Delete Service" button only appears for trial services or platform admins
|
||||
- label: Clicking "Delete Service" shows confirmation modal with service name
|
||||
- label: Confirming deletion suspends the service and shows success message
|
||||
- label: Deleted service is removed from the services table
|
||||
- label: Cancel buttons on all forms return to dashboard without making changes
|
||||
|
||||
- type: checkboxes
|
||||
id: sign_out
|
||||
attributes:
|
||||
|
||||
@@ -57,9 +57,41 @@
|
||||
}
|
||||
}
|
||||
|
||||
function initDeleteServiceConfirmation() {
|
||||
var deleteButtons = document.querySelectorAll('[data-open-modal="confirmDeleteModal"]');
|
||||
var confirmDeleteButton = document.getElementById('delete-service-confirm-btn');
|
||||
var deleteServiceNameDisplay = document.getElementById('delete-service-name-display');
|
||||
var deleteForm = document.getElementById('delete-service-form');
|
||||
|
||||
if (deleteButtons.length > 0 && confirmDeleteButton && deleteForm) {
|
||||
var currentServiceId = null;
|
||||
|
||||
deleteButtons.forEach(function(button) {
|
||||
button.addEventListener('click', function() {
|
||||
currentServiceId = button.getAttribute('data-service-id');
|
||||
var serviceName = button.getAttribute('data-service-name');
|
||||
|
||||
if (deleteServiceNameDisplay && serviceName) {
|
||||
deleteServiceNameDisplay.textContent = serviceName;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
confirmDeleteButton.addEventListener('click', function() {
|
||||
if (currentServiceId) {
|
||||
var orgId = window.location.pathname.split('/')[2];
|
||||
deleteForm.action = '/organizations/' + encodeURIComponent(orgId) + '?action=delete-service&service_id=' + encodeURIComponent(currentServiceId);
|
||||
|
||||
deleteForm.submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initForms();
|
||||
initEditServiceConfirmation();
|
||||
initDeleteServiceConfirmation();
|
||||
});
|
||||
|
||||
window.OrganizationDashboard = {
|
||||
|
||||
@@ -48,3 +48,8 @@
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.button-not-clickable {
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -47,3 +47,26 @@ td.table-empty-message {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.is-highlighted {
|
||||
td {
|
||||
background-color: color('blue-cool-5v');
|
||||
animation: fadeHighlight 3s ease-out forwards;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeHighlight {
|
||||
0% {
|
||||
background-color: color('blue-cool-10v');
|
||||
}
|
||||
50% {
|
||||
background-color: color('blue-cool-5v');
|
||||
}
|
||||
100% {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.job-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ def get_filtered_jobs(service_id, page):
|
||||
|
||||
|
||||
@main.route("/activity/services/<uuid:service_id>")
|
||||
@user_has_permissions(ServicePermission.VIEW_ACTIVITY)
|
||||
@user_has_permissions(ServicePermission.VIEW_ACTIVITY, allow_org_user=True)
|
||||
def all_jobs_activity(service_id):
|
||||
service_data_retention_days = 8
|
||||
page = get_page_from_request()
|
||||
|
||||
@@ -23,7 +23,7 @@ dummy_bearer_token = "bearer_token_set" # nosec B105 - this is not a real token
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/api")
|
||||
@user_has_permissions("manage_api_keys")
|
||||
@user_has_permissions("manage_api_keys", allow_org_user=True)
|
||||
def api_integration(service_id):
|
||||
callbacks_link = (
|
||||
".api_callbacks"
|
||||
@@ -40,7 +40,7 @@ def api_integration(service_id):
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/api/documentation")
|
||||
@user_has_permissions("manage_api_keys")
|
||||
@user_has_permissions("manage_api_keys", allow_org_user=True)
|
||||
def api_documentation(service_id):
|
||||
return redirect(url_for(".documentation"), code=301)
|
||||
|
||||
@@ -51,7 +51,7 @@ def api_documentation(service_id):
|
||||
endpoint="old_guest_list",
|
||||
)
|
||||
@main.route("/services/<uuid:service_id>/api/guest-list", methods=["GET", "POST"])
|
||||
@user_has_permissions("manage_api_keys")
|
||||
@user_has_permissions("manage_api_keys", allow_org_user=True)
|
||||
def guest_list(service_id):
|
||||
form = GuestList()
|
||||
if form.validate_on_submit():
|
||||
@@ -70,7 +70,7 @@ def guest_list(service_id):
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/api/keys")
|
||||
@user_has_permissions("manage_api_keys")
|
||||
@user_has_permissions("manage_api_keys", allow_org_user=True)
|
||||
def api_keys(service_id):
|
||||
return render_template(
|
||||
"views/api/keys.html",
|
||||
@@ -78,7 +78,7 @@ def api_keys(service_id):
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/api/keys/create", methods=["GET", "POST"])
|
||||
@user_has_permissions("manage_api_keys", restrict_admin_usage=True)
|
||||
@user_has_permissions("manage_api_keys", restrict_admin_usage=True, allow_org_user=True)
|
||||
def create_api_key(service_id):
|
||||
form = CreateKeyForm(current_service.api_keys)
|
||||
form.key_type.choices = [
|
||||
@@ -118,7 +118,7 @@ def create_api_key(service_id):
|
||||
@main.route(
|
||||
"/services/<uuid:service_id>/api/keys/revoke/<uuid:key_id>", methods=["GET", "POST"]
|
||||
)
|
||||
@user_has_permissions("manage_api_keys")
|
||||
@user_has_permissions("manage_api_keys", allow_org_user=True)
|
||||
def revoke_api_key(service_id, key_id):
|
||||
key_name = current_service.get_api_key(key_id)["name"]
|
||||
if request.method == "GET":
|
||||
@@ -161,7 +161,7 @@ def check_token_against_dummy_bearer(token):
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/api/callbacks", methods=["GET"])
|
||||
@user_has_permissions("manage_api_keys")
|
||||
@user_has_permissions("manage_api_keys", allow_org_user=True)
|
||||
def api_callbacks(service_id):
|
||||
if not current_service.has_permission(ServicePermission.INBOUND_SMS):
|
||||
return redirect(url_for(".delivery_status_callback", service_id=service_id))
|
||||
@@ -192,7 +192,7 @@ def get_delivery_status_callback_details():
|
||||
"/services/<uuid:service_id>/api/callbacks/delivery-status-callback",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@user_has_permissions("manage_api_keys")
|
||||
@user_has_permissions("manage_api_keys", allow_org_user=True)
|
||||
def delivery_status_callback(service_id):
|
||||
delivery_status_callback = get_delivery_status_callback_details()
|
||||
back_link = (
|
||||
@@ -259,7 +259,7 @@ def get_received_text_messages_callback():
|
||||
"/services/<uuid:service_id>/api/callbacks/received-text-messages-callback",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@user_has_permissions("manage_api_keys")
|
||||
@user_has_permissions("manage_api_keys", allow_org_user=True)
|
||||
def received_text_messages_callback(service_id):
|
||||
if not current_service.has_permission(ServicePermission.INBOUND_SMS):
|
||||
return redirect(url_for(".api_integration", service_id=service_id))
|
||||
|
||||
@@ -13,7 +13,7 @@ from notifications_utils.template import SMSPreviewTemplate
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/conversation/<uuid:notification_id>")
|
||||
@user_has_permissions(ServicePermission.VIEW_ACTIVITY)
|
||||
@user_has_permissions(ServicePermission.VIEW_ACTIVITY, allow_org_user=True)
|
||||
def conversation(service_id, notification_id):
|
||||
user_number = get_user_number(service_id, notification_id)
|
||||
|
||||
@@ -31,7 +31,7 @@ def conversation(service_id, notification_id):
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/conversation/<uuid:notification_id>.json")
|
||||
@user_has_permissions(ServicePermission.VIEW_ACTIVITY)
|
||||
@user_has_permissions(ServicePermission.VIEW_ACTIVITY, allow_org_user=True)
|
||||
def conversation_updates(service_id, notification_id):
|
||||
return jsonify(
|
||||
get_conversation_partials(
|
||||
@@ -46,7 +46,7 @@ def conversation_updates(service_id, notification_id):
|
||||
@main.route(
|
||||
"/services/<uuid:service_id>/conversation/<uuid:notification_id>/reply-with/from-folder/<uuid:from_folder>"
|
||||
)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, allow_org_user=True)
|
||||
def conversation_reply(
|
||||
service_id,
|
||||
notification_id,
|
||||
@@ -70,7 +70,7 @@ def conversation_reply(
|
||||
@main.route(
|
||||
"/services/<uuid:service_id>/conversation/<uuid:notification_id>/reply-with/<uuid:template_id>"
|
||||
)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, allow_org_user=True)
|
||||
def conversation_reply_with_template(
|
||||
service_id,
|
||||
notification_id,
|
||||
|
||||
@@ -24,13 +24,13 @@ from app.utils.user import user_has_permissions
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/dashboard")
|
||||
@user_has_permissions(ServicePermission.VIEW_ACTIVITY, ServicePermission.SEND_MESSAGES)
|
||||
@user_has_permissions(ServicePermission.VIEW_ACTIVITY, ServicePermission.SEND_MESSAGES, allow_org_user=True)
|
||||
def old_service_dashboard(service_id):
|
||||
return redirect(url_for(".service_dashboard", service_id=service_id))
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>")
|
||||
@user_has_permissions()
|
||||
@user_has_permissions(allow_org_user=True)
|
||||
def service_dashboard(service_id):
|
||||
|
||||
if session.get("invited_user_id"):
|
||||
@@ -84,7 +84,7 @@ def job_is_finished(job_dict):
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/daily-stats.json")
|
||||
@user_has_permissions()
|
||||
@user_has_permissions(allow_org_user=True)
|
||||
def get_daily_stats(service_id):
|
||||
date_range = get_stats_date_range()
|
||||
days = date_range["days"]
|
||||
@@ -148,7 +148,7 @@ def get_local_daily_stats_for_last_x_days(stats_utc, user_timezone, days):
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/daily-stats-by-user.json")
|
||||
@user_has_permissions()
|
||||
@user_has_permissions(allow_org_user=True)
|
||||
def get_daily_stats_by_user(service_id):
|
||||
date_range = get_stats_date_range()
|
||||
days = date_range["days"]
|
||||
@@ -166,7 +166,7 @@ def get_daily_stats_by_user(service_id):
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/template-usage")
|
||||
@user_has_permissions(ServicePermission.VIEW_ACTIVITY)
|
||||
@user_has_permissions(ServicePermission.VIEW_ACTIVITY, allow_org_user=True)
|
||||
def template_usage(service_id):
|
||||
year, current_financial_year = requested_and_current_financial_year(request)
|
||||
stats = template_statistics_client.get_monthly_template_usage_for_service(
|
||||
|
||||
@@ -11,7 +11,7 @@ from app.utils.user import user_has_permissions
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/history")
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def history(service_id):
|
||||
events = _get_events(current_service.id, request.args.get("selected"))
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ from notifications_utils.template import EmailPreviewTemplate, SMSBodyPreviewTem
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/jobs")
|
||||
@user_has_permissions()
|
||||
@user_has_permissions(allow_org_user=True)
|
||||
def view_jobs(service_id):
|
||||
return redirect(
|
||||
url_for(
|
||||
@@ -37,7 +37,7 @@ def view_jobs(service_id):
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/jobs/<uuid:job_id>")
|
||||
@user_has_permissions()
|
||||
@user_has_permissions(allow_org_user=True)
|
||||
def view_job(service_id, job_id):
|
||||
job = Job.from_id(job_id, service_id=current_service.id)
|
||||
if job.cancelled:
|
||||
@@ -83,7 +83,7 @@ def view_job(service_id, job_id):
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/jobs/<uuid:job_id>.csv")
|
||||
@user_has_permissions(ServicePermission.VIEW_ACTIVITY)
|
||||
@user_has_permissions(ServicePermission.VIEW_ACTIVITY, allow_org_user=True)
|
||||
def view_job_csv(service_id, job_id):
|
||||
job = Job.from_id(job_id, service_id=service_id)
|
||||
filter_args = parse_filter_args(request.args)
|
||||
@@ -111,14 +111,14 @@ def view_job_csv(service_id, job_id):
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/jobs/<uuid:job_id>", methods=["POST"])
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, allow_org_user=True)
|
||||
def cancel_job(service_id, job_id):
|
||||
Job.from_id(job_id, service_id=service_id).cancel()
|
||||
return redirect(url_for("main.service_dashboard", service_id=service_id))
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/jobs/<uuid:job_id>/status.json")
|
||||
@user_has_permissions()
|
||||
@user_has_permissions(allow_org_user=True)
|
||||
def view_job_status_poll(service_id, job_id):
|
||||
from app.notify_client.job_api_client import job_api_client
|
||||
|
||||
@@ -147,7 +147,7 @@ def view_job_status_poll(service_id, job_id):
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/jobs/<uuid:job_id>/notifications-table")
|
||||
@user_has_permissions()
|
||||
@user_has_permissions(allow_org_user=True)
|
||||
def view_job_notifications_table(service_id, job_id):
|
||||
"""Endpoint that returns only the notifications table HTML fragment."""
|
||||
job = Job.from_id(job_id, service_id=current_service.id)
|
||||
|
||||
@@ -44,7 +44,7 @@ def manage_users(service_id):
|
||||
@main.route(
|
||||
"/services/<uuid:service_id>/users/invite/<uuid:user_id>", methods=["GET", "POST"]
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def invite_user(service_id, user_id=None):
|
||||
form_class = InviteUserForm
|
||||
form = form_class(
|
||||
@@ -116,7 +116,7 @@ def invite_user(service_id, user_id=None):
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/users/<uuid:user_id>", methods=["GET", "POST"])
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def edit_user_permissions(service_id, user_id):
|
||||
service_has_email_auth = current_service.has_permission(
|
||||
ServicePermission.EMAIL_AUTH
|
||||
@@ -169,7 +169,7 @@ def edit_user_permissions(service_id, user_id):
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/users/<uuid:user_id>/delete", methods=["POST"])
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def remove_user_from_service(service_id, user_id):
|
||||
try:
|
||||
service_api_client.remove_user_from_service(service_id, user_id)
|
||||
@@ -192,7 +192,7 @@ def remove_user_from_service(service_id, user_id):
|
||||
"/services/<uuid:service_id>/users/<uuid:user_id>/edit-email",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def edit_user_email(service_id, user_id):
|
||||
user = current_service.get_team_member(user_id)
|
||||
user_email = user.email_address
|
||||
@@ -225,7 +225,7 @@ def edit_user_email(service_id, user_id):
|
||||
"/services/<uuid:service_id>/users/<uuid:user_id>/edit-email/confirm",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def confirm_edit_user_email(service_id, user_id):
|
||||
user = current_service.get_team_member(user_id)
|
||||
session_key = "team_member_email_change-{}".format(user_id)
|
||||
@@ -263,7 +263,7 @@ def confirm_edit_user_email(service_id, user_id):
|
||||
"/services/<uuid:service_id>/users/<uuid:user_id>/edit-mobile-number",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def edit_user_mobile_number(service_id, user_id):
|
||||
user = current_service.get_team_member(user_id)
|
||||
user_mobile_number = redact_mobile_number(user.mobile_number)
|
||||
@@ -293,7 +293,7 @@ def edit_user_mobile_number(service_id, user_id):
|
||||
"/services/<uuid:service_id>/users/<uuid:user_id>/edit-mobile-number/confirm",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def confirm_edit_user_mobile_number(service_id, user_id):
|
||||
user = current_service.get_team_member(user_id)
|
||||
if "team_member_mobile_change" in session:
|
||||
@@ -331,7 +331,7 @@ def confirm_edit_user_mobile_number(service_id, user_id):
|
||||
"/services/<uuid:service_id>/cancel-invited-user/<uuid:invited_user_id>",
|
||||
methods=["GET"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def cancel_invited_user(service_id, invited_user_id):
|
||||
current_service.cancel_invite(invited_user_id)
|
||||
|
||||
@@ -350,7 +350,7 @@ def cancel_invited_user(service_id, invited_user_id):
|
||||
"/services/<uuid:service_id>/resend-invite/<uuid:invited_user_id>",
|
||||
methods=["GET"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def resend_invite(service_id, invited_user_id):
|
||||
current_service.resend_invite(invited_user_id)
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ from notifications_utils.s3 import S3ObjectNotFound
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/notification/<uuid:notification_id>")
|
||||
@user_has_permissions(ServicePermission.VIEW_ACTIVITY, ServicePermission.SEND_MESSAGES)
|
||||
@user_has_permissions(ServicePermission.VIEW_ACTIVITY, ServicePermission.SEND_MESSAGES, allow_org_user=True)
|
||||
def view_notification(service_id, notification_id, error_message=None):
|
||||
if error_message:
|
||||
flash(error_message)
|
||||
@@ -108,7 +108,7 @@ def view_notification(service_id, notification_id, error_message=None):
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/notification/<uuid:notification_id>.json")
|
||||
@user_has_permissions(ServicePermission.VIEW_ACTIVITY, ServicePermission.SEND_MESSAGES)
|
||||
@user_has_permissions(ServicePermission.VIEW_ACTIVITY, ServicePermission.SEND_MESSAGES, allow_org_user=True)
|
||||
def view_notification_updates(service_id, notification_id):
|
||||
return jsonify(
|
||||
**get_single_notification_partials(
|
||||
@@ -149,7 +149,7 @@ PERIOD_TO_S3_FILENAME = {
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/download-notifications.csv")
|
||||
@user_has_permissions(ServicePermission.VIEW_ACTIVITY)
|
||||
@user_has_permissions(ServicePermission.VIEW_ACTIVITY, allow_org_user=True)
|
||||
def download_notifications_csv(service_id):
|
||||
set_timezone()
|
||||
filter_args = parse_filter_args(request.args)
|
||||
|
||||
@@ -87,18 +87,14 @@ def add_organization():
|
||||
return render_template("views/organizations/add-organization.html", form=form)
|
||||
|
||||
|
||||
def get_organization_message_allowance(org_id):
|
||||
def get_organization_messages_sent(org_id):
|
||||
try:
|
||||
message_usage = organizations_client.get_organization_message_usage(org_id)
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"Error fetching organization message usage: {e}")
|
||||
message_usage = {}
|
||||
|
||||
return {
|
||||
"messages_sent": message_usage.get("messages_sent", 0),
|
||||
"messages_remaining": message_usage.get("messages_remaining", 0),
|
||||
"total_message_limit": message_usage.get("total_message_limit", 0),
|
||||
}
|
||||
return message_usage.get("messages_sent", 0)
|
||||
|
||||
|
||||
def _handle_create_service(org_id):
|
||||
@@ -210,9 +206,7 @@ def _handle_delete_service(org_id, service_id):
|
||||
cached_service_user_ids = [user.id for user in service.active_users]
|
||||
|
||||
service_api_client.archive_service(service_id, cached_service_user_ids)
|
||||
create_archive_service_event(
|
||||
service_id=service_id, archived_by_id=current_user.id
|
||||
)
|
||||
create_archive_service_event(service_id=service_id, archived_by_id=current_user.id)
|
||||
|
||||
cache.redis_client.delete("organizations")
|
||||
|
||||
@@ -247,10 +241,10 @@ def get_services_dashboard_data(organization, year):
|
||||
if sms_sent > 0 or sms_remainder > 0:
|
||||
if sms_cost > 0:
|
||||
usage_parts.append(
|
||||
f"{sms_sent:,} sms ({sms_remainder:,} remaining, ${sms_cost:,.2f})"
|
||||
f"{sms_sent:,} sms ({sms_remainder:,} message parts remaining, ${sms_cost:,.2f})"
|
||||
)
|
||||
else:
|
||||
usage_parts.append(f"{sms_sent:,} sms ({sms_remainder:,} remaining)")
|
||||
usage_parts.append(f"{sms_sent:,} sms ({sms_remainder:,} message parts remaining)")
|
||||
|
||||
service["usage"] = ", ".join(usage_parts) if usage_parts else "No usage"
|
||||
|
||||
@@ -292,7 +286,7 @@ def organization_dashboard(org_id):
|
||||
elif action == "delete-service" and service_id:
|
||||
return _handle_delete_service(org_id, service_id)
|
||||
|
||||
message_allowance = get_organization_message_allowance(org_id)
|
||||
messages_sent = get_organization_messages_sent(org_id)
|
||||
|
||||
return render_template(
|
||||
"views/organizations/organization/index.html",
|
||||
@@ -307,7 +301,7 @@ def organization_dashboard(org_id):
|
||||
edit_service_data=edit_service_data,
|
||||
new_service_id=session.pop("new_service_id", None),
|
||||
updated_service_id=session.pop("updated_service_id", None),
|
||||
**message_allowance,
|
||||
messages_sent=messages_sent,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ def get_example_csv_rows(template, use_example_as_example=True, submitted_fields
|
||||
@main.route(
|
||||
"/services/<uuid:service_id>/send/<uuid:template_id>/csv", methods=["GET", "POST"]
|
||||
)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True, allow_org_user=True)
|
||||
def send_messages(service_id, template_id):
|
||||
notification_count = service_api_client.get_notification_count(service_id)
|
||||
remaining_messages = current_service.message_limit - notification_count
|
||||
@@ -189,7 +189,7 @@ def send_messages(service_id, template_id):
|
||||
|
||||
@main.route("/services/<uuid:service_id>/send/<uuid:template_id>.csv", methods=["GET"])
|
||||
@user_has_permissions(
|
||||
ServicePermission.SEND_MESSAGES, ServicePermission.MANAGE_TEMPLATES
|
||||
ServicePermission.SEND_MESSAGES, ServicePermission.MANAGE_TEMPLATES, allow_org_user=True
|
||||
)
|
||||
def get_example_csv(service_id, template_id):
|
||||
template = get_template(
|
||||
@@ -215,7 +215,7 @@ def get_example_csv(service_id, template_id):
|
||||
"/services/<uuid:service_id>/send/<uuid:template_id>/set-sender",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True, allow_org_user=True)
|
||||
def set_sender(service_id, template_id):
|
||||
session["sender_id"] = None
|
||||
redirect_to_one_off = redirect(
|
||||
@@ -344,7 +344,7 @@ def get_sender_details(service_id, template_type):
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/send/<uuid:template_id>/one-off")
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True, allow_org_user=True)
|
||||
def send_one_off(service_id, template_id):
|
||||
session["recipient"] = None
|
||||
session["placeholders"] = {}
|
||||
@@ -388,7 +388,7 @@ def get_notification_check_endpoint(service_id, template):
|
||||
"/services/<uuid:service_id>/send/<uuid:template_id>/one-off/step-<int:step_index>",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True, allow_org_user=True)
|
||||
def send_one_off_step(service_id, template_id, step_index):
|
||||
if {"recipient", "placeholders"} - set(session.keys()):
|
||||
return redirect(
|
||||
@@ -650,7 +650,7 @@ def _check_messages(service_id, template_id, upload_id, preview_row, **kwargs):
|
||||
"/services/<uuid:service_id>/<uuid:template_id>/check/<uuid:upload_id>/row-<int:row_index>",
|
||||
methods=["GET"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True, allow_org_user=True)
|
||||
def check_messages(service_id, template_id, upload_id, row_index=2):
|
||||
data = _check_messages(service_id, template_id, upload_id, row_index)
|
||||
data["allowed_file_extensions"] = Spreadsheet.ALLOWED_FILE_EXTENSIONS
|
||||
@@ -694,7 +694,7 @@ def check_messages(service_id, template_id, upload_id, row_index=2):
|
||||
"/services/<uuid:service_id>/<uuid:template_id>/check/<uuid:upload_id>/preview/row-<int:row_index>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True, allow_org_user=True)
|
||||
def preview_job(service_id, template_id, upload_id, row_index=2):
|
||||
session["scheduled_for"] = request.form.get("scheduled_for", "")
|
||||
data = _check_messages(
|
||||
@@ -709,7 +709,7 @@ def preview_job(service_id, template_id, upload_id, row_index=2):
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/start-job/<uuid:upload_id>", methods=["POST"])
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True, allow_org_user=True)
|
||||
def start_job(service_id, upload_id):
|
||||
scheduled_for = session.pop("scheduled_for", None)
|
||||
job_api_client.create_job(
|
||||
@@ -862,7 +862,7 @@ def get_skip_link(step_index, template):
|
||||
"/services/<uuid:service_id>/template/<uuid:template_id>/one-off/send-to-myself",
|
||||
methods=["GET"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True, allow_org_user=True)
|
||||
def send_one_off_to_myself(service_id, template_id):
|
||||
current_app.logger.info("Send one off to myself")
|
||||
try:
|
||||
@@ -899,7 +899,7 @@ def send_one_off_to_myself(service_id, template_id):
|
||||
"/services/<uuid:service_id>/template/<uuid:template_id>/notification/check",
|
||||
methods=["GET"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True, allow_org_user=True)
|
||||
def check_notification(service_id, template_id):
|
||||
return render_template(
|
||||
"views/notifications/check.html",
|
||||
@@ -981,7 +981,7 @@ def get_template_error_dict(exception):
|
||||
"/services/<uuid:service_id>/template/<uuid:template_id>/notification/check/preview",
|
||||
methods=["POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True, allow_org_user=True)
|
||||
def preview_notification(service_id, template_id):
|
||||
recipient = get_recipient()
|
||||
if not recipient:
|
||||
@@ -1013,7 +1013,7 @@ def preview_notification(service_id, template_id):
|
||||
"/services/<uuid:service_id>/template/<uuid:template_id>/notification/check",
|
||||
methods=["POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True, allow_org_user=True)
|
||||
def send_notification(service_id, template_id):
|
||||
recipient = get_recipient()
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ PLATFORM_ADMIN_SERVICE_PERMISSIONS = OrderedDict(
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/service-settings")
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, "manage_api_keys")
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, "manage_api_keys", allow_org_user=True)
|
||||
def service_settings(service_id):
|
||||
return render_template(
|
||||
"views/service-settings.html",
|
||||
@@ -81,7 +81,7 @@ def service_settings(service_id):
|
||||
@main.route(
|
||||
"/services/<uuid:service_id>/service-settings/name", methods=["GET", "POST"]
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def service_name_change(service_id):
|
||||
form = RenameServiceForm(name=current_service.name)
|
||||
|
||||
@@ -195,7 +195,7 @@ def service_set_permission(service_id, permission):
|
||||
@main.route(
|
||||
"/services/<uuid:service_id>/service-settings/archive", methods=["GET", "POST"]
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def archive_service(service_id):
|
||||
if not current_service.active or not (
|
||||
current_service.trial_mode or current_user.platform_admin
|
||||
@@ -268,7 +268,7 @@ def resume_service(service_id):
|
||||
"/services/<uuid:service_id>/service-settings/send-files-by-email",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def send_files_by_email_contact_details(service_id):
|
||||
form = ServiceContactDetailsForm()
|
||||
contact_details = None
|
||||
@@ -298,7 +298,7 @@ def send_files_by_email_contact_details(service_id):
|
||||
@main.route(
|
||||
"/services/<uuid:service_id>/service-settings/set-reply-to-email", methods=["GET"]
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def service_set_reply_to_email(service_id):
|
||||
return redirect(url_for(".service_email_reply_to", service_id=service_id))
|
||||
|
||||
@@ -306,7 +306,7 @@ def service_set_reply_to_email(service_id):
|
||||
@main.route(
|
||||
"/services/<uuid:service_id>/service-settings/email-reply-to", methods=["GET"]
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, "manage_api_keys")
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, "manage_api_keys", allow_org_user=True)
|
||||
def service_email_reply_to(service_id):
|
||||
return render_template("views/service-settings/email_reply_to.html")
|
||||
|
||||
@@ -315,7 +315,7 @@ def service_email_reply_to(service_id):
|
||||
"/services/<uuid:service_id>/service-settings/email-reply-to/add",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def service_add_email_reply_to(service_id):
|
||||
form = ServiceReplyToEmailForm()
|
||||
first_email_address = current_service.count_email_reply_to_addresses == 0
|
||||
@@ -359,7 +359,7 @@ def service_add_email_reply_to(service_id):
|
||||
"/services/<uuid:service_id>/service-settings/email-reply-to/<uuid:notification_id>/verify",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def service_verify_reply_to_address(service_id, notification_id):
|
||||
replace = request.args.get("replace", False)
|
||||
is_default = request.args.get("is_default", False)
|
||||
@@ -379,7 +379,7 @@ def service_verify_reply_to_address(service_id, notification_id):
|
||||
@main.route(
|
||||
"/services/<uuid:service_id>/service-settings/email-reply-to/<uuid:notification_id>/verify.json"
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def service_verify_reply_to_address_updates(service_id, notification_id):
|
||||
return jsonify(
|
||||
**get_service_verify_reply_to_address_partials(service_id, notification_id)
|
||||
@@ -456,7 +456,7 @@ def get_service_verify_reply_to_address_partials(service_id, notification_id):
|
||||
methods=["GET"],
|
||||
endpoint="service_confirm_delete_email_reply_to",
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def service_edit_email_reply_to(service_id, reply_to_email_id):
|
||||
form = ServiceReplyToEmailForm()
|
||||
reply_to_email_address = current_service.get_email_reply_to_address(
|
||||
@@ -525,7 +525,7 @@ def service_edit_email_reply_to(service_id, reply_to_email_id):
|
||||
"/services/<uuid:service_id>/service-settings/email-reply-to/<uuid:reply_to_email_id>/delete",
|
||||
methods=["POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def service_delete_email_reply_to(service_id, reply_to_email_id):
|
||||
service_api_client.delete_reply_to_email_address(
|
||||
service_id=current_service.id,
|
||||
@@ -538,7 +538,7 @@ def service_delete_email_reply_to(service_id, reply_to_email_id):
|
||||
"/services/<uuid:service_id>/service-settings/set-inbound-number",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def service_set_inbound_number(service_id):
|
||||
available_inbound_numbers = (
|
||||
inbound_number_client.get_available_inbound_sms_numbers()
|
||||
@@ -571,7 +571,7 @@ def service_set_inbound_number(service_id):
|
||||
@main.route(
|
||||
"/services/<uuid:service_id>/service-settings/sms-prefix", methods=["GET", "POST"]
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def service_set_sms_prefix(service_id):
|
||||
form = SMSPrefixForm(enabled=current_service.prefix_sms)
|
||||
|
||||
@@ -590,7 +590,7 @@ def service_set_sms_prefix(service_id):
|
||||
"/services/<uuid:service_id>/service-settings/set-international-sms",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def service_set_international_sms(service_id):
|
||||
form = ServiceOnOffSettingForm(
|
||||
"Send text messages to international phone numbers",
|
||||
@@ -611,7 +611,7 @@ def service_set_international_sms(service_id):
|
||||
@main.route(
|
||||
"/services/<uuid:service_id>/service-settings/set-inbound-sms", methods=["GET"]
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def service_set_inbound_sms(service_id):
|
||||
return render_template(
|
||||
"views/service-settings/set-inbound-sms.html",
|
||||
@@ -622,7 +622,7 @@ def service_set_inbound_sms(service_id):
|
||||
"/services/<uuid:service_id>/service-settings/set-<channel>",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def service_set_channel(service_id, channel):
|
||||
if channel not in {"email", "sms"}:
|
||||
abort(404)
|
||||
@@ -647,7 +647,7 @@ def service_set_channel(service_id, channel):
|
||||
@main.route(
|
||||
"/services/<uuid:service_id>/service-settings/set-auth-type", methods=["GET"]
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def service_set_auth_type(service_id):
|
||||
return render_template(
|
||||
"views/service-settings/set-auth-type.html",
|
||||
@@ -655,7 +655,7 @@ def service_set_auth_type(service_id):
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/service-settings/sms-sender", methods=["GET"])
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, "manage_api_keys")
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, "manage_api_keys", allow_org_user=True)
|
||||
def service_sms_senders(service_id):
|
||||
return render_template(
|
||||
"views/service-settings/sms-senders.html",
|
||||
@@ -666,7 +666,7 @@ def service_sms_senders(service_id):
|
||||
"/services/<uuid:service_id>/service-settings/sms-sender/add",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def service_add_sms_sender(service_id):
|
||||
form = ServiceSmsSenderForm()
|
||||
first_sms_sender = current_service.count_sms_senders == 0
|
||||
@@ -694,7 +694,7 @@ def service_add_sms_sender(service_id):
|
||||
methods=["GET"],
|
||||
endpoint="service_confirm_delete_sms_sender",
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def service_edit_sms_sender(service_id, sms_sender_id):
|
||||
sms_sender = current_service.get_sms_sender(sms_sender_id)
|
||||
is_inbound_number = sms_sender["inbound_number_id"]
|
||||
@@ -732,7 +732,7 @@ def service_edit_sms_sender(service_id, sms_sender_id):
|
||||
"/services/<uuid:service_id>/service-settings/sms-sender/<uuid:sms_sender_id>/delete",
|
||||
methods=["POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE)
|
||||
@user_has_permissions(ServicePermission.MANAGE_SERVICE, allow_org_user=True)
|
||||
def service_delete_sms_sender(service_id, sms_sender_id):
|
||||
service_api_client.delete_sms_sender(
|
||||
service_id=current_service.id,
|
||||
|
||||
@@ -40,7 +40,7 @@ form_objects = {
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/templates/<uuid:template_id>")
|
||||
@user_has_permissions()
|
||||
@user_has_permissions(allow_org_user=True)
|
||||
def view_template(service_id, template_id):
|
||||
template = current_service.get_template(template_id)
|
||||
template_folder = current_service.get_template_folder(template["folder"])
|
||||
@@ -82,7 +82,7 @@ def view_template(service_id, template_id):
|
||||
"/services/<uuid:service_id>/templates/<template_type:template_type>/folders/<uuid:template_folder_id>",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@user_has_permissions()
|
||||
@user_has_permissions(allow_org_user=True)
|
||||
def choose_template(service_id, template_type="all", template_folder_id=None):
|
||||
template_folder = current_service.get_template_folder(template_folder_id)
|
||||
|
||||
@@ -244,7 +244,7 @@ def _view_template_version(service_id, template_id, version):
|
||||
@main.route(
|
||||
"/services/<uuid:service_id>/templates/<uuid:template_id>/version/<int:version>"
|
||||
)
|
||||
@user_has_permissions()
|
||||
@user_has_permissions(allow_org_user=True)
|
||||
def view_template_version(service_id, template_id, version):
|
||||
return render_template(
|
||||
"views/templates/template_history.html",
|
||||
@@ -291,7 +291,7 @@ def _add_template_by_type(template_type, template_folder_id):
|
||||
@main.route(
|
||||
"/services/<uuid:service_id>/templates/copy/from-service/<uuid:from_service>/from-folder/<uuid:from_folder>"
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_TEMPLATES)
|
||||
@user_has_permissions(ServicePermission.MANAGE_TEMPLATES, allow_org_user=True)
|
||||
def choose_template_to_copy(
|
||||
service_id,
|
||||
from_service=None,
|
||||
@@ -323,7 +323,7 @@ def choose_template_to_copy(
|
||||
"/services/<uuid:service_id>/templates/copy/<uuid:template_id>",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_TEMPLATES)
|
||||
@user_has_permissions(ServicePermission.MANAGE_TEMPLATES, allow_org_user=True)
|
||||
def copy_template(service_id, template_id):
|
||||
from_service = request.args.get("from_service")
|
||||
|
||||
@@ -380,7 +380,7 @@ def _get_template_copy_name(template, existing_templates):
|
||||
"<template_type:notification_type>/<return_to>/<uuid:template_id>"
|
||||
)
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_TEMPLATES)
|
||||
@user_has_permissions(ServicePermission.MANAGE_TEMPLATES, allow_org_user=True)
|
||||
def action_blocked(service_id, notification_type, return_to, template_id=None):
|
||||
back_link = {
|
||||
"add_new_template": partial(
|
||||
@@ -412,7 +412,7 @@ def action_blocked(service_id, notification_type, return_to, template_id=None):
|
||||
"/services/<uuid:service_id>/templates/folders/<uuid:template_folder_id>/manage",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_TEMPLATES)
|
||||
@user_has_permissions(ServicePermission.MANAGE_TEMPLATES, allow_org_user=True)
|
||||
def manage_template_folder(service_id, template_folder_id):
|
||||
template_folder = current_service.get_template_folder_with_user_permission_or_403(
|
||||
template_folder_id, current_user
|
||||
@@ -462,7 +462,7 @@ def manage_template_folder(service_id, template_folder_id):
|
||||
"/services/<uuid:service_id>/templates/folders/<uuid:template_folder_id>/delete",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_TEMPLATES)
|
||||
@user_has_permissions(ServicePermission.MANAGE_TEMPLATES, allow_org_user=True)
|
||||
def delete_template_folder(service_id, template_folder_id):
|
||||
template_folder = current_service.get_template_folder_with_user_permission_or_403(
|
||||
template_folder_id, current_user
|
||||
@@ -527,7 +527,7 @@ def delete_template_folder(service_id, template_folder_id):
|
||||
"/services/<uuid:service_id>/templates/folders/<uuid:template_folder_id>/add-<template_type:template_type>",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_TEMPLATES)
|
||||
@user_has_permissions(ServicePermission.MANAGE_TEMPLATES, allow_org_user=True)
|
||||
def add_service_template(service_id, template_type, template_folder_id=None):
|
||||
if template_type not in current_service.available_template_types:
|
||||
return redirect(
|
||||
@@ -591,7 +591,7 @@ def abort_403_if_not_admin_user():
|
||||
"/services/<uuid:service_id>/templates/<uuid:template_id>/edit",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_TEMPLATES)
|
||||
@user_has_permissions(ServicePermission.MANAGE_TEMPLATES, allow_org_user=True)
|
||||
def edit_service_template(service_id, template_id):
|
||||
template = current_service.get_template_with_user_permission_or_403(
|
||||
template_id, current_user
|
||||
@@ -679,7 +679,7 @@ def edit_service_template(service_id, template_id):
|
||||
"/services/<uuid:service_id>/templates/count-<template_type:template_type>-length",
|
||||
methods=["POST"],
|
||||
)
|
||||
@user_has_permissions()
|
||||
@user_has_permissions(allow_org_user=True)
|
||||
def count_content_length(service_id, template_type):
|
||||
if template_type not in {"sms"}:
|
||||
abort(404)
|
||||
@@ -749,7 +749,7 @@ def _get_content_count_error_and_message_for_template(template):
|
||||
"/services/<uuid:service_id>/templates/<uuid:template_id>/delete",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_TEMPLATES)
|
||||
@user_has_permissions(ServicePermission.MANAGE_TEMPLATES, allow_org_user=True)
|
||||
def delete_service_template(service_id, template_id):
|
||||
template = current_service.get_template_with_user_permission_or_403(
|
||||
template_id, current_user
|
||||
@@ -807,7 +807,7 @@ def delete_service_template(service_id, template_id):
|
||||
@main.route(
|
||||
"/services/<uuid:service_id>/templates/<uuid:template_id>/redact", methods=["GET"]
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_TEMPLATES)
|
||||
@user_has_permissions(ServicePermission.MANAGE_TEMPLATES, allow_org_user=True)
|
||||
def confirm_redact_template(service_id, template_id):
|
||||
template = current_service.get_template_with_user_permission_or_403(
|
||||
template_id, current_user
|
||||
@@ -828,7 +828,7 @@ def confirm_redact_template(service_id, template_id):
|
||||
@main.route(
|
||||
"/services/<uuid:service_id>/templates/<uuid:template_id>/redact", methods=["POST"]
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_TEMPLATES)
|
||||
@user_has_permissions(ServicePermission.MANAGE_TEMPLATES, allow_org_user=True)
|
||||
def redact_template(service_id, template_id):
|
||||
service_api_client.redact_service_template(service_id, template_id)
|
||||
|
||||
@@ -847,7 +847,7 @@ def redact_template(service_id, template_id):
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/templates/<uuid:template_id>/versions")
|
||||
@user_has_permissions(ServicePermission.VIEW_ACTIVITY)
|
||||
@user_has_permissions(ServicePermission.VIEW_ACTIVITY, allow_org_user=True)
|
||||
def view_template_versions(service_id, template_id):
|
||||
return render_template(
|
||||
"views/templates/choose_history.html",
|
||||
@@ -867,7 +867,7 @@ def view_template_versions(service_id, template_id):
|
||||
"/services/<uuid:service_id>/templates/<uuid:template_id>/set-template-sender",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.MANAGE_TEMPLATES)
|
||||
@user_has_permissions(ServicePermission.MANAGE_TEMPLATES, allow_org_user=True)
|
||||
def set_template_sender(service_id, template_id):
|
||||
template = current_service.get_template_with_user_permission_or_403(
|
||||
template_id, current_user
|
||||
|
||||
@@ -15,7 +15,7 @@ from app.utils.user import user_has_permissions
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/tour/<uuid:template_id>")
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, allow_org_user=True)
|
||||
def begin_tour(service_id, template_id):
|
||||
db_template = current_service.get_template_with_user_permission_or_403(
|
||||
template_id, current_user
|
||||
@@ -48,7 +48,7 @@ def begin_tour(service_id, template_id):
|
||||
"/services/<uuid:service_id>/tour/<uuid:template_id>/step-<int:step_index>",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True, allow_org_user=True)
|
||||
def tour_step(service_id, template_id, step_index):
|
||||
db_template = current_service.get_template_with_user_permission_or_403(
|
||||
template_id, current_user
|
||||
@@ -167,7 +167,7 @@ def _get_tour_step_back_link(service_id, template_id, step_index):
|
||||
@main.route(
|
||||
"/services/<uuid:service_id>/tour/<uuid:template_id>/check", methods=["GET"]
|
||||
)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True)
|
||||
@user_has_permissions(ServicePermission.SEND_MESSAGES, restrict_admin_usage=True, allow_org_user=True)
|
||||
def check_tour_notification(service_id, template_id):
|
||||
db_template = current_service.get_template_with_user_permission_or_403(
|
||||
template_id, current_user
|
||||
@@ -224,7 +224,7 @@ def check_tour_notification(service_id, template_id):
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/end-tour/<uuid:example_template_id>")
|
||||
@user_has_permissions(ServicePermission.MANAGE_TEMPLATES)
|
||||
@user_has_permissions(ServicePermission.MANAGE_TEMPLATES, allow_org_user=True)
|
||||
def go_to_dashboard_after_tour(service_id, example_template_id):
|
||||
service_api_client.delete_service_template(service_id, example_template_id)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ MAX_FILE_UPLOAD_SIZE = 2 * 1024 * 1024 # 2MB
|
||||
|
||||
|
||||
@main.route("/services/<uuid:service_id>/uploads")
|
||||
@user_has_permissions()
|
||||
@user_has_permissions(allow_org_user=True)
|
||||
def uploads(service_id):
|
||||
# No tests have been written, this has been quickly prepared for user research.
|
||||
# It's also very like that a new view will be created to show uploads.
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
<ul class="usa-sidenav">
|
||||
{% if current_user.has_permissions() %}
|
||||
{% if current_user.has_permissions(ServicePermission.VIEW_ACTIVITY) %}
|
||||
<li class="usa-sidenav__item"><a class="{{ main_navigation.is_selected('dashboard') }}" href="{{ url_for('.service_dashboard', service_id=current_service.id) }}">Dashboard</a></li>
|
||||
<li class="usa-sidenav__item"><a class="{{ main_navigation.is_selected('activity') }}" href="{{ url_for('.all_jobs_activity', service_id=current_service.id) }}">Activity</a></li>
|
||||
<li class="usa-sidenav__item"><a class="usa-link {{ main_navigation.is_selected('dashboard') }}" href="{{ url_for('.service_dashboard', service_id=current_service.id) }}">Dashboard</a></li>
|
||||
<li class="usa-sidenav__item"><a class="usa-link {{ main_navigation.is_selected('activity') }}" href="{{ url_for('.all_jobs_activity', service_id=current_service.id) }}">Activity</a></li>
|
||||
{% endif %}
|
||||
{% elif current_user.has_permissions(allow_org_user=True) %}
|
||||
<li class="usa-sidenav__item"><a class="usa-link{{ main_navigation.is_selected('usage') }}" href="{{ url_for('.usage', service_id=current_service.id) }}">Usage</a></li>
|
||||
<li class="usa-sidenav__item"><a class="usa-link{{ main_navigation.is_selected('team-members') }}" href="{{ url_for('.manage_users', service_id=current_service.id) }}">Team members</a></li>
|
||||
<li class="usa-sidenav__item"><a class="usa-link {{ main_navigation.is_selected('usage') }}" href="{{ url_for('.usage', service_id=current_service.id) }}">Usage</a></li>
|
||||
<li class="usa-sidenav__item"><a class="usa-link {{ main_navigation.is_selected('team-members') }}" href="{{ url_for('.manage_users', service_id=current_service.id) }}">Team members</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<nav id="nav-org-nav" aria-label="Organization navigation" class="nav margin-bottom-4">
|
||||
<ul class="usa-sidenav">
|
||||
{% if config.ORGANIZATION_DASHBOARD_ENABLED %}
|
||||
<li class="usa-sidenav__item"><a class="usa-link{{ org_navigation.is_selected('dashboard') }}" href="{{ url_for('.organization_dashboard', org_id=current_org.id) }}">Dashboard</a></li>
|
||||
<li class="usa-sidenav__item"><a class="usa-link {{ org_navigation.is_selected('dashboard') }}" href="{{ url_for('.organization_dashboard', org_id=current_org.id) }}">Dashboard</a></li>
|
||||
{% endif %}
|
||||
<li class="usa-sidenav__item"><a class="usa-link{{ org_navigation.is_selected('usage') }}" href="{{ url_for('.organization_usage', org_id=current_org.id) }}">Usage</a></li>
|
||||
<li class="usa-sidenav__item"><a class="usa-link{{ org_navigation.is_selected('team-members') }}" href="{{ url_for('.manage_org_users', org_id=current_org.id) }}">Team members</a></li>
|
||||
<li class="usa-sidenav__item"><a class="usa-link {{ org_navigation.is_selected('usage') }}" href="{{ url_for('.organization_usage', org_id=current_org.id) }}">Usage</a></li>
|
||||
<li class="usa-sidenav__item"><a class="usa-link {{ org_navigation.is_selected('team-members') }}" href="{{ url_for('.manage_org_users', org_id=current_org.id) }}">Org admins</a></li>
|
||||
{% if current_user.platform_admin %}
|
||||
<li class="usa-sidenav__item"><a class="usa-link{{ org_navigation.is_selected('settings') }}" href="{{ url_for('.organization_settings', org_id=current_org.id) }}">Settings</a></li>
|
||||
<li class="usa-sidenav__item"><a class="usa-link{{ org_navigation.is_selected('trial-services') }}" href="{{ url_for('.organization_trial_mode_services', org_id=current_org.id) }}">Trial mode services</a></li>
|
||||
<li class="usa-sidenav__item"><a class="usa-link{{ org_navigation.is_selected('billing') }}" href="{{ url_for('.organization_billing', org_id=current_org.id) }}">Billing</a></li>
|
||||
<li class="usa-sidenav__item"><a class="usa-link {{ org_navigation.is_selected('settings') }}" href="{{ url_for('.organization_settings', org_id=current_org.id) }}">Settings</a></li>
|
||||
<li class="usa-sidenav__item"><a class="usa-link {{ org_navigation.is_selected('trial-services') }}" href="{{ url_for('.organization_trial_mode_services', org_id=current_org.id) }}">Trial mode services</a></li>
|
||||
<li class="usa-sidenav__item"><a class="usa-link {{ org_navigation.is_selected('billing') }}" href="{{ url_for('.organization_billing', org_id=current_org.id) }}">Billing</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@@ -3,14 +3,12 @@
|
||||
{% else %}
|
||||
<nav class="nav">
|
||||
<ul class="usa-sidenav">
|
||||
{# {% if current_user.has_permissions() %} #}
|
||||
<li class="usa-sidenav__item"><a class="{{ main_navigation.is_selected('settings') }}"
|
||||
<li class="usa-sidenav__item"><a class="usa-link {{ main_navigation.is_selected('settings') }}"
|
||||
href="{{ url_for('main.service_settings', service_id=current_service.id) }}">General</a></li>
|
||||
<li class="usa-sidenav__item"><a class="{{ main_navigation.is_selected('user-profile') }}"
|
||||
<li class="usa-sidenav__item"><a class="usa-link {{ main_navigation.is_selected('user-profile') }}"
|
||||
href="{{ url_for('main.user_profile', service_id=current_service.id) }}">User profile</a></li>
|
||||
<li class="usa-sidenav__item"><a class="{{ main_navigation.is_selected('team-members') }}"
|
||||
<li class="usa-sidenav__item"><a class="usa-link {{ main_navigation.is_selected('team-members') }}"
|
||||
href="{{ url_for('main.manage_users', service_id=current_service.id) }}">Team members</a></li>
|
||||
{# {% endif %} #}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
itemscope
|
||||
itemtype="http://schema.org/ListItem"
|
||||
>
|
||||
<a class="{% if has_subnav %}parent-link {% endif %}{% if item['link'] == request.endpoint %} usa-current {% endif %}" href="{{ url_for(item['link']) }}" itemprop="item">
|
||||
<a class="usa-link {% if has_subnav %}parent-link {% endif %}{% if item['link'] == request.endpoint %}usa-current {% endif %}" href="{{ url_for(item['link']) }}" itemprop="item">
|
||||
<span itemprop="name">{{item['name']}}</span>
|
||||
</a>
|
||||
{% if caller %}
|
||||
|
||||
@@ -9,23 +9,21 @@
|
||||
|
||||
{{ page_header('Organization Dashboard', size='large') }}
|
||||
|
||||
<div id="totalMessageChartContainer" data-messages-sent="{{ messages_sent|default(0) }}" data-messages-remaining="{{ messages_remaining|default(0) }}" data-total-message-limit="{{ total_message_limit|default(0) }}">
|
||||
<div class="margin-bottom-3">
|
||||
<div class="grid-row flex-align-center">
|
||||
<h2 id="chartTitle" class="margin-right-1 margin-y-0">Overall {{ selected_year }} Total Message Allowance</h2>
|
||||
<h2 id="chartTitle" class="margin-right-1 margin-y-0">Overall {{ selected_year }} Messages Sent</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="usa-tooltip usa-tooltip__information margin-right-0"
|
||||
data-position="top"
|
||||
title="Combined totals across all services in {{ current_org.name }}: pending, failed, or delivered"
|
||||
title="Total messages sent across all services in {{ current_org.name }}"
|
||||
>
|
||||
<span class="usa-sr-only">More information</span>
|
||||
<img src="{{ asset_url('img/usa-icons/info.svg') }}" alt="" class="usa-icon" role="presentation" />
|
||||
</button>
|
||||
</div>
|
||||
<svg id="totalMessageChart"></svg>
|
||||
<div id="message"></div>
|
||||
<div class="font-sans-xl text-primary-darker margin-top-1">{{ "{:,}".format(messages_sent|default(0)) }} sent</div>
|
||||
</div>
|
||||
<div id="totalMessageTable" class="margin-bottom-3"></div>
|
||||
<div class="grid-row margin-bottom-3 maxw-tablet">
|
||||
<div class="grid-col-12 tablet:grid-col-6 margin-bottom-2 tablet:margin-bottom-0 tablet:padding-right-1">
|
||||
<div class="usa-summary-box height-full" role="region">
|
||||
@@ -39,7 +37,7 @@
|
||||
<div class="text-ink font-body-2xs line-height-sans-3">
|
||||
<span class="text-bold">{{ live_services }}</span> Live
|
||||
<span class="margin-left-1 text-bold">{{ trial_services }}</span> Trial
|
||||
<span class="margin-left-1 text-bold">{{ suspended_services }}</span> Suspended
|
||||
<span class="margin-left-1 text-bold">{{ suspended_services }}</span> Archived
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -70,7 +68,7 @@
|
||||
</div>
|
||||
|
||||
{% if create_service_form %}
|
||||
<div id="create-service-form" class="padding-3 radius-md margin-bottom-3">
|
||||
<div id="create-service-form" class="padding-3 radius-md margin-bottom-3 bg-gray-5">
|
||||
<h3 class="margin-top-0">Create a new service</h3>
|
||||
<form method="post" action="{{ url_for('.organization_dashboard', org_id=current_org.id, action='create-service') }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ create_service_form.csrf_token._value() }}"/>
|
||||
@@ -142,6 +140,9 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
<div class="border-top-1px border-base-lighter margin-top-3 padding-top-3">
|
||||
<a href="{{ url_for('main.invite_user', service_id=edit_service_data.id) }}" class="usa-button usa-button--outline">Invite team member</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -238,7 +239,7 @@
|
||||
</h3>
|
||||
<div id="what-is-service-content" class="usa-accordion__content usa-prose" hidden>
|
||||
<p>
|
||||
When you join Notify, you're added to a service. This is your organization's workspace for sending text messages and emails. Within your service, you can:
|
||||
Services are your organization's workspaces for sending text messages and emails. If you have multiple teams using Notify for different purposes, it is helpful to set up a separate Service for each team, with its own managers and templates. There is no limit to the number of Services you can create, and messages sent by all of your Services contribute to your overall messaging usage. Within each Service, you can:
|
||||
</p>
|
||||
<ol class="usa-list">
|
||||
<li>Create and edit message templates</li>
|
||||
@@ -246,7 +247,8 @@
|
||||
<li>View message status and history</li>
|
||||
<li>Manage team members and permissions</li>
|
||||
<li>Track usage and delivery statistics</li>
|
||||
<li>If you work for multiple organizations, you may belong to multiple services and can switch between them.</li>
|
||||
<li>Service members can be added to multiple services and can switch between them</li>
|
||||
<li>Organizational administrators can always see all Services</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
@@ -275,7 +277,7 @@
|
||||
<td><a href="{{ url_for('main.service_dashboard', service_id=service.id) }}" class="usa-link">{{ service.name }}</a></td>
|
||||
<td>
|
||||
{% if not service.active %}
|
||||
Suspended
|
||||
Archived
|
||||
{% elif service.restricted %}
|
||||
Trial
|
||||
{% else %}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{% from "components/components/button/macro.njk" import usaButton %}
|
||||
|
||||
{% block org_page_title %}
|
||||
Team members
|
||||
Organization administrators
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
@@ -20,7 +20,7 @@
|
||||
{% endif %}
|
||||
|
||||
<h1 class="font-body-2xl">
|
||||
Team members
|
||||
Organization administrators
|
||||
</h1>
|
||||
|
||||
{% if show_search_box %}
|
||||
@@ -66,7 +66,7 @@
|
||||
<div class="js-stick-at-bottom-when-scrolling">
|
||||
{{ usaButton({
|
||||
"element": "a",
|
||||
"text": "Invite team member",
|
||||
"text": "Invite an organization administrator",
|
||||
"href": url_for('.invite_org_user', org_id=current_org.id),
|
||||
"classes": "usa-button"
|
||||
}) }}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
{{ page_header("Invite a team member") }}
|
||||
{{ page_header("Invite an organization administrator") }}
|
||||
{% call form_wrapper() %}
|
||||
{{ form.email_address(
|
||||
param_extensions={
|
||||
@@ -26,11 +26,11 @@
|
||||
|
||||
<div class="margin-bottom-3">
|
||||
<p class="form-label">
|
||||
{{ current_org.name }} team members can:
|
||||
{{ current_org.name }} organization administrators can:
|
||||
</p>
|
||||
<ul class="list list-bullet">
|
||||
<li>see usage and team members for each service</li>
|
||||
<li>invite other team members</li>
|
||||
<li>access and manage all services</li>
|
||||
<li>invite or remove other organization administrators</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
5277
package-lock.json
generated
5277
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@@ -27,7 +27,8 @@
|
||||
"license": "CC0",
|
||||
"homepage": "https://github.com/GSA/notifications-admin#readme",
|
||||
"overrides": {
|
||||
"graceful-fs": "^4.2.11"
|
||||
"graceful-fs": "^4.2.11",
|
||||
"glob@^10": "10.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@rollup/plugin-commonjs": "^29.0.0",
|
||||
@@ -37,7 +38,7 @@
|
||||
"cbor-js": "0.1.0",
|
||||
"ci-info": "^4.3.1",
|
||||
"d3": "^7.9.0",
|
||||
"playwright": "^1.56.1",
|
||||
"playwright": "^1.57.0",
|
||||
"python": "^0.0.4",
|
||||
"query-command-supported": "1.0.0",
|
||||
"sass-embedded": "^1.93.3",
|
||||
@@ -60,10 +61,10 @@
|
||||
"merge-stream": "^2.0.0",
|
||||
"mini-css-extract-plugin": "^2.9.4",
|
||||
"postcss-loader": "^8.2.0",
|
||||
"sass": "^1.94.0",
|
||||
"sass": "^1.94.2",
|
||||
"sass-loader": "^16.0.6",
|
||||
"terser-webpack-plugin": "^5.3.14",
|
||||
"webpack": "^5.102.1",
|
||||
"terser-webpack-plugin": "^5.3.15",
|
||||
"webpack": "^5.103.0",
|
||||
"webpack-cli": "^6.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
558
poetry.lock
generated
558
poetry.lock
generated
@@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "ago"
|
||||
@@ -97,14 +97,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "bandit"
|
||||
version = "1.8.6"
|
||||
version = "1.9.2"
|
||||
description = "Security oriented static analyser for python code."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
python-versions = ">=3.10"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "bandit-1.8.6-py3-none-any.whl", hash = "sha256:3348e934d736fcdb68b6aa4030487097e23a501adf3e7827b63658df464dddd0"},
|
||||
{file = "bandit-1.8.6.tar.gz", hash = "sha256:dbfe9c25fc6961c2078593de55fd19f2559f9e45b99f1272341f5b95dea4e56b"},
|
||||
{file = "bandit-1.9.2-py3-none-any.whl", hash = "sha256:bda8d68610fc33a6e10b7a8f1d61d92c8f6c004051d5e946406be1fb1b16a868"},
|
||||
{file = "bandit-1.9.2.tar.gz", hash = "sha256:32410415cd93bf9c8b91972159d5cf1e7f063a9146d70345641cd3877de348ce"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -122,18 +122,18 @@ yaml = ["PyYAML"]
|
||||
|
||||
[[package]]
|
||||
name = "beautifulsoup4"
|
||||
version = "4.14.2"
|
||||
version = "4.14.3"
|
||||
description = "Screen-scraping library"
|
||||
optional = false
|
||||
python-versions = ">=3.7.0"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515"},
|
||||
{file = "beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e"},
|
||||
{file = "beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb"},
|
||||
{file = "beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
soupsieve = ">1.2"
|
||||
soupsieve = ">=1.6.1"
|
||||
typing-extensions = ">=4.0.0"
|
||||
|
||||
[package.extras]
|
||||
@@ -326,26 +326,26 @@ redis = ["redis (>=2.10.5)"]
|
||||
|
||||
[[package]]
|
||||
name = "cachetools"
|
||||
version = "6.2.1"
|
||||
version = "6.2.2"
|
||||
description = "Extensible memoizing collections and decorators"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "cachetools-6.2.1-py3-none-any.whl", hash = "sha256:09868944b6dde876dfd44e1d47e18484541eaf12f26f29b7af91b26cc892d701"},
|
||||
{file = "cachetools-6.2.1.tar.gz", hash = "sha256:3f391e4bd8f8bf0931169baf7456cc822705f4e2a31f840d218f445b9a854201"},
|
||||
{file = "cachetools-6.2.2-py3-none-any.whl", hash = "sha256:6c09c98183bf58560c97b2abfcedcbaf6a896a490f534b031b661d3723b45ace"},
|
||||
{file = "cachetools-6.2.2.tar.gz", hash = "sha256:8e6d266b25e539df852251cfd6f990b4bc3a141db73b939058d809ebd2590fc6"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.10.5"
|
||||
version = "2025.11.12"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main", "dev"]
|
||||
files = [
|
||||
{file = "certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de"},
|
||||
{file = "certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43"},
|
||||
{file = "certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b"},
|
||||
{file = "certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -598,14 +598,14 @@ rapidfuzz = ">=3.0.0,<4.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.3.0"
|
||||
version = "8.3.1"
|
||||
description = "Composable command line interface toolkit"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
groups = ["main", "dev"]
|
||||
files = [
|
||||
{file = "click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc"},
|
||||
{file = "click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4"},
|
||||
{file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"},
|
||||
{file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -626,104 +626,104 @@ markers = {main = "platform_system == \"Windows\""}
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.11.3"
|
||||
version = "7.12.0"
|
||||
description = "Code coverage measurement for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "coverage-7.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0c986537abca9b064510f3fd104ba33e98d3036608c7f2f5537f869bc10e1ee5"},
|
||||
{file = "coverage-7.11.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:28c5251b3ab1d23e66f1130ca0c419747edfbcb4690de19467cd616861507af7"},
|
||||
{file = "coverage-7.11.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4f2bb4ee8dd40f9b2a80bb4adb2aecece9480ba1fa60d9382e8c8e0bd558e2eb"},
|
||||
{file = "coverage-7.11.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e5f4bfac975a2138215a38bda599ef00162e4143541cf7dd186da10a7f8e69f1"},
|
||||
{file = "coverage-7.11.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f4cbfff5cf01fa07464439a8510affc9df281535f41a1f5312fbd2b59b4ab5c"},
|
||||
{file = "coverage-7.11.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:31663572f20bf3406d7ac00d6981c7bbbcec302539d26b5ac596ca499664de31"},
|
||||
{file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9799bd6a910961cb666196b8583ed0ee125fa225c6fdee2cbf00232b861f29d2"},
|
||||
{file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:097acc18bedf2c6e3144eaf09b5f6034926c3c9bb9e10574ffd0942717232507"},
|
||||
{file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6f033dec603eea88204589175782290a038b436105a8f3637a81c4359df27832"},
|
||||
{file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dd9ca2d44ed8018c90efb72f237a2a140325a4c3339971364d758e78b175f58e"},
|
||||
{file = "coverage-7.11.3-cp310-cp310-win32.whl", hash = "sha256:900580bc99c145e2561ea91a2d207e639171870d8a18756eb57db944a017d4bb"},
|
||||
{file = "coverage-7.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:c8be5bfcdc7832011b2652db29ed7672ce9d353dd19bce5272ca33dbcf60aaa8"},
|
||||
{file = "coverage-7.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:200bb89fd2a8a07780eafcdff6463104dec459f3c838d980455cfa84f5e5e6e1"},
|
||||
{file = "coverage-7.11.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d264402fc179776d43e557e1ca4a7d953020d3ee95f7ec19cc2c9d769277f06"},
|
||||
{file = "coverage-7.11.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:385977d94fc155f8731c895accdfcc3dd0d9dd9ef90d102969df95d3c637ab80"},
|
||||
{file = "coverage-7.11.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0542ddf6107adbd2592f29da9f59f5d9cff7947b5bb4f734805085c327dcffaa"},
|
||||
{file = "coverage-7.11.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d60bf4d7f886989ddf80e121a7f4d140d9eac91f1d2385ce8eb6bda93d563297"},
|
||||
{file = "coverage-7.11.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0a3b6e32457535df0d41d2d895da46434706dd85dbaf53fbc0d3bd7d914b362"},
|
||||
{file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:876a3ee7fd2613eb79602e4cdb39deb6b28c186e76124c3f29e580099ec21a87"},
|
||||
{file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a730cd0824e8083989f304e97b3f884189efb48e2151e07f57e9e138ab104200"},
|
||||
{file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b5cd111d3ab7390be0c07ad839235d5ad54d2ca497b5f5db86896098a77180a4"},
|
||||
{file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:074e6a5cd38e06671580b4d872c1a67955d4e69639e4b04e87fc03b494c1f060"},
|
||||
{file = "coverage-7.11.3-cp311-cp311-win32.whl", hash = "sha256:86d27d2dd7c7c5a44710565933c7dc9cd70e65ef97142e260d16d555667deef7"},
|
||||
{file = "coverage-7.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:ca90ef33a152205fb6f2f0c1f3e55c50df4ef049bb0940ebba666edd4cdebc55"},
|
||||
{file = "coverage-7.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:56f909a40d68947ef726ce6a34eb38f0ed241ffbe55c5007c64e616663bcbafc"},
|
||||
{file = "coverage-7.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b771b59ac0dfb7f139f70c85b42717ef400a6790abb6475ebac1ecee8de782f"},
|
||||
{file = "coverage-7.11.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:603c4414125fc9ae9000f17912dcfd3d3eb677d4e360b85206539240c96ea76e"},
|
||||
{file = "coverage-7.11.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:77ffb3b7704eb7b9b3298a01fe4509cef70117a52d50bcba29cffc5f53dd326a"},
|
||||
{file = "coverage-7.11.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4d4ca49f5ba432b0755ebb0fc3a56be944a19a16bb33802264bbc7311622c0d1"},
|
||||
{file = "coverage-7.11.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05fd3fb6edff0c98874d752013588836f458261e5eba587afe4c547bba544afd"},
|
||||
{file = "coverage-7.11.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0e920567f8c3a3ce68ae5a42cf7c2dc4bb6cc389f18bff2235dd8c03fa405de5"},
|
||||
{file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4bec8c7160688bd5a34e65c82984b25409563134d63285d8943d0599efbc448e"},
|
||||
{file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:adb9b7b42c802bd8cb3927de8c1c26368ce50c8fdaa83a9d8551384d77537044"},
|
||||
{file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c8f563b245b4ddb591e99f28e3cd140b85f114b38b7f95b2e42542f0603eb7d7"},
|
||||
{file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e2a96fdc7643c9517a317553aca13b5cae9bad9a5f32f4654ce247ae4d321405"},
|
||||
{file = "coverage-7.11.3-cp312-cp312-win32.whl", hash = "sha256:e8feeb5e8705835f0622af0fe7ff8d5cb388948454647086494d6c41ec142c2e"},
|
||||
{file = "coverage-7.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:abb903ffe46bd319d99979cdba350ae7016759bb69f47882242f7b93f3356055"},
|
||||
{file = "coverage-7.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:1451464fd855d9bd000c19b71bb7dafea9ab815741fb0bd9e813d9b671462d6f"},
|
||||
{file = "coverage-7.11.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84b892e968164b7a0498ddc5746cdf4e985700b902128421bb5cec1080a6ee36"},
|
||||
{file = "coverage-7.11.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f761dbcf45e9416ec4698e1a7649248005f0064ce3523a47402d1bff4af2779e"},
|
||||
{file = "coverage-7.11.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1410bac9e98afd9623f53876fae7d8a5db9f5a0ac1c9e7c5188463cb4b3212e2"},
|
||||
{file = "coverage-7.11.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:004cdcea3457c0ea3233622cd3464c1e32ebba9b41578421097402bee6461b63"},
|
||||
{file = "coverage-7.11.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f067ada2c333609b52835ca4d4868645d3b63ac04fb2b9a658c55bba7f667d3"},
|
||||
{file = "coverage-7.11.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07bc7745c945a6d95676953e86ba7cebb9f11de7773951c387f4c07dc76d03f5"},
|
||||
{file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bba7e4743e37484ae17d5c3b8eb1ce78b564cb91b7ace2e2182b25f0f764cb5"},
|
||||
{file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbffc22d80d86fbe456af9abb17f7a7766e7b2101f7edaacc3535501691563f7"},
|
||||
{file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0dba4da36730e384669e05b765a2c49f39514dd3012fcc0398dd66fba8d746d5"},
|
||||
{file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ae12fe90b00b71a71b69f513773310782ce01d5f58d2ceb2b7c595ab9d222094"},
|
||||
{file = "coverage-7.11.3-cp313-cp313-win32.whl", hash = "sha256:12d821de7408292530b0d241468b698bce18dd12ecaf45316149f53877885f8c"},
|
||||
{file = "coverage-7.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:6bb599052a974bb6cedfa114f9778fedfad66854107cf81397ec87cb9b8fbcf2"},
|
||||
{file = "coverage-7.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:bb9d7efdb063903b3fdf77caec7b77c3066885068bdc0d44bc1b0c171033f944"},
|
||||
{file = "coverage-7.11.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:fb58da65e3339b3dbe266b607bb936efb983d86b00b03eb04c4ad5b442c58428"},
|
||||
{file = "coverage-7.11.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8d16bbe566e16a71d123cd66382c1315fcd520c7573652a8074a8fe281b38c6a"},
|
||||
{file = "coverage-7.11.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8258f10059b5ac837232c589a350a2df4a96406d6d5f2a09ec587cbdd539655"},
|
||||
{file = "coverage-7.11.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c5627429f7fbff4f4131cfdd6abd530734ef7761116811a707b88b7e205afd7"},
|
||||
{file = "coverage-7.11.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:465695268414e149bab754c54b0c45c8ceda73dd4a5c3ba255500da13984b16d"},
|
||||
{file = "coverage-7.11.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ebcddfcdfb4c614233cff6e9a3967a09484114a8b2e4f2c7a62dc83676ba13f"},
|
||||
{file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13b2066303a1c1833c654d2af0455bb009b6e1727b3883c9964bc5c2f643c1d0"},
|
||||
{file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d8750dd20362a1b80e3cf84f58013d4672f89663aee457ea59336df50fab6739"},
|
||||
{file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ab6212e62ea0e1006531a2234e209607f360d98d18d532c2fa8e403c1afbdd71"},
|
||||
{file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b17c2b5e0b9bb7702449200f93e2d04cb04b1414c41424c08aa1e5d352da76"},
|
||||
{file = "coverage-7.11.3-cp313-cp313t-win32.whl", hash = "sha256:426559f105f644b69290ea414e154a0d320c3ad8a2bb75e62884731f69cf8e2c"},
|
||||
{file = "coverage-7.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:90a96fcd824564eae6137ec2563bd061d49a32944858d4bdbae5c00fb10e76ac"},
|
||||
{file = "coverage-7.11.3-cp313-cp313t-win_arm64.whl", hash = "sha256:1e33d0bebf895c7a0905fcfaff2b07ab900885fc78bba2a12291a2cfbab014cc"},
|
||||
{file = "coverage-7.11.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fdc5255eb4815babcdf236fa1a806ccb546724c8a9b129fd1ea4a5448a0bf07c"},
|
||||
{file = "coverage-7.11.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fe3425dc6021f906c6325d3c415e048e7cdb955505a94f1eb774dafc779ba203"},
|
||||
{file = "coverage-7.11.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4ca5f876bf41b24378ee67c41d688155f0e54cdc720de8ef9ad6544005899240"},
|
||||
{file = "coverage-7.11.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9061a3e3c92b27fd8036dafa26f25d95695b6aa2e4514ab16a254f297e664f83"},
|
||||
{file = "coverage-7.11.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abcea3b5f0dc44e1d01c27090bc32ce6ffb7aa665f884f1890710454113ea902"},
|
||||
{file = "coverage-7.11.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:68c4eb92997dbaaf839ea13527be463178ac0ddd37a7ac636b8bc11a51af2428"},
|
||||
{file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:149eccc85d48c8f06547534068c41d69a1a35322deaa4d69ba1561e2e9127e75"},
|
||||
{file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:08c0bcf932e47795c49f0406054824b9d45671362dfc4269e0bc6e4bff010704"},
|
||||
{file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:39764c6167c82d68a2d8c97c33dba45ec0ad9172570860e12191416f4f8e6e1b"},
|
||||
{file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3224c7baf34e923ffc78cb45e793925539d640d42c96646db62dbd61bbcfa131"},
|
||||
{file = "coverage-7.11.3-cp314-cp314-win32.whl", hash = "sha256:c713c1c528284d636cd37723b0b4c35c11190da6f932794e145fc40f8210a14a"},
|
||||
{file = "coverage-7.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:c381a252317f63ca0179d2c7918e83b99a4ff3101e1b24849b999a00f9cd4f86"},
|
||||
{file = "coverage-7.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:3e33a968672be1394eded257ec10d4acbb9af2ae263ba05a99ff901bb863557e"},
|
||||
{file = "coverage-7.11.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f9c96a29c6d65bd36a91f5634fef800212dff69dacdb44345c4c9783943ab0df"},
|
||||
{file = "coverage-7.11.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2ec27a7a991d229213c8070d31e3ecf44d005d96a9edc30c78eaeafaa421c001"},
|
||||
{file = "coverage-7.11.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:72c8b494bd20ae1c58528b97c4a67d5cfeafcb3845c73542875ecd43924296de"},
|
||||
{file = "coverage-7.11.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:60ca149a446da255d56c2a7a813b51a80d9497a62250532598d249b3cdb1a926"},
|
||||
{file = "coverage-7.11.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb5069074db19a534de3859c43eec78e962d6d119f637c41c8e028c5ab3f59dd"},
|
||||
{file = "coverage-7.11.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac5d5329c9c942bbe6295f4251b135d860ed9f86acd912d418dce186de7c19ac"},
|
||||
{file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e22539b676fafba17f0a90ac725f029a309eb6e483f364c86dcadee060429d46"},
|
||||
{file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2376e8a9c889016f25472c452389e98bc6e54a19570b107e27cde9d47f387b64"},
|
||||
{file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4234914b8c67238a3c4af2bba648dc716aa029ca44d01f3d51536d44ac16854f"},
|
||||
{file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0b4101e2b3c6c352ff1f70b3a6fcc7c17c1ab1a91ccb7a33013cb0782af9820"},
|
||||
{file = "coverage-7.11.3-cp314-cp314t-win32.whl", hash = "sha256:305716afb19133762e8cf62745c46c4853ad6f9eeba54a593e373289e24ea237"},
|
||||
{file = "coverage-7.11.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9245bd392572b9f799261c4c9e7216bafc9405537d0f4ce3ad93afe081a12dc9"},
|
||||
{file = "coverage-7.11.3-cp314-cp314t-win_arm64.whl", hash = "sha256:9a1d577c20b4334e5e814c3d5fe07fa4a8c3ae42a601945e8d7940bab811d0bd"},
|
||||
{file = "coverage-7.11.3-py3-none-any.whl", hash = "sha256:351511ae28e2509c8d8cae5311577ea7dd511ab8e746ffc8814a0896c3d33fbe"},
|
||||
{file = "coverage-7.11.3.tar.gz", hash = "sha256:0f59387f5e6edbbffec2281affb71cdc85e0776c1745150a3ab9b6c1d016106b"},
|
||||
{file = "coverage-7.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:32b75c2ba3f324ee37af3ccee5b30458038c50b349ad9b88cee85096132a575b"},
|
||||
{file = "coverage-7.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cb2a1b6ab9fe833714a483a915de350abc624a37149649297624c8d57add089c"},
|
||||
{file = "coverage-7.12.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5734b5d913c3755e72f70bf6cc37a0518d4f4745cde760c5d8e12005e62f9832"},
|
||||
{file = "coverage-7.12.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b527a08cdf15753279b7afb2339a12073620b761d79b81cbe2cdebdb43d90daa"},
|
||||
{file = "coverage-7.12.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bb44c889fb68004e94cab71f6a021ec83eac9aeabdbb5a5a88821ec46e1da73"},
|
||||
{file = "coverage-7.12.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4b59b501455535e2e5dde5881739897967b272ba25988c89145c12d772810ccb"},
|
||||
{file = "coverage-7.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d8842f17095b9868a05837b7b1b73495293091bed870e099521ada176aa3e00e"},
|
||||
{file = "coverage-7.12.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c5a6f20bf48b8866095c6820641e7ffbe23f2ac84a2efc218d91235e404c7777"},
|
||||
{file = "coverage-7.12.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:5f3738279524e988d9da2893f307c2093815c623f8d05a8f79e3eff3a7a9e553"},
|
||||
{file = "coverage-7.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0d68c1f7eabbc8abe582d11fa393ea483caf4f44b0af86881174769f185c94d"},
|
||||
{file = "coverage-7.12.0-cp310-cp310-win32.whl", hash = "sha256:7670d860e18b1e3ee5930b17a7d55ae6287ec6e55d9799982aa103a2cc1fa2ef"},
|
||||
{file = "coverage-7.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:f999813dddeb2a56aab5841e687b68169da0d3f6fc78ccf50952fa2463746022"},
|
||||
{file = "coverage-7.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa124a3683d2af98bd9d9c2bfa7a5076ca7e5ab09fdb96b81fa7d89376ae928f"},
|
||||
{file = "coverage-7.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d93fbf446c31c0140208dcd07c5d882029832e8ed7891a39d6d44bd65f2316c3"},
|
||||
{file = "coverage-7.12.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:52ca620260bd8cd6027317bdd8b8ba929be1d741764ee765b42c4d79a408601e"},
|
||||
{file = "coverage-7.12.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f3433ffd541380f3a0e423cff0f4926d55b0cc8c1d160fdc3be24a4c03aa65f7"},
|
||||
{file = "coverage-7.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7bbb321d4adc9f65e402c677cd1c8e4c2d0105d3ce285b51b4d87f1d5db5245"},
|
||||
{file = "coverage-7.12.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22a7aade354a72dff3b59c577bfd18d6945c61f97393bc5fb7bd293a4237024b"},
|
||||
{file = "coverage-7.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3ff651dcd36d2fea66877cd4a82de478004c59b849945446acb5baf9379a1b64"},
|
||||
{file = "coverage-7.12.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:31b8b2e38391a56e3cea39d22a23faaa7c3fc911751756ef6d2621d2a9daf742"},
|
||||
{file = "coverage-7.12.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:297bc2da28440f5ae51c845a47c8175a4db0553a53827886e4fb25c66633000c"},
|
||||
{file = "coverage-7.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ff7651cc01a246908eac162a6a86fc0dbab6de1ad165dfb9a1e2ec660b44984"},
|
||||
{file = "coverage-7.12.0-cp311-cp311-win32.whl", hash = "sha256:313672140638b6ddb2c6455ddeda41c6a0b208298034544cfca138978c6baed6"},
|
||||
{file = "coverage-7.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1783ed5bd0d5938d4435014626568dc7f93e3cb99bc59188cc18857c47aa3c4"},
|
||||
{file = "coverage-7.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:4648158fd8dd9381b5847622df1c90ff314efbfc1df4550092ab6013c238a5fc"},
|
||||
{file = "coverage-7.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:29644c928772c78512b48e14156b81255000dcfd4817574ff69def189bcb3647"},
|
||||
{file = "coverage-7.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8638cbb002eaa5d7c8d04da667813ce1067080b9a91099801a0053086e52b736"},
|
||||
{file = "coverage-7.12.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:083631eeff5eb9992c923e14b810a179798bb598e6a0dd60586819fc23be6e60"},
|
||||
{file = "coverage-7.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:99d5415c73ca12d558e07776bd957c4222c687b9f1d26fa0e1b57e3598bdcde8"},
|
||||
{file = "coverage-7.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e949ebf60c717c3df63adb4a1a366c096c8d7fd8472608cd09359e1bd48ef59f"},
|
||||
{file = "coverage-7.12.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d907ddccbca819afa2cd014bc69983b146cca2735a0b1e6259b2a6c10be1e70"},
|
||||
{file = "coverage-7.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b1518ecbad4e6173f4c6e6c4a46e49555ea5679bf3feda5edb1b935c7c44e8a0"},
|
||||
{file = "coverage-7.12.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51777647a749abdf6f6fd8c7cffab12de68ab93aab15efc72fbbb83036c2a068"},
|
||||
{file = "coverage-7.12.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:42435d46d6461a3b305cdfcad7cdd3248787771f53fe18305548cba474e6523b"},
|
||||
{file = "coverage-7.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5bcead88c8423e1855e64b8057d0544e33e4080b95b240c2a355334bb7ced937"},
|
||||
{file = "coverage-7.12.0-cp312-cp312-win32.whl", hash = "sha256:dcbb630ab034e86d2a0f79aefd2be07e583202f41e037602d438c80044957baa"},
|
||||
{file = "coverage-7.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:2fd8354ed5d69775ac42986a691fbf68b4084278710cee9d7c3eaa0c28fa982a"},
|
||||
{file = "coverage-7.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:737c3814903be30695b2de20d22bcc5428fdae305c61ba44cdc8b3252984c49c"},
|
||||
{file = "coverage-7.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47324fffca8d8eae7e185b5bb20c14645f23350f870c1649003618ea91a78941"},
|
||||
{file = "coverage-7.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ccf3b2ede91decd2fb53ec73c1f949c3e034129d1e0b07798ff1d02ea0c8fa4a"},
|
||||
{file = "coverage-7.12.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b365adc70a6936c6b0582dc38746b33b2454148c02349345412c6e743efb646d"},
|
||||
{file = "coverage-7.12.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bc13baf85cd8a4cfcf4a35c7bc9d795837ad809775f782f697bf630b7e200211"},
|
||||
{file = "coverage-7.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:099d11698385d572ceafb3288a5b80fe1fc58bf665b3f9d362389de488361d3d"},
|
||||
{file = "coverage-7.12.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:473dc45d69694069adb7680c405fb1e81f60b2aff42c81e2f2c3feaf544d878c"},
|
||||
{file = "coverage-7.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:583f9adbefd278e9de33c33d6846aa8f5d164fa49b47144180a0e037f0688bb9"},
|
||||
{file = "coverage-7.12.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2089cc445f2dc0af6f801f0d1355c025b76c24481935303cf1af28f636688f0"},
|
||||
{file = "coverage-7.12.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:950411f1eb5d579999c5f66c62a40961f126fc71e5e14419f004471957b51508"},
|
||||
{file = "coverage-7.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b1aab7302a87bafebfe76b12af681b56ff446dc6f32ed178ff9c092ca776e6bc"},
|
||||
{file = "coverage-7.12.0-cp313-cp313-win32.whl", hash = "sha256:d7e0d0303c13b54db495eb636bc2465b2fb8475d4c8bcec8fe4b5ca454dfbae8"},
|
||||
{file = "coverage-7.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:ce61969812d6a98a981d147d9ac583a36ac7db7766f2e64a9d4d059c2fe29d07"},
|
||||
{file = "coverage-7.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:bcec6f47e4cb8a4c2dc91ce507f6eefc6a1b10f58df32cdc61dff65455031dfc"},
|
||||
{file = "coverage-7.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:459443346509476170d553035e4a3eed7b860f4fe5242f02de1010501956ce87"},
|
||||
{file = "coverage-7.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:04a79245ab2b7a61688958f7a855275997134bc84f4a03bc240cf64ff132abf6"},
|
||||
{file = "coverage-7.12.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:09a86acaaa8455f13d6a99221d9654df249b33937b4e212b4e5a822065f12aa7"},
|
||||
{file = "coverage-7.12.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:907e0df1b71ba77463687a74149c6122c3f6aac56c2510a5d906b2f368208560"},
|
||||
{file = "coverage-7.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b57e2d0ddd5f0582bae5437c04ee71c46cd908e7bc5d4d0391f9a41e812dd12"},
|
||||
{file = "coverage-7.12.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:58c1c6aa677f3a1411fe6fb28ec3a942e4f665df036a3608816e0847fad23296"},
|
||||
{file = "coverage-7.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4c589361263ab2953e3c4cd2a94db94c4ad4a8e572776ecfbad2389c626e4507"},
|
||||
{file = "coverage-7.12.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:91b810a163ccad2e43b1faa11d70d3cf4b6f3d83f9fd5f2df82a32d47b648e0d"},
|
||||
{file = "coverage-7.12.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:40c867af715f22592e0d0fb533a33a71ec9e0f73a6945f722a0c85c8c1cbe3a2"},
|
||||
{file = "coverage-7.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:68b0d0a2d84f333de875666259dadf28cc67858bc8fd8b3f1eae84d3c2bec455"},
|
||||
{file = "coverage-7.12.0-cp313-cp313t-win32.whl", hash = "sha256:73f9e7fbd51a221818fd11b7090eaa835a353ddd59c236c57b2199486b116c6d"},
|
||||
{file = "coverage-7.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:24cff9d1f5743f67db7ba46ff284018a6e9aeb649b67aa1e70c396aa1b7cb23c"},
|
||||
{file = "coverage-7.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c87395744f5c77c866d0f5a43d97cc39e17c7f1cb0115e54a2fe67ca75c5d14d"},
|
||||
{file = "coverage-7.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a1c59b7dc169809a88b21a936eccf71c3895a78f5592051b1af8f4d59c2b4f92"},
|
||||
{file = "coverage-7.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8787b0f982e020adb732b9f051f3e49dd5054cebbc3f3432061278512a2b1360"},
|
||||
{file = "coverage-7.12.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5ea5a9f7dc8877455b13dd1effd3202e0bca72f6f3ab09f9036b1bcf728f69ac"},
|
||||
{file = "coverage-7.12.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fdba9f15849534594f60b47c9a30bc70409b54947319a7c4fd0e8e3d8d2f355d"},
|
||||
{file = "coverage-7.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a00594770eb715854fb1c57e0dea08cce6720cfbc531accdb9850d7c7770396c"},
|
||||
{file = "coverage-7.12.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5560c7e0d82b42eb1951e4f68f071f8017c824ebfd5a6ebe42c60ac16c6c2434"},
|
||||
{file = "coverage-7.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d6c2e26b481c9159c2773a37947a9718cfdc58893029cdfb177531793e375cfc"},
|
||||
{file = "coverage-7.12.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6e1a8c066dabcde56d5d9fed6a66bc19a2883a3fe051f0c397a41fc42aedd4cc"},
|
||||
{file = "coverage-7.12.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f7ba9da4726e446d8dd8aae5a6cd872511184a5d861de80a86ef970b5dacce3e"},
|
||||
{file = "coverage-7.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e0f483ab4f749039894abaf80c2f9e7ed77bbf3c737517fb88c8e8e305896a17"},
|
||||
{file = "coverage-7.12.0-cp314-cp314-win32.whl", hash = "sha256:76336c19a9ef4a94b2f8dc79f8ac2da3f193f625bb5d6f51a328cd19bfc19933"},
|
||||
{file = "coverage-7.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:7c1059b600aec6ef090721f8f633f60ed70afaffe8ecab85b59df748f24b31fe"},
|
||||
{file = "coverage-7.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:172cf3a34bfef42611963e2b661302a8931f44df31629e5b1050567d6b90287d"},
|
||||
{file = "coverage-7.12.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:aa7d48520a32cb21c7a9b31f81799e8eaec7239db36c3b670be0fa2403828d1d"},
|
||||
{file = "coverage-7.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:90d58ac63bc85e0fb919f14d09d6caa63f35a5512a2205284b7816cafd21bb03"},
|
||||
{file = "coverage-7.12.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca8ecfa283764fdda3eae1bdb6afe58bf78c2c3ec2b2edcb05a671f0bba7b3f9"},
|
||||
{file = "coverage-7.12.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:874fe69a0785d96bd066059cd4368022cebbec1a8958f224f0016979183916e6"},
|
||||
{file = "coverage-7.12.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b3c889c0b8b283a24d721a9eabc8ccafcfc3aebf167e4cd0d0e23bf8ec4e339"},
|
||||
{file = "coverage-7.12.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8bb5b894b3ec09dcd6d3743229dc7f2c42ef7787dc40596ae04c0edda487371e"},
|
||||
{file = "coverage-7.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:79a44421cd5fba96aa57b5e3b5a4d3274c449d4c622e8f76882d76635501fd13"},
|
||||
{file = "coverage-7.12.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:33baadc0efd5c7294f436a632566ccc1f72c867f82833eb59820ee37dc811c6f"},
|
||||
{file = "coverage-7.12.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c406a71f544800ef7e9e0000af706b88465f3573ae8b8de37e5f96c59f689ad1"},
|
||||
{file = "coverage-7.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e71bba6a40883b00c6d571599b4627f50c360b3d0d02bfc658168936be74027b"},
|
||||
{file = "coverage-7.12.0-cp314-cp314t-win32.whl", hash = "sha256:9157a5e233c40ce6613dead4c131a006adfda70e557b6856b97aceed01b0e27a"},
|
||||
{file = "coverage-7.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e84da3a0fd233aeec797b981c51af1cabac74f9bd67be42458365b30d11b5291"},
|
||||
{file = "coverage-7.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:01d24af36fedda51c2b1aca56e4330a3710f83b02a5ff3743a6b015ffa7c9384"},
|
||||
{file = "coverage-7.12.0-py3-none-any.whl", hash = "sha256:159d50c0b12e060b15ed3d39f87ed43d4f7f7ad40b8a534f4dd331adbb51104a"},
|
||||
{file = "coverage-7.12.0.tar.gz", hash = "sha256:fc11e0a4e372cb5f282f16ef90d4a585034050ccda536451901abfb19a57f40c"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -978,14 +978,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
description = "Backport of PEP 654 (exception groups)"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"},
|
||||
{file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"},
|
||||
{file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"},
|
||||
{file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -1067,14 +1067,14 @@ pyflakes = ">=3.4.0,<3.5.0"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-bugbear"
|
||||
version = "25.10.21"
|
||||
version = "25.11.29"
|
||||
description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle."
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "flake8_bugbear-25.10.21-py3-none-any.whl", hash = "sha256:f1c5654f9d9d3e62e90da1f0335551fdbc565c51749713177dbcfb9edb105405"},
|
||||
{file = "flake8_bugbear-25.10.21.tar.gz", hash = "sha256:2876afcaed8bfb3464cf33e3ec42cc3bec0a004165b84400dc3392b0547c2714"},
|
||||
{file = "flake8_bugbear-25.11.29-py3-none-any.whl", hash = "sha256:9bf15e2970e736d2340da4c0a70493db964061c9c38f708cfe1f7b2d87392298"},
|
||||
{file = "flake8_bugbear-25.11.29.tar.gz", hash = "sha256:b5d06710f3d26e595541ad303ad4d5cb52578bd4bccbb2c2c0b2c72e243dafc8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1342,6 +1342,8 @@ files = [
|
||||
{file = "greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d"},
|
||||
{file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5"},
|
||||
{file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f"},
|
||||
{file = "greenlet-3.2.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f47617f698838ba98f4ff4189aef02e7343952df3a615f847bb575c3feb177a7"},
|
||||
{file = "greenlet-3.2.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af41be48a4f60429d5cad9d22175217805098a9ef7c40bfef44f7669fb9d74d8"},
|
||||
{file = "greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c"},
|
||||
{file = "greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2"},
|
||||
{file = "greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246"},
|
||||
@@ -1351,6 +1353,8 @@ files = [
|
||||
{file = "greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8"},
|
||||
{file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52"},
|
||||
{file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa"},
|
||||
{file = "greenlet-3.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9c6de1940a7d828635fbd254d69db79e54619f165ee7ce32fda763a9cb6a58c"},
|
||||
{file = "greenlet-3.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03c5136e7be905045160b1b9fdca93dd6727b180feeafda6818e6496434ed8c5"},
|
||||
{file = "greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9"},
|
||||
{file = "greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd"},
|
||||
{file = "greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb"},
|
||||
@@ -1360,6 +1364,8 @@ files = [
|
||||
{file = "greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0"},
|
||||
{file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0"},
|
||||
{file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f"},
|
||||
{file = "greenlet-3.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee7a6ec486883397d70eec05059353b8e83eca9168b9f3f9a361971e77e0bcd0"},
|
||||
{file = "greenlet-3.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:326d234cbf337c9c3def0676412eb7040a35a768efc92504b947b3e9cfc7543d"},
|
||||
{file = "greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02"},
|
||||
{file = "greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31"},
|
||||
{file = "greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945"},
|
||||
@@ -1369,6 +1375,8 @@ files = [
|
||||
{file = "greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671"},
|
||||
{file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b"},
|
||||
{file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae"},
|
||||
{file = "greenlet-3.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e343822feb58ac4d0a1211bd9399de2b3a04963ddeec21530fc426cc121f19b"},
|
||||
{file = "greenlet-3.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca7f6f1f2649b89ce02f6f229d7c19f680a6238af656f61e0115b24857917929"},
|
||||
{file = "greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b"},
|
||||
{file = "greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0"},
|
||||
{file = "greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f"},
|
||||
@@ -1376,6 +1384,8 @@ files = [
|
||||
{file = "greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1"},
|
||||
{file = "greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735"},
|
||||
{file = "greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337"},
|
||||
{file = "greenlet-3.2.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2917bdf657f5859fbf3386b12d68ede4cf1f04c90c3a6bc1f013dd68a22e2269"},
|
||||
{file = "greenlet-3.2.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:015d48959d4add5d6c9f6c5210ee3803a830dce46356e3bc326d6776bde54681"},
|
||||
{file = "greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01"},
|
||||
{file = "greenlet-3.2.4-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c"},
|
||||
{file = "greenlet-3.2.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d"},
|
||||
@@ -1385,6 +1395,8 @@ files = [
|
||||
{file = "greenlet-3.2.4-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df"},
|
||||
{file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594"},
|
||||
{file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98"},
|
||||
{file = "greenlet-3.2.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:28a3c6b7cd72a96f61b0e4b2a36f681025b60ae4779cc73c1535eb5f29560b10"},
|
||||
{file = "greenlet-3.2.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:52206cd642670b0b320a1fd1cbfd95bca0e043179c1d8a045f2c6109dfe973be"},
|
||||
{file = "greenlet-3.2.4-cp39-cp39-win32.whl", hash = "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b"},
|
||||
{file = "greenlet-3.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb"},
|
||||
{file = "greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d"},
|
||||
@@ -1494,24 +1506,24 @@ tests = ["freezegun", "pytest", "pytest-cov"]
|
||||
|
||||
[[package]]
|
||||
name = "hypothesis"
|
||||
version = "6.147.0"
|
||||
description = "A library for property-based testing"
|
||||
version = "6.148.7"
|
||||
description = "The property-based testing library for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "hypothesis-6.147.0-py3-none-any.whl", hash = "sha256:de588807b6da33550d32f47bcd42b1a86d061df85673aa73e6443680249d185e"},
|
||||
{file = "hypothesis-6.147.0.tar.gz", hash = "sha256:72e6004ea3bd1460bdb4640b6389df23b87ba7a4851893fd84d1375635d3e507"},
|
||||
{file = "hypothesis-6.148.7-py3-none-any.whl", hash = "sha256:94dbd58ebf259afa3bafb1d3bf5761ac1bde6f1477de494798cbf7960aabbdee"},
|
||||
{file = "hypothesis-6.148.7.tar.gz", hash = "sha256:b96e817e715c5b1a278411e3b9baf6d599d5b12207ba25e41a8f066929f6c2a6"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
sortedcontainers = ">=2.1.0,<3.0.0"
|
||||
|
||||
[package.extras]
|
||||
all = ["black (>=20.8b0)", "click (>=7.0)", "crosshair-tool (>=0.0.97)", "django (>=4.2)", "dpcontracts (>=0.4)", "hypothesis-crosshair (>=0.0.25)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.21.6)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2025.2) ; sys_platform == \"win32\" or sys_platform == \"emscripten\"", "watchdog (>=4.0.0)"]
|
||||
all = ["black (>=20.8b0)", "click (>=7.0)", "crosshair-tool (>=0.0.99)", "django (>=4.2)", "dpcontracts (>=0.4)", "hypothesis-crosshair (>=0.0.26)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.21.6)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2025.2) ; sys_platform == \"win32\" or sys_platform == \"emscripten\"", "watchdog (>=4.0.0)"]
|
||||
cli = ["black (>=20.8b0)", "click (>=7.0)", "rich (>=9.0.0)"]
|
||||
codemods = ["libcst (>=0.3.16)"]
|
||||
crosshair = ["crosshair-tool (>=0.0.97)", "hypothesis-crosshair (>=0.0.25)"]
|
||||
crosshair = ["crosshair-tool (>=0.0.99)", "hypothesis-crosshair (>=0.0.26)"]
|
||||
dateutil = ["python-dateutil (>=1.4)"]
|
||||
django = ["django (>=4.2)"]
|
||||
dpcontracts = ["dpcontracts (>=0.4)"]
|
||||
@@ -2132,14 +2144,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "moto"
|
||||
version = "5.1.16"
|
||||
version = "5.1.18"
|
||||
description = "A library that allows you to easily mock out tests based on AWS infrastructure"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "moto-5.1.16-py3-none-any.whl", hash = "sha256:8e6186f20b3aa91755d186e47701fe7e47f74e625c36fdf3bd7747da68468b19"},
|
||||
{file = "moto-5.1.16.tar.gz", hash = "sha256:792045b345d16a8aa09068ad4a7656894e707c796f0799b438fffb738e8fae7c"},
|
||||
{file = "moto-5.1.18-py3-none-any.whl", hash = "sha256:b65aa8fc9032c5c574415451e14fd7da4e43fd50b8bdcb5f10289ad382c25bcf"},
|
||||
{file = "moto-5.1.18.tar.gz", hash = "sha256:45298ef7b88561b839f6fe3e9da2a6e2ecd10283c7bf3daf43a07a97465885f9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2313,86 +2325,86 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.3.4"
|
||||
version = "2.3.5"
|
||||
description = "Fundamental package for array computing in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.11"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "numpy-2.3.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e78aecd2800b32e8347ce49316d3eaf04aed849cd5b38e0af39f829a4e59f5eb"},
|
||||
{file = "numpy-2.3.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7fd09cc5d65bda1e79432859c40978010622112e9194e581e3415a3eccc7f43f"},
|
||||
{file = "numpy-2.3.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1b219560ae2c1de48ead517d085bc2d05b9433f8e49d0955c82e8cd37bd7bf36"},
|
||||
{file = "numpy-2.3.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:bafa7d87d4c99752d07815ed7a2c0964f8ab311eb8168f41b910bd01d15b6032"},
|
||||
{file = "numpy-2.3.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36dc13af226aeab72b7abad501d370d606326a0029b9f435eacb3b8c94b8a8b7"},
|
||||
{file = "numpy-2.3.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7b2f9a18b5ff9824a6af80de4f37f4ec3c2aab05ef08f51c77a093f5b89adda"},
|
||||
{file = "numpy-2.3.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9984bd645a8db6ca15d850ff996856d8762c51a2239225288f08f9050ca240a0"},
|
||||
{file = "numpy-2.3.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:64c5825affc76942973a70acf438a8ab618dbd692b84cd5ec40a0a0509edc09a"},
|
||||
{file = "numpy-2.3.4-cp311-cp311-win32.whl", hash = "sha256:ed759bf7a70342f7817d88376eb7142fab9fef8320d6019ef87fae05a99874e1"},
|
||||
{file = "numpy-2.3.4-cp311-cp311-win_amd64.whl", hash = "sha256:faba246fb30ea2a526c2e9645f61612341de1a83fb1e0c5edf4ddda5a9c10996"},
|
||||
{file = "numpy-2.3.4-cp311-cp311-win_arm64.whl", hash = "sha256:4c01835e718bcebe80394fd0ac66c07cbb90147ebbdad3dcecd3f25de2ae7e2c"},
|
||||
{file = "numpy-2.3.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ef1b5a3e808bc40827b5fa2c8196151a4c5abe110e1726949d7abddfe5c7ae11"},
|
||||
{file = "numpy-2.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2f91f496a87235c6aaf6d3f3d89b17dba64996abadccb289f48456cff931ca9"},
|
||||
{file = "numpy-2.3.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f77e5b3d3da652b474cc80a14084927a5e86a5eccf54ca8ca5cbd697bf7f2667"},
|
||||
{file = "numpy-2.3.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:8ab1c5f5ee40d6e01cbe96de5863e39b215a4d24e7d007cad56c7184fdf4aeef"},
|
||||
{file = "numpy-2.3.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77b84453f3adcb994ddbd0d1c5d11db2d6bda1a2b7fd5ac5bd4649d6f5dc682e"},
|
||||
{file = "numpy-2.3.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4121c5beb58a7f9e6dfdee612cb24f4df5cd4db6e8261d7f4d7450a997a65d6a"},
|
||||
{file = "numpy-2.3.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:65611ecbb00ac9846efe04db15cbe6186f562f6bb7e5e05f077e53a599225d16"},
|
||||
{file = "numpy-2.3.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dabc42f9c6577bcc13001b8810d300fe814b4cfbe8a92c873f269484594f9786"},
|
||||
{file = "numpy-2.3.4-cp312-cp312-win32.whl", hash = "sha256:a49d797192a8d950ca59ee2d0337a4d804f713bb5c3c50e8db26d49666e351dc"},
|
||||
{file = "numpy-2.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:985f1e46358f06c2a09921e8921e2c98168ed4ae12ccd6e5e87a4f1857923f32"},
|
||||
{file = "numpy-2.3.4-cp312-cp312-win_arm64.whl", hash = "sha256:4635239814149e06e2cb9db3dd584b2fa64316c96f10656983b8026a82e6e4db"},
|
||||
{file = "numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c090d4860032b857d94144d1a9976b8e36709e40386db289aaf6672de2a81966"},
|
||||
{file = "numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a13fc473b6db0be619e45f11f9e81260f7302f8d180c49a22b6e6120022596b3"},
|
||||
{file = "numpy-2.3.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:3634093d0b428e6c32c3a69b78e554f0cd20ee420dcad5a9f3b2a63762ce4197"},
|
||||
{file = "numpy-2.3.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:043885b4f7e6e232d7df4f51ffdef8c36320ee9d5f227b380ea636722c7ed12e"},
|
||||
{file = "numpy-2.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ee6a571d1e4f0ea6d5f22d6e5fbd6ed1dc2b18542848e1e7301bd190500c9d7"},
|
||||
{file = "numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc8a63918b04b8571789688b2780ab2b4a33ab44bfe8ccea36d3eba51228c953"},
|
||||
{file = "numpy-2.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:40cc556d5abbc54aabe2b1ae287042d7bdb80c08edede19f0c0afb36ae586f37"},
|
||||
{file = "numpy-2.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ecb63014bb7f4ce653f8be7f1df8cbc6093a5a2811211770f6606cc92b5a78fd"},
|
||||
{file = "numpy-2.3.4-cp313-cp313-win32.whl", hash = "sha256:e8370eb6925bb8c1c4264fec52b0384b44f675f191df91cbe0140ec9f0955646"},
|
||||
{file = "numpy-2.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:56209416e81a7893036eea03abcb91c130643eb14233b2515c90dcac963fe99d"},
|
||||
{file = "numpy-2.3.4-cp313-cp313-win_arm64.whl", hash = "sha256:a700a4031bc0fd6936e78a752eefb79092cecad2599ea9c8039c548bc097f9bc"},
|
||||
{file = "numpy-2.3.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:86966db35c4040fdca64f0816a1c1dd8dbd027d90fca5a57e00e1ca4cd41b879"},
|
||||
{file = "numpy-2.3.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:838f045478638b26c375ee96ea89464d38428c69170360b23a1a50fa4baa3562"},
|
||||
{file = "numpy-2.3.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d7315ed1dab0286adca467377c8381cd748f3dc92235f22a7dfc42745644a96a"},
|
||||
{file = "numpy-2.3.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:84f01a4d18b2cc4ade1814a08e5f3c907b079c847051d720fad15ce37aa930b6"},
|
||||
{file = "numpy-2.3.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:817e719a868f0dacde4abdfc5c1910b301877970195db9ab6a5e2c4bd5b121f7"},
|
||||
{file = "numpy-2.3.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85e071da78d92a214212cacea81c6da557cab307f2c34b5f85b628e94803f9c0"},
|
||||
{file = "numpy-2.3.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2ec646892819370cf3558f518797f16597b4e4669894a2ba712caccc9da53f1f"},
|
||||
{file = "numpy-2.3.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:035796aaaddfe2f9664b9a9372f089cfc88bd795a67bd1bfe15e6e770934cf64"},
|
||||
{file = "numpy-2.3.4-cp313-cp313t-win32.whl", hash = "sha256:fea80f4f4cf83b54c3a051f2f727870ee51e22f0248d3114b8e755d160b38cfb"},
|
||||
{file = "numpy-2.3.4-cp313-cp313t-win_amd64.whl", hash = "sha256:15eea9f306b98e0be91eb344a94c0e630689ef302e10c2ce5f7e11905c704f9c"},
|
||||
{file = "numpy-2.3.4-cp313-cp313t-win_arm64.whl", hash = "sha256:b6c231c9c2fadbae4011ca5e7e83e12dc4a5072f1a1d85a0a7b3ed754d145a40"},
|
||||
{file = "numpy-2.3.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:81c3e6d8c97295a7360d367f9f8553973651b76907988bb6066376bc2252f24e"},
|
||||
{file = "numpy-2.3.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7c26b0b2bf58009ed1f38a641f3db4be8d960a417ca96d14e5b06df1506d41ff"},
|
||||
{file = "numpy-2.3.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:62b2198c438058a20b6704351b35a1d7db881812d8512d67a69c9de1f18ca05f"},
|
||||
{file = "numpy-2.3.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:9d729d60f8d53a7361707f4b68a9663c968882dd4f09e0d58c044c8bf5faee7b"},
|
||||
{file = "numpy-2.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd0c630cf256b0a7fd9d0a11c9413b42fef5101219ce6ed5a09624f5a65392c7"},
|
||||
{file = "numpy-2.3.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5e081bc082825f8b139f9e9fe42942cb4054524598aaeb177ff476cc76d09d2"},
|
||||
{file = "numpy-2.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15fb27364ed84114438fff8aaf998c9e19adbeba08c0b75409f8c452a8692c52"},
|
||||
{file = "numpy-2.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:85d9fb2d8cd998c84d13a79a09cc0c1091648e848e4e6249b0ccd7f6b487fa26"},
|
||||
{file = "numpy-2.3.4-cp314-cp314-win32.whl", hash = "sha256:e73d63fd04e3a9d6bc187f5455d81abfad05660b212c8804bf3b407e984cd2bc"},
|
||||
{file = "numpy-2.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:3da3491cee49cf16157e70f607c03a217ea6647b1cea4819c4f48e53d49139b9"},
|
||||
{file = "numpy-2.3.4-cp314-cp314-win_arm64.whl", hash = "sha256:6d9cd732068e8288dbe2717177320723ccec4fb064123f0caf9bbd90ab5be868"},
|
||||
{file = "numpy-2.3.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:22758999b256b595cf0b1d102b133bb61866ba5ceecf15f759623b64c020c9ec"},
|
||||
{file = "numpy-2.3.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9cb177bc55b010b19798dc5497d540dea67fd13a8d9e882b2dae71de0cf09eb3"},
|
||||
{file = "numpy-2.3.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0f2bcc76f1e05e5ab58893407c63d90b2029908fa41f9f1cc51eecce936c3365"},
|
||||
{file = "numpy-2.3.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dc20bde86802df2ed8397a08d793da0ad7a5fd4ea3ac85d757bf5dd4ad7c252"},
|
||||
{file = "numpy-2.3.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e199c087e2aa71c8f9ce1cb7a8e10677dc12457e7cc1be4798632da37c3e86e"},
|
||||
{file = "numpy-2.3.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85597b2d25ddf655495e2363fe044b0ae999b75bc4d630dc0d886484b03a5eb0"},
|
||||
{file = "numpy-2.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04a69abe45b49c5955923cf2c407843d1c85013b424ae8a560bba16c92fe44a0"},
|
||||
{file = "numpy-2.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e1708fac43ef8b419c975926ce1eaf793b0c13b7356cfab6ab0dc34c0a02ac0f"},
|
||||
{file = "numpy-2.3.4-cp314-cp314t-win32.whl", hash = "sha256:863e3b5f4d9915aaf1b8ec79ae560ad21f0b8d5e3adc31e73126491bb86dee1d"},
|
||||
{file = "numpy-2.3.4-cp314-cp314t-win_amd64.whl", hash = "sha256:962064de37b9aef801d33bc579690f8bfe6c5e70e29b61783f60bcba838a14d6"},
|
||||
{file = "numpy-2.3.4-cp314-cp314t-win_arm64.whl", hash = "sha256:8b5a9a39c45d852b62693d9b3f3e0fe052541f804296ff401a72a1b60edafb29"},
|
||||
{file = "numpy-2.3.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6e274603039f924c0fe5cb73438fa9246699c78a6df1bd3decef9ae592ae1c05"},
|
||||
{file = "numpy-2.3.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d149aee5c72176d9ddbc6803aef9c0f6d2ceeea7626574fc68518da5476fa346"},
|
||||
{file = "numpy-2.3.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:6d34ed9db9e6395bb6cd33286035f73a59b058169733a9db9f85e650b88df37e"},
|
||||
{file = "numpy-2.3.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:fdebe771ca06bb8d6abce84e51dca9f7921fe6ad34a0c914541b063e9a68928b"},
|
||||
{file = "numpy-2.3.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e92defe6c08211eb77902253b14fe5b480ebc5112bc741fd5e9cd0608f847"},
|
||||
{file = "numpy-2.3.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13b9062e4f5c7ee5c7e5be96f29ba71bc5a37fed3d1d77c37390ae00724d296d"},
|
||||
{file = "numpy-2.3.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:81b3a59793523e552c4a96109dde028aa4448ae06ccac5a76ff6532a85558a7f"},
|
||||
{file = "numpy-2.3.4.tar.gz", hash = "sha256:a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a"},
|
||||
{file = "numpy-2.3.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de5672f4a7b200c15a4127042170a694d4df43c992948f5e1af57f0174beed10"},
|
||||
{file = "numpy-2.3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:acfd89508504a19ed06ef963ad544ec6664518c863436306153e13e94605c218"},
|
||||
{file = "numpy-2.3.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ffe22d2b05504f786c867c8395de703937f934272eb67586817b46188b4ded6d"},
|
||||
{file = "numpy-2.3.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:872a5cf366aec6bb1147336480fef14c9164b154aeb6542327de4970282cd2f5"},
|
||||
{file = "numpy-2.3.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3095bdb8dd297e5920b010e96134ed91d852d81d490e787beca7e35ae1d89cf7"},
|
||||
{file = "numpy-2.3.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cba086a43d54ca804ce711b2a940b16e452807acebe7852ff327f1ecd49b0d4"},
|
||||
{file = "numpy-2.3.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6cf9b429b21df6b99f4dee7a1218b8b7ffbbe7df8764dc0bd60ce8a0708fed1e"},
|
||||
{file = "numpy-2.3.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:396084a36abdb603546b119d96528c2f6263921c50df3c8fd7cb28873a237748"},
|
||||
{file = "numpy-2.3.5-cp311-cp311-win32.whl", hash = "sha256:b0c7088a73aef3d687c4deef8452a3ac7c1be4e29ed8bf3b366c8111128ac60c"},
|
||||
{file = "numpy-2.3.5-cp311-cp311-win_amd64.whl", hash = "sha256:a414504bef8945eae5f2d7cb7be2d4af77c5d1cb5e20b296c2c25b61dff2900c"},
|
||||
{file = "numpy-2.3.5-cp311-cp311-win_arm64.whl", hash = "sha256:0cd00b7b36e35398fa2d16af7b907b65304ef8bb4817a550e06e5012929830fa"},
|
||||
{file = "numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e"},
|
||||
{file = "numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769"},
|
||||
{file = "numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5"},
|
||||
{file = "numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4"},
|
||||
{file = "numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d"},
|
||||
{file = "numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28"},
|
||||
{file = "numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b"},
|
||||
{file = "numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c"},
|
||||
{file = "numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952"},
|
||||
{file = "numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa"},
|
||||
{file = "numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013"},
|
||||
{file = "numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff"},
|
||||
{file = "numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188"},
|
||||
{file = "numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0"},
|
||||
{file = "numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903"},
|
||||
{file = "numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d"},
|
||||
{file = "numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017"},
|
||||
{file = "numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf"},
|
||||
{file = "numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce"},
|
||||
{file = "numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e"},
|
||||
{file = "numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b"},
|
||||
{file = "numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae"},
|
||||
{file = "numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd"},
|
||||
{file = "numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f"},
|
||||
{file = "numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a"},
|
||||
{file = "numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139"},
|
||||
{file = "numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e"},
|
||||
{file = "numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9"},
|
||||
{file = "numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946"},
|
||||
{file = "numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1"},
|
||||
{file = "numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3"},
|
||||
{file = "numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234"},
|
||||
{file = "numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7"},
|
||||
{file = "numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82"},
|
||||
{file = "numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0"},
|
||||
{file = "numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63"},
|
||||
{file = "numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9"},
|
||||
{file = "numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b"},
|
||||
{file = "numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520"},
|
||||
{file = "numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c"},
|
||||
{file = "numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8"},
|
||||
{file = "numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248"},
|
||||
{file = "numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e"},
|
||||
{file = "numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2"},
|
||||
{file = "numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41"},
|
||||
{file = "numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad"},
|
||||
{file = "numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39"},
|
||||
{file = "numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20"},
|
||||
{file = "numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52"},
|
||||
{file = "numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b"},
|
||||
{file = "numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3"},
|
||||
{file = "numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227"},
|
||||
{file = "numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5"},
|
||||
{file = "numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf"},
|
||||
{file = "numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42"},
|
||||
{file = "numpy-2.3.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f0963b55cdd70fad460fa4c1341f12f976bb26cb66021a5580329bd498988310"},
|
||||
{file = "numpy-2.3.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f4255143f5160d0de972d28c8f9665d882b5f61309d8362fdd3e103cf7bf010c"},
|
||||
{file = "numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:a4b9159734b326535f4dd01d947f919c6eefd2d9827466a696c44ced82dfbc18"},
|
||||
{file = "numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2feae0d2c91d46e59fcd62784a3a83b3fb677fead592ce51b5a6fbb4f95965ff"},
|
||||
{file = "numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffac52f28a7849ad7576293c0cb7b9f08304e8f7d738a8cb8a90ec4c55a998eb"},
|
||||
{file = "numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63c0e9e7eea69588479ebf4a8a270d5ac22763cc5854e9a7eae952a3908103f7"},
|
||||
{file = "numpy-2.3.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f16417ec91f12f814b10bafe79ef77e70113a2f5f7018640e7425ff979253425"},
|
||||
{file = "numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2490,14 +2502,14 @@ install = ["zstandard (>=0.21.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "phonenumbers"
|
||||
version = "9.0.18"
|
||||
version = "9.0.20"
|
||||
description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "phonenumbers-9.0.18-py2.py3-none-any.whl", hash = "sha256:d3354454ac31c97f8a08121df97a7145b8dca641f734c6f1518a41c2f60c5764"},
|
||||
{file = "phonenumbers-9.0.18.tar.gz", hash = "sha256:5537c61ba95b11b992c95e804da6e49193cc06b1224f632ade64631518a48ed1"},
|
||||
{file = "phonenumbers-9.0.20-py2.py3-none-any.whl", hash = "sha256:03bf5dd14891891284ba96f803d0e5e7e11b9306a0ec4fdf25756ada39eacb86"},
|
||||
{file = "phonenumbers-9.0.20.tar.gz", hash = "sha256:849788eec8e5a9737a99c8b906d18a62d9fced6497ba7033784b6a7e4c89bb2d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2529,32 +2541,34 @@ pip = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pip-audit"
|
||||
version = "2.9.0"
|
||||
version = "2.10.0"
|
||||
description = "A tool for scanning Python environments for known vulnerabilities"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
python-versions = ">=3.10"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "pip_audit-2.9.0-py3-none-any.whl", hash = "sha256:348b16e60895749a0839875d7cc27ebd692e1584ebe5d5cb145941c8e25a80bd"},
|
||||
{file = "pip_audit-2.9.0.tar.gz", hash = "sha256:0b998410b58339d7a231e5aa004326a294e4c7c6295289cdc9d5e1ef07b1f44d"},
|
||||
{file = "pip_audit-2.10.0-py3-none-any.whl", hash = "sha256:16e02093872fac97580303f0848fa3ad64f7ecf600736ea7835a2b24de49613f"},
|
||||
{file = "pip_audit-2.10.0.tar.gz", hash = "sha256:427ea5bf61d1d06b98b1ae29b7feacc00288a2eced52c9c58ceed5253ef6c2a4"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
CacheControl = {version = ">=0.13.0", extras = ["filecache"]}
|
||||
cyclonedx-python-lib = ">=5,<10"
|
||||
cyclonedx-python-lib = ">=5,<12"
|
||||
packaging = ">=23.0.0"
|
||||
pip-api = ">=0.0.28"
|
||||
pip-requirements-parser = ">=32.0.0"
|
||||
platformdirs = ">=4.2.0"
|
||||
requests = ">=2.31.0"
|
||||
rich = ">=12.4"
|
||||
toml = ">=0.10"
|
||||
tomli = ">=2.2.1"
|
||||
tomli-w = ">=1.2.0"
|
||||
|
||||
[package.extras]
|
||||
cov = ["coverage[toml] (>=7.0,!=7.3.3,<8.0)"]
|
||||
dev = ["build", "pip-audit[doc,lint,test]"]
|
||||
doc = ["pdoc"]
|
||||
lint = ["interrogate (>=1.6,<2.0)", "mypy", "ruff (>=0.9,<1.0)", "types-requests", "types-toml"]
|
||||
test = ["coverage[toml] (>=7.0,!=7.3.3,<8.0)", "pretend", "pytest", "pytest-cov"]
|
||||
lint = ["interrogate (>=1.6,<2.0)", "mypy", "ruff (>=0.11)", "types-requests", "types-toml"]
|
||||
test = ["pip-audit[cov]", "pretend", "pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "pip-requirements-parser"
|
||||
@@ -2711,14 +2725,14 @@ python-dotenv = ">=0.10.0"
|
||||
|
||||
[[package]]
|
||||
name = "pre-commit"
|
||||
version = "4.4.0"
|
||||
version = "4.5.0"
|
||||
description = "A framework for managing and maintaining multi-language pre-commit hooks."
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "pre_commit-4.4.0-py2.py3-none-any.whl", hash = "sha256:b35ea52957cbf83dcc5d8ee636cbead8624e3a15fbfa61a370e42158ac8a5813"},
|
||||
{file = "pre_commit-4.4.0.tar.gz", hash = "sha256:f0233ebab440e9f17cabbb558706eb173d19ace965c68cdce2c081042b4fab15"},
|
||||
{file = "pre_commit-4.5.0-py2.py3-none-any.whl", hash = "sha256:25e2ce09595174d9c97860a95609f9f852c0614ba602de3561e267547f2335e1"},
|
||||
{file = "pre_commit-4.5.0.tar.gz", hash = "sha256:dc5a065e932b19fc1d4c653c6939068fe54325af8e741e74e88db4d28a4dd66b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -3047,20 +3061,20 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.4.2"
|
||||
version = "9.0.1"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
python-versions = ">=3.10"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"},
|
||||
{file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"},
|
||||
{file = "pytest-9.0.1-py3-none-any.whl", hash = "sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad"},
|
||||
{file = "pytest-9.0.1.tar.gz", hash = "sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""}
|
||||
iniconfig = ">=1"
|
||||
packaging = ">=20"
|
||||
iniconfig = ">=1.0.1"
|
||||
packaging = ">=22"
|
||||
pluggy = ">=1.5,<2"
|
||||
pygments = ">=2.7.2"
|
||||
|
||||
@@ -3124,19 +3138,19 @@ dev = ["pre-commit", "pytest-asyncio", "tox"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-playwright"
|
||||
version = "0.7.1"
|
||||
version = "0.7.2"
|
||||
description = "A pytest wrapper with fixtures for Playwright to automate web browsers"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
python-versions = ">=3.10"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "pytest_playwright-0.7.1-py3-none-any.whl", hash = "sha256:fcc46510fb75f8eba6df3bc8e84e4e902483d92be98075f20b9d160651a36d90"},
|
||||
{file = "pytest_playwright-0.7.1.tar.gz", hash = "sha256:94b551b2677ecdc16284fcd6a4f0045eafda47a60e74410f3fe4d8260e12cabf"},
|
||||
{file = "pytest_playwright-0.7.2-py3-none-any.whl", hash = "sha256:8084e015b2b3ecff483c2160f1c8219b38b66c0d4578b23c0f700d1b0240ea38"},
|
||||
{file = "pytest_playwright-0.7.2.tar.gz", hash = "sha256:247b61123b28c7e8febb993a187a07e54f14a9aa04edc166f7a976d88f04c770"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
playwright = ">=1.18"
|
||||
pytest = ">=6.2.4,<9.0.0"
|
||||
pytest = ">=6.2.4,<10.0.0"
|
||||
pytest-base-url = ">=1.0.0,<3.0.0"
|
||||
python-slugify = ">=6.0.0,<9.0.0"
|
||||
|
||||
@@ -3461,14 +3475,14 @@ all = ["numpy"]
|
||||
|
||||
[[package]]
|
||||
name = "redis"
|
||||
version = "7.0.1"
|
||||
version = "7.1.0"
|
||||
description = "Python client for Redis database and key-value store"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
python-versions = ">=3.10"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "redis-7.0.1-py3-none-any.whl", hash = "sha256:4977af3c7d67f8f0eb8b6fec0dafc9605db9343142f634041fb0235f67c0588a"},
|
||||
{file = "redis-7.0.1.tar.gz", hash = "sha256:c949df947dca995dc68fdf5a7863950bf6df24f8d6022394585acc98e81624f1"},
|
||||
{file = "redis-7.1.0-py3-none-any.whl", hash = "sha256:23c52b208f92b56103e17c5d06bdc1a6c2c0b3106583985a76a18f83b265de2b"},
|
||||
{file = "redis-7.1.0.tar.gz", hash = "sha256:b1cc3cfa5a2cb9c2ab3ba700864fb0ad75617b41f01352ce5779dabf6d5f9c3c"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -3965,15 +3979,67 @@ files = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.10.2"
|
||||
description = "Python Library for Tom's Obvious, Minimal Language"
|
||||
name = "tomli"
|
||||
version = "2.3.0"
|
||||
description = "A lil' TOML parser"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
|
||||
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
||||
{file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"},
|
||||
{file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"},
|
||||
{file = "tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf"},
|
||||
{file = "tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441"},
|
||||
{file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845"},
|
||||
{file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c"},
|
||||
{file = "tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456"},
|
||||
{file = "tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be"},
|
||||
{file = "tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac"},
|
||||
{file = "tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22"},
|
||||
{file = "tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f"},
|
||||
{file = "tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52"},
|
||||
{file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8"},
|
||||
{file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6"},
|
||||
{file = "tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876"},
|
||||
{file = "tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878"},
|
||||
{file = "tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b"},
|
||||
{file = "tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae"},
|
||||
{file = "tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b"},
|
||||
{file = "tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf"},
|
||||
{file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f"},
|
||||
{file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05"},
|
||||
{file = "tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606"},
|
||||
{file = "tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999"},
|
||||
{file = "tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e"},
|
||||
{file = "tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3"},
|
||||
{file = "tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc"},
|
||||
{file = "tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0"},
|
||||
{file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879"},
|
||||
{file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005"},
|
||||
{file = "tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463"},
|
||||
{file = "tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8"},
|
||||
{file = "tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77"},
|
||||
{file = "tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf"},
|
||||
{file = "tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530"},
|
||||
{file = "tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b"},
|
||||
{file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67"},
|
||||
{file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f"},
|
||||
{file = "tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0"},
|
||||
{file = "tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba"},
|
||||
{file = "tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"},
|
||||
{file = "tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tomli-w"
|
||||
version = "1.2.0"
|
||||
description = "A lil' TOML writer"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90"},
|
||||
{file = "tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4014,21 +4080,21 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.5.0"
|
||||
version = "2.6.0"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main", "dev"]
|
||||
files = [
|
||||
{file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"},
|
||||
{file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"},
|
||||
{file = "urllib3-2.6.0-py3-none-any.whl", hash = "sha256:c90f7a39f716c572c4e3e58509581ebd83f9b59cced005b7db7ad2d22b0db99f"},
|
||||
{file = "urllib3-2.6.0.tar.gz", hash = "sha256:cb9bcef5a4b345d5da5d145dc3e30834f58e8018828cbc724d30b4cb7d4d49f1"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""]
|
||||
brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""]
|
||||
h2 = ["h2 (>=4,<5)"]
|
||||
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
zstd = ["zstandard (>=0.18.0)"]
|
||||
zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""]
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
@@ -4077,18 +4143,18 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "werkzeug"
|
||||
version = "3.1.3"
|
||||
version = "3.1.4"
|
||||
description = "The comprehensive WSGI web application library."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main", "dev"]
|
||||
files = [
|
||||
{file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"},
|
||||
{file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"},
|
||||
{file = "werkzeug-3.1.4-py3-none-any.whl", hash = "sha256:2ad50fb9ed09cc3af22c54698351027ace879a0b60a3b5edf5730b2f7d876905"},
|
||||
{file = "werkzeug-3.1.4.tar.gz", hash = "sha256:cd3cd98b1b92dc3b7b3995038826c68097dcb16f9baa63abe35f20eafeb9fe5e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = ">=2.1.1"
|
||||
markupsafe = ">=2.1.1"
|
||||
|
||||
[package.extras]
|
||||
watchdog = ["watchdog (>=2.3)"]
|
||||
@@ -4428,4 +4494,4 @@ cffi = ["cffi (>=1.17) ; python_version >= \"3.13\" and platform_python_implemen
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = "^3.13.2"
|
||||
content-hash = "1335fddbebfbc7063b8472df2456478073b2adff3a242f963cac1a65b7d86110"
|
||||
content-hash = "e4982f084acb029455079bb568d3eb91542d22508faadb37c763cf7d8c75d3c6"
|
||||
|
||||
@@ -10,17 +10,17 @@ package-mode = false
|
||||
axe-core-python = "^0.1.0"
|
||||
python = "^3.13.2"
|
||||
ago = "~=0.1.0"
|
||||
beautifulsoup4 = "^4.14.2"
|
||||
beautifulsoup4 = "^4.14.3"
|
||||
blinker = "~=1.8"
|
||||
gevent = "==25.9.1"
|
||||
exceptiongroup = "==1.3.0"
|
||||
exceptiongroup = "==1.3.1"
|
||||
flask = "~=3.1"
|
||||
flask-basicauth = "~=0.2"
|
||||
flask-login = "^0.6"
|
||||
flask-talisman = "*"
|
||||
flask-wtf = "^1.2"
|
||||
gunicorn = {version = "==23.0.0", extras = ["gevent"]}
|
||||
hypothesis = "^6.147.0"
|
||||
hypothesis = "^6.148.7"
|
||||
humanize = "~=4.14"
|
||||
itsdangerous = "~=2.2"
|
||||
jinja2 = "^3.1.6"
|
||||
@@ -34,7 +34,7 @@ openpyxl = "==3.0.10"
|
||||
pyproj = "==3.7.2"
|
||||
python-dotenv = "==1.2.1"
|
||||
rtreelib = "==0.2.0"
|
||||
werkzeug = "^3.1.3"
|
||||
werkzeug = "^3.1.4"
|
||||
wtforms = "~=3.1"
|
||||
markdown = "^3.10"
|
||||
mistune = "^3.1.4"
|
||||
@@ -42,33 +42,33 @@ async-timeout = "^5.0.1"
|
||||
bleach = "^6.3.0"
|
||||
boto3 = "^1.35.99"
|
||||
botocore = "^1.35.99"
|
||||
cachetools = "^6.2.1"
|
||||
cachetools = "^6.2.2"
|
||||
cffi = "^2.0.0"
|
||||
cryptography = "^46.0.3"
|
||||
flask-redis = "^0.4.0"
|
||||
geojson = "^3.1.0"
|
||||
jmespath = "^1.0.1"
|
||||
numpy = "^2.3.4"
|
||||
numpy = "^2.3.5"
|
||||
ordered-set = "^4.1.0"
|
||||
phonenumbers = "^9.0.18"
|
||||
phonenumbers = "^9.0.20"
|
||||
pycparser = "^2.23"
|
||||
pyjwt = "^2.10.1"
|
||||
python-json-logger = "^4.0.0"
|
||||
redis = "^7.0.1"
|
||||
redis = "^7.1.0"
|
||||
regex = "^2025.11.3"
|
||||
s3transfer = "^0.10.2"
|
||||
shapely = "^2.1.2"
|
||||
smartypants = "^2.0.1"
|
||||
certifi = "^2025.10.5"
|
||||
certifi = "^2025.11.12"
|
||||
charset-normalizer = "^3.4.4"
|
||||
click = "^8.3.0"
|
||||
click = "^8.3.1"
|
||||
idna = "^3.11"
|
||||
markupsafe = "^3.0.3"
|
||||
python-dateutil = "^2.9.0.post0"
|
||||
pyyaml = "^6.0.3"
|
||||
requests = "^2.32.5"
|
||||
six = "^1.16.0"
|
||||
urllib3 = "^2.5.0"
|
||||
urllib3 = "^2.6.0"
|
||||
webencodings = "^0.5.1"
|
||||
virtualenv = "<20.36"
|
||||
|
||||
@@ -81,18 +81,18 @@ coverage = "*"
|
||||
detect-secrets = "^1.5.0"
|
||||
freezegun = "^1.5.5"
|
||||
flake8 = "^7.3.0"
|
||||
flake8-bugbear = "^25.10.21"
|
||||
flake8-bugbear = "^25.11.29"
|
||||
flake8-print = "^5.0.0"
|
||||
flake8-pytest-style = "^2.2.0"
|
||||
isort = "^7.0.0"
|
||||
jinja2-cli = {version = "==0.8.2", extras = ["yaml"]}
|
||||
moto = "*"
|
||||
pip-audit = "*"
|
||||
pre-commit = "^4.4.0"
|
||||
pytest = "^8.4.2"
|
||||
pre-commit = "^4.5.0"
|
||||
pytest = "^9.0.1"
|
||||
pytest-env = "^1.2.0"
|
||||
pytest-mock = "^3.15.1"
|
||||
pytest-playwright = "^0.7.1"
|
||||
pytest-playwright = "^0.7.2"
|
||||
pytest-xdist = "^3.8.0"
|
||||
radon = "^6.0.1"
|
||||
requests-mock = "^1.11.0"
|
||||
|
||||
@@ -81,7 +81,7 @@ def test_cancel_invited_org_user_cancels_user_invitations(
|
||||
invited_user_id=sample_org_invite["id"],
|
||||
_follow_redirects=True,
|
||||
)
|
||||
assert normalize_spaces(page.h1.text) == "Team members"
|
||||
assert normalize_spaces(page.h1.text) == "Organization administrators"
|
||||
flash_banner = normalize_spaces(
|
||||
page.select_one(".usa-alert--success .usa-alert__text").text
|
||||
)
|
||||
|
||||
@@ -839,7 +839,7 @@ def test_edit_organization_user_shows_the_delete_confirmation_banner(
|
||||
user_id=active_user_with_permissions["id"],
|
||||
)
|
||||
|
||||
assert normalize_spaces(page.h1) == "Team members"
|
||||
assert normalize_spaces(page.h1) == "Organization administrators"
|
||||
|
||||
banner = page.select_one(".usa-alert--error")
|
||||
banner_text = banner.select_one(".usa-alert__text")
|
||||
@@ -1521,7 +1521,7 @@ def test_organization_billing_page_not_accessible_if_not_platform_admin(
|
||||
)
|
||||
|
||||
|
||||
def test_organization_dashboard_shows_message_usage(
|
||||
def test_organization_dashboard_shows_messages_sent(
|
||||
client_request,
|
||||
mock_get_organization,
|
||||
mocker,
|
||||
@@ -1531,8 +1531,6 @@ def test_organization_dashboard_shows_message_usage(
|
||||
"app.organizations_client.get_organization_message_usage",
|
||||
return_value={
|
||||
"messages_sent": 1000,
|
||||
"messages_remaining": 2000,
|
||||
"total_message_limit": 3000,
|
||||
},
|
||||
)
|
||||
mocker.patch(
|
||||
@@ -1553,11 +1551,7 @@ def test_organization_dashboard_shows_message_usage(
|
||||
mock_message_usage.assert_called_once_with(ORGANISATION_ID)
|
||||
|
||||
assert normalize_spaces(page.select_one("h1").text) == "Organization Dashboard"
|
||||
|
||||
chart_container = page.select_one("#totalMessageChartContainer")
|
||||
assert chart_container["data-messages-sent"] == "1000"
|
||||
assert chart_container["data-messages-remaining"] == "2000"
|
||||
assert chart_container["data-total-message-limit"] == "3000"
|
||||
assert "1,000 sent" in page.text
|
||||
|
||||
|
||||
def test_organization_dashboard_shows_service_counts(
|
||||
@@ -1622,7 +1616,7 @@ def test_organization_dashboard_shows_service_counts(
|
||||
assert "3" in normalize_spaces(service_box.text)
|
||||
assert "1 Live" in normalize_spaces(service_box.text)
|
||||
assert "1 Trial" in normalize_spaces(service_box.text)
|
||||
assert "1 Suspended" in normalize_spaces(service_box.text)
|
||||
assert "1 Archived" in normalize_spaces(service_box.text)
|
||||
|
||||
|
||||
def test_organization_dashboard_services_table(
|
||||
@@ -1699,7 +1693,7 @@ def test_organization_dashboard_services_table(
|
||||
assert normalize_spaces(first_row_cells[1].text) == "Live"
|
||||
assert "1,500 emails" in normalize_spaces(first_row_cells[2].text)
|
||||
assert "500 sms" in normalize_spaces(first_row_cells[2].text)
|
||||
assert "249,500 remaining" in normalize_spaces(first_row_cells[2].text)
|
||||
assert "249,500 message parts remaining" in normalize_spaces(first_row_cells[2].text)
|
||||
assert normalize_spaces(first_row_cells[3].text) == "N/A"
|
||||
assert normalize_spaces(first_row_cells[4].text) == "Welcome SMS"
|
||||
|
||||
@@ -1708,7 +1702,7 @@ def test_organization_dashboard_services_table(
|
||||
assert normalize_spaces(second_row_cells[1].text) == "Trial"
|
||||
assert "250 emails" in normalize_spaces(second_row_cells[2].text)
|
||||
assert "100 sms" in normalize_spaces(second_row_cells[2].text)
|
||||
assert "249,900 remaining" in normalize_spaces(second_row_cells[2].text)
|
||||
assert "249,900 message parts remaining" in normalize_spaces(second_row_cells[2].text)
|
||||
assert normalize_spaces(second_row_cells[3].text) == "N/A"
|
||||
assert normalize_spaces(second_row_cells[4].text) == "Reminder SMS"
|
||||
|
||||
|
||||
@@ -52,6 +52,9 @@ def test_all_activity(
|
||||
"app.job_api_client.get_page_of_jobs", return_value=MOCK_JOBS
|
||||
)
|
||||
mocker.patch("app.job_api_client.get_immediate_jobs", return_value=[])
|
||||
mock_s3_obj = mocker.Mock()
|
||||
mock_s3_obj.content_length = 0
|
||||
mocker.patch("app.s3_client.get_s3_object", return_value=mock_s3_obj)
|
||||
mocker.patch("app.s3_client.check_s3_file_exists", return_value=False)
|
||||
mocker.patch("app.s3_client.s3_csv_client.get_csv_upload", return_value=None)
|
||||
|
||||
@@ -140,6 +143,9 @@ def test_all_activity_no_jobs(client_request, mocker):
|
||||
},
|
||||
)
|
||||
mocker.patch("app.job_api_client.get_immediate_jobs", return_value=[])
|
||||
mock_s3_obj = mocker.Mock()
|
||||
mock_s3_obj.content_length = 0
|
||||
mocker.patch("app.s3_client.get_s3_object", return_value=mock_s3_obj)
|
||||
mocker.patch("app.s3_client.check_s3_file_exists", return_value=False)
|
||||
mocker.patch("app.s3_client.s3_csv_client.get_csv_upload", return_value=None)
|
||||
response = client_request.get_response(
|
||||
@@ -195,6 +201,9 @@ def test_all_activity_pagination(client_request, mocker):
|
||||
},
|
||||
)
|
||||
mocker.patch("app.job_api_client.get_immediate_jobs", return_value=[])
|
||||
mock_s3_obj = mocker.Mock()
|
||||
mock_s3_obj.content_length = 0
|
||||
mocker.patch("app.s3_client.get_s3_object", return_value=mock_s3_obj)
|
||||
mocker.patch("app.s3_client.check_s3_file_exists", return_value=False)
|
||||
mocker.patch("app.s3_client.s3_csv_client.get_csv_upload", return_value=None)
|
||||
|
||||
@@ -234,6 +243,9 @@ def test_all_activity_filters(client_request, mocker, filter_type, expected_limi
|
||||
"app.job_api_client.get_page_of_jobs", return_value=MOCK_JOBS
|
||||
)
|
||||
mocker.patch("app.job_api_client.get_immediate_jobs", return_value=[])
|
||||
mock_s3_obj = mocker.Mock()
|
||||
mock_s3_obj.content_length = 0
|
||||
mocker.patch("app.s3_client.get_s3_object", return_value=mock_s3_obj)
|
||||
mocker.patch("app.s3_client.check_s3_file_exists", return_value=False)
|
||||
mocker.patch("app.s3_client.s3_csv_client.get_csv_upload", return_value=None)
|
||||
|
||||
|
||||
223
tests/javascripts/organizationDashboard.test.js
Normal file
223
tests/javascripts/organizationDashboard.test.js
Normal file
@@ -0,0 +1,223 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
describe('organizationDashboard.js', () => {
|
||||
let originalLocation;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
jest.useFakeTimers();
|
||||
|
||||
originalLocation = window.location;
|
||||
Element.prototype.scrollIntoView = jest.fn();
|
||||
window.requestAnimationFrame = jest.fn((cb) => setTimeout(cb, 0));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
describe('Delete Service Confirmation', () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = `
|
||||
<button
|
||||
data-open-modal="confirmDeleteModal"
|
||||
data-service-id="service-123"
|
||||
data-service-name="Test Service">
|
||||
Delete
|
||||
</button>
|
||||
<div id="delete-service-name-display"></div>
|
||||
<button id="delete-service-confirm-btn">Confirm Delete</button>
|
||||
<form id="delete-service-form"></form>
|
||||
`;
|
||||
|
||||
require('../../app/assets/javascripts/organizationDashboard.js');
|
||||
document.dispatchEvent(new Event('DOMContentLoaded'));
|
||||
});
|
||||
|
||||
test('sets service name when delete button clicked', () => {
|
||||
const deleteButton = document.querySelector('[data-service-id="service-123"]');
|
||||
const nameDisplay = document.getElementById('delete-service-name-display');
|
||||
|
||||
deleteButton.click();
|
||||
|
||||
expect(nameDisplay.textContent).toBe('Test Service');
|
||||
});
|
||||
|
||||
test('does not update name display if missing', () => {
|
||||
document.getElementById('delete-service-name-display').remove();
|
||||
|
||||
const deleteButton = document.querySelector('[data-service-id="service-123"]');
|
||||
|
||||
expect(() => deleteButton.click()).not.toThrow();
|
||||
});
|
||||
|
||||
test('does nothing if required elements missing', () => {
|
||||
document.body.innerHTML = '<div>No delete elements</div>';
|
||||
|
||||
require('../../app/assets/javascripts/organizationDashboard.js');
|
||||
|
||||
expect(() => document.dispatchEvent(new Event('DOMContentLoaded'))).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edit Service Confirmation', () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = `
|
||||
<button id="edit-service-confirm-btn">Confirm Edit</button>
|
||||
<form id="edit-service-form"></form>
|
||||
`;
|
||||
|
||||
require('../../app/assets/javascripts/organizationDashboard.js');
|
||||
document.dispatchEvent(new Event('DOMContentLoaded'));
|
||||
});
|
||||
|
||||
test('submits edit form when confirm button clicked', () => {
|
||||
const confirmButton = document.getElementById('edit-service-confirm-btn');
|
||||
const editForm = document.getElementById('edit-service-form');
|
||||
editForm.submit = jest.fn();
|
||||
|
||||
confirmButton.click();
|
||||
|
||||
expect(editForm.submit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('does not throw if form missing when confirm clicked', () => {
|
||||
document.getElementById('edit-service-form').remove();
|
||||
const confirmButton = document.getElementById('edit-service-confirm-btn');
|
||||
|
||||
expect(() => confirmButton.click()).not.toThrow();
|
||||
});
|
||||
|
||||
test('does nothing if confirm button missing', () => {
|
||||
document.body.innerHTML = '<form id="edit-service-form"></form>';
|
||||
|
||||
require('../../app/assets/javascripts/organizationDashboard.js');
|
||||
|
||||
expect(() => document.dispatchEvent(new Event('DOMContentLoaded'))).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Form Initialization', () => {
|
||||
test('scrolls to and focuses create service form', () => {
|
||||
document.body.innerHTML = `
|
||||
<form id="create-service-form">
|
||||
<input type="text" name="service-name" />
|
||||
</form>
|
||||
`;
|
||||
|
||||
const form = document.getElementById('create-service-form');
|
||||
const input = form.querySelector('input[type="text"]');
|
||||
|
||||
require('../../app/assets/javascripts/organizationDashboard.js');
|
||||
document.dispatchEvent(new Event('DOMContentLoaded'));
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(form.scrollIntoView).toHaveBeenCalled();
|
||||
expect(document.activeElement).toBe(input);
|
||||
});
|
||||
|
||||
test('scrolls to and focuses invite user form', () => {
|
||||
document.body.innerHTML = `
|
||||
<form id="invite-user-form">
|
||||
<input type="email" name="email" />
|
||||
</form>
|
||||
`;
|
||||
|
||||
const form = document.getElementById('invite-user-form');
|
||||
const input = form.querySelector('input[type="email"]');
|
||||
|
||||
require('../../app/assets/javascripts/organizationDashboard.js');
|
||||
document.dispatchEvent(new Event('DOMContentLoaded'));
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(form.scrollIntoView).toHaveBeenCalled();
|
||||
expect(document.activeElement).toBe(input);
|
||||
});
|
||||
|
||||
test('scrolls to and focuses edit service form', () => {
|
||||
document.body.innerHTML = `
|
||||
<form id="edit-service-form">
|
||||
<input type="text" name="service-name" />
|
||||
</form>
|
||||
`;
|
||||
|
||||
const form = document.getElementById('edit-service-form');
|
||||
const input = form.querySelector('input[type="text"]');
|
||||
|
||||
require('../../app/assets/javascripts/organizationDashboard.js');
|
||||
document.dispatchEvent(new Event('DOMContentLoaded'));
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(form.scrollIntoView).toHaveBeenCalled();
|
||||
expect(document.activeElement).toBe(input);
|
||||
});
|
||||
|
||||
test('does nothing when no relevant forms present', () => {
|
||||
document.body.innerHTML = '<div>No forms here</div>';
|
||||
|
||||
require('../../app/assets/javascripts/organizationDashboard.js');
|
||||
document.dispatchEvent(new Event('DOMContentLoaded'));
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(Element.prototype.scrollIntoView).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Service Highlighting', () => {
|
||||
test('scrolls to and removes highlight from service', () => {
|
||||
document.body.innerHTML = `
|
||||
<div id="service-test-123" class="is-highlighted"></div>
|
||||
`;
|
||||
|
||||
const serviceRow = document.getElementById('service-test-123');
|
||||
|
||||
require('../../app/assets/javascripts/organizationDashboard.js');
|
||||
document.dispatchEvent(new Event('DOMContentLoaded'));
|
||||
|
||||
window.OrganizationDashboard.highlightAndScrollToService('test-123');
|
||||
|
||||
jest.advanceTimersByTime(300);
|
||||
expect(serviceRow.scrollIntoView).toHaveBeenCalled();
|
||||
|
||||
expect(serviceRow.classList.contains('is-highlighted')).toBe(true);
|
||||
|
||||
jest.advanceTimersByTime(3100);
|
||||
expect(serviceRow.classList.contains('is-highlighted')).toBe(false);
|
||||
});
|
||||
|
||||
test('removes class attribute when empty after highlight removal', () => {
|
||||
document.body.innerHTML = `
|
||||
<div id="service-test-123" class="is-highlighted"></div>
|
||||
`;
|
||||
|
||||
const serviceRow = document.getElementById('service-test-123');
|
||||
|
||||
require('../../app/assets/javascripts/organizationDashboard.js');
|
||||
document.dispatchEvent(new Event('DOMContentLoaded'));
|
||||
|
||||
window.OrganizationDashboard.highlightAndScrollToService('test-123');
|
||||
|
||||
jest.advanceTimersByTime(3400);
|
||||
expect(serviceRow.hasAttribute('class')).toBe(false);
|
||||
});
|
||||
|
||||
test('handles missing service element gracefully', () => {
|
||||
document.body.innerHTML = '<div>No service here</div>';
|
||||
|
||||
require('../../app/assets/javascripts/organizationDashboard.js');
|
||||
document.dispatchEvent(new Event('DOMContentLoaded'));
|
||||
|
||||
expect(() => {
|
||||
window.OrganizationDashboard.highlightAndScrollToService('nonexistent');
|
||||
jest.runAllTimers();
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user