Przeglądaj źródła

Merge remote-tracking branch 'upstream/master' into postgres-query-builder

Sven Klemm 7 lat temu
rodzic
commit
6c7984a840
93 zmienionych plików z 2234 dodań i 2100 usunięć
  1. 10 5
      .circleci/config.yml
  2. 14 0
      CHANGELOG.md
  3. 3 3
      Gopkg.lock
  4. 1 1
      Gopkg.toml
  5. 1 1
      docs/sources/administration/provisioning.md
  6. 1 1
      docs/sources/features/datasources/graphite.md
  7. 1 1
      docs/sources/guides/whats-new-in-v5-1.md
  8. 21 0
      docs/sources/http_api/org.md
  9. 5 0
      docs/sources/installation/docker.md
  10. 0 7
      docs/sources/plugins/developing/datasources.md
  11. 4 4
      docs/sources/project/building_from_source.md
  12. 2 2
      latest.json
  13. 0 1
      package.json
  14. 1 0
      pkg/api/dtos/plugins.go
  15. 1 11
      pkg/api/frontendsettings.go
  16. 2 1
      pkg/api/index.go
  17. 20 8
      pkg/api/pluginproxy/ds_proxy.go
  18. 134 0
      pkg/api/pluginproxy/ds_proxy_test.go
  19. 9 0
      pkg/api/pluginproxy/test-data/access-token-1.json
  20. 9 0
      pkg/api/pluginproxy/test-data/access-token-2.json
  21. 1 0
      pkg/api/plugins.go
  22. 0 1
      pkg/models/datasource.go
  23. 3 1
      pkg/plugins/dashboard_importer.go
  24. 1 0
      pkg/plugins/dashboards.go
  25. 10 12
      pkg/services/alerting/notifiers/teams.go
  26. 1 1
      pkg/services/sqlstore/sqlstore.go
  27. 2 2
      pkg/tsdb/elasticsearch/client/client.go
  28. 1 0
      pkg/tsdb/influxdb/query_part.go
  29. 32 78
      pkg/tsdb/influxdb/query_part_test.go
  30. 1 1
      public/app/core/components/grafana_app.ts
  31. 4 0
      public/app/core/components/manage_dashboards/manage_dashboards.html
  32. 10 0
      public/app/core/components/manage_dashboards/manage_dashboards.ts
  33. 2 2
      public/app/core/components/search/search.html
  34. 1 0
      public/app/core/config.ts
  35. 2 1
      public/app/core/services/keybindingSrv.ts
  36. 10 0
      public/app/core/utils/kbn.ts
  37. 13 13
      public/app/features/alerting/specs/threshold_mapper.jest.ts
  38. 25 1
      public/app/features/dashboard/dashboard_import_ctrl.ts
  39. 12 1
      public/app/features/dashboard/dashgrid/AddPanelPanel.tsx
  40. 11 3
      public/app/features/dashboard/dashgrid/DashboardRow.tsx
  41. 17 14
      public/app/features/dashboard/folder_picker/folder_picker.ts
  42. 16 2
      public/app/features/dashboard/partials/dashboard_import.html
  43. 15 2
      public/app/features/dashboard/save_modal.ts
  44. 194 0
      public/app/features/dashboard/specs/exporter.jest.ts
  45. 0 187
      public/app/features/dashboard/specs/exporter_specs.ts
  46. 1 1
      public/app/features/panel/metrics_panel_ctrl.ts
  47. 1 1
      public/app/features/panel/panel_header.ts
  48. 12 1
      public/app/features/panel/specs/metrics_panel_ctrl.jest.ts
  49. 12 13
      public/app/features/playlist/specs/playlist_edit_ctrl.jest.ts
  50. 0 2
      public/app/features/plugins/built_in_plugins.ts
  51. 0 28
      public/app/features/plugins/plugin_component.ts
  52. 17 0
      public/app/features/plugins/plugin_loader.ts
  53. 0 100
      public/app/features/plugins/row_ctrl.ts
  54. 0 5
      public/app/plugins/datasource/elasticsearch/module.ts
  55. 105 106
      public/app/plugins/datasource/elasticsearch/specs/elastic_response.jest.ts
  56. 11 12
      public/app/plugins/datasource/elasticsearch/specs/index_pattern.jest.ts
  57. 57 58
      public/app/plugins/datasource/elasticsearch/specs/query_builder.jest.ts
  58. 93 0
      public/app/plugins/datasource/elasticsearch/specs/query_def.jest.ts
  59. 0 95
      public/app/plugins/datasource/elasticsearch/specs/query_def_specs.ts
  60. 0 26
      public/app/plugins/datasource/influxdb-ifql/README.md
  61. 0 233
      public/app/plugins/datasource/influxdb-ifql/datasource.ts
  62. 0 26
      public/app/plugins/datasource/influxdb-ifql/img/influxdb_logo.svg
  63. 0 17
      public/app/plugins/datasource/influxdb-ifql/module.ts
  64. 0 24
      public/app/plugins/datasource/influxdb-ifql/partials/annotations.editor.html
  65. 0 24
      public/app/plugins/datasource/influxdb-ifql/partials/config.html
  66. 0 24
      public/app/plugins/datasource/influxdb-ifql/partials/query.editor.html
  67. 0 24
      public/app/plugins/datasource/influxdb-ifql/plugin.json
  68. 0 27
      public/app/plugins/datasource/influxdb-ifql/query_ctrl.ts
  69. 0 88
      public/app/plugins/datasource/influxdb-ifql/response_parser.ts
  70. 0 53
      public/app/plugins/datasource/influxdb-ifql/specs/datasource.jest.ts
  71. 0 63
      public/app/plugins/datasource/influxdb-ifql/specs/response_parser.jest.ts
  72. 0 349
      public/app/plugins/datasource/influxdb-ifql/specs/sample_response_csv.ts
  73. 0 1
      public/app/plugins/datasource/influxdb/specs/query_builder.jest.ts
  74. 15 15
      public/app/plugins/panel/graph/specs/graph_tooltip.jest.ts
  75. 20 22
      public/app/plugins/panel/graph/specs/threshold_manager.jest.ts
  76. 11 13
      public/app/plugins/panel/singlestat/specs/singlestat_panel.jest.ts
  77. 16 0
      public/sass/components/_buttons.scss
  78. 12 4
      public/sass/components/_row.scss
  79. 1 1
      public/sass/pages/_alerting.scss
  80. 15 8
      public/sass/pages/_dashboard.scss
  81. 13 3
      public/sass/pages/_login.scss
  82. 6 8
      public/test/core/utils/version_jest.ts
  83. 18 0
      public/test/jest-setup.ts
  84. 1 1
      scripts/webpack/webpack.hot.js
  85. 531 106
      vendor/github.com/mattn/go-sqlite3/sqlite3-binding.c
  86. 529 104
      vendor/github.com/mattn/go-sqlite3/sqlite3-binding.h
  87. 60 32
      vendor/github.com/mattn/go-sqlite3/sqlite3.go
  88. 2 0
      vendor/github.com/mattn/go-sqlite3/sqlite3_go18.go
  89. 12 0
      vendor/github.com/mattn/go-sqlite3/sqlite3_solaris.go
  90. 12 10
      vendor/github.com/mattn/go-sqlite3/sqlite3_trace.go
  91. 7 0
      vendor/github.com/mattn/go-sqlite3/sqlite3ext.h
  92. 21 0
      vendor/github.com/mattn/go-sqlite3/static_mock.go
  93. 0 4
      yarn.lock

+ 10 - 5
.circleci/config.yml

@@ -183,16 +183,21 @@ jobs:
           command: 'sudo pip install awscli'
       - run:
           name: deploy to s3
-          command: 'aws s3 sync ./dist s3://$BUCKET_NAME/master'
+          command: |
+            # Also
+            cp dist/grafana-latest.linux-x64.tar.gz dist/grafana-master-$(echo "${CIRCLE_SHA1}" | cut -b1-7).linux-x64.tar.gz
+            aws s3 sync ./dist s3://$BUCKET_NAME/master
       - run:
           name: Trigger Windows build
           command: './scripts/trigger_windows_build.sh ${APPVEYOR_TOKEN} ${CIRCLE_SHA1} master'
       - run:
           name: Trigger Docker build
-          command: './scripts/trigger_docker_build.sh ${TRIGGER_GRAFANA_PACKER_CIRCLECI_TOKEN}'
+          command: './scripts/trigger_docker_build.sh ${TRIGGER_GRAFANA_PACKER_CIRCLECI_TOKEN} master-$(echo "${CIRCLE_SHA1}" | cut -b1-7)'
       - run:
           name: Publish to Grafana.com
-          command: './scripts/publish -apiKey ${GRAFANA_COM_API_KEY}'
+          command: |
+            rm dist/grafana-master-$(echo "${CIRCLE_SHA1}" | cut -b1-7).linux-x64.tar.gz
+            ./scripts/publish -apiKey ${GRAFANA_COM_API_KEY}
 
   deploy-release:
     docker:
@@ -241,8 +246,8 @@ workflows:
             - mysql-integration-test
             - postgres-integration-test
           filters:
-            branches:
-              only: master
+           branches:
+             only: master
   release:
     jobs:
       - build-all:

+ 14 - 0
CHANGELOG.md

@@ -1,5 +1,19 @@
 # 5.2.0 (unreleased)
 
