Sfoglia il codice sorgente

Merge branch 'master' into add_google_hangouts_chat_notifier

* master: (322 commits)
  graphInterval needs to update after query execution, fixes #14364
  Explore: Parse initial dates
  Aligned styling of stats popover/box with rest of grafana & minor css refactoring
  Prometheus: Make result transformer more robust for empty responses
  Rebase fixes
  Explore: Logging line parsing and field stats
  fixed unit tests
  made unknown color theme aware and sync with graph color, some minor cleanup
  Explore: improve error handling
  use render props instead of cloneElement
  sort of a hacky way to figure if the small variation should be used for the label
  add basic button group component, using the the same label style as is
  explore logs styling
  wip: alternative level styling & hover effect
  wip: explore logs styling
  more detailed error message for loki
  If user login equals user email, only show the email once #14341
  UserPicker and TeamPicker should use min-width instead of fixed widths to avoid overflowing form buttons.  #14341
  wip: explore logs styling
  restoring monospace & making sure width are correct when hiding columns
  ...
bergquist 7 anni fa
parent
commit
5d720674e3
100 ha cambiato i file con 1834 aggiunte e 505 eliminazioni
  1. 11 0
      .babelrc
  2. 2 2
      .bra.toml
  3. 9 2
      .circleci/config.yml
  4. 46 15
      CHANGELOG.md
  5. 2 1
      Dockerfile
  6. 1 1
      Makefile
  7. 2 0
      README.md
  8. 2 2
      UPGRADING_DEPENDENCIES.md
  9. 2 0
      build.go
  10. 7 4
      conf/defaults.ini
  11. 1 1
      conf/provisioning/datasources/sample.yaml
  12. 3 0
      conf/sample.ini
  13. 670 245
      devenv/dev-dashboards/testdata_alerts.json
  14. 1 1
      devenv/docker/ha_test/docker-compose.yaml
  15. 1 0
      devenv/docker/ha_test/grafana/provisioning/alerts.jsonnet
  16. 1 1
      docs/sources/administration/provisioning.md
  17. 5 1
      docs/sources/alerting/notifications.md
  18. 16 3
      docs/sources/alerting/rules.md
  19. 3 2
      docs/sources/auth/ldap.md
  20. 21 1
      docs/sources/auth/overview.md
  21. 2 2
      docs/sources/contribute/cla.md
  22. 1 1
      docs/sources/enterprise/index.md
  23. 3 3
      docs/sources/features/datasources/mysql.md
  24. 14 4
      docs/sources/features/datasources/stackdriver.md
  25. 1 1
      docs/sources/guides/whats-new-in-v4.md
  26. 83 0
      docs/sources/guides/whats-new-in-v5-4.md
  27. 4 1
      docs/sources/http_api/alerting.md
  28. 1 1
      docs/sources/http_api/index.md
  29. 39 39
      docs/sources/http_api/org.md
  30. 37 3
      docs/sources/http_api/user.md
  31. 3 3
      docs/sources/index.md
  32. 6 0
      docs/sources/installation/configuration.md
  33. 2 0
      docs/sources/reference/export_import.md
  34. 1 1
      docs/sources/reference/templating.md
  35. 2 1
      docs/versions.json
  36. 2 2
      latest.json
  37. 16 20
      package.json
  38. 1 1
      packaging/deb/init.d/grafana-server
  39. 1 0
      packaging/deb/systemd/grafana-server.service
  40. 2 1
      packaging/docker/Dockerfile
  41. 14 1
      packaging/docker/build-enterprise.sh
  42. 1 0
      packaging/docker/run.sh
  43. 1 1
      packaging/rpm/init.d/grafana-server
  44. 1 0
      packaging/rpm/systemd/grafana-server.service
  45. 6 0
      pkg/api/admin_users.go
  46. 50 0
      pkg/api/admin_users_test.go
  47. 1 1
      pkg/api/alerting.go
  48. 1 0
      pkg/api/api.go
  49. 19 0
      pkg/api/basic_auth.go
  50. 45 0
      pkg/api/basic_auth_test.go
  51. 0 4
      pkg/api/dashboard.go
  52. 0 1
      pkg/api/dashboard_test.go
  53. 9 0
      pkg/api/http_server.go
  54. 30 0
      pkg/api/http_server_test.go
  55. 22 0
      pkg/api/login.go
  56. 8 0
      pkg/api/password.go
  57. 3 3
      pkg/api/pluginproxy/ds_auth_provider.go
  58. 1 1
      pkg/api/pluginproxy/pluginproxy.go
  59. 11 3
      pkg/api/user.go
  60. 17 2
      pkg/cmd/grafana-server/main.go
  61. 26 6
      pkg/cmd/grafana-server/server.go
  62. 1 1
      pkg/components/dynmap/dynmap.go
  63. 1 1
      pkg/components/dynmap/dynmap_test.go
  64. 9 6
      pkg/metrics/metrics.go
  65. 3 0
      pkg/metrics/metrics_test.go
  66. 1 0
      pkg/middleware/recovery.go
  67. 10 3
      pkg/models/alert.go
  68. 0 1
      pkg/models/dashboards.go
  69. 2 1
      pkg/models/user.go
  70. 29 3
      pkg/services/alerting/eval_context.go
  71. 172 67
      pkg/services/alerting/eval_context_test.go
  72. 11 1
      pkg/services/alerting/extractor.go
  73. 13 7
      pkg/services/alerting/extractor_test.go
  74. 47 0
      pkg/services/alerting/notifiers/alertmanager_test.go
  75. 10 0
      pkg/services/alerting/notifiers/base.go
  76. 25 7
      pkg/services/alerting/notifiers/base_test.go
  77. 3 0
      pkg/services/alerting/result_handler.go
  78. 5 0
      pkg/services/alerting/rule.go
  79. 0 0
      pkg/services/alerting/testdata/collapsed-panels.json
  80. 0 0
      pkg/services/alerting/testdata/dash-without-id.json
  81. 1 0
      pkg/services/alerting/testdata/graphite-alert.json
  82. 0 0
      pkg/services/alerting/testdata/influxdb-alert.json
  83. 0 0
      pkg/services/alerting/testdata/panel-with-id-0.json
  84. 0 0
      pkg/services/alerting/testdata/panels-missing-id.json
  85. 0 0
      pkg/services/alerting/testdata/v5-dashboard.json
  86. 1 1
      pkg/services/dashboards/dashboard_service.go
  87. 5 4
      pkg/services/sqlstore/alert.go
  88. 2 2
      pkg/services/sqlstore/alert_test.go
  89. 4 0
      pkg/services/sqlstore/migrations/alert_mig.go
  90. 1 1
      pkg/services/sqlstore/org_test.go
  91. 6 6
      pkg/services/sqlstore/quota.go
  92. 65 0
      pkg/services/sqlstore/quota_test.go
  93. 25 1
      pkg/services/sqlstore/user.go
  94. 26 0
      pkg/services/sqlstore/user_test.go
  95. 9 0
      pkg/setting/setting.go
  96. 2 0
      pkg/social/google_oauth.go
  97. 25 2
      pkg/tsdb/cloudwatch/cloudwatch.go
  98. 22 0
      pkg/tsdb/cloudwatch/cloudwatch_test.go
  99. 4 0
      pkg/tsdb/cloudwatch/metric_find_query.go
  100. 1 1
      pkg/tsdb/elasticsearch/client/client.go

+ 11 - 0
.babelrc

@@ -0,0 +1,11 @@
+{
+  "presets": [
+    [
+      "@babel/preset-env",
+      {
+		  "targets": { "browsers": "last 3 versions" },
+		  "useBuiltIns": "entry"
+      }
+    ]
+  ]
+}

+ 2 - 2
.bra.toml

@@ -1,7 +1,7 @@
 [run]
 init_cmds = [
   ["go", "run", "build.go", "-dev", "build-server"],
-	["./bin/grafana-server", "cfg:app_mode=development"]
+	["./bin/grafana-server", "-packaging=dev", "cfg:app_mode=development"]
 ]
 watch_all = true
 follow_symlinks = true
@@ -14,5 +14,5 @@ watch_exts = [".go", ".ini", ".toml", ".template.html"]
 build_delay = 1500
 cmds = [
   ["go", "run", "build.go", "-dev", "build-server"],
-	["./bin/grafana-server", "cfg:app_mode=development"]
+	["./bin/grafana-server", "-packaging=dev", "cfg:app_mode=development"]
 ]

+ 9 - 2
.circleci/config.yml

@@ -162,8 +162,8 @@ jobs:
           name: Build Grafana.com master publisher
           command: 'go build -o scripts/publish scripts/build/publish.go'
       - run:
-          name: Build Grafana.com release publisher
-          command: 'cd scripts/build/release_publisher && go build -o release_publisher .'
+          name: Test and build Grafana.com release publisher
+          command: 'cd scripts/build/release_publisher && go test . && go build -o release_publisher .'
       - persist_to_workspace:
           root: .
           paths:
@@ -191,6 +191,9 @@ jobs:
       - run:
           name: sha-sum packages
           command: 'go run build.go sha-dist'
+      - run:
+          name: Test Grafana.com release publisher
+          command: 'cd scripts/build/release_publisher && go test .'
       - persist_to_workspace:
           root: .
           paths:
@@ -359,6 +362,9 @@ jobs:
       - run:
           name: deploy to gcp
           command: '/opt/google-cloud-sdk/bin/gsutil cp ./enterprise-dist/* gs://$GCP_BUCKET_NAME/enterprise/release'
+      - run:
+          name: Deploy to Grafana.com
+          command: './scripts/build/publish.sh --enterprise'
 
   deploy-master:
     docker:
@@ -507,6 +513,7 @@ workflows:
       - grafana-docker-release:
           requires:
             - build-all
+            - build-all-enterprise
             - test-backend
             - test-frontend
             - codespell

+ 46 - 15
CHANGELOG.md

