Просмотр исходного кода

Merge branch 'master' into heatmap-panel

Alexander Zobnin 8 лет назад
Родитель
Сommit
84b15fc74c
35 измененных файлов с 346 добавлено и 51 удалено
  1. 1 0
      CHANGELOG.md
  2. 3 1
      conf/defaults.ini
  3. 3 1
      conf/sample.ini
  4. 11 11
      docs/sources/guides/whats-new-in-v4.md
  5. 9 0
      docs/sources/installation/configuration.md
  6. 11 10
      pkg/api/frontendsettings.go
  7. 1 0
      pkg/services/alerting/notifiers/slack.go
  8. 16 1
      pkg/services/sqlstore/migrations/alert_mig.go
  9. 10 0
      pkg/services/sqlstore/migrations/annotation_mig.go
  10. 9 3
      pkg/services/sqlstore/migrations/apikey_mig.go
  11. 13 2
      pkg/services/sqlstore/migrations/dashboard_mig.go
  12. 11 3
      pkg/services/sqlstore/migrations/dashboard_snapshot_mig.go
  13. 16 2
      pkg/services/sqlstore/migrations/datasource_mig.go
  14. 16 1
      pkg/services/sqlstore/migrations/org_mig.go
  15. 11 0
      pkg/services/sqlstore/migrations/playlist_mig.go
  16. 7 1
      pkg/services/sqlstore/migrations/plugin_setting.go
  17. 5 0
      pkg/services/sqlstore/migrations/preferences_mig.go
  18. 5 1
      pkg/services/sqlstore/migrations/quota_mig.go
  19. 9 0
      pkg/services/sqlstore/migrations/temp_user.go
  20. 15 4
      pkg/services/sqlstore/migrations/user_mig.go
  21. 6 1
      pkg/services/sqlstore/migrator/dialect.go
  22. 14 0
      pkg/services/sqlstore/migrator/migrations.go
  23. 22 1
      pkg/services/sqlstore/migrator/mysql_dialect.go
  24. 11 0
      pkg/services/sqlstore/migrator/postgres_dialect.go
  25. 1 4
      pkg/services/sqlstore/sqlstore.go
  26. 1 1
      pkg/services/sqlstore/sqlutil/sqlutil.go
  27. 2 0
      pkg/setting/setting.go
  28. 1 1
      public/app/core/components/sidemenu/sidemenu.ts
  29. 16 2
      public/app/features/panel/panel_directive.ts
  30. 11 0
      public/app/plugins/datasource/elasticsearch/bucket_agg.js
  31. 11 0
      public/app/plugins/datasource/elasticsearch/partials/bucket_agg.html
  32. 17 0
      public/app/plugins/datasource/elasticsearch/query_builder.js
  33. 1 0
      public/app/plugins/datasource/elasticsearch/query_def.js
  34. 33 0
      public/app/plugins/datasource/elasticsearch/specs/elastic_response_specs.ts
  35. 17 0
      public/app/plugins/datasource/elasticsearch/specs/query_builder_specs.ts

+ 1 - 0
CHANGELOG.md

