diff --git a/.circleci/config.yml b/.circleci/config.yml index 8144956773b..69cea87dccd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -35,7 +35,7 @@ jobs: - run: cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h 127.0.0.1 -P 3306 -u root -prootpass - run: name: mysql integration tests - command: 'GRAFANA_TEST_DB=mysql go test ./pkg/services/sqlstore/... ./pkg/tsdb/mysql/... ' + command: './scripts/circle-test-mysql.sh' postgres-integration-test: docker: @@ -54,7 +54,7 @@ jobs: - run: 'PGPASSWORD=grafanatest psql -p 5432 -h 127.0.0.1 -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql' - run: name: postgres integration tests - command: 'GRAFANA_TEST_DB=postgres go test ./pkg/services/sqlstore/... ./pkg/tsdb/postgres/...' + command: './scripts/circle-test-postgres.sh' codespell: docker: diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 8086a6b86e5..00000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,16 +0,0 @@ -Read before posting: - -- Questions should be posted to https://community.grafana.com. Please search there and here on GitHub for similar issues before creating a new issue. -- Checkout FAQ: https://community.grafana.com/c/howto/faq -- Checkout How to troubleshoot metric query issues: https://community.grafana.com/t/how-to-troubleshoot-metric-query-issues/50 - -Please include this information: -### What Grafana version are you using? -### What datasource are you using? -### What OS are you running grafana on? -### What did you do? -### What was the expected result? -### What happened instead? -### If related to metric query / data viz: -### Include raw network request & response: get by opening Chrome Dev Tools (F12, Ctrl+Shift+I on windows, Cmd+Opt+I on Mac), go the network tab. - diff --git a/.github/ISSUE_TEMPLATE/1-bug_report.md b/.github/ISSUE_TEMPLATE/1-bug_report.md new file mode 100644 index 00000000000..eb87a7fc1c9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1-bug_report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Report a bug you found when using Grafana +labels: 'type: bug' +--- + + + +**What happened**: + +**What you expected to happen**: + +**How to reproduce it (as minimally and precisely as possible)**: + +**Anything else we need to know?**: + +**Environment**: +- Grafana version: +- Data source type & version: +- OS Grafana is installed on: +- User OS & Browser: +- Grafana plugins: +- Others: diff --git a/.github/ISSUE_TEMPLATE/2-feature_request.md b/.github/ISSUE_TEMPLATE/2-feature_request.md new file mode 100644 index 00000000000..ccb9f3cc391 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2-feature_request.md @@ -0,0 +1,11 @@ +--- +name: Enhancement request +about: Suggest an enhancement or new feature for the Grafana project +labels: 'type: feature request' +--- + + + +**What would you like to be added**: + +**Why is this needed**: diff --git a/.github/ISSUE_TEMPLATE/3-accessibility.md b/.github/ISSUE_TEMPLATE/3-accessibility.md new file mode 100644 index 00000000000..51b3ecc255b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/3-accessibility.md @@ -0,0 +1,26 @@ +--- +name: Accessibility issue +about: Help make Grafana be better at keyboard navigation, screen-readable and accessible to all. +labels: 'type: accessibility' +--- + + + +**Steps to reproduce**: + +**Actual Result**: + +**Expected Result** + +**Relevant WCAG Criteria:** [#.#.# WCAG Criterion](link to https://www.w3.org/WAI/WCAG21/quickref/?versions=2.0) + +**Environment**: +- Grafana version: +- Data source type & version: +- User OS & Browser: +- Others: diff --git a/.github/ISSUE_TEMPLATE/4-question.md b/.github/ISSUE_TEMPLATE/4-question.md new file mode 100644 index 00000000000..3e3a20e49ed --- /dev/null +++ b/.github/ISSUE_TEMPLATE/4-question.md @@ -0,0 +1,14 @@ +--- +name: Support request +about: 'Question or support request relating to using Grafana' +title: '' +labels: '' +assignees: '' +--- + +STOP -- PLEASE READ! + +GitHub is not the right place for questions and support requests. + +Please ask questions on our community site: [https://community.grafana.com/](https://community.grafana.com/) + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 69ce3168730..22642808fa4 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,5 +1,27 @@ -* Follow the contribution guidelines in [`CONTRIBUTING.md`](https://github.com/grafana/grafana/blob/master/CONTRIBUTING.md) -* Rebase your PR if it gets out of sync with master -* Include `closes #` or a link to the issue in the description + + +**What this PR does / why we need it**: + +**Which issue(s) this PR fixes**: + +Fixes # + +**Special notes for your reviewer**: + +**Release note**: + +```release-note + +``` diff --git a/.gitignore b/.gitignore index 2945746832a..a9547973fc8 100644 --- a/.gitignore +++ b/.gitignore @@ -78,3 +78,9 @@ debug.test /scripts/build/release_publisher/release_publisher *.patch + + +# Ignoring frontend packages specifics +/packages/**/dist +/packages/**/compiled +/packages/**/.rpt2_cache diff --git a/.prettierignore b/.prettierignore index b7a33870ddd..336d03e2551 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,4 +5,5 @@ pkg/ node_modules public/vendor/ vendor/ +data/ diff --git a/CHANGELOG.md b/CHANGELOG.md index faa2f49faff..5483529f75e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,46 @@ -# 6.0.0-beta3 (unreleased) +# 6.1.0 (unreleased) + +### New Features +* **Prometheus**: adhoc filter support [#8253](https://github.com/grafana/grafana/issues/8253), thx [@mtanda](https://github.com/mtanda) + +### Minor +* **Cloudwatch**: Add AWS RDS MaximumUsedTransactionIDs metric [#15077](https://github.com/grafana/grafana/pull/15077), thx [@activeshadow](https://github.com/activeshadow) +* **Heatmap**: `Middle` bucket bound option [#15683](https://github.com/grafana/grafana/issues/15683) +* **Heatmap**: `Reverse order` option for changing order of buckets [#15683](https://github.com/grafana/grafana/issues/15683) + +### Bug Fixes +* **Api**: Invalid org invite code [#10506](https://github.com/grafana/grafana/issues/10506) +* **Datasource**: Handles nil jsondata field gracefully [#14239](https://github.com/grafana/grafana/issues/14239) +* **Gauge**: Interpolate scoped variables in repeated gauges [#15739](https://github.com/grafana/grafana/issues/15739) +* **Datasource**: Empty user/password was not updated when updating datasources [#15608](https://github.com/grafana/grafana/pull/15608), thx [@Maddin-619](https://github.com/Maddin-619) +* **Heatmap**: legend shows wrong colors for small values [#14019](https://github.com/grafana/grafana/issues/14019) + +# 6.0.1 (2019-03-06) + +### Bug Fixes +* **Metrics**: Fixes broken usagestats metrics for /metrics [#15651](https://github.com/grafana/grafana/issues/15651) +* **Dashboard**: Fixes kiosk mode should have &kiosk appended to the url [#15765](https://github.com/grafana/grafana/issues/15765) +* **Dashboard**: Fixes kiosk=tv mode with autofitpanels should respect header [#15650](https://github.com/grafana/grafana/issues/15650) +* **Image rendering**: Fixed image rendering issue for dashboards with auto refresh, . [#15818](https://github.com/grafana/grafana/pull/15818), [@torkelo](https://github.com/torkelo) +* **Dashboard**: Fix only users that can edit a dashboard should be able to update panel json. [#15805](https://github.com/grafana/grafana/pull/15805), [@marefr](https://github.com/marefr) +* **LDAP**: fix allow anonymous initial bind for ldap search. [#15803](https://github.com/grafana/grafana/pull/15803), [@marefr](https://github.com/marefr) +* **UX**: Fixed scrollbar not visible initially (only after manual scroll). [#15798](https://github.com/grafana/grafana/pull/15798), [@torkelo](https://github.com/torkelo) +* **Datasource admin** TestData [#15793](https://github.com/grafana/grafana/pull/15793), [@hugohaggmark](https://github.com/hugohaggmark) +* **Dashboard**: Fixed scrolling issue that caused scroll to be locked to bottom. [#15792](https://github.com/grafana/grafana/pull/15792), [@torkelo](https://github.com/torkelo) +* **Explore**: Viewers with viewers_can_edit should be able to access /explore. [#15787](https://github.com/grafana/grafana/pull/15787), [@jschill](https://github.com/jschill) +* **Security** fix: limit access to org admin and alerting pages. [#15761](https://github.com/grafana/grafana/pull/15761), [@marefr](https://github.com/marefr) +* **Panel Edit** minInterval changes did not persist [#15757](https://github.com/grafana/grafana/pull/15757), [@hugohaggmark](https://github.com/hugohaggmark) +* **Teams**: Fixed bug when getting teams for user. [#15595](https://github.com/grafana/grafana/pull/15595), [@hugohaggmark](https://github.com/hugohaggmark) +* **Stackdriver**: fix for float64 bounds for distribution metrics [#14509](https://github.com/grafana/grafana/issues/14509) +* **Stackdriver**: no reducers available for distribution type [#15179](https://github.com/grafana/grafana/issues/15179) + +# 6.0.0 stable (2019-02-25) + +### Bug Fixes +* **Dashboard**: fixes click after scroll in series override menu [#15621](https://github.com/grafana/grafana/issues/15621) +* **MySQL**: fix mysql query using _interval_ms variable throws error [#14507](https://github.com/grafana/grafana/issues/14507) + +# 6.0.0-beta3 (2019-02-19) ### Minor * **CLI**: Grafana CLI should preserve permissions for backend binaries for Linux and Darwin [#15500](https://github.com/grafana/grafana/issues/15500) @@ -30,6 +72,7 @@ * **AzureMonitor**: improve autocomplete for Log Analytics and App Insights editor [#15131](https://github.com/grafana/grafana/issues/15131) * **LDAP**: Fix IPA/FreeIPA v4.6.4 does not allow LDAP searches with empty attributes [#14432](https://github.com/grafana/grafana/issues/14432) * **Provisioning**: Allow testing data sources that were added by config [#12164](https://github.com/grafana/grafana/issues/12164) +* **Security**: Fix CSRF Token validation for POSTs [#1441](https://github.com/grafana/grafana/issues/1441) ### Breaking changes @@ -53,6 +96,7 @@ * **Snapshots**: Enable deletion of public snapshot [#14109](https://github.com/grafana/grafana/issues/14109) * **Provisioning**: Provisioning support for alert notifiers [#10487](https://github.com/grafana/grafana/issues/10487), thx [@pbakulev](https://github.com/pbakulev) * **Explore**: A whole new way to do ad-hoc metric queries and exploration. Split view in half and compare metrics & logs and much much more. [Read more here](http://docs.grafana.org/features/explore/) +* **Auth**: Replace remember me cookie solution for Grafana's builtin, LDAP and OAuth authentication with a solution based on short-lived tokens [#15303](https://github.com/grafana/grafana/issues/15303) ### Minor diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 677a12831dc..d21ef5232d6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,10 +34,10 @@ To setup a local development environment we recommend reading [Building Grafana ### Pull requests with new features Commits should be as small as possible, while ensuring that each commit is correct independently (i.e., each commit should compile and pass tests). -Make sure to include `closes #` or `fixes #` in the pull request description. +Make sure to include `Closes #` or `Fixes #` in the pull request description. ### Pull requests with bug fixes -Please make all changes in one commit if possible. Include `closes #12345` in bottom of the commit message. +Please make all changes in one commit if possible. Include `Closes #` in bottom of the commit message. A commit message for a bug fix should look something like this. ``` @@ -48,7 +48,7 @@ provsioners each provisioner overwrite each other. filling up dashboard_versions quite fast if using default settings. -closes #12864 +Closes #12864 ``` -If the pull request needs changes before its merged the new commits should be rebased into one commit before its merged. \ No newline at end of file +If the pull request needs changes before its merged the new commits should be rebased into one commit before its merged. diff --git a/Gopkg.lock b/Gopkg.lock index dca36f1b3d0..235a315f1e8 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -2,30 +2,39 @@ [[projects]] + digest = "1:f8ad8a53fa865a70efbe215b0ca34735523f50ea39e0efde319ab6fc80089b44" name = "cloud.google.com/go" packages = ["compute/metadata"] + pruneopts = "NUT" revision = "056a55f54a6cc77b440b31a56a5e7c3982d32811" version = "v0.22.0" [[projects]] + digest = "1:167b6f65a6656de568092189ae791253939f076df60231fdd64588ac703892a1" name = "github.com/BurntSushi/toml" packages = ["."] + pruneopts = "NUT" revision = "b26d9c308763d68093482582cea63d69be07a0f0" version = "v0.3.0" [[projects]] branch = "master" + digest = "1:7d23e6e1889b8bb4bbb37a564708fdab4497ce232c3a99d66406c975b642a6ff" name = "github.com/Unknwon/com" packages = ["."] + pruneopts = "NUT" revision = "7677a1d7c1137cd3dd5ba7a076d0c898a1ef4520" [[projects]] branch = "master" + digest = "1:1610787cd9726e29d8fecc2a80e43e4fced008a1f560fec6688fc4d946f17835" name = "github.com/VividCortex/mysqlerr" packages = ["."] + pruneopts = "NUT" revision = "6c6b55f8796f578c870b7e19bafb16103bc40095" [[projects]] + digest = "1:ebe102b61c1615d2954734e3cfe1b6b06a5088c25a41055b38661d41ad7b8f27" name = "github.com/aws/aws-sdk-go" packages = [ "aws", @@ -69,399 +78,507 @@ "service/resourcegroupstaggingapi", "service/resourcegroupstaggingapi/resourcegroupstaggingapiiface", "service/s3", - "service/sts" + "service/sts", ] + pruneopts = "NUT" revision = "62936e15518acb527a1a9cb4a39d96d94d0fd9a2" version = "v1.16.15" [[projects]] branch = "master" + digest = "1:79cad073c7be02632d3fa52f62486848b089f560db1e94536de83a408c0f4726" name = "github.com/benbjohnson/clock" packages = ["."] + pruneopts = "NUT" revision = "7dc76406b6d3c05b5f71a86293cbcf3c4ea03b19" [[projects]] branch = "master" + digest = "1:707ebe952a8b3d00b343c01536c79c73771d100f63ec6babeaed5c79e2b8a8dd" name = "github.com/beorn7/perks" packages = ["quantile"] + pruneopts = "NUT" revision = "3a771d992973f24aa725d07868b467d1ddfceafb" [[projects]] branch = "master" + digest = "1:433a2ff0ef4e2f8634614aab3174783c5ff80120b487712db96cc3712f409583" name = "github.com/bmizerany/assert" packages = ["."] + pruneopts = "NUT" revision = "b7ed37b82869576c289d7d97fb2bbd8b64a0cb28" [[projects]] branch = "master" + digest = "1:d8f9145c361920507a4f85ffb7f70b96beaedacba2ce8c00aa663adb08689d3e" name = "github.com/bradfitz/gomemcache" packages = ["memcache"] + pruneopts = "NUT" revision = "1952afaa557dc08e8e0d89eafab110fb501c1a2b" [[projects]] branch = "master" + digest = "1:8ecb89af7dfe3ac401bdb0c9390b134ef96a97e85f732d2b0604fb7b3977839f" name = "github.com/codahale/hdrhistogram" packages = ["."] + pruneopts = "NUT" revision = "3a0bb77429bd3a61596f5e8a3172445844342120" [[projects]] + digest = "1:5dba68a1600a235630e208cb7196b24e58fcbb77bb7a6bec08fcd23f081b0a58" name = "github.com/codegangsta/cli" packages = ["."] + pruneopts = "NUT" revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1" version = "v1.20.0" [[projects]] + digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39" name = "github.com/davecgh/go-spew" packages = ["spew"] + pruneopts = "NUT" revision = "346938d642f2ec3594ed81d874461961cd0faa76" version = "v1.1.0" [[projects]] + digest = "1:1b318d2dd6cea8a1a8d8ec70348852303bd3e491df74e8bca6e32eb5a4d06970" name = "github.com/denisenkom/go-mssqldb" packages = [ ".", - "internal/cp" + "internal/cp", ] + pruneopts = "NUT" revision = "270bc3860bb94dd3a3ffd047377d746c5e276726" [[projects]] branch = "master" + digest = "1:2da5f11ad66ff01a27a5c3dba4620b7eee2327be75b32c9ee9f87c9a8001ecbf" name = "github.com/facebookgo/inject" packages = ["."] + pruneopts = "NUT" revision = "cc1aa653e50f6a9893bcaef89e673e5b24e1e97b" [[projects]] branch = "master" + digest = "1:1108df7f658c90db041e0d6174d55be689aaeb0585913b9c3c7aab51a3a6b2b1" name = "github.com/facebookgo/structtag" packages = ["."] + pruneopts = "NUT" revision = "217e25fb96916cc60332e399c9aa63f5c422ceed" [[projects]] + digest = "1:ade392a843b2035effb4b4a2efa2c3bab3eb29b992e98bacf9c898b0ecb54e45" name = "github.com/fatih/color" packages = ["."] + pruneopts = "NUT" revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4" version = "v1.7.0" -[[projects]] - name = "github.com/go-ini/ini" - packages = ["."] - revision = "6529cf7c58879c08d927016dde4477f18a0634cb" - version = "v1.36.0" - [[projects]] branch = "master" + digest = "1:682a0aca743a1a4a36697f3d7f86c0ed403c4e3a780db9935f633242855eac9c" name = "github.com/go-macaron/binding" packages = ["."] + pruneopts = "NUT" revision = "ac54ee249c27dca7e76fad851a4a04b73bd1b183" [[projects]] branch = "master" + digest = "1:6326b27f8e0c8e135c8674ddbc619fae879664ac832e8e6fa6a23ce0d279ed4d" name = "github.com/go-macaron/gzip" packages = ["."] + pruneopts = "NUT" revision = "cad1c6580a07c56f5f6bc52d66002a05985c5854" [[projects]] branch = "master" + digest = "1:fb8711b648d1ff03104fc1d9593a13cb1d5120be7ba2b01641c14ccae286a9e3" name = "github.com/go-macaron/inject" packages = ["."] + pruneopts = "NUT" revision = "d8a0b8677191f4380287cfebd08e462217bac7ad" [[projects]] branch = "master" + digest = "1:21577aafe885f088e8086a3415f154c63c0b7ce956a6994df2ac5776bc01b7e3" name = "github.com/go-macaron/session" packages = [ ".", "memcache", "postgres", - "redis" + "redis", ] + pruneopts = "NUT" revision = "068d408f9c54c7fa7fcc5e2bdd3241ab21280c9e" [[projects]] + digest = "1:fddd4bada6100d6fc49a9f32f18ba5718db45a58e4b00aa6377e1cfbf06af34f" name = "github.com/go-sql-driver/mysql" packages = ["."] + pruneopts = "NUT" revision = "2cc627ac8defc45d65066ae98f898166f580f9a4" [[projects]] + digest = "1:a1efdbc2762667c8a41cbf02b19a0549c846bf2c1d08cad4f445e3344089f1f0" name = "github.com/go-stack/stack" packages = ["."] + pruneopts = "NUT" revision = "259ab82a6cad3992b4e21ff5cac294ccb06474bc" version = "v1.7.0" [[projects]] + digest = "1:06d21295033f211588d0ad7ff391cc1b27e72b60cb6d4b7db0d70cffae4cf228" name = "github.com/go-xorm/builder" packages = ["."] - revision = "bad0a612f0d6277b953910822ab5dfb30dd18237" - version = "v0.2.0" + pruneopts = "NUT" + revision = "1d658d7596c25394aab557ef5b50ef35bf706384" + version = "v0.3.4" [[projects]] + digest = "1:b26928aab0fff92592e8728c5bc9d6e404fa2017d6a8e841ae5e60a42237f6fc" name = "github.com/go-xorm/core" packages = ["."] - revision = "da1adaf7a28ca792961721a34e6e04945200c890" - version = "v0.5.7" + pruneopts = "NUT" + revision = "ccc80c1adf1f6172bbc548877f50a1163041a40a" + version = "v0.6.2" [[projects]] + digest = "1:407316703b32d68ccf5d39bdae57d411b6954e253e07d0fff0988a3f39861f2f" name = "github.com/go-xorm/xorm" packages = ["."] - revision = "1933dd69e294c0a26c0266637067f24dbb25770c" - version = "v0.6.4" + pruneopts = "NUT" + revision = "1f39c590c64924f358c0d89016ac9b2bb84e9125" + version = "v0.7.1" [[projects]] branch = "master" + digest = "1:ffbb19fb66f140b5ea059428d1f84246a055d1bc3d9456c1e5c3d143611f03d0" name = "github.com/golang/protobuf" packages = [ "proto", "ptypes", "ptypes/any", "ptypes/duration", - "ptypes/timestamp" + "ptypes/timestamp", ] + pruneopts = "NUT" revision = "927b65914520a8b7d44f5c9057611cfec6b2e2d0" [[projects]] branch = "master" + digest = "1:f14d1b50e0075fb00177f12a96dd7addf93d1e2883c25befd17285b779549795" name = "github.com/gopherjs/gopherjs" packages = ["js"] + pruneopts = "NUT" revision = "8dffc02ea1cb8398bb73f30424697c60fcf8d4c5" [[projects]] + digest = "1:3b708ebf63bfa9ba3313bedb8526bc0bb284e51474e65e958481476a9d4a12aa" name = "github.com/gorilla/websocket" packages = ["."] + pruneopts = "NUT" revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b" version = "v1.2.0" [[projects]] + digest = "1:4e771d1c6e15ca4516ad971c34205c822b5cff2747179679d7b321e4e1bfe431" name = "github.com/gosimple/slug" packages = ["."] + pruneopts = "NUT" revision = "e9f42fa127660e552d0ad2b589868d403a9be7c6" version = "v1.1.1" [[projects]] branch = "master" + digest = "1:08e53c69cd267ef7d71eeae5d953153d0d2bc1b8e0b498731fe9acaead7001b6" name = "github.com/grafana/grafana-plugin-model" packages = [ "go/datasource", - "go/renderer" + "go/renderer", ] + pruneopts = "NUT" revision = "84176c64269d8060f99e750ee8aba6f062753336" [[projects]] branch = "master" + digest = "1:58ba5285227b0f635652cd4aa82c4cfd00b590191eadd823462f0c9f64e3ae07" name = "github.com/hashicorp/go-hclog" packages = ["."] + pruneopts = "NUT" revision = "69ff559dc25f3b435631604f573a5fa1efdb6433" [[projects]] + digest = "1:532090ffc3b05a7e4c0229dd2698d79149f2e0683df993224a8b202f607fb605" name = "github.com/hashicorp/go-plugin" packages = ["."] + pruneopts = "NUT" revision = "e8d22c780116115ae5624720c9af0c97afe4f551" [[projects]] branch = "master" + digest = "1:8925116d1edcd85fc0c014e1aa69ce12892489b48ee633a605c46d893b8c151f" name = "github.com/hashicorp/go-version" packages = ["."] + pruneopts = "NUT" revision = "23480c0665776210b5fbbac6eaaee40e3e6a96b7" [[projects]] branch = "master" + digest = "1:8deb0c5545c824dfeb0ac77ab8eb67a3d541eab76df5c85ce93064ef02d44cd0" name = "github.com/hashicorp/yamux" packages = ["."] + pruneopts = "NUT" revision = "7221087c3d281fda5f794e28c2ea4c6e4d5c4558" [[projects]] + digest = "1:efbe016b6d198cf44f1db0ed2fbdf1b36ebf1f6956cc9b76d6affa96f022d368" name = "github.com/inconshreveable/log15" packages = ["."] + pruneopts = "NUT" revision = "0decfc6c20d9ca0ad143b0e89dcaa20f810b4fb3" version = "v2.13" [[projects]] + digest = "1:1f2aebae7e7c856562355ec0198d8ca2fa222fb05e5b1b66632a1fce39631885" name = "github.com/jmespath/go-jmespath" packages = ["."] - revision = "0b12d6b5" + pruneopts = "NUT" + revision = "c2b33e84" [[projects]] + digest = "1:6ddab442e52381bab82fb6c07ef3f4b565ff7ec4b8fae96d8dd4b8573a460597" name = "github.com/jtolds/gls" packages = ["."] + pruneopts = "NUT" revision = "77f18212c9c7edc9bd6a33d383a7b545ce62f064" version = "v4.2.1" [[projects]] + digest = "1:1da1796a71eb70f1e3e085984d044f67840bb0326816ec8276231aa87b1b9fc3" name = "github.com/klauspost/compress" packages = [ "flate", - "gzip" + "gzip", ] + pruneopts = "NUT" revision = "6c8db69c4b49dd4df1fff66996cf556176d0b9bf" version = "v1.2.1" [[projects]] + digest = "1:5e55a8699c9ff7aba1e4c8952aeda209685d88d4cb63a8766c338e333b8e65d6" name = "github.com/klauspost/cpuid" packages = ["."] + pruneopts = "NUT" revision = "ae7887de9fa5d2db4eaa8174a7eff2c1ac00f2da" version = "v1.1" [[projects]] + digest = "1:b95da1293525625ef6f07be79d537b9bf2ecd7901efcf9a92193edafbd55b9ef" name = "github.com/klauspost/crc32" packages = ["."] + pruneopts = "NUT" revision = "cb6bfca970f6908083f26f39a79009d608efd5cd" version = "v1.1" [[projects]] + digest = "1:7b21c7fc5551b46d1308b4ffa9e9e49b66c7a8b0ba88c0130474b0e7a20d859f" name = "github.com/kr/pretty" packages = ["."] + pruneopts = "NUT" revision = "73f6ac0b30a98e433b289500d779f50c1a6f0712" version = "v0.1.0" [[projects]] + digest = "1:c3a7836b5904db0f8b609595b619916a6831cb35b8b714aec39f96d00c6155d8" name = "github.com/kr/text" packages = ["."] + pruneopts = "NUT" revision = "e2ffdb16a802fe2bb95e2e35ff34f0e53aeef34f" version = "v0.1.0" [[projects]] branch = "master" + digest = "1:7a1e592f0349d56fac8ce47f28469e4e7f4ce637cb26f40c88da9dff25db1c98" name = "github.com/lib/pq" packages = [ ".", - "oid" + "oid", ] + pruneopts = "NUT" revision = "d34b9ff171c21ad295489235aec8b6626023cd04" [[projects]] + digest = "1:08c231ec84231a7e23d67e4b58f975e1423695a32467a362ee55a803f9de8061" name = "github.com/mattn/go-colorable" packages = ["."] + pruneopts = "NUT" revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072" version = "v0.0.9" [[projects]] + digest = "1:bc4f7eec3b7be8c6cb1f0af6c1e3333d5bb71072951aaaae2f05067b0803f287" name = "github.com/mattn/go-isatty" packages = ["."] + pruneopts = "NUT" revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39" version = "v0.0.3" [[projects]] + digest = "1:536979f1c56397dbf91c2785159b37dec37e35d3bffa3cd1cfe66d25f51f8088" name = "github.com/mattn/go-sqlite3" packages = ["."] + pruneopts = "NUT" revision = "323a32be5a2421b8c7087225079c6c900ec397cd" version = "v1.7.0" [[projects]] + digest = "1:5985ef4caf91ece5d54817c11ea25f182697534f8ae6521eadcd628c142ac4b6" name = "github.com/matttproud/golang_protobuf_extensions" packages = ["pbutil"] + pruneopts = "NUT" revision = "3247c84500bff8d9fb6d579d800f20b3e091582c" version = "v1.0.0" [[projects]] branch = "master" + digest = "1:18b773b92ac82a451c1276bd2776c1e55ce057ee202691ab33c8d6690efcc048" name = "github.com/mitchellh/go-testing-interface" packages = ["."] + pruneopts = "NUT" revision = "a61a99592b77c9ba629d254a693acffaeb4b7e28" [[projects]] + digest = "1:3b517122f3aad1ecce45a630ea912b3092b4729f25532a911d0cb2935a1f9352" name = "github.com/oklog/run" packages = ["."] + pruneopts = "NUT" revision = "4dadeb3030eda0273a12382bb2348ffc7c9d1a39" version = "v1.0.0" [[projects]] + digest = "1:7da29c22bcc5c2ffb308324377dc00b5084650348c2799e573ed226d8cc9faf0" name = "github.com/opentracing/opentracing-go" packages = [ ".", "ext", - "log" + "log", ] + pruneopts = "NUT" revision = "1949ddbfd147afd4d964a9f00b24eb291e0e7c38" version = "v1.0.2" [[projects]] + digest = "1:748946761cf99c8b73cef5a3c0ee3e040859dd713a20cece0d0e0dc04e6ceca7" name = "github.com/patrickmn/go-cache" packages = ["."] + pruneopts = "NUT" revision = "a3647f8e31d79543b2d0f0ae2fe5c379d72cedc0" version = "v2.1.0" [[projects]] + digest = "1:5cf3f025cbee5951a4ee961de067c8a89fc95a5adabead774f82822efabab121" name = "github.com/pkg/errors" packages = ["."] + pruneopts = "NUT" revision = "645ef00459ed84a119197bfb8d8205042c6df63d" version = "v0.8.0" [[projects]] + digest = "1:4759bed95e3a52febc18c071db28790a5c6e9e106ee201a37add6f6a056f8f9c" name = "github.com/prometheus/client_golang" packages = [ "api", "api/prometheus/v1", "prometheus", - "prometheus/promhttp" + "prometheus/promhttp", ] + pruneopts = "NUT" revision = "967789050ba94deca04a5e84cce8ad472ce313c1" version = "v0.9.0-pre1" [[projects]] branch = "master" + digest = "1:32d10bdfa8f09ecf13598324dba86ab891f11db3c538b6a34d1c3b5b99d7c36b" name = "github.com/prometheus/client_model" packages = ["go"] + pruneopts = "NUT" revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c" [[projects]] branch = "master" + digest = "1:768b555b86742de2f28beb37f1dedce9a75f91f871d75b5717c96399c1a78c08" name = "github.com/prometheus/common" packages = [ "expfmt", "internal/bitbucket.org/ww/goautoneg", - "model" + "model", ] + pruneopts = "NUT" revision = "d811d2e9bf898806ecfb6ef6296774b13ffc314c" [[projects]] branch = "master" + digest = "1:c4a213a8d73fbb0b13f717ba7996116602ef18ecb42b91d77405877914cb0349" name = "github.com/prometheus/procfs" packages = [ ".", "internal/util", "nfs", - "xfs" + "xfs", ] + pruneopts = "NUT" revision = "8b1c2da0d56deffdbb9e48d4414b4e674bd8083e" [[projects]] branch = "master" + digest = "1:16e2136a67ec44aa2d1d6b0fd65394b3c4a8b2a1b6730c77967f7b7b06b179b2" name = "github.com/rainycape/unidecode" packages = ["."] + pruneopts = "NUT" revision = "cb7f23ec59bec0d61b19c56cd88cee3d0cc1870c" [[projects]] + digest = "1:d917313f309bda80d27274d53985bc65651f81a5b66b820749ac7f8ef061fd04" name = "github.com/sergi/go-diff" packages = ["diffmatchpatch"] + pruneopts = "NUT" revision = "1744e2970ca51c86172c8190fadad617561ed6e7" version = "v1.0.0" [[projects]] + digest = "1:1f0b284a6858827de4c27c66b49b2b25df3e16b031c2b57b7892273131e7dd2b" name = "github.com/smartystreets/assertions" packages = [ ".", "internal/go-render/render", - "internal/oglematchers" + "internal/oglematchers", ] + pruneopts = "NUT" revision = "7678a5452ebea5b7090a6b163f844c133f523da2" version = "1.8.3" [[projects]] + digest = "1:7efd0b2309cdd6468029fa30c808c50a820c9344df07e1a4bbdaf18f282907aa" name = "github.com/smartystreets/goconvey" packages = [ "convey", "convey/gotest", - "convey/reporting" + "convey/reporting", ] + pruneopts = "NUT" revision = "9e8dc3f972df6c8fcc0375ef492c24d0bb204857" version = "1.6.3" [[projects]] branch = "master" + digest = "1:a66add8dd963bfc72649017c1b321198f596cb4958cb1a11ff91a1be8691020b" name = "github.com/teris-io/shortid" packages = ["."] + pruneopts = "NUT" revision = "771a37caa5cf0c81f585d7b6df4dfc77e0615b5c" [[projects]] + digest = "1:3d48c38e0eca8c66df62379c5ae7a83fb5cd839b94f241354c07ba077da7bc45" name = "github.com/uber/jaeger-client-go" packages = [ ".", @@ -479,45 +596,55 @@ "thrift-gen/jaeger", "thrift-gen/sampling", "thrift-gen/zipkincore", - "utils" + "utils", ] + pruneopts = "NUT" revision = "b043381d944715b469fd6b37addfd30145ca1758" version = "v2.14.0" [[projects]] + digest = "1:0f09db8429e19d57c8346ad76fbbc679341fa86073d3b8fb5ac919f0357d8f4c" name = "github.com/uber/jaeger-lib" packages = ["metrics"] + pruneopts = "NUT" revision = "ed3a127ec5fef7ae9ea95b01b542c47fbd999ce5" version = "v1.5.0" [[projects]] + digest = "1:4c7d12ad3ef47bb03892a52e2609dc9a9cff93136ca9c7d31c00b79fcbc23c7b" name = "github.com/yudai/gojsondiff" packages = [ ".", - "formatter" + "formatter", ] + pruneopts = "NUT" revision = "7b1b7adf999dab73a6eb02669c3d82dbb27a3dd6" version = "1.0.0" [[projects]] branch = "master" + digest = "1:e50cbf8eba568d59b71e08c22c2a77809ed4646ae06ef4abb32b3d3d3fdb1a77" name = "github.com/yudai/golcs" packages = ["."] + pruneopts = "NUT" revision = "ecda9a501e8220fae3b4b600c3db4b0ba22cfc68" [[projects]] branch = "master" + digest = "1:758f363e0dff33cf00b234be2efb12f919d79b42d5ae3909ff9eb69ef2c3cca5" name = "golang.org/x/crypto" packages = [ "ed25519", "ed25519/internal/edwards25519", "md4", - "pbkdf2" + "pbkdf2", ] + pruneopts = "NUT" revision = "1a580b3eff7814fc9b40602fd35256c63b50f491" [[projects]] branch = "master" + digest = "1:0b3fee9c4472022a0982ee0d81e08b3cc3e595f50befd7a4b358b48540d9d8c5" name = "golang.org/x/net" packages = [ "context", @@ -527,35 +654,43 @@ "http2/hpack", "idna", "internal/timeseries", - "trace" + "trace", ] + pruneopts = "NUT" revision = "2491c5de3490fced2f6cff376127c667efeed857" [[projects]] branch = "master" + digest = "1:46bd4e66bfce5e77f08fc2e8dcacc3676e679241ce83d9c150ff0397d686dd44" name = "golang.org/x/oauth2" packages = [ ".", "google", "internal", "jws", - "jwt" + "jwt", ] + pruneopts = "NUT" revision = "cdc340f7c179dbbfa4afd43b7614e8fcadde4269" [[projects]] branch = "master" + digest = "1:39ebcc2b11457b703ae9ee2e8cca0f68df21969c6102cb3b705f76cca0ea0239" name = "golang.org/x/sync" packages = ["errgroup"] + pruneopts = "NUT" revision = "1d60e4601c6fd243af51cc01ddf169918a5407ca" [[projects]] branch = "master" + digest = "1:ec21c5bf0572488865b93e30ffd9132afbf85bec0b20c2d6cbcf349cf2031ed5" name = "golang.org/x/sys" packages = ["unix"] + pruneopts = "NUT" revision = "7c87d13f8e835d2fb3a70a2912c811ed0c1d241b" [[projects]] + digest = "1:e7071ed636b5422cc51c0e3a6cebc229d6c9fffc528814b519a980641422d619" name = "golang.org/x/text" packages = [ "collate", @@ -571,12 +706,14 @@ "unicode/bidi", "unicode/cldr", "unicode/norm", - "unicode/rangetable" + "unicode/rangetable", ] + pruneopts = "NUT" revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" version = "v0.3.0" [[projects]] + digest = "1:dbd5568923513ee74aa626d027e2a8a352cf8f35df41d19f4e34491d1858c38b" name = "google.golang.org/appengine" packages = [ ".", @@ -589,18 +726,22 @@ "internal/modules", "internal/remote_api", "internal/urlfetch", - "urlfetch" + "urlfetch", ] + pruneopts = "NUT" revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a" version = "v1.0.0" [[projects]] branch = "master" + digest = "1:3c24554c312721e98fa6b76403e7100cf974eb46b1255ea7fc6471db9a9ce498" name = "google.golang.org/genproto" packages = ["googleapis/rpc/status"] + pruneopts = "NUT" revision = "7bb2a897381c9c5ab2aeb8614f758d7766af68ff" [[projects]] + digest = "1:840b77b6eb539b830bb760b6e30b688ed2ff484bd83466fce2395835ed9367fe" name = "google.golang.org/grpc" packages = [ ".", @@ -627,78 +768,177 @@ "stats", "status", "tap", - "transport" + "transport", ] + pruneopts = "NUT" revision = "1e2570b1b19ade82d8dbb31bba4e65e9f9ef5b34" version = "v1.11.1" [[projects]] branch = "v3" + digest = "1:1244a9b3856f70d5ffb74bbfd780fc9d47f93f2049fa265c6fb602878f507bf8" name = "gopkg.in/alexcesaro/quotedprintable.v3" packages = ["."] + pruneopts = "NUT" revision = "2caba252f4dc53eaf6b553000885530023f54623" [[projects]] + digest = "1:aea6e9483c167cc6fdf1274c442558c5dda8fd3373372be04d98c79100868da1" name = "gopkg.in/asn1-ber.v1" packages = ["."] + pruneopts = "NUT" revision = "379148ca0225df7a432012b8df0355c2a2063ac0" version = "v1.2" [[projects]] + digest = "1:24bfc2e8bf971485cb5ba0f0e5b08a1b806cca5828134df76b32d1ea50f2ab49" name = "gopkg.in/bufio.v1" packages = ["."] + pruneopts = "NUT" revision = "567b2bfa514e796916c4747494d6ff5132a1dfce" version = "v1" [[projects]] + digest = "1:e05711632e1515319b014e8fe4cbe1d30ab024c473403f60cf0fdeb4c586a474" name = "gopkg.in/ini.v1" packages = ["."] + pruneopts = "NUT" revision = "6529cf7c58879c08d927016dde4477f18a0634cb" version = "v1.36.0" [[projects]] + digest = "1:c847b7fea4c7e6db5281a37dffc4620cb78c1227403a79e5aa290db517657ac1" name = "gopkg.in/ldap.v3" packages = ["."] + pruneopts = "NUT" revision = "5c2c0f997205c29de14cb6c35996370c2c5dfab1" version = "v3" [[projects]] + digest = "1:3b0cf3a465fd07f76e5fc1a9d0783c662dac0de9fc73d713ebe162768fd87b5f" name = "gopkg.in/macaron.v1" packages = ["."] + pruneopts = "NUT" revision = "c1be95e6d21e769e44e1ec33cec9da5837861c10" version = "v1.3.1" [[projects]] branch = "v2" + digest = "1:d52332f9e9f2c6343652e13aa3fd40cfd03353520c9a48d90f21215d3012d50f" name = "gopkg.in/mail.v2" packages = ["."] + pruneopts = "NUT" revision = "5bc5c8bb07bd8d2803831fbaf8cbd630fcde2c68" [[projects]] + digest = "1:00126f697efdcab42f07c89ac8bf0095fb2328aef6464e070055154088cea859" name = "gopkg.in/redis.v2" packages = ["."] + pruneopts = "NUT" revision = "e6179049628164864e6e84e973cfb56335748dea" version = "v2.3.2" [[projects]] + digest = "1:a50fabe7a46692dc7c656310add3d517abe7914df02afd151ef84da884605dc8" name = "gopkg.in/square/go-jose.v2" packages = [ ".", "cipher", - "json" + "json", ] + pruneopts = "NUT" revision = "ef984e69dd356202fd4e4910d4d9c24468bdf0b8" version = "v2.1.9" [[projects]] branch = "v2" + digest = "1:7c95b35057a0ff2e19f707173cc1a947fa43a6eb5c4d300d196ece0334046082" name = "gopkg.in/yaml.v2" packages = ["."] + pruneopts = "NUT" revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "88f0eb826b9c154ba46ea3bb64767707d86db75449ec75199eb2b8cf2b337fd4" + input-imports = [ + "github.com/BurntSushi/toml", + "github.com/Unknwon/com", + "github.com/VividCortex/mysqlerr", + "github.com/aws/aws-sdk-go/aws", + "github.com/aws/aws-sdk-go/aws/awserr", + "github.com/aws/aws-sdk-go/aws/awsutil", + "github.com/aws/aws-sdk-go/aws/credentials", + "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds", + "github.com/aws/aws-sdk-go/aws/credentials/endpointcreds", + "github.com/aws/aws-sdk-go/aws/defaults", + "github.com/aws/aws-sdk-go/aws/ec2metadata", + "github.com/aws/aws-sdk-go/aws/endpoints", + "github.com/aws/aws-sdk-go/aws/request", + "github.com/aws/aws-sdk-go/aws/session", + "github.com/aws/aws-sdk-go/service/cloudwatch", + "github.com/aws/aws-sdk-go/service/ec2", + "github.com/aws/aws-sdk-go/service/ec2/ec2iface", + "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi", + "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi/resourcegroupstaggingapiiface", + "github.com/aws/aws-sdk-go/service/s3", + "github.com/aws/aws-sdk-go/service/sts", + "github.com/benbjohnson/clock", + "github.com/bmizerany/assert", + "github.com/codegangsta/cli", + "github.com/davecgh/go-spew/spew", + "github.com/denisenkom/go-mssqldb", + "github.com/facebookgo/inject", + "github.com/fatih/color", + "github.com/go-macaron/binding", + "github.com/go-macaron/gzip", + "github.com/go-macaron/session", + "github.com/go-macaron/session/memcache", + "github.com/go-macaron/session/postgres", + "github.com/go-macaron/session/redis", + "github.com/go-sql-driver/mysql", + "github.com/go-stack/stack", + "github.com/go-xorm/core", + "github.com/go-xorm/xorm", + "github.com/gorilla/websocket", + "github.com/gosimple/slug", + "github.com/grafana/grafana-plugin-model/go/datasource", + "github.com/grafana/grafana-plugin-model/go/renderer", + "github.com/hashicorp/go-hclog", + "github.com/hashicorp/go-plugin", + "github.com/hashicorp/go-version", + "github.com/inconshreveable/log15", + "github.com/lib/pq", + "github.com/mattn/go-isatty", + "github.com/mattn/go-sqlite3", + "github.com/opentracing/opentracing-go", + "github.com/opentracing/opentracing-go/ext", + "github.com/opentracing/opentracing-go/log", + "github.com/patrickmn/go-cache", + "github.com/pkg/errors", + "github.com/prometheus/client_golang/api", + "github.com/prometheus/client_golang/api/prometheus/v1", + "github.com/prometheus/client_golang/prometheus", + "github.com/prometheus/client_golang/prometheus/promhttp", + "github.com/prometheus/client_model/go", + "github.com/prometheus/common/expfmt", + "github.com/prometheus/common/model", + "github.com/smartystreets/goconvey/convey", + "github.com/teris-io/shortid", + "github.com/uber/jaeger-client-go/config", + "github.com/yudai/gojsondiff", + "github.com/yudai/gojsondiff/formatter", + "golang.org/x/net/context/ctxhttp", + "golang.org/x/oauth2", + "golang.org/x/oauth2/google", + "golang.org/x/oauth2/jwt", + "golang.org/x/sync/errgroup", + "gopkg.in/ini.v1", + "gopkg.in/ldap.v3", + "gopkg.in/macaron.v1", + "gopkg.in/mail.v2", + "gopkg.in/square/go-jose.v2", + "gopkg.in/yaml.v2", + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 83e6890b0f4..d1bc0f55bae 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -81,11 +81,15 @@ ignored = [ [[constraint]] name = "github.com/go-xorm/core" - version = "=0.5.7" + version = "=0.6.2" + +[[override]] + name = "github.com/go-xorm/builder" + version = "=0.3.4" [[constraint]] name = "github.com/go-xorm/xorm" - version = "=0.6.4" + version = "=0.7.1" [[constraint]] name = "github.com/gorilla/websocket" diff --git a/README.md b/README.md index 550e7facfa8..8c84bfd0e87 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,6 @@ Grafana is an open source, feature rich metrics dashboard and graph editor for Graphite, Elasticsearch, OpenTSDB, Prometheus and InfluxDB. -![](https://www.grafanacon.org/2019/images/grafanacon_la_nav-logo.png) - -Join us Feb 25-26 in Los Angeles, California for GrafanaCon - a two-day event with talks focused on Grafana and the surrounding open source monitoring ecosystem. Get deep dives into Loki, the Explore workflow and all of the new features of Grafana 6, plus participate in hands on workshops to help you get the most out of your data. - -Time is running out - grab your ticket now! http://grafanacon.org - diff --git a/conf/defaults.ini b/conf/defaults.ini index a87aba10adb..044d8e59a7a 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -231,6 +231,7 @@ verify_email_enabled = false # Background text for the user field on the login page login_hint = email or username +password_hint = password # Default UI theme ("dark" or "light") default_theme = dark @@ -243,6 +244,9 @@ external_manage_info = # Viewers can edit/inspect dashboard settings in the browser. But not save the dashboard. viewers_can_edit = false +# Editors can administrate dashboard, folders and teams they create +editors_can_own = false + [auth] # Login cookie name login_cookie_name = grafana_session diff --git a/conf/sample.ini b/conf/sample.ini index dbbb3593f0f..dc1e4fbde8e 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -211,6 +211,7 @@ log_queries = # Background text for the user field on the login page ;login_hint = email or username +;password_hint = password # Default UI theme ("dark" or "light") ;default_theme = dark @@ -223,6 +224,9 @@ log_queries = # Viewers can edit/inspect dashboard settings in the browser. But not save the dashboard. ;viewers_can_edit = false +# Editors can administrate dashboard, folders and teams they create +;editors_can_own = false + [auth] # Login cookie name ;login_cookie_name = grafana_session diff --git a/devenv/docker/blocks/influxdb/docker-compose.yaml b/devenv/docker/blocks/influxdb/docker-compose.yaml index e3ca81ced22..f34a7ef4978 100644 --- a/devenv/docker/blocks/influxdb/docker-compose.yaml +++ b/devenv/docker/blocks/influxdb/docker-compose.yaml @@ -1,5 +1,3 @@ -version: '2' -services: influxdb: image: influxdb:latest container_name: influxdb diff --git a/devenv/docker/blocks/prometheus2/Dockerfile b/devenv/docker/blocks/prometheus2/Dockerfile index 03edf4c9ee2..c9a2327bd4a 100644 --- a/devenv/docker/blocks/prometheus2/Dockerfile +++ b/devenv/docker/blocks/prometheus2/Dockerfile @@ -1,3 +1,3 @@ -FROM prom/prometheus:v2.2.0 +FROM prom/prometheus:v2.7.2 ADD prometheus.yml /etc/prometheus/ ADD alert.rules /etc/prometheus/ diff --git a/docs/sources/alerting/notifications.md b/docs/sources/alerting/notifications.md index 5327039341f..8aafdb0362c 100644 --- a/docs/sources/alerting/notifications.md +++ b/docs/sources/alerting/notifications.md @@ -83,7 +83,11 @@ or a bot integration via Slack Apps. Follow Slack's guide to set up a bot integr Setting | Description ---------- | ----------- -Recipient | allows you to override the Slack recipient. +Url | Slack incoming webhook url. +Username | Set the username for the bot's message. +Recipient | Allows you to override the Slack recipient. +Icon emoji | Provide an emoji to use as the icon for the bot's message. Ex :smile: +Icon URL | Provide a url to an image to use as the icon for the bot's message. Mention | make it possible to include a mention in the Slack notification sent by Grafana. Ex @here or @channel Token | If provided, Grafana will upload the generated image via Slack's file.upload API method, not the external image destination. diff --git a/docs/sources/alerting/rules.md b/docs/sources/alerting/rules.md index 036ff97056d..510f466c3eb 100644 --- a/docs/sources/alerting/rules.md +++ b/docs/sources/alerting/rules.md @@ -27,9 +27,10 @@ and the conditions that need to be met for the alert to change state and trigger ## Execution The alert rules are evaluated in the Grafana backend in a scheduler and query execution engine that is part -of core Grafana. Only some data sources are supported right now. They include `Graphite`, `Prometheus`, `Elasticsearch`, `InfluxDB`, `OpenTSDB`, `MySQL`, `Postgres` and `Cloudwatch`. +of core Grafana. Only some data sources are supported right now. They include `Graphite`, `Prometheus`, `InfluxDB`, `Elasticsearch`, +`Stackdriver`, `Cloudwatch`, `Azure Monitor`, `MySQL`, `PostgreSQL`, `MSSQL` and `OpenTSDB`. -> Alerting support for Elasticsearch is only available in Grafana v5.2 and above. +> Alerting support for Azure Monitor is only available in Grafana v6.0 and above. ### Clustering @@ -52,9 +53,9 @@ Here you can specify the name of the alert rule and how often the scheduler shou > 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. +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. +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" >}} diff --git a/docs/sources/auth/generic-oauth.md b/docs/sources/auth/generic-oauth.md index 1a83432b8f7..510776750f3 100644 --- a/docs/sources/auth/generic-oauth.md +++ b/docs/sources/auth/generic-oauth.md @@ -217,10 +217,10 @@ Some OAuth2 providers might not support `client_id` and `client_secret` passed v results in `invalid_client` error. To allow Grafana to authenticate via these type of providers, the client identifiers must be send via POST body, which can be enabled via the following settings: - ```bash - [auth.generic_oauth] - send_client_credentials_via_post = true - ``` +```bash +[auth.generic_oauth] +send_client_credentials_via_post = true +```
diff --git a/docs/sources/features/datasources/azuremonitor.md b/docs/sources/features/datasources/azuremonitor.md new file mode 100644 index 00000000000..d72f5273925 --- /dev/null +++ b/docs/sources/features/datasources/azuremonitor.md @@ -0,0 +1,271 @@ ++++ +title = "Using Azure Monitor in Grafana" +description = "Guide for using Azure Monitor in Grafana" +keywords = ["grafana", "microsoft", "azure", "monitor", "application", "insights", "log", "analytics", "guide"] +type = "docs" +aliases = ["/datasources/azuremonitor"] +[menu.docs] +name = "Azure Monitor" +parent = "datasources" +weight = 5 ++++ + +# Using Azure Monitor in Grafana + +> Officially released in Grafana v6.0.0 + +As of Grafana 6.0, the Azure Monitor plugin has been moved into Grafana so it now ships with built-in support for Azure Monitor. + +The Azure Monitor Datasource supports multiple services in the Azure cloud: + +- **[Azure Monitor]({{< relref "#querying-the-azure-monitor-service" >}})** is the platform service that provides a single source for monitoring Azure resources. +- **[Application Insights]({{< relref "#querying-the-application-insights-service" >}})** is an extensible Application Performance Management (APM) service for web developers on multiple platforms and can be used to monitor your live web application - it will automatically detect performance anomalies. +- **[Azure Log Analytics]({{< relref "#querying-the-azure-log-analytics-service" >}})** (or Azure Logs) gives you access to log data collected by Azure Monitor. +- **[Application Insights Analytics]({{< relref "#writing-analytics-queries-for-the-application-insights-service" >}})** allows you to query [Application Insights data](https://docs.microsoft.com/en-us/azure/azure-monitor/app/analytics) using the same query language used for Azure Log Analytics. + +## Adding the data source to Grafana + +The datasource can access metrics from four different services. You can configure access to the services that you use. It is also possible to use the same credentials for multiple services if that is how you have set it up in Azure AD. + +- [Guide to setting up an Azure Active Directory Application for Azure Monitor.](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal) +- [Guide to setting up an Azure Active Directory Application for Azure Log Analytics.](https://dev.loganalytics.io/documentation/Authorization/AAD-Setup) +- [Quickstart Guide for Application Insights.](https://dev.applicationinsights.io/quickstart/) + +1. Accessed from the Grafana main menu, newly installed data sources can be added immediately within the Data Sources section. Next, click the "Add data source" button in the upper right. The data source will be available for selection in the Type select box. + +2. Select Azure Monitor from the Type dropdown:
+![Data Source Type](https://raw.githubusercontent.com/grafana/azure-monitor-datasource/master/src/img/config_1_select_type.png) +3. In the name field, fill in a name for the data source. It can be anything. Some suggestions are Azure Monitor or App Insights. + +4. If you are using Azure Monitor, then you need 4 pieces of information from the Azure portal (see link above for detailed instructions): + - **Tenant Id** (Azure Active Directory -> Properties -> Directory ID) + - **Subscription Id** (Subscriptions -> Choose subscription -> Overview -> Subscription ID) + - **Client Id** (Azure Active Directory -> App Registrations -> Choose your app -> Application ID) + - **Client Secret** ( Azure Active Directory -> App Registrations -> Choose your app -> Keys) + +5. Paste these four items into the fields in the Azure Monitor API Details section:
+![Azure Monitor API Details](https://raw.githubusercontent.com/grafana/azure-monitor-datasource/master/src/img/config_2_azure_monitor_api_details.png) + +6. If you are also using the Azure Log Analytics service, then you need to specify these two config values (or you can reuse the Client Id and Secret from the previous step). + - Client Id (Azure Active Directory -> App Registrations -> Choose your app -> Application ID) + - Client Secret ( Azure Active Directory -> App Registrations -> Choose your app -> Keys -> Create a key -> Use client secret) + +7. If you are are using Application Insights, then you need two pieces of information from the Azure Portal (see link above for detailed instructions): + - Application ID + - API Key + +8. Paste these two items into the appropriate fields in the Application Insights API Details section:
+![Application Insights API Details](https://raw.githubusercontent.com/grafana/azure-monitor-datasource/master/src/img/config_3_app_insights_api_details.png) + +9. Test that the configuration details are correct by clicking on the "Save & Test" button:
+![Azure Monitor API Details](https://raw.githubusercontent.com/grafana/azure-monitor-datasource/master/src/img/config_4_save_and_test.png) + +Alternatively on step 4 if creating a new Azure Active Directory App, use the [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/?view=azure-cli-latest): + +```bash +az ad sp create-for-rbac -n "http://localhost:3000" +``` + +## Choose a Service + +In the query editor for a panel, after choosing your Azure Monitor datasource, the first option is to choose a service. There are three options here: Azure Monitor, Application Insights and Azure Log Analytics. The query editor will change depending on which one you pick. Azure Monitor is the default. + +## Querying the Azure Monitor Service + +The Azure Monitor service provides metrics for all the Azure services that you have running. It helps you understand how your applications on Azure are performing and to proactively find issues affecting your applications. + +Examples of metrics that you can get from the service are: + +- Microsoft.Compute/virtualMachines - Percentage CPU +- Microsoft.Network/networkInterfaces - Bytes sent +- Microsoft.Storage/storageAccounts - Used Capacity + +{{< docs-imagebox img="/img/docs/v60/azuremonitor-service-query-editor.png" class="docs-image--no-shadow" caption="Azure Monitor Query Editor" >}} + +### Formatting Legend Keys with Aliases for the Azure Monitor Service + +The default legend formatting for the Azure Monitor API is: + +`resourceName{dimensionValue=dimensionName}.metricName` + +These can be quite long but this formatting can be changed using aliases. In the Legend Format field, the aliases which are defined below can be combined any way you want. + +Azure Monitor Examples: + +- `dimension: {{dimensionvalue}}` +- `{{resourcegroup}} - {{resourcename}}` + +### Alias Patterns for Azure Monitor + +- `{{resourcegroup}}` = replaced with the value of the Resource Group +- `{{namespace}}` = replaced with the value of the Namespace (e.g. Microsoft.Compute/virtualMachines) +- `{{resourcename}}` = replaced with the value of the Resource Name +- `{{metric}}` = replaced with metric name (e.g. Percentage CPU) +- `{{dimensionname}}` = replaced with dimension key/label (e.g. blobtype) +- `{{dimensionvalue}}` = replaced with dimension value (e.g. BlockBlob) + +### Templating with Variables for the Azure Monitor Service + +Instead of hard-coding things like server, application and sensor name in you metric queries you can use variables in their place. Variables are shown as dropdown select boxes at the top of the dashboard. These dropdowns makes it easy to change the data being displayed in your dashboard. + +Note that the Azure Monitor service does not support multiple values yet. If you want to visualize multiple time series (for example, metrics for server1 and server2) then you have to add multiple queries to able to view them on the same graph or in the same table. + +The Azure Monitor Datasource Plugin provides the following queries you can specify in the `Query` field in the Variable edit view. They allow you to fill a variable's options list. + +| Name | Description | +| -------------------------------------------------------- | -------------------------------------------------------------- | +| *ResourceGroups()* | Returns a list of resource groups. | +| *Namespaces(aResourceGroup)* | Returns a list of namespaces for the specified resource group. | +| *ResourceNames(aResourceGroup, aNamespace)* | Returns a list of resource names. | +| *MetricNames(aResourceGroup, aNamespace, aResourceName)* | Returns a list of metric names. | + +Examples: + +- Resource Groups query: `ResourceGroups()` +- Passing in metric name variable: `Namespaces(cosmo)` +- Chaining template variables: `ResourceNames($rg, $ns)` +- Do not quote parameters: `MetricNames(hg, Microsoft.Network/publicIPAddresses, grafanaIP)` + +{{< docs-imagebox img="/img/docs/v60/azuremonitor-service-variables.png" class="docs-image--no-shadow" caption="Nested Azure Monitor Template Variables" >}} + +Checkout the [Templating]({{< relref "reference/templating.md" >}}) documentation for an introduction to the templating feature and the different +types of template variables. + +### Azure Monitor Metrics Whitelist + +Not all metrics returned by the Azure Monitor API have values. The Grafana datasource has a whitelist to only return metric names if it is possible they might have values. This whitelist is updated regularly as new services and metrics are added to the Azure cloud. You can find the current whitelist [here](https://github.com/grafana/grafana/blob/master/public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/supported_namespaces.ts). + +### Azure Monitor Alerting + +Grafana alerting is supported for the Azure Monitor service. This is not Azure Alerts support. Read more about how alerting in Grafana works [here]({{< relref "alerting/rules.md" >}}). + +{{< docs-imagebox img="/img/docs/v60/azuremonitor-alerting.png" class="docs-image--no-shadow" caption="Azure Monitor Alerting" >}} + +## Querying the Application Insights Service + +{{< docs-imagebox img="/img/docs/v60/appinsights-service-query-editor.png" class="docs-image--no-shadow" caption="Application Insights Query Editor" >}} + +### Formatting Legend Keys with Aliases for the Application Insights Service + +The default legend formatting is: + +`metric/name{group/by="groupbyvalue"}` + +In the Legend Format field, the aliases which are defined below can be combined any way you want. + +Application Insights Examples: + +- `server: {{groupbyvalue}}` +- `city: {{groupbyvalue}}` +- `{{groupbyname}}: {{groupbyvalue}}` + +### Alias Patterns for Application Insights + +- `{{groupbyvalue}}` = replaced with the value of the group by +- `{{groupbyname}}` = replaced with the name/label of the group by +- `{{metric}}` = replaced with metric name (e.g. requests/count) + +### Filter Expressions for Application Insights + +The filter field takes an OData filter expression. + +Examples: + +- `client/city eq 'Boydton'` +- `client/city ne 'Boydton'` +- `client/city ne 'Boydton' and client/city ne 'Dublin'` +- `client/city eq 'Boydton' or client/city eq 'Dublin'` + +### Templating with Variables for Application Insights + +Use the one of the following queries in the `Query` field in the Variable edit view. + +Checkout the [Templating]({{< relref "reference/templating.md" >}}) documentation for an introduction to the templating feature and the different +types of template variables. + +| Name | Description | +| ---------------------------------- | ---------------------------------------------------------- | +| *AppInsightsMetricNames()* | Returns a list of metric names. | +| *AppInsightsGroupBys(aMetricName)* | Returns a list of group bys for the specified metric name. | + +Examples: + +- Metric Names query: `AppInsightsMetricNames()` +- Passing in metric name variable: `AppInsightsGroupBys(requests/count)` +- Chaining template variables: `AppInsightsGroupBys($metricnames)` + +{{< docs-imagebox img="/img/docs/v60/appinsights-service-variables.png" class="docs-image--no-shadow" caption="Nested Application Insights Template Variables" >}} + +### Application Insights Alerting + +Not implemented yet. + +## Querying the Azure Log Analytics Service + +Queries are written in the new [Azure Log Analytics (or KustoDB) Query Language](https://docs.loganalytics.io/index). A Log Analytics Query can be formatted as Time Series data or as Table data. + +Time Series queries are for the Graph Panel (and other panels like the Single Stat panel) and must contain a datetime column, a metric name column and a value column. Here is an example query that returns the aggregated count grouped by the Category column and grouped by hour: + +``` +AzureActivity +| where $__timeFilter(TimeGenerated) +| summarize count() by Category, bin(TimeGenerated, 1h) +| order by TimeGenerated asc +``` + +Table queries are mainly used in the Table panel and row a list of columns and rows. This example query returns rows with the 6 specified columns: + +``` +AzureActivity +| where $__timeFilter() +| project TimeGenerated, ResourceGroup, Category, OperationName, ActivityStatus, Caller +| order by TimeGenerated desc +``` + +{{< docs-imagebox img="/img/docs/v60/azureloganalytics-service-query-editor.png" class="docs-image--no-shadow" caption="Azure Log Analytics Query Editor" >}} + +### Azure Log Analytics Macros + +To make writing queries easier there are several Grafana macros that can be used in the where clause of a query: + +- `$__timeFilter()` - Expands to + `TimeGenerated ≥ datetime(2018-06-05T18:09:58.907Z) and` + `TimeGenerated ≤ datetime(2018-06-05T20:09:58.907Z)` where the from and to datetimes are from the Grafana time picker. + +- `$__timeFilter(datetimeColumn)` - Expands to + `datetimeColumn ≥ datetime(2018-06-05T18:09:58.907Z) and` + `datetimeColumn ≤ datetime(2018-06-05T20:09:58.907Z)` where the from and to datetimes are from the Grafana time picker. + +- `$__escapeMulti($myVar)` - is to be used with multi-value template variables that contains illegal characters. If $myVar has the value `'\\grafana-vm\Network(eth0)\Total','\\hello!'`, it expands to: `@'\\grafana-vm\Network(eth0)\Total', @'\\hello!'`. If using single value variables there no need for this macro, simply escape the variable inline instead - `@'\$myVar'` + +- `$__contains(colName, $myVar)` - is to be used with multi-value template variables. If $myVar has the value `'value1','value2'`, it expands to: `colName in ('value1','value2')`. + + If using the `All` option, then check the `Include All Option` checkbox and in the `Custom all value` field type in the following value: `all`. If $myVar has value `all` then the macro will instead expand to `1 == 1`. For template variables with a lot of options, this will increase the query performance by not building a large where..in clause. + +### Azure Log Analytics Builtin Variables + +There are also some Grafana variables that can be used in Azure Log Analytics queries: + +- `$__from` - Returns the From datetime from the Grafana picker. Example: `datetime(2018-06-05T18:09:58.907Z)`. +- `$__to` - Returns the From datetime from the Grafana picker. Example: `datetime(2018-06-05T20:09:58.907Z)`. +- `$__interval` - Grafana calculates the minimum time grain that can be used to group by time in queries. More details on how it works [here]({{< relref "reference/templating.md#interval-variables" >}}). It returns a time grain like `5m` or `1h` that can be used in the bin function. E.g. `summarize count() by bin(TimeGenerated, $__interval)` + +### Azure Log Analytics Alerting + +Not implemented yet. + +### Writing Analytics Queries For the Application Insights Service + +If you change the service type to "Application Insights", the menu icon to the right adds another option, "Toggle Edit Mode". Once clicked, the query edit mode changes to give you a full text area in which to write log analytics queries. (This is identical to how the InfluxDB datasource lets you write raw queries.) + +Once a query is written, the column names are automatically parsed out of the response data. You can then select them in the "X-axis", "Y-axis", and "Split On" dropdown menus, or just type them out. + +There are some important caveats to remember: + +- You'll want to order your y-axis in the query, eg. `order by timestamp asc`. The graph may come out looking bizarre otherwise. It's better to have Microsoft sort it on their side where it's faster, than to implement this in the plugin. + +- If you copy a log analytics query, typically they'll end with a render instruction, like `render barchart`. This is unnecessary, but harmless. + +- Currently, four default dashboard variables are supported: `$__timeFilter()`, `$__from`, `$__to`, and `$__interval`. If you're searching in timestamped data, replace the beginning of your where clause to `where $__timeFilter()`. Dashboard changes by time region are handled as you'd expect, as long as you leave the name of the `timestamp` column alone. Likewise, `$__interval` will automatically change based on the dashboard's time region _and_ the width of the chart being displayed. Use it in bins, so `bin(timestamp,$__interval)` changes into something like `bin(timestamp,1s)`. Use `$__from` and `$__to` if you just want the formatted dates to be inserted. + +- Templated dashboard variables are not yet supported! They will come in a future version. diff --git a/docs/sources/features/datasources/cloudwatch.md b/docs/sources/features/datasources/cloudwatch.md index 6ca10b2f8e8..ed88fcfc509 100644 --- a/docs/sources/features/datasources/cloudwatch.md +++ b/docs/sources/features/datasources/cloudwatch.md @@ -8,7 +8,7 @@ aliases = ["/datasources/cloudwatch"] name = "AWS Cloudwatch" identifier = "cloudwatch" parent = "datasources" -weight = 10 +weight = 5 +++ # Using AWS CloudWatch in Grafana @@ -77,9 +77,9 @@ Here is a minimal policy example: }, { "Sid": "AllowReadingResourcesForTags", - "Effect" : "Allow", - "Action" : "tag:GetResources", - "Resource" : "*" + "Effect" : "Allow", + "Action" : "tag:GetResources", + "Resource" : "*" } ] } diff --git a/docs/sources/features/datasources/index.md b/docs/sources/features/datasources/index.md index 42ce3f84819..9046c986f98 100644 --- a/docs/sources/features/datasources/index.md +++ b/docs/sources/features/datasources/index.md @@ -22,15 +22,18 @@ The query language and capabilities of each Data Source are obviously very diffe The following datasources are officially supported: * [Graphite]({{< relref "graphite.md" >}}) -* [Elasticsearch]({{< relref "elasticsearch.md" >}}) -* [CloudWatch]({{< relref "cloudwatch.md" >}}) -* [InfluxDB]({{< relref "influxdb.md" >}}) -* [OpenTSDB]({{< relref "opentsdb.md" >}}) * [Prometheus]({{< relref "prometheus.md" >}}) +* [InfluxDB]({{< relref "influxdb.md" >}}) +* [Elasticsearch]({{< relref "elasticsearch.md" >}}) +* [Google Stackdriver]({{< relref "stackdriver.md" >}}) +* [AWS CloudWatch]({{< relref "cloudwatch.md" >}}) +* [Azure Monitor]({{< relref "azuremonitor.md" >}}) * [Loki]({{< relref "loki.md" >}}) * [MySQL]({{< relref "mysql.md" >}}) -* [Postgres]({{< relref "postgres.md" >}}) +* [PostgreSQL]({{< relref "postgres.md" >}}) * [Microsoft SQL Server (MSSQL)]({{< relref "mssql.md" >}}) +* [OpenTSDB]({{< relref "opentsdb.md" >}}) +* [Testdata]({{< relref "testdata.md" >}}) ## Data source plugins diff --git a/docs/sources/features/datasources/influxdb.md b/docs/sources/features/datasources/influxdb.md index bc96190e9b1..94ae448f16d 100644 --- a/docs/sources/features/datasources/influxdb.md +++ b/docs/sources/features/datasources/influxdb.md @@ -7,7 +7,7 @@ aliases = ["/datasources/influxdb"] [menu.docs] name = "InfluxDB" parent = "datasources" -weight = 3 +weight = 2 +++ # Using InfluxDB in Grafana diff --git a/docs/sources/features/datasources/loki.md b/docs/sources/features/datasources/loki.md index d43ef982b4b..8dc4eaeaf4c 100644 --- a/docs/sources/features/datasources/loki.md +++ b/docs/sources/features/datasources/loki.md @@ -7,7 +7,7 @@ aliases = ["/datasources/loki"] [menu.docs] name = "Loki" parent = "datasources" -weight = 11 +weight = 6 +++ # Using Loki in Grafana diff --git a/docs/sources/features/datasources/opentsdb.md b/docs/sources/features/datasources/opentsdb.md index d2cd0b1dc0e..866619d380c 100644 --- a/docs/sources/features/datasources/opentsdb.md +++ b/docs/sources/features/datasources/opentsdb.md @@ -7,7 +7,7 @@ aliases = ["/datasources/opentsdb", "docs/features/opentsdb"] [menu.docs] name = "OpenTSDB" parent = "datasources" -weight = 5 +weight = 19 +++ # Using OpenTSDB in Grafana diff --git a/docs/sources/features/datasources/prometheus.md b/docs/sources/features/datasources/prometheus.md index 611a3b4d9e2..0a5c087803f 100644 --- a/docs/sources/features/datasources/prometheus.md +++ b/docs/sources/features/datasources/prometheus.md @@ -7,7 +7,7 @@ aliases = ["/datasources/prometheus"] [menu.docs] name = "Prometheus" parent = "datasources" -weight = 2 +weight = 1 +++ # Using Prometheus in Grafana @@ -68,6 +68,7 @@ provides the following functions you can use in the `Query` input field. Name | Description ---- | -------- +*label_names()* | Returns a list of label names. *label_values(label)* | Returns a list of label values for the `label` in every metric. *label_values(metric, label)* | Returns a list of label values for the `label` in the specified metric. *metrics(metric)* | Returns a list of metrics matching the specified `metric` regex. diff --git a/docs/sources/features/datasources/stackdriver.md b/docs/sources/features/datasources/stackdriver.md index d1cc1088276..e0f0e576b8b 100644 --- a/docs/sources/features/datasources/stackdriver.md +++ b/docs/sources/features/datasources/stackdriver.md @@ -5,9 +5,9 @@ keywords = ["grafana", "stackdriver", "google", "guide"] type = "docs" aliases = ["/datasources/stackdriver"] [menu.docs] -name = "Stackdriver" +name = "Google Stackdriver" parent = "datasources" -weight = 11 +weight = 4 +++ # Using Google Stackdriver in Grafana @@ -66,7 +66,7 @@ Click on the links above and click the `Enable` button: 4. Some new fields will appear. Fill in a name for the service account in the `Service account name` field and then choose the `Monitoring Viewer` role from the `Role` dropdown: {{< docs-imagebox img="/img/docs/v53/stackdriver_service_account_choose_role.png" class="docs-image--no-shadow" caption="Choose role" >}} - + 5. Click the Create button. A JSON key file will be created and downloaded to your computer. Store this file in a secure place as it allows access to your Stackdriver data. 6. Upload it to Grafana on the datasource Configuration page. You can either upload the file or paste in the contents of the file. @@ -156,7 +156,7 @@ Example Alias By: `{{metric.type}} - {{metric.labels.instance_name}}` Example Result: `compute.googleapis.com/instance/cpu/usage_time - server1-prod` -It is also possible to resolve the name of the Monitored Resource Type. +It is also possible to resolve the name of the Monitored Resource Type. | Alias Pattern Format | Description | Example Result | | -------------------- | ----------------------------------------------- | -------------- | diff --git a/docs/sources/guides/whats-new-in-v6-0.md b/docs/sources/guides/whats-new-in-v6-0.md index 2ba7e86b385..1506cee6dbd 100644 --- a/docs/sources/guides/whats-new-in-v6-0.md +++ b/docs/sources/guides/whats-new-in-v6-0.md @@ -12,9 +12,7 @@ weight = -11 # What's New in Grafana v6.0 -This update to Grafana introduces a new way of exploring your data, support for log data and tons of other features. - -Grafana v6.0 is out in **Beta**, [Download Now!](https://grafana.com/grafana/download/beta) +This update to Grafana introduces a new way of exploring your data, support for log data, and tons of other features. The main highlights are: @@ -27,24 +25,24 @@ The main highlights are: - [Azure Monitor]({{< relref "#azure-monitor-datasource" >}}) plugin is ported from being an external plugin to being a core datasource - [React Plugin]({{< relref "#react-panels-query-editors" >}}) support enables an easier way to build plugins. - [Named Colors]({{< relref "#named-colors" >}}) in our new improved color picker. -- [Removal of user session storage]({{< relref "#easier-to-deploy-improved-security" >}}) makes Grafana easier to deploy & improves security. +- [Removal of user session storage]({{< relref "#easier-to-deploy-improved-security" >}}) makes Grafana easier to deploy and improves security. ## Explore {{< docs-imagebox img="/img/docs/v60/explore_prometheus.png" max-width="800px" class="docs-image--right" caption="Screenshot of the new Explore option in the panel menu" >}} -Grafana's dashboard UI is all about building dashboards for visualization. **Explore** strips away all the dashboard and panel options so that you can focus on the query & metric exploration. Iterate until you have a working query and then think about building a dashboard. You can also jump from a dashboard panel into **Explore** and from there do some ad-hoc query exporation with the panel queries as a starting point. +Grafana's dashboard UI is all about building dashboards for visualization. **Explore** strips away all the dashboard and panel options so that you can focus on the query and metric exploration. Iterate until you have a working query and then think about building a dashboard. You can also jump from a dashboard panel into **Explore** and from there do some ad-hoc query exporation with the panel queries as a starting point. For infrastructure monitoring and incident response, you no longer need to switch to other tools to debug what went wrong. **Explore** allows you to dig deeper into your metrics and logs to find the cause. Grafana's new logging datasource, [Loki](https://github.com/grafana/loki) is tightly integrated into Explore and allows you to correlate metrics and logs by viewing them side-by-side. **Explore** is a new paradigm for Grafana. It creates a new interactive debugging workflow that integrates two pillars -of observability - metrics and logs. Explore works with every datasource but for Prometheus we have customized the +of observability—metrics and logs. Explore works with every datasource but for Prometheus we have customized the query editor and the experience to provide the best possible exploration UX. ### Explore and Prometheus Explore features a new [Prometheus query editor](/features/explore/#prometheus-specific-features). This new editor has improved autocomplete, metric tree selector, -integrations with the Explore table view for easy label filtering and useful query hints that can automatically apply +integrations with the Explore table view for easy label filtering, and useful query hints that can automatically apply functions to your query. There is also integration between Prometheus and Grafana Loki (see more about Loki below) that enabled jumping between metrics query and logs query with preserved label filters. @@ -80,8 +78,8 @@ for other log sources to Explore and the next planned integration is Elasticsear ## New Panel Editor Grafana v6.0 has a completely redesigned UX around editing panels. You can now resize the visualization area if you want -more space for queries & options and vice versa. You can now also change visualization (panel type) from within the new -panel edit mode. No need to add a new panel to try out different visualizations! Checkout the +more space for queries/options and vice versa. You can now also change visualization (panel type) from within the new +panel edit mode. No need to add a new panel to try out different visualizations! Check out the video below to see the new Panel Editor in action.
@@ -96,7 +94,7 @@ video below to see the new Panel Editor in action. ### Gauge Panel We have created a new separate Gauge panel as we felt having this visualization be a hidden option in the Singlestat panel -was not ideal. When it supports 100% of the Singlestat Gauge features we plan to add a migration so all +was not ideal. When it supports 100% of the Singlestat Gauge features, we plan to add a migration so all singlestats that use it become Gauge panels instead. This new panel contains a new **Threshold** editor that we will continue to refine and start using in other panels. @@ -107,9 +105,9 @@ continue to refine and start using in other panels. ### React Panels & Query Editors A major part of all the work that has gone into Grafana v6.0 has been on the migration to React. This investment -is part of the future proofing of Grafana and it's code base and ecosystem. Starting in v6.0 **Panels** and **Data +is part of the future-proofing of Grafana's code base and ecosystem. Starting in v6.0 **Panels** and **Data source** plugins can be written in React using our published `@grafana/ui` sdk library. More information on this -will be shared closer to or just after release. +will be shared soon. {{< docs-imagebox img="/img/docs/v60/react_panels.png" max-width="600px" caption="React Panel" >}}
@@ -122,21 +120,23 @@ To get started read the guide: [Using Google Stackdriver in Grafana](/features/d ## Azure Monitor Datasource -One of the goals of the Grafana v6.0 release is to add support for the three major clouds. Amazon Cloudwatch has been a core datasource for years and Google Stackdriver is also now supported. We developed an external plugin for Azure Monitor last year and for this release the [plugin](https://grafana.com/plugins/grafana-azure-monitor-datasource) is being moved into Grafana to be one of the built-in datasources. For users of the external plugin, Grafana will automatically start using the built-in version. As a core datasource, the Azure Monitor datasource will get alerting support for the official 6.0 release. +One of the goals of the Grafana v6.0 release is to add support for the three major clouds. Amazon CloudWatch has been a core datasource for years and Google Stackdriver is also now supported. We developed an external plugin for Azure Monitor last year and for this release the [plugin](https://grafana.com/plugins/grafana-azure-monitor-datasource) is being moved into Grafana to be one of the built-in datasources. For users of the external plugin, Grafana will automatically start using the built-in version. As a core datasource, the Azure Monitor datasource is able to get alerting support, in the 6.0 release alerting is supported for the Azure Monitor service, with the rest to follow. The Azure Monitor datasource integrates four Azure services with Grafana - Azure Monitor, Azure Log Analytics, Azure Application Insights and Azure Application Insights Analytics. +Please read [Using Azure Monitor in Grafana documentation](/features/datasources/azuremonitor/) for more detailed information on how to get started and use it. + ## Provisioning support for alert notifiers -Grafana now added support for provisioning alert notifiers from configuration files. Allowing operators to provision notifiers without using the UI or the API. A new field called `uid` has been introduced which is a string identifier that the administrator can set themselves. Same kind of identifier used for dashboards since v5.0. This feature makes it possible to use the same notifier configuration in multiple environments and refer to notifiers in dashboard json by a string identifier instead of the numeric id which depends on insert order and how many notifiers that exists in the instance. +Grafana now has support for provisioning alert notifiers from configuration files, allowing operators to provision notifiers without using the UI or the API. A new field called `uid` has been introduced which is a string identifier that the administrator can set themselves. This is the same kind of identifier used for dashboards since v5.0. This feature makes it possible to use the same notifier configuration in multiple environments and refer to notifiers in dashboard json by a string identifier instead of the numeric id which depends on insert order and how many notifiers exist in the instance. ## Easier to deploy & improved security -Grafana 6.0 removes the need of configuring and setup of additional storage for [user sessions](/tutorials/ha_setup/#user-sessions). This should make it easier to deploy and operate Grafana in a -high availability setup and/or if you're using a stateless user session storage like Redis, Memcache, Postgres or MySQL. +Grafana 6.0 removes the need to configure and set up additional storage for [user sessions](/tutorials/ha_setup/#user-sessions). This should make it easier to deploy and operate Grafana in a +high availability setup and/or if you're using a stateless user session store like Redis, Memcache, Postgres or MySQL. -Instead of user sessions a solution based on short-lived tokens that are rotated frequently have been implemented. This also replaces the old "remember me cookie" -solution, which allowed a user to be logged in between browser sessions, and which have been subject to several security holes throughout the years. +Instead of user sessions, we've implemented a solution based on short-lived tokens that are rotated frequently. This also replaces the old "remember me cookie" +solution, which allowed a user to be logged in between browser sessions and which have been subject to several security holes throughout the years. Read more about the short-lived token solution and how to configure it [here](/auth/overview/#login-and-short-lived-tokens). > Please note that due to these changes, all users will be required to login upon next visit after upgrade. @@ -146,15 +146,15 @@ Besides these changes we have also made security improvements regarding Cross-Si * Cookies are per default using the [SameSite](/installation/configuration/#cookie-samesite) attribute to protect against CSRF attacks * Script tags in text panels are per default [disabled](/installation/configuration/#disable-sanitize-html) to protect against XSS attacks -> If you're using [Auth Proxy Authentication](/auth/auth-proxy/) you still need to have user sessions setup and configured -but our goal is to remove this requirements in a near future. +> If you're using [Auth Proxy Authentication](/auth/auth-proxy/) you still need to have user sessions set up and configured +but our goal is to remove this requirement in the near future. ## Named Colors {{< docs-imagebox img="/img/docs/v60/named_colors.png" max-width="400px" class="docs-image--right" caption="Named Colors" >}} We have updated the color picker to show named colors and primary colors. We hope this will improve accessibility and -helps making colors more consistent across dashboards. We hope to do more in this color picker in the future, like show +helps making colors more consistent across dashboards. We hope to do more in this color picker in the future, like showing colors used in the dashboard. Named colors also enables Grafana to adapt colors to the current theme. @@ -163,7 +163,7 @@ Named colors also enables Grafana to adapt colors to the current theme. ## Other features -- The ElasticSearch datasource now supports [bucket script pipeline aggregations](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-pipeline-bucket-script-aggregation.html). This gives the ability to do per bucket computations like the difference or ratio between two metrics. +- The ElasticSearch datasource now supports [bucket script pipeline aggregations](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-pipeline-bucket-script-aggregation.html). This gives the ability to do per-bucket computations like the difference or ratio between two metrics. - Support for Google Hangouts Chat alert notifications - New built in template variables for the current time range in `$__from` and `$__to` diff --git a/docs/sources/http_api/user.md b/docs/sources/http_api/user.md index a7b6c96aac5..669e8003247 100644 --- a/docs/sources/http_api/user.md +++ b/docs/sources/http_api/user.md @@ -156,6 +156,7 @@ HTTP/1.1 200 Content-Type: application/json { + "id": 1, "email": "user@mygraf.com", "name": "admin", "login": "admin", diff --git a/docs/sources/index.md b/docs/sources/index.md index 3e8e014e606..a0b1ff42ea0 100644 --- a/docs/sources/index.md +++ b/docs/sources/index.md @@ -60,9 +60,9 @@ aliases = ["v1.1", "guides/reference/admin"]

Provisioning

A guide to help you automate your Grafana setup & configuration.

- }}" class="nav-cards__item nav-cards__item--guide"> -

What's new in v5.4

-

Article on all the new cool features and enhancements in v5.4

+
}}" class="nav-cards__item nav-cards__item--guide"> +

What's new in v6.0

+

Article on all the new cool features and enhancements in v6.0

}}" class="nav-cards__item nav-cards__item--guide">

Screencasts

@@ -89,12 +89,20 @@ aliases = ["v1.1", "guides/reference/admin"]
Prometheus
}}" class="nav-cards__item nav-cards__item--ds"> - +
Google Stackdriver
}}" class="nav-cards__item nav-cards__item--ds"> -
Cloudwatch
+
AWS CloudWatch
+
+ }}" class="nav-cards__item nav-cards__item--ds"> + +
Azure Monitor
+
+ }}" class="nav-cards__item nav-cards__item--ds"> + +
Loki
}}" class="nav-cards__item nav-cards__item--ds"> diff --git a/docs/sources/installation/configuration.md b/docs/sources/installation/configuration.md index f0418ad31a6..3d1b25979c3 100644 --- a/docs/sources/installation/configuration.md +++ b/docs/sources/installation/configuration.md @@ -162,9 +162,9 @@ executed with working directory set to the installation path. ### enable_gzip -Set this option to `true` to enable HTTP compression, this can improve -transfer speed and bandwidth utilization. It is recommended that most -users set it to `true`. By default it is set to `false` for compatibility +Set this option to `true` to enable HTTP compression, this can improve +transfer speed and bandwidth utilization. It is recommended that most +users set it to `true`. By default it is set to `false` for compatibility reasons. ### cert_file @@ -342,6 +342,14 @@ options are `Admin` and `Editor`. e.g. : Viewers can edit/inspect dashboard settings in the browser. But not save the dashboard. Defaults to `false`. +### login_hint + +Text used as placeholder text on login page for login/username input. + +### password_hint + +Text used as placeholder text on login page for password input. +
## [auth] diff --git a/docs/sources/installation/upgrading.md b/docs/sources/installation/upgrading.md index e235f25b9e9..511d6117eb3 100644 --- a/docs/sources/installation/upgrading.md +++ b/docs/sources/installation/upgrading.md @@ -120,7 +120,7 @@ If you're using systemd and have a large amount of annotations consider temporar ## Upgrading to v6.0 -If you have text panels with script tags they will no longer work due to a new setting that per default disallow unsanitzied HTML. +If you have text panels with script tags they will no longer work due to a new setting that per default disallow unsanitized HTML. Read more [here](/installation/configuration/#disable-sanitize-html) about this new setting. ### Authentication and security @@ -147,4 +147,4 @@ login_maximum_inactive_lifetime_days = 1 login_maximum_lifetime_days = 1 ``` -The default cookie name for storing the auth token is `grafana_session`. you can configure this with `login_cookie_name` in `[auth]` settings. \ No newline at end of file +The default cookie name for storing the auth token is `grafana_session`. you can configure this with `login_cookie_name` in `[auth]` settings. diff --git a/docs/sources/project/building_from_source.md b/docs/sources/project/building_from_source.md index d4eb1a917d6..35e840d6e88 100644 --- a/docs/sources/project/building_from_source.md +++ b/docs/sources/project/building_from_source.md @@ -83,7 +83,7 @@ go get github.com/Unknwon/bra bra run ``` -You'll also need to run `yarn watch` to watch for changes to the front-end (typescript, html, sass) +You'll also need to run `yarn start` to watch for changes to the front-end (typescript, html, sass) ### Running tests diff --git a/docs/versions.json b/docs/versions.json index b1e447d5aa0..d1eab9afa51 100644 --- a/docs/versions.json +++ b/docs/versions.json @@ -1,5 +1,6 @@ [ - { "version": "v5.4", "path": "/", "archived": false, "current": true }, + { "version": "v6.0", "path": "/", "archived": false, "current": true }, + { "version": "v5.4", "path": "/v5.4", "archived": 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 }, diff --git a/latest.json b/latest.json index dc83446f588..e19a0f8550d 100644 --- a/latest.json +++ b/latest.json @@ -1,4 +1,4 @@ { - "stable": "5.4.3", - "testing": "5.4.3" + "stable": "6.0.1", + "testing": "6.0.1" } diff --git a/package.json b/package.json index 71339bee2be..c951e5241c1 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "company": "Grafana Labs" }, "name": "grafana", - "version": "6.0.0-pre3", + "version": "6.1.0-pre", "repository": { "type": "git", "url": "http://github.com/grafana/grafana.git" @@ -17,11 +17,13 @@ "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.1.0", "@rtsao/plugin-proposal-class-properties": "^7.0.1-patch.1", + "@types/angular": "^1.6.6", "@types/chalk": "^2.2.0", "@types/classnames": "^2.2.6", "@types/commander": "^2.12.2", "@types/d3": "^4.10.1", "@types/enzyme": "^3.1.13", + "@types/inquirer": "^0.0.43", "@types/jest": "^23.3.2", "@types/jquery": "^1.10.35", "@types/node": "^8.0.31", @@ -47,6 +49,7 @@ "enzyme-to-json": "^3.3.4", "es6-promise": "^3.0.2", "es6-shim": "^0.35.3", + "execa": "^1.0.0", "expect.js": "~0.2.0", "expose-loader": "^0.7.3", "file-loader": "^1.1.11", @@ -70,6 +73,7 @@ "html-webpack-harddisk-plugin": "^0.2.0", "html-webpack-plugin": "^3.2.0", "husky": "^1.3.1", + "inquirer": "^6.2.2", "jest": "^23.6.0", "jest-date-mock": "^1.0.6", "lint-staged": "^8.1.3", @@ -83,6 +87,7 @@ "node-sass": "^4.11.0", "npm": "^5.4.2", "optimize-css-assets-webpack-plugin": "^4.0.2", + "ora": "^3.1.0", "phantomjs-prebuilt": "^2.1.15", "postcss-browser-reporter": "^0.5.0", "postcss-loader": "^2.0.6", @@ -92,8 +97,10 @@ "react-test-renderer": "^16.5.0", "redux-mock-store": "^1.5.3", "regexp-replace-loader": "^1.0.1", + "rimraf": "^2.6.3", "sass-lint": "^1.10.2", "sass-loader": "^7.0.1", + "semver": "^5.6.0", "sinon": "1.17.6", "style-loader": "^0.21.0", "systemjs": "0.20.19", @@ -117,10 +124,10 @@ }, "scripts": { "dev": "webpack --progress --colors --mode development --config scripts/webpack/webpack.dev.js", - "start": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts --theme", - "start:hot": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts --hot --theme", - "start:ignoreTheme": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts --hot", - "watch": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts --theme -d watch,start", + "start": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts core:start --watchTheme", + "start:hot": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts core:start --hot --watchTheme", + "start:ignoreTheme": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts core:start --hot", + "watch": "yarn start -d watch,start core:start --watchTheme ", "build": "grunt build", "test": "grunt test", "tslint": "tslint -c tslint.json --project tsconfig.json", @@ -129,7 +136,12 @@ "api-tests": "jest --notify --watch --config=tests/api/jest.js", "storybook": "cd packages/grafana-ui && yarn storybook", "themes:generate": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/generateSassVariableFiles.ts", - "prettier:check": "prettier --list-different \"**/*.{ts,tsx,scss}\"" + "prettier:check": "prettier --list-different \"**/*.{ts,tsx,scss}\"", + "gui:build": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts gui:build", + "gui:releasePrepare": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts gui:release", + "gui:publish": "cd packages/grafana-ui/dist && npm publish --access public", + "gui:release": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts gui:release -p --createVersionCommit", + "cli": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/index.ts" }, "husky": { "hooks": { @@ -154,14 +166,13 @@ "license": "Apache-2.0", "dependencies": { "@babel/polyfill": "^7.0.0", - "@torkelo/react-select": "2.1.1", + "@torkelo/react-select": "2.4.1", "@types/reselect": "^2.2.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", - "ansicolor": "1.1.78", "baron": "^3.0.3", "brace": "^0.10.0", "classnames": "^2.2.6", diff --git a/packages/grafana-ui/CHANGELOG.md b/packages/grafana-ui/CHANGELOG.md new file mode 100644 index 00000000000..cbc7afc2609 --- /dev/null +++ b/packages/grafana-ui/CHANGELOG.md @@ -0,0 +1,6 @@ +# 6.0.0-alpha.0 (2019-02-22) +Version update to 6.0.0 to keep @grafana/ui version in sync with [Grafana](https://github.com/grafana/grafana) + +# 1.0.0-alpha.0 (2019-02-21) +First public release + diff --git a/packages/grafana-ui/README.md b/packages/grafana-ui/README.md index 1413965f7da..935124e99ba 100644 --- a/packages/grafana-ui/README.md +++ b/packages/grafana-ui/README.md @@ -1,3 +1,47 @@ -# Grafana (WIP) shared component library +# Grafana UI components library + +@grafana/ui is a collection of components used by [Grafana](https://github.com/grafana/grafana) + +Our goal is to deliver Grafana's common UI elements for plugins developers and contributors. + +See [package source](https://github.com/grafana/grafana/tree/master/packages/grafana-ui) for more details. + +## Installation + +`yarn add @grafana/ui` + +`npm install @grafana/ui` + +## Development + +For development purposes we suggest using `yarn link` that will create symlink to @grafana/ui lib. To do so navigate to `packages/grafana-ui` and run `yarn link`. Then, navigate to your project and run `yarn link @grafana/ui` to use the linked version of the lib. To unlink follow the same procedure, but use `yarn unlink` instead. + +## Building @grafana/ui +To build @grafana/ui run `npm run gui:build` script *from Grafana repository root*. The build will be created in `packages/grafana-ui/dist` directory. Following steps from [Development](#development) you can test built package. + +## Releasing new version +To release new version run `npm run gui:release` script *from Grafana repository root*. The script will prepare the distribution package as well as prompt you to bump library version and publish it to the NPM registry. + +### Automatic version bump +When running `npm run gui:release` package.json file will be automatically updated. Also, package.json file will be commited and pushed to upstream branch. + +### Manual version bump +To use `package.json` defined version run `npm run gui:release --usePackageJsonVersion` *from Grafana repository root*. + +### Preparing release package without publishing to NPM registry +For testing purposes there is `npm run gui:releasePrepare` task that prepares distribution package without publishing it to the NPM registry. + +### V1 release process overview +1. Package is compiled with TSC. Typings are created in `/dist` directory, and the compiled js lands in `/compiled` dir +2. Rollup creates a CommonJS package based on compiled sources, and outputs it to `/dist` directory +3. Readme, changelog and index.js files are moved to `/dist` directory +4. Package version is bumped in both `@grafana/ui` package dir and in dist directory. +5. Version commit is created and pushed to master branch +5. Package is published to npm + + +## Versioning +To limit the confusion related to @grafana/ui and Grafana versioning we decided to keep the major version in sync between those two. +This means, that first version of @grafana/ui is taged with 6.0.0-alpha.0 to keep version in sync with Grafana 6.0 release. + -Used by internal & external plugins. diff --git a/packages/grafana-ui/index.js b/packages/grafana-ui/index.js new file mode 100644 index 00000000000..d1a4363350e --- /dev/null +++ b/packages/grafana-ui/index.js @@ -0,0 +1,7 @@ +'use strict' + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./index.production.js'); +} else { + module.exports = require('./index.development.js'); +} diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index a0c76f711af..aec32fd9282 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -1,12 +1,19 @@ { "name": "@grafana/ui", - "version": "1.0.0", - "description": "", + "version": "6.0.1-alpha.0", + "description": "Grafana Components Library", + "keywords": [ + "typescript", + "react", + "react-component" + ], "main": "src/index.ts", "scripts": { "tslint": "tslint -c tslint.json --project tsconfig.json", "typecheck": "tsc --noEmit", - "storybook": "start-storybook -p 9001 -c .storybook -s ../../public" + "storybook": "start-storybook -p 9001 -c .storybook -s ../../public", + "clean": "rimraf ./dist ./compiled", + "build": "rollup -c rollup.config.ts" }, "author": "Grafana Labs", "license": "Apache-2.0", @@ -17,6 +24,7 @@ "jquery": "^3.2.1", "lodash": "^4.17.10", "moment": "^2.22.2", + "papaparse": "^4.6.3", "react": "^16.6.3", "react-color": "^2.17.0", "react-custom-scrollbars": "^4.2.1", @@ -39,6 +47,7 @@ "@types/jquery": "^1.10.35", "@types/lodash": "^4.14.119", "@types/node": "^10.12.18", + "@types/papaparse": "^4.5.9", "@types/react": "^16.7.6", "@types/react-custom-scrollbars": "^4.0.5", "@types/react-test-renderer": "^16.0.3", @@ -53,6 +62,13 @@ "react-docgen-typescript-loader": "^3.0.0", "react-docgen-typescript-webpack-plugin": "^1.1.0", "react-test-renderer": "^16.7.0", + "rollup": "^1.1.2", + "rollup-plugin-commonjs": "^9.2.0", + "rollup-plugin-node-resolve": "^4.0.0", + "rollup-plugin-sourcemaps": "^0.4.2", + "rollup-plugin-terser": "^4.0.4", + "rollup-plugin-typescript2": "^0.19.2", + "rollup-plugin-visualizer": "^0.9.2", "typescript": "^3.2.2" }, "resolutions": { diff --git a/packages/grafana-ui/rollup.config.ts b/packages/grafana-ui/rollup.config.ts new file mode 100644 index 00000000000..28f949d6e4e --- /dev/null +++ b/packages/grafana-ui/rollup.config.ts @@ -0,0 +1,54 @@ +import resolve from 'rollup-plugin-node-resolve'; +import commonjs from 'rollup-plugin-commonjs'; +import sourceMaps from 'rollup-plugin-sourcemaps'; +import { terser } from 'rollup-plugin-terser'; + +const pkg = require('./package.json'); + +const libraryName = pkg.name; + +const buildCjsPackage = ({ env }) => { + return { + input: `compiled/index.js`, + output: [ + { + file: `dist/index.${env}.js`, + name: libraryName, + format: 'cjs', + sourcemap: true, + exports: 'named', + globals: { + react: 'React', + 'prop-types': 'PropTypes', + }, + }, + ], + external: ['react', 'react-dom'], + plugins: [ + commonjs({ + include: /node_modules/, + namedExports: { + '../../node_modules/lodash/lodash.js': [ + 'flatten', + 'find', + 'upperFirst', + 'debounce', + 'isNil', + 'isNumber', + 'flattenDeep', + 'map', + 'chunk', + 'sortBy', + 'uniqueId', + 'zip', + ], + '../../node_modules/react-color/lib/components/common': ['Saturation', 'Hue', 'Alpha'], + }, + }), + resolve(), + sourceMaps(), + env === 'production' && terser(), + ], + }; +}; +export default [buildCjsPackage({ env: 'development' }), buildCjsPackage({ env: 'production' })]; diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorInput.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorInput.tsx index 4c5df5314b8..c67407f2e44 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorInput.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorInput.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { ColorPickerProps } from './ColorPicker'; +import { ColorPickerProps } from './ColorPickerPopover'; import tinycolor from 'tinycolor2'; -import { debounce } from 'lodash'; +import debounce from 'lodash/debounce'; interface ColorInputState { previousColor: string; @@ -39,7 +39,7 @@ class ColorInput extends React.PureComponent { this.props.onChange(color); }; - handleChange = (event: React.SyntheticEvent) => { + onChange = (event: React.SyntheticEvent) => { const newColor = tinycolor(event.currentTarget.value); this.setState({ @@ -51,7 +51,7 @@ class ColorInput extends React.PureComponent { } }; - handleBlur = () => { + onBlur = () => { const newColor = tinycolor(this.state.value); if (!newColor.isValid()) { @@ -84,7 +84,7 @@ class ColorInput extends React.PureComponent { flexGrow: 1, }} > - +
); diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx index 321323ac58b..5a6ddcd01b9 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx @@ -1,32 +1,11 @@ import React, { Component, createRef } from 'react'; import { PopperController } from '../Tooltip/PopperController'; import { Popper } from '../Tooltip/Popper'; -import { ColorPickerPopover } from './ColorPickerPopover'; -import { Themeable } from '../../types'; +import { ColorPickerPopover, ColorPickerProps, ColorPickerChangeHandler } from './ColorPickerPopover'; import { getColorFromHexRgbOrName } from '../../utils/namedColorsPalette'; import { SeriesColorPickerPopover } from './SeriesColorPickerPopover'; -import propDeprecationWarning from '../../utils/propDeprecationWarning'; + import { withTheme } from '../../themes/ThemeContext'; -type ColorPickerChangeHandler = (color: string) => void; - -export interface ColorPickerProps extends Themeable { - color: string; - onChange: ColorPickerChangeHandler; - - /** - * @deprecated Use onChange instead - */ - onColorChange?: ColorPickerChangeHandler; - enableNamedColors?: boolean; - children?: JSX.Element; -} - -export const warnAboutColorPickerPropsDeprecation = (componentName: string, props: ColorPickerProps) => { - const { onColorChange } = props; - if (onColorChange) { - propDeprecationWarning(componentName, 'onColorChange', 'onChange'); - } -}; export const colorPickerFactory = ( popover: React.ComponentType, @@ -36,7 +15,7 @@ export const colorPickerFactory = ( static displayName = displayName; pickerTriggerRef = createRef(); - handleColorChange = (color: string) => { + onColorChange = (color: string) => { const { onColorChange, onChange } = this.props; const changeHandler = (onColorChange || onChange) as ColorPickerChangeHandler; @@ -46,7 +25,7 @@ export const colorPickerFactory = ( render() { const popoverElement = React.createElement(popover, { ...this.props, - onChange: this.handleColorChange, + onChange: this.onColorChange, }); const { theme, children } = this.props; diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.test.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.test.tsx index 7a5000653d4..df4b5bb24a2 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.test.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.test.tsx @@ -3,7 +3,7 @@ import { mount, ReactWrapper } from 'enzyme'; import { ColorPickerPopover } from './ColorPickerPopover'; import { getColorDefinitionByName, getNamedColorPalette } from '../../utils/namedColorsPalette'; import { ColorSwatch } from './NamedColorsGroup'; -import { flatten } from 'lodash'; +import flatten from 'lodash/flatten'; import { GrafanaThemeType } from '../../types'; import { getTheme } from '../../themes'; diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx index ee879f94762..674a283ddb9 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx @@ -1,23 +1,37 @@ import React from 'react'; import { NamedColorsPalette } from './NamedColorsPalette'; import { getColorName, getColorFromHexRgbOrName } from '../../utils/namedColorsPalette'; -import { ColorPickerProps, warnAboutColorPickerPropsDeprecation } from './ColorPicker'; import { PopperContentProps } from '../Tooltip/PopperController'; import SpectrumPalette from './SpectrumPalette'; -import { GrafanaThemeType } from '../../types/theme'; +import { GrafanaThemeType, Themeable } from '../../types/theme'; +import { warnAboutColorPickerPropsDeprecation } from './warnAboutColorPickerPropsDeprecation'; +export type ColorPickerChangeHandler = (color: string) => void; + +export interface ColorPickerProps extends Themeable { + color: string; + onChange: ColorPickerChangeHandler; + + /** + * @deprecated Use onChange instead + */ + onColorChange?: ColorPickerChangeHandler; + enableNamedColors?: boolean; + children?: JSX.Element; +} export interface Props extends ColorPickerProps, PopperContentProps { customPickers?: T; } type PickerType = 'palette' | 'spectrum'; -interface CustomPickersDescriptor { +export interface CustomPickersDescriptor { [key: string]: { tabComponent: React.ComponentType; name: string; }; } + interface State { activePicker: PickerType | keyof T; } @@ -46,7 +60,7 @@ export class ColorPickerPopover extends React changeHandler(getColorFromHexRgbOrName(color, theme.type)); }; - handleTabChange = (tab: PickerType | keyof T) => { + onTabChange = (tab: PickerType | keyof T) => { return () => this.setState({ activePicker: tab }); }; @@ -90,7 +104,7 @@ export class ColorPickerPopover extends React <> {Object.keys(customPickers).map(key => { return ( -
+
{customPickers[key].name}
); @@ -105,10 +119,10 @@ export class ColorPickerPopover extends React return (
-
+
Colors
-
+
Custom
{this.renderCustomPickerTabs()} diff --git a/packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx b/packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx index 2b5f7bb9b57..b488f14fd62 100644 --- a/packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/NamedColorsGroup.tsx @@ -2,7 +2,8 @@ import React, { FunctionComponent } from 'react'; import { Themeable } from '../../types'; import { ColorDefinition, getColorForTheme } from '../../utils/namedColorsPalette'; import { Color } from 'csstype'; -import { find, upperFirst } from 'lodash'; +import upperFirst from 'lodash/upperFirst'; +import find from 'lodash/find'; import { selectThemeVariant } from '../../themes/selectThemeVariant'; type ColorChangeHandler = (color: ColorDefinition) => void; diff --git a/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx index f4901b28bfd..3e5639172be 100644 --- a/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.story.tsx @@ -8,7 +8,7 @@ import { renderComponentWithTheme } from '../../utils/storybook/withTheme'; import { UseState } from '../../utils/storybook/UseState'; const BasicGreen = getColorDefinitionByName('green'); -const BasicBlue = getColorDefinitionByName('blue'); +const BasicRed = getColorDefinitionByName('red'); const LightBlue = getColorDefinitionByName('light-blue'); const NamedColorsPaletteStories = storiesOf('UI/ColorPicker/Palettes/NamedColorsPalette', module); @@ -41,7 +41,7 @@ NamedColorsPaletteStories.add('Named colors swatch - support for named colors', 'Selected color', { Green: BasicGreen.variants.dark, - Red: BasicBlue.variants.dark, + Red: BasicRed.variants.dark, 'Light blue': LightBlue.variants.dark, }, 'red' diff --git a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx index 5fdad9c43cf..219d2dd357a 100644 --- a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx @@ -1,7 +1,6 @@ import React, { FunctionComponent } from 'react'; -import { ColorPickerPopover } from './ColorPickerPopover'; -import { ColorPickerProps } from './ColorPicker'; +import { ColorPickerPopover, ColorPickerProps } from './ColorPickerPopover'; import { PopperContentProps } from '../Tooltip/PopperController'; import { Switch } from '../Switch/Switch'; import { withTheme } from '../../themes/ThemeContext'; diff --git a/packages/grafana-ui/src/components/ColorPicker/warnAboutColorPickerPropsDeprecation.ts b/packages/grafana-ui/src/components/ColorPicker/warnAboutColorPickerPropsDeprecation.ts new file mode 100644 index 00000000000..fb4e454d2f9 --- /dev/null +++ b/packages/grafana-ui/src/components/ColorPicker/warnAboutColorPickerPropsDeprecation.ts @@ -0,0 +1,9 @@ +import deprecationWarning from '../../utils/deprecationWarning'; +import { ColorPickerProps } from './ColorPickerPopover'; + +export const warnAboutColorPickerPropsDeprecation = (componentName: string, props: ColorPickerProps) => { + const { onColorChange } = props; + if (onColorChange) { + deprecationWarning(componentName, 'onColorChange', 'onChange'); + } +}; diff --git a/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx b/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx index 39bae62e28b..945e3d825d8 100644 --- a/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx +++ b/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx @@ -1,5 +1,5 @@ -import React, { PureComponent } from 'react'; -import _ from 'lodash'; +import React, { Component } from 'react'; +import isNil from 'lodash/isNil'; import classNames from 'classnames'; import Scrollbars from 'react-custom-scrollbars'; @@ -15,12 +15,13 @@ interface Props { scrollTop?: number; setScrollTop: (event: any) => void; autoHeightMin?: number | string; + updateAfterMountMs?: number; } /** * Wraps component into component from `react-custom-scrollbars` */ -export class CustomScrollbar extends PureComponent { +export class CustomScrollbar extends Component { static defaultProps: Partial = { autoHide: false, autoHideTimeout: 200, @@ -41,17 +42,27 @@ export class CustomScrollbar extends PureComponent { updateScroll() { const ref = this.ref.current; - if (ref && !_.isNil(this.props.scrollTop)) { - if (this.props.scrollTop > 10000) { - ref.scrollToBottom(); - } else { - ref.scrollTop(this.props.scrollTop); - } + if (ref && !isNil(this.props.scrollTop)) { + ref.scrollTop(this.props.scrollTop); } } componentDidMount() { this.updateScroll(); + + // this logic is to make scrollbar visible when content is added body after mount + if (this.props.updateAfterMountMs) { + setTimeout(() => this.updateAfterMount(), this.props.updateAfterMountMs); + } + } + + updateAfterMount() { + if (this.ref && this.ref.current) { + const scrollbar = this.ref.current as any; + if (scrollbar.update) { + scrollbar.update(); + } + } } componentDidUpdate() { diff --git a/packages/grafana-ui/src/components/DeleteButton/DeleteButton.story.tsx b/packages/grafana-ui/src/components/DeleteButton/DeleteButton.story.tsx index ccbccea9c5c..0f5e85414eb 100644 --- a/packages/grafana-ui/src/components/DeleteButton/DeleteButton.story.tsx +++ b/packages/grafana-ui/src/components/DeleteButton/DeleteButton.story.tsx @@ -1,24 +1,17 @@ -import React, { FunctionComponent } from 'react'; +import React from 'react'; import { storiesOf } from '@storybook/react'; import { DeleteButton } from './DeleteButton'; - -const CenteredStory: FunctionComponent<{}> = ({ children }) => { - return ( -
- {children} -
- ); -}; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import { action } from '@storybook/addon-actions'; storiesOf('UI/DeleteButton', module) - .addDecorator(story => {story()}) + .addDecorator(withCenteredStory) .add('default', () => { - return {}} />; + return ( + { + action('Delete Confirmed')('delete!'); + }} + /> + ); }); diff --git a/packages/grafana-ui/src/components/FormField/FormField.tsx b/packages/grafana-ui/src/components/FormField/FormField.tsx index 593678c7383..89e879ccc64 100644 --- a/packages/grafana-ui/src/components/FormField/FormField.tsx +++ b/packages/grafana-ui/src/components/FormField/FormField.tsx @@ -1,5 +1,5 @@ import React, { InputHTMLAttributes, FunctionComponent } from 'react'; -import { FormLabel } from '..'; +import { FormLabel } from '../FormLabel/FormLabel'; export interface Props extends InputHTMLAttributes { label: string; diff --git a/packages/grafana-ui/src/components/FormField/_FormField.scss b/packages/grafana-ui/src/components/FormField/_FormField.scss index 36955e2fca6..0c69e67f82a 100644 --- a/packages/grafana-ui/src/components/FormField/_FormField.scss +++ b/packages/grafana-ui/src/components/FormField/_FormField.scss @@ -1,5 +1,5 @@ .form-field { - margin-bottom: $gf-form-margin; + margin-bottom: $space-xxs; display: flex; flex-direction: row; align-items: center; diff --git a/packages/grafana-ui/src/components/Gauge/Gauge.tsx b/packages/grafana-ui/src/components/Gauge/Gauge.tsx index 85c42d086f5..3d1567d4937 100644 --- a/packages/grafana-ui/src/components/Gauge/Gauge.tsx +++ b/packages/grafana-ui/src/components/Gauge/Gauge.tsx @@ -123,7 +123,7 @@ export class Gauge extends PureComponent { Math.min(dimension / 5, 100) * (formattedValue !== null ? this.getFontScale(formattedValue.length) : 1); const thresholdLabelFontSize = fontSize / 2.5; - const options = { + const options: any = { series: { gauges: { gauge: { diff --git a/packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss b/packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss index 4ce9c5264ea..2b1a401d1e4 100644 --- a/packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss +++ b/packages/grafana-ui/src/components/PanelOptionsGroup/_PanelOptionsGroup.scss @@ -53,7 +53,7 @@ } .panel-options-group__title { - font-size: 1.1rem; + font-size: 16px; position: relative; top: 1px; } diff --git a/packages/grafana-ui/src/components/Select/Select.tsx b/packages/grafana-ui/src/components/Select/Select.tsx index 06973dc08e8..5528c886e0f 100644 --- a/packages/grafana-ui/src/components/Select/Select.tsx +++ b/packages/grafana-ui/src/components/Select/Select.tsx @@ -16,7 +16,7 @@ import SelectOptionGroup from './SelectOptionGroup'; import IndicatorsContainer from './IndicatorsContainer'; import NoOptionsMessage from './NoOptionsMessage'; import resetSelectStyles from './resetSelectStyles'; -import { CustomScrollbar } from '..'; +import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar'; export interface SelectOptionItem { label?: string; diff --git a/packages/grafana-ui/src/components/Select/_Select.scss b/packages/grafana-ui/src/components/Select/_Select.scss index bc18ed9d369..f0270b1422e 100644 --- a/packages/grafana-ui/src/components/Select/_Select.scss +++ b/packages/grafana-ui/src/components/Select/_Select.scss @@ -3,7 +3,7 @@ $select-input-bg-disabled: $input-bg-disabled; @mixin select-control() { width: 100%; - margin-right: $gf-form-margin; + margin-right: $space-xs; @include border-radius($input-border-radius-sm); background-color: $input-bg; } diff --git a/packages/grafana-ui/src/components/Switch/Switch.tsx b/packages/grafana-ui/src/components/Switch/Switch.tsx index f79de95fd2d..e2a25682519 100644 --- a/packages/grafana-ui/src/components/Switch/Switch.tsx +++ b/packages/grafana-ui/src/components/Switch/Switch.tsx @@ -1,5 +1,5 @@ import React, { PureComponent } from 'react'; -import _ from 'lodash'; +import uniqueId from 'lodash/uniqueId'; export interface Props { label: string; @@ -17,7 +17,7 @@ export interface State { export class Switch extends PureComponent { state = { - id: _.uniqueId('check-'), + id: uniqueId(), }; internalOnChange = (event: React.FormEvent) => { diff --git a/packages/grafana-ui/src/components/Table/TableInputCSV.story.tsx b/packages/grafana-ui/src/components/Table/TableInputCSV.story.tsx new file mode 100644 index 00000000000..604eefca7a2 --- /dev/null +++ b/packages/grafana-ui/src/components/Table/TableInputCSV.story.tsx @@ -0,0 +1,25 @@ +import React from 'react'; + +import { storiesOf } from '@storybook/react'; +import TableInputCSV from './TableInputCSV'; +import { action } from '@storybook/addon-actions'; +import { TableData } from '../../types/data'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + +const TableInputStories = storiesOf('UI/Table/Input', module); + +TableInputStories.addDecorator(withCenteredStory); + +TableInputStories.add('default', () => { + return ( +
+ { + console.log('Table', table, text); + action('Table')(table, text); + }} + /> +
+ ); +}); diff --git a/packages/grafana-ui/src/components/Table/TableInputCSV.test.tsx b/packages/grafana-ui/src/components/Table/TableInputCSV.test.tsx new file mode 100644 index 00000000000..9e2dfbf80c3 --- /dev/null +++ b/packages/grafana-ui/src/components/Table/TableInputCSV.test.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +import renderer from 'react-test-renderer'; +import TableInputCSV from './TableInputCSV'; +import { TableData } from '../../types/data'; + +describe('TableInputCSV', () => { + it('renders correctly', () => { + const tree = renderer + .create( + { + // console.log('Table:', table, 'from:', text); + }} + /> + ) + .toJSON(); + //expect(tree).toMatchSnapshot(); + expect(tree).toBeDefined(); + }); +}); diff --git a/packages/grafana-ui/src/components/Table/TableInputCSV.tsx b/packages/grafana-ui/src/components/Table/TableInputCSV.tsx new file mode 100644 index 00000000000..14bc68550c3 --- /dev/null +++ b/packages/grafana-ui/src/components/Table/TableInputCSV.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import debounce from 'lodash/debounce'; +import { parseCSV, TableParseOptions, TableParseDetails } from '../../utils/processTableData'; +import { TableData } from '../../types/data'; +import { AutoSizer } from 'react-virtualized'; + +interface Props { + options?: TableParseOptions; + text: string; + onTableParsed: (table: TableData, text: string) => void; +} + +interface State { + text: string; + table: TableData; + details: TableParseDetails; +} + +/** + * Expects the container div to have size set and will fill it 100% + */ +class TableInputCSV extends React.PureComponent { + constructor(props: Props) { + super(props); + + // Shoud this happen in onComponentMounted? + const { text, options, onTableParsed } = props; + const details = {}; + const table = parseCSV(text, options, details); + this.state = { + text, + table, + details, + }; + onTableParsed(table, text); + } + + readCSV = debounce(() => { + const details = {}; + const table = parseCSV(this.state.text, this.props.options, details); + this.setState({ table, details }); + }, 150); + + componentDidUpdate(prevProps: Props, prevState: State) { + const { text } = this.state; + if (text !== prevState.text || this.props.options !== prevProps.options) { + this.readCSV(); + } + // If the props text has changed, replace our local version + if (this.props.text !== prevProps.text && this.props.text !== text) { + this.setState({ text: this.props.text }); + } + + if (this.state.table !== prevState.table) { + this.props.onTableParsed(this.state.table, this.state.text); + } + } + + onFooterClicked = (event: any) => { + console.log('Errors', this.state); + const message = this.state.details + .errors!.map(err => { + return err.message; + }) + .join('\n'); + alert('CSV Parsing Errors:\n' + message); + }; + + onTextChange = (event: any) => { + this.setState({ text: event.target.value }); + }; + + render() { + const { table, details } = this.state; + + const hasErrors = details.errors && details.errors.length > 0; + const footerClassNames = hasErrors ? 'gf-table-input-csv-err' : ''; + + return ( + + {({ height, width }) => ( +
+ +
- +
-
+
- +
@@ -21,108 +40,154 @@
-
+
+
+
+ -
-
- + + +
- - -
+
+ +
-
- -
+
+ +
-
- -
+
+
+
+
-
-
-
-
+
+
+ +
-
-
- -
+
+ + +
-
- - -
+
+ +
-
- -
+
+
+
+
-
-
-
-
+
+
+ -
-
- + + +
- - -
+
+ +
-
- -
- -
-
-
-
+
+
+
+
- +
-
-
+
+
- +
-
-
+
+
- +
-
-
+
+
- +
@@ -133,7 +198,12 @@
- +
@@ -141,15 +211,21 @@
-
+
- +
- diff --git a/public/app/plugins/datasource/influxdb/plugin.json b/public/app/plugins/datasource/influxdb/plugin.json index ab8f36c29cb..c666a2085ea 100644 --- a/public/app/plugins/datasource/influxdb/plugin.json +++ b/public/app/plugins/datasource/influxdb/plugin.json @@ -22,7 +22,6 @@ "logos": { "small": "img/influxdb_logo.svg", "large": "img/influxdb_logo.svg" - }, - "version": "5.0.0" + } } } diff --git a/public/app/plugins/datasource/loki/partials/config.html b/public/app/plugins/datasource/loki/partials/config.html index d209b51730a..425e460a240 100644 --- a/public/app/plugins/datasource/loki/partials/config.html +++ b/public/app/plugins/datasource/loki/partials/config.html @@ -5,7 +5,7 @@
Maximum lines - + Loki queries must contain a limit of the maximum number of lines returned (default: 1000). Increase this limit to have a bigger result set for ad-hoc analysis. diff --git a/public/app/plugins/datasource/loki/result_transformer.test.ts b/public/app/plugins/datasource/loki/result_transformer.test.ts index bacc96c6f41..bf433162003 100644 --- a/public/app/plugins/datasource/loki/result_transformer.test.ts +++ b/public/app/plugins/datasource/loki/result_transformer.test.ts @@ -11,11 +11,11 @@ import { describe('getLoglevel()', () => { it('returns no log level on empty line', () => { - expect(getLogLevel('')).toBe(LogLevel.unkown); + expect(getLogLevel('')).toBe(LogLevel.unknown); }); it('returns no log level on when level is part of a word', () => { - expect(getLogLevel('this is information')).toBe(LogLevel.unkown); + expect(getLogLevel('this is information')).toBe(LogLevel.unknown); }); it('returns same log level for long and short version', () => { @@ -158,4 +158,46 @@ describe('mergeStreamsToLogs()', () => { }, ]); }); + + it('detects ANSI codes', () => { + expect( + mergeStreamsToLogs([ + { + labels: '{foo="bar"}', + entries: [ + { + line: "foo: 'bar'", + ts: '1970-01-01T00:00:00Z', + }, + ], + }, + { + labels: '{bar="foo"}', + entries: [ + { + line: "bar: 'foo'", + ts: '1970-01-01T00:00:00Z', + }, + ], + }, + ]).rows + ).toMatchObject([ + { + entry: "bar: 'foo'", + hasAnsi: false, + key: 'EK1970-01-01T00:00:00Z{bar="foo"}', + labels: { bar: 'foo' }, + logLevel: 'unknown', + raw: "bar: 'foo'", + }, + { + entry: "foo: 'bar'", + hasAnsi: true, + key: 'EK1970-01-01T00:00:00Z{foo="bar"}', + labels: { foo: 'bar' }, + logLevel: 'unknown', + raw: "foo: 'bar'", + }, + ]); + }); }); diff --git a/public/app/plugins/datasource/loki/result_transformer.ts b/public/app/plugins/datasource/loki/result_transformer.ts index 9cd4ee0779b..4f5896c782f 100644 --- a/public/app/plugins/datasource/loki/result_transformer.ts +++ b/public/app/plugins/datasource/loki/result_transformer.ts @@ -1,3 +1,4 @@ +import ansicolor from 'vendor/ansicolor/ansicolor'; import _ from 'lodash'; import moment from 'moment'; @@ -11,6 +12,7 @@ import { LogsStreamLabels, LogsMetaKind, } from 'app/core/logs_model'; +import { hasAnsiCodes } from 'app/core/utils/text'; import { DEFAULT_MAX_LINES } from './datasource'; /** @@ -21,7 +23,7 @@ import { DEFAULT_MAX_LINES } from './datasource'; */ export function getLogLevel(line: string): LogLevel { if (!line) { - return LogLevel.unkown; + return LogLevel.unknown; } let level: LogLevel; Object.keys(LogLevel).forEach(key => { @@ -33,7 +35,7 @@ export function getLogLevel(line: string): LogLevel { } }); if (!level) { - level = LogLevel.unkown; + level = LogLevel.unknown; } return level; } @@ -125,6 +127,7 @@ export function processEntry( const timeFromNow = time.fromNow(); const timeLocal = time.format('YYYY-MM-DD HH:mm:ss'); const logLevel = getLogLevel(line); + const hasAnsi = hasAnsiCodes(line); return { key, @@ -133,7 +136,9 @@ export function processEntry( timeEpochMs, timeLocal, uniqueLabels, - entry: line, + hasAnsi, + entry: hasAnsi ? ansicolor.strip(line) : line, + raw: line, labels: parsedLabels, searchWords: search ? [search] : [], timestamp: ts, diff --git a/public/app/plugins/datasource/mssql/datasource.ts b/public/app/plugins/datasource/mssql/datasource.ts index 303cd0471d7..9343e0f3860 100644 --- a/public/app/plugins/datasource/mssql/datasource.ts +++ b/public/app/plugins/datasource/mssql/datasource.ts @@ -12,7 +12,7 @@ export class MssqlDatasource { this.name = instanceSettings.name; this.id = instanceSettings.id; this.responseParser = new ResponseParser(this.$q); - this.interval = (instanceSettings.jsonData || {}).timeInterval; + this.interval = (instanceSettings.jsonData || {}).timeInterval || '1m'; } interpolateVariable(value, variable) { diff --git a/public/app/plugins/datasource/mssql/partials/config.html b/public/app/plugins/datasource/mssql/partials/config.html index db76f60e5e3..f3ac7e87064 100644 --- a/public/app/plugins/datasource/mssql/partials/config.html +++ b/public/app/plugins/datasource/mssql/partials/config.html @@ -50,7 +50,7 @@
Max open - + The maximum number of open connections to the database. If Max idle connections is greater than 0 and the Max open connections is less than Max idle connections, then Max idle connections will be @@ -60,7 +60,7 @@
Max idle - + The maximum number of connections in the idle connection pool. If Max open connections is greater than 0 but less than the Max idle connections, then the Max idle connections will be reduced to match the @@ -69,7 +69,7 @@
Max lifetime - + The maximum amount of time in seconds a connection may be reused. If set to 0, connections are reused forever. @@ -82,7 +82,7 @@
Min time interval - + A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example 1m if your data is written every minute. diff --git a/public/app/plugins/datasource/mysql/datasource.ts b/public/app/plugins/datasource/mysql/datasource.ts index f0381d53b70..a864d42fcc4 100644 --- a/public/app/plugins/datasource/mysql/datasource.ts +++ b/public/app/plugins/datasource/mysql/datasource.ts @@ -15,7 +15,7 @@ export class MysqlDatasource { this.id = instanceSettings.id; this.responseParser = new ResponseParser(this.$q); this.queryModel = new MysqlQuery({}); - this.interval = (instanceSettings.jsonData || {}).timeInterval; + this.interval = (instanceSettings.jsonData || {}).timeInterval || '1m'; } interpolateVariable = (value, variable) => { diff --git a/public/app/plugins/datasource/mysql/partials/config.html b/public/app/plugins/datasource/mysql/partials/config.html index 8221a06e1ee..f08d9b3da15 100644 --- a/public/app/plugins/datasource/mysql/partials/config.html +++ b/public/app/plugins/datasource/mysql/partials/config.html @@ -44,7 +44,7 @@
Max open - + The maximum number of open connections to the database. If Max idle connections is greater than 0 and the Max open connections is less than Max idle connections, then Max idle connections will be @@ -54,7 +54,7 @@
Max idle - + The maximum number of connections in the idle connection pool. If Max open connections is greater than 0 but less than the Max idle connections, then the Max idle connections will be reduced to match the @@ -63,7 +63,7 @@
Max lifetime - + The maximum amount of time in seconds a connection may be reused. If set to 0, connections are reused forever.

This should always be lower than configured wait_timeout in MySQL. @@ -77,7 +77,7 @@
Min time interval - + A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example 1m if your data is written every minute. diff --git a/public/app/plugins/datasource/mysql/partials/query.editor.html b/public/app/plugins/datasource/mysql/partials/query.editor.html index 0cb47061a9e..25f33b6a534 100644 --- a/public/app/plugins/datasource/mysql/partials/query.editor.html +++ b/public/app/plugins/datasource/mysql/partials/query.editor.html @@ -45,8 +45,10 @@
diff --git a/public/app/plugins/datasource/mysql/plugin.json b/public/app/plugins/datasource/mysql/plugin.json index b03e48f4cec..cbb22130c13 100644 --- a/public/app/plugins/datasource/mysql/plugin.json +++ b/public/app/plugins/datasource/mysql/plugin.json @@ -12,8 +12,7 @@ "logos": { "small": "img/mysql_logo.svg", "large": "img/mysql_logo.svg" - }, - "version": "5.0.0" + } }, "alerting": true, diff --git a/public/app/plugins/datasource/opentsdb/partials/query.editor.html b/public/app/plugins/datasource/opentsdb/partials/query.editor.html index a2db8a68840..44ee8ebb57f 100644 --- a/public/app/plugins/datasource/opentsdb/partials/query.editor.html +++ b/public/app/plugins/datasource/opentsdb/partials/query.editor.html @@ -49,7 +49,7 @@
- diff --git a/public/app/plugins/datasource/opentsdb/plugin.json b/public/app/plugins/datasource/opentsdb/plugin.json index 711ecd8512a..4dbd677af84 100644 --- a/public/app/plugins/datasource/opentsdb/plugin.json +++ b/public/app/plugins/datasource/opentsdb/plugin.json @@ -18,7 +18,6 @@ "logos": { "small": "img/opentsdb_logo.png", "large": "img/opentsdb_logo.png" - }, - "version": "5.0.0" + } } } diff --git a/public/app/plugins/datasource/postgres/datasource.ts b/public/app/plugins/datasource/postgres/datasource.ts index 13948c5d793..be27cdd37b6 100644 --- a/public/app/plugins/datasource/postgres/datasource.ts +++ b/public/app/plugins/datasource/postgres/datasource.ts @@ -17,7 +17,7 @@ export class PostgresDatasource { this.jsonData = instanceSettings.jsonData; this.responseParser = new ResponseParser(this.$q); this.queryModel = new PostgresQuery({}); - this.interval = (instanceSettings.jsonData || {}).timeInterval; + this.interval = (instanceSettings.jsonData || {}).timeInterval || '1m'; } interpolateVariable = (value, variable) => { diff --git a/public/app/plugins/datasource/postgres/partials/config.html b/public/app/plugins/datasource/postgres/partials/config.html index 13b74d6b20e..c0e5a30496d 100644 --- a/public/app/plugins/datasource/postgres/partials/config.html +++ b/public/app/plugins/datasource/postgres/partials/config.html @@ -43,7 +43,7 @@
Max open - + The maximum number of open connections to the database. If Max idle connections is greater than 0 and the Max open connections is less than Max idle connections, then Max idle connections will be @@ -53,7 +53,7 @@
Max idle - + The maximum number of connections in the idle connection pool. If Max open connections is greater than 0 but less than the Max idle connections, then the Max idle connections will be reduced to match the @@ -62,7 +62,7 @@
Max lifetime - + The maximum amount of time in seconds a connection may be reused. If set to 0, connections are reused forever. @@ -95,7 +95,7 @@
Min time interval - + A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example 1m if your data is written every minute. diff --git a/public/app/plugins/datasource/postgres/partials/query.editor.html b/public/app/plugins/datasource/postgres/partials/query.editor.html index b5e2b5d87ed..5411427cfe6 100644 --- a/public/app/plugins/datasource/postgres/partials/query.editor.html +++ b/public/app/plugins/datasource/postgres/partials/query.editor.html @@ -45,8 +45,10 @@
diff --git a/public/app/plugins/datasource/postgres/plugin.json b/public/app/plugins/datasource/postgres/plugin.json index e90e3e9e7cd..bce485264ea 100644 --- a/public/app/plugins/datasource/postgres/plugin.json +++ b/public/app/plugins/datasource/postgres/plugin.json @@ -12,8 +12,7 @@ "logos": { "small": "img/postgresql_logo.svg", "large": "img/postgresql_logo.svg" - }, - "version": "5.0.0" + } }, "alerting": true, diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index a4f4ceb55d7..bde56431683 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -379,6 +379,24 @@ export class PrometheusDatasource implements DataSourceApi { }); } + getTagKeys(options) { + const url = '/api/v1/labels'; + return this.metadataRequest(url).then(result => { + return _.map(result.data.data, value => { + return { text: value }; + }); + }); + } + + getTagValues(options) { + const url = '/api/v1/label/' + options.key + '/values'; + return this.metadataRequest(url).then(result => { + return _.map(result.data.data, value => { + return { text: value }; + }); + }); + } + testDatasource() { const now = new Date().getTime(); return this.performInstantQuery({ expr: '1+1' }, now / 1000).then(response => { @@ -396,6 +414,10 @@ export class PrometheusDatasource implements DataSourceApi { const expandedQueries = queries.map(query => ({ ...query, expr: this.templateSrv.replace(query.expr, {}, this.interpolateQueryExpr), + + // null out values we don't support in Explore yet + legendFormat: null, + step: null, })); state = { ...state, diff --git a/public/app/plugins/datasource/prometheus/metric_find_query.ts b/public/app/plugins/datasource/prometheus/metric_find_query.ts index 680f7a8fb98..5992efbf61c 100644 --- a/public/app/plugins/datasource/prometheus/metric_find_query.ts +++ b/public/app/plugins/datasource/prometheus/metric_find_query.ts @@ -12,10 +12,16 @@ export default class PrometheusMetricFindQuery { } process() { + const labelNamesRegex = /^label_names\(\)\s*$/; const labelValuesRegex = /^label_values\((?:(.+),\s*)?([a-zA-Z_][a-zA-Z0-9_]*)\)\s*$/; const metricNamesRegex = /^metrics\((.+)\)\s*$/; const queryResultRegex = /^query_result\((.+)\)\s*$/; + const labelNamesQuery = this.query.match(labelNamesRegex); + if (labelNamesQuery) { + return this.labelNamesQuery(); + } + const labelValuesQuery = this.query.match(labelValuesRegex); if (labelValuesQuery) { if (labelValuesQuery[1]) { @@ -39,6 +45,15 @@ export default class PrometheusMetricFindQuery { return this.metricNameAndLabelsQuery(this.query); } + labelNamesQuery() { + const url = '/api/v1/labels'; + return this.datasource.metadataRequest(url).then(result => { + return _.map(result.data.data, value => { + return { text: value }; + }); + }); + } + labelValuesQuery(label, metric) { let url; diff --git a/public/app/plugins/datasource/prometheus/partials/config.html b/public/app/plugins/datasource/prometheus/partials/config.html index a7a85e696b0..aaeec7f5336 100644 --- a/public/app/plugins/datasource/prometheus/partials/config.html +++ b/public/app/plugins/datasource/prometheus/partials/config.html @@ -5,7 +5,7 @@
Scrape interval - + Set this to your global scrape interval defined in your Prometheus config file. This will be used as a lower limit for the Prometheus step query parameter. @@ -16,7 +16,7 @@
Query timeout - + Set the Prometheus query timeout. diff --git a/public/app/plugins/datasource/prometheus/partials/query.editor.html b/public/app/plugins/datasource/prometheus/partials/query.editor.html index f63beeedbd3..94819582f6d 100644 --- a/public/app/plugins/datasource/prometheus/partials/query.editor.html +++ b/public/app/plugins/datasource/prometheus/partials/query.editor.html @@ -10,7 +10,7 @@
- @@ -21,7 +21,7 @@
- diff --git a/public/app/plugins/datasource/prometheus/plugin.json b/public/app/plugins/datasource/prometheus/plugin.json index f5ea3b7f224..6d2b526a7c7 100644 --- a/public/app/plugins/datasource/prometheus/plugin.json +++ b/public/app/plugins/datasource/prometheus/plugin.json @@ -42,7 +42,6 @@ "name": "Prometheus", "url": "https://prometheus.io/" } - ], - "version": "5.0.0" + ] } } diff --git a/public/app/plugins/datasource/prometheus/result_transformer.ts b/public/app/plugins/datasource/prometheus/result_transformer.ts index 3c21e0c3d51..b0fab2564eb 100644 --- a/public/app/plugins/datasource/prometheus/result_transformer.ts +++ b/public/app/plugins/datasource/prometheus/result_transformer.ts @@ -1,5 +1,6 @@ import _ from 'lodash'; import TableModel from 'app/core/table_model'; +import { TimeSeries } from '@grafana/ui'; export class ResultTransformer { constructor(private templateSrv) {} @@ -18,10 +19,10 @@ export class ResultTransformer { ]; } else if (prometheusResult && options.format === 'heatmap') { let seriesList = []; - prometheusResult.sort(sortSeriesByLabel); for (const metricData of prometheusResult) { seriesList.push(this.transformMetricData(metricData, options, options.start, options.end)); } + seriesList.sort(sortSeriesByLabel); seriesList = this.transformToHistogramOverTime(seriesList); return seriesList; } else if (prometheusResult) { @@ -100,7 +101,7 @@ export class ResultTransformer { table.columns.push({ text: 'Time', type: 'time' }); _.each(sortedLabels, (label, labelIndex) => { metricLabels[label] = labelIndex + 1; - table.columns.push({ text: label, filterable: !label.startsWith('__') }); + table.columns.push({ text: label, filterable: true }); }); const valueText = resultCount > 1 || valueWithRefId ? `Value #${refId}` : 'Value'; table.columns.push({ text: valueText }); @@ -197,13 +198,13 @@ export class ResultTransformer { } } -function sortSeriesByLabel(s1, s2): number { +function sortSeriesByLabel(s1: TimeSeries, s2: TimeSeries): number { let le1, le2; try { // fail if not integer. might happen with bad queries - le1 = parseHistogramLabel(s1.metric.le); - le2 = parseHistogramLabel(s2.metric.le); + le1 = parseHistogramLabel(s1.target); + le2 = parseHistogramLabel(s2.target); } catch (err) { console.log(err); return 0; diff --git a/public/app/plugins/datasource/prometheus/specs/metric_find_query.test.ts b/public/app/plugins/datasource/prometheus/specs/metric_find_query.test.ts index 1466bd8ac96..5e45f56303a 100644 --- a/public/app/plugins/datasource/prometheus/specs/metric_find_query.test.ts +++ b/public/app/plugins/datasource/prometheus/specs/metric_find_query.test.ts @@ -42,6 +42,24 @@ describe('PrometheusMetricFindQuery', () => { }); describe('When performing metricFindQuery', () => { + it('label_names() should generate label name search query', async () => { + const query = ctx.setupMetricFindQuery({ + query: 'label_names()', + response: { + data: ['name1', 'name2', 'name3'], + }, + }); + const results = await query.process(); + + expect(results).toHaveLength(3); + expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledTimes(1); + expect(ctx.backendSrvMock.datasourceRequest).toHaveBeenCalledWith({ + method: 'GET', + url: 'proxied/api/v1/labels', + silent: true, + }); + }); + it('label_values(resource) should generate label search query', async () => { const query = ctx.setupMetricFindQuery({ query: 'label_values(resource)', diff --git a/public/app/plugins/datasource/prometheus/specs/result_transformer.test.ts b/public/app/plugins/datasource/prometheus/specs/result_transformer.test.ts index d7e42237f8a..3c376334187 100644 --- a/public/app/plugins/datasource/prometheus/specs/result_transformer.test.ts +++ b/public/app/plugins/datasource/prometheus/specs/result_transformer.test.ts @@ -66,11 +66,12 @@ describe('Prometheus Result Transformer', () => { ]); expect(table.columns).toMatchObject([ { text: 'Time', type: 'time' }, - { text: '__name__' }, - { text: 'instance' }, + { text: '__name__', filterable: true }, + { text: 'instance', filterable: true }, { text: 'job' }, { text: 'Value' }, ]); + expect(table.columns[4].filterable).toBeUndefined(); }); it('should column title include refId if response count is more than 2', () => { diff --git a/public/app/plugins/datasource/stackdriver/components/Aggregations.test.tsx b/public/app/plugins/datasource/stackdriver/components/Aggregations.test.tsx index 2402cc5da2c..5b14fa73a72 100644 --- a/public/app/plugins/datasource/stackdriver/components/Aggregations.test.tsx +++ b/public/app/plugins/datasource/stackdriver/components/Aggregations.test.tsx @@ -49,7 +49,8 @@ describe('Aggregations', () => { }); it('', () => { const options = wrapper.state().aggOptions[0].options; - expect(options.length).toEqual(5); + + expect(options.length).toEqual(10); expect(options.map(o => o.value)).toEqual(expect.arrayContaining(['REDUCE_NONE'])); }); }); diff --git a/public/app/plugins/datasource/stackdriver/components/Aggregations.tsx b/public/app/plugins/datasource/stackdriver/components/Aggregations.tsx index c616d55ba7a..9b8dca01151 100644 --- a/public/app/plugins/datasource/stackdriver/components/Aggregations.tsx +++ b/public/app/plugins/datasource/stackdriver/components/Aggregations.tsx @@ -73,7 +73,7 @@ export class Aggregations extends React.Component { value={crossSeriesReducer} variables={templateSrv.variables} options={aggOptions} - placeholder="Select Aggregation" + placeholder="Select Reducer" className="width-15" />
diff --git a/public/app/plugins/datasource/stackdriver/components/Metrics.tsx b/public/app/plugins/datasource/stackdriver/components/Metrics.tsx index 06094d0c1e9..a09b6108370 100644 --- a/public/app/plugins/datasource/stackdriver/components/Metrics.tsx +++ b/public/app/plugins/datasource/stackdriver/components/Metrics.tsx @@ -185,7 +185,7 @@ export class Metrics extends React.Component { }, ]} placeholder="Select Metric" - className="width-15" + className="width-26" />
diff --git a/public/app/plugins/datasource/stackdriver/components/__snapshots__/Aggregations.test.tsx.snap b/public/app/plugins/datasource/stackdriver/components/__snapshots__/Aggregations.test.tsx.snap index defd8ef01a4..ed9fe98bbf8 100644 --- a/public/app/plugins/datasource/stackdriver/components/__snapshots__/Aggregations.test.tsx.snap +++ b/public/app/plugins/datasource/stackdriver/components/__snapshots__/Aggregations.test.tsx.snap @@ -28,7 +28,7 @@ Array [
- Select Aggregation + Select Reducer
- Select Aggregation + Select Reducer
{} export class BarGaugePanel extends PureComponent { renderBarGauge(value, width, height) { - const { onInterpolate, options } = this.props; + const { replaceVariables, options } = this.props; const { valueOptions } = options; - const prefix = onInterpolate(valueOptions.prefix); - const suffix = onInterpolate(valueOptions.suffix); + + const prefix = replaceVariables(valueOptions.prefix); + const suffix = replaceVariables(valueOptions.suffix); return ( > { onThresholdsChanged = (thresholds: Threshold[]) => - this.props.onChange({ + this.props.onOptionsChange({ ...this.props.options, thresholds, }); onValueMappingsChanged = (valueMappings: ValueMapping[]) => - this.props.onChange({ + this.props.onOptionsChange({ ...this.props.options, valueMappings, }); onValueOptionsChanged = (valueOptions: SingleStatValueOptions) => - this.props.onChange({ + this.props.onOptionsChange({ ...this.props.options, valueOptions, }); - onMinValueChange = ({ target }) => this.props.onChange({ ...this.props.options, minValue: target.value }); - onMaxValueChange = ({ target }) => this.props.onChange({ ...this.props.options, maxValue: target.value }); - onOrientationChange = ({ value }) => this.props.onChange({ ...this.props.options, orientation: value }); + onMinValueChange = ({ target }) => this.props.onOptionsChange({ ...this.props.options, minValue: target.value }); + onMaxValueChange = ({ target }) => this.props.onOptionsChange({ ...this.props.options, maxValue: target.value }); + onOrientationChange = ({ value }) => this.props.onOptionsChange({ ...this.props.options, orientation: value }); render() { const { options } = this.props; diff --git a/public/app/plugins/panel/dashlist/plugin.json b/public/app/plugins/panel/dashlist/plugin.json index 11f9db928b0..4915722d2ca 100644 --- a/public/app/plugins/panel/dashlist/plugin.json +++ b/public/app/plugins/panel/dashlist/plugin.json @@ -14,7 +14,6 @@ "logos": { "small": "img/icn-dashlist-panel.svg", "large": "img/icn-dashlist-panel.svg" - }, - "version": "5.0.0" + } } } diff --git a/public/app/plugins/panel/gauge/GaugeOptionsBox.tsx b/public/app/plugins/panel/gauge/GaugeOptionsBox.tsx index 43ccd56a304..d0cb9dc3598 100644 --- a/public/app/plugins/panel/gauge/GaugeOptionsBox.tsx +++ b/public/app/plugins/panel/gauge/GaugeOptionsBox.tsx @@ -12,14 +12,17 @@ export class GaugeOptionsBox extends PureComponent - this.props.onChange({ ...this.props.options, showThresholdLabels: !this.props.options.showThresholdLabels }); + this.props.onOptionsChange({ ...this.props.options, showThresholdLabels: !this.props.options.showThresholdLabels }); onToggleThresholdMarkers = () => - this.props.onChange({ ...this.props.options, showThresholdMarkers: !this.props.options.showThresholdMarkers }); + this.props.onOptionsChange({ + ...this.props.options, + showThresholdMarkers: !this.props.options.showThresholdMarkers, + }); - onMinValueChange = ({ target }) => this.props.onChange({ ...this.props.options, minValue: target.value }); + onMinValueChange = ({ target }) => this.props.onOptionsChange({ ...this.props.options, minValue: target.value }); - onMaxValueChange = ({ target }) => this.props.onChange({ ...this.props.options, maxValue: target.value }); + onMaxValueChange = ({ target }) => this.props.onOptionsChange({ ...this.props.options, maxValue: target.value }); render() { const { options } = this.props; diff --git a/public/app/plugins/panel/gauge/GaugePanel.tsx b/public/app/plugins/panel/gauge/GaugePanel.tsx index 1ca3e05b86a..ca7f4d5057e 100644 --- a/public/app/plugins/panel/gauge/GaugePanel.tsx +++ b/public/app/plugins/panel/gauge/GaugePanel.tsx @@ -16,10 +16,11 @@ interface Props extends PanelProps {} export class GaugePanel extends PureComponent { renderGauge(value, width, height) { - const { onInterpolate, options } = this.props; + const { replaceVariables, options } = this.props; const { valueOptions } = options; - const prefix = onInterpolate(valueOptions.prefix); - const suffix = onInterpolate(valueOptions.suffix); + + const prefix = replaceVariables(valueOptions.prefix); + const suffix = replaceVariables(valueOptions.suffix); return ( > { onThresholdsChanged = (thresholds: Threshold[]) => - this.props.onChange({ + this.props.onOptionsChange({ ...this.props.options, thresholds, }); onValueMappingsChanged = (valueMappings: ValueMapping[]) => - this.props.onChange({ + this.props.onOptionsChange({ ...this.props.options, valueMappings, }); onValueOptionsChanged = (valueOptions: SingleStatValueOptions) => - this.props.onChange({ + this.props.onOptionsChange({ ...this.props.options, valueOptions, }); render() { - const { onChange, options } = this.props; + const { onOptionsChange, options } = this.props; return ( <> - + diff --git a/public/app/plugins/panel/gauge/SingleStatValueEditor.tsx b/public/app/plugins/panel/gauge/SingleStatValueEditor.tsx index 86c177bb5e5..e711df6a2d3 100644 --- a/public/app/plugins/panel/gauge/SingleStatValueEditor.tsx +++ b/public/app/plugins/panel/gauge/SingleStatValueEditor.tsx @@ -2,8 +2,7 @@ import React, { PureComponent } from 'react'; // Components -import UnitPicker from 'app/core/components/Select/UnitPicker'; -import { FormField, FormLabel, PanelOptionsGroup, Select } from '@grafana/ui'; +import { FormField, FormLabel, PanelOptionsGroup, Select, UnitPicker } from '@grafana/ui'; // Types import { SingleStatValueOptions } from './types'; diff --git a/public/app/plugins/panel/graph/plugin.json b/public/app/plugins/panel/graph/plugin.json index 81f7c6c54cf..7aed6f972cd 100644 --- a/public/app/plugins/panel/graph/plugin.json +++ b/public/app/plugins/panel/graph/plugin.json @@ -14,7 +14,6 @@ "logos": { "small": "img/icn-graph-panel.svg", "large": "img/icn-graph-panel.svg" - }, - "version": "5.0.0" + } } } diff --git a/public/app/plugins/panel/graph/series_overrides_ctrl.ts b/public/app/plugins/panel/graph/series_overrides_ctrl.ts index 90d61a362ab..fdb8f06c270 100644 --- a/public/app/plugins/panel/graph/series_overrides_ctrl.ts +++ b/public/app/plugins/panel/graph/series_overrides_ctrl.ts @@ -11,7 +11,7 @@ export function SeriesOverridesCtrl($scope, $element, popoverSrv) { const option = { text: name, propertyName: propertyName, - index: $scope.overrideMenu.lenght, + index: $scope.overrideMenu.length, values: values, submenu: _.map(values, value => { return { text: String(value), value: value }; diff --git a/public/app/plugins/panel/graph/tab_display.html b/public/app/plugins/panel/graph/tab_display.html index 976b9bcc940..e6f5b597e1b 100644 --- a/public/app/plugins/panel/graph/tab_display.html +++ b/public/app/plugins/panel/graph/tab_display.html @@ -1,110 +1,194 @@ +
+
+
Draw Modes
+ + + +
+
+
Mode Options
+
+ +
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+
+
+
+
Hover tooltip
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
-
-
-
Draw Modes
- - - -
-
-
Mode Options
-
- -
- -
-
-
- -
- -
-
- - -
- -
- -
-
-
-
-
Hover tooltip
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
+
+
Stacking & Null value
+ + + + +
+ +
+ +
+
+
+
-
-
Stacking & Null value
- - - - -
- -
- -
-
-
-
+
+
+
+ +
+
+ +
+
+ +
-
-
-
- -
-
- -
-
- -
+
+ + +
-
- - -
- -
-
-
- -
- -
-
-
- -
-
+
+
+
+
+ +
+
+
+ +
+
diff --git a/public/app/plugins/panel/graph2/GraphPanelEditor.tsx b/public/app/plugins/panel/graph2/GraphPanelEditor.tsx index 80b17ccd5c4..1a64290759c 100644 --- a/public/app/plugins/panel/graph2/GraphPanelEditor.tsx +++ b/public/app/plugins/panel/graph2/GraphPanelEditor.tsx @@ -8,15 +8,15 @@ import { Options } from './types'; export class GraphPanelEditor extends PureComponent> { onToggleLines = () => { - this.props.onChange({ ...this.props.options, showLines: !this.props.options.showLines }); + this.props.onOptionsChange({ ...this.props.options, showLines: !this.props.options.showLines }); }; onToggleBars = () => { - this.props.onChange({ ...this.props.options, showBars: !this.props.options.showBars }); + this.props.onOptionsChange({ ...this.props.options, showBars: !this.props.options.showBars }); }; onTogglePoints = () => { - this.props.onChange({ ...this.props.options, showPoints: !this.props.options.showPoints }); + this.props.onOptionsChange({ ...this.props.options, showPoints: !this.props.options.showPoints }); }; render() { diff --git a/public/app/plugins/panel/heatmap/axes_editor.ts b/public/app/plugins/panel/heatmap/axes_editor.ts index 81df957e2ea..cec3df9f8c5 100644 --- a/public/app/plugins/panel/heatmap/axes_editor.ts +++ b/public/app/plugins/panel/heatmap/axes_editor.ts @@ -32,6 +32,7 @@ export class AxesEditorCtrl { Auto: 'auto', Upper: 'upper', Lower: 'lower', + Middle: 'middle', }; } diff --git a/public/app/plugins/panel/heatmap/color_legend.ts b/public/app/plugins/panel/heatmap/color_legend.ts index c36fad45cba..a1c80629c7e 100644 --- a/public/app/plugins/panel/heatmap/color_legend.ts +++ b/public/app/plugins/panel/heatmap/color_legend.ts @@ -11,6 +11,8 @@ const LEGEND_HEIGHT_PX = 6; const LEGEND_WIDTH_PX = 100; const LEGEND_TICK_SIZE = 0; const LEGEND_VALUE_MARGIN = 0; +const LEGEND_PADDING_LEFT = 10; +const LEGEND_SEGMENT_WIDTH = 10; /** * Color legend for heatmap editor. @@ -19,7 +21,7 @@ coreModule.directive('colorLegend', () => { return { restrict: 'E', template: '
', - link: (scope, elem, attrs) => { + link: (scope: any, elem, attrs) => { const ctrl = scope.ctrl; const panel = scope.ctrl.panel; @@ -55,7 +57,7 @@ coreModule.directive('heatmapLegend', () => { return { restrict: 'E', template: `
`, - link: (scope, elem, attrs) => { + link: (scope: any, elem, attrs) => { const ctrl = scope.ctrl; const panel = scope.ctrl.panel; @@ -67,10 +69,11 @@ coreModule.directive('heatmapLegend', () => { function render() { clearLegend(elem); if (!_.isEmpty(ctrl.data) && !_.isEmpty(ctrl.data.cards)) { - const rangeFrom = 0; - const rangeTo = ctrl.data.cardStats.max; - const maxValue = panel.color.max || rangeTo; - const minValue = panel.color.min || 0; + const cardStats = ctrl.data.cardStats; + const rangeFrom = _.isNil(panel.color.min) ? Math.min(cardStats.min, 0) : panel.color.min; + const rangeTo = _.isNil(panel.color.max) ? cardStats.max : panel.color.max; + const maxValue = cardStats.max; + const minValue = cardStats.min; if (panel.color.mode === 'spectrum') { const colorScheme = _.find(ctrl.colorSchemes, { @@ -95,27 +98,27 @@ function drawColorLegend(elem, colorScheme, rangeFrom, rangeTo, maxValue, minVal const legendWidth = Math.floor(legendElem.outerWidth()) - 30; const legendHeight = legendElem.attr('height'); - let rangeStep = 1; - if (rangeTo - rangeFrom > legendWidth) { - rangeStep = Math.floor((rangeTo - rangeFrom) / legendWidth); - } + const rangeStep = ((rangeTo - rangeFrom) / legendWidth) * LEGEND_SEGMENT_WIDTH; const widthFactor = legendWidth / (rangeTo - rangeFrom); const valuesRange = d3.range(rangeFrom, rangeTo, rangeStep); const colorScale = getColorScale(colorScheme, contextSrv.user.lightTheme, maxValue, minValue); legend + .append('g') + .attr('class', 'legend-color-bar') + .attr('transform', 'translate(' + LEGEND_PADDING_LEFT + ',0)') .selectAll('.heatmap-color-legend-rect') .data(valuesRange) .enter() .append('rect') - .attr('x', d => d * widthFactor) + .attr('x', d => Math.round((d - rangeFrom) * widthFactor)) .attr('y', 0) - .attr('width', rangeStep * widthFactor + 1) // Overlap rectangles to prevent gaps + .attr('width', Math.round(rangeStep * widthFactor + 1)) // Overlap rectangles to prevent gaps .attr('height', legendHeight) .attr('stroke-width', 0) .attr('fill', d => colorScale(d)); - drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth); + drawLegendValues(elem, rangeFrom, rangeTo, maxValue, minValue, legendWidth, valuesRange); } function drawOpacityLegend(elem, options, rangeFrom, rangeTo, maxValue, minValue) { @@ -126,31 +129,31 @@ function drawOpacityLegend(elem, options, rangeFrom, rangeTo, maxValue, minValue const legendWidth = Math.floor(legendElem.outerWidth()) - 30; const legendHeight = legendElem.attr('height'); - let rangeStep = 1; - if (rangeTo - rangeFrom > legendWidth) { - rangeStep = Math.floor((rangeTo - rangeFrom) / legendWidth); - } + const rangeStep = ((rangeTo - rangeFrom) / legendWidth) * LEGEND_SEGMENT_WIDTH; const widthFactor = legendWidth / (rangeTo - rangeFrom); const valuesRange = d3.range(rangeFrom, rangeTo, rangeStep); const opacityScale = getOpacityScale(options, maxValue, minValue); legend + .append('g') + .attr('class', 'legend-color-bar') + .attr('transform', 'translate(' + LEGEND_PADDING_LEFT + ',0)') .selectAll('.heatmap-opacity-legend-rect') .data(valuesRange) .enter() .append('rect') - .attr('x', d => d * widthFactor) + .attr('x', d => Math.round((d - rangeFrom) * widthFactor)) .attr('y', 0) - .attr('width', rangeStep * widthFactor) + .attr('width', Math.round(rangeStep * widthFactor)) .attr('height', legendHeight) .attr('stroke-width', 0) .attr('fill', options.cardColor) .style('opacity', d => opacityScale(d)); - drawLegendValues(elem, opacityScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth); + drawLegendValues(elem, rangeFrom, rangeTo, maxValue, minValue, legendWidth, valuesRange); } -function drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth) { +function drawLegendValues(elem, rangeFrom, rangeTo, maxValue, minValue, legendWidth, valuesRange) { const legendElem = $(elem).find('svg'); const legend = d3.select(legendElem.get(0)); @@ -160,10 +163,10 @@ function drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minVal const legendValueScale = d3 .scaleLinear() - .domain([0, rangeTo]) + .domain([rangeFrom, rangeTo]) .range([0, legendWidth]); - const ticks = buildLegendTicks(0, rangeTo, maxValue, minValue); + const ticks = buildLegendTicks(rangeFrom, rangeTo, maxValue, minValue); const xAxis = d3 .axisBottom(legendValueScale) .tickValues(ticks) @@ -171,7 +174,7 @@ function drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minVal const colorRect = legendElem.find(':first-child'); const posY = getSvgElemHeight(legendElem) + LEGEND_VALUE_MARGIN; - const posX = getSvgElemX(colorRect); + const posX = getSvgElemX(colorRect) + LEGEND_PADDING_LEFT; d3.select(legendElem.get(0)) .append('g') @@ -284,11 +287,12 @@ function getSvgElemHeight(elem) { function buildLegendTicks(rangeFrom, rangeTo, maxValue, minValue) { const range = rangeTo - rangeFrom; const tickStepSize = tickStep(rangeFrom, rangeTo, 3); - const ticksNum = Math.round(range / tickStepSize); + const ticksNum = Math.ceil(range / tickStepSize); + const firstTick = getFirstCloseTick(rangeFrom, tickStepSize); let ticks = []; for (let i = 0; i < ticksNum; i++) { - const current = tickStepSize * i; + const current = firstTick + tickStepSize * i; // Add user-defined min and max if it had been set if (isValueCloseTo(minValue, current, tickStepSize)) { ticks.push(minValue); @@ -302,7 +306,7 @@ function buildLegendTicks(rangeFrom, rangeTo, maxValue, minValue) { } else if (maxValue < current) { ticks.push(maxValue); } - ticks.push(tickStepSize * i); + ticks.push(current); } if (!isValueCloseTo(maxValue, rangeTo, tickStepSize)) { ticks.push(maxValue); @@ -316,3 +320,10 @@ function isValueCloseTo(val, valueTo, step) { const diff = Math.abs(val - valueTo); return diff < step * 0.3; } + +function getFirstCloseTick(minValue, step) { + if (minValue < 0) { + return Math.floor(minValue / step) * step; + } + return 0; +} diff --git a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts index 71e059a5750..99741a0c6d7 100644 --- a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts +++ b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts @@ -34,6 +34,7 @@ const panelDefaults = { }, dataFormat: 'timeseries', yBucketBound: 'auto', + reverseYBuckets: false, xAxis: { show: true, }, @@ -55,6 +56,7 @@ const panelDefaults = { showHistogram: false, }, highlightCards: true, + hideZeroBuckets: false, }; const colorModes = ['opacity', 'spectrum']; @@ -97,7 +99,7 @@ const colorSchemes = [ { name: 'YlOrRd', value: 'interpolateYlOrRd', invert: 'dark' }, ]; -const dsSupportHistogramSort = ['prometheus', 'elasticsearch']; +const dsSupportHistogramSort = ['elasticsearch']; export class HeatmapCtrl extends MetricsPanelCtrl { static templateUrl = 'module.html'; @@ -108,7 +110,7 @@ export class HeatmapCtrl extends MetricsPanelCtrl { selectionActivated: boolean; unitFormats: any; data: any; - series: any; + series: any[]; timeSrv: any; dataWarning: any; decimals: number; @@ -146,7 +148,7 @@ export class HeatmapCtrl extends MetricsPanelCtrl { } onRender() { - if (!this.range) { + if (!this.range || !this.series) { return; } @@ -204,7 +206,7 @@ export class HeatmapCtrl extends MetricsPanelCtrl { yBucketSize = 1; } - const { cards, cardStats } = convertToCards(bucketsData); + const { cards, cardStats } = convertToCards(bucketsData, this.panel.hideZeroBuckets); this.data = { buckets: bucketsData, @@ -225,13 +227,20 @@ export class HeatmapCtrl extends MetricsPanelCtrl { this.series.sort(sortSeriesByLabel); } + if (this.panel.reverseYBuckets) { + this.series.reverse(); + } + // Convert histogram to heatmap. Each histogram bucket represented by the series which name is - // a top (or bottom, depends of datasource) bucket bound. Further, these values will be used as X axis labels. + // a top (or bottom, depends of datasource) bucket bound. Further, these values will be used as Y axis labels. bucketsData = histogramToHeatmap(this.series); tsBuckets = _.map(this.series, 'label'); const yBucketBound = this.panel.yBucketBound; - if ((panelDatasource === 'prometheus' && yBucketBound !== 'lower') || yBucketBound === 'upper') { + if ( + (panelDatasource === 'prometheus' && yBucketBound !== 'lower' && yBucketBound !== 'middle') || + yBucketBound === 'upper' + ) { // Prometheus labels are upper inclusive bounds, so add empty bottom bucket label. tsBuckets = [''].concat(tsBuckets); } else { @@ -246,7 +255,7 @@ export class HeatmapCtrl extends MetricsPanelCtrl { // Always let yBucketSize=1 in 'tsbuckets' mode yBucketSize = 1; - const { cards, cardStats } = convertToCards(bucketsData); + const { cards, cardStats } = convertToCards(bucketsData, this.panel.hideZeroBuckets); this.data = { buckets: bucketsData, diff --git a/public/app/plugins/panel/heatmap/heatmap_data_converter.ts b/public/app/plugins/panel/heatmap/heatmap_data_converter.ts index 99b61be40dc..07960f68ae3 100644 --- a/public/app/plugins/panel/heatmap/heatmap_data_converter.ts +++ b/public/app/plugins/panel/heatmap/heatmap_data_converter.ts @@ -93,25 +93,43 @@ function parseHistogramLabel(label: string): number { return value; } +interface HeatmapCard { + x: number; + y: number; + yBounds: { + top: number | null; + bottom: number | null; + }; + values: number[]; + count: number; +} + +interface HeatmapCardStats { + min: number; + max: number; +} + /** * Convert buckets into linear array of "cards" - objects, represented heatmap elements. * @param {Object} buckets - * @return {Array} Array of "card" objects + * @return {Object} Array of "card" objects and stats */ -function convertToCards(buckets) { +function convertToCards(buckets: any, hideZero = false): { cards: HeatmapCard[]; cardStats: HeatmapCardStats } { let min = 0, max = 0; - const cards = []; + const cards: HeatmapCard[] = []; _.forEach(buckets, xBucket => { _.forEach(xBucket.buckets, yBucket => { - const card = { + const card: HeatmapCard = { x: xBucket.x, y: yBucket.y, yBounds: yBucket.bounds, values: yBucket.values, count: yBucket.count, }; - cards.push(card); + if (!hideZero || card.count !== 0) { + cards.push(card); + } if (cards.length === 1) { min = yBucket.count; diff --git a/public/app/plugins/panel/heatmap/heatmap_tooltip.ts b/public/app/plugins/panel/heatmap/heatmap_tooltip.ts index c447825eef8..90cbecd6657 100644 --- a/public/app/plugins/panel/heatmap/heatmap_tooltip.ts +++ b/public/app/plugins/panel/heatmap/heatmap_tooltip.ts @@ -114,7 +114,9 @@ export class HeatmapTooltip { }; boundBottom = tickFormatter(yBucketIndex); - boundTop = yBucketIndex < data.tsBuckets.length - 1 ? tickFormatter(yBucketIndex + 1) : ''; + if (this.panel.yBucketBound !== 'middle') { + boundTop = yBucketIndex < data.tsBuckets.length - 1 ? tickFormatter(yBucketIndex + 1) : ''; + } } else { // Display 0 if bucket is a special 'zero' bucket const bottom = yData.y ? yData.bounds.bottom : 0; @@ -122,8 +124,9 @@ export class HeatmapTooltip { boundTop = bucketBoundFormatter(yData.bounds.top); } valuesNumber = countValueFormatter(yData.count); + const boundStr = boundTop && boundBottom ? `${boundBottom} - ${boundTop}` : boundBottom || boundTop; tooltipHtml += `
- bucket: ${boundBottom} - ${boundTop}
+ bucket: ${boundStr}
count: ${valuesNumber}
`; } else { diff --git a/public/app/plugins/panel/heatmap/partials/axes_editor.html b/public/app/plugins/panel/heatmap/partials/axes_editor.html index 0b12ac52e24..0327ee87251 100644 --- a/public/app/plugins/panel/heatmap/partials/axes_editor.html +++ b/public/app/plugins/panel/heatmap/partials/axes_editor.html @@ -40,6 +40,11 @@
+ +
diff --git a/public/app/plugins/panel/heatmap/partials/display_editor.html b/public/app/plugins/panel/heatmap/partials/display_editor.html index 929cf1fe7d4..7b38061e2ff 100644 --- a/public/app/plugins/panel/heatmap/partials/display_editor.html +++ b/public/app/plugins/panel/heatmap/partials/display_editor.html @@ -63,6 +63,10 @@
Buckets
+ +
diff --git a/public/app/plugins/panel/heatmap/plugin.json b/public/app/plugins/panel/heatmap/plugin.json index ca5d424ede2..d72f12f4723 100644 --- a/public/app/plugins/panel/heatmap/plugin.json +++ b/public/app/plugins/panel/heatmap/plugin.json @@ -18,7 +18,6 @@ "links": [ { "name": "Brendan Gregg - Heatmaps", "url": "http://www.brendangregg.com/heatmaps.html" }, { "name": "Brendan Gregg - Latency Heatmaps", "url": " http://www.brendangregg.com/HeatMaps/latency.html" } - ], - "version": "5.0.0" + ] } } diff --git a/public/app/plugins/panel/heatmap/rendering.ts b/public/app/plugins/panel/heatmap/rendering.ts index 42b418b06cb..702704b1ada 100644 --- a/public/app/plugins/panel/heatmap/rendering.ts +++ b/public/app/plugins/panel/heatmap/rendering.ts @@ -379,6 +379,12 @@ export class HeatmapRenderer { const posX = this.getYAxisWidth(this.heatmap) + Y_AXIS_TICK_PADDING; this.heatmap.select('.axis-y').attr('transform', 'translate(' + posX + ',' + posY + ')'); + if (this.panel.yBucketBound === 'middle' && tickValues && tickValues.length) { + // Shift Y axis labels to the middle of bucket + const tickShift = 0 - this.chartHeight / (tickValues.length - 1) / 2; + this.heatmap.selectAll('.axis-y text').attr('transform', 'translate(' + 0 + ',' + tickShift + ')'); + } + // Remove vertical line in the right of axis labels (called domain in d3) this.heatmap .select('.axis-y') @@ -518,14 +524,16 @@ export class HeatmapRenderer { } const cardsData = this.data.cards; - const maxValueAuto = this.data.cardStats.max; - const maxValue = this.panel.color.max || maxValueAuto; - const minValue = this.panel.color.min || 0; + const cardStats = this.data.cardStats; + const maxValueAuto = cardStats.max; + const minValueAuto = Math.min(cardStats.min, 0); + const maxValue = _.isNil(this.panel.color.max) ? maxValueAuto : this.panel.color.max; + const minValue = _.isNil(this.panel.color.min) ? minValueAuto : this.panel.color.min; const colorScheme = _.find(this.ctrl.colorSchemes, { value: this.panel.color.colorScheme, }); this.colorScale = getColorScale(colorScheme, contextSrv.user.lightTheme, maxValue, minValue); - this.opacityScale = getOpacityScale(this.panel.color, maxValue); + this.opacityScale = getOpacityScale(this.panel.color, maxValue, minValue); this.setCardSize(); let cards = this.heatmap.selectAll('.heatmap-card').data(cardsData); @@ -615,8 +623,8 @@ export class HeatmapRenderer { w = this.cardWidth; } - // Card width should be MIN_CARD_SIZE at least - w = Math.max(w, MIN_CARD_SIZE); + // Card width should be MIN_CARD_SIZE at least, but cut cards shouldn't be displayed + w = w > 0 ? Math.max(w, MIN_CARD_SIZE) : 0; return w; } diff --git a/public/app/plugins/panel/pluginlist/plugin.json b/public/app/plugins/panel/pluginlist/plugin.json index 8927b25b880..95166f57e9e 100644 --- a/public/app/plugins/panel/pluginlist/plugin.json +++ b/public/app/plugins/panel/pluginlist/plugin.json @@ -14,7 +14,6 @@ "logos": { "small": "img/icn-dashlist-panel.svg", "large": "img/icn-dashlist-panel.svg" - }, - "version": "5.0.0" + } } } diff --git a/public/app/plugins/panel/singlestat/plugin.json b/public/app/plugins/panel/singlestat/plugin.json index e64d0ec7fe0..3f803a6b270 100644 --- a/public/app/plugins/panel/singlestat/plugin.json +++ b/public/app/plugins/panel/singlestat/plugin.json @@ -14,7 +14,6 @@ "logos": { "small": "img/icn-singlestat-panel.svg", "large": "img/icn-singlestat-panel.svg" - }, - "version": "5.0.0" + } } } diff --git a/public/app/plugins/panel/table/plugin.json b/public/app/plugins/panel/table/plugin.json index 212688fbfa8..6177a3d2695 100644 --- a/public/app/plugins/panel/table/plugin.json +++ b/public/app/plugins/panel/table/plugin.json @@ -14,7 +14,6 @@ "logos": { "small": "img/icn-table-panel.svg", "large": "img/icn-table-panel.svg" - }, - "version": "5.0.0" + } } } diff --git a/public/app/plugins/panel/table/renderer.ts b/public/app/plugins/panel/table/renderer.ts index e9bf89f45fe..6b6189f7482 100644 --- a/public/app/plugins/panel/table/renderer.ts +++ b/public/app/plugins/panel/table/renderer.ts @@ -1,7 +1,6 @@ import _ from 'lodash'; import moment from 'moment'; -import kbn from 'app/core/utils/kbn'; -import { getValueFormat, getColorFromHexRgbOrName, GrafanaThemeType } from '@grafana/ui'; +import { getValueFormat, getColorFromHexRgbOrName, GrafanaThemeType, stringToJsRegex } from '@grafana/ui'; export class TableRenderer { formatters: any[]; @@ -35,7 +34,7 @@ export class TableRenderer { for (let i = 0; i < this.panel.styles.length; i++) { const style = this.panel.styles[i]; - const regex = kbn.stringToJsRegex(style.pattern); + const regex = stringToJsRegex(style.pattern); if (column.text.match(regex)) { column.style = style; diff --git a/public/app/plugins/panel/text/plugin.json b/public/app/plugins/panel/text/plugin.json index 6667185ba4a..e0f03f580e4 100644 --- a/public/app/plugins/panel/text/plugin.json +++ b/public/app/plugins/panel/text/plugin.json @@ -13,7 +13,6 @@ "logos": { "small": "img/icn-text-panel.svg", "large": "img/icn-text-panel.svg" - }, - "version": "5.0.0" + } } } diff --git a/public/app/plugins/panel/text2/TextPanel.tsx b/public/app/plugins/panel/text2/TextPanel.tsx new file mode 100644 index 00000000000..688c2734a55 --- /dev/null +++ b/public/app/plugins/panel/text2/TextPanel.tsx @@ -0,0 +1,90 @@ +// Libraries +import React, { PureComponent } from 'react'; +import Remarkable from 'remarkable'; +import { debounce } from 'lodash'; + +// Utils +import { sanitize } from 'app/core/utils/text'; +import config from 'app/core/config'; + +// Types +import { TextOptions } from './types'; +import { PanelProps } from '@grafana/ui/src/types'; + +interface Props extends PanelProps {} +interface State { + html: string; +} + +export class TextPanel extends PureComponent { + remarkable: Remarkable; + + constructor(props) { + super(props); + + this.state = { + html: this.processContent(props.options), + }; + } + + updateHTML = debounce(() => { + const html = this.processContent(this.props.options); + if (html !== this.state.html) { + this.setState({ html }); + } + }, 150); + + componentDidUpdate(prevProps: Props) { + // Since any change could be referenced in a template variable, + // This needs to process everytime (with debounce) + this.updateHTML(); + } + + prepareHTML(html: string): string { + const { replaceVariables } = this.props; + + html = config.disableSanitizeHtml ? html : sanitize(html); + + return replaceVariables(html); + } + + prepareText(content: string): string { + return this.prepareHTML( + content + .replace(/&/g, '&') + .replace(/>/g, '>') + .replace(/') + ); + } + + prepareMarkdown(content: string): string { + if (!this.remarkable) { + this.remarkable = new Remarkable(); + } + return this.prepareHTML(this.remarkable.render(content)); + } + + processContent(options: TextOptions): string { + const { mode, content } = options; + + if (!content) { + return ''; + } + + if (mode === 'markdown') { + return this.prepareMarkdown(content); + } + if (mode === 'html') { + return this.prepareHTML(content); + } + + return this.prepareText(content); + } + + render() { + const { html } = this.state; + + return
; + } +} diff --git a/public/app/plugins/panel/text2/TextPanelEditor.tsx b/public/app/plugins/panel/text2/TextPanelEditor.tsx new file mode 100644 index 00000000000..7bf8704bd46 --- /dev/null +++ b/public/app/plugins/panel/text2/TextPanelEditor.tsx @@ -0,0 +1,38 @@ +// Libraries +import React, { PureComponent, ChangeEvent } from 'react'; + +// Components +import { PanelEditorProps, PanelOptionsGroup, Select, SelectOptionItem } from '@grafana/ui'; + +// Types +import { TextOptions } from './types'; + +export class TextPanelEditor extends PureComponent> { + modes: SelectOptionItem[] = [ + { value: 'markdown', label: 'Markdown' }, + { value: 'text', label: 'Text' }, + { value: 'html', label: 'HTML' }, + ]; + + onModeChange = (item: SelectOptionItem) => this.props.onOptionsChange({ ...this.props.options, mode: item.value }); + + onContentChange = (evt: ChangeEvent) => { + this.props.onOptionsChange({ ...this.props.options, content: (event.target as any).value }); + }; + + render() { + const { mode, content } = this.props.options; + + return ( + +
+
+ Mode +