From f830489b3770ed5d327794786be9094a22a8504a Mon Sep 17 00:00:00 2001 From: Dmytro Bogatov <dmytro@dbogatov.org> Date: Tue, 8 Aug 2017 14:06:38 +0300 Subject: [PATCH] Rerender data tables on plot zoom. --- client/ts/metric.ts | 1 + client/ts/modules/constants.ts | 6 ++ client/ts/modules/metric-page/abstract.ts | 77 +++++++++++++++++--- client/ts/modules/metric-page/cpu-load.ts | 21 +++++- client/ts/modules/metric-page/health.ts | 35 ++++++--- client/ts/modules/metric-page/ping.ts | 21 +++++- client/ts/modules/metric-page/user-action.ts | 21 +++++- src/web/Controllers/View/HomeController.cs | 42 ++++++++++- src/web/Views/Home/Metric.cshtml | 4 + 9 files changed, 197 insertions(+), 31 deletions(-) diff --git a/client/ts/metric.ts b/client/ts/metric.ts index 11c8fa5..593929b 100644 --- a/client/ts/metric.ts +++ b/client/ts/metric.ts @@ -13,6 +13,7 @@ import "bootstrap" declare var source: string; declare var type: number; + declare var min: number; declare var max: number; diff --git a/client/ts/modules/constants.ts b/client/ts/modules/constants.ts index 3d5e165..67224ff 100644 --- a/client/ts/modules/constants.ts +++ b/client/ts/modules/constants.ts @@ -55,4 +55,10 @@ export class Constants { * of 5 minutes and sums of user actions will be displayed per interval. */ static USER_ACTIONS_AGGREGATION_INTERVAL : number = 30; + + /** + * The interval in milliseconds that defines a default time frame of data + * on metric pages. + */ + static METRIC_PAGE_DATA_PREVIEW : number = 2 * 60 * 60 * 1000; } diff --git a/client/ts/modules/metric-page/abstract.ts b/client/ts/modules/metric-page/abstract.ts index 0d60904..6ae2115 100644 --- a/client/ts/modules/metric-page/abstract.ts +++ b/client/ts/modules/metric-page/abstract.ts @@ -10,8 +10,12 @@ import "../../vendor/jquery.flot.time.js"; import "../../vendor/jquery.flot.selection.js"; import "../../vendor/jquery.flot.threshold.js"; import "../../vendor/jquery.flot.tooltip.js"; +import { Constants } from "../constants"; +declare var start: number; +declare var end: number; + /** * Represents set of procedures for rendering metric page. * @@ -23,6 +27,7 @@ export abstract class MetricPage<T extends Metric<DataPoint>> { protected metric: T; protected dataTablesRendered: boolean = false; + protected dataTable : DataTables.DataTable; /** * Minimal theoretical value for data series. @@ -50,10 +55,21 @@ export abstract class MetricPage<T extends Metric<DataPoint>> { protected minData: number; protected maxData: number; + protected start: Date = null; + protected end: Date = null; + constructor(min: number, max: number) { this.max = max; this.min = min; + if (start > 0) { + this.start = new Date(start); + } + + if (end > 0) { + this.end = new Date(end); + } + window.setTimeout(async () => { await this.metric.loadData(60 * 60 * 24 * 3); // 3 days @@ -84,7 +100,7 @@ export abstract class MetricPage<T extends Metric<DataPoint>> { * @memberOf MetricPage */ private renderPlot(): void { - + var plot: any = $.plot( $("#metric-detailed-plot"), this.formattedData, @@ -113,17 +129,46 @@ export abstract class MetricPage<T extends Metric<DataPoint>> { // don't fire event on the overview to prevent eternal loop overview.setSelection(ranges, true); + + this.renderTable(true, new Date(ranges.xaxis.from), new Date(ranges.xaxis.to)); })); $("#metric-overview-plot").bind("plotselected", <any>((event, ranges) => { plot.setSelection(ranges); })); - // if latest data point is more than 2 hours ago - // select recent 2 hours in plot - if (new Date().getTime() - this.minData > 2 * 60 * 60 * 1000) { - let from = new Date().getTime() - 2 * 60 * 60 * 1000; - plot.setSelection({ xaxis: { from: from, to: this.maxData }, yaxis: { from: 0, to: 0 } }); + if (this.start != null) { + + let from = Math.min( + Math.max( + this.start.getTime(), + this.minData + ), + this.maxData) + ; + + let to = + this.end == null ? + Math.min( + this.maxData, + this.start.getTime() + Constants.METRIC_PAGE_DATA_PREVIEW + ) : + Math.max( + Math.min( + this.end.getTime(), + this.maxData + ), + this.minData) + ; + + plot.setSelection({ xaxis: { from: from, to: to }, yaxis: { from: 0, to: 0 } }); + } else { + // if latest data point is more than 2 hours ago + // select recent 2 hours in plot + if (new Date().getTime() - this.minData > Constants.METRIC_PAGE_DATA_PREVIEW) { + let from = new Date().getTime() - Constants.METRIC_PAGE_DATA_PREVIEW; + plot.setSelection({ xaxis: { from: from, to: this.maxData }, yaxis: { from: 0, to: 0 } }); + } } } @@ -135,7 +180,19 @@ export abstract class MetricPage<T extends Metric<DataPoint>> { * * @memberOf MetricPage */ - protected abstract renderTable(): void; + + /** + * Renders data tables in the UI. + * Does not load the data. + * + * @protected + * @abstract + * @param {boolean} redraw if data table has to be force-redrawn; implies non-null values of start and end; + * @param {Date} start start date for filter + * @param {Date} end end date for filter + * @memberof MetricPage + */ + protected abstract renderTable(redraw: boolean, start: Date, end: Date): void; /** * Renders numeric values in the UI. @@ -160,10 +217,10 @@ export abstract class MetricPage<T extends Metric<DataPoint>> { */ public render(): void { - this.configurePlot() + this.configurePlot() this.renderPlot(); - + this.renderValues(); - this.renderTable(); + this.renderTable(false, null, null); } } diff --git a/client/ts/modules/metric-page/cpu-load.ts b/client/ts/modules/metric-page/cpu-load.ts index 3b21f22..5dea416 100644 --- a/client/ts/modules/metric-page/cpu-load.ts +++ b/client/ts/modules/metric-page/cpu-load.ts @@ -106,9 +106,13 @@ export class CpuLoadMetricPage extends MetricPage<Metric<CpuLoadDataPoint>> { }; }; - protected renderTable(): void { + protected renderTable(redraw: boolean, start: Date, end: Date): void { - if (!this.dataTablesRendered) { + if (!this.dataTablesRendered || redraw) { + + if (this.dataTablesRendered) { + this.dataTable.destroy(); + } let header = ` <tr> @@ -124,6 +128,17 @@ export class CpuLoadMetricPage extends MetricPage<Metric<CpuLoadDataPoint>> { this.metric .data .map(dp => <CpuLoadDataPoint>dp) + .filter((value, index, array) => { + if (start != null && value.timestamp < start) { + return false; + } + + if (end != null && value.timestamp > end) { + return false; + } + + return true; + }) .map( dp => ` <tr> @@ -135,7 +150,7 @@ export class CpuLoadMetricPage extends MetricPage<Metric<CpuLoadDataPoint>> { .join() ); - $('#metric-data').DataTable({ + this.dataTable = $('#metric-data').DataTable({ "order": [[0, "desc"]], lengthChange: false, searching: false, diff --git a/client/ts/modules/metric-page/health.ts b/client/ts/modules/metric-page/health.ts index 0756f9d..cb2924d 100644 --- a/client/ts/modules/metric-page/health.ts +++ b/client/ts/modules/metric-page/health.ts @@ -111,9 +111,13 @@ export class HealthMetricPage extends MetricPage<Metric<HealthDataPoint>> { }; }; - protected renderTable(): void { + protected renderTable(redraw: boolean, start: Date, end: Date): void { - if (!this.dataTablesRendered) { + if (!this.dataTablesRendered || redraw) { + + if (this.dataTablesRendered) { + this.dataTable.destroy(); + } let header = ` <tr> @@ -130,6 +134,17 @@ export class HealthMetricPage extends MetricPage<Metric<HealthDataPoint>> { this.metric .data .map(dp => <HealthDataPoint>dp) + .filter((value, index, array) => { + if (start != null && value.timestamp < start) { + return false; + } + + if (end != null && value.timestamp > end) { + return false; + } + + return true; + }) .map( dp => ` <tr> @@ -160,7 +175,7 @@ export class HealthMetricPage extends MetricPage<Metric<HealthDataPoint>> { .join() ); - $('#metric-data').DataTable({ + this.dataTable = $('#metric-data').DataTable({ "order": [[0, "desc"]], lengthChange: false, searching: false, @@ -173,8 +188,8 @@ export class HealthMetricPage extends MetricPage<Metric<HealthDataPoint>> { 'showDetails', (e: CustomEvent) => { - let data : any[] = e.detail.data; - let timestamp : Date = e.detail.timestamp; + let data: any[] = e.detail.data; + let timestamp: Date = e.detail.timestamp; let code = ` <div @@ -211,17 +226,17 @@ export class HealthMetricPage extends MetricPage<Metric<HealthDataPoint>> { </thead> <tbody> ${ - data - .sortByProperty(el => el.Source) - .map(el => ` + data + .sortByProperty(el => el.Source) + .map(el => ` <tr> <th>${el.Type}</th> <th>${el.Source}</th> <th>${el.Label}</th> </tr> `) - .join("") - } + .join("") + } </tbody> </table> </div> diff --git a/client/ts/modules/metric-page/ping.ts b/client/ts/modules/metric-page/ping.ts index f81fae8..90e41a1 100644 --- a/client/ts/modules/metric-page/ping.ts +++ b/client/ts/modules/metric-page/ping.ts @@ -149,9 +149,13 @@ export class PingMetricPage extends MetricPage<Metric<PingDataPoint>> { }; }; - protected renderTable(): void { + protected renderTable(redraw: boolean, start: Date, end: Date): void { - if (!this.dataTablesRendered) { + if (!this.dataTablesRendered || redraw) { + + if (this.dataTablesRendered) { + this.dataTable.destroy(); + } let header = ` <tr> @@ -168,6 +172,17 @@ export class PingMetricPage extends MetricPage<Metric<PingDataPoint>> { this.metric .data .map(dp => <PingDataPoint>dp) + .filter((value, index, array) => { + if (start != null && value.timestamp < start) { + return false; + } + + if (end != null && value.timestamp > end) { + return false; + } + + return true; + }) .map( dp => ` <tr> @@ -180,7 +195,7 @@ export class PingMetricPage extends MetricPage<Metric<PingDataPoint>> { .join() ); - $('#metric-data').DataTable({ + this.dataTable = $('#metric-data').DataTable({ "order": [[0, "desc"]], lengthChange: false, searching: false, diff --git a/client/ts/modules/metric-page/user-action.ts b/client/ts/modules/metric-page/user-action.ts index 2ad7368..6044c8c 100644 --- a/client/ts/modules/metric-page/user-action.ts +++ b/client/ts/modules/metric-page/user-action.ts @@ -154,9 +154,13 @@ export class UserActionMetricPage extends MetricPage<Metric<UserActionDataPoint> }; }; - protected renderTable(): void { + protected renderTable(redraw: boolean, start: Date, end: Date): void { - if (!this.dataTablesRendered) { + if (!this.dataTablesRendered || redraw) { + + if (this.dataTablesRendered) { + this.dataTable.destroy(); + } let header = ` <tr> @@ -173,6 +177,17 @@ export class UserActionMetricPage extends MetricPage<Metric<UserActionDataPoint> this.metric .data .map(dp => <UserActionDataPoint>dp) + .filter((value, index, array) => { + if (start != null && value.timestamp < start) { + return false; + } + + if (end != null && value.timestamp > end) { + return false; + } + + return true; + }) .map( dp => ` <tr> @@ -185,7 +200,7 @@ export class UserActionMetricPage extends MetricPage<Metric<UserActionDataPoint> .join() ); - $('#metric-data').DataTable({ + this.dataTable = $('#metric-data').DataTable({ "order": [[0, "desc"]], lengthChange: false, searching: false, diff --git a/src/web/Controllers/View/HomeController.cs b/src/web/Controllers/View/HomeController.cs index 63404c8..14371aa 100755 --- a/src/web/Controllers/View/HomeController.cs +++ b/src/web/Controllers/View/HomeController.cs @@ -65,8 +65,8 @@ namespace StatusMonitor.Web.Controllers.View return View(model); } - [Route("Home/Metric/{type}/{source}")] - public async Task<IActionResult> Metric(string type, string source) + [Route("Home/Metric/{type}/{source}/{start?}/{end?}")] + public async Task<IActionResult> Metric(string type, string source, string start = null, string end = null) { Metrics metricType; @@ -79,6 +79,41 @@ namespace StatusMonitor.Web.Controllers.View return BadRequest("Bad type. Needs to be one of Metrics type."); } + if (start != null) + { + try + { + DateTime.SpecifyKind(new DateTime(1970, 1, 1), DateTimeKind.Utc).AddMilliseconds(Convert.ToInt64(start)); + } + catch (System.Exception) + { + return BadRequest("Bad start date. Needs to be the number of milliseconds since Epoch."); + } + } + + if (end != null) + { + try + { + DateTime.SpecifyKind(new DateTime(1970, 1, 1), DateTimeKind.Utc).AddMilliseconds(Convert.ToInt64(end)); + } + catch (System.Exception) + { + return BadRequest("Bad end date. Needs to be the number of milliseconds since Epoch."); + } + } + + if (start != null && end != null) + { + if ( + DateTime.SpecifyKind(new DateTime(1970, 1, 1), DateTimeKind.Utc).AddMilliseconds(Convert.ToInt64(start)) >= + DateTime.SpecifyKind(new DateTime(1970, 1, 1), DateTimeKind.Utc).AddMilliseconds(Convert.ToInt64(end)) + ) + { + return BadRequest("Bad dates. End date needs to be greater than the start date."); + } + } + var model = await _metricService.GetMetricsAsync(metricType, source); if (model.Count() == 0) @@ -107,6 +142,9 @@ namespace StatusMonitor.Web.Controllers.View ViewBag.Uptime = await _uptime.ComputeUptimeAsync(source); } + ViewBag.Start = start ?? 0.ToString(); + ViewBag.End = end ?? 0.ToString(); + return View(model.First()); } diff --git a/src/web/Views/Home/Metric.cshtml b/src/web/Views/Home/Metric.cshtml index 296f686..754e171 100644 --- a/src/web/Views/Home/Metric.cshtml +++ b/src/web/Views/Home/Metric.cshtml @@ -186,8 +186,12 @@ <script> var source = "@Model.Source"; var type = @Model.Type; + var min = @ViewData["Min"]; var max = @ViewData["Max"]; + + var start = @ViewData["Start"]; + var end = @ViewData["End"]; </script> <environment names="Development"> -- GitLab