diff --git a/Makefile b/Makefile index 9e0eeb46e..66ce809bb 100644 --- a/Makefile +++ b/Makefile @@ -62,6 +62,13 @@ py-lint: ## Run python linting scanners and black poetry run flake8 . poetry run isort --check-only ./app ./tests +.PHONY: tada +tada: ## Run python linting scanners and black + poetry run isort ./app ./tests + poetry run black . + poetry run flake8 . + + .PHONY: avg-complexity avg-complexity: echo "*** Shows average complexity in radon of all code ***" diff --git a/app/assets/javascripts/activityChart.js b/app/assets/javascripts/activityChart.js index 62c1e6e3e..efdda477f 100644 --- a/app/assets/javascripts/activityChart.js +++ b/app/assets/javascripts/activityChart.js @@ -1,10 +1,14 @@ (function (window) { if (document.getElementById('activityChartContainer')) { - + let currentType = 'service'; + const tableContainer = document.getElementById('activityContainer'); + const currentUserName = tableContainer.getAttribute('data-currentUserName'); + const currentServiceId = tableContainer.getAttribute('data-currentServiceId'); const COLORS = { delivered: '#0076d6', failed: '#fa9441', + pending: '#C7CACE', text: '#666' }; @@ -12,7 +16,7 @@ const FONT_WEIGHT = 'bold'; const MAX_Y = 120; - const createChart = function(containerId, labels, deliveredData, failedData) { + const createChart = function(containerId, labels, deliveredData, failedData, pendingData) { const container = d3.select(containerId); container.selectAll('*').remove(); // Clear any existing content @@ -35,7 +39,7 @@ } // Calculate total messages - const totalMessages = d3.sum(deliveredData) + d3.sum(failedData); + const totalMessages = d3.sum(deliveredData) + d3.sum(failedData) + d3.sum(pendingData); // Create legend only if there are messages const legendContainer = d3.select('.chart-legend'); @@ -45,7 +49,8 @@ // Show legend if there are messages const legendData = [ { label: 'Delivered', color: COLORS.delivered }, - { label: 'Failed', color: COLORS.failed } + { label: 'Failed', color: COLORS.failed }, + { label: 'Pending', color: COLORS.pending } ]; const legendItem = legendContainer.selectAll('.legend-item') @@ -76,8 +81,9 @@ .range([0, width]) .padding(0.1); // Adjust the y-axis domain to add some space above the tallest bar - const maxY = d3.max(deliveredData.map((d, i) => d + (failedData[i] || 0))); - const y = d3.scaleSqrt() + const maxY = d3.max(deliveredData.map((d, i) => d + (failedData[i] || 0) + (pendingData[i] || 0))); + + const y = d3.scaleSymlog() .domain([0, maxY + 2]) // Add 2 units of space at the top .nice() .range([height, 0]); @@ -89,7 +95,7 @@ // Generate the y-axis with whole numbers const yAxis = d3.axisLeft(y) - .ticks(Math.min(maxY + 2, 10)) // Generate up to 10 ticks based on the data + .ticks(Math.min(maxY + 2, 3)) .tickFormat(d3.format('d')); // Ensure whole numbers on the y-axis svg.append('g') @@ -100,12 +106,13 @@ const stackData = labels.map((label, i) => ({ label: label, delivered: deliveredData[i], - failed: failedData[i] || 0 // Ensure there's a value for failed, even if it's 0 + failed: failedData[i] || 0, + pending: pendingData[i] || 0 })); // Stack the data const stack = d3.stack() - .keys(['delivered', 'failed']) + .keys(['delivered', 'failed', 'pending']) .order(d3.stackOrderNone) .offset(d3.stackOffsetNone); @@ -113,8 +120,8 @@ // Color scale const color = d3.scaleOrdinal() - .domain(['delivered', 'failed']) - .range([COLORS.delivered, COLORS.failed]); + .domain(['delivered', 'failed', 'pending']) + .range([COLORS.delivered, COLORS.failed, COLORS.pending]); // Create bars with animation const barGroups = svg.selectAll('.bar-group') @@ -152,7 +159,7 @@ }; // Function to create an accessible table - const createTable = function(tableId, chartType, labels, deliveredData, failedData) { + const createTable = function(tableId, chartType, labels, deliveredData, failedData, pendingData) { const table = document.getElementById(tableId); table.innerHTML = ""; // Clear previous data @@ -164,7 +171,7 @@ // Create table header const headerRow = document.createElement('tr'); - const headers = ['Day', 'Delivered', 'Failed']; + const headers = ['Day', 'Delivered', 'Failed', 'Pending']; headers.forEach(headerText => { const th = document.createElement('th'); th.textContent = headerText; @@ -187,6 +194,10 @@ cellFailed.textContent = failedData[index]; row.appendChild(cellFailed); + const cellPending = document.createElement('td'); + cellPending.textContent = pendingData[index]; + row.appendChild(cellPending); + tbody.appendChild(row); }); @@ -196,12 +207,13 @@ }; const fetchData = function(type) { + var ctx = document.getElementById('weeklyChart'); if (!ctx) { return; } - var url = type === 'service' ? `/daily_stats.json` : `/daily_stats_by_user.json`; + var url = type === 'service' ? `/services/${currentServiceId}/daily-stats.json` : `/services/${currentServiceId}/daily-stats-by-user.json`; return fetch(url) .then(response => { if (!response.ok) { @@ -213,7 +225,7 @@ labels = []; deliveredData = []; failedData = []; - + pendingData = []; let totalMessages = 0; for (var dateString in data) { @@ -224,6 +236,8 @@ labels.push(formattedDate); deliveredData.push(data[dateString].sms.delivered); failedData.push(data[dateString].sms.failure); + pendingData.push(data[dateString].sms.pending || 0); + totalMessages += data[dateString].sms.delivered + data[dateString].sms.failure + data[dateString].sms.pending; // Calculate the total number of messages totalMessages += data[dateString].sms.delivered + data[dateString].sms.failure; @@ -252,17 +266,18 @@ } } else { // If there are messages, create the chart and table - createChart('#weeklyChart', labels, deliveredData, failedData); - createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData); - } - - return data; - }) - .catch(error => console.error('Error fetching daily stats:', error)); - }; + createChart('#weeklyChart', labels, deliveredData, failedData, pendingData); + createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData, pendingData); + } + return data; + }) + .catch(error => console.error('Error fetching daily stats:', error)); + }; + setInterval(() => fetchData(currentType), 25000); const handleDropdownChange = function(event) { const selectedValue = event.target.value; + currentType = selectedValue; const subTitle = document.querySelector(`#activityChartContainer .chart-subtitle`); const selectElement = document.getElementById('options'); const selectedText = selectElement.options[selectElement.selectedIndex].text; @@ -270,36 +285,67 @@ subTitle.textContent = `${selectedText} - last 7 days`; fetchData(selectedValue); - // Update ARIA live region const liveRegion = document.getElementById('aria-live-account'); liveRegion.textContent = `Data updated for ${selectedText} - last 7 days`; - // Switch tables based on dropdown selection - const selectedTable = selectedValue === "individual" ? "table1" : "table2"; - const tables = document.querySelectorAll('.table-overflow-x-auto'); - tables.forEach(function(table) { - table.classList.add('hidden'); // Hide all tables by adding the hidden class - table.classList.remove('visible'); // Ensure they are not visible - }); - const tableToShow = document.getElementById(selectedTable); - tableToShow.classList.remove('hidden'); // Remove hidden class - tableToShow.classList.add('visible'); // Add visible class + const tableHeading = document.querySelector('#tableActivity h2'); + const senderColumns = document.querySelectorAll('.sender-column'); + const allRows = document.querySelectorAll('#activity-table tbody tr'); + const caption = document.querySelector('#activity-table caption'); + + if (selectedValue === 'individual') { + + tableHeading.textContent = 'My activity'; + caption.textContent = `Table showing the sent jobs for ${currentUserName}`; + + senderColumns.forEach(col => { + col.style.display = 'none'; + }); + + allRows.forEach(row => row.style.display = 'none'); + + const userRows = Array.from(allRows).filter(row => { + const senderCell = row.querySelector('.sender-column'); + const rowSender = senderCell ? senderCell.textContent.trim() : ''; + return rowSender === currentUserName; + }); + + userRows.slice(0, 5).forEach(row => { + row.style.display = ''; + }); + } else { + + tableHeading.textContent = 'Service activity'; + caption.textContent = `Table showing the sent jobs for service`; + + senderColumns.forEach(col => { + col.style.display = ''; + }); + + allRows.forEach((row, index) => { + row.style.display = (index < 5) ? '' : 'none'; + }); + } }; document.addEventListener('DOMContentLoaded', function() { // Initialize activityChart chart and table with service data by default - fetchData('service'); + fetchData(currentType); + + const allRows = Array.from(document.querySelectorAll('#activity-table tbody tr')); + allRows.forEach((row, index) => { + row.style.display = (index < 5) ? '' : 'none'; + }); - // Add event listener to the dropdown const dropdown = document.getElementById('options'); dropdown.addEventListener('change', handleDropdownChange); }); // Resize chart on window resize window.addEventListener('resize', function() { - if (labels.length > 0 && deliveredData.length > 0 && failedData.length > 0) { - createChart('#weeklyChart', labels, deliveredData, failedData); - createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData); + if (labels.length > 0 && deliveredData.length > 0 && failedData.length > 0 && pendingData.length > 0) { + createChart('#weeklyChart', labels, deliveredData, failedData, pendingData); + createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData, pendingData); } }); diff --git a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss index 7fafb8276..0bb1aab7e 100644 --- a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss +++ b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss @@ -1024,7 +1024,3 @@ nav.nav { font-size: units(3); font-weight: bold; } - -.form-control-error { - border: 4px solid #b10e1e -} diff --git a/app/content/get-started.md b/app/content/get-started.md index 7d18aacbd..c97b9efc6 100644 --- a/app/content/get-started.md +++ b/app/content/get-started.md @@ -5,7 +5,7 @@ Explore Notify, add team members, and practice [sending messages to teammates](/using-notify/trial-mode). 2. ## Personalize content -Learn how to [personalize messages](/using-notify/guidance) to increase response. +Learn how to [personalize messages](/using-notify/how-to) to increase response. 3. ## Check delivery status [Analyze the delivery](/using-notify/delivery-status) of your messages and download reports diff --git a/app/main/views/dashboard.py b/app/main/views/dashboard.py index 8013acb9d..6b5edf6f4 100644 --- a/app/main/views/dashboard.py +++ b/app/main/views/dashboard.py @@ -14,7 +14,7 @@ from app import ( service_api_client, template_statistics_client, ) -from app.formatters import format_date_numeric, format_datetime_numeric, get_time_left +from app.formatters import format_date_numeric, format_datetime_numeric from app.main import main from app.main.views.user_profile import set_timezone from app.statistics_utils import get_formatted_percentage @@ -62,57 +62,38 @@ def service_dashboard(service_id): job_response = job_api_client.get_jobs(service_id)["data"] service_data_retention_days = 7 - jobs = [ - { - "job_id": job["id"], - "time_left": get_time_left(job["created_at"]), - "download_link": url_for( - ".view_job_csv", service_id=current_service.id, job_id=job["id"] - ), - "view_job_link": url_for( - ".view_job", service_id=current_service.id, job_id=job["id"] - ), - "created_at": job["created_at"], - "processing_finished": job.get("processing_finished"), - "processing_started": job.get("processing_started"), - "notification_count": job["notification_count"], - "created_by": job["created_by"], - "template_name": job["template_name"], - "original_file_name": job["original_file_name"], - } - for job in job_response - if job["job_status"] != "cancelled" - ] + filtered_jobs = [job for job in job_response if job["job_status"] != "cancelled"] + sorted_jobs = sorted(filtered_jobs, key=lambda job: job["created_at"], reverse=True) + return render_template( "views/dashboard/dashboard.html", updates_url=url_for(".service_dashboard_updates", service_id=service_id), partials=get_dashboard_partials(service_id), - jobs=jobs, + jobs=sorted_jobs, service_data_retention_days=service_data_retention_days, sms_sent=sms_sent, sms_allowance_remaining=sms_allowance_remaining, ) -@main.route("/daily_stats.json") -def get_daily_stats(): - service_id = session.get("service_id") +@main.route("/services//daily-stats.json") +@user_has_permissions() +def get_daily_stats(service_id): date_range = get_stats_date_range() - stats = service_api_client.get_service_notification_statistics_by_day( service_id, start_date=date_range["start_date"], days=date_range["days"] ) return jsonify(stats) -@main.route("/daily_stats_by_user.json") -def get_daily_stats_by_user(): +@main.route("/services//daily-stats-by-user.json") +@user_has_permissions() +def get_daily_stats_by_user(service_id): service_id = session.get("service_id") date_range = get_stats_date_range() - user_id = current_user.id stats = service_api_client.get_user_service_notification_statistics_by_day( service_id, - user_id, + user_id=current_user.id, start_date=date_range["start_date"], days=date_range["days"], ) diff --git a/app/main/views/index.py b/app/main/views/index.py index 8b63d2bd8..5c0312afc 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -217,11 +217,11 @@ def benchmark_performance(): ) -@main.route("/using-notify/guidance") +@main.route("/using-notify/how-to") @user_is_logged_in -def guidance_index(): +def how_to(): return render_template( - "views/guidance/index.html", + "views/how-to/index.html", navigation_links=using_notify_nav(), ) @@ -266,29 +266,29 @@ def join_notify(): ) -@main.route("/using-notify/guidance/create-and-send-messages") +@main.route("/using-notify/how-to/create-and-send-messages") @user_is_logged_in def create_and_send_messages(): return render_template( - "views/guidance/create-and-send-messages.html", + "views/how-to/create-and-send-messages.html", navigation_links=using_notify_nav(), ) -@main.route("/using-notify/guidance/edit-and-format-messages") +@main.route("/using-notify/how-to/edit-and-format-messages") @user_is_logged_in def edit_and_format_messages(): return render_template( - "views/guidance/edit-and-format-messages.html", + "views/how-to/edit-and-format-messages.html", navigation_links=using_notify_nav(), ) -@main.route("/using-notify/guidance/send-files-by-email") +@main.route("/using-notify/how-to/send-files-by-email") @user_is_logged_in def send_files_by_email(): return render_template( - "views/guidance/send-files-by-email.html", + "views/how-to/send-files-by-email.html", navigation_links=using_notify_nav(), ) diff --git a/app/main/views/jobs.py b/app/main/views/jobs.py index dddf838a1..bb95f630e 100644 --- a/app/main/views/jobs.py +++ b/app/main/views/jobs.py @@ -57,7 +57,6 @@ def view_job(service_id, job_id): filter_args = parse_filter_args(request.args) filter_args["status"] = set_status_filters(filter_args) - return render_template( "views/jobs/job.html", job=job, @@ -402,7 +401,7 @@ def get_job_partials(job): ) if request.referrer is not None: - session["arrived_from_preview_page"] = "check" in request.referrer + session["arrived_from_preview_page"] = ("check" in request.referrer) or ("help=0" in request.referrer) else: session["arrived_from_preview_page"] = False diff --git a/app/main/views/platform_admin.py b/app/main/views/platform_admin.py index 783ce01b6..bfd3749f3 100644 --- a/app/main/views/platform_admin.py +++ b/app/main/views/platform_admin.py @@ -162,7 +162,6 @@ def get_redis_report(): writer.writerow(["", "Max Memory", max_memory]) writer.writerow(["", "Memory Fragmentation Ratio", mem_fragmentation]) writer.writerow(["", "Memory Fragmentation Quality", frag_quality, frag_note]) - #writer.writerow(["", "Memory Fragmentation Note", frag_note]) writer.writerow([]) writer.writerow(["Keys Overview"]) diff --git a/app/main/views/sub_navigation_dictionaries.py b/app/main/views/sub_navigation_dictionaries.py index 3b2cf84c1..db086e511 100644 --- a/app/main/views/sub_navigation_dictionaries.py +++ b/app/main/views/sub_navigation_dictionaries.py @@ -2,7 +2,7 @@ def using_notify_nav(): nav_items = [ {"name": "Get started", "link": "main.get_started"}, { - "name": "Best Practices", + "name": "Best practices", "link": "main.best_practices", "sub_navigation_items": [ { @@ -33,8 +33,8 @@ def using_notify_nav(): }, {"name": "Trial mode", "link": "main.trial_mode_new"}, {"name": "Tracking usage", "link": "main.pricing"}, - {"name": "Delivery Status", "link": "main.message_status"}, - {"name": "Guidance", "link": "main.guidance_index"}, + {"name": "Delivery status", "link": "main.message_status"}, + {"name": "How to", "link": "main.how_to"}, ] return nav_items diff --git a/app/main/views/templates.py b/app/main/views/templates.py index 3a7315db7..5c59e1e7c 100644 --- a/app/main/views/templates.py +++ b/app/main/views/templates.py @@ -645,7 +645,6 @@ def edit_service_template(service_id, template_id): return render_template( "views/edit-{}-template.html".format(template["template_type"]), form=form, - errors=form.errors if form.errors else None, template=template, heading_action="Edit", ) diff --git a/app/navigation.py b/app/navigation.py index 424d03ae3..f20df4e5f 100644 --- a/app/navigation.py +++ b/app/navigation.py @@ -54,7 +54,7 @@ class HeaderNavigation(Navigation): "pricing", "trial_mode_new", "message_status", - "guidance_index", + "how_to", }, "accounts-or-dashboard": { "conversation", diff --git a/app/templates/components/components/input/template.njk b/app/templates/components/components/input/template.njk index 9e2cff08c..7f5634651 100644 --- a/app/templates/components/components/input/template.njk +++ b/app/templates/components/components/input/template.njk @@ -42,7 +42,5 @@ {%- if describedBy %} aria-describedby="{{ describedBy }}"{% endif %} {%- if params.autocomplete %} autocomplete="{{ params.autocomplete}}"{% endif %} {%- if params.pattern %} pattern="{{ params.pattern }}"{% endif %} - {%- for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor -%} - {%- if params.required %} required{% endif %} -/> + {%- for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor -%}> diff --git a/app/templates/components/form.html b/app/templates/components/form.html index 5875a19a9..17cabc7f0 100644 --- a/app/templates/components/form.html +++ b/app/templates/components/form.html @@ -19,6 +19,7 @@ data-{{ key }}="{{ val }}" {% endif %} {% endfor %} + novalidate > {{ caller() }} diff --git a/app/templates/components/textbox.html b/app/templates/components/textbox.html index e8b6f813d..3e479cbce 100644 --- a/app/templates/components/textbox.html +++ b/app/templates/components/textbox.html @@ -13,13 +13,22 @@ safe_error_message=False, rows=8, extra_form_group_classes='', - placeholder='', - required=None + placeholder='' ) %}
+ {% if field.errors %} + + {% endif %}
{% endif %} - {% if field.errors %} - - Error: - {% if not safe_error_message %}{{ field.errors[0] }}{% else %}{{ field.errors[0]|safe }}{% endif %} - - {% endif %} {% if highlight_placeholders or autosize %} @@ -56,8 +59,6 @@ data_highlight_placeholders='true' if highlight_placeholders else 'false', rows=rows|string, placeholder=placeholder, - aria_describedby=field.name+"-error", - required='required' if required else None, **kwargs ) }} {% if suffix %} diff --git a/app/templates/partials/jobs/status.html b/app/templates/partials/jobs/status.html index fb0a745f8..0f3288813 100644 --- a/app/templates/partials/jobs/status.html +++ b/app/templates/partials/jobs/status.html @@ -21,11 +21,11 @@

