diff --git a/cmd/server/assets/realmadmin/_stats_codes.html b/cmd/server/assets/realmadmin/_stats_codes.html index bcedaf4a9..6fcc919f1 100644 --- a/cmd/server/assets/realmadmin/_stats_codes.html +++ b/cmd/server/assets/realmadmin/_stats_codes.html @@ -6,19 +6,31 @@ Codes issued, claimed, & invalid
-
+

Loading chart...

+
-
Issue to claim age distribution (7 day sum)
+
Issue-to-claim age distribution (7 day sum)

Loading chart...

+ +
+
Mean issue-to-claim age
+
+
+
+

Loading chart...

+
+
+
+ Learn more about this chart @@ -104,102 +116,198 @@ dataType: 'json', }) .done(function(data, status, xhr) { + if (data.statistics) { + $issueAgeSlider.attr('min', Math.min(smoothing, data.statistics.length)); + $issueAgeSlider.attr('max', data.statistics.length); + $issueAgeSlider.val(data.statistics.length); + } + drawClaimChart(data); + drawMeanClaimAgeChart(data); drawClaimAgeChart(data); }).fail(function(xhr, status, err) { flash.error('Failed to render realm stats: ' + err); }); } -function drawClaimChart(data) { - let $realmChartDiv = $('#realm_chart'); + function drawClaimChart(data) { + let $realmChartDiv = $('#realm_chart_div'); - if (!data.statistics) { - $realmChartDiv.find('p').text('No data yet.'); - return; - } + if (!data.statistics) { + $realmChartDiv.find('p').text('No data yet.'); + return; + } - let tenDaysAgo = new Date(data.statistics[data.statistics.length-10].date); + let tenDaysAgo = new Date(data.statistics[data.statistics.length-10].date); - let dataTable = new google.visualization.DataTable(); - dataTable.addColumn('date', 'Date'); - dataTable.addColumn('number', 'Issued'); - dataTable.addColumn('number', 'Claimed'); - dataTable.addColumn('number', 'Invalid'); + let dataTable = new google.visualization.DataTable(); + dataTable.addColumn('date', 'Date'); + dataTable.addColumn('number', 'Issued'); + dataTable.addColumn('number', 'Claimed'); + dataTable.addColumn('number', 'Invalid'); - data.statistics.reverse().forEach(function(row) { - dataTable.addRow([utcDate(row.date), row.data.codes_issued, row.data.codes_claimed, row.data.codes_invalid]); - }); + data.statistics.reverse().forEach(function(row) { + dataTable.addRow([utcDate(row.date), row.data.codes_issued, row.data.codes_claimed, row.data.codes_invalid]); + }); - let dateFormatter = new google.visualization.DateFormat({ - pattern: 'MMM dd', - }); - dateFormatter.format(dataTable, 0); + let dateFormatter = new google.visualization.DateFormat({ + pattern: 'MMM dd', + }); + dateFormatter.format(dataTable, 0); - let dashboard = new google.visualization.Dashboard(document.getElementById('dashboard_div')); + let dashboard = new google.visualization.Dashboard(document.getElementById('dashboard_div')); - let filter = new google.visualization.ControlWrapper({ - controlType: 'ChartRangeFilter', - containerId: 'filter_div', - state: { - range: { - start: tenDaysAgo, - }, - }, - options: { - filterColumnIndex: 0, - series: { - 0: { - opacity: 0, - } + let filter = new google.visualization.ControlWrapper({ + controlType: 'ChartRangeFilter', + containerId: 'filter_div', + state: { + range: { + start: tenDaysAgo, + }, }, - ui: { - chartType: 'LineChart', - chartOptions: { - colors: ['#dddddd'], - chartArea: { - width: '100%', - height: '100%', - top: 0, - right: 40, - bottom: 20, - left: 60, - }, - hAxis: { format: 'M/d' }, + options: { + filterColumnIndex: 0, + series: { + 0: { + opacity: 0, + } }, - chartView: { - columns: [0,1], + ui: { + chartType: 'LineChart', + chartOptions: { + colors: ['#dddddd'], + chartArea: { + width: '100%', + height: '100%', + top: 0, + right: 40, + bottom: 20, + left: 60, + }, + hAxis: { format: 'M/d' }, + }, + chartView: { + columns: [0,1], + }, }, }, + }); + + let realmChart = new google.visualization.ChartWrapper({ + chartType: 'LineChart', + containerId: 'realm_chart_div', + options: { + colors: ['#007bff', '#28a745', '#dc3545'], + chartArea: { + left: 60, + right: 40, + bottom: 5, + top: 40, + width: '100%', + height: '300', + }, + hAxis: { textPosition: 'none' }, + legend: { position: 'top' }, + width: '100%', }, }); - let realmChart = new google.visualization.ChartWrapper({ - chartType: 'LineChart', - containerId: 'chart_div', - options: { - colors: ['#007bff', '#28a745', '#dc3545'], - chartArea: { - left: 60, - right: 40, - bottom: 5, - top: 40, + dashboard.bind(filter, realmChart); + dashboard.draw(dataTable); + chartData.push({ + chart: dashboard, + data: dataTable, + }); + } + + function drawMeanClaimAgeChart(data) { + let $realmChartDiv = $('#mean_claim_age_chart_div'); + + if (!data.statistics) { + $realmChartDiv.find('p').text('No data yet.'); + return; + } + + let tenDaysAgo = new Date(data.statistics[data.statistics.length-10].date); + + let dataTable = new google.visualization.DataTable(); + dataTable.addColumn('date', 'Date'); + dataTable.addColumn('number', 'Mean issue-claim age'); + + data.statistics.reverse().forEach(function(row) { + dataTable.addRow([utcDate(row.date), row.data.code_claim_mean_age_seconds]); + }); + + let dateFormatter = new google.visualization.DateFormat({ + pattern: 'MMM dd', + }); + dateFormatter.format(dataTable, 0); + + let dashboard = new google.visualization.Dashboard(document.getElementById('mean_claim_age_chart_div')); + + let filter = new google.visualization.ControlWrapper({ + controlType: 'ChartRangeFilter', + containerId: 'mean_claim_age_filter_div', + state: { + range: { + start: tenDaysAgo, + }, + }, + options: { + filterColumnIndex: 0, + series: { + 0: { + opacity: 0, + } + }, + ui: { + chartType: 'LineChart', + chartOptions: { + colors: ['#dddddd'], + chartArea: { + width: '100%', + height: '100%', + top: 0, + right: 40, + bottom: 20, + left: 60, + }, + hAxis: { format: 'M/d' }, + }, + chartView: { + columns: [0,1], + }, + }, + }, + }); + + let realmChart = new google.visualization.ChartWrapper({ + chartType: 'LineChart', + containerId: 'mean_claim_age_chart_div', + options: { + colors: ['#007bff'], + chartArea: { + left: 60, + right: 40, + bottom: 5, + top: 40, + width: '100%', + height: '300', + }, + hAxis: { textPosition: 'none' }, + vAxis: { title: 'Seconds' }, + legend: { position: 'top' }, width: '100%', - height: '300', }, - hAxis: { textPosition: 'none' }, - legend: { position: 'top' }, - width: '100%', - }, - }); + }); - dashboard.bind(filter, realmChart); - dashboard.draw(dataTable); - chartData.push({ - chart: dashboard, - data: dataTable, - }); -} + dashboard.bind(filter, realmChart); + dashboard.draw(dataTable); + chartData.push({ + chart: dashboard, + data: dataTable, + }); + } let ageChart; let ageOptions; @@ -207,7 +315,7 @@ function drawClaimAgeChart(data) { let $div = $('#issue_age_dist_chart_div'); - if (!data.statistics || !data.statistics[0].code_claim_age_distribution) { + if (!data.statistics || !data.statistics[0].data.code_claim_age_distribution) { $div.find('p').text('No data yet.'); return; } @@ -227,7 +335,8 @@ hAxis: { title: 'Days from issue to claim', gridlines: { color: 'transparent' }, - ticks: [1,2,3,4,5,6,7,8,9,10], + ticks: [{v:1, f:'1m'}, {v:2, f:'5m'}, {v:3, f:'15m'}, {v:4, f:'30m'}, {v:5, f:'1h'}, + {v:6, f:'2h'}, {v:7, f:'3h'}, {v:8, f:'6h'}, {v:9, f:'24h'}], showTextEvery: 1, }, titlePosition: "out", @@ -252,10 +361,10 @@ dataTable.addColumn({type:'string', role:'annotation'}) // sum over last ${smoothing} days - let table = new Array(claimStats[dateIndex].code_claim_age_distribution.length).fill(0); + let table = new Array(claimStats[dateIndex].data.code_claim_age_distribution.length).fill(0); let i; for (i = dateIndex; i <= dateIndex + smoothing && i < claimStats.length; i++) { - let row = claimStats[i]; + let row = claimStats[i].data; let j; for (j = 0; j < row.code_claim_age_distribution.length; j++) { table[j] += row.code_claim_age_distribution[j]; @@ -264,7 +373,7 @@ for (i = 0; i < table.length; i++) { let r = [i+1, table[i],"",""] - if (i == 11) { + if (i == 9) { r[2] = "#6c757d"; r[3] = ">1 day"; } @@ -279,15 +388,15 @@ if (!claimStats) { return } - let indx = stats.length - $issueAgeSlider.val(); - onsetOptions.title = `${smoothing} days from ` + stats[indx].day.split("T")[0]; - onsetOptions.animation = { + let indx = claimStats.length - $issueAgeSlider.val(); + ageOptions.title = `${smoothing} days from ` + claimStats[indx].date.split("T")[0]; + ageOptions.animation = { startup: false, duration: 500, easing: 'inAndOut', }; - ageData = getClaimAgeDataTable(0); + ageData = getClaimAgeDataTable(indx); ageChart.draw(ageData, ageOptions); }); }); diff --git a/cmd/server/assets/realmadmin/_stats_keyserver.html b/cmd/server/assets/realmadmin/_stats_keyserver.html index b84d34c9f..958bec780 100644 --- a/cmd/server/assets/realmadmin/_stats_keyserver.html +++ b/cmd/server/assets/realmadmin/_stats_keyserver.html @@ -11,12 +11,14 @@
Publish requests

Loading chart...

+
Publish requests by OS

Loading chart...

+
TEK age distribution (7 day sum)
@@ -25,14 +27,16 @@
TEK age distribution (7 day sum)<
+
-
Onset to upload distribution (7 day sum)
+
Onset-to-upload distribution (7 day sum)

Loading chart...

+ Learn more about this chart @@ -96,10 +100,10 @@ .done(function(data, status, xhr) { $slider.attr('min', Math.min(smoothing, data.length)); $slider.attr('max', data.length); - $slider.attr('value',data.length); - $slider.attr('min', Math.min(smoothing, data.length)); + $slider.val(data.length); + $onsetSlider.attr('min', Math.min(smoothing, data.length)); $onsetSlider.attr('max', data.length); - $onsetSlider.attr('value',data.length); + $onsetSlider.val(data.length); drawStatsChart(data); drawOSChart(data); diff --git a/cmd/server/assets/realmadmin/stats.html b/cmd/server/assets/realmadmin/stats.html index c1f0dff1b..f5c8d3489 100644 --- a/cmd/server/assets/realmadmin/stats.html +++ b/cmd/server/assets/realmadmin/stats.html @@ -70,18 +70,24 @@

Realm stats

$(function() { let $smoothDrop = $('#smooth-drop'); - $smoothDrop.on("click", function(event) { + $smoothDrop.on("change", function(event) { event.preventDefault(); smoothing = $smoothDrop.val(); $('.sum-title').text(`(${smoothing} day sum)`) - let max = $slider.attr('max') + let max = $slider.attr('max'); $slider.attr('min', Math.min(smoothing, max)); - $onsetSlider.attr('min', Math.min(smoothing, max)); $slider.val(max); - $onsetSlider.val(max); $slider.trigger("input"); + + $onsetSlider.attr('min', Math.min(smoothing, max)); + $onsetSlider.val(max); $onsetSlider.trigger("input"); + + max = $issueAgeSlider.attr('max'); + $issueAgeSlider.attr('min', Math.min(smoothing, max)); + $issueAgeSlider.val(max); + $issueAgeSlider.trigger("input"); }); }); diff --git a/pkg/database/migrations.go b/pkg/database/migrations.go index 0ce7a8df3..84797f228 100644 --- a/pkg/database/migrations.go +++ b/pkg/database/migrations.go @@ -2087,8 +2087,7 @@ func (db *Database) Migrations(ctx context.Context) []*gormigrate.Migration { ID: "00093-AddClaimMeanAgeToRealmStats", Migrate: func(tx *gorm.DB) error { return multiExec(tx, - `ALTER TABLE realm_stats ADD COLUMN IF NOT EXISTS code_claim_mean_age BIGINT NOT NULL DEFAULT 0`, - `ALTER TABLE realm_stats DROP COLUMN IF EXISTS codes_claimed_age_distribution`) // result of previous mistake + `ALTER TABLE realm_stats ADD COLUMN IF NOT EXISTS code_claim_mean_age BIGINT NOT NULL DEFAULT 0`) }, Rollback: func(tx *gorm.DB) error { return multiExec(tx,