From 6d0d07a55b0adf1de199cc1fcdf593786c7f6b27 Mon Sep 17 00:00:00 2001 From: Nick Triller Date: Mon, 28 May 2018 16:15:31 +0200 Subject: [PATCH 001/322] Document oauth_auto_login setting --- docs/sources/auth/overview.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/sources/auth/overview.md b/docs/sources/auth/overview.md index a372600ac46..d4360d554c1 100644 --- a/docs/sources/auth/overview.md +++ b/docs/sources/auth/overview.md @@ -73,7 +73,18 @@ You can hide the Grafana login form using the below configuration settings. ```bash [auth] -disable_login_form ⁼ true +disable_login_form = true +``` + +### Automatic OAuth login + +Set to true to attempt login with OAuth automatically, skipping the login screen. +This setting is ignored if multiple OAuth providers are configured. +Defaults to `false`. + +```bash +[auth] +oauth_auto_login = true ``` ### Hide sign-out menu From c1347e4ecb02ae931313664dddcbf48181b2316c Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Mon, 29 Oct 2018 12:18:41 +0100 Subject: [PATCH 002/322] WIP babel 7 --- .babelrc | 11 + package.json | 17 +- public/app/app.ts | 2 +- public/test/index.ts | 2 +- scripts/webpack/webpack.hot.js | 26 +- yarn.lock | 828 +++++++++++++++++++++++++++++++-- 6 files changed, 842 insertions(+), 44 deletions(-) create mode 100644 .babelrc diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000000..3a0f40dcf59 --- /dev/null +++ b/.babelrc @@ -0,0 +1,11 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "targets": { "browsers": "last 3 versions" }, + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/package.json b/package.json index b4c70c5b3a8..0bbd19383d4 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,12 @@ "url": "http://github.com/grafana/grafana.git" }, "devDependencies": { + "@babel/core": "^7.1.2", + "@babel/plugin-proposal-class-properties": "^7.1.0", + "@babel/plugin-syntax-dynamic-import": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@babel/preset-react": "^7.0.0", + "@babel/preset-typescript": "^7.1.0", "@types/d3": "^4.10.1", "@types/enzyme": "^3.1.13", "@types/jest": "^23.3.2", @@ -21,10 +27,10 @@ "angular-mocks": "1.6.6", "autoprefixer": "^6.4.0", "axios": "^0.17.1", - "babel-core": "^6.26.0", - "babel-loader": "^7.1.4", - "babel-plugin-syntax-dynamic-import": "^6.18.0", - "babel-preset-es2015": "^6.24.1", + "babel-core": "^7.0.0-bridge", + "babel-jest": "^23.6.0", + "babel-loader": "^8.0.4", + "babel-plugin-angularjs-annotate": "^0.9.0", "clean-webpack-plugin": "^0.1.19", "css-loader": "^0.28.7", "enzyme": "^3.6.0", @@ -127,13 +133,12 @@ }, "license": "Apache-2.0", "dependencies": { + "@babel/polyfill": "^7.0.0", "angular": "1.6.6", "angular-bindonce": "0.3.1", "angular-native-dragdrop": "1.2.2", "angular-route": "1.6.6", "angular-sanitize": "1.6.6", - "babel-jest": "^23.6.0", - "babel-polyfill": "^6.26.0", "baron": "^3.0.3", "brace": "^0.10.0", "classnames": "^2.2.5", diff --git a/public/app/app.ts b/public/app/app.ts index 9647fbe5416..2b94ec0fe33 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -1,4 +1,4 @@ -import 'babel-polyfill'; +import '@babel/polyfill'; import 'file-saver'; import 'lodash'; import 'jquery'; diff --git a/public/test/index.ts b/public/test/index.ts index 798d8b57de7..dfc56c9a511 100644 --- a/public/test/index.ts +++ b/public/test/index.ts @@ -2,7 +2,7 @@ // context.keys().forEach(context); // module.exports = context; -import 'babel-polyfill'; +import '@babel/polyfill'; import 'jquery'; import angular from 'angular'; import 'angular-mocks'; diff --git a/scripts/webpack/webpack.hot.js b/scripts/webpack/webpack.hot.js index 0305a6f465c..61e416d7ba3 100644 --- a/scripts/webpack/webpack.hot.js +++ b/scripts/webpack/webpack.hot.js @@ -47,7 +47,7 @@ module.exports = merge(common, { module: { rules: [ { - test: /\.tsx?$/, + test: /\.(j|t)sx?$/, exclude: /node_modules/, use: [{ loader: 'babel-loader', @@ -55,17 +55,23 @@ module.exports = merge(common, { cacheDirectory: true, babelrc: false, plugins: [ - 'syntax-dynamic-import', + ["@babel/plugin-proposal-class-properties", { loose: true }], + 'angularjs-annotate', + 'syntax-dynamic-import', // needed for `() => import()` in routes.ts 'react-hot-loader/babel' - ] + ], + presets: [ + [ + '@babel/preset-env', + { + targets: { browsers: 'last 3 versions' }, + useBuiltIns: 'entry' + } + ], + '@babel/preset-typescript', + '@babel/preset-react' + ], } - }, - { - loader: 'ts-loader', - options: { - transpileOnly: true, - experimentalWatchApi: true - }, }], }, { diff --git a/yarn.lock b/yarn.lock index 5c5156b9970..d36cad38b6c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,13 +2,123 @@ # yarn lockfile v1 -"@babel/code-frame@^7.0.0-beta.35": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.0.0-beta.35": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA== dependencies: "@babel/highlight" "^7.0.0" +"@babel/core@^7.1.2": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.1.2.tgz#f8d2a9ceb6832887329a7b60f9d035791400ba4e" + integrity sha512-IFeSSnjXdhDaoysIlev//UzHZbdEmm7D0EIH2qtse9xK7mXEZQpYjs2P00XlP1qYsYvid79p+Zgg6tz1mp6iVw== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/generator" "^7.1.2" + "@babel/helpers" "^7.1.2" + "@babel/parser" "^7.1.2" + "@babel/template" "^7.1.2" + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.1.2" + convert-source-map "^1.1.0" + debug "^3.1.0" + json5 "^0.5.0" + lodash "^4.17.10" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.1.2", "@babel/generator@^7.1.3": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.1.3.tgz#2103ec9c42d9bdad9190a6ad5ff2d456fd7b8673" + integrity sha512-ZoCZGcfIJFJuZBqxcY9OjC1KW2lWK64qrX1o4UYL3yshVhwKFYgzpWZ0vvtGMNJdTlvkw0W+HR1VnYN8q3QPFQ== + dependencies: + "@babel/types" "^7.1.3" + jsesc "^2.5.1" + lodash "^4.17.10" + source-map "^0.5.0" + trim-right "^1.0.1" + +"@babel/helper-annotate-as-pure@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32" + integrity sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz#6b69628dfe4087798e0c4ed98e3d4a6b2fbd2f5f" + integrity sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-builder-react-jsx@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.0.0.tgz#fa154cb53eb918cf2a9a7ce928e29eb649c5acdb" + integrity sha512-ebJ2JM6NAKW0fQEqN8hOLxK84RbRz9OkUhGS/Xd5u56ejMfVbayJ4+LykERZCOUM6faa6Fp3SZNX3fcT16MKHw== + dependencies: + "@babel/types" "^7.0.0" + esutils "^2.0.0" + +"@babel/helper-call-delegate@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.1.0.tgz#6a957f105f37755e8645343d3038a22e1449cc4a" + integrity sha512-YEtYZrw3GUK6emQHKthltKNZwszBcHK58Ygcis+gVUrF4/FmTVr5CCqQNSfmvg2y+YDEANyYoaLz/SHsnusCwQ== + dependencies: + "@babel/helper-hoist-variables" "^7.0.0" + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-define-map@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz#3b74caec329b3c80c116290887c0dd9ae468c20c" + integrity sha512-yPPcW8dc3gZLN+U1mhYV91QU3n5uTbx7DUdf8NnPbjS0RMwBuHi9Xt2MUgppmNz7CJxTBWsGczTiEp1CSOTPRg== + dependencies: + "@babel/helper-function-name" "^7.1.0" + "@babel/types" "^7.0.0" + lodash "^4.17.10" + +"@babel/helper-explode-assignable-expression@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz#537fa13f6f1674df745b0c00ec8fe4e99681c8f6" + integrity sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA== + dependencies: + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-function-name@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz#a0ceb01685f73355d4360c1247f582bfafc8ff53" + integrity sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw== + dependencies: + "@babel/helper-get-function-arity" "^7.0.0" + "@babel/template" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-get-function-arity@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz#83572d4320e2a4657263734113c42868b64e49c3" + integrity sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-hoist-variables@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0.tgz#46adc4c5e758645ae7a45deb92bab0918c23bb88" + integrity sha512-Ggv5sldXUeSKsuzLkddtyhyHe2YantsxWKNi7A+7LeD12ExRDWTRk29JCXpaHPAbMaIPZSil7n+lq78WY2VY7w== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-member-expression-to-functions@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz#8cd14b0a0df7ff00f009e7d7a436945f47c7a16f" + integrity sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg== + dependencies: + "@babel/types" "^7.0.0" + "@babel/helper-module-imports@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz#96081b7111e486da4d2cd971ad1a4fe216cc2e3d" @@ -16,6 +126,92 @@ dependencies: "@babel/types" "^7.0.0" +"@babel/helper-module-transforms@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.1.0.tgz#470d4f9676d9fad50b324cdcce5fbabbc3da5787" + integrity sha512-0JZRd2yhawo79Rcm4w0LwSMILFmFXjugG3yqf+P/UsKsRS1mJCmMwwlHDlMg7Avr9LrvSpp4ZSULO9r8jpCzcw== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-simple-access" "^7.1.0" + "@babel/helper-split-export-declaration" "^7.0.0" + "@babel/template" "^7.1.0" + "@babel/types" "^7.0.0" + lodash "^4.17.10" + +"@babel/helper-optimise-call-expression@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz#a2920c5702b073c15de51106200aa8cad20497d5" + integrity sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-plugin-utils@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250" + integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA== + +"@babel/helper-regex@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.0.0.tgz#2c1718923b57f9bbe64705ffe5640ac64d9bdb27" + integrity sha512-TR0/N0NDCcUIUEbqV6dCO+LptmmSQFQ7q70lfcEB4URsjD0E1HzicrwUH+ap6BAQ2jhCX9Q4UqZy4wilujWlkg== + dependencies: + lodash "^4.17.10" + +"@babel/helper-remap-async-to-generator@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz#361d80821b6f38da75bd3f0785ece20a88c5fe7f" + integrity sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.0.0" + "@babel/helper-wrap-function" "^7.1.0" + "@babel/template" "^7.1.0" + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-replace-supers@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.1.0.tgz#5fc31de522ec0ef0899dc9b3e7cf6a5dd655f362" + integrity sha512-BvcDWYZRWVuDeXTYZWxekQNO5D4kO55aArwZOTFXw6rlLQA8ZaDicJR1sO47h+HrnCiDFiww0fSPV0d713KBGQ== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.0.0" + "@babel/helper-optimise-call-expression" "^7.0.0" + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-simple-access@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz#65eeb954c8c245beaa4e859da6188f39d71e585c" + integrity sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w== + dependencies: + "@babel/template" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-split-export-declaration@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz#3aae285c0311c2ab095d997b8c9a94cad547d813" + integrity sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-wrap-function@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.1.0.tgz#8cf54e9190706067f016af8f75cb3df829cc8c66" + integrity sha512-R6HU3dete+rwsdAfrOzTlE9Mcpk4RjU3aX3gi9grtmugQY0u79X7eogUvfXA5sI81Mfq1cn6AgxihfN33STjJA== + dependencies: + "@babel/helper-function-name" "^7.1.0" + "@babel/template" "^7.1.0" + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helpers@^7.1.2": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.1.2.tgz#ab752e8c35ef7d39987df4e8586c63b8846234b5" + integrity sha512-Myc3pUE8eswD73aWcartxB16K6CGmHDv9KxOmD2CeOs/FaEAQodr3VYGmlvOmog60vNQ2w8QbatuahepZwrHiA== + dependencies: + "@babel/template" "^7.1.2" + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.1.2" + "@babel/highlight@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4" @@ -25,6 +221,474 @@ esutils "^2.0.2" js-tokens "^4.0.0" +"@babel/parser@^7.1.2", "@babel/parser@^7.1.3": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.3.tgz#2c92469bac2b7fbff810b67fca07bd138b48af77" + integrity sha512-gqmspPZOMW3MIRb9HlrnbZHXI1/KHTOroBwN1NcLL6pWxzqzEKGvRTq0W/PxS45OtQGbaFikSQpkS5zbnsQm2w== + +"@babel/plugin-proposal-async-generator-functions@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.1.0.tgz#41c1a702e10081456e23a7b74d891922dd1bb6ce" + integrity sha512-Fq803F3Jcxo20MXUSDdmZZXrPe6BWyGcWBPPNB/M7WaUYESKDeKMOGIxEzQOjGSmW/NWb6UaPZrtTB2ekhB/ew== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-remap-async-to-generator" "^7.1.0" + "@babel/plugin-syntax-async-generators" "^7.0.0" + +"@babel/plugin-proposal-class-properties@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.1.0.tgz#9af01856b1241db60ec8838d84691aa0bd1e8df4" + integrity sha512-/PCJWN+CKt5v1xcGn4vnuu13QDoV+P7NcICP44BoonAJoPSGwVkgrXihFIQGiEjjPlUDBIw1cM7wYFLARS2/hw== + dependencies: + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-member-expression-to-functions" "^7.0.0" + "@babel/helper-optimise-call-expression" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-replace-supers" "^7.1.0" + "@babel/plugin-syntax-class-properties" "^7.0.0" + +"@babel/plugin-proposal-json-strings@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.0.0.tgz#3b4d7b5cf51e1f2e70f52351d28d44fc2970d01e" + integrity sha512-kfVdUkIAGJIVmHmtS/40i/fg/AGnw/rsZBCaapY5yjeO5RA9m165Xbw9KMOu2nqXP5dTFjEjHdfNdoVcHv133Q== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-json-strings" "^7.0.0" + +"@babel/plugin-proposal-object-rest-spread@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.0.0.tgz#9a17b547f64d0676b6c9cecd4edf74a82ab85e7e" + integrity sha512-14fhfoPcNu7itSen7Py1iGN0gEm87hX/B+8nZPqkdmANyyYWYMY2pjA3r8WXbWVKMzfnSNS0xY8GVS0IjXi/iw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-object-rest-spread" "^7.0.0" + +"@babel/plugin-proposal-optional-catch-binding@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.0.0.tgz#b610d928fe551ff7117d42c8bb410eec312a6425" + integrity sha512-JPqAvLG1s13B/AuoBjdBYvn38RqW6n1TzrQO839/sIpqLpbnXKacsAgpZHzLD83Sm8SDXMkkrAvEnJ25+0yIpw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.0.0" + +"@babel/plugin-proposal-unicode-property-regex@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.0.0.tgz#498b39cd72536cd7c4b26177d030226eba08cd33" + integrity sha512-tM3icA6GhC3ch2SkmSxv7J/hCWKISzwycub6eGsDrFDgukD4dZ/I+x81XgW0YslS6mzNuQ1Cbzh5osjIMgepPQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-regex" "^7.0.0" + regexpu-core "^4.2.0" + +"@babel/plugin-syntax-async-generators@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.0.0.tgz#bf0891dcdbf59558359d0c626fdc9490e20bc13c" + integrity sha512-im7ged00ddGKAjcZgewXmp1vxSZQQywuQXe2B1A7kajjZmDeY/ekMPmWr9zJgveSaQH0k7BcGrojQhcK06l0zA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-class-properties@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.0.0.tgz#e051af5d300cbfbcec4a7476e37a803489881634" + integrity sha512-cR12g0Qzn4sgkjrbrzWy2GE7m9vMl/sFkqZ3gIpAQdrvPDnLM8180i+ANDFIXfjHo9aqp0ccJlQ0QNZcFUbf9w== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-dynamic-import@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.0.0.tgz#6dfb7d8b6c3be14ce952962f658f3b7eb54c33ee" + integrity sha512-Gt9xNyRrCHCiyX/ZxDGOcBnlJl0I3IWicpZRC4CdC0P5a/I07Ya2OAMEBU+J7GmRFVmIetqEYRko6QYRuKOESw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-json-strings@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.0.0.tgz#0d259a68090e15b383ce3710e01d5b23f3770cbd" + integrity sha512-UlSfNydC+XLj4bw7ijpldc1uZ/HB84vw+U6BTuqMdIEmz/LDe63w/GHtpQMdXWdqQZFeAI9PjnHe/vDhwirhKA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-jsx@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.0.0.tgz#034d5e2b4e14ccaea2e4c137af7e4afb39375ffd" + integrity sha512-PdmL2AoPsCLWxhIr3kG2+F9v4WH06Q3z+NoGVpQgnUNGcagXHq5sB3OXxkSahKq9TLdNMN/AJzFYSOo8UKDMHg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-object-rest-spread@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.0.0.tgz#37d8fbcaf216bd658ea1aebbeb8b75e88ebc549b" + integrity sha512-5A0n4p6bIiVe5OvQPxBnesezsgFJdHhSs3uFSvaPdMqtsovajLZ+G2vZyvNe10EzJBWWo3AcHGKhAFUxqwp2dw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.0.0.tgz#886f72008b3a8b185977f7cb70713b45e51ee475" + integrity sha512-Wc+HVvwjcq5qBg1w5RG9o9RVzmCaAg/Vp0erHCKpAYV8La6I94o4GQAmFYNmkzoMO6gzoOSulpKeSSz6mPEoZw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-typescript@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.0.0.tgz#90f4fe0a741ae9c0dcdc3017717c05a0cbbd5158" + integrity sha512-5fxmdqiAQVQTIS+KSvYeZuTt91wKtBTYi6JlIkvbQ6hmO+9fZE81ezxmMiFMIsxE7CdRSgzn7nQ1BChcvK9OpA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-arrow-functions@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.0.0.tgz#a6c14875848c68a3b4b3163a486535ef25c7e749" + integrity sha512-2EZDBl1WIO/q4DIkIp4s86sdp4ZifL51MoIviLY/gG/mLSuOIEg7J8o6mhbxOTvUJkaN50n+8u41FVsr5KLy/w== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-async-to-generator@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.1.0.tgz#109e036496c51dd65857e16acab3bafdf3c57811" + integrity sha512-rNmcmoQ78IrvNCIt/R9U+cixUHeYAzgusTFgIAv+wQb9HJU4szhpDD6e5GCACmj/JP5KxuCwM96bX3L9v4ZN/g== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-remap-async-to-generator" "^7.1.0" + +"@babel/plugin-transform-block-scoped-functions@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.0.0.tgz#482b3f75103927e37288b3b67b65f848e2aa0d07" + integrity sha512-AOBiyUp7vYTqz2Jibe1UaAWL0Hl9JUXEgjFvvvcSc9MVDItv46ViXFw2F7SVt1B5k+KWjl44eeXOAk3UDEaJjQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-block-scoping@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.0.0.tgz#1745075edffd7cdaf69fab2fb6f9694424b7e9bc" + integrity sha512-GWEMCrmHQcYWISilUrk9GDqH4enf3UmhOEbNbNrlNAX1ssH3MsS1xLOS6rdjRVPgA7XXVPn87tRkdTEoA/dxEg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + lodash "^4.17.10" + +"@babel/plugin-transform-classes@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.1.0.tgz#ab3f8a564361800cbc8ab1ca6f21108038432249" + integrity sha512-rNaqoD+4OCBZjM7VaskladgqnZ1LO6o2UxuWSDzljzW21pN1KXkB7BstAVweZdxQkHAujps5QMNOTWesBciKFg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.0.0" + "@babel/helper-define-map" "^7.1.0" + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-optimise-call-expression" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-replace-supers" "^7.1.0" + "@babel/helper-split-export-declaration" "^7.0.0" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.0.0.tgz#2fbb8900cd3e8258f2a2ede909b90e7556185e31" + integrity sha512-ubouZdChNAv4AAWAgU7QKbB93NU5sHwInEWfp+/OzJKA02E6Woh9RVoX4sZrbRwtybky/d7baTUqwFx+HgbvMA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-destructuring@^7.0.0": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.1.3.tgz#e69ff50ca01fac6cb72863c544e516c2b193012f" + integrity sha512-Mb9M4DGIOspH1ExHOUnn2UUXFOyVTiX84fXCd+6B5iWrQg/QMeeRmSwpZ9lnjYLSXtZwiw80ytVMr3zue0ucYw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-dotall-regex@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.0.0.tgz#73a24da69bc3c370251f43a3d048198546115e58" + integrity sha512-00THs8eJxOJUFVx1w8i1MBF4XH4PsAjKjQ1eqN/uCH3YKwP21GCKfrn6YZFZswbOk9+0cw1zGQPHVc1KBlSxig== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-regex" "^7.0.0" + regexpu-core "^4.1.3" + +"@babel/plugin-transform-duplicate-keys@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.0.0.tgz#a0601e580991e7cace080e4cf919cfd58da74e86" + integrity sha512-w2vfPkMqRkdxx+C71ATLJG30PpwtTpW7DDdLqYt2acXU7YjztzeWW2Jk1T6hKqCLYCcEA5UQM/+xTAm+QCSnuQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-exponentiation-operator@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.1.0.tgz#9c34c2ee7fd77e02779cfa37e403a2e1003ccc73" + integrity sha512-uZt9kD1Pp/JubkukOGQml9tqAeI8NkE98oZnHZ2qHRElmeKCodbTZgOEUtujSCSLhHSBWbzNiFSDIMC4/RBTLQ== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.1.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-for-of@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.0.0.tgz#f2ba4eadb83bd17dc3c7e9b30f4707365e1c3e39" + integrity sha512-TlxKecN20X2tt2UEr2LNE6aqA0oPeMT1Y3cgz8k4Dn1j5ObT8M3nl9aA37LLklx0PBZKETC9ZAf9n/6SujTuXA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-function-name@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.1.0.tgz#29c5550d5c46208e7f730516d41eeddd4affadbb" + integrity sha512-VxOa1TMlFMtqPW2IDYZQaHsFrq/dDoIjgN098NowhexhZcz3UGlvPgZXuE1jEvNygyWyxRacqDpCZt+par1FNg== + dependencies: + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-literals@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.0.0.tgz#2aec1d29cdd24c407359c930cdd89e914ee8ff86" + integrity sha512-1NTDBWkeNXgpUcyoVFxbr9hS57EpZYXpje92zv0SUzjdu3enaRwF/l3cmyRnXLtIdyJASyiS6PtybK+CgKf7jA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-modules-amd@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.1.0.tgz#f9e0a7072c12e296079b5a59f408ff5b97bf86a8" + integrity sha512-wt8P+xQ85rrnGNr2x1iV3DW32W8zrB6ctuBkYBbf5/ZzJY99Ob4MFgsZDFgczNU76iy9PWsy4EuxOliDjdKw6A== + dependencies: + "@babel/helper-module-transforms" "^7.1.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-modules-commonjs@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.1.0.tgz#0a9d86451cbbfb29bd15186306897c67f6f9a05c" + integrity sha512-wtNwtMjn1XGwM0AXPspQgvmE6msSJP15CX2RVfpTSTNPLhKhaOjaIfBaVfj4iUZ/VrFSodcFedwtPg/NxwQlPA== + dependencies: + "@babel/helper-module-transforms" "^7.1.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-simple-access" "^7.1.0" + +"@babel/plugin-transform-modules-systemjs@^7.0.0": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.1.3.tgz#2119a3e3db612fd74a19d88652efbfe9613a5db0" + integrity sha512-PvTxgjxQAq4pvVUZF3mD5gEtVDuId8NtWkJsZLEJZMZAW3TvgQl1pmydLLN1bM8huHFVVU43lf0uvjQj9FRkKw== + dependencies: + "@babel/helper-hoist-variables" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-modules-umd@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.1.0.tgz#a29a7d85d6f28c3561c33964442257cc6a21f2a8" + integrity sha512-enrRtn5TfRhMmbRwm7F8qOj0qEYByqUvTttPEGimcBH4CJHphjyK1Vg7sdU7JjeEmgSpM890IT/efS2nMHwYig== + dependencies: + "@babel/helper-module-transforms" "^7.1.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-new-target@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0.tgz#ae8fbd89517fa7892d20e6564e641e8770c3aa4a" + integrity sha512-yin069FYjah+LbqfGeTfzIBODex/e++Yfa0rH0fpfam9uTbuEeEOx5GLGr210ggOV77mVRNoeqSYqeuaqSzVSw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-object-super@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.1.0.tgz#b1ae194a054b826d8d4ba7ca91486d4ada0f91bb" + integrity sha512-/O02Je1CRTSk2SSJaq0xjwQ8hG4zhZGNjE8psTsSNPXyLRCODv7/PBozqT5AmQMzp7MI3ndvMhGdqp9c96tTEw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-replace-supers" "^7.1.0" + +"@babel/plugin-transform-parameters@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.1.0.tgz#44f492f9d618c9124026e62301c296bf606a7aed" + integrity sha512-vHV7oxkEJ8IHxTfRr3hNGzV446GAb+0hgbA7o/0Jd76s+YzccdWuTU296FOCOl/xweU4t/Ya4g41yWz80RFCRw== + dependencies: + "@babel/helper-call-delegate" "^7.1.0" + "@babel/helper-get-function-arity" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-react-display-name@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.0.0.tgz#93759e6c023782e52c2da3b75eca60d4f10533ee" + integrity sha512-BX8xKuQTO0HzINxT6j/GiCwoJB0AOMs0HmLbEnAvcte8U8rSkNa/eSCAY+l1OA4JnCVq2jw2p6U8QQryy2fTPg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-react-jsx-self@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.0.0.tgz#a84bb70fea302d915ea81d9809e628266bb0bc11" + integrity sha512-pymy+AK12WO4safW1HmBpwagUQRl9cevNX+82AIAtU1pIdugqcH+nuYP03Ja6B+N4gliAaKWAegIBL/ymALPHA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-jsx" "^7.0.0" + +"@babel/plugin-transform-react-jsx-source@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.0.0.tgz#28e00584f9598c0dd279f6280eee213fa0121c3c" + integrity sha512-OSeEpFJEH5dw/TtxTg4nijl4nHBbhqbKL94Xo/Y17WKIf2qJWeIk/QeXACF19lG1vMezkxqruwnTjVizaW7u7w== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-jsx" "^7.0.0" + +"@babel/plugin-transform-react-jsx@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.0.0.tgz#524379e4eca5363cd10c4446ba163f093da75f3e" + integrity sha512-0TMP21hXsSUjIQJmu/r7RiVxeFrXRcMUigbKu0BLegJK9PkYodHstaszcig7zxXfaBji2LYUdtqIkHs+hgYkJQ== + dependencies: + "@babel/helper-builder-react-jsx" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-jsx" "^7.0.0" + +"@babel/plugin-transform-regenerator@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0.tgz#5b41686b4ed40bef874d7ed6a84bdd849c13e0c1" + integrity sha512-sj2qzsEx8KDVv1QuJc/dEfilkg3RRPvPYx/VnKLtItVQRWt1Wqf5eVCOLZm29CiGFfYYsA3VPjfizTCV0S0Dlw== + dependencies: + regenerator-transform "^0.13.3" + +"@babel/plugin-transform-shorthand-properties@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.0.0.tgz#85f8af592dcc07647541a0350e8c95c7bf419d15" + integrity sha512-g/99LI4vm5iOf5r1Gdxq5Xmu91zvjhEG5+yZDJW268AZELAu4J1EiFLnkSG3yuUsZyOipVOVUKoGPYwfsTymhw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-spread@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.0.0.tgz#93583ce48dd8c85e53f3a46056c856e4af30b49b" + integrity sha512-L702YFy2EvirrR4shTj0g2xQp7aNwZoWNCkNu2mcoU0uyzMl0XRwDSwzB/xp6DSUFiBmEXuyAyEN16LsgVqGGQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-sticky-regex@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.0.0.tgz#30a9d64ac2ab46eec087b8530535becd90e73366" + integrity sha512-LFUToxiyS/WD+XEWpkx/XJBrUXKewSZpzX68s+yEOtIbdnsRjpryDw9U06gYc6klYEij/+KQVRnD3nz3AoKmjw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-regex" "^7.0.0" + +"@babel/plugin-transform-template-literals@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.0.0.tgz#084f1952efe5b153ddae69eb8945f882c7a97c65" + integrity sha512-vA6rkTCabRZu7Nbl9DfLZE1imj4tzdWcg5vtdQGvj+OH9itNNB6hxuRMHuIY8SGnEt1T9g5foqs9LnrHzsqEFg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-typeof-symbol@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.0.0.tgz#4dcf1e52e943e5267b7313bff347fdbe0f81cec9" + integrity sha512-1r1X5DO78WnaAIvs5uC48t41LLckxsYklJrZjNKcevyz83sF2l4RHbw29qrCPr/6ksFsdfRpT/ZgxNWHXRnffg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-typescript@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.1.0.tgz#81e7b4be90e7317cbd04bf1163ebf06b2adee60b" + integrity sha512-TOTtVeT+fekAesiCHnPz+PSkYSdOSLyLn42DI45nxg6iCdlQY6LIj/tYqpMB0y+YicoTUiYiXqF8rG6SKfhw6Q== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-typescript" "^7.0.0" + +"@babel/plugin-transform-unicode-regex@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.0.0.tgz#c6780e5b1863a76fe792d90eded9fcd5b51d68fc" + integrity sha512-uJBrJhBOEa3D033P95nPHu3nbFwFE9ZgXsfEitzoIXIwqAZWk7uXcg06yFKXz9FSxBH5ucgU/cYdX0IV8ldHKw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-regex" "^7.0.0" + regexpu-core "^4.1.3" + +"@babel/polyfill@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.0.0.tgz#c8ff65c9ec3be6a1ba10113ebd40e8750fb90bff" + integrity sha512-dnrMRkyyr74CRelJwvgnnSUDh2ge2NCTyHVwpOdvRMHtJUyxLtMAfhBN3s64pY41zdw0kgiLPh6S20eb1NcX6Q== + dependencies: + core-js "^2.5.7" + regenerator-runtime "^0.11.1" + +"@babel/preset-env@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.1.0.tgz#e67ea5b0441cfeab1d6f41e9b5c79798800e8d11" + integrity sha512-ZLVSynfAoDHB/34A17/JCZbyrzbQj59QC1Anyueb4Bwjh373nVPq5/HMph0z+tCmcDjXDe+DlKQq9ywQuvWrQg== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-async-generator-functions" "^7.1.0" + "@babel/plugin-proposal-json-strings" "^7.0.0" + "@babel/plugin-proposal-object-rest-spread" "^7.0.0" + "@babel/plugin-proposal-optional-catch-binding" "^7.0.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.0.0" + "@babel/plugin-syntax-async-generators" "^7.0.0" + "@babel/plugin-syntax-object-rest-spread" "^7.0.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.0.0" + "@babel/plugin-transform-arrow-functions" "^7.0.0" + "@babel/plugin-transform-async-to-generator" "^7.1.0" + "@babel/plugin-transform-block-scoped-functions" "^7.0.0" + "@babel/plugin-transform-block-scoping" "^7.0.0" + "@babel/plugin-transform-classes" "^7.1.0" + "@babel/plugin-transform-computed-properties" "^7.0.0" + "@babel/plugin-transform-destructuring" "^7.0.0" + "@babel/plugin-transform-dotall-regex" "^7.0.0" + "@babel/plugin-transform-duplicate-keys" "^7.0.0" + "@babel/plugin-transform-exponentiation-operator" "^7.1.0" + "@babel/plugin-transform-for-of" "^7.0.0" + "@babel/plugin-transform-function-name" "^7.1.0" + "@babel/plugin-transform-literals" "^7.0.0" + "@babel/plugin-transform-modules-amd" "^7.1.0" + "@babel/plugin-transform-modules-commonjs" "^7.1.0" + "@babel/plugin-transform-modules-systemjs" "^7.0.0" + "@babel/plugin-transform-modules-umd" "^7.1.0" + "@babel/plugin-transform-new-target" "^7.0.0" + "@babel/plugin-transform-object-super" "^7.1.0" + "@babel/plugin-transform-parameters" "^7.1.0" + "@babel/plugin-transform-regenerator" "^7.0.0" + "@babel/plugin-transform-shorthand-properties" "^7.0.0" + "@babel/plugin-transform-spread" "^7.0.0" + "@babel/plugin-transform-sticky-regex" "^7.0.0" + "@babel/plugin-transform-template-literals" "^7.0.0" + "@babel/plugin-transform-typeof-symbol" "^7.0.0" + "@babel/plugin-transform-unicode-regex" "^7.0.0" + browserslist "^4.1.0" + invariant "^2.2.2" + js-levenshtein "^1.1.3" + semver "^5.3.0" + +"@babel/preset-react@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.0.0.tgz#e86b4b3d99433c7b3e9e91747e2653958bc6b3c0" + integrity sha512-oayxyPS4Zj+hF6Et11BwuBkmpgT/zMxyuZgFrMeZID6Hdh3dGlk4sHCAhdBCpuCKW2ppBfl2uCCetlrUIJRY3w== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-react-display-name" "^7.0.0" + "@babel/plugin-transform-react-jsx" "^7.0.0" + "@babel/plugin-transform-react-jsx-self" "^7.0.0" + "@babel/plugin-transform-react-jsx-source" "^7.0.0" + +"@babel/preset-typescript@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.1.0.tgz#49ad6e2084ff0bfb5f1f7fb3b5e76c434d442c7f" + integrity sha512-LYveByuF9AOM8WrsNne5+N79k1YxjNB6gmpCQsnuSBAcV8QUeB+ZUxQzL7Rz7HksPbahymKkq2qBR+o36ggFZA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-typescript" "^7.1.0" + +"@babel/template@^7.1.0", "@babel/template@^7.1.2": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644" + integrity sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.1.2" + "@babel/types" "^7.1.2" + +"@babel/traverse@^7.1.0": + version "7.1.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.1.4.tgz#f4f83b93d649b4b2c91121a9087fa2fa949ec2b4" + integrity sha512-my9mdrAIGdDiSVBuMjpn/oXYpva0/EZwWL3sm3Wcy/AVWO2eXnsoZruOT9jOGNRXU8KbCIu5zsKnXcAJ6PcV6Q== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/generator" "^7.1.3" + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-split-export-declaration" "^7.0.0" + "@babel/parser" "^7.1.3" + "@babel/types" "^7.1.3" + debug "^3.1.0" + globals "^11.1.0" + lodash "^4.17.10" + "@babel/types@^7.0.0": version "7.1.2" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.1.2.tgz#183e7952cf6691628afdc2e2b90d03240bac80c0" @@ -34,6 +698,15 @@ lodash "^4.17.10" to-fast-properties "^2.0.0" +"@babel/types@^7.1.2", "@babel/types@^7.1.3": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.1.3.tgz#3a767004567060c2f40fca49a304712c525ee37d" + integrity sha512-RpPOVfK+yatXyn8n4PB1NW6k9qjinrXrRR8ugBN8fD6hCy5RXI6PSbVqpOJBO9oSaY7Nom4ohj35feb0UR9hSA== + dependencies: + esutils "^2.0.2" + lodash "^4.17.10" + to-fast-properties "^2.0.0" + "@emotion/babel-utils@^0.6.4": version "0.6.10" resolved "https://registry.yarnpkg.com/@emotion/babel-utils/-/babel-utils-0.6.10.tgz#83dbf3dfa933fae9fc566e54fbb45f14674c6ccc" @@ -1193,6 +1866,11 @@ babel-core@^6.0.0, babel-core@^6.26.0: slash "^1.0.0" source-map "^0.5.7" +babel-core@^7.0.0-bridge: + version "7.0.0-bridge.0" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece" + integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg== + babel-generator@^6.18.0, babel-generator@^6.26.0: version "6.26.1" resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" @@ -1347,14 +2025,15 @@ babel-jest@^23.6.0: babel-plugin-istanbul "^4.1.6" babel-preset-jest "^23.2.0" -babel-loader@^7.1.4: - version "7.1.5" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.5.tgz#e3ee0cd7394aa557e013b02d3e492bfd07aa6d68" - integrity sha512-iCHfbieL5d1LfOQeeVJEUyD9rTwBcP/fcEbRCfempxTDuqrKpu0AZjLAQHEQa3Yqyj9ORKe2iHfoj4rHLf7xpw== +babel-loader@^8.0.4: + version "8.0.4" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.4.tgz#7bbf20cbe4560629e2e41534147692d3fecbdce6" + integrity sha512-fhBhNkUToJcW9nV46v8w87AJOwAJDz84c1CL57n3Stj73FANM/b9TbCUK4YhdOwEyZ+OxhYpdeZDNzSI29Firw== dependencies: find-cache-dir "^1.0.0" loader-utils "^1.0.2" mkdirp "^0.5.1" + util.promisify "^1.0.0" babel-messages@^6.23.0: version "6.23.0" @@ -1363,6 +2042,15 @@ babel-messages@^6.23.0: dependencies: babel-runtime "^6.22.0" +babel-plugin-angularjs-annotate@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/babel-plugin-angularjs-annotate/-/babel-plugin-angularjs-annotate-0.9.0.tgz#5c499bb04d9ae5802b31e1068aedb8b2286a83af" + integrity sha512-erYvZAJgnrgeyEZqIJOAiK6vUK44HsVb0+Tid4zTBcsvdQuas0Z5Teh0w/hcINKW3G0xweqA5LGfg2ZWlp3nMA== + dependencies: + babel-code-frame "^6.26.0" + babel-types "^6.26.0" + simple-is "~0.2.0" + babel-plugin-check-es2015-constants@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" @@ -1757,16 +2445,7 @@ babel-plugin-transform-strict-mode@^6.24.1: babel-runtime "^6.22.0" babel-types "^6.24.1" -babel-polyfill@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.26.0.tgz#379937abc67d7895970adc621f284cd966cf2153" - integrity sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM= - dependencies: - babel-runtime "^6.26.0" - core-js "^2.5.0" - regenerator-runtime "^0.10.5" - -babel-preset-es2015@^6.24.1, babel-preset-es2015@^6.9.0: +babel-preset-es2015@^6.9.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939" integrity sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk= @@ -2193,6 +2872,15 @@ browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: caniuse-db "^1.0.30000639" electron-to-chromium "^1.2.7" +browserslist@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.3.4.tgz#4477b737db6a1b07077275b24791e680d4300425" + integrity sha512-u5iz+ijIMUlmV8blX82VGFrB9ecnUg5qEt55CMZ/YJEhha+d8qpBfOFuutJ6F/VKRXjZoD33b6uvarpPxcl3RA== + dependencies: + caniuse-lite "^1.0.30000899" + electron-to-chromium "^1.3.82" + node-releases "^1.0.1" + bs-logger@0.x: version "0.2.5" resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.5.tgz#1d82f0cf88864e1341cd9262237f8d0748a49b22" @@ -2442,6 +3130,11 @@ caniuse-db@1.0.30000772, caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, can resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000772.tgz#51aae891768286eade4a3d8319ea76d6a01b512b" integrity sha1-UarokXaChureSj2DGep21qAbUSs= +caniuse-lite@^1.0.30000899: + version "1.0.30000900" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000900.tgz#015cfe37897a3386a3075a914498800c29afe77e" + integrity sha512-xDVs8pBFr6bzq9pXUkLKpGQQnzsF/l6/yX38UnCkTcUcwC0rDl1NGZGildcJVTU+uGBxfsyniK/ZWagPNn1Oqw== + capture-exit@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-1.2.0.tgz#1c5fcc489fd0ab00d4f1ac7ae1072e3173fbab6f" @@ -3102,7 +3795,7 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== -convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.5.1: +convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.5.1: version "1.6.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A== @@ -3141,7 +3834,7 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= -core-js@^2.0.0, core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.0: +core-js@^2.0.0, core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.0, core-js@^2.5.7: version "2.5.7" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" integrity sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw== @@ -4325,6 +5018,11 @@ electron-to-chromium@^1.2.7: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.75.tgz#dd04551739e7371862b0ac7f4ddaa9f3f95b7e68" integrity sha512-nLo03Qpw++8R6BxDZL/B1c8SQvUe/htdgc5LWYHe5YotV2jVvRUMP5AlOmxOsyeOzgMiXrNln2mC05Ixz6vuUQ== +electron-to-chromium@^1.3.82: + version "1.3.82" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.82.tgz#7d13ae4437d2a783de3f4efba96b186c540b67b1" + integrity sha512-NI4nB2IWGcU4JVT1AE8kBb/dFor4zjLHMLsOROPahppeHrR0FG5uslxMmkp/thO1MvPjM2xhlKoY29/I60s0ew== + elegant-spinner@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" @@ -4733,7 +5431,7 @@ estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM= -esutils@^2.0.2: +esutils@^2.0.0, esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= @@ -5704,6 +6402,11 @@ global@^4.3.0: min-document "^2.19.0" process "~0.5.1" +globals@^11.1.0: + version "11.8.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.8.0.tgz#c1ef45ee9bed6badf0663c5cb90e8d1adec1321d" + integrity sha512-io6LkyPVuzCHBSQV9fmOwxZkUk6nIaGmxheLDgmuFv89j0fm2aqDbIXKAGfzCMHqz3HLF2Zf8WSG6VqMh2qFmA== + globals@^9.18.0, globals@^9.2.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" @@ -7659,6 +8362,11 @@ js-base64@^2.1.8, js-base64@^2.1.9: resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.9.tgz#748911fb04f48a60c4771b375cac45a80df11c03" integrity sha512-xcinL3AuDJk7VSzsHgb9DvvIXayBbadtMZ4HFPx8rUszbW1MuNMlwYVC4zzCZ6e1sqZpnNS5ZFYOhXqA39T7LQ== +js-levenshtein@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.4.tgz#3a56e3cbf589ca0081eb22cd9ba0b1290a16d26e" + integrity sha512-PxfGzSs0ztShKrUYPIn5r0MtyAhYcCwmndozzpz8YObbPnD1jFxzlBGbRnX2mIu6Z13xN6+PTu05TQFnZFlzow== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -7782,6 +8490,11 @@ jsesc@^1.3.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s= +jsesc@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.1.tgz#e421a2a8e20d6b0819df28908f782526b96dd1fe" + integrity sha1-5CGiqOINawgZ3yiQj3glJrlt0f4= + json-buffer@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" @@ -9272,6 +9985,13 @@ node-pre-gyp@^0.10.0: semver "^5.3.0" tar "^4" +node-releases@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.0.1.tgz#957a2735d2ca737d7005588f8e85e6c27032555b" + integrity sha512-/kOv7jA26OBwkBPx6B9xR/FzJzs2OkMtcWjS8uPQRMHE7IELdSfN0QKZvmiWnf5P1QJ8oYq/e9qe0aCZISB1pQ== + dependencies: + semver "^5.3.0" + node-sass@^4.7.2: version "4.9.3" resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.9.3.tgz#f407cf3d66f78308bb1e346b24fa428703196224" @@ -11663,17 +12383,19 @@ redux@^4.0.0: loose-envify "^1.1.0" symbol-observable "^1.2.0" -regenerate@^1.2.1: +regenerate-unicode-properties@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz#107405afcc4a190ec5ed450ecaa00ed0cafa7a4c" + integrity sha512-s5NGghCE4itSlUS+0WUj88G6cfMVMmH8boTPNvABf8od+2dhT9WDlWu8n01raQAJZMOK8Ch6jSexaRO7swd6aw== + dependencies: + regenerate "^1.4.0" + +regenerate@^1.2.1, regenerate@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== -regenerator-runtime@^0.10.5: - version "0.10.5" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" - integrity sha1-M2w+/BIgrc7dosn6tntaeVWjNlg= - -regenerator-runtime@^0.11.0: +regenerator-runtime@^0.11.0, regenerator-runtime@^0.11.1: version "0.11.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== @@ -11687,6 +12409,13 @@ regenerator-transform@^0.10.0: babel-types "^6.19.0" private "^0.1.6" +regenerator-transform@^0.13.3: + version "0.13.3" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.3.tgz#264bd9ff38a8ce24b06e0636496b2c856b57bcbb" + integrity sha512-5ipTrZFSq5vU2YoGoww4uaRVAK4wyYC4TSICibbfEPOruUu8FFP7ErV0BjmbIOEpn3O/k9na9UEdYR/3m7N6uA== + dependencies: + private "^0.1.6" + regex-cache@^0.4.2: version "0.4.4" resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" @@ -11720,6 +12449,18 @@ regexpu-core@^2.0.0: regjsgen "^0.2.0" regjsparser "^0.1.4" +regexpu-core@^4.1.3, regexpu-core@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.2.0.tgz#a3744fa03806cffe146dea4421a3e73bdcc47b1d" + integrity sha512-Z835VSnJJ46CNBttalHD/dB+Sj2ezmY6Xp38npwU87peK6mqOzOpV8eYktdkLTEkzzD+JsTcxd84ozd8I14+rw== + dependencies: + regenerate "^1.4.0" + regenerate-unicode-properties "^7.0.0" + regjsgen "^0.4.0" + regjsparser "^0.3.0" + unicode-match-property-ecmascript "^1.0.4" + unicode-match-property-value-ecmascript "^1.0.2" + registry-auth-token@^3.0.1: version "3.3.2" resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.2.tgz#851fd49038eecb586911115af845260eec983f20" @@ -11740,6 +12481,11 @@ regjsgen@^0.2.0: resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" integrity sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc= +regjsgen@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.4.0.tgz#c1eb4c89a209263f8717c782591523913ede2561" + integrity sha512-X51Lte1gCYUdlwhF28+2YMO0U6WeN0GLpgpA7LK7mbdDnkQYiwvEpmpe0F/cv5L14EbxgrdayAG3JETBv0dbXA== + regjsparser@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" @@ -11747,6 +12493,13 @@ regjsparser@^0.1.4: dependencies: jsesc "~0.5.0" +regjsparser@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.3.0.tgz#3c326da7fcfd69fa0d332575a41c8c0cdf588c96" + integrity sha512-zza72oZBBHzt64G7DxdqrOo/30bhHkwMUoT0WqfGu98XLd7N+1tsy5MJ96Bk4MD0y74n629RhmrGW6XlnLLwCA== + dependencies: + jsesc "~0.5.0" + relateurl@0.2.x: version "0.2.7" resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" @@ -12773,7 +13526,7 @@ source-map@0.5.6: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" integrity sha1-dc449SvwczxafwwRjYEzSiu19BI= -source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.0, source-map@~0.5.1, source-map@~0.5.3: +source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.0, source-map@~0.5.1, source-map@~0.5.3: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= @@ -13752,6 +14505,29 @@ underscore@~1.7.0: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209" integrity sha1-a7rwh3UA02vjTsqlhODbn+8DUgk= +unicode-canonical-property-names-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" + integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ== + +unicode-match-property-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" + integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg== + dependencies: + unicode-canonical-property-names-ecmascript "^1.0.4" + unicode-property-aliases-ecmascript "^1.0.4" + +unicode-match-property-value-ecmascript@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.0.2.tgz#9f1dc76926d6ccf452310564fd834ace059663d4" + integrity sha512-Rx7yODZC1L/T8XKo/2kNzVAQaRE88AaMvI1EF/Xnj3GW2wzN6fop9DDWuFAKUVFH7vozkz26DzP0qyWLKLIVPQ== + +unicode-property-aliases-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz#5a533f31b4317ea76f17d807fa0d116546111dd0" + integrity sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg== + union-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" From 3414be18bc46167f7493d12360327d2c5477f1c4 Mon Sep 17 00:00:00 2001 From: Nick Triller Date: Mon, 28 May 2018 16:16:48 +0200 Subject: [PATCH 003/322] Implement oauth_auto_login setting Redirect in backend --- pkg/api/login.go | 22 ++++++++++++++++++++++ pkg/setting/setting.go | 2 ++ 2 files changed, 24 insertions(+) diff --git a/pkg/api/login.go b/pkg/api/login.go index 1083f89adfd..05afc40e59a 100644 --- a/pkg/api/login.go +++ b/pkg/api/login.go @@ -39,6 +39,10 @@ func (hs *HTTPServer) LoginView(c *m.ReqContext) { viewData.Settings["loginError"] = loginError } + if tryOAuthAutoLogin(c) { + return + } + if !tryLoginUsingRememberCookie(c) { c.HTML(200, ViewIndex, viewData) return @@ -53,6 +57,24 @@ func (hs *HTTPServer) LoginView(c *m.ReqContext) { c.Redirect(setting.AppSubUrl + "/") } +func tryOAuthAutoLogin(c *m.ReqContext) bool { + if !setting.OAuthAutoLogin { + return false + } + oauthInfos := setting.OAuthService.OAuthInfos + if len(oauthInfos) != 1 { + log.Warn("Skipping OAuth auto login because multiple OAuth providers are configured.") + return false + } + for key := range setting.OAuthService.OAuthInfos { + redirectUrl := setting.AppSubUrl + "/login/" + key + log.Info("OAuth auto login enabled. Redirecting to " + redirectUrl) + c.Redirect(redirectUrl, 307) + return true + } + return false +} + func tryLoginUsingRememberCookie(c *m.ReqContext) bool { // Check auto-login. uname := c.GetCookie(setting.CookieUserName) diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 58901e55c6b..7543d91c463 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -108,6 +108,7 @@ var ( ExternalUserMngLinkUrl string ExternalUserMngLinkName string ExternalUserMngInfo string + OAuthAutoLogin bool ViewersCanEdit bool // Http auth @@ -622,6 +623,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error { auth := iniFile.Section("auth") DisableLoginForm = auth.Key("disable_login_form").MustBool(false) DisableSignoutMenu = auth.Key("disable_signout_menu").MustBool(false) + OAuthAutoLogin = auth.Key("oauth_auto_login").MustBool(false) SignoutRedirectUrl = auth.Key("signout_redirect_url").String() // anonymous access From ccfd9c89b2645fde4b12aad0819c178ef5afb979 Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 1 Nov 2018 16:04:38 +0100 Subject: [PATCH 004/322] introduces hard coded deboucing for alerting --- pkg/services/alerting/eval_context.go | 21 ++- pkg/services/alerting/eval_context_test.go | 186 +++++++++++++-------- pkg/services/alerting/result_handler.go | 3 + pkg/services/alerting/rule.go | 5 + 4 files changed, 145 insertions(+), 70 deletions(-) diff --git a/pkg/services/alerting/eval_context.go b/pkg/services/alerting/eval_context.go index d0441d379b7..49e28bbf5ec 100644 --- a/pkg/services/alerting/eval_context.go +++ b/pkg/services/alerting/eval_context.go @@ -69,7 +69,7 @@ func (c *EvalContext) GetStateModel() *StateDescription { Text: "Alerting", } default: - panic("Unknown rule state " + c.Rule.State) + panic("Unknown rule state for alert notifications " + c.Rule.State) } } @@ -125,11 +125,26 @@ func (c *EvalContext) GetNewState() m.AlertStateType { return c.PrevAlertState } return c.Rule.ExecutionErrorState.ToAlertState() + } - } else if c.Firing { + if c.Firing && c.Rule.DebounceDuration != 0 { + since := time.Now().Sub(c.Rule.LastStateChange) + if since > c.Rule.DebounceDuration { + return m.AlertStateAlerting + } + + if c.PrevAlertState == m.AlertStateAlerting { + return m.AlertStateAlerting + } + + return m.AlertStatePending + } + + if c.Firing { return m.AlertStateAlerting + } - } else if c.NoDataFound { + if c.NoDataFound { c.log.Info("Alert Rule returned no data", "ruleId", c.Rule.Id, "name", c.Rule.Name, diff --git a/pkg/services/alerting/eval_context_test.go b/pkg/services/alerting/eval_context_test.go index 750fa959683..2abf581d830 100644 --- a/pkg/services/alerting/eval_context_test.go +++ b/pkg/services/alerting/eval_context_test.go @@ -2,11 +2,11 @@ package alerting import ( "context" - "fmt" + "errors" "testing" + "time" "github.com/grafana/grafana/pkg/models" - . "github.com/smartystreets/goconvey/convey" ) func TestStateIsUpdatedWhenNeeded(t *testing.T) { @@ -31,71 +31,123 @@ func TestStateIsUpdatedWhenNeeded(t *testing.T) { }) } -func TestAlertingEvalContext(t *testing.T) { - Convey("Should compute and replace properly new rule state", t, func() { +func TestGetStateFromEvalContext(t *testing.T) { + tcs := []struct { + name string + expected models.AlertStateType + applyFn func(ec *EvalContext) + focus bool + }{ + { + name: "ok -> alerting", + expected: models.AlertStateAlerting, + applyFn: func(ec *EvalContext) { + ec.Firing = true + ec.PrevAlertState = models.AlertStateOK + }, + }, + { + name: "ok -> error(alerting)", + expected: models.AlertStateAlerting, + applyFn: func(ec *EvalContext) { + ec.PrevAlertState = models.AlertStateOK + ec.Error = errors.New("test error") + ec.Rule.ExecutionErrorState = models.ExecutionErrorSetAlerting + }, + }, + { + name: "ok -> pending. since its been firing for less than FOR", + expected: models.AlertStatePending, + applyFn: func(ec *EvalContext) { + ec.PrevAlertState = models.AlertStateOK + ec.Firing = true + ec.Rule.LastStateChange = time.Now().Add(-time.Minute * 2) + ec.Rule.DebounceDuration = time.Minute * 5 + }, + }, + { + name: "ok -> alerting. since its been firing for more than FOR", + expected: models.AlertStateAlerting, + applyFn: func(ec *EvalContext) { + ec.PrevAlertState = models.AlertStateOK + ec.Firing = true + ec.Rule.LastStateChange = time.Now().Add(-(time.Hour * 5)) + ec.Rule.DebounceDuration = time.Minute * 2 + }, + }, + { + name: "alerting -> alerting. should not update regardless of FOR", + expected: models.AlertStateAlerting, + applyFn: func(ec *EvalContext) { + ec.PrevAlertState = models.AlertStateAlerting + ec.Firing = true + ec.Rule.LastStateChange = time.Now().Add(-time.Minute * 5) + ec.Rule.DebounceDuration = time.Minute * 2 + }, + }, + { + name: "ok -> ok. should not update regardless of FOR", + expected: models.AlertStateOK, + applyFn: func(ec *EvalContext) { + ec.PrevAlertState = models.AlertStateOK + ec.Rule.LastStateChange = time.Now().Add(-time.Minute * 5) + ec.Rule.DebounceDuration = time.Minute * 2 + }, + }, + { + name: "ok -> error(keep_last)", + expected: models.AlertStateOK, + applyFn: func(ec *EvalContext) { + ec.PrevAlertState = models.AlertStateOK + ec.Error = errors.New("test error") + ec.Rule.ExecutionErrorState = models.ExecutionErrorKeepState + }, + }, + { + name: "pending -> error(keep_last)", + expected: models.AlertStatePending, + applyFn: func(ec *EvalContext) { + ec.PrevAlertState = models.AlertStatePending + ec.Error = errors.New("test error") + ec.Rule.ExecutionErrorState = models.ExecutionErrorKeepState + }, + }, + { + name: "ok -> no_data(alerting)", + expected: models.AlertStateAlerting, + applyFn: func(ec *EvalContext) { + ec.PrevAlertState = models.AlertStateOK + ec.Rule.NoDataState = models.NoDataSetAlerting + ec.NoDataFound = true + }, + }, + { + name: "ok -> no_data(keep_last)", + expected: models.AlertStateOK, + applyFn: func(ec *EvalContext) { + ec.PrevAlertState = models.AlertStateOK + ec.Rule.NoDataState = models.NoDataKeepState + ec.NoDataFound = true + }, + }, + { + name: "pending -> no_data(keep_last)", + expected: models.AlertStatePending, + applyFn: func(ec *EvalContext) { + ec.PrevAlertState = models.AlertStatePending + ec.Rule.NoDataState = models.NoDataKeepState + ec.NoDataFound = true + }, + }, + } + + for _, tc := range tcs { ctx := NewEvalContext(context.TODO(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}}) - dummieError := fmt.Errorf("dummie error") - Convey("ok -> alerting", func() { - ctx.PrevAlertState = models.AlertStateOK - ctx.Firing = true - - ctx.Rule.State = ctx.GetNewState() - So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting) - }) - - Convey("ok -> error(alerting)", func() { - ctx.PrevAlertState = models.AlertStateOK - ctx.Error = dummieError - ctx.Rule.ExecutionErrorState = models.ExecutionErrorSetAlerting - - ctx.Rule.State = ctx.GetNewState() - So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting) - }) - - Convey("ok -> error(keep_last)", func() { - ctx.PrevAlertState = models.AlertStateOK - ctx.Error = dummieError - ctx.Rule.ExecutionErrorState = models.ExecutionErrorKeepState - - ctx.Rule.State = ctx.GetNewState() - So(ctx.Rule.State, ShouldEqual, models.AlertStateOK) - }) - - Convey("pending -> error(keep_last)", func() { - ctx.PrevAlertState = models.AlertStatePending - ctx.Error = dummieError - ctx.Rule.ExecutionErrorState = models.ExecutionErrorKeepState - - ctx.Rule.State = ctx.GetNewState() - So(ctx.Rule.State, ShouldEqual, models.AlertStatePending) - }) - - Convey("ok -> no_data(alerting)", func() { - ctx.PrevAlertState = models.AlertStateOK - ctx.Rule.NoDataState = models.NoDataSetAlerting - ctx.NoDataFound = true - - ctx.Rule.State = ctx.GetNewState() - So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting) - }) - - Convey("ok -> no_data(keep_last)", func() { - ctx.PrevAlertState = models.AlertStateOK - ctx.Rule.NoDataState = models.NoDataKeepState - ctx.NoDataFound = true - - ctx.Rule.State = ctx.GetNewState() - So(ctx.Rule.State, ShouldEqual, models.AlertStateOK) - }) - - Convey("pending -> no_data(keep_last)", func() { - ctx.PrevAlertState = models.AlertStatePending - ctx.Rule.NoDataState = models.NoDataKeepState - ctx.NoDataFound = true - - ctx.Rule.State = ctx.GetNewState() - So(ctx.Rule.State, ShouldEqual, models.AlertStatePending) - }) - }) + tc.applyFn(ctx) + have := ctx.GetNewState() + if have != tc.expected { + t.Errorf("failed: %s \n expected '%s' have '%s'\n", tc.name, tc.expected, string(have)) + } + } } diff --git a/pkg/services/alerting/result_handler.go b/pkg/services/alerting/result_handler.go index 420ffeb9a55..ce12a8a6b96 100644 --- a/pkg/services/alerting/result_handler.go +++ b/pkg/services/alerting/result_handler.go @@ -73,6 +73,9 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error { // when two servers are raising. This makes sure that the server // with the last state change always sends a notification. evalContext.Rule.StateChanges = cmd.Result.StateChanges + + // Update the last state change of the alert rule in memory + evalContext.Rule.LastStateChange = time.Now() } // save annotation diff --git a/pkg/services/alerting/rule.go b/pkg/services/alerting/rule.go index 999611f15c4..3fb69b48f9f 100644 --- a/pkg/services/alerting/rule.go +++ b/pkg/services/alerting/rule.go @@ -4,6 +4,7 @@ import ( "fmt" "regexp" "strconv" + "time" "github.com/grafana/grafana/pkg/components/simplejson" @@ -18,6 +19,8 @@ type Rule struct { Frequency int64 Name string Message string + LastStateChange time.Time + DebounceDuration time.Duration NoDataState m.NoDataOption ExecutionErrorState m.ExecutionErrorOption State m.AlertStateType @@ -100,6 +103,8 @@ func NewRuleFromDBAlert(ruleDef *m.Alert) (*Rule, error) { model.Message = ruleDef.Message model.Frequency = ruleDef.Frequency model.State = ruleDef.State + model.LastStateChange = ruleDef.NewStateDate + model.DebounceDuration = time.Minute * 2 // hard coded for now model.NoDataState = m.NoDataOption(ruleDef.Settings.Get("noDataState").MustString("no_data")) model.ExecutionErrorState = m.ExecutionErrorOption(ruleDef.Settings.Get("executionErrorState").MustString("alerting")) model.StateChanges = ruleDef.StateChanges From 2d3a5754891ddc093524dc72138272a72160694f Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 2 Nov 2018 09:00:56 +0100 Subject: [PATCH 005/322] adds db migration for debounce_duration --- pkg/models/alert.go | 27 ++++++++++--------- pkg/services/alerting/rule.go | 2 +- pkg/services/sqlstore/migrations/alert_mig.go | 4 +++ 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/pkg/models/alert.go b/pkg/models/alert.go index ba1fc0779ba..e35ba106688 100644 --- a/pkg/models/alert.go +++ b/pkg/models/alert.go @@ -59,19 +59,20 @@ func (s ExecutionErrorOption) ToAlertState() AlertStateType { } type Alert struct { - Id int64 - Version int64 - OrgId int64 - DashboardId int64 - PanelId int64 - Name string - Message string - Severity string - State AlertStateType - Handler int64 - Silenced bool - ExecutionError string - Frequency int64 + Id int64 + Version int64 + OrgId int64 + DashboardId int64 + PanelId int64 + Name string + Message string + Severity string //Unused + State AlertStateType + Handler int64 //Unused + Silenced bool + ExecutionError string + Frequency int64 + DebounceDuration time.Duration EvalData *simplejson.Json NewStateDate time.Time diff --git a/pkg/services/alerting/rule.go b/pkg/services/alerting/rule.go index 3fb69b48f9f..c9fbddbf393 100644 --- a/pkg/services/alerting/rule.go +++ b/pkg/services/alerting/rule.go @@ -104,7 +104,7 @@ func NewRuleFromDBAlert(ruleDef *m.Alert) (*Rule, error) { model.Frequency = ruleDef.Frequency model.State = ruleDef.State model.LastStateChange = ruleDef.NewStateDate - model.DebounceDuration = time.Minute * 2 // hard coded for now + model.DebounceDuration = time.Duration(ruleDef.DebounceDuration) model.NoDataState = m.NoDataOption(ruleDef.Settings.Get("noDataState").MustString("no_data")) model.ExecutionErrorState = m.ExecutionErrorOption(ruleDef.Settings.Get("executionErrorState").MustString("alerting")) model.StateChanges = ruleDef.StateChanges diff --git a/pkg/services/sqlstore/migrations/alert_mig.go b/pkg/services/sqlstore/migrations/alert_mig.go index 198a47b50ff..f575fb3b02b 100644 --- a/pkg/services/sqlstore/migrations/alert_mig.go +++ b/pkg/services/sqlstore/migrations/alert_mig.go @@ -133,4 +133,8 @@ func addAlertMigrations(mg *Migrator) { mg.AddMigration("create alert_notification_state table v1", NewAddTableMigration(alert_notification_state)) mg.AddMigration("add index alert_notification_state org_id & alert_id & notifier_id", NewAddIndexMigration(alert_notification_state, alert_notification_state.Indices[0])) + + mg.AddMigration("Add decounce_duration to alert table", NewAddColumnMigration(alertV1, &Column{ + Name: "debounce_duration", Type: DB_BigInt, Nullable: true, + })) } From 4526660cb2ad1e5a1b555cf15b4c89c4e471ad6d Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 2 Nov 2018 10:38:02 +0100 Subject: [PATCH 006/322] wire up debounce setting in the ui --- pkg/services/alerting/extractor.go | 27 +- pkg/services/sqlstore/alert.go | 3 +- public/app/features/alerting/AlertTabCtrl.ts | 1 + .../features/alerting/partials/alert_tab.html | 272 +++++++++--------- 4 files changed, 164 insertions(+), 139 deletions(-) diff --git a/pkg/services/alerting/extractor.go b/pkg/services/alerting/extractor.go index edfab2dedee..221d58feaf2 100644 --- a/pkg/services/alerting/extractor.go +++ b/pkg/services/alerting/extractor.go @@ -2,6 +2,7 @@ package alerting import ( "errors" + "time" "fmt" @@ -113,15 +114,25 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json, return nil, ValidationError{Reason: "Could not parse frequency"} } + rawDebouce := jsonAlert.Get("debounceDuration").MustString() + var debounceDuration time.Duration + if rawDebouce != "" { + debounceDuration, err = time.ParseDuration(rawDebouce) + if err != nil { + return nil, ValidationError{Reason: "Could not parse debounceDuration"} + } + } + alert := &m.Alert{ - DashboardId: e.Dash.Id, - OrgId: e.OrgID, - PanelId: panelID, - Id: jsonAlert.Get("id").MustInt64(), - Name: jsonAlert.Get("name").MustString(), - Handler: jsonAlert.Get("handler").MustInt64(), - Message: jsonAlert.Get("message").MustString(), - Frequency: frequency, + DashboardId: e.Dash.Id, + OrgId: e.OrgID, + PanelId: panelID, + Id: jsonAlert.Get("id").MustInt64(), + Name: jsonAlert.Get("name").MustString(), + Handler: jsonAlert.Get("handler").MustInt64(), + Message: jsonAlert.Get("message").MustString(), + Frequency: frequency, + DebounceDuration: debounceDuration, } for _, condition := range jsonAlert.Get("conditions").MustArray() { diff --git a/pkg/services/sqlstore/alert.go b/pkg/services/sqlstore/alert.go index 2f17402b80c..88ffa3a9b4e 100644 --- a/pkg/services/sqlstore/alert.go +++ b/pkg/services/sqlstore/alert.go @@ -193,7 +193,8 @@ func updateAlerts(existingAlerts []*m.Alert, cmd *m.SaveAlertsCommand, sess *DBS if alertToUpdate.ContainsUpdates(alert) { alert.Updated = timeNow() alert.State = alertToUpdate.State - sess.MustCols("message") + sess.MustCols("message", "debounce_duration") + _, err := sess.ID(alert.Id).Update(alert) if err != nil { return err diff --git a/public/app/features/alerting/AlertTabCtrl.ts b/public/app/features/alerting/AlertTabCtrl.ts index 146b7026353..43752d0a8c9 100644 --- a/public/app/features/alerting/AlertTabCtrl.ts +++ b/public/app/features/alerting/AlertTabCtrl.ts @@ -169,6 +169,7 @@ export class AlertTabCtrl { alert.frequency = alert.frequency || '1m'; alert.handler = alert.handler || 1; alert.notifications = alert.notifications || []; + alert.debounceDuration = alert.debounceDuration || '5m'; const defaultName = this.panel.title + ' alert'; alert.name = alert.name || defaultName; diff --git a/public/app/features/alerting/partials/alert_tab.html b/public/app/features/alerting/partials/alert_tab.html index cb101672aa4..de5fc4df382 100644 --- a/public/app/features/alerting/partials/alert_tab.html +++ b/public/app/features/alerting/partials/alert_tab.html @@ -1,147 +1,159 @@
- -
-
-
- {{ctrl.error}} -
+
+
+
+ {{ctrl.error}} +
-
-
Alert Config
-
- Name - - Evaluate every - -
-
+
+
Alert Config
+
+ Name + +
+
+
+ Evaluate every + +
+
+ + + + Configuring this value means that an alert rule have to be firing for atleast this duration before changing state. + This should reduce false positive alerts and avoid flapping alerts. + +
+
+
-
-
Conditions
-
-
- - WHEN -
-
- - - OF -
-
- - -
-
- - - - -
-
- -
-
+
+
Conditions
+
+
+ + WHEN +
+
+ + + OF +
+
+ + +
+
+ + + + +
+
+ +
+
-
- -
-
+
+ +
+
-
-
- If no data or all values are null - SET STATE TO -
- -
-
+
+
+ If no data or all values are null + SET STATE TO +
+ +
+
-
- If execution error or timeout - SET STATE TO -
- -
-
+
+ If execution error or timeout + SET STATE TO +
+ +
+
-
- -
-
+
+ +
+
-
- Evaluating rule -
+
+ Evaluating rule +
-
- -
-
+
+ +
+
-
-
Notifications
-
-
- Send to - -  {{nc.name}}  - - - -
-
-
- Message - -
-
+
+
Notifications
+
+
+ Send to + +  {{nc.name}}  + + + +
+
+
+ Message + +
+
-
- -
- State history (last 50 state changes) -
+
+ +
+ State history (last 50 state changes) +
-
-
- No state changes recorded -
+
+
+ No state changes recorded +
  1. From 6f748d8a96ee5a1ee21fb0f9c9309724086252eb Mon Sep 17 00:00:00 2001 From: bergquist Date: Fri, 2 Nov 2018 13:53:47 +0100 Subject: [PATCH 007/322] fixes go meta lint issue --- pkg/services/alerting/rule.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/services/alerting/rule.go b/pkg/services/alerting/rule.go index c9fbddbf393..ac1885fb380 100644 --- a/pkg/services/alerting/rule.go +++ b/pkg/services/alerting/rule.go @@ -104,7 +104,7 @@ func NewRuleFromDBAlert(ruleDef *m.Alert) (*Rule, error) { model.Frequency = ruleDef.Frequency model.State = ruleDef.State model.LastStateChange = ruleDef.NewStateDate - model.DebounceDuration = time.Duration(ruleDef.DebounceDuration) + model.DebounceDuration = ruleDef.DebounceDuration model.NoDataState = m.NoDataOption(ruleDef.Settings.Get("noDataState").MustString("no_data")) model.ExecutionErrorState = m.ExecutionErrorOption(ruleDef.Settings.Get("executionErrorState").MustString("alerting")) model.StateChanges = ruleDef.StateChanges From d25284a36441ef3ec2f1e8953acbd7fb4680f7a9 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 5 Nov 2018 10:23:43 +0100 Subject: [PATCH 008/322] introduce state `unknown` for rules that have not been evaluated yet --- pkg/api/alerting.go | 2 +- pkg/models/alert.go | 8 +++++++- pkg/services/alerting/eval_context.go | 5 +++++ pkg/services/alerting/notifiers/base.go | 5 +++++ pkg/services/alerting/notifiers/base_test.go | 16 ++++++++++++++++ pkg/services/sqlstore/alert.go | 6 +++--- pkg/services/sqlstore/alert_test.go | 4 ++-- public/app/features/alerting/state/alertDef.ts | 7 +++++++ 8 files changed, 46 insertions(+), 7 deletions(-) diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index a936d696207..b007b3a3492 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -291,7 +291,7 @@ func PauseAlert(c *m.ReqContext, dto dtos.PauseAlertCommand) Response { return Error(500, "", err) } - var response m.AlertStateType = m.AlertStatePending + var response m.AlertStateType = m.AlertStateUnknown pausedState := "un-paused" if cmd.Paused { response = m.AlertStatePaused diff --git a/pkg/models/alert.go b/pkg/models/alert.go index e35ba106688..37f40134796 100644 --- a/pkg/models/alert.go +++ b/pkg/models/alert.go @@ -19,6 +19,7 @@ const ( AlertStateAlerting AlertStateType = "alerting" AlertStateOK AlertStateType = "ok" AlertStatePending AlertStateType = "pending" + AlertStateUnknown AlertStateType = "unknown" ) const ( @@ -39,7 +40,12 @@ var ( ) func (s AlertStateType) IsValid() bool { - return s == AlertStateOK || s == AlertStateNoData || s == AlertStatePaused || s == AlertStatePending + return s == AlertStateOK || + s == AlertStateNoData || + s == AlertStatePaused || + s == AlertStatePending || + s == AlertStateAlerting || + s == AlertStateUnknown } func (s NoDataOption) IsValid() bool { diff --git a/pkg/services/alerting/eval_context.go b/pkg/services/alerting/eval_context.go index 49e28bbf5ec..8986af85406 100644 --- a/pkg/services/alerting/eval_context.go +++ b/pkg/services/alerting/eval_context.go @@ -68,6 +68,11 @@ func (c *EvalContext) GetStateModel() *StateDescription { Color: "#D63232", Text: "Alerting", } + case m.AlertStateUnknown: + return &StateDescription{ + Color: "888888", + Text: "Unknown", + } default: panic("Unknown rule state for alert notifications " + c.Rule.State) } diff --git a/pkg/services/alerting/notifiers/base.go b/pkg/services/alerting/notifiers/base.go index d141d6cd257..35d3ff518a0 100644 --- a/pkg/services/alerting/notifiers/base.go +++ b/pkg/services/alerting/notifiers/base.go @@ -67,6 +67,11 @@ func (n *NotifierBase) ShouldNotify(ctx context.Context, context *alerting.EvalC } // Do not notify when we become OK for the first time. + if context.PrevAlertState == models.AlertStateUnknown && context.Rule.State == models.AlertStateOK { + return false + } + + // Do not notify when we become OK from pending if context.PrevAlertState == models.AlertStatePending && context.Rule.State == models.AlertStateOK { return false } diff --git a/pkg/services/alerting/notifiers/base_test.go b/pkg/services/alerting/notifiers/base_test.go index 5062828cb4f..388c2db17ee 100644 --- a/pkg/services/alerting/notifiers/base_test.go +++ b/pkg/services/alerting/notifiers/base_test.go @@ -132,6 +132,22 @@ func TestShouldSendAlertNotification(t *testing.T) { prevState: m.AlertStateOK, state: &m.AlertNotificationState{State: m.AlertNotificationStatePending, UpdatedAt: tnow.Add(-2 * time.Minute).Unix()}, + expect: true, + }, + { + name: "unknown -> ok", + prevState: m.AlertStateUnknown, + newState: m.AlertStateOK, + state: &m.AlertNotificationState{}, + + expect: false, + }, + { + name: "unknown -> alerting", + prevState: m.AlertStateUnknown, + newState: m.AlertStateAlerting, + state: &m.AlertNotificationState{}, + expect: true, }, } diff --git a/pkg/services/sqlstore/alert.go b/pkg/services/sqlstore/alert.go index 88ffa3a9b4e..78a71cc8497 100644 --- a/pkg/services/sqlstore/alert.go +++ b/pkg/services/sqlstore/alert.go @@ -205,7 +205,7 @@ func updateAlerts(existingAlerts []*m.Alert, cmd *m.SaveAlertsCommand, sess *DBS } else { alert.Updated = timeNow() alert.Created = timeNow() - alert.State = m.AlertStatePending + alert.State = m.AlertStateUnknown alert.NewStateDate = timeNow() _, err := sess.Insert(alert) @@ -300,7 +300,7 @@ func PauseAlert(cmd *m.PauseAlertCommand) error { params = append(params, string(m.AlertStatePaused)) params = append(params, timeNow()) } else { - params = append(params, string(m.AlertStatePending)) + params = append(params, string(m.AlertStateUnknown)) params = append(params, timeNow()) } @@ -324,7 +324,7 @@ func PauseAllAlerts(cmd *m.PauseAllAlertCommand) error { if cmd.Paused { newState = string(m.AlertStatePaused) } else { - newState = string(m.AlertStatePending) + newState = string(m.AlertStateUnknown) } res, err := sess.Exec(`UPDATE alert SET state = ?, new_state_date = ?`, newState, timeNow()) diff --git a/pkg/services/sqlstore/alert_test.go b/pkg/services/sqlstore/alert_test.go index d97deb45f0e..40867e96b4d 100644 --- a/pkg/services/sqlstore/alert_test.go +++ b/pkg/services/sqlstore/alert_test.go @@ -109,7 +109,7 @@ func TestAlertingDataAccess(t *testing.T) { So(alert.DashboardId, ShouldEqual, testDash.Id) So(alert.PanelId, ShouldEqual, 1) So(alert.Name, ShouldEqual, "Alerting title") - So(alert.State, ShouldEqual, "pending") + So(alert.State, ShouldEqual, m.AlertStateUnknown) So(alert.NewStateDate, ShouldNotBeNil) So(alert.EvalData, ShouldNotBeNil) So(alert.EvalData.Get("test").MustString(), ShouldEqual, "test") @@ -154,7 +154,7 @@ func TestAlertingDataAccess(t *testing.T) { So(query.Result[0].Name, ShouldEqual, "Name") Convey("Alert state should not be updated", func() { - So(query.Result[0].State, ShouldEqual, "pending") + So(query.Result[0].State, ShouldEqual, m.AlertStateUnknown) }) }) diff --git a/public/app/features/alerting/state/alertDef.ts b/public/app/features/alerting/state/alertDef.ts index 11d2aafaa7f..378be0afb91 100644 --- a/public/app/features/alerting/state/alertDef.ts +++ b/public/app/features/alerting/state/alertDef.ts @@ -99,6 +99,13 @@ function getStateDisplayModel(state) { stateClass: 'alert-state-warning', }; } + case 'unknown': { + return { + text: 'UNKNOWN', + iconClass: 'fa fa-question', + stateClass: 'alert-state-paused', + }; + } } throw { message: 'Unknown alert state' }; From ccd89eee974d08f7f74f7859767e8f32a347e2a5 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 5 Nov 2018 11:05:30 +0100 Subject: [PATCH 009/322] renames `debouceduration` to `for` --- pkg/models/alert.go | 28 +++++++++---------- pkg/services/alerting/eval_context.go | 4 +-- pkg/services/alerting/eval_context_test.go | 8 +++--- pkg/services/alerting/extractor.go | 28 +++++++++---------- pkg/services/alerting/rule.go | 4 +-- pkg/services/sqlstore/migrations/alert_mig.go | 4 +-- public/app/features/alerting/AlertTabCtrl.ts | 2 +- .../features/alerting/partials/alert_tab.html | 12 ++++---- 8 files changed, 45 insertions(+), 45 deletions(-) diff --git a/pkg/models/alert.go b/pkg/models/alert.go index 37f40134796..760e9eada48 100644 --- a/pkg/models/alert.go +++ b/pkg/models/alert.go @@ -65,20 +65,20 @@ func (s ExecutionErrorOption) ToAlertState() AlertStateType { } type Alert struct { - Id int64 - Version int64 - OrgId int64 - DashboardId int64 - PanelId int64 - Name string - Message string - Severity string //Unused - State AlertStateType - Handler int64 //Unused - Silenced bool - ExecutionError string - Frequency int64 - DebounceDuration time.Duration + Id int64 + Version int64 + OrgId int64 + DashboardId int64 + PanelId int64 + Name string + Message string + Severity string //Unused + State AlertStateType + Handler int64 //Unused + Silenced bool + ExecutionError string + Frequency int64 + For time.Duration EvalData *simplejson.Json NewStateDate time.Time diff --git a/pkg/services/alerting/eval_context.go b/pkg/services/alerting/eval_context.go index 8986af85406..208fe1d188b 100644 --- a/pkg/services/alerting/eval_context.go +++ b/pkg/services/alerting/eval_context.go @@ -132,9 +132,9 @@ func (c *EvalContext) GetNewState() m.AlertStateType { return c.Rule.ExecutionErrorState.ToAlertState() } - if c.Firing && c.Rule.DebounceDuration != 0 { + if c.Firing && c.Rule.For != 0 { since := time.Now().Sub(c.Rule.LastStateChange) - if since > c.Rule.DebounceDuration { + if since > c.Rule.For { return m.AlertStateAlerting } diff --git a/pkg/services/alerting/eval_context_test.go b/pkg/services/alerting/eval_context_test.go index 2abf581d830..cc0bed79d10 100644 --- a/pkg/services/alerting/eval_context_test.go +++ b/pkg/services/alerting/eval_context_test.go @@ -62,7 +62,7 @@ func TestGetStateFromEvalContext(t *testing.T) { ec.PrevAlertState = models.AlertStateOK ec.Firing = true ec.Rule.LastStateChange = time.Now().Add(-time.Minute * 2) - ec.Rule.DebounceDuration = time.Minute * 5 + ec.Rule.For = time.Minute * 5 }, }, { @@ -72,7 +72,7 @@ func TestGetStateFromEvalContext(t *testing.T) { ec.PrevAlertState = models.AlertStateOK ec.Firing = true ec.Rule.LastStateChange = time.Now().Add(-(time.Hour * 5)) - ec.Rule.DebounceDuration = time.Minute * 2 + ec.Rule.For = time.Minute * 2 }, }, { @@ -82,7 +82,7 @@ func TestGetStateFromEvalContext(t *testing.T) { ec.PrevAlertState = models.AlertStateAlerting ec.Firing = true ec.Rule.LastStateChange = time.Now().Add(-time.Minute * 5) - ec.Rule.DebounceDuration = time.Minute * 2 + ec.Rule.For = time.Minute * 2 }, }, { @@ -91,7 +91,7 @@ func TestGetStateFromEvalContext(t *testing.T) { applyFn: func(ec *EvalContext) { ec.PrevAlertState = models.AlertStateOK ec.Rule.LastStateChange = time.Now().Add(-time.Minute * 5) - ec.Rule.DebounceDuration = time.Minute * 2 + ec.Rule.For = time.Minute * 2 }, }, { diff --git a/pkg/services/alerting/extractor.go b/pkg/services/alerting/extractor.go index 221d58feaf2..244dc0a0770 100644 --- a/pkg/services/alerting/extractor.go +++ b/pkg/services/alerting/extractor.go @@ -114,25 +114,25 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json, return nil, ValidationError{Reason: "Could not parse frequency"} } - rawDebouce := jsonAlert.Get("debounceDuration").MustString() - var debounceDuration time.Duration - if rawDebouce != "" { - debounceDuration, err = time.ParseDuration(rawDebouce) + rawFow := jsonAlert.Get("for").MustString() + var forValue time.Duration + if rawFow != "" { + forValue, err = time.ParseDuration(rawFow) if err != nil { - return nil, ValidationError{Reason: "Could not parse debounceDuration"} + return nil, ValidationError{Reason: "Could not parse for"} } } alert := &m.Alert{ - DashboardId: e.Dash.Id, - OrgId: e.OrgID, - PanelId: panelID, - Id: jsonAlert.Get("id").MustInt64(), - Name: jsonAlert.Get("name").MustString(), - Handler: jsonAlert.Get("handler").MustInt64(), - Message: jsonAlert.Get("message").MustString(), - Frequency: frequency, - DebounceDuration: debounceDuration, + DashboardId: e.Dash.Id, + OrgId: e.OrgID, + PanelId: panelID, + Id: jsonAlert.Get("id").MustInt64(), + Name: jsonAlert.Get("name").MustString(), + Handler: jsonAlert.Get("handler").MustInt64(), + Message: jsonAlert.Get("message").MustString(), + Frequency: frequency, + For: forValue, } for _, condition := range jsonAlert.Get("conditions").MustArray() { diff --git a/pkg/services/alerting/rule.go b/pkg/services/alerting/rule.go index ac1885fb380..d2a505145ac 100644 --- a/pkg/services/alerting/rule.go +++ b/pkg/services/alerting/rule.go @@ -20,7 +20,7 @@ type Rule struct { Name string Message string LastStateChange time.Time - DebounceDuration time.Duration + For time.Duration NoDataState m.NoDataOption ExecutionErrorState m.ExecutionErrorOption State m.AlertStateType @@ -104,7 +104,7 @@ func NewRuleFromDBAlert(ruleDef *m.Alert) (*Rule, error) { model.Frequency = ruleDef.Frequency model.State = ruleDef.State model.LastStateChange = ruleDef.NewStateDate - model.DebounceDuration = ruleDef.DebounceDuration + model.For = ruleDef.For model.NoDataState = m.NoDataOption(ruleDef.Settings.Get("noDataState").MustString("no_data")) model.ExecutionErrorState = m.ExecutionErrorOption(ruleDef.Settings.Get("executionErrorState").MustString("alerting")) model.StateChanges = ruleDef.StateChanges diff --git a/pkg/services/sqlstore/migrations/alert_mig.go b/pkg/services/sqlstore/migrations/alert_mig.go index f575fb3b02b..b5aeb26483c 100644 --- a/pkg/services/sqlstore/migrations/alert_mig.go +++ b/pkg/services/sqlstore/migrations/alert_mig.go @@ -134,7 +134,7 @@ func addAlertMigrations(mg *Migrator) { mg.AddMigration("add index alert_notification_state org_id & alert_id & notifier_id", NewAddIndexMigration(alert_notification_state, alert_notification_state.Indices[0])) - mg.AddMigration("Add decounce_duration to alert table", NewAddColumnMigration(alertV1, &Column{ - Name: "debounce_duration", Type: DB_BigInt, Nullable: true, + mg.AddMigration("Add for to alert table", NewAddColumnMigration(alertV1, &Column{ + Name: "for", Type: DB_BigInt, Nullable: true, })) } diff --git a/public/app/features/alerting/AlertTabCtrl.ts b/public/app/features/alerting/AlertTabCtrl.ts index 43752d0a8c9..758b3273d1a 100644 --- a/public/app/features/alerting/AlertTabCtrl.ts +++ b/public/app/features/alerting/AlertTabCtrl.ts @@ -169,7 +169,7 @@ export class AlertTabCtrl { alert.frequency = alert.frequency || '1m'; alert.handler = alert.handler || 1; alert.notifications = alert.notifications || []; - alert.debounceDuration = alert.debounceDuration || '5m'; + alert.for = alert.for || '5m'; const defaultName = this.panel.title + ' alert'; alert.name = alert.name || defaultName; diff --git a/public/app/features/alerting/partials/alert_tab.html b/public/app/features/alerting/partials/alert_tab.html index de5fc4df382..676a1d32937 100644 --- a/public/app/features/alerting/partials/alert_tab.html +++ b/public/app/features/alerting/partials/alert_tab.html @@ -28,16 +28,16 @@
    Alert Config
    Name - +
    - Evaluate every - + Evaluate every +
    -
    - - +
    + + Configuring this value means that an alert rule have to be firing for atleast this duration before changing state. This should reduce false positive alerts and avoid flapping alerts. From ae2d536740136ba538b5d24605b48905dacfdc8e Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 5 Nov 2018 13:14:02 +0100 Subject: [PATCH 010/322] adds tests for extracting for property --- pkg/services/alerting/extractor.go | 9 ++++----- pkg/services/alerting/extractor_test.go | 20 ++++++++++++------- .../collapsed-panels.json | 0 .../dash-without-id.json | 0 .../graphite-alert.json | 1 + .../influxdb-alert.json | 0 .../panel-with-id-0.json | 0 .../panels-missing-id.json | 0 .../{test-data => testdata}/v5-dashboard.json | 0 9 files changed, 18 insertions(+), 12 deletions(-) rename pkg/services/alerting/{test-data => testdata}/collapsed-panels.json (100%) rename pkg/services/alerting/{test-data => testdata}/dash-without-id.json (100%) rename pkg/services/alerting/{test-data => testdata}/graphite-alert.json (98%) rename pkg/services/alerting/{test-data => testdata}/influxdb-alert.json (100%) rename pkg/services/alerting/{test-data => testdata}/panel-with-id-0.json (100%) rename pkg/services/alerting/{test-data => testdata}/panels-missing-id.json (100%) rename pkg/services/alerting/{test-data => testdata}/v5-dashboard.json (100%) diff --git a/pkg/services/alerting/extractor.go b/pkg/services/alerting/extractor.go index 244dc0a0770..0d902b388a8 100644 --- a/pkg/services/alerting/extractor.go +++ b/pkg/services/alerting/extractor.go @@ -2,9 +2,8 @@ package alerting import ( "errors" - "time" - "fmt" + "time" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/simplejson" @@ -114,10 +113,10 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json, return nil, ValidationError{Reason: "Could not parse frequency"} } - rawFow := jsonAlert.Get("for").MustString() + rawFor := jsonAlert.Get("for").MustString() var forValue time.Duration - if rawFow != "" { - forValue, err = time.ParseDuration(rawFow) + if rawFor != "" { + forValue, err = time.ParseDuration(rawFor) if err != nil { return nil, ValidationError{Reason: "Could not parse for"} } diff --git a/pkg/services/alerting/extractor_test.go b/pkg/services/alerting/extractor_test.go index e2dc01a1181..d03565ead90 100644 --- a/pkg/services/alerting/extractor_test.go +++ b/pkg/services/alerting/extractor_test.go @@ -3,6 +3,7 @@ package alerting import ( "io/ioutil" "testing" + "time" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/simplejson" @@ -46,7 +47,7 @@ func TestAlertRuleExtraction(t *testing.T) { return nil }) - json, err := ioutil.ReadFile("./test-data/graphite-alert.json") + json, err := ioutil.ReadFile("./testdata/graphite-alert.json") So(err, ShouldBeNil) Convey("Extractor should not modify the original json", func() { @@ -118,6 +119,11 @@ func TestAlertRuleExtraction(t *testing.T) { So(alerts[1].PanelId, ShouldEqual, 4) }) + Convey("should extract for param", func() { + So(alerts[0].For, ShouldEqual, time.Minute*2) + So(alerts[1].For, ShouldEqual, time.Duration(0)) + }) + Convey("should extract name and desc", func() { So(alerts[0].Name, ShouldEqual, "name1") So(alerts[0].Message, ShouldEqual, "desc1") @@ -140,7 +146,7 @@ func TestAlertRuleExtraction(t *testing.T) { }) Convey("Panels missing id should return error", func() { - panelWithoutId, err := ioutil.ReadFile("./test-data/panels-missing-id.json") + panelWithoutId, err := ioutil.ReadFile("./testdata/panels-missing-id.json") So(err, ShouldBeNil) dashJson, err := simplejson.NewJson(panelWithoutId) @@ -156,7 +162,7 @@ func TestAlertRuleExtraction(t *testing.T) { }) Convey("Panel with id set to zero should return error", func() { - panelWithIdZero, err := ioutil.ReadFile("./test-data/panel-with-id-0.json") + panelWithIdZero, err := ioutil.ReadFile("./testdata/panel-with-id-0.json") So(err, ShouldBeNil) dashJson, err := simplejson.NewJson(panelWithIdZero) @@ -172,7 +178,7 @@ func TestAlertRuleExtraction(t *testing.T) { }) Convey("Parse alerts from dashboard without rows", func() { - json, err := ioutil.ReadFile("./test-data/v5-dashboard.json") + json, err := ioutil.ReadFile("./testdata/v5-dashboard.json") So(err, ShouldBeNil) dashJson, err := simplejson.NewJson(json) @@ -192,7 +198,7 @@ func TestAlertRuleExtraction(t *testing.T) { }) Convey("Parse and validate dashboard containing influxdb alert", func() { - json, err := ioutil.ReadFile("./test-data/influxdb-alert.json") + json, err := ioutil.ReadFile("./testdata/influxdb-alert.json") So(err, ShouldBeNil) dashJson, err := simplejson.NewJson(json) @@ -221,7 +227,7 @@ func TestAlertRuleExtraction(t *testing.T) { }) Convey("Should be able to extract collapsed panels", func() { - json, err := ioutil.ReadFile("./test-data/collapsed-panels.json") + json, err := ioutil.ReadFile("./testdata/collapsed-panels.json") So(err, ShouldBeNil) dashJson, err := simplejson.NewJson(json) @@ -242,7 +248,7 @@ func TestAlertRuleExtraction(t *testing.T) { }) Convey("Parse and validate dashboard without id and containing an alert", func() { - json, err := ioutil.ReadFile("./test-data/dash-without-id.json") + json, err := ioutil.ReadFile("./testdata/dash-without-id.json") So(err, ShouldBeNil) dashJSON, err := simplejson.NewJson(json) diff --git a/pkg/services/alerting/test-data/collapsed-panels.json b/pkg/services/alerting/testdata/collapsed-panels.json similarity index 100% rename from pkg/services/alerting/test-data/collapsed-panels.json rename to pkg/services/alerting/testdata/collapsed-panels.json diff --git a/pkg/services/alerting/test-data/dash-without-id.json b/pkg/services/alerting/testdata/dash-without-id.json similarity index 100% rename from pkg/services/alerting/test-data/dash-without-id.json rename to pkg/services/alerting/testdata/dash-without-id.json diff --git a/pkg/services/alerting/test-data/graphite-alert.json b/pkg/services/alerting/testdata/graphite-alert.json similarity index 98% rename from pkg/services/alerting/test-data/graphite-alert.json rename to pkg/services/alerting/testdata/graphite-alert.json index 5f23e224f9a..3cb4ae1dd22 100644 --- a/pkg/services/alerting/test-data/graphite-alert.json +++ b/pkg/services/alerting/testdata/graphite-alert.json @@ -23,6 +23,7 @@ "message": "desc1", "handler": 1, "frequency": "60s", + "for": "2m", "conditions": [ { "type": "query", diff --git a/pkg/services/alerting/test-data/influxdb-alert.json b/pkg/services/alerting/testdata/influxdb-alert.json similarity index 100% rename from pkg/services/alerting/test-data/influxdb-alert.json rename to pkg/services/alerting/testdata/influxdb-alert.json diff --git a/pkg/services/alerting/test-data/panel-with-id-0.json b/pkg/services/alerting/testdata/panel-with-id-0.json similarity index 100% rename from pkg/services/alerting/test-data/panel-with-id-0.json rename to pkg/services/alerting/testdata/panel-with-id-0.json diff --git a/pkg/services/alerting/test-data/panels-missing-id.json b/pkg/services/alerting/testdata/panels-missing-id.json similarity index 100% rename from pkg/services/alerting/test-data/panels-missing-id.json rename to pkg/services/alerting/testdata/panels-missing-id.json diff --git a/pkg/services/alerting/test-data/v5-dashboard.json b/pkg/services/alerting/testdata/v5-dashboard.json similarity index 100% rename from pkg/services/alerting/test-data/v5-dashboard.json rename to pkg/services/alerting/testdata/v5-dashboard.json From 3789583014fc6d1e2de0eb03b479953feea0e696 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 5 Nov 2018 13:51:35 +0100 Subject: [PATCH 011/322] for: use 0m as default for existing alerts and 5m for new --- public/app/features/alerting/AlertTabCtrl.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/app/features/alerting/AlertTabCtrl.ts b/public/app/features/alerting/AlertTabCtrl.ts index 758b3273d1a..2efd3c062c4 100644 --- a/public/app/features/alerting/AlertTabCtrl.ts +++ b/public/app/features/alerting/AlertTabCtrl.ts @@ -169,7 +169,7 @@ export class AlertTabCtrl { alert.frequency = alert.frequency || '1m'; alert.handler = alert.handler || 1; alert.notifications = alert.notifications || []; - alert.for = alert.for || '5m'; + alert.for = alert.for || '0m'; const defaultName = this.panel.title + ' alert'; alert.name = alert.name || defaultName; @@ -355,6 +355,7 @@ export class AlertTabCtrl { enable() { this.panel.alert = {}; this.initModel(); + this.panel.alert.for = '5m'; //default value for new alerts. for existing alerts we use 0m to avoid breaking changes } evaluatorParamsChanged() { From 0ddfd92f8c5fa9c3a4a2204de21f76db21c8c73c Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 7 Nov 2018 22:30:23 +0100 Subject: [PATCH 012/322] adds debounce duration for alert dashboards in ha_test --- devenv/docker/ha_test/docker-compose.yaml | 2 +- devenv/docker/ha_test/grafana/provisioning/alerts.jsonnet | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/devenv/docker/ha_test/docker-compose.yaml b/devenv/docker/ha_test/docker-compose.yaml index ce8630d88a4..1195e2a977c 100644 --- a/devenv/docker/ha_test/docker-compose.yaml +++ b/devenv/docker/ha_test/docker-compose.yaml @@ -9,7 +9,7 @@ services: - /var/run/docker.sock:/tmp/docker.sock:ro db: - image: mysql + image: mysql:5.6 environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: grafana diff --git a/devenv/docker/ha_test/grafana/provisioning/alerts.jsonnet b/devenv/docker/ha_test/grafana/provisioning/alerts.jsonnet index 86ded7e79d6..e9b8abfbb9c 100644 --- a/devenv/docker/ha_test/grafana/provisioning/alerts.jsonnet +++ b/devenv/docker/ha_test/grafana/provisioning/alerts.jsonnet @@ -39,6 +39,7 @@ local alertDashboardTemplate = { "executionErrorState": "alerting", "frequency": "10s", "handler": 1, + "for": "1m", "name": "bulk alerting", "noDataState": "no_data", "notifications": [ From 975f0aa064634c9181418cf50e8598d5a0cb747a Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 5 Nov 2018 11:25:37 +0100 Subject: [PATCH 013/322] alerting: adds docs about the for setting --- docs/sources/alerting/rules.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/sources/alerting/rules.md b/docs/sources/alerting/rules.md index 488619055e2..2e4a7e5c191 100644 --- a/docs/sources/alerting/rules.md +++ b/docs/sources/alerting/rules.md @@ -39,7 +39,7 @@ Currently alerting supports a limited form of high availability. Since v4.2.0 of ## Rule Config -{{< imgbox max-width="40%" img="/img/docs/v4/alerting_conditions.png" caption="Alerting Conditions" >}} + Currently only the graph panel supports alert rules but this will be added to the **Singlestat** and **Table** panels as well in a future release. @@ -48,6 +48,16 @@ panels as well in a future release. Here you can specify the name of the alert rule and how often the scheduler should evaluate the alert rule. +### For + +> This setting is available in Grafana 5.4 and above. + +The `For` setting allows you to specify a duration for which the alert has to violate the threshold before switching to `Alerting` state and sending notifications. This is useful when you want to reduce the amount of false positive alerts and problems from which the system selfheal. Which in case a human does not need to be woken up. + +Typically, it's always a good idea to use this setting since its often worse to get false positive than wait a few minutes before the alert notification triggers. + +{{< imgbox max-width="40%" img="/img/docs/v4/alerting_conditions.png" caption="Alerting Conditions" >}} + ### Conditions Currently the only condition type that exists is a `Query` condition that allows you to @@ -57,11 +67,11 @@ specify a query letter, time range and an aggregation function. ### Query condition example ```sql -avg() OF query(A, 5m, now) IS BELOW 14 +avg() OF query(A, 15m, now) IS BELOW 14 ``` - `avg()` Controls how the values for **each** series should be reduced to a value that can be compared against the threshold. Click on the function to change it to another aggregation function. -- `query(A, 5m, now)` The letter defines what query to execute from the **Metrics** tab. The second two parameters define the time range, `5m, now` means 5 minutes ago to now. You can also do `10m, now-2m` to define a time range that will be 10 minutes ago to 2 minutes ago. This is useful if you want to ignore the last 2 minutes of data. +- `query(A, 15m, now)` The letter defines what query to execute from the **Metrics** tab. The second two parameters define the time range, `15m, now` means 5 minutes ago to now. You can also do `10m, now-2m` to define a time range that will be 10 minutes ago to 2 minutes ago. This is useful if you want to ignore the last 2 minutes of data. - `IS BELOW 14` Defines the type of threshold and the threshold value. You can click on `IS BELOW` to change the type of threshold. The query used in an alert rule cannot contain any template variables. Currently we only support `AND` and `OR` operators between conditions and they are executed serially. From aa1b80fe45b2a8fdf7afcb3e62e6ba54cbed112a Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 8 Nov 2018 14:16:58 +0100 Subject: [PATCH 014/322] docs: improve helper test for `For` --- docs/sources/alerting/rules.md | 2 +- public/app/features/alerting/partials/alert_tab.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sources/alerting/rules.md b/docs/sources/alerting/rules.md index 2e4a7e5c191..387132ecfeb 100644 --- a/docs/sources/alerting/rules.md +++ b/docs/sources/alerting/rules.md @@ -52,7 +52,7 @@ 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. -The `For` setting allows you to specify a duration for which the alert has to violate the threshold before switching to `Alerting` state and sending notifications. This is useful when you want to reduce the amount of false positive alerts and problems from which the system selfheal. Which in case a human does not need to be woken up. +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 its often worse to get false positive than wait a few minutes before the alert notification triggers. diff --git a/public/app/features/alerting/partials/alert_tab.html b/public/app/features/alerting/partials/alert_tab.html index 676a1d32937..2b5e9bdf0cb 100644 --- a/public/app/features/alerting/partials/alert_tab.html +++ b/public/app/features/alerting/partials/alert_tab.html @@ -39,8 +39,8 @@ - Configuring this value means that an alert rule have to be firing for atleast this duration before changing state. - This should reduce false positive alerts and avoid flapping alerts. + 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.
    From efd3343d19fb94ebc5e4fecdc829c98f4b470643 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 24 Oct 2018 14:08:37 +0200 Subject: [PATCH 015/322] stackdriver: add basic directive for loading react plugin components --- public/app/features/plugins/all.ts | 1 + .../plugins/plugin_react_component.tsx | 47 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 public/app/features/plugins/plugin_react_component.tsx diff --git a/public/app/features/plugins/all.ts b/public/app/features/plugins/all.ts index c9fb250266c..bd94a82b31e 100644 --- a/public/app/features/plugins/all.ts +++ b/public/app/features/plugins/all.ts @@ -4,3 +4,4 @@ import './import_list/import_list'; import './ds_edit_ctrl'; import './datasource_srv'; import './plugin_component'; +import './plugin_react_component'; diff --git a/public/app/features/plugins/plugin_react_component.tsx b/public/app/features/plugins/plugin_react_component.tsx new file mode 100644 index 00000000000..d20679c9681 --- /dev/null +++ b/public/app/features/plugins/plugin_react_component.tsx @@ -0,0 +1,47 @@ +import _ from 'lodash'; +import coreModule from 'app/core/core_module'; +import { importPluginModule } from './plugin_loader'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Provider } from 'react-redux'; + +function WrapInProvider(Component, props) { + return ( + + + + ); +} + +/** @ngInject */ +function pluginReactDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $templateCache, $timeout) { + async function getModule(scope, attrs) { + switch (attrs.type) { + case 'template-query-ctrl': { + const dsModule = await importPluginModule(scope.currentDatasource.meta.module); + console.log(dsModule); + return dsModule.TemplateQueryCtrl; + } + default: { + return $q.reject({ + message: 'Could not find component type: ' + attrs.type, + }); + } + } + } + + return { + restrict: 'E', + link: async (scope, elem, attrs) => { + const component = await getModule(scope, attrs); + const props = { datasourceSrv }; + ReactDOM.render(WrapInProvider(component, props), elem[0]); + + scope.$on('$destroy', () => { + ReactDOM.unmountComponentAtNode(elem[0]); + }); + }, + }; +} + +coreModule.directive('pluginReactComponent', pluginReactDirectiveLoader); From 94ac522109cba9b431017cf26fe4fa0b78a42c08 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 24 Oct 2018 14:10:20 +0200 Subject: [PATCH 016/322] stackdriver: make it possible to load react plugin components from template query page --- public/app/features/templating/editor_ctrl.ts | 5 +++++ public/app/features/templating/partials/editor.html | 9 ++++++++- public/app/types/plugins.ts | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/public/app/features/templating/editor_ctrl.ts b/public/app/features/templating/editor_ctrl.ts index cef7c9cc912..4bd00a9c1ee 100644 --- a/public/app/features/templating/editor_ctrl.ts +++ b/public/app/features/templating/editor_ctrl.ts @@ -171,6 +171,11 @@ export class VariableEditorCtrl { $scope.showMoreOptions = () => { $scope.optionsLimit += 20; }; + + $scope.datasourceChanged = async () => { + $scope.currentDatasource = await datasourceSrv.get($scope.current.datasource); + console.log($scope.currentDatasource); + }; } } diff --git a/public/app/features/templating/partials/editor.html b/public/app/features/templating/partials/editor.html index c4463972177..2655a7708e8 100644 --- a/public/app/features/templating/partials/editor.html +++ b/public/app/features/templating/partials/editor.html @@ -170,11 +170,12 @@
    Data source
    -
    +
    Refresh @@ -187,6 +188,12 @@
+ + + + + +
Query diff --git a/public/app/types/plugins.ts b/public/app/types/plugins.ts index 817777669d8..a22879daa70 100644 --- a/public/app/types/plugins.ts +++ b/public/app/types/plugins.ts @@ -6,6 +6,7 @@ export interface PluginExports { QueryCtrl?: any; ConfigCtrl?: any; AnnotationsQueryCtrl?: any; + TemplateQueryCtrl?: any; ExploreQueryField?: any; ExploreStartPage?: any; From 0792c182cc2fe6aa120ee2942c041dbd744334c5 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 24 Oct 2018 14:11:10 +0200 Subject: [PATCH 017/322] stackdriver: add react component for template query editor --- .../plugins/datasource/stackdriver/module.ts | 2 ++ .../stackdriver/templateQueryCtrl.tsx | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 public/app/plugins/datasource/stackdriver/templateQueryCtrl.tsx diff --git a/public/app/plugins/datasource/stackdriver/module.ts b/public/app/plugins/datasource/stackdriver/module.ts index 183c5c9ff88..a23ea4e6eda 100644 --- a/public/app/plugins/datasource/stackdriver/module.ts +++ b/public/app/plugins/datasource/stackdriver/module.ts @@ -2,10 +2,12 @@ import StackdriverDatasource from './datasource'; import { StackdriverQueryCtrl } from './query_ctrl'; import { StackdriverConfigCtrl } from './config_ctrl'; import { StackdriverAnnotationsQueryCtrl } from './annotations_query_ctrl'; +import { StackdriverTemplateQueryCtrl } from './templateQueryCtrl'; export { StackdriverDatasource as Datasource, StackdriverQueryCtrl as QueryCtrl, StackdriverConfigCtrl as ConfigCtrl, StackdriverAnnotationsQueryCtrl as AnnotationsQueryCtrl, + StackdriverTemplateQueryCtrl as TemplateQueryCtrl, }; diff --git a/public/app/plugins/datasource/stackdriver/templateQueryCtrl.tsx b/public/app/plugins/datasource/stackdriver/templateQueryCtrl.tsx new file mode 100644 index 00000000000..5236878bc80 --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/templateQueryCtrl.tsx @@ -0,0 +1,17 @@ +import React, { PureComponent } from 'react'; + +interface Props {} + +export class StackdriverTemplateQueryCtrl extends PureComponent { + constructor(props) { + super(props); + } + + componentDidMount() { + console.log('componentDidMount'); + } + + render() { + return

Hello Stackdriver Template Query

; + } +} From 637b91ab8d21d98f7ac72886cd1c3fb89a0007df Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 24 Oct 2018 16:26:05 +0200 Subject: [PATCH 018/322] stackdriver: conditional template component rendering --- public/app/features/plugins/all.ts | 2 +- .../pluginTemplateQueryComponentLoader.tsx | 41 ++++++++ .../plugins/plugin_react_component.tsx | 47 --------- .../templating/defaultTemplateQueryCtrl.tsx | 32 +++++++ .../features/templating/partials/editor.html | 96 ++++++++++--------- .../stackdriver/templateQueryCtrl.tsx | 7 +- 6 files changed, 132 insertions(+), 93 deletions(-) create mode 100644 public/app/features/plugins/pluginTemplateQueryComponentLoader.tsx delete mode 100644 public/app/features/plugins/plugin_react_component.tsx create mode 100644 public/app/features/templating/defaultTemplateQueryCtrl.tsx diff --git a/public/app/features/plugins/all.ts b/public/app/features/plugins/all.ts index bd94a82b31e..5dc6f3783bd 100644 --- a/public/app/features/plugins/all.ts +++ b/public/app/features/plugins/all.ts @@ -4,4 +4,4 @@ import './import_list/import_list'; import './ds_edit_ctrl'; import './datasource_srv'; import './plugin_component'; -import './plugin_react_component'; +import './pluginTemplateQueryComponentLoader'; diff --git a/public/app/features/plugins/pluginTemplateQueryComponentLoader.tsx b/public/app/features/plugins/pluginTemplateQueryComponentLoader.tsx new file mode 100644 index 00000000000..c45cde271c9 --- /dev/null +++ b/public/app/features/plugins/pluginTemplateQueryComponentLoader.tsx @@ -0,0 +1,41 @@ +import _ from 'lodash'; +import coreModule from 'app/core/core_module'; +import { importPluginModule } from './plugin_loader'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Provider } from 'react-redux'; +import DefaultTemplateQueryCtrl from '../templating/defaultTemplateQueryCtrl'; + +function WrapInProvider(Component, props) { + return ( + + + + ); +} + +async function loadComponent(module) { + const component = await importPluginModule(module); + if (!component.TemplateQueryCtrl) { + return DefaultTemplateQueryCtrl; + } else { + return component.TemplateQueryCtrl; + } +} + +/** @ngInject */ +function pluginTemplateQueryComponentLoader(datasourceSrv) { + return { + restrict: 'E', + link: async (scope, elem) => { + const component = await loadComponent(scope.currentDatasource.meta.module); + const props = { datasourceSrv, query: scope.current.query, isValid: scope.current.isValid }; + ReactDOM.render(WrapInProvider(component, props), elem[0]); + scope.$on('$destroy', () => { + ReactDOM.unmountComponentAtNode(elem[0]); + }); + }, + }; +} + +coreModule.directive('pluginTemplateQueryComponent', pluginTemplateQueryComponentLoader); diff --git a/public/app/features/plugins/plugin_react_component.tsx b/public/app/features/plugins/plugin_react_component.tsx deleted file mode 100644 index d20679c9681..00000000000 --- a/public/app/features/plugins/plugin_react_component.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import _ from 'lodash'; -import coreModule from 'app/core/core_module'; -import { importPluginModule } from './plugin_loader'; -import React from 'react'; -import ReactDOM from 'react-dom'; -import { Provider } from 'react-redux'; - -function WrapInProvider(Component, props) { - return ( - - - - ); -} - -/** @ngInject */ -function pluginReactDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $templateCache, $timeout) { - async function getModule(scope, attrs) { - switch (attrs.type) { - case 'template-query-ctrl': { - const dsModule = await importPluginModule(scope.currentDatasource.meta.module); - console.log(dsModule); - return dsModule.TemplateQueryCtrl; - } - default: { - return $q.reject({ - message: 'Could not find component type: ' + attrs.type, - }); - } - } - } - - return { - restrict: 'E', - link: async (scope, elem, attrs) => { - const component = await getModule(scope, attrs); - const props = { datasourceSrv }; - ReactDOM.render(WrapInProvider(component, props), elem[0]); - - scope.$on('$destroy', () => { - ReactDOM.unmountComponentAtNode(elem[0]); - }); - }, - }; -} - -coreModule.directive('pluginReactComponent', pluginReactDirectiveLoader); diff --git a/public/app/features/templating/defaultTemplateQueryCtrl.tsx b/public/app/features/templating/defaultTemplateQueryCtrl.tsx new file mode 100644 index 00000000000..56334590620 --- /dev/null +++ b/public/app/features/templating/defaultTemplateQueryCtrl.tsx @@ -0,0 +1,32 @@ +import React, { PureComponent } from 'react'; + +interface Props { + query: string; +} + +export default class DefaultTemplateQueryCtrl extends PureComponent { + constructor(props) { + super(props); + } + + componentDidMount() { + console.log('componentDidMount'); + } + + render() { + return ( +
+ Query + +
+ ); + } +} diff --git a/public/app/features/templating/partials/editor.html b/public/app/features/templating/partials/editor.html index 2655a7708e8..d217fd4d930 100644 --- a/public/app/features/templating/partials/editor.html +++ b/public/app/features/templating/partials/editor.html @@ -17,14 +17,16 @@
What do variables do?
-

Variables enable more interactive and dynamic dashboards. Instead of hard-coding things like server or sensor names - in your metric queries you can use variables in their place. Variables are shown as dropdown select boxes at the top of - the dashboard. These dropdowns make it easy to change the data being displayed in your dashboard. +

Variables enable more interactive and dynamic dashboards. Instead of hard-coding things like server or sensor + names + in your metric queries you can use variables in their place. Variables are shown as dropdown select boxes at the + top of + the dashboard. These dropdowns make it easy to change the data being displayed in your dashboard. - Check out the - - Templating documentation - for more information. + Check out the + + Templating documentation + for more information.

@@ -32,7 +34,7 @@
@@ -77,7 +79,8 @@
Name - +
@@ -87,13 +90,15 @@
- +
- Template names cannot begin with '__', that's reserved for Grafana's global variables + Template names cannot begin with '__', that's reserved for + Grafana's global variables
@@ -127,14 +132,16 @@ Step count How many times should the current time range be divided to calculate the value
- +
Min interval The calculated value will not go below this threshold - +
@@ -143,7 +150,8 @@
Custom Options
Values separated by comma - +
@@ -170,7 +178,8 @@
Data source
-
@@ -188,16 +197,16 @@
- + - - + + -
+
Regex @@ -205,7 +214,8 @@ Optional, if you want to extract part of a series name or metric node segment. - +
@@ -215,7 +225,8 @@
- +
@@ -226,7 +237,8 @@
- +
@@ -241,7 +253,8 @@ - + @@ -250,7 +263,8 @@
Data source
-
@@ -260,18 +274,11 @@
Selection Options
- + - +
@@ -286,11 +293,13 @@
Tags query - +
  • Tag values query
  • - +
    @@ -298,11 +307,11 @@
    Preview of values
    - {{option.text}} -
    -
    - Show more -
    + {{option.text}} +
    +
    + Show more +
    @@ -317,4 +326,3 @@ - diff --git a/public/app/plugins/datasource/stackdriver/templateQueryCtrl.tsx b/public/app/plugins/datasource/stackdriver/templateQueryCtrl.tsx index 5236878bc80..62f98625981 100644 --- a/public/app/plugins/datasource/stackdriver/templateQueryCtrl.tsx +++ b/public/app/plugins/datasource/stackdriver/templateQueryCtrl.tsx @@ -1,6 +1,11 @@ import React, { PureComponent } from 'react'; +import DatasourceSrv from 'app/features/plugins/datasource_srv'; -interface Props {} +interface Props { + query: string; + datasourceSrv: DatasourceSrv; + isValid: any; +} export class StackdriverTemplateQueryCtrl extends PureComponent { constructor(props) { From 4f7d3fccb7582ada3b5599a4ecc6f053e4006626 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 25 Oct 2018 13:30:39 +0200 Subject: [PATCH 019/322] stackdriver: make sure default template query editor state is propagted to parent angular scope --- .../pluginTemplateQueryComponentLoader.tsx | 20 +++++++------------ .../templating/defaultTemplateQueryCtrl.tsx | 20 +++++++++++++------ public/app/features/templating/editor_ctrl.ts | 10 ++++++++-- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/public/app/features/plugins/pluginTemplateQueryComponentLoader.tsx b/public/app/features/plugins/pluginTemplateQueryComponentLoader.tsx index c45cde271c9..ac7d94a3f15 100644 --- a/public/app/features/plugins/pluginTemplateQueryComponentLoader.tsx +++ b/public/app/features/plugins/pluginTemplateQueryComponentLoader.tsx @@ -1,19 +1,9 @@ -import _ from 'lodash'; import coreModule from 'app/core/core_module'; import { importPluginModule } from './plugin_loader'; import React from 'react'; import ReactDOM from 'react-dom'; -import { Provider } from 'react-redux'; import DefaultTemplateQueryCtrl from '../templating/defaultTemplateQueryCtrl'; -function WrapInProvider(Component, props) { - return ( - - - - ); -} - async function loadComponent(module) { const component = await importPluginModule(module); if (!component.TemplateQueryCtrl) { @@ -28,9 +18,13 @@ function pluginTemplateQueryComponentLoader(datasourceSrv) { return { restrict: 'E', link: async (scope, elem) => { - const component = await loadComponent(scope.currentDatasource.meta.module); - const props = { datasourceSrv, query: scope.current.query, isValid: scope.current.isValid }; - ReactDOM.render(WrapInProvider(component, props), elem[0]); + const Component = await loadComponent(scope.currentDatasource.meta.module); + const props = { + datasourceSrv, + query: scope.current.query, + onChange: scope.onQueryChange, + }; + ReactDOM.render(, elem[0]); scope.$on('$destroy', () => { ReactDOM.unmountComponentAtNode(elem[0]); }); diff --git a/public/app/features/templating/defaultTemplateQueryCtrl.tsx b/public/app/features/templating/defaultTemplateQueryCtrl.tsx index 56334590620..85f0ac7ee7d 100644 --- a/public/app/features/templating/defaultTemplateQueryCtrl.tsx +++ b/public/app/features/templating/defaultTemplateQueryCtrl.tsx @@ -2,15 +2,23 @@ import React, { PureComponent } from 'react'; interface Props { query: string; + onChange: (c: string) => void; } -export default class DefaultTemplateQueryCtrl extends PureComponent { +export default class DefaultTemplateQueryCtrl extends PureComponent { constructor(props) { super(props); + this.state = { value: props.query }; + this.handleChange = this.handleChange.bind(this); + this.handleBlur = this.handleBlur.bind(this); } - componentDidMount() { - console.log('componentDidMount'); + handleChange(event) { + this.setState({ value: event.target.value }); + } + + handleBlur(event) { + this.props.onChange(event.target.value); } render() { @@ -20,10 +28,10 @@ export default class DefaultTemplateQueryCtrl extends PureComponent { diff --git a/public/app/features/templating/editor_ctrl.ts b/public/app/features/templating/editor_ctrl.ts index 4bd00a9c1ee..6bdd71620b3 100644 --- a/public/app/features/templating/editor_ctrl.ts +++ b/public/app/features/templating/editor_ctrl.ts @@ -106,6 +106,11 @@ export class VariableEditorCtrl { }); }; + $scope.onQueryChange = value => { + $scope.current.query = value; + $scope.runQuery(); + }; + $scope.edit = variable => { $scope.current = variable; $scope.currentIsNew = false; @@ -173,8 +178,9 @@ export class VariableEditorCtrl { }; $scope.datasourceChanged = async () => { - $scope.currentDatasource = await datasourceSrv.get($scope.current.datasource); - console.log($scope.currentDatasource); + datasourceSrv.get($scope.current.datasource).then(ds => { + $scope.currentDatasource = ds; + }); }; } } From 1969ad41e82447f480a7b4bb5cf8d41b04b84dbe Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 25 Oct 2018 13:53:02 +0200 Subject: [PATCH 020/322] stackdriver: refactor stackdriver query ctrl --- .../pluginTemplateQueryComponentLoader.tsx | 4 +-- .../stackdriver/templateQueryCtrl.tsx | 28 ++++++++++++++----- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/public/app/features/plugins/pluginTemplateQueryComponentLoader.tsx b/public/app/features/plugins/pluginTemplateQueryComponentLoader.tsx index ac7d94a3f15..9a27643ace5 100644 --- a/public/app/features/plugins/pluginTemplateQueryComponentLoader.tsx +++ b/public/app/features/plugins/pluginTemplateQueryComponentLoader.tsx @@ -14,13 +14,13 @@ async function loadComponent(module) { } /** @ngInject */ -function pluginTemplateQueryComponentLoader(datasourceSrv) { +function pluginTemplateQueryComponentLoader() { return { restrict: 'E', link: async (scope, elem) => { const Component = await loadComponent(scope.currentDatasource.meta.module); const props = { - datasourceSrv, + datasource: scope.currentDatasource, query: scope.current.query, onChange: scope.onQueryChange, }; diff --git a/public/app/plugins/datasource/stackdriver/templateQueryCtrl.tsx b/public/app/plugins/datasource/stackdriver/templateQueryCtrl.tsx index 62f98625981..4900c4b15da 100644 --- a/public/app/plugins/datasource/stackdriver/templateQueryCtrl.tsx +++ b/public/app/plugins/datasource/stackdriver/templateQueryCtrl.tsx @@ -1,22 +1,36 @@ import React, { PureComponent } from 'react'; -import DatasourceSrv from 'app/features/plugins/datasource_srv'; +import StackdriverDatasource from './datasource'; interface Props { + datasource: StackdriverDatasource; query: string; - datasourceSrv: DatasourceSrv; - isValid: any; + onChange: (c: string) => void; } -export class StackdriverTemplateQueryCtrl extends PureComponent { +export class StackdriverTemplateQueryCtrl extends PureComponent { constructor(props) { super(props); } - componentDidMount() { - console.log('componentDidMount'); + async componentDidMount() { + const metricDescriptors = await this.props.datasource.getMetricTypes(this.props.datasource.projectName); + console.log(metricDescriptors); } render() { - return

    Hello Stackdriver Template Query

    ; + return ( +
    + Query + +
    + ); } } From ba4d52e0482d091ecdc70c7fb9f4a6afaf5464ee Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 25 Oct 2018 17:00:32 +0200 Subject: [PATCH 021/322] stackdriver: add selector components for service and metric type --- .../datasource/stackdriver/metricTypes.tsx | 43 +++++++++++ .../datasource/stackdriver/services.tsx | 32 ++++++++ .../stackdriver/templateQueryCtrl.tsx | 73 +++++++++++++++---- 3 files changed, 135 insertions(+), 13 deletions(-) create mode 100644 public/app/plugins/datasource/stackdriver/metricTypes.tsx create mode 100644 public/app/plugins/datasource/stackdriver/services.tsx diff --git a/public/app/plugins/datasource/stackdriver/metricTypes.tsx b/public/app/plugins/datasource/stackdriver/metricTypes.tsx new file mode 100644 index 00000000000..0e844f49439 --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/metricTypes.tsx @@ -0,0 +1,43 @@ +import React, { SFC } from 'react'; +import uniqBy from 'lodash/uniqBy'; + +interface Props { + onMetricTypeChanged: any; + selectedService: string; + metricDescriptors: any[]; +} + +const MetricTypes: SFC = props => { + const extractMetricTypes = () => { + if (!props.selectedService) { + return []; + } + + return props.metricDescriptors.filter(m => m.service === props.selectedService).map(m => ({ + value: m.service, + name: m.displayName, + })); + }; + + uniqBy(props.metricDescriptors, 'service').map(m => ({ + value: m.service, + name: m.serviceShortName, + })); + + return ( +
    + Metric Types +
    + +
    +
    + ); +}; + +export default MetricTypes; diff --git a/public/app/plugins/datasource/stackdriver/services.tsx b/public/app/plugins/datasource/stackdriver/services.tsx new file mode 100644 index 00000000000..e8da82933d2 --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/services.tsx @@ -0,0 +1,32 @@ +import React, { SFC } from 'react'; +import uniqBy from 'lodash/uniqBy'; + +interface Props { + onServiceChange: any; + metricDescriptors: any[]; +} + +const Services: SFC = props => { + const extractServices = () => + uniqBy(props.metricDescriptors, 'service').map(m => ({ + value: m.service, + name: m.serviceShortName, + })); + + return ( +
    + Service +
    + +
    +
    + ); +}; + +export default Services; diff --git a/public/app/plugins/datasource/stackdriver/templateQueryCtrl.tsx b/public/app/plugins/datasource/stackdriver/templateQueryCtrl.tsx index 4900c4b15da..20a2cf28077 100644 --- a/public/app/plugins/datasource/stackdriver/templateQueryCtrl.tsx +++ b/public/app/plugins/datasource/stackdriver/templateQueryCtrl.tsx @@ -1,5 +1,7 @@ import React, { PureComponent } from 'react'; import StackdriverDatasource from './datasource'; +import Services from './services'; +import MetricTypes from './metricTypes'; interface Props { datasource: StackdriverDatasource; @@ -8,29 +10,74 @@ interface Props { } export class StackdriverTemplateQueryCtrl extends PureComponent { + queryTypes: Array<{ value: string; name: string }> = [ + { value: 'services', name: 'Services' }, + { value: 'metricTypes', name: 'Metric Types' }, + { value: 'metricLabels', name: 'Metric labels For Metric Type' }, + ]; + constructor(props) { super(props); + this.handleChange = this.handleChange.bind(this); + this.onServiceChange = this.onServiceChange.bind(this); + this.onMetricTypeChanged = this.onMetricTypeChanged.bind(this); + this.state = { queryType: undefined, metricDescriptors: [], service: undefined, metricType: undefined }; } async componentDidMount() { const metricDescriptors = await this.props.datasource.getMetricTypes(this.props.datasource.projectName); - console.log(metricDescriptors); + this.setState({ metricDescriptors }); + } + + handleChange(event) { + this.setState({ queryType: event.target.value }); + } + + onServiceChange(event) { + this.setState({ service: event.target.value }); + } + + onMetricTypeChanged(event) { + this.setState({ metricType: event.target.value }); + } + + renderSwitch(queryType) { + switch (queryType) { + case 'metricTypes': + return ; + case 'metricLabels': + return ( + + + + + ); + default: + return ''; + } } render() { return ( -
    - Query - -
    + +
    + Query Type +
    + +
    +
    + {this.renderSwitch(this.state.queryType)} +
    ); } } From 4bcf3bf1ffe184cf7a175b8aa274be886f8a899a Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 25 Oct 2018 17:01:13 +0200 Subject: [PATCH 022/322] stackdriver: remove not used code --- public/app/plugins/datasource/stackdriver/metricTypes.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/metricTypes.tsx b/public/app/plugins/datasource/stackdriver/metricTypes.tsx index 0e844f49439..d4e82dbc380 100644 --- a/public/app/plugins/datasource/stackdriver/metricTypes.tsx +++ b/public/app/plugins/datasource/stackdriver/metricTypes.tsx @@ -1,5 +1,4 @@ import React, { SFC } from 'react'; -import uniqBy from 'lodash/uniqBy'; interface Props { onMetricTypeChanged: any; @@ -19,11 +18,6 @@ const MetricTypes: SFC = props => { })); }; - uniqBy(props.metricDescriptors, 'service').map(m => ({ - value: m.service, - name: m.serviceShortName, - })); - return (
    Metric Types From 33c9217cc987cf4aee124c623cebe0bd45cbb7fb Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 26 Oct 2018 09:44:36 +0200 Subject: [PATCH 023/322] stackdriver: refactoring - rename react components and file structure changes --- .../pluginTemplateQueryComponentLoader.tsx | 6 ++-- .../MetricTypeSelector.tsx} | 12 ++++---- .../ServiceSelector.tsx} | 4 +-- .../TemplateQueryComponent.tsx} | 30 ++++++++++--------- .../plugins/datasource/stackdriver/module.ts | 4 +-- public/app/types/plugins.ts | 2 +- 6 files changed, 30 insertions(+), 28 deletions(-) rename public/app/plugins/datasource/stackdriver/{metricTypes.tsx => components/MetricTypeSelector.tsx} (77%) rename public/app/plugins/datasource/stackdriver/{services.tsx => components/ServiceSelector.tsx} (90%) rename public/app/plugins/datasource/stackdriver/{templateQueryCtrl.tsx => components/TemplateQueryComponent.tsx} (69%) diff --git a/public/app/features/plugins/pluginTemplateQueryComponentLoader.tsx b/public/app/features/plugins/pluginTemplateQueryComponentLoader.tsx index 9a27643ace5..9db8fc97088 100644 --- a/public/app/features/plugins/pluginTemplateQueryComponentLoader.tsx +++ b/public/app/features/plugins/pluginTemplateQueryComponentLoader.tsx @@ -6,10 +6,10 @@ import DefaultTemplateQueryCtrl from '../templating/defaultTemplateQueryCtrl'; async function loadComponent(module) { const component = await importPluginModule(module); - if (!component.TemplateQueryCtrl) { - return DefaultTemplateQueryCtrl; + if (component && component.TemplateQueryComponent) { + return component.TemplateQueryComponent; } else { - return component.TemplateQueryCtrl; + return DefaultTemplateQueryCtrl; } } diff --git a/public/app/plugins/datasource/stackdriver/metricTypes.tsx b/public/app/plugins/datasource/stackdriver/components/MetricTypeSelector.tsx similarity index 77% rename from public/app/plugins/datasource/stackdriver/metricTypes.tsx rename to public/app/plugins/datasource/stackdriver/components/MetricTypeSelector.tsx index d4e82dbc380..cf25a100747 100644 --- a/public/app/plugins/datasource/stackdriver/metricTypes.tsx +++ b/public/app/plugins/datasource/stackdriver/components/MetricTypeSelector.tsx @@ -1,13 +1,13 @@ import React, { SFC } from 'react'; interface Props { - onMetricTypeChanged: any; + onMetricTypeChange: any; selectedService: string; metricDescriptors: any[]; } -const MetricTypes: SFC = props => { - const extractMetricTypes = () => { +const MetricTypeSelector: SFC = props => { + const filterMetricTypes = () => { if (!props.selectedService) { return []; } @@ -22,8 +22,8 @@ const MetricTypes: SFC = props => {
    Metric Types
    - + {filterMetricTypes().map((qt, i) => ( @@ -34,4 +34,4 @@ const MetricTypes: SFC = props => { ); }; -export default MetricTypes; +export default MetricTypeSelector; diff --git a/public/app/plugins/datasource/stackdriver/services.tsx b/public/app/plugins/datasource/stackdriver/components/ServiceSelector.tsx similarity index 90% rename from public/app/plugins/datasource/stackdriver/services.tsx rename to public/app/plugins/datasource/stackdriver/components/ServiceSelector.tsx index e8da82933d2..44cf6122ebf 100644 --- a/public/app/plugins/datasource/stackdriver/services.tsx +++ b/public/app/plugins/datasource/stackdriver/components/ServiceSelector.tsx @@ -6,7 +6,7 @@ interface Props { metricDescriptors: any[]; } -const Services: SFC = props => { +const ServiceSelector: SFC = props => { const extractServices = () => uniqBy(props.metricDescriptors, 'service').map(m => ({ value: m.service, @@ -29,4 +29,4 @@ const Services: SFC = props => { ); }; -export default Services; +export default ServiceSelector; diff --git a/public/app/plugins/datasource/stackdriver/templateQueryCtrl.tsx b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx similarity index 69% rename from public/app/plugins/datasource/stackdriver/templateQueryCtrl.tsx rename to public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx index 20a2cf28077..a0ca1ad422e 100644 --- a/public/app/plugins/datasource/stackdriver/templateQueryCtrl.tsx +++ b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx @@ -1,15 +1,15 @@ import React, { PureComponent } from 'react'; -import StackdriverDatasource from './datasource'; -import Services from './services'; -import MetricTypes from './metricTypes'; +import StackdriverDatasource from '../datasource'; +import ServiceSelector from './ServiceSelector'; +import MetricTypeSelector from './MetricTypeSelector'; interface Props { datasource: StackdriverDatasource; - query: string; + query: any; onChange: (c: string) => void; } -export class StackdriverTemplateQueryCtrl extends PureComponent { +export class StackdriverTemplateQueryComponent extends PureComponent { queryTypes: Array<{ value: string; name: string }> = [ { value: 'services', name: 'Services' }, { value: 'metricTypes', name: 'Metric Types' }, @@ -18,9 +18,9 @@ export class StackdriverTemplateQueryCtrl extends PureComponent { constructor(props) { super(props); - this.handleChange = this.handleChange.bind(this); + this.handleQueryTypeChange = this.handleQueryTypeChange.bind(this); this.onServiceChange = this.onServiceChange.bind(this); - this.onMetricTypeChanged = this.onMetricTypeChanged.bind(this); + this.onMetricTypeChange = this.onMetricTypeChange.bind(this); this.state = { queryType: undefined, metricDescriptors: [], service: undefined, metricType: undefined }; } @@ -29,7 +29,7 @@ export class StackdriverTemplateQueryCtrl extends PureComponent { this.setState({ metricDescriptors }); } - handleChange(event) { + handleQueryTypeChange(event) { this.setState({ queryType: event.target.value }); } @@ -37,22 +37,24 @@ export class StackdriverTemplateQueryCtrl extends PureComponent { this.setState({ service: event.target.value }); } - onMetricTypeChanged(event) { + onMetricTypeChange(event) { this.setState({ metricType: event.target.value }); } renderSwitch(queryType) { switch (queryType) { case 'metricTypes': - return ; + return ( + + ); case 'metricLabels': return ( - - + ); @@ -67,7 +69,7 @@ export class StackdriverTemplateQueryCtrl extends PureComponent {
    Query Type
    - {this.queryTypes.map((qt, i) => (
    +
    \ No newline at end of file From f0cba0b0d88fbfe3445105f764b4ca926b29fc00 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 26 Oct 2018 11:10:54 +0200 Subject: [PATCH 025/322] stackdriver: rename default component --- public/app/features/plugins/TemplateQueryComponentLoader.tsx | 4 ++-- ...emplateQueryCtrl.tsx => DefaultTemplateQueryComponent.tsx} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename public/app/features/templating/{defaultTemplateQueryCtrl.tsx => DefaultTemplateQueryComponent.tsx} (90%) diff --git a/public/app/features/plugins/TemplateQueryComponentLoader.tsx b/public/app/features/plugins/TemplateQueryComponentLoader.tsx index a2b3f8cbaa9..bc2ff323ae6 100644 --- a/public/app/features/plugins/TemplateQueryComponentLoader.tsx +++ b/public/app/features/plugins/TemplateQueryComponentLoader.tsx @@ -2,14 +2,14 @@ import coreModule from 'app/core/core_module'; import { importPluginModule } from './plugin_loader'; import React from 'react'; import ReactDOM from 'react-dom'; -import DefaultTemplateQueryCtrl from '../templating/defaultTemplateQueryCtrl'; +import DefaultTemplateQueryComponent from '../templating/DefaultTemplateQueryComponent'; async function loadComponent(module) { const component = await importPluginModule(module); if (component && component.TemplateQueryComponent) { return component.TemplateQueryComponent; } else { - return DefaultTemplateQueryCtrl; + return DefaultTemplateQueryComponent; } } diff --git a/public/app/features/templating/defaultTemplateQueryCtrl.tsx b/public/app/features/templating/DefaultTemplateQueryComponent.tsx similarity index 90% rename from public/app/features/templating/defaultTemplateQueryCtrl.tsx rename to public/app/features/templating/DefaultTemplateQueryComponent.tsx index 85f0ac7ee7d..88e2b82c7e7 100644 --- a/public/app/features/templating/defaultTemplateQueryCtrl.tsx +++ b/public/app/features/templating/DefaultTemplateQueryComponent.tsx @@ -5,7 +5,7 @@ interface Props { onChange: (c: string) => void; } -export default class DefaultTemplateQueryCtrl extends PureComponent { +export default class DefaultTemplateQueryComponent extends PureComponent { constructor(props) { super(props); this.state = { value: props.query }; From 7ccce76b80389798f4a9a11f34bb1fbcef8d7b0c Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 26 Oct 2018 14:03:05 +0200 Subject: [PATCH 026/322] stackdriver: return values for services and metric types --- .../DefaultTemplateQueryComponent.tsx | 8 ++--- public/app/features/templating/editor_ctrl.ts | 1 + .../stackdriver/StackdriverMetricFindQuery.ts | 36 +++++++++++++++++++ .../components/MetricTypeSelector.tsx | 3 +- .../components/ServiceSelector.tsx | 7 ++-- .../components/TemplateQueryComponent.tsx | 26 +++++++------- .../datasource/stackdriver/datasource.ts | 33 ++++++++++------- .../datasource/stackdriver/functions.ts | 6 ++++ public/app/types/plugins.ts | 7 ++++ 9 files changed, 92 insertions(+), 35 deletions(-) create mode 100644 public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts create mode 100644 public/app/plugins/datasource/stackdriver/functions.ts diff --git a/public/app/features/templating/DefaultTemplateQueryComponent.tsx b/public/app/features/templating/DefaultTemplateQueryComponent.tsx index 88e2b82c7e7..cd4c5adf157 100644 --- a/public/app/features/templating/DefaultTemplateQueryComponent.tsx +++ b/public/app/features/templating/DefaultTemplateQueryComponent.tsx @@ -1,11 +1,7 @@ import React, { PureComponent } from 'react'; +import { TemplateQueryProps } from 'app/types/plugins'; -interface Props { - query: string; - onChange: (c: string) => void; -} - -export default class DefaultTemplateQueryComponent extends PureComponent { +export default class DefaultTemplateQueryComponent extends PureComponent { constructor(props) { super(props); this.state = { value: props.query }; diff --git a/public/app/features/templating/editor_ctrl.ts b/public/app/features/templating/editor_ctrl.ts index 6bdd71620b3..aef5f5ee502 100644 --- a/public/app/features/templating/editor_ctrl.ts +++ b/public/app/features/templating/editor_ctrl.ts @@ -72,6 +72,7 @@ export class VariableEditorCtrl { if ( $scope.current.type === 'query' && + _.isString($scope.current.query) && $scope.current.query.match(new RegExp('\\$' + $scope.current.name + '(/| |$)')) ) { appEvents.emit('alert-warning', [ diff --git a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts new file mode 100644 index 00000000000..abe88131cc7 --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts @@ -0,0 +1,36 @@ +import { extractServicesFromMetricDescriptors, getMetricTypesByService } from './functions'; + +export default class StackdriverMetricFindQuery { + constructor(private datasource) {} + + async query(query: any) { + switch (query.type) { + case 'services': + return this.handleServiceQueryType(); + case 'metricTypes': + return this.handleMetricTypesQueryType(query); + default: + return []; + } + } + + async handleServiceQueryType() { + const metricDescriptors = await this.datasource.getMetricTypes(this.datasource.projectName); + const services = extractServicesFromMetricDescriptors(metricDescriptors); + return services.map(s => ({ + text: s.name, + expandable: true, + })); + } + + async handleMetricTypesQueryType({ service }) { + if (!service) { + return []; + } + const metricDescriptors = await this.datasource.getMetricTypes(this.datasource.projectName); + return getMetricTypesByService(metricDescriptors, service).map(s => ({ + text: s.name, + expandable: true, + })); + } +} diff --git a/public/app/plugins/datasource/stackdriver/components/MetricTypeSelector.tsx b/public/app/plugins/datasource/stackdriver/components/MetricTypeSelector.tsx index cf25a100747..9aa3f9de6cb 100644 --- a/public/app/plugins/datasource/stackdriver/components/MetricTypeSelector.tsx +++ b/public/app/plugins/datasource/stackdriver/components/MetricTypeSelector.tsx @@ -1,4 +1,5 @@ import React, { SFC } from 'react'; +import { getMetricTypesByService } from '../functions'; interface Props { onMetricTypeChange: any; @@ -12,7 +13,7 @@ const MetricTypeSelector: SFC = props => { return []; } - return props.metricDescriptors.filter(m => m.service === props.selectedService).map(m => ({ + return getMetricTypesByService(props.metricDescriptors, props.selectedService).map(m => ({ value: m.service, name: m.displayName, })); diff --git a/public/app/plugins/datasource/stackdriver/components/ServiceSelector.tsx b/public/app/plugins/datasource/stackdriver/components/ServiceSelector.tsx index 44cf6122ebf..6bb29a46a6a 100644 --- a/public/app/plugins/datasource/stackdriver/components/ServiceSelector.tsx +++ b/public/app/plugins/datasource/stackdriver/components/ServiceSelector.tsx @@ -1,5 +1,5 @@ import React, { SFC } from 'react'; -import uniqBy from 'lodash/uniqBy'; +import { extractServicesFromMetricDescriptors } from '../functions'; interface Props { onServiceChange: any; @@ -7,11 +7,12 @@ interface Props { } const ServiceSelector: SFC = props => { - const extractServices = () => - uniqBy(props.metricDescriptors, 'service').map(m => ({ + const extractServices = () => { + return extractServicesFromMetricDescriptors(props.metricDescriptors).map(m => ({ value: m.service, name: m.serviceShortName, })); + }; return (
    diff --git a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx index a0ca1ad422e..95cd9cd6c64 100644 --- a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx +++ b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx @@ -1,27 +1,24 @@ import React, { PureComponent } from 'react'; -import StackdriverDatasource from '../datasource'; +// import StackdriverDatasource from '../datasource'; import ServiceSelector from './ServiceSelector'; import MetricTypeSelector from './MetricTypeSelector'; +import { TemplateQueryProps } from 'app/types/plugins'; +import defaultsDeep from 'lodash/defaultsDeep'; -interface Props { - datasource: StackdriverDatasource; - query: any; - onChange: (c: string) => void; -} - -export class StackdriverTemplateQueryComponent extends PureComponent { +export class StackdriverTemplateQueryComponent extends PureComponent { queryTypes: Array<{ value: string; name: string }> = [ { value: 'services', name: 'Services' }, { value: 'metricTypes', name: 'Metric Types' }, { value: 'metricLabels', name: 'Metric labels For Metric Type' }, ]; + defaults = { type: undefined, metricDescriptors: [], service: undefined, metricType: undefined }; - constructor(props) { + constructor(props: TemplateQueryProps) { super(props); this.handleQueryTypeChange = this.handleQueryTypeChange.bind(this); this.onServiceChange = this.onServiceChange.bind(this); this.onMetricTypeChange = this.onMetricTypeChange.bind(this); - this.state = { queryType: undefined, metricDescriptors: [], service: undefined, metricType: undefined }; + this.state = defaultsDeep(this.props.query, this.defaults); } async componentDidMount() { @@ -30,7 +27,7 @@ export class StackdriverTemplateQueryComponent extends PureComponent } handleQueryTypeChange(event) { - this.setState({ queryType: event.target.value }); + this.setState({ type: event.target.value }); } onServiceChange(event) { @@ -41,6 +38,11 @@ export class StackdriverTemplateQueryComponent extends PureComponent this.setState({ metricType: event.target.value }); } + componentDidUpdate() { + const { metricDescriptors, ...queryModel } = this.state; + this.props.onChange(queryModel); + } + renderSwitch(queryType) { switch (queryType) { case 'metricTypes': @@ -78,7 +80,7 @@ export class StackdriverTemplateQueryComponent extends PureComponent
    - {this.renderSwitch(this.state.queryType)} + {this.renderSwitch(this.state.type)} ); } diff --git a/public/app/plugins/datasource/stackdriver/datasource.ts b/public/app/plugins/datasource/stackdriver/datasource.ts index 034333cbb86..d04e571b616 100644 --- a/public/app/plugins/datasource/stackdriver/datasource.ts +++ b/public/app/plugins/datasource/stackdriver/datasource.ts @@ -1,6 +1,7 @@ import { stackdriverUnitMappings } from './constants'; import appEvents from 'app/core/app_events'; import _ from 'lodash'; +import StackdriverMetricFindQuery from './StackdriverMetricFindQuery'; export default class StackdriverDatasource { id: number; @@ -9,6 +10,7 @@ export default class StackdriverDatasource { projectName: string; authenticationType: string; queryPromise: Promise; + metricTypes: any[]; /** @ngInject */ constructor(instanceSettings, private backendSrv, private templateSrv, private timeSrv) { @@ -18,6 +20,7 @@ export default class StackdriverDatasource { this.id = instanceSettings.id; this.projectName = instanceSettings.jsonData.defaultProject || ''; this.authenticationType = instanceSettings.jsonData.authenticationType || 'jwt'; + this.metricTypes = []; } async getTimeSeries(options) { @@ -177,8 +180,10 @@ export default class StackdriverDatasource { return results; } - metricFindQuery(query) { - throw new Error('Template variables support is not yet imlemented'); + async metricFindQuery(query) { + const stackdriverMetricFindQuery = new StackdriverMetricFindQuery(this); + return stackdriverMetricFindQuery.query(query); + // throw new Error('Template variables support is not yet imlemented'); } async testDatasource() { @@ -258,19 +263,21 @@ export default class StackdriverDatasource { async getMetricTypes(projectName: string) { try { - const metricsApiPath = `v3/projects/${projectName}/metricDescriptors`; - const { data } = await this.doRequest(`${this.baseUrl}${metricsApiPath}`); + if (this.metricTypes.length === 0) { + const metricsApiPath = `v3/projects/${projectName}/metricDescriptors`; + const { data } = await this.doRequest(`${this.baseUrl}${metricsApiPath}`); - const metrics = data.metricDescriptors.map(m => { - const [service] = m.type.split('/'); - const [serviceShortName] = service.split('.'); - m.service = service; - m.serviceShortName = serviceShortName; - m.displayName = m.displayName || m.type; - return m; - }); + this.metricTypes = data.metricDescriptors.map(m => { + const [service] = m.type.split('/'); + const [serviceShortName] = service.split('.'); + m.service = service; + m.serviceShortName = serviceShortName; + m.displayName = m.displayName || m.type; + return m; + }); + } - return metrics; + return this.metricTypes; } catch (error) { appEvents.emit('ds-request-error', this.formatStackdriverError(error)); return []; diff --git a/public/app/plugins/datasource/stackdriver/functions.ts b/public/app/plugins/datasource/stackdriver/functions.ts new file mode 100644 index 00000000000..15e84f050e8 --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/functions.ts @@ -0,0 +1,6 @@ +import uniqBy from 'lodash/uniqBy'; + +export const extractServicesFromMetricDescriptors = metricDescriptors => uniqBy(metricDescriptors, 'service'); + +export const getMetricTypesByService = (metricDescriptors, service) => + metricDescriptors.filter(m => m.service === service); diff --git a/public/app/types/plugins.ts b/public/app/types/plugins.ts index 4d6ad6584c6..eb8ff5dcaf2 100644 --- a/public/app/types/plugins.ts +++ b/public/app/types/plugins.ts @@ -99,3 +99,10 @@ export interface PluginsState { hasFetched: boolean; dashboards: PluginDashboard[]; } + +export interface TemplateQueryProps { + query: any; + onChange: (c: any) => void; + datasource: any; + // datasource: StackdriverDatasource; +} From cbb663015e3602869bf6d79dc682ed642304597b Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 26 Oct 2018 14:56:55 +0200 Subject: [PATCH 027/322] stackdriver: move response parsing to datasource file --- public/app/plugins/datasource/stackdriver/datasource.ts | 4 +++- .../plugins/datasource/stackdriver/query_filter_ctrl.ts | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/datasource.ts b/public/app/plugins/datasource/stackdriver/datasource.ts index d04e571b616..5c6ad9183a8 100644 --- a/public/app/plugins/datasource/stackdriver/datasource.ts +++ b/public/app/plugins/datasource/stackdriver/datasource.ts @@ -70,7 +70,7 @@ export default class StackdriverDatasource { } async getLabels(metricType, refId) { - return await this.getTimeSeries({ + const response = await this.getTimeSeries({ targets: [ { refId: refId, @@ -84,6 +84,8 @@ export default class StackdriverDatasource { ], range: this.timeSrv.timeRange(), }); + + return response.results[refId]; } interpolateGroupBys(groupBys: string[], scopedVars): string[] { diff --git a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts index 4c383e5d09e..5a002ef1165 100644 --- a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts @@ -150,10 +150,10 @@ export class StackdriverFilterCtrl { async getLabels() { this.loadLabelsPromise = new Promise(async resolve => { try { - const data = await this.datasource.getLabels(this.target.metricType, this.target.refId); - this.metricLabels = data.results[this.target.refId].meta.metricLabels; - this.resourceLabels = data.results[this.target.refId].meta.resourceLabels; - this.resourceTypes = data.results[this.target.refId].meta.resourceTypes; + const { meta } = await this.datasource.getLabels(this.target.metricType, this.target.refId); + this.metricLabels = meta.metricLabels; + this.resourceLabels = meta.resourceLabels; + this.resourceTypes = meta.resourceTypes; resolve(); } catch (error) { if (error.data && error.data.message) { From 4ab278dca49b9e940c4e26ec8655c49cc3600eb2 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 26 Oct 2018 15:57:01 +0200 Subject: [PATCH 028/322] stackdriver: add metric labels query --- .../stackdriver/StackdriverMetricFindQuery.ts | 17 ++++++++ .../components/MetricLabelKeySelector.tsx | 26 ++++++++++++ .../components/MetricTypeSelector.tsx | 2 +- .../components/TemplateQueryComponent.tsx | 40 +++++++++++++++++-- 4 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 public/app/plugins/datasource/stackdriver/components/MetricLabelKeySelector.tsx diff --git a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts index abe88131cc7..1e8d1807f86 100644 --- a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts +++ b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts @@ -1,4 +1,5 @@ import { extractServicesFromMetricDescriptors, getMetricTypesByService } from './functions'; +import has from 'lodash/has'; export default class StackdriverMetricFindQuery { constructor(private datasource) {} @@ -9,6 +10,8 @@ export default class StackdriverMetricFindQuery { return this.handleServiceQueryType(); case 'metricTypes': return this.handleMetricTypesQueryType(query); + case 'metricLabels': + return this.handleMetricLabelsQueryType(query); default: return []; } @@ -33,4 +36,18 @@ export default class StackdriverMetricFindQuery { expandable: true, })); } + + async handleMetricLabelsQueryType({ metricType, metricLabelKey }) { + if (!metricType || !metricLabelKey) { + return []; + } + const refId = 'handleMetricLabelsQueryType'; + const response = await this.datasource.getLabels(metricType, refId); + return has(response, `meta.metricLabels.${metricLabelKey}`) + ? response.meta.metricLabels[metricLabelKey].map(s => ({ + text: s, + expandable: true, + })) + : []; + } } diff --git a/public/app/plugins/datasource/stackdriver/components/MetricLabelKeySelector.tsx b/public/app/plugins/datasource/stackdriver/components/MetricLabelKeySelector.tsx new file mode 100644 index 00000000000..72d1e10e330 --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/components/MetricLabelKeySelector.tsx @@ -0,0 +1,26 @@ +import React, { SFC } from 'react'; + +interface Props { + onMetricLabelKeyChange: any; + metricLabels: any; + metricLabelKey: string; +} + +const MetricLabelKeySelector: SFC = props => { + return ( +
    + Metric Labels +
    + +
    +
    + ); +}; + +export default MetricLabelKeySelector; diff --git a/public/app/plugins/datasource/stackdriver/components/MetricTypeSelector.tsx b/public/app/plugins/datasource/stackdriver/components/MetricTypeSelector.tsx index 9aa3f9de6cb..530512defcb 100644 --- a/public/app/plugins/datasource/stackdriver/components/MetricTypeSelector.tsx +++ b/public/app/plugins/datasource/stackdriver/components/MetricTypeSelector.tsx @@ -14,7 +14,7 @@ const MetricTypeSelector: SFC = props => { } return getMetricTypesByService(props.metricDescriptors, props.selectedService).map(m => ({ - value: m.service, + value: m.type, name: m.displayName, })); }; diff --git a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx index 95cd9cd6c64..a9e1cbc9fc2 100644 --- a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx +++ b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx @@ -1,23 +1,37 @@ import React, { PureComponent } from 'react'; -// import StackdriverDatasource from '../datasource'; import ServiceSelector from './ServiceSelector'; import MetricTypeSelector from './MetricTypeSelector'; +import MetricLabelKeySelector from './MetricLabelKeySelector'; import { TemplateQueryProps } from 'app/types/plugins'; import defaultsDeep from 'lodash/defaultsDeep'; +import has from 'lodash/has'; export class StackdriverTemplateQueryComponent extends PureComponent { queryTypes: Array<{ value: string; name: string }> = [ { value: 'services', name: 'Services' }, { value: 'metricTypes', name: 'Metric Types' }, - { value: 'metricLabels', name: 'Metric labels For Metric Type' }, + { value: 'metricLabels', name: 'Metric Labels' }, + { value: 'resourceLabels', name: 'Resource Labels' }, + { value: 'resourceTypes', name: 'Resource Types' }, + { value: 'aggregations', name: 'Aggregations' }, + { value: 'alignerns', name: 'Aligners' }, + { value: 'alignmentPeriods', name: 'Alignment Periods' }, ]; - defaults = { type: undefined, metricDescriptors: [], service: undefined, metricType: undefined }; + defaults = { + type: undefined, + metricDescriptors: [], + service: undefined, + metricType: undefined, + metricLabels: [], + metricLabelKey: undefined, + }; constructor(props: TemplateQueryProps) { super(props); this.handleQueryTypeChange = this.handleQueryTypeChange.bind(this); this.onServiceChange = this.onServiceChange.bind(this); this.onMetricTypeChange = this.onMetricTypeChange.bind(this); + this.onMetricLabelKeyChange = this.onMetricLabelKeyChange.bind(this); this.state = defaultsDeep(this.props.query, this.defaults); } @@ -26,6 +40,14 @@ export class StackdriverTemplateQueryComponent extends PureComponent + ); default: From 8c3874ee30d849b86bdaa1c3e523dcd222c036b0 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 26 Oct 2018 16:35:12 +0200 Subject: [PATCH 029/322] stackdriver: return friendly display name --- .../datasource/stackdriver/StackdriverMetricFindQuery.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts index 1e8d1807f86..f314b6b9d31 100644 --- a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts +++ b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts @@ -21,7 +21,8 @@ export default class StackdriverMetricFindQuery { const metricDescriptors = await this.datasource.getMetricTypes(this.datasource.projectName); const services = extractServicesFromMetricDescriptors(metricDescriptors); return services.map(s => ({ - text: s.name, + text: s.serviceShortName, + value: s.name, expandable: true, })); } @@ -32,7 +33,8 @@ export default class StackdriverMetricFindQuery { } const metricDescriptors = await this.datasource.getMetricTypes(this.datasource.projectName); return getMetricTypesByService(metricDescriptors, service).map(s => ({ - text: s.name, + text: s.displayName, + value: s.name, expandable: true, })); } From cb0d563aaef13db9c2b13953cf666ee8bf6d60bb Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Mon, 29 Oct 2018 14:56:55 +0100 Subject: [PATCH 030/322] stackdriver: add support for resource label queries --- .../stackdriver/StackdriverMetricFindQuery.ts | 23 +++++---- ...abelKeySelector.tsx => SimpleDropdown.tsx} | 17 ++++--- .../components/TemplateQueryComponent.tsx | 50 +++++++++++++++---- 3 files changed, 62 insertions(+), 28 deletions(-) rename public/app/plugins/datasource/stackdriver/components/{MetricLabelKeySelector.tsx => SimpleDropdown.tsx} (56%) diff --git a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts index f314b6b9d31..5b7c5fd2bf8 100644 --- a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts +++ b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts @@ -11,7 +11,8 @@ export default class StackdriverMetricFindQuery { case 'metricTypes': return this.handleMetricTypesQueryType(query); case 'metricLabels': - return this.handleMetricLabelsQueryType(query); + case 'resourceLabels': + return this.handleLabelQueryType(query); default: return []; } @@ -39,17 +40,19 @@ export default class StackdriverMetricFindQuery { })); } - async handleMetricLabelsQueryType({ metricType, metricLabelKey }) { - if (!metricType || !metricLabelKey) { + async handleLabelQueryType({ type, metricType, metricLabelKey, resourceLabelKey }) { + if (!metricType) { return []; } - const refId = 'handleMetricLabelsQueryType'; + const key = type === 'metricLabels' ? metricLabelKey : resourceLabelKey; + const refId = 'handleLabelsQueryType'; const response = await this.datasource.getLabels(metricType, refId); - return has(response, `meta.metricLabels.${metricLabelKey}`) - ? response.meta.metricLabels[metricLabelKey].map(s => ({ - text: s, - expandable: true, - })) - : []; + if (!has(response, `meta.${type}.${key}`)) { + return []; + } + return response.meta[type][key].map(s => ({ + text: s, + expandable: true, + })); } } diff --git a/public/app/plugins/datasource/stackdriver/components/MetricLabelKeySelector.tsx b/public/app/plugins/datasource/stackdriver/components/SimpleDropdown.tsx similarity index 56% rename from public/app/plugins/datasource/stackdriver/components/MetricLabelKeySelector.tsx rename to public/app/plugins/datasource/stackdriver/components/SimpleDropdown.tsx index 72d1e10e330..bbd99a79488 100644 --- a/public/app/plugins/datasource/stackdriver/components/MetricLabelKeySelector.tsx +++ b/public/app/plugins/datasource/stackdriver/components/SimpleDropdown.tsx @@ -1,18 +1,19 @@ import React, { SFC } from 'react'; interface Props { - onMetricLabelKeyChange: any; - metricLabels: any; - metricLabelKey: string; + onValueChange: any; + options: any; + value: string; + label: string; } -const MetricLabelKeySelector: SFC = props => { +const SimpleDropdown: SFC = props => { return (
    - Metric Labels + {props.label}
    - + {props.options.map((qt, i) => ( @@ -23,4 +24,4 @@ const MetricLabelKeySelector: SFC = props => { ); }; -export default MetricLabelKeySelector; +export default SimpleDropdown; diff --git a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx index a9e1cbc9fc2..c4c7487e029 100644 --- a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx +++ b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx @@ -1,7 +1,7 @@ import React, { PureComponent } from 'react'; import ServiceSelector from './ServiceSelector'; import MetricTypeSelector from './MetricTypeSelector'; -import MetricLabelKeySelector from './MetricLabelKeySelector'; +import SimpleDropdown from './SimpleDropdown'; import { TemplateQueryProps } from 'app/types/plugins'; import defaultsDeep from 'lodash/defaultsDeep'; import has from 'lodash/has'; @@ -17,13 +17,16 @@ export class StackdriverTemplateQueryComponent extends PureComponent ); case 'metricLabels': + case 'resourceLabels': + const dropdown = + queryType === 'resourceLabels' ? ( + + ) : ( + + ); return ( @@ -85,11 +119,7 @@ export class StackdriverTemplateQueryComponent extends PureComponent - + {dropdown} ); default: From b3edad40a9bf8a4f6880f70e1b0c4bef04ed26fb Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Mon, 29 Oct 2018 15:41:11 +0100 Subject: [PATCH 031/322] stackdriver: add resource types query --- .../stackdriver/StackdriverMetricFindQuery.ts | 34 ++++++++++++- .../components/TemplateQueryComponent.tsx | 51 +++++++++++-------- 2 files changed, 63 insertions(+), 22 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts index 5b7c5fd2bf8..83fe2094a82 100644 --- a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts +++ b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts @@ -13,6 +13,8 @@ export default class StackdriverMetricFindQuery { case 'metricLabels': case 'resourceLabels': return this.handleLabelQueryType(query); + case 'resourceTypes': + return this.handleResourceType(query); default: return []; } @@ -40,11 +42,23 @@ export default class StackdriverMetricFindQuery { })); } - async handleLabelQueryType({ type, metricType, metricLabelKey, resourceLabelKey }) { + getLabelKey({ type, metricLabelKey, resourceLabelKey }) { + switch (type) { + case 'metricLabels': + return metricLabelKey; + break; + case 'resourceLabels': + return resourceLabelKey; + default: + return ''; + } + } + + async handleLabelQueryType({ type, metricType, metricLabelKey, resourceLabelKey, resourceTypeKey }) { if (!metricType) { return []; } - const key = type === 'metricLabels' ? metricLabelKey : resourceLabelKey; + const key = this.getLabelKey({ type, metricLabelKey, resourceLabelKey }); const refId = 'handleLabelsQueryType'; const response = await this.datasource.getLabels(metricType, refId); if (!has(response, `meta.${type}.${key}`)) { @@ -55,4 +69,20 @@ export default class StackdriverMetricFindQuery { expandable: true, })); } + + async handleResourceType({ metricType }) { + if (!metricType) { + return []; + } + try { + const refId = 'handleResourceTypeQueryType'; + const response = await this.datasource.getLabels(metricType, refId); + return response.meta.resourceTypes.map(s => ({ + text: s, + expandable: true, + })); + } catch (error) { + return []; + } + } } diff --git a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx index c4c7487e029..3d1bef0aa15 100644 --- a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx +++ b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx @@ -44,6 +44,10 @@ export class StackdriverTemplateQueryComponent extends PureComponent + ); + case 'metricLabels': + return ( + + ); + default: + return ''; + } + } + renderSwitch(queryType) { switch (queryType) { case 'metricTypes': @@ -95,22 +120,8 @@ export class StackdriverTemplateQueryComponent extends PureComponent - ) : ( - - ); + case 'resourceTypes': + const dropdown = this.switchMetaType(queryType); return ( From a0b8c4acba8eb9d07cec7fd0630206e90c69d8f7 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Mon, 29 Oct 2018 16:27:53 +0100 Subject: [PATCH 032/322] stackdriver: add aligner query --- .../stackdriver/StackdriverMetricFindQuery.ts | 17 ++++++++++++++++- .../components/TemplateQueryComponent.tsx | 11 +++++++++++ .../plugins/datasource/stackdriver/functions.ts | 9 +++++++++ .../stackdriver/query_aggregation_ctrl.ts | 9 ++------- 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts index 83fe2094a82..7568c853848 100644 --- a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts +++ b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts @@ -1,4 +1,8 @@ -import { extractServicesFromMetricDescriptors, getMetricTypesByService } from './functions'; +import { + extractServicesFromMetricDescriptors, + getMetricTypesByService, + getAlignmentOptionsByMetric, +} from './functions'; import has from 'lodash/has'; export default class StackdriverMetricFindQuery { @@ -15,6 +19,8 @@ export default class StackdriverMetricFindQuery { return this.handleLabelQueryType(query); case 'resourceTypes': return this.handleResourceType(query); + case 'alignerns': + return this.handleAlignersType(query); default: return []; } @@ -85,4 +91,13 @@ export default class StackdriverMetricFindQuery { return []; } } + + async handleAlignersType({ metricType }) { + if (!metricType) { + return []; + } + const metricDescriptors = await this.datasource.getMetricTypes(this.datasource.projectName); + const { valueType, metricKind } = metricDescriptors.find(m => m.type === metricType); + return getAlignmentOptionsByMetric(valueType, metricKind); + } } diff --git a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx index 3d1bef0aa15..d68864d5a34 100644 --- a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx +++ b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx @@ -133,6 +133,17 @@ export class StackdriverTemplateQueryComponent extends PureComponent ); + case 'alignerns': + return ( + + + + + ); default: return ''; } diff --git a/public/app/plugins/datasource/stackdriver/functions.ts b/public/app/plugins/datasource/stackdriver/functions.ts index 15e84f050e8..8b3551d153a 100644 --- a/public/app/plugins/datasource/stackdriver/functions.ts +++ b/public/app/plugins/datasource/stackdriver/functions.ts @@ -1,6 +1,15 @@ +import { alignOptions } from './constants'; import uniqBy from 'lodash/uniqBy'; export const extractServicesFromMetricDescriptors = metricDescriptors => uniqBy(metricDescriptors, 'service'); export const getMetricTypesByService = (metricDescriptors, service) => metricDescriptors.filter(m => m.service === service); + +export const getAlignmentOptionsByMetric = (metricValueType, metricKind) => { + return !metricValueType + ? [] + : alignOptions.filter(i => { + return i.valueTypes.indexOf(metricValueType) !== -1 && i.metricKinds.indexOf(metricKind) !== -1; + }); +}; diff --git a/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts index 6cd6c805463..1340aa7f690 100644 --- a/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts @@ -1,6 +1,7 @@ import coreModule from 'app/core/core_module'; import _ from 'lodash'; import * as options from './constants'; +import { getAlignmentOptionsByMetric } from './functions'; import kbn from 'app/core/utils/kbn'; export class StackdriverAggregation { @@ -41,13 +42,7 @@ export class StackdriverAggregationCtrl { } setAlignOptions() { - this.alignOptions = !this.target.valueType - ? [] - : options.alignOptions.filter(i => { - return ( - i.valueTypes.indexOf(this.target.valueType) !== -1 && i.metricKinds.indexOf(this.target.metricKind) !== -1 - ); - }); + this.alignOptions = getAlignmentOptionsByMetric(this.target.valueType, this.target.metricKind); if (!this.alignOptions.find(o => o.value === this.target.aggregation.perSeriesAligner)) { this.target.aggregation.perSeriesAligner = this.alignOptions.length > 0 ? this.alignOptions[0].value : ''; } From b1db07789b6f6c303e7983ab0177267abc8d31ac Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Mon, 29 Oct 2018 16:40:59 +0100 Subject: [PATCH 033/322] stackdriver: add alignment periods --- .../stackdriver/StackdriverMetricFindQuery.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts index 7568c853848..7105f4c805a 100644 --- a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts +++ b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts @@ -3,6 +3,7 @@ import { getMetricTypesByService, getAlignmentOptionsByMetric, } from './functions'; +import { alignmentPeriods } from './constants'; import has from 'lodash/has'; export default class StackdriverMetricFindQuery { @@ -21,6 +22,8 @@ export default class StackdriverMetricFindQuery { return this.handleResourceType(query); case 'alignerns': return this.handleAlignersType(query); + case 'alignmentPeriods': + return this.handleAlignmentPeriodType(); default: return []; } @@ -98,6 +101,16 @@ export default class StackdriverMetricFindQuery { } const metricDescriptors = await this.datasource.getMetricTypes(this.datasource.projectName); const { valueType, metricKind } = metricDescriptors.find(m => m.type === metricType); - return getAlignmentOptionsByMetric(valueType, metricKind); + return getAlignmentOptionsByMetric(valueType, metricKind).map(o => ({ + ...o, + expandable: true, + })); + } + + handleAlignmentPeriodType() { + return alignmentPeriods.map(s => ({ + ...s, + expandable: true, + })); } } From ca77cdc37e1c59c778e27924ae13bd2ca8a8b8a6 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Mon, 29 Oct 2018 16:57:07 +0100 Subject: [PATCH 034/322] stackdriver: add aggregation query --- .../stackdriver/StackdriverMetricFindQuery.ts | 15 +++++++++++++++ .../components/TemplateQueryComponent.tsx | 1 + .../plugins/datasource/stackdriver/functions.ts | 10 +++++++++- .../stackdriver/query_aggregation_ctrl.ts | 10 ++-------- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts index 7105f4c805a..a4865bf7074 100644 --- a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts +++ b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts @@ -2,6 +2,7 @@ import { extractServicesFromMetricDescriptors, getMetricTypesByService, getAlignmentOptionsByMetric, + getAggregationOptionsByMetric, } from './functions'; import { alignmentPeriods } from './constants'; import has from 'lodash/has'; @@ -24,6 +25,8 @@ export default class StackdriverMetricFindQuery { return this.handleAlignersType(query); case 'alignmentPeriods': return this.handleAlignmentPeriodType(); + case 'aggregations': + return this.handleAggregationType(query); default: return []; } @@ -107,6 +110,18 @@ export default class StackdriverMetricFindQuery { })); } + async handleAggregationType({ metricType }) { + if (!metricType) { + return []; + } + const metricDescriptors = await this.datasource.getMetricTypes(this.datasource.projectName); + const { valueType, metricKind } = metricDescriptors.find(m => m.type === metricType); + return getAggregationOptionsByMetric(valueType, metricKind).map(o => ({ + ...o, + expandable: true, + })); + } + handleAlignmentPeriodType() { return alignmentPeriods.map(s => ({ ...s, diff --git a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx index d68864d5a34..651e4ea633f 100644 --- a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx +++ b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx @@ -134,6 +134,7 @@ export class StackdriverTemplateQueryComponent extends PureComponent ); case 'alignerns': + case 'aggregations': return ( diff --git a/public/app/plugins/datasource/stackdriver/functions.ts b/public/app/plugins/datasource/stackdriver/functions.ts index 8b3551d153a..f7891203dad 100644 --- a/public/app/plugins/datasource/stackdriver/functions.ts +++ b/public/app/plugins/datasource/stackdriver/functions.ts @@ -1,4 +1,4 @@ -import { alignOptions } from './constants'; +import { alignOptions, aggOptions } from './constants'; import uniqBy from 'lodash/uniqBy'; export const extractServicesFromMetricDescriptors = metricDescriptors => uniqBy(metricDescriptors, 'service'); @@ -13,3 +13,11 @@ export const getAlignmentOptionsByMetric = (metricValueType, metricKind) => { return i.valueTypes.indexOf(metricValueType) !== -1 && i.metricKinds.indexOf(metricKind) !== -1; }); }; + +export const getAggregationOptionsByMetric = (valueType, metricKind) => { + return !metricKind + ? [] + : aggOptions.filter(i => { + return i.valueTypes.indexOf(valueType) !== -1 && i.metricKinds.indexOf(metricKind) !== -1; + }); +}; diff --git a/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts index 1340aa7f690..512035103f5 100644 --- a/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts @@ -1,7 +1,7 @@ import coreModule from 'app/core/core_module'; import _ from 'lodash'; import * as options from './constants'; -import { getAlignmentOptionsByMetric } from './functions'; +import { getAlignmentOptionsByMetric, getAggregationOptionsByMetric } from './functions'; import kbn from 'app/core/utils/kbn'; export class StackdriverAggregation { @@ -49,13 +49,7 @@ export class StackdriverAggregationCtrl { } setAggOptions() { - this.aggOptions = !this.target.metricKind - ? [] - : options.aggOptions.filter(i => { - return ( - i.valueTypes.indexOf(this.target.valueType) !== -1 && i.metricKinds.indexOf(this.target.metricKind) !== -1 - ); - }); + this.aggOptions = getAggregationOptionsByMetric(this.target.valueType, this.target.metricKind); if (!this.aggOptions.find(o => o.value === this.target.aggregation.crossSeriesReducer)) { this.deselectAggregationOption('REDUCE_NONE'); From f0b4af9f6c5e1d2170034f2cbca1c2d3b427bc8b Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Mon, 29 Oct 2018 17:38:43 +0100 Subject: [PATCH 035/322] stackdriver: use enum for query type --- .../stackdriver/StackdriverMetricFindQuery.ts | 101 ++++++++---------- .../components/TemplateQueryComponent.tsx | 35 +++--- .../plugins/datasource/stackdriver/types.ts | 10 ++ 3 files changed, 74 insertions(+), 72 deletions(-) create mode 100644 public/app/plugins/datasource/stackdriver/types.ts diff --git a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts index a4865bf7074..130e9e0c78b 100644 --- a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts +++ b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts @@ -6,33 +6,35 @@ import { } from './functions'; import { alignmentPeriods } from './constants'; import has from 'lodash/has'; +import isString from 'lodash/isString'; +import { MetricFindQueryTypes } from './types'; export default class StackdriverMetricFindQuery { constructor(private datasource) {} async query(query: any) { switch (query.type) { - case 'services': - return this.handleServiceQueryType(); - case 'metricTypes': - return this.handleMetricTypesQueryType(query); - case 'metricLabels': - case 'resourceLabels': - return this.handleLabelQueryType(query); - case 'resourceTypes': - return this.handleResourceType(query); - case 'alignerns': - return this.handleAlignersType(query); - case 'alignmentPeriods': - return this.handleAlignmentPeriodType(); - case 'aggregations': - return this.handleAggregationType(query); + case MetricFindQueryTypes.Services: + return this.handleServiceQuery(); + case MetricFindQueryTypes.MetricTypes: + return this.handleMetricTypesQuery(query); + case MetricFindQueryTypes.MetricLabels: + case MetricFindQueryTypes.ResourceLabels: + return this.handleLabelQuery(query); + case MetricFindQueryTypes.ResourceTypes: + return this.handleResourceTypeQuery(query); + case MetricFindQueryTypes.Alignerns: + return this.handleAlignersQuery(query); + case MetricFindQueryTypes.AlignmentPeriods: + return this.handleAlignmentPeriodQuery(); + case MetricFindQueryTypes.Aggregations: + return this.handleAggregationQuery(query); default: return []; } } - async handleServiceQueryType() { + async handleServiceQuery() { const metricDescriptors = await this.datasource.getMetricTypes(this.datasource.projectName); const services = extractServicesFromMetricDescriptors(metricDescriptors); return services.map(s => ({ @@ -42,7 +44,7 @@ export default class StackdriverMetricFindQuery { })); } - async handleMetricTypesQueryType({ service }) { + async handleMetricTypesQuery({ service }) { if (!service) { return []; } @@ -54,19 +56,7 @@ export default class StackdriverMetricFindQuery { })); } - getLabelKey({ type, metricLabelKey, resourceLabelKey }) { - switch (type) { - case 'metricLabels': - return metricLabelKey; - break; - case 'resourceLabels': - return resourceLabelKey; - default: - return ''; - } - } - - async handleLabelQueryType({ type, metricType, metricLabelKey, resourceLabelKey, resourceTypeKey }) { + async handleLabelQuery({ type, metricType, metricLabelKey, resourceLabelKey, resourceTypeKey }) { if (!metricType) { return []; } @@ -76,56 +66,57 @@ export default class StackdriverMetricFindQuery { if (!has(response, `meta.${type}.${key}`)) { return []; } - return response.meta[type][key].map(s => ({ - text: s, - expandable: true, - })); + return response.meta[type][key].map(this.toFindQueryResult); } - async handleResourceType({ metricType }) { + async handleResourceTypeQuery({ metricType }) { if (!metricType) { return []; } try { - const refId = 'handleResourceTypeQueryType'; + const refId = 'handleResourceTypeQueryQueryType'; const response = await this.datasource.getLabels(metricType, refId); - return response.meta.resourceTypes.map(s => ({ - text: s, - expandable: true, - })); + return response.meta.resourceTypes.map(this.toFindQueryResult); } catch (error) { return []; } } - async handleAlignersType({ metricType }) { + async handleAlignersQuery({ metricType }) { if (!metricType) { return []; } const metricDescriptors = await this.datasource.getMetricTypes(this.datasource.projectName); const { valueType, metricKind } = metricDescriptors.find(m => m.type === metricType); - return getAlignmentOptionsByMetric(valueType, metricKind).map(o => ({ - ...o, - expandable: true, - })); + return getAlignmentOptionsByMetric(valueType, metricKind).map(this.toFindQueryResult); } - async handleAggregationType({ metricType }) { + async handleAggregationQuery({ metricType }) { if (!metricType) { return []; } const metricDescriptors = await this.datasource.getMetricTypes(this.datasource.projectName); const { valueType, metricKind } = metricDescriptors.find(m => m.type === metricType); - return getAggregationOptionsByMetric(valueType, metricKind).map(o => ({ - ...o, - expandable: true, - })); + return getAggregationOptionsByMetric(valueType, metricKind).map(this.toFindQueryResult); } - handleAlignmentPeriodType() { - return alignmentPeriods.map(s => ({ - ...s, - expandable: true, - })); + handleAlignmentPeriodQuery() { + return alignmentPeriods.map(this.toFindQueryResult); + } + + toFindQueryResult(x) { + return isString(x) ? { text: x, expandable: true } : { ...x, expandable: true }; + } + + getLabelKey({ type, metricLabelKey, resourceLabelKey }) { + switch (type) { + case MetricFindQueryTypes.MetricLabels: + return metricLabelKey; + break; + case MetricFindQueryTypes.ResourceLabels: + return resourceLabelKey; + default: + return ''; + } } } diff --git a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx index 651e4ea633f..2ddc1b1fae2 100644 --- a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx +++ b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx @@ -5,17 +5,18 @@ import SimpleDropdown from './SimpleDropdown'; import { TemplateQueryProps } from 'app/types/plugins'; import defaultsDeep from 'lodash/defaultsDeep'; import has from 'lodash/has'; +import { MetricFindQueryTypes } from '../types'; export class StackdriverTemplateQueryComponent extends PureComponent { queryTypes: Array<{ value: string; name: string }> = [ - { value: 'services', name: 'Services' }, - { value: 'metricTypes', name: 'Metric Types' }, - { value: 'metricLabels', name: 'Metric Labels' }, - { value: 'resourceLabels', name: 'Resource Labels' }, - { value: 'resourceTypes', name: 'Resource Types' }, - { value: 'aggregations', name: 'Aggregations' }, - { value: 'alignerns', name: 'Aligners' }, - { value: 'alignmentPeriods', name: 'Alignment Periods' }, + { value: MetricFindQueryTypes.Services, name: 'Services' }, + { value: MetricFindQueryTypes.MetricTypes, name: 'Metric Types' }, + { value: MetricFindQueryTypes.MetricLabels, name: 'Metric Labels' }, + { value: MetricFindQueryTypes.ResourceLabels, name: 'Resource Labels' }, + { value: MetricFindQueryTypes.ResourceTypes, name: 'Resource Types' }, + { value: MetricFindQueryTypes.Aggregations, name: 'Aggregations' }, + { value: MetricFindQueryTypes.Alignerns, name: 'Aligners' }, + { value: MetricFindQueryTypes.AlignmentPeriods, name: 'Alignment Periods' }, ]; defaults = { @@ -45,7 +46,7 @@ export class StackdriverTemplateQueryComponent extends PureComponent ); - case 'metricLabels': + case MetricFindQueryTypes.MetricLabels: return ( ); - case 'metricLabels': - case 'resourceLabels': - case 'resourceTypes': + case MetricFindQueryTypes.MetricLabels: + case MetricFindQueryTypes.ResourceLabels: + case MetricFindQueryTypes.ResourceTypes: const dropdown = this.switchMetaType(queryType); return ( @@ -133,8 +134,8 @@ export class StackdriverTemplateQueryComponent extends PureComponent ); - case 'alignerns': - case 'aggregations': + case MetricFindQueryTypes.Alignerns: + case MetricFindQueryTypes.Aggregations: return ( diff --git a/public/app/plugins/datasource/stackdriver/types.ts b/public/app/plugins/datasource/stackdriver/types.ts new file mode 100644 index 00000000000..43605623c78 --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/types.ts @@ -0,0 +1,10 @@ +export enum MetricFindQueryTypes { + Services = 'services', + MetricTypes = 'metricTypes', + MetricLabels = 'metricLabels', + ResourceLabels = 'resourceLabels', + ResourceTypes = 'resourceTypes', + Aggregations = 'aggregations', + Alignerns = 'alignerns', + AlignmentPeriods = 'alignmentPeriods', +} From 727472a3cbeb7c8ff3277c7ed4ffbf8d3c04d201 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Mon, 29 Oct 2018 17:42:15 +0100 Subject: [PATCH 036/322] stackdriver: refactor TemplateQueryComponent --- .../components/TemplateQueryComponent.tsx | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx index 2ddc1b1fae2..4eeb706b538 100644 --- a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx +++ b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx @@ -20,14 +20,14 @@ export class StackdriverTemplateQueryComponent extends PureComponent @@ -151,6 +147,10 @@ export class StackdriverTemplateQueryComponent extends PureComponent @@ -166,7 +166,7 @@ export class StackdriverTemplateQueryComponent extends PureComponent
    - {this.renderSwitch(this.state.type)} + {this.renderQueryTypeSwitch(this.state.type)} ); } From d80025d21559b45b55952350db01537327fae508 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Mon, 29 Oct 2018 17:59:10 +0100 Subject: [PATCH 037/322] stackdriver: streamline label change --- .../components/TemplateQueryComponent.tsx | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx index 4eeb706b538..429abb4a5a0 100644 --- a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx +++ b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx @@ -35,8 +35,7 @@ export class StackdriverTemplateQueryComponent extends PureComponent ); @@ -100,7 +100,7 @@ export class StackdriverTemplateQueryComponent extends PureComponent ); @@ -147,10 +147,6 @@ export class StackdriverTemplateQueryComponent extends PureComponent From 16c6d79dd6131d6827bdd1584cf7a97423e4272e Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 30 Oct 2018 09:43:08 +0100 Subject: [PATCH 038/322] stackdriver: use standard naming convention for selects --- ...MetricTypeSelector.tsx => MetricTypePicker.tsx} | 4 ++-- .../{ServiceSelector.tsx => ServicePicker.tsx} | 4 ++-- .../components/TemplateQueryComponent.tsx | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) rename public/app/plugins/datasource/stackdriver/components/{MetricTypeSelector.tsx => MetricTypePicker.tsx} (91%) rename public/app/plugins/datasource/stackdriver/components/{ServiceSelector.tsx => ServicePicker.tsx} (91%) diff --git a/public/app/plugins/datasource/stackdriver/components/MetricTypeSelector.tsx b/public/app/plugins/datasource/stackdriver/components/MetricTypePicker.tsx similarity index 91% rename from public/app/plugins/datasource/stackdriver/components/MetricTypeSelector.tsx rename to public/app/plugins/datasource/stackdriver/components/MetricTypePicker.tsx index 530512defcb..6c6917b6d78 100644 --- a/public/app/plugins/datasource/stackdriver/components/MetricTypeSelector.tsx +++ b/public/app/plugins/datasource/stackdriver/components/MetricTypePicker.tsx @@ -7,7 +7,7 @@ interface Props { metricDescriptors: any[]; } -const MetricTypeSelector: SFC = props => { +const MetricTypePicker: SFC = props => { const filterMetricTypes = () => { if (!props.selectedService) { return []; @@ -35,4 +35,4 @@ const MetricTypeSelector: SFC = props => { ); }; -export default MetricTypeSelector; +export default MetricTypePicker; diff --git a/public/app/plugins/datasource/stackdriver/components/ServiceSelector.tsx b/public/app/plugins/datasource/stackdriver/components/ServicePicker.tsx similarity index 91% rename from public/app/plugins/datasource/stackdriver/components/ServiceSelector.tsx rename to public/app/plugins/datasource/stackdriver/components/ServicePicker.tsx index 6bb29a46a6a..ec6c0d9cde8 100644 --- a/public/app/plugins/datasource/stackdriver/components/ServiceSelector.tsx +++ b/public/app/plugins/datasource/stackdriver/components/ServicePicker.tsx @@ -6,7 +6,7 @@ interface Props { metricDescriptors: any[]; } -const ServiceSelector: SFC = props => { +const ServicePicker: SFC = props => { const extractServices = () => { return extractServicesFromMetricDescriptors(props.metricDescriptors).map(m => ({ value: m.service, @@ -30,4 +30,4 @@ const ServiceSelector: SFC = props => { ); }; -export default ServiceSelector; +export default ServicePicker; diff --git a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx index 429abb4a5a0..ed03de9dbac 100644 --- a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx +++ b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; -import ServiceSelector from './ServiceSelector'; -import MetricTypeSelector from './MetricTypeSelector'; +import ServicePicker from './ServicePicker'; +import MetricTypePicker from './MetricTypePicker'; import SimpleDropdown from './SimpleDropdown'; import { TemplateQueryProps } from 'app/types/plugins'; import defaultsDeep from 'lodash/defaultsDeep'; @@ -113,7 +113,7 @@ export class StackdriverTemplateQueryComponent extends PureComponent + ); case MetricFindQueryTypes.MetricLabels: case MetricFindQueryTypes.ResourceLabels: @@ -121,8 +121,8 @@ export class StackdriverTemplateQueryComponent extends PureComponent - - + - - + Date: Tue, 30 Oct 2018 14:48:07 +0100 Subject: [PATCH 039/322] stackdriver: improve default state handling --- .../components/MetricTypePicker.tsx | 3 +- .../stackdriver/components/ServicePicker.tsx | 9 ++- .../stackdriver/components/SimpleDropdown.tsx | 2 +- .../components/TemplateQueryComponent.tsx | 55 ++++++++++++++----- 4 files changed, 52 insertions(+), 17 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/components/MetricTypePicker.tsx b/public/app/plugins/datasource/stackdriver/components/MetricTypePicker.tsx index 6c6917b6d78..f5a566a1e6e 100644 --- a/public/app/plugins/datasource/stackdriver/components/MetricTypePicker.tsx +++ b/public/app/plugins/datasource/stackdriver/components/MetricTypePicker.tsx @@ -4,6 +4,7 @@ import { getMetricTypesByService } from '../functions'; interface Props { onMetricTypeChange: any; selectedService: string; + selectedMetricType: string; metricDescriptors: any[]; } @@ -23,7 +24,7 @@ const MetricTypePicker: SFC = props => {
    Metric Types
    - {filterMetricTypes().map((qt, i) => (
    diff --git a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx index 8c8c89f9197..7b59d50a0fb 100644 --- a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx +++ b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx @@ -96,7 +96,8 @@ export class StackdriverTemplateQueryComponent extends PureComponent q.value === this.state.selectedQueryType); + this.props.onChange(queryModel, `Stackdriver - ${queryName.name}`); } isLabelQuery(queryType) { diff --git a/public/app/types/plugins.ts b/public/app/types/plugins.ts index eb8ff5dcaf2..f365e254736 100644 --- a/public/app/types/plugins.ts +++ b/public/app/types/plugins.ts @@ -102,7 +102,7 @@ export interface PluginsState { export interface TemplateQueryProps { query: any; - onChange: (c: any) => void; + onChange: (c: any, definition: string) => void; datasource: any; - // datasource: StackdriverDatasource; + definition: string; } From 2d14bd10f337e25e84e9d7535f740202557500fd Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 8 Nov 2018 15:00:25 +0100 Subject: [PATCH 064/322] stackdriver: add default value for query type --- .../stackdriver/components/TemplateQueryComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx index 7b59d50a0fb..cbac49867cb 100644 --- a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx +++ b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx @@ -18,7 +18,7 @@ export class StackdriverTemplateQueryComponent extends PureComponent Date: Thu, 8 Nov 2018 15:01:22 +0100 Subject: [PATCH 065/322] stackdriver: persist template variable definition --- public/app/features/templating/query_variable.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/public/app/features/templating/query_variable.ts b/public/app/features/templating/query_variable.ts index d3f39023cfb..5e6bdc264ed 100644 --- a/public/app/features/templating/query_variable.ts +++ b/public/app/features/templating/query_variable.ts @@ -44,6 +44,7 @@ export class QueryVariable implements Variable { tagsQuery: '', tagValuesQuery: '', skipUrlSync: false, + definition: '', }; /** @ngInject */ From f168dd5d3455e5102c9c7a07af3c4ba2b41338e9 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 8 Nov 2018 15:02:05 +0100 Subject: [PATCH 066/322] stackdriver: remove not used prop --- public/app/types/plugins.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/public/app/types/plugins.ts b/public/app/types/plugins.ts index f365e254736..80fd4f7c522 100644 --- a/public/app/types/plugins.ts +++ b/public/app/types/plugins.ts @@ -104,5 +104,4 @@ export interface TemplateQueryProps { query: any; onChange: (c: any, definition: string) => void; datasource: any; - definition: string; } From 24cb44e029e6ee0a000437b27530037f19550f9e Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 8 Nov 2018 15:15:34 +0100 Subject: [PATCH 067/322] stackdriver: rename params --- public/app/types/plugins.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/types/plugins.ts b/public/app/types/plugins.ts index 80fd4f7c522..c70dfc07d8f 100644 --- a/public/app/types/plugins.ts +++ b/public/app/types/plugins.ts @@ -102,6 +102,6 @@ export interface PluginsState { export interface TemplateQueryProps { query: any; - onChange: (c: any, definition: string) => void; + onChange: (query: any, definition: string) => void; datasource: any; } From 8333bf0b76834b2a43c92dcc6e70e1071ae726cc Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 8 Nov 2018 15:16:11 +0100 Subject: [PATCH 068/322] stackdriver: add simple render test --- .../TemplateQueryComponent.test.tsx | 17 +++++ .../TemplateQueryComponent.test.tsx.snap | 67 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.test.tsx create mode 100644 public/app/plugins/datasource/stackdriver/components/__snapshots__/TemplateQueryComponent.test.tsx.snap diff --git a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.test.tsx b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.test.tsx new file mode 100644 index 00000000000..acbff44bd4a --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.test.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import renderer from 'react-test-renderer'; +import { StackdriverTemplateQueryComponent } from './TemplateQueryComponent'; +import { TemplateQueryProps } from 'app/types/plugins'; + +describe('StackdriverTemplateQueryComponent', () => { + const props: TemplateQueryProps = { + onChange: (query, definition) => {}, + query: '', + datasource: {}, + }; + + it('renders correctly', () => { + const tree = renderer.create().toJSON(); + expect(tree).toMatchSnapshot(); + }); +}); diff --git a/public/app/plugins/datasource/stackdriver/components/__snapshots__/TemplateQueryComponent.test.tsx.snap b/public/app/plugins/datasource/stackdriver/components/__snapshots__/TemplateQueryComponent.test.tsx.snap new file mode 100644 index 00000000000..026e8ba09c4 --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/components/__snapshots__/TemplateQueryComponent.test.tsx.snap @@ -0,0 +1,67 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`StackdriverTemplateQueryComponent renders correctly 1`] = ` +Array [ +
    + + Query Types + +
    + +
    +
    , + "", +] +`; From 85ef5c252315634ef9809d24c8e617a3af1f93f6 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 8 Nov 2018 15:52:00 +0100 Subject: [PATCH 069/322] stackdriver: make sure we don't crash when selected service doesnt have a value --- .../stackdriver/components/TemplateQueryComponent.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx index cbac49867cb..6f6045b03e3 100644 --- a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx +++ b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx @@ -43,9 +43,14 @@ export class StackdriverTemplateQueryComponent extends PureComponent s.value === this.state.selectedService) - ? this.state.selectedService - : services[0].value; + + let selectedService = ''; + if (services.some(s => s.value === this.state.selectedService)) { + selectedService = this.state.selectedService; + } else if (services && services.length > 0) { + selectedService = services[0].value; + } + const { metricTypes, selectedMetricType } = getMetricTypes( metricDescriptors, this.state.selectedMetricType, From 7bdf97d3e61f3e54b8a0f09c3ca0628deeafd239 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 9 Nov 2018 09:54:11 +0100 Subject: [PATCH 070/322] stackdriver: remove lodash since object assign will do the trick --- .../stackdriver/components/TemplateQueryComponent.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx index 6f6045b03e3..8170521ff87 100644 --- a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx +++ b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx @@ -1,6 +1,5 @@ import React, { PureComponent } from 'react'; import { TemplateQueryProps } from 'app/types/plugins'; -import defaultsDeep from 'lodash/defaultsDeep'; import SimpleSelect from './SimpleSelect'; import { getMetricTypes, extractServicesFromMetricDescriptors } from '../functions'; import { MetricFindQueryTypes, TemplateQueryComponentData } from '../types'; @@ -34,7 +33,7 @@ export class StackdriverTemplateQueryComponent extends PureComponent Date: Fri, 9 Nov 2018 10:20:45 +0100 Subject: [PATCH 071/322] stackdriver: add tests for render snapshop and default query type --- .../TemplateQueryComponent.test.tsx | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.test.tsx b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.test.tsx index acbff44bd4a..05519c2e17d 100644 --- a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.test.tsx +++ b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.test.tsx @@ -3,15 +3,30 @@ import renderer from 'react-test-renderer'; import { StackdriverTemplateQueryComponent } from './TemplateQueryComponent'; import { TemplateQueryProps } from 'app/types/plugins'; -describe('StackdriverTemplateQueryComponent', () => { - const props: TemplateQueryProps = { - onChange: (query, definition) => {}, - query: '', - datasource: {}, - }; +jest.mock('../functions', () => ({ + getMetricTypes: () => Promise.resolve({ metricTypes: [], selectedMetricType: '' }), + extractServicesFromMetricDescriptors: m => m, +})); +const props: TemplateQueryProps = { + onChange: (query, definition) => {}, + query: '', + datasource: { + getMetricTypes: async p => [], + }, +}; + +describe('StackdriverTemplateQueryComponent', () => { it('renders correctly', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); + + it('should use the first query type in the array if no query type was saved before', done => { + props.onChange = (query, definition) => { + expect(definition).toBe('Stackdriver - Services'); + done(); + }; + renderer.create().toJSON(); + }); }); From 209b1c026e1a19df5498d5e820977f2fc7717d22 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 9 Nov 2018 10:22:36 +0100 Subject: [PATCH 072/322] stackdriver: update tests --- .../stackdriver/components/TemplateQueryComponent.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.test.tsx b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.test.tsx index 05519c2e17d..a31948e9a16 100644 --- a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.test.tsx +++ b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.test.tsx @@ -4,7 +4,7 @@ import { StackdriverTemplateQueryComponent } from './TemplateQueryComponent'; import { TemplateQueryProps } from 'app/types/plugins'; jest.mock('../functions', () => ({ - getMetricTypes: () => Promise.resolve({ metricTypes: [], selectedMetricType: '' }), + getMetricTypes: async () => ({ metricTypes: [], selectedMetricType: '' }), extractServicesFromMetricDescriptors: m => m, })); From 45f49c2e5bec50302ea4715603f698760b94580f Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 9 Nov 2018 10:29:22 +0100 Subject: [PATCH 073/322] stackdriver: remove services query type --- .../stackdriver/StackdriverMetricFindQuery.ts | 19 +------------------ .../components/TemplateQueryComponent.tsx | 6 +++--- .../datasource/stackdriver/functions.ts | 3 --- 3 files changed, 4 insertions(+), 24 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts index dd7fe6a22e3..e3537cc9599 100644 --- a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts +++ b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts @@ -2,12 +2,7 @@ import has from 'lodash/has'; import isString from 'lodash/isString'; import { alignmentPeriods } from './constants'; import { MetricFindQueryTypes } from './types'; -import { - extractServicesFromMetricDescriptors, - getMetricTypesByService, - getAlignmentOptionsByMetric, - getAggregationOptionsByMetric, -} from './functions'; +import { getMetricTypesByService, getAlignmentOptionsByMetric, getAggregationOptionsByMetric } from './functions'; export default class StackdriverMetricFindQuery { constructor(private datasource) {} @@ -15,8 +10,6 @@ export default class StackdriverMetricFindQuery { async query(query: any) { try { switch (query.selectedQueryType) { - case MetricFindQueryTypes.Services: - return this.handleServiceQuery(); case MetricFindQueryTypes.MetricTypes: return this.handleMetricTypesQuery(query); case MetricFindQueryTypes.MetricLabels: @@ -39,16 +32,6 @@ export default class StackdriverMetricFindQuery { } } - async handleServiceQuery() { - const metricDescriptors = await this.datasource.getMetricTypes(this.datasource.projectName); - const services = extractServicesFromMetricDescriptors(metricDescriptors); - return services.map(s => ({ - text: s.serviceShortName, - value: s.name, - expandable: true, - })); - } - async handleMetricTypesQuery({ selectedService }) { if (!selectedService) { return []; diff --git a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx index 8170521ff87..e9784d485dd 100644 --- a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx +++ b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.tsx @@ -1,12 +1,12 @@ import React, { PureComponent } from 'react'; +import uniqBy from 'lodash/uniqBy'; import { TemplateQueryProps } from 'app/types/plugins'; import SimpleSelect from './SimpleSelect'; -import { getMetricTypes, extractServicesFromMetricDescriptors } from '../functions'; +import { getMetricTypes } from '../functions'; import { MetricFindQueryTypes, TemplateQueryComponentData } from '../types'; export class StackdriverTemplateQueryComponent extends PureComponent { queryTypes: Array<{ value: string; name: string }> = [ - { value: MetricFindQueryTypes.Services, name: 'Services' }, { value: MetricFindQueryTypes.MetricTypes, name: 'Metric Types' }, { value: MetricFindQueryTypes.MetricLabels, name: 'Metric Labels' }, { value: MetricFindQueryTypes.ResourceLabels, name: 'Resource Labels' }, @@ -38,7 +38,7 @@ export class StackdriverTemplateQueryComponent extends PureComponent ({ + const services = uniqBy(metricDescriptors, 'service').map(m => ({ value: m.service, name: m.serviceShortName, })); diff --git a/public/app/plugins/datasource/stackdriver/functions.ts b/public/app/plugins/datasource/stackdriver/functions.ts index 30b4a7b84f6..79e8aa30325 100644 --- a/public/app/plugins/datasource/stackdriver/functions.ts +++ b/public/app/plugins/datasource/stackdriver/functions.ts @@ -1,8 +1,5 @@ -import uniqBy from 'lodash/uniqBy'; import { alignOptions, aggOptions } from './constants'; -export const extractServicesFromMetricDescriptors = metricDescriptors => uniqBy(metricDescriptors, 'service'); - export const getMetricTypesByService = (metricDescriptors, service) => metricDescriptors.filter(m => m.service === service); From b262be73e021e604a065e2191fcde2f7abba5dfd Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 9 Nov 2018 10:31:41 +0100 Subject: [PATCH 074/322] stackdriver: fix failing tests --- .../TemplateQueryComponent.test.tsx | 3 +-- .../TemplateQueryComponent.test.tsx.snap | 27 ++++++++++++++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.test.tsx b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.test.tsx index a31948e9a16..0d9f0e6d5b4 100644 --- a/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.test.tsx +++ b/public/app/plugins/datasource/stackdriver/components/TemplateQueryComponent.test.tsx @@ -5,7 +5,6 @@ import { TemplateQueryProps } from 'app/types/plugins'; jest.mock('../functions', () => ({ getMetricTypes: async () => ({ metricTypes: [], selectedMetricType: '' }), - extractServicesFromMetricDescriptors: m => m, })); const props: TemplateQueryProps = { @@ -24,7 +23,7 @@ describe('StackdriverTemplateQueryComponent', () => { it('should use the first query type in the array if no query type was saved before', done => { props.onChange = (query, definition) => { - expect(definition).toBe('Stackdriver - Services'); + expect(definition).toBe('Stackdriver - Metric Types'); done(); }; renderer.create().toJSON(); diff --git a/public/app/plugins/datasource/stackdriver/components/__snapshots__/TemplateQueryComponent.test.tsx.snap b/public/app/plugins/datasource/stackdriver/components/__snapshots__/TemplateQueryComponent.test.tsx.snap index 026e8ba09c4..c7e4d708383 100644 --- a/public/app/plugins/datasource/stackdriver/components/__snapshots__/TemplateQueryComponent.test.tsx.snap +++ b/public/app/plugins/datasource/stackdriver/components/__snapshots__/TemplateQueryComponent.test.tsx.snap @@ -17,13 +17,8 @@ Array [ className="gf-form-input" onChange={[Function]} required={true} - value="services" + value="metricTypes" > - + From b9d5fa1b36527080b86e35379a0923927ea231d0 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 15 Nov 2018 13:20:32 +0100 Subject: [PATCH 100/322] stackdriver: revert test code --- .../datasource/stackdriver/partials/query.editor.html | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/partials/query.editor.html b/public/app/plugins/datasource/stackdriver/partials/query.editor.html index 8763bfc36db..74e1b4a8501 100755 --- a/public/app/plugins/datasource/stackdriver/partials/query.editor.html +++ b/public/app/plugins/datasource/stackdriver/partials/query.editor.html @@ -13,14 +13,9 @@
    -
    Project - +
    - {{variable.query}} + {{variable.definition ? variable.definition : variable.query}}
    - - ) : null} - {supportsLogs && showingLogs ? ( - - ) : null} + + )} + {supportsLogs && ( + + + + )} )} diff --git a/public/app/features/explore/Graph.tsx b/public/app/features/explore/Graph.tsx index 9e4fea0d3de..61cf5753b19 100644 --- a/public/app/features/explore/Graph.tsx +++ b/public/app/features/explore/Graph.tsx @@ -77,7 +77,6 @@ interface GraphProps { data: any[]; height?: string; // e.g., '200px' id?: string; - loading?: boolean; range: RawTimeRange; split?: boolean; size?: { width: number; height: number }; @@ -188,12 +187,11 @@ export class Graph extends PureComponent { } render() { - const { height = '100px', id = 'graph', loading = false } = this.props; + const { height = '100px', id = 'graph' } = this.props; const data = this.getGraphData(); return ( -
    - {loading &&
    } + <> {this.props.data && this.props.data.length > MAX_NUMBER_OF_TIME_SERIES && !this.state.showAllTimeSeries && ( @@ -207,7 +205,7 @@ export class Graph extends PureComponent { )}
    -
    + ); } } diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index edde5acba92..a00aaccb028 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -97,7 +97,7 @@ export default class Logs extends PureComponent { />
    -
    +
    @@ -116,33 +116,30 @@ export default class Logs extends PureComponent {
    -
    - {loading &&
    } -
    - {hasData && - data.rows.map(row => ( - -
    - {showUtc &&
    {row.timestamp}
    } - {showLocalTime &&
    {row.timeLocal}
    } - {showLabels && ( -
    - {row.labels} -
    - )} -
    - +
    + {hasData && + data.rows.map(row => ( + +
    + {showUtc &&
    {row.timestamp}
    } + {showLocalTime &&
    {row.timeLocal}
    } + {showLabels && ( +
    + {row.labels}
    - - ))} -
    - {!loading && !hasData && 'No data was returned.'} + )} +
    + +
    +
    + ))}
    + {!loading && !hasData && 'No data was returned.'}
    ); } diff --git a/public/app/features/explore/Panel.tsx b/public/app/features/explore/Panel.tsx new file mode 100644 index 00000000000..dc75cb0ecca --- /dev/null +++ b/public/app/features/explore/Panel.tsx @@ -0,0 +1,34 @@ +import React, { PureComponent } from 'react'; + +interface Props { + isOpen: boolean; + label: string; + loading?: boolean; + onToggle: (isOpen: boolean) => void; +} + +export default class Panel extends PureComponent { + onClickToggle = () => this.props.onToggle(!this.props.isOpen); + + render() { + const { isOpen, loading } = this.props; + const iconClass = isOpen ? 'fa fa-caret-up' : 'fa fa-caret-down'; + const loaderClass = loading ? 'explore-panel__loader explore-panel__loader--active' : 'explore-panel__loader'; + return ( +
    +
    +
    + +
    +
    {this.props.label}
    +
    + {isOpen && ( +
    +
    + {this.props.children} +
    + )} +
    + ); + } +} diff --git a/public/app/features/explore/__snapshots__/Graph.test.tsx.snap b/public/app/features/explore/__snapshots__/Graph.test.tsx.snap index fd2010a76d3..6b9553d2e1d 100644 --- a/public/app/features/explore/__snapshots__/Graph.test.tsx.snap +++ b/public/app/features/explore/__snapshots__/Graph.test.tsx.snap @@ -1,9 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Render should render component 1`] = ` -
    +
    -
    +
    `; exports[`Render should render component with disclaimer 1`] = ` -
    +
    @@ -952,13 +948,11 @@ exports[`Render should render component with disclaimer 1`] = ` ] } /> -
    +
    `; exports[`Render should show query return no time series 1`] = ` -
    +
    -
    +
    `; diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 5a9db7e9b53..662835633db 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -173,6 +173,7 @@ export interface ExploreState { range: RawTimeRange; showingGraph: boolean; showingLogs: boolean; + showingStartPage?: boolean; showingTable: boolean; supportsGraph: boolean | null; supportsLogs: boolean | null; diff --git a/public/sass/pages/_explore.scss b/public/sass/pages/_explore.scss index 58b4ca17840..210920e848d 100644 --- a/public/sass/pages/_explore.scss +++ b/public/sass/pages/_explore.scss @@ -18,10 +18,39 @@ margin-left: 15px; } - // Graph panel needs a bit extra padding at top - .panel-container { + .explore-panel { + margin-top: $panel-margin; + } + + .explore-panel__body { padding: $panel-padding; - padding-top: 10px; + } + + .explore-panel__header { + padding: $panel-padding; + padding-top: 5px; + padding-bottom: 0; + display: flex; + cursor: pointer; + margin-bottom: 5px; + transition: all 0.1s linear; + } + + .explore-panel__header:hover { + transform: translateY(-1px); + } + + .explore-panel__header-label { + font-weight: 500; + margin-right: $panel-margin; + font-size: $font-size-h6; + box-shadow: $text-shadow-faint; + } + + .explore-panel__header-buttons { + margin-right: $panel-margin; + font-size: $font-size-lg; + line-height: $font-size-h6; } // Make sure wrap buttons around on small screens @@ -91,11 +120,16 @@ height: 2px; position: relative; overflow: hidden; - background: $text-color-faint; + background: none; margin: $panel-margin / 2; + transition: background-color 1s ease; } - .explore-panel__loader:after { + .explore-panel__loader--active { + background: $text-color-faint; + } + + .explore-panel__loader--active:after { content: ' '; display: block; width: 25%; @@ -221,17 +255,18 @@ .logs-controls { display: flex; + background-color: $page-bg; + padding: $panel-padding; + padding-top: 10px; + border-radius: $border-radius; + margin: 2*$panel-margin 0; + border: $panel-border; > * { margin-right: 1em; } } - .logs-options, - .logs-graph { - margin-bottom: $panel-margin; - } - .logs-meta { flex: 1; color: $text-color-weak; From 057f6111942d05b8fe64701c6398f7d75ae87568 Mon Sep 17 00:00:00 2001 From: Vinicyus Macedo <7549205+vinicyusmacedo@users.noreply.github.com> Date: Sat, 17 Nov 2018 00:48:54 -0200 Subject: [PATCH 109/322] Added google oauth account id --- pkg/social/google_oauth.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/social/google_oauth.go b/pkg/social/google_oauth.go index e9ab08305f6..91247bfb56f 100644 --- a/pkg/social/google_oauth.go +++ b/pkg/social/google_oauth.go @@ -32,6 +32,7 @@ func (s *SocialGoogle) IsSignupAllowed() bool { func (s *SocialGoogle) UserInfo(client *http.Client, token *oauth2.Token) (*BasicUserInfo, error) { var data struct { + Id int `json:"id"` Name string `json:"name"` Email string `json:"email"` } From 152261413dd4f3287e3417a4c57a76285c2a7587 Mon Sep 17 00:00:00 2001 From: Vinicyus Macedo <7549205+vinicyusmacedo@users.noreply.github.com> Date: Sat, 17 Nov 2018 12:05:06 -0200 Subject: [PATCH 110/322] Added Id to BasicUserInfo returns --- pkg/social/google_oauth.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/social/google_oauth.go b/pkg/social/google_oauth.go index 91247bfb56f..81f19d95faf 100644 --- a/pkg/social/google_oauth.go +++ b/pkg/social/google_oauth.go @@ -48,6 +48,7 @@ func (s *SocialGoogle) UserInfo(client *http.Client, token *oauth2.Token) (*Basi } return &BasicUserInfo{ + Id: fmt.Sprintf("%d", data.Id), Name: data.Name, Email: data.Email, Login: data.Email, From ba5a0023236249279b4fc9cb8766d6a913d2d624 Mon Sep 17 00:00:00 2001 From: Victor Cinaglia Date: Sun, 18 Nov 2018 18:17:43 -0500 Subject: [PATCH 111/322] Mitigate XSS vulnerabilities in Singlestat panel Sanitize `prefix` and `postfix` fields. Re-arrange code slightly in order to handle variable interpolation. --- public/app/plugins/panel/singlestat/module.ts | 30 +++++++------------ .../panel/singlestat/specs/singlestat.test.ts | 4 ++- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts index eafa3cf23f4..26ce6b7722d 100644 --- a/public/app/plugins/panel/singlestat/module.ts +++ b/public/app/plugins/panel/singlestat/module.ts @@ -77,7 +77,7 @@ class SingleStatCtrl extends MetricsPanelCtrl { }; /** @ngInject */ - constructor($scope, $injector, private linkSrv) { + constructor($scope, $injector, private linkSrv, private $sanitize) { super($scope, $injector); _.defaults(this.panel, this.panelDefaults); @@ -398,14 +398,15 @@ class SingleStatCtrl extends MetricsPanelCtrl { const $location = this.$location; const linkSrv = this.linkSrv; const $timeout = this.$timeout; + const $sanitize = this.$sanitize; const panel = ctrl.panel; const templateSrv = this.templateSrv; let data, linkInfo; const $panelContainer = elem.find('.panel-container'); elem = elem.find('.singlestat-panel'); - function applyColoringThresholds(value, valueString) { - const color = getColorForValue(data, value); + function applyColoringThresholds(valueString) { + const color = getColorForValue(data, data.value); if (color) { return '' + valueString + ''; } @@ -413,8 +414,9 @@ class SingleStatCtrl extends MetricsPanelCtrl { return valueString; } - function getSpan(className, fontSize, value) { - value = templateSrv.replace(value, data.scopedVars); + function getSpan(className, fontSize, applyColoring, value) { + value = $sanitize(templateSrv.replace(value, data.scopedVars)); + value = applyColoring ? applyColoringThresholds(value) : value; return '' + value + ''; } @@ -422,25 +424,13 @@ class SingleStatCtrl extends MetricsPanelCtrl { let body = '
    '; if (panel.prefix) { - let prefix = panel.prefix; - if (panel.colorPrefix) { - prefix = applyColoringThresholds(data.value, panel.prefix); - } - body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, prefix); + body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, panel.colorPrefix, panel.prefix); } - let value = data.valueFormatted; - if (panel.colorValue) { - value = applyColoringThresholds(data.value, value); - } - body += getSpan('singlestat-panel-value', panel.valueFontSize, value); + body += getSpan('singlestat-panel-value', panel.valueFontSize, panel.colorValue, data.valueFormatted); if (panel.postfix) { - let postfix = panel.postfix; - if (panel.colorPostfix) { - postfix = applyColoringThresholds(data.value, panel.postfix); - } - body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, postfix); + body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, panel.colorPostfix, panel.postfix); } body += '
    '; diff --git a/public/app/plugins/panel/singlestat/specs/singlestat.test.ts b/public/app/plugins/panel/singlestat/specs/singlestat.test.ts index 6003acd89a6..902d722f0ad 100644 --- a/public/app/plugins/panel/singlestat/specs/singlestat.test.ts +++ b/public/app/plugins/panel/singlestat/specs/singlestat.test.ts @@ -14,6 +14,8 @@ describe('SingleStatCtrl', () => { get: () => {}, }; + const $sanitize = {}; + SingleStatCtrl.prototype.panel = { events: { on: () => {}, @@ -31,7 +33,7 @@ describe('SingleStatCtrl', () => { describe(desc, () => { ctx.setup = setupFunc => { beforeEach(() => { - ctx.ctrl = new SingleStatCtrl($scope, $injector, {}); + ctx.ctrl = new SingleStatCtrl($scope, $injector, {}, $sanitize); setupFunc(); ctx.ctrl.onDataReceived(ctx.data); ctx.data = ctx.ctrl.data; From 76cbd7f0de481743c327ef3832527c3e1f38f1bf Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 19 Nov 2018 09:10:19 +0100 Subject: [PATCH 112/322] alerting: reduce the length of range queries since we introduce deboucing the length should matter less. But going below 5m as default might be weird depending on datasource. --- public/app/features/alerting/AlertTabCtrl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/alerting/AlertTabCtrl.ts b/public/app/features/alerting/AlertTabCtrl.ts index 2efd3c062c4..ef68ddcf4a5 100644 --- a/public/app/features/alerting/AlertTabCtrl.ts +++ b/public/app/features/alerting/AlertTabCtrl.ts @@ -218,7 +218,7 @@ export class AlertTabCtrl { buildDefaultCondition() { return { type: 'query', - query: { params: ['A', '15m', 'now'] }, + query: { params: ['A', '5m', 'now'] }, reducer: { type: 'avg', params: [] }, evaluator: { type: 'gt', params: [null] }, operator: { type: 'and' }, From fb5c59c6111beb8ee5541c21715e3d0a013e624c Mon Sep 17 00:00:00 2001 From: supercharlesliu Date: Mon, 19 Nov 2018 17:08:10 +0800 Subject: [PATCH 113/322] Add GET /api/users/:id/teams for orgAdmin --- pkg/api/api.go | 1 + pkg/api/user.go | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pkg/api/api.go b/pkg/api/api.go index c372debdb72..0526ee80afe 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -140,6 +140,7 @@ func (hs *HTTPServer) registerRoutes() { usersRoute.Get("/", Wrap(SearchUsers)) usersRoute.Get("/search", Wrap(SearchUsersWithPaging)) usersRoute.Get("/:id", Wrap(GetUserByID)) + usersRoute.Get("/:id/teams", Wrap(GetUserTeams)) usersRoute.Get("/:id/orgs", Wrap(GetUserOrgList)) // query parameters /users/lookup?loginOrEmail=admin@example.com usersRoute.Get("/lookup", Wrap(GetUserByLoginOrEmail)) diff --git a/pkg/api/user.go b/pkg/api/user.go index 7116ad83f3f..f03f2d90990 100644 --- a/pkg/api/user.go +++ b/pkg/api/user.go @@ -113,7 +113,16 @@ func GetSignedInUserOrgList(c *m.ReqContext) Response { // GET /api/user/teams func GetSignedInUserTeamList(c *m.ReqContext) Response { - query := m.GetTeamsByUserQuery{OrgId: c.OrgId, UserId: c.UserId} + return getUserTeamList(c.OrgId, c.UserId) +} + +// GET /api/users/:id/teams +func GetUserTeams(c *m.ReqContext) Response { + return getUserTeamList(c.OrgId, c.ParamsInt64("id")) +} + +func getUserTeamList(userID int64, orgID int64) Response { + query := m.GetTeamsByUserQuery{OrgId: orgID, UserId: userID} if err := bus.Dispatch(&query); err != nil { return Error(500, "Failed to get user teams", err) @@ -122,11 +131,10 @@ func GetSignedInUserTeamList(c *m.ReqContext) Response { for _, team := range query.Result { team.AvatarUrl = dtos.GetGravatarUrlWithDefault(team.Email, team.Name) } - return JSON(200, query.Result) } -// GET /api/user/:id/orgs +// GET /api/users/:id/orgs func GetUserOrgList(c *m.ReqContext) Response { return getUserOrgList(c.ParamsInt64(":id")) } From 215242128bdefb5aa0cb6e7e537760b936ad0370 Mon Sep 17 00:00:00 2001 From: supercharlesliu Date: Mon, 19 Nov 2018 17:09:45 +0800 Subject: [PATCH 114/322] Fix param --- pkg/api/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/api/user.go b/pkg/api/user.go index f03f2d90990..6db9c6f6baf 100644 --- a/pkg/api/user.go +++ b/pkg/api/user.go @@ -118,7 +118,7 @@ func GetSignedInUserTeamList(c *m.ReqContext) Response { // GET /api/users/:id/teams func GetUserTeams(c *m.ReqContext) Response { - return getUserTeamList(c.OrgId, c.ParamsInt64("id")) + return getUserTeamList(c.OrgId, c.ParamsInt64(":id")) } func getUserTeamList(userID int64, orgID int64) Response { From 23b1fbcf48ee787a940bc35696edba2e6d6bc1b7 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 19 Nov 2018 10:22:03 +0100 Subject: [PATCH 115/322] changelog: adds note about closing #11893 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea6b5b9732f..0fe86b2601e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * **Stackdriver**: Not possible to authenticate using GCE metadata server [#13669](https://github.com/grafana/grafana/issues/13669) * **Teams**: Team preferences (theme, home dashboard, timezone) support [#12550](https://github.com/grafana/grafana/issues/12550) * **Graph**: Time regions support enabling highlight of weekdays and/or certain timespans [#5930](https://github.com/grafana/grafana/issues/5930) +* **Auth**: Automatic redirect to sign-in with OAuth [#11893](https://github.com/grafana/grafana/issues/11893), thx [@Nick-Triller]https://github.com/Nick-Triller ### Minor From 8130067fd1301cdee8da2de22a6cb5a00bc2e24b Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 19 Nov 2018 10:47:37 +0100 Subject: [PATCH 116/322] changelog: adds note about closing #7886 & #6202 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fe86b2601e..7826c0ae214 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### New Features +* **Alerting**: Introduce alert debouncing with the `FOR` setting. [#7886](https://github.com/grafana/grafana/issues/7886) & [#6202](https://github.com/grafana/grafana/issues/6202) * **Alerting**: Option to disable OK alert notifications [#12330](https://github.com/grafana/grafana/issues/12330) & [#6696](https://github.com/grafana/grafana/issues/6696), thx [@davewat](https://github.com/davewat) * **Postgres/MySQL/MSSQL**: Adds support for configuration of max open/idle connections and connection max lifetime. Also, panels with multiple SQL queries will now be executed concurrently [#11711](https://github.com/grafana/grafana/issues/11711), thx [@connection-reset](https://github.com/connection-reset) * **MySQL**: Graphical query builder [#13762](https://github.com/grafana/grafana/issues/13762), thx [svenklemm](https://github.com/svenklemm) From 60fd8ee9d4fa0a2b65762509e7a6c040aebc55ca Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 19 Nov 2018 11:11:13 +0100 Subject: [PATCH 117/322] fix id returned from google is a string --- pkg/social/google_oauth.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/social/google_oauth.go b/pkg/social/google_oauth.go index 81f19d95faf..05ae2a481f2 100644 --- a/pkg/social/google_oauth.go +++ b/pkg/social/google_oauth.go @@ -32,7 +32,7 @@ func (s *SocialGoogle) IsSignupAllowed() bool { func (s *SocialGoogle) UserInfo(client *http.Client, token *oauth2.Token) (*BasicUserInfo, error) { var data struct { - Id int `json:"id"` + Id string `json:"id"` Name string `json:"name"` Email string `json:"email"` } @@ -48,7 +48,7 @@ func (s *SocialGoogle) UserInfo(client *http.Client, token *oauth2.Token) (*Basi } return &BasicUserInfo{ - Id: fmt.Sprintf("%d", data.Id), + Id: data.Id, Name: data.Name, Email: data.Email, Login: data.Email, From e2007733f4abeff9a9a931b099fb396814eb3792 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 19 Nov 2018 11:20:44 +0100 Subject: [PATCH 118/322] build: table-driven tests for publisher. --- .../build/release_publisher/publisher_test.go | 75 +++++++++++-------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/scripts/build/release_publisher/publisher_test.go b/scripts/build/release_publisher/publisher_test.go index 76a1446406a..a61f6ef432d 100644 --- a/scripts/build/release_publisher/publisher_test.go +++ b/scripts/build/release_publisher/publisher_test.go @@ -4,44 +4,57 @@ import "testing" func TestPreparingReleaseFromRemote(t *testing.T) { - var builder releaseBuilder - - versionIn := "v5.2.0-beta1" - expectedVersion := "5.2.0-beta1" - whatsNewUrl := "https://whatsnews.foo/" - relNotesUrl := "https://relnotes.foo/" - expectedArch := "amd64" - expectedOs := "linux" - buildArtifacts := []buildArtifact{{expectedOs, expectedArch, ".linux-amd64.tar.gz"}} - - builder = releaseFromExternalContent{ - getter: mockHttpGetter{}, - rawVersion: versionIn, - artifactConfigurations: buildArtifactConfigurations, + cases := []struct { + version string + expectedVersion string + whatsNewUrl string + relNotesUrl string + expectedArch string + expectedOs string + buildArtifacts []buildArtifact + }{ + { + version: "v5.2.0-beta1", + expectedVersion: "5.2.0-beta1", + whatsNewUrl: "https://whatsnews.foo/", + relNotesUrl: "https://relnotes.foo/", + expectedArch: "amd64", + expectedOs: "linux", + buildArtifacts: []buildArtifact{{"linux", "amd64", ".linux-amd64.tar.gz"}}, + }, } - rel, _ := builder.prepareRelease("https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana", whatsNewUrl, relNotesUrl, false) + for _, test := range cases { + var builder releaseBuilder + builder = releaseFromExternalContent{ + getter: mockHttpGetter{}, + rawVersion: test.version, + artifactConfigurations: test.buildArtifacts, + } - if !rel.Beta || rel.Stable { - t.Errorf("%s should have been tagged as beta (not stable), but wasn't .", versionIn) - } + rel, _ := builder.prepareRelease("https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana", test.whatsNewUrl, test.relNotesUrl, false) - if rel.Version != expectedVersion { - t.Errorf("Expected version to be %s, but it was %s.", expectedVersion, rel.Version) - } + if !rel.Beta || rel.Stable { + t.Errorf("%s should have been tagged as beta (not stable), but wasn't .", test.version) + } - expectedBuilds := len(buildArtifacts) - if len(rel.Builds) != expectedBuilds { - t.Errorf("Expected %v builds, but got %v.", expectedBuilds, len(rel.Builds)) - } + if rel.Version != test.expectedVersion { + t.Errorf("Expected version to be %s, but it was %s.", test.expectedVersion, rel.Version) + } - build := rel.Builds[0] - if build.Arch != expectedArch { - t.Errorf("Expected arch to be %v, but it was %v", expectedArch, build.Arch) - } + expectedBuilds := len(test.buildArtifacts) + if len(rel.Builds) != expectedBuilds { + t.Errorf("Expected %v builds, but got %v.", expectedBuilds, len(rel.Builds)) + } - if build.Os != expectedOs { - t.Errorf("Expected arch to be %v, but it was %v", expectedOs, build.Os) + build := rel.Builds[0] + if build.Arch != test.expectedArch { + t.Errorf("Expected arch to be %v, but it was %v", test.expectedArch, build.Arch) + } + + if build.Os != test.expectedOs { + t.Errorf("Expected arch to be %v, but it was %v", test.expectedOs, build.Os) + } } } From 6c68976cab6d0921af92b099e238ba7826339c49 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 19 Nov 2018 11:29:03 +0100 Subject: [PATCH 119/322] changelog: add notes about closing #13924 [skip ci] --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7826c0ae214..0625b01786e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ * **Stackdriver**: Not possible to authenticate using GCE metadata server [#13669](https://github.com/grafana/grafana/issues/13669) * **Teams**: Team preferences (theme, home dashboard, timezone) support [#12550](https://github.com/grafana/grafana/issues/12550) * **Graph**: Time regions support enabling highlight of weekdays and/or certain timespans [#5930](https://github.com/grafana/grafana/issues/5930) -* **Auth**: Automatic redirect to sign-in with OAuth [#11893](https://github.com/grafana/grafana/issues/11893), thx [@Nick-Triller]https://github.com/Nick-Triller +* **OAuth**: Automatic redirect to sign-in with OAuth [#11893](https://github.com/grafana/grafana/issues/11893), thx [@Nick-Triller](https://github.com/Nick-Triller) ### Minor @@ -27,6 +27,7 @@ * **Alerting**: Can't receive DingDing alert when alert is triggered [#13723](https://github.com/grafana/grafana/issues/13723), thx [@Yukinoshita-Yukino](https://github.com/Yukinoshita-Yukino) * **Internal metrics**: Renamed `grafana_info` to `grafana_build_info` and added branch, goversion and revision [#13876](https://github.com/grafana/grafana/pull/13876) * **Datasource Proxy**: Keep trailing slash for datasource proxy requests [#13326](https://github.com/grafana/grafana/pull/13326), thx [@ryantxu](https://github.com/ryantxu) +* **OAuth**: Fix Google OAuth relies on email, not google account id [#13924](https://github.com/grafana/grafana/issues/13924), thx [@vinicyusmacedo](https://github.com/vinicyusmacedo) ### Breaking changes From 26b1cc5dcf47248e55da58c7927aa7a97dcd7d6d Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Mon, 19 Nov 2018 11:34:40 +0100 Subject: [PATCH 120/322] stackdriver: join resource and metric labels. split them in values and keys --- .../stackdriver/StackdriverMetricFindQuery.ts | 35 ++++++++++++++----- .../components/VariableQueryEditor.test.tsx | 4 +-- .../components/VariableQueryEditor.tsx | 34 +++++++----------- .../plugins/datasource/stackdriver/types.ts | 4 +-- 4 files changed, 43 insertions(+), 34 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts index 226bdf7ee22..2dea8c0405a 100644 --- a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts +++ b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts @@ -1,4 +1,3 @@ -import has from 'lodash/has'; import isString from 'lodash/isString'; import { alignmentPeriods } from './constants'; import { MetricFindQueryTypes } from './types'; @@ -12,9 +11,10 @@ export default class StackdriverMetricFindQuery { switch (query.selectedQueryType) { case MetricFindQueryTypes.MetricTypes: return this.handleMetricTypesQuery(query); - case MetricFindQueryTypes.MetricLabels: - case MetricFindQueryTypes.ResourceLabels: - return this.handleLabelQuery(query); + case MetricFindQueryTypes.LabelKeys: + return this.handleLabelKeysQuery(query); + case MetricFindQueryTypes.LabelValues: + return this.handleLabelValuesQuery(query); case MetricFindQueryTypes.ResourceTypes: return this.handleResourceTypeQuery(query); case MetricFindQueryTypes.Aligners: @@ -44,16 +44,33 @@ export default class StackdriverMetricFindQuery { })); } - async handleLabelQuery({ selectedQueryType, selectedMetricType, labelKey }) { + async handleLabelKeysQuery({ selectedQueryType, selectedMetricType, labelKey }) { if (!selectedMetricType) { return []; } - const refId = 'handleLabelsQueryType'; + const refId = 'handleLabelKeysQuery'; const response = await this.datasource.getLabels(selectedMetricType, refId); - if (!has(response, `meta.${selectedQueryType}.${labelKey}`)) { + const labelKeys = response.meta + ? [...Object.keys(response.meta.resourceLabels), ...Object.keys(response.meta.metricLabels)] + : []; + return labelKeys.map(this.toFindQueryResult); + } + + async handleLabelValuesQuery({ selectedQueryType, selectedMetricType, labelKey }) { + if (!selectedMetricType) { return []; } - return response.meta[selectedQueryType][labelKey].map(this.toFindQueryResult); + const refId = 'handleLabelValuesQuery'; + const response = await this.datasource.getLabels(selectedMetricType, refId); + + let values = []; + if (response.meta && response.meta.metricLabels && response.meta.metricLabels.hasOwnProperty(labelKey)) { + values = response.meta.metricLabels[labelKey]; + } else if (response.meta && response.meta.resourceLabels && response.meta.resourceLabels.hasOwnProperty(labelKey)) { + values = response.meta.resourceLabels[labelKey]; + } + + return values.map(this.toFindQueryResult); } async handleResourceTypeQuery({ selectedMetricType }) { @@ -62,7 +79,7 @@ export default class StackdriverMetricFindQuery { } const refId = 'handleResourceTypeQueryQueryType'; const response = await this.datasource.getLabels(selectedMetricType, refId); - return response.meta.resourceTypes.map(this.toFindQueryResult); + return response.meta.resourceTypes ? response.meta.resourceTypes.map(this.toFindQueryResult) : []; } async handleAlignersQuery({ selectedMetricType }) { diff --git a/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.test.tsx b/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.test.tsx index d3b1aa54656..5143348f0fe 100644 --- a/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.test.tsx +++ b/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.test.tsx @@ -34,9 +34,9 @@ describe('VariableQueryEditor', () => { describe('and an existing variable is edited', () => { it('should trigger new query using the saved query type', done => { - props.query = { selectedQueryType: MetricFindQueryTypes.MetricLabels }; + props.query = { selectedQueryType: MetricFindQueryTypes.LabelKeys }; props.onChange = (query, definition) => { - expect(definition).toBe('Stackdriver - Metric Labels'); + expect(definition).toBe('Stackdriver - Labels Keys'); done(); }; renderer.create().toJSON(); diff --git a/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.tsx b/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.tsx index dae612fcc64..4ce5910e191 100644 --- a/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.tsx +++ b/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.tsx @@ -8,8 +8,8 @@ import { MetricFindQueryTypes, VariableQueryData } from '../types'; export class StackdriverVariableQueryEditor extends PureComponent { queryTypes: Array<{ value: string; name: string }> = [ { value: MetricFindQueryTypes.MetricTypes, name: 'Metric Types' }, - { value: MetricFindQueryTypes.MetricLabels, name: 'Metric Labels' }, - { value: MetricFindQueryTypes.ResourceLabels, name: 'Resource Labels' }, + { value: MetricFindQueryTypes.LabelKeys, name: 'Label Keys' }, + { value: MetricFindQueryTypes.LabelValues, name: 'Label Values' }, { value: MetricFindQueryTypes.ResourceTypes, name: 'Resource Types' }, { value: MetricFindQueryTypes.Aggregations, name: 'Aggregations' }, { value: MetricFindQueryTypes.Aligners, name: 'Aligners' }, @@ -57,7 +57,7 @@ export class StackdriverVariableQueryEditor extends PureComponent l === this.state.labelKey) ? this.state.labelKey : labels[0]; result = { labels, labelKey }; } @@ -127,8 +123,8 @@ export class StackdriverVariableQueryEditor extends PureComponent ); - case MetricFindQueryTypes.MetricLabels: - case MetricFindQueryTypes.ResourceLabels: + case MetricFindQueryTypes.LabelKeys: + case MetricFindQueryTypes.LabelValues: case MetricFindQueryTypes.ResourceTypes: return ( @@ -144,16 +140,12 @@ export class StackdriverVariableQueryEditor extends PureComponent this.onMetricTypeChange(e)} label="Metric Types" /> - {queryType !== MetricFindQueryTypes.ResourceTypes && ( + {queryType === MetricFindQueryTypes.LabelValues && ( ({ value: l, name: l }))} onValueChange={e => this.onLabelKeyChange(e)} - label={ - this.state.selectedQueryType === MetricFindQueryTypes.ResourceLabels - ? 'Resource Label Key' - : 'Metric Label Key' - } + label="Label Keys" /> )} diff --git a/public/app/plugins/datasource/stackdriver/types.ts b/public/app/plugins/datasource/stackdriver/types.ts index 80d416cbe5a..f88bd427b28 100644 --- a/public/app/plugins/datasource/stackdriver/types.ts +++ b/public/app/plugins/datasource/stackdriver/types.ts @@ -1,7 +1,7 @@ export enum MetricFindQueryTypes { MetricTypes = 'metricTypes', - MetricLabels = 'metricLabels', - ResourceLabels = 'resourceLabels', + LabelKeys = 'labelKeys', + LabelValues = 'labelValues', ResourceTypes = 'resourceTypes', Aggregations = 'aggregations', Aligners = 'aligners', From d81d2f00f65673f9cab8635d990460d3560277b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 19 Nov 2018 12:26:15 +0100 Subject: [PATCH 121/322] fixed issue with panel size when going into edit mode --- public/app/features/dashboard/panel_model.ts | 2 +- public/app/features/panel/panel_directive.ts | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard/panel_model.ts b/public/app/features/dashboard/panel_model.ts index ed032a118fe..dc8a509f2eb 100644 --- a/public/app/features/dashboard/panel_model.ts +++ b/public/app/features/dashboard/panel_model.ts @@ -95,7 +95,7 @@ export class PanelModel { setViewMode(fullscreen: boolean, isEditing: boolean) { this.fullscreen = fullscreen; this.isEditing = isEditing; - this.events.emit('panel-size-changed'); + this.events.emit('view-mode-changed'); } updateGridPos(newPos: GridPos) { diff --git a/public/app/features/panel/panel_directive.ts b/public/app/features/panel/panel_directive.ts index aef7ca5e256..61c2be2adea 100644 --- a/public/app/features/panel/panel_directive.ts +++ b/public/app/features/panel/panel_directive.ts @@ -140,6 +140,19 @@ module.directive('grafanaPanel', ($rootScope, $document, $timeout) => { }); }); + ctrl.events.on('view-mode-changed', () => { + // first wait one pass for dashboard fullscreen view mode to take effect (classses being applied) + setTimeout(() => { + // then recalc style + ctrl.calculatePanelHeight(); + // then wait another cycle (this might not be needed) + $timeout(() => { + ctrl.render(); + resizeScrollableContent(); + }); + }); + }); + // set initial height ctrl.calculatePanelHeight(); From c22e7f42633d0bdd6e37efa8c55550ed064c5007 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 19 Nov 2018 13:23:17 +0100 Subject: [PATCH 122/322] fix group sync cta link --- public/app/features/teams/TeamGroupSync.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/app/features/teams/TeamGroupSync.tsx b/public/app/features/teams/TeamGroupSync.tsx index 939dfcc8e31..3c3561c605e 100644 --- a/public/app/features/teams/TeamGroupSync.tsx +++ b/public/app/features/teams/TeamGroupSync.tsx @@ -124,7 +124,11 @@ export class TeamGroupSync extends PureComponent {
    {headerTooltip} - + Learn more
    From 2d361eeabff27c30b71bb93b388ff527cccb9a80 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 19 Nov 2018 13:26:35 +0100 Subject: [PATCH 123/322] builds: introduces enum for relase type. --- .../build/release_publisher/externalrelease.go | 8 ++++++-- scripts/build/release_publisher/localrelease.go | 2 +- scripts/build/release_publisher/publisher.go | 16 ++++++++++++---- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/scripts/build/release_publisher/externalrelease.go b/scripts/build/release_publisher/externalrelease.go index 181dd4088ad..c18b94e5bbb 100644 --- a/scripts/build/release_publisher/externalrelease.go +++ b/scripts/build/release_publisher/externalrelease.go @@ -17,14 +17,18 @@ type releaseFromExternalContent struct { func (re releaseFromExternalContent) prepareRelease(baseArchiveUrl, whatsNewUrl string, releaseNotesUrl string, nightly bool) (*release, error) { version := re.rawVersion[1:] isBeta := strings.Contains(version, "beta") + var rt ReleaseType + if isBeta { + rt = BETA + } builds := []build{} for _, ba := range re.artifactConfigurations { - sha256, err := re.getter.getContents(fmt.Sprintf("%s.sha256", ba.getUrl(baseArchiveUrl, version, isBeta))) + sha256, err := re.getter.getContents(fmt.Sprintf("%s.sha256", ba.getUrl(baseArchiveUrl, version, rt))) if err != nil { return nil, err } - builds = append(builds, newBuild(baseArchiveUrl, ba, version, isBeta, sha256)) + builds = append(builds, newBuild(baseArchiveUrl, ba, version, rt, sha256)) } r := release{ diff --git a/scripts/build/release_publisher/localrelease.go b/scripts/build/release_publisher/localrelease.go index 0bbecff9327..e416a6dd490 100644 --- a/scripts/build/release_publisher/localrelease.go +++ b/scripts/build/release_publisher/localrelease.go @@ -70,7 +70,7 @@ func createBuildWalker(path string, data *buildData, archiveTypes []buildArtifac data.version = version data.builds = append(data.builds, build{ Os: archive.os, - Url: archive.getUrl(baseArchiveUrl, version, false), + Url: archive.getUrl(baseArchiveUrl, version, NIGHTLY), Sha256: string(shaBytes), Arch: archive.arch, }) diff --git a/scripts/build/release_publisher/publisher.go b/scripts/build/release_publisher/publisher.go index d2c10d1640f..24be6d7dc85 100644 --- a/scripts/build/release_publisher/publisher.go +++ b/scripts/build/release_publisher/publisher.go @@ -61,13 +61,21 @@ func (p *publisher) postRelease(r *release) error { return nil } +type ReleaseType int + +const ( + STABLE ReleaseType = iota + 1 + BETA + NIGHTLY +) + type buildArtifact struct { os string arch string urlPostfix string } -func (t buildArtifact) getUrl(baseArchiveUrl, version string, isBeta bool) string { +func (t buildArtifact) getUrl(baseArchiveUrl, version string, rt ReleaseType) string { prefix := "-" rhelReleaseExtra := "" @@ -75,7 +83,7 @@ func (t buildArtifact) getUrl(baseArchiveUrl, version string, isBeta bool) strin prefix = "_" } - if !isBeta && t.os == "rhel" { + if rt == BETA && t.os == "rhel" { rhelReleaseExtra = "-1" } @@ -141,10 +149,10 @@ var buildArtifactConfigurations = []buildArtifact{ }, } -func newBuild(baseArchiveUrl string, ba buildArtifact, version string, isBeta bool, sha256 string) build { +func newBuild(baseArchiveUrl string, ba buildArtifact, version string, rt ReleaseType, sha256 string) build { return build{ Os: ba.os, - Url: ba.getUrl(baseArchiveUrl, version, isBeta), + Url: ba.getUrl(baseArchiveUrl, version, rt), Sha256: sha256, Arch: ba.arch, } From 638eca3cdb809ebfc7b4360bef47d926b8b12f91 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 19 Nov 2018 13:59:20 +0100 Subject: [PATCH 124/322] update changelog [skip ci] --- CHANGELOG.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0625b01786e..26ec99e9437 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ ### Minor +* **Security**: Upgrade macaron session package to fix security issue. [#14043](https://github.com/grafana/grafana/pull/14043) * **Cloudwatch**: Show all available CloudWatch regions [#12308](https://github.com/grafana/grafana/issues/12308), thx [@mtanda](https://github.com/mtanda) * **Cloudwatch**: AWS/Connect metrics and dimensions [#13970](https://github.com/grafana/grafana/pull/13970), thx [@zcoffy](https://github.com/zcoffy) * **Postgres**: Add delta window function to postgres query builder [#13925](https://github.com/grafana/grafana/issues/13925), thx [svenklemm](https://github.com/svenklemm) @@ -33,10 +34,6 @@ * Postgres/MySQL/MSSQL datasources now per default uses `max open connections` = `unlimited` (earlier 10), `max idle connections` = `2` (earlier 10) and `connection max lifetime` = `4` hours (earlier unlimited) -# 5.3.5 (unreleased) - -* **Security**: Upgrade macaron session package to fix security issue. [#14043](https://github.com/grafana/grafana/pull/14043) - # 5.3.4 (2018-11-13) * **Alerting**: Delete alerts when parent folder was deleted [#13322](https://github.com/grafana/grafana/issues/13322) From 862815d18de44d23d2cc58b9ddb6d97a8940664c Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 19 Nov 2018 13:59:16 +0100 Subject: [PATCH 125/322] go meta lint errors --- pkg/services/alerting/eval_context.go | 2 +- pkg/services/alerting/eval_context_test.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/services/alerting/eval_context.go b/pkg/services/alerting/eval_context.go index 17ed448bd2a..4db942e0a55 100644 --- a/pkg/services/alerting/eval_context.go +++ b/pkg/services/alerting/eval_context.go @@ -125,7 +125,7 @@ func (c *EvalContext) GetNewState() m.AlertStateType { return ns } - since := time.Now().Sub(c.Rule.LastStateChange) + since := time.Since(c.Rule.LastStateChange) if c.PrevAlertState == m.AlertStatePending && since > c.Rule.For { return m.AlertStateAlerting } diff --git a/pkg/services/alerting/eval_context_test.go b/pkg/services/alerting/eval_context_test.go index 4c9b88f1881..af7e66b2f07 100644 --- a/pkg/services/alerting/eval_context_test.go +++ b/pkg/services/alerting/eval_context_test.go @@ -36,7 +36,6 @@ func TestGetStateFromEvalContext(t *testing.T) { name string expected models.AlertStateType applyFn func(ec *EvalContext) - focus bool }{ { name: "ok -> alerting", From 8f0d3ff7eac934eff66a86d087310dda606bd34a Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 19 Nov 2018 14:06:18 +0100 Subject: [PATCH 126/322] build: fixes a bug where nightly rpm builds would be handled as stable. --- .../release_publisher/externalrelease.go | 14 ++-- .../build/release_publisher/localrelease.go | 3 + scripts/build/release_publisher/publisher.go | 16 ++++- .../build/release_publisher/publisher_test.go | 67 ++++++++++++++++--- 4 files changed, 83 insertions(+), 17 deletions(-) diff --git a/scripts/build/release_publisher/externalrelease.go b/scripts/build/release_publisher/externalrelease.go index c18b94e5bbb..992cba38f90 100644 --- a/scripts/build/release_publisher/externalrelease.go +++ b/scripts/build/release_publisher/externalrelease.go @@ -16,10 +16,14 @@ type releaseFromExternalContent struct { func (re releaseFromExternalContent) prepareRelease(baseArchiveUrl, whatsNewUrl string, releaseNotesUrl string, nightly bool) (*release, error) { version := re.rawVersion[1:] - isBeta := strings.Contains(version, "beta") + beta := strings.Contains(version, "beta") var rt ReleaseType - if isBeta { + if beta { rt = BETA + } else if nightly { + rt = NIGHTLY + } else { + rt = STABLE } builds := []build{} @@ -34,9 +38,9 @@ func (re releaseFromExternalContent) prepareRelease(baseArchiveUrl, whatsNewUrl r := release{ Version: version, ReleaseDate: time.Now().UTC(), - Stable: !isBeta && !nightly, - Beta: isBeta, - Nightly: nightly, + Stable: rt.stable(), + Beta: rt.beta(), + Nightly: rt.nightly(), WhatsNewUrl: whatsNewUrl, ReleaseNotesUrl: releaseNotesUrl, Builds: builds, diff --git a/scripts/build/release_publisher/localrelease.go b/scripts/build/release_publisher/localrelease.go index e416a6dd490..4f4575c4ff4 100644 --- a/scripts/build/release_publisher/localrelease.go +++ b/scripts/build/release_publisher/localrelease.go @@ -18,6 +18,9 @@ type releaseLocalSources struct { } func (r releaseLocalSources) prepareRelease(baseArchiveUrl, whatsNewUrl string, releaseNotesUrl string, nightly bool) (*release, error) { + if !nightly { + return nil, errors.New("Local releases only supported for nightly builds.") + } buildData := r.findBuilds(baseArchiveUrl) rel := release{ diff --git a/scripts/build/release_publisher/publisher.go b/scripts/build/release_publisher/publisher.go index 24be6d7dc85..dd0415ad3ce 100644 --- a/scripts/build/release_publisher/publisher.go +++ b/scripts/build/release_publisher/publisher.go @@ -69,13 +69,25 @@ const ( NIGHTLY ) +func (rt ReleaseType) beta() bool { + return rt == BETA +} + +func (rt ReleaseType) stable() bool { + return rt == STABLE +} + +func (rt ReleaseType) nightly() bool { + return rt == NIGHTLY +} + type buildArtifact struct { os string arch string urlPostfix string } -func (t buildArtifact) getUrl(baseArchiveUrl, version string, rt ReleaseType) string { +func (t buildArtifact) getUrl(baseArchiveUrl, version string, releaseType ReleaseType) string { prefix := "-" rhelReleaseExtra := "" @@ -83,7 +95,7 @@ func (t buildArtifact) getUrl(baseArchiveUrl, version string, rt ReleaseType) st prefix = "_" } - if rt == BETA && t.os == "rhel" { + if releaseType == STABLE && t.os == "rhel" { rhelReleaseExtra = "-1" } diff --git a/scripts/build/release_publisher/publisher_test.go b/scripts/build/release_publisher/publisher_test.go index a61f6ef432d..39a5bd5969b 100644 --- a/scripts/build/release_publisher/publisher_test.go +++ b/scripts/build/release_publisher/publisher_test.go @@ -5,23 +5,61 @@ import "testing" func TestPreparingReleaseFromRemote(t *testing.T) { cases := []struct { - version string + version string expectedVersion string - whatsNewUrl string - relNotesUrl string - expectedArch string - expectedOs string - buildArtifacts []buildArtifact + whatsNewUrl string + relNotesUrl string + nightly bool + expectedBeta bool + expectedStable bool + expectedArch string + expectedOs string + expectedUrl string + baseArchiveUrl string + buildArtifacts []buildArtifact }{ { version: "v5.2.0-beta1", expectedVersion: "5.2.0-beta1", whatsNewUrl: "https://whatsnews.foo/", relNotesUrl: "https://relnotes.foo/", + nightly: false, + expectedBeta: true, + expectedStable: false, expectedArch: "amd64", expectedOs: "linux", + expectedUrl: "https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.2.0-beta1.linux-amd64.tar.gz", + baseArchiveUrl: "https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana", buildArtifacts: []buildArtifact{{"linux", "amd64", ".linux-amd64.tar.gz"}}, }, + { + version: "v5.2.3", + expectedVersion: "5.2.3", + whatsNewUrl: "https://whatsnews.foo/", + relNotesUrl: "https://relnotes.foo/", + nightly: false, + expectedBeta: false, + expectedStable: true, + expectedArch: "amd64", + expectedOs: "rhel", + expectedUrl: "https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.2.3-1.x86_64.rpm", + baseArchiveUrl: "https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana", + buildArtifacts: []buildArtifact{{"rhel", "amd64", ".x86_64.rpm"}}, + }, + { + version: "v5.4.0-pre1asdf", + expectedVersion: "5.4.0-pre1asdf", + whatsNewUrl: "https://whatsnews.foo/", + relNotesUrl: "https://relnotes.foo/", + nightly: true, + expectedBeta: false, + expectedStable: false, + expectedArch: "amd64", + expectedOs: "rhel", + expectedUrl: "https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.4.0-pre1asdf.x86_64.rpm", + baseArchiveUrl: "https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana", + buildArtifacts: []buildArtifact{{"rhel", "amd64", ".x86_64.rpm"}}, + }, } for _, test := range cases { @@ -32,10 +70,10 @@ func TestPreparingReleaseFromRemote(t *testing.T) { artifactConfigurations: test.buildArtifacts, } - rel, _ := builder.prepareRelease("https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana", test.whatsNewUrl, test.relNotesUrl, false) + rel, _ := builder.prepareRelease(test.baseArchiveUrl, test.whatsNewUrl, test.relNotesUrl, test.nightly) - if !rel.Beta || rel.Stable { - t.Errorf("%s should have been tagged as beta (not stable), but wasn't .", test.version) + if rel.Beta != test.expectedBeta || rel.Stable != test.expectedStable { + t.Errorf("%s should have been tagged as beta=%v, stable=%v.", test.version, test.expectedBeta, test.expectedStable) } if rel.Version != test.expectedVersion { @@ -53,7 +91,11 @@ func TestPreparingReleaseFromRemote(t *testing.T) { } if build.Os != test.expectedOs { - t.Errorf("Expected arch to be %v, but it was %v", test.expectedOs, build.Os) + t.Errorf("Expected os to be %v, but it was %v", test.expectedOs, build.Os) + } + + if build.Url != test.expectedUrl { + t.Errorf("Expected url to be %v, but it was %v", test.expectedUrl, build.Url) } } } @@ -129,4 +171,9 @@ func TestPreparingReleaseFromLocal(t *testing.T) { if build.Os != expectedOs { t.Fatalf("Expected os to be %s, but was %s", expectedOs, build.Os) } + + _, err := builder.prepareRelease("", "", "", false) + if err == nil { + t.Error("Error was nil, but expected an error as the local releaser only supports nightly builds.") + } } From ac55aeff953b76be440a8c0056a05211dc611c80 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 19 Nov 2018 14:12:04 +0100 Subject: [PATCH 127/322] build: minor refactor. --- scripts/build/release_publisher/publisher.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build/release_publisher/publisher.go b/scripts/build/release_publisher/publisher.go index dd0415ad3ce..ad54a1ccb9b 100644 --- a/scripts/build/release_publisher/publisher.go +++ b/scripts/build/release_publisher/publisher.go @@ -95,7 +95,7 @@ func (t buildArtifact) getUrl(baseArchiveUrl, version string, releaseType Releas prefix = "_" } - if releaseType == STABLE && t.os == "rhel" { + if releaseType.stable() && t.os == "rhel" { rhelReleaseExtra = "-1" } From 1157a62375a2c69d336bd573d974246bc725235f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 19 Nov 2018 14:20:19 +0100 Subject: [PATCH 128/322] fixed issue switching back from mixed data source, introduced by react panels changes --- public/app/features/panel/metrics_tab.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/panel/metrics_tab.ts b/public/app/features/panel/metrics_tab.ts index f520b5eefc0..508e7ac1ab4 100644 --- a/public/app/features/panel/metrics_tab.ts +++ b/public/app/features/panel/metrics_tab.ts @@ -76,7 +76,6 @@ export class MetricsTabCtrl { return; } - this.datasourceInstance = option.datasource; this.setDatasource(option.datasource); this.updateDatasourceOptions(); } @@ -96,6 +95,7 @@ export class MetricsTabCtrl { }); } + this.datasourceInstance = datasource; this.panel.datasource = datasource.value; this.panel.refresh(); } From b041ad4134935d536e47c0d613ad81d12e44276c Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 19 Nov 2018 14:32:39 +0100 Subject: [PATCH 129/322] linter. --- scripts/build/release_publisher/publisher_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/build/release_publisher/publisher_test.go b/scripts/build/release_publisher/publisher_test.go index 39a5bd5969b..1d5fb683b2c 100644 --- a/scripts/build/release_publisher/publisher_test.go +++ b/scripts/build/release_publisher/publisher_test.go @@ -63,8 +63,7 @@ func TestPreparingReleaseFromRemote(t *testing.T) { } for _, test := range cases { - var builder releaseBuilder - builder = releaseFromExternalContent{ + builder := releaseFromExternalContent{ getter: mockHttpGetter{}, rawVersion: test.version, artifactConfigurations: test.buildArtifacts, From 0aeaec1ac67021a0f64c2265ad7c601d3b77d72b Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Mon, 19 Nov 2018 14:44:40 +0100 Subject: [PATCH 130/322] stackdriver: add support for template variables --- .../plugins/VariableQueryComponentLoader.tsx | 3 +- .../stackdriver/StackdriverMetricFindQuery.ts | 38 +++++++++++-------- .../components/VariableQueryEditor.test.tsx | 1 + .../components/VariableQueryEditor.tsx | 35 ++++++++++------- .../datasource/stackdriver/functions.ts | 16 +++++++- .../stackdriver/query_aggregation_ctrl.ts | 4 +- public/app/types/plugins.ts | 1 + 7 files changed, 64 insertions(+), 34 deletions(-) diff --git a/public/app/features/plugins/VariableQueryComponentLoader.tsx b/public/app/features/plugins/VariableQueryComponentLoader.tsx index c7bb826d302..631161b4e9b 100644 --- a/public/app/features/plugins/VariableQueryComponentLoader.tsx +++ b/public/app/features/plugins/VariableQueryComponentLoader.tsx @@ -14,7 +14,7 @@ async function loadComponent(module) { } /** @ngInject */ -function variableQueryEditorLoader() { +function variableQueryEditorLoader(templateSrv) { return { restrict: 'E', link: async (scope, elem) => { @@ -23,6 +23,7 @@ function variableQueryEditorLoader() { datasource: scope.currentDatasource, query: scope.current.query, onChange: scope.onQueryChange, + templateSrv, }; ReactDOM.render(, elem[0]); scope.$on('$destroy', () => { diff --git a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts index 2dea8c0405a..8920fed989e 100644 --- a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts +++ b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts @@ -1,7 +1,12 @@ import isString from 'lodash/isString'; import { alignmentPeriods } from './constants'; import { MetricFindQueryTypes } from './types'; -import { getMetricTypesByService, getAlignmentOptionsByMetric, getAggregationOptionsByMetric } from './functions'; +import { + getMetricTypesByService, + getAlignmentOptionsByMetric, + getAggregationOptionsByMetric, + getLabelKeys, +} from './functions'; export default class StackdriverMetricFindQuery { constructor(private datasource) {} @@ -37,37 +42,34 @@ export default class StackdriverMetricFindQuery { return []; } const metricDescriptors = await this.datasource.getMetricTypes(this.datasource.projectName); - return getMetricTypesByService(metricDescriptors, selectedService).map(s => ({ + return getMetricTypesByService(metricDescriptors, this.datasource.templateSrv.replace(selectedService)).map(s => ({ text: s.displayName, value: s.type, expandable: true, })); } - async handleLabelKeysQuery({ selectedQueryType, selectedMetricType, labelKey }) { + async handleLabelKeysQuery({ selectedMetricType }) { if (!selectedMetricType) { return []; } - const refId = 'handleLabelKeysQuery'; - const response = await this.datasource.getLabels(selectedMetricType, refId); - const labelKeys = response.meta - ? [...Object.keys(response.meta.resourceLabels), ...Object.keys(response.meta.metricLabels)] - : []; + const labelKeys = await getLabelKeys(this.datasource, selectedMetricType); return labelKeys.map(this.toFindQueryResult); } - async handleLabelValuesQuery({ selectedQueryType, selectedMetricType, labelKey }) { + async handleLabelValuesQuery({ selectedMetricType, labelKey }) { if (!selectedMetricType) { return []; } const refId = 'handleLabelValuesQuery'; const response = await this.datasource.getLabels(selectedMetricType, refId); - + const interpolatedKey = this.datasource.templateSrv.replace(labelKey); + const [name] = interpolatedKey.split('.').reverse(); let values = []; - if (response.meta && response.meta.metricLabels && response.meta.metricLabels.hasOwnProperty(labelKey)) { - values = response.meta.metricLabels[labelKey]; - } else if (response.meta && response.meta.resourceLabels && response.meta.resourceLabels.hasOwnProperty(labelKey)) { - values = response.meta.resourceLabels[labelKey]; + if (response.meta && response.meta.metricLabels && response.meta.metricLabels.hasOwnProperty(name)) { + values = response.meta.metricLabels[name]; + } else if (response.meta && response.meta.resourceLabels && response.meta.resourceLabels.hasOwnProperty(name)) { + values = response.meta.resourceLabels[name]; } return values.map(this.toFindQueryResult); @@ -87,7 +89,9 @@ export default class StackdriverMetricFindQuery { return []; } const metricDescriptors = await this.datasource.getMetricTypes(this.datasource.projectName); - const { valueType, metricKind } = metricDescriptors.find(m => m.type === selectedMetricType); + const { valueType, metricKind } = metricDescriptors.find( + m => m.type === this.datasource.templateSrv.replace(selectedMetricType) + ); return getAlignmentOptionsByMetric(valueType, metricKind).map(this.toFindQueryResult); } @@ -96,7 +100,9 @@ export default class StackdriverMetricFindQuery { return []; } const metricDescriptors = await this.datasource.getMetricTypes(this.datasource.projectName); - const { valueType, metricKind } = metricDescriptors.find(m => m.type === selectedMetricType); + const { valueType, metricKind } = metricDescriptors.find( + m => m.type === this.datasource.templateSrv.replace(selectedMetricType) + ); return getAggregationOptionsByMetric(valueType, metricKind).map(this.toFindQueryResult); } diff --git a/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.test.tsx b/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.test.tsx index 5143348f0fe..d1fc3d87288 100644 --- a/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.test.tsx +++ b/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.test.tsx @@ -14,6 +14,7 @@ const props: VariableQueryProps = { datasource: { getMetricTypes: async p => [], }, + templateSrv: { replace: s => s }, }; describe('VariableQueryEditor', () => { diff --git a/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.tsx b/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.tsx index 4ce5910e191..20eb451ce4b 100644 --- a/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.tsx +++ b/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.tsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import uniqBy from 'lodash/uniqBy'; import { VariableQueryProps } from 'app/types/plugins'; import SimpleSelect from './SimpleSelect'; -import { getMetricTypes } from '../functions'; +import { getMetricTypes, getLabelKeys } from '../functions'; import { MetricFindQueryTypes, VariableQueryData } from '../types'; export class StackdriverVariableQueryEditor extends PureComponent { @@ -40,7 +40,7 @@ export class StackdriverVariableQueryEditor extends PureComponent s.value === this.state.selectedService)) { + if (services.some(s => s.value === this.props.templateSrv.replace(this.state.selectedService))) { selectedService = this.state.selectedService; } else if (services && services.length > 0) { selectedService = services[0].value; @@ -49,6 +49,7 @@ export class StackdriverVariableQueryEditor extends PureComponent l === this.state.labelKey) ? this.state.labelKey : labels[0]; + const labels = await getLabelKeys(this.props.datasource, selectedMetricType); + const labelKey = labels.some(l => l === this.props.templateSrv.replace(this.state.labelKey)) + ? this.state.labelKey + : labels[0]; result = { labels, labelKey }; } return result; } + insertTemplateVariables(options) { + const templateVariables = this.props.templateSrv.variables.map(v => ({ name: `$${v.name}`, value: `$${v.name}` })); + return [...templateVariables, ...options]; + } + renderQueryTypeSwitch(queryType) { switch (queryType) { case MetricFindQueryTypes.MetricTypes: @@ -136,14 +143,14 @@ export class StackdriverVariableQueryEditor extends PureComponent this.onMetricTypeChange(e)} label="Metric Types" /> {queryType === MetricFindQueryTypes.LabelValues && ( ({ value: l, name: l }))} + options={this.insertTemplateVariables(this.state.labels.map(l => ({ value: l, name: l })))} onValueChange={e => this.onLabelKeyChange(e)} label="Label Keys" /> @@ -162,7 +169,7 @@ export class StackdriverVariableQueryEditor extends PureComponent this.onMetricTypeChange(e)} label="Metric Types" /> diff --git a/public/app/plugins/datasource/stackdriver/functions.ts b/public/app/plugins/datasource/stackdriver/functions.ts index 79e8aa30325..0b1a37aa933 100644 --- a/public/app/plugins/datasource/stackdriver/functions.ts +++ b/public/app/plugins/datasource/stackdriver/functions.ts @@ -3,12 +3,12 @@ import { alignOptions, aggOptions } from './constants'; export const getMetricTypesByService = (metricDescriptors, service) => metricDescriptors.filter(m => m.service === service); -export const getMetricTypes = (metricDescriptors, metricType, selectedService) => { +export const getMetricTypes = (metricDescriptors, metricType, interpolatedMetricType, selectedService) => { const metricTypes = getMetricTypesByService(metricDescriptors, selectedService).map(m => ({ value: m.type, name: m.displayName, })); - const metricTypeExistInArray = metricTypes.some(m => m.value === metricType); + const metricTypeExistInArray = metricTypes.some(m => m.value === interpolatedMetricType); const selectedMetricType = metricTypeExistInArray ? metricType : metricTypes[0].value; return { metricTypes, @@ -31,3 +31,15 @@ export const getAggregationOptionsByMetric = (valueType, metricKind) => { return i.valueTypes.indexOf(valueType) !== -1 && i.metricKinds.indexOf(metricKind) !== -1; }); }; + +export const getLabelKeys = async (datasource, selectedMetricType) => { + const refId = 'handleLabelKeysQuery'; + const response = await datasource.getLabels(selectedMetricType, refId); + const labelKeys = response.meta + ? [ + ...Object.keys(response.meta.resourceLabels).map(l => `resource.label.${l}`), + ...Object.keys(response.meta.metricLabels).map(l => `metric.label.${l}`), + ] + : []; + return labelKeys; +}; diff --git a/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts index 03fa3b6cecb..628cc494242 100644 --- a/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts @@ -65,7 +65,9 @@ export class StackdriverAggregationCtrl { const selectedAlignment = this.alignOptions.find( ap => ap.value === this.templateSrv.replace(this.target.aggregation.perSeriesAligner) ); - return `${kbn.secondsToHms(this.$scope.alignmentPeriod)} interval (${selectedAlignment.text})`; + return `${kbn.secondsToHms(this.$scope.alignmentPeriod)} interval (${ + selectedAlignment ? selectedAlignment.text : '' + })`; } deselectAggregationOption(notValidOptionValue: string) { diff --git a/public/app/types/plugins.ts b/public/app/types/plugins.ts index 95d27afd496..9e3264be70c 100644 --- a/public/app/types/plugins.ts +++ b/public/app/types/plugins.ts @@ -104,4 +104,5 @@ export interface VariableQueryProps { query: any; onChange: (query: any, definition: string) => void; datasource: any; + templateSrv: any; } From 6aaaadac216511b76be39bfb63c9b719ca2a56b3 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Mon, 19 Nov 2018 14:48:26 +0100 Subject: [PATCH 131/322] stackdriver: update docs --- docs/sources/features/datasources/stackdriver.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/sources/features/datasources/stackdriver.md b/docs/sources/features/datasources/stackdriver.md index c346710fe73..2c14d897d8e 100644 --- a/docs/sources/features/datasources/stackdriver.md +++ b/docs/sources/features/datasources/stackdriver.md @@ -158,9 +158,9 @@ Example Result: `compute.googleapis.com/instance/cpu/usage_time - server1-prod` It is also possible to resolve the name of the Monitored Resource Type. -| Alias Pattern Format | Description | Example Result | -| ------------------------ | ------------------------------------------------| ---------------- | -| `{{resource.type}}` | returns the name of the monitored resource type | `gce_instance` | +| Alias Pattern Format | Description | Example Result | +| -------------------- | ----------------------------------------------- | -------------- | +| `{{resource.type}}` | returns the name of the monitored resource type | `gce_instance` | Example Alias By: `{{resource.type}} - {{metric.type}}` @@ -181,9 +181,9 @@ Variable of the type *Query* allows you to query Stackdriver for various types o | Name | Description | | ------------------- | ------------------------------------------------------------------------------------------------- | -| *Mectric Types* | Returns a list of metric type names that are available for the specified service. | -| *Metric Labels* | Returns a list of label values for the `metric label` in the specified metric. | -| *Resource Labels* | Returns a list of label values for the `resource label` in the specified metric. | +| *Metric Types* | Returns a list of metric type names that are available for the specified service. | +| *Labels Keys* | Returns a list of keys for `metric label` and `resource label` in the specified metric. | +| *Labels Values* | Returns a list of values for the label in the specified metric. | | *Resource Types* | Returns a list of resource types for the the specified metric. | | *Aggregations* | Returns a list of aggregations (cross series reducers) for the the specified metric. | | *Aligners* | Returns a list of aligners (per series aligners) for the the specified metric. | From 3c91e4de561e3647c6a1c326ff0b102de5651226 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 19 Nov 2018 14:35:47 +0100 Subject: [PATCH 132/322] changelog: add notes about closing #13655 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26ec99e9437..2fee7f312c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ * **Internal metrics**: Renamed `grafana_info` to `grafana_build_info` and added branch, goversion and revision [#13876](https://github.com/grafana/grafana/pull/13876) * **Datasource Proxy**: Keep trailing slash for datasource proxy requests [#13326](https://github.com/grafana/grafana/pull/13326), thx [@ryantxu](https://github.com/ryantxu) * **OAuth**: Fix Google OAuth relies on email, not google account id [#13924](https://github.com/grafana/grafana/issues/13924), thx [@vinicyusmacedo](https://github.com/vinicyusmacedo) +* **Dashboard**: Toggle legend using keyboard shortcut [#13655](https://github.com/grafana/grafana/issues/13655), thx [@davewat](https://github.com/davewat) ### Breaking changes From 1a64b44c913278b31e2f2069ccc601e68fe78f21 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 19 Nov 2018 14:40:41 +0100 Subject: [PATCH 133/322] changelog: add notes about closing #13425 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fee7f312c0..855bc630801 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ * **Datasource Proxy**: Keep trailing slash for datasource proxy requests [#13326](https://github.com/grafana/grafana/pull/13326), thx [@ryantxu](https://github.com/ryantxu) * **OAuth**: Fix Google OAuth relies on email, not google account id [#13924](https://github.com/grafana/grafana/issues/13924), thx [@vinicyusmacedo](https://github.com/vinicyusmacedo) * **Dashboard**: Toggle legend using keyboard shortcut [#13655](https://github.com/grafana/grafana/issues/13655), thx [@davewat](https://github.com/davewat) +* **Teams**: Fix cannot select team if not included in initial search [#13425](https://github.com/grafana/grafana/issues/13425) ### Breaking changes From a77c86169e012b5c98e2cc65361ae77d0111abe7 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 19 Nov 2018 14:42:15 +0100 Subject: [PATCH 134/322] changelog: add notes about closing #13555 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 855bc630801..9c6207b77ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ * **Datasource Proxy**: Keep trailing slash for datasource proxy requests [#13326](https://github.com/grafana/grafana/pull/13326), thx [@ryantxu](https://github.com/ryantxu) * **OAuth**: Fix Google OAuth relies on email, not google account id [#13924](https://github.com/grafana/grafana/issues/13924), thx [@vinicyusmacedo](https://github.com/vinicyusmacedo) * **Dashboard**: Toggle legend using keyboard shortcut [#13655](https://github.com/grafana/grafana/issues/13655), thx [@davewat](https://github.com/davewat) +* **Dashboard**: Fix render dashboard row drag handle only in edit mode [#13555](https://github.com/grafana/grafana/issues/13555), thx [@praveensastry](https://github.com/praveensastry) * **Teams**: Fix cannot select team if not included in initial search [#13425](https://github.com/grafana/grafana/issues/13425) ### Breaking changes From 8246ee343c058e8111483744675593092df7f953 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 19 Nov 2018 14:43:20 +0100 Subject: [PATCH 135/322] changelog: add notes about closing #13946 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c6207b77ef..66dc012d069 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ * **Elasticsearch**: Fix switching to/from es raw document metric query [#6367](https://github.com/grafana/grafana/issues/6367) * **Elasticsearch**: Fix deprecation warning about terms aggregation order key in Elasticsearch 6.x [#11977](https://github.com/grafana/grafana/issues/11977) * **Table**: Fix CSS alpha background-color applied twice in table cell with link [#13606](https://github.com/grafana/grafana/issues/13606), thx [@grisme](https://github.com/grisme) +* **Singlestat**: Fix XSS in prefix/postfix [#13946](https://github.com/grafana/grafana/issues/13946), thx [@cinaglia](https://github.com/cinaglia) * **Units**: New clock time format, to format ms or second values as for example `01h:59m`, [#13635](https://github.com/grafana/grafana/issues/13635), thx [@franciscocpg](https://github.com/franciscocpg) * **Alerting**: Increaste default duration for queries [#13945](https://github.com/grafana/grafana/pull/13945) * **Alerting**: More options for the Slack Alert notifier [#13993](https://github.com/grafana/grafana/issues/13993), thx [@andreykaipov](https://github.com/andreykaipov) From dbb7396c772a1a9a51ec03773ed73d1c422de79f Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 19 Nov 2018 14:44:58 +0100 Subject: [PATCH 136/322] changelog: add notes about closing #13876 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66dc012d069..ffee373adc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ * **Alerting**: Increaste default duration for queries [#13945](https://github.com/grafana/grafana/pull/13945) * **Alerting**: More options for the Slack Alert notifier [#13993](https://github.com/grafana/grafana/issues/13993), thx [@andreykaipov](https://github.com/andreykaipov) * **Alerting**: Can't receive DingDing alert when alert is triggered [#13723](https://github.com/grafana/grafana/issues/13723), thx [@Yukinoshita-Yukino](https://github.com/Yukinoshita-Yukino) +* **Alerting**: Increase Telegram captions length limit [#13876](https://github.com/grafana/grafana/pull/13876), thx [@skgsergio](https://github.com/skgsergio) * **Internal metrics**: Renamed `grafana_info` to `grafana_build_info` and added branch, goversion and revision [#13876](https://github.com/grafana/grafana/pull/13876) * **Datasource Proxy**: Keep trailing slash for datasource proxy requests [#13326](https://github.com/grafana/grafana/pull/13326), thx [@ryantxu](https://github.com/ryantxu) * **OAuth**: Fix Google OAuth relies on email, not google account id [#13924](https://github.com/grafana/grafana/issues/13924), thx [@vinicyusmacedo](https://github.com/vinicyusmacedo) From 34746e42efb2f8dee6fea0408131249861f8c387 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 19 Nov 2018 14:47:01 +0100 Subject: [PATCH 137/322] changelog: add notes about closing #13605 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ffee373adc4..654d2477451 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ * **Postgres**: Add delta window function to postgres query builder [#13925](https://github.com/grafana/grafana/issues/13925), thx [svenklemm](https://github.com/svenklemm) * **Elasticsearch**: Fix switching to/from es raw document metric query [#6367](https://github.com/grafana/grafana/issues/6367) * **Elasticsearch**: Fix deprecation warning about terms aggregation order key in Elasticsearch 6.x [#11977](https://github.com/grafana/grafana/issues/11977) +* **Graph**: Render dots when no connecting line can be made [#13605](https://github.com/grafana/grafana/issues/13605), thx [@jsferrei](https://github.com/jsferrei) * **Table**: Fix CSS alpha background-color applied twice in table cell with link [#13606](https://github.com/grafana/grafana/issues/13606), thx [@grisme](https://github.com/grisme) * **Singlestat**: Fix XSS in prefix/postfix [#13946](https://github.com/grafana/grafana/issues/13946), thx [@cinaglia](https://github.com/cinaglia) * **Units**: New clock time format, to format ms or second values as for example `01h:59m`, [#13635](https://github.com/grafana/grafana/issues/13635), thx [@franciscocpg](https://github.com/franciscocpg) From f5dfaacee3822217cb6571f3482f13a63a534c03 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 19 Nov 2018 14:48:12 +0100 Subject: [PATCH 138/322] changelog: add notes about closing #13810 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 654d2477451..92f3c1b8c2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ * **Security**: Upgrade macaron session package to fix security issue. [#14043](https://github.com/grafana/grafana/pull/14043) * **Cloudwatch**: Show all available CloudWatch regions [#12308](https://github.com/grafana/grafana/issues/12308), thx [@mtanda](https://github.com/mtanda) * **Cloudwatch**: AWS/Connect metrics and dimensions [#13970](https://github.com/grafana/grafana/pull/13970), thx [@zcoffy](https://github.com/zcoffy) +* **Cloudwatch**: Enable using variables in the stats field [#13810](https://github.com/grafana/grafana/issues/13810), thx [@mtanda](https://github.com/mtanda) * **Postgres**: Add delta window function to postgres query builder [#13925](https://github.com/grafana/grafana/issues/13925), thx [svenklemm](https://github.com/svenklemm) * **Elasticsearch**: Fix switching to/from es raw document metric query [#6367](https://github.com/grafana/grafana/issues/6367) * **Elasticsearch**: Fix deprecation warning about terms aggregation order key in Elasticsearch 6.x [#11977](https://github.com/grafana/grafana/issues/11977) From ebadcdb5357ad6eabb66f2a64045e54a529a7226 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 19 Nov 2018 14:50:12 +0100 Subject: [PATCH 139/322] changelog: add notes about closing #13352 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92f3c1b8c2f..43743a3ba2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ * **Dashboard**: Toggle legend using keyboard shortcut [#13655](https://github.com/grafana/grafana/issues/13655), thx [@davewat](https://github.com/davewat) * **Dashboard**: Fix render dashboard row drag handle only in edit mode [#13555](https://github.com/grafana/grafana/issues/13555), thx [@praveensastry](https://github.com/praveensastry) * **Teams**: Fix cannot select team if not included in initial search [#13425](https://github.com/grafana/grafana/issues/13425) +* **Render**: Support full height screenshots using phantomjs render script [#13352](https://github.com/grafana/grafana/pull/13352), thx [@amuraru](https://github.com/amuraru) ### Breaking changes From 795af5deeda24835114323fa1cb996212b39f5d2 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Mon, 19 Nov 2018 14:51:52 +0100 Subject: [PATCH 140/322] stackdriver: fix failing tests --- .../stackdriver/components/VariableQueryEditor.test.tsx | 4 ++-- .../__snapshots__/VariableQueryEditor.test.tsx.snap | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.test.tsx b/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.test.tsx index d1fc3d87288..b6e0793b10f 100644 --- a/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.test.tsx +++ b/public/app/plugins/datasource/stackdriver/components/VariableQueryEditor.test.tsx @@ -14,7 +14,7 @@ const props: VariableQueryProps = { datasource: { getMetricTypes: async p => [], }, - templateSrv: { replace: s => s }, + templateSrv: { replace: s => s, variables: [] }, }; describe('VariableQueryEditor', () => { @@ -37,7 +37,7 @@ describe('VariableQueryEditor', () => { it('should trigger new query using the saved query type', done => { props.query = { selectedQueryType: MetricFindQueryTypes.LabelKeys }; props.onChange = (query, definition) => { - expect(definition).toBe('Stackdriver - Labels Keys'); + expect(definition).toBe('Stackdriver - Label Keys'); done(); }; renderer.create().toJSON(); diff --git a/public/app/plugins/datasource/stackdriver/components/__snapshots__/VariableQueryEditor.test.tsx.snap b/public/app/plugins/datasource/stackdriver/components/__snapshots__/VariableQueryEditor.test.tsx.snap index 7478bb7a32e..e32a361c84f 100644 --- a/public/app/plugins/datasource/stackdriver/components/__snapshots__/VariableQueryEditor.test.tsx.snap +++ b/public/app/plugins/datasource/stackdriver/components/__snapshots__/VariableQueryEditor.test.tsx.snap @@ -25,14 +25,14 @@ Array [ Metric Types
    - {syncEnabled ? this.renderLabels(member.labels) : null} + {syncEnabled && this.renderLabels(member.labels)} @@ -132,7 +132,7 @@ export class TeamMembers extends PureComponent { - {syncEnabled ? From 0900470ddac14df9be22ca53d1050f19200e834d Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 19 Nov 2018 18:10:34 +0100 Subject: [PATCH 149/322] Add Cloudwatch/CloudHSM Metrics and dimensionMaps --- pkg/tsdb/cloudwatch/metric_find_query.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/tsdb/cloudwatch/metric_find_query.go b/pkg/tsdb/cloudwatch/metric_find_query.go index 1a860519f2b..76eadf1c8dc 100644 --- a/pkg/tsdb/cloudwatch/metric_find_query.go +++ b/pkg/tsdb/cloudwatch/metric_find_query.go @@ -46,6 +46,7 @@ func init() { "AWS/Billing": {"EstimatedCharges"}, "AWS/CloudFront": {"Requests", "BytesDownloaded", "BytesUploaded", "TotalErrorRate", "4xxErrorRate", "5xxErrorRate"}, "AWS/CloudSearch": {"SuccessfulRequests", "SearchableDocuments", "IndexUtilization", "Partitions"}, + "AWS/CloudHSM": {"HsmUnhealthy", "HsmTemperature", "HsmKeysSessionOccupied", "HsmKeysTokenOccupied", "HsmSslCtxsOccupied", "HsmSessionCount", "HsmUsersAvailable", "HsmUsersMax", "InterfaceEth2OctetsInput", "InterfaceEth2OctetsOutput"}, "AWS/Connect": {"CallsBreachingConcurrencyQuota", "CallBackNotDialableNumber", "CallRecordingUploadError", "CallsPerInterval", "ConcurrentCalls", "ConcurrentCallsPercentage", "ContactFlowErrors", "ContactFlowFatalErrors", "LongestQueueWaitTime", "MissedCalls", "MisconfiguredPhoneNumbers", "PublicSigningKeyUsage", "QueueCapacityExceededError", "QueueSize", "ThrottledCalls", "ToInstancePacketLossRate"}, "AWS/DMS": {"FreeableMemory", "WriteIOPS", "ReadIOPS", "WriteThroughput", "ReadThroughput", "WriteLatency", "ReadLatency", "SwapUsage", "NetworkTransmitThroughput", "NetworkReceiveThroughput", "FullLoadThroughputBandwidthSource", "FullLoadThroughputBandwidthTarget", "FullLoadThroughputRowsSource", "FullLoadThroughputRowsTarget", "CDCIncomingChanges", "CDCChangesMemorySource", "CDCChangesMemoryTarget", "CDCChangesDiskSource", "CDCChangesDiskTarget", "CDCThroughputBandwidthTarget", "CDCThroughputRowsSource", "CDCThroughputRowsTarget", "CDCLatencySource", "CDCLatencyTarget"}, "AWS/DX": {"ConnectionState", "ConnectionBpsEgress", "ConnectionBpsIngress", "ConnectionPpsEgress", "ConnectionPpsIngress", "ConnectionCRCErrorCount", "ConnectionLightLevelTx", "ConnectionLightLevelRx"}, @@ -121,6 +122,7 @@ func init() { "AWS/Billing": {"ServiceName", "LinkedAccount", "Currency"}, "AWS/CloudFront": {"DistributionId", "Region"}, "AWS/CloudSearch": {}, + "AWS/CloudHSM": {"Region", "ClusterId", "HsmId"}, "AWS/Connect": {"InstanceId", "MetricGroup", "Participant", "QueueName", "Stream Type", "Type of Connection"}, "AWS/DMS": {"ReplicationInstanceIdentifier", "ReplicationTaskIdentifier"}, "AWS/DX": {"ConnectionId"}, From fa2c46edb9e8c4d9b4921231407202d50961e2aa Mon Sep 17 00:00:00 2001 From: Roland Dunn Date: Mon, 19 Nov 2018 18:21:37 +0000 Subject: [PATCH 150/322] Revert "Update google analytics code to submit full URL not just path" --- public/app/core/services/analytics.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/core/services/analytics.ts b/public/app/core/services/analytics.ts index 40e20b16a29..be4371adb26 100644 --- a/public/app/core/services/analytics.ts +++ b/public/app/core/services/analytics.ts @@ -26,7 +26,7 @@ export class Analytics { init() { this.$rootScope.$on('$viewContentLoaded', () => { - const track = { location: this.$location.url() }; + const track = { page: this.$location.url() }; const ga = (window as any).ga || this.gaInit(); ga('set', track); ga('send', 'pageview'); From 16ff8a182bf2823bcc2435e7929a86706f4165f3 Mon Sep 17 00:00:00 2001 From: Bob Shannon Date: Mon, 19 Nov 2018 13:15:18 -0500 Subject: [PATCH 151/322] Re-organize packages and add basic auth test --- pkg/{util/auth.go => api/basic_auth.go} | 4 +-- pkg/api/basic_auth_test.go | 45 +++++++++++++++++++++++++ pkg/api/http_server.go | 9 ++--- pkg/api/http_server_test.go | 30 +++++++++++++++++ 4 files changed, 82 insertions(+), 6 deletions(-) rename pkg/{util/auth.go => api/basic_auth.go} (84%) create mode 100644 pkg/api/basic_auth_test.go create mode 100644 pkg/api/http_server_test.go diff --git a/pkg/util/auth.go b/pkg/api/basic_auth.go similarity index 84% rename from pkg/util/auth.go rename to pkg/api/basic_auth.go index 723cc79e244..376cfb24c91 100644 --- a/pkg/util/auth.go +++ b/pkg/api/basic_auth.go @@ -1,11 +1,11 @@ -package util +package api import ( "crypto/subtle" macaron "gopkg.in/macaron.v1" ) -// BasicAuthenticated parses the provided HTTP request for basic authentication credentials +// BasicAuthenticatedRequest parses the provided HTTP request for basic authentication credentials // and returns true if the provided credentials match the expected username and password. // Returns false if the request is unauthenticated. // Uses constant-time comparison in order to mitigate timing attacks. diff --git a/pkg/api/basic_auth_test.go b/pkg/api/basic_auth_test.go new file mode 100644 index 00000000000..0b5051c3e2a --- /dev/null +++ b/pkg/api/basic_auth_test.go @@ -0,0 +1,45 @@ +package api + +import ( + "encoding/base64" + "fmt" + "net/http" + "testing" + + . "github.com/smartystreets/goconvey/convey" + "gopkg.in/macaron.v1" +) + +func TestBasicAuthenticatedRequest(t *testing.T) { + expectedUser := "prometheus" + expectedPass := "password" + + Convey("Given a valid set of basic auth credentials", t, func() { + httpReq, err := http.NewRequest("GET", "http://localhost:3000/metrics", nil) + So(err, ShouldBeNil) + req := macaron.Request{ + Request: httpReq, + } + encodedCreds := encodeBasicAuthCredentials(expectedUser, expectedPass) + req.Header.Add("Authorization", fmt.Sprintf("Basic %s", encodedCreds)) + authenticated := BasicAuthenticatedRequest(req, expectedUser, expectedPass) + So(authenticated, ShouldBeTrue) + }) + + Convey("Given an invalid set of basic auth credentials", t, func() { + httpReq, err := http.NewRequest("GET", "http://localhost:3000/metrics", nil) + So(err, ShouldBeNil) + req := macaron.Request{ + Request: httpReq, + } + encodedCreds := encodeBasicAuthCredentials("invaliduser", "invalidpass") + req.Header.Add("Authorization", fmt.Sprintf("Basic %s", encodedCreds)) + authenticated := BasicAuthenticatedRequest(req, expectedUser, expectedPass) + So(authenticated, ShouldBeFalse) + }) +} + +func encodeBasicAuthCredentials(user, pass string) string { + creds := fmt.Sprintf("%s:%s", user, pass) + return base64.StdEncoding.EncodeToString([]byte(creds)) +} diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index 70feb44268d..d4d7b41bec5 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -32,7 +32,6 @@ import ( "github.com/grafana/grafana/pkg/services/hooks" "github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/util" ) func init() { @@ -246,9 +245,7 @@ func (hs *HTTPServer) metricsEndpoint(ctx *macaron.Context) { return } - if hs.Cfg.MetricsEndpointBasicAuthUsername != "" && - hs.Cfg.MetricsEndpointBasicAuthPassword != "" && - !util.BasicAuthenticatedRequest(ctx.Req, hs.Cfg.MetricsEndpointBasicAuthUsername, hs.Cfg.MetricsEndpointBasicAuthPassword) { + if hs.metricsEndpointBasicAuthEnabled() && !BasicAuthenticatedRequest(ctx.Req, hs.Cfg.MetricsEndpointBasicAuthUsername, hs.Cfg.MetricsEndpointBasicAuthPassword) { ctx.Resp.WriteHeader(http.StatusUnauthorized) return } @@ -307,3 +304,7 @@ func (hs *HTTPServer) mapStatic(m *macaron.Macaron, rootDir string, dir string, }, )) } + +func (hs *HTTPServer) metricsEndpointBasicAuthEnabled() bool { + return hs.Cfg.MetricsEndpointBasicAuthUsername != "" && hs.Cfg.MetricsEndpointBasicAuthPassword != "" +} diff --git a/pkg/api/http_server_test.go b/pkg/api/http_server_test.go new file mode 100644 index 00000000000..0f99ae82db5 --- /dev/null +++ b/pkg/api/http_server_test.go @@ -0,0 +1,30 @@ +package api + +import ( + "testing" + + "github.com/grafana/grafana/pkg/setting" + . "github.com/smartystreets/goconvey/convey" +) + +func TestHTTPServer(t *testing.T) { + Convey("Given a HTTPServer", t, func() { + ts := &HTTPServer{ + Cfg: setting.NewCfg(), + } + + Convey("Given that basic auth on the metrics endpoint is enabled", func() { + ts.Cfg.MetricsEndpointBasicAuthUsername = "foo" + ts.Cfg.MetricsEndpointBasicAuthPassword = "bar" + + So(ts.metricsEndpointBasicAuthEnabled(), ShouldBeTrue) + }) + + Convey("Given that basic auth on the metrics endpoint is disabled", func() { + ts.Cfg.MetricsEndpointBasicAuthUsername = "" + ts.Cfg.MetricsEndpointBasicAuthPassword = "" + + So(ts.metricsEndpointBasicAuthEnabled(), ShouldBeFalse) + }) + }) +} From a241f67fba94e319741e5d5e5275fac4dd08b48d Mon Sep 17 00:00:00 2001 From: supercharlesliu Date: Tue, 20 Nov 2018 10:17:46 +0800 Subject: [PATCH 152/322] Add doc for api "GET /api/users/:id/teams" --- docs/sources/http_api/user.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/sources/http_api/user.md b/docs/sources/http_api/user.md index b9047187b2d..047d4473603 100644 --- a/docs/sources/http_api/user.md +++ b/docs/sources/http_api/user.md @@ -226,6 +226,40 @@ Content-Type: application/json ] ``` +## Get Teams for user + +`GET /api/users/:id/teams` + +**Example Request**: + +```http +GET /api/users/1/teams HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Basic YWRtaW46YWRtaW4= +``` + +Requires basic authentication and that the authenticated user is a Grafana Admin. + +**Example Response**: + +```http +HTTP/1.1 200 +Content-Type: application/json + +[ + { + "id":1, + "orgId":1, + "name":"team1", + "email":"", + "avatarUrl":"/avatar/3fcfe295eae3bcb67a49349377428a66", + "memberCount":1 + } +] +``` + + ## User ## Actual User From 87707c964c23820ccec0a309eb06badc41827b06 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Tue, 20 Nov 2018 08:46:09 +0100 Subject: [PATCH 153/322] Revert "docs: building Grafana on arm." Further work-arounds are needed to make this work. This reverts commit 8a2de587284d9cf3e50938e35e6604ec4febf16f. --- docs/sources/project/building_from_source.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/sources/project/building_from_source.md b/docs/sources/project/building_from_source.md index fa2876d88a8..eed05f05fa6 100644 --- a/docs/sources/project/building_from_source.md +++ b/docs/sources/project/building_from_source.md @@ -50,10 +50,6 @@ The Grafana backend includes Sqlite3 which requires GCC to compile. So in order npm --add-python-to-path='true' --debug install --global windows-build-tools ``` -#### Building on arm - -There is no `phantomjs-prebuilt` for arm, unfortunately that means that the frontend build will fail on arm. To fix this on arm, remove the `phantomjs-prebuilt` dependency (the whole line) from `package.json`. This also means that you won't be able to generate images of your graphs when running Grafana on arm. - ## Build the Frontend Assets For this you need nodejs (v.6+). @@ -149,4 +145,4 @@ Please contribute to the Grafana project and submit a pull request! Build new fe ## Logging in for the first time To run Grafana open your browser and go to the default port http://localhost:3000 or the port you have configured. -Then follow the instructions [here](/guides/getting_started/). +Then follow the instructions [here](/guides/getting_started/). \ No newline at end of file From 84832cb6cb7480b58e7dc650b1d89018c632e97f Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 19 Nov 2018 16:55:30 +0100 Subject: [PATCH 154/322] build: releaser supports releasing only some artifacts. --- .circleci/config.yml | 3 ++ scripts/build/publish.sh | 4 +- scripts/build/release_publisher/main.go | 37 +++++++++++++------ scripts/build/release_publisher/publisher.go | 28 +++++++++++++- .../build/release_publisher/publisher_test.go | 26 ++++++++++++- 5 files changed, 83 insertions(+), 15 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 424744324ae..f8f0ba6789a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -359,6 +359,9 @@ jobs: - run: name: deploy to gcp command: '/opt/google-cloud-sdk/bin/gsutil cp ./enterprise-dist/* gs://$GCP_BUCKET_NAME/enterprise/release' + - run: + name: Deploy to Grafana.com + command: './scripts/build/publish.sh --enterprise' deploy-master: docker: diff --git a/scripts/build/publish.sh b/scripts/build/publish.sh index c03146eb910..e688748b8b1 100755 --- a/scripts/build/publish.sh +++ b/scripts/build/publish.sh @@ -2,6 +2,8 @@ # no relation to publish.go +EXTRA_OPTS="$@" + # Right now we hack this in into the publish script. # Eventually we might want to keep a list of all previous releases somewhere. _releaseNoteUrl="https://community.grafana.com/t/release-notes-v5-3-x/10244" @@ -11,4 +13,4 @@ _whatsNewUrl="http://docs.grafana.org/guides/whats-new-in-v5-3/" --wn ${_whatsNewUrl} \ --rn ${_releaseNoteUrl} \ --version ${CIRCLE_TAG} \ - --apikey ${GRAFANA_COM_API_KEY} + --apikey ${GRAFANA_COM_API_KEY} ${EXTRA_OPTS} diff --git a/scripts/build/release_publisher/main.go b/scripts/build/release_publisher/main.go index 27430d4cb64..d31c01b1a84 100644 --- a/scripts/build/release_publisher/main.go +++ b/scripts/build/release_publisher/main.go @@ -41,30 +41,43 @@ func main() { var builder releaseBuilder var product string + archiveProviderRoot := "https://s3-us-west-2.amazonaws.com" + buildArtifacts := completeBuildArtifactConfigurations + + if enterprise { + product = "grafana-enterprise" + baseUrl = createBaseUrl(archiveProviderRoot, "grafana-enterprise-releases", product, nightly) + var err error + buildArtifacts, err = filterBuildArtifacts([]artifactFilter{ + {os: "deb", arch: "amd64"}, + {os: "rpm", arch: "amd64"}, + {os: "linux", arch: "amd64"}, + {os: "windows", arch: "amd64"}, + }) + + if err != nil { + log.Fatalf("Could not filter to the selected build artifacts, err=%v", err) + } + + } else { + product = "grafana" + baseUrl = createBaseUrl(archiveProviderRoot, "grafana-releases", product, nightly) + } + if fromLocal { path, _ := os.Getwd() builder = releaseLocalSources{ path: path, - artifactConfigurations: buildArtifactConfigurations, + artifactConfigurations: buildArtifacts, } } else { builder = releaseFromExternalContent{ getter: getHttpContents{}, rawVersion: version, - artifactConfigurations: buildArtifactConfigurations, + artifactConfigurations: buildArtifacts, } } - archiveProviderRoot := "https://s3-us-west-2.amazonaws.com" - - if enterprise { - product = "grafana-enterprise" - baseUrl = createBaseUrl(archiveProviderRoot, "grafana-enterprise-releases", product, nightly) - } else { - product = "grafana" - baseUrl = createBaseUrl(archiveProviderRoot, "grafana-releases", product, nightly) - } - p := publisher{ apiKey: apiKey, apiUri: "https://grafana.com/api", diff --git a/scripts/build/release_publisher/publisher.go b/scripts/build/release_publisher/publisher.go index ad54a1ccb9b..1d93c1e306e 100644 --- a/scripts/build/release_publisher/publisher.go +++ b/scripts/build/release_publisher/publisher.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "fmt" + "github.com/pkg/errors" "io/ioutil" "log" "net/http" @@ -103,7 +104,7 @@ func (t buildArtifact) getUrl(baseArchiveUrl, version string, releaseType Releas return url } -var buildArtifactConfigurations = []buildArtifact{ +var completeBuildArtifactConfigurations = []buildArtifact{ { os: "deb", arch: "arm64", @@ -161,6 +162,31 @@ var buildArtifactConfigurations = []buildArtifact{ }, } +type artifactFilter struct { + os string + arch string +} + +func filterBuildArtifacts(filters []artifactFilter) ([]buildArtifact, error) { + var artifacts []buildArtifact + for _, f := range filters { + matched := false + + for _, a := range completeBuildArtifactConfigurations { + if f.os == a.os && f.arch == a.arch { + artifacts = append(artifacts, a) + matched = true + break + } + } + + if !matched { + return nil, errors.New(fmt.Sprintf("No buildArtifact for os=%v, arch=%v", f.os, f.arch)) + } + } + return artifacts, nil +} + func newBuild(baseArchiveUrl string, ba buildArtifact, version string, rt ReleaseType, sha256 string) build { return build{ Os: ba.os, diff --git a/scripts/build/release_publisher/publisher_test.go b/scripts/build/release_publisher/publisher_test.go index 1d5fb683b2c..a7ac3bb8483 100644 --- a/scripts/build/release_publisher/publisher_test.go +++ b/scripts/build/release_publisher/publisher_test.go @@ -115,7 +115,7 @@ func TestPreparingReleaseFromLocal(t *testing.T) { testDataPath := "testdata" builder = releaseLocalSources{ path: testDataPath, - artifactConfigurations: buildArtifactConfigurations, + artifactConfigurations: completeBuildArtifactConfigurations, } relAll, _ := builder.prepareRelease("https://s3-us-west-2.amazonaws.com/grafana-enterprise-releases/master/grafana-enterprise", whatsNewUrl, relNotesUrl, true) @@ -176,3 +176,27 @@ func TestPreparingReleaseFromLocal(t *testing.T) { t.Error("Error was nil, but expected an error as the local releaser only supports nightly builds.") } } + +func TestFilterBuildArtifacts(t *testing.T) { + buildArtifacts, _ := filterBuildArtifacts([]artifactFilter{ + {os: "deb", arch: "amd64"}, + {os: "rhel", arch: "amd64"}, + {os: "linux", arch: "amd64"}, + {os: "win", arch: "amd64"}, + }) + + if len(buildArtifacts) != 4 { + t.Errorf("Expected 4 build artifacts after filtering, but was %v", len(buildArtifacts)) + } + + _, err := filterBuildArtifacts([]artifactFilter{ + {os: "foobar", arch: "amd64"}, + }) + + + + if err == nil { + t.Errorf("Expected an error as a we tried to filter on a nonexiststant os.") + } + +} From 7e2298ce3133d2133555bf0c063ab5fd60eeebd0 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 19 Nov 2018 17:22:16 +0100 Subject: [PATCH 155/322] build: correct filters for ge build artifacts. --- scripts/build/release_publisher/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/build/release_publisher/main.go b/scripts/build/release_publisher/main.go index d31c01b1a84..9df4888bea5 100644 --- a/scripts/build/release_publisher/main.go +++ b/scripts/build/release_publisher/main.go @@ -50,9 +50,9 @@ func main() { var err error buildArtifacts, err = filterBuildArtifacts([]artifactFilter{ {os: "deb", arch: "amd64"}, - {os: "rpm", arch: "amd64"}, + {os: "rhel", arch: "amd64"}, {os: "linux", arch: "amd64"}, - {os: "windows", arch: "amd64"}, + {os: "win", arch: "amd64"}, }) if err != nil { From 1a554e2421b51783100c0dbca9ecbb0f8d7109c1 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 19 Nov 2018 20:21:06 +0100 Subject: [PATCH 156/322] linters. --- scripts/build/release_publisher/publisher_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/build/release_publisher/publisher_test.go b/scripts/build/release_publisher/publisher_test.go index a7ac3bb8483..2aea55d5ee1 100644 --- a/scripts/build/release_publisher/publisher_test.go +++ b/scripts/build/release_publisher/publisher_test.go @@ -193,8 +193,6 @@ func TestFilterBuildArtifacts(t *testing.T) { {os: "foobar", arch: "amd64"}, }) - - if err == nil { t.Errorf("Expected an error as a we tried to filter on a nonexiststant os.") } From 8ec7fd56d4c607451727b4464ee436599b5499b9 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 20 Nov 2018 09:47:24 +0100 Subject: [PATCH 157/322] changelog: add notes about closing #14129 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43743a3ba2a..337c70e6e54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ * **Security**: Upgrade macaron session package to fix security issue. [#14043](https://github.com/grafana/grafana/pull/14043) * **Cloudwatch**: Show all available CloudWatch regions [#12308](https://github.com/grafana/grafana/issues/12308), thx [@mtanda](https://github.com/mtanda) * **Cloudwatch**: AWS/Connect metrics and dimensions [#13970](https://github.com/grafana/grafana/pull/13970), thx [@zcoffy](https://github.com/zcoffy) +* **Cloudwatch**: CloudHSM metrics and dimensions [#14129](https://github.com/grafana/grafana/pull/14129), thx [@daktari](https://github.com/daktari) * **Cloudwatch**: Enable using variables in the stats field [#13810](https://github.com/grafana/grafana/issues/13810), thx [@mtanda](https://github.com/mtanda) * **Postgres**: Add delta window function to postgres query builder [#13925](https://github.com/grafana/grafana/issues/13925), thx [svenklemm](https://github.com/svenklemm) * **Elasticsearch**: Fix switching to/from es raw document metric query [#6367](https://github.com/grafana/grafana/issues/6367) From b948c9bdf53bb65102641d4ee59a0ad66b890cf0 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 20 Nov 2018 09:59:35 +0100 Subject: [PATCH 158/322] changelog: add notes about closing #14120 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 337c70e6e54..e0262ab942a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ * **Dashboard**: Fix render dashboard row drag handle only in edit mode [#13555](https://github.com/grafana/grafana/issues/13555), thx [@praveensastry](https://github.com/praveensastry) * **Teams**: Fix cannot select team if not included in initial search [#13425](https://github.com/grafana/grafana/issues/13425) * **Render**: Support full height screenshots using phantomjs render script [#13352](https://github.com/grafana/grafana/pull/13352), thx [@amuraru](https://github.com/amuraru) +* **HTTP API**: Support retrieving teams by user [#14120](https://github.com/grafana/grafana/pull/14120), thx [@supercharlesliu](https://github.com/supercharlesliu) ### Breaking changes From aa65796ee1b9ee2d761af14bdbbe4d529a64e109 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 20 Nov 2018 10:00:01 +0100 Subject: [PATCH 159/322] stackdriver: reset defaults.ini --- conf/defaults.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index c535adc1330..679a6a88eb7 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -249,7 +249,7 @@ signout_redirect_url = #################################### Anonymous Auth ###################### [auth.anonymous] # enable anonymous access -enabled = true +enabled = false # specify organization name that should be used for unauthenticated users org_name = Main Org. From c999394b4974b6fb15c7d17bed8dbcf5ddc088a7 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 20 Nov 2018 11:07:39 +0100 Subject: [PATCH 160/322] adds basic auth configuration to default.ini --- conf/defaults.ini | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/conf/defaults.ini b/conf/defaults.ini index 679a6a88eb7..306c625d980 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -490,6 +490,10 @@ enabled = false enabled = true interval_seconds = 10 +#If both are set, basic auth will be required for the metrics endpoint. +basic_auth_username = +basic_auth_password = + # Send internal Grafana metrics to graphite [metrics.graphite] # Enable by setting the address setting (ex localhost:2003) From 8bd0aeb56edc6599d4c715634f0e399dccf04d69 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 20 Nov 2018 11:13:18 +0100 Subject: [PATCH 161/322] changelog: adds note about closing #13577 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0262ab942a..c3718b97225 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ * **Teams**: Fix cannot select team if not included in initial search [#13425](https://github.com/grafana/grafana/issues/13425) * **Render**: Support full height screenshots using phantomjs render script [#13352](https://github.com/grafana/grafana/pull/13352), thx [@amuraru](https://github.com/amuraru) * **HTTP API**: Support retrieving teams by user [#14120](https://github.com/grafana/grafana/pull/14120), thx [@supercharlesliu](https://github.com/supercharlesliu) +* **Metrics**: Add basic authentication to metrics endpoint [#13577](https://github.com/grafana/grafana/issues/13577), thx [@bobmshannon](https://github.com/bobmshannon) ### Breaking changes From 7fc8ed942f4f9b2d8a69fe44027edf312424c49b Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 20 Nov 2018 12:30:31 +0100 Subject: [PATCH 162/322] stackdriver: make sure object type queries are also checked for vtemplate variables --- public/app/features/templating/variable.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/app/features/templating/variable.ts b/public/app/features/templating/variable.ts index 1994e86eff0..930d2c49228 100644 --- a/public/app/features/templating/variable.ts +++ b/public/app/features/templating/variable.ts @@ -1,3 +1,4 @@ +import _ from 'lodash'; import { assignModelProperties } from 'app/core/utils/model_utils'; /* @@ -28,6 +29,7 @@ export { assignModelProperties }; export function containsVariable(...args: any[]) { const variableName = args[args.length - 1]; + args[0] = _.isString(args[0]) ? args[0] : Object['values'](args[0]).join(' '); const variableString = args.slice(0, -1).join(' '); const matches = variableString.match(variableRegex); const isMatchingVariable = From b200f92710e6f6c0ee196a7fa06af159bc94bf1e Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 20 Nov 2018 13:18:00 +0100 Subject: [PATCH 163/322] stackdriver: use angular dropdown so that we can restrict user input --- .../stackdriver/partials/query.filter.html | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/partials/query.filter.html b/public/app/plugins/datasource/stackdriver/partials/query.filter.html index dba4509be87..b96b0720e33 100644 --- a/public/app/plugins/datasource/stackdriver/partials/query.filter.html +++ b/public/app/plugins/datasource/stackdriver/partials/query.filter.html @@ -1,17 +1,12 @@
    Service - +
    Metric From 7021e07ab26387f748160072e468cce183c0cd1e Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 20 Nov 2018 13:22:43 +0100 Subject: [PATCH 164/322] stackdriver: remove not used variable --- public/app/plugins/datasource/stackdriver/query_ctrl.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/public/app/plugins/datasource/stackdriver/query_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_ctrl.ts index dcac8d135aa..c2607964456 100644 --- a/public/app/plugins/datasource/stackdriver/query_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_ctrl.ts @@ -52,7 +52,6 @@ export class StackdriverQueryCtrl extends QueryCtrl { metricKind: '', valueType: '', }; - projects: any; showHelp: boolean; showLastQuery: boolean; From 55fefde2a0dad3af4b91597ed5c08ec3446cbf71 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 20 Nov 2018 13:23:44 +0100 Subject: [PATCH 165/322] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3718b97225..f0b65e1f505 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,7 +43,7 @@ ### Breaking changes -* Postgres/MySQL/MSSQL datasources now per default uses `max open connections` = `unlimited` (earlier 10), `max idle connections` = `2` (earlier 10) and `connection max lifetime` = `4` hours (earlier unlimited) +* Postgres/MySQL/MSSQL datasources now per default uses `max open connections` = `unlimited` (earlier 10), `max idle connections` = `2` (earlier 10) and `connection max lifetime` = `4` hours (earlier unlimited). # 5.3.4 (2018-11-13) From 42d5003be335187432c25ac738254365ad11f3b9 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 20 Nov 2018 13:43:48 +0100 Subject: [PATCH 166/322] changelog: adds note for #13561 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0b65e1f505..1fbffe434c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * **Teams**: Team preferences (theme, home dashboard, timezone) support [#12550](https://github.com/grafana/grafana/issues/12550) * **Graph**: Time regions support enabling highlight of weekdays and/or certain timespans [#5930](https://github.com/grafana/grafana/issues/5930) * **OAuth**: Automatic redirect to sign-in with OAuth [#11893](https://github.com/grafana/grafana/issues/11893), thx [@Nick-Triller](https://github.com/Nick-Triller) +* **Stackdriver**: Template query editor [#13561](https://github.com/grafana/grafana/issues/13561) ### Minor From 34c70ca7ceba4f91a1f070b8135ac26039589814 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 20 Nov 2018 13:46:52 +0100 Subject: [PATCH 167/322] fix for issue with error view in production builds --- pkg/middleware/recovery.go | 1 + scripts/webpack/webpack.dev.js | 2 +- scripts/webpack/webpack.prod.js | 10 +++++----- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pkg/middleware/recovery.go b/pkg/middleware/recovery.go index eef07c8c24a..b8ca85fb45b 100644 --- a/pkg/middleware/recovery.go +++ b/pkg/middleware/recovery.go @@ -115,6 +115,7 @@ func Recovery() macaron.Handler { c.Data["Title"] = "Server Error" c.Data["AppSubUrl"] = setting.AppSubUrl + c.Data["Theme"] = setting.DefaultTheme if setting.Env == setting.DEV { if theErr, ok := err.(error); ok { diff --git a/scripts/webpack/webpack.dev.js b/scripts/webpack/webpack.dev.js index 228df79b3f8..7e103b32606 100644 --- a/scripts/webpack/webpack.dev.js +++ b/scripts/webpack/webpack.dev.js @@ -85,7 +85,7 @@ module.exports = merge(common, { new HtmlWebpackPlugin({ filename: path.resolve(__dirname, '../../public/views/error.html'), template: path.resolve(__dirname, '../../public/views/error-template.html'), - inject: 'false', + inject: false, }), new HtmlWebpackPlugin({ filename: path.resolve(__dirname, '../../public/views/index.html'), diff --git a/scripts/webpack/webpack.prod.js b/scripts/webpack/webpack.prod.js index 5d3ffa61219..d45c78352b0 100644 --- a/scripts/webpack/webpack.prod.js +++ b/scripts/webpack/webpack.prod.js @@ -74,17 +74,17 @@ module.exports = merge(common, { filename: "grafana.[name].[hash].css" }), new ngAnnotatePlugin(), + new HtmlWebpackPlugin({ + filename: path.resolve(__dirname, '../../public/views/error.html'), + template: path.resolve(__dirname, '../../public/views/error-template.html'), + inject: false, + }), new HtmlWebpackPlugin({ filename: path.resolve(__dirname, '../../public/views/index.html'), template: path.resolve(__dirname, '../../public/views/index-template.html'), inject: 'body', chunks: ['vendor', 'app'], }), - new HtmlWebpackPlugin({ - filename: path.resolve(__dirname, '../../public/views/error.html'), - template: path.resolve(__dirname, '../../public/views/error-template.html'), - inject: false, - }), function () { this.hooks.done.tap('Done', function (stats) { if (stats.compilation.errors && stats.compilation.errors.length) { From 8d75fcb6f7994e4576740c55d5519ad7ecf92f8b Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 20 Nov 2018 14:35:38 +0100 Subject: [PATCH 168/322] what's new in v5.4 placeholder --- docs/sources/guides/whats-new-in-v5-4.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 docs/sources/guides/whats-new-in-v5-4.md diff --git a/docs/sources/guides/whats-new-in-v5-4.md b/docs/sources/guides/whats-new-in-v5-4.md new file mode 100644 index 00000000000..e0a3fd5c4ba --- /dev/null +++ b/docs/sources/guides/whats-new-in-v5-4.md @@ -0,0 +1,18 @@ ++++ +title = "What's New in Grafana v5.4" +description = "Feature & improvement highlights for Grafana v5.4" +keywords = ["grafana", "new", "documentation", "5.4"] +type = "docs" +[menu.docs] +name = "Version 5.4" +identifier = "v5.4" +parent = "whatsnew" +weight = -10 ++++ + +# What's New in Grafana v5.4 + +## Changelog + +Checkout the [CHANGELOG.md](https://github.com/grafana/grafana/blob/master/CHANGELOG.md) file for a complete list +of new features, changes, and bug fixes. From c7dc557e918e4290b610924d6c66b28e0b43e15f Mon Sep 17 00:00:00 2001 From: Michael Huynh Date: Thu, 15 Nov 2018 21:56:52 +0800 Subject: [PATCH 169/322] Add visibility toggle for explore graph series The implemented toggling UX is similar to how the dashboard graph plugin behaves. Also incorporates review feedback to persist series visibility state by means of the alias property, with the limitation it carries too. Related: #13522 --- public/app/features/explore/Graph.tsx | 105 +++++++++++++----- public/app/features/explore/Legend.tsx | 74 +++++++++--- .../explore/__snapshots__/Graph.test.tsx.snap | 6 + public/app/features/explore/utils/set.ts | 34 ++++++ 4 files changed, 174 insertions(+), 45 deletions(-) create mode 100644 public/app/features/explore/utils/set.ts diff --git a/public/app/features/explore/Graph.tsx b/public/app/features/explore/Graph.tsx index 61cf5753b19..01e9d5e7921 100644 --- a/public/app/features/explore/Graph.tsx +++ b/public/app/features/explore/Graph.tsx @@ -13,6 +13,7 @@ import * as dateMath from 'app/core/utils/datemath'; import TimeSeries from 'app/core/time_series2'; import Legend from './Legend'; +import { equal, intersect } from './utils/set'; const MAX_NUMBER_OF_TIME_SERIES = 20; @@ -85,13 +86,20 @@ interface GraphProps { } interface GraphState { + /** + * Type parameter refers to the `alias` property of a `TimeSeries`. + * Consequently, all series sharing the same alias will share visibility state. + */ + hiddenSeries: Set; showAllTimeSeries: boolean; } export class Graph extends PureComponent { $el: any; + dynamicOptions = null; state = { + hiddenSeries: new Set(), showAllTimeSeries: false, }; @@ -107,13 +115,14 @@ export class Graph extends PureComponent { this.$el.bind('plotselected', this.onPlotSelected); } - componentDidUpdate(prevProps: GraphProps) { + componentDidUpdate(prevProps: GraphProps, prevState: GraphState) { if ( prevProps.data !== this.props.data || prevProps.range !== this.props.range || prevProps.split !== this.props.split || prevProps.height !== this.props.height || - (prevProps.size && prevProps.size.width !== this.props.size.width) + (prevProps.size && prevProps.size.width !== this.props.size.width) || + !equal(prevState.hiddenSeries, this.state.hiddenSeries) ) { this.draw(); } @@ -133,30 +142,8 @@ export class Graph extends PureComponent { } }; - onShowAllTimeSeries = () => { - this.setState( - { - showAllTimeSeries: true, - }, - this.draw - ); - }; - - draw() { - const { range, size, userOptions = {} } = this.props; - const data = this.getGraphData(); - - const $el = $(`#${this.props.id}`); - let series = [{ data: [[0, 0]] }]; - - if (data && data.length > 0) { - series = data.map((ts: TimeSeries) => ({ - color: ts.color, - label: ts.label, - data: ts.getFlotPairs('null'), - })); - } - + getDynamicOptions() { + const { range, size } = this.props; const ticks = (size.width || 0) / 100; let { from, to } = range; if (!moment.isMoment(from)) { @@ -167,7 +154,7 @@ export class Graph extends PureComponent { } const min = from.valueOf(); const max = to.valueOf(); - const dynamicOptions = { + return { xaxis: { mode: 'time', min: min, @@ -178,16 +165,76 @@ export class Graph extends PureComponent { timeformat: time_format(ticks, min, max), }, }; + } + + onShowAllTimeSeries = () => { + this.setState( + { + showAllTimeSeries: true, + }, + this.draw + ); + }; + + onToggleSeries = (series: TimeSeries, exclusive: boolean) => { + this.setState((state, props) => { + const { data } = props; + const { hiddenSeries } = state; + const hidden = hiddenSeries.has(series.alias); + // Deduplicate series as visibility tracks the alias property + const oneSeriesVisible = hiddenSeries.size === new Set(data.map(d => d.alias)).size - 1; + if (exclusive) { + return { + hiddenSeries: + !hidden && oneSeriesVisible + ? new Set() + : new Set(data.filter(d => d.alias !== series.alias).map(d => d.alias)), + }; + } + // Prune hidden series no longer part of those available from the most recent query + const availableSeries = new Set(data.map(d => d.alias)); + const nextHiddenSeries = intersect(new Set(hiddenSeries), availableSeries); + if (nextHiddenSeries.has(series.alias)) { + nextHiddenSeries.delete(series.alias); + } else { + nextHiddenSeries.add(series.alias); + } + return { + hiddenSeries: nextHiddenSeries, + }; + }, this.draw); + }; + + draw() { + const { userOptions = {} } = this.props; + const { hiddenSeries } = this.state; + const data = this.getGraphData(); + + const $el = $(`#${this.props.id}`); + let series = [{ data: [[0, 0]] }]; + + if (data && data.length > 0) { + series = data.filter((ts: TimeSeries) => !hiddenSeries.has(ts.alias)).map((ts: TimeSeries) => ({ + color: ts.color, + label: ts.label, + data: ts.getFlotPairs('null'), + })); + } + + this.dynamicOptions = this.getDynamicOptions(); + const options = { ...FLOT_OPTIONS, - ...dynamicOptions, + ...this.dynamicOptions, ...userOptions, }; + $.plot($el, series, options); } render() { const { height = '100px', id = 'graph' } = this.props; + const { hiddenSeries } = this.state; const data = this.getGraphData(); return ( @@ -204,7 +251,7 @@ export class Graph extends PureComponent {
    )}
    - + ); } diff --git a/public/app/features/explore/Legend.tsx b/public/app/features/explore/Legend.tsx index 439b6c3e54f..3b67aa74d91 100644 --- a/public/app/features/explore/Legend.tsx +++ b/public/app/features/explore/Legend.tsx @@ -1,23 +1,65 @@ -import React, { PureComponent } from 'react'; +import React, { MouseEvent, PureComponent } from 'react'; +import classNames from 'classnames'; +import { TimeSeries } from 'app/core/core'; -const LegendItem = ({ series }) => ( - -); +interface LegendProps { + data: TimeSeries[]; + hiddenSeries: Set; + onToggleSeries?: (series: TimeSeries, exclusive: boolean) => void; +} + +interface LegendItemProps { + hidden: boolean; + onClickLabel?: (series: TimeSeries, event: MouseEvent) => void; + series: TimeSeries; +} + +class LegendItem extends PureComponent { + onClickLabel = e => this.props.onClickLabel(this.props.series, e); -export default class Legend extends PureComponent { render() { - const { className = '', data } = this.props; - const items = data || []; + const { hidden, series } = this.props; + const seriesClasses = classNames({ + 'graph-legend-series-hidden': hidden, + }); return ( -
    - {items.map(series => )} + + ); + } +} + +export default class Legend extends PureComponent { + static defaultProps = { + onToggleSeries: () => {}, + }; + + onClickLabel = (series: TimeSeries, event: MouseEvent) => { + const { onToggleSeries } = this.props; + const exclusive = event.ctrlKey || event.metaKey || event.shiftKey; + onToggleSeries(series, !exclusive); + }; + + render() { + const { data, hiddenSeries } = this.props; + const items = data || []; + return ( +
    + {items.map((series, i) => ( +
    ); } diff --git a/public/app/features/explore/__snapshots__/Graph.test.tsx.snap b/public/app/features/explore/__snapshots__/Graph.test.tsx.snap index 6b9553d2e1d..a7ec6deb22c 100644 --- a/public/app/features/explore/__snapshots__/Graph.test.tsx.snap +++ b/public/app/features/explore/__snapshots__/Graph.test.tsx.snap @@ -453,6 +453,8 @@ exports[`Render should render component 1`] = ` }, ] } + hiddenSeries={Set {}} + onToggleSeries={[Function]} /> `; @@ -947,6 +949,8 @@ exports[`Render should render component with disclaimer 1`] = ` }, ] } + hiddenSeries={Set {}} + onToggleSeries={[Function]} /> `; @@ -964,6 +968,8 @@ exports[`Render should show query return no time series 1`] = ` /> `; diff --git a/public/app/features/explore/utils/set.ts b/public/app/features/explore/utils/set.ts new file mode 100644 index 00000000000..a625567ac18 --- /dev/null +++ b/public/app/features/explore/utils/set.ts @@ -0,0 +1,34 @@ +/** + * Performs a shallow comparison of two sets with the same item type. + */ +export function equal(a: Set, b: Set): boolean { + if (a.size !== b.size) { + return false; + } + const it = a.values(); + while (true) { + const { value, done } = it.next(); + if (b.has(value)) { + return false; + } + if (done) { + return true; + } + } +} + +/** + * Returns the first set with items in the second set through shallow comparison. + */ +export function intersect(a: Set, b: Set): Set { + const it = b.values(); + while (true) { + const { value, done } = it.next(); + if (!a.has(value)) { + a.delete(value); + } + if (done) { + return a; + } + } +} From e421c387ea60d359e87536945692c634abca8e37 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 20 Nov 2018 16:23:24 +0100 Subject: [PATCH 170/322] update release publish script links --- scripts/build/publish.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/build/publish.sh b/scripts/build/publish.sh index e688748b8b1..264d930e51b 100755 --- a/scripts/build/publish.sh +++ b/scripts/build/publish.sh @@ -4,10 +4,10 @@ EXTRA_OPTS="$@" -# Right now we hack this in into the publish script. +# Right now we hack this in into the publish script. # Eventually we might want to keep a list of all previous releases somewhere. -_releaseNoteUrl="https://community.grafana.com/t/release-notes-v5-3-x/10244" -_whatsNewUrl="http://docs.grafana.org/guides/whats-new-in-v5-3/" +_releaseNoteUrl="https://community.grafana.com/t/release-notes-v5-4-x/12215" +_whatsNewUrl="http://docs.grafana.org/guides/whats-new-in-v5-4/" ./scripts/build/release_publisher/release_publisher \ --wn ${_whatsNewUrl} \ From baba8a92ee37841bf1e98a868500f56f59fade65 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 20 Nov 2018 17:09:21 +0100 Subject: [PATCH 171/322] update path to alerting for image --- docs/sources/alerting/rules.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/sources/alerting/rules.md b/docs/sources/alerting/rules.md index 387132ecfeb..03d400b2f7d 100644 --- a/docs/sources/alerting/rules.md +++ b/docs/sources/alerting/rules.md @@ -54,7 +54,12 @@ Here you can specify the name of the alert rule and how often the scheduler shou 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 its often worse to get false positive than wait a few minutes before the alert notification triggers. +Typically, it's always a good idea to use this setting since its 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 alert in pending state. + +Example timeline of an alert. +{{< imgbox max-width="80%" img="/img/docs/v54/alerting-for-dark-theme.png" caption="Alerting For" >}} + +In the previous image you can see how an alert rule transition over time. {{< imgbox max-width="40%" img="/img/docs/v4/alerting_conditions.png" caption="Alerting Conditions" >}} From 6c267cb592bd0f5180152c860ddfd5bb4d9322b7 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Tue, 20 Nov 2018 17:00:20 +0100 Subject: [PATCH 172/322] build: docker build for ge. --- .circleci/config.yml | 1 + packaging/docker/build-enterprise.sh | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f8f0ba6789a..dc30554fc5c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -510,6 +510,7 @@ workflows: - grafana-docker-release: requires: - build-all + - build-all-enterprise - test-backend - test-frontend - codespell diff --git a/packaging/docker/build-enterprise.sh b/packaging/docker/build-enterprise.sh index 2f59e436d95..e10e0d691e8 100755 --- a/packaging/docker/build-enterprise.sh +++ b/packaging/docker/build-enterprise.sh @@ -1,9 +1,17 @@ #!/bin/sh set -e -_grafana_tag=$1 +_raw_grafana_tag=$1 _docker_repo=${2:-grafana/grafana-enterprise} +if echo "$_raw_grafana_tag" | grep -q "^v"; then + _grafana_tag=$(echo "${_raw_grafana_tag}" | cut -d "v" -f 2) +else + _grafana_tag="${_raw_grafana_tag}" +fi + +echo "Building and deploying ${_docker_repo}:${_grafana_tag}" + docker build \ --tag "${_docker_repo}:${_grafana_tag}"\ --no-cache=true \ From 1491ea1c758b42cc2b79d1598104a9b7e09f7168 Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 21 Nov 2018 11:11:46 +0100 Subject: [PATCH 173/322] docs: adds example timeline for alerting for --- docs/sources/alerting/rules.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/sources/alerting/rules.md b/docs/sources/alerting/rules.md index 03d400b2f7d..6723dc80cc9 100644 --- a/docs/sources/alerting/rules.md +++ b/docs/sources/alerting/rules.md @@ -56,10 +56,8 @@ If an alert rule has a configured `For` and the query violates the configured th Typically, it's always a good idea to use this setting since its 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 alert in pending state. -Example timeline of an alert. -{{< imgbox max-width="80%" img="/img/docs/v54/alerting-for-dark-theme.png" caption="Alerting For" >}} - -In the previous image you can see how an alert rule transition over time. +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 4minutes it changes to `Alerting` which is when alert notifications are sent. Once the series falls back to normal the alert rule goes back to `OK`. +{{< imgbox img="/img/docs/v54/alerting-for-dark-theme.png" caption="Alerting For" >}} {{< imgbox max-width="40%" img="/img/docs/v4/alerting_conditions.png" caption="Alerting Conditions" >}} From afbc16c499a47512add9abc3436233e7fc594e88 Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 21 Nov 2018 11:34:21 +0100 Subject: [PATCH 174/322] gfdev: adds alert always in pending state --- devenv/dev-dashboards/testdata_alerts.json | 139 ++++++++++++++++++++- 1 file changed, 137 insertions(+), 2 deletions(-) diff --git a/devenv/dev-dashboards/testdata_alerts.json b/devenv/dev-dashboards/testdata_alerts.json index 8fd7d4d9db5..de378a3702f 100644 --- a/devenv/dev-dashboards/testdata_alerts.json +++ b/devenv/dev-dashboards/testdata_alerts.json @@ -104,6 +104,7 @@ } ], "timeFrom": null, + "timeRegions": [], "timeShift": null, "title": "Always OK", "tooltip": { @@ -232,6 +233,7 @@ } ], "timeFrom": null, + "timeRegions": [], "timeShift": null, "title": "Always Alerting", "tooltip": { @@ -362,6 +364,7 @@ } ], "timeFrom": null, + "timeRegions": [], "timeShift": null, "title": "No data", "tooltip": { @@ -432,7 +435,7 @@ "for": "1m", "frequency": "1m", "handler": 1, - "name": "TestData - Always Alerting with For", + "name": "TestData - Always Pending", "noDataState": "no_data", "notifications": [] }, @@ -492,6 +495,138 @@ } ], "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Always Alerting with For", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": "", + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "conditions": [ + { + "evaluator": { + "params": [ + 100 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "A", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "9000000h", + "frequency": "1m", + "handler": 1, + "name": "Always Pending", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "gdev-testdata", + "editable": true, + "error": false, + "fill": 1, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 14 + }, + "id": 7, + "isNew": true, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "refId": "A", + "scenario": "random_walk", + "scenarioId": "csv_metric_values", + "stringInput": "200,445,100,150,200,220,190", + "target": "" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 100 + } + ], + "timeFrom": null, + "timeRegions": [], "timeShift": null, "title": "Always Alerting with For", "tooltip": { @@ -573,5 +708,5 @@ "timezone": "browser", "title": "Alerting with TestData", "uid": "7MeksYbmk", - "version": 1 + "version": 7 } \ No newline at end of file From cab92f88afb6374eee354667c62dae6b9df5c6c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 21 Nov 2018 11:38:15 +0100 Subject: [PATCH 175/322] fixed issue with new legend not checking if panel.legend.show --- public/app/plugins/panel/graph/graph.ts | 26 ++++++++++++------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index c5f98792568..ff248d68201 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -58,15 +58,7 @@ class GraphElement { // panel events this.ctrl.events.on('panel-teardown', this.onPanelTeardown.bind(this)); - - /** - * Split graph rendering into two parts. - * First, calculate series stats in buildFlotPairs() function. Then legend rendering started - * (see ctrl.events.on('render') in legend.ts). - * When legend is rendered it emits 'legend-rendering-complete' and graph rendered. - */ this.ctrl.events.on('render', this.onRender.bind(this)); - this.ctrl.events.on('legend-rendering-complete', this.onLegendRenderingComplete.bind(this)); // global events appEvents.on('graph-hover', this.onGraphHover.bind(this), scope); @@ -85,11 +77,20 @@ class GraphElement { if (!this.data) { return; } + this.annotations = this.ctrl.annotations || []; this.buildFlotPairs(this.data); const graphHeight = this.elem.height(); updateLegendValues(this.data, this.panel, graphHeight); + if (!this.panel.legend.show) { + if (this.legendElem.hasChildNodes()) { + ReactDOM.unmountComponentAtNode(this.legendElem); + } + this.renderPanel(); + return; + } + const { values, min, max, avg, current, total } = this.panel.legend; const { alignAsTable, rightSide, sideWidth, sort, sortDesc, hideEmpty, hideZero } = this.panel.legend; const legendOptions = { alignAsTable, rightSide, sideWidth, sort, sortDesc, hideEmpty, hideZero }; @@ -104,12 +105,9 @@ class GraphElement { onColorChange: this.ctrl.onColorChange, onToggleAxis: this.ctrl.onToggleAxis, }; - const legendReactElem = React.createElement(Legend, legendProps); - ReactDOM.render(legendReactElem, this.legendElem, () => this.onLegendRenderingComplete()); - } - onLegendRenderingComplete() { - this.render_panel(); + const legendReactElem = React.createElement(Legend, legendProps); + ReactDOM.render(legendReactElem, this.legendElem, () => this.renderPanel()); } onGraphHover(evt) { @@ -281,7 +279,7 @@ class GraphElement { } // Function for rendering panel - render_panel() { + renderPanel() { this.panelWidth = this.elem.width(); if (this.shouldAbortRender()) { return; From 4bd8b2c1301462ac296ce5e72cbcdcadacfd55d5 Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 21 Nov 2018 11:41:54 +0100 Subject: [PATCH 176/322] gfdev: fixes unparseable for duration --- devenv/dev-dashboards/testdata_alerts.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devenv/dev-dashboards/testdata_alerts.json b/devenv/dev-dashboards/testdata_alerts.json index de378a3702f..9f36638c012 100644 --- a/devenv/dev-dashboards/testdata_alerts.json +++ b/devenv/dev-dashboards/testdata_alerts.json @@ -563,7 +563,7 @@ } ], "executionErrorState": "alerting", - "for": "9000000h", + "for": "900000h", "frequency": "1m", "handler": 1, "name": "Always Pending", From 0731b8635ba6542eb6a3e951b0c3ffb581d7a6a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 21 Nov 2018 13:53:32 +0100 Subject: [PATCH 177/322] fixed failing graph tests --- public/app/plugins/panel/graph/specs/graph.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/panel/graph/specs/graph.test.ts b/public/app/plugins/panel/graph/specs/graph.test.ts index d86e860b27d..be243587820 100644 --- a/public/app/plugins/panel/graph/specs/graph.test.ts +++ b/public/app/plugins/panel/graph/specs/graph.test.ts @@ -125,7 +125,7 @@ describe('grafanaGraph', () => { //Emulate functions called by event listeners link.buildFlotPairs(link.data); - link.render_panel(); + link.renderPanel(); ctx.plotData = ctrl.plot.mock.calls[0][1]; ctx.plotOptions = ctrl.plot.mock.calls[0][2]; From fdbccdc3474d923596fab38d5afa5d7fd4c61700 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Wed, 21 Nov 2018 14:41:35 +0100 Subject: [PATCH 178/322] typos in docs/sources/alerting/rules.md Co-Authored-By: bergquist --- docs/sources/alerting/rules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/alerting/rules.md b/docs/sources/alerting/rules.md index 6723dc80cc9..883f0877848 100644 --- a/docs/sources/alerting/rules.md +++ b/docs/sources/alerting/rules.md @@ -54,7 +54,7 @@ Here you can specify the name of the alert rule and how often the scheduler shou 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 its 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 alert 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 4minutes 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" >}} From f74a7cd97c1bb6a5444a5bc392959ebadf4e0649 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Wed, 21 Nov 2018 14:41:51 +0100 Subject: [PATCH 179/322] typos in docs/sources/alerting/rules.md Co-Authored-By: bergquist --- docs/sources/alerting/rules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/alerting/rules.md b/docs/sources/alerting/rules.md index 883f0877848..e496162f8cb 100644 --- a/docs/sources/alerting/rules.md +++ b/docs/sources/alerting/rules.md @@ -56,7 +56,7 @@ If an alert rule has a configured `For` and the query violates the configured th 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 4minutes 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`. +Below you can see an example timeline of an alert using the `For` setting. At ~16:04 the alert state changes to `Pending` and after 4 minutes it changes to `Alerting` which is when alert notifications are sent. Once the series falls back to normal the alert rule goes back to `OK`. {{< imgbox img="/img/docs/v54/alerting-for-dark-theme.png" caption="Alerting For" >}} {{< imgbox max-width="40%" img="/img/docs/v4/alerting_conditions.png" caption="Alerting Conditions" >}} From a333c31ea8ec6139a82dbc390d443cb3320fcba9 Mon Sep 17 00:00:00 2001 From: Michael Huynh Date: Wed, 21 Nov 2018 21:42:53 +0800 Subject: [PATCH 180/322] Fix set utilities for explore section Discovered implementation issues with the set utilities used in the explore section after adding some tests. Added tests to reinforce the expected behaviour of the utility functions. Related: #13522 --- public/app/features/explore/utils/set.test.ts | 52 +++++++++++++++++++ public/app/features/explore/utils/set.ts | 17 +++--- 2 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 public/app/features/explore/utils/set.test.ts diff --git a/public/app/features/explore/utils/set.test.ts b/public/app/features/explore/utils/set.test.ts new file mode 100644 index 00000000000..4f586814fc7 --- /dev/null +++ b/public/app/features/explore/utils/set.test.ts @@ -0,0 +1,52 @@ +import { equal, intersect } from './set'; + +describe('equal', () => { + it('returns false for two sets of differing sizes', () => { + const s1 = new Set([1, 2, 3]); + const s2 = new Set([4, 5, 6, 7]); + expect(equal(s1, s2)).toBe(false); + }); + it('returns false for two sets where one is a subset of the other', () => { + const s1 = new Set([1, 2, 3]); + const s2 = new Set([1, 2, 3, 4]); + expect(equal(s1, s2)).toBe(false); + }); + it('returns false for two sets with uncommon elements', () => { + const s1 = new Set([1, 2, 3, 4]); + const s2 = new Set([1, 2, 5, 6]); + expect(equal(s1, s2)).toBe(false); + }); + it('returns false for two deeply equivalent sets', () => { + const s1 = new Set([{ a: 1 }, { b: 2 }, { c: 3 }, { d: 4 }]); + const s2 = new Set([{ a: 1 }, { b: 2 }, { c: 3 }, { d: 4 }]); + expect(equal(s1, s2)).toBe(false); + }); + it('returns true for two sets with the same elements', () => { + const s1 = new Set([1, 2, 3, 4]); + const s2 = new Set([4, 3, 2, 1]); + expect(equal(s1, s2)).toBe(true); + }); +}); + +describe('intersect', () => { + it('returns an empty set for two sets without any common elements', () => { + const s1 = new Set([1, 2, 3, 4]); + const s2 = new Set([5, 6, 7, 8]); + expect(intersect(s1, s2)).toEqual(new Set()); + }); + it('returns an empty set for two deeply equivalent sets', () => { + const s1 = new Set([{ a: 1 }, { b: 2 }, { c: 3 }, { d: 4 }]); + const s2 = new Set([{ a: 1 }, { b: 2 }, { c: 3 }, { d: 4 }]); + expect(intersect(s1, s2)).toEqual(new Set()); + }); + it('returns a set containing common elements between two sets of the same size', () => { + const s1 = new Set([1, 2, 3, 4]); + const s2 = new Set([5, 2, 7, 4]); + expect(intersect(s1, s2)).toEqual(new Set([2, 4])); + }); + it('returns a set containing common elements between two sets of differing sizes', () => { + const s1 = new Set([1, 2, 3, 4]); + const s2 = new Set([5, 4, 3, 2, 1]); + expect(intersect(s1, s2)).toEqual(new Set([1, 2, 3, 4])); + }); +}); diff --git a/public/app/features/explore/utils/set.ts b/public/app/features/explore/utils/set.ts index a625567ac18..12430384fe4 100644 --- a/public/app/features/explore/utils/set.ts +++ b/public/app/features/explore/utils/set.ts @@ -8,27 +8,28 @@ export function equal(a: Set, b: Set): boolean { const it = a.values(); while (true) { const { value, done } = it.next(); - if (b.has(value)) { - return false; - } if (done) { return true; } + if (!b.has(value)) { + return false; + } } } /** - * Returns the first set with items in the second set through shallow comparison. + * Returns a new set with items in both sets using shallow comparison. */ export function intersect(a: Set, b: Set): Set { + const result = new Set(); const it = b.values(); while (true) { const { value, done } = it.next(); - if (!a.has(value)) { - a.delete(value); - } if (done) { - return a; + return result; + } + if (a.has(value)) { + result.add(value); } } } From b3161bea5a8040906c86ba2b919998cfb388f255 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Wed, 21 Nov 2018 14:45:57 +0100 Subject: [PATCH 181/322] Explore: Introduce DataQuery interface for query handling - Queries in Explore have been string based - This PR introduces the use of the DataQuery type to denote all queries handled in Explore - Within Explore all handling of DataQueries is transparent - Modifying DataQueries is left to the datasource - Using `target` as variable names for DataQueries to be consistent with the rest of Grafana --- public/app/core/utils/explore.test.ts | 49 +-- public/app/core/utils/explore.ts | 107 +++++- public/app/features/explore/Explore.tsx | 345 ++++++++---------- public/app/features/explore/QueryField.tsx | 18 +- public/app/features/explore/QueryRows.tsx | 26 +- .../explore/QueryTransactionStatus.tsx | 4 +- public/app/features/explore/utils/query.ts | 16 - .../logging/components/LoggingCheatSheet.tsx | 5 +- .../logging/components/LoggingQueryField.tsx | 25 +- .../logging/components/LoggingStartPage.tsx | 2 +- .../plugins/datasource/logging/datasource.ts | 4 +- .../datasource/logging/language_provider.ts | 18 +- .../prometheus/components/PromCheatSheet.tsx | 5 +- .../prometheus/components/PromQueryField.tsx | 29 +- .../prometheus/components/PromStart.tsx | 2 +- .../datasource/prometheus/datasource.ts | 40 +- public/app/types/explore.ts | 27 +- 17 files changed, 386 insertions(+), 336 deletions(-) delete mode 100644 public/app/features/explore/utils/query.ts diff --git a/public/app/core/utils/explore.test.ts b/public/app/core/utils/explore.test.ts index 4252730338d..aaa7e4d4281 100644 --- a/public/app/core/utils/explore.test.ts +++ b/public/app/core/utils/explore.test.ts @@ -10,7 +10,7 @@ const DEFAULT_EXPLORE_STATE: ExploreState = { exploreDatasources: [], graphRange: DEFAULT_RANGE, history: [], - queries: [], + initialTargets: [], queryTransactions: [], range: DEFAULT_RANGE, showingGraph: true, @@ -26,17 +26,17 @@ describe('state functions', () => { it('returns default state on empty string', () => { expect(parseUrlState('')).toMatchObject({ datasource: null, - queries: [], + targets: [], range: DEFAULT_RANGE, }); }); it('returns a valid Explore state from URL parameter', () => { const paramValue = - '%7B"datasource":"Local","queries":%5B%7B"query":"metric"%7D%5D,"range":%7B"from":"now-1h","to":"now"%7D%7D'; + '%7B"datasource":"Local","targets":%5B%7B"expr":"metric"%7D%5D,"range":%7B"from":"now-1h","to":"now"%7D%7D'; expect(parseUrlState(paramValue)).toMatchObject({ datasource: 'Local', - queries: [{ query: 'metric' }], + targets: [{ expr: 'metric' }], range: { from: 'now-1h', to: 'now', @@ -45,10 +45,10 @@ describe('state functions', () => { }); it('returns a valid Explore state from a compact URL parameter', () => { - const paramValue = '%5B"now-1h","now","Local","metric"%5D'; + const paramValue = '%5B"now-1h","now","Local",%7B"expr":"metric"%7D%5D'; expect(parseUrlState(paramValue)).toMatchObject({ datasource: 'Local', - queries: [{ query: 'metric' }], + targets: [{ expr: 'metric' }], range: { from: 'now-1h', to: 'now', @@ -66,18 +66,20 @@ describe('state functions', () => { from: 'now-5h', to: 'now', }, - queries: [ + initialTargets: [ { - query: 'metric{test="a/b"}', + refId: '1', + expr: 'metric{test="a/b"}', }, { - query: 'super{foo="x/z"}', + refId: '2', + expr: 'super{foo="x/z"}', }, ], }; expect(serializeStateToUrlParam(state)).toBe( - '{"datasource":"foo","queries":[{"query":"metric{test=\\"a/b\\"}"},' + - '{"query":"super{foo=\\"x/z\\"}"}],"range":{"from":"now-5h","to":"now"}}' + '{"datasource":"foo","targets":[{"expr":"metric{test=\\"a/b\\"}"},' + + '{"expr":"super{foo=\\"x/z\\"}"}],"range":{"from":"now-5h","to":"now"}}' ); }); @@ -89,17 +91,19 @@ describe('state functions', () => { from: 'now-5h', to: 'now', }, - queries: [ + initialTargets: [ { - query: 'metric{test="a/b"}', + refId: '1', + expr: 'metric{test="a/b"}', }, { - query: 'super{foo="x/z"}', + refId: '2', + expr: 'super{foo="x/z"}', }, ], }; expect(serializeStateToUrlParam(state, true)).toBe( - '["now-5h","now","foo","metric{test=\\"a/b\\"}","super{foo=\\"x/z\\"}"]' + '["now-5h","now","foo",{"expr":"metric{test=\\"a/b\\"}"},{"expr":"super{foo=\\"x/z\\"}"}]' ); }); }); @@ -113,12 +117,14 @@ describe('state functions', () => { from: 'now - 5h', to: 'now', }, - queries: [ + initialTargets: [ { - query: 'metric{test="a/b"}', + refId: '1', + expr: 'metric{test="a/b"}', }, { - query: 'super{foo="x/z"}', + refId: '2', + expr: 'super{foo="x/z"}', }, ], }; @@ -126,14 +132,15 @@ describe('state functions', () => { const parsed = parseUrlState(serialized); // Account for datasource vs datasourceName - const { datasource, ...rest } = parsed; - const sameState = { + const { datasource, targets, ...rest } = parsed; + const resultState = { ...rest, datasource: DEFAULT_EXPLORE_STATE.datasource, datasourceName: datasource, + initialTargets: targets, }; - expect(state).toMatchObject(sameState); + expect(state).toMatchObject(resultState); }); }); }); diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts index ecd11a495ad..bb1d1c9bbf1 100644 --- a/public/app/core/utils/explore.ts +++ b/public/app/core/utils/explore.ts @@ -1,11 +1,20 @@ import { renderUrl } from 'app/core/utils/url'; -import { ExploreState, ExploreUrlState } from 'app/types/explore'; +import { ExploreState, ExploreUrlState, HistoryItem } from 'app/types/explore'; +import { DataQuery, RawTimeRange } from 'app/types/series'; + +import kbn from 'app/core/utils/kbn'; +import colors from 'app/core/utils/colors'; +import TimeSeries from 'app/core/time_series2'; +import { parse as parseDate } from 'app/core/utils/datemath'; +import store from 'app/core/store'; export const DEFAULT_RANGE = { from: 'now-6h', to: 'now', }; +const MAX_HISTORY_ITEMS = 100; + /** * Returns an Explore-URL that contains a panel's queries and the dashboard time range. * @@ -70,30 +79,106 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState { to: parsed[1], }; const datasource = parsed[2]; - const queries = parsed.slice(3).map(query => ({ query })); - return { datasource, queries, range }; + const targets = parsed.slice(3); + return { datasource, targets, range }; } return parsed; } catch (e) { console.error(e); } } - return { datasource: null, queries: [], range: DEFAULT_RANGE }; + return { datasource: null, targets: [], range: DEFAULT_RANGE }; } export function serializeStateToUrlParam(state: ExploreState, compact?: boolean): string { const urlState: ExploreUrlState = { datasource: state.datasourceName, - queries: state.queries.map(q => ({ query: q.query })), + targets: state.initialTargets.map(({ key, refId, ...rest }) => rest), range: state.range, }; if (compact) { - return JSON.stringify([ - urlState.range.from, - urlState.range.to, - urlState.datasource, - ...urlState.queries.map(q => q.query), - ]); + return JSON.stringify([urlState.range.from, urlState.range.to, urlState.datasource, ...urlState.targets]); } return JSON.stringify(urlState); } + +export function generateKey(index = 0): string { + return `Q-${Date.now()}-${Math.random()}-${index}`; +} + +export function generateRefId(index = 0): string { + return `${index + 1}`; +} + +export function generateTargetKeys(index = 0): { refId: string; key: string } { + return { refId: generateRefId(index), key: generateKey(index) }; +} + +/** + * Ensure at least one target exists and that targets have the necessary keys + */ +export function ensureTargets(targets?: DataQuery[]): DataQuery[] { + if (targets && typeof targets === 'object' && targets.length > 0) { + return targets.map((target, i) => ({ ...target, ...generateTargetKeys(i) })); + } + return [{ ...generateTargetKeys() }]; +} + +/** + * A target is non-empty when it has keys other than refId and key. + */ +export function hasNonEmptyTarget(targets: DataQuery[]): boolean { + return targets.some(target => Object.keys(target).length > 2); +} + +export function getIntervals( + range: RawTimeRange, + datasource, + resolution: number +): { interval: string; intervalMs: number } { + if (!datasource || !resolution) { + return { interval: '1s', intervalMs: 1000 }; + } + const absoluteRange: RawTimeRange = { + from: parseDate(range.from, false), + to: parseDate(range.to, true), + }; + return kbn.calculateInterval(absoluteRange, resolution, datasource.interval); +} + +export function makeTimeSeriesList(dataList, options) { + return dataList.map((seriesData, index) => { + const datapoints = seriesData.datapoints || []; + const alias = seriesData.target; + const colorIndex = index % colors.length; + const color = colors[colorIndex]; + + const series = new TimeSeries({ + datapoints, + alias, + color, + unit: seriesData.unit, + }); + + return series; + }); +} + +/** + * Update the query history. Side-effect: store history in local storage + */ +export function updateHistory(history: HistoryItem[], datasourceId: string, targets: DataQuery[]): HistoryItem[] { + const ts = Date.now(); + targets.forEach(target => { + history = [{ target, ts }, ...history]; + }); + + if (history.length > MAX_HISTORY_ITEMS) { + history = history.slice(0, MAX_HISTORY_ITEMS); + } + + // Combine all queries of a datasource type into one history + const historyKey = `grafana.explore.history.${datasourceId}`; + store.setObject(historyKey, history); + return history; +} diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 5d39992c4a2..e88e6417fcf 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -4,14 +4,26 @@ import Select from 'react-select'; import _ from 'lodash'; import { DataSource } from 'app/types/datasources'; -import { ExploreState, ExploreUrlState, HistoryItem, Query, QueryTransaction, ResultType } from 'app/types/explore'; +import { + ExploreState, + ExploreUrlState, + QueryTransaction, + ResultType, + QueryHintGetter, + QueryHint, +} from 'app/types/explore'; import { RawTimeRange, DataQuery } from 'app/types/series'; -import kbn from 'app/core/utils/kbn'; -import colors from 'app/core/utils/colors'; import store from 'app/core/store'; -import TimeSeries from 'app/core/time_series2'; -import { parse as parseDate } from 'app/core/utils/datemath'; -import { DEFAULT_RANGE } from 'app/core/utils/explore'; +import { + DEFAULT_RANGE, + ensureTargets, + getIntervals, + generateKey, + generateTargetKeys, + hasNonEmptyTarget, + makeTimeSeriesList, + updateHistory, +} from 'app/core/utils/explore'; import ResetStyles from 'app/core/components/Picker/ResetStyles'; import PickerOption from 'app/core/components/Picker/PickerOption'; import IndicatorsContainer from 'app/core/components/Picker/IndicatorsContainer'; @@ -26,57 +38,6 @@ import Logs from './Logs'; import Table from './Table'; import ErrorBoundary from './ErrorBoundary'; import TimePicker from './TimePicker'; -import { ensureQueries, generateQueryKey, hasQuery } from './utils/query'; - -const MAX_HISTORY_ITEMS = 100; - -function getIntervals(range: RawTimeRange, datasource, resolution: number): { interval: string; intervalMs: number } { - if (!datasource || !resolution) { - return { interval: '1s', intervalMs: 1000 }; - } - const absoluteRange: RawTimeRange = { - from: parseDate(range.from, false), - to: parseDate(range.to, true), - }; - return kbn.calculateInterval(absoluteRange, resolution, datasource.interval); -} - -function makeTimeSeriesList(dataList, options) { - return dataList.map((seriesData, index) => { - const datapoints = seriesData.datapoints || []; - const alias = seriesData.target; - const colorIndex = index % colors.length; - const color = colors[colorIndex]; - - const series = new TimeSeries({ - datapoints, - alias, - color, - unit: seriesData.unit, - }); - - return series; - }); -} - -/** - * Update the query history. Side-effect: store history in local storage - */ -function updateHistory(history: HistoryItem[], datasourceId: string, queries: string[]): HistoryItem[] { - const ts = Date.now(); - queries.forEach(query => { - history = [{ query, ts }, ...history]; - }); - - if (history.length > MAX_HISTORY_ITEMS) { - history = history.slice(0, MAX_HISTORY_ITEMS); - } - - // Combine all queries of a datasource type into one history - const historyKey = `grafana.explore.history.${datasourceId}`; - store.setObject(historyKey, history); - return history; -} interface ExploreProps { datasourceSrv: DatasourceSrv; @@ -89,14 +50,20 @@ interface ExploreProps { urlState: ExploreUrlState; } +/** + * Explore provides an area for quick query iteration for a given datasource. + * Once a datasource is selected it populates the query section at the top. + * When queries are run, their results are being displayed in the main section. + * The datasource determines what kind of query editor it brings, and what kind + * of results viewers it supports. + */ export class Explore extends React.PureComponent { el: any; /** * Current query expressions of the rows including their modifications, used for running queries. * Not kept in component state to prevent edit-render roundtrips. - * TODO: make this generic (other datasources might not have string representations of current query state) */ - queryExpressions: string[]; + modifiedTargets: DataQuery[]; /** * Local ID cache to compare requested vs selected datasource */ @@ -105,14 +72,14 @@ export class Explore extends React.PureComponent { constructor(props) { super(props); const splitState: ExploreState = props.splitState; - let initialQueries: Query[]; + let initialTargets: DataQuery[]; if (splitState) { // Split state overrides everything this.state = splitState; - initialQueries = splitState.queries; + initialTargets = splitState.initialTargets; } else { - const { datasource, queries, range } = props.urlState as ExploreUrlState; - initialQueries = ensureQueries(queries); + const { datasource, targets, range } = props.urlState as ExploreUrlState; + initialTargets = ensureTargets(targets); const initialRange = range || { ...DEFAULT_RANGE }; this.state = { datasource: null, @@ -122,8 +89,8 @@ export class Explore extends React.PureComponent { datasourceName: datasource, exploreDatasources: [], graphRange: initialRange, + initialTargets, history: [], - queries: initialQueries, queryTransactions: [], range: initialRange, showingGraph: true, @@ -135,7 +102,7 @@ export class Explore extends React.PureComponent { supportsTable: null, }; } - this.queryExpressions = initialQueries.map(q => q.query); + this.modifiedTargets = initialTargets.slice(); } async componentDidMount() { @@ -198,32 +165,26 @@ export class Explore extends React.PureComponent { } // Check if queries can be imported from previously selected datasource - let queryExpressions = this.queryExpressions; + let modifiedTargets = this.modifiedTargets; if (origin) { if (origin.meta.id === datasource.meta.id) { // Keep same queries if same type of datasource - queryExpressions = [...this.queryExpressions]; + modifiedTargets = [...this.modifiedTargets]; } else if (datasource.importQueries) { - // Datasource-specific importers, wrapping to satisfy interface - const wrappedQueries: DataQuery[] = this.queryExpressions.map((query, index) => ({ - refId: String(index), - expr: query, - })); - const modifiedQueries: DataQuery[] = await datasource.importQueries(wrappedQueries, origin.meta); - queryExpressions = modifiedQueries.map(({ expr }) => expr); + // Datasource-specific importers + modifiedTargets = await datasource.importQueries(this.modifiedTargets, origin.meta); } else { // Default is blank queries - queryExpressions = this.queryExpressions.map(() => ''); + modifiedTargets = ensureTargets(); } } // Reset edit state with new queries - const nextQueries = this.state.queries.map((q, i) => ({ - ...q, - key: generateQueryKey(i), - query: queryExpressions[i], + const nextTargets = this.state.initialTargets.map((q, i) => ({ + ...modifiedTargets[i], + ...generateTargetKeys(i), })); - this.queryExpressions = queryExpressions; + this.modifiedTargets = modifiedTargets; // Custom components const StartPage = datasource.pluginExports.ExploreStartPage; @@ -239,7 +200,7 @@ export class Explore extends React.PureComponent { supportsTable, datasourceLoading: false, datasourceName: datasource.name, - queries: nextQueries, + initialTargets: nextTargets, showingStartPage: Boolean(StartPage), }, () => { @@ -256,16 +217,15 @@ export class Explore extends React.PureComponent { onAddQueryRow = index => { // Local cache - this.queryExpressions[index + 1] = ''; + this.modifiedTargets[index + 1] = { ...generateTargetKeys(index + 1) }; this.setState(state => { - const { queries, queryTransactions } = state; + const { initialTargets, queryTransactions } = state; - // Add row by generating new react key - const nextQueries = [ - ...queries.slice(0, index + 1), - { query: '', key: generateQueryKey() }, - ...queries.slice(index + 1), + const nextTargets = [ + ...initialTargets.slice(0, index + 1), + { ...this.modifiedTargets[index + 1] }, + ...initialTargets.slice(index + 1), ]; // Ongoing transactions need to update their row indices @@ -279,7 +239,7 @@ export class Explore extends React.PureComponent { return qt; }); - return { queries: nextQueries, queryTransactions: nextQueryTransactions }; + return { initialTargets: nextTargets, queryTransactions: nextQueryTransactions }; }); }; @@ -296,26 +256,26 @@ export class Explore extends React.PureComponent { this.setDatasource(datasource as any, origin); }; - onChangeQuery = (value: string, index: number, override?: boolean) => { + onChangeQuery = (value: DataQuery, index: number, override?: boolean) => { // Keep current value in local cache - this.queryExpressions[index] = value; + this.modifiedTargets[index] = value; if (override) { this.setState(state => { // Replace query row - const { queries, queryTransactions } = state; - const nextQuery: Query = { - key: generateQueryKey(index), - query: value, + const { initialTargets, queryTransactions } = state; + const target: DataQuery = { + ...value, + ...generateTargetKeys(index), }; - const nextQueries = [...queries]; - nextQueries[index] = nextQuery; + const nextTargets = [...initialTargets]; + nextTargets[index] = target; // Discard ongoing transaction related to row query const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index); return { - queries: nextQueries, + initialTargets: nextTargets, queryTransactions: nextQueryTransactions, }; }, this.onSubmit); @@ -330,10 +290,10 @@ export class Explore extends React.PureComponent { }; onClickClear = () => { - this.queryExpressions = ['']; + this.modifiedTargets = ensureTargets(); this.setState( prevState => ({ - queries: ensureQueries(), + initialTargets: [...this.modifiedTargets], queryTransactions: [], showingStartPage: Boolean(prevState.StartPage), }), @@ -387,10 +347,10 @@ export class Explore extends React.PureComponent { }; // Use this in help pages to set page to a single query - onClickQuery = query => { - const nextQueries = [{ query, key: generateQueryKey() }]; - this.queryExpressions = nextQueries.map(q => q.query); - this.setState({ queries: nextQueries }, this.onSubmit); + onClickExample = (target: DataQuery) => { + const nextTargets = [{ ...target, ...generateTargetKeys() }]; + this.modifiedTargets = [...nextTargets]; + this.setState({ initialTargets: nextTargets }, this.onSubmit); }; onClickSplit = () => { @@ -430,28 +390,28 @@ export class Explore extends React.PureComponent { const preventSubmit = action.preventSubmit; this.setState( state => { - const { queries, queryTransactions } = state; - let nextQueries; + const { initialTargets, queryTransactions } = state; + let nextTargets: DataQuery[]; let nextQueryTransactions; if (index === undefined) { // Modify all queries - nextQueries = queries.map((q, i) => ({ - key: generateQueryKey(i), - query: datasource.modifyQuery(this.queryExpressions[i], action), + nextTargets = initialTargets.map((target, i) => ({ + ...datasource.modifyQuery(this.modifiedTargets[i], action), + ...generateTargetKeys(i), })); // Discard all ongoing transactions nextQueryTransactions = []; } else { // Modify query only at index - nextQueries = queries.map((q, i) => { + nextTargets = initialTargets.map((target, i) => { // Synchronise all queries with local query cache to ensure consistency - q.query = this.queryExpressions[i]; + // TODO still needed? return i === index ? { - key: generateQueryKey(index), - query: datasource.modifyQuery(q.query, action), + ...datasource.modifyQuery(this.modifiedTargets[i], action), + ...generateTargetKeys(i), } - : q; + : target; }); nextQueryTransactions = queryTransactions // Consume the hint corresponding to the action @@ -464,9 +424,9 @@ export class Explore extends React.PureComponent { // Preserve previous row query transaction to keep results visible if next query is incomplete .filter(qt => preventSubmit || qt.rowIndex !== index); } - this.queryExpressions = nextQueries.map(q => q.query); + this.modifiedTargets = [...nextTargets]; return { - queries: nextQueries, + initialTargets: nextTargets, queryTransactions: nextQueryTransactions, }; }, @@ -478,22 +438,22 @@ export class Explore extends React.PureComponent { onRemoveQueryRow = index => { // Remove from local cache - this.queryExpressions = [...this.queryExpressions.slice(0, index), ...this.queryExpressions.slice(index + 1)]; + this.modifiedTargets = [...this.modifiedTargets.slice(0, index), ...this.modifiedTargets.slice(index + 1)]; this.setState( state => { - const { queries, queryTransactions } = state; - if (queries.length <= 1) { + const { initialTargets, queryTransactions } = state; + if (initialTargets.length <= 1) { return null; } // Remove row from react state - const nextQueries = [...queries.slice(0, index), ...queries.slice(index + 1)]; + const nextTargets = [...initialTargets.slice(0, index), ...initialTargets.slice(index + 1)]; // Discard transactions related to row query const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index); return { - queries: nextQueries, + initialTargets: nextTargets, queryTransactions: nextQueryTransactions, }; }, @@ -515,40 +475,39 @@ export class Explore extends React.PureComponent { this.saveState(); }; - buildQueryOptions( - query: string, - rowIndex: number, - targetOptions: { format: string; hinting?: boolean; instant?: boolean } - ) { + buildQueryOptions(target: DataQuery, targetOptions: { format: string; hinting?: boolean; instant?: boolean }) { const { datasource, range } = this.state; const { interval, intervalMs } = getIntervals(range, datasource, this.el.offsetWidth); const targets = [ { ...targetOptions, - // Target identifier is needed for table transformations - refId: rowIndex + 1, - expr: query, + ...target, }, ]; // Clone range for query request const queryRange: RawTimeRange = { ...range }; + // Datasource is using `panelId + target.refId` for cancellation logic. + // Using `format` here because it relates to the view panel that the request is for. + const panelId = targetOptions.format; + return { interval, intervalMs, + panelId, targets, range: queryRange, }; } - startQueryTransaction(query: string, rowIndex: number, resultType: ResultType, options: any): QueryTransaction { - const queryOptions = this.buildQueryOptions(query, rowIndex, options); + startQueryTransaction(target: DataQuery, rowIndex: number, resultType: ResultType, options: any): QueryTransaction { + const queryOptions = this.buildQueryOptions(target, options); const transaction: QueryTransaction = { - query, + target, resultType, rowIndex, - id: generateQueryKey(), + id: generateKey(), // reusing for unique ID done: false, latency: 0, options: queryOptions, @@ -578,7 +537,7 @@ export class Explore extends React.PureComponent { transactionId: string, result: any, latency: number, - queries: string[], + targets: DataQuery[], datasourceId: string ) { const { datasource } = this.state; @@ -597,9 +556,9 @@ export class Explore extends React.PureComponent { } // Get query hints - let hints; - if (datasource.getQueryHints) { - hints = datasource.getQueryHints(transaction.query, result); + let hints: QueryHint[]; + if (datasource.getQueryHints as QueryHintGetter) { + hints = datasource.getQueryHints(transaction.target, result); } // Mark transactions as complete @@ -616,7 +575,7 @@ export class Explore extends React.PureComponent { return qt; }); - const nextHistory = updateHistory(history, datasourceId, queries); + const nextHistory = updateHistory(history, datasourceId, targets); return { history: nextHistory, @@ -634,7 +593,7 @@ export class Explore extends React.PureComponent { failQueryTransaction(transactionId: string, response: any, datasourceId: string) { const { datasource } = this.state; - if (datasource.meta.id !== datasourceId) { + if (datasource.meta.id !== datasourceId || response.cancelled) { // Navigated away, queries did not matter return; } @@ -679,87 +638,75 @@ export class Explore extends React.PureComponent { } async runGraphQueries() { - const queries = [...this.queryExpressions]; - if (!hasQuery(queries)) { + const targets = [...this.modifiedTargets]; + if (!hasNonEmptyTarget(targets)) { return; } const { datasource } = this.state; const datasourceId = datasource.meta.id; // Run all queries concurrently - queries.forEach(async (query, rowIndex) => { - if (query) { - const transaction = this.startQueryTransaction(query, rowIndex, 'Graph', { - format: 'time_series', - instant: false, - }); - try { - const now = Date.now(); - const res = await datasource.query(transaction.options); - const latency = Date.now() - now; - const results = makeTimeSeriesList(res.data, transaction.options); - this.completeQueryTransaction(transaction.id, results, latency, queries, datasourceId); - this.setState({ graphRange: transaction.options.range }); - } catch (response) { - this.failQueryTransaction(transaction.id, response, datasourceId); - } - } else { - this.discardTransactions(rowIndex); + targets.forEach(async (target, rowIndex) => { + const transaction = this.startQueryTransaction(target, rowIndex, 'Graph', { + format: 'time_series', + instant: false, + }); + try { + const now = Date.now(); + const res = await datasource.query(transaction.options); + const latency = Date.now() - now; + const results = makeTimeSeriesList(res.data, transaction.options); + this.completeQueryTransaction(transaction.id, results, latency, targets, datasourceId); + this.setState({ graphRange: transaction.options.range }); + } catch (response) { + this.failQueryTransaction(transaction.id, response, datasourceId); } }); } async runTableQuery() { - const queries = [...this.queryExpressions]; - if (!hasQuery(queries)) { + const targets = [...this.modifiedTargets]; + if (!hasNonEmptyTarget(targets)) { return; } const { datasource } = this.state; const datasourceId = datasource.meta.id; // Run all queries concurrently - queries.forEach(async (query, rowIndex) => { - if (query) { - const transaction = this.startQueryTransaction(query, rowIndex, 'Table', { - format: 'table', - instant: true, - valueWithRefId: true, - }); - try { - const now = Date.now(); - const res = await datasource.query(transaction.options); - const latency = Date.now() - now; - const results = res.data[0]; - this.completeQueryTransaction(transaction.id, results, latency, queries, datasourceId); - } catch (response) { - this.failQueryTransaction(transaction.id, response, datasourceId); - } - } else { - this.discardTransactions(rowIndex); + targets.forEach(async (target, rowIndex) => { + const transaction = this.startQueryTransaction(target, rowIndex, 'Table', { + format: 'table', + instant: true, + valueWithRefId: true, + }); + try { + const now = Date.now(); + const res = await datasource.query(transaction.options); + const latency = Date.now() - now; + const results = res.data[0]; + this.completeQueryTransaction(transaction.id, results, latency, targets, datasourceId); + } catch (response) { + this.failQueryTransaction(transaction.id, response, datasourceId); } }); } async runLogsQuery() { - const queries = [...this.queryExpressions]; - if (!hasQuery(queries)) { + const targets = [...this.modifiedTargets]; + if (!hasNonEmptyTarget(targets)) { return; } const { datasource } = this.state; const datasourceId = datasource.meta.id; // Run all queries concurrently - queries.forEach(async (query, rowIndex) => { - if (query) { - const transaction = this.startQueryTransaction(query, rowIndex, 'Logs', { format: 'logs' }); - try { - const now = Date.now(); - const res = await datasource.query(transaction.options); - const latency = Date.now() - now; - const results = res.data; - this.completeQueryTransaction(transaction.id, results, latency, queries, datasourceId); - } catch (response) { - this.failQueryTransaction(transaction.id, response, datasourceId); - } - } else { - this.discardTransactions(rowIndex); + targets.forEach(async (target, rowIndex) => { + const transaction = this.startQueryTransaction(target, rowIndex, 'Logs', { format: 'logs' }); + try { + const now = Date.now(); + const res = await datasource.query(transaction.options); + const latency = Date.now() - now; + const results = res.data; + this.completeQueryTransaction(transaction.id, results, latency, targets, datasourceId); + } catch (response) { + this.failQueryTransaction(transaction.id, response, datasourceId); } }); } @@ -769,7 +716,7 @@ export class Explore extends React.PureComponent { return { ...this.state, queryTransactions: [], - queries: ensureQueries(this.queryExpressions.map(query => ({ query }))), + initialTargets: [...this.modifiedTargets], }; } @@ -789,7 +736,7 @@ export class Explore extends React.PureComponent { exploreDatasources, graphRange, history, - queries, + initialTargets, queryTransactions, range, showingGraph, @@ -903,7 +850,7 @@ export class Explore extends React.PureComponent { { />
    - {showingStartPage && } + {showingStartPage && } {!showingStartPage && ( <> {supportsGraph && ( diff --git a/public/app/features/explore/QueryField.tsx b/public/app/features/explore/QueryField.tsx index 224d34574b8..d5cba981951 100644 --- a/public/app/features/explore/QueryField.tsx +++ b/public/app/features/explore/QueryField.tsx @@ -27,14 +27,14 @@ function hasSuggestions(suggestions: CompletionItemGroup[]): boolean { return suggestions && suggestions.length > 0; } -interface QueryFieldProps { +export interface QueryFieldProps { additionalPlugins?: any[]; cleanText?: (text: string) => string; - initialValue: string | null; + initialQuery: string | null; onBlur?: () => void; onFocus?: () => void; onTypeahead?: (typeahead: TypeaheadInput) => TypeaheadOutput; - onValueChanged?: (value: Value) => void; + onValueChanged?: (value: string) => void; onWillApplySuggestion?: (suggestion: string, state: QueryFieldState) => string; placeholder?: string; portalOrigin?: string; @@ -60,16 +60,22 @@ export interface TypeaheadInput { wrapperNode: Element; } +/** + * Renders an editor field. + * Pass initial value as initialQuery and listen to changes in props.onValueChanged. + * This component can only process strings. Internally it uses Slate Value. + * Implement props.onTypeahead to use suggestions, see PromQueryField.tsx as an example. + */ export class QueryField extends React.PureComponent { menuEl: HTMLElement | null; placeholdersBuffer: PlaceholdersBuffer; plugins: any[]; resetTimer: any; - constructor(props, context) { + constructor(props: QueryFieldProps, context) { super(props, context); - this.placeholdersBuffer = new PlaceholdersBuffer(props.initialValue || ''); + this.placeholdersBuffer = new PlaceholdersBuffer(props.initialQuery || ''); // Base plugins this.plugins = [ClearPlugin(), NewlinePlugin(), ...props.additionalPlugins].filter(p => p); @@ -92,7 +98,7 @@ export class QueryField extends React.PureComponent qt.hints && qt.hints.length > 0); @@ -16,7 +16,7 @@ function getFirstHintFromTransactions(transactions: QueryTransaction[]): QueryHi interface QueryRowEventHandlers { onAddQueryRow: (index: number) => void; - onChangeQuery: (value: string, index: number, override?: boolean) => void; + onChangeQuery: (value: DataQuery, index: number, override?: boolean) => void; onClickHintFix: (action: object, index?: number) => void; onExecuteQuery: () => void; onRemoveQueryRow: (index: number) => void; @@ -32,11 +32,11 @@ interface QueryRowCommonProps { type QueryRowProps = QueryRowCommonProps & QueryRowEventHandlers & { index: number; - query: string; + initialTarget: DataQuery; }; class QueryRow extends PureComponent { - onChangeQuery = (value, override?: boolean) => { + onChangeQuery = (value: DataQuery, override?: boolean) => { const { index, onChangeQuery } = this.props; if (onChangeQuery) { onChangeQuery(value, index, override); @@ -51,7 +51,7 @@ class QueryRow extends PureComponent { }; onClickClearButton = () => { - this.onChangeQuery('', true); + this.onChangeQuery(null, true); }; onClickHintFix = action => { @@ -76,7 +76,7 @@ class QueryRow extends PureComponent { }; render() { - const { datasource, history, query, transactions } = this.props; + const { datasource, history, initialTarget, transactions } = this.props; const transactionWithError = transactions.find(t => t.error !== undefined); const hint = getFirstHintFromTransactions(transactions); const queryError = transactionWithError ? transactionWithError.error : null; @@ -91,7 +91,7 @@ class QueryRow extends PureComponent { datasource={datasource} error={queryError} hint={hint} - initialQuery={query} + initialTarget={initialTarget} history={history} onClickHintFix={this.onClickHintFix} onPressEnter={this.onPressEnter} @@ -116,19 +116,19 @@ class QueryRow extends PureComponent { type QueryRowsProps = QueryRowCommonProps & QueryRowEventHandlers & { - queries: Query[]; + initialTargets: DataQuery[]; }; export default class QueryRows extends PureComponent { render() { - const { className = '', queries, transactions, ...handlers } = this.props; + const { className = '', initialTargets, transactions, ...handlers } = this.props; return (
    - {queries.map((q, index) => ( + {initialTargets.map((target, index) => ( t.rowIndex === index)} {...handlers} /> diff --git a/public/app/features/explore/QueryTransactionStatus.tsx b/public/app/features/explore/QueryTransactionStatus.tsx index 77a50b7d2ca..6f47f147645 100644 --- a/public/app/features/explore/QueryTransactionStatus.tsx +++ b/public/app/features/explore/QueryTransactionStatus.tsx @@ -35,7 +35,9 @@ export default class QueryTransactionStatus extends PureComponent - {transactions.map((t, i) => )} + {transactions.map((t, i) => ( + + ))}
    ); } diff --git a/public/app/features/explore/utils/query.ts b/public/app/features/explore/utils/query.ts deleted file mode 100644 index 193ee2dbc52..00000000000 --- a/public/app/features/explore/utils/query.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Query } from 'app/types/explore'; - -export function generateQueryKey(index = 0): string { - return `Q-${Date.now()}-${Math.random()}-${index}`; -} - -export function ensureQueries(queries?: Query[]): Query[] { - if (queries && typeof queries === 'object' && queries.length > 0 && typeof queries[0].query === 'string') { - return queries.map(({ query }, i) => ({ key: generateQueryKey(i), query })); - } - return [{ key: generateQueryKey(), query: '' }]; -} - -export function hasQuery(queries: string[]): boolean { - return queries.some(q => Boolean(q)); -} diff --git a/public/app/plugins/datasource/logging/components/LoggingCheatSheet.tsx b/public/app/plugins/datasource/logging/components/LoggingCheatSheet.tsx index a7af48b6eda..651c28783d9 100644 --- a/public/app/plugins/datasource/logging/components/LoggingCheatSheet.tsx +++ b/public/app/plugins/datasource/logging/components/LoggingCheatSheet.tsx @@ -19,7 +19,10 @@ export default (props: any) => ( {CHEAT_SHEET_ITEMS.map(item => (
    {item.title}
    -
    props.onClickQuery(item.expression)}> +
    props.onClickExample({ refId: '1', expr: item.expression })} + > {item.expression}
    {item.label}
    diff --git a/public/app/plugins/datasource/logging/components/LoggingQueryField.tsx b/public/app/plugins/datasource/logging/components/LoggingQueryField.tsx index ce79d38f9a8..78bdf271a3e 100644 --- a/public/app/plugins/datasource/logging/components/LoggingQueryField.tsx +++ b/public/app/plugins/datasource/logging/components/LoggingQueryField.tsx @@ -10,7 +10,8 @@ import { TypeaheadOutput } from 'app/types/explore'; import { getNextCharacter, getPreviousCousin } from 'app/features/explore/utils/dom'; import BracesPlugin from 'app/features/explore/slate-plugins/braces'; import RunnerPlugin from 'app/features/explore/slate-plugins/runner'; -import TypeaheadField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField'; +import QueryField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField'; +import { DataQuery } from 'app/types'; const PRISM_SYNTAX = 'promql'; @@ -53,10 +54,10 @@ interface LoggingQueryFieldProps { error?: string | JSX.Element; hint?: any; history?: any[]; - initialQuery?: string | null; + initialTarget?: DataQuery; onClickHintFix?: (action: any) => void; onPressEnter?: () => void; - onQueryChange?: (value: string, override?: boolean) => void; + onQueryChange?: (value: DataQuery, override?: boolean) => void; } interface LoggingQueryFieldState { @@ -134,9 +135,13 @@ class LoggingQueryField extends React.PureComponent { // Send text change to parent - const { onQueryChange } = this.props; + const { initialTarget, onQueryChange } = this.props; if (onQueryChange) { - onQueryChange(value, override); + const target = { + ...initialTarget, + expr: value, + }; + onQueryChange(target, override); } }; @@ -181,7 +186,7 @@ class LoggingQueryField extends React.PureComponent
    - {error ?
    {error}
    : null} diff --git a/public/app/plugins/datasource/logging/components/LoggingStartPage.tsx b/public/app/plugins/datasource/logging/components/LoggingStartPage.tsx index 89262999637..2c25a248fa9 100644 --- a/public/app/plugins/datasource/logging/components/LoggingStartPage.tsx +++ b/public/app/plugins/datasource/logging/components/LoggingStartPage.tsx @@ -52,7 +52,7 @@ export default class LoggingStartPage extends PureComponent
    - {active === 'start' && } + {active === 'start' && }
    ); diff --git a/public/app/plugins/datasource/logging/datasource.ts b/public/app/plugins/datasource/logging/datasource.ts index 494dcd78d6c..5d27d3f1f5c 100644 --- a/public/app/plugins/datasource/logging/datasource.ts +++ b/public/app/plugins/datasource/logging/datasource.ts @@ -112,8 +112,8 @@ export default class LoggingDatasource { }); } - async importQueries(queries: DataQuery[], originMeta: PluginMeta): Promise { - return this.languageProvider.importQueries(queries, originMeta.id); + async importQueries(targets: DataQuery[], originMeta: PluginMeta): Promise { + return this.languageProvider.importQueries(targets, originMeta.id); } metadataRequest(url) { diff --git a/public/app/plugins/datasource/logging/language_provider.ts b/public/app/plugins/datasource/logging/language_provider.ts index 00745d2eee8..419470c7f06 100644 --- a/public/app/plugins/datasource/logging/language_provider.ts +++ b/public/app/plugins/datasource/logging/language_provider.ts @@ -158,25 +158,29 @@ export default class LoggingLanguageProvider extends LanguageProvider { return { context, refresher, suggestions }; } - async importQueries(queries: DataQuery[], datasourceType: string): Promise { + async importQueries(targets: DataQuery[], datasourceType: string): Promise { if (datasourceType === 'prometheus') { return Promise.all( - queries.map(async query => { - const expr = await this.importPrometheusQuery(query.expr); + targets.map(async target => { + const expr = await this.importPrometheusQuery(target.expr); return { - ...query, + ...target, expr, }; }) ); } - return queries.map(query => ({ - ...query, + return targets.map(target => ({ + ...target, expr: '', })); } async importPrometheusQuery(query: string): Promise { + if (!query) { + return ''; + } + // Consider only first selector in query const selectorMatch = query.match(selectorRegexp); if (selectorMatch) { @@ -192,7 +196,7 @@ export default class LoggingLanguageProvider extends LanguageProvider { const commonLabels = {}; for (const key in labels) { const existingKeys = this.labelKeys[EMPTY_SELECTOR]; - if (existingKeys.indexOf(key) > -1) { + if (existingKeys && existingKeys.indexOf(key) > -1) { // Should we check for label value equality here? commonLabels[key] = labels[key]; } diff --git a/public/app/plugins/datasource/prometheus/components/PromCheatSheet.tsx b/public/app/plugins/datasource/prometheus/components/PromCheatSheet.tsx index a2d3a03d794..ea9a373e67a 100644 --- a/public/app/plugins/datasource/prometheus/components/PromCheatSheet.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromCheatSheet.tsx @@ -25,7 +25,10 @@ export default (props: any) => ( {CHEAT_SHEET_ITEMS.map(item => (
    {item.title}
    -
    props.onClickQuery(item.expression)}> +
    props.onClickExample({ refId: '1', expr: item.expression })} + > {item.expression}
    {item.label}
    diff --git a/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx b/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx index a7787096d85..cefd44eba64 100644 --- a/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx @@ -10,7 +10,8 @@ import { TypeaheadOutput } from 'app/types/explore'; import { getNextCharacter, getPreviousCousin } from 'app/features/explore/utils/dom'; import BracesPlugin from 'app/features/explore/slate-plugins/braces'; import RunnerPlugin from 'app/features/explore/slate-plugins/runner'; -import TypeaheadField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField'; +import QueryField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField'; +import { DataQuery } from 'app/types'; const HISTOGRAM_GROUP = '__histograms__'; const METRIC_MARK = 'metric'; @@ -84,17 +85,17 @@ interface CascaderOption { disabled?: boolean; } -interface PromQueryFieldProps { +type PromQueryFieldProps = { datasource: any; error?: string | JSX.Element; + initialTarget: DataQuery; hint?: any; history?: any[]; - initialQuery?: string | null; metricsByPrefix?: CascaderOption[]; onClickHintFix?: (action: any) => void; onPressEnter?: () => void; - onQueryChange?: (value: string, override?: boolean) => void; -} + onQueryChange?: (value: DataQuery, override?: boolean) => void; +}; interface PromQueryFieldState { metricsOptions: any[]; @@ -161,11 +162,15 @@ class PromQueryField extends React.PureComponent { + onChangeQuery = (query: string, override?: boolean) => { // Send text change to parent - const { onQueryChange } = this.props; + const { initialTarget, onQueryChange } = this.props; if (onQueryChange) { - onQueryChange(value, override); + const target: DataQuery = { + ...initialTarget, + expr: query, + }; + onQueryChange(target, override); } }; @@ -227,10 +232,10 @@ class PromQueryField extends React.PureComponent @@ -242,10 +247,10 @@ class PromQueryField extends React.PureComponent
    - {
    - {active === 'start' && } + {active === 'start' && }
    ); diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index 0cedafdff75..f845fc17106 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -11,6 +11,8 @@ import { BackendSrv } from 'app/core/services/backend_srv'; import addLabelToQuery from './add_label_to_query'; import { getQueryHints } from './query_hints'; import { expandRecordingRules } from './language_utils'; +import { DataQuery } from 'app/types'; +import { ExploreUrlState } from 'app/types/explore'; export function alignRange(start, end, step) { const alignedEnd = Math.ceil(end / step) * step; @@ -419,24 +421,23 @@ export class PrometheusDatasource { }); } - getExploreState(targets: any[]) { - let state = {}; + getExploreState(targets: DataQuery[]): Partial { + let state: Partial = { datasource: this.name }; if (targets && targets.length > 0) { - const queries = targets.map(t => ({ - query: this.templateSrv.replace(t.expr, {}, this.interpolateQueryExpr), - format: t.format, + const expandedTargets = targets.map(target => ({ + ...target, + expr: this.templateSrv.replace(target.expr, {}, this.interpolateQueryExpr), })); state = { ...state, - queries, - datasource: this.name, + targets: expandedTargets, }; } return state; } - getQueryHints(query: string, result: any[]) { - return getQueryHints(query, result, this); + getQueryHints(target: DataQuery, result: any[]) { + return getQueryHints(target.expr, result, this); } loadRules() { @@ -454,28 +455,35 @@ export class PrometheusDatasource { }); } - modifyQuery(query: string, action: any): string { + modifyQuery(target: DataQuery, action: any): DataQuery { + let query = target.expr; switch (action.type) { case 'ADD_FILTER': { - return addLabelToQuery(query, action.key, action.value); + query = addLabelToQuery(query, action.key, action.value); + break; } case 'ADD_HISTOGRAM_QUANTILE': { - return `histogram_quantile(0.95, sum(rate(${query}[5m])) by (le))`; + query = `histogram_quantile(0.95, sum(rate(${query}[5m])) by (le))`; + break; } case 'ADD_RATE': { - return `rate(${query}[5m])`; + query = `rate(${query}[5m])`; + break; } case 'ADD_SUM': { - return `sum(${query.trim()}) by ($1)`; + query = `sum(${query.trim()}) by ($1)`; + break; } case 'EXPAND_RULES': { if (action.mapping) { - return expandRecordingRules(query, action.mapping); + query = expandRecordingRules(query, action.mapping); } + break; } default: - return query; + break; } + return { ...target, expr: query }; } getPrometheusTime(date, roundUp) { diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 662835633db..bbfbc88c1e5 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -1,6 +1,6 @@ import { Value } from 'slate'; -import { RawTimeRange } from './series'; +import { DataQuery, RawTimeRange } from './series'; export interface CompletionItem { /** @@ -79,7 +79,7 @@ interface ExploreDatasource { export interface HistoryItem { ts: number; - query: string; + target: DataQuery; } export abstract class LanguageProvider { @@ -107,11 +107,6 @@ export interface TypeaheadOutput { suggestions: CompletionItemGroup[]; } -export interface Query { - query: string; - key?: string; -} - export interface QueryFix { type: string; label: string; @@ -130,6 +125,10 @@ export interface QueryHint { fix?: QueryFix; } +export interface QueryHintGetter { + (target: DataQuery, results: any[], ...rest: any): QueryHint[]; +} + export interface QueryTransaction { id: string; done: boolean; @@ -137,10 +136,10 @@ export interface QueryTransaction { hints?: QueryHint[]; latency: number; options: any; - query: string; result?: any; // Table model / Timeseries[] / Logs resultType: ResultType; rowIndex: number; + target: DataQuery; } export interface TextMatch { @@ -160,15 +159,7 @@ export interface ExploreState { exploreDatasources: ExploreDatasource[]; graphRange: RawTimeRange; history: HistoryItem[]; - /** - * Initial rows of queries to push down the tree. - * Modifications do not end up here, but in `this.queryExpressions`. - * The only way to reset a query is to change its `key`. - */ - queries: Query[]; - /** - * Hints gathered for the query row. - */ + initialTargets: DataQuery[]; queryTransactions: QueryTransaction[]; range: RawTimeRange; showingGraph: boolean; @@ -182,7 +173,7 @@ export interface ExploreState { export interface ExploreUrlState { datasource: string; - queries: Query[]; + targets: any[]; // Should be a DataQuery, but we're going to strip refIds, so typing makes less sense range: RawTimeRange; } From 60c291c8dca1823cb39d39cc0390f03a8fa48725 Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 21 Nov 2018 15:42:25 +0100 Subject: [PATCH 182/322] format: remove and align tabs --- public/app/features/alerting/partials/alert_tab.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/app/features/alerting/partials/alert_tab.html b/public/app/features/alerting/partials/alert_tab.html index 2b5e9bdf0cb..2ebe2b53a76 100644 --- a/public/app/features/alerting/partials/alert_tab.html +++ b/public/app/features/alerting/partials/alert_tab.html @@ -64,9 +64,9 @@
    - - - + + +
    {member.login} {member.email} this.onRemoveMember(member)} /> Name Email : ''} + {syncEnabled && }
    +
    )} {supportsLogs && ( @@ -941,6 +941,7 @@ export class Explore extends React.PureComponent { loading={logsLoading} position={position} onChangeTime={this.onChangeTime} + onClickLabel={this.onClickLabel} onStartScanning={this.onStartScanning} onStopScanning={this.onStopScanning} range={range} diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index 58965df4514..2c78d9782b9 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -1,9 +1,18 @@ +import _ from 'lodash'; import React, { Fragment, PureComponent } from 'react'; import Highlighter from 'react-highlight-words'; import * as rangeUtil from 'app/core/utils/rangeutil'; import { RawTimeRange } from 'app/types/series'; -import { LogsDedupStrategy, LogsModel, dedupLogRows, filterLogLevels, LogLevel } from 'app/core/logs_model'; +import { + LogsDedupStrategy, + LogsModel, + dedupLogRows, + filterLogLevels, + LogLevel, + LogsStreamLabels, + LogsMetaKind, +} from 'app/core/logs_model'; import { findHighlightChunksInText } from 'app/core/utils/text'; import { Switch } from 'app/core/components/Switch/Switch'; @@ -23,6 +32,51 @@ const graphOptions = { }, }; +function renderMetaItem(value: any, kind: LogsMetaKind) { + if (kind === LogsMetaKind.LabelsMap) { + return ( + + + + ); + } + return value; +} + +class Label extends PureComponent<{ + label: string; + value: string; + onClickLabel?: (label: string, value: string) => void; +}> { + onClickLabel = () => { + const { onClickLabel, label, value } = this.props; + if (onClickLabel) { + onClickLabel(label, value); + } + }; + + render() { + const { label, value } = this.props; + const tooltip = `${label}: ${value}`; + return ( + + {value} + + ); + } +} +class Labels extends PureComponent<{ + labels: LogsStreamLabels; + onClickLabel?: (label: string, value: string) => void; +}> { + render() { + const { labels, onClickLabel } = this.props; + return Object.keys(labels).map(key => ( +