- {% if job.still_processing or arrived_from_preview_page_url %} + {% if not job.finished_processing %} {% if job.scheduled_for %}

-

Your text has been scheduled

+

Your message has been scheduled

{{ job.template_name }} - {{ current_service.name }} was scheduled on {{ job.scheduled_for|format_datetime_normal }} by {{ job.created_by.name }}

@@ -33,18 +33,46 @@
{{display_message_status}} {% else %} -
-
-

Your text has been sent

-

- {{ job.template_name }} - {{ current_service.name }} was sent on {% if job.processing_started %} - {{ job.processing_started|format_datetime_table }} {% else %} - {{ job.created_at|format_datetime_table }} {% endif %} by {{ job.created_by.name }} -

+ {% if job.processing_started %} +
+
+

+ Your message is sending +

+

+ {{ job.template_name }} - {{ current_service.name }} + has been sending since {{job.processing_started| format_datetime_normal}} by {{ job.created_by.name }} +

+
-
+ {% else %} +
+
+

+ Your message is pending +

+

+ {{ job.template_name }} - {{ current_service.name }} + has been pending since {{job.created_at|format_datetime_normal}} by {{ job.created_by.name }} +

+
+
+ {% endif %} {{display_message_status}} {% endif %} + {% elif arrived_from_preview_page_url %} +
+
+

+ Your message has been sent +

+

+ {{ job.template_name }} - {{ current_service.name }} + was sent on {{job.processing_started|format_datetime_normal}} by {{ job.created_by.name }} +

+
+
+ {{display_message_status}} {% endif %}

{% if job.status == 'sending limits exceeded'%} diff --git a/app/templates/views/dashboard/activity-table.html b/app/templates/views/dashboard/activity-table.html new file mode 100644 index 000000000..7a8bd7736 --- /dev/null +++ b/app/templates/views/dashboard/activity-table.html @@ -0,0 +1,72 @@ +

Recent activity

+
+
+ + +
+
+
+
{{ current_service.name }} - last 7 days
+
+
+
+
+
+
+
+
+
+

Service activity

+ + + + + + + + + + + + + + + {% if jobs %} + {% for job in jobs %} + + + + + + + + {% endfor %} + {% else %} + + + + {% endif %} + +
Table showing the sent jobs for {{current_service.name}}
Job ID#TemplateJob statusSender + # of Recipients
+ + {{ job.id[:8] if job.id else 'Manually entered number' }} + + {{ job.template_name }} + {% if job.scheduled_for and not job.processing_finished %} + Scheduled for {{ job.scheduled_for|format_datetime_table }} + {% elif job.processing_finished and not job.statistics|selectattr('status', 'equalto', 'sending')|list %} + Sent on {{ job.processing_finished|format_datetime_table }} + {% elif job.processing_started %} + Sending since {{ job.processing_started|format_datetime_table }} + {% else %} + Pending since {{ job.created_at|format_datetime_table }} + {% endif %} + {{ job.created_by.name }}{{ job.notification_count }}
No batched job messages found  (messages are + kept for {{ service_data_retention_days }} days).
+
+
diff --git a/app/templates/views/dashboard/dashboard.html b/app/templates/views/dashboard/dashboard.html index 04abe1afc..bd19585c3 100644 --- a/app/templates/views/dashboard/dashboard.html +++ b/app/templates/views/dashboard/dashboard.html @@ -6,140 +6,38 @@ {% from "components/ajax-block.html" import ajax_block %} {% block service_page_title %} - Dashboard + Dashboard {% endblock %} {% block maincolumn_content %} - + -
+
-

