From fca5ee4beae2a07b2e3e856bde250b622dcea10b Mon Sep 17 00:00:00 2001 From: Josh Date: Wed, 24 Apr 2019 02:57:42 -0400 Subject: [PATCH 01/12] Provisioning: Support FolderUid in Dashboard Provisioning Config (#16559) * add folderUid to DashbaordsAsConfig structs and DashbardProviderConfigs struct, set these values in mapping func look for new folderUid values in config_reader tests set dashboard folder Uid explicitly in file_reader, which has no affect when not given * formatting and docstrings * add folderUid to DashbaordsAsConfig structs and DashbardProviderConfigs struct, set these values in mapping func look for new folderUid values in config_reader tests set dashboard folder Uid explicitly in file_reader, which has no affect when not given * formatting and docstrings * add folderUid option, as well as documentation for the rest of the fields * add blank folderUid in devenv example. * add folderUid to provisioning sample yaml * instead of just warning, return error if unmarshalling dashboard provisioning file fails * Removing the error handling and adding comment * Add duplicity check for folder Uids Co-authored-by: swtch1 --- conf/provisioning/dashboards/sample.yaml | 1 + devenv/dashboards.yaml | 1 + docs/sources/administration/provisioning.md | 13 +++++++- .../provisioning/dashboards/config_reader.go | 30 ++++++++++++++----- .../dashboards/config_reader_test.go | 2 ++ .../provisioning/dashboards/file_reader.go | 7 ++++- .../dashboards-from-disk/dev-dashboards.yaml | 1 + .../test-configs/version-0/version-0.yaml | 1 + pkg/services/provisioning/dashboards/types.go | 5 ++++ 9 files changed, 51 insertions(+), 10 deletions(-) diff --git a/conf/provisioning/dashboards/sample.yaml b/conf/provisioning/dashboards/sample.yaml index d70bd42563486..6f3ac570ca428 100644 --- a/conf/provisioning/dashboards/sample.yaml +++ b/conf/provisioning/dashboards/sample.yaml @@ -5,6 +5,7 @@ apiVersion: 1 # - name: 'default' # orgId: 1 # folder: '' +# folderUid: '' # type: file # options: # path: /var/lib/grafana/dashboards diff --git a/devenv/dashboards.yaml b/devenv/dashboards.yaml index 3e0e21ef4fefa..98c726408de48 100644 --- a/devenv/dashboards.yaml +++ b/devenv/dashboards.yaml @@ -3,6 +3,7 @@ apiVersion: 1 providers: - name: 'gdev dashboards' folder: 'gdev dashboards' + folderUid: '' type: file updateIntervalSeconds: 60 options: diff --git a/docs/sources/administration/provisioning.md b/docs/sources/administration/provisioning.md index 1ac1c8e396619..c09c3caec8249 100644 --- a/docs/sources/administration/provisioning.md +++ b/docs/sources/administration/provisioning.md @@ -203,13 +203,24 @@ The dashboard provider config file looks somewhat like this: apiVersion: 1 providers: + # provider name - name: 'default' + # org id. will default to orgId 1 if not specified orgId: 1 + # name of the dashboard folder. Required folder: '' + # folder UID. will be automatically generated if not specified + folderUid: '' + # provider type. Required type: file + # disable dashboard deletion disableDeletion: false - updateIntervalSeconds: 10 #how often Grafana will scan for changed dashboards + # enable dashboard editing + editable: true + # how often Grafana will scan for changed dashboards + updateIntervalSeconds: 10 options: + # path to dashboard files on disk. Required path: /var/lib/grafana/dashboards ``` diff --git a/pkg/services/provisioning/dashboards/config_reader.go b/pkg/services/provisioning/dashboards/config_reader.go index c57ca1c55e153..50cedc079e331 100644 --- a/pkg/services/provisioning/dashboards/config_reader.go +++ b/pkg/services/provisioning/dashboards/config_reader.go @@ -24,10 +24,15 @@ func (cr *configReader) parseConfigs(file os.FileInfo) ([]*DashboardsAsConfig, e } apiVersion := &ConfigVersion{ApiVersion: 0} - yaml.Unmarshal(yamlFile, &apiVersion) - if apiVersion.ApiVersion > 0 { + // We ignore the error here because it errors out for version 0 which does not have apiVersion + // specified (so 0 is default). This can also error in case the apiVersion is not an integer but at the moment + // this does not handle that case and would still go on as if version = 0. + // TODO: return appropriate error in case the apiVersion is specified but isn't integer (or even if it is + // integer > max version?). + _ = yaml.Unmarshal(yamlFile, &apiVersion) + if apiVersion.ApiVersion > 0 { v1 := &DashboardAsConfigV1{} err := yaml.Unmarshal(yamlFile, &v1) if err != nil { @@ -37,7 +42,6 @@ func (cr *configReader) parseConfigs(file os.FileInfo) ([]*DashboardsAsConfig, e if v1 != nil { return v1.mapToDashboardAsConfig(), nil } - } else { var v0 []*DashboardsAsConfigV0 err := yaml.Unmarshal(yamlFile, &v0) @@ -78,13 +82,23 @@ func (cr *configReader) readConfig() ([]*DashboardsAsConfig, error) { } } - for i := range dashboards { - if dashboards[i].OrgId == 0 { - dashboards[i].OrgId = 1 + uidUsage := map[string]uint8{} + for _, dashboard := range dashboards { + if dashboard.OrgId == 0 { + dashboard.OrgId = 1 } - if dashboards[i].UpdateIntervalSeconds == 0 { - dashboards[i].UpdateIntervalSeconds = 10 + if dashboard.UpdateIntervalSeconds == 0 { + dashboard.UpdateIntervalSeconds = 10 + } + if len(dashboard.FolderUid) > 0 { + uidUsage[dashboard.FolderUid] += 1 + } + } + + for uid, times := range uidUsage { + if times > 1 { + cr.log.Error("the same 'folderUid' is used more than once", "folderUid", uid) } } diff --git a/pkg/services/provisioning/dashboards/config_reader_test.go b/pkg/services/provisioning/dashboards/config_reader_test.go index d386e42349d88..8ce322a76079f 100644 --- a/pkg/services/provisioning/dashboards/config_reader_test.go +++ b/pkg/services/provisioning/dashboards/config_reader_test.go @@ -66,6 +66,7 @@ func validateDashboardAsConfig(t *testing.T, cfg []*DashboardsAsConfig) { So(ds.Type, ShouldEqual, "file") So(ds.OrgId, ShouldEqual, 2) So(ds.Folder, ShouldEqual, "developers") + So(ds.FolderUid, ShouldEqual, "xyz") So(ds.Editable, ShouldBeTrue) So(len(ds.Options), ShouldEqual, 1) So(ds.Options["path"], ShouldEqual, "/var/lib/grafana/dashboards") @@ -77,6 +78,7 @@ func validateDashboardAsConfig(t *testing.T, cfg []*DashboardsAsConfig) { So(ds2.Type, ShouldEqual, "file") So(ds2.OrgId, ShouldEqual, 1) So(ds2.Folder, ShouldEqual, "") + So(ds2.FolderUid, ShouldEqual, "") So(ds2.Editable, ShouldBeFalse) So(len(ds2.Options), ShouldEqual, 1) So(ds2.Options["path"], ShouldEqual, "/var/lib/grafana/dashboards") diff --git a/pkg/services/provisioning/dashboards/file_reader.go b/pkg/services/provisioning/dashboards/file_reader.go index dd5f27dc2727c..b12057c963bdb 100644 --- a/pkg/services/provisioning/dashboards/file_reader.go +++ b/pkg/services/provisioning/dashboards/file_reader.go @@ -78,6 +78,7 @@ func (fr *fileReader) ReadAndListen(ctx context.Context) error { } } +// startWalkingDisk finds and saves dashboards on disk. func (fr *fileReader) startWalkingDisk() error { resolvedPath := fr.resolvePath(fr.Path) if _, err := os.Stat(resolvedPath); err != nil { @@ -119,6 +120,7 @@ func (fr *fileReader) startWalkingDisk() error { return nil } +// handleMissingDashboardFiles will unprovision or delete dashboards which are missing on disk. func (fr *fileReader) handleMissingDashboardFiles(provisionedDashboardRefs map[string]*models.DashboardProvisioning, filesFoundOnDisk map[string]os.FileInfo) { // find dashboards to delete since json file is missing var dashboardToDelete []int64 @@ -151,6 +153,7 @@ func (fr *fileReader) handleMissingDashboardFiles(provisionedDashboardRefs map[s } } +// saveDashboard saves or updates the dashboard provisioning file at path. func (fr *fileReader) saveDashboard(path string, folderId int64, fileInfo os.FileInfo, provisionedDashboardRefs map[string]*models.DashboardProvisioning) (provisioningMetadata, error) { provisioningMetadata := provisioningMetadata{} resolvedFileInfo, err := resolveSymlink(fileInfo, path) @@ -189,7 +192,7 @@ func (fr *fileReader) saveDashboard(path string, folderId int64, fileInfo os.Fil dash.Dashboard.SetId(provisionedData.DashboardId) } - fr.log.Debug("saving new dashboard", "provisoner", fr.Cfg.Name, "file", path, "folderId", dash.Dashboard.FolderId) + fr.log.Debug("saving new dashboard", "provisioner", fr.Cfg.Name, "file", path, "folderId", dash.Dashboard.FolderId) dp := &models.DashboardProvisioning{ ExternalId: path, Name: fr.Cfg.Name, @@ -234,6 +237,8 @@ func getOrCreateFolderId(cfg *DashboardsAsConfig, service dashboards.DashboardPr dash.Dashboard.IsFolder = true dash.Overwrite = true dash.OrgId = cfg.OrgId + // set dashboard folderUid if given + dash.Dashboard.SetUid(cfg.FolderUid) dbDash, err := service.SaveFolderForProvisionedDashboards(dash) if err != nil { return 0, err diff --git a/pkg/services/provisioning/dashboards/testdata/test-configs/dashboards-from-disk/dev-dashboards.yaml b/pkg/services/provisioning/dashboards/testdata/test-configs/dashboards-from-disk/dev-dashboards.yaml index c43c4a14c5366..ee3d8ea73b0fa 100644 --- a/pkg/services/provisioning/dashboards/testdata/test-configs/dashboards-from-disk/dev-dashboards.yaml +++ b/pkg/services/provisioning/dashboards/testdata/test-configs/dashboards-from-disk/dev-dashboards.yaml @@ -4,6 +4,7 @@ providers: - name: 'general dashboards' orgId: 2 folder: 'developers' + folderUid: 'xyz' editable: true disableDeletion: true updateIntervalSeconds: 15 diff --git a/pkg/services/provisioning/dashboards/testdata/test-configs/version-0/version-0.yaml b/pkg/services/provisioning/dashboards/testdata/test-configs/version-0/version-0.yaml index 8b7b8991759f0..253031ef1ceaa 100644 --- a/pkg/services/provisioning/dashboards/testdata/test-configs/version-0/version-0.yaml +++ b/pkg/services/provisioning/dashboards/testdata/test-configs/version-0/version-0.yaml @@ -1,6 +1,7 @@ - name: 'general dashboards' org_id: 2 folder: 'developers' + folderUid: 'xyz' editable: true disableDeletion: true updateIntervalSeconds: 15 diff --git a/pkg/services/provisioning/dashboards/types.go b/pkg/services/provisioning/dashboards/types.go index a658b816c7d37..d5364c33509d4 100644 --- a/pkg/services/provisioning/dashboards/types.go +++ b/pkg/services/provisioning/dashboards/types.go @@ -14,6 +14,7 @@ type DashboardsAsConfig struct { Type string OrgId int64 Folder string + FolderUid string Editable bool Options map[string]interface{} DisableDeletion bool @@ -25,6 +26,7 @@ type DashboardsAsConfigV0 struct { Type string `json:"type" yaml:"type"` OrgId int64 `json:"org_id" yaml:"org_id"` Folder string `json:"folder" yaml:"folder"` + FolderUid string `json:"folderUid" yaml:"folderUid"` Editable bool `json:"editable" yaml:"editable"` Options map[string]interface{} `json:"options" yaml:"options"` DisableDeletion bool `json:"disableDeletion" yaml:"disableDeletion"` @@ -44,6 +46,7 @@ type DashboardProviderConfigs struct { Type string `json:"type" yaml:"type"` OrgId int64 `json:"orgId" yaml:"orgId"` Folder string `json:"folder" yaml:"folder"` + FolderUid string `json:"folderUid" yaml:"folderUid"` Editable bool `json:"editable" yaml:"editable"` Options map[string]interface{} `json:"options" yaml:"options"` DisableDeletion bool `json:"disableDeletion" yaml:"disableDeletion"` @@ -75,6 +78,7 @@ func mapV0ToDashboardAsConfig(v0 []*DashboardsAsConfigV0) []*DashboardsAsConfig Type: v.Type, OrgId: v.OrgId, Folder: v.Folder, + FolderUid: v.FolderUid, Editable: v.Editable, Options: v.Options, DisableDeletion: v.DisableDeletion, @@ -94,6 +98,7 @@ func (dc *DashboardAsConfigV1) mapToDashboardAsConfig() []*DashboardsAsConfig { Type: v.Type, OrgId: v.OrgId, Folder: v.Folder, + FolderUid: v.FolderUid, Editable: v.Editable, Options: v.Options, DisableDeletion: v.DisableDeletion, From 89a01a680c5772a363dd624c0e4e49ebf397f862 Mon Sep 17 00:00:00 2001 From: Utkarsh Bhatnagar Date: Wed, 24 Apr 2019 01:00:02 -0700 Subject: [PATCH 02/12] CloudWatch: Use default alias if there is no alias for metrics (#16732) Fixes #16605 --- pkg/tsdb/cloudwatch/cloudwatch.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/tsdb/cloudwatch/cloudwatch.go b/pkg/tsdb/cloudwatch/cloudwatch.go index ab9c5ce8335fb..56e9bd30e98b0 100644 --- a/pkg/tsdb/cloudwatch/cloudwatch.go +++ b/pkg/tsdb/cloudwatch/cloudwatch.go @@ -228,5 +228,9 @@ func formatAlias(query *CloudWatchQuery, stat string, dimensions map[string]stri return in }) + if string(result) == "" { + return metricName + "_" + stat + } + return string(result) } From 7dadebb3f0f82d419ff1303d96b4be4332d194df Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Wed, 24 Apr 2019 10:10:16 +0200 Subject: [PATCH 03/12] playlist: fix loading dashboards by tag (#16727) Fixes #16704 --- pkg/api/playlist_play.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/api/playlist_play.go b/pkg/api/playlist_play.go index 2757f245060f6..21c91a1288da6 100644 --- a/pkg/api/playlist_play.go +++ b/pkg/api/playlist_play.go @@ -55,7 +55,7 @@ func populateDashboardsByTag(orgID int64, signedInUser *m.SignedInUser, dashboar Slug: item.Slug, Title: item.Title, Uri: item.Uri, - Url: m.GetDashboardUrl(item.Uid, item.Slug), + Url: item.Url, Order: dashboardTagOrder[tag], }) } From 739cdcfb6e6aa0fc857c284fa7b1c2f737863799 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Wed, 24 Apr 2019 10:14:18 +0200 Subject: [PATCH 04/12] Feature: Migrate Legend components to grafana/ui (#16468) * Introduced Abstract list, List and InlineList components for easier lists generation * Enable custom item key on abstract list items * Enable $.flot in storybook * Expose onOptionsChange to react panel. Allow React panels to be function components * Update type on graph panel options to group graph draw options * Introduce GraphPanelController for state and effects handling of new graph panel * Group visualisation related stories under Visualisations --- packages/grafana-ui/.storybook/config.ts | 12 +- .../CustomScrollbar/CustomScrollbar.tsx | 33 +- .../CustomScrollbar.test.tsx.snap | 4 +- .../grafana-ui/src/components/Graph/Graph.tsx | 15 +- .../components/Graph/GraphLegend.story.tsx | 105 + .../src/components/Graph/GraphLegend.tsx | 118 + .../src/components/Graph/GraphLegendItem.tsx | 117 + .../Graph/GraphWithLegend.story.tsx | 87 + .../src/components/Graph/GraphWithLegend.tsx | 125 + .../Graph/mockGraphWithLegendData.ts | 3316 +++++++++++++++++ .../src/components/Legend/Legend.story.tsx | 142 + .../src/components/Legend/Legend.tsx | 43 + .../src/components/Legend/LegendList.tsx | 66 + .../components/Legend/LegendSeriesIcon.tsx | 35 + .../src/components/Legend/LegendStatsList.tsx | 28 + .../src/components/Legend/LegendTable.tsx | 83 + .../src/components/Legend/SeriesIcon.tsx | 5 + .../src/components/List/AbstractList.test.tsx | 42 + .../src/components/List/AbstractList.tsx | 53 + .../src/components/List/InlineList.tsx | 8 + .../src/components/List/List.story.tsx | 68 + .../grafana-ui/src/components/List/List.tsx | 8 + .../__snapshots__/AbstractList.test.tsx.snap | 93 + packages/grafana-ui/src/components/index.ts | 6 +- packages/grafana-ui/src/types/graph.ts | 2 + packages/grafana-ui/src/types/index.ts | 1 + packages/grafana-ui/src/types/panel.ts | 7 +- packages/grafana-ui/src/types/utils.ts | 2 + packages/grafana-ui/src/utils/index.ts | 1 + .../src/utils/storybook/UseState.tsx | 1 - .../__snapshots__/ServerStats.test.tsx.snap | 4 +- .../dashboard/dashgrid/PanelChrome.tsx | 5 + .../app/plugins/panel/graph/Legend/Legend.tsx | 2 +- .../panel/graph2/GraphLegendEditor.tsx | 103 + .../app/plugins/panel/graph2/GraphPanel.tsx | 116 +- .../panel/graph2/GraphPanelController.tsx | 156 + .../plugins/panel/graph2/GraphPanelEditor.tsx | 44 +- .../panel/graph2/getGraphSeriesModel.ts | 83 + public/app/plugins/panel/graph2/types.ts | 31 +- 39 files changed, 5070 insertions(+), 100 deletions(-) create mode 100644 packages/grafana-ui/src/components/Graph/GraphLegend.story.tsx create mode 100644 packages/grafana-ui/src/components/Graph/GraphLegend.tsx create mode 100644 packages/grafana-ui/src/components/Graph/GraphLegendItem.tsx create mode 100644 packages/grafana-ui/src/components/Graph/GraphWithLegend.story.tsx create mode 100644 packages/grafana-ui/src/components/Graph/GraphWithLegend.tsx create mode 100644 packages/grafana-ui/src/components/Graph/mockGraphWithLegendData.ts create mode 100644 packages/grafana-ui/src/components/Legend/Legend.story.tsx create mode 100644 packages/grafana-ui/src/components/Legend/Legend.tsx create mode 100644 packages/grafana-ui/src/components/Legend/LegendList.tsx create mode 100644 packages/grafana-ui/src/components/Legend/LegendSeriesIcon.tsx create mode 100644 packages/grafana-ui/src/components/Legend/LegendStatsList.tsx create mode 100644 packages/grafana-ui/src/components/Legend/LegendTable.tsx create mode 100644 packages/grafana-ui/src/components/Legend/SeriesIcon.tsx create mode 100644 packages/grafana-ui/src/components/List/AbstractList.test.tsx create mode 100644 packages/grafana-ui/src/components/List/AbstractList.tsx create mode 100644 packages/grafana-ui/src/components/List/InlineList.tsx create mode 100644 packages/grafana-ui/src/components/List/List.story.tsx create mode 100644 packages/grafana-ui/src/components/List/List.tsx create mode 100644 packages/grafana-ui/src/components/List/__snapshots__/AbstractList.test.tsx.snap create mode 100644 packages/grafana-ui/src/types/utils.ts create mode 100644 public/app/plugins/panel/graph2/GraphLegendEditor.tsx create mode 100644 public/app/plugins/panel/graph2/GraphPanelController.tsx create mode 100644 public/app/plugins/panel/graph2/getGraphSeriesModel.ts diff --git a/packages/grafana-ui/.storybook/config.ts b/packages/grafana-ui/.storybook/config.ts index b87604438d723..98a4ee8bd9e04 100644 --- a/packages/grafana-ui/.storybook/config.ts +++ b/packages/grafana-ui/.storybook/config.ts @@ -2,7 +2,17 @@ import { configure, addDecorator } from '@storybook/react'; import { withKnobs } from '@storybook/addon-knobs'; import { withTheme } from '../src/utils/storybook/withTheme'; import { withPaddedStory } from '../src/utils/storybook/withPaddedStory'; - +import 'jquery'; +import '../../../public/vendor/flot/jquery.flot.js'; +import '../../../public/vendor/flot/jquery.flot.selection'; +import '../../../public/vendor/flot/jquery.flot.time'; +import '../../../public/vendor/flot/jquery.flot.stack'; +import '../../../public/vendor/flot/jquery.flot.pie'; +import '../../../public/vendor/flot/jquery.flot.stackpercent'; +import '../../../public/vendor/flot/jquery.flot.fillbelow'; +import '../../../public/vendor/flot/jquery.flot.crosshair'; +import '../../../public/vendor/flot/jquery.flot.dashes'; +import '../../../public/vendor/flot/jquery.flot.gauge'; // @ts-ignore import lightTheme from '../../../public/sass/grafana.light.scss'; // @ts-ignore diff --git a/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx b/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx index 945e3d825d812..f2f6b236e14b4 100644 --- a/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx +++ b/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx @@ -2,6 +2,7 @@ import React, { Component } from 'react'; import isNil from 'lodash/isNil'; import classNames from 'classnames'; import Scrollbars from 'react-custom-scrollbars'; +import { cx, css } from 'emotion'; interface Props { className?: string; @@ -10,8 +11,8 @@ interface Props { autoHideDuration?: number; autoHeightMax?: string; hideTracksWhenNotNeeded?: boolean; - renderTrackHorizontal?: React.FunctionComponent; - renderTrackVertical?: React.FunctionComponent; + hideHorizontalTrack?: boolean; + hideVerticalTrack?: boolean; scrollTop?: number; setScrollTop: (event: any) => void; autoHeightMin?: number | string; @@ -79,8 +80,8 @@ export class CustomScrollbar extends Component { autoHide, autoHideTimeout, hideTracksWhenNotNeeded, - renderTrackHorizontal, - renderTrackVertical, + hideHorizontalTrack, + hideVerticalTrack, } = this.props; return ( @@ -96,8 +97,28 @@ export class CustomScrollbar extends Component { // Before these where set to inhert but that caused problems with cut of legends in firefox autoHeightMax={autoHeightMax} autoHeightMin={autoHeightMin} - renderTrackHorizontal={renderTrackHorizontal || (props =>
)} - renderTrackVertical={renderTrackVertical || (props =>
)} + renderTrackHorizontal={props => ( +
+ )} + renderTrackVertical={props => ( +
+ )} renderThumbHorizontal={props =>
} renderThumbVertical={props =>
} renderView={props =>
} diff --git a/packages/grafana-ui/src/components/CustomScrollbar/__snapshots__/CustomScrollbar.test.tsx.snap b/packages/grafana-ui/src/components/CustomScrollbar/__snapshots__/CustomScrollbar.test.tsx.snap index dd3f59ad1e199..b348f7dd8bd06 100644 --- a/packages/grafana-ui/src/components/CustomScrollbar/__snapshots__/CustomScrollbar.test.tsx.snap +++ b/packages/grafana-ui/src/components/CustomScrollbar/__snapshots__/CustomScrollbar.test.tsx.snap @@ -37,7 +37,7 @@ exports[`CustomScrollbar renders correctly 1`] = `

{ const ticks = width / 100; const min = timeRange.from.valueOf(); const max = timeRange.to.valueOf(); - + const yaxes = uniqBy( + series.map(s => { + return { + show: true, + index: s.yAxis, + position: s.yAxis === 1 ? 'left' : 'right', + }; + }), + yAxisConfig => yAxisConfig.index + ); const flotOptions = { legend: { show: false, @@ -80,6 +90,7 @@ export class Graph extends PureComponent { ticks: ticks, timeformat: timeFormat(ticks, min, max), }, + yaxes, grid: { minBorderMargin: 0, markings: [], diff --git a/packages/grafana-ui/src/components/Graph/GraphLegend.story.tsx b/packages/grafana-ui/src/components/Graph/GraphLegend.story.tsx new file mode 100644 index 0000000000000..1acc4d50a36b9 --- /dev/null +++ b/packages/grafana-ui/src/components/Graph/GraphLegend.story.tsx @@ -0,0 +1,105 @@ +import React from 'react'; +import { storiesOf } from '@storybook/react'; + +import { GraphLegend } from './GraphLegend'; +import { action } from '@storybook/addon-actions'; +import { select, number } from '@storybook/addon-knobs'; +import { withHorizontallyCenteredStory } from '../../utils/storybook/withCenteredStory'; +import { generateLegendItems } from '../Legend/Legend.story'; +import { LegendPlacement, LegendDisplayMode } from '../Legend/Legend'; + +const GraphLegendStories = storiesOf('Visualizations/Graph/GraphLegend', module); +GraphLegendStories.addDecorator(withHorizontallyCenteredStory); + +const getStoriesKnobs = (isList = false) => { + const statsToDisplay = select( + 'Stats to display', + { + none: [], + 'single (min)': [{ text: '10ms', title: 'min', numeric: 10 }], + 'multiple (min, max)': [ + { text: '10ms', title: 'min', numeric: 10 }, + { text: '100ms', title: 'max', numeric: 100 }, + ], + }, + [] + ); + + const numberOfSeries = number('Number of series', 3); + + const containerWidth = select( + 'Container width', + { + Small: '200px', + Medium: '500px', + 'Full width': '100%', + }, + '100%' + ); + + const legendPlacement = select( + 'Legend placement', + { + under: 'under', + right: 'right', + }, + 'under' + ); + + return { + statsToDisplay, + numberOfSeries, + containerWidth, + legendPlacement, + }; +}; + +GraphLegendStories.add('list', () => { + const { statsToDisplay, numberOfSeries, containerWidth, legendPlacement } = getStoriesKnobs(true); + return ( +
+ { + action('Series label clicked')(item, event); + }} + onSeriesColorChange={(label, color) => { + action('Series color changed')(label, color); + }} + onSeriesAxisToggle={(label, useRightYAxis) => { + action('Series axis toggle')(label, useRightYAxis); + }} + onToggleSort={sortBy => { + action('Toggle legend sort')(sortBy); + }} + placement={legendPlacement} + /> +
+ ); +}); + +GraphLegendStories.add('table', () => { + const { statsToDisplay, numberOfSeries, containerWidth, legendPlacement } = getStoriesKnobs(); + return ( +
+ { + action('Series label clicked')(item); + }} + onSeriesColorChange={(label, color) => { + action('Series color changed')(label, color); + }} + onSeriesAxisToggle={(label, useRightYAxis) => { + action('Series axis toggle')(label, useRightYAxis); + }} + onToggleSort={sortBy => { + action('Toggle legend sort')(sortBy); + }} + placement={legendPlacement} + /> +
+ ); +}); diff --git a/packages/grafana-ui/src/components/Graph/GraphLegend.tsx b/packages/grafana-ui/src/components/Graph/GraphLegend.tsx new file mode 100644 index 0000000000000..451acf4bac805 --- /dev/null +++ b/packages/grafana-ui/src/components/Graph/GraphLegend.tsx @@ -0,0 +1,118 @@ +import React, { useContext } from 'react'; +import { LegendProps, LegendItem, LegendDisplayMode } from '../Legend/Legend'; +import { GraphLegendListItem, GraphLegendTableRow } from './GraphLegendItem'; +import { SeriesColorChangeHandler, SeriesAxisToggleHandler } from './GraphWithLegend'; +import { LegendTable } from '../Legend/LegendTable'; +import { LegendList } from '../Legend/LegendList'; +import union from 'lodash/union'; +import sortBy from 'lodash/sortBy'; +import { ThemeContext } from '../../themes/ThemeContext'; +import { css } from 'emotion'; +import { selectThemeVariant } from '../../themes/index'; + +interface GraphLegendProps extends LegendProps { + displayMode: LegendDisplayMode; + sortBy?: string; + sortDesc?: boolean; + onSeriesColorChange: SeriesColorChangeHandler; + onSeriesAxisToggle?: SeriesAxisToggleHandler; + onToggleSort: (sortBy: string) => void; + onLabelClick: (item: LegendItem, event: React.MouseEvent) => void; +} + +export const GraphLegend: React.FunctionComponent = ({ + items, + displayMode, + sortBy: sortKey, + sortDesc, + onToggleSort, + onSeriesAxisToggle, + placement, + className, + ...graphLegendItemProps +}) => { + const theme = useContext(ThemeContext); + + if (displayMode === LegendDisplayMode.Table) { + const columns = items + .map(item => { + if (item.displayValues) { + return item.displayValues.map(i => i.title); + } + return []; + }) + .reduce( + (acc, current) => { + return union(acc, current.filter(item => !!item)); + }, + [''] + ) as string[]; + + const sortedItems = sortKey + ? sortBy(items, item => { + if (item.displayValues) { + const stat = item.displayValues.filter(stat => stat.title === sortKey)[0]; + return stat && stat.numeric; + } + return undefined; + }) + : items; + + const legendTableEvenRowBackground = selectThemeVariant( + { + dark: theme.colors.dark6, + light: theme.colors.gray5, + }, + theme.type + ); + + return ( + ( + { + if (onSeriesAxisToggle) { + onSeriesAxisToggle(item.label, item.yAxis === 1 ? 2 : 1); + } + }} + className={css` + background: ${index % 2 === 0 ? legendTableEvenRowBackground : 'none'}; + `} + {...graphLegendItemProps} + /> + )} + onToggleSort={onToggleSort} + /> + ); + } + return ( + ( + { + if (onSeriesAxisToggle) { + onSeriesAxisToggle(item.label, item.yAxis === 1 ? 2 : 1); + } + }} + {...graphLegendItemProps} + /> + )} + /> + ); +}; diff --git a/packages/grafana-ui/src/components/Graph/GraphLegendItem.tsx b/packages/grafana-ui/src/components/Graph/GraphLegendItem.tsx new file mode 100644 index 0000000000000..e116287d4d21e --- /dev/null +++ b/packages/grafana-ui/src/components/Graph/GraphLegendItem.tsx @@ -0,0 +1,117 @@ +import React, { useContext } from 'react'; +import { css, cx } from 'emotion'; +import { LegendSeriesIcon } from '../Legend/LegendSeriesIcon'; +import { LegendItem } from '../Legend/Legend'; +import { SeriesColorChangeHandler } from './GraphWithLegend'; +import { LegendStatsList } from '../Legend/LegendStatsList'; +import { ThemeContext } from '../../themes/ThemeContext'; + +export interface GraphLegendItemProps { + key?: React.Key; + item: LegendItem; + className?: string; + onLabelClick: (item: LegendItem, event: React.MouseEvent) => void; + onSeriesColorChange: SeriesColorChangeHandler; + onToggleAxis: () => void; +} + +export const GraphLegendListItem: React.FunctionComponent = ({ + item, + onSeriesColorChange, + onToggleAxis, + onLabelClick, +}) => { + return ( + <> + onSeriesColorChange(item.label, color)} + onToggleAxis={onToggleAxis} + yAxis={item.yAxis} + /> +
onLabelClick(item, event)} + className={css` + cursor: pointer; + white-space: nowrap; + `} + > + {item.label} +
+ + {item.displayValues && } + + ); +}; + +export const GraphLegendTableRow: React.FunctionComponent = ({ + item, + onSeriesColorChange, + onToggleAxis, + onLabelClick, + className, +}) => { + const theme = useContext(ThemeContext); + + return ( + + + + onSeriesColorChange(item.label, color)} + onToggleAxis={onToggleAxis} + yAxis={item.yAxis} + /> +
onLabelClick(item, event)} + className={css` + cursor: pointer; + white-space: nowrap; + `} + > + {item.label}{' '} + {item.yAxis === 2 && ( + + (right y-axis) + + )} +
+
+ + {item.displayValues && + item.displayValues.map((stat, index) => { + return ( + + {stat.text} + + ); + })} + + ); +}; diff --git a/packages/grafana-ui/src/components/Graph/GraphWithLegend.story.tsx b/packages/grafana-ui/src/components/Graph/GraphWithLegend.story.tsx new file mode 100644 index 0000000000000..c0fbf52d38fd6 --- /dev/null +++ b/packages/grafana-ui/src/components/Graph/GraphWithLegend.story.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import { storiesOf } from '@storybook/react'; + +import { select, text } from '@storybook/addon-knobs'; +import { withHorizontallyCenteredStory } from '../../utils/storybook/withCenteredStory'; +import { GraphWithLegend } from './GraphWithLegend'; + +import { mockGraphWithLegendData } from './mockGraphWithLegendData'; +import { action } from '@storybook/addon-actions'; +import { LegendPlacement, LegendDisplayMode } from '../Legend/Legend'; +const GraphWithLegendStories = storiesOf('Visualizations/Graph/GraphWithLegend', module); +GraphWithLegendStories.addDecorator(withHorizontallyCenteredStory); + +const getStoriesKnobs = () => { + const containerWidth = select( + 'Container width', + { + Small: '200px', + Medium: '500px', + 'Full width': '100%', + }, + '100%' + ); + const containerHeight = select( + 'Container height', + { + Small: '200px', + Medium: '400px', + 'Full height': '100%', + }, + '400px' + ); + + const rightAxisSeries = text('Right y-axis series, i.e. A,C', ''); + + const legendPlacement = select( + 'Legend placement', + { + under: 'under', + right: 'right', + }, + 'under' + ); + const renderLegendAsTable = select( + 'Render legend as', + { + list: false, + table: true, + }, + false + ); + + return { + containerWidth, + containerHeight, + rightAxisSeries, + legendPlacement, + renderLegendAsTable, + }; +}; + +GraphWithLegendStories.add('default', () => { + const { containerWidth, containerHeight, rightAxisSeries, legendPlacement, renderLegendAsTable } = getStoriesKnobs(); + + const props = mockGraphWithLegendData({ + onSeriesColorChange: action('Series color changed'), + onSeriesAxisToggle: action('Series y-axis changed'), + displayMode: renderLegendAsTable ? LegendDisplayMode.Table : LegendDisplayMode.List, + }); + const series = props.series.map(s => { + if ( + rightAxisSeries + .split(',') + .map(s => s.trim()) + .indexOf(s.label.split('-')[0]) > -1 + ) { + s.yAxis = 2; + } + + return s; + }); + return ( +
+ , +
+ ); +}); diff --git a/packages/grafana-ui/src/components/Graph/GraphWithLegend.tsx b/packages/grafana-ui/src/components/Graph/GraphWithLegend.tsx new file mode 100644 index 0000000000000..162c24758eae7 --- /dev/null +++ b/packages/grafana-ui/src/components/Graph/GraphWithLegend.tsx @@ -0,0 +1,125 @@ +// Libraries +import _ from 'lodash'; +import React from 'react'; + +import { css } from 'emotion'; +import { Graph, GraphProps } from './Graph'; +import { LegendRenderOptions, LegendItem, LegendDisplayMode } from '../Legend/Legend'; +import { GraphLegend } from './GraphLegend'; +import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar'; +import { GraphSeriesValue } from '../../types/graph'; + +export type SeriesOptionChangeHandler = (label: string, option: TOption) => void; +export type SeriesColorChangeHandler = SeriesOptionChangeHandler; +export type SeriesAxisToggleHandler = SeriesOptionChangeHandler; + +export interface GraphWithLegendProps extends GraphProps, LegendRenderOptions { + isLegendVisible: boolean; + displayMode: LegendDisplayMode; + sortLegendBy?: string; + sortLegendDesc?: boolean; + onSeriesColorChange: SeriesColorChangeHandler; + onSeriesAxisToggle?: SeriesAxisToggleHandler; + onSeriesToggle?: (label: string, event: React.MouseEvent) => void; + onToggleSort: (sortBy: string) => void; +} + +const getGraphWithLegendStyles = ({ placement }: GraphWithLegendProps) => ({ + wrapper: css` + display: flex; + flex-direction: ${placement === 'under' ? 'column' : 'row'}; + height: 100%; + `, + graphContainer: css` + min-height: 65%; + flex-grow: 1; + `, + legendContainer: css` + padding: 10px 0; + max-height: ${placement === 'under' ? '35%' : 'none'}; + `, +}); + +const shouldHideLegendItem = (data: GraphSeriesValue[][], hideEmpty = false, hideZero = false) => { + const isZeroOnlySeries = data.reduce((acc, current) => acc + (current[1] || 0), 0) === 0; + const isNullOnlySeries = !data.reduce((acc, current) => acc && current[1] !== null, true); + + return (hideEmpty && isNullOnlySeries) || (hideZero && isZeroOnlySeries); +}; + +export const GraphWithLegend: React.FunctionComponent = (props: GraphWithLegendProps) => { + const { + series, + timeRange, + width, + height, + showBars, + showLines, + showPoints, + sortLegendBy, + sortLegendDesc, + isLegendVisible, + displayMode, + placement, + onSeriesAxisToggle, + onSeriesColorChange, + onSeriesToggle, + onToggleSort, + hideEmpty, + hideZero, + } = props; + const { graphContainer, wrapper, legendContainer } = getGraphWithLegendStyles(props); + + const legendItems = series.reduce((acc, s) => { + return shouldHideLegendItem(s.data, hideEmpty, hideZero) + ? acc + : acc.concat([ + { + label: s.label, + color: s.color, + isVisible: s.isVisible, + yAxis: s.yAxis, + displayValues: s.info || [], + }, + ]); + }, []); + + return ( +
+
+ !!s.isVisible)} + timeRange={timeRange} + showLines={showLines} + showPoints={showPoints} + showBars={showBars} + width={width} + height={height} + key={isLegendVisible ? 'legend-visible' : 'legend-invisible'} + /> +
+ + {isLegendVisible && ( +
+ + { + if (onSeriesToggle) { + onSeriesToggle(item.label, event); + } + }} + onSeriesColorChange={onSeriesColorChange} + onSeriesAxisToggle={onSeriesAxisToggle} + onToggleSort={onToggleSort} + /> + +
+ )} +
+ ); +}; diff --git a/packages/grafana-ui/src/components/Graph/mockGraphWithLegendData.ts b/packages/grafana-ui/src/components/Graph/mockGraphWithLegendData.ts new file mode 100644 index 0000000000000..2e150eb04ea53 --- /dev/null +++ b/packages/grafana-ui/src/components/Graph/mockGraphWithLegendData.ts @@ -0,0 +1,3316 @@ +import { GraphWithLegendProps } from './GraphWithLegend'; +import moment from 'moment'; +import { LegendDisplayMode } from '../Legend/Legend'; +// import { LegendList } from '../Legend/LegendList'; + +export const mockGraphWithLegendData = ({ + displayMode, + onSeriesColorChange, + onSeriesAxisToggle, +}: Partial): GraphWithLegendProps => ({ + series: [ + { + label: 'A-series', + data: [ + [1554793274247, 20.313880709398614], + [1554793294247, 20.08104852830009], + [1554793314247, 19.992843162258378], + [1554793334247, 20.41499645651864], + [1554793354247, 20.121418991609794], + [1554793374247, 19.680864164371194], + [1554793394247, 19.246572984138286], + [1554793414247, 19.46125180187392], + [1554793434247, 19.039716655273562], + [1554793454247, 18.92237339584932], + [1554793474247, 19.032006074688397], + [1554793494247, 18.554026324427063], + [1554793514247, 18.638230492753188], + [1554793534247, 18.30793730234939], + [1554793554247, 18.19449466810041], + [1554793574247, 18.344646319460164], + [1554793594247, 18.11110436496195], + [1554793614247, 18.22114686279577], + [1554793634247, 17.963238261916597], + [1554793654247, 18.066099068004895], + [1554793674247, 17.820666244047295], + [1554793694247, 17.658322234243347], + [1554793714247, 17.446542397799462], + [1554793734247, 17.371388468792254], + [1554793754247, 17.217876617816966], + [1554793774247, 17.257521109488476], + [1554793794247, 17.58336498893354], + [1554793814247, 17.59090539787382], + [1554793834247, 17.87356599440561], + [1554793854247, 17.4806967502818], + [1554793874247, 17.92887698327966], + [1554793894247, 17.905603390367062], + [1554793914247, 18.3347835049674], + [1554793934247, 18.25272866220099], + [1554793954247, 17.864277261462757], + [1554793974247, 18.152124227305972], + [1554793994247, 18.259495538365456], + [1554794014247, 18.489018553448787], + [1554794034247, 18.821247189702426], + [1554794054247, 19.08003017107941], + [1554794074247, 19.491262702076856], + [1554794094247, 19.1827682465781], + [1554794114247, 19.182869096769963], + [1554794134247, 19.095115140924634], + [1554794154247, 19.05299994112004], + [1554794174247, 18.590606725871183], + [1554794194247, 19.07931422785976], + [1554794214247, 19.477891144970496], + [1554794234247, 19.21897148797869], + [1554794254247, 18.77220964737733], + [1554794274247, 18.803310056748714], + [1554794294247, 18.88481860551549], + [1554794314247, 19.194980590780798], + [1554794334247, 19.13519891641105], + [1554794354247, 19.22496773205921], + [1554794374247, 18.735469623659885], + [1554794394247, 19.021576009377863], + [1554794414247, 19.246289879789813], + [1554794434247, 19.105419144705216], + [1554794454247, 19.571674122877262], + [1554794474247, 19.891144340237346], + [1554794494247, 19.676557234305022], + [1554794514247, 19.345971553425187], + [1554794534247, 19.048466632282935], + [1554794554247, 18.570617820383877], + [1554794574247, 18.734467617535582], + [1554794594247, 19.04927504415475], + [1554794614247, 19.166389592466707], + [1554794634247, 18.794409779030502], + [1554794654247, 19.181010534033533], + [1554794674247, 19.37350362803945], + [1554794694247, 18.950932112848196], + [1554794714247, 18.488019913458178], + [1554794734247, 18.86684581263146], + [1554794754247, 18.983697195043614], + [1554794774247, 18.531710787820366], + [1554794794247, 19.01566943622132], + [1554794814247, 19.40748484804793], + [1554794834247, 19.435103380796], + [1554794854247, 19.39782995665856], + [1554794874247, 19.438254903387058], + [1554794894247, 19.10031735117575], + [1554794914247, 18.980821678099883], + [1554794934247, 18.834722745415952], + [1554794954247, 18.373878360320436], + [1554794974247, 18.84943998519851], + [1554794994247, 19.106939136485085], + [1554795014247, 19.052362017480817], + [1554795034247, 18.648880645925313], + [1554795054247, 18.8814523423611], + [1554795074247, 18.857826541729597], + [1554795094247, 19.008845156827967], + [1554795114247, 19.28178805280102], + [1554795134247, 19.602626486969534], + [1554795154247, 19.985163722852473], + [1554795174247, 20.322606652974994], + [1554795194247, 20.586587642388157], + [1554795214247, 20.39315542962396], + [1554795234247, 20.842997727238604], + [1554795254247, 20.479452567602156], + [1554795274247, 20.56819178541087], + [1554795294247, 20.317663197821815], + [1554795314247, 20.188138468315195], + [1554795334247, 19.705561316215853], + [1554795354247, 19.479509927065397], + [1554795374247, 19.447752179188306], + [1554795394247, 19.181232835157697], + [1554795414247, 19.459427085329224], + [1554795434247, 19.05132746752971], + [1554795454247, 18.841056436711025], + [1554795474247, 18.831360086228283], + [1554795494247, 18.86658975782061], + [1554795514247, 18.698033676789784], + [1554795534247, 18.929839598501857], + [1554795554247, 19.390241447580827], + [1554795574247, 19.339437395648535], + [1554795594247, 19.666634660902492], + [1554795614247, 19.767170656886673], + [1554795634247, 19.911342704118248], + [1554795654247, 20.37312718893495], + [1554795674247, 20.43933881511363], + [1554795694247, 20.653859896728182], + [1554795714247, 20.77646124725375], + [1554795734247, 20.98307062620013], + [1554795754247, 20.76453112065912], + [1554795774247, 20.705974067242977], + [1554795794247, 20.260579571260333], + [1554795814247, 20.662046577866715], + [1554795834247, 20.2449850759113], + [1554795854247, 19.756051593693513], + [1554795874247, 19.619517424157557], + [1554795894247, 19.29791716902326], + [1554795914247, 19.179238038293985], + [1554795934247, 19.46627554259614], + [1554795954247, 19.66575441011187], + [1554795974247, 19.940391553759927], + [1554795994247, 20.298913074442243], + [1554796014247, 20.13096011323593], + [1554796034247, 20.23340372021052], + [1554796054247, 20.585390686449408], + [1554796074247, 20.997775066783095], + [1554796094247, 21.04685139350083], + [1554796114247, 20.778323773832028], + [1554796134247, 20.72898684239931], + [1554796154247, 20.746854320861416], + [1554796174247, 21.080311484818665], + [1554796194247, 21.318217286443893], + [1554796214247, 21.54956043220836], + [1554796234247, 21.271074017912362], + [1554796254247, 21.116019947184835], + [1554796274247, 20.62448718389609], + [1554796294247, 20.721537234112986], + [1554796314247, 20.36305734406211], + [1554796334247, 20.42644876993651], + [1554796354247, 20.16545544539798], + [1554796374247, 20.26968616779487], + [1554796394247, 20.3397829790123], + [1554796414247, 20.03067628549628], + [1554796434247, 20.131150696804713], + [1554796454247, 19.996918423173987], + [1554796474247, 20.3383860035356], + [1554796494247, 19.874575784832267], + [1554796514247, 19.874280591895396], + [1554796534247, 19.555747916068814], + [1554796554247, 19.512322604239518], + [1554796574247, 19.971727883913204], + [1554796594247, 19.972663038357112], + [1554796614247, 20.215750537357355], + [1554796634247, 19.811615680952983], + [1554796654247, 19.420570259916218], + [1554796674247, 19.78388048792971], + [1554796694247, 19.809444584661886], + [1554796714247, 19.744609482332653], + [1554796734247, 20.039793233334624], + [1554796754247, 19.656750321018873], + [1554796774247, 19.655016503008568], + [1554796794247, 19.91706698974138], + [1554796814247, 20.023553168738367], + [1554796834247, 20.077071328813926], + [1554796854247, 19.88531080019798], + [1554796874247, 20.274356827283746], + [1554796894247, 19.871521517855285], + [1554796914247, 19.849189625281237], + [1554796934247, 19.545290760324306], + [1554796954247, 19.18091527224302], + [1554796974247, 19.452336236879457], + [1554796994247, 19.146012613561403], + [1554797014247, 19.354033871521427], + [1554797034247, 19.374660496846577], + [1554797054247, 19.545775904269995], + [1554797074247, 19.466624079433085], + [1554797094247, 19.024037215414324], + [1554797114247, 18.676381124829952], + [1554797134247, 18.607609672523235], + [1554797154247, 18.45093227466119], + [1554797174247, 18.497642680307425], + [1554797194247, 18.84995936205565], + [1554797214247, 19.254794470921848], + [1554797234247, 19.118449528190986], + [1554797254247, 19.30669615290519], + [1554797274247, 19.289538518091465], + [1554797294247, 19.402478297163942], + [1554797314247, 19.142856488362764], + [1554797334247, 18.86825661399161], + [1554797354247, 18.439007909998104], + [1554797374247, 18.776471890064464], + [1554797394247, 18.491952005082823], + [1554797414247, 18.44926487148877], + [1554797434247, 18.610300172432634], + [1554797454247, 18.800869256547237], + [1554797474247, 19.155873759250404], + [1554797494247, 19.31190799141301], + [1554797514247, 18.825285394687576], + [1554797534247, 19.095824434613274], + [1554797554247, 19.578941846370167], + [1554797574247, 19.53513022972872], + [1554797594247, 19.3934810416622], + [1554797614247, 19.763910623076562], + [1554797634247, 19.804650392017304], + [1554797654247, 20.20814902135129], + [1554797674247, 20.42627007519049], + [1554797694247, 20.511388593346943], + [1554797714247, 20.433559812648035], + [1554797734247, 20.020788343527283], + [1554797754247, 19.82882881602156], + [1554797774247, 19.956206997916635], + [1554797794247, 19.56420055511053], + [1554797814247, 19.58095464334045], + [1554797834247, 19.102116442251454], + [1554797854247, 19.321044748170134], + [1554797874247, 19.278620558285734], + [1554797894247, 19.084803610335157], + [1554797914247, 19.163238894440624], + [1554797934247, 19.025819265964852], + [1554797954247, 18.738817306013523], + [1554797974247, 18.510704203480046], + [1554797994247, 18.92399476064368], + [1554798014247, 18.440467253152406], + [1554798034247, 18.283511925716606], + [1554798054247, 18.19183485113992], + [1554798074247, 17.83694725296019], + [1554798094247, 18.017076994342407], + [1554798114247, 18.413747553873502], + [1554798134247, 18.005072087426772], + [1554798154247, 17.65050119520046], + [1554798174247, 17.457829234397302], + [1554798194247, 17.275106596433275], + [1554798214247, 17.004692080070477], + [1554798234247, 17.22971019286104], + [1554798254247, 16.894301628540166], + [1554798274247, 16.44401141001666], + [1554798294247, 16.09012511787796], + [1554798314247, 15.833370717085522], + [1554798334247, 15.678198461092794], + [1554798354247, 15.617087273922538], + [1554798374247, 15.642521752347575], + [1554798394247, 15.824114909605015], + [1554798414247, 15.83242331224305], + [1554798434247, 16.26807306007194], + [1554798454247, 15.873706905242111], + [1554798474247, 16.164271858937447], + [1554798494247, 16.273629141890627], + [1554798514247, 15.837914408005892], + [1554798534247, 16.05357618383023], + [1554798554247, 16.358538521510823], + [1554798574247, 16.625077510565273], + [1554798594247, 16.235680011268176], + [1554798614247, 16.61406857262163], + [1554798634247, 16.158350150979114], + [1554798654247, 15.812238103859952], + [1554798674247, 15.650024480664284], + [1554798694247, 15.154774996351108], + [1554798714247, 15.08214977373202], + [1554798734247, 15.227710040181536], + [1554798754247, 15.373882728706388], + [1554798774247, 15.107453513219458], + [1554798794247, 14.849139739563727], + [1554798814247, 14.814129962471274], + [1554798834247, 14.542311965505606], + [1554798854247, 14.427101844163694], + [1554798874247, 14.74888608186797], + [1554798894247, 14.792451034664934], + [1554798914247, 15.175048493928175], + [1554798934247, 15.014974152971416], + [1554798954247, 15.268983047378917], + [1554798974247, 15.282030183678799], + [1554798994247, 15.602132376241688], + [1554799014247, 15.23005106786267], + [1554799034247, 15.636887587953181], + [1554799054247, 15.805420273666], + [1554799074247, 16.048541268247742], + [1554799094247, 15.838719939306882], + [1554799114247, 15.708752782909732], + [1554799134247, 16.066680817193237], + [1554799154247, 16.08650897981167], + [1554799174247, 16.256833754570117], + [1554799194247, 16.58911099339985], + [1554799214247, 16.649537934236353], + [1554799234247, 16.887066362197004], + [1554799254247, 16.47612075923721], + [1554799274247, 16.295702450730463], + [1554799294247, 15.959097792634903], + [1554799314247, 15.576774485820334], + [1554799334247, 15.903959111453402], + [1554799354247, 15.850548648128646], + [1554799374247, 15.988279814202075], + [1554799394247, 16.04008606971051], + [1554799414247, 16.334614032926087], + [1554799434247, 16.487440096615437], + [1554799454247, 16.075387529551623], + [1554799474247, 16.01349775782911], + [1554799494247, 16.40687699618258], + [1554799514247, 16.776868163518795], + [1554799534247, 17.185567067824188], + [1554799554247, 17.015084468455086], + [1554799574247, 17.088300049509055], + [1554799594247, 17.08802703704262], + [1554799614247, 17.451986823807108], + [1554799634247, 16.95292997178481], + [1554799654247, 16.51059402590916], + [1554799674247, 16.60084209687119], + [1554799694247, 17.06600437213062], + [1554799714247, 17.233540826974814], + [1554799734247, 16.945799567917064], + [1554799754247, 17.37443597552288], + [1554799774247, 16.90064599344329], + [1554799794247, 17.22123127821873], + [1554799814247, 16.954378456750387], + [1554799834247, 17.148060018111345], + [1554799854247, 17.090794394519804], + [1554799874247, 17.35099506839045], + [1554799894247, 17.62732436629698], + [1554799914247, 17.576380903908223], + [1554799934247, 17.5787616954438], + [1554799954247, 17.89583025685836], + [1554799974247, 18.376038600962016], + [1554799994247, 17.91345499687831], + [1554800014247, 17.564064762434413], + [1554800034247, 18.034917870957752], + [1554800054247, 18.105816481120268], + [1554800074247, 17.77072863251834], + [1554800094247, 17.992278168519807], + [1554800114247, 18.07420472728541], + [1554800134247, 18.022893864572822], + [1554800154247, 18.511067946730545], + [1554800174247, 18.53868749198732], + [1554800194247, 18.4665014110292], + [1554800214247, 18.93261420838234], + [1554800234247, 18.62757758529829], + [1554800254247, 18.82704470974516], + [1554800274247, 18.617547568653528], + [1554800294247, 18.73952779241911], + [1554800314247, 18.444297284601785], + [1554800334247, 18.406712402146002], + [1554800354247, 18.53724765698832], + [1554800374247, 18.68355494811299], + [1554800394247, 19.05074641300459], + [1554800414247, 19.410594032681672], + [1554800434247, 19.13445258188613], + [1554800454247, 18.759185011234695], + [1554800474247, 18.68542467209139], + [1554800494247, 19.06896639146912], + [1554800514247, 18.6051957309167], + [1554800534247, 18.42031016075946], + [1554800554247, 18.348974546182863], + [1554800574247, 18.20505247170292], + [1554800594247, 18.69452503019689], + [1554800614247, 18.934944507853064], + [1554800634247, 18.66230144695166], + [1554800654247, 18.771678982276487], + [1554800674247, 18.902003446382544], + [1554800694247, 18.817100898765727], + [1554800714247, 18.59223352705925], + [1554800734247, 18.25969133611703], + [1554800754247, 17.831061793894364], + [1554800774247, 17.6728034881081], + [1554800794247, 17.233411759672073], + [1554800814247, 16.8450149813612], + [1554800834247, 16.755767493272042], + [1554800854247, 16.41492274775498], + [1554800874247, 16.334526892010413], + [1554800894247, 16.29889828198967], + [1554800914247, 16.122001485153216], + [1554800934247, 15.738121737315627], + [1554800954247, 16.182985021379192], + [1554800974247, 15.77999826616122], + [1554800994247, 15.870712900527263], + [1554801014247, 16.220417258476434], + [1554801034247, 15.824531476775649], + [1554801054247, 15.385318138167277], + [1554801074247, 15.402539411608153], + [1554801094247, 15.435752247259211], + [1554801114247, 15.141629819254383], + [1554801134247, 14.871864320006623], + [1554801154247, 14.873766246615965], + [1554801174247, 15.345995441924948], + [1554801194247, 15.557439903201686], + [1554801214247, 15.555467370168937], + [1554801234247, 15.767954832856459], + [1554801254247, 15.724226707330411], + [1554801274247, 16.141832734977395], + [1554801294247, 16.078874676735936], + [1554801314247, 16.520447865480683], + [1554801334247, 16.852278087210898], + [1554801354247, 16.391326466348595], + [1554801374247, 16.83676425276248], + [1554801394247, 17.326748972037034], + [1554801414247, 17.48254647807906], + [1554801434247, 17.785717992907795], + [1554801454247, 18.161130877112615], + [1554801474247, 18.65474919931944], + [1554801494247, 19.151368669051376], + [1554801514247, 19.51103704690084], + [1554801534247, 19.244060870618746], + [1554801554247, 18.762678457259234], + [1554801574247, 18.283405636211146], + [1554801594247, 18.2058134112917], + [1554801614247, 18.318742517158878], + [1554801634247, 18.125680497027943], + [1554801654247, 17.859067813647037], + [1554801674247, 17.430770939942565], + [1554801694247, 17.48829025285418], + [1554801714247, 17.41660346714896], + [1554801734247, 17.267176902967986], + [1554801754247, 17.134933206965716], + [1554801774247, 16.859998621613713], + [1554801794247, 17.17957666828268], + [1554801814247, 16.85553452636789], + [1554801834247, 17.021618900377234], + [1554801854247, 17.28697744467099], + [1554801874247, 17.026404541469358], + [1554801894247, 16.74964033058671], + [1554801914247, 17.1446553563511], + [1554801934247, 16.686389751463988], + [1554801954247, 16.813825821001483], + [1554801974247, 17.00642230043151], + [1554801994247, 17.377258024761936], + [1554802014247, 17.50127422146399], + [1554802034247, 17.3817548239187], + [1554802054247, 17.291601771428926], + [1554802074247, 17.221925959717726], + [1554802094247, 17.465156492221098], + [1554802114247, 17.356019827095594], + [1554802134247, 17.779054323656062], + [1554802154247, 18.062316118223833], + [1554802174247, 17.86066337329332], + [1554802194247, 17.642699917109947], + [1554802214247, 17.889975752328375], + [1554802234247, 18.20280332736594], + [1554802254247, 18.128064273603368], + [1554802274247, 18.341350547206854], + [1554802294247, 18.69202676598315], + [1554802314247, 18.997182695237544], + [1554802334247, 18.56932097324519], + [1554802354247, 18.127417617110936], + [1554802374247, 18.563591061578247], + [1554802394247, 18.935739093711494], + [1554802414247, 19.328009657837754], + [1554802434247, 19.051987873183407], + [1554802454247, 19.302136515646502], + [1554802474247, 19.444975293950954], + [1554802494247, 19.056805905772976], + [1554802514247, 19.489349320985696], + [1554802534247, 19.029312427568964], + [1554802554247, 18.66188283167321], + [1554802574247, 18.966232924802636], + [1554802594247, 19.333887271116005], + [1554802614247, 19.49454092865925], + [1554802634247, 19.225058732253423], + [1554802654247, 19.50584851870466], + [1554802674247, 19.825454559183065], + [1554802694247, 20.275633981781876], + [1554802714247, 19.796255964131436], + [1554802734247, 19.51553115747093], + [1554802754247, 19.168722344585948], + [1554802774247, 19.259002618369472], + [1554802794247, 19.41016958266869], + [1554802814247, 19.907823260030593], + [1554802834247, 19.631151352570843], + [1554802854247, 19.905244374936135], + [1554802874247, 19.798764882824536], + [1554802894247, 20.008082677788725], + [1554802914247, 20.064688572823655], + [1554802934247, 19.98935102323253], + [1554802954247, 19.685443067226227], + [1554802974247, 19.24365424044176], + [1554802994247, 19.67853298503077], + [1554803014247, 19.417464736403762], + [1554803034247, 19.099948997361082], + [1554803054247, 18.884888830958005], + [1554803074247, 19.275934909503246], + [1554803094247, 18.857388403280176], + [1554803114247, 18.395179891885807], + [1554803134247, 18.15582485375867], + [1554803154247, 18.461384373169917], + [1554803174247, 18.65820855056107], + [1554803194247, 18.780890910474543], + [1554803214247, 18.934246513949834], + [1554803234247, 18.940994398971654], + [1554803254247, 18.80380381297457], + [1554803274247, 18.975556784451616], + [1554803294247, 18.937892650321324], + [1554803314247, 19.208253250171996], + [1554803334247, 19.479912061820972], + [1554803354247, 19.817839292405047], + [1554803374247, 20.216166795485478], + [1554803394247, 20.037801151725823], + [1554803414247, 20.089230983878828], + [1554803434247, 19.82509936207721], + [1554803454247, 19.499858580193926], + [1554803474247, 19.241945160507615], + [1554803494247, 19.59878062745236], + [1554803514247, 19.769393970536225], + [1554803534247, 19.618300805498567], + [1554803554247, 19.308561167940837], + [1554803574247, 18.97105055750365], + [1554803594247, 18.686699428116373], + [1554803614247, 18.678264142459323], + [1554803634247, 18.20910669657999], + [1554803654247, 18.400110408790137], + [1554803674247, 18.586150272944028], + [1554803694247, 19.04398053920283], + [1554803714247, 18.668729219956465], + [1554803734247, 18.98873600767759], + [1554803754247, 18.56574832702069], + [1554803774247, 18.79337113112325], + [1554803794247, 18.593258461416298], + [1554803814247, 18.800523265764216], + [1554803834247, 18.65948976366625], + [1554803854247, 18.817685852229896], + [1554803874247, 18.340420869044404], + [1554803894247, 18.18299492246204], + [1554803914247, 17.802066447573516], + [1554803934247, 18.071547724968173], + [1554803954247, 18.049751013799774], + [1554803974247, 17.87610824900385], + [1554803994247, 18.070504699571167], + [1554804014247, 18.27223858743518], + [1554804034247, 18.659606010902305], + [1554804054247, 18.39868265036132], + [1554804074247, 18.629068097966716], + [1554804094247, 18.337125728095433], + [1554804114247, 17.95304427209962], + [1554804134247, 18.116835197158572], + [1554804154247, 17.98894688777001], + [1554804174247, 18.487042644098864], + [1554804194247, 18.48570091736673], + [1554804214247, 18.512055466874543], + [1554804234247, 18.333051234583998], + [1554804254247, 18.603280990702924], + [1554804274247, 18.40207930027631], + [1554804294247, 17.971588925595327], + [1554804314247, 17.961758137993403], + [1554804334247, 18.226019089234516], + [1554804354247, 18.35225998608116], + [1554804374247, 18.639765096991333], + [1554804394247, 18.550712331665693], + [1554804414247, 18.692217967806517], + [1554804434247, 19.050071777223973], + [1554804454247, 19.37757827497838], + [1554804474247, 19.256081586202434], + [1554804494247, 18.90425477864548], + [1554804514247, 19.328915814839135], + [1554804534247, 18.94477208210245], + [1554804554247, 18.921554398027723], + [1554804574247, 19.2492095289071], + [1554804594247, 19.24109165045788], + [1554804614247, 19.204718006137995], + [1554804634247, 19.314243927875925], + [1554804654247, 18.93826896103946], + [1554804674247, 18.843386261025607], + [1554804694247, 18.452727827390838], + [1554804714247, 18.31288494939828], + [1554804734247, 18.28969441044889], + [1554804754247, 18.279839879184603], + [1554804774247, 17.89656879221293], + [1554804794247, 17.779765781544064], + [1554804814247, 18.047473543757114], + [1554804834247, 17.581871597783845], + [1554804854247, 17.788255131385938], + [1554804874247, 17.73564406039265], + [1554804894247, 17.85521468570053], + [1554804914247, 17.93504986472167], + [1554804934247, 18.346975101938344], + [1554804954247, 18.746813567661178], + [1554804974247, 18.513442964330473], + [1554804994247, 18.689558935359702], + [1554805014247, 18.81268731017689], + [1554805034247, 18.92030204915925], + [1554805054247, 18.525588855553714], + [1554805074247, 18.39543929691923], + [1554805094247, 18.724426995823144], + [1554805114247, 18.97615036082755], + [1554805134247, 18.735574008669783], + [1554805154247, 18.51218532804814], + [1554805174247, 19.002998946420824], + [1554805194247, 19.327807288989124], + [1554805214247, 19.782716047902646], + [1554805234247, 20.0799405830441], + [1554805254247, 20.309169195940356], + [1554805274247, 20.424494554101425], + [1554805294247, 20.016587666434507], + [1554805314247, 19.90717258752296], + [1554805334247, 19.705116713312897], + [1554805354247, 19.714068611166343], + [1554805374247, 20.092668883679085], + [1554805394247, 20.45876075819353], + [1554805414247, 19.978968126702078], + [1554805434247, 19.726344289707207], + [1554805454247, 19.67555531958179], + [1554805474247, 19.248318379398153], + [1554805494247, 18.92559402301265], + [1554805514247, 19.05593780593657], + [1554805534247, 18.78655877710173], + [1554805554247, 18.72278713083883], + [1554805574247, 18.883178543004075], + [1554805594247, 19.214524420867246], + [1554805614247, 19.312331181864046], + [1554805634247, 19.36197097685949], + [1554805654247, 18.873989064227487], + [1554805674247, 18.50980867117683], + [1554805694247, 18.591329413843802], + [1554805714247, 18.440948189650424], + [1554805734247, 18.49790909406058], + [1554805754247, 18.22993181565998], + [1554805774247, 18.176792706963482], + [1554805794247, 17.742068630596], + [1554805814247, 18.127171061480716], + [1554805834247, 18.61113434280144], + [1554805854247, 18.266545971249155], + [1554805874247, 18.051239661919034], + [1554805894247, 18.121586360148203], + [1554805914247, 18.28596328116828], + [1554805934247, 18.03804682147004], + [1554805954247, 17.92912894978805], + [1554805974247, 18.248018885767422], + [1554805994247, 18.170264071486372], + [1554806014247, 18.047108689907635], + [1554806034247, 18.379913022515094], + [1554806054247, 18.7985656956094], + [1554806074247, 18.456930330499166], + [1554806094247, 18.295835868859527], + [1554806114247, 18.19306037002757], + [1554806134247, 17.93075614519311], + [1554806154247, 17.57537661265962], + [1554806174247, 17.182737877206783], + [1554806194247, 17.608978667641548], + [1554806214247, 18.091581656416327], + [1554806234247, 18.12482889513131], + [1554806254247, 18.185538016887143], + [1554806274247, 18.20938728273094], + [1554806294247, 18.57802459483719], + [1554806314247, 18.27478663407127], + [1554806334247, 18.18309828031528], + [1554806354247, 18.046046587405275], + [1554806374247, 18.110230133577016], + [1554806394247, 17.77958854513807], + [1554806414247, 17.755552238573014], + [1554806434247, 17.93407403212182], + [1554806454247, 17.834950651753168], + [1554806474247, 17.62487333533259], + [1554806494247, 17.818756369279967], + [1554806514247, 17.39004492227227], + [1554806534247, 17.193372581431124], + [1554806554247, 16.95129928749413], + [1554806574247, 17.016306124404707], + [1554806594247, 16.712795603328992], + [1554806614247, 16.53416774729842], + [1554806634247, 16.291809836675082], + [1554806654247, 16.672668617071984], + [1554806674247, 16.873191162896983], + [1554806694247, 17.10782981368545], + [1554806714247, 16.727951485311106], + [1554806734247, 16.96036493957726], + [1554806754247, 16.514521429647758], + [1554806774247, 16.084114804956304], + [1554806794247, 16.30254578168955], + [1554806814247, 16.534087254833164], + [1554806834247, 16.056953526055793], + [1554806854247, 15.584026045940329], + [1554806874247, 15.618649451155633], + [1554806894247, 15.538672348396526], + [1554806914247, 15.9620988253781], + [1554806934247, 16.43967579598588], + [1554806954247, 16.828110677544426], + [1554806974247, 16.71539758814196], + [1554806994247, 16.91114798772243], + [1554807014247, 16.970190521091418], + [1554807034247, 16.652980147832025], + [1554807054247, 16.95589224983671], + [1554807074247, 17.42272242301996], + [1554807094247, 17.268816017793508], + [1554807114247, 16.977764959290678], + [1554807134247, 17.30696723687278], + [1554807154247, 16.809909430780127], + [1554807174247, 16.834741273519402], + [1554807194247, 16.417886405774215], + [1554807214247, 16.762016909140442], + [1554807234247, 17.138100938886808], + [1554807254247, 17.230621881528826], + [1554807274247, 17.72741817378255], + [1554807294247, 17.776039783674833], + [1554807314247, 17.87884125809111], + [1554807334247, 17.38224934430264], + [1554807354247, 17.412052920655796], + [1554807374247, 17.75691024403483], + [1554807394247, 17.435099479511265], + [1554807414247, 17.151274766857476], + [1554807434247, 17.593342658698447], + [1554807454247, 17.334653653522253], + [1554807474247, 17.476092698124962], + [1554807494247, 17.630521911429216], + [1554807514247, 18.00861111106235], + [1554807534247, 18.17241595532054], + [1554807554247, 18.19384073799544], + [1554807574247, 18.17660445507042], + [1554807594247, 17.889367920812205], + [1554807614247, 17.662933364897235], + [1554807634247, 17.996510291559048], + [1554807654247, 18.10999075434485], + [1554807674247, 18.08907251127535], + [1554807694247, 18.362301935391887], + [1554807714247, 18.73675882969702], + [1554807734247, 18.777242368554464], + [1554807754247, 18.728454583314942], + [1554807774247, 18.67778255980091], + [1554807794247, 18.21170532262526], + [1554807814247, 18.336013592669683], + [1554807834247, 18.486708118751547], + [1554807854247, 18.481457501410357], + [1554807874247, 18.580337444483], + [1554807894247, 18.585776154287643], + [1554807914247, 18.277367743334594], + [1554807934247, 18.4748120256564], + [1554807954247, 17.988109644084805], + [1554807974247, 17.50359155734211], + [1554807994247, 17.054621181707596], + [1554808014247, 17.050657416275882], + [1554808034247, 17.53340725427101], + [1554808054247, 17.2823824698551], + [1554808074247, 17.230073631700503], + [1554808094247, 17.660070302810404], + [1554808114247, 17.715497232909698], + [1554808134247, 17.89362479407042], + [1554808154247, 17.675851255462916], + [1554808174247, 17.798916844787072], + [1554808194247, 17.370179004365454], + [1554808214247, 16.935754571814158], + [1554808234247, 16.575111172315417], + [1554808254247, 16.957750516945833], + [1554808274247, 16.906067262875016], + [1554808294247, 17.147262833877377], + [1554808314247, 17.25481302019992], + [1554808334247, 16.88623932971355], + [1554808354247, 16.826900270442206], + [1554808374247, 17.10501975118114], + [1554808394247, 17.374628130862487], + [1554808414247, 17.07918741156744], + [1554808434247, 17.23444863499987], + [1554808454247, 16.810847489538], + [1554808474247, 17.113170666785386], + [1554808494247, 17.034661603204178], + [1554808514247, 17.423832159197985], + [1554808534247, 17.384975048960655], + [1554808554247, 17.157321849221564], + [1554808574247, 17.295587749135628], + [1554808594247, 17.422000437377765], + [1554808614247, 17.569027537150625], + [1554808634247, 17.802041495838445], + [1554808654247, 17.424428662815046], + [1554808674247, 17.75925159046375], + [1554808694247, 18.22257416365628], + [1554808714247, 18.044788857332886], + [1554808734247, 17.71747698325384], + [1554808754247, 17.42290034812379], + [1554808774247, 17.78912609157009], + [1554808794247, 18.1697394821823], + [1554808814247, 18.370976940779865], + [1554808834247, 18.29486854059247], + [1554808854247, 18.551667794503583], + [1554808874247, 18.39828137765856], + [1554808894247, 18.25110684354119], + [1554808914247, 18.424132243647602], + [1554808934247, 17.928269310694105], + [1554808954247, 17.891590956635838], + [1554808974247, 17.615722459795265], + [1554808994247, 17.772868766141663], + [1554809014247, 17.3521751556697], + [1554809034247, 17.11182923580405], + [1554809054247, 17.305146213842725], + [1554809074247, 17.492812063228744], + [1554809094247, 17.15927912926812], + [1554809114247, 17.121703806114706], + [1554809134247, 17.501420046950543], + [1554809154247, 17.66727157116937], + [1554809174247, 17.812646877248156], + [1554809194247, 17.8037690723927], + [1554809214247, 17.80162956689889], + [1554809234247, 18.086665506982694], + [1554809254247, 18.03889674669137], + [1554809274247, 17.677390098732573], + [1554809294247, 17.879679198772564], + [1554809314247, 17.584585692982444], + [1554809334247, 17.959637391545176], + [1554809354247, 17.70704655382901], + [1554809374247, 17.689773136209624], + [1554809394247, 17.647501038318484], + [1554809414247, 18.10854669233636], + [1554809434247, 18.547843706612916], + [1554809454247, 18.982618348379056], + [1554809474247, 19.409752361871913], + [1554809494247, 19.86787108134553], + [1554809514247, 19.444411700870905], + [1554809534247, 19.89027726349466], + [1554809554247, 20.055647760029323], + [1554809574247, 20.332601604977377], + [1554809594247, 20.186961614150928], + [1554809614247, 19.91407780746553], + [1554809634247, 20.06820779595699], + [1554809654247, 20.080243472499426], + [1554809674247, 19.87713706193293], + [1554809694247, 19.681250241399276], + [1554809714247, 19.407668380876753], + [1554809734247, 19.56481750238362], + [1554809754247, 20.004756709116997], + [1554809774247, 19.535665690455815], + [1554809794247, 19.703425271030913], + [1554809814247, 19.54778722171676], + [1554809834247, 19.420410850783384], + [1554809854247, 19.753529304758832], + [1554809874247, 19.982263471778904], + [1554809894247, 19.648157753690636], + [1554809914247, 19.603042433340118], + [1554809934247, 19.589539301758066], + [1554809954247, 19.754466582431032], + [1554809974247, 20.200289417535693], + [1554809994247, 20.535073990717688], + [1554810014247, 20.96943211675856], + [1554810034247, 20.603270299687363], + [1554810054247, 20.173587705343607], + [1554810074247, 20.49979443254795], + [1554810094247, 20.822318394334552], + [1554810114247, 20.71823032496461], + [1554810134247, 20.536638182114388], + [1554810154247, 20.16245224098834], + [1554810174247, 20.365654035559984], + [1554810194247, 20.750786422033908], + [1554810214247, 20.88604429258989], + [1554810234247, 21.18290294700043], + [1554810254247, 20.96277049586266], + [1554810274247, 21.32180279162898], + [1554810294247, 21.0016299534294], + [1554810314247, 20.515341526228337], + [1554810334247, 20.780007817271024], + [1554810354247, 21.129163926216105], + [1554810374247, 20.934017218027808], + [1554810394247, 20.510533890923607], + [1554810414247, 20.960082138122935], + [1554810434247, 20.872825242653533], + [1554810454247, 21.292186812890325], + [1554810474247, 21.760142931714785], + [1554810494247, 21.306646483526887], + [1554810514247, 21.437367768346387], + [1554810534247, 20.988811366969344], + [1554810554247, 20.826107468511292], + [1554810574247, 21.013480581344588], + [1554810594247, 20.878538074356992], + [1554810614247, 20.445714347430684], + [1554810634247, 20.54588524875655], + [1554810654247, 20.934978857440115], + [1554810674247, 21.105549392177977], + [1554810694247, 21.207736265000023], + [1554810714247, 21.203503866950733], + [1554810734247, 21.429431725814705], + [1554810754247, 21.89991339950124], + [1554810774247, 21.83477989894718], + [1554810794247, 21.78661197761795], + [1554810814247, 22.13335025227573], + [1554810834247, 22.516701040475297], + [1554810854247, 22.310167692370655], + [1554810874247, 22.475935327328717], + [1554810894247, 22.8694841788587], + [1554810914247, 23.05226599324635], + [1554810934247, 22.866715279406986], + [1554810954247, 23.008981145929027], + [1554810974247, 23.36750693188728], + [1554810994247, 23.48291778171053], + [1554811014247, 23.240930373151908], + [1554811034247, 22.944886738114114], + [1554811054247, 23.158830075240527], + [1554811074247, 23.596562495280565], + [1554811094247, 23.40021118468351], + [1554811114247, 23.42527640835139], + [1554811134247, 23.881198207863623], + [1554811154247, 24.090637642151574], + [1554811174247, 23.847092938048707], + [1554811194247, 23.572586528171684], + [1554811214247, 24.047730244352827], + [1554811234247, 24.28476980671547], + [1554811254247, 24.220078573950836], + [1554811274247, 24.46310903911906], + [1554811294247, 24.466900483058215], + [1554811314247, 24.792636886264376], + [1554811334247, 24.409607815763987], + [1554811354247, 24.04038145482966], + [1554811374247, 23.94228680381039], + [1554811394247, 23.783424741152356], + [1554811414247, 23.99408856096362], + [1554811434247, 24.47637383884743], + [1554811454247, 24.51629571775181], + [1554811474247, 24.22032496180614], + [1554811494247, 23.999718831088842], + [1554811514247, 24.05610267025658], + [1554811534247, 24.266273560654778], + [1554811554247, 23.899707062230714], + [1554811574247, 23.449757627088108], + [1554811594247, 23.275400835189835], + [1554811614247, 23.358131530933832], + [1554811634247, 22.858872033834462], + [1554811654247, 23.155103991605444], + [1554811674247, 23.546405884685615], + [1554811694247, 23.90917052403149], + [1554811714247, 23.543095226841295], + [1554811734247, 23.06667148021866], + [1554811754247, 23.299268579089393], + [1554811774247, 22.996973766301174], + [1554811794247, 22.96294946666954], + [1554811814247, 23.416145844721562], + [1554811834247, 23.445491666152734], + [1554811854247, 23.78238653255798], + [1554811874247, 23.970608966935178], + [1554811894247, 24.0777671680898], + [1554811914247, 24.00485380564156], + [1554811934247, 23.615361773996426], + [1554811954247, 24.042391986475458], + [1554811974247, 24.30736210082872], + [1554811994247, 24.553424136396035], + [1554812014247, 24.071551516329688], + [1554812034247, 23.912887651146793], + [1554812054247, 24.058433667682323], + [1554812074247, 24.38218660318733], + [1554812094247, 23.95689725397855], + [1554812114247, 24.317964378479104], + [1554812134247, 24.5559033202204], + [1554812154247, 24.94115173656496], + [1554812174247, 24.53212651671396], + [1554812194247, 24.33543858114263], + [1554812214247, 24.054507222611196], + [1554812234247, 24.345650133921207], + [1554812254247, 24.146007669420054], + [1554812274247, 23.826238279851083], + [1554812294247, 23.337278633231435], + [1554812314247, 23.391970698372745], + [1554812334247, 23.854408022629926], + [1554812354247, 23.74054371722396], + [1554812374247, 23.358933571121483], + [1554812394247, 23.53597766617613], + [1554812414247, 23.214915406643048], + [1554812434247, 22.72418254100616], + [1554812454247, 23.124702432769304], + [1554812474247, 22.646160083682506], + [1554812494247, 22.95948496526543], + [1554812514247, 22.908704358394786], + [1554812534247, 23.24272599646963], + [1554812554247, 22.948667210920263], + [1554812574247, 22.88147470162189], + [1554812594247, 23.335409408552557], + [1554812614247, 23.2645912633166], + [1554812634247, 23.172964825433606], + [1554812654247, 22.792339297136], + [1554812674247, 22.451584230344984], + [1554812694247, 22.256332645514753], + [1554812714247, 22.70493151367683], + [1554812734247, 23.19120036442452], + [1554812754247, 22.98024145197298], + [1554812774247, 23.310399865487852], + [1554812794247, 23.423185487024213], + [1554812814247, 23.08331352748343], + [1554812834247, 22.843268303245647], + [1554812854247, 22.796590167015605], + [1554812874247, 22.680223263419595], + [1554812894247, 22.1936616130379], + [1554812914247, 22.276714302075444], + [1554812934247, 22.478761618242125], + [1554812954247, 22.744794053235506], + [1554812974247, 22.533635764779486], + [1554812994247, 22.856595863520614], + [1554813014247, 23.297624790450946], + [1554813034247, 23.744759897505187], + [1554813054247, 23.78062656641052], + [1554813074247, 23.670312262657962], + [1554813094247, 23.94875247338334], + [1554813114247, 24.159246180951172], + [1554813134247, 23.729042572247714], + [1554813154247, 23.851976354313422], + [1554813174247, 24.33054746691777], + [1554813194247, 24.431817667166843], + [1554813214247, 24.900251155368196], + [1554813234247, 25.38279196750522], + [1554813254247, 25.122592302324744], + [1554813274247, 25.477135073121406], + [1554813294247, 25.129731525812613], + [1554813314247, 25.09671010378041], + [1554813334247, 24.90703450812979], + [1554813354247, 24.410500684901685], + [1554813374247, 24.81457973663616], + [1554813394247, 24.468295893431918], + [1554813414247, 24.899199539507308], + [1554813434247, 24.85172339452315], + [1554813454247, 25.344326207633387], + [1554813474247, 25.17219266393189], + [1554813494247, 24.693990881064718], + [1554813514247, 24.388403883062693], + [1554813534247, 24.326079764182648], + [1554813554247, 24.411873941334573], + [1554813574247, 24.270472386262604], + [1554813594247, 24.324012831628146], + [1554813614247, 24.099857615227467], + [1554813634247, 24.21954267428356], + [1554813654247, 24.650473211711404], + [1554813674247, 24.44907260287802], + [1554813694247, 24.107046790019766], + [1554813714247, 24.510413313602093], + [1554813734247, 24.88113783461183], + [1554813754247, 24.73526251148137], + [1554813774247, 25.149750677672976], + [1554813794247, 25.014564303805702], + [1554813814247, 25.026928370829886], + [1554813834247, 24.86256716448043], + [1554813854247, 24.560488758456557], + [1554813874247, 24.615621474980536], + [1554813894247, 24.138639002410695], + [1554813914247, 23.940103271596822], + [1554813934247, 24.06200449551318], + [1554813954247, 24.159453290017183], + [1554813974247, 24.214708220020334], + [1554813994247, 23.94120965407676], + [1554814014247, 23.95298355113604], + [1554814034247, 24.314485240167457], + [1554814054247, 24.335912954173985], + [1554814074247, 24.52467444905973], + [1554814094247, 24.385124477285046], + [1554814114247, 24.700108123691678], + [1554814134247, 24.90576677567081], + [1554814154247, 24.99310554556539], + [1554814174247, 25.195800841696705], + [1554814194247, 25.278335915127883], + [1554814214247, 25.414083468286634], + [1554814234247, 25.52353974423631], + [1554814254247, 25.295016469723016], + [1554814274247, 25.419288424881053], + [1554814294247, 25.126441819261434], + [1554814314247, 24.681588328242032], + [1554814334247, 24.810238455213636], + [1554814354247, 24.410339756314695], + [1554814374247, 24.396192731458672], + [1554814394247, 24.325590744742602], + [1554814414247, 24.00219866428782], + [1554814434247, 23.57700638622354], + [1554814454247, 23.539308621824333], + [1554814474247, 23.190753289360014], + [1554814494247, 23.00850401336011], + [1554814514247, 23.137994435866098], + [1554814534247, 23.197808317385363], + [1554814554247, 22.756545947551], + [1554814574247, 22.942948839743238], + [1554814594247, 23.072813723042167], + [1554814614247, 22.881504025565448], + [1554814634247, 23.138709943466257], + [1554814654247, 23.06237555382352], + [1554814674247, 23.10019915491298], + [1554814694247, 23.51780549910103], + [1554814714247, 23.274386831939132], + [1554814734247, 22.780534530293703], + [1554814754247, 22.64347728787694], + [1554814774247, 22.578901585680992], + [1554814794247, 22.73474307069496], + [1554814814247, 22.3017582734572], + [1554814834247, 22.23466427843663], + [1554814854247, 22.21301416289505], + ], + color: '#7EB26D', + info: [ + { title: 'min', text: '14.42', numeric: 14.427101844163694 }, + { title: 'max', text: '18.42', numeric: 18.427101844163694 }, + ], + isVisible: true, + yAxis: 1, + }, + { + label: 'B-series', + data: [ + [1554793274247, 9.248820139157093], + [1554793294247, 9.273875054706759], + [1554793314247, 9.352176032498832], + [1554793334247, 9.807948429334353], + [1554793354247, 9.607817141399979], + [1554793374247, 9.827775726389026], + [1554793394247, 9.386666730812092], + [1554793414247, 9.372938370724333], + [1554793434247, 9.783904274932814], + [1554793454247, 9.93568291865629], + [1554793474247, 10.285904951141445], + [1554793494247, 10.356151800899413], + [1554793514247, 10.087924134921929], + [1554793534247, 10.130969065761265], + [1554793554247, 10.039871466196065], + [1554793574247, 10.48196042129985], + [1554793594247, 10.81294570657847], + [1554793614247, 10.425308181694218], + [1554793634247, 10.630859250874947], + [1554793654247, 10.735866163433657], + [1554793674247, 10.414049311911526], + [1554793694247, 10.443991943278128], + [1554793714247, 10.295408470211672], + [1554793734247, 10.614658355598694], + [1554793754247, 11.076290920269997], + [1554793774247, 11.425597202690865], + [1554793794247, 11.262277454840875], + [1554793814247, 11.17043615581524], + [1554793834247, 11.275581328847908], + [1554793854247, 11.481598266537757], + [1554793874247, 11.953363680050632], + [1554793894247, 12.422008588787305], + [1554793914247, 12.069849672231994], + [1554793934247, 11.943803466171598], + [1554793954247, 12.075583775000037], + [1554793974247, 12.068750850266534], + [1554793994247, 11.936651489454178], + [1554794014247, 11.996688750970568], + [1554794034247, 12.052434480528358], + [1554794054247, 12.536599186046683], + [1554794074247, 13.023718202899493], + [1554794094247, 12.966075141324536], + [1554794114247, 12.540756307703582], + [1554794134247, 12.712148183846951], + [1554794154247, 12.256702310766554], + [1554794174247, 12.212372314019388], + [1554794194247, 12.247568287118359], + [1554794214247, 12.010251845188867], + [1554794234247, 11.7115171988383], + [1554794254247, 11.981072233758754], + [1554794274247, 11.970827657632446], + [1554794294247, 12.117812031971802], + [1554794314247, 11.885655481089309], + [1554794334247, 12.139423837249328], + [1554794354247, 12.629111619970866], + [1554794374247, 12.838751307157468], + [1554794394247, 12.698782425513922], + [1554794414247, 12.228729591764855], + [1554794434247, 12.212858609888734], + [1554794454247, 12.549516160357083], + [1554794474247, 12.12789035051455], + [1554794494247, 11.843341194402191], + [1554794514247, 11.70830817970737], + [1554794534247, 11.512228940393427], + [1554794554247, 11.395363965811262], + [1554794574247, 11.86036846413425], + [1554794594247, 12.06123558542618], + [1554794614247, 12.204159715000836], + [1554794634247, 12.271910337675797], + [1554794654247, 12.282439537392525], + [1554794674247, 12.03790201620353], + [1554794694247, 11.829464424427744], + [1554794714247, 11.493890332653676], + [1554794734247, 11.413793733691467], + [1554794754247, 10.917571315788575], + [1554794774247, 10.502250358068137], + [1554794794247, 10.943314534163564], + [1554794814247, 11.371349945440832], + [1554794834247, 10.976930007662157], + [1554794854247, 10.499203550344646], + [1554794874247, 10.8294008121535], + [1554794894247, 10.830849260190083], + [1554794914247, 10.679528914904482], + [1554794934247, 11.124683240448253], + [1554794954247, 11.007602273533589], + [1554794974247, 11.291030570800228], + [1554794994247, 10.878973316312234], + [1554795014247, 10.783601804652415], + [1554795034247, 10.433756430969048], + [1554795054247, 10.024756414413504], + [1554795074247, 9.73857829270142], + [1554795094247, 9.501542190816165], + [1554795114247, 9.236454777535167], + [1554795134247, 9.703872577154437], + [1554795154247, 9.63913490209853], + [1554795174247, 9.380130864761673], + [1554795194247, 9.416021803813496], + [1554795214247, 9.427268719159454], + [1554795234247, 9.628563778508864], + [1554795254247, 9.31603705937074], + [1554795274247, 9.288467805990262], + [1554795294247, 9.445118597922967], + [1554795314247, 9.532344831308432], + [1554795334247, 9.079446462804185], + [1554795354247, 9.170228355998656], + [1554795374247, 9.086782779998948], + [1554795394247, 9.167605210581598], + [1554795414247, 9.337226400746871], + [1554795434247, 9.742704787869735], + [1554795454247, 9.737360720830406], + [1554795474247, 10.137939689453866], + [1554795494247, 9.700801043894144], + [1554795514247, 9.768218908003545], + [1554795534247, 9.75411192216661], + [1554795554247, 9.995651363958773], + [1554795574247, 9.6245144237306], + [1554795594247, 9.226961963966849], + [1554795614247, 8.753654850082542], + [1554795634247, 8.47135391545531], + [1554795654247, 8.183973941349567], + [1554795674247, 8.489931082159094], + [1554795694247, 8.817639566937025], + [1554795714247, 8.830670343882522], + [1554795734247, 8.892737303950666], + [1554795754247, 8.617048476643877], + [1554795774247, 8.956785057895], + [1554795794247, 8.9991406285351], + [1554795814247, 9.356203855221441], + [1554795834247, 8.99285081187721], + [1554795854247, 8.808922317600796], + [1554795874247, 9.159210350271348], + [1554795894247, 9.18335663931962], + [1554795914247, 9.373201449339069], + [1554795934247, 8.968119468638838], + [1554795954247, 8.576562756144522], + [1554795974247, 8.2637127277604], + [1554795994247, 8.721724203921328], + [1554796014247, 8.48997462768482], + [1554796034247, 8.923630475037411], + [1554796054247, 8.87332008767114], + [1554796074247, 8.600691352718252], + [1554796094247, 9.06144030038234], + [1554796114247, 8.573016788676542], + [1554796134247, 8.681863903053694], + [1554796154247, 8.286195763190447], + [1554796174247, 8.760651174815626], + [1554796194247, 8.782606476359007], + [1554796214247, 8.9825170729235], + [1554796234247, 8.50917994378477], + [1554796254247, 8.745078588661844], + [1554796274247, 8.486368444624361], + [1554796294247, 8.333749227691007], + [1554796314247, 7.920482802236725], + [1554796334247, 8.118910114704367], + [1554796354247, 8.052092789154447], + [1554796374247, 8.541526055677005], + [1554796394247, 8.532160960797517], + [1554796414247, 8.035315149728314], + [1554796434247, 8.153626777679058], + [1554796454247, 8.33489217274486], + [1554796474247, 8.63434786317988], + [1554796494247, 9.072612270861644], + [1554796514247, 9.15410238978958], + [1554796534247, 9.33569542338061], + [1554796554247, 9.617098624150145], + [1554796574247, 10.022020147992146], + [1554796594247, 9.96942587043037], + [1554796614247, 9.638489822305603], + [1554796634247, 9.148499945010006], + [1554796654247, 9.046323760634762], + [1554796674247, 8.950899003863489], + [1554796694247, 9.011582132180608], + [1554796714247, 8.821456019019363], + [1554796734247, 9.317497357412448], + [1554796754247, 8.933797285164973], + [1554796774247, 8.696553723087488], + [1554796794247, 8.486435423924668], + [1554796814247, 8.143914447574895], + [1554796834247, 7.663636585550101], + [1554796854247, 7.512977158724448], + [1554796874247, 7.3230605728686795], + [1554796894247, 6.9231107879457525], + [1554796914247, 6.982154895754249], + [1554796934247, 6.736558799914573], + [1554796954247, 6.62557430103121], + [1554796974247, 6.5962473119591], + [1554796994247, 7.009103777604233], + [1554797014247, 7.181557924155956], + [1554797034247, 7.354116677397331], + [1554797054247, 7.12587782475754], + [1554797074247, 6.866345342957572], + [1554797094247, 7.17115251468767], + [1554797114247, 6.722184764408618], + [1554797134247, 6.696153361069251], + [1554797154247, 6.9207740284137085], + [1554797174247, 6.6119572131599895], + [1554797194247, 6.815945935756884], + [1554797214247, 6.95466363827982], + [1554797234247, 6.7285014667012355], + [1554797254247, 6.891818567042779], + [1554797274247, 6.89849891212912], + [1554797294247, 6.460695100547899], + [1554797314247, 6.0069925335796475], + [1554797334247, 6.2603322238560875], + [1554797354247, 5.977611497076692], + [1554797374247, 5.942359898081184], + [1554797394247, 5.562777480448413], + [1554797414247, 5.841398822617335], + [1554797434247, 5.818365744964448], + [1554797454247, 5.571787499171553], + [1554797474247, 5.579907439786188], + [1554797494247, 5.398124887811567], + [1554797514247, 5.89247467855865], + [1554797534247, 6.109491399069382], + [1554797554247, 5.695647549392316], + [1554797574247, 5.869284172406741], + [1554797594247, 5.690239022459525], + [1554797614247, 5.450820783424683], + [1554797634247, 5.524583351054576], + [1554797654247, 5.36614969566086], + [1554797674247, 5.204730683218754], + [1554797694247, 5.427484119934154], + [1554797714247, 5.565761968712947], + [1554797734247, 5.932909999509335], + [1554797754247, 5.610458298441828], + [1554797774247, 5.324880465000206], + [1554797794247, 5.419125551757933], + [1554797814247, 5.02300209165337], + [1554797834247, 5.352305000126072], + [1554797854247, 5.36748698339688], + [1554797874247, 5.258792248781452], + [1554797894247, 5.125077286167015], + [1554797914247, 5.492028016529195], + [1554797934247, 5.568653332100721], + [1554797954247, 5.825704870348322], + [1554797974247, 6.27267451715126], + [1554797994247, 6.001452331648551], + [1554798014247, 5.8585768410841235], + [1554798034247, 5.819201114148435], + [1554798054247, 5.618753511565376], + [1554798074247, 5.24963457018995], + [1554798094247, 5.50387501275164], + [1554798114247, 5.84091043365146], + [1554798134247, 5.821629272116871], + [1554798154247, 6.210352707759286], + [1554798174247, 5.952483297516694], + [1554798194247, 6.031666355548255], + [1554798214247, 5.790748754808771], + [1554798234247, 5.594603662397832], + [1554798254247, 5.811695120800497], + [1554798274247, 5.837715057496696], + [1554798294247, 6.249345269583456], + [1554798314247, 5.934712699943487], + [1554798334247, 6.376771693988007], + [1554798354247, 6.1569566425758575], + [1554798374247, 5.711519906753933], + [1554798394247, 5.869137479992213], + [1554798414247, 5.560979081989093], + [1554798434247, 5.882435554334975], + [1554798454247, 5.388570139180064], + [1554798474247, 5.659073675509275], + [1554798494247, 5.5982211991238415], + [1554798514247, 5.6870744245787845], + [1554798534247, 5.600717003335124], + [1554798554247, 5.340048174168355], + [1554798574247, 5.095171088107014], + [1554798594247, 5.405805007535655], + [1554798614247, 5.271225976449607], + [1554798634247, 4.982494391562869], + [1554798654247, 5.2276371554858265], + [1554798674247, 5.442769621857752], + [1554798694247, 5.044185095640327], + [1554798714247, 4.79576810257608], + [1554798734247, 4.453831871416357], + [1554798754247, 4.618243387467281], + [1554798774247, 4.57918370988977], + [1554798794247, 4.4832728526544745], + [1554798814247, 4.024337135722461], + [1554798834247, 3.851845907034049], + [1554798854247, 3.542163220970746], + [1554798874247, 3.9690958016116413], + [1554798894247, 4.158181186559055], + [1554798914247, 4.079568209963879], + [1554798934247, 3.634349523153986], + [1554798954247, 3.3598575963443826], + [1554798974247, 3.16802878490503], + [1554798994247, 3.0133968929917563], + [1554799014247, 3.3437902298453492], + [1554799034247, 3.675049740942942], + [1554799054247, 3.467177915984232], + [1554799074247, 3.3518871913608907], + [1554799094247, 3.1957041604556817], + [1554799114247, 2.927123761256529], + [1554799134247, 3.2523340095072575], + [1554799154247, 3.015290599561679], + [1554799174247, 2.9890942934718248], + [1554799194247, 3.143167106507552], + [1554799214247, 3.5681222443713825], + [1554799234247, 3.2396432204688566], + [1554799254247, 3.24901159853982], + [1554799274247, 3.3625936643841348], + [1554799294247, 3.3339645808631038], + [1554799314247, 3.4205949091651635], + [1554799334247, 3.593597781275604], + [1554799354247, 3.4861342898248844], + [1554799374247, 3.8907746271806865], + [1554799394247, 4.111342004275268], + [1554799414247, 4.5959477789863366], + [1554799434247, 4.262140254359245], + [1554799454247, 4.134177960593391], + [1554799474247, 3.6575368680515146], + [1554799494247, 3.937414100768729], + [1554799514247, 4.078725112633452], + [1554799534247, 3.6451505190142974], + [1554799554247, 3.8471615375736894], + [1554799574247, 4.115159681991413], + [1554799594247, 3.9742174075208], + [1554799614247, 3.685238600479783], + [1554799634247, 4.103333280579201], + [1554799654247, 4.018245494514007], + [1554799674247, 4.446780576803342], + [1554799694247, 4.793421236531562], + [1554799714247, 4.554098466596562], + [1554799734247, 5.0417143849764425], + [1554799754247, 4.973734699399603], + [1554799774247, 5.193809388225786], + [1554799794247, 5.586071096427007], + [1554799814247, 5.778047979188536], + [1554799834247, 6.192594160585018], + [1554799854247, 5.927676977504205], + [1554799874247, 5.7050333060389695], + [1554799894247, 6.1718889065434634], + [1554799914247, 5.966274359120673], + [1554799934247, 5.5056942032960166], + [1554799954247, 5.160442061460211], + [1554799974247, 4.96018368853153], + [1554799994247, 4.691238462710444], + [1554800014247, 4.532492207601897], + [1554800034247, 4.07403732545026], + [1554800054247, 4.220770009365944], + [1554800074247, 4.0290646354752635], + [1554800094247, 3.8737691030982773], + [1554800114247, 4.238658311988558], + [1554800134247, 4.221236984114166], + [1554800154247, 4.6669293808298375], + [1554800174247, 4.843390965598206], + [1554800194247, 5.060187956069504], + [1554800214247, 4.868078717632957], + [1554800234247, 4.679175899389998], + [1554800254247, 4.777389196992289], + [1554800274247, 5.145363815002936], + [1554800294247, 4.781852858563081], + [1554800314247, 4.971885981820668], + [1554800334247, 4.646445194315369], + [1554800354247, 5.020517271418575], + [1554800374247, 4.7023153801084225], + [1554800394247, 4.876203872672264], + [1554800414247, 4.925017187669332], + [1554800434247, 4.663330164585453], + [1554800454247, 4.737769778515407], + [1554800474247, 4.3073255475285555], + [1554800494247, 3.903303008949847], + [1554800514247, 4.329087565576297], + [1554800534247, 3.977267569905087], + [1554800554247, 3.8740696509486328], + [1554800574247, 3.6792996433238767], + [1554800594247, 3.972924170147491], + [1554800614247, 3.7307104568781844], + [1554800634247, 3.867618350171384], + [1554800654247, 4.221917146372988], + [1554800674247, 4.3589781205929], + [1554800694247, 4.263474974513956], + [1554800714247, 3.836080641849756], + [1554800734247, 3.443769259025261], + [1554800754247, 3.3972787053685054], + [1554800774247, 3.7643724876162024], + [1554800794247, 4.170506414878274], + [1554800814247, 4.441115469873164], + [1554800834247, 4.371381466672394], + [1554800854247, 4.2168701882977295], + [1554800874247, 4.235647487408082], + [1554800894247, 3.9459791663426493], + [1554800914247, 4.017151772199118], + [1554800934247, 4.367278988021905], + [1554800954247, 4.342883446992778], + [1554800974247, 4.329662881338973], + [1554800994247, 4.283512014326498], + [1554801014247, 4.5715550079307645], + [1554801034247, 4.8311393525220945], + [1554801054247, 4.485030491381298], + [1554801074247, 4.34901947903894], + [1554801094247, 4.754091657245004], + [1554801114247, 4.8317691761516635], + [1554801134247, 5.204902590034885], + [1554801154247, 5.132781976138682], + [1554801174247, 5.034906361767744], + [1554801194247, 4.767543856573749], + [1554801214247, 5.094041902089003], + [1554801234247, 4.929546085290836], + [1554801254247, 4.697829514794909], + [1554801274247, 5.025374415167784], + [1554801294247, 5.096887871269491], + [1554801314247, 5.364791455663852], + [1554801334247, 5.561817762992182], + [1554801354247, 5.469314840240021], + [1554801374247, 5.95262630215483], + [1554801394247, 6.213311956412662], + [1554801414247, 5.715304095448328], + [1554801434247, 6.08523588145186], + [1554801454247, 5.857696643773423], + [1554801474247, 6.268212934058957], + [1554801494247, 6.5603945673698], + [1554801514247, 6.125495029826776], + [1554801534247, 6.110878886315267], + [1554801554247, 5.775584198941563], + [1554801574247, 6.001104024362178], + [1554801594247, 6.0476835587793705], + [1554801614247, 5.955664108319069], + [1554801634247, 5.955184743612128], + [1554801654247, 5.9330618446677414], + [1554801674247, 5.8424118755201855], + [1554801694247, 5.62553004227859], + [1554801714247, 6.0869222772786395], + [1554801734247, 5.803355849422298], + [1554801754247, 5.387776194851587], + [1554801774247, 5.40981853663267], + [1554801794247, 5.351276314268535], + [1554801814247, 5.788250432657768], + [1554801834247, 6.103420102386489], + [1554801854247, 6.211025937006611], + [1554801874247, 5.8449537650030825], + [1554801894247, 5.365170741934555], + [1554801914247, 4.887574074342324], + [1554801934247, 4.897693584588608], + [1554801954247, 5.318243270154184], + [1554801974247, 4.9396552679865025], + [1554801994247, 5.32527754563186], + [1554802014247, 4.886074615684099], + [1554802034247, 4.847379978693837], + [1554802054247, 4.504956591530262], + [1554802074247, 4.307454044981433], + [1554802094247, 3.867514732543229], + [1554802114247, 4.090981756269712], + [1554802134247, 4.578287695242435], + [1554802154247, 4.639711326460183], + [1554802174247, 4.245817171069919], + [1554802194247, 4.633160402543173], + [1554802214247, 5.112224969641575], + [1554802234247, 5.0089061103738945], + [1554802254247, 4.822364064588437], + [1554802274247, 5.212440187109399], + [1554802294247, 4.929867512296385], + [1554802314247, 5.245183785839094], + [1554802334247, 5.008463578035349], + [1554802354247, 5.1378596145435935], + [1554802374247, 5.033798588757337], + [1554802394247, 5.476424927294858], + [1554802414247, 5.972598629566117], + [1554802434247, 6.370358278045766], + [1554802454247, 5.893722712045765], + [1554802474247, 5.993207071076519], + [1554802494247, 5.930735938671154], + [1554802514247, 5.448559121598676], + [1554802534247, 5.207154019732361], + [1554802554247, 4.853297326279563], + [1554802574247, 5.04987559253604], + [1554802594247, 4.847597124701677], + [1554802614247, 5.146928442837356], + [1554802634247, 4.699178752084193], + [1554802654247, 4.6411064164223825], + [1554802674247, 4.665072535289884], + [1554802694247, 4.529885143860296], + [1554802714247, 4.996514308630512], + [1554802734247, 5.22226329518542], + [1554802754247, 5.575602394914211], + [1554802774247, 5.422257363444805], + [1554802794247, 5.855827664152273], + [1554802814247, 5.58414729075323], + [1554802834247, 5.103587756899033], + [1554802854247, 4.686442663523442], + [1554802874247, 4.351199700286399], + [1554802894247, 4.446556476531035], + [1554802914247, 4.877022325792788], + [1554802934247, 5.06408000687434], + [1554802954247, 4.891564589262834], + [1554802974247, 5.286434271773061], + [1554802994247, 5.524908643196997], + [1554803014247, 5.924389675102854], + [1554803034247, 5.547245691246638], + [1554803054247, 5.202222179374266], + [1554803074247, 5.168745154790057], + [1554803094247, 4.675315213121577], + [1554803114247, 4.470816264658528], + [1554803134247, 4.738579106980465], + [1554803154247, 4.369706105812872], + [1554803174247, 4.62735842629406], + [1554803194247, 4.153751390299434], + [1554803214247, 4.136048104401695], + [1554803234247, 3.7990691391221483], + [1554803254247, 3.8795830766275676], + [1554803274247, 3.772500704071465], + [1554803294247, 3.9319620260590384], + [1554803314247, 3.559929630866206], + [1554803334247, 3.6113629367099938], + [1554803354247, 3.7376027498577726], + [1554803374247, 3.5578753439899113], + [1554803394247, 4.018133755965429], + [1554803414247, 4.288623643831334], + [1554803434247, 4.318645979671817], + [1554803454247, 4.749471110335788], + [1554803474247, 4.372628054764743], + [1554803494247, 4.622094039373034], + [1554803514247, 4.244606535531101], + [1554803534247, 3.816516521086009], + [1554803554247, 4.017892070701937], + [1554803574247, 3.89079416050709], + [1554803594247, 4.261198695219943], + [1554803614247, 4.428362341587736], + [1554803634247, 4.880986638912973], + [1554803654247, 5.0299660882410775], + [1554803674247, 5.411982269567302], + [1554803694247, 5.194408289760711], + [1554803714247, 5.012769948412448], + [1554803734247, 5.0072233733100004], + [1554803754247, 5.401394397533787], + [1554803774247, 5.326562162713341], + [1554803794247, 5.623163927554523], + [1554803814247, 5.5559453288335785], + [1554803834247, 5.577487170979451], + [1554803854247, 6.045429148567111], + [1554803874247, 5.7407369273358855], + [1554803894247, 5.9662724514162395], + [1554803914247, 5.626123193057087], + [1554803934247, 5.827530375461689], + [1554803954247, 6.219201754092099], + [1554803974247, 6.300657792657276], + [1554803994247, 6.542166827712119], + [1554804014247, 6.303296215678175], + [1554804034247, 6.252832131614809], + [1554804054247, 6.001141932163406], + [1554804074247, 6.452357078229282], + [1554804094247, 6.5222239382705745], + [1554804114247, 6.320907147559073], + [1554804134247, 6.811884397530322], + [1554804154247, 7.032984930368987], + [1554804174247, 6.715498474732585], + [1554804194247, 6.672161634739476], + [1554804214247, 6.994547337932146], + [1554804234247, 6.858854183936963], + [1554804254247, 6.633667847711406], + [1554804274247, 6.52885680764893], + [1554804294247, 6.2708537451521265], + [1554804314247, 6.325984685221548], + [1554804334247, 6.265281332886626], + [1554804354247, 5.790005511484586], + [1554804374247, 5.909314128865535], + [1554804394247, 6.019228158579621], + [1554804414247, 5.767753759200176], + [1554804434247, 5.877389216946951], + [1554804454247, 6.344658548950036], + [1554804474247, 6.2725006803076315], + [1554804494247, 6.159015121408378], + [1554804514247, 6.177644730506093], + [1554804534247, 6.574127902942029], + [1554804554247, 6.279446561068236], + [1554804574247, 6.532293875875611], + [1554804594247, 6.546265619012084], + [1554804614247, 6.669672237291822], + [1554804634247, 6.458459577608597], + [1554804654247, 6.3181910426234555], + [1554804674247, 6.330484950381867], + [1554804694247, 6.704701222610657], + [1554804714247, 6.296604674112797], + [1554804734247, 6.045934293115862], + [1554804754247, 5.626761485544506], + [1554804774247, 5.984868539333273], + [1554804794247, 5.782143366601749], + [1554804814247, 5.881833269531409], + [1554804834247, 6.341620115867811], + [1554804854247, 6.190837619933998], + [1554804874247, 5.7755446957461265], + [1554804894247, 5.927590146263014], + [1554804914247, 6.229025865781088], + [1554804934247, 5.9289926927844325], + [1554804954247, 6.283677647437369], + [1554804974247, 5.887290894902884], + [1554804994247, 5.60516096881941], + [1554805014247, 5.124922711378898], + [1554805034247, 5.022411278302609], + [1554805054247, 5.441282885351261], + [1554805074247, 5.211041708475881], + [1554805094247, 5.315539280767537], + [1554805114247, 4.860316378001272], + [1554805134247, 4.597541762813888], + [1554805154247, 4.701388340442914], + [1554805174247, 4.8857311808651795], + [1554805194247, 4.911170700334517], + [1554805214247, 4.7607973589457275], + [1554805234247, 5.2374533806100105], + [1554805254247, 5.635862787165801], + [1554805274247, 5.190782427510569], + [1554805294247, 5.040752906711101], + [1554805314247, 5.431677208644257], + [1554805334247, 5.276048739941259], + [1554805354247, 5.175810345425181], + [1554805374247, 5.448545682460811], + [1554805394247, 5.168286373993526], + [1554805414247, 4.915522433549292], + [1554805434247, 5.2403189761702755], + [1554805454247, 5.549674728141264], + [1554805474247, 5.346700869868238], + [1554805494247, 5.188114699782227], + [1554805514247, 5.054805968686958], + [1554805534247, 4.921991599219343], + [1554805554247, 5.252967706754598], + [1554805574247, 5.528822819853359], + [1554805594247, 5.163180135702445], + [1554805614247, 5.459094564903271], + [1554805634247, 5.205802999429608], + [1554805654247, 5.654372323923422], + [1554805674247, 6.005308016326213], + [1554805694247, 6.225307598518053], + [1554805714247, 6.265609851224129], + [1554805734247, 6.464569754513396], + [1554805754247, 6.213421273189288], + [1554805774247, 6.109005465627603], + [1554805794247, 6.388571590681015], + [1554805814247, 5.940826816262089], + [1554805834247, 6.152567556318539], + [1554805854247, 5.6778725758159245], + [1554805874247, 5.5459357762000145], + [1554805894247, 5.245881317787439], + [1554805914247, 5.16962721413826], + [1554805934247, 5.07586323530142], + [1554805954247, 5.079999397697077], + [1554805974247, 5.110929127356195], + [1554805994247, 5.4651260693748345], + [1554806014247, 5.333693563931255], + [1554806034247, 5.107568465043172], + [1554806054247, 5.549034075311475], + [1554806074247, 5.680774155981773], + [1554806094247, 5.449462358103411], + [1554806114247, 5.296928229571512], + [1554806134247, 4.801889842979068], + [1554806154247, 5.266423958416515], + [1554806174247, 5.394775355310104], + [1554806194247, 4.986628678003934], + [1554806214247, 4.927257141199989], + [1554806234247, 4.736707861872728], + [1554806254247, 5.217522955513845], + [1554806274247, 5.159523886652105], + [1554806294247, 5.1343440103709375], + [1554806314247, 5.4355027352491065], + [1554806334247, 4.989476007458429], + [1554806354247, 4.962491244463504], + [1554806374247, 5.234929203969406], + [1554806394247, 5.354611454712647], + [1554806414247, 5.819971337557214], + [1554806434247, 5.4537351462427655], + [1554806454247, 5.675427728347797], + [1554806474247, 5.717239078112083], + [1554806494247, 5.9665112054249505], + [1554806514247, 6.330042031470756], + [1554806534247, 6.554062137484854], + [1554806554247, 6.98908148194185], + [1554806574247, 6.550888018972389], + [1554806594247, 6.76067898332396], + [1554806614247, 6.766932559585224], + [1554806634247, 6.884507789101927], + [1554806654247, 6.982112269213111], + [1554806674247, 6.612531075414423], + [1554806694247, 6.831170284034089], + [1554806714247, 6.564458211861151], + [1554806734247, 6.5928702335259555], + [1554806754247, 6.307307819202318], + [1554806774247, 6.14296202627164], + [1554806794247, 5.850517533316699], + [1554806814247, 6.013477089375544], + [1554806834247, 5.788350959514567], + [1554806854247, 6.213462521998331], + [1554806874247, 6.135358062071788], + [1554806894247, 6.5090674301724265], + [1554806914247, 6.366207234773551], + [1554806934247, 6.717787701154512], + [1554806954247, 6.938004745742623], + [1554806974247, 6.608685270420923], + [1554806994247, 6.616342669591905], + [1554807014247, 7.111245244027054], + [1554807034247, 6.8382135174842515], + [1554807054247, 7.233472706615843], + [1554807074247, 7.086607581699311], + [1554807094247, 7.469047250077706], + [1554807114247, 7.230352648399958], + [1554807134247, 7.227645424764407], + [1554807154247, 7.4153920798629915], + [1554807174247, 7.026938941179673], + [1554807194247, 6.834372496767788], + [1554807214247, 6.632614720484994], + [1554807234247, 6.9176209603808205], + [1554807254247, 7.093991324735687], + [1554807274247, 7.49838324274419], + [1554807294247, 7.248815237417004], + [1554807314247, 7.59741703470027], + [1554807334247, 7.767235801748564], + [1554807354247, 7.798699694025993], + [1554807374247, 8.022398085783173], + [1554807394247, 8.219990876891334], + [1554807414247, 8.112971309076434], + [1554807434247, 8.391034098841457], + [1554807454247, 8.363882609872281], + [1554807474247, 7.971781311420273], + [1554807494247, 7.523868567624482], + [1554807514247, 7.597999604461199], + [1554807534247, 7.98131948849502], + [1554807554247, 8.211001366222089], + [1554807574247, 8.339946777071436], + [1554807594247, 8.321908649004829], + [1554807614247, 8.283911248846037], + [1554807634247, 7.95287844789605], + [1554807654247, 7.907639543478707], + [1554807674247, 7.8725971247401745], + [1554807694247, 7.510817707264657], + [1554807714247, 7.453138721251027], + [1554807734247, 7.945662384008237], + [1554807754247, 7.689782595310916], + [1554807774247, 7.222797934226393], + [1554807794247, 7.198697752316905], + [1554807814247, 7.134050929634677], + [1554807834247, 6.8576983886263525], + [1554807854247, 6.813355504109371], + [1554807874247, 6.871596166448773], + [1554807894247, 6.4936669876216335], + [1554807914247, 6.856768002872756], + [1554807934247, 6.49860793254361], + [1554807954247, 6.293200026824586], + [1554807974247, 5.947670166407878], + [1554807994247, 6.022336570265149], + [1554808014247, 6.518767909482903], + [1554808034247, 6.239492464787651], + [1554808054247, 6.727058806972739], + [1554808074247, 7.121308144408187], + [1554808094247, 6.782001741160709], + [1554808114247, 6.911079377114777], + [1554808134247, 6.8930569721432065], + [1554808154247, 7.026120004477112], + [1554808174247, 7.426405016599918], + [1554808194247, 7.101843615788554], + [1554808214247, 6.682553980564458], + [1554808234247, 6.489957896759141], + [1554808254247, 6.935104685760811], + [1554808274247, 7.272271426738921], + [1554808294247, 6.896043753021477], + [1554808314247, 6.453354071271065], + [1554808334247, 6.6400664095774005], + [1554808354247, 6.43533378238653], + [1554808374247, 6.892462502509553], + [1554808394247, 6.815418828468179], + [1554808414247, 6.884193266819189], + [1554808434247, 6.626443732396062], + [1554808454247, 6.9516516783657165], + [1554808474247, 7.049560022739215], + [1554808494247, 7.1055987133330785], + [1554808514247, 6.750008467983265], + [1554808534247, 7.207166348519568], + [1554808554247, 6.7168905957818845], + [1554808574247, 7.13070327526958], + [1554808594247, 7.079731512657319], + [1554808614247, 7.510314201924746], + [1554808634247, 7.706230930087699], + [1554808654247, 7.81411401302101], + [1554808674247, 7.478003760714301], + [1554808694247, 7.922427996204289], + [1554808714247, 7.907863457551709], + [1554808734247, 8.248186807433832], + [1554808754247, 8.576712081296638], + [1554808774247, 8.214689808808208], + [1554808794247, 8.663946930276753], + [1554808814247, 8.194761986653258], + [1554808834247, 8.575717709102516], + [1554808854247, 8.345850007916788], + [1554808874247, 8.612381234175777], + [1554808894247, 8.758703497768783], + [1554808914247, 8.45228506635527], + [1554808934247, 8.365323711621404], + [1554808954247, 8.772268719879923], + [1554808974247, 8.414503354013195], + [1554808994247, 8.33575391274245], + [1554809014247, 7.847212876502609], + [1554809034247, 7.820165181384834], + [1554809054247, 7.749613823906183], + [1554809074247, 8.171181374434301], + [1554809094247, 8.012821172876176], + [1554809114247, 7.632473633132171], + [1554809134247, 7.927346280103529], + [1554809154247, 8.38222644684866], + [1554809174247, 8.873146858741773], + [1554809194247, 9.139361430999534], + [1554809214247, 8.773999973423352], + [1554809234247, 8.503974910333003], + [1554809254247, 8.851608924895134], + [1554809274247, 9.258358922834823], + [1554809294247, 9.004521432325152], + [1554809314247, 8.663646594659093], + [1554809334247, 9.062943096024762], + [1554809354247, 8.927196322628053], + [1554809374247, 8.860884892690317], + [1554809394247, 8.72560917543646], + [1554809414247, 8.62396089915321], + [1554809434247, 8.767613126137167], + [1554809454247, 9.055419594223759], + [1554809474247, 8.569888672466254], + [1554809494247, 8.736703861623493], + [1554809514247, 8.94976206317658], + [1554809534247, 8.521394791609685], + [1554809554247, 8.3698829938199], + [1554809574247, 8.645533125455511], + [1554809594247, 8.389932129633864], + [1554809614247, 8.119152603087166], + [1554809634247, 8.119883595476143], + [1554809654247, 8.070896546230118], + [1554809674247, 8.11029896993352], + [1554809694247, 8.060761966261271], + [1554809714247, 8.50921225305014], + [1554809734247, 8.725356063040449], + [1554809754247, 8.727934761508802], + [1554809774247, 8.356828269208117], + [1554809794247, 8.637691261479478], + [1554809814247, 8.500996427635334], + [1554809834247, 8.343058481731683], + [1554809854247, 8.091250360224562], + [1554809874247, 7.706923991641506], + [1554809894247, 7.994107748320774], + [1554809914247, 7.675799246882235], + [1554809934247, 7.197886464997558], + [1554809954247, 7.188277445993742], + [1554809974247, 7.036209963564159], + [1554809994247, 6.947875119270903], + [1554810014247, 7.044499042781682], + [1554810034247, 6.663631394974621], + [1554810054247, 6.544553868473274], + [1554810074247, 6.244585802324538], + [1554810094247, 5.790424680888914], + [1554810114247, 5.597125792706711], + [1554810134247, 5.838197514962413], + [1554810154247, 5.569538296626776], + [1554810174247, 5.622066021193228], + [1554810194247, 5.6709480376132335], + [1554810214247, 5.3826562886664515], + [1554810234247, 5.495003785016907], + [1554810254247, 5.129314033185204], + [1554810274247, 4.709722774580275], + [1554810294247, 5.058233056559091], + [1554810314247, 5.149581150382685], + [1554810334247, 5.313471284226376], + [1554810354247, 5.724599134003778], + [1554810374247, 5.329889761110913], + [1554810394247, 5.746948046516922], + [1554810414247, 5.627652937866056], + [1554810434247, 5.142896397418332], + [1554810454247, 5.046133901694889], + [1554810474247, 5.507954638298896], + [1554810494247, 5.685628153810458], + [1554810514247, 6.159063025037185], + [1554810534247, 6.586439421400087], + [1554810554247, 6.882778595688622], + [1554810574247, 7.2490121652682395], + [1554810594247, 6.992372134925944], + [1554810614247, 6.866722248884183], + [1554810634247, 6.490212612921014], + [1554810654247, 6.104505357845294], + [1554810674247, 6.367774595212844], + [1554810694247, 6.083761787710358], + [1554810714247, 5.737294108204808], + [1554810734247, 6.102847667978216], + [1554810754247, 6.318239116092701], + [1554810774247, 5.920431833139119], + [1554810794247, 5.509946128359078], + [1554810814247, 5.124840200214926], + [1554810834247, 5.49899101103313], + [1554810854247, 5.470314709501598], + [1554810874247, 5.375614537897641], + [1554810894247, 5.3648225965695495], + [1554810914247, 5.135118670963028], + [1554810934247, 5.3362339554547065], + [1554810954247, 5.218712068436682], + [1554810974247, 5.412912108653002], + [1554810994247, 5.470415053122083], + [1554811014247, 5.728323741298232], + [1554811034247, 5.6932642393444075], + [1554811054247, 5.749008578598318], + [1554811074247, 6.09970432098925], + [1554811094247, 6.071920828705983], + [1554811114247, 5.828661341760444], + [1554811134247, 5.524965142249962], + [1554811154247, 5.575358061295394], + [1554811174247, 5.446919825099063], + [1554811194247, 5.93800790342962], + [1554811214247, 6.071568697482172], + [1554811234247, 6.310969859015277], + [1554811254247, 5.821955584869536], + [1554811274247, 6.199421058701339], + [1554811294247, 6.674118388812211], + [1554811314247, 6.673227102219742], + [1554811334247, 6.195363115639559], + [1554811354247, 5.820263795090813], + [1554811374247, 5.915530667539109], + [1554811394247, 6.331135066773232], + [1554811414247, 5.948853295013205], + [1554811434247, 5.451153941151292], + [1554811454247, 5.3919812114719905], + [1554811474247, 4.933551578138852], + [1554811494247, 5.099962987800049], + [1554811514247, 5.4460689354241545], + [1554811534247, 5.298376393189034], + [1554811554247, 5.051670370021742], + [1554811574247, 5.065328716862751], + [1554811594247, 4.942328036504453], + [1554811614247, 5.430221059400023], + [1554811634247, 5.338449689010827], + [1554811654247, 5.5716140235693805], + [1554811674247, 5.57866789314628], + [1554811694247, 5.090129632378411], + [1554811714247, 5.3389428704372515], + [1554811734247, 5.640001527104899], + [1554811754247, 5.8258428437827146], + [1554811774247, 6.045096248760302], + [1554811794247, 6.013981734904431], + [1554811814247, 5.915532054198841], + [1554811834247, 6.034610673432962], + [1554811854247, 6.414970154241204], + [1554811874247, 6.867945955465651], + [1554811894247, 6.933730078574364], + [1554811914247, 6.875497349505578], + [1554811934247, 6.809570407471085], + [1554811954247, 6.750819417545482], + [1554811974247, 7.028896424987769], + [1554811994247, 7.487999347921053], + [1554812014247, 7.200375020912814], + [1554812034247, 6.729037158447847], + [1554812054247, 6.2332135753766265], + [1554812074247, 6.2788869958132345], + [1554812094247, 6.551210083494132], + [1554812114247, 6.948556190676634], + [1554812134247, 6.810029771056861], + [1554812154247, 6.36992272456798], + [1554812174247, 6.144755770243405], + [1554812194247, 5.819900475823894], + [1554812214247, 5.842632687609575], + [1554812234247, 6.0229913623019105], + [1554812254247, 5.59543607823725], + [1554812274247, 5.240974306421704], + [1554812294247, 4.961540573276396], + [1554812314247, 5.063113720528529], + [1554812334247, 4.701806251073283], + [1554812354247, 4.383406380737377], + [1554812374247, 4.551643367095542], + [1554812394247, 4.501437131078793], + [1554812414247, 4.5896287936775515], + [1554812434247, 4.396798361915996], + [1554812454247, 4.094488884344564], + [1554812474247, 3.763950671274415], + [1554812494247, 3.3649910218348174], + [1554812514247, 2.9420483196562572], + [1554812534247, 2.4690716873035665], + [1554812554247, 2.40032467067903], + [1554812574247, 2.3999430459174027], + [1554812594247, 1.9716754362118059], + [1554812614247, 2.22897786032354], + [1554812634247, 1.936502183061513], + [1554812654247, 1.6697202952760788], + [1554812674247, 1.6161425233220745], + [1554812694247, 1.2979508442614471], + [1554812714247, 1.7795512009915573], + [1554812734247, 1.9775676458236746], + [1554812754247, 1.9857859272271818], + [1554812774247, 1.9712956178036478], + [1554812794247, 1.9954131810535451], + [1554812814247, 1.6639380475508858], + [1554812834247, 1.2921332932291232], + [1554812854247, 1.5623317516730828], + [1554812874247, 1.5630008010336873], + [1554812894247, 1.2945730371419555], + [1554812914247, 1.1345153304204763], + [1554812934247, 0.6485479592305394], + [1554812954247, 0.9712442704296393], + [1554812974247, 0.47564130406558597], + [1554812994247, 0.20444990972474225], + [1554813014247, -0.047090913437836], + [1554813034247, 0.2921861763460779], + [1554813054247, -0.15467934586406012], + [1554813074247, -0.3425495302000647], + [1554813094247, -0.19797787217917812], + [1554813114247, 0.24356265774967373], + [1554813134247, 0.6623693719986659], + [1554813154247, 1.0886329481274146], + [1554813174247, 0.7905383067051148], + [1554813194247, 0.43695310832152934], + [1554813214247, 0.5450623072818498], + [1554813234247, 0.9831498244033907], + [1554813254247, 0.5367271614005622], + [1554813274247, 0.34521375260155496], + [1554813294247, 0.49674059769702794], + [1554813314247, 0.8545220988091075], + [1554813334247, 1.1454002559545144], + [1554813354247, 1.5939691226426291], + [1554813374247, 1.2925743210955847], + [1554813394247, 1.202697680270512], + [1554813414247, 0.8248346749243624], + [1554813434247, 1.0508182706093616], + [1554813454247, 0.8933882588609936], + [1554813474247, 1.0868459054070774], + [1554813494247, 1.215067537959666], + [1554813514247, 1.6859453420595623], + [1554813534247, 1.8408803337528425], + [1554813554247, 1.503566069541031], + [1554813574247, 1.5025754914986305], + [1554813594247, 1.757013848848623], + [1554813614247, 1.8005931714680625], + [1554813634247, 1.993059816901711], + [1554813654247, 1.733598878547323], + [1554813674247, 1.7996930998117175], + [1554813694247, 1.7718023286326967], + [1554813714247, 1.9424689430549809], + [1554813734247, 1.8262152184502831], + [1554813754247, 1.8579680942725383], + [1554813774247, 1.9147990478151855], + [1554813794247, 1.5793884871771346], + [1554813814247, 1.7840058908387082], + [1554813834247, 1.524252777720136], + [1554813854247, 1.4086013386788108], + [1554813874247, 1.6938093491734791], + [1554813894247, 1.5204801601796416], + [1554813914247, 1.3677304479303785], + [1554813934247, 0.9070965699397421], + [1554813954247, 0.9001093789228389], + [1554813974247, 1.3596888033017467], + [1554813994247, 1.4244525184581716], + [1554814014247, 1.0681045937169595], + [1554814034247, 1.5021342501361263], + [1554814054247, 1.4735658199316348], + [1554814074247, 1.4142680194453459], + [1554814094247, 1.5307344331738741], + [1554814114247, 1.7600295139395037], + [1554814134247, 1.309541539278152], + [1554814154247, 0.8147628448203794], + [1554814174247, 1.2615036691775383], + [1554814194247, 1.7594036318960855], + [1554814214247, 1.3904263592100619], + [1554814234247, 1.088464774283428], + [1554814254247, 1.261188919478455], + [1554814274247, 0.7793099148276835], + [1554814294247, 0.7216892684946898], + [1554814314247, 0.5979274119186977], + [1554814334247, 0.7518018696509412], + [1554814354247, 0.877188700342351], + [1554814374247, 0.9674514096611574], + [1554814394247, 1.1939480091418346], + [1554814414247, 1.4909691399213152], + [1554814434247, 1.3506310992415735], + [1554814454247, 1.0871979315135374], + [1554814474247, 0.8619366874699526], + [1554814494247, 0.46428502886042183], + [1554814514247, 0.7896726455955152], + [1554814534247, 1.1538665346611536], + [1554814554247, 0.9916800353742865], + [1554814574247, 0.5090921441099303], + [1554814594247, 0.8273292250812865], + [1554814614247, 0.881693750857172], + [1554814634247, 0.8101430303449187], + [1554814654247, 1.0941807855302021], + [1554814674247, 0.9874142260877046], + [1554814694247, 0.8635324695445575], + [1554814714247, 0.9077447503631652], + [1554814734247, 0.6122162804684913], + [1554814754247, 0.15771010809450015], + [1554814774247, -0.13091442032472111], + [1554814794247, -0.2896902546208621], + [1554814814247, 0.09852909084992972], + [1554814834247, 0.39703026948260606], + [1554814854247, 0.2283837169989657], + ], + color: '#EAB839', + info: [ + { title: 'min', text: '14.42', numeric: 14.427101844163694 }, + { title: 'max', text: '18.42', numeric: 18.427101844163694 }, + ], + isVisible: true, + yAxis: 1, + }, + { + label: 'C-series', + data: [ + [1554793274247, 49.69913267127473], + [1554793294247, 49.33359410789192], + [1554793314247, 49.78163348684188], + [1554793334247, 50.17676830260008], + [1554793354247, 50.40926697029897], + [1554793374247, 50.063754605356486], + [1554793394247, 50.076999844329656], + [1554793414247, 49.948006752209906], + [1554793434247, 49.62287160565001], + [1554793454247, 50.03914976018486], + [1554793474247, 50.36736533496234], + [1554793494247, 50.21324796822654], + [1554793514247, 49.99112476335388], + [1554793534247, 49.84106879158749], + [1554793554247, 49.412375094520144], + [1554793574247, 49.78349539263808], + [1554793594247, 50.25259706652223], + [1554793614247, 49.888060632553064], + [1554793634247, 49.464424676361375], + [1554793654247, 49.095492684839165], + [1554793674247, 49.06868173776792], + [1554793694247, 48.574525936741985], + [1554793714247, 48.216592532164306], + [1554793734247, 47.98607300294584], + [1554793754247, 47.83078553622775], + [1554793774247, 47.93338669184849], + [1554793794247, 48.31669530028857], + [1554793814247, 47.82408913984656], + [1554793834247, 47.863721900360474], + [1554793854247, 47.51177912971504], + [1554793874247, 47.757151295055756], + [1554793894247, 47.95834636268655], + [1554793914247, 47.61863670851333], + [1554793934247, 47.93496547494361], + [1554793954247, 48.27168892260189], + [1554793974247, 48.44358577552156], + [1554793994247, 48.29808350161488], + [1554794014247, 48.10007721078963], + [1554794034247, 48.2714717233677], + [1554794054247, 48.05508995997583], + [1554794074247, 47.868813005942215], + [1554794094247, 47.876025343915664], + [1554794114247, 48.32683862002359], + [1554794134247, 48.342512548370195], + [1554794154247, 48.38648493951903], + [1554794174247, 48.199354522690825], + [1554794194247, 48.492498553794455], + [1554794214247, 48.380215201095], + [1554794234247, 48.115635145152744], + [1554794254247, 48.490793461399434], + [1554794274247, 48.900207945175495], + [1554794294247, 49.22861321463161], + [1554794314247, 49.4870357160768], + [1554794334247, 49.09749063286741], + [1554794354247, 49.092467346240014], + [1554794374247, 49.48778572019057], + [1554794394247, 49.979432673247885], + [1554794414247, 50.071201766907855], + [1554794434247, 49.9744124782064], + [1554794454247, 49.55016977456924], + [1554794474247, 49.06533151723648], + [1554794494247, 49.261080300725936], + [1554794514247, 49.26585857314833], + [1554794534247, 49.2429751536119], + [1554794554247, 49.346043390042986], + [1554794574247, 48.95990675036499], + [1554794594247, 49.12651334454535], + [1554794614247, 49.29857292163306], + [1554794634247, 49.186770426141514], + [1554794654247, 48.79264174796861], + [1554794674247, 48.88789309162548], + [1554794694247, 49.04173033445755], + [1554794714247, 49.403693322860484], + [1554794734247, 49.21528708472506], + [1554794754247, 49.349890005123356], + [1554794774247, 49.82581895338948], + [1554794794247, 50.10348602630898], + [1554794814247, 50.5396830040285], + [1554794834247, 50.152316888567], + [1554794854247, 49.849522224408226], + [1554794874247, 49.839186901052365], + [1554794894247, 49.51423985768722], + [1554794914247, 49.47484962979224], + [1554794934247, 49.69559173812953], + [1554794954247, 50.061967708930766], + [1554794974247, 49.72554084142337], + [1554794994247, 50.203775891826396], + [1554795014247, 50.46185209673081], + [1554795034247, 50.47156978415908], + [1554795054247, 50.501613750647564], + [1554795074247, 50.94633357030688], + [1554795094247, 51.210166610968514], + [1554795114247, 51.10160382793679], + [1554795134247, 51.1997077763274], + [1554795154247, 51.487603830051704], + [1554795174247, 51.36947130848638], + [1554795194247, 51.506665895696045], + [1554795214247, 51.47614904037313], + [1554795234247, 50.98365689110939], + [1554795254247, 50.85070473835243], + [1554795274247, 50.43383257691916], + [1554795294247, 50.232766901028626], + [1554795314247, 50.07161209496396], + [1554795334247, 49.837713407554816], + [1554795354247, 50.143606201515325], + [1554795374247, 50.345131163346394], + [1554795394247, 50.18313221599123], + [1554795414247, 49.84883601334913], + [1554795434247, 49.5272710525089], + [1554795454247, 49.215130902407815], + [1554795474247, 48.97804448667156], + [1554795494247, 49.31482206869563], + [1554795514247, 49.53974601218905], + [1554795534247, 49.07096949398067], + [1554795554247, 48.71727129896322], + [1554795574247, 48.93720156267952], + [1554795594247, 49.392801702481954], + [1554795614247, 49.216552679751196], + [1554795634247, 49.23021526312906], + [1554795654247, 49.36899096723609], + [1554795674247, 49.09191616409923], + [1554795694247, 49.15661848532974], + [1554795714247, 49.13503380602352], + [1554795734247, 49.55044458774344], + [1554795754247, 49.46886788033894], + [1554795774247, 49.30135034486024], + [1554795794247, 49.297518995279404], + [1554795814247, 48.925566481922225], + [1554795834247, 49.27559370678866], + [1554795854247, 49.332340911077424], + [1554795874247, 49.26076465409638], + [1554795894247, 49.53767640806078], + [1554795914247, 49.8030905200301], + [1554795934247, 49.32264966726677], + [1554795954247, 49.544792317070254], + [1554795974247, 49.21562164942147], + [1554795994247, 49.07254074465956], + [1554796014247, 49.18878762024898], + [1554796034247, 49.2306235947342], + [1554796054247, 49.62054143705226], + [1554796074247, 49.63187621927985], + [1554796094247, 49.98017604831396], + [1554796114247, 49.7600715923475], + [1554796134247, 49.27655228840302], + [1554796154247, 49.62483387018148], + [1554796174247, 49.21209572522005], + [1554796194247, 48.744631547286936], + [1554796214247, 48.63286811583694], + [1554796234247, 49.12296413252627], + [1554796254247, 48.910115698414295], + [1554796274247, 48.96613678532863], + [1554796294247, 49.05941150019551], + [1554796314247, 49.508439412178774], + [1554796334247, 49.16173498832025], + [1554796354247, 49.45053302619814], + [1554796374247, 49.00286141334283], + [1554796394247, 49.30217759710238], + [1554796414247, 49.501741513928174], + [1554796434247, 49.4972198005734], + [1554796454247, 49.16075824584829], + [1554796474247, 49.37241873098192], + [1554796494247, 49.63656657285561], + [1554796514247, 49.43718574292024], + [1554796534247, 49.48756155841603], + [1554796554247, 49.24303681625806], + [1554796574247, 49.382518871358485], + [1554796594247, 49.02247723343229], + [1554796614247, 49.27667512613039], + [1554796634247, 49.64825856084839], + [1554796654247, 49.321745040753285], + [1554796674247, 48.91447440186897], + [1554796694247, 48.81302425720652], + [1554796714247, 48.905546629260954], + [1554796734247, 49.07774732856527], + [1554796754247, 49.227140342516535], + [1554796774247, 49.72198806496772], + [1554796794247, 49.48668530730508], + [1554796814247, 49.46590246465986], + [1554796834247, 48.99586979519719], + [1554796854247, 48.798302244243935], + [1554796874247, 48.61059457103293], + [1554796894247, 48.93616700832869], + [1554796914247, 49.36797907473593], + [1554796934247, 49.47791061560138], + [1554796954247, 49.05321495421578], + [1554796974247, 49.193714250569805], + [1554796994247, 49.16178917646284], + [1554797014247, 49.61296666235341], + [1554797034247, 49.12291229001563], + [1554797054247, 48.95504159499004], + [1554797074247, 49.32814845656858], + [1554797094247, 48.943532330596284], + [1554797114247, 48.46180988614885], + [1554797134247, 48.11959030750559], + [1554797154247, 48.09494703813437], + [1554797174247, 47.983429675390965], + [1554797194247, 47.56241734828039], + [1554797214247, 47.979661755353014], + [1554797234247, 48.123073042355], + [1554797254247, 48.27362205867366], + [1554797274247, 48.71404324003502], + [1554797294247, 48.89226743535597], + [1554797314247, 49.29328058894711], + [1554797334247, 49.084461777558545], + [1554797354247, 48.83021345179746], + [1554797374247, 48.91026000597451], + [1554797394247, 48.99484128183615], + [1554797414247, 48.86756007189439], + [1554797434247, 48.479138233845504], + [1554797454247, 48.374298022845906], + [1554797474247, 48.286981712867664], + [1554797494247, 48.198201299191574], + [1554797514247, 47.77195291962695], + [1554797534247, 47.738964722883075], + [1554797554247, 48.13659961076256], + [1554797574247, 47.915605075289456], + [1554797594247, 48.30049282619518], + [1554797614247, 48.23976344811622], + [1554797634247, 48.165658285800426], + [1554797654247, 47.86756846582645], + [1554797674247, 48.29556200780647], + [1554797694247, 48.29145743008182], + [1554797714247, 47.8840989961646], + [1554797734247, 48.00901203088797], + [1554797754247, 47.641268558534534], + [1554797774247, 47.85700504489876], + [1554797794247, 47.754409771890366], + [1554797814247, 47.46442119094209], + [1554797834247, 47.37778670338178], + [1554797854247, 47.69125754568886], + [1554797874247, 47.65675109438546], + [1554797894247, 47.35188105593815], + [1554797914247, 46.99017604656002], + [1554797934247, 46.49962289247365], + [1554797954247, 46.50698076300431], + [1554797974247, 46.529341299373776], + [1554797994247, 46.14246130646399], + [1554798014247, 46.64128938702536], + [1554798034247, 46.71332038658063], + [1554798054247, 46.22766299792843], + [1554798074247, 46.655383290728864], + [1554798094247, 46.45705284677324], + [1554798114247, 46.45713571405586], + [1554798134247, 46.86151766756407], + [1554798154247, 46.90672301003809], + [1554798174247, 46.51535856335299], + [1554798194247, 46.10570297530587], + [1554798214247, 46.33418626714916], + [1554798234247, 46.003003880616205], + [1554798254247, 46.2925486741529], + [1554798274247, 46.39633084477583], + [1554798294247, 46.16591301596327], + [1554798314247, 46.416551590010194], + [1554798334247, 46.45425310871843], + [1554798354247, 46.0032881844452], + [1554798374247, 45.82313918899386], + [1554798394247, 46.219193352046396], + [1554798414247, 45.8586762819549], + [1554798434247, 46.040014378177375], + [1554798454247, 46.236218833624115], + [1554798474247, 46.45926020739284], + [1554798494247, 46.897768102161415], + [1554798514247, 47.36566794657619], + [1554798534247, 47.2382175292508], + [1554798554247, 47.50315436537415], + [1554798574247, 47.195192237664564], + [1554798594247, 47.63493540272602], + [1554798614247, 47.507004471059325], + [1554798634247, 47.97730627271725], + [1554798654247, 47.84750321321647], + [1554798674247, 47.779067424183054], + [1554798694247, 47.51070588382222], + [1554798714247, 47.31024519328251], + [1554798734247, 47.38794688477116], + [1554798754247, 47.32981223758303], + [1554798774247, 47.72299840553466], + [1554798794247, 47.95529996227096], + [1554798814247, 48.31157095625241], + [1554798834247, 48.023368909559515], + [1554798854247, 47.72332648683902], + [1554798874247, 47.8896007675284], + [1554798894247, 47.52159434109153], + [1554798914247, 47.36082882158501], + [1554798934247, 47.25781883471349], + [1554798954247, 47.345951933554566], + [1554798974247, 46.94903667465156], + [1554798994247, 47.39688904725867], + [1554799014247, 47.52423404078518], + [1554799034247, 47.0397640935533], + [1554799054247, 47.46602364797375], + [1554799074247, 47.111211461266905], + [1554799094247, 47.601388184562914], + [1554799114247, 47.68148443042845], + [1554799134247, 47.58525617262463], + [1554799154247, 47.78701709976165], + [1554799174247, 48.036966778117275], + [1554799194247, 47.970336996592096], + [1554799214247, 47.759473765364], + [1554799234247, 47.34751038233216], + [1554799254247, 47.57114234065436], + [1554799274247, 47.907061454075155], + [1554799294247, 48.084671942100634], + [1554799314247, 47.681986292923746], + [1554799334247, 47.95817351464097], + [1554799354247, 48.04032430472102], + [1554799374247, 47.97074694936207], + [1554799394247, 48.053606942051346], + [1554799414247, 48.03665265330263], + [1554799434247, 48.40213107480879], + [1554799454247, 48.66966744616825], + [1554799474247, 48.37860616360151], + [1554799494247, 48.69223531031285], + [1554799514247, 48.897104105650236], + [1554799534247, 48.72228611537678], + [1554799554247, 49.20245075823246], + [1554799574247, 49.08271267686173], + [1554799594247, 49.01995415573646], + [1554799614247, 49.405566345327166], + [1554799634247, 49.338088388754954], + [1554799654247, 49.42610506682994], + [1554799674247, 49.26870188087562], + [1554799694247, 48.99504223682648], + [1554799714247, 49.13641512533093], + [1554799734247, 48.68046815153081], + [1554799754247, 48.23884835789657], + [1554799774247, 48.34490487390389], + [1554799794247, 47.8621097886342], + [1554799814247, 48.10324901149713], + [1554799834247, 47.65769254778163], + [1554799854247, 48.04863600220461], + [1554799874247, 48.504658322467876], + [1554799894247, 48.84155760382869], + [1554799914247, 48.78203918293767], + [1554799934247, 48.69555264932167], + [1554799954247, 48.534953659795086], + [1554799974247, 48.32753840030407], + [1554799994247, 48.319124059010136], + [1554800014247, 48.08900748459846], + [1554800034247, 48.41592904647356], + [1554800054247, 48.654168369087024], + [1554800074247, 48.174716148384945], + [1554800094247, 47.838563784529015], + [1554800114247, 47.688834110534465], + [1554800134247, 48.01330592764464], + [1554800154247, 47.66787416286568], + [1554800174247, 47.480850102022586], + [1554800194247, 47.831846287590565], + [1554800214247, 47.41691220431661], + [1554800234247, 47.8412914393716], + [1554800254247, 47.83510232228141], + [1554800274247, 47.773877998469274], + [1554800294247, 47.515405390707095], + [1554800314247, 47.993473718819686], + [1554800334247, 48.34644356161968], + [1554800354247, 48.36355857235089], + [1554800374247, 48.57236022294879], + [1554800394247, 48.90877596270302], + [1554800414247, 49.062335690183524], + [1554800434247, 48.90760032123759], + [1554800454247, 48.40792258975594], + [1554800474247, 48.15638977565654], + [1554800494247, 48.514039980655234], + [1554800514247, 48.95574910285047], + [1554800534247, 48.99709284602056], + [1554800554247, 49.38005823887513], + [1554800574247, 49.65241792934206], + [1554800594247, 49.95055381158393], + [1554800614247, 50.28428874387261], + [1554800634247, 50.47285733412508], + [1554800654247, 50.35112364359045], + [1554800674247, 50.5519475699319], + [1554800694247, 50.61198314523461], + [1554800714247, 51.027913008897656], + [1554800734247, 50.822749098517605], + [1554800754247, 51.234030918977524], + [1554800774247, 51.207186361081156], + [1554800794247, 51.05369641506709], + [1554800814247, 51.20809949100641], + [1554800834247, 51.00097254666107], + [1554800854247, 50.502863733149596], + [1554800874247, 50.079695380284896], + [1554800894247, 49.86342104655181], + [1554800914247, 49.46833918185673], + [1554800934247, 49.6119576574696], + [1554800954247, 49.963978699045946], + [1554800974247, 49.59214014681593], + [1554800994247, 50.08518436521155], + [1554801014247, 50.40915968570567], + [1554801034247, 50.602524820648696], + [1554801054247, 50.985501356814034], + [1554801074247, 50.60559150837181], + [1554801094247, 50.485487661329294], + [1554801114247, 50.867623335094024], + [1554801134247, 50.96489864591541], + [1554801154247, 51.15309133118275], + [1554801174247, 51.24579939680081], + [1554801194247, 51.424479415888285], + [1554801214247, 51.5588899009813], + [1554801234247, 51.54490468599705], + [1554801254247, 51.40662689618962], + [1554801274247, 51.75006583094692], + [1554801294247, 52.20228577285978], + [1554801314247, 52.348547272368506], + [1554801334247, 52.39186508724331], + [1554801354247, 52.542927652025924], + [1554801374247, 52.27966546246372], + [1554801394247, 52.20452443809642], + [1554801414247, 51.915875146571814], + [1554801434247, 51.85700055331651], + [1554801454247, 51.41503493304628], + [1554801474247, 51.91375992161767], + [1554801494247, 51.64979593631921], + [1554801514247, 51.59721672310636], + [1554801534247, 51.36646019351009], + [1554801554247, 51.17970052059037], + [1554801574247, 51.51534040387782], + [1554801594247, 51.88251684232427], + [1554801614247, 51.703014931819474], + [1554801634247, 51.547017299912504], + [1554801654247, 51.275856922468904], + [1554801674247, 51.71448641006434], + [1554801694247, 51.576144190557336], + [1554801714247, 51.354383834775916], + [1554801734247, 51.04396571574281], + [1554801754247, 50.876641592142796], + [1554801774247, 51.336655229980316], + [1554801794247, 50.867416279953595], + [1554801814247, 51.130911647942355], + [1554801834247, 51.18234874849458], + [1554801854247, 51.50380094567395], + [1554801874247, 51.828265541951005], + [1554801894247, 51.41901070104453], + [1554801914247, 50.99815354555354], + [1554801934247, 51.32753225892714], + [1554801954247, 51.59398074693891], + [1554801974247, 51.83542718732424], + [1554801994247, 51.927373369486894], + [1554802014247, 52.34926003699012], + [1554802034247, 52.33484803971464], + [1554802054247, 52.32711385955333], + [1554802074247, 51.83460572908414], + [1554802094247, 51.71984751064315], + [1554802114247, 51.77033331018975], + [1554802134247, 51.350955694201005], + [1554802154247, 51.65773417914936], + [1554802174247, 51.48342088576234], + [1554802194247, 51.16267545464356], + [1554802214247, 51.323530207826956], + [1554802234247, 51.567191168336976], + [1554802254247, 51.13294706734198], + [1554802274247, 50.79800554137457], + [1554802294247, 51.04722627688948], + [1554802314247, 51.318544212080354], + [1554802334247, 51.532386858163754], + [1554802354247, 51.47703791469364], + [1554802374247, 51.78920946190912], + [1554802394247, 51.85211680399902], + [1554802414247, 51.99320526572213], + [1554802434247, 51.57547183300737], + [1554802454247, 51.97761981686591], + [1554802474247, 51.87004631283087], + [1554802494247, 52.158068947935384], + [1554802514247, 51.94930117584308], + [1554802534247, 51.53478371041338], + [1554802554247, 51.17882972197612], + [1554802574247, 50.701283694457466], + [1554802594247, 50.90473753625081], + [1554802614247, 50.65241735456049], + [1554802634247, 50.438246098999585], + [1554802654247, 50.37870735226924], + [1554802674247, 50.85584471285298], + [1554802694247, 51.274089045530395], + [1554802714247, 51.59978276742954], + [1554802734247, 51.562936160576584], + [1554802754247, 51.42151903058987], + [1554802774247, 51.894976900116355], + [1554802794247, 51.83169631691224], + [1554802814247, 51.79801425800586], + [1554802834247, 51.479633959435034], + [1554802854247, 51.70290776584143], + [1554802874247, 51.757226685563765], + [1554802894247, 52.03704183387305], + [1554802914247, 52.3819953868567], + [1554802934247, 51.88287073947722], + [1554802954247, 52.10125989403958], + [1554802974247, 51.872702036903526], + [1554802994247, 52.009175627089505], + [1554803014247, 52.50785086552939], + [1554803034247, 52.36391362787773], + [1554803054247, 52.77582474643076], + [1554803074247, 53.14634835172726], + [1554803094247, 53.571861564649836], + [1554803114247, 53.34550413497026], + [1554803134247, 53.53080010106201], + [1554803154247, 53.73421984607534], + [1554803174247, 53.56007952437269], + [1554803194247, 53.131414787142056], + [1554803214247, 52.94547963572799], + [1554803234247, 53.14281647882233], + [1554803254247, 52.73823765064605], + [1554803274247, 52.956467351101274], + [1554803294247, 53.35534255411743], + [1554803314247, 53.192930959674406], + [1554803334247, 53.32694937486834], + [1554803354247, 53.15027458209927], + [1554803374247, 53.5587680311651], + [1554803394247, 53.568883926439334], + [1554803414247, 53.80781403307825], + [1554803434247, 54.170116862726246], + [1554803454247, 53.79726949862653], + [1554803474247, 54.118793890386996], + [1554803494247, 54.19522192357821], + [1554803514247, 53.93837314058516], + [1554803534247, 53.61455261022302], + [1554803554247, 53.25549925605131], + [1554803574247, 53.30871660112379], + [1554803594247, 53.025228370296], + [1554803614247, 52.715005370790905], + [1554803634247, 52.26518145403466], + [1554803654247, 51.77433258023653], + [1554803674247, 51.92907944820857], + [1554803694247, 51.799802420253314], + [1554803714247, 52.23576201540642], + [1554803734247, 51.94765338498968], + [1554803754247, 51.65532307329866], + [1554803774247, 51.95953898434276], + [1554803794247, 52.30572709425199], + [1554803814247, 52.50628901507423], + [1554803834247, 52.18392152073497], + [1554803854247, 52.37085384093291], + [1554803874247, 51.958905637529526], + [1554803894247, 52.33884893220496], + [1554803914247, 51.945176746826625], + [1554803934247, 52.2903551152492], + [1554803954247, 52.45484660570278], + [1554803974247, 52.26531643836918], + [1554803994247, 51.83132366758987], + [1554804014247, 51.72448213626581], + [1554804034247, 51.387397812656125], + [1554804054247, 51.482075969410865], + [1554804074247, 51.516830773532355], + [1554804094247, 51.48614199714788], + [1554804114247, 51.1107772949971], + [1554804134247, 51.58592082858312], + [1554804154247, 52.01002593365593], + [1554804174247, 52.05010430476184], + [1554804194247, 51.69390021751488], + [1554804214247, 52.10787479914773], + [1554804234247, 52.17172758152165], + [1554804254247, 51.78777541365326], + [1554804274247, 51.67579928345696], + [1554804294247, 51.94442819030671], + [1554804314247, 51.80897520694817], + [1554804334247, 51.67986689129912], + [1554804354247, 51.55210735174368], + [1554804374247, 51.77569039319583], + [1554804394247, 51.45788841681898], + [1554804414247, 51.87269232666622], + [1554804434247, 51.82302735551395], + [1554804454247, 52.31024585013042], + [1554804474247, 51.93060365264004], + [1554804494247, 52.40571312978707], + [1554804514247, 52.39673639936394], + [1554804534247, 52.18557628791588], + [1554804554247, 52.60978266763105], + [1554804574247, 52.16131048329209], + [1554804594247, 52.505784425001984], + [1554804614247, 52.32401176586753], + [1554804634247, 52.30325652498879], + [1554804654247, 52.28613876880225], + [1554804674247, 52.080496843116386], + [1554804694247, 51.61527428444171], + [1554804714247, 51.83680620548246], + [1554804734247, 52.30374804621723], + [1554804754247, 52.67939694931608], + [1554804774247, 52.94943544533422], + [1554804794247, 53.24374366240067], + [1554804814247, 52.768273806147725], + [1554804834247, 53.074934594212785], + [1554804854247, 52.8116690446343], + [1554804874247, 52.420101586577], + [1554804894247, 52.375842717401966], + [1554804914247, 52.3698756882421], + [1554804934247, 51.87583553645484], + [1554804954247, 51.54912664248644], + [1554804974247, 51.990562270095786], + [1554804994247, 52.090483035778746], + [1554805014247, 51.67299602002489], + [1554805034247, 51.378645555389255], + [1554805054247, 51.18008092332962], + [1554805074247, 50.90350661363346], + [1554805094247, 50.85344076579689], + [1554805114247, 50.75969452484323], + [1554805134247, 51.15665586466479], + [1554805154247, 51.311445500103396], + [1554805174247, 51.236856143793126], + [1554805194247, 51.17414261055051], + [1554805214247, 51.108641060373124], + [1554805234247, 50.75890985719905], + [1554805254247, 50.48058630203468], + [1554805274247, 50.76721935252362], + [1554805294247, 50.5330383603889], + [1554805314247, 50.26520598051574], + [1554805334247, 50.60944764624979], + [1554805354247, 50.74484810624328], + [1554805374247, 51.01668313286863], + [1554805394247, 51.05429730952428], + [1554805414247, 51.39068964671044], + [1554805434247, 51.317735823836614], + [1554805454247, 51.25736086149264], + [1554805474247, 50.922379102839166], + [1554805494247, 50.98179933241315], + [1554805514247, 51.374526290084134], + [1554805534247, 51.40831930835523], + [1554805554247, 51.443173852379545], + [1554805574247, 51.4683090318251], + [1554805594247, 51.709059003470124], + [1554805614247, 52.19184281346864], + [1554805634247, 52.35070138588975], + [1554805654247, 51.97957436658507], + [1554805674247, 51.91458431154472], + [1554805694247, 52.41026984953236], + [1554805714247, 52.275201030560105], + [1554805734247, 52.18307838063212], + [1554805754247, 52.060069338900774], + [1554805774247, 51.61450171082168], + [1554805794247, 52.09853956209946], + [1554805814247, 52.588843625759424], + [1554805834247, 52.80348947533139], + [1554805854247, 53.28197181050794], + [1554805874247, 53.70501200876997], + [1554805894247, 53.89498917310595], + [1554805914247, 53.99791259724504], + [1554805934247, 53.62968839158572], + [1554805954247, 53.99473243614239], + [1554805974247, 53.97607431885154], + [1554805994247, 54.1654752913762], + [1554806014247, 54.29381284957149], + [1554806034247, 54.26736760766921], + [1554806054247, 54.72579383573786], + [1554806074247, 54.87585753445682], + [1554806094247, 54.90114957236757], + [1554806114247, 54.45131273475261], + [1554806134247, 54.00663438718738], + [1554806154247, 54.36866367166484], + [1554806174247, 54.455988047905954], + [1554806194247, 54.53444237413403], + [1554806214247, 54.259447240560334], + [1554806234247, 54.739815020637415], + [1554806254247, 54.53713835073127], + [1554806274247, 54.207215355017205], + [1554806294247, 54.5440608018207], + [1554806314247, 54.358821571481016], + [1554806334247, 54.72879724971994], + [1554806354247, 54.90023956328739], + [1554806374247, 54.74057764265007], + [1554806394247, 54.75935443450963], + [1554806414247, 55.02078995986204], + [1554806434247, 55.47735667708813], + [1554806454247, 55.728823396928945], + [1554806474247, 56.163253634213675], + [1554806494247, 55.8515954825293], + [1554806514247, 56.129890392645194], + [1554806534247, 55.74162749726028], + [1554806554247, 56.21329274387774], + [1554806574247, 55.998639128941015], + [1554806594247, 56.17167173612524], + [1554806614247, 56.37502616405982], + [1554806634247, 56.163483013167344], + [1554806654247, 55.84694130467721], + [1554806674247, 55.45846837023379], + [1554806694247, 55.04755139168062], + [1554806714247, 55.023136962195196], + [1554806734247, 55.03318249113286], + [1554806754247, 55.15746201013343], + [1554806774247, 54.69192101415061], + [1554806794247, 54.84111015085251], + [1554806814247, 54.58742405929199], + [1554806834247, 54.97799911256185], + [1554806854247, 54.764821076597485], + [1554806874247, 54.78776554693746], + [1554806894247, 54.36349387408045], + [1554806914247, 53.89745720207634], + [1554806934247, 54.17384926356723], + [1554806954247, 53.846082255988286], + [1554806974247, 53.90613692731394], + [1554806994247, 53.87258573355891], + [1554807014247, 54.17549073728332], + [1554807034247, 53.68618357720563], + [1554807054247, 53.513969787757105], + [1554807074247, 53.0552099855896], + [1554807094247, 52.765588324483865], + [1554807114247, 52.658001079532866], + [1554807134247, 53.07486561753133], + [1554807154247, 52.69459960292874], + [1554807174247, 52.98255693457559], + [1554807194247, 53.20218278203097], + [1554807214247, 52.97957847809027], + [1554807234247, 53.203425156589425], + [1554807254247, 53.12562342353098], + [1554807274247, 52.98722273991035], + [1554807294247, 52.82655589418688], + [1554807314247, 52.52987556980093], + [1554807334247, 52.491534753287645], + [1554807354247, 52.55176305705825], + [1554807374247, 52.13841375230353], + [1554807394247, 51.834840312920164], + [1554807414247, 52.184416639498664], + [1554807434247, 51.72479740399346], + [1554807454247, 52.155588780091456], + [1554807474247, 51.843576760185535], + [1554807494247, 51.63505755687057], + [1554807514247, 51.328848338540325], + [1554807534247, 51.17434126071598], + [1554807554247, 51.22528683963288], + [1554807574247, 51.45420767833925], + [1554807594247, 51.22268991224942], + [1554807614247, 50.79238198146152], + [1554807634247, 50.45484627009857], + [1554807654247, 50.85902478247322], + [1554807674247, 51.05110301744823], + [1554807694247, 51.441065782940804], + [1554807714247, 51.72675194566211], + [1554807734247, 51.34741055949714], + [1554807754247, 50.920382272281294], + [1554807774247, 50.70536279085003], + [1554807794247, 50.55798114104046], + [1554807814247, 50.725557394433494], + [1554807834247, 50.60243126287948], + [1554807854247, 50.14375392566316], + [1554807874247, 50.2002531691062], + [1554807894247, 50.200943028986934], + [1554807914247, 49.93557347736677], + [1554807934247, 50.3241686237509], + [1554807954247, 50.74023874549823], + [1554807974247, 50.381498198272375], + [1554807994247, 50.52372793713143], + [1554808014247, 50.59619769171313], + [1554808034247, 50.89556341815888], + [1554808054247, 50.86443137192155], + [1554808074247, 50.63167033746789], + [1554808094247, 50.13964173171047], + [1554808114247, 50.25093231733134], + [1554808134247, 50.08498877315317], + [1554808154247, 50.11947998142], + [1554808174247, 49.987009677804366], + [1554808194247, 49.840080913269475], + [1554808214247, 50.209998565510354], + [1554808234247, 50.531756264070935], + [1554808254247, 50.74837122490037], + [1554808274247, 50.7311698620495], + [1554808294247, 50.26107114525714], + [1554808314247, 50.57160680670211], + [1554808334247, 50.658461548491324], + [1554808354247, 50.32651326535061], + [1554808374247, 50.661562835023595], + [1554808394247, 50.44958975353214], + [1554808414247, 50.22399999500883], + [1554808434247, 50.58871685273966], + [1554808454247, 50.6742183549089], + [1554808474247, 50.32618916949026], + [1554808494247, 49.97104996971649], + [1554808514247, 50.435289475414216], + [1554808534247, 50.60512926447027], + [1554808554247, 50.73020639421864], + [1554808574247, 50.99932725118429], + [1554808594247, 51.34816166255093], + [1554808614247, 51.26324189269789], + [1554808634247, 50.85324941286893], + [1554808654247, 50.72520384570293], + [1554808674247, 51.089644509784655], + [1554808694247, 51.542456610721025], + [1554808714247, 51.777359837645164], + [1554808734247, 52.135547900174195], + [1554808754247, 52.28862099588846], + [1554808774247, 51.997792836163434], + [1554808794247, 52.305297731262264], + [1554808814247, 52.221352299608874], + [1554808834247, 52.02839560401225], + [1554808854247, 51.63103387134092], + [1554808874247, 51.54216467728416], + [1554808894247, 51.55386052088342], + [1554808914247, 51.17586087923488], + [1554808934247, 50.76208251333271], + [1554808954247, 50.317727703878695], + [1554808974247, 50.090846251422974], + [1554808994247, 50.06945817010757], + [1554809014247, 50.02269714272485], + [1554809034247, 49.9014869250931], + [1554809054247, 49.54981076067255], + [1554809074247, 49.84951930203291], + [1554809094247, 49.47499972389107], + [1554809114247, 49.62465014644696], + [1554809134247, 49.24747194031203], + [1554809154247, 49.069372398247346], + [1554809174247, 49.51527762106267], + [1554809194247, 49.55929829562035], + [1554809214247, 49.14007484550787], + [1554809234247, 49.55967463057968], + [1554809254247, 49.92414029604148], + [1554809274247, 50.28248263822046], + [1554809294247, 50.43547187450998], + [1554809314247, 50.01088683196451], + [1554809334247, 49.677926301450555], + [1554809354247, 49.97511400319861], + [1554809374247, 50.224853104822266], + [1554809394247, 50.220580489563496], + [1554809414247, 50.32549316137843], + [1554809434247, 50.81418718936579], + [1554809454247, 51.28120757217762], + [1554809474247, 51.465547229465], + [1554809494247, 51.37421458009423], + [1554809514247, 51.04893929102602], + [1554809534247, 50.66827537100915], + [1554809554247, 51.010305384682916], + [1554809574247, 50.74651884448325], + [1554809594247, 50.61682216706967], + [1554809614247, 50.45361096216425], + [1554809634247, 50.90490891959407], + [1554809654247, 50.622456452782494], + [1554809674247, 50.50344283767147], + [1554809694247, 50.46493050792487], + [1554809714247, 50.35998380458337], + [1554809734247, 50.1328954252928], + [1554809754247, 49.84079495406359], + [1554809774247, 50.13123680838925], + [1554809794247, 50.20403867276622], + [1554809814247, 50.004272675190784], + [1554809834247, 49.723751138918296], + [1554809854247, 49.49859072862423], + [1554809874247, 49.538307673194836], + [1554809894247, 49.62089922968913], + [1554809914247, 49.82385421066983], + [1554809934247, 49.84161674017104], + [1554809954247, 49.526737636369795], + [1554809974247, 49.931126418386356], + [1554809994247, 49.53343714924538], + [1554810014247, 49.42313707765715], + [1554810034247, 49.16979485487088], + [1554810054247, 49.652563787202645], + [1554810074247, 49.48023797398182], + [1554810094247, 49.966840603633734], + [1554810114247, 49.472083383816674], + [1554810134247, 49.37956146522102], + [1554810154247, 48.913166987107715], + [1554810174247, 48.70672990770373], + [1554810194247, 49.1880143597863], + [1554810214247, 49.49138355568559], + [1554810234247, 49.0630916077481], + [1554810254247, 48.85748269209717], + [1554810274247, 49.28639478935244], + [1554810294247, 49.13826091989152], + [1554810314247, 48.983630923627935], + [1554810334247, 48.68240787752351], + [1554810354247, 48.36663230019177], + [1554810374247, 48.52948288449895], + [1554810394247, 48.82498752624838], + [1554810414247, 49.10206080290292], + [1554810434247, 48.8130786016997], + [1554810454247, 48.663637941429585], + [1554810474247, 48.78385244438397], + [1554810494247, 48.5385370554751], + [1554810514247, 48.65982342796413], + [1554810534247, 48.2793032813205], + [1554810554247, 48.368720363392434], + [1554810574247, 47.95631221866125], + [1554810594247, 48.04947801392955], + [1554810614247, 47.92730902313688], + [1554810634247, 47.791227561595186], + [1554810654247, 47.696413872767344], + [1554810674247, 48.003461905264565], + [1554810694247, 48.41866753821385], + [1554810714247, 48.3323818553399], + [1554810734247, 48.55875807089029], + [1554810754247, 48.69664614708888], + [1554810774247, 48.39911556887364], + [1554810794247, 48.11355417510691], + [1554810814247, 47.68051884606699], + [1554810834247, 47.1839923323315], + [1554810854247, 47.521145818447444], + [1554810874247, 47.435239847122254], + [1554810894247, 47.80415137706029], + [1554810914247, 47.63696258266796], + [1554810934247, 48.03428238075079], + [1554810954247, 47.94997360430622], + [1554810974247, 47.55449851528431], + [1554810994247, 47.78824911083491], + [1554811014247, 47.4893779355486], + [1554811034247, 47.64650668855729], + [1554811054247, 47.22649114069579], + [1554811074247, 47.10626496382279], + [1554811094247, 46.853256635084975], + [1554811114247, 46.58521435687729], + [1554811134247, 46.46807667444405], + [1554811154247, 46.59110720595821], + [1554811174247, 46.47156843975408], + [1554811194247, 46.30570534424654], + [1554811214247, 46.327884115808345], + [1554811234247, 45.87249321102525], + [1554811254247, 45.93662730816857], + [1554811274247, 45.830703114024715], + [1554811294247, 45.7471098907337], + [1554811314247, 45.97554190426588], + [1554811334247, 45.83195232100273], + [1554811354247, 46.311066254110614], + [1554811374247, 46.502026295217874], + [1554811394247, 46.85743404788076], + [1554811414247, 47.058397205858235], + [1554811434247, 46.71734957659286], + [1554811454247, 47.00406489994063], + [1554811474247, 46.908589679853144], + [1554811494247, 46.46429522803091], + [1554811514247, 46.35314410074062], + [1554811534247, 46.08606779214885], + [1554811554247, 45.59440554131089], + [1554811574247, 46.01004712520209], + [1554811594247, 46.33290514899631], + [1554811614247, 45.90387315090703], + [1554811634247, 45.804826673859495], + [1554811654247, 45.58814979542495], + [1554811674247, 45.6383366715778], + [1554811694247, 46.098869094510555], + [1554811714247, 46.27645434323368], + [1554811734247, 46.38377282639434], + [1554811754247, 46.60623046278854], + [1554811774247, 46.853513275876644], + [1554811794247, 46.81150563219055], + [1554811814247, 46.325544759803684], + [1554811834247, 46.39222319511721], + [1554811854247, 46.05237287548122], + [1554811874247, 46.357861427033505], + [1554811894247, 45.872808350625355], + [1554811914247, 45.73033158647349], + [1554811934247, 46.181966738488526], + [1554811954247, 46.61144780966708], + [1554811974247, 46.944186256067475], + [1554811994247, 46.446866815105544], + [1554812014247, 46.87455438198626], + [1554812034247, 46.49680004841036], + [1554812054247, 46.110314234703566], + [1554812074247, 45.72718212902214], + [1554812094247, 45.855039988603075], + [1554812114247, 45.831083020621925], + [1554812134247, 45.934195744884576], + [1554812154247, 45.793162191919734], + [1554812174247, 45.595669324309405], + [1554812194247, 45.34395417586054], + [1554812214247, 45.48878147415903], + [1554812234247, 45.18708811432028], + [1554812254247, 45.68654757702763], + [1554812274247, 45.25733330257729], + [1554812294247, 44.802476591068185], + [1554812314247, 44.90227449426073], + [1554812334247, 44.77621515016869], + [1554812354247, 44.43700939403772], + [1554812374247, 44.89535195708861], + [1554812394247, 44.66555490148931], + [1554812414247, 44.77656357009823], + [1554812434247, 45.0781456336617], + [1554812454247, 45.02266276801925], + [1554812474247, 45.17853761454366], + [1554812494247, 45.206345465197174], + [1554812514247, 44.74293332634654], + [1554812534247, 45.12058926393326], + [1554812554247, 45.484527330308026], + [1554812574247, 45.72220471641109], + [1554812594247, 46.13939152292791], + [1554812614247, 46.00759269422592], + [1554812634247, 46.15320023087146], + [1554812654247, 46.31453520052207], + [1554812674247, 46.63327463975146], + [1554812694247, 46.74008671110519], + [1554812714247, 46.43464466851368], + [1554812734247, 46.09437986713492], + [1554812754247, 45.76744795370013], + [1554812774247, 46.15933621956665], + [1554812794247, 46.49926171251873], + [1554812814247, 46.26031394263079], + [1554812834247, 46.40700021317878], + [1554812854247, 46.019356637458465], + [1554812874247, 45.663769053656914], + [1554812894247, 46.11543163861163], + [1554812914247, 46.01937845681326], + [1554812934247, 46.053876490893266], + [1554812954247, 46.49976036351762], + [1554812974247, 46.48642420084204], + [1554812994247, 46.33380830950621], + [1554813014247, 46.46158553555841], + [1554813034247, 45.974232040531696], + [1554813054247, 45.84763240974679], + [1554813074247, 46.06094295457176], + [1554813094247, 45.57542828478516], + [1554813114247, 45.107768244929794], + [1554813134247, 45.29289069830043], + [1554813154247, 45.50682878428712], + [1554813174247, 45.48588008195146], + [1554813194247, 44.98951523195182], + [1554813214247, 44.682577096293755], + [1554813234247, 44.84745376781998], + [1554813254247, 44.58220779177513], + [1554813274247, 44.34705935598955], + [1554813294247, 43.91212590970287], + [1554813314247, 44.04615663810462], + [1554813334247, 43.78133590063513], + [1554813354247, 43.41643624560887], + [1554813374247, 43.63708147900446], + [1554813394247, 43.38739883558116], + [1554813414247, 43.64690789208579], + [1554813434247, 43.240387286772794], + [1554813454247, 43.02887852514058], + [1554813474247, 42.64466609459708], + [1554813494247, 42.59509438582544], + [1554813514247, 42.800700150025904], + [1554813534247, 42.99279809120495], + [1554813554247, 42.71212019392293], + [1554813574247, 42.26453618628849], + [1554813594247, 42.156627021840094], + [1554813614247, 42.18984321867831], + [1554813634247, 42.29340892976422], + [1554813654247, 42.593900952016476], + [1554813674247, 42.2330620746611], + [1554813694247, 41.86806010030196], + [1554813714247, 41.92031494441884], + [1554813734247, 41.77029002001442], + [1554813754247, 41.62068939271727], + [1554813774247, 41.27522742225526], + [1554813794247, 41.59092178660088], + [1554813814247, 41.197602991055604], + [1554813834247, 40.894310341221576], + [1554813854247, 40.9605769039487], + [1554813874247, 40.924569026392284], + [1554813894247, 40.6219617605231], + [1554813914247, 40.66747690052986], + [1554813934247, 40.3502087650845], + [1554813954247, 40.75856493329949], + [1554813974247, 40.77424153954944], + [1554813994247, 40.765533525784875], + [1554814014247, 40.7150752518103], + [1554814034247, 41.07494126786947], + [1554814054247, 41.50291852374512], + [1554814074247, 41.247377467265686], + [1554814094247, 41.60383347544849], + [1554814114247, 41.21723434866782], + [1554814134247, 41.17362119491221], + [1554814154247, 41.5483199633518], + [1554814174247, 41.26881119300047], + [1554814194247, 41.11926507536818], + [1554814214247, 41.279830040613255], + [1554814234247, 41.37424366244724], + [1554814254247, 41.23223435709267], + [1554814274247, 41.22891130945054], + [1554814294247, 40.84273309880227], + [1554814314247, 40.77105807274393], + [1554814334247, 40.86144344756839], + [1554814354247, 40.53399404435103], + [1554814374247, 40.89965536321252], + [1554814394247, 40.551632896315354], + [1554814414247, 40.27233656089394], + [1554814434247, 39.79467584395313], + [1554814454247, 39.5446056978286], + [1554814474247, 39.21168731652927], + [1554814494247, 39.53512815542741], + [1554814514247, 39.99562353822233], + [1554814534247, 39.85823942167259], + [1554814554247, 40.124808305253865], + [1554814574247, 40.356725295094996], + [1554814594247, 39.88169507281861], + [1554814614247, 40.09602202671889], + [1554814634247, 40.32994988463872], + [1554814654247, 40.6652027871041], + [1554814674247, 40.33146187156191], + [1554814694247, 39.89510766819647], + [1554814714247, 40.282027306139604], + [1554814734247, 40.34382349011193], + [1554814754247, 40.74449254471113], + [1554814774247, 40.605736246604756], + [1554814794247, 40.71323696936045], + [1554814814247, 40.98756203169225], + [1554814834247, 41.15554546599332], + [1554814854247, 40.97696657263369], + ], + color: '#6ED0E0', + info: [ + { title: 'min', text: '14.42', numeric: 14.427101844163694 }, + { title: 'max', text: '18.42', numeric: 18.427101844163694 }, + ], + isVisible: true, + yAxis: 1, + }, + ], + timeRange: { + from: moment('2019-04-09T07:01:14.247Z'), + to: moment('2019-04-09T13:01:14.247Z'), + raw: { + from: 'now-6h', + to: 'now', + }, + }, + width: 944, + height: 294, + isLegendVisible: true, + showBars: false, + showLines: true, + showPoints: false, + placement: 'under', + onSeriesColorChange: (label, color) => { + if (onSeriesColorChange) { + onSeriesColorChange(label, color); + } + }, + onSeriesAxisToggle: (label, yAxis) => { + if (onSeriesAxisToggle) { + onSeriesAxisToggle(label, yAxis); + } + }, + onToggleSort: () => {}, + displayMode: displayMode || LegendDisplayMode.List, +}); diff --git a/packages/grafana-ui/src/components/Legend/Legend.story.tsx b/packages/grafana-ui/src/components/Legend/Legend.story.tsx new file mode 100644 index 0000000000000..3cd561f03014a --- /dev/null +++ b/packages/grafana-ui/src/components/Legend/Legend.story.tsx @@ -0,0 +1,142 @@ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { LegendList, LegendPlacement, LegendItem, LegendTable } from './Legend'; +import tinycolor from 'tinycolor2'; +import { DisplayValue } from '../../types/index'; +import { number, select, text } from '@storybook/addon-knobs'; +import { action } from '@storybook/addon-actions'; +import { GraphLegendListItem, GraphLegendTableRow, GraphLegendItemProps } from '../Graph/GraphLegendItem'; + +export const generateLegendItems = (numberOfSeries: number, statsToDisplay?: DisplayValue[]): LegendItem[] => { + const alphabet = 'abcdefghijklmnopqrstuvwxyz'.split(''); + + return [...new Array(numberOfSeries)].map((item, i) => { + return { + label: `${alphabet[i].toUpperCase()}-series`, + color: tinycolor.fromRatio({ h: i / alphabet.length, s: 1, v: 1 }).toHexString(), + isVisible: true, + yAxis: 1, + displayValues: statsToDisplay || [], + }; + }); +}; + +const getStoriesKnobs = (table = false) => { + const numberOfSeries = number('Number of series', 3); + const containerWidth = select( + 'Container width', + { + Small: '200px', + Medium: '500px', + 'Full width': '100%', + }, + '100%' + ); + + const rawRenderer = (item: LegendItem) => ( + <> + Label: {item.label}, Color: {item.color}, isVisible:{' '} + {item.isVisible ? 'yes' : 'no'} + + ); + + const customRenderer = (component: React.ComponentType) => (item: LegendItem) => + React.createElement(component, { + item, + onLabelClick: action('GraphLegendItem label clicked'), + onSeriesColorChange: action('Series color changed'), + onToggleAxis: action('Y-axis toggle'), + }); + + const typeSpecificRenderer = table + ? { + 'Custom renderer(GraphLegendTablerow)': 'custom-tabe', + } + : { + 'Custom renderer(GraphLegendListItem)': 'custom-list', + }; + const legendItemRenderer = select( + 'Item rendered', + { + 'Raw renderer': 'raw', + ...typeSpecificRenderer, + }, + 'raw' + ); + + const rightAxisSeries = text('Right y-axis series, i.e. A,C', ''); + + const legendPlacement = select( + 'Legend placement', + { + under: 'under', + right: 'right', + }, + 'under' + ); + + return { + numberOfSeries, + containerWidth, + itemRenderer: + legendItemRenderer === 'raw' + ? rawRenderer + : customRenderer(legendItemRenderer === 'custom-list' ? GraphLegendListItem : GraphLegendTableRow), + rightAxisSeries, + legendPlacement, + }; +}; + +const LegendStories = storiesOf('UI/Legend/Legend', module); + +LegendStories.add('list', () => { + const { numberOfSeries, itemRenderer, containerWidth, rightAxisSeries, legendPlacement } = getStoriesKnobs(); + let items = generateLegendItems(numberOfSeries); + + items = items.map(i => { + if ( + rightAxisSeries + .split(',') + .map(s => s.trim()) + .indexOf(i.label.split('-')[0]) > -1 + ) { + i.yAxis = 2; + } + + return i; + }); + return ( +
+ +
+ ); +}); + +LegendStories.add('table', () => { + const { numberOfSeries, itemRenderer, containerWidth, rightAxisSeries, legendPlacement } = getStoriesKnobs(true); + let items = generateLegendItems(numberOfSeries); + + items = items.map(i => { + if ( + rightAxisSeries + .split(',') + .map(s => s.trim()) + .indexOf(i.label.split('-')[0]) > -1 + ) { + i.yAxis = 2; + } + + return { + ...i, + info: [ + { title: 'min', text: '14.42', numeric: 14.427101844163694 }, + { title: 'max', text: '18.42', numeric: 18.427101844163694 }, + ], + }; + }); + return ( +
+ +
+ ); +}); diff --git a/packages/grafana-ui/src/components/Legend/Legend.tsx b/packages/grafana-ui/src/components/Legend/Legend.tsx new file mode 100644 index 0000000000000..6f417f7257463 --- /dev/null +++ b/packages/grafana-ui/src/components/Legend/Legend.tsx @@ -0,0 +1,43 @@ +import { DisplayValue } from '../../types/index'; + +import { LegendList } from './LegendList'; +import { LegendTable } from './LegendTable'; + +export enum LegendDisplayMode { + List = 'list', + Table = 'table', +} +export interface LegendBasicOptions { + isVisible: boolean; + asTable: boolean; +} + +export interface LegendRenderOptions { + placement: LegendPlacement; + hideEmpty?: boolean; + hideZero?: boolean; +} + +export type LegendPlacement = 'under' | 'right' | 'over'; // Over used by piechart + +export interface LegendOptions extends LegendBasicOptions, LegendRenderOptions {} + +export interface LegendItem { + label: string; + color: string; + isVisible: boolean; + yAxis: number; + displayValues?: DisplayValue[]; +} + +export interface LegendComponentProps { + className?: string; + items: LegendItem[]; + placement: LegendPlacement; + // Function to render given item + itemRenderer?: (item: LegendItem, index: number) => JSX.Element; +} + +export interface LegendProps extends LegendComponentProps {} + +export { LegendList, LegendTable }; diff --git a/packages/grafana-ui/src/components/Legend/LegendList.tsx b/packages/grafana-ui/src/components/Legend/LegendList.tsx new file mode 100644 index 0000000000000..d103aa3ed8067 --- /dev/null +++ b/packages/grafana-ui/src/components/Legend/LegendList.tsx @@ -0,0 +1,66 @@ +import React, { useContext } from 'react'; +import { LegendComponentProps, LegendItem } from './Legend'; +import { InlineList } from '../List/InlineList'; +import { List } from '../List/List'; +import { css, cx } from 'emotion'; +import { ThemeContext } from '../../themes/ThemeContext'; + +export const LegendList: React.FunctionComponent = ({ + items, + itemRenderer, + placement, + className, +}) => { + const theme = useContext(ThemeContext); + + const renderItem = (item: LegendItem, index: number) => { + return ( + + {itemRenderer ? itemRenderer(item, index) : item.label} + + ); + }; + + const getItemKey = (item: LegendItem) => item.label; + + const styles = { + wrapper: cx( + css` + display: flex; + flex-wrap: wrap; + justify-content: space-between; + width: 100%; + `, + className + ), + section: css` + display: flex; + `, + sectionRight: css` + justify-content: flex-end; + flex-grow: 1; + `, + }; + + return placement === 'under' ? ( +
+
+ item.yAxis === 1)} renderItem={renderItem} getItemKey={getItemKey} /> +
+
+ item.yAxis !== 1)} renderItem={renderItem} getItemKey={getItemKey} /> +
+
+ ) : ( + + ); +}; + +LegendList.displayName = 'LegendList'; diff --git a/packages/grafana-ui/src/components/Legend/LegendSeriesIcon.tsx b/packages/grafana-ui/src/components/Legend/LegendSeriesIcon.tsx new file mode 100644 index 0000000000000..1913c2e500d25 --- /dev/null +++ b/packages/grafana-ui/src/components/Legend/LegendSeriesIcon.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { SeriesColorPicker } from '../ColorPicker/ColorPicker'; +import { SeriesIcon } from './SeriesIcon'; + +interface LegendSeriesIconProps { + color: string; + yAxis: number; + onColorChange: (color: string) => void; + onToggleAxis?: () => void; +} + +export const LegendSeriesIcon: React.FunctionComponent = ({ + yAxis, + color, + onColorChange, + onToggleAxis, +}) => { + return ( + + {({ ref, showColorPicker, hideColorPicker }) => ( + + + + )} + + ); +}; + +LegendSeriesIcon.displayName = 'LegendSeriesIcon'; diff --git a/packages/grafana-ui/src/components/Legend/LegendStatsList.tsx b/packages/grafana-ui/src/components/Legend/LegendStatsList.tsx new file mode 100644 index 0000000000000..b53f3cc2a536c --- /dev/null +++ b/packages/grafana-ui/src/components/Legend/LegendStatsList.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { InlineList } from '../List/InlineList'; +import { css } from 'emotion'; +import { DisplayValue } from '../../types/displayValue'; +import capitalize from 'lodash/capitalize'; + +const LegendItemStat: React.FunctionComponent<{ stat: DisplayValue }> = ({ stat }) => { + return ( +
+ {stat.title && `${capitalize(stat.title)}:`} {stat.text} +
+ ); +}; + +LegendItemStat.displayName = 'LegendItemStat'; + +export const LegendStatsList: React.FunctionComponent<{ stats: DisplayValue[] }> = ({ stats }) => { + if (stats.length === 0) { + return null; + } + return } />; +}; + +LegendStatsList.displayName = 'LegendStatsList'; diff --git a/packages/grafana-ui/src/components/Legend/LegendTable.tsx b/packages/grafana-ui/src/components/Legend/LegendTable.tsx new file mode 100644 index 0000000000000..38a6bd1497b96 --- /dev/null +++ b/packages/grafana-ui/src/components/Legend/LegendTable.tsx @@ -0,0 +1,83 @@ +import React, { useContext } from 'react'; +import { css, cx } from 'emotion'; +import { LegendComponentProps } from './Legend'; +import { ThemeContext } from '../../themes/ThemeContext'; + +interface LegendTableProps extends LegendComponentProps { + columns: string[]; + sortBy?: string; + sortDesc?: boolean; + onToggleSort?: (sortBy: string) => void; +} + +export const LegendTable: React.FunctionComponent = ({ + items, + columns, + sortBy, + sortDesc, + itemRenderer, + className, + onToggleSort, +}) => { + const theme = useContext(ThemeContext); + + return ( + + + + {columns.map(columnHeader => { + return ( + + ); + })} + + + + {items.map((item, index) => { + return itemRenderer ? ( + itemRenderer(item, index) + ) : ( + + + + ); + })} + +
{ + if (onToggleSort) { + onToggleSort(columnHeader); + } + }} + > + {columnHeader} + {sortBy === columnHeader && ( + + )} +
{item.label}
+ ); +}; diff --git a/packages/grafana-ui/src/components/Legend/SeriesIcon.tsx b/packages/grafana-ui/src/components/Legend/SeriesIcon.tsx new file mode 100644 index 0000000000000..091d79f7fd0d0 --- /dev/null +++ b/packages/grafana-ui/src/components/Legend/SeriesIcon.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +export const SeriesIcon: React.FunctionComponent<{ color: string }> = ({ color }) => { + return ; +}; diff --git a/packages/grafana-ui/src/components/List/AbstractList.test.tsx b/packages/grafana-ui/src/components/List/AbstractList.test.tsx new file mode 100644 index 0000000000000..f38b7cd2988e5 --- /dev/null +++ b/packages/grafana-ui/src/components/List/AbstractList.test.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { AbstractList } from './AbstractList'; + +describe('AbstractList', () => { + it('renders items using renderItem prop function', () => { + const items = [{ name: 'Item 1', id: 'item1' }, { name: 'Item 2', id: 'item2' }, { name: 'Item 3', id: 'item3' }]; + + const list = shallow( + ( +
+

{item.name}

+ {item.id} +
+ )} + /> + ); + + expect(list).toMatchSnapshot(); + }); + + it('allows custom item key', () => { + const items = [{ name: 'Item 1', id: 'item1' }, { name: 'Item 2', id: 'item2' }, { name: 'Item 3', id: 'item3' }]; + + const list = shallow( + item.id} + renderItem={item => ( +
+

{item.name}

+ {item.id} +
+ )} + /> + ); + + expect(list).toMatchSnapshot(); + }); +}); diff --git a/packages/grafana-ui/src/components/List/AbstractList.tsx b/packages/grafana-ui/src/components/List/AbstractList.tsx new file mode 100644 index 0000000000000..7c978753276a4 --- /dev/null +++ b/packages/grafana-ui/src/components/List/AbstractList.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { cx, css } from 'emotion'; + +export interface ListProps { + items: T[]; + renderItem: (item: T, index: number) => JSX.Element; + getItemKey?: (item: T) => string; + className?: string; +} + +interface AbstractListProps extends ListProps { + inline?: boolean; +} + +export class AbstractList extends React.PureComponent> { + constructor(props: AbstractListProps) { + super(props); + this.getListStyles = this.getListStyles.bind(this); + } + + getListStyles() { + const { inline, className } = this.props; + return { + list: cx([ + css` + list-style-type: none; + margin: 0; + padding: 0; + `, + className, + ]), + item: css` + display: ${(inline && 'inline-block') || 'block'}; + `, + }; + } + + render() { + const { items, renderItem, getItemKey } = this.props; + const styles = this.getListStyles(); + return ( +
    + {items.map((item, i) => { + return ( +
  • + {renderItem(item, i)} +
  • + ); + })} +
+ ); + } +} diff --git a/packages/grafana-ui/src/components/List/InlineList.tsx b/packages/grafana-ui/src/components/List/InlineList.tsx new file mode 100644 index 0000000000000..aadfd84046f8a --- /dev/null +++ b/packages/grafana-ui/src/components/List/InlineList.tsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { ListProps, AbstractList } from './AbstractList'; + +export class InlineList extends React.PureComponent> { + render() { + return ; + } +} diff --git a/packages/grafana-ui/src/components/List/List.story.tsx b/packages/grafana-ui/src/components/List/List.story.tsx new file mode 100644 index 0000000000000..3911039b5225e --- /dev/null +++ b/packages/grafana-ui/src/components/List/List.story.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { number, select } from '@storybook/addon-knobs'; +import { List } from './List'; +import { css, cx } from 'emotion'; +import tinycolor from 'tinycolor2'; +import { InlineList } from './InlineList'; + +const ListStories = storiesOf('UI/List', module); + +const generateListItems = (numberOfItems: number) => { + return [...new Array(numberOfItems)].map((item, i) => { + return { + name: `Item-${i}`, + id: `item-${i}`, + }; + }); +}; + +const getStoriesKnobs = (inline = false) => { + const numberOfItems = number('Number of items', 3); + const rawRenderer = (item: any) => <>{item.name}; + const customRenderer = (item: any, index: number) => ( +
+ {item.name} +
+ ); + + const itemRenderer = select( + 'Item rendered', + { + 'Raw renderer': 'raw', + 'Custom renderer': 'custom', + }, + 'raw' + ); + + return { + numberOfItems, + renderItem: itemRenderer === 'raw' ? rawRenderer : customRenderer, + }; +}; + +ListStories.add('default', () => { + const { numberOfItems, renderItem } = getStoriesKnobs(); + return ; +}); + +ListStories.add('inline', () => { + const { numberOfItems, renderItem } = getStoriesKnobs(true); + return ; +}); diff --git a/packages/grafana-ui/src/components/List/List.tsx b/packages/grafana-ui/src/components/List/List.tsx new file mode 100644 index 0000000000000..1fcc7d9ae0138 --- /dev/null +++ b/packages/grafana-ui/src/components/List/List.tsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { ListProps, AbstractList } from './AbstractList'; + +export class List extends React.PureComponent> { + render() { + return ; + } +} diff --git a/packages/grafana-ui/src/components/List/__snapshots__/AbstractList.test.tsx.snap b/packages/grafana-ui/src/components/List/__snapshots__/AbstractList.test.tsx.snap new file mode 100644 index 0000000000000..b4c30ebce0683 --- /dev/null +++ b/packages/grafana-ui/src/components/List/__snapshots__/AbstractList.test.tsx.snap @@ -0,0 +1,93 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AbstractList allows custom item key 1`] = ` +
    +
  • +
    +

    + Item 1 +

    + + item1 + +
    +
  • +
  • +
    +

    + Item 2 +

    + + item2 + +
    +
  • +
  • +
    +

    + Item 3 +

    + + item3 + +
    +
  • +
+`; + +exports[`AbstractList renders items using renderItem prop function 1`] = ` +
    +
  • +
    +

    + Item 1 +

    + + item1 + +
    +
  • +
  • +
    +

    + Item 2 +

    + + item2 + +
    +
  • +
  • +
    +

    + Item 3 +

    + + item3 + +
    +
  • +
+`; diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index c3dbfa9e9bb23..7a64318c128d3 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -22,7 +22,7 @@ export { SecretFormField } from './SecretFormFied/SecretFormField'; export { LoadingPlaceholder } from './LoadingPlaceholder/LoadingPlaceholder'; export { ColorPicker, SeriesColorPicker } from './ColorPicker/ColorPicker'; export { SeriesColorPickerPopover, SeriesColorPickerPopoverWithTheme } from './ColorPicker/SeriesColorPickerPopover'; -export { ThresholdsEditor } from './ThresholdsEditor/ThresholdsEditor'; + export { PanelOptionsGroup } from './PanelOptionsGroup/PanelOptionsGroup'; export { PanelOptionsGrid } from './PanelOptionsGrid/PanelOptionsGrid'; export { ValueMappingsEditor } from './ValueMappingsEditor/ValueMappingsEditor'; @@ -44,8 +44,12 @@ export { TableInputCSV } from './Table/TableInputCSV'; export { BigValue } from './BigValue/BigValue'; export { Gauge } from './Gauge/Gauge'; export { Graph } from './Graph/Graph'; +export { GraphWithLegend } from './Graph/GraphWithLegend'; export { BarGauge } from './BarGauge/BarGauge'; export { VizRepeater } from './VizRepeater/VizRepeater'; +export { LegendOptions, LegendBasicOptions, LegendRenderOptions, LegendList, LegendTable } from './Legend/Legend'; +// Panel editors +export { ThresholdsEditor } from './ThresholdsEditor/ThresholdsEditor'; export { ClickOutsideWrapper } from './ClickOutsideWrapper/ClickOutsideWrapper'; export * from './SingleStatShared/shared'; export { CallToActionCard } from './CallToActionCard/CallToActionCard'; diff --git a/packages/grafana-ui/src/types/graph.ts b/packages/grafana-ui/src/types/graph.ts index 0e2c2361a6942..e8b2d4e1141c7 100644 --- a/packages/grafana-ui/src/types/graph.ts +++ b/packages/grafana-ui/src/types/graph.ts @@ -8,4 +8,6 @@ export interface GraphSeriesXY { color: string; data: GraphSeriesValue[][]; // [x,y][] info?: DisplayValue[]; // Legend info + isVisible: boolean; + yAxis: number; } diff --git a/packages/grafana-ui/src/types/index.ts b/packages/grafana-ui/src/types/index.ts index ab8c05138f97a..bf3b5e2995fdc 100644 --- a/packages/grafana-ui/src/types/index.ts +++ b/packages/grafana-ui/src/types/index.ts @@ -9,3 +9,4 @@ export * from './threshold'; export * from './input'; export * from './logs'; export * from './displayValue'; +export * from './utils'; diff --git a/packages/grafana-ui/src/types/panel.ts b/packages/grafana-ui/src/types/panel.ts index daea7e2bf0db8..c06ea7acd42bb 100644 --- a/packages/grafana-ui/src/types/panel.ts +++ b/packages/grafana-ui/src/types/panel.ts @@ -1,4 +1,4 @@ -import { ComponentClass } from 'react'; +import { ComponentClass, ComponentType } from 'react'; import { LoadingState, SeriesData } from './data'; import { TimeRange } from './time'; import { ScopedVars, DataQueryRequest, DataQueryError, LegacyResponseData } from './datasource'; @@ -21,6 +21,7 @@ export interface PanelProps { timeRange: TimeRange; options: T; + onOptionsChange: (options: T) => void; renderCounter: number; width: number; height: number; @@ -53,13 +54,13 @@ export type PanelTypeChangedHandler = ( ) => Partial; export class ReactPanelPlugin { - panel: ComponentClass>; + panel: ComponentType>; editor?: ComponentClass>; defaults?: TOptions; onPanelMigration?: PanelMigrationHandler; onPanelTypeChanged?: PanelTypeChangedHandler; - constructor(panel: ComponentClass>) { + constructor(panel: ComponentType>) { this.panel = panel; } diff --git a/packages/grafana-ui/src/types/utils.ts b/packages/grafana-ui/src/types/utils.ts new file mode 100644 index 0000000000000..06c797ae5fad2 --- /dev/null +++ b/packages/grafana-ui/src/types/utils.ts @@ -0,0 +1,2 @@ +export type Omit = Pick>; +export type Subtract = Omit; diff --git a/packages/grafana-ui/src/utils/index.ts b/packages/grafana-ui/src/utils/index.ts index dbd2f8a3e62e9..5d5a7b32c60eb 100644 --- a/packages/grafana-ui/src/utils/index.ts +++ b/packages/grafana-ui/src/utils/index.ts @@ -12,4 +12,5 @@ export * from './logs'; export * from './labels'; export { getMappedValue } from './valueMappings'; export * from './validate'; +export { getFlotPairs } from './flotPairs'; export * from './object'; diff --git a/packages/grafana-ui/src/utils/storybook/UseState.tsx b/packages/grafana-ui/src/utils/storybook/UseState.tsx index fb9bf06bfb210..6dc3ff342f736 100644 --- a/packages/grafana-ui/src/utils/storybook/UseState.tsx +++ b/packages/grafana-ui/src/utils/storybook/UseState.tsx @@ -28,7 +28,6 @@ export class UseState extends React.Component, { value: T } handleStateUpdate = (nextState: T) => { - console.log(nextState); this.setState({ value: nextState }); }; diff --git a/public/app/features/admin/__snapshots__/ServerStats.test.tsx.snap b/public/app/features/admin/__snapshots__/ServerStats.test.tsx.snap index da9a80aeb674c..23d4d714559d3 100644 --- a/public/app/features/admin/__snapshots__/ServerStats.test.tsx.snap +++ b/public/app/features/admin/__snapshots__/ServerStats.test.tsx.snap @@ -212,7 +212,7 @@ exports[`ServerStats Should render table with stats 1`] = `
{ }); }; + onOptionsChange = (options: any) => { + this.props.panel.updateOptions(options); + }; + replaceVariables = (value: string, extraVars?: ScopedVars, format?: string) => { let vars = this.props.panel.scopedVars; if (extraVars) { @@ -223,6 +227,7 @@ export class PanelChrome extends PureComponent { height={height - PANEL_HEADER_HEIGHT - config.theme.panelPadding.vertical} renderCounter={renderCounter} replaceVariables={this.replaceVariables} + onOptionsChange={this.onOptionsChange} />
diff --git a/public/app/plugins/panel/graph/Legend/Legend.tsx b/public/app/plugins/panel/graph/Legend/Legend.tsx index 8702bdac85934..c20623f8c0ed0 100644 --- a/public/app/plugins/panel/graph/Legend/Legend.tsx +++ b/public/app/plugins/panel/graph/Legend/Legend.tsx @@ -311,7 +311,7 @@ class LegendTableHeaderItem extends PureComponent { render() { return ( -
}> + ); diff --git a/public/app/plugins/panel/graph2/GraphLegendEditor.tsx b/public/app/plugins/panel/graph2/GraphLegendEditor.tsx new file mode 100644 index 0000000000000..7d5d914fb9242 --- /dev/null +++ b/public/app/plugins/panel/graph2/GraphLegendEditor.tsx @@ -0,0 +1,103 @@ +import React from 'react'; +import capitalize from 'lodash/capitalize'; +import without from 'lodash/without'; +import { LegendOptions, PanelOptionsGroup, Switch, Input } from '@grafana/ui'; + +export interface GraphLegendEditorLegendOptions extends LegendOptions { + stats?: string[]; + decimals?: number; + sortBy?: string; + sortDesc?: boolean; +} + +interface GraphLegendEditorProps { + stats: string[]; + options: GraphLegendEditorLegendOptions; + onChange: (options: GraphLegendEditorLegendOptions) => void; +} + +export const GraphLegendEditor: React.FunctionComponent = props => { + const { stats, options, onChange } = props; + + const onStatToggle = (stat: string) => (event?: React.SyntheticEvent) => { + let newStats; + if (!event) { + return; + } + + // @ts-ignore + if (event.target.checked) { + newStats = (options.stats || []).concat([stat]); + } else { + newStats = without(options.stats, stat); + } + onChange({ + ...options, + stats: newStats, + }); + }; + + const onOptionToggle = (option: keyof LegendOptions) => (event?: React.SyntheticEvent) => { + const newOption = {}; + if (!event) { + return; + } + // TODO: fix the ignores + // @ts-ignore + newOption[option] = event.target.checked; + if (option === 'placement') { + // @ts-ignore + newOption[option] = event.target.checked ? 'right' : 'under'; + } + + onChange({ + ...options, + ...newOption, + }); + }; + + return ( + +
+

Options

+ + + +
+
+

Values

+ {stats.map(stat => { + return ( + -1} + onChange={onStatToggle(stat)} + key={stat} + /> + ); + })} +
+
Decimals
+ { + onChange({ + ...options, + decimals: parseInt(event.target.value, 10), + }); + }} + /> +
+
+ +
+

Hidden series

+ {/* */} + +
+
+ ); +}; diff --git a/public/app/plugins/panel/graph2/GraphPanel.tsx b/public/app/plugins/panel/graph2/GraphPanel.tsx index a6821323b272e..7b544d7e67464 100644 --- a/public/app/plugins/panel/graph2/GraphPanel.tsx +++ b/public/app/plugins/panel/graph2/GraphPanel.tsx @@ -1,67 +1,57 @@ -// Libraries -import _ from 'lodash'; -import React, { PureComponent } from 'react'; - -import { Graph, PanelProps, NullValueMode, colors, GraphSeriesXY, FieldType, getFirstTimeField } from '@grafana/ui'; +import React from 'react'; +import { PanelProps, GraphWithLegend /*, GraphSeriesXY*/ } from '@grafana/ui'; import { Options } from './types'; -import { getFlotPairs } from '@grafana/ui/src/utils/flotPairs'; - -interface Props extends PanelProps {} - -export class GraphPanel extends PureComponent { - render() { - const { data, timeRange, width, height } = this.props; - const { showLines, showBars, showPoints } = this.props.options; - - const graphs: GraphSeriesXY[] = []; - for (const series of data.series) { - const timeColumn = getFirstTimeField(series); - if (timeColumn < 0) { - continue; - } - - for (let i = 0; i < series.fields.length; i++) { - const field = series.fields[i]; - - // Show all numeric columns - if (field.type === FieldType.number) { - // Use external calculator just to make sure it works :) - const points = getFlotPairs({ - series, - xIndex: timeColumn, - yIndex: i, - nullValueMode: NullValueMode.Null, - }); - - if (points.length > 0) { - graphs.push({ - label: field.name, - data: points, - color: colors[graphs.length % colors.length], - }); - } - } - } - } - - if (graphs.length < 1) { - return ( -
-

No data found in response

-
- ); - } - +import { GraphPanelController } from './GraphPanelController'; +import { LegendDisplayMode } from '@grafana/ui/src/components/Legend/Legend'; + +interface GraphPanelProps extends PanelProps {} + +export const GraphPanel: React.FunctionComponent = ({ + data, + timeRange, + width, + height, + options, + onOptionsChange, +}) => { + if (!data) { return ( - +
+

No data found in response

+
); } -} + + const { + graph: { showLines, showBars, showPoints }, + legend: legendOptions, + } = options; + + const graphProps = { + showBars, + showLines, + showPoints, + }; + const { asTable, isVisible, ...legendProps } = legendOptions; + return ( + + {({ onSeriesToggle, ...controllerApi }) => { + return ( + + ); + }} + + ); +}; diff --git a/public/app/plugins/panel/graph2/GraphPanelController.tsx b/public/app/plugins/panel/graph2/GraphPanelController.tsx new file mode 100644 index 0000000000000..5ac4811586c08 --- /dev/null +++ b/public/app/plugins/panel/graph2/GraphPanelController.tsx @@ -0,0 +1,156 @@ +import React from 'react'; +import { GraphSeriesXY, PanelData } from '@grafana/ui'; +import difference from 'lodash/difference'; +import { getGraphSeriesModel } from './getGraphSeriesModel'; +import { Options, SeriesOptions } from './types'; +import { SeriesColorChangeHandler, SeriesAxisToggleHandler } from '@grafana/ui/src/components/Graph/GraphWithLegend'; + +interface GraphPanelControllerAPI { + series: GraphSeriesXY[]; + onSeriesAxisToggle: SeriesAxisToggleHandler; + onSeriesColorChange: SeriesColorChangeHandler; + onSeriesToggle: (label: string, event: React.MouseEvent) => void; + onToggleSort: (sortBy: string) => void; +} + +interface GraphPanelControllerProps { + children: (api: GraphPanelControllerAPI) => JSX.Element; + options: Options; + data: PanelData; + onOptionsChange: (options: Options) => void; +} + +interface GraphPanelControllerState { + graphSeriesModel: GraphSeriesXY[]; + hiddenSeries: string[]; +} + +export class GraphPanelController extends React.Component { + constructor(props: GraphPanelControllerProps) { + super(props); + + this.onSeriesToggle = this.onSeriesToggle.bind(this); + this.onSeriesColorChange = this.onSeriesColorChange.bind(this); + this.onSeriesAxisToggle = this.onSeriesAxisToggle.bind(this); + this.onToggleSort = this.onToggleSort.bind(this); + + this.state = { + graphSeriesModel: getGraphSeriesModel( + props.data, + props.options.series, + props.options.graph, + props.options.legend + ), + hiddenSeries: [], + }; + } + + static getDerivedStateFromProps(props: GraphPanelControllerProps, state: GraphPanelControllerState) { + return { + ...state, + graphSeriesModel: getGraphSeriesModel( + props.data, + props.options.series, + props.options.graph, + props.options.legend + ), + }; + } + + onSeriesOptionsUpdate(label: string, optionsUpdate: SeriesOptions) { + const { onOptionsChange, options } = this.props; + const updatedSeriesOptions: { [label: string]: SeriesOptions } = { ...options.series }; + updatedSeriesOptions[label] = optionsUpdate; + onOptionsChange({ + ...options, + series: updatedSeriesOptions, + }); + } + + onSeriesAxisToggle(label: string, yAxis: number) { + const { + options: { series }, + } = this.props; + const seriesOptionsUpdate: SeriesOptions = series[label] + ? { + ...series[label], + yAxis, + } + : { + yAxis, + }; + this.onSeriesOptionsUpdate(label, seriesOptionsUpdate); + } + + onSeriesColorChange(label: string, color: string) { + const { + options: { series }, + } = this.props; + const seriesOptionsUpdate: SeriesOptions = series[label] + ? { + ...series[label], + color, + } + : { + color, + }; + + this.onSeriesOptionsUpdate(label, seriesOptionsUpdate); + } + + onToggleSort(sortBy: string) { + const { onOptionsChange, options } = this.props; + onOptionsChange({ + ...options, + legend: { + ...options.legend, + sortBy, + sortDesc: sortBy === options.legend.sortBy ? !options.legend.sortDesc : false, + }, + }); + } + + onSeriesToggle(label: string, event: React.MouseEvent) { + const { hiddenSeries, graphSeriesModel } = this.state; + + if (event.ctrlKey || event.metaKey || event.shiftKey) { + // Toggling series with key makes the series itself to toggle + if (hiddenSeries.indexOf(label) > -1) { + this.setState({ + hiddenSeries: hiddenSeries.filter(series => series !== label), + }); + } else { + this.setState({ + hiddenSeries: hiddenSeries.concat([label]), + }); + } + } else { + // Toggling series with out key toggles all the series but the clicked one + const allSeriesLabels = graphSeriesModel.map(series => series.label); + + if (hiddenSeries.length + 1 === allSeriesLabels.length) { + this.setState({ hiddenSeries: [] }); + } else { + this.setState({ + hiddenSeries: difference(allSeriesLabels, [label]), + }); + } + } + } + + render() { + const { children } = this.props; + const { graphSeriesModel, hiddenSeries } = this.state; + + return children({ + series: graphSeriesModel.map(series => ({ + ...series, + isVisible: hiddenSeries.indexOf(series.label) === -1, + })), + onSeriesToggle: this.onSeriesToggle, + onSeriesColorChange: this.onSeriesColorChange, + onSeriesAxisToggle: this.onSeriesAxisToggle, + onToggleSort: this.onToggleSort, + }); + } +} diff --git a/public/app/plugins/panel/graph2/GraphPanelEditor.tsx b/public/app/plugins/panel/graph2/GraphPanelEditor.tsx index 1a64290759cab..afcbb68d2c159 100644 --- a/public/app/plugins/panel/graph2/GraphPanelEditor.tsx +++ b/public/app/plugins/panel/graph2/GraphPanelEditor.tsx @@ -3,40 +3,56 @@ import _ from 'lodash'; import React, { PureComponent } from 'react'; // Types -import { PanelEditorProps, Switch } from '@grafana/ui'; -import { Options } from './types'; +import { PanelEditorProps, Switch, LegendOptions, StatID } from '@grafana/ui'; +import { Options, GraphOptions } from './types'; +import { GraphLegendEditor } from './GraphLegendEditor'; export class GraphPanelEditor extends PureComponent> { + onGraphOptionsChange = (options: Partial) => { + this.props.onOptionsChange({ + ...this.props.options, + graph: { + ...this.props.options.graph, + ...options, + }, + }); + }; + + onLegendOptionsChange = (options: LegendOptions) => { + this.props.onOptionsChange({ ...this.props.options, legend: options }); + }; + onToggleLines = () => { - this.props.onOptionsChange({ ...this.props.options, showLines: !this.props.options.showLines }); + this.onGraphOptionsChange({ showLines: !this.props.options.graph.showLines }); }; onToggleBars = () => { - this.props.onOptionsChange({ ...this.props.options, showBars: !this.props.options.showBars }); + this.onGraphOptionsChange({ showBars: !this.props.options.graph.showBars }); }; onTogglePoints = () => { - this.props.onOptionsChange({ ...this.props.options, showPoints: !this.props.options.showPoints }); + this.onGraphOptionsChange({ showPoints: !this.props.options.graph.showPoints }); }; render() { - const { showBars, showPoints, showLines } = this.props.options; + const { + graph: { showBars, showPoints, showLines }, + } = this.props.options; return ( -
+ <>
Draw Modes
-
-
Test Options
- - - -
-
+ + ); } } diff --git a/public/app/plugins/panel/graph2/getGraphSeriesModel.ts b/public/app/plugins/panel/graph2/getGraphSeriesModel.ts new file mode 100644 index 0000000000000..c39a1627c1a1d --- /dev/null +++ b/public/app/plugins/panel/graph2/getGraphSeriesModel.ts @@ -0,0 +1,83 @@ +import { + GraphSeriesXY, + getFirstTimeField, + FieldType, + NullValueMode, + calculateStats, + colors, + getFlotPairs, + getColorFromHexRgbOrName, + getDisplayProcessor, + DisplayValue, + PanelData, +} from '@grafana/ui'; +import { SeriesOptions, GraphOptions } from './types'; +import { GraphLegendEditorLegendOptions } from './GraphLegendEditor'; + +export const getGraphSeriesModel = ( + data: PanelData, + seriesOptions: SeriesOptions, + graphOptions: GraphOptions, + legendOptions: GraphLegendEditorLegendOptions +) => { + const graphs: GraphSeriesXY[] = []; + + const displayProcessor = getDisplayProcessor({ + decimals: legendOptions.decimals, + }); + + for (const series of data.series) { + const timeColumn = getFirstTimeField(series); + if (timeColumn < 0) { + continue; + } + + for (let i = 0; i < series.fields.length; i++) { + const field = series.fields[i]; + + // Show all numeric columns + if (field.type === FieldType.number) { + // Use external calculator just to make sure it works :) + const points = getFlotPairs({ + series, + xIndex: timeColumn, + yIndex: i, + nullValueMode: NullValueMode.Null, + }); + + if (points.length > 0) { + const seriesStats = calculateStats({ series, stats: legendOptions.stats, fieldIndex: i }); + let statsDisplayValues; + + if (legendOptions.stats) { + statsDisplayValues = legendOptions.stats.map(stat => { + const statDisplayValue = displayProcessor(seriesStats[stat]); + + return { + ...statDisplayValue, + text: statDisplayValue.text, + title: stat, + }; + }); + } + + const seriesColor = + seriesOptions[field.name] && seriesOptions[field.name].color + ? getColorFromHexRgbOrName(seriesOptions[field.name].color) + : colors[graphs.length % colors.length]; + + graphs.push({ + label: field.name, + data: points, + color: seriesColor, + info: statsDisplayValues, + isVisible: true, + yAxis: (seriesOptions[field.name] && seriesOptions[field.name].yAxis) || 1, + }); + } + } + } + } + + return graphs; +}; diff --git a/public/app/plugins/panel/graph2/types.ts b/public/app/plugins/panel/graph2/types.ts index 1f6e9c093dd7b..18885c5025406 100644 --- a/public/app/plugins/panel/graph2/types.ts +++ b/public/app/plugins/panel/graph2/types.ts @@ -1,11 +1,34 @@ -export interface Options { +import { LegendOptions } from '@grafana/ui'; +import { GraphLegendEditorLegendOptions } from './GraphLegendEditor'; + +export interface SeriesOptions { + color?: string; + yAxis?: number; +} +export interface GraphOptions { showBars: boolean; showLines: boolean; showPoints: boolean; } +export interface Options { + graph: GraphOptions; + legend: LegendOptions & GraphLegendEditorLegendOptions; + series: { + [alias: string]: SeriesOptions; + }; +} + export const defaults: Options = { - showBars: false, - showLines: true, - showPoints: false, + graph: { + showBars: false, + showLines: true, + showPoints: false, + }, + legend: { + asTable: false, + isVisible: true, + placement: 'under', + }, + series: {}, }; From eb8af01a8a900494fc4acbf849431cc051372b93 Mon Sep 17 00:00:00 2001 From: Carl Bergquist Date: Wed, 24 Apr 2019 13:18:16 +0200 Subject: [PATCH 05/12] admin: add more stats about roles (#16667) closes #14967 --- pkg/models/stats.go | 27 +++++--- pkg/services/sqlstore/stars_test.go | 1 - pkg/services/sqlstore/stats.go | 86 +++++++++++++++++-------- pkg/services/sqlstore/stats_test.go | 19 ++++-- public/app/features/admin/state/apis.ts | 9 ++- scripts/circle-test-postgres.sh | 7 +- 6 files changed, 100 insertions(+), 49 deletions(-) diff --git a/pkg/models/stats.go b/pkg/models/stats.go index 0edd204ec032f..994170786b045 100644 --- a/pkg/models/stats.go +++ b/pkg/models/stats.go @@ -51,16 +51,23 @@ type GetAlertNotifierUsageStatsQuery struct { } type AdminStats struct { - Users int `json:"users"` - Orgs int `json:"orgs"` - Dashboards int `json:"dashboards"` - Snapshots int `json:"snapshots"` - Tags int `json:"tags"` - Datasources int `json:"datasources"` - Playlists int `json:"playlists"` - Stars int `json:"stars"` - Alerts int `json:"alerts"` - ActiveUsers int `json:"activeUsers"` + Orgs int `json:"orgs"` + Dashboards int `json:"dashboards"` + Snapshots int `json:"snapshots"` + Tags int `json:"tags"` + Datasources int `json:"datasources"` + Playlists int `json:"playlists"` + Stars int `json:"stars"` + Alerts int `json:"alerts"` + Users int `json:"users"` + Admins int `json:"admins"` + Editors int `json:"editors"` + Viewers int `json:"viewers"` + ActiveUsers int `json:"activeUsers"` + ActiveAdmins int `json:"activeAdmins"` + ActiveEditors int `json:"activeEditors"` + ActiveViewers int `json:"activeViewers"` + ActiveSessions int `json:"activeSessions"` } type GetAdminStatsQuery struct { diff --git a/pkg/services/sqlstore/stars_test.go b/pkg/services/sqlstore/stars_test.go index 4e48dfdbcb35c..43a612c1a5b8a 100644 --- a/pkg/services/sqlstore/stars_test.go +++ b/pkg/services/sqlstore/stars_test.go @@ -36,7 +36,6 @@ func TestUserStarsDataAccess(t *testing.T) { So(query.Result, ShouldBeFalse) }) - }) }) } diff --git a/pkg/services/sqlstore/stats.go b/pkg/services/sqlstore/stats.go index 2b7c35a4b4ad2..a1191cb21c8ee 100644 --- a/pkg/services/sqlstore/stats.go +++ b/pkg/services/sqlstore/stats.go @@ -89,52 +89,84 @@ func GetSystemStats(query *m.GetSystemStatsQuery) error { } func GetAdminStats(query *m.GetAdminStatsQuery) error { + activeEndDate := time.Now().Add(-activeUserTimeLimit) + roleCounter := func(role, alias string) string { + sql := + ` + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("user") + ` as u + WHERE + (SELECT COUNT(*) + FROM org_user + WHERE org_user.user_id=u.id + AND org_user.role='` + role + `')>0 + ) as ` + alias + `, + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("user") + ` as u + WHERE + (SELECT COUNT(*) + FROM org_user + WHERE org_user.user_id=u.id + AND org_user.role='` + role + `')>0 + AND u.last_seen_at>? + ) as active_` + alias + + return sql + } + var rawSql = `SELECT - ( - SELECT COUNT(*) - FROM ` + dialect.Quote("user") + ` - ) AS users, - ( + ( SELECT COUNT(*) FROM ` + dialect.Quote("org") + ` - ) AS orgs, - ( + ) AS orgs, + ( SELECT COUNT(*) FROM ` + dialect.Quote("dashboard") + ` - ) AS dashboards, - ( + ) AS dashboards, + ( SELECT COUNT(*) FROM ` + dialect.Quote("dashboard_snapshot") + ` - ) AS snapshots, - ( + ) AS snapshots, + ( SELECT COUNT( DISTINCT ( ` + dialect.Quote("term") + ` )) FROM ` + dialect.Quote("dashboard_tag") + ` - ) AS tags, - ( + ) AS tags, + ( SELECT COUNT(*) FROM ` + dialect.Quote("data_source") + ` - ) AS datasources, - ( + ) AS datasources, + ( SELECT COUNT(*) FROM ` + dialect.Quote("playlist") + ` - ) AS playlists, - ( + ) AS playlists, + ( SELECT COUNT(*) FROM ` + dialect.Quote("star") + ` - ) AS stars, - ( + ) AS stars, + ( SELECT COUNT(*) FROM ` + dialect.Quote("alert") + ` - ) AS alerts, - ( - SELECT COUNT(*) - from ` + dialect.Quote("user") + ` where last_seen_at > ? - ) as active_users + ) AS alerts, + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("user") + ` + ) AS users, + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("user") + ` where last_seen_at > ? + ) as active_users, + ` + roleCounter("Admin", "admins") + `, + ` + roleCounter("Editor", "editors") + `, + ` + roleCounter("Viewer", "viewers") + `, + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("user_auth_token") + ` where rotated_at > ? + ) as active_sessions ` - activeUserDeadlineDate := time.Now().Add(-activeUserTimeLimit) - var stats m.AdminStats - _, err := x.SQL(rawSql, activeUserDeadlineDate).Get(&stats) + _, err := x.SQL(rawSql, activeEndDate, activeEndDate, activeEndDate, activeEndDate, activeEndDate.Unix()).Get(&stats) if err != nil { return err } diff --git a/pkg/services/sqlstore/stats_test.go b/pkg/services/sqlstore/stats_test.go index 6949a0dbda2d9..38396aef5b058 100644 --- a/pkg/services/sqlstore/stats_test.go +++ b/pkg/services/sqlstore/stats_test.go @@ -4,43 +4,48 @@ import ( "context" "testing" - m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/models" . "github.com/smartystreets/goconvey/convey" ) func TestStatsDataAccess(t *testing.T) { - Convey("Testing Stats Data Access", t, func() { InitTestDB(t) Convey("Get system stats should not results in error", func() { - query := m.GetSystemStatsQuery{} + query := models.GetSystemStatsQuery{} err := GetSystemStats(&query) So(err, ShouldBeNil) }) Convey("Get system user count stats should not results in error", func() { - query := m.GetSystemUserCountStatsQuery{} + query := models.GetSystemUserCountStatsQuery{} err := GetSystemUserCountStats(context.Background(), &query) So(err, ShouldBeNil) }) Convey("Get datasource stats should not results in error", func() { - query := m.GetDataSourceStatsQuery{} + query := models.GetDataSourceStatsQuery{} err := GetDataSourceStats(&query) So(err, ShouldBeNil) }) Convey("Get datasource access stats should not results in error", func() { - query := m.GetDataSourceAccessStatsQuery{} + query := models.GetDataSourceAccessStatsQuery{} err := GetDataSourceAccessStats(&query) So(err, ShouldBeNil) }) Convey("Get alert notifier stats should not results in error", func() { - query := m.GetAlertNotifierUsageStatsQuery{} + query := models.GetAlertNotifierUsageStatsQuery{} err := GetAlertNotifiersUsageStats(context.Background(), &query) So(err, ShouldBeNil) }) + + Convey("Get admin stats should not result in error", func() { + query := models.GetAdminStatsQuery{} + err := GetAdminStats(&query) + So(err, ShouldBeNil) + }) }) } diff --git a/public/app/features/admin/state/apis.ts b/public/app/features/admin/state/apis.ts index d81fd299493fd..05321c6e71484 100644 --- a/public/app/features/admin/state/apis.ts +++ b/public/app/features/admin/state/apis.ts @@ -10,8 +10,15 @@ export const getServerStats = async (): Promise => { const res = await getBackendSrv().get('api/admin/stats'); return [ { name: 'Total users', value: res.users }, - { name: 'Total dashboards', value: res.dashboards }, + { name: 'Total admins', value: res.admins }, + { name: 'Total editors', value: res.editors }, + { name: 'Total viewers', value: res.viewers }, { name: 'Active users (seen last 30 days)', value: res.activeUsers }, + { name: 'Active admins (seen last 30 days)', value: res.activeAdmins }, + { name: 'Active editors (seen last 30 days)', value: res.activeEditors }, + { name: 'Active viewers (seen last 30 days)', value: res.activeViewers }, + { name: 'Active sessions', value: res.activeSessions }, + { name: 'Total dashboards', value: res.dashboards }, { name: 'Total orgs', value: res.orgs }, { name: 'Total playlists', value: res.playlists }, { name: 'Total snapshots', value: res.snapshots }, diff --git a/scripts/circle-test-postgres.sh b/scripts/circle-test-postgres.sh index 7ddfe6887d933..df4897484ad4f 100755 --- a/scripts/circle-test-postgres.sh +++ b/scripts/circle-test-postgres.sh @@ -12,6 +12,7 @@ function exit_if_fail { export GRAFANA_TEST_DB=postgres -time for d in $(go list ./pkg/...); do - exit_if_fail go test -tags=integration $d -done \ No newline at end of file +exit_if_fail go test -v -run="StatsDataAccess" -tags=integration ./pkg/services/sqlstore/... +#time for d in $(go list ./pkg/...); do +# exit_if_fail go test -tags=integration $d +#done \ No newline at end of file From fcebd713a59b89cfaed30d5f8fc54629d990e3f2 Mon Sep 17 00:00:00 2001 From: Andrej Ocenas Date: Wed, 24 Apr 2019 15:39:47 +0200 Subject: [PATCH 06/12] Provisioning: Interpolate env vars in provisioning files (#16499) * Add value types with custom unmarshalling logic * Add env support for notifications config * Use env vars in json data tests for values * Add some more complexities to value tests * Update comment with example usage * Set env directly in the tests, removing patching * Update documentation * Add env var to the file reader tests * Add raw value * Post merge fixes * Add comment --- docs/sources/administration/provisioning.md | 38 +--- .../dashboards/config_reader_test.go | 3 + .../dashboards-from-disk/dev-dashboards.yaml | 2 +- pkg/services/provisioning/dashboards/types.go | 37 +-- .../datasources/config_reader_test.go | 3 + .../all-properties/all-properties.yaml | 2 +- .../provisioning/datasources/types.go | 96 ++++---- .../notifiers/alert_notifications.go | 33 --- .../provisioning/notifiers/config_reader.go | 2 +- .../notifiers/config_reader_test.go | 3 + .../correct-properties.yaml | 4 +- pkg/services/provisioning/notifiers/types.go | 100 +++++++-- pkg/services/provisioning/values/values.go | 206 +++++++++++++++++ .../provisioning/values/values_test.go | 210 ++++++++++++++++++ 14 files changed, 598 insertions(+), 141 deletions(-) create mode 100644 pkg/services/provisioning/values/values.go create mode 100644 pkg/services/provisioning/values/values_test.go diff --git a/docs/sources/administration/provisioning.md b/docs/sources/administration/provisioning.md index c09c3caec8249..aa84863bf7ae1 100644 --- a/docs/sources/administration/provisioning.md +++ b/docs/sources/administration/provisioning.md @@ -30,33 +30,19 @@ Checkout the [configuration](/installation/configuration) page for more informat ### Using Environment Variables -All options in the configuration file (listed below) can be overridden -using environment variables using the syntax: +It is possible to use environment variable interpolation in all 3 provisioning config types. Allowed syntax +is either `$ENV_VAR_NAME` or `${ENV_VAR_NAME}` and can be used only for values not for keys or bigger parts +of the configs. It is not available in the dashboards definition files just the dashboard provisioning +configuration. +Example: -```bash -GF__ -``` - -Where the section name is the text within the brackets. Everything -should be upper case and `.` should be replaced by `_`. For example, given these configuration settings: - -```bash -# default section -instance_name = ${HOSTNAME} - -[security] -admin_user = admin - -[auth.google] -client_secret = 0ldS3cretKey -``` - -Overriding will be done like so: - -```bash -export GF_DEFAULT_INSTANCE_NAME=my-instance -export GF_SECURITY_ADMIN_USER=true -export GF_AUTH_GOOGLE_CLIENT_SECRET=newS3cretKey +```yaml +datasources: +- name: Graphite + url: http://localhost:$PORT + user: $USER + secureJsonData: + password: $PASSWORD ```
diff --git a/pkg/services/provisioning/dashboards/config_reader_test.go b/pkg/services/provisioning/dashboards/config_reader_test.go index 8ce322a76079f..18d8022d62d04 100644 --- a/pkg/services/provisioning/dashboards/config_reader_test.go +++ b/pkg/services/provisioning/dashboards/config_reader_test.go @@ -1,6 +1,7 @@ package dashboards import ( + "os" "testing" "github.com/grafana/grafana/pkg/log" @@ -18,8 +19,10 @@ func TestDashboardsAsConfig(t *testing.T) { logger := log.New("test-logger") Convey("Can read config file version 1 format", func() { + _ = os.Setenv("TEST_VAR", "general") cfgProvider := configReader{path: simpleDashboardConfig, log: logger} cfg, err := cfgProvider.readConfig() + _ = os.Unsetenv("TEST_VAR") So(err, ShouldBeNil) validateDashboardAsConfig(t, cfg) diff --git a/pkg/services/provisioning/dashboards/testdata/test-configs/dashboards-from-disk/dev-dashboards.yaml b/pkg/services/provisioning/dashboards/testdata/test-configs/dashboards-from-disk/dev-dashboards.yaml index ee3d8ea73b0fa..42b6ecd75a5e2 100644 --- a/pkg/services/provisioning/dashboards/testdata/test-configs/dashboards-from-disk/dev-dashboards.yaml +++ b/pkg/services/provisioning/dashboards/testdata/test-configs/dashboards-from-disk/dev-dashboards.yaml @@ -1,7 +1,7 @@ apiVersion: 1 providers: -- name: 'general dashboards' +- name: '$TEST_VAR dashboards' orgId: 2 folder: 'developers' folderUid: 'xyz' diff --git a/pkg/services/provisioning/dashboards/types.go b/pkg/services/provisioning/dashboards/types.go index d5364c33509d4..547e5a4427c26 100644 --- a/pkg/services/provisioning/dashboards/types.go +++ b/pkg/services/provisioning/dashboards/types.go @@ -1,6 +1,7 @@ package dashboards import ( + "github.com/grafana/grafana/pkg/services/provisioning/values" "time" "github.com/grafana/grafana/pkg/components/simplejson" @@ -42,15 +43,15 @@ type DashboardAsConfigV1 struct { } type DashboardProviderConfigs struct { - Name string `json:"name" yaml:"name"` - Type string `json:"type" yaml:"type"` - OrgId int64 `json:"orgId" yaml:"orgId"` - Folder string `json:"folder" yaml:"folder"` - FolderUid string `json:"folderUid" yaml:"folderUid"` - Editable bool `json:"editable" yaml:"editable"` - Options map[string]interface{} `json:"options" yaml:"options"` - DisableDeletion bool `json:"disableDeletion" yaml:"disableDeletion"` - UpdateIntervalSeconds int64 `json:"updateIntervalSeconds" yaml:"updateIntervalSeconds"` + Name values.StringValue `json:"name" yaml:"name"` + Type values.StringValue `json:"type" yaml:"type"` + OrgId values.Int64Value `json:"orgId" yaml:"orgId"` + Folder values.StringValue `json:"folder" yaml:"folder"` + FolderUid values.StringValue `json:"folderUid" yaml:"folderUid"` + Editable values.BoolValue `json:"editable" yaml:"editable"` + Options values.JSONValue `json:"options" yaml:"options"` + DisableDeletion values.BoolValue `json:"disableDeletion" yaml:"disableDeletion"` + UpdateIntervalSeconds values.Int64Value `json:"updateIntervalSeconds" yaml:"updateIntervalSeconds"` } func createDashboardJson(data *simplejson.Json, lastModified time.Time, cfg *DashboardsAsConfig, folderId int64) (*dashboards.SaveDashboardDTO, error) { @@ -94,15 +95,15 @@ func (dc *DashboardAsConfigV1) mapToDashboardAsConfig() []*DashboardsAsConfig { for _, v := range dc.Providers { r = append(r, &DashboardsAsConfig{ - Name: v.Name, - Type: v.Type, - OrgId: v.OrgId, - Folder: v.Folder, - FolderUid: v.FolderUid, - Editable: v.Editable, - Options: v.Options, - DisableDeletion: v.DisableDeletion, - UpdateIntervalSeconds: v.UpdateIntervalSeconds, + Name: v.Name.Value(), + Type: v.Type.Value(), + OrgId: v.OrgId.Value(), + Folder: v.Folder.Value(), + FolderUid: v.FolderUid.Value(), + Editable: v.Editable.Value(), + Options: v.Options.Value(), + DisableDeletion: v.DisableDeletion.Value(), + UpdateIntervalSeconds: v.UpdateIntervalSeconds.Value(), }) } diff --git a/pkg/services/provisioning/datasources/config_reader_test.go b/pkg/services/provisioning/datasources/config_reader_test.go index 07c8d68e75c14..6aba826221415 100644 --- a/pkg/services/provisioning/datasources/config_reader_test.go +++ b/pkg/services/provisioning/datasources/config_reader_test.go @@ -1,6 +1,7 @@ package datasources import ( + "os" "testing" "github.com/grafana/grafana/pkg/bus" @@ -146,8 +147,10 @@ func TestDatasourceAsConfig(t *testing.T) { }) Convey("can read all properties from version 1", func() { + _ = os.Setenv("TEST_VAR", "name") cfgProvifer := &configReader{log: log.New("test logger")} cfg, err := cfgProvifer.readConfig(allProperties) + _ = os.Unsetenv("TEST_VAR") if err != nil { t.Fatalf("readConfig return an error %v", err) } diff --git a/pkg/services/provisioning/datasources/testdata/all-properties/all-properties.yaml b/pkg/services/provisioning/datasources/testdata/all-properties/all-properties.yaml index b92b81f70792d..abd1253f8399f 100644 --- a/pkg/services/provisioning/datasources/testdata/all-properties/all-properties.yaml +++ b/pkg/services/provisioning/datasources/testdata/all-properties/all-properties.yaml @@ -1,7 +1,7 @@ apiVersion: 1 datasources: - - name: name + - name: $TEST_VAR type: type access: proxy orgId: 2 diff --git a/pkg/services/provisioning/datasources/types.go b/pkg/services/provisioning/datasources/types.go index f619fd5f0054b..bbd072052dd70 100644 --- a/pkg/services/provisioning/datasources/types.go +++ b/pkg/services/provisioning/datasources/types.go @@ -4,6 +4,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/provisioning/values" ) type ConfigVersion struct { @@ -64,8 +65,8 @@ type DeleteDatasourceConfigV0 struct { } type DeleteDatasourceConfigV1 struct { - OrgId int64 `json:"orgId" yaml:"orgId"` - Name string `json:"name" yaml:"name"` + OrgId values.Int64Value `json:"orgId" yaml:"orgId"` + Name values.StringValue `json:"name" yaml:"name"` } type DataSourceFromConfigV0 struct { @@ -89,23 +90,23 @@ type DataSourceFromConfigV0 struct { } type DataSourceFromConfigV1 struct { - OrgId int64 `json:"orgId" yaml:"orgId"` - Version int `json:"version" yaml:"version"` - Name string `json:"name" yaml:"name"` - Type string `json:"type" yaml:"type"` - Access string `json:"access" yaml:"access"` - Url string `json:"url" yaml:"url"` - Password string `json:"password" yaml:"password"` - User string `json:"user" yaml:"user"` - Database string `json:"database" yaml:"database"` - BasicAuth bool `json:"basicAuth" yaml:"basicAuth"` - BasicAuthUser string `json:"basicAuthUser" yaml:"basicAuthUser"` - BasicAuthPassword string `json:"basicAuthPassword" yaml:"basicAuthPassword"` - WithCredentials bool `json:"withCredentials" yaml:"withCredentials"` - IsDefault bool `json:"isDefault" yaml:"isDefault"` - JsonData map[string]interface{} `json:"jsonData" yaml:"jsonData"` - SecureJsonData map[string]string `json:"secureJsonData" yaml:"secureJsonData"` - Editable bool `json:"editable" yaml:"editable"` + OrgId values.Int64Value `json:"orgId" yaml:"orgId"` + Version values.IntValue `json:"version" yaml:"version"` + Name values.StringValue `json:"name" yaml:"name"` + Type values.StringValue `json:"type" yaml:"type"` + Access values.StringValue `json:"access" yaml:"access"` + Url values.StringValue `json:"url" yaml:"url"` + Password values.StringValue `json:"password" yaml:"password"` + User values.StringValue `json:"user" yaml:"user"` + Database values.StringValue `json:"database" yaml:"database"` + BasicAuth values.BoolValue `json:"basicAuth" yaml:"basicAuth"` + BasicAuthUser values.StringValue `json:"basicAuthUser" yaml:"basicAuthUser"` + BasicAuthPassword values.StringValue `json:"basicAuthPassword" yaml:"basicAuthPassword"` + WithCredentials values.BoolValue `json:"withCredentials" yaml:"withCredentials"` + IsDefault values.BoolValue `json:"isDefault" yaml:"isDefault"` + JsonData values.JSONValue `json:"jsonData" yaml:"jsonData"` + SecureJsonData values.StringMapValue `json:"secureJsonData" yaml:"secureJsonData"` + Editable values.BoolValue `json:"editable" yaml:"editable"` } func (cfg *DatasourcesAsConfigV1) mapToDatasourceFromConfig(apiVersion int64) *DatasourcesAsConfig { @@ -119,36 +120,47 @@ func (cfg *DatasourcesAsConfigV1) mapToDatasourceFromConfig(apiVersion int64) *D for _, ds := range cfg.Datasources { r.Datasources = append(r.Datasources, &DataSourceFromConfig{ - OrgId: ds.OrgId, - Name: ds.Name, - Type: ds.Type, - Access: ds.Access, - Url: ds.Url, - Password: ds.Password, - User: ds.User, - Database: ds.Database, - BasicAuth: ds.BasicAuth, - BasicAuthUser: ds.BasicAuthUser, - BasicAuthPassword: ds.BasicAuthPassword, - WithCredentials: ds.WithCredentials, - IsDefault: ds.IsDefault, - JsonData: ds.JsonData, - SecureJsonData: ds.SecureJsonData, - Editable: ds.Editable, - Version: ds.Version, + OrgId: ds.OrgId.Value(), + Name: ds.Name.Value(), + Type: ds.Type.Value(), + Access: ds.Access.Value(), + Url: ds.Url.Value(), + Password: ds.Password.Value(), + User: ds.User.Value(), + Database: ds.Database.Value(), + BasicAuth: ds.BasicAuth.Value(), + BasicAuthUser: ds.BasicAuthUser.Value(), + BasicAuthPassword: ds.BasicAuthPassword.Value(), + WithCredentials: ds.WithCredentials.Value(), + IsDefault: ds.IsDefault.Value(), + JsonData: ds.JsonData.Value(), + SecureJsonData: ds.SecureJsonData.Value(), + Editable: ds.Editable.Value(), + Version: ds.Version.Value(), }) - if ds.Password != "" { - cfg.log.Warn("[Deprecated] the use of password field is deprecated. Please use secureJsonData.password", "datasource name", ds.Name) + + // Using Raw value for the warnings here so that even if it uses env interpolation and the env var is empty + // it will still warn + if len(ds.Password.Raw) > 0 { + cfg.log.Warn( + "[Deprecated] the use of password field is deprecated. Please use secureJsonData.password", + "datasource name", + ds.Name.Value(), + ) } - if ds.BasicAuthPassword != "" { - cfg.log.Warn("[Deprecated] the use of basicAuthPassword field is deprecated. Please use secureJsonData.basicAuthPassword", "datasource name", ds.Name) + if len(ds.BasicAuthPassword.Raw) > 0 { + cfg.log.Warn( + "[Deprecated] the use of basicAuthPassword field is deprecated. Please use secureJsonData.basicAuthPassword", + "datasource name", + ds.Name.Value(), + ) } } for _, ds := range cfg.DeleteDatasources { r.DeleteDatasources = append(r.DeleteDatasources, &DeleteDatasourceConfig{ - OrgId: ds.OrgId, - Name: ds.Name, + OrgId: ds.OrgId.Value(), + Name: ds.Name.Value(), }) } diff --git a/pkg/services/provisioning/notifiers/alert_notifications.go b/pkg/services/provisioning/notifiers/alert_notifications.go index 7b595a3d32f24..3659a73832ee5 100644 --- a/pkg/services/provisioning/notifiers/alert_notifications.go +++ b/pkg/services/provisioning/notifiers/alert_notifications.go @@ -131,39 +131,6 @@ func (dc *NotificationProvisioner) mergeNotifications(notificationToMerge []*not return nil } -func (cfg *notificationsAsConfig) mapToNotificationFromConfig() *notificationsAsConfig { - r := ¬ificationsAsConfig{} - if cfg == nil { - return r - } - - for _, notification := range cfg.Notifications { - r.Notifications = append(r.Notifications, ¬ificationFromConfig{ - Uid: notification.Uid, - OrgId: notification.OrgId, - OrgName: notification.OrgName, - Name: notification.Name, - Type: notification.Type, - IsDefault: notification.IsDefault, - Settings: notification.Settings, - DisableResolveMessage: notification.DisableResolveMessage, - Frequency: notification.Frequency, - SendReminder: notification.SendReminder, - }) - } - - for _, notification := range cfg.DeleteNotifications { - r.DeleteNotifications = append(r.DeleteNotifications, &deleteNotificationConfig{ - Uid: notification.Uid, - OrgId: notification.OrgId, - OrgName: notification.OrgName, - Name: notification.Name, - }) - } - - return r -} - func (dc *NotificationProvisioner) applyChanges(configPath string) error { configs, err := dc.cfgProvider.readConfig(configPath) if err != nil { diff --git a/pkg/services/provisioning/notifiers/config_reader.go b/pkg/services/provisioning/notifiers/config_reader.go index c1b4cbf9f29b5..896d5105cedba 100644 --- a/pkg/services/provisioning/notifiers/config_reader.go +++ b/pkg/services/provisioning/notifiers/config_reader.go @@ -63,7 +63,7 @@ func (cr *configReader) parseNotificationConfig(path string, file os.FileInfo) ( return nil, err } - var cfg *notificationsAsConfig + var cfg *notificationsAsConfigV0 err = yaml.Unmarshal(yamlFile, &cfg) if err != nil { return nil, err diff --git a/pkg/services/provisioning/notifiers/config_reader_test.go b/pkg/services/provisioning/notifiers/config_reader_test.go index 87645ee7d31b4..d705bd0d93d9d 100644 --- a/pkg/services/provisioning/notifiers/config_reader_test.go +++ b/pkg/services/provisioning/notifiers/config_reader_test.go @@ -1,6 +1,7 @@ package notifiers import ( + "os" "testing" "github.com/grafana/grafana/pkg/log" @@ -43,8 +44,10 @@ func TestNotificationAsConfig(t *testing.T) { }) Convey("Can read correct properties", func() { + _ = os.Setenv("TEST_VAR", "default") cfgProvifer := &configReader{log: log.New("test logger")} cfg, err := cfgProvifer.readConfig(correct_properties) + _ = os.Unsetenv("TEST_VAR") if err != nil { t.Fatalf("readConfig return an error %v", err) } diff --git a/pkg/services/provisioning/notifiers/testdata/test-configs/correct-properties/correct-properties.yaml b/pkg/services/provisioning/notifiers/testdata/test-configs/correct-properties/correct-properties.yaml index af0736f35a4a6..1d846f64473ba 100644 --- a/pkg/services/provisioning/notifiers/testdata/test-configs/correct-properties/correct-properties.yaml +++ b/pkg/services/provisioning/notifiers/testdata/test-configs/correct-properties/correct-properties.yaml @@ -1,5 +1,5 @@ notifiers: - - name: default-slack-notification + - name: $TEST_VAR-slack-notification type: slack uid: notifier1 org_id: 2 @@ -39,4 +39,4 @@ delete_notifiers: org_id: 0 uid: "notifier3" - name: Deleted notification with whitespaces in name - uid: "notifier4" \ No newline at end of file + uid: "notifier4" diff --git a/pkg/services/provisioning/notifiers/types.go b/pkg/services/provisioning/notifiers/types.go index f788da79c79af..48ff9a6ce1d95 100644 --- a/pkg/services/provisioning/notifiers/types.go +++ b/pkg/services/provisioning/notifiers/types.go @@ -1,30 +1,61 @@ package notifiers -import "github.com/grafana/grafana/pkg/components/simplejson" +import ( + "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/services/provisioning/values" +) +// notificationsAsConfig is normalized data object for notifications config data. Any config version should be mappable +// to this type. type notificationsAsConfig struct { - Notifications []*notificationFromConfig `json:"notifiers" yaml:"notifiers"` - DeleteNotifications []*deleteNotificationConfig `json:"delete_notifiers" yaml:"delete_notifiers"` + Notifications []*notificationFromConfig + DeleteNotifications []*deleteNotificationConfig } type deleteNotificationConfig struct { - Uid string `json:"uid" yaml:"uid"` - Name string `json:"name" yaml:"name"` - OrgId int64 `json:"org_id" yaml:"org_id"` - OrgName string `json:"org_name" yaml:"org_name"` + Uid string + Name string + OrgId int64 + OrgName string } type notificationFromConfig struct { - Uid string `json:"uid" yaml:"uid"` - OrgId int64 `json:"org_id" yaml:"org_id"` - OrgName string `json:"org_name" yaml:"org_name"` - Name string `json:"name" yaml:"name"` - Type string `json:"type" yaml:"type"` - SendReminder bool `json:"send_reminder" yaml:"send_reminder"` - DisableResolveMessage bool `json:"disable_resolve_message" yaml:"disable_resolve_message"` - Frequency string `json:"frequency" yaml:"frequency"` - IsDefault bool `json:"is_default" yaml:"is_default"` - Settings map[string]interface{} `json:"settings" yaml:"settings"` + Uid string + OrgId int64 + OrgName string + Name string + Type string + SendReminder bool + DisableResolveMessage bool + Frequency string + IsDefault bool + Settings map[string]interface{} +} + +// notificationsAsConfigV0 is mapping for zero version configs. This is mapped to its normalised version. +type notificationsAsConfigV0 struct { + Notifications []*notificationFromConfigV0 `json:"notifiers" yaml:"notifiers"` + DeleteNotifications []*deleteNotificationConfigV0 `json:"delete_notifiers" yaml:"delete_notifiers"` +} + +type deleteNotificationConfigV0 struct { + Uid values.StringValue `json:"uid" yaml:"uid"` + Name values.StringValue `json:"name" yaml:"name"` + OrgId values.Int64Value `json:"org_id" yaml:"org_id"` + OrgName values.StringValue `json:"org_name" yaml:"org_name"` +} + +type notificationFromConfigV0 struct { + Uid values.StringValue `json:"uid" yaml:"uid"` + OrgId values.Int64Value `json:"org_id" yaml:"org_id"` + OrgName values.StringValue `json:"org_name" yaml:"org_name"` + Name values.StringValue `json:"name" yaml:"name"` + Type values.StringValue `json:"type" yaml:"type"` + SendReminder values.BoolValue `json:"send_reminder" yaml:"send_reminder"` + DisableResolveMessage values.BoolValue `json:"disable_resolve_message" yaml:"disable_resolve_message"` + Frequency values.StringValue `json:"frequency" yaml:"frequency"` + IsDefault values.BoolValue `json:"is_default" yaml:"is_default"` + Settings values.JSONValue `json:"settings" yaml:"settings"` } func (notification notificationFromConfig) SettingsToJson() *simplejson.Json { @@ -36,3 +67,38 @@ func (notification notificationFromConfig) SettingsToJson() *simplejson.Json { } return settings } + +// mapToNotificationFromConfig maps config syntax to normalized notificationsAsConfig object. Every version +// of the config syntax should have this function. +func (cfg *notificationsAsConfigV0) mapToNotificationFromConfig() *notificationsAsConfig { + r := ¬ificationsAsConfig{} + if cfg == nil { + return r + } + + for _, notification := range cfg.Notifications { + r.Notifications = append(r.Notifications, ¬ificationFromConfig{ + Uid: notification.Uid.Value(), + OrgId: notification.OrgId.Value(), + OrgName: notification.OrgName.Value(), + Name: notification.Name.Value(), + Type: notification.Type.Value(), + IsDefault: notification.IsDefault.Value(), + Settings: notification.Settings.Value(), + DisableResolveMessage: notification.DisableResolveMessage.Value(), + Frequency: notification.Frequency.Value(), + SendReminder: notification.SendReminder.Value(), + }) + } + + for _, notification := range cfg.DeleteNotifications { + r.DeleteNotifications = append(r.DeleteNotifications, &deleteNotificationConfig{ + Uid: notification.Uid.Value(), + OrgId: notification.OrgId.Value(), + OrgName: notification.OrgName.Value(), + Name: notification.Name.Value(), + }) + } + + return r +} diff --git a/pkg/services/provisioning/values/values.go b/pkg/services/provisioning/values/values.go new file mode 100644 index 0000000000000..48c7cd6444e63 --- /dev/null +++ b/pkg/services/provisioning/values/values.go @@ -0,0 +1,206 @@ +// A set of value types to use in provisioning. They add custom unmarshaling logic that puts the string values +// through os.ExpandEnv. +// Usage: +// type Data struct { +// Field StringValue `yaml:"field"` // Instead of string +// } +// d := &Data{} +// // unmarshal into d +// d.Field.Value() // returns the final interpolated value from the yaml file +// +package values + +import ( + "github.com/pkg/errors" + "os" + "reflect" + "strconv" +) + +type IntValue struct { + value int + Raw string +} + +func (val *IntValue) UnmarshalYAML(unmarshal func(interface{}) error) error { + interpolated, err := getInterpolated(unmarshal) + if err != nil { + return err + } + if len(interpolated.value) == 0 { + // To keep the same behaviour as the yaml lib which just does not set the value if it is empty. + return nil + } + val.Raw = interpolated.raw + val.value, err = strconv.Atoi(interpolated.value) + return errors.Wrap(err, "cannot convert value int") +} + +func (val *IntValue) Value() int { + return val.value +} + +type Int64Value struct { + value int64 + Raw string +} + +func (val *Int64Value) UnmarshalYAML(unmarshal func(interface{}) error) error { + interpolated, err := getInterpolated(unmarshal) + if err != nil { + return err + } + if len(interpolated.value) == 0 { + // To keep the same behaviour as the yaml lib which just does not set the value if it is empty. + return nil + } + val.Raw = interpolated.raw + val.value, err = strconv.ParseInt(interpolated.value, 10, 64) + return err +} + +func (val *Int64Value) Value() int64 { + return val.value +} + +type StringValue struct { + value string + Raw string +} + +func (val *StringValue) UnmarshalYAML(unmarshal func(interface{}) error) error { + interpolated, err := getInterpolated(unmarshal) + if err != nil { + return err + } + val.Raw = interpolated.raw + val.value = interpolated.value + return err +} + +func (val *StringValue) Value() string { + return val.value +} + +type BoolValue struct { + value bool + Raw string +} + +func (val *BoolValue) UnmarshalYAML(unmarshal func(interface{}) error) error { + interpolated, err := getInterpolated(unmarshal) + if err != nil { + return err + } + val.Raw = interpolated.raw + val.value, err = strconv.ParseBool(interpolated.value) + return err +} + +func (val *BoolValue) Value() bool { + return val.value +} + +type JSONValue struct { + value map[string]interface{} + Raw map[string]interface{} +} + +func (val *JSONValue) UnmarshalYAML(unmarshal func(interface{}) error) error { + unmarshaled := make(map[string]interface{}) + err := unmarshal(unmarshaled) + if err != nil { + return err + } + val.Raw = unmarshaled + interpolated := make(map[string]interface{}) + for key, val := range unmarshaled { + interpolated[key] = tranformInterface(val) + } + val.value = interpolated + return err +} + +func (val *JSONValue) Value() map[string]interface{} { + return val.value +} + +type StringMapValue struct { + value map[string]string + Raw map[string]string +} + +func (val *StringMapValue) UnmarshalYAML(unmarshal func(interface{}) error) error { + unmarshaled := make(map[string]string) + err := unmarshal(unmarshaled) + if err != nil { + return err + } + val.Raw = unmarshaled + interpolated := make(map[string]string) + for key, val := range unmarshaled { + interpolated[key] = interpolateValue(val) + } + val.value = interpolated + return err +} + +func (val *StringMapValue) Value() map[string]string { + return val.value +} + +// tranformInterface tries to transform any interface type into proper value with env expansion. It travers maps and +// slices and the actual interpolation is done on all simple string values in the structure. It returns a copy of any +// map or slice value instead of modifying them in place. +func tranformInterface(i interface{}) interface{} { + switch reflect.TypeOf(i).Kind() { + case reflect.Slice: + return transformSlice(i.([]interface{})) + case reflect.Map: + return transformMap(i.(map[interface{}]interface{})) + case reflect.String: + return interpolateValue(i.(string)) + default: + // Was int, float or some other value that we do not need to do any transform on. + return i + } +} + +func transformSlice(i []interface{}) interface{} { + var transformed []interface{} + for _, val := range i { + transformed = append(transformed, tranformInterface(val)) + } + return transformed +} + +func transformMap(i map[interface{}]interface{}) interface{} { + transformed := make(map[interface{}]interface{}) + for key, val := range i { + transformed[key] = tranformInterface(val) + } + return transformed +} + +// interpolateValue returns final value after interpolation. At the moment only env var interpolation is done +// here but in the future something like interpolation from file could be also done here. +func interpolateValue(val string) string { + return os.ExpandEnv(val) +} + +type interpolated struct { + value string + raw string +} + +// getInterpolated unmarshals the value as string and runs interpolation on it. It is the responsibility of each +// value type to convert this string value to appropriate type. +func getInterpolated(unmarshal func(interface{}) error) (*interpolated, error) { + var raw string + err := unmarshal(&raw) + if err != nil { + return &interpolated{}, err + } + value := interpolateValue(raw) + return &interpolated{raw: raw, value: value}, nil +} diff --git a/pkg/services/provisioning/values/values_test.go b/pkg/services/provisioning/values/values_test.go new file mode 100644 index 0000000000000..284069be39584 --- /dev/null +++ b/pkg/services/provisioning/values/values_test.go @@ -0,0 +1,210 @@ +package values + +import ( + . "github.com/smartystreets/goconvey/convey" + "gopkg.in/yaml.v2" + "os" + "testing" +) + +func TestValues(t *testing.T) { + Convey("Values", t, func() { + os.Setenv("INT", "1") + os.Setenv("STRING", "test") + os.Setenv("BOOL", "true") + + Convey("IntValue", func() { + type Data struct { + Val IntValue `yaml:"val"` + } + d := &Data{} + + Convey("Should unmarshal simple number", func() { + unmarshalingTest(`val: 1`, d) + So(d.Val.Value(), ShouldEqual, 1) + So(d.Val.Raw, ShouldEqual, "1") + }) + + Convey("Should unmarshal env var", func() { + unmarshalingTest(`val: $INT`, d) + So(d.Val.Value(), ShouldEqual, 1) + So(d.Val.Raw, ShouldEqual, "$INT") + }) + + Convey("Should ignore empty value", func() { + unmarshalingTest(`val: `, d) + So(d.Val.Value(), ShouldEqual, 0) + So(d.Val.Raw, ShouldEqual, "") + }) + }) + + Convey("StringValue", func() { + type Data struct { + Val StringValue `yaml:"val"` + } + d := &Data{} + + Convey("Should unmarshal simple string", func() { + unmarshalingTest(`val: test`, d) + So(d.Val.Value(), ShouldEqual, "test") + So(d.Val.Raw, ShouldEqual, "test") + }) + + Convey("Should unmarshal env var", func() { + unmarshalingTest(`val: $STRING`, d) + So(d.Val.Value(), ShouldEqual, "test") + So(d.Val.Raw, ShouldEqual, "$STRING") + }) + + Convey("Should ignore empty value", func() { + unmarshalingTest(`val: `, d) + So(d.Val.Value(), ShouldEqual, "") + So(d.Val.Raw, ShouldEqual, "") + }) + }) + + Convey("BoolValue", func() { + type Data struct { + Val BoolValue `yaml:"val"` + } + d := &Data{} + + Convey("Should unmarshal bool value", func() { + unmarshalingTest(`val: true`, d) + So(d.Val.Value(), ShouldBeTrue) + So(d.Val.Raw, ShouldEqual, "true") + }) + + Convey("Should unmarshal explicit string", func() { + unmarshalingTest(`val: "true"`, d) + So(d.Val.Value(), ShouldBeTrue) + So(d.Val.Raw, ShouldEqual, "true") + }) + + Convey("Should unmarshal env var", func() { + unmarshalingTest(`val: $BOOL`, d) + So(d.Val.Value(), ShouldBeTrue) + So(d.Val.Raw, ShouldEqual, "$BOOL") + }) + + Convey("Should ignore empty value", func() { + unmarshalingTest(`val: `, d) + So(d.Val.Value(), ShouldBeFalse) + So(d.Val.Raw, ShouldEqual, "") + }) + }) + + Convey("JSONValue", func() { + + type Data struct { + Val JSONValue `yaml:"val"` + } + d := &Data{} + + Convey("Should unmarshal variable nesting", func() { + doc := ` + val: + one: 1 + two: $STRING + three: + - 1 + - two + - three: + inside: $STRING + four: + nested: + onemore: $INT + multiline: > + Some text with $STRING + anchor: &label $INT + anchored: *label + ` + unmarshalingTest(doc, d) + + type anyMap = map[interface{}]interface{} + So(d.Val.Value(), ShouldResemble, map[string]interface{}{ + "one": 1, + "two": "test", + "three": []interface{}{ + 1, "two", anyMap{ + "three": anyMap{ + "inside": "test", + }, + }, + }, + "four": anyMap{ + "nested": anyMap{ + "onemore": "1", + }, + }, + "multiline": "Some text with test\n", + "anchor": "1", + "anchored": "1", + }) + + So(d.Val.Raw, ShouldResemble, map[string]interface{}{ + "one": 1, + "two": "$STRING", + "three": []interface{}{ + 1, "two", anyMap{ + "three": anyMap{ + "inside": "$STRING", + }, + }, + }, + "four": anyMap{ + "nested": anyMap{ + "onemore": "$INT", + }, + }, + "multiline": "Some text with $STRING\n", + "anchor": "$INT", + "anchored": "$INT", + }) + }) + }) + + Convey("StringMapValue", func() { + type Data struct { + Val StringMapValue `yaml:"val"` + } + d := &Data{} + + Convey("Should unmarshal mapping", func() { + doc := ` + val: + one: 1 + two: "test string" + three: $STRING + four: true + ` + unmarshalingTest(doc, d) + So(d.Val.Value(), ShouldResemble, map[string]string{ + "one": "1", + "two": "test string", + "three": "test", + "four": "true", + }) + + So(d.Val.Raw, ShouldResemble, map[string]string{ + "one": "1", + "two": "test string", + "three": "$STRING", + "four": "true", + }) + + }) + }) + + Reset(func() { + os.Unsetenv("INT") + os.Unsetenv("STRING") + os.Unsetenv("BOOL") + }) + }) +} + +func unmarshalingTest(document string, out interface{}) { + err := yaml.Unmarshal([]byte(document), out) + So(err, ShouldBeNil) +} From 1e98c102358affa36824a2318855360725bb4e16 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Wed, 24 Apr 2019 22:41:13 +0900 Subject: [PATCH 07/12] user friendly guide (#16743) CloudWatch: Update docs for setting auth provider --- docs/sources/features/datasources/cloudwatch.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/sources/features/datasources/cloudwatch.md b/docs/sources/features/datasources/cloudwatch.md index 9c7fd5207c31c..7a00b377122e8 100644 --- a/docs/sources/features/datasources/cloudwatch.md +++ b/docs/sources/features/datasources/cloudwatch.md @@ -29,9 +29,10 @@ Name | Description ------------ | ------------- *Name* | The data source name. This is how you refer to the data source in panels & queries. *Default* | Default data source means that it will be pre-selected for new panels. -*Credentials* profile name | Specify the name of the profile to use (if you use `~/.aws/credentials` file), leave blank for default. *Default Region* | Used in query editor to set region (can be changed on per query basis) *Custom Metrics namespace* | Specify the CloudWatch namespace of Custom metrics +*Auth Provider* | Specify the provider to get credentials. +*Credentials* profile name | Specify the name of the profile to use (if you use `~/.aws/credentials` file), leave blank for default. *Assume Role Arn* | Specify the ARN of the role to assume ## Authentication @@ -85,6 +86,16 @@ Here is a minimal policy example: } ``` +### AWS credentials +If Auth Provider is `Credentials file`, Grafana try to get credentials by following order. + +- Environment variables. (`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`) +- Hard-code credentials. +- Shared credentials file. +- IAM role for Amazon EC2. + +Checkout AWS docs on [Configuring the AWS SDK for Go](https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html) + ### AWS credentials file Create a file at `~/.aws/credentials`. That is the `HOME` path for user running grafana-server. From 2d6b33ab61503cca78a4ca3db2ebf9763d4c887d Mon Sep 17 00:00:00 2001 From: Tom Petr Date: Wed, 24 Apr 2019 10:05:35 -0400 Subject: [PATCH 08/12] sqlstore: use column name in order by (#16583) Use column name in ORDER BY in GetDashboardAclInfoList(). --- pkg/services/sqlstore/dashboard_acl.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/services/sqlstore/dashboard_acl.go b/pkg/services/sqlstore/dashboard_acl.go index 0b195c4562b8a..31015ff932f13 100644 --- a/pkg/services/sqlstore/dashboard_acl.go +++ b/pkg/services/sqlstore/dashboard_acl.go @@ -112,7 +112,7 @@ func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error { LEFT JOIN ` + dialect.Quote("user") + ` AS u ON u.id = da.user_id LEFT JOIN team ug on ug.id = da.team_id WHERE d.org_id = ? AND d.id = ? AND da.id IS NOT NULL - ORDER BY 1 ASC + ORDER BY da.id ASC ` query.Result = make([]*m.DashboardAclInfoDTO, 0) From 834bb6baa73a9777045ca072d2c09e9e5e7bca58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 24 Apr 2019 17:04:20 +0200 Subject: [PATCH 09/12] PanelQuery: Minor refactoring --- .../dashboard/dashgrid/PanelChrome.tsx | 2 +- .../dashboard/state/PanelQueryRunner.test.ts | 38 +----------- .../dashboard/state/PanelQueryRunner.ts | 61 +++++++------------ .../dashboard/state/PanelQueryState.test.ts | 38 +++++++++++- .../dashboard/state/PanelQueryState.ts | 60 +++++++++++++----- 5 files changed, 104 insertions(+), 95 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index d4f6c62294860..f06bfd3c8098a 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -22,7 +22,7 @@ import { ScopedVars } from '@grafana/ui'; import templateSrv from 'app/features/templating/template_srv'; -import { getProcessedSeriesData } from '../state/PanelQueryRunner'; +import { getProcessedSeriesData } from '../state/PanelQueryState'; import { Unsubscribable } from 'rxjs'; const DEFAULT_PLUGIN_ERROR = 'Error in plugin'; diff --git a/public/app/features/dashboard/state/PanelQueryRunner.test.ts b/public/app/features/dashboard/state/PanelQueryRunner.test.ts index 33db473b1d014..9286d7d73468d 100644 --- a/public/app/features/dashboard/state/PanelQueryRunner.test.ts +++ b/public/app/features/dashboard/state/PanelQueryRunner.test.ts @@ -1,43 +1,7 @@ -import { getProcessedSeriesData, PanelQueryRunner } from './PanelQueryRunner'; +import { PanelQueryRunner } from './PanelQueryRunner'; import { PanelData, DataQueryRequest } from '@grafana/ui/src/types'; import moment from 'moment'; -describe('PanelQueryRunner', () => { - it('converts timeseries to table skipping nulls', () => { - const input1 = { - target: 'Field Name', - datapoints: [[100, 1], [200, 2]], - }; - const input2 = { - // without target - target: '', - datapoints: [[100, 1], [200, 2]], - }; - const data = getProcessedSeriesData([null, input1, input2, null, null]); - expect(data.length).toBe(2); - expect(data[0].fields[0].name).toBe(input1.target); - expect(data[0].rows).toBe(input1.datapoints); - - // Default name - expect(data[1].fields[0].name).toEqual('Value'); - - // Every colun should have a name and a type - for (const table of data) { - for (const column of table.fields) { - expect(column.name).toBeDefined(); - expect(column.type).toBeDefined(); - } - } - }); - - it('supports null values from query OK', () => { - expect(getProcessedSeriesData([null, null, null, null])).toEqual([]); - expect(getProcessedSeriesData(undefined)).toEqual([]); - expect(getProcessedSeriesData((null as unknown) as any[])).toEqual([]); - expect(getProcessedSeriesData([])).toEqual([]); - }); -}); - interface ScenarioContext { setup: (fn: () => void) => void; maxDataPoints?: number | null; diff --git a/public/app/features/dashboard/state/PanelQueryRunner.ts b/public/app/features/dashboard/state/PanelQueryRunner.ts index 496ece9a84073..49cbc080cd939 100644 --- a/public/app/features/dashboard/state/PanelQueryRunner.ts +++ b/public/app/features/dashboard/state/PanelQueryRunner.ts @@ -7,21 +7,11 @@ import { Subject, Unsubscribable, PartialObserver } from 'rxjs'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import kbn from 'app/core/utils/kbn'; import templateSrv from 'app/features/templating/template_srv'; - -// Components & Types -import { - guessFieldTypes, - toSeriesData, - PanelData, - DataQuery, - TimeRange, - ScopedVars, - DataQueryRequest, - SeriesData, - DataSourceApi, -} from '@grafana/ui'; import { PanelQueryState } from './PanelQueryState'; +// Types +import { PanelData, DataQuery, TimeRange, ScopedVars, DataQueryRequest, DataSourceApi } from '@grafana/ui'; + export interface QueryRunnerOptions { datasource: string | DataSourceApi; queries: TQuery[]; @@ -107,6 +97,13 @@ export class PanelQueryRunner { delayStateNotification, } = options; + // filter out hidden queries & deep clone them + const clonedAndFilteredQueries = cloneDeep( + queries.filter(q => { + return !q.hide; // Skip any hidden queries + }) + ); + const request: DataQueryRequest = { requestId: getNextRequestId(), timezone, @@ -116,26 +113,20 @@ export class PanelQueryRunner { timeInfo, interval: '', intervalMs: 0, - targets: cloneDeep( - queries.filter(q => { - return !q.hide; // Skip any hidden queries - }) - ), + targets: clonedAndFilteredQueries, maxDataPoints: maxDataPoints || widthPixels, scopedVars: scopedVars || {}, cacheTimeout, startTime: Date.now(), }; - // Deprecated + + // Add deprecated property (request as any).rangeRaw = timeRange.raw; let loadingStateTimeoutId = 0; try { - const ds = - datasource && (datasource as any).query - ? (datasource as DataSourceApi) - : await getDatasourceSrv().get(datasource as string, request.scopedVars); + const ds = await getDataSource(datasource, request.scopedVars); // Attach the datasource name to each query request.targets = request.targets.map(query => { @@ -218,22 +209,12 @@ export class PanelQueryRunner { } } -/** - * All panels will be passed tables that have our best guess at colum type set - * - * This is also used by PanelChrome for snapshot support - */ -export function getProcessedSeriesData(results?: any[]): SeriesData[] { - if (!results) { - return []; +async function getDataSource( + datasource: string | DataSourceApi | null, + scopedVars: ScopedVars +): Promise { + if (datasource && (datasource as any).query) { + return datasource as DataSourceApi; } - - const series: SeriesData[] = []; - for (const r of results) { - if (r) { - series.push(guessFieldTypes(toSeriesData(r))); - } - } - - return series; + return await getDatasourceSrv().get(datasource as string, scopedVars); } diff --git a/public/app/features/dashboard/state/PanelQueryState.test.ts b/public/app/features/dashboard/state/PanelQueryState.test.ts index 6d7004a931042..894a770201109 100644 --- a/public/app/features/dashboard/state/PanelQueryState.test.ts +++ b/public/app/features/dashboard/state/PanelQueryState.test.ts @@ -1,4 +1,4 @@ -import { toDataQueryError, PanelQueryState } from './PanelQueryState'; +import { toDataQueryError, PanelQueryState, getProcessedSeriesData } from './PanelQueryState'; import { MockDataSourceApi } from 'test/mocks/datasource_srv'; import { DataQueryResponse } from '@grafana/ui'; import { getQueryOptions } from 'test/helpers/getQueryOptions'; @@ -44,3 +44,39 @@ describe('PanelQueryState', () => { expect(hasRun).toBeFalsy(); }); }); + +describe('getProcessedSeriesData', () => { + it('converts timeseries to table skipping nulls', () => { + const input1 = { + target: 'Field Name', + datapoints: [[100, 1], [200, 2]], + }; + const input2 = { + // without target + target: '', + datapoints: [[100, 1], [200, 2]], + }; + const data = getProcessedSeriesData([null, input1, input2, null, null]); + expect(data.length).toBe(2); + expect(data[0].fields[0].name).toBe(input1.target); + expect(data[0].rows).toBe(input1.datapoints); + + // Default name + expect(data[1].fields[0].name).toEqual('Value'); + + // Every colun should have a name and a type + for (const table of data) { + for (const column of table.fields) { + expect(column.name).toBeDefined(); + expect(column.type).toBeDefined(); + } + } + }); + + it('supports null values from query OK', () => { + expect(getProcessedSeriesData([null, null, null, null])).toEqual([]); + expect(getProcessedSeriesData(undefined)).toEqual([]); + expect(getProcessedSeriesData((null as unknown) as any[])).toEqual([]); + expect(getProcessedSeriesData([])).toEqual([]); + }); +}); diff --git a/public/app/features/dashboard/state/PanelQueryState.ts b/public/app/features/dashboard/state/PanelQueryState.ts index 2e52835ec7a82..cb8e7e25bf868 100644 --- a/public/app/features/dashboard/state/PanelQueryState.ts +++ b/public/app/features/dashboard/state/PanelQueryState.ts @@ -1,21 +1,25 @@ +// Libraries import isString from 'lodash/isString'; +import isEqual from 'lodash/isEqual'; + +// Utils & Services +import { getBackendSrv } from 'app/core/services/backend_srv'; +import * as dateMath from 'app/core/utils/datemath'; +import { guessFieldTypes, toSeriesData, isSeriesData } from '@grafana/ui/src/utils'; + +// Types import { DataSourceApi, DataQueryRequest, PanelData, LoadingState, toLegacyResponseData, - isSeriesData, - toSeriesData, DataQueryError, DataStreamObserver, DataStreamState, SeriesData, + DataQueryResponseData, } from '@grafana/ui'; -import { getProcessedSeriesData } from './PanelQueryRunner'; -import { getBackendSrv } from 'app/core/services/backend_srv'; -import isEqual from 'lodash/isEqual'; -import * as dateMath from 'app/core/utils/datemath'; export class PanelQueryState { // The current/last running request @@ -107,7 +111,7 @@ export class PanelQueryState { // Set the loading state immediatly this.response.state = LoadingState.Loading; - return (this.executor = new Promise((resolve, reject) => { + this.executor = new Promise((resolve, reject) => { this.rejector = reject; return ds @@ -125,21 +129,16 @@ export class PanelQueryState { state: LoadingState.Done, request: this.request, series: this.sendSeries ? getProcessedSeriesData(resp.data) : [], - legacy: this.sendLegacy - ? resp.data.map(v => { - if (isSeriesData(v)) { - return toLegacyResponseData(v); - } - return v; - }) - : undefined, + legacy: this.sendLegacy ? translateToLegacyData(resp.data) : undefined, }; resolve(this.getPanelData()); }) .catch(err => { resolve(this.setError(err)); }); - })); + }); + + return this.executor; } // Send a notice when the stream has updated the current model @@ -309,3 +308,32 @@ export function toDataQueryError(err: any): DataQueryError { } return error; } + +function translateToLegacyData(data: DataQueryResponseData) { + return data.map(v => { + if (isSeriesData(v)) { + return toLegacyResponseData(v); + } + return v; + }); +} + +/** + * All panels will be passed tables that have our best guess at colum type set + * + * This is also used by PanelChrome for snapshot support + */ +export function getProcessedSeriesData(results?: any[]): SeriesData[] { + if (!results) { + return []; + } + + const series: SeriesData[] = []; + for (const r of results) { + if (r) { + series.push(guessFieldTypes(toSeriesData(r))); + } + } + + return series; +} From 8b922ebc714b0154c27a7c7687d820dcdb0ded03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 24 Apr 2019 17:28:25 +0200 Subject: [PATCH 10/12] PanelQueryState: give code a bit of air (space) --- .../dashboard/state/PanelQueryRunner.ts | 6 +- .../dashboard/state/PanelQueryState.ts | 55 +++++++++++-------- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/public/app/features/dashboard/state/PanelQueryRunner.ts b/public/app/features/dashboard/state/PanelQueryRunner.ts index 49cbc080cd939..848ccfc2204f5 100644 --- a/public/app/features/dashboard/state/PanelQueryRunner.ts +++ b/public/app/features/dashboard/state/PanelQueryRunner.ts @@ -98,11 +98,7 @@ export class PanelQueryRunner { } = options; // filter out hidden queries & deep clone them - const clonedAndFilteredQueries = cloneDeep( - queries.filter(q => { - return !q.hide; // Skip any hidden queries - }) - ); + const clonedAndFilteredQueries = cloneDeep(queries.filter(q => !q.hide)); const request: DataQueryRequest = { requestId: getNextRequestId(), diff --git a/public/app/features/dashboard/state/PanelQueryState.ts b/public/app/features/dashboard/state/PanelQueryState.ts index cb8e7e25bf868..65e89b75568af 100644 --- a/public/app/features/dashboard/state/PanelQueryState.ts +++ b/public/app/features/dashboard/state/PanelQueryState.ts @@ -167,35 +167,43 @@ export class PanelQueryState { } active.push(stream); } + this.streams = active; this.streamCallback(); }; closeStreams(keepSeries = false) { - if (this.streams.length) { - const series: SeriesData[] = []; - for (const stream of this.streams) { - if (stream.series) { - series.push.apply(series, stream.series); - } - try { - stream.unsubscribe(); - } catch {} + if (!this.streams.length) { + return; + } + + const series: SeriesData[] = []; + + for (const stream of this.streams) { + if (stream.series) { + series.push.apply(series, stream.series); } - this.streams = []; - - // Move the series from streams to the resposne - if (keepSeries) { - const { response } = this; - this.response = { - ...response, - series: [ - ...response.series, - ...series, // Append the streamed series - ], - }; + + try { + stream.unsubscribe(); + } catch { + console.log('Failed to unsubscribe to stream'); } } + + this.streams = []; + + // Move the series from streams to the resposne + if (keepSeries) { + const { response } = this; + this.response = { + ...response, + series: [ + ...response.series, + ...series, // Append the streamed series + ], + }; + } } getPanelData(): PanelData { @@ -212,6 +220,7 @@ export class PanelQueryState { let done = this.isFinished(response.state); const series = [...response.series]; const active: DataStreamState[] = []; + for (const stream of this.streams) { if (shouldDisconnect(request, stream)) { stream.unsubscribe(); @@ -220,10 +229,12 @@ export class PanelQueryState { active.push(stream); series.push.apply(series, stream.series); + if (!this.isFinished(stream.state)) { done = false; } } + this.streams = active; // Update the time range @@ -239,7 +250,7 @@ export class PanelQueryState { return { state: done ? LoadingState.Done : LoadingState.Streaming, series, // Union of series from response and all streams - legacy: this.sendLegacy ? series.map(s => toLegacyResponseData(s)) : undefined, + legacy: this.sendLegacy ? translateToLegacyData(series) : undefined, request: { ...this.request, range: timeRange, // update the time range From 78abd2c0ad50a60063ffe72f84e577951aa64842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 24 Apr 2019 21:13:15 +0200 Subject: [PATCH 11/12] TestData: don't reuse unsubbed stream workers --- .../dashboard/state/PanelQueryState.ts | 3 +- .../datasource/testdata/StreamHandler.ts | 65 +++++++++++-------- 2 files changed, 40 insertions(+), 28 deletions(-) diff --git a/public/app/features/dashboard/state/PanelQueryState.ts b/public/app/features/dashboard/state/PanelQueryState.ts index 65e89b75568af..e5ae946e612ac 100644 --- a/public/app/features/dashboard/state/PanelQueryState.ts +++ b/public/app/features/dashboard/state/PanelQueryState.ts @@ -74,9 +74,10 @@ export class PanelQueryState { cancel(reason: string) { const { request } = this; try { + // If no endTime the call to datasource.query did not complete + // call rejector to reject the executor promise if (!request.endTime) { request.endTime = Date.now(); - this.rejector('Canceled:' + reason); } diff --git a/public/app/plugins/datasource/testdata/StreamHandler.ts b/public/app/plugins/datasource/testdata/StreamHandler.ts index 825b11d8cd8fd..9b97cc3ef5d34 100644 --- a/public/app/plugins/datasource/testdata/StreamHandler.ts +++ b/public/app/plugins/datasource/testdata/StreamHandler.ts @@ -27,33 +27,40 @@ export class StreamHandler { process(req: DataQueryRequest, observer: DataStreamObserver): DataQueryResponse | undefined { let resp: DataQueryResponse; + for (const query of req.targets) { - if ('streaming_client' === query.scenarioId) { - if (!resp) { - resp = { data: [] }; - } - query.stream = defaults(query.stream, defaultQuery); - - const key = req.dashboardId + '/' + req.panelId + '/' + query.refId; - if (this.workers[key]) { - const existing = this.workers[key]; - if (existing.update(query, req)) { - continue; - } - existing.unsubscribe(); - delete this.workers[key]; - } - const type = query.stream.type; - if (type === 'signal') { - this.workers[key] = new SignalWorker(key, query, req, observer); - } else if (type === 'logs') { - this.workers[key] = new LogsWorker(key, query, req, observer); - } else { - throw { - message: 'Unknown Stream type: ' + type, - refId: query.refId, - } as DataQueryError; + if ('streaming_client' !== query.scenarioId) { + continue; + } + + if (!resp) { + resp = { data: [] }; + } + + // set stream option defaults + query.stream = defaults(query.stream, defaultQuery); + // create stream key + const key = req.dashboardId + '/' + req.panelId + '/' + query.refId; + + if (this.workers[key]) { + const existing = this.workers[key]; + if (existing.update(query, req)) { + continue; } + existing.unsubscribe(); + delete this.workers[key]; + } + + const type = query.stream.type; + if (type === 'signal') { + this.workers[key] = new SignalWorker(key, query, req, observer); + } else if (type === 'logs') { + this.workers[key] = new LogsWorker(key, query, req, observer); + } else { + throw { + message: 'Unknown Stream type: ' + type, + refId: query.refId, + } as DataQueryError; } } return resp; @@ -92,7 +99,8 @@ export class StreamWorker { }; update(query: TestDataQuery, request: DataQueryRequest): boolean { - if (this.query.type !== query.stream.type) { + // Check if stream has been unsubscribed or query changed type + if (this.observer === null || this.query.type !== query.stream.type) { return false; } this.query = query.stream; @@ -121,7 +129,10 @@ export class StreamWorker { // Broadcast the changes if (this.observer) { this.observer(stream); + } else { + console.log('StreamWorker working without any observer'); } + this.last = Date.now(); } } @@ -131,7 +142,7 @@ export class SignalWorker extends StreamWorker { constructor(key: string, query: TestDataQuery, request: DataQueryRequest, observer: DataStreamObserver) { super(key, query, request, observer); - window.setTimeout(() => { + setTimeout(() => { this.stream.series = [this.initBuffer(query.refId)]; this.looper(); }, 10); From 5cc8811c3f969da0ee17d587ac498fc0a58836e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 24 Apr 2019 22:01:35 +0200 Subject: [PATCH 12/12] PanelChrome: delay state update while other panel is in fullscreen/edit --- .../dashboard/dashgrid/PanelChrome.tsx | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index 2a454691d810d..c69e7be0c4e63 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -48,6 +48,7 @@ export interface State { export class PanelChrome extends PureComponent { timeSrv: TimeSrv = getTimeSrv(); querySubscription: Unsubscribable; + delayedStateUpdate: Partial; constructor(props: Props) { super(props); @@ -118,7 +119,15 @@ export class PanelChrome extends PureComponent { } } - this.setState({ isFirstLoad, errorMessage, data }); + const stateUpdate = { isFirstLoad, errorMessage, data }; + + if (this.isVisible) { + this.setState(stateUpdate); + } else { + // if we are getting data while another panel is in fullscreen / edit mode + // we need to store the data but not update state yet + this.delayedStateUpdate = stateUpdate; + } }, }; @@ -162,9 +171,15 @@ export class PanelChrome extends PureComponent { }; onRender = () => { - this.setState({ - renderCounter: this.state.renderCounter + 1, - }); + const stateUpdate = { renderCounter: this.state.renderCounter + 1 }; + + // If we have received a data update while hidden copy over that state as well + if (this.delayedStateUpdate) { + Object.assign(stateUpdate, this.delayedStateUpdate); + this.delayedStateUpdate = null; + } + + this.setState(stateUpdate); }; onOptionsChange = (options: any) => {