@@ -6,6 +6,7 @@
 * **InfluxDB**: Small fix for the "glow" when focus the field for LIMIT and SLIMIT [#7799](https://github.com/grafana/grafana/pull/7799) thx [@thuck](https://github.com/thuck)
 * **Panels**: Delay loading & Lazy load panels as they become visible (scrolled into view) [#5216](https://github.com/grafana/grafana/issues/5216) thx [@jifwin](https://github.com/jifwin)
 * **Graph**: Support auto grid min/max when using log scale [#3090](https://github.com/grafana/grafana/issues/3090), thx [@bigbenhur](https://github.com/bigbenhur)
+* **Elasticsearch**: Support histogram aggregations [#3164](https://github.com/grafana/grafana/issues/3164)
 
 ## Minor Enchancements
 

+ 3 - 1
conf/defaults.ini

@@ -74,7 +74,6 @@ password =
 url =
 
 # Max conn setting default is 0 (mean not set)
-max_conn =
 max_idle_conn =
 max_open_conn =
 
@@ -206,6 +205,9 @@ default_theme = dark
 # Set to true to disable (hide) the login form, useful if you use OAuth
 disable_login_form = false
 
+# Set to true to disable the signout link in the side menu. useful if you use auth.proxy
+disable_signout_menu = false
+
 #################################### Anonymous Auth ######################
 [auth.anonymous]
 # enable anonymous access

+ 3 - 1
conf/sample.ini

@@ -83,7 +83,6 @@
 ;path = grafana.db
 
 # Max conn setting default is 0 (mean not set)
-;max_conn =
 ;max_idle_conn =
 ;max_open_conn =
 
@@ -193,6 +192,9 @@
 # Set to true to disable (hide) the login form, useful if you use OAuth, defaults to false
 ;disable_login_form = false
 
+# Set to true to disable the signout link in the side menu. useful if you use auth.proxy, defaults to false
+;disable_signout_menu = false
+
 #################################### Anonymous Auth ##########################
 [auth.anonymous]
 # enable anonymous access

+ 11 - 11
docs/sources/guides/whats-new-in-v4.md

@@ -27,7 +27,7 @@ notifications will be sent out when the rule conditions are met.
 
 This feature has been worked on for over a year with many iterations and rewrites
 just to make sure the foundations are really solid. We are really proud to finally release it!
-Since the alerting execution is processed in the backend all data source plugins are not supported.
+Since the alerting execution is processed in the backend not all data source plugins are supported.
 Right now Graphite, Prometheus, InfluxDB and OpenTSDB are supported. Elasticsearch is being worked
 on but will be not ready for v4 release.
 
@@ -70,8 +70,8 @@ to add graph comments in the form of annotations directly from within Grafana in
 
 {{< imgbox max-width="30%" img="/img/docs/v4/alert_list_panel.png" caption="Alert List Panel" >}}
 
-This new panel allows you to show alert rules or a history of alert rule state changes. You can filter based on states your
-interested in. Very useful panel for overview style dashboards.
+This new panel allows you to show alert rules or a history of alert rule state changes. You can filter based on states you are
+interested in. This panel is very useful for overview style dashboards.
 
 <div class="clearfix"></div>
 
@@ -79,8 +79,8 @@ interested in. Very useful panel for overview style dashboards.
 
 {{< imgbox max-width="30%" img="/img/docs/v4/adhoc_filters.gif" caption="Ad-hoc filters variable" >}}
 
-This is a new and very different type of template variable. It will allow you to create new key/value filters on the fly.
-With autocomplete for both key and values. The filter condition will be automatically applied to all
+This is a new and very different type of template variable. It will allow you to create new key/value filters on the fly
+with autocomplete for both key and values. The filter condition will be automatically applied to all
 queries that use that data source. This feature opens up more exploratory dashboards. In the gif animation to the right
 you have a dashboard for Elasticsearch log data. It uses one query variable that allow you to quickly change how the data
 is grouped, and an interval variable for controlling the granularity of the time buckets. What was missing
@@ -126,15 +126,15 @@ We always try to bring some UX/UI refinements & polish in every release.
 
 {{< imgbox max-width="50%" img="/img/docs/v4/add_panel.gif" caption="Add Panel flow" >}}
 
-We spent a lot of time improving the dashboard building experience. Trying to make it both
+We spent a lot of time improving the dashboard building experience to make it both
 more efficient and easier for beginners. After many good but not great experiments
 with a `build mode` we eventually decided to just improve the green row menu and
 continue work on a `build mode` for a future release.
 
 The new row menu automatically slides out when you mouse over the edge of the row. You no longer need
-to hover over the small green icon and the click it to expand the row menu.
+to hover over the small green icon and then click it to expand the row menu.
 
-There is some minor improvements to drag and drop behaviour. Now when dragging a panel from one row
+There are some minor improvements to drag and drop behaviour. Now when dragging a panel from one row
 to another you will insert the panel and Grafana will automatically make room for it.
 When you drag a panel within a row you will simply reorder the panels.
 
@@ -150,7 +150,7 @@ We plan to further improve dashboard building in the future with a more rich gri
 {{< imgbox max-width="40%" img="/img/docs/v4/shortcuts.png" caption="Shortcuts" >}}
 
 Grafana v4 introduces a number of really powerful keyboard shortcuts. You can now focus a panel
-by hovering over it with your mouse. With a panel focused you can simple hit `e` to toggle panel
+by hovering over it with your mouse. With a panel focused you can simply hit `e` to toggle panel
 edit mode, or `v` to toggle fullscreen mode. `p r` removes the panel. `p s` opens share
 modal.
 
@@ -164,10 +164,10 @@ Some nice navigation shortcuts are:
 
 ## Upgrade & Breaking changes
 
-There are no breaking changes. Old dashboards and features should work the same. Grafana-server will automatically upgrade it's db
+There are no breaking changes. Old dashboards and features should work the same. Grafana-server will automatically upgrade its db
 schema on restart. It's advisable to do a backup of Grafana's database before updating.
 
-If your are using plugins make sure to update your plugins as some might not work perfectly v4.
+If you are using plugins make sure to update your plugins as some might not work perfectly v4.
 
 You can update plugins using grafana-cli
 

+ 9 - 0
docs/sources/installation/configuration.md

@@ -267,6 +267,10 @@ options are `Admin` and `Editor` and `Read Only Editor`. e.g. :
 
 Set to true to disable (hide) the login form, useful if you use OAuth, defaults to false.
 
+### disable_signout_menu
+
+Set to true to disable the signout link in the side menu. useful if you use auth.proxy, defaults to false.
+
 <hr>
 
 ## [auth.anonymous]
@@ -427,6 +431,11 @@ Set to `true` to enable LDAP integration (default: `false`)
 ### config_file
 Path to the LDAP specific configuration file (default: `/etc/grafana/ldap.toml`)
 
+### allow_sign_up
+
+Allow sign up should almost always be true (default) to allow new Grafana users to be created (if ldap authentication is ok). If set to
+false only pre-existing Grafana users will be able to login (if ldap authentication is ok).
+
 > For details on LDAP Configuration, go to the [LDAP Integration]({{< relref "ldap.md" >}}) page.
 
 <hr>

+ 11 - 10
pkg/api/frontendsettings.go

@@ -133,16 +133,17 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
 	}
 
 	jsonObj := map[string]interface{}{
-		"defaultDatasource": defaultDatasource,
-		"datasources":       datasources,
-		"panels":            panels,
-		"appSubUrl":         setting.AppSubUrl,
-		"allowOrgCreate":    (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin,
-		"authProxyEnabled":  setting.AuthProxyEnabled,
-		"ldapEnabled":       setting.LdapEnabled,
-		"alertingEnabled":   setting.AlertingEnabled,
-		"googleAnalyticsId": setting.GoogleAnalyticsId,
-		"disableLoginForm":  setting.DisableLoginForm,
+		"defaultDatasource":  defaultDatasource,
+		"datasources":        datasources,
+		"panels":             panels,
+		"appSubUrl":          setting.AppSubUrl,
+		"allowOrgCreate":     (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin,
+		"authProxyEnabled":   setting.AuthProxyEnabled,
+		"ldapEnabled":        setting.LdapEnabled,
+		"alertingEnabled":    setting.AlertingEnabled,
+		"googleAnalyticsId":  setting.GoogleAnalyticsId,
+		"disableLoginForm":   setting.DisableLoginForm,
+		"disableSignoutMenu": setting.DisableSignoutMenu,
 		"buildInfo": map[string]interface{}{
 			"version":       setting.BuildVersion,
 			"commit":        setting.BuildCommit,

+ 1 - 0
pkg/services/alerting/notifiers/slack.go

@@ -116,6 +116,7 @@ func (this *SlackNotifier) Notify(evalContext *alerting.EvalContext) error {
 	body := map[string]interface{}{
 		"attachments": []map[string]interface{}{
 			{
+				"fallback":    evalContext.GetNotificationTitle(),
 				"color":       evalContext.GetStateModel().Color,
 				"title":       evalContext.GetNotificationTitle(),
 				"title_link":  ruleUrl,

+ 16 - 1
pkg/services/sqlstore/migrations/alert_mig.go

@@ -50,7 +50,7 @@ func addAlertMigrations(mg *Migrator) {
 		Columns: []*Column{
 			{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
 			{Name: "org_id", Type: DB_BigInt, Nullable: false},
-			{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
+			{Name: "name", Type: DB_NVarchar, Length: 190, Nullable: false},
 			{Name: "type", Type: DB_NVarchar, Length: 255, Nullable: false},
 			{Name: "settings", Type: DB_Text, Nullable: false},
 			{Name: "created", Type: DB_DateTime, Nullable: false},
@@ -67,4 +67,19 @@ func addAlertMigrations(mg *Migrator) {
 	}))
 	mg.AddMigration("add index alert_notification org_id & name", NewAddIndexMigration(alert_notification, alert_notification.Indices[0]))
 
+	mg.AddMigration("Update alert table charset", NewTableCharsetMigration("alert", []*Column{
+		{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
+		{Name: "message", Type: DB_Text, Nullable: false},
+		{Name: "state", Type: DB_NVarchar, Length: 255, Nullable: false},
+		{Name: "settings", Type: DB_Text, Nullable: false},
+		{Name: "severity", Type: DB_Text, Nullable: false},
+		{Name: "execution_error", Type: DB_Text, Nullable: false},
+		{Name: "eval_data", Type: DB_Text, Nullable: true},
+	}))
+
+	mg.AddMigration("Update alert_notification table charset", NewTableCharsetMigration("alert_notification", []*Column{
+		{Name: "name", Type: DB_NVarchar, Length: 190, Nullable: false},
+		{Name: "type", Type: DB_NVarchar, Length: 255, Nullable: false},
+		{Name: "settings", Type: DB_Text, Nullable: false},
+	}))
 }

+ 10 - 0
pkg/services/sqlstore/migrations/annotation_mig.go

@@ -44,4 +44,14 @@ func addAnnotationMig(mg *Migrator) {
 	mg.AddMigration("add index annotation 2 v3", NewAddIndexMigration(table, table.Indices[2]))
 	mg.AddMigration("add index annotation 3 v3", NewAddIndexMigration(table, table.Indices[3]))
 	mg.AddMigration("add index annotation 4 v3", NewAddIndexMigration(table, table.Indices[4]))
+
+	mg.AddMigration("Update annotation table charset", NewTableCharsetMigration("annotation", []*Column{
+		{Name: "type", Type: DB_NVarchar, Length: 25, Nullable: false},
+		{Name: "title", Type: DB_Text, Nullable: false},
+		{Name: "text", Type: DB_Text, Nullable: false},
+		{Name: "metric", Type: DB_NVarchar, Length: 255, Nullable: true},
+		{Name: "prev_state", Type: DB_NVarchar, Length: 25, Nullable: false},
+		{Name: "new_state", Type: DB_NVarchar, Length: 25, Nullable: false},
+		{Name: "data", Type: DB_Text, Nullable: false},
+	}))
 }

+ 9 - 3
pkg/services/sqlstore/migrations/apikey_mig.go

@@ -8,7 +8,7 @@ func addApiKeyMigrations(mg *Migrator) {
 		Columns: []*Column{
 			{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
 			{Name: "account_id", Type: DB_BigInt, Nullable: false},
-			{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
+			{Name: "name", Type: DB_NVarchar, Length: 190, Nullable: false},
 			{Name: "key", Type: DB_Varchar, Length: 64, Nullable: false},
 			{Name: "role", Type: DB_NVarchar, Length: 255, Nullable: false},
 			{Name: "created", Type: DB_DateTime, Nullable: false},
@@ -41,8 +41,8 @@ func addApiKeyMigrations(mg *Migrator) {
 		Columns: []*Column{
 			{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
 			{Name: "org_id", Type: DB_BigInt, Nullable: false},
-			{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
-			{Name: "key", Type: DB_Varchar, Length: 255, Nullable: false},
+			{Name: "name", Type: DB_NVarchar, Length: 190, Nullable: false},
+			{Name: "key", Type: DB_Varchar, Length: 190, Nullable: false},
 			{Name: "role", Type: DB_NVarchar, Length: 255, Nullable: false},
 			{Name: "created", Type: DB_DateTime, Nullable: false},
 			{Name: "updated", Type: DB_DateTime, Nullable: false},
@@ -72,4 +72,10 @@ func addApiKeyMigrations(mg *Migrator) {
 	}))
 
 	mg.AddMigration("Drop old table api_key_v1", NewDropTableMigration("api_key_v1"))
+
+	mg.AddMigration("Update api_key table charset", NewTableCharsetMigration("api_key", []*Column{
+		{Name: "name", Type: DB_NVarchar, Length: 190, Nullable: false},
+		{Name: "key", Type: DB_Varchar, Length: 190, Nullable: false},
+		{Name: "role", Type: DB_NVarchar, Length: 255, Nullable: false},
+	}))
 }

+ 13 - 2
pkg/services/sqlstore/migrations/dashboard_mig.go

@@ -8,7 +8,7 @@ func addDashboardMigration(mg *Migrator) {
 		Columns: []*Column{
 			{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
 			{Name: "version", Type: DB_Int, Nullable: false},
-			{Name: "slug", Type: DB_NVarchar, Length: 255, Nullable: false},
+			{Name: "slug", Type: DB_NVarchar, Length: 190, Nullable: false},
 			{Name: "title", Type: DB_NVarchar, Length: 255, Nullable: false},
 			{Name: "data", Type: DB_Text, Nullable: false},
 			{Name: "account_id", Type: DB_BigInt, Nullable: false},
@@ -56,7 +56,7 @@ func addDashboardMigration(mg *Migrator) {
 		Columns: []*Column{
 			{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
 			{Name: "version", Type: DB_Int, Nullable: false},
-			{Name: "slug", Type: DB_NVarchar, Length: 255, Nullable: false},
+			{Name: "slug", Type: DB_NVarchar, Length: 190, Nullable: false},
 			{Name: "title", Type: DB_NVarchar, Length: 255, Nullable: false},
 			{Name: "data", Type: DB_Text, Nullable: false},
 			{Name: "org_id", Type: DB_BigInt, Nullable: false},
@@ -125,4 +125,15 @@ func addDashboardMigration(mg *Migrator) {
 	mg.AddMigration("Add index for dashboard_id in dashboard_tag", NewAddIndexMigration(dashboardTagV1, &Index{
 		Cols: []string{"dashboard_id"}, Type: IndexType,
 	}))
+
+	mg.AddMigration("Update dashboard table charset", NewTableCharsetMigration("dashboard", []*Column{
+		{Name: "slug", Type: DB_NVarchar, Length: 190, Nullable: false},
+		{Name: "title", Type: DB_NVarchar, Length: 255, Nullable: false},
+		{Name: "plugin_id", Type: DB_NVarchar, Nullable: true, Length: 255},
+		{Name: "data", Type: DB_MediumText, Nullable: false},
+	}))
+
+	mg.AddMigration("Update dashboard_tag table charset", NewTableCharsetMigration("dashboard_tag", []*Column{
+		{Name: "term", Type: DB_NVarchar, Length: 50, Nullable: false},
+	}))
 }

+ 11 - 3
pkg/services/sqlstore/migrations/dashboard_snapshot_mig.go

@@ -8,7 +8,7 @@ func addDashboardSnapshotMigrations(mg *Migrator) {
 		Columns: []*Column{
 			{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
 			{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
-			{Name: "key", Type: DB_NVarchar, Length: 255, Nullable: false},
+			{Name: "key", Type: DB_NVarchar, Length: 190, Nullable: false},
 			{Name: "dashboard", Type: DB_Text, Nullable: false},
 			{Name: "expires", Type: DB_DateTime, Nullable: false},
 			{Name: "created", Type: DB_DateTime, Nullable: false},
@@ -28,8 +28,8 @@ func addDashboardSnapshotMigrations(mg *Migrator) {
 		Columns: []*Column{
 			{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
 			{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
-			{Name: "key", Type: DB_NVarchar, Length: 255, Nullable: false},
-			{Name: "delete_key", Type: DB_NVarchar, Length: 255, Nullable: false},
+			{Name: "key", Type: DB_NVarchar, Length: 190, Nullable: false},
+			{Name: "delete_key", Type: DB_NVarchar, Length: 190, Nullable: false},
 			{Name: "org_id", Type: DB_BigInt, Nullable: false},
 			{Name: "user_id", Type: DB_BigInt, Nullable: false},
 			{Name: "external", Type: DB_Bool, Nullable: false},
@@ -54,4 +54,12 @@ func addDashboardSnapshotMigrations(mg *Migrator) {
 		Sqlite("SELECT 0 WHERE 0;").
 		Postgres("SELECT 0;").
 		Mysql("ALTER TABLE dashboard_snapshot MODIFY dashboard MEDIUMTEXT;"))
+
+	mg.AddMigration("Update dashboard_snapshot table charset", NewTableCharsetMigration("dashboard_snapshot", []*Column{
+		{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
+		{Name: "key", Type: DB_NVarchar, Length: 190, Nullable: false},
+		{Name: "delete_key", Type: DB_NVarchar, Length: 190, Nullable: false},
+		{Name: "external_url", Type: DB_NVarchar, Length: 255, Nullable: false},
+		{Name: "dashboard", Type: DB_MediumText, Nullable: false},
+	}))
 }

+ 16 - 2
pkg/services/sqlstore/migrations/datasource_mig.go

@@ -10,7 +10,7 @@ func addDataSourceMigration(mg *Migrator) {
 			{Name: "account_id", Type: DB_BigInt, Nullable: false},
 			{Name: "version", Type: DB_Int, Nullable: false},
 			{Name: "type", Type: DB_NVarchar, Length: 255, Nullable: false},
-			{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
+			{Name: "name", Type: DB_NVarchar, Length: 190, Nullable: false},
 			{Name: "access", Type: DB_NVarchar, Length: 255, Nullable: false},
 			{Name: "url", Type: DB_NVarchar, Length: 255, Nullable: false},
 			{Name: "password", Type: DB_NVarchar, Length: 255, Nullable: true},
@@ -49,7 +49,7 @@ func addDataSourceMigration(mg *Migrator) {
 			{Name: "org_id", Type: DB_BigInt, Nullable: false},
 			{Name: "version", Type: DB_Int, Nullable: false},
 			{Name: "type", Type: DB_NVarchar, Length: 255, Nullable: false},
-			{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
+			{Name: "name", Type: DB_NVarchar, Length: 190, Nullable: false},
 			{Name: "access", Type: DB_NVarchar, Length: 255, Nullable: false},
 			{Name: "url", Type: DB_NVarchar, Length: 255, Nullable: false},
 			{Name: "password", Type: DB_NVarchar, Length: 255, Nullable: true},
@@ -106,4 +106,18 @@ func addDataSourceMigration(mg *Migrator) {
 	mg.AddMigration("Add secure json data column", NewAddColumnMigration(tableV2, &Column{
 		Name: "secure_json_data", Type: DB_Text, Nullable: true,
 	}))
+
+	mg.AddMigration("Update data_source table charset", NewTableCharsetMigration(tableV2.Name, []*Column{
+		{Name: "type", Type: DB_NVarchar, Length: 255, Nullable: false},
+		{Name: "name", Type: DB_NVarchar, Length: 190, Nullable: false},
+		{Name: "access", Type: DB_NVarchar, Length: 255, Nullable: false},
+		{Name: "url", Type: DB_NVarchar, Length: 255, Nullable: false},
+		{Name: "password", Type: DB_NVarchar, Length: 255, Nullable: true},
+		{Name: "user", Type: DB_NVarchar, Length: 255, Nullable: true},
+		{Name: "database", Type: DB_NVarchar, Length: 255, Nullable: true},
+		{Name: "basic_auth_user", Type: DB_NVarchar, Length: 255, Nullable: true},
+		{Name: "basic_auth_password", Type: DB_NVarchar, Length: 255, Nullable: true},
+		{Name: "json_data", Type: DB_Text, Nullable: true},
+		{Name: "secure_json_data", Type: DB_Text, Nullable: true},
+	}))
 }

+ 16 - 1
pkg/services/sqlstore/migrations/org_mig.go

@@ -8,7 +8,7 @@ func addOrgMigrations(mg *Migrator) {
 		Columns: []*Column{
 			{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
 			{Name: "version", Type: DB_Int, Nullable: false},
-			{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
+			{Name: "name", Type: DB_NVarchar, Length: 190, Nullable: false},
 			{Name: "address1", Type: DB_NVarchar, Length: 255, Nullable: true},
 			{Name: "address2", Type: DB_NVarchar, Length: 255, Nullable: true},
 			{Name: "city", Type: DB_NVarchar, Length: 255, Nullable: true},
@@ -68,4 +68,19 @@ func addOrgMigrations(mg *Migrator) {
 
 	mg.AddMigration("Drop old table account", NewDropTableMigration("account"))
 	mg.AddMigration("Drop old table account_user", NewDropTableMigration("account_user"))
+
+	mg.AddMigration("Update org table charset", NewTableCharsetMigration("org", []*Column{
+		{Name: "name", Type: DB_NVarchar, Length: 190, Nullable: false},
+		{Name: "address1", Type: DB_NVarchar, Length: 255, Nullable: true},
+		{Name: "address2", Type: DB_NVarchar, Length: 255, Nullable: true},
+		{Name: "city", Type: DB_NVarchar, Length: 255, Nullable: true},
+		{Name: "state", Type: DB_NVarchar, Length: 255, Nullable: true},
+		{Name: "zip_code", Type: DB_NVarchar, Length: 50, Nullable: true},
+		{Name: "country", Type: DB_NVarchar, Length: 255, Nullable: true},
+		{Name: "billing_email", Type: DB_NVarchar, Length: 255, Nullable: true},
+	}))
+
+	mg.AddMigration("Update org_user table charset", NewTableCharsetMigration("org_user", []*Column{
+		{Name: "role", Type: DB_NVarchar, Length: 20},
+	}))
 }

+ 11 - 0
pkg/services/sqlstore/migrations/playlist_mig.go

@@ -32,4 +32,15 @@ func addPlaylistMigrations(mg *Migrator) {
 	}
 
 	mg.AddMigration("create playlist item table v2", NewAddTableMigration(playlistItemV2))
+
+	mg.AddMigration("Update playlist table charset", NewTableCharsetMigration("playlist", []*Column{
+		{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
+		{Name: "interval", Type: DB_NVarchar, Length: 255, Nullable: false},
+	}))
+
+	mg.AddMigration("Update playlist_item table charset", NewTableCharsetMigration("playlist_item", []*Column{
+		{Name: "type", Type: DB_NVarchar, Length: 255, Nullable: false},
+		{Name: "value", Type: DB_Text, Nullable: false},
+		{Name: "title", Type: DB_Text, Nullable: false},
+	}))
 }

+ 7 - 1
pkg/services/sqlstore/migrations/plugin_setting.go

@@ -9,7 +9,7 @@ func addAppSettingsMigration(mg *Migrator) {
 		Columns: []*Column{
 			{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
 			{Name: "org_id", Type: DB_BigInt, Nullable: true},
-			{Name: "plugin_id", Type: DB_NVarchar, Length: 255, Nullable: false},
+			{Name: "plugin_id", Type: DB_NVarchar, Length: 190, Nullable: false},
 			{Name: "enabled", Type: DB_Bool, Nullable: false},
 			{Name: "pinned", Type: DB_Bool, Nullable: false},
 			{Name: "json_data", Type: DB_Text, Nullable: true},
@@ -32,4 +32,10 @@ func addAppSettingsMigration(mg *Migrator) {
 		Name: "plugin_version", Type: DB_NVarchar, Nullable: true, Length: 50,
 	}))
 
+	mg.AddMigration("Update plugin_setting table charset", NewTableCharsetMigration("plugin_setting", []*Column{
+		{Name: "plugin_id", Type: DB_NVarchar, Length: 190, Nullable: false},
+		{Name: "json_data", Type: DB_Text, Nullable: true},
+		{Name: "secure_json_data", Type: DB_Text, Nullable: true},
+		{Name: "plugin_version", Type: DB_NVarchar, Nullable: true, Length: 50},
+	}))
 }

+ 5 - 0
pkg/services/sqlstore/migrations/preferences_mig.go

@@ -29,4 +29,9 @@ func addPreferencesMigrations(mg *Migrator) {
 
 	// create table
 	mg.AddMigration("create preferences table v3", NewAddTableMigration(preferencesV2))
+
+	mg.AddMigration("Update preferences table charset", NewTableCharsetMigration("preferences", []*Column{
+		{Name: "timezone", Type: DB_NVarchar, Length: 50, Nullable: false},
+		{Name: "theme", Type: DB_NVarchar, Length: 20, Nullable: false},
+	}))
 }

+ 5 - 1
pkg/services/sqlstore/migrations/quota_mig.go

@@ -12,7 +12,7 @@ func addQuotaMigration(mg *Migrator) {
 			{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
 			{Name: "org_id", Type: DB_BigInt, Nullable: true},
 			{Name: "user_id", Type: DB_BigInt, Nullable: true},
-			{Name: "target", Type: DB_NVarchar, Length: 255, Nullable: false},
+			{Name: "target", Type: DB_NVarchar, Length: 190, Nullable: false},
 			{Name: "limit", Type: DB_BigInt, Nullable: false},
 			{Name: "created", Type: DB_DateTime, Nullable: false},
 			{Name: "updated", Type: DB_DateTime, Nullable: false},
@@ -25,4 +25,8 @@ func addQuotaMigration(mg *Migrator) {
 
 	//-------  indexes ------------------
 	addTableIndicesMigrations(mg, "v1", quotaV1)
+
+	mg.AddMigration("Update quota table charset", NewTableCharsetMigration("quota", []*Column{
+		{Name: "target", Type: DB_NVarchar, Length: 190, Nullable: false},
+	}))
 }

+ 9 - 0
pkg/services/sqlstore/migrations/temp_user.go

@@ -35,4 +35,13 @@ func addTempUserMigrations(mg *Migrator) {
 	// create table
 	mg.AddMigration("create temp user table v1-7", NewAddTableMigration(tempUserV1))
 	addTableIndicesMigrations(mg, "v1-7", tempUserV1)
+
+	mg.AddMigration("Update temp_user table charset", NewTableCharsetMigration("temp_user", []*Column{
+		{Name: "email", Type: DB_NVarchar, Length: 255},
+		{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: true},
+		{Name: "role", Type: DB_NVarchar, Length: 20, Nullable: true},
+		{Name: "code", Type: DB_NVarchar, Length: 255},
+		{Name: "status", Type: DB_Varchar, Length: 20},
+		{Name: "remote_addr", Type: DB_Varchar, Length: 255, Nullable: true},
+	}))
 }

+ 15 - 4
pkg/services/sqlstore/migrations/user_mig.go

@@ -8,8 +8,8 @@ func addUserMigrations(mg *Migrator) {
 		Columns: []*Column{
 			{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
 			{Name: "version", Type: DB_Int, Nullable: false},
-			{Name: "login", Type: DB_NVarchar, Length: 255, Nullable: false},
-			{Name: "email", Type: DB_NVarchar, Length: 255, Nullable: false},
+			{Name: "login", Type: DB_NVarchar, Length: 190, Nullable: false},
+			{Name: "email", Type: DB_NVarchar, Length: 190, Nullable: false},
 			{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: true},
 			{Name: "password", Type: DB_NVarchar, Length: 255, Nullable: true},
 			{Name: "salt", Type: DB_NVarchar, Length: 50, Nullable: true},
@@ -47,8 +47,8 @@ func addUserMigrations(mg *Migrator) {
 		Columns: []*Column{
 			{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
 			{Name: "version", Type: DB_Int, Nullable: false},
-			{Name: "login", Type: DB_NVarchar, Length: 255, Nullable: false},
-			{Name: "email", Type: DB_NVarchar, Length: 255, Nullable: false},
+			{Name: "login", Type: DB_NVarchar, Length: 190, Nullable: false},
+			{Name: "email", Type: DB_NVarchar, Length: 190, Nullable: false},
 			{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: true},
 			{Name: "password", Type: DB_NVarchar, Length: 255, Nullable: true},
 			{Name: "salt", Type: DB_NVarchar, Length: 50, Nullable: true},
@@ -92,4 +92,15 @@ func addUserMigrations(mg *Migrator) {
 	mg.AddMigration("Add column help_flags1 to user table", NewAddColumnMigration(userV2, &Column{
 		Name: "help_flags1", Type: DB_BigInt, Nullable: false, Default: "0",
 	}))
+
+	mg.AddMigration("Update user table charset", NewTableCharsetMigration("user", []*Column{
+		{Name: "login", Type: DB_NVarchar, Length: 190, Nullable: false},
+		{Name: "email", Type: DB_NVarchar, Length: 190, Nullable: false},
+		{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: true},
+		{Name: "password", Type: DB_NVarchar, Length: 255, Nullable: true},
+		{Name: "salt", Type: DB_NVarchar, Length: 50, Nullable: true},
+		{Name: "rands", Type: DB_NVarchar, Length: 50, Nullable: true},
+		{Name: "company", Type: DB_NVarchar, Length: 255, Nullable: true},
+		{Name: "theme", Type: DB_NVarchar, Length: 255, Nullable: true},
+	}))
 }

+ 6 - 1
pkg/services/sqlstore/migrator/dialect.go

@@ -29,6 +29,7 @@ type Dialect interface {
 
 	TableCheckSql(tableName string) (string, []interface{})
 	RenameTable(oldName string, newName string) string
+	UpdateTableSql(tableName string, columns []*Column) string
 }
 
 func NewDialect(name string) Dialect {
@@ -102,7 +103,7 @@ func (b *BaseDialect) CreateTableSql(table *Table) string {
 
 	sql = sql[:len(sql)-2] + ")"
 	if b.dialect.SupportEngine() {
-		sql += " ENGINE=InnoDB DEFAULT CHARSET UTF8 "
+		sql += " ENGINE=InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci"
 	}
 
 	sql += ";"
@@ -160,3 +161,7 @@ func (db *BaseDialect) DropIndexSql(tableName string, index *Index) string {
 	name = index.XName(tableName)
 	return fmt.Sprintf("DROP INDEX %v ON %s", quote(name), quote(tableName))
 }
+
+func (db *BaseDialect) UpdateTableSql(tableName string, columns []*Column) string {
+	return "-- NOT REQUIRED"
+}

+ 14 - 0
pkg/services/sqlstore/migrator/migrations.go

@@ -200,3 +200,17 @@ func (m *CopyTableDataMigration) IfTableExists(tableName string) *CopyTableDataM
 func (m *CopyTableDataMigration) Sql(d Dialect) string {
 	return d.CopyTableData(m.sourceTable, m.targetTable, m.sourceCols, m.targetCols)
 }
+
+type TableCharsetMigration struct {
+	MigrationBase
+	tableName string
+	columns   []*Column
+}
+
+func NewTableCharsetMigration(tableName string, columns []*Column) *TableCharsetMigration {
+	return &TableCharsetMigration{tableName: tableName, columns: columns}
+}
+
+func (m *TableCharsetMigration) Sql(d Dialect) string {
+	return d.UpdateTableSql(m.tableName, m.columns)
+}

+ 22 - 1
pkg/services/sqlstore/migrator/mysql_dialect.go

@@ -1,6 +1,9 @@
 package migrator
 
-import "strconv"
+import (
+	"strconv"
+	"strings"
+)
 
 type Mysql struct {
 	BaseDialect
@@ -76,6 +79,12 @@ func (db *Mysql) SqlType(c *Column) string {
 	} else if hasLen1 {
 		res += "(" + strconv.Itoa(c.Length) + ")"
 	}
+
+	switch c.Type {
+	case DB_Char, DB_Varchar, DB_NVarchar, DB_TinyText, DB_Text, DB_MediumText, DB_LongText:
+		res += " CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"
+	}
+
 	return res
 }
 
@@ -84,3 +93,15 @@ func (db *Mysql) TableCheckSql(tableName string) (string, []interface{}) {
 	sql := "SELECT `TABLE_NAME` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? and `TABLE_NAME`=?"
 	return sql, args
 }
+
+func (db *Mysql) UpdateTableSql(tableName string, columns []*Column) string {
+	var statements = []string{}
+
+	statements = append(statements, "DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci")
+
+	for _, col := range columns {
+		statements = append(statements, "MODIFY "+col.StringNoPk(db))
+	}
+
+	return "ALTER TABLE " + db.Quote(tableName) + " " + strings.Join(statements, ", ") + ";"
+}

+ 11 - 0
pkg/services/sqlstore/migrator/postgres_dialect.go

@@ -3,6 +3,7 @@ package migrator
 import (
 	"fmt"
 	"strconv"
+	"strings"
 )
 
 type Postgres struct {
@@ -112,3 +113,13 @@ func (db *Postgres) DropIndexSql(tableName string, index *Index) string {
 	idxName := index.XName(tableName)
 	return fmt.Sprintf("DROP INDEX %v", quote(idxName))
 }
+
+func (db *Postgres) UpdateTableSql(tableName string, columns []*Column) string {
+	var statements = []string{}
+
+	for _, col := range columns {
+		statements = append(statements, "ALTER "+db.QuoteStr()+col.Name+db.QuoteStr()+" TYPE "+db.SqlType(col))
+	}
+
+	return "ALTER TABLE " + db.Quote(tableName) + " " + strings.Join(statements, ", ") + ";"
+}

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

@@ -29,7 +29,6 @@ type DatabaseConfig struct {
 	ClientKeyPath                              string
 	ClientCertPath                             string
 	ServerCertName                             string
-	MaxConn                                    int
 	MaxOpenConn                                int
 	MaxIdleConn                                int
 }
@@ -115,7 +114,7 @@ func getEngine() (*xorm.Engine, error) {
 			protocol = "unix"
 		}
 
-		cnnstr = fmt.Sprintf("%s:%s@%s(%s)/%s?charset=utf8",
+		cnnstr = fmt.Sprintf("%s:%s@%s(%s)/%s?charset=utf8mb4",
 			DbCfg.User, DbCfg.Pwd, protocol, DbCfg.Host, DbCfg.Name)
 
 		if DbCfg.SslMode == "true" || DbCfg.SslMode == "skip-verify" {
@@ -157,7 +156,6 @@ func getEngine() (*xorm.Engine, error) {
 	if err != nil {
 		return nil, err
 	} else {
-		engine.SetMaxConns(DbCfg.MaxConn)
 		engine.SetMaxOpenConns(DbCfg.MaxOpenConn)
 		engine.SetMaxIdleConns(DbCfg.MaxIdleConn)
 		// engine.SetLogger(NewXormLogger(log.LvlInfo, log.New("sqlstore.xorm")))
@@ -191,7 +189,6 @@ func LoadConfig() {
 		DbCfg.Host = sec.Key("host").String()
 		DbCfg.Name = sec.Key("name").String()
 		DbCfg.User = sec.Key("user").String()
-		DbCfg.MaxConn = sec.Key("max_conn").MustInt(0)
 		DbCfg.MaxOpenConn = sec.Key("max_open_conn").MustInt(0)
 		DbCfg.MaxIdleConn = sec.Key("max_idle_conn").MustInt(0)
 		if len(DbCfg.Pwd) == 0 {

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

@@ -12,7 +12,7 @@ type TestDB struct {
 }
 
 var TestDB_Sqlite3 = TestDB{DriverName: "sqlite3", ConnStr: ":memory:?_loc=Local"}
-var TestDB_Mysql = TestDB{DriverName: "mysql", ConnStr: "grafana:password@tcp(localhost:3306)/grafana_tests?charset=utf8"}
+var TestDB_Mysql = TestDB{DriverName: "mysql", ConnStr: "grafana:password@tcp(localhost:3306)/grafana_tests?charset=utf8mb4"}
 var TestDB_Postgres = TestDB{DriverName: "postgres", ConnStr: "user=grafanatest password=grafanatest host=localhost port=5432 dbname=grafanatest sslmode=disable"}
 
 func CleanDB(x *xorm.Engine) {

+ 2 - 0
pkg/setting/setting.go

@@ -96,6 +96,7 @@ var (
 	LoginHint          string
 	DefaultTheme       string
 	DisableLoginForm   bool
+	DisableSignoutMenu bool
 
 	// Http auth
 	AdminUser     string
@@ -528,6 +529,7 @@ func NewConfigContext(args *CommandLineArgs) error {
 	// auth
 	auth := Cfg.Section("auth")
 	DisableLoginForm = auth.Key("disable_login_form").MustBool(false)
+	DisableSignoutMenu = auth.Key("disable_signout_menu").MustBool(false)
 
 	// anonymous access
 	AnonymousEnabled = Cfg.Section("auth.anonymous").Key("enabled").MustBool(false)

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

@@ -23,7 +23,7 @@ export class SideMenuCtrl {
     this.isSignedIn = contextSrv.isSignedIn;
     this.user = contextSrv.user;
     this.appSubUrl = config.appSubUrl;
-    this.showSignout = this.contextSrv.isSignedIn && !config['authProxyEnabled'];
+    this.showSignout = this.contextSrv.isSignedIn && !config['disableSignoutMenu'];
     this.maxShownOrgs = 10;
 
     this.mainLinks = config.bootData.mainNavLinks;

+ 16 - 2
public/app/features/panel/panel_directive.ts

@@ -156,11 +156,21 @@ module.directive('grafanaPanel', function($rootScope, $document) {
             content: function() {
               return ctrl.getInfoContent({mode: 'tooltip'});
             },
-            position: 'top center',
             classes: ctrl.error ? 'drop-error' : 'drop-help',
             openOn: 'hover',
             hoverOpenDelay: 100,
-            constrainToScrollParent: false,
+            remove: true,
+            tetherOptions: {
+              attachment: 'bottom left',
+              targetAttachment: 'top left',
+              constraints: [
+                {
+                  to: 'window',
+                  attachment: 'together',
+                  pin: true
+                }
+              ],
+            }
           });
         }
       }
@@ -185,6 +195,10 @@ module.directive('grafanaPanel', function($rootScope, $document) {
         if (ctrl.skippedLastRefresh) {
           ctrl.refresh();
         }
+
+        if (infoDrop) {
+          infoDrop.position();
+        }
       };
 
       $document.on('scroll', refreshOnScroll);

+ 11 - 0
public/app/plugins/datasource/elasticsearch/bucket_agg.js

@@ -50,6 +50,7 @@ function (angular, _, queryDef) {
 
       switch($scope.agg.type) {
         case 'date_histogram':
+        case 'histogram':
         case 'terms':  {
           delete $scope.agg.query;
           $scope.agg.field = 'select field';
@@ -132,6 +133,16 @@ function (angular, _, queryDef) {
           }
           break;
         }
+        case 'histogram': {
+          settings.interval = settings.interval || 1000;
+          settings.min_doc_count = _.defaultTo(settings.min_doc_count, 1);
+          settingsLinkText = 'Interval: ' + settings.interval;
+
+          if (settings.min_doc_count > 0) {
+            settingsLinkText += ', Min Doc Count: ' + settings.min_doc_count;
+          }
+          break;
+        }
         case 'geohash_grid': {
           // limit precision to 7
           settings.precision = Math.max(Math.min(settings.precision, 7), 1);

+ 11 - 0
public/app/plugins/datasource/elasticsearch/partials/bucket_agg.html

@@ -52,6 +52,17 @@
 		</div>
 	</div>
 
+	<div ng-if="agg.type === 'histogram'">
+		<div class="gf-form offset-width-7">
+			<label class="gf-form-label width-10">Interval</label>
+			<input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.interval" ng-blur="onChangeInternal()">
+		</div>
+		<div class="gf-form offset-width-7">
+			<label class="gf-form-label width-10">Min Doc Count</label>
+			<input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.min_doc_count" ng-blur="onChangeInternal()">
+		</div>
+	</div>
+
 	<div ng-if="agg.type === 'terms'">
 		<div class="gf-form offset-width-7">
 			<label class="gf-form-label width-10">Order</label>

+ 17 - 0
public/app/plugins/datasource/elasticsearch/query_builder.js

@@ -79,6 +79,19 @@ function (queryDef) {
     return esAgg;
   };
 
+  ElasticQueryBuilder.prototype.getHistogramAgg = function(aggDef) {
+    var esAgg = {};
+    var settings = aggDef.settings || {};
+    esAgg.interval = settings.interval;
+    esAgg.field = aggDef.field;
+    esAgg.min_doc_count = settings.min_doc_count || 0;
+
+    if (settings.missing) {
+      esAgg.missing = settings.missing;
+    }
+    return esAgg;
+  };
+
   ElasticQueryBuilder.prototype.getFiltersAgg = function(aggDef) {
     var filterObj = {};
     for (var i = 0; i < aggDef.settings.filters.length; i++) {
@@ -192,6 +205,10 @@ function (queryDef) {
           esAgg["date_histogram"] = this.getDateHistogramAgg(aggDef);
           break;
         }
+        case 'histogram': {
+          esAgg["histogram"] = this.getHistogramAgg(aggDef);
+          break;
+        }
         case 'filters': {
           esAgg["filters"] = {filters: this.getFiltersAgg(aggDef)};
           break;

+ 1 - 0
public/app/plugins/datasource/elasticsearch/query_def.js

@@ -24,6 +24,7 @@ function (_) {
       {text: "Filters",         value: 'filters' },
       {text: "Geo Hash Grid",   value: 'geohash_grid', requiresField: true},
       {text: "Date Histogram",  value: 'date_histogram', requiresField: true},
+      {text: "Histogram",       value: 'histogram', requiresField: true},
     ],
 
     orderByOptions: [

+ 33 - 0
public/app/plugins/datasource/elasticsearch/specs/elastic_response_specs.ts

@@ -361,6 +361,39 @@ describe('ElasticResponse', function() {
     });
   });
 
+  describe('histogram response', function() {
+    var result;
+
+    beforeEach(function() {
+      targets = [{
+        refId: 'A',
+        metrics: [{type: 'count', id: '1'}],
+        bucketAggs: [{type: 'histogram', field: 'bytes', id: '3'}],
+      }];
+      response =  {
+        responses: [{
+          aggregations: {
+            "3": {
+              buckets: [
+                {doc_count: 1, key: 1000},
+                {doc_count: 3, key: 2000},
+                {doc_count: 2, key: 1000},
+              ]
+            }
+          }
+        }]
+      };
+
+      result = new ElasticResponse(targets, response).getTimeSeries();
+    });
+
+    it('should return docs with byte and count', function() {
+      expect(result.data[0].datapoints.length).to.be(3);
+      expect(result.data[0].datapoints[0].Count).to.be(1);
+      expect(result.data[0].datapoints[0].bytes).to.be(1000);
+    });
+  });
+
   describe('with two filters agg', function() {
     var result;
 

+ 17 - 0
public/app/plugins/datasource/elasticsearch/specs/query_builder_specs.ts

@@ -249,6 +249,23 @@ describe('ElasticQueryBuilder', function() {
     expect(firstLevel.aggs["2"].derivative.buckets_path).to.be("3");
   });
 
+  it('with histogram', function() {
+    var query = builder.build({
+      metrics: [
+        {id: '1', type: 'count' },
+      ],
+      bucketAggs: [
+        {type: 'histogram', field: 'bytes', id: '3', settings: {interval: 10, min_doc_count: 2, missing: 5}}
+      ],
+    });
+
+    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);
+  });
+
   it('with adhoc filters', function() {
     var query = builder.build({
       metrics: [{type: 'Count', id: '0'}],