Dashboard

- {% if current_user.has_permissions('manage_templates') and not current_service.all_templates %} - {% include 'views/dashboard/write-first-messages.html' %} - {% endif %} +

Dashboard

+{% if current_user.has_permissions('manage_templates') and not current_service.all_templates %} + {% include 'views/dashboard/write-first-messages.html' %} +{% endif %} - {{ ajax_block(partials, updates_url, 'upcoming') }} +{{ ajax_block(partials, updates_url, 'upcoming') }} -

{{ current_service.name }} Dashboard

+

{{ current_service.name }} Dashboard

- {{ ajax_block(partials, updates_url, 'inbox') }} +{{ ajax_block(partials, updates_url, 'inbox') }} -
-

Total messages

- -
-
-
+
+

Total messages

+ +
+
+
-

Recent activity

-
-
- - -
-
-
-
{{ current_service.name }} - last 7 days
-
-
-
-
-
-
-
+{% include 'views/dashboard/activity-table.html' %} - {% if current_user.has_permissions('manage_service') %}{% endif %} +{% if current_user.has_permissions('manage_service') %}{% endif %} -
- - -
-

Service activity

- - - - - - - - - - - - - {% if jobs %} - {% for job in jobs[:5] %} - {% set notification = job.notifications[0] %} - - - - - - - - {% endfor %} - {% else %} - - - - {% endif %} - -
Table showing the sent jobs for this service
Job ID#TemplateJob statusSender# of Recipients
- - {{ job.job_id[:8] if job.job_id else 'Manually entered number' }} - - {{ job.template_name }}Sent on - {{ (job.processing_finished if job.processing_finished else job.processing_started - if job.processing_started else job.created_at)|format_datetime_table }} - {{ job.created_by.name }}{{ job.notification_count }}
No batched job messages found  (messages are kept for {{ service_data_retention_days }} days).
-
-
- {{ ajax_block(partials, updates_url, 'template-statistics') }} -
+{{ ajax_block(partials, updates_url, 'template-statistics') }} +
{% endblock %} diff --git a/app/templates/views/edit-sms-template.html b/app/templates/views/edit-sms-template.html index 728b92cd6..97eac73dc 100644 --- a/app/templates/views/edit-sms-template.html +++ b/app/templates/views/edit-sms-template.html @@ -29,11 +29,9 @@ {% call form_wrapper() %}
-