+### New Features
+
+* **Dashboard**: Import dashboard to folder [#10796](https://github.com/grafana/grafana/issues/10796)
+
+### Minor
+
+* **Dashboard**: Fix so panel titles doesn't wrap [#11074](https://github.com/grafana/grafana/issues/11074)
+* **Dashboard**: Prevent double-click when saving dashboard [#11963](https://github.com/grafana/grafana/issues/11963)
+* **Dashboard**: AutoFocus the add-panel search filter [#12189](https://github.com/grafana/grafana/pull/12189) thx [@ryantxu](https://github.com/ryantxu)
+* **Units**: W/m2 (energy), l/h (flow) and kPa (pressure) [#11233](https://github.com/grafana/grafana/pull/11233), thx [@flopp999](https://github.com/flopp999)
+* **Units**: Litre/min (flow) and milliLitre/min (flow) [#12282](https://github.com/grafana/grafana/pull/12282), thx [@flopp999](https://github.com/flopp999)
+* **Alerting**: Fix mobile notifications for Microsoft Teams alert notifier [#11484](https://github.com/grafana/grafana/pull/11484), thx [@manacker](https://github.com/manacker)
+* **Influxdb**: Add support for mode function [#12286](https://github.com/grafana/grafana/issues/12286)
+
 # 5.2.0-beta1 (2018-06-05)
 
 ### New Features

+ 3 - 3
Gopkg.lock

@@ -331,8 +331,8 @@
 [[projects]]
   name = "github.com/mattn/go-sqlite3"
   packages = ["."]
-  revision = "6c771bb9887719704b210e87e934f08be014bdb1"
-  version = "v1.6.0"
+  revision = "323a32be5a2421b8c7087225079c6c900ec397cd"
+  version = "v1.7.0"
 
 [[projects]]
   name = "github.com/matttproud/golang_protobuf_extensions"
@@ -670,6 +670,6 @@
 [solve-meta]
   analyzer-name = "dep"
   analyzer-version = 1
-  inputs-digest = "08b97771990365d506af4788acb33cdf283ce89856669262ecb84860ad45bfcb"
+  inputs-digest = "85cc057e0cc074ab5b43bd620772d63d51e07b04e8782fcfe55e6929d2fc40f7"
   solver-name = "gps-cdcl"
   solver-version = 1

+ 1 - 1
Gopkg.toml

@@ -129,7 +129,7 @@ ignored = [
 
 [[constraint]]
   name = "github.com/mattn/go-sqlite3"
-  version = "1.6.0"
+  version = "1.7.0"
 
 [[constraint]]
   name = "github.com/opentracing/opentracing-go"

+ 1 - 1
docs/sources/administration/provisioning.md

@@ -76,7 +76,7 @@ Saltstack | [https://github.com/salt-formulas/salt-formula-grafana](https://gith
 
 > This feature is available from v5.0
 
-It's possible to manage datasources in Grafana by adding one or more yaml config files in the [`provisioning/datasources`](/installation/configuration/#provisioning) directory. Each config file can contain a list of `datasources` that will be added or updated during start up. If the datasource already exists, Grafana will update it to match the configuration file. The config file can also contain a list of datasources that should be deleted. That list is called `delete_datasources`. Grafana will delete datasources listed in `delete_datasources` before inserting/updating those in the `datasource` list.
+It's possible to manage datasources in Grafana by adding one or more yaml config files in the [`provisioning/datasources`](/installation/configuration/#provisioning) directory. Each config file can contain a list of `datasources` that will be added or updated during start up. If the datasource already exists, Grafana will update it to match the configuration file. The config file can also contain a list of datasources that should be deleted. That list is called `deleteDatasources`. Grafana will delete datasources listed in `deleteDatasources` before inserting/updating those in the `datasource` list.
 
 ### Running Multiple Grafana Instances
 

+ 1 - 1
docs/sources/features/datasources/graphite.md

@@ -20,7 +20,7 @@ queries through the use of query references.
 ## Adding the data source
 
 1. Open the side menu by clicking the Grafana icon in the top header.
-2. In the side menu under the `Dashboards` link you should find a link named `Data Sources`.
+2. In the side menu under the `Configuration` link you should find a link named `Data Sources`.
 3. Click the `+ Add data source` button in the top header.
 4. Select `Graphite` from the *Type* dropdown.
 

+ 1 - 1
docs/sources/guides/whats-new-in-v5-1.md

@@ -115,7 +115,7 @@ Grafana v5.1 brings an improved workflow for provisioned dashboards:
 
 
 Available options in the dialog will let you `Copy JSON to Clipboard` and/or `Save JSON to file` which can help you synchronize your dashboard changes back to the provisioning source.
-More information in the [Provisioning documentation](/features/datasources/prometheus/).
+More information in the [Provisioning documentation](/administration/provisioning/).
 
 <div class="clearfix"></div>
 

+ 21 - 0
docs/sources/http_api/org.md

@@ -331,6 +331,27 @@ Content-Type: application/json
 {"message":"Organization updated"}
 ```
 
+## Delete Organisation
+
+`DELETE /api/orgs/:orgId`
+
+**Example Request**:
+
+```http
+DELETE /api/orgs/1 HTTP/1.1
+Accept: application/json
+Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
+```
+
+**Example Response**:
+
+```http
+HTTP/1.1 200
+Content-Type: application/json
+
+{"message":"Organization deleted"}
+```
+
 ## Get Users in Organisation
 
 `GET /api/orgs/:orgId/users`

+ 5 - 0
docs/sources/installation/docker.md

@@ -49,6 +49,11 @@ $ docker run \
   grafana/grafana:5.1.0
 ```
 
+## Running of the master branch
+
+For every successful commit we publish a Grafana container to [`grafana/grafana`](https://hub.docker.com/r/grafana/grafana/tags/) and [`grafana/grafana-dev`](https://hub.docker.com/r/grafana/grafana-dev/tags/). In `grafana/grafana` container we will always overwrite the `master` tag with the latest version. In `grafana/grafana-dev` we will include
+the git commit in the tag. If you run Grafana master in production we **strongly** recommend that you use the later since different machines might run different version of grafana if they pull the master tag at different times. 
+
 ## Installing Plugins for Grafana
 
 Pass the plugins you want installed to docker with the `GF_INSTALL_PLUGINS` environment variable as a comma separated list. This will pass each plugin name to `grafana-cli plugins install ${plugin}` and install them when Grafana starts.

+ 0 - 7
docs/sources/plugins/developing/datasources.md

@@ -25,7 +25,6 @@ To interact with the rest of grafana the plugins module file can export 5 differ
 - Datasource (Required)
 - QueryCtrl (Required)
 - ConfigCtrl (Required)
-- QueryOptionsCtrl
 - AnnotationsQueryCtrl
 
 ## Plugin json
@@ -182,12 +181,6 @@ A JavaScript class that will be instantiated and treated as an Angular controlle
 
 Requires a static template or templateUrl variable which will be rendered as the view for this controller.
 
-## QueryOptionsCtrl
-
-A JavaScript class that will be instantiated and treated as an Angular controller when the user edits metrics in a panel. This controller is responsible for handling panel wide settings for the datasource, such as interval, rate and aggregations if needed.
-
-Requires a static template or templateUrl variable which will be rendered as the view for this controller.
-
 ## AnnotationsQueryCtrl
 
 A JavaScript class that will be instantiated and treated as an Angular controller when the user choose this type of datasource in the templating menu in the dashboard.

+ 4 - 4
docs/sources/project/building_from_source.md

@@ -13,7 +13,7 @@ dev environment. Grafana ships with its own required backend server; also comple
 
 ## Dependencies
 
-- [Go 1.9.2](https://golang.org/dl/)
+- [Go 1.10](https://golang.org/dl/)
 - [Git](https://git-scm.com/downloads)
 - [NodeJS LTS](https://nodejs.org/download/)
 - node-gyp is the Node.js native addon build tool and it requires extra dependencies: python 2.7, make and GCC. These are already installed for most Linux distros and MacOS. See the Building On Windows section or the [node-gyp installation instructions](https://github.com/nodejs/node-gyp#installation) for more details.
@@ -66,13 +66,13 @@ You can run a local instance of Grafana by running:
 ```bash
 ./bin/grafana-server
 ```
-If you built the binary with `go run build.go build`, run `./bin/grafana-server`
+Or, if you built the binary with `go run build.go build`, run `./bin/<os>-<architecture>/grafana-server`
 
 If you built it with `go build .`, run `./grafana`
 
 Open grafana in your browser (default [http://localhost:3000](http://localhost:3000)) and login with admin user (default user/pass = admin/admin).
 
-## Developing Grafana
+# Developing Grafana
 
 To add features, customize your config, etc, you'll need to rebuild the backend when you change the source code. We use a tool named `bra` that
 does this.
@@ -124,7 +124,7 @@ Learn more about Grafana config options in the [Configuration section](/installa
 ## Create a pull requests
 Please contribute to the Grafana project and submit a pull request! Build new features, write or update documentation, fix bugs and generally make Grafana even more awesome.
 
-## Troubleshooting
+# Troubleshooting
 
 **Problem**: PhantomJS or node-sass errors when running grunt
 

+ 2 - 2
latest.json

@@ -1,4 +1,4 @@
 {
-  "stable": "5.0.4",
-  "testing": "5.0.4"
+  "stable": "5.1.3",
+  "testing": "5.1.3"
 }

+ 0 - 1
package.json

@@ -157,7 +157,6 @@
     "moment": "^2.18.1",
     "mousetrap": "^1.6.0",
     "mousetrap-global-bind": "^1.1.0",
-    "papaparse": "^4.4.0",
     "prismjs": "^1.6.0",
     "prop-types": "^15.6.0",
     "react": "^16.2.0",

+ 1 - 0
pkg/api/dtos/plugins.go

@@ -57,4 +57,5 @@ type ImportDashboardCommand struct {
 	Overwrite bool                           `json:"overwrite"`
 	Dashboard *simplejson.Json               `json:"dashboard"`
 	Inputs    []plugins.ImportDashboardInput `json:"inputs"`
+	FolderId  int64                          `json:"folderId"`
 }

+ 1 - 11
pkg/api/frontendsettings.go

@@ -85,13 +85,6 @@ func getFrontendSettingsMap(c *m.ReqContext) (map[string]interface{}, error) {
 				dsMap["database"] = ds.Database
 				dsMap["url"] = url
 			}
-
-			if ds.Type == m.DS_INFLUXDB_IFQL {
-				dsMap["username"] = ds.User
-				dsMap["password"] = ds.Password
-				dsMap["database"] = ds.Database
-				dsMap["url"] = url
-			}
 		}
 
 		if ds.Type == m.DS_ES {
@@ -102,10 +95,6 @@ func getFrontendSettingsMap(c *m.ReqContext) (map[string]interface{}, error) {
 			dsMap["database"] = ds.Database
 		}
 
-		if ds.Type == m.DS_INFLUXDB_IFQL {
-			dsMap["database"] = ds.Database
-		}
-
 		if ds.Type == m.DS_PROMETHEUS {
 			// add unproxied server URL for link to Prometheus web UI
 			dsMap["directUrl"] = ds.Url
@@ -151,6 +140,7 @@ func getFrontendSettingsMap(c *m.ReqContext) (map[string]interface{}, error) {
 		"authProxyEnabled":        setting.AuthProxyEnabled,
 		"ldapEnabled":             setting.LdapEnabled,
 		"alertingEnabled":         setting.AlertingEnabled,
+		"exploreEnabled":          setting.ExploreEnabled,
 		"googleAnalyticsId":       setting.GoogleAnalyticsId,
 		"disableLoginForm":        setting.DisableLoginForm,
 		"externalUserMngInfo":     setting.ExternalUserMngInfo,

+ 2 - 1
pkg/api/index.go

@@ -99,9 +99,10 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
 
 		if c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR {
 			children = append(children, &dtos.NavLink{Text: "Folder", SubTitle: "Create a new folder to organize your dashboards", Id: "folder", Icon: "gicon gicon-folder-new", Url: setting.AppSubUrl + "/dashboards/folder/new"})
-			children = append(children, &dtos.NavLink{Text: "Import", SubTitle: "Import dashboard from file or Grafana.com", Id: "import", Icon: "gicon gicon-dashboard-import", Url: setting.AppSubUrl + "/dashboard/import"})
 		}
 
+		children = append(children, &dtos.NavLink{Text: "Import", SubTitle: "Import dashboard from file or Grafana.com", Id: "import", Icon: "gicon gicon-dashboard-import", Url: setting.AppSubUrl + "/dashboard/import"})
+
 		data.NavTree = append(data.NavTree, &dtos.NavLink{
 			Text:     "Create",
 			Id:       "create",

+ 20 - 8
pkg/api/pluginproxy/ds_proxy.go

@@ -25,12 +25,9 @@ import (
 )
 
 var (
-	logger = log.New("data-proxy-log")
-	client = &http.Client{
-		Timeout:   time.Second * 30,
-		Transport: &http.Transport{Proxy: http.ProxyFromEnvironment},
-	}
-	tokenCache = map[int64]*jwtToken{}
+	logger     = log.New("data-proxy-log")
+	tokenCache = map[string]*jwtToken{}
+	client     = newHTTPClient()
 )
 
 type jwtToken struct {
@@ -48,6 +45,10 @@ type DataSourceProxy struct {
 	plugin    *plugins.DataSourcePlugin
 }
 
+type httpClient interface {
+	Do(req *http.Request) (*http.Response, error)
+}
+
 func NewDataSourceProxy(ds *m.DataSource, plugin *plugins.DataSourcePlugin, ctx *m.ReqContext, proxyPath string) *DataSourceProxy {
 	targetURL, _ := url.Parse(ds.Url)
 
@@ -60,6 +61,13 @@ func NewDataSourceProxy(ds *m.DataSource, plugin *plugins.DataSourcePlugin, ctx
 	}
 }
 
+func newHTTPClient() httpClient {
+	return &http.Client{
+		Timeout:   time.Second * 30,
+		Transport: &http.Transport{Proxy: http.ProxyFromEnvironment},
+	}
+}
+
 func (proxy *DataSourceProxy) HandleRequest() {
 	if err := proxy.validateRequest(); err != nil {
 		proxy.ctx.JsonApiErr(403, err.Error(), nil)
@@ -311,7 +319,7 @@ func (proxy *DataSourceProxy) applyRoute(req *http.Request) {
 }
 
 func (proxy *DataSourceProxy) getAccessToken(data templateData) (string, error) {
-	if cachedToken, found := tokenCache[proxy.ds.Id]; found {
+	if cachedToken, found := tokenCache[proxy.getAccessTokenCacheKey()]; found {
 		if cachedToken.ExpiresOn.After(time.Now().Add(time.Second * 10)) {
 			logger.Info("Using token from cache")
 			return cachedToken.AccessToken, nil
@@ -350,12 +358,16 @@ func (proxy *DataSourceProxy) getAccessToken(data templateData) (string, error)
 
 	expiresOnEpoch, _ := strconv.ParseInt(token.ExpiresOnString, 10, 64)
 	token.ExpiresOn = time.Unix(expiresOnEpoch, 0)
-	tokenCache[proxy.ds.Id] = &token
+	tokenCache[proxy.getAccessTokenCacheKey()] = &token
 
 	logger.Info("Got new access token", "ExpiresOn", token.ExpiresOn)
 	return token.AccessToken, nil
 }
 
+func (proxy *DataSourceProxy) getAccessTokenCacheKey() string {
+	return fmt.Sprintf("%v_%v_%v", proxy.ds.Id, proxy.route.Path, proxy.route.Method)
+}
+
 func interpolateString(text string, data templateData) (string, error) {
 	t, err := template.New("content").Parse(text)
 	if err != nil {

+ 134 - 0
pkg/api/pluginproxy/ds_proxy_test.go

@@ -1,9 +1,13 @@
 package pluginproxy
 
 import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
 	"net/http"
 	"net/url"
 	"testing"
+	"time"
 
 	macaron "gopkg.in/macaron.v1"
 
@@ -100,6 +104,112 @@ func TestDSRouteRule(t *testing.T) {
 			})
 		})
 
+		Convey("Plugin with multiple routes for token auth", func() {
+			plugin := &plugins.DataSourcePlugin{
+				Routes: []*plugins.AppPluginRoute{
+					{
+						Path: "pathwithtoken1",
+						Url:  "https://api.nr1.io/some/path",
+						TokenAuth: &plugins.JwtTokenAuth{
+							Url: "https://login.server.com/{{.JsonData.tenantId}}/oauth2/token",
+							Params: map[string]string{
+								"grant_type":    "client_credentials",
+								"client_id":     "{{.JsonData.clientId}}",
+								"client_secret": "{{.SecureJsonData.clientSecret}}",
+								"resource":      "https://api.nr1.io",
+							},
+						},
+					},
+					{
+						Path: "pathwithtoken2",
+						Url:  "https://api.nr2.io/some/path",
+						TokenAuth: &plugins.JwtTokenAuth{
+							Url: "https://login.server.com/{{.JsonData.tenantId}}/oauth2/token",
+							Params: map[string]string{
+								"grant_type":    "client_credentials",
+								"client_id":     "{{.JsonData.clientId}}",
+								"client_secret": "{{.SecureJsonData.clientSecret}}",
+								"resource":      "https://api.nr2.io",
+							},
+						},
+					},
+				},
+			}
+
+			setting.SecretKey = "password"
+			key, _ := util.Encrypt([]byte("123"), "password")
+
+			ds := &m.DataSource{
+				JsonData: simplejson.NewFromAny(map[string]interface{}{
+					"clientId": "asd",
+					"tenantId": "mytenantId",
+				}),
+				SecureJsonData: map[string][]byte{
+					"clientSecret": key,
+				},
+			}
+
+			req, _ := http.NewRequest("GET", "http://localhost/asd", nil)
+			ctx := &m.ReqContext{
+				Context: &macaron.Context{
+					Req: macaron.Request{Request: req},
+				},
+				SignedInUser: &m.SignedInUser{OrgRole: m.ROLE_EDITOR},
+			}
+
+			Convey("When creating and caching access tokens", func() {
+				var authorizationHeaderCall1 string
+				var authorizationHeaderCall2 string
+
+				Convey("first call should add authorization header with access token", func() {
+					json, err := ioutil.ReadFile("./test-data/access-token-1.json")
+					So(err, ShouldBeNil)
+
+					client = newFakeHTTPClient(json)
+					proxy1 := NewDataSourceProxy(ds, plugin, ctx, "pathwithtoken1")
+					proxy1.route = plugin.Routes[0]
+					proxy1.applyRoute(req)
+
+					authorizationHeaderCall1 = req.Header.Get("Authorization")
+					So(req.URL.String(), ShouldEqual, "https://api.nr1.io/some/path")
+					So(authorizationHeaderCall1, ShouldStartWith, "Bearer eyJ0e")
+
+					Convey("second call to another route should add a different access token", func() {
+						json2, err := ioutil.ReadFile("./test-data/access-token-2.json")
+						So(err, ShouldBeNil)
+
+						req, _ := http.NewRequest("GET", "http://localhost/asd", nil)
+						client = newFakeHTTPClient(json2)
+						proxy2 := NewDataSourceProxy(ds, plugin, ctx, "pathwithtoken2")
+						proxy2.route = plugin.Routes[1]
+						proxy2.applyRoute(req)
+
+						authorizationHeaderCall2 = req.Header.Get("Authorization")
+
+						So(req.URL.String(), ShouldEqual, "https://api.nr2.io/some/path")
+						So(authorizationHeaderCall1, ShouldStartWith, "Bearer eyJ0e")
+						So(authorizationHeaderCall2, ShouldStartWith, "Bearer eyJ0e")
+						So(authorizationHeaderCall2, ShouldNotEqual, authorizationHeaderCall1)
+
+						Convey("third call to first route should add cached access token", func() {
+							req, _ := http.NewRequest("GET", "http://localhost/asd", nil)
+
+							client = newFakeHTTPClient([]byte{})
+							proxy3 := NewDataSourceProxy(ds, plugin, ctx, "pathwithtoken1")
+							proxy3.route = plugin.Routes[0]
+							proxy3.applyRoute(req)
+
+							authorizationHeaderCall3 := req.Header.Get("Authorization")
+							So(req.URL.String(), ShouldEqual, "https://api.nr1.io/some/path")
+							So(authorizationHeaderCall1, ShouldStartWith, "Bearer eyJ0e")
+							So(authorizationHeaderCall3, ShouldStartWith, "Bearer eyJ0e")
+							So(authorizationHeaderCall3, ShouldEqual, authorizationHeaderCall1)
+						})
+					})
+				})
+			})
+		})
+
 		Convey("When proxying graphite", func() {
 			plugin := &plugins.DataSourcePlugin{}
 			ds := &m.DataSource{Url: "htttp://graphite:8080", Type: m.DS_GRAPHITE}
@@ -214,3 +324,27 @@ func TestDSRouteRule(t *testing.T) {
 
 	})
 }
+
+type httpClientStub struct {
+	fakeBody []byte
+}
+
+func (c *httpClientStub) Do(req *http.Request) (*http.Response, error) {
+	bodyJSON, _ := simplejson.NewJson(c.fakeBody)
+	_, passedTokenCacheTest := bodyJSON.CheckGet("expires_on")
+	So(passedTokenCacheTest, ShouldBeTrue)
+
+	bodyJSON.Set("expires_on", fmt.Sprint(time.Now().Add(time.Second*60).Unix()))
+	body, _ := bodyJSON.MarshalJSON()
+	resp := &http.Response{
+		Body: ioutil.NopCloser(bytes.NewReader(body)),
+	}
+
+	return resp, nil
+}
+
+func newFakeHTTPClient(fakeBody []byte) httpClient {
+	return &httpClientStub{
+		fakeBody: fakeBody,
+	}
+}

+ 9 - 0
pkg/api/pluginproxy/test-data/access-token-1.json

@@ -0,0 +1,9 @@
+{
+  "token_type": "Bearer",
+  "expires_in": "3599",
+  "ext_expires_in": "0",
+  "expires_on": "1528740417",
+  "not_before": "1528736517",
+  "resource": "https://api.nr1.io",
+  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImlCakwxUmNxemhpeTRmcHhJeGRacW9oTTJZayIsImtpZCI6ImlCakwxUmNxemhpeTRmcHhJeGRacW9oTTJZayJ9.eyJhdWQiOiJodHRwczovL2FwaS5sb2dhbmFseXRpY3MuaW8iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9lN2YzZjY2MS1hOTMzLTRiM2YtODE3Ni01MWM0Zjk4MmVjNDgvIiwiaWF0IjoxNTI4NzM2NTE3LCJuYmYiOjE1Mjg3MzY1MTcsImV4cCI6MTUyODc0MDQxNywiYWlvIjoiWTJkZ1lBaStzaWRsT3NmQ2JicGhLMSsremttN0NBQT0iLCJhcHBpZCI6IjdmMzJkYjdjLTZmNmYtNGU4OC05M2Q5LTlhZTEyNmMwYTU1ZiIsImFwcGlkYWNyIjoiMSIsImlkcCI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0L2U3ZjNmNjYxLWE5MzMtNGIzZi04MTc2LTUxYzRmOTgyZWM0OC8iLCJvaWQiOiI1NDQ5ZmJjOS1mYWJhLTRkNjItODE2Yy05ZmMwMzZkMWViN2UiLCJzdWIiOiI1NDQ5ZmJjOS1mYWJhLTRkNjItODE2Yy05ZmMwMzZkMWViN2UiLCJ0aWQiOiJlN2YzZjY2MS1hOTMzLTRiM2YtODE3Ni01MWM0Zjk4MmVjNDgiLCJ1dGkiOiJZQTlQa2lxUy1VV1hMQjhIRnU0U0FBIiwidmVyIjoiMS4wIn0.ga5qudt4LDMKTStAxUmzjyZH8UFBAaFirJqpTdmYny4NtkH6JT2EILvjTjYxlKeTQisvwx9gof0PyicZIab9d6wlMa2xiLzr2nmaOonYClY8fqBaRTgc1xVjrKFw5SCgpx3FnEyJhIWvVPIfaWaogSHcQbIpe4kdk4tz-ccmrx0D1jsziSI4BZcJcX04aJuHZGz9k4mQZ_AA5sQSeQaNuojIng6rYoIifAXFYBZPTbeeeqmiGq8v0IOLeNKbC0POeQCJC_KKBG6Z_MV2KgPxFEzQuX2ZFmRD_wGPteV5TUBxh1kARdqexA3e0zAKSawR9kmrAiZ21lPr4tX2Br_HDg"
+}

+ 9 - 0
pkg/api/pluginproxy/test-data/access-token-2.json

@@ -0,0 +1,9 @@
+{
+  "token_type": "Bearer",
+  "expires_in": "3599",
+  "ext_expires_in": "0",
+  "expires_on": "1528662059",
+  "not_before": "1528658159",
+  "resource": "https://api.nr2.io",
+  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImlCakwxUmNxemhpeTRmcHhJeGRacW9oTTJZayIsImtpZCI6ImlCakwxUmNxemhpeTRmcHhJeGRacW9oTTJZayJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuYXp1cmUuY29tLyIsImlzcyI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0L2U3ZjNmNjYxLWE5MzMtNGIzZi04MTc2LTUxYzRmOTgyZWM0OC8iLCJpYXQiOjE1Mjg2NTgxNTksIm5iZiI6MTUyODY1ODE1OSwiZXhwIjoxNTI4NjYyMDU5LCJhaW8iOiJZMmRnWUFpK3NpZGxPc2ZDYmJwaEsxKyt6a203Q0FBPSIsImFwcGlkIjoiODg5YjdlZDgtMWFlZC00ODZlLTk3ODktODE5NzcwYmJiNjFhIiwiYXBwaWRhY3IiOiIxIiwiaWRwIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvZTdmM2Y2NjEtYTkzMy00YjNmLTgxNzYtNTFjNGY5ODJlYzQ4LyIsIm9pZCI6IjY0YzQxNjMyLTliOWUtNDczNy05MTYwLTBlNjAzZTg3NjljYyIsInN1YiI6IjY0YzQxNjMyLTliOWUtNDczNy05MTYwLTBlNjAzZTg3NjljYyIsInRpZCI6ImU3ZjNmNjYxLWE5MzMtNGIzZi04MTc2LTUxYzRmOTgyZWM0OCIsInV0aSI6IkQ1ODZHSGUySDBPd0ptOU0xeVlKQUEiLCJ2ZXIiOiIxLjAifQ.Pw8c8gpoZptw3lGreQoHQaMVOozSaTE5D38Vm2aCHRB3DvD3N-Qcm1x0ZCakUEV2sJd7jvx4XtPFuW7063T0V1deExL4rzzvIo0ZfMmURf9tCTiKFKYibqf8_PtfPSz0t9eNDEUGmWDh1Wgssb4W_H-wPqgl9VPMT7T6ynkfIm0-ODPZTBzgSHiY8C_L1-DkhsK7XiqbUlSDgx9FpfChZS3ah8QhA8geqnb_HVuSktg7WhpxmogSpK5QdrwSE3jsbItpzOfLJ4iBd2ExzS2C0y8H_Coluk3Y1YA07tAxJ6Y7oBv-XwGqNfZhveOCQOzX-U3dFod3fXXysjB0UB89WQ"
+}

+ 1 - 0
pkg/api/plugins.go

@@ -174,6 +174,7 @@ func ImportDashboard(c *m.ReqContext, apiCmd dtos.ImportDashboardCommand) Respon
 		Path:      apiCmd.Path,
 		Inputs:    apiCmd.Inputs,
 		Overwrite: apiCmd.Overwrite,
+		FolderId:  apiCmd.FolderId,
 		Dashboard: apiCmd.Dashboard,
 	}
 

+ 0 - 1
pkg/models/datasource.go

@@ -12,7 +12,6 @@ const (
 	DS_GRAPHITE      = "graphite"
 	DS_INFLUXDB      = "influxdb"
 	DS_INFLUXDB_08   = "influxdb_08"
-	DS_INFLUXDB_IFQL = "influxdb-ifql"
 	DS_ES            = "elasticsearch"
 	DS_OPENTSDB      = "opentsdb"
 	DS_CLOUDWATCH    = "cloudwatch"

+ 3 - 1
pkg/plugins/dashboard_importer.go

@@ -16,6 +16,7 @@ type ImportDashboardCommand struct {
 	Path      string
 	Inputs    []ImportDashboardInput
 	Overwrite bool
+	FolderId  int64
 
 	OrgId    int64
 	User     *m.SignedInUser
@@ -70,7 +71,7 @@ func ImportDashboard(cmd *ImportDashboardCommand) error {
 		UserId:    cmd.User.UserId,
 		Overwrite: cmd.Overwrite,
 		PluginId:  cmd.PluginId,
-		FolderId:  dashboard.FolderId,
+		FolderId:  cmd.FolderId,
 	}
 
 	dto := &dashboards.SaveDashboardDTO{
@@ -91,6 +92,7 @@ func ImportDashboard(cmd *ImportDashboardCommand) error {
 		Title:            savedDash.Title,
 		Path:             cmd.Path,
 		Revision:         savedDash.Data.Get("revision").MustInt64(1),
+		FolderId:         savedDash.FolderId,
 		ImportedUri:      "db/" + savedDash.Slug,
 		ImportedUrl:      savedDash.GetUrl(),
 		ImportedRevision: dashboard.Data.Get("revision").MustInt64(1),

+ 1 - 0
pkg/plugins/dashboards.go

@@ -17,6 +17,7 @@ type PluginDashboardInfoDTO struct {
 	ImportedUrl      string `json:"importedUrl"`
 	Slug             string `json:"slug"`
 	DashboardId      int64  `json:"dashboardId"`
+	FolderId         int64  `json:"folderId"`
 	ImportedRevision int64  `json:"importedRevision"`
 	Revision         int64  `json:"revision"`
 	Description      string `json:"description"`

+ 10 - 12
pkg/services/alerting/notifiers/teams.go

@@ -41,10 +41,8 @@ func NewTeamsNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
 
 type TeamsNotifier struct {
 	NotifierBase
-	Url       string
-	Recipient string
-	Mention   string
-	log       log.Logger
+	Url string
+	log log.Logger
 }
 
 func (this *TeamsNotifier) Notify(evalContext *alerting.EvalContext) error {
@@ -75,17 +73,17 @@ func (this *TeamsNotifier) Notify(evalContext *alerting.EvalContext) error {
 		})
 	}
 
-	message := this.Mention
-	if evalContext.Rule.State != m.AlertStateOK { //don't add message when going back to alert state ok.
-		message += " " + evalContext.Rule.Message
-	} else {
-		message += " " // summary must not be empty
+	message := ""
+	if evalContext.Rule.State != m.AlertStateOK { //dont add message when going back to alert state ok.
+		message = evalContext.Rule.Message
 	}
 
 	body := map[string]interface{}{
-		"@type":      "MessageCard",
-		"@context":   "http://schema.org/extensions",
-		"summary":    message,
+		"@type":    "MessageCard",
+		"@context": "http://schema.org/extensions",
+		// summary MUST not be empty or the webhook request fails
+		// summary SHOULD contain some meaningful information, since it is used for mobile notifications
+		"summary":    evalContext.GetNotificationTitle(),
 		"title":      evalContext.GetNotificationTitle(),
 		"themeColor": evalContext.GetStateModel().Color,
 		"sections": []map[string]interface{}{

+ 1 - 1
pkg/services/sqlstore/sqlstore.go

@@ -89,7 +89,7 @@ func (ss *SqlStore) ensureAdminUser() error {
 	systemUserCountQuery := m.GetSystemUserCountStatsQuery{}
 
 	if err := bus.Dispatch(&systemUserCountQuery); err != nil {
-		fmt.Errorf("Could not determine if admin user exists: %v", err)
+		return fmt.Errorf("Could not determine if admin user exists: %v", err)
 	}
 
 	if systemUserCountQuery.Result.Count > 0 {

+ 2 - 2
pkg/tsdb/elasticsearch/client/client.go

@@ -43,12 +43,12 @@ type Client interface {
 var NewClient = func(ctx context.Context, ds *models.DataSource, timeRange *tsdb.TimeRange) (Client, error) {
 	version, err := ds.JsonData.Get("esVersion").Int()
 	if err != nil {
-		return nil, fmt.Errorf("eleasticsearch version is required, err=%v", err)
+		return nil, fmt.Errorf("elasticsearch version is required, err=%v", err)
 	}
 
 	timeField, err := ds.JsonData.Get("timeField").String()
 	if err != nil {
-		return nil, fmt.Errorf("eleasticsearch time field name is required, err=%v", err)
+		return nil, fmt.Errorf("elasticsearch time field name is required, err=%v", err)
 	}
 
 	indexInterval := ds.JsonData.Get("interval").MustString()

+ 1 - 0
pkg/tsdb/influxdb/query_part.go

@@ -31,6 +31,7 @@ func init() {
 	renders["mean"] = QueryDefinition{Renderer: functionRenderer}
 	renders["median"] = QueryDefinition{Renderer: functionRenderer}
 	renders["sum"] = QueryDefinition{Renderer: functionRenderer}
+	renders["mode"] = QueryDefinition{Renderer: functionRenderer}
 
 	renders["holt_winters"] = QueryDefinition{
 		Renderer: functionRenderer,

+ 32 - 78
pkg/tsdb/influxdb/query_part_test.go

@@ -4,85 +4,39 @@ import (
 	"testing"
 
 	"github.com/grafana/grafana/pkg/tsdb"
-	. "github.com/smartystreets/goconvey/convey"
 )
 
 func TestInfluxdbQueryPart(t *testing.T) {
-	Convey("Influxdb query parts", t, func() {
-
-		queryContext := &tsdb.TsdbQuery{TimeRange: tsdb.NewTimeRange("5m", "now")}
-		query := &Query{}
-
-		Convey("render field ", func() {
-			part, err := NewQueryPart("field", []string{"value"})
-			So(err, ShouldBeNil)
-
-			res := part.Render(query, queryContext, "value")
-			So(res, ShouldEqual, `"value"`)
-		})
-
-		Convey("render nested part", func() {
-			part, err := NewQueryPart("derivative", []string{"10s"})
-			So(err, ShouldBeNil)
-
-			res := part.Render(query, queryContext, "mean(value)")
-			So(res, ShouldEqual, "derivative(mean(value), 10s)")
-		})
-
-		Convey("render bottom", func() {
-			part, err := NewQueryPart("bottom", []string{"3"})
-			So(err, ShouldBeNil)
-
-			res := part.Render(query, queryContext, "value")
-			So(res, ShouldEqual, "bottom(value, 3)")
-		})
-
-		Convey("render time with $interval", func() {
-			part, err := NewQueryPart("time", []string{"$interval"})
-			So(err, ShouldBeNil)
-
-			res := part.Render(query, queryContext, "")
-			So(res, ShouldEqual, "time($interval)")
-		})
-
-		Convey("render time with auto", func() {
-			part, err := NewQueryPart("time", []string{"auto"})
-			So(err, ShouldBeNil)
-
-			res := part.Render(query, queryContext, "")
-			So(res, ShouldEqual, "time($__interval)")
-		})
-
-		Convey("render spread", func() {
-			part, err := NewQueryPart("spread", []string{})
-			So(err, ShouldBeNil)
-
-			res := part.Render(query, queryContext, "value")
-			So(res, ShouldEqual, `spread(value)`)
-		})
-
-		Convey("render suffix", func() {
-			part, err := NewQueryPart("math", []string{"/ 100"})
-			So(err, ShouldBeNil)
-
-			res := part.Render(query, queryContext, "mean(value)")
-			So(res, ShouldEqual, "mean(value) / 100")
-		})
-
-		Convey("render alias", func() {
-			part, err := NewQueryPart("alias", []string{"test"})
-			So(err, ShouldBeNil)
-
-			res := part.Render(query, queryContext, "mean(value)")
-			So(res, ShouldEqual, `mean(value) AS "test"`)
-		})
-
-		Convey("render count distinct", func() {
-			part, err := NewQueryPart("count", []string{})
-			So(err, ShouldBeNil)
-
-			res := part.Render(query, queryContext, "distinct(value)")
-			So(res, ShouldEqual, `count(distinct(value))`)
-		})
-	})
+	tcs := []struct {
+		mode     string
+		input    string
+		params   []string
+		expected string
+	}{
+		{mode: "field", params: []string{"value"}, input: "value", expected: `"value"`},
+		{mode: "derivative", params: []string{"10s"}, input: "mean(value)", expected: `derivative(mean(value), 10s)`},
+		{mode: "bottom", params: []string{"3"}, input: "value", expected: `bottom(value, 3)`},
+		{mode: "time", params: []string{"$interval"}, input: "", expected: `time($interval)`},
+		{mode: "time", params: []string{"auto"}, input: "", expected: `time($__interval)`},
+		{mode: "spread", params: []string{}, input: "value", expected: `spread(value)`},
+		{mode: "math", params: []string{"/ 100"}, input: "mean(value)", expected: `mean(value) / 100`},
+		{mode: "alias", params: []string{"test"}, input: "mean(value)", expected: `mean(value) AS "test"`},
+		{mode: "count", params: []string{}, input: "distinct(value)", expected: `count(distinct(value))`},
+		{mode: "mode", params: []string{}, input: "value", expected: `mode(value)`},
+	}
+
+	queryContext := &tsdb.TsdbQuery{TimeRange: tsdb.NewTimeRange("5m", "now")}
+	query := &Query{}
+
+	for _, tc := range tcs {
+		part, err := NewQueryPart(tc.mode, tc.params)
+		if err != nil {
+			t.Errorf("Expected NewQueryPart to not return an error. error: %v", err)
+		}
+
+		res := part.Render(query, queryContext, tc.input)
+		if res != tc.expected {
+			t.Errorf("expected %v to render into %s", tc, tc.expected)
+		}
+	}
 }

+ 1 - 1
public/app/core/components/grafana_app.ts

@@ -199,7 +199,7 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop
       body.mousemove(userActivityDetected);
       body.keydown(userActivityDetected);
       // set useCapture = true to catch event here
-      document.addEventListener('wheel', userActivityDetected, true);
+      document.addEventListener('wheel', userActivityDetected, { capture: true, passive: true });
       // treat tab change as activity
       document.addEventListener('visibilitychange', userActivityDetected);
 

+ 4 - 0
public/app/core/components/manage_dashboards/manage_dashboards.html

@@ -13,6 +13,10 @@
       <i class="fa fa-plus"></i>
       Folder
     </a>
+    <a class="btn btn-success" href="{{ctrl.importDashboardUrl()}}" ng-if="ctrl.hasEditPermissionInFolders || ctrl.canSave">
+      <i class="fa fa-plus"></i>
+      Import
+    </a>
   </div>
 
   <div class="page-action-bar page-action-bar--narrow" ng-show="ctrl.hasFilters">

+ 10 - 0
public/app/core/components/manage_dashboards/manage_dashboards.ts

@@ -294,6 +294,16 @@ export class ManageDashboardsCtrl {
 
     return url;
   }
+
+  importDashboardUrl() {
+    let url = 'dashboard/import';
+
+    if (this.folderId) {
+      url += `?folderId=${this.folderId}`;
+    }
+
+    return url;
+  }
 }
 
 export function manageDashboardsDirective() {

+ 2 - 2
public/app/core/components/search/search.html

@@ -52,11 +52,11 @@
         <a href="dashboards/folder/new" class="search-filter-box-link" ng-if="ctrl.isEditor">
           <i class="gicon gicon-folder-new"></i> New folder
         </a>
-        <a href="dashboard/import" class="search-filter-box-link" ng-if="ctrl.isEditor">
+        <a href="dashboard/import" class="search-filter-box-link" ng-if="ctrl.isEditor || ctrl.hasEditPermissionInFolders">
           <i class="gicon gicon-dashboard-import"></i> Import dashboard
         </a>
         <a class="search-filter-box-link" target="_blank" href="https://grafana.com/dashboards?utm_source=grafana_search">
-          <img src="public/img/icn-dashboard-tiny.svg" width="20" /> Find  dashboards on Grafana.com
+          <img src="public/img/icn-dashboard-tiny.svg" width="20" /> Find dashboards on Grafana.com
         </a>
       </div>
     </div>

+ 1 - 0
public/app/core/config.ts

@@ -16,6 +16,7 @@ class Settings {
   defaultDatasource: string;
   alertingEnabled: boolean;
   authProxyEnabled: boolean;
+  exploreEnabled: boolean;
   ldapEnabled: boolean;
   oauth: any;
   disableUserSignUp: boolean;

+ 2 - 1
public/app/core/services/keybindingSrv.ts

@@ -1,6 +1,7 @@
 import $ from 'jquery';
 import _ from 'lodash';
 
+import config from 'app/core/config';
 import coreModule from 'app/core/core_module';
 import appEvents from 'app/core/app_events';
 import { encodePathComponent } from 'app/core/utils/location_util';
@@ -178,7 +179,7 @@ export class KeybindingSrv {
     });
 
     // jump to explore if permissions allow
-    if (this.contextSrv.isEditor) {
+    if (this.contextSrv.isEditor && config.exploreEnabled) {
       this.bind('x', async () => {
         if (dashboard.meta.focusPanelId) {
           const panel = dashboard.getPanelById(dashboard.meta.focusPanelId);

+ 10 - 0
public/app/core/utils/kbn.ts

@@ -499,6 +499,7 @@ kbn.valueFormats.watt = kbn.formatBuilders.decimalSIPrefix('W');
 kbn.valueFormats.kwatt = kbn.formatBuilders.decimalSIPrefix('W', 1);
 kbn.valueFormats.mwatt = kbn.formatBuilders.decimalSIPrefix('W', -1);
 kbn.valueFormats.kwattm = kbn.formatBuilders.decimalSIPrefix('W/Min', 1);
+kbn.valueFormats.Wm2 = kbn.formatBuilders.fixedUnit('W/m2');
 kbn.valueFormats.voltamp = kbn.formatBuilders.decimalSIPrefix('VA');
 kbn.valueFormats.kvoltamp = kbn.formatBuilders.decimalSIPrefix('VA', 1);
 kbn.valueFormats.voltampreact = kbn.formatBuilders.decimalSIPrefix('var');
@@ -528,6 +529,7 @@ kbn.valueFormats.pressurebar = kbn.formatBuilders.decimalSIPrefix('bar');
 kbn.valueFormats.pressurembar = kbn.formatBuilders.decimalSIPrefix('bar', -1);
 kbn.valueFormats.pressurekbar = kbn.formatBuilders.decimalSIPrefix('bar', 1);
 kbn.valueFormats.pressurehpa = kbn.formatBuilders.fixedUnit('hPa');
+kbn.valueFormats.pressurekpa = kbn.formatBuilders.fixedUnit('kPa');
 kbn.valueFormats.pressurehg = kbn.formatBuilders.fixedUnit('"Hg');
 kbn.valueFormats.pressurepsi = kbn.formatBuilders.scaledUnits(1000, [' psi', ' ksi', ' Mpsi']);
 
@@ -579,6 +581,9 @@ kbn.valueFormats.flowgpm = kbn.formatBuilders.fixedUnit('gpm');
 kbn.valueFormats.flowcms = kbn.formatBuilders.fixedUnit('cms');
 kbn.valueFormats.flowcfs = kbn.formatBuilders.fixedUnit('cfs');
 kbn.valueFormats.flowcfm = kbn.formatBuilders.fixedUnit('cfm');
+kbn.valueFormats.litreh = kbn.formatBuilders.fixedUnit('l/h');
+kbn.valueFormats.flowlpm = kbn.formatBuilders.decimalSIPrefix('L');
+kbn.valueFormats.flowmlpm = kbn.formatBuilders.decimalSIPrefix('L', -1);
 
 // Angle
 kbn.valueFormats.degree = kbn.formatBuilders.fixedUnit('°');
@@ -1014,6 +1019,7 @@ kbn.getUnitFormats = function() {
         { text: 'Watt (W)', value: 'watt' },
         { text: 'Kilowatt (kW)', value: 'kwatt' },
         { text: 'Milliwatt (mW)', value: 'mwatt' },
+        { text: 'Watt per square metre (W/m2)', value: 'Wm2' },
         { text: 'Volt-ampere (VA)', value: 'voltamp' },
         { text: 'Kilovolt-ampere (kVA)', value: 'kvoltamp' },
         { text: 'Volt-ampere reactive (var)', value: 'voltampreact' },
@@ -1049,6 +1055,7 @@ kbn.getUnitFormats = function() {
         { text: 'Bars', value: 'pressurebar' },
         { text: 'Kilobars', value: 'pressurekbar' },
         { text: 'Hectopascals', value: 'pressurehpa' },
+        { text: 'Kilopascals', value: 'pressurekpa' },
         { text: 'Inches of mercury', value: 'pressurehg' },
         { text: 'PSI', value: 'pressurepsi' },
       ],
@@ -1069,6 +1076,9 @@ kbn.getUnitFormats = function() {
         { text: 'Cubic meters/sec (cms)', value: 'flowcms' },
         { text: 'Cubic feet/sec (cfs)', value: 'flowcfs' },
         { text: 'Cubic feet/min (cfm)', value: 'flowcfm' },
+        { text: 'Litre/hour', value: 'litreh' },
+        { text: 'Litre/min (l/min)', value: 'flowlpm' },
+        { text: 'milliLitre/min (mL/min)', value: 'flowmlpm' },
       ],
     },
     {

+ 13 - 13
public/app/features/alerting/specs/threshold_mapper_specs.ts → public/app/features/alerting/specs/threshold_mapper.jest.ts

@@ -18,9 +18,9 @@ describe('ThresholdMapper', () => {
       };
 
       var updated = ThresholdMapper.alertToGraphThresholds(panel);
-      expect(updated).to.be(true);
-      expect(panel.thresholds[0].op).to.be('gt');
-      expect(panel.thresholds[0].value).to.be(100);
+      expect(updated).toBe(true);
+      expect(panel.thresholds[0].op).toBe('gt');
+      expect(panel.thresholds[0].value).toBe(100);
     });
   });
 
@@ -39,12 +39,12 @@ describe('ThresholdMapper', () => {
       };
 
       var updated = ThresholdMapper.alertToGraphThresholds(panel);
-      expect(updated).to.be(true);
-      expect(panel.thresholds[0].op).to.be('lt');
-      expect(panel.thresholds[0].value).to.be(100);
+      expect(updated).toBe(true);
+      expect(panel.thresholds[0].op).toBe('lt');
+      expect(panel.thresholds[0].value).toBe(100);
 
-      expect(panel.thresholds[1].op).to.be('gt');
-      expect(panel.thresholds[1].value).to.be(200);
+      expect(panel.thresholds[1].op).toBe('gt');
+      expect(panel.thresholds[1].value).toBe(200);
     });
   });
 
@@ -63,12 +63,12 @@ describe('ThresholdMapper', () => {
       };
 
       var updated = ThresholdMapper.alertToGraphThresholds(panel);
-      expect(updated).to.be(true);
-      expect(panel.thresholds[0].op).to.be('gt');
-      expect(panel.thresholds[0].value).to.be(100);
+      expect(updated).toBe(true);
+      expect(panel.thresholds[0].op).toBe('gt');
+      expect(panel.thresholds[0].value).toBe(100);
 
-      expect(panel.thresholds[1].op).to.be('lt');
-      expect(panel.thresholds[1].value).to.be(200);
+      expect(panel.thresholds[1].op).toBe('lt');
+      expect(panel.thresholds[1].value).toBe(200);
     });
   });
 });

+ 25 - 1
public/app/features/dashboard/dashboard_import_ctrl.ts

@@ -21,6 +21,9 @@ export class DashboardImportCtrl {
   uidValidationError: any;
   autoGenerateUid: boolean;
   autoGenerateUidValue: string;
+  folderId: number;
+  initialFolderTitle: string;
+  isValidFolderSelection: boolean;
 
   /** @ngInject */
   constructor(private backendSrv, private validationSrv, navModelSrv, private $location, $routeParams) {
@@ -31,6 +34,8 @@ export class DashboardImportCtrl {
     this.uidExists = false;
     this.autoGenerateUid = true;
     this.autoGenerateUidValue = 'auto-generated';
+    this.folderId = $routeParams.folderId ? Number($routeParams.folderId) || 0 : null;
+    this.initialFolderTitle = 'Select a folder';
 
     // check gnetId in url
     if ($routeParams.gnetId) {
@@ -102,8 +107,9 @@ export class DashboardImportCtrl {
     this.nameExists = false;
 
     this.validationSrv
-      .validateNewDashboardName(0, this.dash.title)
+      .validateNewDashboardName(this.folderId, this.dash.title)
       .then(() => {
+        this.nameExists = false;
         this.hasNameValidationError = false;
       })
       .catch(err => {
@@ -138,6 +144,23 @@ export class DashboardImportCtrl {
       });
   }
 
+  onFolderChange(folder) {
+    this.folderId = folder.id;
+    this.titleChanged();
+  }
+
+  onEnterFolderCreation() {
+    this.inputsValid = false;
+  }
+
+  onExitFolderCreation() {
+    this.inputValueChanged();
+  }
+
+  isValid() {
+    return this.inputsValid && this.folderId !== null;
+  }
+
   saveDashboard() {
     var inputs = this.inputs.map(input => {
       return {
@@ -153,6 +176,7 @@ export class DashboardImportCtrl {
         dashboard: this.dash,
         overwrite: true,
         inputs: inputs,
+        folderId: this.folderId,
       })
       .then(res => {
         this.$location.url(res.importedUrl);

+ 12 - 1
public/app/features/dashboard/dashgrid/AddPanelPanel.tsx

@@ -154,6 +154,15 @@ export class AddPanelPanel extends React.Component<AddPanelPanelProps, AddPanelP
     });
   }
 
+  filterKeyPress(evt) {
+    if (evt.key === 'Enter') {
+      let panel = _.head(this.state.panelPlugins);
+      if (panel) {
+        this.onAddPanel(panel);
+      }
+    }
+  }
+
   filterPanels(panels, filter) {
     let regex = new RegExp(filter, 'i');
     return panels.filter(panel => {
@@ -229,10 +238,12 @@ export class AddPanelPanel extends React.Component<AddPanelPanelProps, AddPanelP
               <label className="gf-form gf-form--grow gf-form--has-input-icon">
                 <input
                   type="text"
-                  className="gf-form-input max-width-20"
+                  autoFocus
+                  className="gf-form-input gf-form--grow"
                   placeholder="Panel Search Filter"
                   value={this.state.filter}
                   onChange={this.filterChange.bind(this)}
+                  onKeyPress={this.filterKeyPress.bind(this)}
                 />
                 <i className="gf-form-input-icon fa fa-search" />
               </label>

+ 11 - 3
public/app/features/dashboard/dashgrid/DashboardRow.tsx

@@ -84,15 +84,18 @@ export class DashboardRow extends React.Component<DashboardRowProps, any> {
       'fa-chevron-right': this.state.collapsed,
     });
 
-    let title = templateSrv.replaceWithText(this.props.panel.title, this.props.panel.scopedVars);
-    const hiddenPanels = this.props.panel.panels ? this.props.panel.panels.length : 0;
+    const title = templateSrv.replaceWithText(this.props.panel.title, this.props.panel.scopedVars);
+    const count = this.props.panel.panels ? this.props.panel.panels.length : 0;
+    const panels = count === 1 ? 'panel' : 'panels';
 
     return (
       <div className={classes}>
         <a className="dashboard-row__title pointer" onClick={this.toggle}>
           <i className={chevronClass} />
           {title}
-          <span className="dashboard-row__panel_count">({hiddenPanels} hidden panels)</span>
+          <span className="dashboard-row__panel_count">
+            ({count} {panels})
+          </span>
         </a>
         {this.dashboard.meta.canEdit === true && (
           <div className="dashboard-row__actions">
@@ -104,6 +107,11 @@ export class DashboardRow extends React.Component<DashboardRowProps, any> {
             </a>
           </div>
         )}
+        {this.state.collapsed === true && (
+          <div className="dashboard-row__toggle-target" onClick={this.toggle}>
+            &nbsp;
+          </div>
+        )}
         <div className="dashboard-row__drag grid-drag-handle" />
       </div>
     );

+ 17 - 14
public/app/features/dashboard/folder_picker/folder_picker.ts

@@ -132,23 +132,26 @@ export class FolderPickerCtrl {
   }
 
   private loadInitialValue() {
-    if (this.initialFolderId && this.initialFolderId > 0) {
-      this.getOptions('').then(result => {
-        this.folder = _.find(result, { value: this.initialFolderId });
-        if (!this.folder) {
-          this.folder = { text: this.initialTitle, value: this.initialFolderId };
-        }
-        this.onFolderLoad();
-      });
-    } else {
-      if (this.initialTitle && this.initialFolderId === null) {
-        this.folder = { text: this.initialTitle, value: null };
-      } else {
-        this.folder = { text: this.rootName, value: 0 };
+    const resetFolder = { text: this.initialTitle, value: null };
+    const rootFolder = { text: this.rootName, value: 0 };
+    this.getOptions('').then(result => {
+      let folder;
+      if (this.initialFolderId) {
+        folder = _.find(result, { value: this.initialFolderId });
+      } else if (this.enableReset && this.initialTitle && this.initialFolderId === null) {
+        folder = resetFolder;
       }
 
+      if (!folder) {
+        if (this.isEditor) {
+          folder = rootFolder;
+        } else {
+          folder = result.length > 0 ? result[0] : resetFolder;
+        }
+      }
+      this.folder = folder;
       this.onFolderLoad();
-    }
+    });
   }
 
   private onFolderLoad() {

+ 16 - 2
public/app/features/dashboard/partials/dashboard_import.html

@@ -80,6 +80,20 @@
         </div>
       </div>
 
+      <div class="gf-form-inline">
+        <div class="gf-form gf-form--grow">
+          <folder-picker  label-class="width-15"
+                          initial-folder-id="ctrl.folderId"
+                          initial-title="ctrl.initialFolderTitle"
+                          on-change="ctrl.onFolderChange($folder)"
+                          on-load="ctrl.onFolderChange($folder)"
+                          enter-folder-creation="ctrl.onEnterFolderCreation()"
+                          exit-folder-creation="ctrl.onExitFolderCreation()"
+                          enable-create-new="true">
+          </folder-picker>
+        </div>
+      </div>
+
       <div class="gf-form-inline">
         <div class="gf-form gf-form--grow">
           <span class="gf-form-label width-15">
@@ -132,10 +146,10 @@
     </div>
 
     <div class="gf-form-button-row">
-      <button type="button" class="btn btn-success width-12" ng-click="ctrl.saveDashboard()" ng-hide="ctrl.nameExists || ctrl.uidExists" ng-disabled="!ctrl.inputsValid">
+      <button type="button" class="btn btn-success width-12" ng-click="ctrl.saveDashboard()" ng-hide="ctrl.nameExists || ctrl.uidExists" ng-disabled="!ctrl.isValid()">
         <i class="fa fa-save"></i> Import
       </button>
-      <button type="button" class="btn btn-danger width-12" ng-click="ctrl.saveDashboard()" ng-show="ctrl.nameExists || ctrl.uidExists" ng-disabled="!ctrl.inputsValid">
+      <button type="button" class="btn btn-danger width-12" ng-click="ctrl.saveDashboard()" ng-show="ctrl.nameExists || ctrl.uidExists" ng-disabled="!ctrl.isValid()">
         <i class="fa fa-save"></i> Import (Overwrite)
       </button>
       <a class="btn btn-link" ng-click="ctrl.back()">Cancel</a>

+ 15 - 2
public/app/features/dashboard/save_modal.ts

@@ -50,8 +50,17 @@ const template = `
     </div>
 
     <div class="gf-form-button-row text-center">
-      <button type="submit" class="btn btn-success" ng-disabled="ctrl.saveForm.$invalid">Save</button>
-      <a class="btn btn-link" ng-click="ctrl.dismiss();">Cancel</a>
+      <button
+        id="saveBtn"
+        type="submit"
+        class="btn btn-success"
+        ng-class="{'btn-success--processing': ctrl.isSaving}"
+        ng-disabled="ctrl.saveForm.$invalid || ctrl.isSaving"
+      >
+        <span ng-if="!ctrl.isSaving">Save</span>
+        <span ng-if="ctrl.isSaving === true">Saving...</span>
+      </button>
+      <button class="btn btn-inverse" ng-click="ctrl.dismiss();">Cancel</button>
     </div>
   </form>
 </div>
@@ -68,6 +77,7 @@ export class SaveDashboardModalCtrl {
   originalCurrent = [];
   max: number;
   saveForm: any;
+  isSaving: boolean;
   dismiss: () => void;
   timeChange = false;
   variableValueChange = false;
@@ -76,6 +86,7 @@ export class SaveDashboardModalCtrl {
   constructor(private dashboardSrv) {
     this.message = '';
     this.max = 64;
+    this.isSaving = false;
     this.templating = dashboardSrv.dash.templating.list;
 
     this.compareTemplating();
@@ -126,6 +137,8 @@ export class SaveDashboardModalCtrl {
     var dashboard = this.dashboardSrv.getCurrent();
     var saveModel = dashboard.getSaveModelClone(options);
 
+    this.isSaving = true;
+
     return this.dashboardSrv.save(saveModel, options).then(this.dismiss);
   }
 }

+ 194 - 0
public/app/features/dashboard/specs/exporter.jest.ts

@@ -0,0 +1,194 @@
+jest.mock('app/core/store', () => {
+  return {
+    getBool: jest.fn(),
+  };
+});
+
+import _ from 'lodash';
+import config from 'app/core/config';
+import { DashboardExporter } from '../export/exporter';
+import { DashboardModel } from '../dashboard_model';
+
+describe('given dashboard with repeated panels', () => {
+  var dash, exported;
+
+  beforeEach(done => {
+    dash = {
+      templating: {
+        list: [
+          {
+            name: 'apps',
+            type: 'query',
+            datasource: 'gfdb',
+            current: { value: 'Asd', text: 'Asd' },
+            options: [{ value: 'Asd', text: 'Asd' }],
+          },
+          {
+            name: 'prefix',
+            type: 'constant',
+            current: { value: 'collectd', text: 'collectd' },
+            options: [],
+          },
+          {
+            name: 'ds',
+            type: 'datasource',
+            query: 'testdb',
+            current: { value: 'prod', text: 'prod' },
+            options: [],
+          },
+        ],
+      },
+      annotations: {
+        list: [
+          {
+            name: 'logs',
+            datasource: 'gfdb',
+          },
+        ],
+      },
+      panels: [
+        { id: 6, datasource: 'gfdb', type: 'graph' },
+        { id: 7 },
+        {
+          id: 8,
+          datasource: '-- Mixed --',
+          targets: [{ datasource: 'other' }],
+        },
+        { id: 9, datasource: '$ds' },
+        {
+          id: 2,
+          repeat: 'apps',
+          datasource: 'gfdb',
+          type: 'graph',
+        },
+        { id: 3, repeat: null, repeatPanelId: 2 },
+      ],
+    };
+
+    config.buildInfo = {
+      version: '3.0.2',
+    };
+
+    //Stubs test function calls
+    var datasourceSrvStub = { get: jest.fn(arg => getStub(arg)) };
+
+    config.panels['graph'] = {
+      id: 'graph',
+      name: 'Graph',
+      info: { version: '1.1.0' },
+    };
+
+    dash = new DashboardModel(dash, {});
+    var exporter = new DashboardExporter(datasourceSrvStub);
+    exporter.makeExportable(dash).then(clean => {
+      exported = clean;
+      done();
+    });
+  });
+
+  it('should replace datasource refs', () => {
+    var panel = exported.panels[0];
+    expect(panel.datasource).toBe('${DS_GFDB}');
+  });
+
+  it('should replace datasource in variable query', () => {
+    expect(exported.templating.list[0].datasource).toBe('${DS_GFDB}');
+    expect(exported.templating.list[0].options.length).toBe(0);
+    expect(exported.templating.list[0].current.value).toBe(undefined);
+    expect(exported.templating.list[0].current.text).toBe(undefined);
+  });
+
+  it('should replace datasource in annotation query', () => {
+    expect(exported.annotations.list[1].datasource).toBe('${DS_GFDB}');
+  });
+
+  it('should add datasource as input', () => {
+    expect(exported.__inputs[0].name).toBe('DS_GFDB');
+    expect(exported.__inputs[0].pluginId).toBe('testdb');
+    expect(exported.__inputs[0].type).toBe('datasource');
+  });
+
+  it('should add datasource to required', () => {
+    var require = _.find(exported.__requires, { name: 'TestDB' });
+    expect(require.name).toBe('TestDB');
+    expect(require.id).toBe('testdb');
+    expect(require.type).toBe('datasource');
+    expect(require.version).toBe('1.2.1');
+  });
+
+  it('should not add built in datasources to required', () => {
+    var require = _.find(exported.__requires, { name: 'Mixed' });
+    expect(require).toBe(undefined);
+  });
+
+  it('should add datasources used in mixed mode', () => {
+    var require = _.find(exported.__requires, { name: 'OtherDB' });
+    expect(require).not.toBe(undefined);
+  });
+
+  it('should add panel to required', () => {
+    var require = _.find(exported.__requires, { name: 'Graph' });
+    expect(require.name).toBe('Graph');
+    expect(require.id).toBe('graph');
+    expect(require.version).toBe('1.1.0');
+  });
+
+  it('should add grafana version', () => {
+    var require = _.find(exported.__requires, { name: 'Grafana' });
+    expect(require.type).toBe('grafana');
+    expect(require.id).toBe('grafana');
+    expect(require.version).toBe('3.0.2');
+  });
+
+  it('should add constant template variables as inputs', () => {
+    var input = _.find(exported.__inputs, { name: 'VAR_PREFIX' });
+    expect(input.type).toBe('constant');
+    expect(input.label).toBe('prefix');
+    expect(input.value).toBe('collectd');
+  });
+
+  it('should templatize constant variables', () => {
+    var variable = _.find(exported.templating.list, { name: 'prefix' });
+    expect(variable.query).toBe('${VAR_PREFIX}');
+    expect(variable.current.text).toBe('${VAR_PREFIX}');
+    expect(variable.current.value).toBe('${VAR_PREFIX}');
+    expect(variable.options[0].text).toBe('${VAR_PREFIX}');
+    expect(variable.options[0].value).toBe('${VAR_PREFIX}');
+  });
+});
+
+// Stub responses
+var stubs = [];
+stubs['gfdb'] = {
+  name: 'gfdb',
+  meta: { id: 'testdb', info: { version: '1.2.1' }, name: 'TestDB' },
+};
+
+stubs['other'] = {
+  name: 'other',
+  meta: { id: 'other', info: { version: '1.2.1' }, name: 'OtherDB' },
+};
+
+stubs['-- Mixed --'] = {
+  name: 'mixed',
+  meta: {
+    id: 'mixed',
+    info: { version: '1.2.1' },
+    name: 'Mixed',
+    builtIn: true,
+  },
+};
+
+stubs['-- Grafana --'] = {
+  name: '-- Grafana --',
+  meta: {
+    id: 'grafana',
+    info: { version: '1.2.1' },
+    name: 'grafana',
+    builtIn: true,
+  },
+};
+
+function getStub(arg) {
+  return Promise.resolve(stubs[arg]);
+}

+ 0 - 187
public/app/features/dashboard/specs/exporter_specs.ts

@@ -1,187 +0,0 @@
-import { describe, beforeEach, it, sinon, expect } from 'test/lib/common';
-
-import _ from 'lodash';
-import config from 'app/core/config';
-import { DashboardExporter } from '../export/exporter';
-import { DashboardModel } from '../dashboard_model';
-
-describe('given dashboard with repeated panels', function() {
-  var dash, exported;
-
-  beforeEach(done => {
-    dash = {
-      templating: { list: [] },
-      annotations: { list: [] },
-    };
-
-    config.buildInfo = {
-      version: '3.0.2',
-    };
-
-    dash.templating.list.push({
-      name: 'apps',
-      type: 'query',
-      datasource: 'gfdb',
-      current: { value: 'Asd', text: 'Asd' },
-      options: [{ value: 'Asd', text: 'Asd' }],
-    });
-
-    dash.templating.list.push({
-      name: 'prefix',
-      type: 'constant',
-      current: { value: 'collectd', text: 'collectd' },
-      options: [],
-    });
-
-    dash.templating.list.push({
-      name: 'ds',
-      type: 'datasource',
-      query: 'testdb',
-      current: { value: 'prod', text: 'prod' },
-      options: [],
-    });
-
-    dash.annotations.list.push({
-      name: 'logs',
-      datasource: 'gfdb',
-    });
-
-    dash.panels = [
-      { id: 6, datasource: 'gfdb', type: 'graph' },
-      { id: 7 },
-      {
-        id: 8,
-        datasource: '-- Mixed --',
-        targets: [{ datasource: 'other' }],
-      },
-      { id: 9, datasource: '$ds' },
-    ];
-
-    dash.panels.push({
-      id: 2,
-      repeat: 'apps',
-      datasource: 'gfdb',
-      type: 'graph',
-    });
-    dash.panels.push({ id: 3, repeat: null, repeatPanelId: 2 });
-
-    var datasourceSrvStub = { get: sinon.stub() };
-    datasourceSrvStub.get.withArgs('gfdb').returns(
-      Promise.resolve({
-        name: 'gfdb',
-        meta: { id: 'testdb', info: { version: '1.2.1' }, name: 'TestDB' },
-      })
-    );
-    datasourceSrvStub.get.withArgs('other').returns(
-      Promise.resolve({
-        name: 'other',
-        meta: { id: 'other', info: { version: '1.2.1' }, name: 'OtherDB' },
-      })
-    );
-    datasourceSrvStub.get.withArgs('-- Mixed --').returns(
-      Promise.resolve({
-        name: 'mixed',
-        meta: {
-          id: 'mixed',
-          info: { version: '1.2.1' },
-          name: 'Mixed',
-          builtIn: true,
-        },
-      })
-    );
-    datasourceSrvStub.get.withArgs('-- Grafana --').returns(
-      Promise.resolve({
-        name: '-- Grafana --',
-        meta: {
-          id: 'grafana',
-          info: { version: '1.2.1' },
-          name: 'grafana',
-          builtIn: true,
-        },
-      })
-    );
-
-    config.panels['graph'] = {
-      id: 'graph',
-      name: 'Graph',
-      info: { version: '1.1.0' },
-    };
-
-    dash = new DashboardModel(dash, {});
-    var exporter = new DashboardExporter(datasourceSrvStub);
-    exporter.makeExportable(dash).then(clean => {
-      exported = clean;
-      done();
-    });
-  });
-
-  it('should replace datasource refs', function() {
-    var panel = exported.panels[0];
-    expect(panel.datasource).to.be('${DS_GFDB}');
-  });
-
-  it('should replace datasource in variable query', function() {
-    expect(exported.templating.list[0].datasource).to.be('${DS_GFDB}');
-    expect(exported.templating.list[0].options.length).to.be(0);
-    expect(exported.templating.list[0].current.value).to.be(undefined);
-    expect(exported.templating.list[0].current.text).to.be(undefined);
-  });
-
-  it('should replace datasource in annotation query', function() {
-    expect(exported.annotations.list[1].datasource).to.be('${DS_GFDB}');
-  });
-
-  it('should add datasource as input', function() {
-    expect(exported.__inputs[0].name).to.be('DS_GFDB');
-    expect(exported.__inputs[0].pluginId).to.be('testdb');
-    expect(exported.__inputs[0].type).to.be('datasource');
-  });
-
-  it('should add datasource to required', function() {
-    var require = _.find(exported.__requires, { name: 'TestDB' });
-    expect(require.name).to.be('TestDB');
-    expect(require.id).to.be('testdb');
-    expect(require.type).to.be('datasource');
-    expect(require.version).to.be('1.2.1');
-  });
-
-  it('should not add built in datasources to required', function() {
-    var require = _.find(exported.__requires, { name: 'Mixed' });
-    expect(require).to.be(undefined);
-  });
-
-  it('should add datasources used in mixed mode', function() {
-    var require = _.find(exported.__requires, { name: 'OtherDB' });
-    expect(require).to.not.be(undefined);
-  });
-
-  it('should add panel to required', function() {
-    var require = _.find(exported.__requires, { name: 'Graph' });
-    expect(require.name).to.be('Graph');
-    expect(require.id).to.be('graph');
-    expect(require.version).to.be('1.1.0');
-  });
-
-  it('should add grafana version', function() {
-    var require = _.find(exported.__requires, { name: 'Grafana' });
-    expect(require.type).to.be('grafana');
-    expect(require.id).to.be('grafana');
-    expect(require.version).to.be('3.0.2');
-  });
-
-  it('should add constant template variables as inputs', function() {
-    var input = _.find(exported.__inputs, { name: 'VAR_PREFIX' });
-    expect(input.type).to.be('constant');
-    expect(input.label).to.be('prefix');
-    expect(input.value).to.be('collectd');
-  });
-
-  it('should templatize constant variables', function() {
-    var variable = _.find(exported.templating.list, { name: 'prefix' });
-    expect(variable.query).to.be('${VAR_PREFIX}');
-    expect(variable.current.text).to.be('${VAR_PREFIX}');
-    expect(variable.current.value).to.be('${VAR_PREFIX}');
-    expect(variable.options[0].text).to.be('${VAR_PREFIX}');
-    expect(variable.options[0].value).to.be('${VAR_PREFIX}');
-  });
-});

+ 1 - 1
public/app/features/panel/metrics_panel_ctrl.ts

@@ -314,7 +314,7 @@ class MetricsPanelCtrl extends PanelCtrl {
 
   getAdditionalMenuItems() {
     const items = [];
-    if (this.contextSrv.isEditor && this.datasource && this.datasource.supportsExplore) {
+    if (config.exploreEnabled && this.contextSrv.isEditor && this.datasource && this.datasource.supportsExplore) {
       items.push({
         text: 'Explore',
         click: 'ctrl.explore();',

+ 1 - 1
public/app/features/panel/panel_header.ts

@@ -25,7 +25,7 @@ var template = `
       <li><a ng-click="ctrl.addDataQuery(datasource);"><i class="fa fa-trash"></i> Remove</a></li>
     </ul>
   </span>
-  <span class="panel-time-info" ng-show="ctrl.timeInfo"><i class="fa fa-clock-o"></i> {{ctrl.timeInfo}}</span>
+  <span class="panel-time-info" ng-if="ctrl.timeInfo"><i class="fa fa-clock-o"></i> {{ctrl.timeInfo}}</span>
 </span>`;
 
 function renderMenuItem(item, ctrl) {

+ 12 - 1
public/app/features/panel/specs/metrics_panel_ctrl.jest.ts

@@ -1,8 +1,19 @@
 jest.mock('app/core/core', () => ({}));
+jest.mock('app/core/config', () => {
+  return {
+    exploreEnabled: true,
+    panels: {
+      test: {
+        id: 'test',
+        name: 'test',
+      },
+    },
+  };
+});
 
-import { MetricsPanelCtrl } from '../metrics_panel_ctrl';
 import q from 'q';
 import { PanelModel } from 'app/features/dashboard/panel_model';
+import { MetricsPanelCtrl } from '../metrics_panel_ctrl';
 
 describe('MetricsPanelCtrl', () => {
   let ctrl;

+ 12 - 13
public/app/features/playlist/specs/playlist_edit_ctrl_specs.ts → public/app/features/playlist/specs/playlist_edit_ctrl.jest.ts

@@ -1,5 +1,4 @@
 import '../playlist_edit_ctrl';
-import { describe, beforeEach, it, expect } from 'test/lib/common';
 import { PlaylistEditCtrl } from '../playlist_edit_ctrl';
 
 describe('PlaylistEditCtrl', () => {
@@ -20,13 +19,13 @@ describe('PlaylistEditCtrl', () => {
 
   describe('searchresult returns 2 dashboards, ', () => {
     it('found dashboard should be 2', () => {
-      expect(ctx.dashboardresult.length).to.be(2);
+      expect(ctx.dashboardresult.length).toBe(2);
     });
 
     it('filtred result should be 2', () => {
       ctx.filterFoundPlaylistItems();
-      expect(ctx.filteredDashboards.length).to.be(2);
-      expect(ctx.filteredTags.length).to.be(2);
+      expect(ctx.filteredDashboards.length).toBe(2);
+      expect(ctx.filteredTags.length).toBe(2);
     });
 
     describe('adds one dashboard to playlist, ', () => {
@@ -37,16 +36,16 @@ describe('PlaylistEditCtrl', () => {
       });
 
       it('playlistitems should be increased by one', () => {
-        expect(ctx.playlistItems.length).to.be(2);
+        expect(ctx.playlistItems.length).toBe(2);
       });
 
       it('filtred playlistitems should be reduced by one', () => {
-        expect(ctx.filteredDashboards.length).to.be(1);
-        expect(ctx.filteredTags.length).to.be(1);
+        expect(ctx.filteredDashboards.length).toBe(1);
+        expect(ctx.filteredTags.length).toBe(1);
       });
 
       it('found dashboard should be 2', () => {
-        expect(ctx.dashboardresult.length).to.be(2);
+        expect(ctx.dashboardresult.length).toBe(2);
       });
 
       describe('removes one dashboard from playlist, ', () => {
@@ -57,14 +56,14 @@ describe('PlaylistEditCtrl', () => {
         });
 
         it('playlistitems should be increased by one', () => {
-          expect(ctx.playlistItems.length).to.be(0);
+          expect(ctx.playlistItems.length).toBe(0);
         });
 
         it('found dashboard should be 2', () => {
-          expect(ctx.dashboardresult.length).to.be(2);
-          expect(ctx.filteredDashboards.length).to.be(2);
-          expect(ctx.filteredTags.length).to.be(2);
-          expect(ctx.tagresult.length).to.be(2);
+          expect(ctx.dashboardresult.length).toBe(2);
+          expect(ctx.filteredDashboards.length).toBe(2);
+          expect(ctx.filteredTags.length).toBe(2);
+          expect(ctx.tagresult.length).toBe(2);
         });
       });
     });

+ 0 - 2
public/app/features/plugins/built_in_plugins.ts

@@ -4,7 +4,6 @@ import * as elasticsearchPlugin from 'app/plugins/datasource/elasticsearch/modul
 import * as opentsdbPlugin from 'app/plugins/datasource/opentsdb/module';
 import * as grafanaPlugin from 'app/plugins/datasource/grafana/module';
 import * as influxdbPlugin from 'app/plugins/datasource/influxdb/module';
-import * as influxdbIfqlPlugin from 'app/plugins/datasource/influxdb-ifql/module';
 import * as mixedPlugin from 'app/plugins/datasource/mixed/module';
 import * as mysqlPlugin from 'app/plugins/datasource/mysql/module';
 import * as postgresPlugin from 'app/plugins/datasource/postgres/module';
@@ -31,7 +30,6 @@ const builtInPlugins = {
   'app/plugins/datasource/opentsdb/module': opentsdbPlugin,
   'app/plugins/datasource/grafana/module': grafanaPlugin,
   'app/plugins/datasource/influxdb/module': influxdbPlugin,
-  'app/plugins/datasource/influxdb-ifql/module': influxdbIfqlPlugin,
   'app/plugins/datasource/mixed/module': mixedPlugin,
   'app/plugins/datasource/mysql/module': mysqlPlugin,
   'app/plugins/datasource/postgres/module': postgresPlugin,

+ 0 - 28
public/app/features/plugins/plugin_component.ts

@@ -6,7 +6,6 @@ import coreModule from 'app/core/core_module';
 import { importPluginModule } from './plugin_loader';
 
 import { UnknownPanelCtrl } from 'app/plugins/panel/unknown/module';
-import { DashboardRowCtrl } from './row_ctrl';
 
 /** @ngInject **/
 function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $templateCache) {
@@ -59,15 +58,6 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
   }
 
   function loadPanelComponentInfo(scope, attrs) {
-    if (scope.panel.type === 'row') {
-      return $q.when({
-        name: 'dashboard-row',
-        bindings: { dashboard: '=', panel: '=' },
-        attrs: { dashboard: 'ctrl.dashboard', panel: 'panel' },
-        Component: DashboardRowCtrl,
-      });
-    }
-
     var componentInfo: any = {
       name: 'panel-plugin-' + scope.panel.type,
       bindings: { dashboard: '=', panel: '=', row: '=' },
@@ -136,24 +126,6 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
           });
         });
       }
-      // QueryOptionsCtrl
-      case 'query-options-ctrl': {
-        return datasourceSrv.get(scope.ctrl.panel.datasource).then(ds => {
-          return importPluginModule(ds.meta.module).then((dsModule): any => {
-            if (!dsModule.QueryOptionsCtrl) {
-              return { notFound: true };
-            }
-
-            return {
-              baseUrl: ds.meta.baseUrl,
-              name: 'query-options-ctrl-' + ds.meta.id,
-              bindings: { panelCtrl: '=' },
-              attrs: { 'panel-ctrl': 'ctrl.panelCtrl' },
-              Component: dsModule.QueryOptionsCtrl,
-            };
-          });
-        });
-      }
       // Annotations
       case 'annotations-query-ctrl': {
         return importPluginModule(scope.ctrl.currentDatasource.meta.module).then(function(dsModule) {

+ 17 - 0
public/app/features/plugins/plugin_loader.ts

@@ -5,6 +5,15 @@ import kbn from 'app/core/utils/kbn';
 import moment from 'moment';
 import angular from 'angular';
 import jquery from 'jquery';
+
+// Experimental module exports
+import prismjs from 'prismjs';
+import slate from 'slate';
+import slateReact from 'slate-react';
+import slatePlain from 'slate-plain-serializer';
+import react from 'react';
+import reactDom from 'react-dom';
+
 import config from 'app/core/config';
 import TimeSeries from 'app/core/time_series2';
 import TableModel from 'app/core/table_model';
@@ -69,6 +78,14 @@ exposeToPlugin('d3', d3);
 exposeToPlugin('rxjs/Subject', Subject);
 exposeToPlugin('rxjs/Observable', Observable);
 
+// Experimental modules
+exposeToPlugin('prismjs', prismjs);
+exposeToPlugin('slate', slate);
+exposeToPlugin('slate-react', slateReact);
+exposeToPlugin('slate-plain-serializer', slatePlain);
+exposeToPlugin('react', react);
+exposeToPlugin('react-dom', reactDom);
+
 // backward compatible path
 exposeToPlugin('vendor/npm/rxjs/Rx', {
   Subject: Subject,

+ 0 - 100
public/app/features/plugins/row_ctrl.ts

@@ -1,100 +0,0 @@
-import _ from 'lodash';
-
-export class DashboardRowCtrl {
-  static template = `
-    <div class="dashboard-row__center">
-      <div class="dashboard-row__actions-left">
-        <i class="fa fa-chevron-down" ng-hide="ctrl.panel.collapse"></i>
-        <i class="fa fa-chevron-right" ng-show="ctrl.panel.collapse"></i>
-      </div>
-      <a class="dashboard-row__title pointer" ng-click="ctrl.toggle()">
-        <span class="dashboard-row__title-text">
-          {{ctrl.panel.title | interpolateTemplateVars:this}}
-        </span>
-      </a>
-      <div class="dashboard-row__actions-right">
-        <a class="pointer" ng-click="ctrl.openSettings()"><span class="fa fa-cog"></i></a>
-      </div>
-    </div>
-
-  <div class="dashboard-row__panel_count">
-    ({{ctrl.panel.hiddenPanels.length}} hidden panels)
-  </div>
-  <div class="dashboard-row__drag grid-drag-handle">
-  </div>
-  `;
-
-  dashboard: any;
-  panel: any;
-
-  constructor() {
-    this.panel.hiddenPanels = this.panel.hiddenPanels || [];
-  }
-
-  toggle() {
-    if (this.panel.collapse) {
-      let panelIndex = _.indexOf(this.dashboard.panels, this.panel);
-
-      for (let child of this.panel.hiddenPanels) {
-        this.dashboard.panels.splice(panelIndex + 1, 0, child);
-        child.y = this.panel.y + 1;
-        console.log('restoring child', child);
-      }
-
-      this.panel.hiddenPanels = [];
-      this.panel.collapse = false;
-      return;
-    }
-
-    this.panel.collapse = true;
-    let foundRow = false;
-
-    for (let i = 0; i < this.dashboard.panels.length; i++) {
-      let panel = this.dashboard.panels[i];
-
-      if (panel === this.panel) {
-        console.log('found row');
-        foundRow = true;
-        continue;
-      }
-
-      if (!foundRow) {
-        continue;
-      }
-
-      if (panel.type === 'row') {
-        break;
-      }
-
-      this.panel.hiddenPanels.push(panel);
-      console.log('hiding child', panel.id);
-    }
-
-    for (let hiddenPanel of this.panel.hiddenPanels) {
-      this.dashboard.removePanel(hiddenPanel, false);
-    }
-  }
-
-  moveUp() {
-    // let panelIndex = _.indexOf(this.dashboard.panels, this.panel);
-    // let rowAbove = null;
-    // for (let index = panelIndex-1; index > 0; index--) {
-    //   panel = this.dashboard.panels[index];
-    //   if (panel.type === 'row') {
-    //     rowAbove = panel;
-    //   }
-    // }
-    //
-    // if (rowAbove) {
-    //   this.panel.y = rowAbove.y;
-    // }
-  }
-
-  link(scope, elem) {
-    elem.addClass('dashboard-row');
-
-    scope.$watch('ctrl.panel.collapse', () => {
-      elem.toggleClass('dashboard-row--collapse', this.panel.collapse === true);
-    });
-  }
-}

+ 0 - 5
public/app/plugins/datasource/elasticsearch/module.ts

@@ -2,10 +2,6 @@ import { ElasticDatasource } from './datasource';
 import { ElasticQueryCtrl } from './query_ctrl';
 import { ElasticConfigCtrl } from './config_ctrl';
 
-class ElasticQueryOptionsCtrl {
-  static templateUrl = 'partials/query.options.html';
-}
-
 class ElasticAnnotationsQueryCtrl {
   static templateUrl = 'partials/annotations.editor.html';
 }
@@ -14,6 +10,5 @@ export {
   ElasticDatasource as Datasource,
   ElasticQueryCtrl as QueryCtrl,
   ElasticConfigCtrl as ConfigCtrl,
-  ElasticQueryOptionsCtrl as QueryOptionsCtrl,
   ElasticAnnotationsQueryCtrl as AnnotationsQueryCtrl,
 };

+ 105 - 106
public/app/plugins/datasource/elasticsearch/specs/elastic_response_specs.ts → public/app/plugins/datasource/elasticsearch/specs/elastic_response.jest.ts

@@ -1,13 +1,12 @@
-import { describe, beforeEach, it, expect } from 'test/lib/common';
 import { ElasticResponse } from '../elastic_response';
 
-describe('ElasticResponse', function() {
+describe('ElasticResponse', () => {
   var targets;
   var response;
   var result;
 
-  describe('simple query and count', function() {
-    beforeEach(function() {
+  describe('simple query and count', () => {
+    beforeEach(() => {
       targets = [
         {
           refId: 'A',
@@ -39,19 +38,19 @@ describe('ElasticResponse', function() {
       result = new ElasticResponse(targets, response).getTimeSeries();
     });
 
-    it('should return 1 series', function() {
-      expect(result.data.length).to.be(1);
-      expect(result.data[0].target).to.be('Count');
-      expect(result.data[0].datapoints.length).to.be(2);
-      expect(result.data[0].datapoints[0][0]).to.be(10);
-      expect(result.data[0].datapoints[0][1]).to.be(1000);
+    it('should return 1 series', () => {
+      expect(result.data.length).toBe(1);
+      expect(result.data[0].target).toBe('Count');
+      expect(result.data[0].datapoints.length).toBe(2);
+      expect(result.data[0].datapoints[0][0]).toBe(10);
+      expect(result.data[0].datapoints[0][1]).toBe(1000);
     });
   });
 
-  describe('simple query count & avg aggregation', function() {
+  describe('simple query count & avg aggregation', () => {
     var result;
 
-    beforeEach(function() {
+    beforeEach(() => {
       targets = [
         {
           refId: 'A',
@@ -85,22 +84,22 @@ describe('ElasticResponse', function() {
       result = new ElasticResponse(targets, response).getTimeSeries();
     });
 
-    it('should return 2 series', function() {
-      expect(result.data.length).to.be(2);
-      expect(result.data[0].datapoints.length).to.be(2);
-      expect(result.data[0].datapoints[0][0]).to.be(10);
-      expect(result.data[0].datapoints[0][1]).to.be(1000);
+    it('should return 2 series', () => {
+      expect(result.data.length).toBe(2);
+      expect(result.data[0].datapoints.length).toBe(2);
+      expect(result.data[0].datapoints[0][0]).toBe(10);
+      expect(result.data[0].datapoints[0][1]).toBe(1000);
 
-      expect(result.data[1].target).to.be('Average value');
-      expect(result.data[1].datapoints[0][0]).to.be(88);
-      expect(result.data[1].datapoints[1][0]).to.be(99);
+      expect(result.data[1].target).toBe('Average value');
+      expect(result.data[1].datapoints[0][0]).toBe(88);
+      expect(result.data[1].datapoints[1][0]).toBe(99);
     });
   });
 
-  describe('single group by query one metric', function() {
+  describe('single group by query one metric', () => {
     var result;
 
-    beforeEach(function() {
+    beforeEach(() => {
       targets = [
         {
           refId: 'A',
@@ -141,18 +140,18 @@ describe('ElasticResponse', function() {
       result = new ElasticResponse(targets, response).getTimeSeries();
     });
 
-    it('should return 2 series', function() {
-      expect(result.data.length).to.be(2);
-      expect(result.data[0].datapoints.length).to.be(2);
-      expect(result.data[0].target).to.be('server1');
-      expect(result.data[1].target).to.be('server2');
+    it('should return 2 series', () => {
+      expect(result.data.length).toBe(2);
+      expect(result.data[0].datapoints.length).toBe(2);
+      expect(result.data[0].target).toBe('server1');
+      expect(result.data[1].target).toBe('server2');
     });
   });
 
-  describe('single group by query two metrics', function() {
+  describe('single group by query two metrics', () => {
     var result;
 
-    beforeEach(function() {
+    beforeEach(() => {
       targets = [
         {
           refId: 'A',
@@ -199,20 +198,20 @@ describe('ElasticResponse', function() {
       result = new ElasticResponse(targets, response).getTimeSeries();
     });
 
-    it('should return 2 series', function() {
-      expect(result.data.length).to.be(4);
-      expect(result.data[0].datapoints.length).to.be(2);
-      expect(result.data[0].target).to.be('server1 Count');
-      expect(result.data[1].target).to.be('server1 Average @value');
-      expect(result.data[2].target).to.be('server2 Count');
-      expect(result.data[3].target).to.be('server2 Average @value');
+    it('should return 2 series', () => {
+      expect(result.data.length).toBe(4);
+      expect(result.data[0].datapoints.length).toBe(2);
+      expect(result.data[0].target).toBe('server1 Count');
+      expect(result.data[1].target).toBe('server1 Average @value');
+      expect(result.data[2].target).toBe('server2 Count');
+      expect(result.data[3].target).toBe('server2 Average @value');
     });
   });
 
-  describe('with percentiles ', function() {
+  describe('with percentiles ', () => {
     var result;
 
-    beforeEach(function() {
+    beforeEach(() => {
       targets = [
         {
           refId: 'A',
@@ -246,21 +245,21 @@ describe('ElasticResponse', function() {
       result = new ElasticResponse(targets, response).getTimeSeries();
     });
 
-    it('should return 2 series', function() {
-      expect(result.data.length).to.be(2);
-      expect(result.data[0].datapoints.length).to.be(2);
-      expect(result.data[0].target).to.be('p75');
-      expect(result.data[1].target).to.be('p90');
-      expect(result.data[0].datapoints[0][0]).to.be(3.3);
-      expect(result.data[0].datapoints[0][1]).to.be(1000);
-      expect(result.data[1].datapoints[1][0]).to.be(4.5);
+    it('should return 2 series', () => {
+      expect(result.data.length).toBe(2);
+      expect(result.data[0].datapoints.length).toBe(2);
+      expect(result.data[0].target).toBe('p75');
+      expect(result.data[1].target).toBe('p90');
+      expect(result.data[0].datapoints[0][0]).toBe(3.3);
+      expect(result.data[0].datapoints[0][1]).toBe(1000);
+      expect(result.data[1].datapoints[1][0]).toBe(4.5);
     });
   });
 
-  describe('with extended_stats', function() {
+  describe('with extended_stats', () => {
     var result;
 
-    beforeEach(function() {
+    beforeEach(() => {
       targets = [
         {
           refId: 'A',
@@ -322,21 +321,21 @@ describe('ElasticResponse', function() {
       result = new ElasticResponse(targets, response).getTimeSeries();
     });
 
-    it('should return 4 series', function() {
-      expect(result.data.length).to.be(4);
-      expect(result.data[0].datapoints.length).to.be(1);
-      expect(result.data[0].target).to.be('server1 Max');
-      expect(result.data[1].target).to.be('server1 Std Dev Upper');
+    it('should return 4 series', () => {
+      expect(result.data.length).toBe(4);
+      expect(result.data[0].datapoints.length).toBe(1);
+      expect(result.data[0].target).toBe('server1 Max');
+      expect(result.data[1].target).toBe('server1 Std Dev Upper');
 
-      expect(result.data[0].datapoints[0][0]).to.be(10.2);
-      expect(result.data[1].datapoints[0][0]).to.be(3);
+      expect(result.data[0].datapoints[0][0]).toBe(10.2);
+      expect(result.data[1].datapoints[0][0]).toBe(3);
     });
   });
 
-  describe('single group by with alias pattern', function() {
+  describe('single group by with alias pattern', () => {
     var result;
 
-    beforeEach(function() {
+    beforeEach(() => {
       targets = [
         {
           refId: 'A',
@@ -385,19 +384,19 @@ describe('ElasticResponse', function() {
       result = new ElasticResponse(targets, response).getTimeSeries();
     });
 
-    it('should return 2 series', function() {
-      expect(result.data.length).to.be(3);
-      expect(result.data[0].datapoints.length).to.be(2);
-      expect(result.data[0].target).to.be('server1 Count and {{not_exist}} server1');
-      expect(result.data[1].target).to.be('server2 Count and {{not_exist}} server2');
-      expect(result.data[2].target).to.be('0 Count and {{not_exist}} 0');
+    it('should return 2 series', () => {
+      expect(result.data.length).toBe(3);
+      expect(result.data[0].datapoints.length).toBe(2);
+      expect(result.data[0].target).toBe('server1 Count and {{not_exist}} server1');
+      expect(result.data[1].target).toBe('server2 Count and {{not_exist}} server2');
+      expect(result.data[2].target).toBe('0 Count and {{not_exist}} 0');
     });
   });
 
-  describe('histogram response', function() {
+  describe('histogram response', () => {
     var result;
 
-    beforeEach(function() {
+    beforeEach(() => {
       targets = [
         {
           refId: 'A',
@@ -420,16 +419,16 @@ describe('ElasticResponse', function() {
       result = new ElasticResponse(targets, response).getTimeSeries();
     });
 
-    it('should return table with byte and count', function() {
-      expect(result.data[0].rows.length).to.be(3);
-      expect(result.data[0].columns).to.eql([{ text: 'bytes', filterable: true }, { text: 'Count' }]);
+    it('should return table with byte and count', () => {
+      expect(result.data[0].rows.length).toBe(3);
+      expect(result.data[0].columns).toEqual([{ text: 'bytes', filterable: true }, { text: 'Count' }]);
     });
   });
 
-  describe('with two filters agg', function() {
+  describe('with two filters agg', () => {
     var result;
 
-    beforeEach(function() {
+    beforeEach(() => {
       targets = [
         {
           refId: 'A',
@@ -472,16 +471,16 @@ describe('ElasticResponse', function() {
       result = new ElasticResponse(targets, response).getTimeSeries();
     });
 
-    it('should return 2 series', function() {
-      expect(result.data.length).to.be(2);
-      expect(result.data[0].datapoints.length).to.be(2);
-      expect(result.data[0].target).to.be('@metric:cpu');
-      expect(result.data[1].target).to.be('@metric:logins.count');
+    it('should return 2 series', () => {
+      expect(result.data.length).toBe(2);
+      expect(result.data[0].datapoints.length).toBe(2);
+      expect(result.data[0].target).toBe('@metric:cpu');
+      expect(result.data[1].target).toBe('@metric:logins.count');
     });
   });
 
-  describe('with dropfirst and last aggregation', function() {
-    beforeEach(function() {
+  describe('with dropfirst and last aggregation', () => {
+    beforeEach(() => {
       targets = [
         {
           refId: 'A',
@@ -528,14 +527,14 @@ describe('ElasticResponse', function() {
       result = new ElasticResponse(targets, response).getTimeSeries();
     });
 
-    it('should remove first and last value', function() {
-      expect(result.data.length).to.be(2);
-      expect(result.data[0].datapoints.length).to.be(1);
+    it('should remove first and last value', () => {
+      expect(result.data.length).toBe(2);
+      expect(result.data[0].datapoints.length).toBe(1);
     });
   });
 
-  describe('No group by time', function() {
-    beforeEach(function() {
+  describe('No group by time', () => {
+    beforeEach(() => {
       targets = [
         {
           refId: 'A',
@@ -570,21 +569,21 @@ describe('ElasticResponse', function() {
       result = new ElasticResponse(targets, response).getTimeSeries();
     });
 
-    it('should return table', function() {
-      expect(result.data.length).to.be(1);
-      expect(result.data[0].type).to.be('table');
-      expect(result.data[0].rows.length).to.be(2);
-      expect(result.data[0].rows[0][0]).to.be('server-1');
-      expect(result.data[0].rows[0][1]).to.be(1000);
-      expect(result.data[0].rows[0][2]).to.be(369);
+    it('should return table', () => {
+      expect(result.data.length).toBe(1);
+      expect(result.data[0].type).toBe('table');
+      expect(result.data[0].rows.length).toBe(2);
+      expect(result.data[0].rows[0][0]).toBe('server-1');
+      expect(result.data[0].rows[0][1]).toBe(1000);
+      expect(result.data[0].rows[0][2]).toBe(369);
 
-      expect(result.data[0].rows[1][0]).to.be('server-2');
-      expect(result.data[0].rows[1][1]).to.be(2000);
+      expect(result.data[0].rows[1][0]).toBe('server-2');
+      expect(result.data[0].rows[1][1]).toBe(2000);
     });
   });
 
-  describe('Multiple metrics of same type', function() {
-    beforeEach(function() {
+  describe('Multiple metrics of same type', () => {
+    beforeEach(() => {
       targets = [
         {
           refId: 'A',
@@ -615,15 +614,15 @@ describe('ElasticResponse', function() {
       result = new ElasticResponse(targets, response).getTimeSeries();
     });
 
-    it('should include field in metric name', function() {
-      expect(result.data[0].type).to.be('table');
-      expect(result.data[0].rows[0][1]).to.be(1000);
-      expect(result.data[0].rows[0][2]).to.be(3000);
+    it('should include field in metric name', () => {
+      expect(result.data[0].type).toBe('table');
+      expect(result.data[0].rows[0][1]).toBe(1000);
+      expect(result.data[0].rows[0][2]).toBe(3000);
     });
   });
 
-  describe('Raw documents query', function() {
-    beforeEach(function() {
+  describe('Raw documents query', () => {
+    beforeEach(() => {
       targets = [
         {
           refId: 'A',
@@ -657,13 +656,13 @@ describe('ElasticResponse', function() {
       result = new ElasticResponse(targets, response).getTimeSeries();
     });
 
-    it('should return docs', function() {
-      expect(result.data.length).to.be(1);
-      expect(result.data[0].type).to.be('docs');
-      expect(result.data[0].total).to.be(100);
-      expect(result.data[0].datapoints.length).to.be(2);
-      expect(result.data[0].datapoints[0].sourceProp).to.be('asd');
-      expect(result.data[0].datapoints[0].fieldProp).to.be('field');
+    it('should return docs', () => {
+      expect(result.data.length).toBe(1);
+      expect(result.data[0].type).toBe('docs');
+      expect(result.data[0].total).toBe(100);
+      expect(result.data[0].datapoints.length).toBe(2);
+      expect(result.data[0].datapoints[0].sourceProp).toBe('asd');
+      expect(result.data[0].datapoints[0].fieldProp).toBe('field');
     });
   });
 });

+ 11 - 12
public/app/plugins/datasource/elasticsearch/specs/index_pattern_specs.ts → public/app/plugins/datasource/elasticsearch/specs/index_pattern.jest.ts

@@ -1,38 +1,37 @@
 ///<amd-dependency path="test/specs/helpers" name="helpers" />
 
-import { describe, it, expect } from 'test/lib/common';
 import moment from 'moment';
 import { IndexPattern } from '../index_pattern';
 
-describe('IndexPattern', function() {
-  describe('when getting index for today', function() {
-    it('should return correct index name', function() {
+describe('IndexPattern', () => {
+  describe('when getting index for today', () => {
+    test('should return correct index name', () => {
       var pattern = new IndexPattern('[asd-]YYYY.MM.DD', 'Daily');
       var expected = 'asd-' + moment.utc().format('YYYY.MM.DD');
 
-      expect(pattern.getIndexForToday()).to.be(expected);
+      expect(pattern.getIndexForToday()).toBe(expected);
     });
   });
 
-  describe('when getting index list for time range', function() {
-    describe('no interval', function() {
-      it('should return correct index', function() {
+  describe('when getting index list for time range', () => {
+    describe('no interval', () => {
+      test('should return correct index', () => {
         var pattern = new IndexPattern('my-metrics', null);
         var from = new Date(2015, 4, 30, 1, 2, 3);
         var to = new Date(2015, 5, 1, 12, 5, 6);
-        expect(pattern.getIndexList(from, to)).to.eql('my-metrics');
+        expect(pattern.getIndexList(from, to)).toEqual('my-metrics');
       });
     });
 
-    describe('daily', function() {
-      it('should return correct index list', function() {
+    describe('daily', () => {
+      test('should return correct index list', () => {
         var pattern = new IndexPattern('[asd-]YYYY.MM.DD', 'Daily');
         var from = new Date(1432940523000);
         var to = new Date(1433153106000);
 
         var expected = ['asd-2015.05.29', 'asd-2015.05.30', 'asd-2015.05.31', 'asd-2015.06.01'];
 
-        expect(pattern.getIndexList(from, to)).to.eql(expected);
+        expect(pattern.getIndexList(from, to)).toEqual(expected);
       });
     });
   });

+ 57 - 58
public/app/plugins/datasource/elasticsearch/specs/query_builder_specs.ts → public/app/plugins/datasource/elasticsearch/specs/query_builder.jest.ts

@@ -1,25 +1,24 @@
-import { describe, beforeEach, it, expect } from 'test/lib/common';
 import { ElasticQueryBuilder } from '../query_builder';
 
-describe('ElasticQueryBuilder', function() {
+describe('ElasticQueryBuilder', () => {
   var builder;
 
-  beforeEach(function() {
+  beforeEach(() => {
     builder = new ElasticQueryBuilder({ timeField: '@timestamp' });
   });
 
-  it('with defaults', function() {
+  it('with defaults', () => {
     var query = builder.build({
       metrics: [{ type: 'Count', id: '0' }],
       timeField: '@timestamp',
       bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '1' }],
     });
 
-    expect(query.query.bool.filter[0].range['@timestamp'].gte).to.be('$timeFrom');
-    expect(query.aggs['1'].date_histogram.extended_bounds.min).to.be('$timeFrom');
+    expect(query.query.bool.filter[0].range['@timestamp'].gte).toBe('$timeFrom');
+    expect(query.aggs['1'].date_histogram.extended_bounds.min).toBe('$timeFrom');
   });
 
-  it('with defaults on es5.x', function() {
+  it('with defaults on es5.x', () => {
     var builder_5x = new ElasticQueryBuilder({
       timeField: '@timestamp',
       esVersion: 5,
@@ -31,11 +30,11 @@ describe('ElasticQueryBuilder', function() {
       bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '1' }],
     });
 
-    expect(query.query.bool.filter[0].range['@timestamp'].gte).to.be('$timeFrom');
-    expect(query.aggs['1'].date_histogram.extended_bounds.min).to.be('$timeFrom');
+    expect(query.query.bool.filter[0].range['@timestamp'].gte).toBe('$timeFrom');
+    expect(query.aggs['1'].date_histogram.extended_bounds.min).toBe('$timeFrom');
   });
 
-  it('with multiple bucket aggs', function() {
+  it('with multiple bucket aggs', () => {
     var query = builder.build({
       metrics: [{ type: 'count', id: '1' }],
       timeField: '@timestamp',
@@ -45,11 +44,11 @@ describe('ElasticQueryBuilder', function() {
       ],
     });
 
-    expect(query.aggs['2'].terms.field).to.be('@host');
-    expect(query.aggs['2'].aggs['3'].date_histogram.field).to.be('@timestamp');
+    expect(query.aggs['2'].terms.field).toBe('@host');
+    expect(query.aggs['2'].aggs['3'].date_histogram.field).toBe('@timestamp');
   });
 
-  it('with select field', function() {
+  it('with select field', () => {
     var query = builder.build(
       {
         metrics: [{ type: 'avg', field: '@value', id: '1' }],
@@ -60,10 +59,10 @@ describe('ElasticQueryBuilder', function() {
     );
 
     var aggs = query.aggs['2'].aggs;
-    expect(aggs['1'].avg.field).to.be('@value');
+    expect(aggs['1'].avg.field).toBe('@value');
   });
 
-  it('with term agg and order by metric agg', function() {
+  it('with term agg and order by metric agg', () => {
     var query = builder.build(
       {
         metrics: [{ type: 'count', id: '1' }, { type: 'avg', field: '@value', id: '5' }],
@@ -84,11 +83,11 @@ describe('ElasticQueryBuilder', function() {
     var firstLevel = query.aggs['2'];
     var secondLevel = firstLevel.aggs['3'];
 
-    expect(firstLevel.aggs['5'].avg.field).to.be('@value');
-    expect(secondLevel.aggs['5'].avg.field).to.be('@value');
+    expect(firstLevel.aggs['5'].avg.field).toBe('@value');
+    expect(secondLevel.aggs['5'].avg.field).toBe('@value');
   });
 
-  it('with metric percentiles', function() {
+  it('with metric percentiles', () => {
     var query = builder.build(
       {
         metrics: [
@@ -109,11 +108,11 @@ describe('ElasticQueryBuilder', function() {
 
     var firstLevel = query.aggs['3'];
 
-    expect(firstLevel.aggs['1'].percentiles.field).to.be('@load_time');
-    expect(firstLevel.aggs['1'].percentiles.percents).to.eql([1, 2, 3, 4]);
+    expect(firstLevel.aggs['1'].percentiles.field).toBe('@load_time');
+    expect(firstLevel.aggs['1'].percentiles.percents).toEqual([1, 2, 3, 4]);
   });
 
-  it('with filters aggs', function() {
+  it('with filters aggs', () => {
     var query = builder.build({
       metrics: [{ type: 'count', id: '1' }],
       timeField: '@timestamp',
@@ -129,12 +128,12 @@ describe('ElasticQueryBuilder', function() {
       ],
     });
 
-    expect(query.aggs['2'].filters.filters['@metric:cpu'].query_string.query).to.be('@metric:cpu');
-    expect(query.aggs['2'].filters.filters['@metric:logins.count'].query_string.query).to.be('@metric:logins.count');
-    expect(query.aggs['2'].aggs['4'].date_histogram.field).to.be('@timestamp');
+    expect(query.aggs['2'].filters.filters['@metric:cpu'].query_string.query).toBe('@metric:cpu');
+    expect(query.aggs['2'].filters.filters['@metric:logins.count'].query_string.query).toBe('@metric:logins.count');
+    expect(query.aggs['2'].aggs['4'].date_histogram.field).toBe('@timestamp');
   });
 
-  it('with filters aggs on es5.x', function() {
+  it('with filters aggs on es5.x', () => {
     var builder_5x = new ElasticQueryBuilder({
       timeField: '@timestamp',
       esVersion: 5,
@@ -154,31 +153,31 @@ describe('ElasticQueryBuilder', function() {
       ],
     });
 
-    expect(query.aggs['2'].filters.filters['@metric:cpu'].query_string.query).to.be('@metric:cpu');
-    expect(query.aggs['2'].filters.filters['@metric:logins.count'].query_string.query).to.be('@metric:logins.count');
-    expect(query.aggs['2'].aggs['4'].date_histogram.field).to.be('@timestamp');
+    expect(query.aggs['2'].filters.filters['@metric:cpu'].query_string.query).toBe('@metric:cpu');
+    expect(query.aggs['2'].filters.filters['@metric:logins.count'].query_string.query).toBe('@metric:logins.count');
+    expect(query.aggs['2'].aggs['4'].date_histogram.field).toBe('@timestamp');
   });
 
-  it('with raw_document metric', function() {
+  it('with raw_document metric', () => {
     var query = builder.build({
       metrics: [{ type: 'raw_document', id: '1', settings: {} }],
       timeField: '@timestamp',
       bucketAggs: [],
     });
 
-    expect(query.size).to.be(500);
+    expect(query.size).toBe(500);
   });
-  it('with raw_document metric size set', function() {
+  it('with raw_document metric size set', () => {
     var query = builder.build({
       metrics: [{ type: 'raw_document', id: '1', settings: { size: 1337 } }],
       timeField: '@timestamp',
       bucketAggs: [],
     });
 
-    expect(query.size).to.be(1337);
+    expect(query.size).toBe(1337);
   });
 
-  it('with moving average', function() {
+  it('with moving average', () => {
     var query = builder.build({
       metrics: [
         {
@@ -198,12 +197,12 @@ describe('ElasticQueryBuilder', function() {
 
     var firstLevel = query.aggs['3'];
 
-    expect(firstLevel.aggs['2']).not.to.be(undefined);
-    expect(firstLevel.aggs['2'].moving_avg).not.to.be(undefined);
-    expect(firstLevel.aggs['2'].moving_avg.buckets_path).to.be('3');
+    expect(firstLevel.aggs['2']).not.toBe(undefined);
+    expect(firstLevel.aggs['2'].moving_avg).not.toBe(undefined);
+    expect(firstLevel.aggs['2'].moving_avg.buckets_path).toBe('3');
   });
 
-  it('with broken moving average', function() {
+  it('with broken moving average', () => {
     var query = builder.build({
       metrics: [
         {
@@ -227,13 +226,13 @@ describe('ElasticQueryBuilder', function() {
 
     var firstLevel = query.aggs['3'];
 
-    expect(firstLevel.aggs['2']).not.to.be(undefined);
-    expect(firstLevel.aggs['2'].moving_avg).not.to.be(undefined);
-    expect(firstLevel.aggs['2'].moving_avg.buckets_path).to.be('3');
-    expect(firstLevel.aggs['4']).to.be(undefined);
+    expect(firstLevel.aggs['2']).not.toBe(undefined);
+    expect(firstLevel.aggs['2'].moving_avg).not.toBe(undefined);
+    expect(firstLevel.aggs['2'].moving_avg.buckets_path).toBe('3');
+    expect(firstLevel.aggs['4']).toBe(undefined);
   });
 
-  it('with derivative', function() {
+  it('with derivative', () => {
     var query = builder.build({
       metrics: [
         {
@@ -252,12 +251,12 @@ describe('ElasticQueryBuilder', function() {
 
     var firstLevel = query.aggs['3'];
 
-    expect(firstLevel.aggs['2']).not.to.be(undefined);
-    expect(firstLevel.aggs['2'].derivative).not.to.be(undefined);
-    expect(firstLevel.aggs['2'].derivative.buckets_path).to.be('3');
+    expect(firstLevel.aggs['2']).not.toBe(undefined);
+    expect(firstLevel.aggs['2'].derivative).not.toBe(undefined);
+    expect(firstLevel.aggs['2'].derivative.buckets_path).toBe('3');
   });
 
-  it('with histogram', function() {
+  it('with histogram', () => {
     var query = builder.build({
       metrics: [{ id: '1', type: 'count' }],
       bucketAggs: [
@@ -271,13 +270,13 @@ describe('ElasticQueryBuilder', function() {
     });
 
     var firstLevel = query.aggs['3'];
-    expect(firstLevel.histogram.field).to.be('bytes');
-    expect(firstLevel.histogram.interval).to.be(10);
-    expect(firstLevel.histogram.min_doc_count).to.be(2);
-    expect(firstLevel.histogram.missing).to.be(5);
+    expect(firstLevel.histogram.field).toBe('bytes');
+    expect(firstLevel.histogram.interval).toBe(10);
+    expect(firstLevel.histogram.min_doc_count).toBe(2);
+    expect(firstLevel.histogram.missing).toBe(5);
   });
 
-  it('with adhoc filters', function() {
+  it('with adhoc filters', () => {
     var query = builder.build(
       {
         metrics: [{ type: 'Count', id: '0' }],
@@ -295,12 +294,12 @@ describe('ElasticQueryBuilder', function() {
       ]
     );
 
-    expect(query.query.bool.must[0].match_phrase['key1'].query).to.be('value1');
-    expect(query.query.bool.must[1].match_phrase['key2'].query).to.be('value2');
-    expect(query.query.bool.must_not[0].match_phrase['key2'].query).to.be('value2');
-    expect(query.query.bool.filter[2].range['key3'].lt).to.be('value3');
-    expect(query.query.bool.filter[3].range['key4'].gt).to.be('value4');
-    expect(query.query.bool.filter[4].regexp['key5']).to.be('value5');
-    expect(query.query.bool.filter[5].bool.must_not.regexp['key6']).to.be('value6');
+    expect(query.query.bool.must[0].match_phrase['key1'].query).toBe('value1');
+    expect(query.query.bool.must[1].match_phrase['key2'].query).toBe('value2');
+    expect(query.query.bool.must_not[0].match_phrase['key2'].query).toBe('value2');
+    expect(query.query.bool.filter[2].range['key3'].lt).toBe('value3');
+    expect(query.query.bool.filter[3].range['key4'].gt).toBe('value4');
+    expect(query.query.bool.filter[4].regexp['key5']).toBe('value5');
+    expect(query.query.bool.filter[5].bool.must_not.regexp['key6']).toBe('value6');
   });
 });

+ 93 - 0
public/app/plugins/datasource/elasticsearch/specs/query_def.jest.ts

@@ -0,0 +1,93 @@
+import * as queryDef from '../query_def';
+
+describe('ElasticQueryDef', () => {
+  describe('getPipelineAggOptions', () => {
+    describe('with zero targets', () => {
+      var response = queryDef.getPipelineAggOptions([]);
+
+      test('should return zero', () => {
+        expect(response.length).toBe(0);
+      });
+    });
+
+    describe('with count and sum targets', () => {
+      var targets = {
+        metrics: [{ type: 'count', field: '@value' }, { type: 'sum', field: '@value' }],
+      };
+
+      var response = queryDef.getPipelineAggOptions(targets);
+
+      test('should return zero', () => {
+        expect(response.length).toBe(2);
+      });
+    });
+
+    describe('with count and moving average targets', () => {
+      var targets = {
+        metrics: [{ type: 'count', field: '@value' }, { type: 'moving_avg', field: '@value' }],
+      };
+
+      var response = queryDef.getPipelineAggOptions(targets);
+
+      test('should return one', () => {
+        expect(response.length).toBe(1);
+      });
+    });
+
+    describe('with derivatives targets', () => {
+      var targets = {
+        metrics: [{ type: 'derivative', field: '@value' }],
+      };
+
+      var response = queryDef.getPipelineAggOptions(targets);
+
+      test('should return zero', () => {
+        expect(response.length).toBe(0);
+      });
+    });
+  });
+
+  describe('isPipelineMetric', () => {
+    describe('moving_avg', () => {
+      var result = queryDef.isPipelineAgg('moving_avg');
+
+      test('is pipe line metric', () => {
+        expect(result).toBe(true);
+      });
+    });
+
+    describe('count', () => {
+      var result = queryDef.isPipelineAgg('count');
+
+      test('is not pipe line metric', () => {
+        expect(result).toBe(false);
+      });
+    });
+  });
+
+  describe('pipeline aggs depending on esverison', () => {
+    describe('using esversion undefined', () => {
+      test('should not get pipeline aggs', () => {
+        expect(queryDef.getMetricAggTypes(undefined).length).toBe(9);
+      });
+    });
+
+    describe('using esversion 1', () => {
+      test('should not get pipeline aggs', () => {
+        expect(queryDef.getMetricAggTypes(1).length).toBe(9);
+      });
+    });
+
+    describe('using esversion 2', () => {
+      test('should get pipeline aggs', () => {
+        expect(queryDef.getMetricAggTypes(2).length).toBe(11);
+      });
+    });
+
+    describe('using esversion 5', () => {
+      test('should get pipeline aggs', () => {
+        expect(queryDef.getMetricAggTypes(5).length).toBe(11);
+      });
+    });
+  });
+});

+ 0 - 95
public/app/plugins/datasource/elasticsearch/specs/query_def_specs.ts

@@ -1,95 +0,0 @@
-import { describe, it, expect } from 'test/lib/common';
-
-import * as queryDef from '../query_def';
-
-describe('ElasticQueryDef', function() {
-  describe('getPipelineAggOptions', function() {
-    describe('with zero targets', function() {
-      var response = queryDef.getPipelineAggOptions([]);
-
-      it('should return zero', function() {
-        expect(response.length).to.be(0);
-      });
-    });
-
-    describe('with count and sum targets', function() {
-      var targets = {
-        metrics: [{ type: 'count', field: '@value' }, { type: 'sum', field: '@value' }],
-      };
-
-      var response = queryDef.getPipelineAggOptions(targets);
-
-      it('should return zero', function() {
-        expect(response.length).to.be(2);
-      });
-    });
-
-    describe('with count and moving average targets', function() {
-      var targets = {
-        metrics: [{ type: 'count', field: '@value' }, { type: 'moving_avg', field: '@value' }],
-      };
-
-      var response = queryDef.getPipelineAggOptions(targets);
-
-      it('should return one', function() {
-        expect(response.length).to.be(1);
-      });
-    });
-
-    describe('with derivatives targets', function() {
-      var targets = {
-        metrics: [{ type: 'derivative', field: '@value' }],
-      };
-
-      var response = queryDef.getPipelineAggOptions(targets);
-
-      it('should return zero', function() {
-        expect(response.length).to.be(0);
-      });
-    });
-  });
-
-  describe('isPipelineMetric', function() {
-    describe('moving_avg', function() {
-      var result = queryDef.isPipelineAgg('moving_avg');
-
-      it('is pipe line metric', function() {
-        expect(result).to.be(true);
-      });
-    });
-
-    describe('count', function() {
-      var result = queryDef.isPipelineAgg('count');
-
-      it('is not pipe line metric', function() {
-        expect(result).to.be(false);
-      });
-    });
-  });
-
-  describe('pipeline aggs depending on esverison', function() {
-    describe('using esversion undefined', function() {
-      it('should not get pipeline aggs', function() {
-        expect(queryDef.getMetricAggTypes(undefined).length).to.be(9);
-      });
-    });
-
-    describe('using esversion 1', function() {
-      it('should not get pipeline aggs', function() {
-        expect(queryDef.getMetricAggTypes(1).length).to.be(9);
-      });
-    });
-
-    describe('using esversion 2', function() {
-      it('should get pipeline aggs', function() {
-        expect(queryDef.getMetricAggTypes(2).length).to.be(11);
-      });
-    });
-
-    describe('using esversion 5', function() {
-      it('should get pipeline aggs', function() {
-        expect(queryDef.getMetricAggTypes(5).length).to.be(11);
-      });
-    });
-  });
-});

+ 0 - 26
public/app/plugins/datasource/influxdb-ifql/README.md

@@ -1,26 +0,0 @@
-# InfluxDB (IFQL) Datasource [BETA] -  Native Plugin
-
-Grafana ships with **built in** support for InfluxDB (>= 1.4.1).
-
-Use this datasource if you want to use IFQL to query your InfluxDB.
-Feel free to run this datasource side-by-side with the non-IFQL datasource.
-If you point both datasources to the same InfluxDB instance, you can switch query mode by switching the datasources.
-
-Read more about IFQL here:
-
-[https://github.com/influxdata/ifql](https://github.com/influxdata/ifql)
-
-Read more about InfluxDB here:
-
-[http://docs.grafana.org/datasources/influxdb/](http://docs.grafana.org/datasources/influxdb/)
-
-## Roadmap
-
-- Sync Grafana time ranges with `range()`
-- Template variable expansion
-- Syntax highlighting
-- Tab completion (functions, values)
-- Result helpers (result counts, table previews)
-- Annotations support
-- Alerting integration
-- Explore UI integration

+ 0 - 233
public/app/plugins/datasource/influxdb-ifql/datasource.ts

@@ -1,233 +0,0 @@
-import _ from 'lodash';
-
-import * as dateMath from 'app/core/utils/datemath';
-
-import { getTableModelFromResult, getTimeSeriesFromResult, parseResults } from './response_parser';
-
-function serializeParams(params) {
-  if (!params) {
-    return '';
-  }
-
-  return _.reduce(
-    params,
-    (memo, value, key) => {
-      if (value === null || value === undefined) {
-        return memo;
-      }
-      memo.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
-      return memo;
-    },
-    []
-  ).join('&');
-}
-
-const MAX_SERIES = 20;
-export default class InfluxDatasource {
-  type: string;
-  url: string;
-  username: string;
-  password: string;
-  name: string;
-  orgName: string;
-  database: any;
-  basicAuth: any;
-  withCredentials: any;
-  interval: any;
-  supportAnnotations: boolean;
-  supportMetrics: boolean;
-
-  /** @ngInject */
-  constructor(instanceSettings, private backendSrv, private templateSrv) {
-    this.type = 'influxdb-ifql';
-    this.url = instanceSettings.url.trim();
-
-    this.username = instanceSettings.username;
-    this.password = instanceSettings.password;
-    this.name = instanceSettings.name;
-    this.orgName = instanceSettings.orgName || 'defaultorgname';
-    this.database = instanceSettings.database;
-    this.basicAuth = instanceSettings.basicAuth;
-    this.withCredentials = instanceSettings.withCredentials;
-    this.interval = (instanceSettings.jsonData || {}).timeInterval;
-    this.supportAnnotations = true;
-    this.supportMetrics = true;
-  }
-
-  prepareQueries(options) {
-    const targets = _.cloneDeep(options.targets);
-    const timeFilter = this.getTimeFilter(options);
-    options.scopedVars.range = { value: timeFilter };
-
-    // Filter empty queries and replace grafana variables
-    const queryTargets = targets.filter(t => t.query).map(t => {
-      const interpolated = this.templateSrv.replace(t.query, options.scopedVars);
-      return {
-        ...t,
-        query: interpolated,
-      };
-    });
-
-    return queryTargets;
-  }
-
-  query(options) {
-    const queryTargets = this.prepareQueries(options);
-    if (queryTargets.length === 0) {
-      return Promise.resolve({ data: [] });
-    }
-
-    const queries = queryTargets.map(target => {
-      const { query, resultFormat } = target;
-
-      if (resultFormat === 'table') {
-        return (
-          this._seriesQuery(query, options)
-            .then(response => parseResults(response.data))
-            // Keep only first result from each request
-            .then(results => results[0])
-            .then(getTableModelFromResult)
-        );
-      } else {
-        return this._seriesQuery(query, options)
-          .then(response => parseResults(response.data))
-          .then(results => results.map(getTimeSeriesFromResult));
-      }
-    });
-
-    return Promise.all(queries).then((series: any) => {
-      let seriesList = _.flattenDeep(series).slice(0, MAX_SERIES);
-      return { data: seriesList };
-    });
-  }
-
-  annotationQuery(options) {
-    if (!options.annotation.query) {
-      return Promise.reject({
-        message: 'Query missing in annotation definition',
-      });
-    }
-
-    var timeFilter = this.getTimeFilter({ rangeRaw: options.rangeRaw });
-    var query = options.annotation.query.replace('$timeFilter', timeFilter);
-    query = this.templateSrv.replace(query, null, 'regex');
-
-    return {};
-  }
-
-  metricFindQuery(query: string, options?: any) {
-    // TODO not implemented
-    var interpolated = this.templateSrv.replace(query, null, 'regex');
-
-    return this._seriesQuery(interpolated, options).then(_.curry(parseResults)(query));
-  }
-
-  _seriesQuery(query: string, options?: any) {
-    if (!query) {
-      return Promise.resolve({ data: '' });
-    }
-    return this._influxRequest('POST', '/v1/query', { q: query }, options);
-  }
-
-  testDatasource() {
-    const query = `from(db:"${this.database}") |> last()`;
-
-    return this._influxRequest('POST', '/v1/query', { q: query })
-      .then(res => {
-        if (res && res.trim()) {
-          return { status: 'success', message: 'Data source connected and database found.' };
-        }
-        return {
-          status: 'error',
-          message:
-            'Data source connected, but has no data. Verify the "Database" field and make sure the database has data.',
-        };
-      })
-      .catch(err => {
-        return { status: 'error', message: err.message };
-      });
-  }
-
-  _influxRequest(method: string, url: string, data: any, options?: any) {
-    let params: any = {
-      orgName: this.orgName,
-    };
-
-    if (this.username) {
-      params.u = this.username;
-      params.p = this.password;
-    }
-
-    // data sent as GET param
-    _.extend(params, data);
-    data = null;
-
-    let req: any = {
-      method: method,
-      url: this.url + url,
-      params: params,
-      data: data,
-      precision: 'ms',
-      inspect: { type: this.type },
-      paramSerializer: serializeParams,
-    };
-
-    req.headers = req.headers || {};
-    if (this.basicAuth || this.withCredentials) {
-      req.withCredentials = true;
-    }
-    if (this.basicAuth) {
-      req.headers.Authorization = this.basicAuth;
-    }
-
-    return this.backendSrv.datasourceRequest(req).then(
-      result => {
-        return result;
-      },
-      function(err) {
-        if (err.status !== 0 || err.status >= 300) {
-          if (err.data && err.data.error) {
-            throw {
-              message: 'InfluxDB Error: ' + err.data.error,
-              data: err.data,
-              config: err.config,
-            };
-          } else {
-            throw {
-              message: 'Network Error: ' + err.statusText + '(' + err.status + ')',
-              data: err.data,
-              config: err.config,
-            };
-          }
-        }
-      }
-    );
-  }
-
-  getTimeFilter(options) {
-    const from = this.getInfluxTime(options.rangeRaw.from, false);
-    const to = this.getInfluxTime(options.rangeRaw.to, true);
-    if (to === 'now') {
-      return `start: ${from}`;
-    }
-    return `start: ${from}, stop: ${to}`;
-  }
-
-  getInfluxTime(date, roundUp) {
-    if (_.isString(date)) {
-      if (date === 'now') {
-        return date;
-      }
-
-      const parts = /^now\s*-\s*(\d+)([d|h|m|s])$/.exec(date);
-      if (parts) {
-        const amount = parseInt(parts[1]);
-        const unit = parts[2];
-        return '-' + amount + unit;
-      }
-      date = dateMath.parse(date, roundUp);
-    }
-
-    return date.toISOString();
-  }
-}

+ 0 - 26
public/app/plugins/datasource/influxdb-ifql/img/influxdb_logo.svg

@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-	 viewBox="0 0 78.8 79.9" style="enable-background:new 0 0 78.8 79.9;" xml:space="preserve">
-<style type="text/css">
-	.st0{fill:url(#symbol_1_);}
-</style>
-<g id="influxdb_logo">
-
-		<linearGradient id="symbol_1_" gradientUnits="userSpaceOnUse" x1="41.5273" y1="41.85" x2="319.2919" y2="41.85" gradientTransform="matrix(1 0 0 -1 0 81.8)">
-		<stop  offset="0" style="stop-color:#4591ED"/>
-		<stop  offset="1" style="stop-color:#00C9FF"/>
-	</linearGradient>
-	<path id="symbol_2_" class="st0" d="M78.7,48.2L71.1,15c-0.4-1.8-2.1-3.6-3.9-4.1L32.3,0.2c-0.5-0.1-1-0.2-1.5-0.2
-		c-1.5,0-3.1,0.6-4,1.5l-25,23.2c-1.3,1.2-2.1,3.6-1.7,5.4l8.1,35.5c0.4,1.8,2.1,3.6,3.9,4.1l32.6,10c0.5,0.1,1,0.2,1.5,0.2
-		c1.5,0,3.1-0.6,4-1.5l26.7-24.8C78.4,52.4,79.1,50,78.7,48.2z M35.9,8l23.9,7.3c0.9,0.3,0.9,0.7,0,0.9l-12.6,2.9
-		c-1,0.2-2.3-0.2-2.9-0.9l-8.8-9.5C34.8,8.1,35,7.8,35.9,8z M50.8,50.9c0.2,1-0.4,1.5-1.3,1.2l-25.8-7.9c-0.9-0.3-1.1-1.1-0.4-1.7
-		l19.8-18.4c0.7-0.7,1.5-0.4,1.7,0.5L50.8,50.9z M8.3,27.5L29.3,8c0.7-0.7,1.8-0.6,2.5,0.1l10.5,11.3c0.7,0.7,0.6,1.8-0.1,2.5
-		l-21,19.5c-0.7,0.7-1.8,0.6-2.5-0.1L8.2,30C7.6,29.3,7.6,28.2,8.3,27.5z M13.4,58.5L7.8,34.2c-0.2-1,0.1-1.1,0.8-0.4l8.8,9.5
-		c0.7,0.7,1,2.1,0.7,3l-3.8,12.3C14.1,59.4,13.6,59.4,13.4,58.5z M44.1,72.6l-27.3-8.4c-0.9-0.3-1.5-1.3-1.2-2.2l4.5-14.8
-		c0.3-0.9,1.3-1.5,2.2-1.2l27.3,8.4c0.9,0.3,1.5,1.3,1.2,2.2l-4.5,14.8C46,72.4,45,72.9,44.1,72.6z M68.4,52.7l-18.3,17
-		c-0.7,0.7-1.1,0.4-0.8-0.5l3.8-12.3c0.3-0.9,1.3-1.9,2.3-2.1L68,51.9C68.9,51.7,69.1,52.1,68.4,52.7z M70.4,49.1l-15.1,3.4
-		c-1,0.2-1.9-0.4-2.1-1.3l-6.4-27.9c-0.2-1,0.4-1.9,1.3-2.1l15.1-3.4c1-0.2,1.9,0.4,2.1,1.3L71.7,47C71.9,47.9,71.3,48.9,70.4,49.1z
-		"/>
-</g>
-</svg>

+ 0 - 17
public/app/plugins/datasource/influxdb-ifql/module.ts

@@ -1,17 +0,0 @@
-import InfluxDatasource from './datasource';
-import { InfluxIfqlQueryCtrl } from './query_ctrl';
-
-class InfluxConfigCtrl {
-  static templateUrl = 'partials/config.html';
-}
-
-class InfluxAnnotationsQueryCtrl {
-  static templateUrl = 'partials/annotations.editor.html';
-}
-
-export {
-  InfluxDatasource as Datasource,
-  InfluxIfqlQueryCtrl as QueryCtrl,
-  InfluxConfigCtrl as ConfigCtrl,
-  InfluxAnnotationsQueryCtrl as AnnotationsQueryCtrl,
-};

+ 0 - 24
public/app/plugins/datasource/influxdb-ifql/partials/annotations.editor.html

@@ -1,24 +0,0 @@
-
-<div class="gf-form-group">
-	<div class="gf-form">
-		<input type="text" class="gf-form-input" ng-model='ctrl.annotation.query' placeholder="select text from events where $timeFilter limit 1000"></input>
-	</div>
-</div>
-
-<h5 class="section-heading">Field mappings <tip>If your influxdb query returns more than one field you need to specify the column names below. An annotation event is composed of a title, tags, and an additional text field.</tip></h5>
-<div class="gf-form-group">
-	<div class="gf-form-inline">
-		<div class="gf-form">
-			<span class="gf-form-label width-4">Text</span>
-			<input type="text" class="gf-form-input max-width-10" ng-model='ctrl.annotation.textColumn' placeholder=""></input>
-		</div>
-		<div class="gf-form">
-			<span class="gf-form-label width-4">Tags</span>
-			<input type="text" class="gf-form-input max-width-10" ng-model='ctrl.annotation.tagsColumn' placeholder=""></input>
-		</div>
-		<div class="gf-form" ng-show="ctrl.annotation.titleColumn">
-			<span class="gf-form-label width-4">Title <em class="muted">(deprecated)</em></span>
-			<input type="text" class="gf-form-input max-width-10" ng-model='ctrl.annotation.titleColumn' placeholder=""></input>
-		</div>
-	</div>
-</div>

+ 0 - 24
public/app/plugins/datasource/influxdb-ifql/partials/config.html

@@ -1,24 +0,0 @@
-<datasource-http-settings current="ctrl.current" no-direct-access="true" suggest-url="http://localhost:8093">
-</datasource-http-settings>
-
-<h3 class="page-heading">InfluxDB Details</h3>
-
-<div class="gf-form-group">
-	<div class="gf-form-inline">
-		<div class="gf-form max-width-30">
-			<span class="gf-form-label width-7">Default Database</span>
-			<input type="text" class="gf-form-input" ng-model='ctrl.current.database' placeholder="" required></input>
-		</div>
-	</div>
-
-	<div class="gf-form-inline">
-		<div class="gf-form max-width-15">
-			<span class="gf-form-label width-7">User</span>
-			<input type="text" class="gf-form-input" ng-model='ctrl.current.user' placeholder=""></input>
-		</div>
-		<div class="gf-form max-width-15">
-			<span class="gf-form-label width-7">Password</span>
-			<input type="password" class="gf-form-input" ng-model='ctrl.current.password' placeholder=""></input>
-		</div>
-	</div>
-</div>

+ 0 - 24
public/app/plugins/datasource/influxdb-ifql/partials/query.editor.html

@@ -1,24 +0,0 @@
-<query-editor-row query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="true">
-
-  <div class="gf-form">
-    <textarea rows="3" class="gf-form-input" ng-model="ctrl.target.query" spellcheck="false" placeholder="IFQL Query" ng-model-onblur
-      ng-change="ctrl.refresh()"></textarea>
-  </div>
-  <div class="gf-form-inline">
-    <div class="gf-form">
-      <label class="gf-form-label query-keyword">FORMAT AS</label>
-      <div class="gf-form-select-wrapper">
-        <select class="gf-form-input gf-size-auto" ng-model="ctrl.target.resultFormat" ng-options="f.value as f.text for f in ctrl.resultFormats"
-          ng-change="ctrl.refresh()"></select>
-      </div>
-    </div>
-    <div class="gf-form max-width-25" ng-hide="ctrl.target.resultFormat === 'table'">
-      <label class="gf-form-label query-keyword">ALIAS BY</label>
-      <input type="text" class="gf-form-input" ng-model="ctrl.target.alias" spellcheck='false' placeholder="Naming pattern" ng-blur="ctrl.refresh()">
-    </div>
-    <div class="gf-form gf-form--grow">
-      <div class="gf-form-label gf-form-label--grow"></div>
-    </div>
-  </div>
-
-</query-editor-row>

+ 0 - 24
public/app/plugins/datasource/influxdb-ifql/plugin.json

@@ -1,24 +0,0 @@
-{
-  "type": "datasource",
-  "name": "InfluxDB (IFQL) [BETA]",
-  "id": "influxdb-ifql",
-  "defaultMatchFormat": "regex values",
-  "metrics": true,
-  "annotations": false,
-  "alerting": false,
-  "queryOptions": {
-    "minInterval": true
-  },
-  "info": {
-    "description": "InfluxDB Data Source for IFQL Queries for Grafana",
-    "author": {
-      "name": "Grafana Project",
-      "url": "https://grafana.com"
-    },
-    "logos": {
-      "small": "img/influxdb_logo.svg",
-      "large": "img/influxdb_logo.svg"
-    },
-    "version": "5.1.0"
-  }
-}

+ 0 - 27
public/app/plugins/datasource/influxdb-ifql/query_ctrl.ts

@@ -1,27 +0,0 @@
-import { QueryCtrl } from 'app/plugins/sdk';
-
-function makeDefaultQuery(database) {
-  return `from(db: "${database}")
-  |> range($range)
-  |> limit(n:1000)
-`;
-}
-export class InfluxIfqlQueryCtrl extends QueryCtrl {
-  static templateUrl = 'partials/query.editor.html';
-
-  resultFormats: any[];
-
-  /** @ngInject **/
-  constructor($scope, $injector) {
-    super($scope, $injector);
-
-    if (this.target.query === undefined) {
-      this.target.query = makeDefaultQuery(this.datasource.database);
-    }
-    this.resultFormats = [{ text: 'Time series', value: 'time_series' }, { text: 'Table', value: 'table' }];
-  }
-
-  getCollapsedText() {
-    return this.target.query;
-  }
-}

+ 0 - 88
public/app/plugins/datasource/influxdb-ifql/response_parser.ts

@@ -1,88 +0,0 @@
-import Papa from 'papaparse';
-import groupBy from 'lodash/groupBy';
-
-import TableModel from 'app/core/table_model';
-
-const filterColumnKeys = key => key && key[0] !== '_' && key !== 'result' && key !== 'table';
-
-const IGNORE_FIELDS_FOR_NAME = ['result', '', 'table'];
-export const getNameFromRecord = record => {
-  // Measurement and field
-  const metric = [record._measurement, record._field];
-
-  // Add tags
-  const tags = Object.keys(record)
-    .filter(key => key[0] !== '_')
-    .filter(key => IGNORE_FIELDS_FOR_NAME.indexOf(key) === -1)
-    .map(key => `${key}=${record[key]}`);
-
-  return [...metric, ...tags].join(' ');
-};
-
-const parseCSV = (input: string) =>
-  Papa.parse(input, {
-    header: true,
-    comments: '#',
-  }).data;
-
-export const parseValue = (input: string) => {
-  const value = parseFloat(input);
-  return isNaN(value) ? null : value;
-};
-
-export const parseTime = (input: string) => Date.parse(input);
-
-export function parseResults(response: string): any[] {
-  return response.trim().split(/\n\s*\s/);
-}
-
-export function getTableModelFromResult(result: string) {
-  const data = parseCSV(result);
-
-  const table = new TableModel();
-  if (data.length > 0) {
-    // First columns are fixed
-    const firstColumns = [
-      { text: 'Time', id: '_time' },
-      { text: 'Measurement', id: '_measurement' },
-      { text: 'Field', id: '_field' },
-    ];
-
-    // Dynamically add columns for tags
-    const firstRecord = data[0];
-    const tags = Object.keys(firstRecord)
-      .filter(filterColumnKeys)
-      .map(key => ({ id: key, text: key }));
-
-    const valueColumn = { id: '_value', text: 'Value' };
-    const columns = [...firstColumns, ...tags, valueColumn];
-    columns.forEach(c => table.addColumn(c));
-
-    // Add rows
-    data.forEach(record => {
-      const row = columns.map(c => record[c.id]);
-      table.addRow(row);
-    });
-  }
-
-  return table;
-}
-
-export function getTimeSeriesFromResult(result: string) {
-  const data = parseCSV(result);
-  if (data.length === 0) {
-    return [];
-  }
-
-  // Group results by table ID (assume one table per timeseries for now)
-  const tables = groupBy(data, 'table');
-  const seriesList = Object.keys(tables)
-    .map(id => tables[id])
-    .map(series => {
-      const datapoints = series.map(record => [parseValue(record._value), parseTime(record._time)]);
-      const alias = getNameFromRecord(series[0]);
-      return { datapoints, target: alias };
-    });
-
-  return seriesList;
-}

+ 0 - 53
public/app/plugins/datasource/influxdb-ifql/specs/datasource.jest.ts

@@ -1,53 +0,0 @@
-import moment from 'moment';
-
-import { TemplateSrv } from 'app/features/templating/template_srv';
-
-import Datasource from '../datasource';
-
-describe('InfluxDB (IFQL)', () => {
-  const templateSrv = new TemplateSrv();
-  const ds = new Datasource({ url: '' }, {}, templateSrv);
-  const DEFAULT_OPTIONS = {
-    rangeRaw: { to: 'now', from: 'now - 3h' },
-    scopedVars: {},
-    targets: [],
-  };
-
-  let queries: any[];
-
-  describe('prepareQueries()', () => {
-    it('filters empty queries', () => {
-      queries = ds.prepareQueries(DEFAULT_OPTIONS);
-      expect(queries.length).toBe(0);
-
-      queries = ds.prepareQueries({
-        ...DEFAULT_OPTIONS,
-        targets: [{ query: '' }],
-      });
-      expect(queries.length).toBe(0);
-    });
-
-    it('replaces $range variable', () => {
-      queries = ds.prepareQueries({
-        ...DEFAULT_OPTIONS,
-        targets: [{ query: 'from(db: "test") |> range($range)' }],
-      });
-      expect(queries.length).toBe(1);
-      expect(queries[0].query).toBe('from(db: "test") |> range(start: -3h)');
-    });
-
-    it('replaces $range variable with custom dates', () => {
-      const to = moment();
-      const from = moment().subtract(1, 'hours');
-      queries = ds.prepareQueries({
-        ...DEFAULT_OPTIONS,
-        rangeRaw: { to, from },
-        targets: [{ query: 'from(db: "test") |> range($range)' }],
-      });
-      expect(queries.length).toBe(1);
-      const start = from.toISOString();
-      const stop = to.toISOString();
-      expect(queries[0].query).toBe(`from(db: "test") |> range(start: ${start}, stop: ${stop})`);
-    });
-  });
-});

+ 0 - 63
public/app/plugins/datasource/influxdb-ifql/specs/response_parser.jest.ts

@@ -1,63 +0,0 @@
-import {
-  getNameFromRecord,
-  getTableModelFromResult,
-  getTimeSeriesFromResult,
-  parseResults,
-  parseValue,
-} from '../response_parser';
-import response from './sample_response_csv';
-
-describe('influxdb ifql response parser', () => {
-  describe('parseResults()', () => {
-    it('expects three results', () => {
-      const results = parseResults(response);
-      expect(results.length).toBe(2);
-    });
-  });
-
-  describe('getTableModelFromResult()', () => {
-    it('expects a table model', () => {
-      const results = parseResults(response);
-      const table = getTableModelFromResult(results[0]);
-      expect(table.columns.length).toBe(6);
-      expect(table.rows.length).toBe(300);
-    });
-  });
-
-  describe('getTimeSeriesFromResult()', () => {
-    it('expects time series', () => {
-      const results = parseResults(response);
-      const series = getTimeSeriesFromResult(results[0]);
-      expect(series.length).toBe(50);
-      expect(series[0].datapoints.length).toBe(6);
-    });
-  });
-
-  describe('getNameFromRecord()', () => {
-    it('expects name based on measurements and tags', () => {
-      const record = {
-        '': '',
-        result: '',
-        table: '0',
-        _start: '2018-06-02T06:35:25.651942602Z',
-        _stop: '2018-06-02T07:35:25.651942602Z',
-        _time: '2018-06-02T06:35:31Z',
-        _value: '0',
-        _field: 'usage_guest',
-        _measurement: 'cpu',
-        cpu: 'cpu-total',
-        host: 'kenobi-3.local',
-      };
-      expect(getNameFromRecord(record)).toBe('cpu usage_guest cpu=cpu-total host=kenobi-3.local');
-    });
-  });
-
-  describe('parseValue()', () => {
-    it('parses a number', () => {
-      expect(parseValue('42.3')).toBe(42.3);
-    });
-    it('parses a non-number to null', () => {
-      expect(parseValue('foo')).toBe(null);
-    });
-  });
-});

+ 0 - 349
public/app/plugins/datasource/influxdb-ifql/specs/sample_response_csv.ts

@@ -1,349 +0,0 @@
-const result = `#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,double,string,string,string,string
-#partition,false,false,true,true,false,false,true,true,true,true
-#default,_result,,,,,,,,,
-,result,table,_start,_stop,_time,_value,_field,_measurement,cpu,host
-,,0,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_guest,cpu,cpu-total,kenobi-3.local
-,,0,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_guest,cpu,cpu-total,kenobi-3.local
-,,0,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_guest,cpu,cpu-total,kenobi-3.local
-,,0,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_guest,cpu,cpu-total,kenobi-3.local
-,,0,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_guest,cpu,cpu-total,kenobi-3.local
-,,0,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_guest,cpu,cpu-total,kenobi-3.local
-,,1,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_guest_nice,cpu,cpu-total,kenobi-3.local
-,,1,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_guest_nice,cpu,cpu-total,kenobi-3.local
-,,1,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_guest_nice,cpu,cpu-total,kenobi-3.local
-,,1,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_guest_nice,cpu,cpu-total,kenobi-3.local
-,,1,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_guest_nice,cpu,cpu-total,kenobi-3.local
-,,1,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_guest_nice,cpu,cpu-total,kenobi-3.local
-,,2,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,81.87046761690422,usage_idle,cpu,cpu-total,kenobi-3.local
-,,2,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,82.03398300849575,usage_idle,cpu,cpu-total,kenobi-3.local
-,,2,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,76.26186906546727,usage_idle,cpu,cpu-total,kenobi-3.local
-,,2,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,79.65465465465465,usage_idle,cpu,cpu-total,kenobi-3.local
-,,2,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,70.72195853110168,usage_idle,cpu,cpu-total,kenobi-3.local
-,,2,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,69.86746686671668,usage_idle,cpu,cpu-total,kenobi-3.local
-,,3,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_iowait,cpu,cpu-total,kenobi-3.local
-,,3,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_iowait,cpu,cpu-total,kenobi-3.local
-,,3,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_iowait,cpu,cpu-total,kenobi-3.local
-,,3,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_iowait,cpu,cpu-total,kenobi-3.local
-,,3,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_iowait,cpu,cpu-total,kenobi-3.local
-,,3,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_iowait,cpu,cpu-total,kenobi-3.local
-,,4,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_irq,cpu,cpu-total,kenobi-3.local
-,,4,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_irq,cpu,cpu-total,kenobi-3.local
-,,4,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_irq,cpu,cpu-total,kenobi-3.local
-,,4,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_irq,cpu,cpu-total,kenobi-3.local
-,,4,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_irq,cpu,cpu-total,kenobi-3.local
-,,4,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_irq,cpu,cpu-total,kenobi-3.local
-,,5,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_nice,cpu,cpu-total,kenobi-3.local
-,,5,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_nice,cpu,cpu-total,kenobi-3.local
-,,5,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_nice,cpu,cpu-total,kenobi-3.local
-,,5,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_nice,cpu,cpu-total,kenobi-3.local
-,,5,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_nice,cpu,cpu-total,kenobi-3.local
-,,5,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_nice,cpu,cpu-total,kenobi-3.local
-,,6,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_softirq,cpu,cpu-total,kenobi-3.local
-,,6,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_softirq,cpu,cpu-total,kenobi-3.local
-,,6,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_softirq,cpu,cpu-total,kenobi-3.local
-,,6,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_softirq,cpu,cpu-total,kenobi-3.local
-,,6,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_softirq,cpu,cpu-total,kenobi-3.local
-,,6,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_softirq,cpu,cpu-total,kenobi-3.local
-,,7,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_steal,cpu,cpu-total,kenobi-3.local
-,,7,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_steal,cpu,cpu-total,kenobi-3.local
-,,7,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_steal,cpu,cpu-total,kenobi-3.local
-,,7,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_steal,cpu,cpu-total,kenobi-3.local
-,,7,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_steal,cpu,cpu-total,kenobi-3.local
-,,7,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_steal,cpu,cpu-total,kenobi-3.local
-,,8,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,6.25156289072268,usage_system,cpu,cpu-total,kenobi-3.local
-,,8,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,8.045977011494253,usage_system,cpu,cpu-total,kenobi-3.local
-,,8,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,8.79560219890055,usage_system,cpu,cpu-total,kenobi-3.local
-,,8,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,8.408408408408409,usage_system,cpu,cpu-total,kenobi-3.local
-,,8,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,11.64126904821384,usage_system,cpu,cpu-total,kenobi-3.local
-,,8,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,13.078269567391848,usage_system,cpu,cpu-total,kenobi-3.local
-,,9,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,11.877969492373094,usage_user,cpu,cpu-total,kenobi-3.local
-,,9,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,9.920039980009996,usage_user,cpu,cpu-total,kenobi-3.local
-,,9,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,14.942528735632184,usage_user,cpu,cpu-total,kenobi-3.local
-,,9,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,11.936936936936936,usage_user,cpu,cpu-total,kenobi-3.local
-,,9,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,17.636772420684487,usage_user,cpu,cpu-total,kenobi-3.local
-,,9,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,17.05426356589147,usage_user,cpu,cpu-total,kenobi-3.local
-,,10,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_guest,cpu,cpu0,kenobi-3.local
-,,10,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_guest,cpu,cpu0,kenobi-3.local
-,,10,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_guest,cpu,cpu0,kenobi-3.local
-,,10,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_guest,cpu,cpu0,kenobi-3.local
-,,10,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_guest,cpu,cpu0,kenobi-3.local
-,,10,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_guest,cpu,cpu0,kenobi-3.local
-,,11,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_guest_nice,cpu,cpu0,kenobi-3.local
-,,11,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_guest_nice,cpu,cpu0,kenobi-3.local
-,,11,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_guest_nice,cpu,cpu0,kenobi-3.local
-,,11,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_guest_nice,cpu,cpu0,kenobi-3.local
-,,11,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_guest_nice,cpu,cpu0,kenobi-3.local
-,,11,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_guest_nice,cpu,cpu0,kenobi-3.local
-,,12,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,73.1,usage_idle,cpu,cpu0,kenobi-3.local
-,,12,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,69.03096903096903,usage_idle,cpu,cpu0,kenobi-3.local
-,,12,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,63.63636363636363,usage_idle,cpu,cpu0,kenobi-3.local
-,,12,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,67.86786786786787,usage_idle,cpu,cpu0,kenobi-3.local
-,,12,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,57.4,usage_idle,cpu,cpu0,kenobi-3.local
-,,12,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,57.8,usage_idle,cpu,cpu0,kenobi-3.local
-,,13,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_iowait,cpu,cpu0,kenobi-3.local
-,,13,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_iowait,cpu,cpu0,kenobi-3.local
-,,13,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_iowait,cpu,cpu0,kenobi-3.local
-,,13,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_iowait,cpu,cpu0,kenobi-3.local
-,,13,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_iowait,cpu,cpu0,kenobi-3.local
-,,13,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_iowait,cpu,cpu0,kenobi-3.local
-,,14,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_irq,cpu,cpu0,kenobi-3.local
-,,14,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_irq,cpu,cpu0,kenobi-3.local
-,,14,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_irq,cpu,cpu0,kenobi-3.local
-,,14,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_irq,cpu,cpu0,kenobi-3.local
-,,14,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_irq,cpu,cpu0,kenobi-3.local
-,,14,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_irq,cpu,cpu0,kenobi-3.local
-,,15,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_nice,cpu,cpu0,kenobi-3.local
-,,15,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_nice,cpu,cpu0,kenobi-3.local
-,,15,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_nice,cpu,cpu0,kenobi-3.local
-,,15,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_nice,cpu,cpu0,kenobi-3.local
-,,15,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_nice,cpu,cpu0,kenobi-3.local
-,,15,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_nice,cpu,cpu0,kenobi-3.local
-,,16,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_softirq,cpu,cpu0,kenobi-3.local
-,,16,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_softirq,cpu,cpu0,kenobi-3.local
-,,16,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_softirq,cpu,cpu0,kenobi-3.local
-,,16,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_softirq,cpu,cpu0,kenobi-3.local
-,,16,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_softirq,cpu,cpu0,kenobi-3.local
-,,16,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_softirq,cpu,cpu0,kenobi-3.local
-,,17,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_steal,cpu,cpu0,kenobi-3.local
-,,17,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_steal,cpu,cpu0,kenobi-3.local
-,,17,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_steal,cpu,cpu0,kenobi-3.local
-,,17,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_steal,cpu,cpu0,kenobi-3.local
-,,17,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_steal,cpu,cpu0,kenobi-3.local
-,,17,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_steal,cpu,cpu0,kenobi-3.local
-,,18,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,9.6,usage_system,cpu,cpu0,kenobi-3.local
-,,18,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,14.985014985014985,usage_system,cpu,cpu0,kenobi-3.local
-,,18,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,14.185814185814186,usage_system,cpu,cpu0,kenobi-3.local
-,,18,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,13.813813813813814,usage_system,cpu,cpu0,kenobi-3.local
-,,18,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,17.9,usage_system,cpu,cpu0,kenobi-3.local
-,,18,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,20,usage_system,cpu,cpu0,kenobi-3.local
-,,19,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,17.3,usage_user,cpu,cpu0,kenobi-3.local
-,,19,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,15.984015984015985,usage_user,cpu,cpu0,kenobi-3.local
-,,19,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,22.17782217782218,usage_user,cpu,cpu0,kenobi-3.local
-,,19,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,18.31831831831832,usage_user,cpu,cpu0,kenobi-3.local
-,,19,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,24.7,usage_user,cpu,cpu0,kenobi-3.local
-,,19,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,22.2,usage_user,cpu,cpu0,kenobi-3.local
-,,20,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_guest,cpu,cpu1,kenobi-3.local
-,,20,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_guest,cpu,cpu1,kenobi-3.local
-,,20,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_guest,cpu,cpu1,kenobi-3.local
-,,20,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_guest,cpu,cpu1,kenobi-3.local
-,,20,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_guest,cpu,cpu1,kenobi-3.local
-,,20,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_guest,cpu,cpu1,kenobi-3.local
-,,21,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_guest_nice,cpu,cpu1,kenobi-3.local
-,,21,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_guest_nice,cpu,cpu1,kenobi-3.local
-,,21,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_guest_nice,cpu,cpu1,kenobi-3.local
-,,21,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_guest_nice,cpu,cpu1,kenobi-3.local
-,,21,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_guest_nice,cpu,cpu1,kenobi-3.local
-,,21,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_guest_nice,cpu,cpu1,kenobi-3.local
-,,22,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,89.8,usage_idle,cpu,cpu1,kenobi-3.local
-,,22,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,91.8,usage_idle,cpu,cpu1,kenobi-3.local
-,,22,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,87.11288711288711,usage_idle,cpu,cpu1,kenobi-3.local
-,,22,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,89.48948948948949,usage_idle,cpu,cpu1,kenobi-3.local
-,,22,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,83,usage_idle,cpu,cpu1,kenobi-3.local
-,,22,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,80.1,usage_idle,cpu,cpu1,kenobi-3.local
-,,23,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_iowait,cpu,cpu1,kenobi-3.local
-,,23,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_iowait,cpu,cpu1,kenobi-3.local
-,,23,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_iowait,cpu,cpu1,kenobi-3.local
-,,23,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_iowait,cpu,cpu1,kenobi-3.local
-,,23,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_iowait,cpu,cpu1,kenobi-3.local
-,,23,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_iowait,cpu,cpu1,kenobi-3.local
-,,24,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_irq,cpu,cpu1,kenobi-3.local
-,,24,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_irq,cpu,cpu1,kenobi-3.local
-,,24,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_irq,cpu,cpu1,kenobi-3.local
-,,24,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_irq,cpu,cpu1,kenobi-3.local
-,,24,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_irq,cpu,cpu1,kenobi-3.local
-,,24,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_irq,cpu,cpu1,kenobi-3.local
-,,25,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_nice,cpu,cpu1,kenobi-3.local
-,,25,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_nice,cpu,cpu1,kenobi-3.local
-,,25,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_nice,cpu,cpu1,kenobi-3.local
-,,25,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_nice,cpu,cpu1,kenobi-3.local
-,,25,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_nice,cpu,cpu1,kenobi-3.local
-,,25,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_nice,cpu,cpu1,kenobi-3.local
-,,26,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_softirq,cpu,cpu1,kenobi-3.local
-,,26,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_softirq,cpu,cpu1,kenobi-3.local
-,,26,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_softirq,cpu,cpu1,kenobi-3.local
-,,26,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_softirq,cpu,cpu1,kenobi-3.local
-,,26,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_softirq,cpu,cpu1,kenobi-3.local
-,,26,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_softirq,cpu,cpu1,kenobi-3.local
-,,27,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_steal,cpu,cpu1,kenobi-3.local
-,,27,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_steal,cpu,cpu1,kenobi-3.local
-,,27,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_steal,cpu,cpu1,kenobi-3.local
-,,27,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_steal,cpu,cpu1,kenobi-3.local
-,,27,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_steal,cpu,cpu1,kenobi-3.local
-,,27,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_steal,cpu,cpu1,kenobi-3.local
-,,28,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,3.5,usage_system,cpu,cpu1,kenobi-3.local
-,,28,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,4,usage_system,cpu,cpu1,kenobi-3.local
-,,28,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,4.895104895104895,usage_system,cpu,cpu1,kenobi-3.local
-,,28,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,4.504504504504505,usage_system,cpu,cpu1,kenobi-3.local
-,,28,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,6.3,usage_system,cpu,cpu1,kenobi-3.local
-,,28,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,7.9,usage_system,cpu,cpu1,kenobi-3.local
-,,29,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,6.7,usage_user,cpu,cpu1,kenobi-3.local
-,,29,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,4.2,usage_user,cpu,cpu1,kenobi-3.local
-,,29,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,7.992007992007992,usage_user,cpu,cpu1,kenobi-3.local
-,,29,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,6.006006006006006,usage_user,cpu,cpu1,kenobi-3.local
-,,29,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,10.7,usage_user,cpu,cpu1,kenobi-3.local
-,,29,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,12,usage_user,cpu,cpu1,kenobi-3.local
-,,30,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_guest,cpu,cpu2,kenobi-3.local
-,,30,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_guest,cpu,cpu2,kenobi-3.local
-,,30,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_guest,cpu,cpu2,kenobi-3.local
-,,30,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_guest,cpu,cpu2,kenobi-3.local
-,,30,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_guest,cpu,cpu2,kenobi-3.local
-,,30,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_guest,cpu,cpu2,kenobi-3.local
-,,31,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_guest_nice,cpu,cpu2,kenobi-3.local
-,,31,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_guest_nice,cpu,cpu2,kenobi-3.local
-,,31,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_guest_nice,cpu,cpu2,kenobi-3.local
-,,31,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_guest_nice,cpu,cpu2,kenobi-3.local
-,,31,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_guest_nice,cpu,cpu2,kenobi-3.local
-,,31,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_guest_nice,cpu,cpu2,kenobi-3.local
-,,32,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,75.17517517517517,usage_idle,cpu,cpu2,kenobi-3.local
-,,32,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,74.82517482517483,usage_idle,cpu,cpu2,kenobi-3.local
-,,32,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,67.9,usage_idle,cpu,cpu2,kenobi-3.local
-,,32,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,72.47247247247248,usage_idle,cpu,cpu2,kenobi-3.local
-,,32,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,61.63836163836164,usage_idle,cpu,cpu2,kenobi-3.local
-,,32,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,62,usage_idle,cpu,cpu2,kenobi-3.local
-,,33,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_iowait,cpu,cpu2,kenobi-3.local
-,,33,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_iowait,cpu,cpu2,kenobi-3.local
-,,33,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_iowait,cpu,cpu2,kenobi-3.local
-,,33,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_iowait,cpu,cpu2,kenobi-3.local
-,,33,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_iowait,cpu,cpu2,kenobi-3.local
-,,33,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_iowait,cpu,cpu2,kenobi-3.local
-,,34,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_irq,cpu,cpu2,kenobi-3.local
-,,34,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_irq,cpu,cpu2,kenobi-3.local
-,,34,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_irq,cpu,cpu2,kenobi-3.local
-,,34,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_irq,cpu,cpu2,kenobi-3.local
-,,34,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_irq,cpu,cpu2,kenobi-3.local
-,,34,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_irq,cpu,cpu2,kenobi-3.local
-,,35,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_nice,cpu,cpu2,kenobi-3.local
-,,35,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_nice,cpu,cpu2,kenobi-3.local
-,,35,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_nice,cpu,cpu2,kenobi-3.local
-,,35,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_nice,cpu,cpu2,kenobi-3.local
-,,35,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_nice,cpu,cpu2,kenobi-3.local
-,,35,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_nice,cpu,cpu2,kenobi-3.local
-,,36,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_softirq,cpu,cpu2,kenobi-3.local
-,,36,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_softirq,cpu,cpu2,kenobi-3.local
-,,36,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_softirq,cpu,cpu2,kenobi-3.local
-,,36,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_softirq,cpu,cpu2,kenobi-3.local
-,,36,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_softirq,cpu,cpu2,kenobi-3.local
-,,36,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_softirq,cpu,cpu2,kenobi-3.local
-,,37,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_steal,cpu,cpu2,kenobi-3.local
-,,37,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_steal,cpu,cpu2,kenobi-3.local
-,,37,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_steal,cpu,cpu2,kenobi-3.local
-,,37,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_steal,cpu,cpu2,kenobi-3.local
-,,37,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_steal,cpu,cpu2,kenobi-3.local
-,,37,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_steal,cpu,cpu2,kenobi-3.local
-,,38,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,8.208208208208209,usage_system,cpu,cpu2,kenobi-3.local
-,,38,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,9.99000999000999,usage_system,cpu,cpu2,kenobi-3.local
-,,38,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,11.2,usage_system,cpu,cpu2,kenobi-3.local
-,,38,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,10.81081081081081,usage_system,cpu,cpu2,kenobi-3.local
-,,38,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,14.785214785214785,usage_system,cpu,cpu2,kenobi-3.local
-,,38,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,16.2,usage_system,cpu,cpu2,kenobi-3.local
-,,39,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,16.616616616616618,usage_user,cpu,cpu2,kenobi-3.local
-,,39,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,15.184815184815184,usage_user,cpu,cpu2,kenobi-3.local
-,,39,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,20.9,usage_user,cpu,cpu2,kenobi-3.local
-,,39,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,16.716716716716718,usage_user,cpu,cpu2,kenobi-3.local
-,,39,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,23.576423576423576,usage_user,cpu,cpu2,kenobi-3.local
-,,39,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,21.8,usage_user,cpu,cpu2,kenobi-3.local
-,,40,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_guest,cpu,cpu3,kenobi-3.local
-,,40,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_guest,cpu,cpu3,kenobi-3.local
-,,40,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_guest,cpu,cpu3,kenobi-3.local
-,,40,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_guest,cpu,cpu3,kenobi-3.local
-,,40,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_guest,cpu,cpu3,kenobi-3.local
-,,40,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_guest,cpu,cpu3,kenobi-3.local
-,,41,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_guest_nice,cpu,cpu3,kenobi-3.local
-,,41,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_guest_nice,cpu,cpu3,kenobi-3.local
-,,41,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_guest_nice,cpu,cpu3,kenobi-3.local
-,,41,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_guest_nice,cpu,cpu3,kenobi-3.local
-,,41,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_guest_nice,cpu,cpu3,kenobi-3.local
-,,41,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_guest_nice,cpu,cpu3,kenobi-3.local
-,,42,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,89.4,usage_idle,cpu,cpu3,kenobi-3.local
-,,42,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,92.5,usage_idle,cpu,cpu3,kenobi-3.local
-,,42,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,86.4,usage_idle,cpu,cpu3,kenobi-3.local
-,,42,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,88.78878878878879,usage_idle,cpu,cpu3,kenobi-3.local
-,,42,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,80.83832335329342,usage_idle,cpu,cpu3,kenobi-3.local
-,,42,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,79.57957957957957,usage_idle,cpu,cpu3,kenobi-3.local
-,,43,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_iowait,cpu,cpu3,kenobi-3.local
-,,43,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_iowait,cpu,cpu3,kenobi-3.local
-,,43,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_iowait,cpu,cpu3,kenobi-3.local
-,,43,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_iowait,cpu,cpu3,kenobi-3.local
-,,43,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_iowait,cpu,cpu3,kenobi-3.local
-,,43,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_iowait,cpu,cpu3,kenobi-3.local
-,,44,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_irq,cpu,cpu3,kenobi-3.local
-,,44,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_irq,cpu,cpu3,kenobi-3.local
-,,44,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_irq,cpu,cpu3,kenobi-3.local
-,,44,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_irq,cpu,cpu3,kenobi-3.local
-,,44,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_irq,cpu,cpu3,kenobi-3.local
-,,44,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_irq,cpu,cpu3,kenobi-3.local
-,,45,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_nice,cpu,cpu3,kenobi-3.local
-,,45,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_nice,cpu,cpu3,kenobi-3.local
-,,45,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_nice,cpu,cpu3,kenobi-3.local
-,,45,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_nice,cpu,cpu3,kenobi-3.local
-,,45,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_nice,cpu,cpu3,kenobi-3.local
-,,45,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_nice,cpu,cpu3,kenobi-3.local
-,,46,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_softirq,cpu,cpu3,kenobi-3.local
-,,46,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_softirq,cpu,cpu3,kenobi-3.local
-,,46,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_softirq,cpu,cpu3,kenobi-3.local
-,,46,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_softirq,cpu,cpu3,kenobi-3.local
-,,46,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_softirq,cpu,cpu3,kenobi-3.local
-,,46,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_softirq,cpu,cpu3,kenobi-3.local
-,,47,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,0,usage_steal,cpu,cpu3,kenobi-3.local
-,,47,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,0,usage_steal,cpu,cpu3,kenobi-3.local
-,,47,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,0,usage_steal,cpu,cpu3,kenobi-3.local
-,,47,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,0,usage_steal,cpu,cpu3,kenobi-3.local
-,,47,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,0,usage_steal,cpu,cpu3,kenobi-3.local
-,,47,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,0,usage_steal,cpu,cpu3,kenobi-3.local
-,,48,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,3.7,usage_system,cpu,cpu3,kenobi-3.local
-,,48,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,3.2,usage_system,cpu,cpu3,kenobi-3.local
-,,48,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,4.9,usage_system,cpu,cpu3,kenobi-3.local
-,,48,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,4.504504504504505,usage_system,cpu,cpu3,kenobi-3.local
-,,48,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,7.584830339321357,usage_system,cpu,cpu3,kenobi-3.local
-,,48,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,8.208208208208209,usage_system,cpu,cpu3,kenobi-3.local
-,,49,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,6.9,usage_user,cpu,cpu3,kenobi-3.local
-,,49,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,4.3,usage_user,cpu,cpu3,kenobi-3.local
-,,49,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,8.7,usage_user,cpu,cpu3,kenobi-3.local
-,,49,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,6.706706706706707,usage_user,cpu,cpu3,kenobi-3.local
-,,49,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,11.57684630738523,usage_user,cpu,cpu3,kenobi-3.local
-,,49,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,12.212212212212211,usage_user,cpu,cpu3,kenobi-3.local
-
-#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,long,string,string,string,string,string,string,string
-#partition,false,false,true,true,false,false,true,true,true,true,true,true,true
-#default,_result,,,,,,,,,,,,
-,result,table,_start,_stop,_time,_value,_field,_measurement,device,fstype,host,mode,path
-,,50,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,9024180224,free,disk,disk1,hfs,kenobi-3.local,rw,/
-,,50,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,9025056768,free,disk,disk1,hfs,kenobi-3.local,rw,/
-,,50,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,9024774144,free,disk,disk1,hfs,kenobi-3.local,rw,/
-,,50,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,9024638976,free,disk,disk1,hfs,kenobi-3.local,rw,/
-,,50,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,9024299008,free,disk,disk1,hfs,kenobi-3.local,rw,/
-,,50,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,9024036864,free,disk,disk1,hfs,kenobi-3.local,rw,/
-,,51,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,4290025660,inodes_free,disk,disk1,hfs,kenobi-3.local,rw,/
-,,51,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,4290025659,inodes_free,disk,disk1,hfs,kenobi-3.local,rw,/
-,,51,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,4290025659,inodes_free,disk,disk1,hfs,kenobi-3.local,rw,/
-,,51,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,4290025660,inodes_free,disk,disk1,hfs,kenobi-3.local,rw,/
-,,51,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,4290025660,inodes_free,disk,disk1,hfs,kenobi-3.local,rw,/
-,,51,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,4290025657,inodes_free,disk,disk1,hfs,kenobi-3.local,rw,/
-,,52,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,4294967279,inodes_total,disk,disk1,hfs,kenobi-3.local,rw,/
-,,52,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,4294967279,inodes_total,disk,disk1,hfs,kenobi-3.local,rw,/
-,,52,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,4294967279,inodes_total,disk,disk1,hfs,kenobi-3.local,rw,/
-,,52,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,4294967279,inodes_total,disk,disk1,hfs,kenobi-3.local,rw,/
-,,52,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,4294967279,inodes_total,disk,disk1,hfs,kenobi-3.local,rw,/
-,,52,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,4294967279,inodes_total,disk,disk1,hfs,kenobi-3.local,rw,/
-,,53,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,4941619,inodes_used,disk,disk1,hfs,kenobi-3.local,rw,/
-,,53,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,4941620,inodes_used,disk,disk1,hfs,kenobi-3.local,rw,/
-,,53,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,4941620,inodes_used,disk,disk1,hfs,kenobi-3.local,rw,/
-,,53,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,4941619,inodes_used,disk,disk1,hfs,kenobi-3.local,rw,/
-,,53,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,4941619,inodes_used,disk,disk1,hfs,kenobi-3.local,rw,/
-,,53,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,4941622,inodes_used,disk,disk1,hfs,kenobi-3.local,rw,/
-,,54,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,249804886016,total,disk,disk1,hfs,kenobi-3.local,rw,/
-,,54,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,249804886016,total,disk,disk1,hfs,kenobi-3.local,rw,/
-,,54,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,249804886016,total,disk,disk1,hfs,kenobi-3.local,rw,/
-,,54,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,249804886016,total,disk,disk1,hfs,kenobi-3.local,rw,/
-,,54,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,249804886016,total,disk,disk1,hfs,kenobi-3.local,rw,/
-,,54,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,249804886016,total,disk,disk1,hfs,kenobi-3.local,rw,/
-,,55,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:23Z,240518561792,used,disk,disk1,hfs,kenobi-3.local,rw,/
-,,55,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:33Z,240517685248,used,disk,disk1,hfs,kenobi-3.local,rw,/
-,,55,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:43Z,240517967872,used,disk,disk1,hfs,kenobi-3.local,rw,/
-,,55,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:54:53Z,240518103040,used,disk,disk1,hfs,kenobi-3.local,rw,/
-,,55,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:03Z,240518443008,used,disk,disk1,hfs,kenobi-3.local,rw,/
-,,55,2018-06-01T12:54:13.516195939Z,2018-06-01T12:55:13.516195939Z,2018-06-01T12:55:13Z,240518705152,used,disk,disk1,hfs,kenobi-3.local,rw,/
-
-`;
-
-export default result;

+ 0 - 1
public/app/plugins/datasource/influxdb/specs/query_builder.jest.ts

@@ -1,4 +1,3 @@
-import { describe, it, expect } from 'test/lib/common';
 import { InfluxQueryBuilder } from '../query_builder';
 
 describe('InfluxQueryBuilder', function() {

+ 15 - 15
public/app/plugins/panel/graph/specs/tooltip_specs.ts → public/app/plugins/panel/graph/specs/graph_tooltip.jest.ts

@@ -1,11 +1,11 @@
-import { describe, beforeEach, it, sinon, expect } from '../../../../../test/lib/common';
+jest.mock('app/core/core', () => ({}));
 
 import $ from 'jquery';
 import GraphTooltip from '../graph_tooltip';
 
 var scope = {
-  appEvent: sinon.spy(),
-  onAppEvent: sinon.spy(),
+  appEvent: jest.fn(),
+  onAppEvent: jest.fn(),
   ctrl: {},
 };
 
@@ -47,22 +47,22 @@ describe('findHoverIndexFromData', function() {
 
   it('should return 0 if posX out of lower bounds', function() {
     var posX = 99;
-    expect(tooltip.findHoverIndexFromData(posX, series)).to.be(0);
+    expect(tooltip.findHoverIndexFromData(posX, series)).toBe(0);
   });
 
   it('should return n - 1 if posX out of upper bounds', function() {
     var posX = 108;
-    expect(tooltip.findHoverIndexFromData(posX, series)).to.be(series.data.length - 1);
+    expect(tooltip.findHoverIndexFromData(posX, series)).toBe(series.data.length - 1);
   });
 
   it('should return i if posX in series', function() {
     var posX = 104;
-    expect(tooltip.findHoverIndexFromData(posX, series)).to.be(4);
+    expect(tooltip.findHoverIndexFromData(posX, series)).toBe(4);
   });
 
   it('should return i if posX not in series and i + 1 > posX', function() {
     var posX = 104.9;
-    expect(tooltip.findHoverIndexFromData(posX, series)).to.be(4);
+    expect(tooltip.findHoverIndexFromData(posX, series)).toBe(4);
   });
 });
 
@@ -73,17 +73,17 @@ describeSharedTooltip('steppedLine false, stack false', function(ctx) {
   });
 
   it('should return 2 series', function() {
-    expect(ctx.results.length).to.be(2);
+    expect(ctx.results.length).toBe(2);
   });
 
   it('should add time to results array', function() {
-    expect(ctx.results.time).to.be(10);
+    expect(ctx.results.time).toBe(10);
   });
 
   it('should set value and hoverIndex', function() {
-    expect(ctx.results[0].value).to.be(15);
-    expect(ctx.results[1].value).to.be(2);
-    expect(ctx.results[0].hoverIndex).to.be(0);
+    expect(ctx.results[0].value).toBe(15);
+    expect(ctx.results[1].value).toBe(2);
+    expect(ctx.results[0].hoverIndex).toBe(0);
   });
 });
 
@@ -121,7 +121,7 @@ describeSharedTooltip('steppedLine false, stack true, individual false', functio
   });
 
   it('should show stacked value', function() {
-    expect(ctx.results[1].value).to.be(17);
+    expect(ctx.results[1].value).toBe(17);
   });
 });
 
@@ -152,7 +152,7 @@ describeSharedTooltip('steppedLine false, stack true, individual false, series s
   });
 
   it('should not show stacked value', function() {
-    expect(ctx.results[1].value).to.be(2);
+    expect(ctx.results[1].value).toBe(2);
   });
 });
 
@@ -184,6 +184,6 @@ describeSharedTooltip('steppedLine false, stack true, individual true', function
   });
 
   it('should not show stacked value', function() {
-    expect(ctx.results[1].value).to.be(2);
+    expect(ctx.results[1].value).toBe(2);
   });
 });

+ 20 - 22
public/app/plugins/panel/graph/specs/threshold_manager_specs.ts → public/app/plugins/panel/graph/specs/threshold_manager.jest.ts

@@ -1,5 +1,3 @@
-import { describe, it, expect } from '../../../../../test/lib/common';
-
 import angular from 'angular';
 import TimeSeries from 'app/core/time_series2';
 import { ThresholdManager } from '../threshold_manager';
@@ -38,16 +36,16 @@ describe('ThresholdManager', function() {
       it('should add fill for threshold with fill: true', function() {
         var markings = ctx.options.grid.markings;
 
-        expect(markings[0].yaxis.from).to.be(300);
-        expect(markings[0].yaxis.to).to.be(Infinity);
-        expect(markings[0].color).to.be('rgba(234, 112, 112, 0.12)');
+        expect(markings[0].yaxis.from).toBe(300);
+        expect(markings[0].yaxis.to).toBe(Infinity);
+        expect(markings[0].color).toBe('rgba(234, 112, 112, 0.12)');
       });
 
       it('should add line', function() {
         var markings = ctx.options.grid.markings;
-        expect(markings[1].yaxis.from).to.be(300);
-        expect(markings[1].yaxis.to).to.be(300);
-        expect(markings[1].color).to.be('rgba(237, 46, 24, 0.60)');
+        expect(markings[1].yaxis.from).toBe(300);
+        expect(markings[1].yaxis.to).toBe(300);
+        expect(markings[1].color).toBe('rgba(237, 46, 24, 0.60)');
       });
     });
 
@@ -59,14 +57,14 @@ describe('ThresholdManager', function() {
 
       it('should add fill for first thresholds to next threshold', function() {
         var markings = ctx.options.grid.markings;
-        expect(markings[0].yaxis.from).to.be(200);
-        expect(markings[0].yaxis.to).to.be(300);
+        expect(markings[0].yaxis.from).toBe(200);
+        expect(markings[0].yaxis.to).toBe(300);
       });
 
       it('should add fill for last thresholds to infinity', function() {
         var markings = ctx.options.grid.markings;
-        expect(markings[1].yaxis.from).to.be(300);
-        expect(markings[1].yaxis.to).to.be(Infinity);
+        expect(markings[1].yaxis.from).toBe(300);
+        expect(markings[1].yaxis.to).toBe(Infinity);
       });
     });
 
@@ -78,14 +76,14 @@ describe('ThresholdManager', function() {
 
       it('should add fill for first thresholds to next threshold', function() {
         var markings = ctx.options.grid.markings;
-        expect(markings[0].yaxis.from).to.be(300);
-        expect(markings[0].yaxis.to).to.be(200);
+        expect(markings[0].yaxis.from).toBe(300);
+        expect(markings[0].yaxis.to).toBe(200);
       });
 
       it('should add fill for last thresholds to itself', function() {
         var markings = ctx.options.grid.markings;
-        expect(markings[1].yaxis.from).to.be(200);
-        expect(markings[1].yaxis.to).to.be(200);
+        expect(markings[1].yaxis.from).toBe(200);
+        expect(markings[1].yaxis.to).toBe(200);
       });
     });
 
@@ -97,14 +95,14 @@ describe('ThresholdManager', function() {
 
       it('should add fill for first thresholds to next threshold', function() {
         var markings = ctx.options.grid.markings;
-        expect(markings[0].yaxis.from).to.be(300);
-        expect(markings[0].yaxis.to).to.be(Infinity);
+        expect(markings[0].yaxis.from).toBe(300);
+        expect(markings[0].yaxis.to).toBe(Infinity);
       });
 
       it('should add fill for last thresholds to itself', function() {
         var markings = ctx.options.grid.markings;
-        expect(markings[1].yaxis.from).to.be(200);
-        expect(markings[1].yaxis.to).to.be(-Infinity);
+        expect(markings[1].yaxis.from).toBe(200);
+        expect(markings[1].yaxis.to).toBe(-Infinity);
       });
     });
 
@@ -130,12 +128,12 @@ describe('ThresholdManager', function() {
 
       it('should add first threshold for left axis', function() {
         var markings = ctx.options.grid.markings;
-        expect(markings[0].yaxis.from).to.be(100);
+        expect(markings[0].yaxis.from).toBe(100);
       });
 
       it('should add second threshold for right axis', function() {
         var markings = ctx.options.grid.markings;
-        expect(markings[1].y2axis.from).to.be(200);
+        expect(markings[1].y2axis.from).toBe(200);
       });
     });
   });

+ 11 - 13
public/app/plugins/panel/singlestat/specs/singlestat_panel_spec.ts → public/app/plugins/panel/singlestat/specs/singlestat_panel.jest.ts

@@ -1,5 +1,3 @@
-import { describe, it, expect } from 'test/lib/common';
-
 import { getColorForValue } from '../module';
 
 describe('grafanaSingleStat', function() {
@@ -11,31 +9,31 @@ describe('grafanaSingleStat', function() {
       };
 
       it('5 should return green', () => {
-        expect(getColorForValue(data, 5)).to.be('green');
+        expect(getColorForValue(data, 5)).toBe('green');
       });
 
       it('19.9 should return green', () => {
-        expect(getColorForValue(data, 19.9)).to.be('green');
+        expect(getColorForValue(data, 19.9)).toBe('green');
       });
 
       it('20 should return yellow', () => {
-        expect(getColorForValue(data, 20)).to.be('yellow');
+        expect(getColorForValue(data, 20)).toBe('yellow');
       });
 
       it('20.1 should return yellow', () => {
-        expect(getColorForValue(data, 20.1)).to.be('yellow');
+        expect(getColorForValue(data, 20.1)).toBe('yellow');
       });
 
       it('25 should return yellow', () => {
-        expect(getColorForValue(data, 25)).to.be('yellow');
+        expect(getColorForValue(data, 25)).toBe('yellow');
       });
 
       it('50 should return red', () => {
-        expect(getColorForValue(data, 50)).to.be('red');
+        expect(getColorForValue(data, 50)).toBe('red');
       });
 
       it('55 should return red', () => {
-        expect(getColorForValue(data, 55)).to.be('red');
+        expect(getColorForValue(data, 55)).toBe('red');
       });
     });
   });
@@ -47,15 +45,15 @@ describe('grafanaSingleStat', function() {
     };
 
     it('-30 should return green', () => {
-      expect(getColorForValue(data, -30)).to.be('green');
+      expect(getColorForValue(data, -30)).toBe('green');
     });
 
     it('1 should return green', () => {
-      expect(getColorForValue(data, 1)).to.be('yellow');
+      expect(getColorForValue(data, 1)).toBe('yellow');
     });
 
     it('22 should return green', () => {
-      expect(getColorForValue(data, 22)).to.be('red');
+      expect(getColorForValue(data, 22)).toBe('red');
     });
   });
 
@@ -66,7 +64,7 @@ describe('grafanaSingleStat', function() {
     };
 
     it('-30 should return green', () => {
-      expect(getColorForValue(data, -26)).to.be('yellow');
+      expect(getColorForValue(data, -26)).toBe('yellow');
     });
   });
 });

+ 16 - 0
public/sass/components/_buttons.scss

@@ -100,6 +100,22 @@
 // Success appears as green
 .btn-success {
   @include buttonBackground($btn-success-bg, $btn-success-bg-hl);
+
+  &--processing {
+    @include button-outline-variant($gray-1);
+    @include box-shadow(none);
+    cursor: default;
+
+    &:hover,
+    &:active,
+    &:active:hover,
+    &:focus,
+    &:disabled {
+      color: $gray-1;
+      background-color: transparent;
+      border-color: $gray-1;
+    }
+  }
 }
 // Info appears as a neutral blue
 .btn-secondary {

+ 12 - 4
public/sass/components/_row.scss

@@ -11,11 +11,20 @@
       display: inline-block;
     }
 
-    .dashboard-row__drag,
-    .dashboard-row__actions {
+    .dashboard-row__drag {
       visibility: visible;
       opacity: 1;
     }
+
+    .dashboard-row__actions {
+      visibility: hidden;
+    }
+
+    .dashboard-row__toggle-target {
+      flex: 1;
+      cursor: pointer;
+      margin-right: 15px;
+    }
   }
 
   &:hover {
@@ -43,7 +52,6 @@
   color: $text-muted;
   visibility: hidden;
   opacity: 0;
-  flex-grow: 1;
   transition: 200ms opacity ease-in 200ms;
 
   a {
@@ -69,7 +77,7 @@
   cursor: move;
   width: 1rem;
   height: 100%;
-  background: url("../img/grab_dark.svg") no-repeat 50% 50%;
+  background: url('../img/grab_dark.svg') no-repeat 50% 50%;
   background-size: 8px;
   visibility: hidden;
   position: absolute;

+ 1 - 1
public/sass/pages/_alerting.scss

@@ -32,7 +32,7 @@
   .panel-alert-icon:before {
     content: '\e611';
     position: relative;
-    top: 1px;
+    top: 5px;
     left: -3px;
   }
 }

+ 15 - 8
public/sass/pages/_dashboard.scss

@@ -68,17 +68,26 @@ div.flot-text {
   font-weight: $font-weight-semi-bold;
   position: relative;
   width: 100%;
-  display: block;
-  padding-bottom: 2px;
+  display: flex;
+  flex-wrap: nowrap;
+  justify-content: center;
+  padding: 4px 0 4px;
 }
 
 .panel-title-text {
+  text-overflow: ellipsis;
+  overflow: hidden;
+  white-space: nowrap;
+  max-width: calc(100% - 38px);
   cursor: pointer;
   font-weight: $font-weight-semi-bold;
 
   &:hover {
     color: $link-hover-color;
   }
+  .panel-has-alert & {
+    max-width: calc(100% - 54px);
+  }
 }
 
 .panel-menu-container {
@@ -97,7 +106,7 @@ div.flot-text {
   width: 16px;
   height: 16px;
   left: 1px;
-  top: 4px;
+  top: 2px;
 
   &:hover {
     color: $link-hover-color;
@@ -114,8 +123,6 @@ div.flot-text {
 }
 
 .panel-header {
-  text-align: center;
-
   &:hover {
     transition: background-color 0.1s ease-in-out;
     background-color: $panel-header-hover-bg;
@@ -156,8 +163,8 @@ div.flot-text {
 
   .fa {
     position: relative;
-    top: -4px;
-    left: -6px;
+    top: -2px;
+    left: 6px;
     font-size: 75%;
     z-index: 1;
   }
@@ -174,7 +181,7 @@ div.flot-text {
     display: block;
     @include panel-corner-color(lighten($panel-bg, 4%));
     .fa {
-      left: -5px;
+      left: 4px;
     }
     .fa:before {
       content: '\f08e';

+ 13 - 3
public/sass/pages/_login.scss

@@ -13,6 +13,13 @@ $login-border: #8daac5;
   justify-content: center;
   background-image: url(../img/heatmap_bg_test.svg);
   background-size: cover;
+  color: #d8d9da;
+  & a {
+    color: #d8d9da !important;
+  }
+  & .btn-primary {
+    @include buttonBackground(#ff6600, #bc3e06);
+  }
 }
 
 input:-webkit-autofill,
@@ -25,8 +32,9 @@ textarea:-webkit-autofill:focus,
 select:-webkit-autofill,
 select:-webkit-autofill:hover,
 select:-webkit-autofill:focus {
-  -webkit-box-shadow: 0 0 0px 1000px $black inset;
-  -webkit-text-fill-color: $gray-7 !important;
+  -webkit-box-shadow: 0 0 0px 1000px $black inset !important;
+  -webkit-text-fill-color: #fbfbfb !important;
+  box-shadow: 0 0 0px 1000px $black inset;
 }
 
 .login-form-group {
@@ -46,6 +54,8 @@ select:-webkit-autofill:focus {
   border: 1px solid $login-border;
   border-radius: 4px;
   opacity: 0.6;
+  background: $black;
+  color: #fbfbfb;
 
   &:focus {
     border: 1px solid $login-border;
@@ -103,7 +113,7 @@ select:-webkit-autofill:focus {
   }
 
   .icon-gf-grafana_wordmark {
-    color: $link-color;
+    color: darken($white, 11%);
     position: relative;
     font-size: 2rem;
     text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.3);

+ 6 - 8
public/test/core/utils/version_specs.ts → public/test/core/utils/version_jest.ts

@@ -1,5 +1,3 @@
-import {describe, beforeEach, it, expect} from 'test/lib/common';
-
 import {SemVersion, isVersionGtOrEq} from 'app/core/utils/version';
 
 describe("SemVersion", () => {
@@ -8,10 +6,10 @@ describe("SemVersion", () => {
   describe('parsing', () => {
     it('should parse version properly', () => {
       let semver = new SemVersion(version);
-      expect(semver.major).to.be(1);
-      expect(semver.minor).to.be(0);
-      expect(semver.patch).to.be(0);
-      expect(semver.meta).to.be('alpha.1');
+      expect(semver.major).toBe(1);
+      expect(semver.minor).toBe(0);
+      expect(semver.patch).toBe(0);
+      expect(semver.meta).toBe('alpha.1');
     });
   });
 
@@ -30,7 +28,7 @@ describe("SemVersion", () => {
         {value: '3.5', expected: false},
       ];
       cases.forEach((testCase) => {
-        expect(semver.isGtOrEq(testCase.value)).to.be(testCase.expected);
+        expect(semver.isGtOrEq(testCase.value)).toBe(testCase.expected);
       });
     });
   });
@@ -48,7 +46,7 @@ describe("SemVersion", () => {
         {values: ['3.4.5', '3.5'], expected: false},
       ];
       cases.forEach((testCase) => {
-        expect(isVersionGtOrEq(testCase.values[0], testCase.values[1])).to.be(testCase.expected);
+        expect(isVersionGtOrEq(testCase.values[0], testCase.values[1])).toBe(testCase.expected);
       });
     });
   });

+ 18 - 0
public/test/jest-setup.ts

@@ -1,4 +1,22 @@
 import { configure } from 'enzyme';
 import Adapter from 'enzyme-adapter-react-16';
+import 'jquery';
+import $ from 'jquery';
+import 'angular';
+import angular from 'angular';
+
+angular.module('grafana', ['ngRoute']);
+angular.module('grafana.services', ['ngRoute', '$strap.directives']);
+angular.module('grafana.panels', []);
+angular.module('grafana.controllers', []);
+angular.module('grafana.directives', []);
+angular.module('grafana.filters', []);
+angular.module('grafana.routes', ['ngRoute']);
+
+jest.mock('app/core/core', () => ({}));
+jest.mock('app/features/plugins/plugin_loader', () => ({}));
 
 configure({ adapter: new Adapter() });
+
+var global = <any>window;
+global.$ = global.jQuery = $;

+ 1 - 1
scripts/webpack/webpack.hot.js

@@ -23,7 +23,7 @@ module.exports = merge(common, {
   },
 
   resolve: {
-    extensions: ['.scss', '.ts', '.tsx', '.es6', '.js', '.json', '.svg', '.woff2', '.png'],
+    extensions: ['.scss', '.ts', '.tsx', '.es6', '.js', '.json', '.svg', '.woff2', '.png', '.html'],
   },
 
   devtool: 'eval-source-map',

Plik diff jest za duży
+ 531 - 106
vendor/github.com/mattn/go-sqlite3/sqlite3-binding.c


Plik diff jest za duży
+ 529 - 104
vendor/github.com/mattn/go-sqlite3/sqlite3-binding.h


+ 60 - 32
vendor/github.com/mattn/go-sqlite3/sqlite3.go

@@ -1,3 +1,5 @@
+// +build cgo
+
 // Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
 //
 // Use of this source code is governed by an MIT-style
@@ -7,10 +9,13 @@ package sqlite3
 
 /*
 #cgo CFLAGS: -std=gnu99
-#cgo CFLAGS: -DSQLITE_ENABLE_RTREE -DSQLITE_THREADSAFE=1
+#cgo CFLAGS: -DSQLITE_ENABLE_RTREE -DSQLITE_THREADSAFE=1 -DHAVE_USLEEP=1
+#cgo linux,!android CFLAGS: -DHAVE_PREAD64=1 -DHAVE_PWRITE64=1
 #cgo CFLAGS: -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_FTS4_UNICODE61
 #cgo CFLAGS: -DSQLITE_TRACE_SIZE_LIMIT=15
+#cgo CFLAGS: -DSQLITE_OMIT_DEPRECATED
 #cgo CFLAGS: -DSQLITE_DISABLE_INTRINSIC
+#cgo CFLAGS: -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT
 #cgo CFLAGS: -Wno-deprecated-declarations
 #ifndef USE_LIBSQLITE3
 #include <sqlite3-binding.h>
@@ -170,6 +175,12 @@ var SQLiteTimestampFormats = []string{
 	"2006-01-02",
 }
 
+const (
+	columnDate      string = "date"
+	columnDatetime  string = "datetime"
+	columnTimestamp string = "timestamp"
+)
+
 func init() {
 	sql.Register("sqlite3", &SQLiteDriver{})
 }
@@ -389,7 +400,7 @@ func (c *SQLiteConn) RegisterCommitHook(callback func() int) {
 	if callback == nil {
 		C.sqlite3_commit_hook(c.db, nil, nil)
 	} else {
-		C.sqlite3_commit_hook(c.db, (*[0]byte)(unsafe.Pointer(C.commitHookTrampoline)), unsafe.Pointer(newHandle(c, callback)))
+		C.sqlite3_commit_hook(c.db, (*[0]byte)(C.commitHookTrampoline), unsafe.Pointer(newHandle(c, callback)))
 	}
 }
 
@@ -402,7 +413,7 @@ func (c *SQLiteConn) RegisterRollbackHook(callback func()) {
 	if callback == nil {
 		C.sqlite3_rollback_hook(c.db, nil, nil)
 	} else {
-		C.sqlite3_rollback_hook(c.db, (*[0]byte)(unsafe.Pointer(C.rollbackHookTrampoline)), unsafe.Pointer(newHandle(c, callback)))
+		C.sqlite3_rollback_hook(c.db, (*[0]byte)(C.rollbackHookTrampoline), unsafe.Pointer(newHandle(c, callback)))
 	}
 }
 
@@ -419,7 +430,7 @@ func (c *SQLiteConn) RegisterUpdateHook(callback func(int, string, string, int64
 	if callback == nil {
 		C.sqlite3_update_hook(c.db, nil, nil)
 	} else {
-		C.sqlite3_update_hook(c.db, (*[0]byte)(unsafe.Pointer(C.updateHookTrampoline)), unsafe.Pointer(newHandle(c, callback)))
+		C.sqlite3_update_hook(c.db, (*[0]byte)(C.updateHookTrampoline), unsafe.Pointer(newHandle(c, callback)))
 	}
 }
 
@@ -501,7 +512,7 @@ func (c *SQLiteConn) RegisterFunc(name string, impl interface{}, pure bool) erro
 }
 
 func sqlite3CreateFunction(db *C.sqlite3, zFunctionName *C.char, nArg C.int, eTextRep C.int, pApp uintptr, xFunc unsafe.Pointer, xStep unsafe.Pointer, xFinal unsafe.Pointer) C.int {
-	return C._sqlite3_create_function(db, zFunctionName, nArg, eTextRep, C.uintptr_t(pApp), (*[0]byte)(unsafe.Pointer(xFunc)), (*[0]byte)(unsafe.Pointer(xStep)), (*[0]byte)(unsafe.Pointer(xFinal)))
+	return C._sqlite3_create_function(db, zFunctionName, nArg, eTextRep, C.uintptr_t(pApp), (*[0]byte)(xFunc), (*[0]byte)(xStep), (*[0]byte)(xFinal))
 }
 
 // RegisterAggregator makes a Go type available as a SQLite aggregation function.
@@ -780,6 +791,8 @@ func errorString(err Error) string {
 //     Enable or disable enforcement of foreign keys.  X can be 1 or 0.
 //   _recursive_triggers=X
 //     Enable or disable recursive triggers.  X can be 1 or 0.
+//   _mutex=XXX
+//     Specify mutex mode. XXX can be "no", "full".
 func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
 	if C.sqlite3_threadsafe() == 0 {
 		return nil, errors.New("sqlite library was not compiled for thread-safe operation")
@@ -790,6 +803,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
 	busyTimeout := 5000
 	foreignKeys := -1
 	recursiveTriggers := -1
+	mutex := C.int(C.SQLITE_OPEN_FULLMUTEX)
 	pos := strings.IndexRune(dsn, '?')
 	if pos >= 1 {
 		params, err := url.ParseQuery(dsn[pos+1:])
@@ -856,6 +870,18 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
 			}
 		}
 
+		// _mutex
+		if val := params.Get("_mutex"); val != "" {
+			switch val {
+			case "no":
+				mutex = C.SQLITE_OPEN_NOMUTEX
+			case "full":
+				mutex = C.SQLITE_OPEN_FULLMUTEX
+			default:
+				return nil, fmt.Errorf("Invalid _mutex: %v", val)
+			}
+		}
+
 		if !strings.HasPrefix(dsn, "file:") {
 			dsn = dsn[:pos]
 		}
@@ -865,9 +891,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
 	name := C.CString(dsn)
 	defer C.free(unsafe.Pointer(name))
 	rv := C._sqlite3_open_v2(name, &db,
-		C.SQLITE_OPEN_FULLMUTEX|
-			C.SQLITE_OPEN_READWRITE|
-			C.SQLITE_OPEN_CREATE,
+		mutex|C.SQLITE_OPEN_READWRITE|C.SQLITE_OPEN_CREATE,
 		nil)
 	if rv != 0 {
 		return nil, Error{Code: ErrNo(rv)}
@@ -1070,7 +1094,7 @@ func (s *SQLiteStmt) bind(args []namedValue) error {
 		case int64:
 			rv = C.sqlite3_bind_int64(s.s, n, C.sqlite3_int64(v))
 		case bool:
-			if bool(v) {
+			if v {
 				rv = C.sqlite3_bind_int(s.s, n, 1)
 			} else {
 				rv = C.sqlite3_bind_int(s.s, n, 0)
@@ -1121,18 +1145,20 @@ func (s *SQLiteStmt) query(ctx context.Context, args []namedValue) (driver.Rows,
 		done:     make(chan struct{}),
 	}
 
-	go func(db *C.sqlite3) {
-		select {
-		case <-ctx.Done():
+	if ctxdone := ctx.Done(); ctxdone != nil {
+		go func(db *C.sqlite3) {
 			select {
+			case <-ctxdone:
+				select {
+				case <-rows.done:
+				default:
+					C.sqlite3_interrupt(db)
+					rows.Close()
+				}
 			case <-rows.done:
-			default:
-				C.sqlite3_interrupt(db)
-				rows.Close()
 			}
-		case <-rows.done:
-		}
-	}(s.c.db)
+		}(s.c.db)
+	}
 
 	return rows, nil
 }
@@ -1166,19 +1192,21 @@ func (s *SQLiteStmt) exec(ctx context.Context, args []namedValue) (driver.Result
 		return nil, err
 	}
 
-	done := make(chan struct{})
-	defer close(done)
-	go func(db *C.sqlite3) {
-		select {
-		case <-done:
-		case <-ctx.Done():
+	if ctxdone := ctx.Done(); ctxdone != nil {
+		done := make(chan struct{})
+		defer close(done)
+		go func(db *C.sqlite3) {
 			select {
 			case <-done:
-			default:
-				C.sqlite3_interrupt(db)
+			case <-ctxdone:
+				select {
+				case <-done:
+				default:
+					C.sqlite3_interrupt(db)
+				}
 			}
-		}
-	}(s.c.db)
+		}(s.c.db)
+	}
 
 	var rowid, changes C.longlong
 	rv := C._sqlite3_step(s.s, &rowid, &changes)
@@ -1272,7 +1300,7 @@ func (rc *SQLiteRows) Next(dest []driver.Value) error {
 		case C.SQLITE_INTEGER:
 			val := int64(C.sqlite3_column_int64(rc.s.s, C.int(i)))
 			switch rc.decltype[i] {
-			case "timestamp", "datetime", "date":
+			case columnTimestamp, columnDatetime, columnDate:
 				var t time.Time
 				// Assume a millisecond unix timestamp if it's 13 digits -- too
 				// large to be a reasonable timestamp in seconds.
@@ -1303,10 +1331,10 @@ func (rc *SQLiteRows) Next(dest []driver.Value) error {
 			n := int(C.sqlite3_column_bytes(rc.s.s, C.int(i)))
 			switch dest[i].(type) {
 			case sql.RawBytes:
-				dest[i] = (*[1 << 30]byte)(unsafe.Pointer(p))[0:n]
+				dest[i] = (*[1 << 30]byte)(p)[0:n]
 			default:
 				slice := make([]byte, n)
-				copy(slice[:], (*[1 << 30]byte)(unsafe.Pointer(p))[0:n])
+				copy(slice[:], (*[1 << 30]byte)(p)[0:n])
 				dest[i] = slice
 			}
 		case C.SQLITE_NULL:
@@ -1319,7 +1347,7 @@ func (rc *SQLiteRows) Next(dest []driver.Value) error {
 			s := C.GoStringN((*C.char)(unsafe.Pointer(C.sqlite3_column_text(rc.s.s, C.int(i)))), C.int(n))
 
 			switch rc.decltype[i] {
-			case "timestamp", "datetime", "date":
+			case columnTimestamp, columnDatetime, columnDate:
 				var t time.Time
 				s = strings.TrimSuffix(s, "Z")
 				for _, format := range SQLiteTimestampFormats {

+ 2 - 0
vendor/github.com/mattn/go-sqlite3/sqlite3_go18.go

@@ -1,3 +1,5 @@
+// +build cgo
+
 // Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
 //
 // Use of this source code is governed by an MIT-style

+ 12 - 0
vendor/github.com/mattn/go-sqlite3/sqlite3_solaris.go

@@ -0,0 +1,12 @@
+// Copyright (C) 2018 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
+//
+// Use of this source code is governed by an MIT-style
+// license that can be found in the LICENSE file.
+// +build solaris
+
+package sqlite3
+
+/*
+#cgo CFLAGS: -D__EXTENSIONS__=1
+*/
+import "C"

+ 12 - 10
vendor/github.com/mattn/go-sqlite3/sqlite3_trace.go

@@ -28,10 +28,10 @@ import (
 // Trace... constants identify the possible events causing callback invocation.
 // Values are same as the corresponding SQLite Trace Event Codes.
 const (
-	TraceStmt    = C.SQLITE_TRACE_STMT
-	TraceProfile = C.SQLITE_TRACE_PROFILE
-	TraceRow     = C.SQLITE_TRACE_ROW
-	TraceClose   = C.SQLITE_TRACE_CLOSE
+	TraceStmt    = uint32(C.SQLITE_TRACE_STMT)
+	TraceProfile = uint32(C.SQLITE_TRACE_PROFILE)
+	TraceRow     = uint32(C.SQLITE_TRACE_ROW)
+	TraceClose   = uint32(C.SQLITE_TRACE_CLOSE)
 )
 
 type TraceInfo struct {
@@ -71,7 +71,7 @@ type TraceUserCallback func(TraceInfo) int
 
 type TraceConfig struct {
 	Callback        TraceUserCallback
-	EventMask       C.uint
+	EventMask       uint32
 	WantExpandedSQL bool
 }
 
@@ -105,6 +105,8 @@ func traceCallbackTrampoline(
 	// Parameter named 'X' in SQLite docs (eXtra event data?):
 	xValue unsafe.Pointer) C.int {
 
+	eventCode := uint32(traceEventCode)
+
 	if ctx == nil {
 		panic(fmt.Sprintf("No context (ev 0x%x)", traceEventCode))
 	}
@@ -114,7 +116,7 @@ func traceCallbackTrampoline(
 
 	var traceConf TraceConfig
 	var found bool
-	if traceEventCode == TraceClose {
+	if eventCode == TraceClose {
 		// clean up traceMap: 'pop' means get and delete
 		traceConf, found = popTraceMapping(connHandle)
 	} else {
@@ -123,16 +125,16 @@ func traceCallbackTrampoline(
 
 	if !found {
 		panic(fmt.Sprintf("Mapping not found for handle 0x%x (ev 0x%x)",
-			connHandle, traceEventCode))
+			connHandle, eventCode))
 	}
 
 	var info TraceInfo
 
-	info.EventCode = uint32(traceEventCode)
+	info.EventCode = eventCode
 	info.AutoCommit = (int(C.sqlite3_get_autocommit(contextDB)) != 0)
 	info.ConnHandle = connHandle
 
-	switch traceEventCode {
+	switch eventCode {
 	case TraceStmt:
 		info.StmtHandle = uintptr(p)
 
@@ -183,7 +185,7 @@ func traceCallbackTrampoline(
 	// registering this callback trampoline with SQLite --- for cleanup.
 	// In the future there may be more events forced to "selected" in SQLite
 	// for the driver's needs.
-	if traceConf.EventMask&traceEventCode == 0 {
+	if traceConf.EventMask&eventCode == 0 {
 		return 0
 	}
 

+ 7 - 0
vendor/github.com/mattn/go-sqlite3/sqlite3ext.h

@@ -293,6 +293,9 @@ struct sqlite3_api_routines {
   int (*bind_pointer)(sqlite3_stmt*,int,void*,const char*,void(*)(void*));
   void (*result_pointer)(sqlite3_context*,void*,const char*,void(*)(void*));
   void *(*value_pointer)(sqlite3_value*,const char*);
+  int (*vtab_nochange)(sqlite3_context*);
+  int (*value_nochange)(sqlite3_value*);
+  const char *(*vtab_collation)(sqlite3_index_info*,int);
 };
 
 /*
@@ -559,6 +562,10 @@ typedef int (*sqlite3_loadext_entry)(
 #define sqlite3_bind_pointer           sqlite3_api->bind_pointer
 #define sqlite3_result_pointer         sqlite3_api->result_pointer
 #define sqlite3_value_pointer          sqlite3_api->value_pointer
+/* Version 3.22.0 and later */
+#define sqlite3_vtab_nochange          sqlite3_api->vtab_nochange
+#define sqlite3_value_nochange         sqlite3_api->value_nochange
+#define sqlite3_vtab_collation         sqlite3_api->vtab_collation
 #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
 
 #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)

+ 21 - 0
vendor/github.com/mattn/go-sqlite3/static_mock.go

@@ -0,0 +1,21 @@
+// +build !cgo
+
+package sqlite3
+
+import (
+	"database/sql"
+	"database/sql/driver"
+	"errors"
+)
+
+func init() {
+	sql.Register("sqlite3", &SQLiteDriverMock{})
+}
+
+type SQLiteDriverMock struct{}
+
+var errorMsg = errors.New("Binary was compiled with 'CGO_ENABLED=0', go-sqlite3 requires cgo to work. This is a stub")
+
+func (SQLiteDriverMock) Open(s string) (driver.Conn, error) {
+	return nil, errorMsg
+}

+ 0 - 4
yarn.lock

@@ -7928,10 +7928,6 @@ pako@~1.0.5:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258"
 
-papaparse@^4.4.0:
-  version "4.4.0"
-  resolved "https://registry.yarnpkg.com/papaparse/-/papaparse-4.4.0.tgz#6bcdbda80873e00cfb0bdcd7a4571c72a9a40168"
-
 parallel-transform@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06"

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików