From 6d008d4e45414f7839528eab1369132a0f61a3c7 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Mon, 13 Jan 2025 15:54:04 -0800 Subject: [PATCH] clean up --- app/assets/javascripts/activityChart.js | 352 ++++++++++--------- app/main/views/dashboard.py | 49 +-- app/main/views/jobs.py | 16 +- app/notify_client/job_api_client.py | 2 +- app/templates/components/big-number.html | 1 - app/templates/components/pill.html | 1 - app/templates/partials/count.html | 1 - app/templates/partials/jobs/status.html | 2 +- app/templates/views/dashboard/dashboard.html | 1 + 9 files changed, 195 insertions(+), 230 deletions(-) diff --git a/app/assets/javascripts/activityChart.js b/app/assets/javascripts/activityChart.js index 438271fa0..820e47a16 100644 --- a/app/assets/javascripts/activityChart.js +++ b/app/assets/javascripts/activityChart.js @@ -13,7 +13,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 @@ -36,7 +36,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'); @@ -78,8 +78,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]); @@ -91,7 +92,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') @@ -102,7 +103,7 @@ 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 })); @@ -119,149 +120,150 @@ .domain(['delivered', 'failed', 'pending']) .range([COLORS.delivered, COLORS.failed, COLORS.pending]); - // Create bars with animation - const barGroups = svg.selectAll('.bar-group') - .data(series) - .enter() - .append('g') - .attr('class', 'bar-group') - .attr('fill', d => color(d.key)); + // Create bars with animation + const barGroups = svg.selectAll('.bar-group') + .data(series) + .enter() + .append('g') + .attr('class', 'bar-group') + .attr('fill', d => color(d.key)); - barGroups.selectAll('rect') - .data(d => d) - .enter() - .append('rect') - .attr('x', d => x(d.data.label)) - .attr('y', height) - .attr('height', 0) - .attr('width', x.bandwidth()) - .on('mouseover', function(event, d) { - const key = d3.select(this.parentNode).datum().key; - const capitalizedKey = key.charAt(0).toUpperCase() + key.slice(1); - tooltip.style('display', 'block') - .html(`${d.data.label}
${capitalizedKey}: ${d.data[key]}`); - }) - .on('mousemove', function(event) { - tooltip.style('left', `${event.pageX + 10}px`) - .style('top', `${event.pageY - 20}px`); - }) - .on('mouseout', function() { - tooltip.style('display', 'none'); - }) - .transition() - .duration(1000) - .attr('y', d => y(d[1])) - .attr('height', d => y(d[0]) - y(d[1])); - }; + barGroups.selectAll('rect') + .data(d => d) + .enter() + .append('rect') + .attr('x', d => x(d.data.label)) + .attr('y', height) + .attr('height', 0) + .attr('width', x.bandwidth()) + .on('mouseover', function(event, d) { + const key = d3.select(this.parentNode).datum().key; + const capitalizedKey = key.charAt(0).toUpperCase() + key.slice(1); + tooltip.style('display', 'block') + .html(`${d.data.label}
${capitalizedKey}: ${d.data[key]}`); + }) + .on('mousemove', function(event) { + tooltip.style('left', `${event.pageX + 10}px`) + .style('top', `${event.pageY - 20}px`); + }) + .on('mouseout', function() { + tooltip.style('display', 'none'); + }) + .transition() + .duration(1000) + .attr('y', d => y(d[1])) + .attr('height', d => y(d[0]) - y(d[1])); + }; - // Function to create an accessible table - const createTable = function(tableId, chartType, labels, deliveredData, failedData) { - const table = document.getElementById(tableId); - table.innerHTML = ""; // Clear previous data + // Function to create an accessible table + const createTable = function(tableId, chartType, labels, deliveredData, failedData, pendingData) { + const table = document.getElementById(tableId); + table.innerHTML = ""; // Clear previous data - const captionText = document.querySelector(`#${chartType} .chart-subtitle`).textContent; - const caption = document.createElement('caption'); - caption.textContent = captionText; - const thead = document.createElement('thead'); - const tbody = document.createElement('tbody'); + const captionText = document.querySelector(`#${chartType} .chart-subtitle`).textContent; + const caption = document.createElement('caption'); + caption.textContent = captionText; + const thead = document.createElement('thead'); + const tbody = document.createElement('tbody'); - // Create table header - const headerRow = document.createElement('tr'); - const headers = ['Day', 'Delivered', 'Failed', 'Pending']; - headers.forEach(headerText => { - const th = document.createElement('th'); - th.textContent = headerText; - headerRow.appendChild(th); - }); - thead.appendChild(headerRow); + // Create table header + const headerRow = document.createElement('tr'); + const headers = ['Day', 'Delivered', 'Failed', 'Pending']; + headers.forEach(headerText => { + const th = document.createElement('th'); + th.textContent = headerText; + headerRow.appendChild(th); + }); + thead.appendChild(headerRow); - // Create table body - labels.forEach((label, index) => { - const row = document.createElement('tr'); - const cellDay = document.createElement('td'); - cellDay.textContent = label; - row.appendChild(cellDay); + // Create table body + labels.forEach((label, index) => { + const row = document.createElement('tr'); + const cellDay = document.createElement('td'); + cellDay.textContent = label; + row.appendChild(cellDay); - const cellDelivered = document.createElement('td'); - cellDelivered.textContent = deliveredData[index]; - row.appendChild(cellDelivered); + const cellDelivered = document.createElement('td'); + cellDelivered.textContent = deliveredData[index]; + row.appendChild(cellDelivered); - const cellFailed = document.createElement('td'); - cellFailed.textContent = failedData[index]; - row.appendChild(cellFailed); + const cellFailed = document.createElement('td'); + cellFailed.textContent = failedData[index]; + row.appendChild(cellFailed); - const cellPending = document.createElement('td'); - cellPending.textContent = pendingData[index]; - row.appendChild(cellPending); + const cellPending = document.createElement('td'); + cellPending.textContent = pendingData[index]; + row.appendChild(cellPending); - tbody.appendChild(row); - }); + tbody.appendChild(row); + }); - table.appendChild(caption); - table.appendChild(thead); - table.append(tbody); - }; + table.appendChild(caption); + table.appendChild(thead); + table.append(tbody); + }; - const fetchData = function(type) { - var ctx = document.getElementById('weeklyChart'); - if (!ctx) { - return; - } + const fetchData = function(type) { + var ctx = document.getElementById('weeklyChart'); + if (!ctx) { + return; + } - var url = type === 'service' ? `/daily_stats.json` : `/daily_stats_by_user.json`; - return fetch(url) - .then(response => { - if (!response.ok) { - throw new Error('Network response was not ok'); + var url = type === 'service' ? `/daily_stats.json` : `/daily_stats_by_user.json`; + return fetch(url) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then(data => { + labels = []; + deliveredData = []; + failedData = []; + pendingData = []; + let totalMessages = 0; + + for (var dateString in data) { + if (data.hasOwnProperty(dateString)) { + const dateParts = dateString.split('-'); + const formattedDate = `${dateParts[1]}/${dateParts[2]}/${dateParts[0].slice(2)}`; + + labels.push(formattedDate); + deliveredData.push(data[dateString].sms.delivered); + failedData.push(data[dateString].sms.failure); + 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; } - return response.json(); - }) - .then(data => { - labels = []; - deliveredData = []; - failedData = []; - pendingData = []; + } - let totalMessages = 0; - - for (var dateString in data) { - if (data.hasOwnProperty(dateString)) { - const dateParts = dateString.split('-'); - const formattedDate = `${dateParts[1]}/${dateParts[2]}/${dateParts[0].slice(2)}`; - - labels.push(formattedDate); - deliveredData.push(data[dateString].sms.delivered); - failedData.push(data[dateString].sms.failure); - pendingData.push(data[dateString].sms.pending || 0); - // Calculate the total number of messages - totalMessages += data[dateString].sms.delivered + data[dateString].sms.failure; - } - } - - // Check if there are no messages sent - const subTitle = document.querySelector(`#activityChartContainer .chart-subtitle`); - if (totalMessages === 0) { - // Remove existing chart and render the alert message - d3.select('#weeklyChart').selectAll('*').remove(); - d3.select('#weeklyChart') - .append('div') - .html(` -
-
-

- No messages sent in the last 7 days -

-
+ // Check if there are no messages sent + const subTitle = document.querySelector(`#activityChartContainer .chart-subtitle`); + if (totalMessages === 0) { + // Remove existing chart and render the alert message + d3.select('#weeklyChart').selectAll('*').remove(); + d3.select('#weeklyChart') + .append('div') + .html(` +
+
+

+ No messages sent in the last 7 days +

- `); - // Hide the subtitle - if (subTitle) { - subTitle.style.display = 'none'; - } - } else { - // If there are messages, create the chart and table - createChart('#weeklyChart', labels, deliveredData, failedData); - createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData); +
+ `); + // Hide the subtitle + if (subTitle) { + subTitle.style.display = 'none'; + } + } else { + // If there are messages, create the chart and table + createChart('#weeklyChart', labels, deliveredData, failedData, pendingData); + createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData, pendingData); } return data; @@ -269,53 +271,53 @@ .catch(error => console.error('Error fetching daily stats:', error)); }; setInterval(() => fetchData('service'), 10000); - const handleDropdownChange = function(event) { - const selectedValue = event.target.value; - const subTitle = document.querySelector(`#activityChartContainer .chart-subtitle`); - const selectElement = document.getElementById('options'); - const selectedText = selectElement.options[selectElement.selectedIndex].text; + const handleDropdownChange = function(event) { + const selectedValue = event.target.value; + const subTitle = document.querySelector(`#activityChartContainer .chart-subtitle`); + const selectElement = document.getElementById('options'); + const selectedText = selectElement.options[selectElement.selectedIndex].text; - subTitle.textContent = `${selectedText} - last 7 days`; - fetchData(selectedValue); + 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`; + // 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 - }; + // 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 + }; - document.addEventListener('DOMContentLoaded', function() { - // Initialize activityChart chart and table with service data by default - fetchData('service'); + document.addEventListener('DOMContentLoaded', function() { + // Initialize activityChart chart and table with service data by default + fetchData('service'); - // Add event listener to the dropdown - const dropdown = document.getElementById('options'); - dropdown.addEventListener('change', handleDropdownChange); + // 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 && pendingData.length > 0) { + createChart('#weeklyChart', labels, deliveredData, failedData, pendingData); + createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData, pendingData); + } }); - // 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); - } - }); - - // Export functions for testing - window.createChart = createChart; - window.createTable = createTable; - window.handleDropdownChange = handleDropdownChange; - window.fetchData = fetchData; - } + // Export functions for testing + window.createChart = createChart; + window.createTable = createTable; + window.handleDropdownChange = handleDropdownChange; + window.fetchData = fetchData; + } })(window); diff --git a/app/main/views/dashboard.py b/app/main/views/dashboard.py index 8ae5ce0b8..8013acb9d 100644 --- a/app/main/views/dashboard.py +++ b/app/main/views/dashboard.py @@ -94,8 +94,19 @@ def service_dashboard(service_id): ) -@main.route("/services//daily_stats_by_user.json") -def get_daily_stats_by_user(service_id): +@main.route("/daily_stats.json") +def get_daily_stats(): + service_id = session.get("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(): service_id = session.get("service_id") date_range = get_stats_date_range() user_id = current_user.id @@ -349,35 +360,7 @@ def aggregate_notifications_stats(template_statistics): return notifications -def fetch_daily_stats(service_id): - date_range = get_stats_date_range() - stats = service_api_client.get_service_notification_statistics_by_day( - service_id, start_date='2025-01-09', days=date_range["days"] - ) - return stats - -# Route using the helper -@main.route("/daily_stats.json") -def get_daily_stats(): - service_id = session.get("service_id") - stats = fetch_daily_stats(service_id) # Use the helper - return jsonify(stats) - - def get_dashboard_partials(service_id): - date_range = get_stats_date_range() - yearly_usage = billing_api_client.get_annual_usage_for_service( - service_id, - get_current_financial_year(), - ) - free_sms_allowance = billing_api_client.get_free_sms_fragment_limit_for_year( - current_service.id, - ) - - usage_data = get_annual_usage_breakdown(yearly_usage, free_sms_allowance) - sms_sent = usage_data["sms_sent"] - sms_allowance_remaining = usage_data["sms_allowance_remaining"] - activityStats = fetch_daily_stats all_statistics = template_statistics_client.get_template_statistics_for_service( service_id, limit_days=7 ) @@ -401,12 +384,6 @@ def get_dashboard_partials(service_id): get_current_financial_year(), ) return { - "activity": render_template( - "views/dashboard/_activity.html", - activityStats=activityStats, - sms_sent=sms_sent, - sms_allowance_remaining=sms_allowance_remaining, - ), "upcoming": render_template( "views/dashboard/_upcoming.html", ), diff --git a/app/main/views/jobs.py b/app/main/views/jobs.py index 09462d0d6..dddf838a1 100644 --- a/app/main/views/jobs.py +++ b/app/main/views/jobs.py @@ -334,18 +334,6 @@ def get_status_filters(service, message_type, statistics): def _get_job_counts(job): - print(''' - - - - job - - - - - ''', dir(job)) - print(job.notification_count) - job_type = job.template_type return [ ( @@ -362,7 +350,7 @@ def _get_job_counts(job): for label, query_param, count in [ [ Markup( - f"""total notification_count + f"""total {"text message" if job_type == "sms" else job_type}s""" ), "", @@ -370,7 +358,7 @@ def _get_job_counts(job): ], [ Markup( - f"""pending notifications_sending + f"""pending {message_count_noun(job.notifications_sending, job_type)}""" ), "pending", diff --git a/app/notify_client/job_api_client.py b/app/notify_client/job_api_client.py index 2cf8f9b6d..9a06e16bf 100644 --- a/app/notify_client/job_api_client.py +++ b/app/notify_client/job_api_client.py @@ -28,7 +28,7 @@ class JobApiClient(NotifyAdminAPIClient): def get_job(self, service_id, job_id): params = {} job = self.get(url=f"/service/{service_id}/job/{job_id}", params=params) - # comment + return job def get_jobs(self, service_id, *, limit_days=None, statuses=None, page=1): diff --git a/app/templates/components/big-number.html b/app/templates/components/big-number.html index ff050d15c..fcb89b7e9 100644 --- a/app/templates/components/big-number.html +++ b/app/templates/components/big-number.html @@ -2,7 +2,6 @@ {% if link %} {% endif %} - big number {% if number is number %} diff --git a/app/templates/components/pill.html b/app/templates/components/pill.html index 8570d6fa9..bb1bb033a 100644 --- a/app/templates/components/pill.html +++ b/app/templates/components/pill.html @@ -8,7 +8,6 @@ ) %}
{% endif %} -

Delivery Stadsadatus

+

Delivery Status

{% endset %}
diff --git a/app/templates/views/dashboard/dashboard.html b/app/templates/views/dashboard/dashboard.html index 04abe1afc..ba236ce21 100644 --- a/app/templates/views/dashboard/dashboard.html +++ b/app/templates/views/dashboard/dashboard.html @@ -33,6 +33,7 @@
+

Recent activity