diff --git a/app/assets/javascripts/activityChart.js b/app/assets/javascripts/activityChart.js index 08fee6cd4..d16d23827 100644 --- a/app/assets/javascripts/activityChart.js +++ b/app/assets/javascripts/activityChart.js @@ -26,6 +26,13 @@ .append('g') .attr('transform', `translate(${margin.left},${margin.top})`); + let tooltip = d3.select('#tooltip'); + + if (tooltip.empty()) { + tooltip = d3.select('body').append('div') + .attr('id', 'tooltip') + .style('display', 'none'); + } // Create legend const legendContainer = d3.select('.chart-legend'); legendContainer.selectAll('*').remove(); // Clear any existing legend @@ -95,10 +102,6 @@ const color = d3.scaleOrdinal() .domain(['delivered', 'failed']) .range([COLORS.delivered, COLORS.failed]); - // Create tooltip - const tooltip = d3.select('body').append('div') - .attr('id', 'tooltip') - .style('display', 'none'); // Create bars with animation const barGroups = svg.selectAll('.bar-group') @@ -185,41 +188,35 @@ return; } - var daily_stats = activityChartContainer.getAttribute('data-daily-stats'); - var daily_stats_by_user = activityChartContainer.getAttribute('data-daily_stats_by_user'); + 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 = []; - try { - // Choose the correct JSON string based on the type ('service' or 'user'), - // replace single quotes with double quotes to ensure valid JSON format, - // then parse the JSON string into a JavaScript object. - var statsJson = type === 'service' ? daily_stats : daily_stats_by_user; - statsJson = statsJson.replace(/'/g, '"'); - data = JSON.parse(statsJson); - } catch (error) { - console.error('Error parsing JSON data:', error); - return; - } - var labels = []; - var deliveredData = []; - var failedData = []; + for (var dateString in data) { + if (data.hasOwnProperty(dateString)) { + const dateParts = dateString.split('-'); + const formattedDate = `${dateParts[1]}/${dateParts[2]}/${dateParts[0].slice(2)}`; - 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); + } + } - labels.push(formattedDate); - deliveredData.push(data[dateString].sms.delivered); - failedData.push(data[dateString].sms.failure); - } - } - - try { - createChart('#weeklyChart', labels, deliveredData, failedData); - createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData); - } catch (error) { - console.error('Error creating chart or table:', error); - } + createChart('#weeklyChart', labels, deliveredData, failedData); + createTable('weeklyTable', 'activityChart', labels, deliveredData, failedData); + return data; + }) + .catch(error => console.error('Error fetching daily stats:', error)); }; const handleDropdownChange = function(event) { @@ -228,13 +225,8 @@ const selectElement = document.getElementById('options'); const selectedText = selectElement.options[selectElement.selectedIndex].text; - if (selectedValue === "individual") { - subTitle.textContent = selectedText + " - last 7 days"; - fetchData('individual'); - } else if (selectedValue === "service") { - subTitle.textContent = selectedText + " - last 7 days"; - fetchData('service'); - } + subTitle.textContent = `${selectedText} - last 7 days`; + fetchData(selectedValue); // Update ARIA live region const liveRegion = document.getElementById('aria-live-account'); @@ -263,8 +255,10 @@ // Resize chart on window resize window.addEventListener('resize', function() { - const selectedValue = document.getElementById('options').value; - handleDropdownChange({ target: { value: selectedValue } }); + 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 diff --git a/app/main/views/dashboard.py b/app/main/views/dashboard.py index d5d26f0a3..0913dcfdd 100644 --- a/app/main/views/dashboard.py +++ b/app/main/views/dashboard.py @@ -53,17 +53,12 @@ def service_dashboard(service_id): free_sms_allowance = billing_api_client.get_free_sms_fragment_limit_for_year( current_service.id, ) - date_range = get_stats_date_range() usage_data = get_annual_usage_breakdown(yearly_usage, free_sms_allowance) sms_sent = usage_data["sms_sent"] sms_allowance_remaining = usage_data["sms_allowance_remaining"] job_response = job_api_client.get_jobs(service_id)["data"] service_data_retention_days = 7 - daily_stats = get_daily_stats(service_id, date_range) - daily_stats_by_user = get_daily_stats_by_user( - service_id, current_user.id, date_range - ) jobs = [ { @@ -94,24 +89,32 @@ def service_dashboard(service_id): service_data_retention_days=service_data_retention_days, sms_sent=sms_sent, sms_allowance_remaining=sms_allowance_remaining, - daily_stats=daily_stats, - daily_stats_by_user=daily_stats_by_user, ) -def get_daily_stats(service_id, date_range): - return service_api_client.get_service_notification_statistics_by_day( +@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) -def get_daily_stats_by_user(service_id, user_id, date_range): - return service_api_client.get_user_service_notification_statistics_by_day( +@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 + stats = service_api_client.get_user_service_notification_statistics_by_day( service_id, user_id, start_date=date_range["start_date"], days=date_range["days"], ) + return jsonify(stats) @main.route("/services//dashboard.json") diff --git a/app/templates/views/dashboard/dashboard.html b/app/templates/views/dashboard/dashboard.html index fd7f2fdc8..2254c3c78 100644 --- a/app/templates/views/dashboard/dashboard.html +++ b/app/templates/views/dashboard/dashboard.html @@ -32,7 +32,7 @@

Activity snapshot

-
+
@@ -124,3 +124,26 @@ test('Check HTML content after chart creation', () => { expect(container.querySelector('svg')).not.toBeNull(); expect(container.querySelectorAll('rect').length).toBeGreaterThan(0); }); + +test('Fetches data and creates chart and table correctly', async () => { + const mockResponse = { + '2024-07-01': { sms: { delivered: 50, failed: 5 } }, + '2024-07-02': { sms: { delivered: 60, failed: 2 } }, + '2024-07-03': { sms: { delivered: 70, failed: 1 } }, + '2024-07-04': { sms: { delivered: 80, failed: 0 } }, + '2024-07-05': { sms: { delivered: 90, failed: 3 } }, + '2024-07-06': { sms: { delivered: 100, failed: 4 } }, + '2024-07-07': { sms: { delivered: 110, failed: 2 } }, + }; + global.fetch = jest.fn(() => + Promise.resolve({ + ok: true, + json: () => Promise.resolve(mockResponse), + }) +); + +const data = await fetchData('service'); + +expect(global.fetch).toHaveBeenCalledWith('/daily_stats.json'); +expect(data).toEqual(mockResponse); +});