@@ -1,7 +1,27 @@
-# 5.4.0 (unreleased)
+# 5.5.0 (unreleased)
+
+### Minor
+
+* **Elasticsearch**: Add support for offset in date histogram aggregation [#12653](https://github.com/grafana/grafana/issues/12653), thx [@mattiarossi](https://github.com/mattiarossi)
+* **Auth**: Prevent password reset when login form is disabled or either LDAP or Auth Proxy is enabled [#14246](https://github.com/grafana/grafana/issues/14246), thx [@SilverFire](https://github.com/SilverFire)
+* **Dataproxy**: Override incoming Authorization header [#13815](https://github.com/grafana/grafana/issues/13815), thx [@kornholi](https://github.com/kornholi)
+* **Admin**: Fix prevent removing last grafana admin permissions [#11067](https://github.com/grafana/grafana/issues/11067), thx [@danielbh](https://github.com/danielbh)
+
+# 5.4.0 (2018-12-03)
+
+* **Cloudwatch**: Fix invalid time range causes segmentation fault [#14150](https://github.com/grafana/grafana/issues/14150)
+* **Cloudwatch**: AWS/CodeBuild metrics and dimensions [#14167](https://github.com/grafana/grafana/issues/14167), thx [@mmcoltman](https://github.com/mmcoltman)
+* **MySQL**: Fix `$__timeFrom()` and `$__timeTo()` should respect local time zone [#14228](https://github.com/grafana/grafana/issues/14228)
+
+### 5.4.0-beta1 fixes
+* **Graph**: Fix legend always visible even if configured to be hidden [#14144](https://github.com/grafana/grafana/issues/14144)
+* **Elasticsearch**: Fix regression when using datasource version 6.0+ and alerting [#14175](https://github.com/grafana/grafana/pull/14175)
+
+# 5.4.0-beta1 (2018-11-20)
 
 ### New Features
 
+* **Alerting**: Introduce alert debouncing with the `FOR` setting. [#7886](https://github.com/grafana/grafana/issues/7886) & [#6202](https://github.com/grafana/grafana/issues/6202)
 * **Alerting**: Option to disable OK alert notifications [#12330](https://github.com/grafana/grafana/issues/12330) & [#6696](https://github.com/grafana/grafana/issues/6696), thx [@davewat](https://github.com/davewat)
 * **Postgres/MySQL/MSSQL**: Adds support for configuration of max open/idle connections and connection max lifetime. Also, panels with multiple SQL queries will now be executed concurrently [#11711](https://github.com/grafana/grafana/issues/11711), thx [@connection-reset](https://github.com/connection-reset)
 * **MySQL**: Graphical query builder [#13762](https://github.com/grafana/grafana/issues/13762), thx [svenklemm](https://github.com/svenklemm)
@@ -10,29 +30,40 @@
 * **Stackdriver**: Not possible to authenticate using GCE metadata server [#13669](https://github.com/grafana/grafana/issues/13669)
 * **Teams**: Team preferences (theme, home dashboard, timezone) support [#12550](https://github.com/grafana/grafana/issues/12550)
 * **Graph**: Time regions support enabling highlight of weekdays and/or certain timespans [#5930](https://github.com/grafana/grafana/issues/5930)
+* **OAuth**: Automatic redirect to sign-in with OAuth [#11893](https://github.com/grafana/grafana/issues/11893), thx [@Nick-Triller](https://github.com/Nick-Triller)
+* **Stackdriver**: Template query editor [#13561](https://github.com/grafana/grafana/issues/13561)
 
 ### Minor
 
+* **Security**: Upgrade macaron session package to fix security issue. [#14043](https://github.com/grafana/grafana/pull/14043)
 * **Cloudwatch**: Show all available CloudWatch regions [#12308](https://github.com/grafana/grafana/issues/12308), thx [@mtanda](https://github.com/mtanda)
 * **Cloudwatch**: AWS/Connect metrics and dimensions [#13970](https://github.com/grafana/grafana/pull/13970), thx [@zcoffy](https://github.com/zcoffy)
+* **Cloudwatch**: CloudHSM metrics and dimensions [#14129](https://github.com/grafana/grafana/pull/14129), thx [@daktari](https://github.com/daktari)
+* **Cloudwatch**: Enable using variables in the stats field [#13810](https://github.com/grafana/grafana/issues/13810), thx [@mtanda](https://github.com/mtanda)
 * **Postgres**: Add delta window function to postgres query builder [#13925](https://github.com/grafana/grafana/issues/13925), thx [svenklemm](https://github.com/svenklemm)
 * **Elasticsearch**: Fix switching to/from es raw document metric query [#6367](https://github.com/grafana/grafana/issues/6367)
 * **Elasticsearch**: Fix deprecation warning about terms aggregation order key in Elasticsearch 6.x [#11977](https://github.com/grafana/grafana/issues/11977)
+* **Graph**: Render dots when no connecting line can be made [#13605](https://github.com/grafana/grafana/issues/13605), thx [@jsferrei](https://github.com/jsferrei)
 * **Table**: Fix CSS alpha background-color applied twice in table cell with link [#13606](https://github.com/grafana/grafana/issues/13606), thx [@grisme](https://github.com/grisme)
+* **Singlestat**: Fix XSS in prefix/postfix [#13946](https://github.com/grafana/grafana/issues/13946), thx [@cinaglia](https://github.com/cinaglia)
 * **Units**: New clock time format, to format ms or second values as for example `01h:59m`, [#13635](https://github.com/grafana/grafana/issues/13635), thx [@franciscocpg](https://github.com/franciscocpg)
 * **Alerting**: Increaste default duration for queries [#13945](https://github.com/grafana/grafana/pull/13945)
 * **Alerting**: More options for the Slack Alert notifier [#13993](https://github.com/grafana/grafana/issues/13993), thx [@andreykaipov](https://github.com/andreykaipov)
 * **Alerting**: Can't receive DingDing alert when alert is triggered [#13723](https://github.com/grafana/grafana/issues/13723), thx [@Yukinoshita-Yukino](https://github.com/Yukinoshita-Yukino)
+* **Alerting**: Increase Telegram captions length limit [#13876](https://github.com/grafana/grafana/pull/13876), thx [@skgsergio](https://github.com/skgsergio)
 * **Internal metrics**: Renamed `grafana_info` to `grafana_build_info` and added branch, goversion and revision [#13876](https://github.com/grafana/grafana/pull/13876)
 * **Datasource Proxy**: Keep trailing slash for datasource proxy requests [#13326](https://github.com/grafana/grafana/pull/13326), thx [@ryantxu](https://github.com/ryantxu)
+* **OAuth**: Fix Google OAuth relies on email, not google account id [#13924](https://github.com/grafana/grafana/issues/13924), thx [@vinicyusmacedo](https://github.com/vinicyusmacedo)
+* **Dashboard**: Toggle legend using keyboard shortcut [#13655](https://github.com/grafana/grafana/issues/13655), thx [@davewat](https://github.com/davewat)
+* **Dashboard**: Fix render dashboard row drag handle only in edit mode [#13555](https://github.com/grafana/grafana/issues/13555), thx [@praveensastry](https://github.com/praveensastry)
+* **Teams**: Fix cannot select team if not included in initial search [#13425](https://github.com/grafana/grafana/issues/13425)
+* **Render**: Support full height screenshots using phantomjs render script [#13352](https://github.com/grafana/grafana/pull/13352), thx [@amuraru](https://github.com/amuraru)
+* **HTTP API**: Support retrieving teams by user [#14120](https://github.com/grafana/grafana/pull/14120), thx [@supercharlesliu](https://github.com/supercharlesliu)
+* **Metrics**: Add basic authentication to metrics endpoint [#13577](https://github.com/grafana/grafana/issues/13577), thx [@bobmshannon](https://github.com/bobmshannon)
 
 ### Breaking changes
 
-* Postgres/MySQL/MSSQL datasources now per default uses `max open connections` = `unlimited` (earlier 10), `max idle connections` = `2` (earlier 10) and `connection max lifetime` = `4` hours (earlier unlimited)
-
-# 5.3.5 (unreleased)
-
-* **Security**: Upgrade macaron session package to fix security issue. [#14043](https://github.com/grafana/grafana/pull/14043)
+* Postgres/MySQL/MSSQL datasources now per default uses `max open connections` = `unlimited` (earlier 10), `max idle connections` = `2` (earlier 10) and `connection max lifetime` = `4` hours (earlier unlimited).
 
 # 5.3.4 (2018-11-13)
 
@@ -140,7 +171,7 @@ See [security announcement](https://community.grafana.com/t/grafana-5-3-3-and-4-
 * **Alerting**: Fix rendering timeout which could cause notifications to not be sent due to rendering timing out [#12151](https://github.com/grafana/grafana/issues/12151)
 * **Docker**: Make it possible to set a specific plugin url [#12861](https://github.com/grafana/grafana/pull/12861), thx [ClementGautier](https://github.com/ClementGautier)
 * **GrafanaCli**: Fixed issue with grafana-cli install plugin resulting in corrupt http response from source error. Fixes [#13079](https://github.com/grafana/grafana/issues/13079)
-* **Provisioning**: Should allow one default datasource per organisation [#12229](https://github.com/grafana/grafana/issues/12229)
+* **Provisioning**: Should allow one default datasource per organization [#12229](https://github.com/grafana/grafana/issues/12229)
 * **Github OAuth**: Allow changes of user info at Github to be synched to Grafana when signing in [#11818](https://github.com/grafana/grafana/issues/11818), thx [@rwaweber](https://github.com/rwaweber)
 * **OAuth**: Fix overriding tls_skip_verify_insecure using environment variable [#12747](https://github.com/grafana/grafana/issues/12747), thx [@jangaraj](https://github.com/jangaraj)
 * **Prometheus**: Fix graph panel bar width issue in aligned prometheus queries [#12379](https://github.com/grafana/grafana/issues/12379)
@@ -263,7 +294,7 @@ See [security announcement](https://community.grafana.com/t/grafana-5-2-3-and-4-
 * **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)
+* **Units**: Liter/min (flow) and milliLiter/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)
 * **Cloudwatch**: Fixes panic caused by bad timerange settings [#12199](https://github.com/grafana/grafana/issues/12199)
@@ -398,7 +429,7 @@ See [security announcement](https://community.grafana.com/t/grafana-5-2-3-and-4-
 * **Units**: Use B/s instead Bps for Bytes per second [#9342](https://github.com/grafana/grafana/pull/9342), thx [@mayli](https://github.com/mayli)
 * **Units**: Radiation units [#11001](https://github.com/grafana/grafana/issues/11001), thx [@victorclaessen](https://github.com/victorclaessen)
 * **Units**: Timeticks unit [#11183](https://github.com/grafana/grafana/pull/11183), thx [@jtyr](https://github.com/jtyr)
-* **Units**: Concentration units and "Normal cubic metre" [#11211](https://github.com/grafana/grafana/issues/11211), thx [@flopp999](https://github.com/flopp999)
+* **Units**: Concentration units and "Normal cubic meter" [#11211](https://github.com/grafana/grafana/issues/11211), thx [@flopp999](https://github.com/flopp999)
 * **Units**: New currency - Czech koruna [#11384](https://github.com/grafana/grafana/pull/11384), thx [@Rohlik](https://github.com/Rohlik)
 * **Avatar**: Fix DISABLE_GRAVATAR option [#11095](https://github.com/grafana/grafana/issues/11095)
 * **Heatmap**: Disable log scale when using time time series buckets [#10792](https://github.com/grafana/grafana/issues/10792)
@@ -715,7 +746,7 @@ See [security announcement](https://community.grafana.com/t/grafana-5-2-3-and-4-
 ## Enhancements
 
 * **GitHub OAuth**: Support for GitHub organizations with 100+ teams. [#8846](https://github.com/grafana/grafana/issues/8846), thx [@skwashd](https://github.com/skwashd)
-* **Graphite**: Calls to Graphite api /metrics/find now include panel or dashboad time range (from & until) in most cases, [#8055](https://github.com/grafana/grafana/issues/8055)
+* **Graphite**: Calls to Graphite api /metrics/find now include panel or dashboard time range (from & until) in most cases, [#8055](https://github.com/grafana/grafana/issues/8055)
 * **Graphite**: Added new graphite 1.0 functions, available if you set version to 1.0.x in data source settings. New Functions: mapSeries, reduceSeries, isNonNull, groupByNodes, offsetToZero, grep, weightedAverage, removeEmptySeries, aggregateLine, averageOutsidePercentile, delay, exponentialMovingAverage, fallbackSeries, integralByInterval, interpolate, invert, linearRegression, movingMin, movingMax, movingSum, multiplySeriesWithWildcards, pow, powSeries, removeBetweenPercentile, squareRoot, timeSlice, closes [#8261](https://github.com/grafana/grafana/issues/8261)
 - **Elasticsearch**: Ad-hoc filters now use query phrase match filters instead of term filters, works on non keyword/raw fields [#9095](https://github.com/grafana/grafana/issues/9095).
 
@@ -880,7 +911,7 @@ Pull Request: [#8472](https://github.com/grafana/grafana/pull/8472)
 * **InfluxDB**: Influxb Datasource test passes even if the Database doesn't exist [#7864](https://github.com/grafana/grafana/issues/7864)
 * **Prometheus**: Displaying Prometheus annotations is incredibly slow [#7750](https://github.com/grafana/grafana/issues/7750), thx [@mtanda](https://github.com/mtanda)
 * **Graphite**: grafana generates empty find query to graphite -> 422 Unprocessable Entity [#7740](https://github.com/grafana/grafana/issues/7740)
-* **Admin**: make organisation filter case insensitive [#8194](https://github.com/grafana/grafana/issues/8194), thx [@Alexander-N](https://github.com/Alexander-N)
+* **Admin**: make organization filter case insensitive [#8194](https://github.com/grafana/grafana/issues/8194), thx [@Alexander-N](https://github.com/Alexander-N)
 
 ## Changes
 * **Elasticsearch**: Changed elasticsearch Terms aggregation to default to Min Doc Count to 1, and sort order to Top [#8321](https://github.com/grafana/grafana/issues/8321)
@@ -1008,7 +1039,7 @@ Pull Request: [#8472](https://github.com/grafana/grafana/pull/8472)
 * **CLI**: Make it possible to reset the admin password using the grafana-cli. [#5479](https://github.com/grafana/grafana/issues/5479)
 * **Influxdb**: Support multiple tags in InfluxDB annotations. [#4550](https://github.com/grafana/grafana/pull/4550), thx [@adrianlzt](https://github.com/adrianlzt)
 * **LDAP**:  Basic Auth now supports LDAP username and password, [#6940](https://github.com/grafana/grafana/pull/6940), thx [@utkarshcmu](https://github.com/utkarshcmu)
-* **LDAP**: Now works with Auth Proxy, role and organisation mapping & sync will regularly be performed. [#6895](https://github.com/grafana/grafana/pull/6895), thx [@Seuf](https://github.com/seuf)
+* **LDAP**: Now works with Auth Proxy, role and organization mapping & sync will regularly be performed. [#6895](https://github.com/grafana/grafana/pull/6895), thx [@Seuf](https://github.com/seuf)
 * **Alerting**: Adds OK as no data option. [#6866](https://github.com/grafana/grafana/issues/6866)
 * **Alert list**: Order alerts based on state. [#6676](https://github.com/grafana/grafana/issues/6676)
 * **Alerting**: Add api endpoint for pausing all alerts. [#6589](https://github.com/grafana/grafana/issues/6589)
@@ -1147,7 +1178,7 @@ due to too many connections/file handles on the data source backend. This proble
 * **Scripts**: Use restart instead of start for deb package script, closes [#5282](https://github.com/grafana/grafana/pull/5282)
 * **Logging**: Moved to structured logging lib, and moved to component specific level filters via config file, closes [#4590](https://github.com/grafana/grafana/issues/4590)
 * **OpenTSDB**: Support nested template variables in tag_values function, closes [#4398](https://github.com/grafana/grafana/issues/4398)
-* **Datasource**: Pending data source requests are cancelled before new ones are issues (Graphite & Prometheus), closes [#5321](https://github.com/grafana/grafana/issues/5321)
+* **Datasource**: Pending data source requests are canceled before new ones are issues (Graphite & Prometheus), closes [#5321](https://github.com/grafana/grafana/issues/5321)
 
 ### Breaking changes
 * **Logging** : Changed default logging output format (now structured into message, and key value pairs, with logger key acting as component). You can also no change in config to json log output.
@@ -1851,7 +1882,7 @@ Read this [blog post](https://grafana.com/blog/2014/09/11/grafana-1.8.0-rc1-rele
 
 #### Fixes
 - [Issue #126](https://github.com/grafana/grafana/issues/126). Graphite query lexer change, can now handle regex parameters for aliasSub function
-- [Issue #447](https://github.com/grafana/grafana/issues/447). Filter option loading when having muliple nested filters now works better. Options are now reloaded correctly and there are no multiple renders/refresh in between.
+- [Issue #447](https://github.com/grafana/grafana/issues/447). Filter option loading when having multiple nested filters now works better. Options are now reloaded correctly and there are no multiple renders/refresh in between.
 - [Issue #412](https://github.com/grafana/grafana/issues/412). After a filter option is changed and a nested template param is reloaded, if the current value exists after the options are reloaded the current selected value is kept.
 - [Issue #460](https://github.com/grafana/grafana/issues/460). Legend Current value did not display when value was zero
 - [Issue #328](https://github.com/grafana/grafana/issues/328). Fix to series toggling bug that caused annotations to be hidden when toggling/hiding series.
@@ -1886,7 +1917,7 @@ Read this [blog post](https://grafana.com/blog/2014/09/11/grafana-1.8.0-rc1-rele
 - Graphite errors are now much easier to see and troubleshoot with the new inspector ([Issue #265](https://github.com/grafana/grafana/issues/265))
 - Use influxdb aliases to distinguish between multiple columns ([Issue #283](https://github.com/grafana/grafana/issues/283))
 - Correction to ms axis formater, now formats days correctly. ([Issue #189](https://github.com/grafana/grafana/issues/189))
-- Css fix for Firefox and using top menu dropdowns in panel fullscren / edit mode ([Issue #106](https://github.com/grafana/grafana/issues/106))
+- Css fix for Firefox and using top menu dropdowns in panel fullscreen / edit mode ([Issue #106](https://github.com/grafana/grafana/issues/106))
 - Browser page title is now Grafana - {{dashboard title}} ([Issue #294](https://github.com/grafana/grafana/issues/294))
 - Disable auto refresh zooming in (every time you change to an absolute time range), refresh will be restored when you change time range back to relative ([Issue #282](https://github.com/grafana/grafana/issues/282))
 - More graphite functions

+ 2 - 1
Dockerfile

@@ -50,7 +50,8 @@ ENV PATH=/usr/share/grafana/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bi
 
 WORKDIR $GF_PATHS_HOME
 
-RUN apt-get update && apt-get install -qq -y libfontconfig ca-certificates && \
+RUN apt-get update && apt-get upgrade -y && \
+    apt-get install -qq -y libfontconfig ca-certificates && \
     apt-get autoremove -y && \
     rm -rf /var/lib/apt/lists/*
 

+ 1 - 1
Makefile

@@ -25,7 +25,7 @@ build: build-go build-js
 
 build-docker-dev:
 	@echo "\033[92mInfo:\033[0m the frontend code is expected to be built already."
-	go run build.go -goos linux -pkg-arch amd64 ${OPT} build package-only latest
+	go run build.go -goos linux -pkg-arch amd64 ${OPT} build pkg-archive latest
 	cp dist/grafana-latest.linux-x64.tar.gz packaging/docker
 	cd packaging/docker && docker build --tag grafana/grafana:dev .
 

+ 2 - 0
README.md

@@ -90,6 +90,8 @@ Choose this option to build on platforms other than linux/amd64 and/or not have
 
 The resulting image will be tagged as `grafana/grafana:dev`
 
+Notice: If you are using Docker for MacOS, be sure to let limit of Memory bigger than 2 GiB (at docker -> Perferences -> Advanced), otherwize you may faild at `grunt build`
+
 ### Dev config
 
 Create a custom.ini in the conf directory to override default configuration options.

+ 2 - 2
UPGRADING_DEPENDENCIES.md

@@ -47,7 +47,7 @@ Our builds run on CircleCI through our build script.
 
 ### grafana/build-container
 
-The main build step (in CircleCI) is built using a custom build container that comes pre-baked with some of the neccesary dependencies.
+The main build step (in CircleCI) is built using a custom build container that comes pre-baked with some of the necessary dependencies.
 
 Link: [grafana-build-container](https://github.com/grafana/grafana-build-container)
 
@@ -86,4 +86,4 @@ There is a Docker build for Grafana in the root of the project that allows anyon
 
 ### Local developer environments
 
-Please send out a notice in the grafana-dev slack channel when updating Go or Node.js to make it easier for everyone to update their local developer environments.
+Please send out a notice in the grafana-dev slack channel when updating Go or Node.js to make it easier for everyone to update their local developer environments.

+ 2 - 0
build.go

@@ -128,6 +128,8 @@ func main() {
 			if goos == linux {
 				createLinuxPackages()
 			}
+		case "pkg-archive":
+			grunt(gruntBuildArg("package")...)
 
 		case "pkg-rpm":
 			grunt(gruntBuildArg("release")...)

+ 7 - 4
conf/defaults.ini

@@ -34,7 +34,7 @@ protocol = http
 # The ip address to bind to, empty will bind to all interfaces
 http_addr =
 
-# The http port  to use
+# The http port to use
 http_port = 3000
 
 # The public facing domain name used to access grafana from a browser
@@ -166,7 +166,7 @@ google_tag_manager_id =
 # default admin user, created on startup
 admin_user = admin
 
-# default admin password, can be changed before first start of grafana,  or in profile settings
+# default admin password, can be changed before first start of grafana, or in profile settings
 admin_password = admin
 
 # used for signing
@@ -372,7 +372,7 @@ templates_pattern = emails/*.html
 
 #################################### Logging ##########################
 [log]
-# Either "console", "file", "syslog". Default is console and  file
+# Either "console", "file", "syslog". Default is console and file
 # Use space to separate multiple modes, e.g. "console file"
 mode = console file
 
@@ -490,6 +490,10 @@ enabled = false
 enabled           = true
 interval_seconds  = 10
 
+#If both are set, basic auth will be required for the metrics endpoint.
+basic_auth_username =
+basic_auth_password =
+
 # Send internal Grafana metrics to graphite
 [metrics.graphite]
 # Enable by setting the address setting (ex localhost:2003)
@@ -561,4 +565,3 @@ enable_alpha = false
 
 [enterprise]
 license_path =
-

+ 1 - 1
conf/provisioning/datasources/sample.yaml

@@ -7,7 +7,7 @@ apiVersion: 1
 #     orgId: 1
 
 # # list of datasources to insert/update depending
-# # on what's available in the datbase
+# # on what's available in the database
 #datasources:
 #   # <string, required> name of the datasource. Required
 # - name: Graphite

+ 3 - 0
conf/sample.ini

@@ -145,6 +145,9 @@ log_queries =
 # Google Analytics universal tracking code, only enabled if you specify an id here
 ;google_analytics_ua_id =
 
+# Google Tag Manager ID, only enabled if you specify an id here
+;google_tag_manager_id =
+
 #################################### Security ####################################
 [security]
 # default admin user, created on startup

+ 670 - 245
devenv/dev-dashboards/testdata_alerts.json

@@ -1,250 +1,681 @@
 {
-  "revision": 2,
-  "title": "Alerting with TestData",
-  "tags": [
-    "grafana-test"
-  ],
-  "style": "dark",
-  "timezone": "browser",
+  "annotations": {
+    "list": [
+      {
+        "builtIn": 1,
+        "datasource": "-- Grafana --",
+        "enable": true,
+        "hide": true,
+        "iconColor": "rgba(0, 211, 255, 1)",
+        "name": "Annotations & Alerts",
+        "type": "dashboard"
+      }
+    ]
+  },
   "editable": true,
-  "hideControls": false,
-  "sharedCrosshair": false,
-  "rows": [
+  "gnetId": null,
+  "graphTooltip": 0,
+  "links": [],
+  "panels": [
     {
-      "collapse": false,
+      "alert": {
+        "conditions": [
+          {
+            "evaluator": {
+              "params": [
+                60
+              ],
+              "type": "gt"
+            },
+            "query": {
+              "params": [
+                "A",
+                "5m",
+                "now"
+              ]
+            },
+            "reducer": {
+              "params": [],
+              "type": "avg"
+            },
+            "type": "query"
+          }
+        ],
+        "enabled": true,
+        "frequency": "60s",
+        "handler": 1,
+        "name": "TestData - Always OK",
+        "noDataState": "no_data",
+        "notifications": []
+      },
+      "aliasColors": {},
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "gdev-testdata",
       "editable": true,
-      "height": 255.625,
-      "panels": [
+      "error": false,
+      "fill": 1,
+      "gridPos": {
+        "h": 7,
+        "w": 12,
+        "x": 0,
+        "y": 0
+      },
+      "id": 3,
+      "isNew": true,
+      "legend": {
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 2,
+      "links": [],
+      "nullPointMode": "connected",
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "refId": "A",
+          "scenario": "random_walk",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0",
+          "target": ""
+        }
+      ],
+      "thresholds": [
+        {
+          "colorMode": "critical",
+          "fill": true,
+          "line": true,
+          "op": "gt",
+          "value": 60
+        }
+      ],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "Always OK",
+      "tooltip": {
+        "msResolution": false,
+        "shared": true,
+        "sort": 0,
+        "value_type": "cumulative"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "short",
+          "label": "",
+          "logBase": 1,
+          "max": "125",
+          "min": "0",
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    },
+    {
+      "alert": {
+        "conditions": [
+          {
+            "evaluator": {
+              "params": [
+                177
+              ],
+              "type": "gt"
+            },
+            "query": {
+              "params": [
+                "A",
+                "5m",
+                "now"
+              ]
+            },
+            "reducer": {
+              "params": [],
+              "type": "avg"
+            },
+            "type": "query"
+          }
+        ],
+        "enabled": true,
+        "executionErrorState": "alerting",
+        "for": "0m",
+        "frequency": "60s",
+        "handler": 1,
+        "name": "TestData - Always Alerting",
+        "noDataState": "no_data",
+        "notifications": []
+      },
+      "aliasColors": {},
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "gdev-testdata",
+      "editable": true,
+      "error": false,
+      "fill": 1,
+      "gridPos": {
+        "h": 7,
+        "w": 12,
+        "x": 12,
+        "y": 0
+      },
+      "id": 4,
+      "isNew": true,
+      "legend": {
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 2,
+      "links": [],
+      "nullPointMode": "connected",
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "refId": "A",
+          "scenario": "random_walk",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "200,445,100,150,200,220,190",
+          "target": ""
+        }
+      ],
+      "thresholds": [
+        {
+          "colorMode": "critical",
+          "fill": true,
+          "line": true,
+          "op": "gt",
+          "value": 177
+        }
+      ],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "Always Alerting",
+      "tooltip": {
+        "msResolution": false,
+        "shared": true,
+        "sort": 0,
+        "value_type": "cumulative"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "short",
+          "label": "",
+          "logBase": 1,
+          "max": null,
+          "min": "0",
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": "",
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    },
+    {
+      "alert": {
+        "conditions": [
+          {
+            "evaluator": {
+              "params": [
+                1
+              ],
+              "type": "gt"
+            },
+            "operator": {
+              "type": "and"
+            },
+            "query": {
+              "params": [
+                "A",
+                "15m",
+                "now"
+              ]
+            },
+            "reducer": {
+              "params": [],
+              "type": "avg"
+            },
+            "type": "query"
+          }
+        ],
+        "executionErrorState": "alerting",
+        "for": "5m",
+        "frequency": "1m",
+        "handler": 1,
+        "name": "TestData - No data",
+        "noDataState": "no_data",
+        "notifications": []
+      },
+      "aliasColors": {},
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "gdev-testdata",
+      "editable": true,
+      "error": false,
+      "fill": 1,
+      "gridPos": {
+        "h": 7,
+        "w": 12,
+        "x": 0,
+        "y": 7
+      },
+      "id": 5,
+      "isNew": true,
+      "legend": {
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 2,
+      "links": [],
+      "nullPointMode": "connected",
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "refId": "A",
+          "scenario": "random_walk",
+          "scenarioId": "no_data_points",
+          "stringInput": "",
+          "target": ""
+        }
+      ],
+      "thresholds": [
+        {
+          "colorMode": "critical",
+          "fill": true,
+          "line": true,
+          "op": "gt",
+          "value": 1
+        }
+      ],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "No data",
+      "tooltip": {
+        "msResolution": false,
+        "shared": true,
+        "sort": 0,
+        "value_type": "cumulative"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "short",
+          "label": "",
+          "logBase": 1,
+          "max": null,
+          "min": "0",
+          "show": true
+        },
         {
-          "alert": {
-            "conditions": [
-              {
-                "evaluator": {
-                  "params": [
-                    60
-                  ],
-                  "type": "gt"
-                },
-                "query": {
-                  "params": [
-                    "A",
-                    "5m",
-                    "now"
-                  ]
-                },
-                "reducer": {
-                  "params": [],
-                  "type": "avg"
-                },
-                "type": "query"
-              }
-            ],
-            "enabled": true,
-            "frequency": "60s",
-            "handler": 1,
-            "name": "TestData - Always OK",
-            "noDataState": "no_data",
-            "notifications": []
-          },
-          "aliasColors": {},
-          "bars": false,
-          "datasource": "gdev-testdata",
-          "editable": true,
-          "error": false,
-          "fill": 1,
-          "id": 3,
-          "isNew": true,
-          "legend": {
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 2,
-          "links": [],
-          "nullPointMode": "connected",
-          "percentage": false,
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "span": 6,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "refId": "A",
-              "scenario": "random_walk",
-              "scenarioId": "csv_metric_values",
-              "stringInput": "1,20,90,30,5,0",
-              "target": ""
-            }
-          ],
-          "thresholds": [
-            {
-              "value": 60,
-              "op": "gt",
-              "fill": true,
-              "line": true,
-              "colorMode": "critical"
-            }
-          ],
-          "timeFrom": null,
-          "timeShift": null,
-          "title": "Always OK",
-          "tooltip": {
-            "msResolution": false,
-            "shared": true,
-            "sort": 0,
-            "value_type": "cumulative"
-          },
-          "type": "graph",
-          "xaxis": {
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": "",
-              "logBase": 1,
-              "max": "125",
-              "min": "0",
-              "show": true
+          "format": "short",
+          "label": "",
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    },
+    {
+      "alert": {
+        "conditions": [
+          {
+            "evaluator": {
+              "params": [
+                177
+              ],
+              "type": "gt"
             },
-            {
-              "format": "short",
-              "label": null,
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ]
+            "operator": {
+              "type": "and"
+            },
+            "query": {
+              "params": [
+                "A",
+                "15m",
+                "now"
+              ]
+            },
+            "reducer": {
+              "params": [],
+              "type": "avg"
+            },
+            "type": "query"
+          }
+        ],
+        "executionErrorState": "alerting",
+        "for": "1m",
+        "frequency": "1m",
+        "handler": 1,
+        "name": "TestData - Always Pending",
+        "noDataState": "no_data",
+        "notifications": []
+      },
+      "aliasColors": {},
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "gdev-testdata",
+      "editable": true,
+      "error": false,
+      "fill": 1,
+      "gridPos": {
+        "h": 7,
+        "w": 12,
+        "x": 12,
+        "y": 7
+      },
+      "id": 6,
+      "isNew": true,
+      "legend": {
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 2,
+      "links": [],
+      "nullPointMode": "connected",
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "refId": "A",
+          "scenario": "random_walk",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "200,445,100,150,200,220,190",
+          "target": ""
+        }
+      ],
+      "thresholds": [
+        {
+          "colorMode": "critical",
+          "fill": true,
+          "line": true,
+          "op": "gt",
+          "value": 177
+        }
+      ],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "Always Alerting with For",
+      "tooltip": {
+        "msResolution": false,
+        "shared": true,
+        "sort": 0,
+        "value_type": "cumulative"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "short",
+          "label": "",
+          "logBase": 1,
+          "max": null,
+          "min": "0",
+          "show": true
         },
         {
-          "alert": {
-            "conditions": [
-              {
-                "evaluator": {
-                  "params": [
-                    177
-                  ],
-                  "type": "gt"
-                },
-                "query": {
-                  "params": [
-                    "A",
-                    "5m",
-                    "now"
-                  ]
-                },
-                "reducer": {
-                  "params": [],
-                  "type": "avg"
-                },
-                "type": "query"
-              }
-            ],
-            "enabled": true,
-            "frequency": "60s",
-            "handler": 1,
-            "name": "TestData - Always Alerting",
-            "noDataState": "no_data",
-            "notifications": []
-          },
-          "aliasColors": {},
-          "bars": false,
-          "datasource": "gdev-testdata",
-          "editable": true,
-          "error": false,
-          "fill": 1,
-          "id": 4,
-          "isNew": true,
-          "legend": {
-            "avg": false,
-            "current": false,
-            "max": false,
-            "min": false,
-            "show": true,
-            "total": false,
-            "values": false
-          },
-          "lines": true,
-          "linewidth": 2,
-          "links": [],
-          "nullPointMode": "connected",
-          "percentage": false,
-          "pointradius": 5,
-          "points": false,
-          "renderer": "flot",
-          "seriesOverrides": [],
-          "span": 6,
-          "stack": false,
-          "steppedLine": false,
-          "targets": [
-            {
-              "refId": "A",
-              "scenario": "random_walk",
-              "scenarioId": "csv_metric_values",
-              "stringInput": "200,445,100,150,200,220,190",
-              "target": ""
-            }
-          ],
-          "thresholds": [
-            {
-              "colorMode": "critical",
-              "fill": true,
-              "line": true,
-              "op": "gt",
-              "value": 177
-            }
-          ],
-          "timeFrom": null,
-          "timeShift": null,
-          "title": "Always Alerting",
-          "tooltip": {
-            "msResolution": false,
-            "shared": true,
-            "sort": 0,
-            "value_type": "cumulative"
-          },
-          "type": "graph",
-          "xaxis": {
-            "mode": "time",
-            "name": null,
-            "show": true,
-            "values": []
-          },
-          "yaxes": [
-            {
-              "format": "short",
-              "label": "",
-              "logBase": 1,
-              "max": null,
-              "min": "0",
-              "show": true
+          "format": "short",
+          "label": "",
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    },
+    {
+      "alert": {
+        "conditions": [
+          {
+            "evaluator": {
+              "params": [
+                100
+              ],
+              "type": "gt"
+            },
+            "operator": {
+              "type": "and"
+            },
+            "query": {
+              "params": [
+                "A",
+                "5m",
+                "now"
+              ]
             },
-            {
-              "format": "short",
-              "label": "",
-              "logBase": 1,
-              "max": null,
-              "min": null,
-              "show": true
-            }
-          ]
+            "reducer": {
+              "params": [],
+              "type": "avg"
+            },
+            "type": "query"
+          }
+        ],
+        "executionErrorState": "alerting",
+        "for": "900000h",
+        "frequency": "1m",
+        "handler": 1,
+        "name": "Always Pending",
+        "noDataState": "no_data",
+        "notifications": []
+      },
+      "aliasColors": {},
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "gdev-testdata",
+      "editable": true,
+      "error": false,
+      "fill": 1,
+      "gridPos": {
+        "h": 7,
+        "w": 12,
+        "x": 12,
+        "y": 14
+      },
+      "id": 7,
+      "isNew": true,
+      "legend": {
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 2,
+      "links": [],
+      "nullPointMode": "connected",
+      "percentage": false,
+      "pointradius": 5,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "refId": "A",
+          "scenario": "random_walk",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "200,445,100,150,200,220,190",
+          "target": ""
         }
       ],
-      "title": "New row"
+      "thresholds": [
+        {
+          "colorMode": "critical",
+          "fill": true,
+          "line": true,
+          "op": "gt",
+          "value": 100
+        }
+      ],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "Always Alerting with For",
+      "tooltip": {
+        "msResolution": false,
+        "shared": true,
+        "sort": 0,
+        "value_type": "cumulative"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "short",
+          "label": "",
+          "logBase": 1,
+          "max": null,
+          "min": "0",
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": "",
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     }
   ],
+  "revision": 2,
+  "schemaVersion": 16,
+  "style": "dark",
+  "tags": [
+    "grafana-test"
+  ],
+  "templating": {
+    "list": []
+  },
   "time": {
     "from": "now-6h",
     "to": "now"
@@ -274,14 +705,8 @@
       "30d"
     ]
   },
-  "templating": {
-    "list": []
-  },
-  "annotations": {
-    "list": []
-  },
-  "schemaVersion": 13,
-  "version": 4,
-  "links": [],
-  "gnetId": null
-}
+  "timezone": "browser",
+  "title": "Alerting with TestData",
+  "uid": "7MeksYbmk",
+  "version": 7
+}

+ 1 - 1
devenv/docker/ha_test/docker-compose.yaml

@@ -9,7 +9,7 @@ services:
       - /var/run/docker.sock:/tmp/docker.sock:ro
 
   db:
-    image: mysql
+    image: mysql:5.6
     environment:
       MYSQL_ROOT_PASSWORD: rootpass
       MYSQL_DATABASE: grafana

+ 1 - 0
devenv/docker/ha_test/grafana/provisioning/alerts.jsonnet

@@ -39,6 +39,7 @@ local alertDashboardTemplate = {
         "executionErrorState": "alerting",
         "frequency": "10s",
         "handler": 1,
+        "for": "1m",
         "name": "bulk alerting",
         "noDataState": "no_data",
         "notifications": [

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

@@ -230,4 +230,4 @@ By default Grafana will delete dashboards in the database if the file is removed
 > **Note.** Provisioning allows you to overwrite existing dashboards
 > which leads to problems if you re-use settings that are supposed to be unique.
 > Be careful not to re-use the same `title` multiple times within a folder
-> or `uid` within the same installation as this will cause weird behaviours.
+> or `uid` within the same installation as this will cause weird behaviors.

+ 5 - 1
docs/sources/alerting/notifications.md

@@ -55,6 +55,10 @@ Alert rule evaluation interval | Send reminders every | Reminder sent every (aft
 
 <div class="clearfix"></div>
 
+### Disable resolve message
+
+When checked, this option will disable resolve message [OK] that is sent when alerting state returns to false.
+
 ## Supported Notification Types
 
 Grafana ships with the following set of notification types:
@@ -132,7 +136,7 @@ In DingTalk PC Client:
 
 2. Click "Robot Manage" item in the pop menu, there will be a new panel call "Robot Manage".
 
-3. In the  "Robot Manage" panel, select "customised: customised robot with Webhook".
+3. In the  "Robot Manage" panel, select "customized: customized robot with Webhook".
 
 4. In the next new panel named "robot detail", click "Add" button.
 

+ 16 - 3
docs/sources/alerting/rules.md

@@ -39,7 +39,7 @@ Currently alerting supports a limited form of high availability. Since v4.2.0 of
 
 ## Rule Config
 
-{{< imgbox max-width="40%" img="/img/docs/v4/alerting_conditions.png" caption="Alerting Conditions" >}}
+
 
 Currently only the graph panel supports alert rules but this will be added to the **Singlestat** and **Table**
 panels as well in a future release.
@@ -48,6 +48,19 @@ panels as well in a future release.
 
 Here you can specify the name of the alert rule and how often the scheduler should evaluate the alert rule.
 
+### For
+
+> This setting is available in Grafana 5.4 and above.
+
+If an alert rule has a configured `For` and the query violates the configured threshold it will first go from `OK` to `Pending`. Going from `OK` to `Pending` Grafana will not send any notifications. Once the alert rule has been firing for more than `For` duration, it will change to `Alerting` and send alert notifications. 
+
+Typically, it's always a good idea to use this setting since it's often worse to get false positive than wait a few minutes before the alert notification triggers. Looking at the `Alert list` or `Alert list panels` you will be able to see alerts in pending state. 
+
+Below you can see an example timeline of an alert using the `For` setting. At ~16:04 the alert state changes to `Pending` and after 4 minutes it changes to `Alerting` which is when alert notifications are sent. Once the series falls back to normal the alert rule goes back to `OK`.
+{{< imgbox img="/img/docs/v54/alerting-for-dark-theme.png" caption="Alerting For" >}}
+
+{{< imgbox max-width="40%" img="/img/docs/v4/alerting_conditions.png" caption="Alerting Conditions" >}}
+
 ### Conditions
 
 Currently the only condition type that exists is a `Query` condition that allows you to
@@ -57,11 +70,11 @@ specify a query letter, time range and an aggregation function.
 ### Query condition example
 
 ```sql
-avg() OF query(A, 5m, now) IS BELOW 14
+avg() OF query(A, 15m, now) IS BELOW 14
 ```
 
 - `avg()` Controls how the values for **each** series should be reduced to a value that can be compared against the threshold. Click on the function to change it to another aggregation function.
-- `query(A, 5m, now)`  The letter defines what query to execute from the **Metrics** tab. The second two parameters define the time range, `5m, now` means 5 minutes ago to now. You can also do `10m, now-2m` to define a time range that will be 10 minutes ago to 2 minutes ago. This is useful if you want to ignore the last 2 minutes of data.
+- `query(A, 15m, now)`  The letter defines what query to execute from the **Metrics** tab. The second two parameters define the time range, `15m, now` means 15 minutes ago to now. You can also do `10m, now-2m` to define a time range that will be 10 minutes ago to 2 minutes ago. This is useful if you want to ignore the last 2 minutes of data.
 - `IS BELOW 14`  Defines the type of threshold and the threshold value.  You can click on `IS BELOW` to change the type of threshold.
 
 The query used in an alert rule cannot contain any template variables. Currently we only support `AND` and `OR` operators between conditions and they are executed serially.

+ 3 - 2
docs/sources/auth/ldap.md

@@ -3,6 +3,7 @@ title = "LDAP Authentication"
 description = "Grafana LDAP Authentication Guide "
 keywords = ["grafana", "configuration", "documentation", "ldap", "active directory"]
 type = "docs"
+aliases = ["/installation/ldap/"]
 [menu.docs]
 name = "LDAP"
 identifier = "ldap"
@@ -162,9 +163,9 @@ org_role = "Viewer"
 Setting | Required | Description | Default
 ------------ | ------------ | ------------- | -------------
 `group_dn` | Yes | LDAP distinguished name (DN) of LDAP group. If you want to match all (or no LDAP groups) then you can use wildcard (`"*"`) |
-`org_role` | Yes | Assign users of `group_dn` the organisation role `"Admin"`, `"Editor"` or `"Viewer"` |
+`org_role` | Yes | Assign users of `group_dn` the organization role `"Admin"`, `"Editor"` or `"Viewer"` |
 `org_id` | No | The Grafana organization database id. Setting this allows for multiple group_dn's to be assigned to the same `org_role` provided the `org_id` differs | `1` (default org id)
-`grafana_admin` | No | When `true` makes user of `group_dn` Grafana server admin. A Grafana server admin has admin access over all organisations and users. Available in Grafana v5.3 and above | `false`
+`grafana_admin` | No | When `true` makes user of `group_dn` Grafana server admin. A Grafana server admin has admin access over all organizations and users. Available in Grafana v5.3 and above | `false`
 
 ### Nested/recursive group membership
 

+ 21 - 1
docs/sources/auth/overview.md

@@ -73,7 +73,18 @@ You can hide the Grafana login form using the below configuration settings.
 
 ```bash
 [auth]
-disable_login_form ⁼ true
+disable_login_form = true
+```
+
+### Automatic OAuth login
+
+Set to true to attempt login with OAuth automatically, skipping the login screen.
+This setting is ignored if multiple OAuth providers are configured.
+Defaults to `false`.
+
+```bash
+[auth]
+oauth_auto_login = true
 ```
 
 ### Hide sign-out menu
@@ -84,3 +95,12 @@ Set to the option detailed below to true to hide sign-out menu link. Useful if y
 [auth]
 disable_signout_menu = true
 ```
+
+### URL redirect after signing out
+
+URL to redirect the user to after signing out from Grafana. This can for example be used to enable signout from oauth provider.
+
+```bash
+[auth]
+signout_redirect_url =
+```

+ 2 - 2
docs/sources/contribute/cla.md

@@ -1,6 +1,6 @@
 +++
-title = "Contributor Licence Agreement (CLA)"
-description = "Contributor Licence Agreement (CLA)"
+title = "Contributor License Agreement (CLA)"
+description = "Contributor License Agreement (CLA)"
 type = "docs"
 aliases = ["/project/cla", "docs/contributing/cla.html"]
 [menu.docs]

+ 1 - 1
docs/sources/enterprise/index.md

@@ -31,7 +31,7 @@ Datasource permissions allow you to restrict query access to only specific Teams
 
 ### Premium Plugins
 
-With a Grafana Enterprise licence you will get access to premium plugins, including:
+With a Grafana Enterprise license you will get access to premium plugins, including:
 
 * [Splunk](https://grafana.com/plugins/grafana-splunk-datasource)
 * [AppDynamics](https://grafana.com/plugins/dlopes7-appdynamics-datasource)

+ 3 - 3
docs/sources/features/datasources/mysql.md

@@ -133,9 +133,9 @@ Macro example | Description
 ------------ | -------------
 *$__time(dateColumn)* | Will be replaced by an expression to convert to a UNIX timestamp and rename the column to `time_sec`. For example, *UNIX_TIMESTAMP(dateColumn) as time_sec*
 *$__timeEpoch(dateColumn)* | Will be replaced by an expression to convert to a UNIX timestamp and rename the column to `time_sec`. For example, *UNIX_TIMESTAMP(dateColumn) as time_sec*
-*$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *dateColumn BETWEEN '2017-04-21T05:01:17Z' AND '2017-04-21T05:06:17Z'*
-*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *'2017-04-21T05:01:17Z'*
-*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *'2017-04-21T05:06:17Z'*
+*$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *dateColumn BETWEEN FROM_UNIXTIME(1494410783) AND FROM_UNIXTIME(1494410983)*
+*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *FROM_UNIXTIME(1494410783)*
+*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *FROM_UNIXTIME(1494410983)*
 *$__timeGroup(dateColumn,'5m')* | Will be replaced by an expression usable in GROUP BY clause. For example, *cast(cast(UNIX_TIMESTAMP(dateColumn)/(300) as signed)*300 as signed),*
 *$__timeGroup(dateColumn,'5m', 0)* | Same as above but with a fill parameter so missing points in that series will be added by grafana and 0 will be used as value.
 *$__timeGroup(dateColumn,'5m', NULL)* | Same as above but NULL will be used as value for missing points.

+ 14 - 4
docs/sources/features/datasources/stackdriver.md

@@ -158,9 +158,9 @@ Example Result: `compute.googleapis.com/instance/cpu/usage_time - server1-prod`
 
 It is also possible to resolve the name of the Monitored Resource Type. 
 
-| Alias Pattern Format     | Description                                     | Example Result   |
-| ------------------------ | ------------------------------------------------| ---------------- |
-| `{{resource.type}}`      | returns the name of the monitored resource type | `gce_instance`     |
+| Alias Pattern Format | Description                                     | Example Result |
+| -------------------- | ----------------------------------------------- | -------------- |
+| `{{resource.type}}`  | returns the name of the monitored resource type | `gce_instance` |
 
 Example Alias By: `{{resource.type}} - {{metric.type}}`
 
@@ -177,7 +177,17 @@ types of template variables.
 
 ### Query Variable
 
-Writing variable queries is not supported yet.
+Variable of the type *Query* allows you to query Stackdriver for various types of data. The Stackdriver data source plugin provides the following `Query Types`.
+
+| Name                | Description                                                                                       |
+| ------------------- | ------------------------------------------------------------------------------------------------- |
+| *Metric Types*      | Returns a list of metric type names that are available for the specified service.                 |
+| *Labels Keys*       | Returns a list of keys for `metric label` and `resource label` in the specified metric.           |
+| *Labels Values*     | Returns a list of values for the label in the specified metric.                                   |
+| *Resource Types*    | Returns a list of resource types for the the specified metric.                                    |
+| *Aggregations*      | Returns a list of aggregations (cross series reducers) for the the specified metric.              |
+| *Aligners*          | Returns a list of aligners (per series aligners) for the the specified metric.                    |
+| *Alignment periods* | Returns a list of all alignment periods that are available in Stackdriver query editor in Grafana |
 
 ### Using variables in queries
 

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

@@ -134,7 +134,7 @@ 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 then click it to expand the row menu.
 
-There are 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 behavior. 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.
 

+ 83 - 0
docs/sources/guides/whats-new-in-v5-4.md

@@ -0,0 +1,83 @@
++++
+title = "What's New in Grafana v5.4"
+description = "Feature & improvement highlights for Grafana v5.4"
+keywords = ["grafana", "new", "documentation", "5.4"]
+type = "docs"
+[menu.docs]
+name = "Version 5.4"
+identifier = "v5.4"
+parent = "whatsnew"
+weight = -10
++++
+
+# What's New in Grafana v5.4
+
+Grafana v5.4 brings new features, many enhancements and bug fixes. This article will detail the major new features and enhancements.
+
+- [Alerting]({{< relref "#alerting" >}}) Limit false positives with the new `For` setting
+- [Google Stackdriver]({{< relref "#google-stackdriver" >}}) Now with support for templating queries
+- [MySQL]({{< relref "#mysql-query-builder" >}}) gets a new query builder!
+- [Graph Panel]({{< relref "#graph-panel-enhancements" >}}) Highlight time regions and more
+- [Team Preferences]({{< relref "#team-preferences" >}}) Give your teams their own home dashboard
+
+## Alerting
+
+{{< docs-imagebox img="/img/docs/v54/alerting-for-dark-theme.png" max-width="600px" class="docs-image--right" >}}
+
+Grafana v5.4 ships with a new alert rule setting named `For` which is great for removing false positives. If an alert rule has a configured `For` and the query violates the configured threshold it will first go from `OK` to `Pending`. Going from `OK` to `Pending` Grafana will not send any notifications. Once the alert rule has been firing for more than `For` duration, it will change to `Alerting` and send alert notifications. Typically, it's always a good idea to use this setting since it's often worse to get false positive than wait a few minutes before the alert notification triggers.
+
+In the screenshot you can see an example timeline of an alert using the `For` setting. At ~16:04 the alert state changes to `Pending` and after 4 minutes it changes to `Alerting` which is when alert notifications are sent. Once the series falls back to normal the alert rule goes back to `OK`. [Learn more](/alerting/rules/#for).
+
+Additionally, there's now support for disable the sending of `OK` alert notifications. [Learn more](/alerting/notifications/#disable-resolve-message).
+
+<div class="clearfix"></div>
+
+## Google Stackdriver
+
+{{< docs-imagebox img="/img/docs/v54/stackdriver_template_query.png" max-width="600px" class="docs-image--right" >}}
+
+Grafana v5.3 included built-in support for [Google Stackdriver](https://cloud.google.com/stackdriver/) which enables you to visualize your Stackdriver metrics in Grafana.
+One important feature missing was support for templating queries. This is now included together with a brand new templating query editor for Stackdriver.
+
+The Stackdriver templating query editor lets you choose from a set of different Query Types. This will in turn reveal additional drop downs to help you
+find, filter and select the templating values you're interested in, see screenshot for details. The templating query editor also supports chaining multiple variables
+making it easy to define variables that's dependent on other variables.
+
+Stackdriver is the first datasource which has support for a custom templating query editor. But starting from Grafana v5.4 it's now possible for all datasources, including plugin datasources, to
+create their very own templating query editor.
+
+Additionally, if Grafana is running on a Google Compute Engine (GCE) virtual machine, it is now possible for Grafana to automatically retrieve default credentials from the metadata server.
+This has the advantage of not needing to generate a private key file for the service account and also not having to upload the file to Grafana. [Learn more](/features/datasources/stackdriver/#using-gce-default-service-account).
+
+Please read [Using Google Stackdriver in Grafana](/features/datasources/stackdriver/) for more detailed information on how to get started and use it.
+
+<div class="clearfix"></div>
+
+## MySQL Query Builder
+
+Grafana v5.4 comes with a new graphical query builder for MySQL. This brings MySQL integration more in line with some of the other datasources and makes it easier for both advanced users and beginners to work with timeseries in MySQL. Learn more about it in the [documentation](/features/datasources/mysql/#query-editor).
+
+{{< docs-imagebox img="/img/docs/v54/mysql_query_still.png" animated-gif="/img/docs/v54/mysql_query.gif" >}}
+
+## Graph Panel Enhancements
+
+Grafana v5.4 adds support for highlighting weekdays and/or certain timespans in the graph panel. This should make it easier to compare for example weekends, business hours and/or off work hours.
+
+{{< docs-imagebox img="/img/docs/v54/graph_time_regions.png" max-width= "800px" >}}
+
+Additionally, when rendering series as lines in the graph panel, should there be only one data point available for one series so that a connecting line cannot be established, a point will
+automatically be rendered for that data point. This should make it easier to understand what's going on when only receiving a single data point.
+
+{{< docs-imagebox img="/img/docs/v54/graph_dot_single_point.png" max-width= "800px" >}}
+
+## Team Preferences
+
+Grafana v5.4 adds support for customizing home dashboard, timezone and theme for teams, in addition to the existing customization on Organization and user Profile level.
+
+1. Specifying a preference on User Profile level will override preference on Team and/or Organization level
+2. Specifying a preference on Team level will override preference on Organization level.
+
+## Changelog
+
+Checkout the [CHANGELOG.md](https://github.com/grafana/grafana/blob/master/CHANGELOG.md) file for a complete list
+of new features, changes, and bug fixes.

+ 4 - 1
docs/sources/http_api/alerting.md

@@ -160,11 +160,14 @@ Content-Type: application/json
 
 `POST /api/admin/pause-all-alerts`
 
+Only works with Basic Authentication (username and password). See [introduction](http://docs.grafana.org/http_api/admin/#admin-api) for an explanation.
+
+**Example Request**:
+
 ```http
 POST /api/admin/pause-all-alerts HTTP/1.1
 Accept: application/json
 Content-Type: application/json
-Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
 
 {
   "paused": true

+ 1 - 1
docs/sources/http_api/index.md

@@ -26,7 +26,7 @@ dashboards, creating users and updating data sources.
 * [Folder Permissions API]({{< relref "http_api/folder_permissions.md" >}})
 * [Folder/dashboard search API]({{< relref "/http_api/folder_dashboard_search.md" >}})
 * [Data Source API]({{< relref "http_api/data_source.md" >}})
-* [Organisation API]({{< relref "http_api/org.md" >}})
+* [Organization API]({{< relref "http_api/org.md" >}})
 * [Snapshot API]({{< relref "http_api/snapshot.md" >}})
 * [Annotations API]({{< relref "http_api/annotations.md" >}})
 * [Alerting API]({{< relref "http_api/alerting.md" >}})

+ 39 - 39
docs/sources/http_api/org.md

@@ -1,24 +1,24 @@
 +++
-title = "Organisation HTTP API "
-description = "Grafana Organisation HTTP API"
-keywords = ["grafana", "http", "documentation", "api", "organisation"]
-aliases = ["/http_api/organisation/"]
+title = "Organization HTTP API "
+description = "Grafana Organization HTTP API"
+keywords = ["grafana", "http", "documentation", "api", "organization"]
+aliases = ["/http_api/organization/"]
 type = "docs"
 [menu.docs]
-name = "Organisation"
+name = "Organization"
 parent = "http_api"
 +++
 
 
-# Organisation API
+# Organization API
 
-The Organisation HTTP API is divided in two resources, `/api/org` (current organisation)
-and `/api/orgs` (admin organisations). One big difference between these are that
-the admin of all organisations API only works with basic authentication, see [Admin Organisations API](#admin-organisations-api) for more information.
+The Organization HTTP API is divided in two resources, `/api/org` (current organization)
+and `/api/orgs` (admin organizations). One big difference between these are that
+the admin of all organizations API only works with basic authentication, see [Admin Organizations API](#admin-organizations-api) for more information.
 
-## Current Organisation API
+## Current Organization API
 
-### Get current Organisation
+### Get current Organization
 
 `GET /api/org/`
 
@@ -43,7 +43,7 @@ Content-Type: application/json
 }
 ```
 
-### Get all users within the current organisation
+### Get all users within the current organization
 
 `GET /api/org/users`
 
@@ -99,7 +99,7 @@ Content-Type: application/json
 {"message":"Organization user updated"}
 ```
 
-### Delete user in current organisation
+### Delete user in current organization
 
 `DELETE /api/org/users/:userId`
 
@@ -121,7 +121,7 @@ Content-Type: application/json
 {"message":"User removed from organization"}
 ```
 
-### Update current Organisation
+### Update current Organization
 
 `PUT /api/org`
 
@@ -147,11 +147,11 @@ Content-Type: application/json
 {"message":"Organization updated"}
 ```
 
-### Add a new user to the current organisation
+### Add a new user to the current organization
 
 `POST /api/org/users`
 
-Adds a global user to the current organisation.
+Adds a global user to the current organization.
 
 **Example Request**:
 
@@ -176,19 +176,19 @@ Content-Type: application/json
 {"message":"User added to organization"}
 ```
 
-## Admin Organisations API
+## Admin Organizations API
 
-The Admin Organisations HTTP API does not currently work with an API Token. API Tokens are currently
+The Admin Organizations HTTP API does not currently work with an API Token. API Tokens are currently
 only linked to an organization and an organization role. They cannot be given the permission of server
 admin, only users can be given that permission. So in order to use these API calls you will have to
 use Basic Auth and the Grafana user must have the Grafana Admin permission (The default admin user
 is called `admin` and has permission to use this API).
 
-### Get Organisation by Id
+### Get Organization by Id
 
 `GET /api/orgs/:orgId`
 
-Only works with Basic Authentication (username and password), see [introduction](#admin-organisations-api).
+Only works with Basic Authentication (username and password), see [introduction](#admin-organizations-api).
 
 **Example Request**:
 
@@ -217,11 +217,11 @@ Content-Type: application/json
   }
 }
 ```
-### Get Organisation by Name
+### Get Organization by Name
 
 `GET /api/orgs/name/:orgName`
 
-Only works with Basic Authentication (username and password), see [introduction](#admin-organisations-api).
+Only works with Basic Authentication (username and password), see [introduction](#admin-organizations-api).
 
 **Example Request**:
 
@@ -251,11 +251,11 @@ Content-Type: application/json
 }
 ```
 
-### Create Organisation
+### Create Organization
 
 `POST /api/orgs`
 
-Only works with Basic Authentication (username and password), see [introduction](#admin-organisations-api).
+Only works with Basic Authentication (username and password), see [introduction](#admin-organizations-api).
 
 **Example Request**:
 
@@ -284,11 +284,11 @@ Content-Type: application/json
 }
 ```
 
-### Search all Organisations
+### Search all Organizations
 
 `GET /api/orgs`
 
-Only works with Basic Authentication (username and password), see [introduction](#admin-organisations-api).
+Only works with Basic Authentication (username and password), see [introduction](#admin-organizations-api).
 
 **Example Request**:
 
@@ -314,12 +314,12 @@ Content-Type: application/json
 ]
 ```
 
-### Update Organisation
+### Update Organization
 
 `PUT /api/orgs/:orgId`
 
-Update Organisation, fields *Address 1*, *Address 2*, *City* are not implemented yet.
-Only works with Basic Authentication (username and password), see [introduction](#admin-organisations-api).
+Update Organization, fields *Address 1*, *Address 2*, *City* are not implemented yet.
+Only works with Basic Authentication (username and password), see [introduction](#admin-organizations-api).
 
 **Example Request**:
 
@@ -342,11 +342,11 @@ Content-Type: application/json
 {"message":"Organization updated"}
 ```
 
-### Delete Organisation
+### Delete Organization
 
 `DELETE /api/orgs/:orgId`
 
-Only works with Basic Authentication (username and password), see [introduction](#admin-organisations-api).
+Only works with Basic Authentication (username and password), see [introduction](#admin-organizations-api).
 
 **Example Request**:
 
@@ -364,11 +364,11 @@ Content-Type: application/json
 {"message":"Organization deleted"}
 ```
 
-### Get Users in Organisation
+### Get Users in Organization
 
 `GET /api/orgs/:orgId/users`
 
-Only works with Basic Authentication (username and password), see [introduction](#admin-organisations-api).
+Only works with Basic Authentication (username and password), see [introduction](#admin-organizations-api).
 
 **Example Request**:
 
@@ -397,11 +397,11 @@ Content-Type: application/json
 ]
 ```
 
-### Add User in Organisation
+### Add User in Organization
 
 `POST /api/orgs/:orgId/users`
 
-Only works with Basic Authentication (username and password), see [introduction](#admin-organisations-api).
+Only works with Basic Authentication (username and password), see [introduction](#admin-organizations-api).
 
 **Example Request**:
 
@@ -425,11 +425,11 @@ Content-Type: application/json
 {"message":"User added to organization"}
 ```
 
-### Update Users in Organisation
+### Update Users in Organization
 
 `PATCH /api/orgs/:orgId/users/:userId`
 
-Only works with Basic Authentication (username and password), see [introduction](#admin-organisations-api).
+Only works with Basic Authentication (username and password), see [introduction](#admin-organizations-api).
 
 **Example Request**:
 
@@ -452,11 +452,11 @@ Content-Type: application/json
 {"message":"Organization user updated"}
 ```
 
-### Delete User in Organisation
+### Delete User in Organization
 
 `DELETE /api/orgs/:orgId/users/:userId`
 
-Only works with Basic Authentication (username and password), see [introduction](#admin-organisations-api).
+Only works with Basic Authentication (username and password), see [introduction](#admin-organizations-api).
 
 **Example Request**:
 

+ 37 - 3
docs/sources/http_api/user.md

@@ -196,7 +196,7 @@ Content-Type: application/json
 {"message":"User updated"}
 ```
 
-## Get Organisations for user
+## Get Organizations for user
 
 `GET /api/users/:id/orgs`
 
@@ -226,6 +226,40 @@ Content-Type: application/json
 ]
 ```
 
+## Get Teams for user
+
+`GET /api/users/:id/teams`
+
+**Example Request**:
+
+```http
+GET /api/users/1/teams HTTP/1.1
+Accept: application/json
+Content-Type: application/json
+Authorization: Basic YWRtaW46YWRtaW4=
+```
+
+Requires basic authentication and that the authenticated user is a Grafana Admin.
+
+**Example Response**:
+
+```http
+HTTP/1.1 200
+Content-Type: application/json
+
+[
+  {
+    "id":1,
+    "orgId":1,
+    "name":"team1",
+    "email":"",
+    "avatarUrl":"/avatar/3fcfe295eae3bcb67a49349377428a66",
+    "memberCount":1
+  }
+]
+```
+
+
 ## User
 
 ## Actual User
@@ -333,11 +367,11 @@ Content-Type: application/json
 {"message":"Active organization changed"}
 ```
 
-## Organisations of the actual User
+## Organizations of the actual User
 
 `GET /api/user/orgs`
 
-Return a list of all organisations of the current user.
+Return a list of all organizations of the current user.
 
 **Example Request**:
 

+ 3 - 3
docs/sources/index.md

@@ -60,9 +60,9 @@ aliases = ["v1.1", "guides/reference/admin"]
         <h4>Provisioning</h4>
         <p>A guide to help you automate your Grafana setup & configuration.</p>
     </a>
-    <a href="{{< relref "guides/whats-new-in-v5-3.md" >}}" class="nav-cards__item nav-cards__item--guide">
-        <h4>What's new in v5.3</h4>
-        <p>Article on all the new cool features and enhancements in v5.3</p>
+    <a href="{{< relref "guides/whats-new-in-v5-4.md" >}}" class="nav-cards__item nav-cards__item--guide">
+        <h4>What's new in v5.4</h4>
+        <p>Article on all the new cool features and enhancements in v5.4</p>
     </a>
     <a href="{{< relref "tutorials/screencasts.md" >}}" class="nav-cards__item nav-cards__item--guide">
         <h4>Screencasts</h4>

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

@@ -454,6 +454,12 @@ Ex `filters = sqlstore:debug`
 ### enabled
 Enable metrics reporting. defaults true. Available via HTTP API `/metrics`.
 
+### basic_auth_username
+If set configures the username to use for basic authentication on the metrics endpoint.
+
+### basic_auth_password
+If set configures the password to use for basic authentication on the metrics endpoint.
+
 ### interval_seconds
 
 Flush/Write interval when sending metrics to external TSDB. Defaults to 10s.

+ 2 - 0
docs/sources/reference/export_import.md

@@ -107,3 +107,5 @@ it as usual and then update the data source option in the metrics tab so that th
 data source. Another alternative is to open the json file in a a text editor and update the data source properties
 to value that matches a name of your data source.
 
+## Note
+In Grafana v5.3.4+ the export modal has new checkbox for sharing for external use (other instances). If the checkbox is not checked then the `__inputs` section will not be included in the exported JSON file.

+ 1 - 1
docs/sources/reference/templating.md

@@ -25,7 +25,7 @@ the value, using the dropdown at the top of the dashboard, your panel's metric q
 
 Panel titles and metric queries can refer to variables using two different syntaxes:
 
-- `$<varname>`  Example: apps.frontend.$server.requests.count
+- `$varname`  Example: apps.frontend.$server.requests.count
 - `[[varname]]` Example: apps.frontend.[[server]].requests.count
 
 Why two ways? The first syntax is easier to read and write but does not allow you to use a variable in the middle of word. Use

+ 2 - 1
docs/versions.json

@@ -1,5 +1,6 @@
 [
-  { "version": "v5.3", "path": "/", "archived": false, "current": true },
+  { "version": "v5.4", "path": "/", "archived": false, "current": true },
+  { "version": "v5.3", "path": "/v5.3", "archived": true },
   { "version": "v5.2", "path": "/v5.2", "archived": true },
   { "version": "v5.1", "path": "/v5.1", "archived": true },
   { "version": "v5.0", "path": "/v5.0", "archived": true },

+ 2 - 2
latest.json

@@ -1,4 +1,4 @@
 {
-  "stable": "5.3.4",
-  "testing": "5.3.4"
+  "stable": "5.4.0",
+  "testing": "5.4.0"
 }

+ 16 - 20
package.json

@@ -4,12 +4,18 @@
     "company": "Grafana Labs"
   },
   "name": "grafana",
-  "version": "5.4.0-pre1",
+  "version": "5.5.0-pre1",
   "repository": {
     "type": "git",
     "url": "http://github.com/grafana/grafana.git"
   },
   "devDependencies": {
+    "@babel/core": "^7.1.2",
+    "@rtsao/plugin-proposal-class-properties": "^7.0.1-patch.1",
+    "@babel/plugin-syntax-dynamic-import": "^7.0.0",
+    "@babel/preset-env": "^7.1.0",
+    "@babel/preset-react": "^7.0.0",
+    "@babel/preset-typescript": "^7.1.0",
     "@types/d3": "^4.10.1",
     "@types/enzyme": "^3.1.13",
     "@types/jest": "^23.3.2",
@@ -21,10 +27,10 @@
     "angular-mocks": "1.6.6",
     "autoprefixer": "^6.4.0",
     "axios": "^0.17.1",
-    "babel-core": "^6.26.0",
-    "babel-loader": "^7.1.4",
-    "babel-plugin-syntax-dynamic-import": "^6.18.0",
-    "babel-preset-es2015": "^6.24.1",
+    "babel-core": "^7.0.0-bridge",
+    "babel-jest": "^23.6.0",
+    "babel-loader": "^8.0.4",
+    "babel-plugin-angularjs-annotate": "^0.9.0",
     "clean-webpack-plugin": "^0.1.19",
     "css-loader": "^0.28.7",
     "enzyme": "^3.6.0",
@@ -108,18 +114,9 @@
     "precommit": "lint-staged && grunt precommit"
   },
   "lint-staged": {
-    "*.{ts,tsx}": [
-      "prettier --write",
-      "git add"
-    ],
-    "*.scss": [
-      "prettier --write",
-      "git add"
-    ],
-    "*pkg/**/*.go": [
-      "gofmt -w -s",
-      "git add"
-    ]
+    "*.{ts,tsx}": ["prettier --write", "git add"],
+    "*.scss": ["prettier --write", "git add"],
+    "*pkg/**/*.go": ["gofmt -w -s", "git add"]
   },
   "prettier": {
     "trailingComma": "es5",
@@ -128,13 +125,12 @@
   },
   "license": "Apache-2.0",
   "dependencies": {
+    "@babel/polyfill": "^7.0.0",
     "angular": "1.6.6",
     "angular-bindonce": "0.3.1",
     "angular-native-dragdrop": "1.2.2",
     "angular-route": "1.6.6",
     "angular-sanitize": "1.6.6",
-    "babel-jest": "^23.6.0",
-    "babel-polyfill": "^6.26.0",
     "baron": "^3.0.3",
     "brace": "^0.10.0",
     "classnames": "^2.2.5",
@@ -156,7 +152,7 @@
     "react-custom-scrollbars": "^4.2.1",
     "react-dom": "^16.5.0",
     "react-grid-layout": "0.16.6",
-    "react-highlight-words": "^0.10.0",
+    "react-highlight-words": "0.11.0",
     "react-popper": "^0.7.5",
     "react-redux": "^5.0.7",
     "react-select": "2.1.0",

+ 1 - 1
packaging/deb/init.d/grafana-server

@@ -56,7 +56,7 @@ if [ -f "$DEFAULT" ]; then
 	. "$DEFAULT"
 fi
 
-DAEMON_OPTS="--pidfile=${PID_FILE} --config=${CONF_FILE} cfg:default.paths.provisioning=$PROVISIONING_CFG_DIR cfg:default.paths.data=${DATA_DIR} cfg:default.paths.logs=${LOG_DIR} cfg:default.paths.plugins=${PLUGINS_DIR}"
+DAEMON_OPTS="--pidfile=${PID_FILE} --config=${CONF_FILE} --packaging=deb cfg:default.paths.provisioning=$PROVISIONING_CFG_DIR cfg:default.paths.data=${DATA_DIR} cfg:default.paths.logs=${LOG_DIR} cfg:default.paths.plugins=${PLUGINS_DIR}"
 
 function checkUser() {
   if [ `id -u` -ne 0 ]; then

+ 1 - 0
packaging/deb/systemd/grafana-server.service

@@ -17,6 +17,7 @@ RuntimeDirectoryMode=0750
 ExecStart=/usr/sbin/grafana-server                                                  \
                             --config=${CONF_FILE}                                   \
                             --pidfile=${PID_FILE_DIR}/grafana-server.pid            \
+                            --packaging=deb                                         \
                             cfg:default.paths.logs=${LOG_DIR}                       \
                             cfg:default.paths.data=${DATA_DIR}                      \
                             cfg:default.paths.plugins=${PLUGINS_DIR}                \

+ 2 - 1
packaging/docker/Dockerfile

@@ -25,7 +25,8 @@ ENV PATH=/usr/share/grafana/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bi
 
 WORKDIR $GF_PATHS_HOME
 
-RUN apt-get update && apt-get install -qq -y libfontconfig ca-certificates curl && \
+RUN apt-get update && apt-get -y upgrade && \
+    apt-get install -qq -y libfontconfig ca-certificates curl && \
     apt-get autoremove -y && \
     rm -rf /var/lib/apt/lists/*
 

+ 14 - 1
packaging/docker/build-enterprise.sh

@@ -1,12 +1,25 @@
 #!/bin/sh
 set -e
 
-_grafana_tag=$1
+_raw_grafana_tag=$1
 _docker_repo=${2:-grafana/grafana-enterprise}
 
+if echo "$_raw_grafana_tag" | grep -q "^v"; then
+  _grafana_tag=$(echo "${_raw_grafana_tag}" | cut -d "v" -f 2)
+else
+  _grafana_tag="${_raw_grafana_tag}"
+fi
+
+echo "Building and deploying ${_docker_repo}:${_grafana_tag}"
+
 docker build \
   --tag "${_docker_repo}:${_grafana_tag}"\
   --no-cache=true \
   .
 
 docker push "${_docker_repo}:${_grafana_tag}"
+
+if echo "$_raw_grafana_tag" | grep -q "^v" && echo "$_raw_grafana_tag" | grep -qv "beta"; then
+  docker tag "${_docker_repo}:${_grafana_tag}" "${_docker_repo}:latest"
+  docker push "${_docker_repo}:latest"
+fi

+ 1 - 0
packaging/docker/run.sh

@@ -80,6 +80,7 @@ fi
 exec grafana-server                                         \
   --homepath="$GF_PATHS_HOME"                               \
   --config="$GF_PATHS_CONFIG"                               \
+  --packaging=docker                                        \
   "$@"                                                      \
   cfg:default.log.mode="console"                            \
   cfg:default.paths.data="$GF_PATHS_DATA"                   \

+ 1 - 1
packaging/rpm/init.d/grafana-server

@@ -60,7 +60,7 @@ fi
 # overwrite settings from default file
 [ -e /etc/sysconfig/$NAME ] && . /etc/sysconfig/$NAME
 
-DAEMON_OPTS="--pidfile=${PID_FILE} --config=${CONF_FILE} cfg:default.paths.provisioning=$PROVISIONING_CFG_DIR cfg:default.paths.data=${DATA_DIR} cfg:default.paths.logs=${LOG_DIR} cfg:default.paths.plugins=${PLUGINS_DIR}"
+DAEMON_OPTS="--pidfile=${PID_FILE} --config=${CONF_FILE} --packaging=rpm cfg:default.paths.provisioning=$PROVISIONING_CFG_DIR cfg:default.paths.data=${DATA_DIR} cfg:default.paths.logs=${LOG_DIR} cfg:default.paths.plugins=${PLUGINS_DIR}"
 
 function isRunning() {
   status -p $PID_FILE $NAME > /dev/null 2>&1

+ 1 - 0
packaging/rpm/systemd/grafana-server.service

@@ -17,6 +17,7 @@ RuntimeDirectoryMode=0750
 ExecStart=/usr/sbin/grafana-server                                                  \
                             --config=${CONF_FILE}                                   \
                             --pidfile=${PID_FILE_DIR}/grafana-server.pid            \
+                            --packaging=rpm                                         \
                             cfg:default.paths.logs=${LOG_DIR}                       \
                             cfg:default.paths.data=${DATA_DIR}                      \
                             cfg:default.paths.plugins=${PLUGINS_DIR}                \

+ 6 - 0
pkg/api/admin_users.go

@@ -76,6 +76,7 @@ func AdminUpdateUserPassword(c *m.ReqContext, form dtos.AdminUpdateUserPasswordF
 	c.JsonOK("User password updated")
 }
 
+// PUT /api/admin/users/:id/permissions
 func AdminUpdateUserPermissions(c *m.ReqContext, form dtos.AdminUpdateUserPermissionsForm) {
 	userID := c.ParamsInt64(":id")
 
@@ -85,6 +86,11 @@ func AdminUpdateUserPermissions(c *m.ReqContext, form dtos.AdminUpdateUserPermis
 	}
 
 	if err := bus.Dispatch(&cmd); err != nil {
+		if err == m.ErrLastGrafanaAdmin {
+			c.JsonApiErr(400, m.ErrLastGrafanaAdmin.Error(), nil)
+			return
+		}
+
 		c.JsonApiErr(500, "Failed to update user permissions", err)
 		return
 	}

+ 50 - 0
pkg/api/admin_users_test.go

@@ -0,0 +1,50 @@
+package api
+
+import (
+	"testing"
+
+	"github.com/grafana/grafana/pkg/api/dtos"
+	"github.com/grafana/grafana/pkg/bus"
+	m "github.com/grafana/grafana/pkg/models"
+
+	. "github.com/smartystreets/goconvey/convey"
+)
+
+func TestAdminApiEndpoint(t *testing.T) {
+	role := m.ROLE_ADMIN
+	Convey("Given a server admin attempts to remove themself as an admin", t, func() {
+
+		updateCmd := dtos.AdminUpdateUserPermissionsForm{
+			IsGrafanaAdmin: false,
+		}
+
+		bus.AddHandler("test", func(cmd *m.UpdateUserPermissionsCommand) error {
+			return m.ErrLastGrafanaAdmin
+		})
+
+		putAdminScenario("When calling PUT on", "/api/admin/users/1/permissions", "/api/admin/users/:id/permissions", role, updateCmd, func(sc *scenarioContext) {
+			sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
+			So(sc.resp.Code, ShouldEqual, 400)
+		})
+	})
+}
+
+func putAdminScenario(desc string, url string, routePattern string, role m.RoleType, cmd dtos.AdminUpdateUserPermissionsForm, fn scenarioFunc) {
+	Convey(desc+" "+url, func() {
+		defer bus.ClearBusHandlers()
+
+		sc := setupScenarioContext(url)
+		sc.defaultHandler = Wrap(func(c *m.ReqContext) {
+			sc.context = c
+			sc.context.UserId = TestUserID
+			sc.context.OrgId = TestOrgID
+			sc.context.OrgRole = role
+
+			AdminUpdateUserPermissions(c, cmd)
+		})
+
+		sc.m.Put(routePattern, sc.defaultHandler)
+
+		fn(sc)
+	})
+}

+ 1 - 1
pkg/api/alerting.go

@@ -295,7 +295,7 @@ func PauseAlert(c *m.ReqContext, dto dtos.PauseAlertCommand) Response {
 		return Error(500, "", err)
 	}
 
-	var response m.AlertStateType = m.AlertStatePending
+	var response m.AlertStateType = m.AlertStateUnknown
 	pausedState := "un-paused"
 	if cmd.Paused {
 		response = m.AlertStatePaused

+ 1 - 0
pkg/api/api.go

@@ -140,6 +140,7 @@ func (hs *HTTPServer) registerRoutes() {
 			usersRoute.Get("/", Wrap(SearchUsers))
 			usersRoute.Get("/search", Wrap(SearchUsersWithPaging))
 			usersRoute.Get("/:id", Wrap(GetUserByID))
+			usersRoute.Get("/:id/teams", Wrap(GetUserTeams))
 			usersRoute.Get("/:id/orgs", Wrap(GetUserOrgList))
 			// query parameters /users/lookup?loginOrEmail=admin@example.com
 			usersRoute.Get("/lookup", Wrap(GetUserByLoginOrEmail))

+ 19 - 0
pkg/api/basic_auth.go

@@ -0,0 +1,19 @@
+package api
+
+import (
+	"crypto/subtle"
+	macaron "gopkg.in/macaron.v1"
+)
+
+// BasicAuthenticatedRequest parses the provided HTTP request for basic authentication credentials
+// and returns true if the provided credentials match the expected username and password.
+// Returns false if the request is unauthenticated.
+// Uses constant-time comparison in order to mitigate timing attacks.
+func BasicAuthenticatedRequest(req macaron.Request, expectedUser, expectedPass string) bool {
+	user, pass, ok := req.BasicAuth()
+	if !ok || subtle.ConstantTimeCompare([]byte(user), []byte(expectedUser)) != 1 || subtle.ConstantTimeCompare([]byte(pass), []byte(expectedPass)) != 1 {
+		return false
+	}
+
+	return true
+}

+ 45 - 0
pkg/api/basic_auth_test.go

@@ -0,0 +1,45 @@
+package api
+
+import (
+	"encoding/base64"
+	"fmt"
+	"net/http"
+	"testing"
+
+	. "github.com/smartystreets/goconvey/convey"
+	"gopkg.in/macaron.v1"
+)
+
+func TestBasicAuthenticatedRequest(t *testing.T) {
+	expectedUser := "prometheus"
+	expectedPass := "password"
+
+	Convey("Given a valid set of basic auth credentials", t, func() {
+		httpReq, err := http.NewRequest("GET", "http://localhost:3000/metrics", nil)
+		So(err, ShouldBeNil)
+		req := macaron.Request{
+			Request: httpReq,
+		}
+		encodedCreds := encodeBasicAuthCredentials(expectedUser, expectedPass)
+		req.Header.Add("Authorization", fmt.Sprintf("Basic %s", encodedCreds))
+		authenticated := BasicAuthenticatedRequest(req, expectedUser, expectedPass)
+		So(authenticated, ShouldBeTrue)
+	})
+
+	Convey("Given an invalid set of basic auth credentials", t, func() {
+		httpReq, err := http.NewRequest("GET", "http://localhost:3000/metrics", nil)
+		So(err, ShouldBeNil)
+		req := macaron.Request{
+			Request: httpReq,
+		}
+		encodedCreds := encodeBasicAuthCredentials("invaliduser", "invalidpass")
+		req.Header.Add("Authorization", fmt.Sprintf("Basic %s", encodedCreds))
+		authenticated := BasicAuthenticatedRequest(req, expectedUser, expectedPass)
+		So(authenticated, ShouldBeFalse)
+	})
+}
+
+func encodeBasicAuthCredentials(user, pass string) string {
+	creds := fmt.Sprintf("%s:%s", user, pass)
+	return base64.StdEncoding.EncodeToString([]byte(creds))
+}

+ 0 - 4
pkg/api/dashboard.go

@@ -277,10 +277,6 @@ func PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand) Response {
 		return Error(500, "Failed to save dashboard", err)
 	}
 
-	if err == m.ErrDashboardFailedToUpdateAlertData {
-		return Error(500, "Invalid alert data. Cannot save dashboard", err)
-	}
-
 	c.TimeRequest(metrics.M_Api_Dashboard_Save)
 	return JSON(200, util.DynMap{
 		"status":  "success",

+ 0 - 1
pkg/api/dashboard_test.go

@@ -727,7 +727,6 @@ func TestDashboardApiEndpoint(t *testing.T) {
 				{SaveError: m.ErrDashboardTitleEmpty, ExpectedStatusCode: 400},
 				{SaveError: m.ErrDashboardFolderCannotHaveParent, ExpectedStatusCode: 400},
 				{SaveError: alerting.ValidationError{Reason: "Mu"}, ExpectedStatusCode: 422},
-				{SaveError: m.ErrDashboardFailedToUpdateAlertData, ExpectedStatusCode: 500},
 				{SaveError: m.ErrDashboardFailedGenerateUniqueUid, ExpectedStatusCode: 500},
 				{SaveError: m.ErrDashboardTypeMismatch, ExpectedStatusCode: 400},
 				{SaveError: m.ErrDashboardFolderWithSameNameAsDashboard, ExpectedStatusCode: 400},

+ 9 - 0
pkg/api/http_server.go

@@ -245,6 +245,11 @@ func (hs *HTTPServer) metricsEndpoint(ctx *macaron.Context) {
 		return
 	}
 
+	if hs.metricsEndpointBasicAuthEnabled() && !BasicAuthenticatedRequest(ctx.Req, hs.Cfg.MetricsEndpointBasicAuthUsername, hs.Cfg.MetricsEndpointBasicAuthPassword) {
+		ctx.Resp.WriteHeader(http.StatusUnauthorized)
+		return
+	}
+
 	promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{}).
 		ServeHTTP(ctx.Resp, ctx.Req.Request)
 }
@@ -299,3 +304,7 @@ func (hs *HTTPServer) mapStatic(m *macaron.Macaron, rootDir string, dir string,
 		},
 	))
 }
+
+func (hs *HTTPServer) metricsEndpointBasicAuthEnabled() bool {
+	return hs.Cfg.MetricsEndpointBasicAuthUsername != "" && hs.Cfg.MetricsEndpointBasicAuthPassword != ""
+}

+ 30 - 0
pkg/api/http_server_test.go

@@ -0,0 +1,30 @@
+package api
+
+import (
+	"testing"
+
+	"github.com/grafana/grafana/pkg/setting"
+	. "github.com/smartystreets/goconvey/convey"
+)
+
+func TestHTTPServer(t *testing.T) {
+	Convey("Given a HTTPServer", t, func() {
+		ts := &HTTPServer{
+			Cfg: setting.NewCfg(),
+		}
+
+		Convey("Given that basic auth on the metrics endpoint is enabled", func() {
+			ts.Cfg.MetricsEndpointBasicAuthUsername = "foo"
+			ts.Cfg.MetricsEndpointBasicAuthPassword = "bar"
+
+			So(ts.metricsEndpointBasicAuthEnabled(), ShouldBeTrue)
+		})
+
+		Convey("Given that basic auth on the metrics endpoint is disabled", func() {
+			ts.Cfg.MetricsEndpointBasicAuthUsername = ""
+			ts.Cfg.MetricsEndpointBasicAuthPassword = ""
+
+			So(ts.metricsEndpointBasicAuthEnabled(), ShouldBeFalse)
+		})
+	})
+}

+ 22 - 0
pkg/api/login.go

@@ -39,6 +39,10 @@ func (hs *HTTPServer) LoginView(c *m.ReqContext) {
 		viewData.Settings["loginError"] = loginError
 	}
 
+	if tryOAuthAutoLogin(c) {
+		return
+	}
+
 	if !tryLoginUsingRememberCookie(c) {
 		c.HTML(200, ViewIndex, viewData)
 		return
@@ -53,6 +57,24 @@ func (hs *HTTPServer) LoginView(c *m.ReqContext) {
 	c.Redirect(setting.AppSubUrl + "/")
 }
 
+func tryOAuthAutoLogin(c *m.ReqContext) bool {
+	if !setting.OAuthAutoLogin {
+		return false
+	}
+	oauthInfos := setting.OAuthService.OAuthInfos
+	if len(oauthInfos) != 1 {
+		log.Warn("Skipping OAuth auto login because multiple OAuth providers are configured.")
+		return false
+	}
+	for key := range setting.OAuthService.OAuthInfos {
+		redirectUrl := setting.AppSubUrl + "/login/" + key
+		log.Info("OAuth auto login enabled. Redirecting to " + redirectUrl)
+		c.Redirect(redirectUrl, 307)
+		return true
+	}
+	return false
+}
+
 func tryLoginUsingRememberCookie(c *m.ReqContext) bool {
 	// Check auto-login.
 	uname := c.GetCookie(setting.CookieUserName)

+ 8 - 0
pkg/api/password.go

@@ -4,10 +4,18 @@ import (
 	"github.com/grafana/grafana/pkg/api/dtos"
 	"github.com/grafana/grafana/pkg/bus"
 	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/setting"
 	"github.com/grafana/grafana/pkg/util"
 )
 
 func SendResetPasswordEmail(c *m.ReqContext, form dtos.SendResetPasswordEmailForm) Response {
+	if setting.LdapEnabled || setting.AuthProxyEnabled {
+		return Error(401, "Not allowed to reset password when LDAP or Auth Proxy is enabled", nil)
+	}
+	if setting.DisableLoginForm {
+		return Error(401, "Not allowed to reset password when login form is disabled", nil)
+	}
+
 	userQuery := m.GetUserByLoginQuery{LoginOrEmail: form.UserOrEmail}
 
 	if err := bus.Dispatch(&userQuery); err != nil {

+ 3 - 3
pkg/api/pluginproxy/ds_auth_provider.go

@@ -51,7 +51,7 @@ func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route
 		if token, err := tokenProvider.getAccessToken(data); err != nil {
 			logger.Error("Failed to get access token", "error", err)
 		} else {
-			req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
+			req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
 		}
 	}
 
@@ -60,7 +60,7 @@ func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route
 		if token, err := tokenProvider.getJwtAccessToken(ctx, data); err != nil {
 			logger.Error("Failed to get access token", "error", err)
 		} else {
-			req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
+			req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
 		}
 	}
 
@@ -73,7 +73,7 @@ func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route
 			if err != nil {
 				logger.Error("Failed to get default access token from meta data server", "error", err)
 			} else {
-				req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token.AccessToken))
+				req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.AccessToken))
 			}
 		}
 	}

+ 1 - 1
pkg/api/pluginproxy/pluginproxy.go

@@ -87,7 +87,7 @@ func NewApiPluginProxy(ctx *m.ReqContext, proxyPath string, route *plugins.AppPl
 			}
 
 			for key, value := range headers {
-				log.Trace("setting key %v value %v", key, value[0])
+				log.Trace("setting key %v value <redacted>", key)
 				req.Header.Set(key, value[0])
 			}
 		}

+ 11 - 3
pkg/api/user.go

@@ -113,7 +113,16 @@ func GetSignedInUserOrgList(c *m.ReqContext) Response {
 
 // GET /api/user/teams
 func GetSignedInUserTeamList(c *m.ReqContext) Response {
-	query := m.GetTeamsByUserQuery{OrgId: c.OrgId, UserId: c.UserId}
+	return getUserTeamList(c.OrgId, c.UserId)
+}
+
+// GET /api/users/:id/teams
+func GetUserTeams(c *m.ReqContext) Response {
+	return getUserTeamList(c.OrgId, c.ParamsInt64(":id"))
+}
+
+func getUserTeamList(userID int64, orgID int64) Response {
+	query := m.GetTeamsByUserQuery{OrgId: orgID, UserId: userID}
 
 	if err := bus.Dispatch(&query); err != nil {
 		return Error(500, "Failed to get user teams", err)
@@ -122,11 +131,10 @@ func GetSignedInUserTeamList(c *m.ReqContext) Response {
 	for _, team := range query.Result {
 		team.AvatarUrl = dtos.GetGravatarUrlWithDefault(team.Email, team.Name)
 	}
-
 	return JSON(200, query.Result)
 }
 
-// GET /api/user/:id/orgs
+// GET /api/users/:id/orgs
 func GetUserOrgList(c *m.ReqContext) Response {
 	return getUserOrgList(c.ParamsInt64(":id"))
 }

+ 17 - 2
pkg/cmd/grafana-server/main.go

@@ -13,7 +13,7 @@ import (
 	"syscall"
 	"time"
 
-	extensions "github.com/grafana/grafana/pkg/extensions"
+	"github.com/grafana/grafana/pkg/extensions"
 	"github.com/grafana/grafana/pkg/log"
 	"github.com/grafana/grafana/pkg/metrics"
 	_ "github.com/grafana/grafana/pkg/services/alerting/conditions"
@@ -39,6 +39,7 @@ var buildstamp string
 var configFile = flag.String("config", "", "path to config file")
 var homePath = flag.String("homepath", "", "path to grafana install/home path, defaults to working directory")
 var pidFile = flag.String("pidfile", "", "path to pid file")
+var packaging = flag.String("packaging", "unknown", "describes the way Grafana was installed")
 
 func main() {
 	v := flag.Bool("v", false, "prints current version and exits")
@@ -53,7 +54,10 @@ func main() {
 	if *profile {
 		runtime.SetBlockProfileRate(1)
 		go func() {
-			http.ListenAndServe(fmt.Sprintf("localhost:%d", *profilePort), nil)
+			err := http.ListenAndServe(fmt.Sprintf("localhost:%d", *profilePort), nil)
+			if err != nil {
+				panic(err)
+			}
 		}()
 
 		f, err := os.Create("trace.out")
@@ -79,6 +83,7 @@ func main() {
 	setting.BuildStamp = buildstampInt64
 	setting.BuildBranch = buildBranch
 	setting.IsEnterprise = extensions.IsEnterprise
+	setting.Packaging = validPackaging(*packaging)
 
 	metrics.SetBuildInformation(version, commit, buildBranch)
 
@@ -95,6 +100,16 @@ func main() {
 	os.Exit(code)
 }
 
+func validPackaging(packaging string) string {
+	validTypes := []string{"dev", "deb", "rpm", "docker", "brew", "hosted", "unknown"}
+	for _, vt := range validTypes {
+		if packaging == vt {
+			return packaging
+		}
+	}
+	return "unknown"
+}
+
 func listenToSystemSignals(server *GrafanaServerImpl) {
 	signalChan := make(chan os.Signal, 1)
 	sighupChan := make(chan os.Signal, 1)

+ 26 - 6
pkg/cmd/grafana-server/server.go

@@ -67,6 +67,7 @@ type GrafanaServerImpl struct {
 }
 
 func (g *GrafanaServerImpl) Run() error {
+	var err error
 	g.loadConfiguration()
 	g.writePIDFile()
 
@@ -74,20 +75,38 @@ func (g *GrafanaServerImpl) Run() error {
 	social.NewOAuthService()
 
 	serviceGraph := inject.Graph{}
-	serviceGraph.Provide(&inject.Object{Value: bus.GetBus()})
-	serviceGraph.Provide(&inject.Object{Value: g.cfg})
-	serviceGraph.Provide(&inject.Object{Value: routing.NewRouteRegister(middleware.RequestMetrics, middleware.RequestTracing)})
-	serviceGraph.Provide(&inject.Object{Value: cache.New(5*time.Minute, 10*time.Minute)})
+	err = serviceGraph.Provide(&inject.Object{Value: bus.GetBus()})
+	if err != nil {
+		return fmt.Errorf("Failed to provide object to the graph: %v", err)
+	}
+	err = serviceGraph.Provide(&inject.Object{Value: g.cfg})
+	if err != nil {
+		return fmt.Errorf("Failed to provide object to the graph: %v", err)
+	}
+	err = serviceGraph.Provide(&inject.Object{Value: routing.NewRouteRegister(middleware.RequestMetrics, middleware.RequestTracing)})
+	if err != nil {
+		return fmt.Errorf("Failed to provide object to the graph: %v", err)
+	}
+	err = serviceGraph.Provide(&inject.Object{Value: cache.New(5*time.Minute, 10*time.Minute)})
+	if err != nil {
+		return fmt.Errorf("Failed to provide object to the graph: %v", err)
+	}
 
 	// self registered services
 	services := registry.GetServices()
 
 	// Add all services to dependency graph
 	for _, service := range services {
-		serviceGraph.Provide(&inject.Object{Value: service.Instance})
+		err = serviceGraph.Provide(&inject.Object{Value: service.Instance})
+		if err != nil {
+			return fmt.Errorf("Failed to provide object to the graph: %v", err)
+		}
 	}
 
-	serviceGraph.Provide(&inject.Object{Value: g})
+	err = serviceGraph.Provide(&inject.Object{Value: g})
+	if err != nil {
+		return fmt.Errorf("Failed to provide object to the graph: %v", err)
+	}
 
 	// Inject dependencies to services
 	if err := serviceGraph.Populate(); err != nil {
@@ -144,6 +163,7 @@ func (g *GrafanaServerImpl) Run() error {
 	}
 
 	sendSystemdNotification("READY=1")
+
 	return g.childRoutines.Wait()
 }
 

+ 1 - 1
pkg/components/dynmap/dynmap.go

@@ -1,5 +1,5 @@
 // uses code from https://github.com/antonholmquist/jason/blob/master/jason.go
-// MIT Licence
+// MIT License
 
 package dynmap
 

+ 1 - 1
pkg/components/dynmap/dynmap_test.go

@@ -1,5 +1,5 @@
 // uses code from https://github.com/antonholmquist/jason/blob/master/jason.go
-// MIT Licence
+// MIT License
 
 package dynmap
 

+ 9 - 6
pkg/metrics/metrics.go

@@ -313,7 +313,7 @@ func init() {
 
 // SetBuildInformation sets the build information for this binary
 func SetBuildInformation(version, revision, branch string) {
-	// We export this info twice for backwards compability.
+	// We export this info twice for backwards compatibility.
 	// Once this have been released for some time we should be able to remote `M_Grafana_Version`
 	// The reason we added a new one is that its common practice in the prometheus community
 	// to name this metric `*_build_info` so its easy to do aggregation on all programs.
@@ -397,11 +397,12 @@ func sendUsageStats(oauthProviders map[string]bool) {
 
 	metrics := map[string]interface{}{}
 	report := map[string]interface{}{
-		"version": version,
-		"metrics": metrics,
-		"os":      runtime.GOOS,
-		"arch":    runtime.GOARCH,
-		"edition": getEdition(),
+		"version":   version,
+		"metrics":   metrics,
+		"os":        runtime.GOOS,
+		"arch":      runtime.GOARCH,
+		"edition":   getEdition(),
+		"packaging": setting.Packaging,
 	}
 
 	statsQuery := models.GetSystemStatsQuery{}
@@ -447,6 +448,8 @@ func sendUsageStats(oauthProviders map[string]bool) {
 	}
 	metrics["stats.ds.other.count"] = dsOtherCount
 
+	metrics["stats.packaging."+setting.Packaging+".count"] = 1
+
 	dsAccessStats := models.GetDataSourceAccessStatsQuery{}
 	if err := bus.Dispatch(&dsAccessStats); err != nil {
 		metricsLogger.Error("Failed to get datasource access stats", "error", err)

+ 3 - 0
pkg/metrics/metrics_test.go

@@ -176,6 +176,7 @@ func TestMetrics(t *testing.T) {
 			setting.BasicAuthEnabled = true
 			setting.LdapEnabled = true
 			setting.AuthProxyEnabled = true
+			setting.Packaging = "deb"
 
 			wg.Add(1)
 			sendUsageStats(oauthProviders)
@@ -243,6 +244,8 @@ func TestMetrics(t *testing.T) {
 				So(metrics.Get("stats.auth_enabled.oauth_google.count").MustInt(), ShouldEqual, 1)
 				So(metrics.Get("stats.auth_enabled.oauth_generic_oauth.count").MustInt(), ShouldEqual, 1)
 				So(metrics.Get("stats.auth_enabled.oauth_grafana_com.count").MustInt(), ShouldEqual, 1)
+
+				So(metrics.Get("stats.packaging.deb.count").MustInt(), ShouldEqual, 1)
 			})
 		})
 

+ 1 - 0
pkg/middleware/recovery.go

@@ -115,6 +115,7 @@ func Recovery() macaron.Handler {
 
 				c.Data["Title"] = "Server Error"
 				c.Data["AppSubUrl"] = setting.AppSubUrl
+				c.Data["Theme"] = setting.DefaultTheme
 
 				if setting.Env == setting.DEV {
 					if theErr, ok := err.(error); ok {

+ 10 - 3
pkg/models/alert.go

@@ -19,6 +19,7 @@ const (
 	AlertStateAlerting AlertStateType = "alerting"
 	AlertStateOK       AlertStateType = "ok"
 	AlertStatePending  AlertStateType = "pending"
+	AlertStateUnknown  AlertStateType = "unknown"
 )
 
 const (
@@ -39,7 +40,12 @@ var (
 )
 
 func (s AlertStateType) IsValid() bool {
-	return s == AlertStateOK || s == AlertStateNoData || s == AlertStatePaused || s == AlertStatePending
+	return s == AlertStateOK ||
+		s == AlertStateNoData ||
+		s == AlertStatePaused ||
+		s == AlertStatePending ||
+		s == AlertStateAlerting ||
+		s == AlertStateUnknown
 }
 
 func (s NoDataOption) IsValid() bool {
@@ -66,12 +72,13 @@ type Alert struct {
 	PanelId        int64
 	Name           string
 	Message        string
-	Severity       string
+	Severity       string //Unused
 	State          AlertStateType
-	Handler        int64
+	Handler        int64 //Unused
 	Silenced       bool
 	ExecutionError string
 	Frequency      int64
+	For            time.Duration
 
 	EvalData     *simplejson.Json
 	NewStateDate time.Time

+ 0 - 1
pkg/models/dashboards.go

@@ -21,7 +21,6 @@ var (
 	ErrDashboardVersionMismatch                = errors.New("The dashboard has been changed by someone else")
 	ErrDashboardTitleEmpty                     = errors.New("Dashboard title cannot be empty")
 	ErrDashboardFolderCannotHaveParent         = errors.New("A Dashboard Folder cannot be added to another folder")
-	ErrDashboardFailedToUpdateAlertData        = errors.New("Failed to save alert data")
 	ErrDashboardsWithSameSlugExists            = errors.New("Multiple dashboards with the same slug exists")
 	ErrDashboardFailedGenerateUniqueUid        = errors.New("Failed to generate unique dashboard id")
 	ErrDashboardTypeMismatch                   = errors.New("Dashboard cannot be changed to a folder")

+ 2 - 1
pkg/models/user.go

@@ -7,7 +7,8 @@ import (
 
 // Typed errors
 var (
-	ErrUserNotFound = errors.New("User not found")
+	ErrUserNotFound     = errors.New("User not found")
+	ErrLastGrafanaAdmin = errors.New("Cannot remove last grafana admin")
 )
 
 type Password string

+ 29 - 3
pkg/services/alerting/eval_context.go

@@ -68,8 +68,13 @@ func (c *EvalContext) GetStateModel() *StateDescription {
 			Color: "#D63232",
 			Text:  "Alerting",
 		}
+	case m.AlertStateUnknown:
+		return &StateDescription{
+			Color: "#888888",
+			Text:  "Unknown",
+		}
 	default:
-		panic("Unknown rule state " + c.Rule.State)
+		panic("Unknown rule state for alert " + c.Rule.State)
 	}
 }
 
@@ -113,7 +118,26 @@ func (c *EvalContext) GetRuleUrl() (string, error) {
 	return fmt.Sprintf(urlFormat, m.GetFullDashboardUrl(ref.Uid, ref.Slug), c.Rule.PanelId, c.Rule.OrgId), nil
 }
 
+// GetNewState returns the new state from the alert rule evaluation
 func (c *EvalContext) GetNewState() m.AlertStateType {
+	ns := getNewStateInternal(c)
+	if ns != m.AlertStateAlerting || c.Rule.For == 0 {
+		return ns
+	}
+
+	since := time.Since(c.Rule.LastStateChange)
+	if c.PrevAlertState == m.AlertStatePending && since > c.Rule.For {
+		return m.AlertStateAlerting
+	}
+
+	if c.PrevAlertState == m.AlertStateAlerting {
+		return m.AlertStateAlerting
+	}
+
+	return m.AlertStatePending
+}
+
+func getNewStateInternal(c *EvalContext) m.AlertStateType {
 	if c.Error != nil {
 		c.log.Error("Alert Rule Result Error",
 			"ruleId", c.Rule.Id,
@@ -125,11 +149,13 @@ func (c *EvalContext) GetNewState() m.AlertStateType {
 			return c.PrevAlertState
 		}
 		return c.Rule.ExecutionErrorState.ToAlertState()
+	}
 
-	} else if c.Firing {
+	if c.Firing {
 		return m.AlertStateAlerting
+	}
 
-	} else if c.NoDataFound {
+	if c.NoDataFound {
 		c.log.Info("Alert Rule returned no data",
 			"ruleId", c.Rule.Id,
 			"name", c.Rule.Name,

+ 172 - 67
pkg/services/alerting/eval_context_test.go

@@ -2,11 +2,11 @@ package alerting
 
 import (
 	"context"
-	"fmt"
+	"errors"
 	"testing"
+	"time"
 
 	"github.com/grafana/grafana/pkg/models"
-	. "github.com/smartystreets/goconvey/convey"
 )
 
 func TestStateIsUpdatedWhenNeeded(t *testing.T) {
@@ -31,71 +31,176 @@ func TestStateIsUpdatedWhenNeeded(t *testing.T) {
 	})
 }
 
-func TestAlertingEvalContext(t *testing.T) {
-	Convey("Should compute and replace properly new rule state", t, func() {
+func TestGetStateFromEvalContext(t *testing.T) {
+	tcs := []struct {
+		name     string
+		expected models.AlertStateType
+		applyFn  func(ec *EvalContext)
+	}{
+		{
+			name:     "ok -> alerting",
+			expected: models.AlertStateAlerting,
+			applyFn: func(ec *EvalContext) {
+				ec.Firing = true
+				ec.PrevAlertState = models.AlertStateOK
+			},
+		},
+		{
+			name:     "ok -> error(alerting)",
+			expected: models.AlertStateAlerting,
+			applyFn: func(ec *EvalContext) {
+				ec.PrevAlertState = models.AlertStateOK
+				ec.Error = errors.New("test error")
+				ec.Rule.ExecutionErrorState = models.ExecutionErrorSetAlerting
+			},
+		},
+		{
+			name:     "ok -> pending. since its been firing for less than FOR",
+			expected: models.AlertStatePending,
+			applyFn: func(ec *EvalContext) {
+				ec.PrevAlertState = models.AlertStateOK
+				ec.Firing = true
+				ec.Rule.LastStateChange = time.Now().Add(-time.Minute * 2)
+				ec.Rule.For = time.Minute * 5
+			},
+		},
+		{
+			name:     "ok -> pending. since it has to be pending longer than FOR and prev state is ok",
+			expected: models.AlertStatePending,
+			applyFn: func(ec *EvalContext) {
+				ec.PrevAlertState = models.AlertStateOK
+				ec.Firing = true
+				ec.Rule.LastStateChange = time.Now().Add(-(time.Hour * 5))
+				ec.Rule.For = time.Minute * 2
+			},
+		},
+		{
+			name:     "pending -> alerting. since its been firing for more than FOR and prev state is pending",
+			expected: models.AlertStateAlerting,
+			applyFn: func(ec *EvalContext) {
+				ec.PrevAlertState = models.AlertStatePending
+				ec.Firing = true
+				ec.Rule.LastStateChange = time.Now().Add(-(time.Hour * 5))
+				ec.Rule.For = time.Minute * 2
+			},
+		},
+		{
+			name:     "alerting -> alerting. should not update regardless of FOR",
+			expected: models.AlertStateAlerting,
+			applyFn: func(ec *EvalContext) {
+				ec.PrevAlertState = models.AlertStateAlerting
+				ec.Firing = true
+				ec.Rule.LastStateChange = time.Now().Add(-time.Minute * 5)
+				ec.Rule.For = time.Minute * 2
+			},
+		},
+		{
+			name:     "ok -> ok. should not update regardless of FOR",
+			expected: models.AlertStateOK,
+			applyFn: func(ec *EvalContext) {
+				ec.PrevAlertState = models.AlertStateOK
+				ec.Rule.LastStateChange = time.Now().Add(-time.Minute * 5)
+				ec.Rule.For = time.Minute * 2
+			},
+		},
+		{
+			name:     "ok -> error(keep_last)",
+			expected: models.AlertStateOK,
+			applyFn: func(ec *EvalContext) {
+				ec.PrevAlertState = models.AlertStateOK
+				ec.Error = errors.New("test error")
+				ec.Rule.ExecutionErrorState = models.ExecutionErrorKeepState
+			},
+		},
+		{
+			name:     "pending -> error(keep_last)",
+			expected: models.AlertStatePending,
+			applyFn: func(ec *EvalContext) {
+				ec.PrevAlertState = models.AlertStatePending
+				ec.Error = errors.New("test error")
+				ec.Rule.ExecutionErrorState = models.ExecutionErrorKeepState
+			},
+		},
+		{
+			name:     "ok -> no_data(alerting)",
+			expected: models.AlertStateAlerting,
+			applyFn: func(ec *EvalContext) {
+				ec.PrevAlertState = models.AlertStateOK
+				ec.Rule.NoDataState = models.NoDataSetAlerting
+				ec.NoDataFound = true
+			},
+		},
+		{
+			name:     "ok -> no_data(keep_last)",
+			expected: models.AlertStateOK,
+			applyFn: func(ec *EvalContext) {
+				ec.PrevAlertState = models.AlertStateOK
+				ec.Rule.NoDataState = models.NoDataKeepState
+				ec.NoDataFound = true
+			},
+		},
+		{
+			name:     "pending -> no_data(keep_last)",
+			expected: models.AlertStatePending,
+			applyFn: func(ec *EvalContext) {
+				ec.PrevAlertState = models.AlertStatePending
+				ec.Rule.NoDataState = models.NoDataKeepState
+				ec.NoDataFound = true
+			},
+		},
+		{
+			name:     "pending -> no_data(alerting) with for duration have not passed",
+			expected: models.AlertStatePending,
+			applyFn: func(ec *EvalContext) {
+				ec.PrevAlertState = models.AlertStatePending
+				ec.Rule.NoDataState = models.NoDataSetAlerting
+				ec.NoDataFound = true
+				ec.Rule.For = time.Minute * 5
+				ec.Rule.LastStateChange = time.Now().Add(-time.Minute * 2)
+			},
+		},
+		{
+			name:     "pending -> no_data(alerting) should set alerting since time passed FOR",
+			expected: models.AlertStateAlerting,
+			applyFn: func(ec *EvalContext) {
+				ec.PrevAlertState = models.AlertStatePending
+				ec.Rule.NoDataState = models.NoDataSetAlerting
+				ec.NoDataFound = true
+				ec.Rule.For = time.Minute * 2
+				ec.Rule.LastStateChange = time.Now().Add(-time.Minute * 5)
+			},
+		},
+		{
+			name:     "pending -> error(alerting) with for duration have not passed ",
+			expected: models.AlertStatePending,
+			applyFn: func(ec *EvalContext) {
+				ec.PrevAlertState = models.AlertStatePending
+				ec.Rule.ExecutionErrorState = models.ExecutionErrorSetAlerting
+				ec.Error = errors.New("test error")
+				ec.Rule.For = time.Minute * 5
+				ec.Rule.LastStateChange = time.Now().Add(-time.Minute * 2)
+			},
+		},
+		{
+			name:     "pending -> error(alerting) should set alerting since time passed FOR",
+			expected: models.AlertStateAlerting,
+			applyFn: func(ec *EvalContext) {
+				ec.PrevAlertState = models.AlertStatePending
+				ec.Rule.ExecutionErrorState = models.ExecutionErrorSetAlerting
+				ec.Error = errors.New("test error")
+				ec.Rule.For = time.Minute * 2
+				ec.Rule.LastStateChange = time.Now().Add(-time.Minute * 5)
+			},
+		},
+	}
+
+	for _, tc := range tcs {
 		ctx := NewEvalContext(context.TODO(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}})
-		dummieError := fmt.Errorf("dummie error")
 
-		Convey("ok -> alerting", func() {
-			ctx.PrevAlertState = models.AlertStateOK
-			ctx.Firing = true
-
-			ctx.Rule.State = ctx.GetNewState()
-			So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting)
-		})
-
-		Convey("ok -> error(alerting)", func() {
-			ctx.PrevAlertState = models.AlertStateOK
-			ctx.Error = dummieError
-			ctx.Rule.ExecutionErrorState = models.ExecutionErrorSetAlerting
-
-			ctx.Rule.State = ctx.GetNewState()
-			So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting)
-		})
-
-		Convey("ok -> error(keep_last)", func() {
-			ctx.PrevAlertState = models.AlertStateOK
-			ctx.Error = dummieError
-			ctx.Rule.ExecutionErrorState = models.ExecutionErrorKeepState
-
-			ctx.Rule.State = ctx.GetNewState()
-			So(ctx.Rule.State, ShouldEqual, models.AlertStateOK)
-		})
-
-		Convey("pending -> error(keep_last)", func() {
-			ctx.PrevAlertState = models.AlertStatePending
-			ctx.Error = dummieError
-			ctx.Rule.ExecutionErrorState = models.ExecutionErrorKeepState
-
-			ctx.Rule.State = ctx.GetNewState()
-			So(ctx.Rule.State, ShouldEqual, models.AlertStatePending)
-		})
-
-		Convey("ok -> no_data(alerting)", func() {
-			ctx.PrevAlertState = models.AlertStateOK
-			ctx.Rule.NoDataState = models.NoDataSetAlerting
-			ctx.NoDataFound = true
-
-			ctx.Rule.State = ctx.GetNewState()
-			So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting)
-		})
-
-		Convey("ok -> no_data(keep_last)", func() {
-			ctx.PrevAlertState = models.AlertStateOK
-			ctx.Rule.NoDataState = models.NoDataKeepState
-			ctx.NoDataFound = true
-
-			ctx.Rule.State = ctx.GetNewState()
-			So(ctx.Rule.State, ShouldEqual, models.AlertStateOK)
-		})
-
-		Convey("pending -> no_data(keep_last)", func() {
-			ctx.PrevAlertState = models.AlertStatePending
-			ctx.Rule.NoDataState = models.NoDataKeepState
-			ctx.NoDataFound = true
-
-			ctx.Rule.State = ctx.GetNewState()
-			So(ctx.Rule.State, ShouldEqual, models.AlertStatePending)
-		})
-	})
+		tc.applyFn(ctx)
+		have := ctx.GetNewState()
+		if have != tc.expected {
+			t.Errorf("failed: %s \n expected '%s' have '%s'\n", tc.name, tc.expected, string(have))
+		}
+	}
 }

+ 11 - 1
pkg/services/alerting/extractor.go

@@ -2,8 +2,8 @@ package alerting
 
 import (
 	"errors"
-
 	"fmt"
+	"time"
 
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/components/simplejson"
@@ -115,6 +115,15 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json,
 			return nil, ValidationError{Reason: "Could not parse frequency"}
 		}
 
+		rawFor := jsonAlert.Get("for").MustString()
+		var forValue time.Duration
+		if rawFor != "" {
+			forValue, err = time.ParseDuration(rawFor)
+			if err != nil {
+				return nil, ValidationError{Reason: "Could not parse for"}
+			}
+		}
+
 		alert := &m.Alert{
 			DashboardId: e.Dash.Id,
 			OrgId:       e.OrgID,
@@ -124,6 +133,7 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json,
 			Handler:     jsonAlert.Get("handler").MustInt64(),
 			Message:     jsonAlert.Get("message").MustString(),
 			Frequency:   frequency,
+			For:         forValue,
 		}
 
 		for _, condition := range jsonAlert.Get("conditions").MustArray() {

+ 13 - 7
pkg/services/alerting/extractor_test.go

@@ -3,6 +3,7 @@ package alerting
 import (
 	"io/ioutil"
 	"testing"
+	"time"
 
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/components/simplejson"
@@ -46,7 +47,7 @@ func TestAlertRuleExtraction(t *testing.T) {
 			return nil
 		})
 
-		json, err := ioutil.ReadFile("./test-data/graphite-alert.json")
+		json, err := ioutil.ReadFile("./testdata/graphite-alert.json")
 		So(err, ShouldBeNil)
 
 		Convey("Extractor should not modify the original json", func() {
@@ -118,6 +119,11 @@ func TestAlertRuleExtraction(t *testing.T) {
 					So(alerts[1].PanelId, ShouldEqual, 4)
 				})
 
+				Convey("should extract for param", func() {
+					So(alerts[0].For, ShouldEqual, time.Minute*2)
+					So(alerts[1].For, ShouldEqual, time.Duration(0))
+				})
+
 				Convey("should extract name and desc", func() {
 					So(alerts[0].Name, ShouldEqual, "name1")
 					So(alerts[0].Message, ShouldEqual, "desc1")
@@ -140,7 +146,7 @@ func TestAlertRuleExtraction(t *testing.T) {
 		})
 
 		Convey("Panels missing id should return error", func() {
-			panelWithoutId, err := ioutil.ReadFile("./test-data/panels-missing-id.json")
+			panelWithoutId, err := ioutil.ReadFile("./testdata/panels-missing-id.json")
 			So(err, ShouldBeNil)
 
 			dashJson, err := simplejson.NewJson(panelWithoutId)
@@ -156,7 +162,7 @@ func TestAlertRuleExtraction(t *testing.T) {
 		})
 
 		Convey("Panel with id set to zero should return error", func() {
-			panelWithIdZero, err := ioutil.ReadFile("./test-data/panel-with-id-0.json")
+			panelWithIdZero, err := ioutil.ReadFile("./testdata/panel-with-id-0.json")
 			So(err, ShouldBeNil)
 
 			dashJson, err := simplejson.NewJson(panelWithIdZero)
@@ -172,7 +178,7 @@ func TestAlertRuleExtraction(t *testing.T) {
 		})
 
 		Convey("Parse alerts from dashboard without rows", func() {
-			json, err := ioutil.ReadFile("./test-data/v5-dashboard.json")
+			json, err := ioutil.ReadFile("./testdata/v5-dashboard.json")
 			So(err, ShouldBeNil)
 
 			dashJson, err := simplejson.NewJson(json)
@@ -192,7 +198,7 @@ func TestAlertRuleExtraction(t *testing.T) {
 		})
 
 		Convey("Parse and validate dashboard containing influxdb alert", func() {
-			json, err := ioutil.ReadFile("./test-data/influxdb-alert.json")
+			json, err := ioutil.ReadFile("./testdata/influxdb-alert.json")
 			So(err, ShouldBeNil)
 
 			dashJson, err := simplejson.NewJson(json)
@@ -221,7 +227,7 @@ func TestAlertRuleExtraction(t *testing.T) {
 		})
 
 		Convey("Should be able to extract collapsed panels", func() {
-			json, err := ioutil.ReadFile("./test-data/collapsed-panels.json")
+			json, err := ioutil.ReadFile("./testdata/collapsed-panels.json")
 			So(err, ShouldBeNil)
 
 			dashJson, err := simplejson.NewJson(json)
@@ -242,7 +248,7 @@ func TestAlertRuleExtraction(t *testing.T) {
 		})
 
 		Convey("Parse and validate dashboard without id and containing an alert", func() {
-			json, err := ioutil.ReadFile("./test-data/dash-without-id.json")
+			json, err := ioutil.ReadFile("./testdata/dash-without-id.json")
 			So(err, ShouldBeNil)
 
 			dashJSON, err := simplejson.NewJson(json)

+ 47 - 0
pkg/services/alerting/notifiers/alertmanager_test.go

@@ -1,13 +1,60 @@
 package notifiers
 
 import (
+	"context"
 	"testing"
 
 	"github.com/grafana/grafana/pkg/components/simplejson"
+	"github.com/grafana/grafana/pkg/log"
 	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/services/alerting"
 	. "github.com/smartystreets/goconvey/convey"
 )
 
+func TestWhenAlertManagerShouldNotify(t *testing.T) {
+	tcs := []struct {
+		prevState m.AlertStateType
+		newState  m.AlertStateType
+
+		expect bool
+	}{
+		{
+			prevState: m.AlertStatePending,
+			newState:  m.AlertStateOK,
+			expect:    false,
+		},
+		{
+			prevState: m.AlertStateAlerting,
+			newState:  m.AlertStateOK,
+			expect:    true,
+		},
+		{
+			prevState: m.AlertStateOK,
+			newState:  m.AlertStatePending,
+			expect:    false,
+		},
+		{
+			prevState: m.AlertStateUnknown,
+			newState:  m.AlertStatePending,
+			expect:    false,
+		},
+	}
+
+	for _, tc := range tcs {
+		am := &AlertmanagerNotifier{log: log.New("test.logger")}
+		evalContext := alerting.NewEvalContext(context.TODO(), &alerting.Rule{
+			State: tc.prevState,
+		})
+
+		evalContext.Rule.State = tc.newState
+
+		res := am.ShouldNotify(context.TODO(), evalContext, &m.AlertNotificationState{})
+		if res != tc.expect {
+			t.Errorf("got %v expected %v", res, tc.expect)
+		}
+	}
+}
+
 func TestAlertmanagerNotifier(t *testing.T) {
 	Convey("Alertmanager notifier tests", t, func() {
 

+ 10 - 0
pkg/services/alerting/notifiers/base.go

@@ -67,6 +67,16 @@ func (n *NotifierBase) ShouldNotify(ctx context.Context, context *alerting.EvalC
 	}
 
 	// Do not notify when we become OK for the first time.
+	if context.PrevAlertState == models.AlertStateUnknown && context.Rule.State == models.AlertStateOK {
+		return false
+	}
+
+	// Do not notify when we become OK for the first time.
+	if context.PrevAlertState == models.AlertStateUnknown && context.Rule.State == models.AlertStatePending {
+		return false
+	}
+
+	// Do not notify when we become OK from pending
 	if context.PrevAlertState == models.AlertStatePending && context.Rule.State == models.AlertStateOK {
 		return false
 	}

+ 25 - 7
pkg/services/alerting/notifiers/base_test.go

@@ -29,7 +29,6 @@ func TestShouldSendAlertNotification(t *testing.T) {
 			newState:     m.AlertStateOK,
 			prevState:    m.AlertStatePending,
 			sendReminder: false,
-			state:        &m.AlertNotificationState{},
 
 			expect: false,
 		},
@@ -38,7 +37,6 @@ func TestShouldSendAlertNotification(t *testing.T) {
 			newState:     m.AlertStateAlerting,
 			prevState:    m.AlertStateOK,
 			sendReminder: false,
-			state:        &m.AlertNotificationState{},
 
 			expect: true,
 		},
@@ -47,7 +45,6 @@ func TestShouldSendAlertNotification(t *testing.T) {
 			newState:     m.AlertStatePending,
 			prevState:    m.AlertStateOK,
 			sendReminder: false,
-			state:        &m.AlertNotificationState{},
 
 			expect: false,
 		},
@@ -56,7 +53,6 @@ func TestShouldSendAlertNotification(t *testing.T) {
 			newState:     m.AlertStateOK,
 			prevState:    m.AlertStateOK,
 			sendReminder: false,
-			state:        &m.AlertNotificationState{},
 
 			expect: false,
 		},
@@ -65,7 +61,6 @@ func TestShouldSendAlertNotification(t *testing.T) {
 			newState:     m.AlertStateOK,
 			prevState:    m.AlertStateOK,
 			sendReminder: true,
-			state:        &m.AlertNotificationState{},
 
 			expect: false,
 		},
@@ -74,7 +69,6 @@ func TestShouldSendAlertNotification(t *testing.T) {
 			newState:     m.AlertStateOK,
 			prevState:    m.AlertStateAlerting,
 			sendReminder: false,
-			state:        &m.AlertNotificationState{},
 
 			expect: true,
 		},
@@ -94,7 +88,6 @@ func TestShouldSendAlertNotification(t *testing.T) {
 			prevState:    m.AlertStateAlerting,
 			frequency:    time.Minute * 10,
 			sendReminder: true,
-			state:        &m.AlertNotificationState{},
 
 			expect: true,
 		},
@@ -132,6 +125,27 @@ func TestShouldSendAlertNotification(t *testing.T) {
 			prevState: m.AlertStateOK,
 			state:     &m.AlertNotificationState{State: m.AlertNotificationStatePending, UpdatedAt: tnow.Add(-2 * time.Minute).Unix()},
 
+			expect: true,
+		},
+		{
+			name:      "unknown -> ok",
+			prevState: m.AlertStateUnknown,
+			newState:  m.AlertStateOK,
+
+			expect: false,
+		},
+		{
+			name:      "unknown -> pending",
+			prevState: m.AlertStateUnknown,
+			newState:  m.AlertStatePending,
+
+			expect: false,
+		},
+		{
+			name:      "unknown -> alerting",
+			prevState: m.AlertStateUnknown,
+			newState:  m.AlertStateAlerting,
+
 			expect: true,
 		},
 	}
@@ -141,6 +155,10 @@ func TestShouldSendAlertNotification(t *testing.T) {
 			State: tc.prevState,
 		})
 
+		if tc.state == nil {
+			tc.state = &m.AlertNotificationState{}
+		}
+
 		evalContext.Rule.State = tc.newState
 		nb := &NotifierBase{SendReminder: tc.sendReminder, Frequency: tc.frequency}
 

+ 3 - 0
pkg/services/alerting/result_handler.go

@@ -73,6 +73,9 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error {
 			// when two servers are raising. This makes sure that the server
 			// with the last state change always sends a notification.
 			evalContext.Rule.StateChanges = cmd.Result.StateChanges
+
+			// Update the last state change of the alert rule in memory
+			evalContext.Rule.LastStateChange = time.Now()
 		}
 
 		// save annotation

+ 5 - 0
pkg/services/alerting/rule.go

@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"regexp"
 	"strconv"
+	"time"
 
 	"github.com/grafana/grafana/pkg/components/simplejson"
 
@@ -18,6 +19,8 @@ type Rule struct {
 	Frequency           int64
 	Name                string
 	Message             string
+	LastStateChange     time.Time
+	For                 time.Duration
 	NoDataState         m.NoDataOption
 	ExecutionErrorState m.ExecutionErrorOption
 	State               m.AlertStateType
@@ -100,6 +103,8 @@ func NewRuleFromDBAlert(ruleDef *m.Alert) (*Rule, error) {
 	model.Message = ruleDef.Message
 	model.Frequency = ruleDef.Frequency
 	model.State = ruleDef.State
+	model.LastStateChange = ruleDef.NewStateDate
+	model.For = ruleDef.For
 	model.NoDataState = m.NoDataOption(ruleDef.Settings.Get("noDataState").MustString("no_data"))
 	model.ExecutionErrorState = m.ExecutionErrorOption(ruleDef.Settings.Get("executionErrorState").MustString("alerting"))
 	model.StateChanges = ruleDef.StateChanges

+ 0 - 0
pkg/services/alerting/test-data/collapsed-panels.json → pkg/services/alerting/testdata/collapsed-panels.json


+ 0 - 0
pkg/services/alerting/test-data/dash-without-id.json → pkg/services/alerting/testdata/dash-without-id.json


+ 1 - 0
pkg/services/alerting/test-data/graphite-alert.json → pkg/services/alerting/testdata/graphite-alert.json

@@ -23,6 +23,7 @@
           "message": "desc1",
           "handler": 1,
           "frequency": "60s",
+          "for": "2m",
           "conditions": [
           {
             "type": "query",

+ 0 - 0
pkg/services/alerting/test-data/influxdb-alert.json → pkg/services/alerting/testdata/influxdb-alert.json


+ 0 - 0
pkg/services/alerting/test-data/panel-with-id-0.json → pkg/services/alerting/testdata/panel-with-id-0.json


+ 0 - 0
pkg/services/alerting/test-data/panels-missing-id.json → pkg/services/alerting/testdata/panels-missing-id.json


+ 0 - 0
pkg/services/alerting/test-data/v5-dashboard.json → pkg/services/alerting/testdata/v5-dashboard.json


+ 1 - 1
pkg/services/dashboards/dashboard_service.go

@@ -165,7 +165,7 @@ func (dr *dashboardServiceImpl) updateAlerting(cmd *models.SaveDashboardCommand,
 	}
 
 	if err := bus.Dispatch(&alertCmd); err != nil {
-		return models.ErrDashboardFailedToUpdateAlertData
+		return err
 	}
 
 	return nil

+ 5 - 4
pkg/services/sqlstore/alert.go

@@ -193,7 +193,8 @@ func updateAlerts(existingAlerts []*m.Alert, cmd *m.SaveAlertsCommand, sess *DBS
 			if alertToUpdate.ContainsUpdates(alert) {
 				alert.Updated = timeNow()
 				alert.State = alertToUpdate.State
-				sess.MustCols("message")
+				sess.MustCols("message", "for")
+
 				_, err := sess.ID(alert.Id).Update(alert)
 				if err != nil {
 					return err
@@ -204,7 +205,7 @@ func updateAlerts(existingAlerts []*m.Alert, cmd *m.SaveAlertsCommand, sess *DBS
 		} else {
 			alert.Updated = timeNow()
 			alert.Created = timeNow()
-			alert.State = m.AlertStatePending
+			alert.State = m.AlertStateUnknown
 			alert.NewStateDate = timeNow()
 
 			_, err := sess.Insert(alert)
@@ -299,7 +300,7 @@ func PauseAlert(cmd *m.PauseAlertCommand) error {
 			params = append(params, string(m.AlertStatePaused))
 			params = append(params, timeNow())
 		} else {
-			params = append(params, string(m.AlertStatePending))
+			params = append(params, string(m.AlertStateUnknown))
 			params = append(params, timeNow())
 		}
 
@@ -323,7 +324,7 @@ func PauseAllAlerts(cmd *m.PauseAllAlertCommand) error {
 		if cmd.Paused {
 			newState = string(m.AlertStatePaused)
 		} else {
-			newState = string(m.AlertStatePending)
+			newState = string(m.AlertStateUnknown)
 		}
 
 		res, err := sess.Exec(`UPDATE alert SET state = ?, new_state_date = ?`, newState, timeNow())

+ 2 - 2
pkg/services/sqlstore/alert_test.go

@@ -109,7 +109,7 @@ func TestAlertingDataAccess(t *testing.T) {
 			So(alert.DashboardId, ShouldEqual, testDash.Id)
 			So(alert.PanelId, ShouldEqual, 1)
 			So(alert.Name, ShouldEqual, "Alerting title")
-			So(alert.State, ShouldEqual, "pending")
+			So(alert.State, ShouldEqual, m.AlertStateUnknown)
 			So(alert.NewStateDate, ShouldNotBeNil)
 			So(alert.EvalData, ShouldNotBeNil)
 			So(alert.EvalData.Get("test").MustString(), ShouldEqual, "test")
@@ -154,7 +154,7 @@ func TestAlertingDataAccess(t *testing.T) {
 				So(query.Result[0].Name, ShouldEqual, "Name")
 
 				Convey("Alert state should not be updated", func() {
-					So(query.Result[0].State, ShouldEqual, "pending")
+					So(query.Result[0].State, ShouldEqual, m.AlertStateUnknown)
 				})
 			})
 

+ 4 - 0
pkg/services/sqlstore/migrations/alert_mig.go

@@ -133,4 +133,8 @@ func addAlertMigrations(mg *Migrator) {
 	mg.AddMigration("create alert_notification_state table v1", NewAddTableMigration(alert_notification_state))
 	mg.AddMigration("add index alert_notification_state org_id & alert_id & notifier_id",
 		NewAddIndexMigration(alert_notification_state, alert_notification_state.Indices[0]))
+
+	mg.AddMigration("Add for to alert table", NewAddColumnMigration(alertV1, &Column{
+		Name: "for", Type: DB_BigInt, Nullable: true,
+	}))
 }

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

@@ -187,7 +187,7 @@ func TestAccountDataAccess(t *testing.T) {
 					err := DeleteOrg(&m.DeleteOrgCommand{Id: ac2.OrgId})
 					So(err, ShouldBeNil)
 
-					// remove frome ac2 from ac1 org
+					// remove ac2 user from ac1 org
 					remCmd := m.RemoveOrgUserCommand{OrgId: ac1.OrgId, UserId: ac2.Id, ShouldDeleteOrphanedUser: true}
 					err = RemoveOrgUser(&remCmd)
 					So(err, ShouldBeNil)

+ 6 - 6
pkg/services/sqlstore/quota.go

@@ -99,14 +99,14 @@ func UpdateOrgQuota(cmd *m.UpdateOrgQuotaCmd) error {
 	return inTransaction(func(sess *DBSession) error {
 		//Check if quota is already defined in the DB
 		quota := m.Quota{
-			Target:  cmd.Target,
-			OrgId:   cmd.OrgId,
-			Updated: time.Now(),
+			Target: cmd.Target,
+			OrgId:  cmd.OrgId,
 		}
 		has, err := sess.Get(&quota)
 		if err != nil {
 			return err
 		}
+		quota.Updated = time.Now()
 		quota.Limit = cmd.Limit
 		if !has {
 			quota.Created = time.Now()
@@ -201,14 +201,14 @@ func UpdateUserQuota(cmd *m.UpdateUserQuotaCmd) error {
 	return inTransaction(func(sess *DBSession) error {
 		//Check if quota is already defined in the DB
 		quota := m.Quota{
-			Target:  cmd.Target,
-			UserId:  cmd.UserId,
-			Updated: time.Now(),
+			Target: cmd.Target,
+			UserId: cmd.UserId,
 		}
 		has, err := sess.Get(&quota)
 		if err != nil {
 			return err
 		}
+		quota.Updated = time.Now()
 		quota.Limit = cmd.Limit
 		if !has {
 			quota.Created = time.Now()

+ 65 - 0
pkg/services/sqlstore/quota_test.go

@@ -2,6 +2,7 @@ package sqlstore
 
 import (
 	"testing"
+	"time"
 
 	m "github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/setting"
@@ -168,5 +169,69 @@ func TestQuotaCommandsAndQueries(t *testing.T) {
 			So(query.Result.Limit, ShouldEqual, 5)
 			So(query.Result.Used, ShouldEqual, 1)
 		})
+
+		// related: https://github.com/grafana/grafana/issues/14342
+		Convey("Should org quota updating is successful even if it called multiple time", func() {
+			orgCmd := m.UpdateOrgQuotaCmd{
+				OrgId:  orgId,
+				Target: "org_user",
+				Limit:  5,
+			}
+			err := UpdateOrgQuota(&orgCmd)
+			So(err, ShouldBeNil)
+
+			query := m.GetOrgQuotaByTargetQuery{OrgId: orgId, Target: "org_user", Default: 1}
+			err = GetOrgQuotaByTarget(&query)
+			So(err, ShouldBeNil)
+			So(query.Result.Limit, ShouldEqual, 5)
+
+			// XXX: resolution of `Updated` column is 1sec, so this makes delay
+			time.Sleep(1 * time.Second)
+
+			orgCmd = m.UpdateOrgQuotaCmd{
+				OrgId:  orgId,
+				Target: "org_user",
+				Limit:  10,
+			}
+			err = UpdateOrgQuota(&orgCmd)
+			So(err, ShouldBeNil)
+
+			query = m.GetOrgQuotaByTargetQuery{OrgId: orgId, Target: "org_user", Default: 1}
+			err = GetOrgQuotaByTarget(&query)
+			So(err, ShouldBeNil)
+			So(query.Result.Limit, ShouldEqual, 10)
+		})
+
+		// related: https://github.com/grafana/grafana/issues/14342
+		Convey("Should user quota updating is successful even if it called multiple time", func() {
+			userQuotaCmd := m.UpdateUserQuotaCmd{
+				UserId: userId,
+				Target: "org_user",
+				Limit:  5,
+			}
+			err := UpdateUserQuota(&userQuotaCmd)
+			So(err, ShouldBeNil)
+
+			query := m.GetUserQuotaByTargetQuery{UserId: userId, Target: "org_user", Default: 1}
+			err = GetUserQuotaByTarget(&query)
+			So(err, ShouldBeNil)
+			So(query.Result.Limit, ShouldEqual, 5)
+
+			// XXX: resolution of `Updated` column is 1sec, so this makes delay
+			time.Sleep(1 * time.Second)
+
+			userQuotaCmd = m.UpdateUserQuotaCmd{
+				UserId: userId,
+				Target: "org_user",
+				Limit:  10,
+			}
+			err = UpdateUserQuota(&userQuotaCmd)
+			So(err, ShouldBeNil)
+
+			query = m.GetUserQuotaByTargetQuery{UserId: userId, Target: "org_user", Default: 1}
+			err = GetUserQuotaByTarget(&query)
+			So(err, ShouldBeNil)
+			So(query.Result.Limit, ShouldEqual, 10)
+		})
 	})
 }

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

@@ -504,8 +504,18 @@ func UpdateUserPermissions(cmd *m.UpdateUserPermissionsCommand) error {
 
 		user.IsAdmin = cmd.IsGrafanaAdmin
 		sess.UseBool("is_admin")
+
 		_, err := sess.ID(user.Id).Update(&user)
-		return err
+		if err != nil {
+			return err
+		}
+
+		// validate that after update there is at least one server admin
+		if err := validateOneAdminLeft(sess); err != nil {
+			return err
+		}
+
+		return nil
 	})
 }
 
@@ -522,3 +532,17 @@ func SetUserHelpFlag(cmd *m.SetUserHelpFlagCommand) error {
 		return err
 	})
 }
+
+func validateOneAdminLeft(sess *DBSession) error {
+	// validate that there is an admin user left
+	count, err := sess.Where("is_admin=?", true).Count(&m.User{})
+	if err != nil {
+		return err
+	}
+
+	if count == 0 {
+		return m.ErrLastGrafanaAdmin
+	}
+
+	return nil
+}

+ 26 - 0
pkg/services/sqlstore/user_test.go

@@ -155,6 +155,32 @@ func TestUserDataAccess(t *testing.T) {
 				})
 			})
 		})
+
+		Convey("Given one grafana admin user", func() {
+			var err error
+			createUserCmd := &m.CreateUserCommand{
+				Email:   fmt.Sprint("admin", "@test.com"),
+				Name:    fmt.Sprint("admin"),
+				Login:   fmt.Sprint("admin"),
+				IsAdmin: true,
+			}
+			err = CreateUser(context.Background(), createUserCmd)
+			So(err, ShouldBeNil)
+
+			Convey("Cannot make themselves a non-admin", func() {
+				updateUserPermsCmd := m.UpdateUserPermissionsCommand{IsGrafanaAdmin: false, UserId: 1}
+				updatePermsError := UpdateUserPermissions(&updateUserPermsCmd)
+
+				So(updatePermsError, ShouldEqual, m.ErrLastGrafanaAdmin)
+
+				query := m.GetUserByIdQuery{Id: createUserCmd.Result.Id}
+				getUserError := GetUserById(&query)
+
+				So(getUserError, ShouldBeNil)
+
+				So(query.Result.IsAdmin, ShouldEqual, true)
+			})
+		})
 	})
 }
 

+ 9 - 0
pkg/setting/setting.go

@@ -57,6 +57,9 @@ var (
 	IsEnterprise    bool
 	ApplicationName string
 
+	// packaging
+	Packaging = "unknown"
+
 	// Paths
 	HomePath       string
 	PluginsPath    string
@@ -112,6 +115,7 @@ var (
 	ExternalUserMngLinkUrl  string
 	ExternalUserMngLinkName string
 	ExternalUserMngInfo     string
+	OAuthAutoLogin          bool
 	ViewersCanEdit          bool
 
 	// Http auth
@@ -215,6 +219,8 @@ type Cfg struct {
 	DisableBruteForceLoginProtection bool
 	TempDataLifetime                 time.Duration
 	MetricsEndpointEnabled           bool
+	MetricsEndpointBasicAuthUsername string
+	MetricsEndpointBasicAuthPassword string
 	EnableAlphaPanels                bool
 	EnterpriseLicensePath            string
 }
@@ -626,6 +632,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
 	auth := iniFile.Section("auth")
 	DisableLoginForm = auth.Key("disable_login_form").MustBool(false)
 	DisableSignoutMenu = auth.Key("disable_signout_menu").MustBool(false)
+	OAuthAutoLogin = auth.Key("oauth_auto_login").MustBool(false)
 	SignoutRedirectUrl = auth.Key("signout_redirect_url").String()
 
 	// anonymous access
@@ -676,6 +683,8 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
 	cfg.PhantomDir = filepath.Join(HomePath, "tools/phantomjs")
 	cfg.TempDataLifetime = iniFile.Section("paths").Key("temp_data_lifetime").MustDuration(time.Second * 3600 * 24)
 	cfg.MetricsEndpointEnabled = iniFile.Section("metrics").Key("enabled").MustBool(true)
+	cfg.MetricsEndpointBasicAuthUsername = iniFile.Section("metrics").Key("basic_auth_username").String()
+	cfg.MetricsEndpointBasicAuthPassword = iniFile.Section("metrics").Key("basic_auth_password").String()
 
 	analytics := iniFile.Section("analytics")
 	ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true)

+ 2 - 0
pkg/social/google_oauth.go

@@ -32,6 +32,7 @@ func (s *SocialGoogle) IsSignupAllowed() bool {
 
 func (s *SocialGoogle) UserInfo(client *http.Client, token *oauth2.Token) (*BasicUserInfo, error) {
 	var data struct {
+		Id    string `json:"id"`
 		Name  string `json:"name"`
 		Email string `json:"email"`
 	}
@@ -47,6 +48,7 @@ func (s *SocialGoogle) UserInfo(client *http.Client, token *oauth2.Token) (*Basi
 	}
 
 	return &BasicUserInfo{
+		Id:    data.Id,
 		Name:  data.Name,
 		Email: data.Email,
 		Login: data.Email,

+ 25 - 2
pkg/tsdb/cloudwatch/cloudwatch.go

@@ -126,6 +126,18 @@ func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo
 		}
 
 		eg.Go(func() error {
+			defer func() {
+				if err := recover(); err != nil {
+					plog.Error("Execute Query Panic", "error", err, "stack", log.Stack(1))
+					if theErr, ok := err.(error); ok {
+						resultChan <- &tsdb.QueryResult{
+							RefId: query.RefId,
+							Error: theErr,
+						}
+					}
+				}
+			}()
+
 			queryRes, err := e.executeQuery(ectx, query, queryContext)
 			if ae, ok := err.(awserr.Error); ok && ae.Code() == "500" {
 				return err
@@ -146,6 +158,17 @@ func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo
 		for region, getMetricDataQuery := range getMetricDataQueries {
 			q := getMetricDataQuery
 			eg.Go(func() error {
+				defer func() {
+					if err := recover(); err != nil {
+						plog.Error("Execute Get Metric Data Query Panic", "error", err, "stack", log.Stack(1))
+						if theErr, ok := err.(error); ok {
+							resultChan <- &tsdb.QueryResult{
+								Error: theErr,
+							}
+						}
+					}
+				}()
+
 				queryResponses, err := e.executeGetMetricDataQuery(ectx, region, q, queryContext)
 				if ae, ok := err.(awserr.Error); ok && ae.Code() == "500" {
 					return err
@@ -188,8 +211,8 @@ func (e *CloudWatchExecutor) executeQuery(ctx context.Context, query *CloudWatch
 		return nil, err
 	}
 
-	if endTime.Before(startTime) {
-		return nil, fmt.Errorf("Invalid time range: End time can't be before start time")
+	if !startTime.Before(endTime) {
+		return nil, fmt.Errorf("Invalid time range: Start time must be before end time")
 	}
 
 	params := &cloudwatch.GetMetricStatisticsInput{

+ 22 - 0
pkg/tsdb/cloudwatch/cloudwatch_test.go

@@ -1,9 +1,13 @@
 package cloudwatch
 
 import (
+	"context"
 	"testing"
 	"time"
 
+	"github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/tsdb"
+
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/service/cloudwatch"
 	"github.com/grafana/grafana/pkg/components/null"
@@ -14,6 +18,24 @@ import (
 func TestCloudWatch(t *testing.T) {
 	Convey("CloudWatch", t, func() {
 
+		Convey("executeQuery", func() {
+			e := &CloudWatchExecutor{
+				DataSource: &models.DataSource{
+					JsonData: simplejson.New(),
+				},
+			}
+
+			Convey("End time before start time should result in error", func() {
+				_, err := e.executeQuery(context.Background(), &CloudWatchQuery{}, &tsdb.TsdbQuery{TimeRange: tsdb.NewTimeRange("now-1h", "now-2h")})
+				So(err.Error(), ShouldEqual, "Invalid time range: Start time must be before end time")
+			})
+
+			Convey("End time equals start time should result in error", func() {
+				_, err := e.executeQuery(context.Background(), &CloudWatchQuery{}, &tsdb.TsdbQuery{TimeRange: tsdb.NewTimeRange("now-1h", "now-1h")})
+				So(err.Error(), ShouldEqual, "Invalid time range: Start time must be before end time")
+			})
+		})
+
 		Convey("can parse cloudwatch json model", func() {
 			json := `
 				{

+ 4 - 0
pkg/tsdb/cloudwatch/metric_find_query.go

@@ -46,6 +46,8 @@ func init() {
 		"AWS/Billing":        {"EstimatedCharges"},
 		"AWS/CloudFront":     {"Requests", "BytesDownloaded", "BytesUploaded", "TotalErrorRate", "4xxErrorRate", "5xxErrorRate"},
 		"AWS/CloudSearch":    {"SuccessfulRequests", "SearchableDocuments", "IndexUtilization", "Partitions"},
+		"AWS/CloudHSM":       {"HsmUnhealthy", "HsmTemperature", "HsmKeysSessionOccupied", "HsmKeysTokenOccupied", "HsmSslCtxsOccupied", "HsmSessionCount", "HsmUsersAvailable", "HsmUsersMax", "InterfaceEth2OctetsInput", "InterfaceEth2OctetsOutput"},
+		"AWS/CodeBuild":      {"BuildDuration", "Builds", "DownloadSourceDuration", "Duration", "FailedBuilds", "FinalizingDuration", "InstallDuration", "PostBuildDuration", "PreBuildDuration", "ProvisioningDuration", "QueuedDuration", "SubmittedDuration", "SucceededBuilds", "UploadArtifactsDuration"},
 		"AWS/Connect":        {"CallsBreachingConcurrencyQuota", "CallBackNotDialableNumber", "CallRecordingUploadError", "CallsPerInterval", "ConcurrentCalls", "ConcurrentCallsPercentage", "ContactFlowErrors", "ContactFlowFatalErrors", "LongestQueueWaitTime", "MissedCalls", "MisconfiguredPhoneNumbers", "PublicSigningKeyUsage", "QueueCapacityExceededError", "QueueSize", "ThrottledCalls", "ToInstancePacketLossRate"},
 		"AWS/DMS":            {"FreeableMemory", "WriteIOPS", "ReadIOPS", "WriteThroughput", "ReadThroughput", "WriteLatency", "ReadLatency", "SwapUsage", "NetworkTransmitThroughput", "NetworkReceiveThroughput", "FullLoadThroughputBandwidthSource", "FullLoadThroughputBandwidthTarget", "FullLoadThroughputRowsSource", "FullLoadThroughputRowsTarget", "CDCIncomingChanges", "CDCChangesMemorySource", "CDCChangesMemoryTarget", "CDCChangesDiskSource", "CDCChangesDiskTarget", "CDCThroughputBandwidthTarget", "CDCThroughputRowsSource", "CDCThroughputRowsTarget", "CDCLatencySource", "CDCLatencyTarget"},
 		"AWS/DX":             {"ConnectionState", "ConnectionBpsEgress", "ConnectionBpsIngress", "ConnectionPpsEgress", "ConnectionPpsIngress", "ConnectionCRCErrorCount", "ConnectionLightLevelTx", "ConnectionLightLevelRx"},
@@ -121,6 +123,8 @@ func init() {
 		"AWS/Billing":          {"ServiceName", "LinkedAccount", "Currency"},
 		"AWS/CloudFront":       {"DistributionId", "Region"},
 		"AWS/CloudSearch":      {},
+		"AWS/CloudHSM":         {"Region", "ClusterId", "HsmId"},
+		"AWS/CodeBuild":        {"ProjectName"},
 		"AWS/Connect":          {"InstanceId", "MetricGroup", "Participant", "QueueName", "Stream Type", "Type of Connection"},
 		"AWS/DMS":              {"ReplicationInstanceIdentifier", "ReplicationTaskIdentifier"},
 		"AWS/DX":               {"ConnectionId"},

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

@@ -65,7 +65,7 @@ var NewClient = func(ctx context.Context, ds *models.DataSource, timeRange *tsdb
 	clientLog.Debug("Creating new client", "version", version, "timeField", timeField, "indices", strings.Join(indices, ", "))
 
 	switch version {
-	case 2, 5, 56:
+	case 2, 5, 56, 60:
 		return &baseClientImpl{
 			ctx:       ctx,
 			ds:        ds,

Some files were not shown because too many files changed in this diff