├── .circleci └── config.yml ├── .gitignore ├── .prettierrc.js ├── CHANGELOG.md ├── LICENSE ├── Magefile.go ├── README.md ├── docker-compose.yml ├── go.mod ├── go.sum ├── jest.config.js ├── package.json ├── pkg ├── converters │ └── converters.go ├── influx │ ├── builder.go │ ├── builder_test.go │ ├── datasource.go │ ├── executor.go │ ├── executor_test.go │ ├── macros.go │ ├── macros_test.go │ ├── mock.go │ └── testdata │ │ ├── aggregate.csv │ │ ├── buckets.csv │ │ ├── grouping.csv │ │ ├── multiple.csv │ │ └── simple.csv ├── main.go └── models │ ├── query.go │ └── settings.go ├── scripts ├── autobuild.sh ├── circle-cmd-lint.sh ├── debug-attach.sh ├── debug-backend.sh ├── debug-build.sh ├── restart-plugin.sh └── revive.toml ├── src ├── DataSource.ts ├── components │ ├── ConfigEditor.tsx │ ├── QueryEditor.test.tsx │ └── QueryEditor.tsx ├── img │ └── influxdb_logo.svg ├── module.ts ├── plugin.json └── types.ts ├── tsconfig.json ├── webpack.config.js └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | parameters: 4 | ssh-fingerprint: 5 | type: string 6 | default: ${GITHUB_SSH_FINGERPRINT} 7 | 8 | aliases: 9 | # Workflow filters 10 | - &filter-only-master 11 | branches: 12 | only: master 13 | - &filter-only-release 14 | branches: 15 | only: /^v[1-9]*[0-9]+\.[1-9]*[0-9]+\.x$/ 16 | 17 | workflows: 18 | plugin_workflow: 19 | jobs: 20 | - build 21 | - report: 22 | requires: 23 | - build 24 | - approve_release: 25 | type: approval 26 | requires: 27 | - report 28 | filters: *filter-only-release 29 | - publish_github_release: 30 | requires: 31 | - approve_release 32 | filters: *filter-only-release 33 | 34 | executors: 35 | default_exec: # declares a reusable executor 36 | docker: 37 | - image: srclosson/grafana-plugin-ci-alpine:latest 38 | e2e_exec: 39 | docker: 40 | - image: srclosson/grafana-plugin-ci-e2e:latest 41 | 42 | jobs: 43 | build: 44 | executor: default_exec 45 | steps: 46 | - checkout 47 | - restore_cache: 48 | name: restore node_modules 49 | keys: 50 | - build-cache-{{ .Environment.CACHE_VERSION }}-{{ checksum "yarn.lock" }} 51 | - run: 52 | name: Install dependencies 53 | command: | 54 | mkdir ci 55 | [ -f ~/project/node_modules/.bin/grafana-toolkit ] || yarn install --frozen-lockfile 56 | - save_cache: 57 | name: save node_modules 58 | paths: 59 | - ~/project/node_modules 60 | key: build-cache-{{ .Environment.CACHE_VERSION }}-{{ checksum "yarn.lock" }} 61 | - save_cache: 62 | name: save cypress cache 63 | paths: 64 | - ~/.cache/Cypress 65 | key: cypress-cache-{{ .Environment.CACHE_VERSION }}-{{ checksum "yarn.lock" }} 66 | - run: 67 | name: Build and test frontend 68 | command: ./node_modules/.bin/grafana-toolkit plugin:ci-build 69 | - run: 70 | name: Build backend 71 | command: mage -v buildAll 72 | - run: 73 | name: Test backend 74 | command: | 75 | mage -v lint 76 | mage -v coverage 77 | - run: 78 | name: Move results to ci folder 79 | command: ./node_modules/.bin/grafana-toolkit plugin:ci-build --finish 80 | - run: 81 | name: Package distribution 82 | command: | 83 | ./node_modules/.bin/grafana-toolkit plugin:ci-package 84 | - persist_to_workspace: 85 | root: . 86 | paths: 87 | - ci/jobs/package 88 | - ci/packages 89 | - ci/dist 90 | - ci/grafana-test-env 91 | - store_artifacts: 92 | path: ci 93 | 94 | report: 95 | executor: default_exec 96 | steps: 97 | - checkout 98 | - attach_workspace: 99 | at: . 100 | - restore_cache: 101 | name: restore node_modules 102 | keys: 103 | - build-cache-{{ .Environment.CACHE_VERSION }}-{{ checksum "yarn.lock" }} 104 | - run: 105 | name: Toolkit report 106 | command: | 107 | ./node_modules/.bin/grafana-toolkit plugin:ci-report 108 | - store_artifacts: 109 | path: ci 110 | 111 | publish_github_release: 112 | executor: default_exec 113 | steps: 114 | - checkout 115 | - add_ssh_keys: 116 | fingerprints: 117 | - << pipeline.parameters.ssh-fingerprint >> 118 | - "dc:60:ab:c7:2d:8c:82:50:2a:2a:97:1a:c0:66:83:14" 119 | - ${GITHUB_SSH_FINGERPRINT} 120 | - attach_workspace: 121 | at: . 122 | - restore_cache: 123 | name: restore node_modules 124 | keys: 125 | - build-cache-{{ .Environment.CACHE_VERSION }}-{{ checksum "yarn.lock" }} 126 | - run: 127 | name: "Publish Release on GitHub" 128 | command: | 129 | ./node_modules/.bin/grafana-toolkit plugin:github-publish 130 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .DS_Store 4 | .vscode 5 | dist/ 6 | coverage/ -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require('@grafana/toolkit/src/config/prettier.plugin.config.json'), 3 | }; 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | 4 | ## [7.0.1] - 2020-05-17 5 | 6 | - Support results without a measurment name 7 | - use release influxdb client 8 | 9 | 10 | ## [7.0.0] - 2020-05-17 11 | 12 | - Migrated to grafana backend plugin 13 | - Signed for grafana 7.x 14 | - Optimized for copy/paste from influxdb 2.0 data explorer. 15 | 16 | 17 | ## [5.4.1] - 2019-10-28 18 | 19 | - Query editor bugfixes 20 | 21 | ## [5.4.0] - 2019-10-09 22 | 23 | - Slate fixes for Grafana 6.4.x 24 | - Fix for queries which group by time, and return _start and _stop rather than _time 25 | 26 | NOTE: This version is only compatible with Grafana v6.4+ 27 | 28 | ## [5.3.2] - 2019-07-07 29 | 30 | - Fix for range error when expanding suggestion [#39](https://github.com/grafana/influxdb-flux-datasource/pull/39) 31 | 32 | ## [5.3.0] - 2019-06-11 33 | 34 | - Update packages 35 | - Add circleci publishing 36 | - Add support for Influx V2.0 Alpha Server 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2018 Grafana Labs 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Magefile.go: -------------------------------------------------------------------------------- 1 | //+build mage 2 | 3 | package main 4 | 5 | import ( 6 | // mage:import 7 | build "github.com/grafana/grafana-plugin-sdk-go/build" 8 | ) 9 | 10 | // Default configures the default target. 11 | var Default = build.BuildAll 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # InfluxDB (Flux) Datasource [BETA] 2 | 3 | Starting at grafana 7.1, Flux is supported directly in grafana. See: 4 | 5 | https://grafana.com/docs/grafana/latest/features/datasources/influxdb/#using-influxdb-in-grafana 6 | 7 | and 8 | 9 | https://www.influxdata.com/blog/how-grafana-dashboard-influxdb-flux-influxql/ 10 | 11 | 12 | The 7+ version of this plugin will be archived, and will revert to the previous frontend only 5x option. 13 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | grafana: 2 | image: grafana/grafana:latest 3 | ports: 4 | - "3000:3000" 5 | volumes: 6 | - ./:/var/lib/grafana/plugins/grafana-influxdb-flux-datasource 7 | - ./provisioning:/etc/grafana/provisioning 8 | environment: 9 | - TERM=linux 10 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/grafana/influxdb-flux-datasource 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/google/go-cmp v0.4.1 7 | github.com/grafana/grafana-plugin-sdk-go v0.66.0 8 | github.com/influxdata/influxdb-client-go v1.2.0 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 4 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 5 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 6 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 7 | github.com/apache/arrow/go/arrow v0.0.0-20200403134915-89ce1cadb678 h1:R72+9UXiP7TnpTAdznM1okjzyqb3bzopSA7HCP7p3gM= 8 | github.com/apache/arrow/go/arrow v0.0.0-20200403134915-89ce1cadb678/go.mod h1:QNYViu/X0HXDHw7m3KXzWSVXIbfUvJqBFe6Gj8/pYA0= 9 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 10 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 11 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 12 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 13 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 14 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= 15 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 16 | github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= 17 | github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= 18 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 19 | github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= 20 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 21 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 22 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 23 | github.com/deepmap/oapi-codegen v1.3.6 h1:Wj44p9A0V0PJ+AUg0BWdyGcsS1LY18U+0rCuPQgK0+o= 24 | github.com/deepmap/oapi-codegen v1.3.6/go.mod h1:aBozjEveG+33xPiP55Iw/XbVkhtZHEGLq3nxlX0+hfU= 25 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 26 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 27 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 28 | github.com/getkin/kin-openapi v0.2.0/go.mod h1:V1z9xl9oF5Wt7v32ne4FmiF1alpS4dM6mNzoywPOXlk= 29 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 30 | github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= 31 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 32 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 33 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 34 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 35 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 36 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 37 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 38 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 39 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 40 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 41 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 42 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 43 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 44 | github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk= 45 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 46 | github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= 47 | github.com/google/flatbuffers v1.11.0 h1:O7CEyB8Cb3/DmtxODGtLHcEvpr81Jm5qLg/hsHnxA2A= 48 | github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= 49 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 50 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 51 | github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0= 52 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 53 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 54 | github.com/grafana/grafana-plugin-sdk-go v0.66.0 h1:Tx8pchA74QUtxtN8moavf0FJoAZbnbW3Fe8RVfSKUoY= 55 | github.com/grafana/grafana-plugin-sdk-go v0.66.0/go.mod h1:w855JyiC5PDP3naWUJP0h/vY8RlzlE4+4fodyoXph+4= 56 | github.com/grpc-ecosystem/go-grpc-middleware v1.2.0 h1:0IKlLyQ3Hs9nDaiK5cSHAGmcQEIC8l2Ts1u6x5Dfrqg= 57 | github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s= 58 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= 59 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 60 | github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd h1:rNuUHR+CvK1IS89MMtcF0EpcVMZtjKfPRp4MEmt/aTs= 61 | github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= 62 | github.com/hashicorp/go-plugin v1.2.2 h1:mgDpq0PkoK5gck2w4ivaMpWRHv/matdOR4xmeScmf/w= 63 | github.com/hashicorp/go-plugin v1.2.2/go.mod h1:F9eH4LrE/ZsRdbwhfjs9k9HoDUwAHnYtXdgmf1AVNs0= 64 | github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= 65 | github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= 66 | github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= 67 | github.com/influxdata/influxdb-client-go v1.2.0 h1:QiRg4BX9KYM28rVxUTk3MQM0mYOMayxC+rM9tGVk1C0= 68 | github.com/influxdata/influxdb-client-go v1.2.0/go.mod h1:ZVjaPW87aKp5hzyny2WVpWVF0UY+iqtPz9veOZ2T1zw= 69 | github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU= 70 | github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= 71 | github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= 72 | github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= 73 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 74 | github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 75 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 76 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 77 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 78 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 79 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 80 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 81 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 82 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 83 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 84 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 85 | github.com/labstack/echo/v4 v4.1.11 h1:z0BZoArY4FqdpUEl+wlHp4hnr/oSR6MTmQmv8OHSoww= 86 | github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= 87 | github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= 88 | github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= 89 | github.com/magefile/mage v1.9.0 h1:t3AU2wNwehMCW97vuqQLtw6puppWXHO+O2MHo5a50XE= 90 | github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= 91 | github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= 92 | github.com/mattetti/filebuffer v1.0.0 h1:ixTvQ0JjBTwWbdpDZ98lLrydo7KRi8xNRIi5RFszsbY= 93 | github.com/mattetti/filebuffer v1.0.0/go.mod h1:X6nyAIge2JGVmuJt2MFCqmHrb/5IHiphfHtot0s5cnI= 94 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 95 | github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= 96 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 97 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 98 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= 99 | github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= 100 | github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= 101 | github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= 102 | github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 103 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 104 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 105 | github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg= 106 | github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 107 | github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= 108 | github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 109 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 110 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 111 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 112 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 113 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 114 | github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= 115 | github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= 116 | github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= 117 | github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= 118 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 119 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 120 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 121 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 122 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 123 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 124 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 125 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 126 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 127 | github.com/prometheus/client_golang v1.3.0 h1:miYCvYqFXtl/J9FIy8eNpBfYthAEFg+Ys0XyUVEcDsc= 128 | github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= 129 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 130 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 131 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 132 | github.com/prometheus/client_model v0.1.0 h1:ElTg5tNp4DqfV7UQjDqv2+RJlNzsDtvNAWccbItceIE= 133 | github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 134 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 135 | github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= 136 | github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= 137 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 138 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 139 | github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= 140 | github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= 141 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 142 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 143 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 144 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 145 | github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 146 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 147 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 148 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 149 | github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= 150 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 151 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 152 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 153 | github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= 154 | github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4= 155 | github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= 156 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 157 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 158 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 159 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 160 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 161 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 162 | golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708 h1:pXVtWnwHkrWD9ru3sDxY/qFK/bfc0egRovX91EjWjf4= 163 | golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 164 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 165 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 166 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 167 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 168 | golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 169 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 170 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 171 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 172 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 173 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 174 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 175 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 176 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 177 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 178 | golang.org/x/net v0.0.0-20191112182307-2180aed22343 h1:00ohfJ4K98s3m6BGUoBd8nyfp4Yl0GoIKvw5abItTjI= 179 | golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 180 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 181 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 182 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 183 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 184 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 185 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 186 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 187 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 188 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 189 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 190 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 191 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 192 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 193 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 194 | golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 195 | golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 196 | golang.org/x/sys v0.0.0-20191220142924-d4481acd189f h1:68K/z8GLUxV76xGSqwTWw2gyk/jwn79LUL43rES2g8o= 197 | golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 198 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 199 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 200 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 201 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 202 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 203 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 204 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 205 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 206 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 207 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 208 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 209 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 210 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 211 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 212 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 213 | google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 214 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 215 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= 216 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 217 | google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= 218 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 219 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 220 | google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk= 221 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 222 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 223 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 224 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 225 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 226 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 227 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 228 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 229 | gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= 230 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 231 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 232 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 233 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // This file is needed because it is used by vscode and other tools that 2 | // call `jest` directly. However, unless you are doing anything special 3 | // do not edit this file 4 | 5 | const standard = require('@grafana/toolkit/src/config/jest.plugin.config'); 6 | 7 | // This process will use the same config that `yarn test` is using 8 | module.exports = standard.jestConfig(); 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grafana-influxdb-flux-datasource", 3 | "version": "7.0.0-dev", 4 | "description": "Grafana data source for InfluxDB (Flux query support)", 5 | "private": true, 6 | "scripts": { 7 | "build": "grafana-toolkit plugin:build", 8 | "test": "grafana-toolkit plugin:test", 9 | "dev": "grafana-toolkit plugin:dev", 10 | "watch": "grafana-toolkit plugin:dev --watch" 11 | }, 12 | "repository": "github:grafana/influxdb-flux-datasource", 13 | "author": "Grafana Labs (https://grafana.com)", 14 | "license": "Apache-2.0", 15 | "devDependencies": { 16 | "@grafana/data": "next", 17 | "@grafana/runtime": "next", 18 | "@grafana/toolkit": "next", 19 | "@grafana/ui": "next", 20 | "@monaco-editor/react": "3.3.0", 21 | "monaco-editor": "^0.20.0", 22 | "@types/lodash": "latest" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pkg/converters/converters.go: -------------------------------------------------------------------------------- 1 | package converters 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/grafana/grafana-plugin-sdk-go/data" 9 | ) 10 | 11 | // Int64NOOP ..... 12 | var Int64NOOP = data.FieldConverter{ 13 | OutputFieldType: data.FieldTypeInt64, 14 | } 15 | 16 | // BoolNOOP ..... 17 | var BoolNOOP = data.FieldConverter{ 18 | OutputFieldType: data.FieldTypeBool, 19 | } 20 | 21 | // Float64NOOP ..... 22 | var Float64NOOP = data.FieldConverter{ 23 | OutputFieldType: data.FieldTypeFloat64, 24 | } 25 | 26 | // StringNOOP value is already in the proper format 27 | var StringNOOP = data.FieldConverter{ 28 | OutputFieldType: data.FieldTypeString, 29 | } 30 | 31 | // AnyToOptionalString any value as a string 32 | var AnyToOptionalString = data.FieldConverter{ 33 | OutputFieldType: data.FieldTypeNullableString, 34 | Converter: func(v interface{}) (interface{}, error) { 35 | if v == nil { 36 | return nil, nil 37 | } 38 | str := fmt.Sprintf("%+v", v) // the +v adds field names 39 | return &str, nil 40 | }, 41 | } 42 | 43 | // Float64ToOptionalFloat64 optional float value 44 | var Float64ToOptionalFloat64 = data.FieldConverter{ 45 | OutputFieldType: data.FieldTypeNullableFloat64, 46 | Converter: func(v interface{}) (interface{}, error) { 47 | if v == nil { 48 | return nil, nil 49 | } 50 | val, ok := v.(float64) 51 | if !ok { // or return some default value instead of erroring 52 | return nil, fmt.Errorf("[float] expected float64 input but got type %T", v) 53 | } 54 | return &val, nil 55 | }, 56 | } 57 | 58 | // Int64ToOptionalInt64 optional int value 59 | var Int64ToOptionalInt64 = data.FieldConverter{ 60 | OutputFieldType: data.FieldTypeNullableInt64, 61 | Converter: func(v interface{}) (interface{}, error) { 62 | if v == nil { 63 | return nil, nil 64 | } 65 | val, ok := v.(int64) 66 | if !ok { // or return some default value instead of erroring 67 | return nil, fmt.Errorf("[int] expected int64 input but got type %T", v) 68 | } 69 | return &val, nil 70 | }, 71 | } 72 | 73 | var TimeToTime = data.FieldConverter{ 74 | OutputFieldType: data.FieldTypeTime, 75 | Converter: func(v interface{}) (interface{}, error) { 76 | if v == nil { 77 | return nil, nil 78 | } 79 | val, ok := v.(time.Time) 80 | if !ok { // or return some default value instead of erroring 81 | return nil, fmt.Errorf("[time] expected time.Time input but got type %T", v) 82 | } 83 | return val, nil 84 | }, 85 | } 86 | 87 | 88 | // UInt64ToOptionalUInt64 optional int value 89 | var UInt64ToOptionalUInt64 = data.FieldConverter{ 90 | OutputFieldType: data.FieldTypeNullableUint64, 91 | Converter: func(v interface{}) (interface{}, error) { 92 | if v == nil { 93 | return nil, nil 94 | } 95 | val, ok := v.(uint64) 96 | if !ok { // or return some default value instead of erroring 97 | return nil, fmt.Errorf("[uint] expected uint64 input but got type %T", v) 98 | } 99 | return &val, nil 100 | }, 101 | } 102 | 103 | // BoolToOptionalBool optional int value 104 | var BoolToOptionalBool = data.FieldConverter{ 105 | OutputFieldType: data.FieldTypeNullableBool, 106 | Converter: func(v interface{}) (interface{}, error) { 107 | if v == nil { 108 | return nil, nil 109 | } 110 | val, ok := v.(bool) 111 | if !ok { // or return some default value instead of erroring 112 | return nil, fmt.Errorf("[bool] expected bool input but got type %T", v) 113 | } 114 | return &val, nil 115 | }, 116 | } 117 | 118 | // RFC3339StringToNullableTime ..... 119 | func RFC3339StringToNullableTime(s string) (*time.Time, error) { 120 | if s == "" { 121 | return nil, nil 122 | } 123 | 124 | rv, err := time.Parse(time.RFC3339, s) 125 | if err != nil { 126 | return nil, err 127 | } 128 | 129 | u := rv.UTC() 130 | return &u, nil 131 | } 132 | 133 | // StringToOptionalFloat64 string to float 134 | var StringToOptionalFloat64 = data.FieldConverter{ 135 | OutputFieldType: data.FieldTypeNullableFloat64, 136 | Converter: func(v interface{}) (interface{}, error) { 137 | if v == nil { 138 | return nil, nil 139 | } 140 | val, ok := v.(string) 141 | if !ok { // or return some default value instead of erroring 142 | return nil, fmt.Errorf("[floatz] expected string input but got type %T", v) 143 | } 144 | fV, err := strconv.ParseFloat(val, 64) 145 | return &fV, err 146 | }, 147 | } 148 | 149 | // Float64EpochSecondsToTime numeric seconds to time 150 | var Float64EpochSecondsToTime = data.FieldConverter{ 151 | OutputFieldType: data.FieldTypeTime, 152 | Converter: func(v interface{}) (interface{}, error) { 153 | fV, ok := v.(float64) 154 | if !ok { // or return some default value instead of erroring 155 | return nil, fmt.Errorf("[seconds] expected float64 input but got type %T", v) 156 | } 157 | return time.Unix(int64(fV), 0).UTC(), nil 158 | }, 159 | } 160 | 161 | // Float64EpochMillisToTime convert to time 162 | var Float64EpochMillisToTime = data.FieldConverter{ 163 | OutputFieldType: data.FieldTypeTime, 164 | Converter: func(v interface{}) (interface{}, error) { 165 | fV, ok := v.(float64) 166 | if !ok { // or return some default value instead of erroring 167 | return nil, fmt.Errorf("[ms] expected float64 input but got type %T", v) 168 | } 169 | return time.Unix(0, int64(fV)*int64(time.Millisecond)).UTC(), nil 170 | }, 171 | } 172 | 173 | // Boolean ... 174 | var Boolean = data.FieldConverter{ 175 | OutputFieldType: data.FieldTypeBool, 176 | Converter: func(v interface{}) (interface{}, error) { 177 | fV, ok := v.(bool) 178 | if !ok { // or return some default value instead of erroring 179 | return nil, fmt.Errorf("[ms] expected bool input but got type %T", v) 180 | } 181 | return fV, nil 182 | }, 183 | } 184 | -------------------------------------------------------------------------------- /pkg/influx/builder.go: -------------------------------------------------------------------------------- 1 | package influx 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/grafana/grafana-plugin-sdk-go/data" 7 | "github.com/influxdata/influxdb-client-go/api/query" 8 | 9 | "github.com/grafana/influxdb-flux-datasource/pkg/converters" 10 | ) 11 | 12 | // Copied from: (Apache 2 license) 13 | // https://github.com/influxdata/influxdb-client-go/blob/master/query.go#L30 14 | const ( 15 | stringDatatype = "string" 16 | doubleDatatype = "double" 17 | boolDatatype = "bool" 18 | longDatatype = "long" 19 | uLongDatatype = "unsignedLong" 20 | durationDatatype = "duration" 21 | base64BinaryDataType = "base64Binary" 22 | timeDatatypeRFC = "dateTime:RFC3339" 23 | timeDatatypeRFCNano = "dateTime:RFC3339Nano" 24 | ) 25 | 26 | type columnInfo struct { 27 | name string 28 | converter *data.FieldConverter 29 | } 30 | 31 | // FrameBuilder is an interface to help testing 32 | type FrameBuilder struct { 33 | tableId int64 34 | active *data.Frame 35 | frames []*data.Frame 36 | value *data.FieldConverter 37 | columns []columnInfo 38 | labels []string 39 | maxPoints int // max points in a series 40 | maxSeries int // max number of series 41 | totalSeries int 42 | isTimeSeries bool 43 | } 44 | 45 | func isTag(schk string) bool { 46 | return (schk != "result" && schk != "table" && schk[0] != '_') 47 | } 48 | 49 | func getConverter(t string) (*data.FieldConverter, error) { 50 | switch t { 51 | case stringDatatype: 52 | return &converters.AnyToOptionalString, nil 53 | case timeDatatypeRFC: 54 | return &converters.TimeToTime, nil 55 | case timeDatatypeRFCNano: 56 | return &converters.TimeToTime, nil 57 | case durationDatatype: 58 | return &converters.Int64ToOptionalInt64, nil 59 | case doubleDatatype: 60 | return &converters.Float64ToOptionalFloat64, nil 61 | case boolDatatype: 62 | return &converters.BoolToOptionalBool, nil 63 | case longDatatype: 64 | return &converters.Int64ToOptionalInt64, nil 65 | case uLongDatatype: 66 | return &converters.UInt64ToOptionalUInt64, nil 67 | case base64BinaryDataType: 68 | return &converters.AnyToOptionalString, nil 69 | } 70 | 71 | return nil, fmt.Errorf("No matching converter found for [%v]", t) 72 | } 73 | 74 | // Init initializes the frame to be returned 75 | // fields points at entries in the frame, and provides easier access 76 | // names indexes the columns encountered 77 | func (fb *FrameBuilder) Init(metadata *query.FluxTableMetadata) error { 78 | columns := metadata.Columns() 79 | fb.frames = make([]*data.Frame, 0) 80 | fb.tableId = -1 81 | fb.value = nil 82 | fb.columns = make([]columnInfo, 0) 83 | fb.isTimeSeries = false 84 | 85 | for _, col := range columns { 86 | switch { 87 | case col.Name() == "_value": 88 | if fb.value != nil { 89 | return fmt.Errorf("multiple values found") 90 | } 91 | converter, err := getConverter(col.DataType()) 92 | if err != nil { 93 | return err 94 | } 95 | fb.value = converter 96 | fb.isTimeSeries = true 97 | case isTag(col.Name()): 98 | fb.labels = append(fb.labels, col.Name()) 99 | } 100 | } 101 | 102 | if !fb.isTimeSeries { 103 | fb.labels = make([]string, 0) 104 | for _, col := range columns { 105 | converter, err := getConverter(col.DataType()) 106 | if err != nil { 107 | return err 108 | } 109 | fb.columns = append(fb.columns, columnInfo{ 110 | name: col.Name(), 111 | converter: converter, 112 | }) 113 | } 114 | } 115 | 116 | return nil 117 | } 118 | 119 | // Append appends a single entry from an influxdb2 record to a data frame 120 | // Values are appended to _value 121 | // Tags are appended as labels 122 | // _measurement holds the dataframe name 123 | // _field holds the field name. 124 | func (fb *FrameBuilder) Append(record *query.FluxRecord) error { 125 | table, ok := record.ValueByKey("table").(int64) 126 | if ok && table != fb.tableId { 127 | fb.totalSeries++ 128 | if fb.totalSeries > fb.maxSeries { 129 | return fmt.Errorf("reached max series limit (%d)", fb.maxSeries) 130 | } 131 | 132 | if fb.isTimeSeries { 133 | frameName := "" 134 | name := record.ValueByKey("_measurement") 135 | if name != nil { 136 | frameName = name.(string) 137 | } 138 | name = record.ValueByKey("_field") 139 | 140 | // Series Data 141 | labels := make(map[string]string) 142 | for _, name := range fb.labels { 143 | labels[name] = record.ValueByKey(name).(string) 144 | } 145 | fb.active = data.NewFrame( 146 | frameName, 147 | data.NewFieldFromFieldType(data.FieldTypeTime, 0), 148 | data.NewFieldFromFieldType(fb.value.OutputFieldType, 0), 149 | ) 150 | 151 | fb.active.Fields[0].Name = "Time" 152 | fb.active.Fields[1].Labels = labels 153 | if name != nil { 154 | fb.active.Fields[1].Name = name.(string) 155 | } 156 | } else { 157 | fields := make([]*data.Field, len(fb.columns)) 158 | for idx, col := range fb.columns { 159 | fields[idx] = data.NewFieldFromFieldType(col.converter.OutputFieldType, 0) 160 | fields[idx].Name = col.name 161 | } 162 | fb.active = data.NewFrame("", fields...) 163 | } 164 | 165 | fb.frames = append(fb.frames, fb.active) 166 | fb.tableId = table 167 | } 168 | 169 | if fb.isTimeSeries { 170 | val, err := fb.value.Converter(record.Value()) 171 | if err != nil { 172 | return err 173 | } 174 | fb.active.Fields[0].Append(record.Time()) 175 | fb.active.Fields[1].Append(val) 176 | } else { 177 | // Table view 178 | for idx, col := range fb.columns { 179 | val, err := col.converter.Converter(record.ValueByKey(col.name)) 180 | if err != nil { 181 | return fmt.Errorf("Can't convert col %s: %s", col.name, err) 182 | } 183 | fb.active.Fields[idx].Append(val) 184 | } 185 | } 186 | 187 | if fb.active.Fields[0].Len() > int(fb.maxPoints) { 188 | return fmt.Errorf("returned too many points in a series: %d", fb.maxPoints) 189 | } 190 | 191 | return nil 192 | } 193 | -------------------------------------------------------------------------------- /pkg/influx/builder_test.go: -------------------------------------------------------------------------------- 1 | package influx 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestColumnIdentification(t *testing.T) { 8 | t.Run("Test Tag Identification", func(t *testing.T) { 9 | tagNames := []string{"header", "value", "tag"} 10 | for _, item := range tagNames { 11 | if !isTag(item) { 12 | t.Fatal("Tag", item, "Expected tag, but got false") 13 | } 14 | } 15 | }) 16 | 17 | t.Run("Test Special Case Tag Identification", func(t *testing.T) { 18 | notTagNames := []string{"table", "result"} 19 | for _, item := range notTagNames { 20 | if isTag(item) { 21 | t.Fatal("Special tag", item, "Expected NOT a tag, but got true") 22 | } 23 | } 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /pkg/influx/datasource.go: -------------------------------------------------------------------------------- 1 | package influx 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/grafana/grafana-plugin-sdk-go/backend" 8 | "github.com/grafana/grafana-plugin-sdk-go/backend/datasource" 9 | "github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt" 10 | "github.com/grafana/grafana-plugin-sdk-go/backend/resource" 11 | "github.com/grafana/influxdb-flux-datasource/pkg/models" 12 | influxdb2 "github.com/influxdata/influxdb-client-go" 13 | "github.com/influxdata/influxdb-client-go/api" 14 | "github.com/influxdata/influxdb-client-go/domain" 15 | ) 16 | 17 | // This is an interface to help testing 18 | type queryRunner interface { 19 | runQuery(ctx context.Context, q string) (*api.QueryTableResult, error) 20 | checkHealth(ctx context.Context) (*domain.HealthCheck, error) 21 | } 22 | 23 | // InfluxRunner This is an interface to help testing 24 | type InfluxRunner struct { 25 | client influxdb2.Client 26 | org string 27 | } 28 | 29 | func (r *InfluxRunner) runQuery(ctx context.Context, q string) (*api.QueryTableResult, error) { 30 | return r.client.QueryApi(r.org).Query(ctx, q) 31 | } 32 | func (r *InfluxRunner) checkHealth(ctx context.Context) (*domain.HealthCheck, error) { 33 | return r.client.Health(ctx) 34 | } 35 | 36 | type instanceSettings struct { 37 | maxSeries int 38 | Runner queryRunner 39 | } 40 | 41 | func newDataSourceInstance(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { 42 | settings, err := models.LoadSettings(s) 43 | if err != nil { 44 | return nil, fmt.Errorf("error reading settings: %s", err.Error()) 45 | } 46 | 47 | return &instanceSettings{ 48 | maxSeries: settings.MaxSeries, 49 | Runner: &InfluxRunner{ 50 | client: influxdb2.NewClientWithOptions(settings.URL, settings.Token, settings.Options), 51 | org: settings.Organization, 52 | }, 53 | }, nil 54 | } 55 | 56 | func (s *instanceSettings) Dispose() { 57 | // Called before creatinga a new instance to allow plugin authors 58 | // to cleanup. 59 | } 60 | 61 | // InfluxDataSource ... 62 | type InfluxDataSource struct { 63 | im instancemgmt.InstanceManager 64 | } 65 | 66 | // NewDatasource creates a new datasource server 67 | func NewDatasource() datasource.ServeOpts { 68 | im := datasource.NewInstanceManager(newDataSourceInstance) 69 | ds := &InfluxDataSource{ 70 | im: im, 71 | } 72 | 73 | return datasource.ServeOpts{ 74 | QueryDataHandler: ds, 75 | CheckHealthHandler: ds, 76 | CallResourceHandler: ds, 77 | } 78 | } 79 | 80 | func (ds *InfluxDataSource) getInstance(ctx backend.PluginContext) (*instanceSettings, error) { 81 | s, err := ds.im.Get(ctx) 82 | if err != nil { 83 | return nil, err 84 | } 85 | return s.(*instanceSettings), nil 86 | } 87 | 88 | // CheckHealth will check the currently configured settings 89 | func (ds *InfluxDataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { 90 | s, err := ds.getInstance(req.PluginContext) 91 | if err != nil { 92 | return &backend.CheckHealthResult{ 93 | Status: backend.HealthStatusError, 94 | Message: err.Error(), 95 | }, nil 96 | } 97 | 98 | _, err = s.Runner.checkHealth(ctx) 99 | if err != nil { 100 | return &backend.CheckHealthResult{ 101 | Status: backend.HealthStatusError, 102 | Message: err.Error(), 103 | }, nil 104 | } 105 | 106 | res, err := s.Runner.runQuery(ctx, "buckets()") 107 | if err != nil { 108 | return &backend.CheckHealthResult{ 109 | Status: backend.HealthStatusError, 110 | Message: err.Error(), 111 | }, nil 112 | } 113 | 114 | dr := readDataFrames(res, 1000, 100) 115 | if dr.Error != nil { 116 | return &backend.CheckHealthResult{ 117 | Status: backend.HealthStatusError, 118 | Message: dr.Error.Error(), 119 | }, nil 120 | } 121 | 122 | rowLen, err := dr.Frames[0].RowLen() 123 | if err != nil { 124 | return &backend.CheckHealthResult{ 125 | Status: backend.HealthStatusError, 126 | Message: dr.Error.Error(), 127 | }, nil 128 | } 129 | 130 | return &backend.CheckHealthResult{ 131 | Status: backend.HealthStatusOk, 132 | Message: fmt.Sprintf("%d buckets", rowLen), // TODO!! 133 | }, nil 134 | } 135 | 136 | // QueryData - Primary method called by grafana-server 137 | func (ds *InfluxDataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { 138 | s, err := ds.getInstance(req.PluginContext) 139 | if err != nil { 140 | return nil, err 141 | } 142 | 143 | res := backend.NewQueryDataResponse() 144 | for _, q := range req.Queries { 145 | query, err := models.GetQueryModel(q) 146 | if err != nil { 147 | res.Responses[q.RefID] = backend.DataResponse{ 148 | Error: err, 149 | } 150 | } else { 151 | res.Responses[q.RefID] = ExecuteQuery(context.Background(), *query, s.Runner, s.maxSeries) 152 | } 153 | } 154 | return res, nil 155 | } 156 | 157 | // CallResource HTTP style resource 158 | func (ds *InfluxDataSource) CallResource(rtx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error { 159 | if req.Path == "hello" { 160 | return resource.SendPlainText(sender, "world") 161 | } 162 | 163 | return fmt.Errorf("unknown resource") 164 | } 165 | -------------------------------------------------------------------------------- /pkg/influx/executor.go: -------------------------------------------------------------------------------- 1 | package influx 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/grafana/grafana-plugin-sdk-go/backend" 8 | "github.com/grafana/grafana-plugin-sdk-go/data" 9 | "github.com/grafana/influxdb-flux-datasource/pkg/models" 10 | "github.com/influxdata/influxdb-client-go/api" 11 | ) 12 | 13 | // ExecuteQuery execute a query 14 | func ExecuteQuery(ctx context.Context, query models.QueryModel, runner queryRunner, maxSeries int) (dr backend.DataResponse) { 15 | dr = backend.DataResponse{} 16 | 17 | flux, err := Interpolate(query) 18 | if err != nil { 19 | dr.Error = err 20 | return 21 | } 22 | 23 | tables, err := runner.runQuery(ctx, flux) 24 | if err == nil { 25 | dr = readDataFrames(tables, int(float64(query.MaxDataPoints)*1.5), maxSeries) 26 | } else { 27 | dr.Error = err 28 | } 29 | 30 | // Add an empty frame 31 | if len(dr.Frames) < 1 { 32 | dr.Frames = append(dr.Frames, data.NewFrame("")) 33 | } 34 | frame := dr.Frames[0] 35 | if frame.Meta == nil { 36 | frame.Meta = &data.FrameMeta{} 37 | } 38 | frame.Meta.ExecutedQueryString = flux 39 | return dr 40 | } 41 | 42 | func readDataFrames(result *api.QueryTableResult, maxPoints int, maxSeries int) (dr backend.DataResponse) { 43 | dr = backend.DataResponse{} 44 | 45 | builder := &FrameBuilder{ 46 | maxPoints: maxPoints, 47 | maxSeries: maxSeries, 48 | } 49 | 50 | for result.Next() { 51 | // Observe when there is new grouping key producing new table 52 | if result.TableChanged() { 53 | if builder.frames != nil { 54 | for _, frame := range builder.frames { 55 | dr.Frames = append(dr.Frames, frame) 56 | } 57 | } 58 | err := builder.Init(result.TableMetadata()) 59 | if err != nil { 60 | dr.Error = err 61 | return 62 | } 63 | } 64 | 65 | if builder.frames == nil { 66 | dr.Error = fmt.Errorf("Invalid state") 67 | return dr 68 | } 69 | 70 | err := builder.Append(result.Record()) 71 | if err != nil { 72 | dr.Error = err 73 | break 74 | } 75 | } 76 | 77 | // Add the inprogress record 78 | if builder.frames != nil { 79 | for _, frame := range builder.frames { 80 | dr.Frames = append(dr.Frames, frame) 81 | } 82 | } 83 | 84 | // Attach any errors (may be null) 85 | if result.Err() != nil { 86 | dr.Error = result.Err() 87 | } 88 | if dr.Error != nil { 89 | // reset frames to prevent any further errors (e.g. by incomplete rows) 90 | dr.Frames = nil 91 | } 92 | return 93 | } 94 | -------------------------------------------------------------------------------- /pkg/influx/executor_test.go: -------------------------------------------------------------------------------- 1 | package influx 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/google/go-cmp/cmp" 10 | "github.com/grafana/influxdb-flux-datasource/pkg/models" 11 | ) 12 | 13 | func TestExecuteSimple(t *testing.T) { 14 | ctx := context.Background() 15 | 16 | t.Run("Simple Test", func(t *testing.T) { 17 | runner := &MockRunner{ 18 | testDataPath: "simple.csv", 19 | } 20 | 21 | dr := ExecuteQuery(ctx, models.QueryModel{MaxDataPoints: 100}, runner, 50) 22 | 23 | if dr.Error != nil { 24 | t.Fatal(dr.Error) 25 | } 26 | 27 | if len(dr.Frames) != 1 { 28 | t.Fatalf("Expected 1 frame, received [%d] frames", len(dr.Frames)) 29 | } 30 | 31 | if !strings.Contains(dr.Frames[0].Name, "test") { 32 | t.Fatalf("Frame must match _measurement column. Expected [%s] Got [%s]", "test", dr.Frames[0].Name) 33 | } 34 | 35 | if len(dr.Frames[0].Fields[1].Labels) != 2 { 36 | t.Fatalf("Error parsing labels. Expected [%d] Got [%d]", 2, len(dr.Frames[0].Fields[1].Labels)) 37 | } 38 | 39 | if dr.Frames[0].Fields[0].Name != "Time" { 40 | t.Fatalf("Error parsing fields. Field 1 should always be time. Got name [%s]", dr.Frames[0].Fields[0].Name) 41 | } 42 | 43 | st, _ := dr.Frames[0].StringTable(-1, -1) 44 | fmt.Println(st) 45 | fmt.Println("----------------------") 46 | }) 47 | } 48 | 49 | func TestExecuteMultiple(t *testing.T) { 50 | ctx := context.Background() 51 | 52 | t.Run("Multiple Test", func(t *testing.T) { 53 | runner := &MockRunner{ 54 | testDataPath: "multiple.csv", 55 | } 56 | 57 | dr := ExecuteQuery(ctx, models.QueryModel{MaxDataPoints: 100}, runner, 50) 58 | 59 | if dr.Error != nil { 60 | t.Fatal(dr.Error) 61 | } 62 | 63 | if len(dr.Frames) != 4 { 64 | t.Fatalf("Expected 4 frames, received [%d] frames", len(dr.Frames)) 65 | } 66 | 67 | if !strings.Contains(dr.Frames[0].Name, "test") { 68 | t.Fatalf("Frame must include _measurement column. Expected [%s] Got [%s]", "test", dr.Frames[0].Name) 69 | } 70 | 71 | if len(dr.Frames[0].Fields[1].Labels) != 2 { 72 | t.Fatalf("Error parsing labels. Expected [%d] Got [%d]", 2, len(dr.Frames[0].Fields[1].Labels)) 73 | } 74 | 75 | if dr.Frames[0].Fields[0].Name != "Time" { 76 | t.Fatalf("Error parsing fields. Field 1 should always be time. Got name [%s]", dr.Frames[0].Fields[0].Name) 77 | } 78 | 79 | st, _ := dr.Frames[0].StringTable(-1, -1) 80 | fmt.Println(st) 81 | fmt.Println("----------------------") 82 | }) 83 | } 84 | 85 | func TestExecuteGrouping(t *testing.T) { 86 | ctx := context.Background() 87 | 88 | t.Run("Grouping Test", func(t *testing.T) { 89 | runner := &MockRunner{ 90 | testDataPath: "grouping.csv", 91 | } 92 | 93 | dr := ExecuteQuery(ctx, models.QueryModel{MaxDataPoints: 100}, runner, 50) 94 | 95 | if dr.Error != nil { 96 | t.Fatal(dr.Error) 97 | } 98 | 99 | if len(dr.Frames) != 3 { 100 | t.Fatalf("Expected 3 frames, received [%d] frames", len(dr.Frames)) 101 | } 102 | 103 | if !strings.Contains(dr.Frames[0].Name, "system") { 104 | t.Fatalf("Frame must match _measurement column. Expected [%s] Got [%s]", "test", dr.Frames[0].Name) 105 | } 106 | 107 | if len(dr.Frames[0].Fields[1].Labels) != 1 { 108 | t.Fatalf("Error parsing labels. Expected [%d] Got [%d]", 1, len(dr.Frames[0].Fields[1].Labels)) 109 | } 110 | 111 | if dr.Frames[0].Fields[0].Name != "Time" { 112 | t.Fatalf("Error parsing fields. Field 1 should always be time. Got name [%s]", dr.Frames[0].Fields[0].Name) 113 | } 114 | 115 | st, _ := dr.Frames[0].StringTable(-1, -1) 116 | fmt.Println(st) 117 | fmt.Println("----------------------") 118 | }) 119 | } 120 | 121 | func TestAggregateGrouping(t *testing.T) { 122 | ctx := context.Background() 123 | 124 | t.Run("Grouping Test", func(t *testing.T) { 125 | runner := &MockRunner{ 126 | testDataPath: "aggregate.csv", 127 | } 128 | 129 | dr := ExecuteQuery(ctx, models.QueryModel{MaxDataPoints: 100}, runner, 50) 130 | 131 | if dr.Error != nil { 132 | t.Fatal(dr.Error) 133 | } 134 | 135 | str, _ := dr.Frames[0].StringTable(-1, -1) 136 | fmt.Println(str) 137 | 138 | expect := `Name: 139 | Dimensions: 2 Fields by 3 Rows 140 | +-------------------------------+--------------------------+ 141 | | Name: Time | Name: | 142 | | Labels: | Labels: host=hostname.ru | 143 | | Type: []time.Time | Type: []*float64 | 144 | +-------------------------------+--------------------------+ 145 | | 2020-06-05 12:06:00 +0000 UTC | 8.291381590647958 | 146 | | 2020-06-05 12:07:00 +0000 UTC | 0.5341565263056448 | 147 | | 2020-06-05 12:08:00 +0000 UTC | 0.6676119389260387 | 148 | +-------------------------------+--------------------------+ 149 | ` 150 | 151 | if diff := cmp.Diff(str, expect); diff != "" { 152 | t.Fatalf("mismatch %s (-want +got):\n%s", "aggregate.csv", diff) 153 | } 154 | 155 | }) 156 | } 157 | 158 | func TestBuckets(t *testing.T) { 159 | ctx := context.Background() 160 | 161 | t.Run("Buckes", func(t *testing.T) { 162 | runner := &MockRunner{ 163 | testDataPath: "buckets.csv", 164 | } 165 | 166 | dr := ExecuteQuery(ctx, models.QueryModel{MaxDataPoints: 100}, runner, 50) 167 | 168 | if dr.Error != nil { 169 | t.Fatal(dr.Error) 170 | } 171 | 172 | st, _ := dr.Frames[0].StringTable(-1, -1) 173 | fmt.Println(st) 174 | fmt.Println("----------------------") 175 | }) 176 | } 177 | -------------------------------------------------------------------------------- /pkg/influx/macros.go: -------------------------------------------------------------------------------- 1 | package influx 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strings" 7 | "time" 8 | 9 | "github.com/grafana/grafana-plugin-sdk-go/backend" 10 | "github.com/grafana/influxdb-flux-datasource/pkg/models" 11 | ) 12 | 13 | const variableFilter = `(?m)([a-zA-Z]+)\.([a-zA-Z]+)` 14 | 15 | // Interpolate processes macros 16 | func Interpolate(query models.QueryModel) (string, error) { 17 | 18 | flux := query.RawQuery 19 | 20 | variableFilterExp, err := regexp.Compile(variableFilter) 21 | matches := variableFilterExp.FindAllStringSubmatch(flux, -1) 22 | if matches != nil { 23 | timeRange := query.TimeRange 24 | from := timeRange.From.UTC().Format(time.RFC3339) 25 | to := timeRange.To.UTC().Format(time.RFC3339) 26 | for _, match := range matches { 27 | switch match[2] { 28 | case "timeRangeStart": 29 | flux = strings.ReplaceAll(flux, match[0], from) 30 | case "timeRangeStop": 31 | flux = strings.ReplaceAll(flux, match[0], to) 32 | case "windowPeriod": 33 | flux = strings.ReplaceAll(flux, match[0], query.Interval.String()) 34 | case "bucket": 35 | flux = strings.ReplaceAll(flux, match[0], "\""+query.Options.Bucket+"\"") 36 | case "defaultBucket": 37 | flux = strings.ReplaceAll(flux, match[0], "\""+query.Options.DefaultBucket+"\"") 38 | case "organization": 39 | flux = strings.ReplaceAll(flux, match[0], "\""+query.Options.Organization+"\"") 40 | } 41 | } 42 | } 43 | 44 | backend.Logger.Info(fmt.Sprintf("%s => %v", flux, query.Options)) 45 | return flux, err 46 | } 47 | -------------------------------------------------------------------------------- /pkg/influx/macros_test.go: -------------------------------------------------------------------------------- 1 | package influx 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | "github.com/grafana/grafana-plugin-sdk-go/backend" 9 | "github.com/grafana/influxdb-flux-datasource/pkg/models" 10 | ) 11 | 12 | func TestInterpolate(t *testing.T) { 13 | // Unix sec: 1500376552 14 | // Unix ms: 1500376552001 15 | 16 | timeRange := backend.TimeRange{ 17 | From: time.Unix(0, 0), 18 | To: time.Unix(0, 0), 19 | } 20 | 21 | options := models.QueryOptions{ 22 | Organization: "grafana1", 23 | Bucket: "grafana2", 24 | DefaultBucket: "grafana3", 25 | } 26 | 27 | tests := []struct { 28 | name string 29 | before string 30 | after string 31 | }{ 32 | { 33 | name: "interpolate flux variables", 34 | before: `v.timeRangeStart, something.timeRangeStop, XYZ.bucket, uuUUu.defaultBucket, aBcDefG.organization, window.windowPeriod, a91{}.bucket`, 35 | after: `1970-01-01T00:00:00Z, 1970-01-01T00:00:00Z, "grafana2", "grafana3", "grafana1", 1s, a91{}.bucket`, 36 | }, 37 | } 38 | for _, tt := range tests { 39 | t.Run(tt.name, func(t *testing.T) { 40 | 41 | query := models.QueryModel{ 42 | RawQuery: tt.before, 43 | Options: options, 44 | TimeRange: timeRange, 45 | MaxDataPoints: 1, 46 | Interval: 1000 * 1000 * 1000, 47 | } 48 | interpolatedQuery, err := Interpolate(query) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | if diff := cmp.Diff(tt.after, interpolatedQuery); diff != "" { 53 | t.Fatalf("Result mismatch (-want +got):\n%s", diff) 54 | } 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /pkg/influx/mock.go: -------------------------------------------------------------------------------- 1 | package influx 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "net/http/httptest" 9 | "time" 10 | 11 | influxdb2 "github.com/influxdata/influxdb-client-go" 12 | "github.com/influxdata/influxdb-client-go/api" 13 | "github.com/influxdata/influxdb-client-go/domain" 14 | ) 15 | 16 | //-------------------------------------------------------------- 17 | // TestData -- reads result from saved files 18 | //-------------------------------------------------------------- 19 | 20 | // MockRunner reads loacal file path 21 | type MockRunner struct { 22 | testDataPath string 23 | } 24 | 25 | func (r *MockRunner) runQuery(ctx context.Context, q string) (*api.QueryTableResult, error) { 26 | bytes, err := ioutil.ReadFile("./testdata/" + r.testDataPath) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 32 | time.Sleep(100 * time.Millisecond) 33 | if r.Method == http.MethodPost { 34 | w.WriteHeader(http.StatusOK) 35 | _, _ = w.Write(bytes) 36 | } else { 37 | w.WriteHeader(http.StatusNotFound) 38 | } 39 | })) 40 | defer server.Close() 41 | 42 | client := influxdb2.NewClient(server.URL, "a") 43 | return client.QueryApi("x").Query(ctx, q) 44 | } 45 | 46 | func (r *MockRunner) checkHealth(ctx context.Context) (*domain.HealthCheck, error) { 47 | return nil, fmt.Errorf("not implemented yet") 48 | } 49 | -------------------------------------------------------------------------------- /pkg/influx/testdata/aggregate.csv: -------------------------------------------------------------------------------- 1 | #datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,string,double 2 | #group,false,false,true,true,false,true,false 3 | #default,_result,,,,,, 4 | ,result,table,_start,_stop,_time,host,_value 5 | ,,0,2020-06-05T12:03:27.444072266Z,2020-06-05T12:08:27.444072266Z,2020-06-05T12:06:00Z,hostname.ru,8.291381590647958 6 | ,,0,2020-06-05T12:03:27.444072266Z,2020-06-05T12:08:27.444072266Z,2020-06-05T12:07:00Z,hostname.ru,0.5341565263056448 7 | ,,0,2020-06-05T12:03:27.444072266Z,2020-06-05T12:08:27.444072266Z,2020-06-05T12:08:00Z,hostname.ru,0.6676119389260387 8 | -------------------------------------------------------------------------------- /pkg/influx/testdata/buckets.csv: -------------------------------------------------------------------------------- 1 | #datatype,string,long,string,string,string,string,long 2 | #group,false,false,false,false,true,false,false 3 | #default,_result,,,,,, 4 | ,result,table,name,id,organizationID,retentionPolicy,retentionPeriod 5 | ,,0,grafana,059b46a59abab001,059b46a59abab000,,604800000000000 6 | ,,0,_tasks,059b46a59abab002,059b46a59abab000,,259200000000000 7 | ,,0,_monitoring,059b46a59abab003,059b46a59abab000,,604800000000000 8 | -------------------------------------------------------------------------------- /pkg/influx/testdata/grouping.csv: -------------------------------------------------------------------------------- 1 | #datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,string,string,string,double,dateTime:RFC3339 2 | #group,false,false,true,true,true,true,true,false,false 3 | #default,mean,,,,,,,, 4 | ,result,table,_start,_stop,_field,_measurement,host,_value,_time 5 | ,,0,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load1,system,hostname,,2020-05-05T18:38:50Z 6 | ,,0,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load1,system,hostname,3.56,2020-05-05T18:39:00Z 7 | ,,0,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load1,system,hostname,,2020-05-05T19:38:47.207881833Z 8 | ,,1,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load15,system,hostname,,2020-05-05T18:38:50Z 9 | ,,1,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load15,system,hostname,2.51,2020-05-05T18:39:00Z 10 | ,,1,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load15,system,hostname,1.74,2020-05-05T19:38:40Z 11 | ,,1,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load15,system,hostname,,2020-05-05T19:38:47.207881833Z 12 | ,,2,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load5,system,hostname,,2020-05-05T18:38:50Z 13 | ,,2,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load5,system,hostname,3.14,2020-05-05T18:39:00Z 14 | ,,2,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load5,system,hostname,3.04,2020-05-05T18:39:10Z 15 | ,,2,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load5,system,hostname,1.8,2020-05-05T19:37:50Z 16 | ,,2,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load5,system,hostname,1.76,2020-05-05T19:38:00Z 17 | ,,2,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load5,system,hostname,1.75,2020-05-05T19:38:10Z 18 | ,,2,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load5,system,hostname,1.71,2020-05-05T19:38:20Z 19 | ,,2,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load5,system,hostname,1.77,2020-05-05T19:38:30Z 20 | ,,2,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load5,system,hostname,1.71,2020-05-05T19:38:40Z 21 | ,,2,2020-05-05T18:38:47.207881833Z,2020-05-05T19:38:47.207881833Z,load5,system,hostname,,2020-05-05T19:38:47.207881833Z -------------------------------------------------------------------------------- /pkg/influx/testdata/multiple.csv: -------------------------------------------------------------------------------- 1 | #datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,double,string,string,string,string 2 | #group,false,false,true,true,false,false,true,true,true,true 3 | #default,_result,,,,,,,,, 4 | ,result,table,_start,_stop,_time,_value,_field,_measurement,a,b 5 | ,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T10:34:08.135814545Z,1.4,f,test,1,adsfasdf 6 | ,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.850214724Z,6.6,f,test,1,adsfasdf 7 | #datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,long,string,string,string,string 8 | #group,false,false,true,true,false,false,true,true,true,true 9 | #default,_result,,,,,,,,, 10 | ,result,table,_start,_stop,_time,_value,_field,_measurement,a,b 11 | ,,1,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T10:34:08.135814545Z,4,i,test,1,adsfasdf 12 | ,,1,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.850214724Z,-1,i,test,1,adsfasdf 13 | #datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,bool,string,string,string,string 14 | #group,false,false,true,true,false,false,true,true,true,true 15 | #default,_result,,,,,,,,, 16 | ,result,table,_start,_stop,_time,_value,_field,_measurement,a,b 17 | ,,2,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.62797864Z,false,f,test,0,adsfasdf 18 | ,,2,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.969100374Z,true,f,test,0,adsfasdf 19 | #datatype,string,long,dateTime:RFC3339Nano,dateTime:RFC3339Nano,dateTime:RFC3339Nano,unsignedLong,string,string,string,string 20 | #group,false,false,true,true,false,false,true,true,true,true 21 | #default,_result,,,,,,,,, 22 | ,result,table,_start,_stop,_time,_value,_field,_measurement,a,b 23 | ,,3,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.62797864Z,0,i,test,0,adsfasdf 24 | ,,3,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.969100374Z,2,i,test,0,adsfasdf 25 | 26 | -------------------------------------------------------------------------------- /pkg/influx/testdata/simple.csv: -------------------------------------------------------------------------------- 1 | #datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,double,string,string,string,string 2 | #group,false,false,true,true,false,false,true,true,true,true 3 | #default,_result,,,,,,,,, 4 | ,result,table,_start,_stop,_time,_value,_field,_measurement,a,b 5 | ,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T10:34:08.135814545Z,1.4,f,test,1,adsfasdf 6 | ,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.850214724Z,6.6,f,test,1,adsfasdf 7 | -------------------------------------------------------------------------------- /pkg/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/grafana/grafana-plugin-sdk-go/backend" 7 | "github.com/grafana/grafana-plugin-sdk-go/backend/datasource" // datassource name conflict (will fix later...) 8 | "github.com/grafana/grafana-plugin-sdk-go/backend/log" 9 | "github.com/grafana/influxdb-flux-datasource/pkg/influx" 10 | ) 11 | 12 | func main() { 13 | // Setup the plugin environment 14 | backend.SetupPluginEnvironment("influx-datasource") 15 | 16 | err := datasource.Serve(influx.NewDatasource()) 17 | 18 | // Log any error if we could start the plugin. 19 | if err != nil { 20 | log.DefaultLogger.Error(err.Error()) 21 | os.Exit(1) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pkg/models/query.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/grafana/grafana-plugin-sdk-go/backend" 9 | ) 10 | 11 | // QueryOptions represents datasource configuration options 12 | type QueryOptions struct { 13 | Bucket string `json:"bucket"` 14 | DefaultBucket string `json:"defaultBucket"` 15 | Organization string `json:"organization"` 16 | } 17 | 18 | // QueryModel represents a spreadsheet query. 19 | type QueryModel struct { 20 | RawQuery string `json:"query"` 21 | Options QueryOptions `json:"options"` 22 | 23 | // Not from JSON 24 | TimeRange backend.TimeRange `json:"-"` 25 | MaxDataPoints int64 `json:"-"` 26 | Interval time.Duration `json:"-"` 27 | } 28 | 29 | func GetQueryModel(query backend.DataQuery) (*QueryModel, error) { 30 | model := &QueryModel{} 31 | 32 | err := json.Unmarshal(query.JSON, &model) 33 | if err != nil { 34 | return nil, fmt.Errorf("error reading query: %s", err.Error()) 35 | } 36 | 37 | // Copy directly from the well typed query 38 | model.TimeRange = query.TimeRange 39 | model.MaxDataPoints = query.MaxDataPoints 40 | model.Interval = query.Interval 41 | return model, nil 42 | } 43 | -------------------------------------------------------------------------------- /pkg/models/settings.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/grafana/grafana-plugin-sdk-go/backend" 8 | influxdb2 "github.com/influxdata/influxdb-client-go" 9 | ) 10 | 11 | // InfluxSettings contains config properties (share with other AWS services?) 12 | type DatasourceSettings struct { 13 | // Loaded from root object 14 | URL string 15 | Token string 16 | Organization string 17 | DefaultBucket string `json:"defaultBucket"` 18 | 19 | // Loaded from jsonData 20 | MaxSeries int `json:"maxSeries"` 21 | 22 | // Influx settings 23 | Options *influxdb2.Options 24 | } 25 | 26 | // // Whether to use GZip compression in requests. Default false 27 | // useGZip bool 28 | // // TLS configuration for secure connection. Default nil 29 | // tlsConfig *tls.Config 30 | // // HTTP request timeout in sec. Default 20 31 | // httpRequestTimeout uint 32 | 33 | func LoadSettings(settings backend.DataSourceInstanceSettings) (*DatasourceSettings, error) { 34 | model := &DatasourceSettings{} 35 | 36 | err := json.Unmarshal(settings.JSONData, &model) 37 | if err != nil { 38 | return nil, fmt.Errorf("error reading settings: %s", err.Error()) 39 | } 40 | if model.MaxSeries < 1 { 41 | model.MaxSeries = 50 // default 42 | } 43 | 44 | model.URL = settings.URL 45 | model.Token = settings.DecryptedSecureJSONData["token"] 46 | 47 | // TODO... other options 48 | model.Options = influxdb2.DefaultOptions() 49 | 50 | return model, nil 51 | } 52 | -------------------------------------------------------------------------------- /scripts/autobuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo rebuilding... 3 | #make build-darwin-debug 4 | mage build:debug 5 | echo rebuild done 6 | echo restarting process... 7 | pkill gpx_splunk_darwin_amd64 8 | if [ $? -eq 0 ]; then 9 | echo restarted 10 | else 11 | echo no process detected 12 | fi 13 | -------------------------------------------------------------------------------- /scripts/circle-cmd-lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function exit_if_fail { 4 | command=$@ 5 | echo "Executing '$command'" 6 | eval $command 7 | rc=$? 8 | if [ $rc -ne 0 ]; then 9 | echo "'$command' returned $rc." 10 | exit $rc 11 | fi 12 | } 13 | 14 | go get -u github.com/mgechev/revive 15 | 16 | # preferred method for linter install 17 | curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(go env GOPATH)/bin v1.21.0 18 | 19 | # use golangci-when possible 20 | exit_if_fail $(go env GOPATH)/bin/golangci-lint run --deadline 10m --disable-all \ 21 | --enable=deadcode\ 22 | --enable=gofmt\ 23 | --enable=ineffassign\ 24 | --enable=structcheck\ 25 | --enable=unconvert\ 26 | --enable=varcheck 27 | 28 | exit_if_fail go vet ./pkg/... 29 | 30 | exit_if_fail revive -formatter stylish -config ./scripts/revive.toml 31 | -------------------------------------------------------------------------------- /scripts/debug-attach.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # so dlv and mage are in the path 4 | export PATH=$(go env GOPATH)/bin:$PATH 5 | 6 | PLUGIN_NAME="$(cat src/plugin.json|jq ".executable" | tr -d '"')" 7 | PLUGIN_PID=`pgrep ${PLUGIN_NAME}` 8 | PORT="${2:-3222}" 9 | 10 | function finish { 11 | kill ${PLUGIN_PID} 12 | kill ${PPID} 13 | } 14 | trap finish EXIT 15 | 16 | # Calling mage causes it to stop working 17 | # PLUGIN_PID=$(mage pid) 18 | 19 | dlv attach ${PLUGIN_PID} --headless --listen=:${PORT} --api-version 2 --log 20 | 21 | ## TODO - why doesn't this work? 22 | #mage -v attach 23 | -------------------------------------------------------------------------------- /scripts/debug-backend.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ "$1" == "-h" ]; then 3 | echo "Usage: ${BASH_SOURCE[0]} [plugin process name] [port]" 4 | exit 5 | fi 6 | 7 | PORT="${2:-3222}" 8 | PLUGIN_NAME="${1:-gpx_splunk}" 9 | 10 | if [ "$OSTYPE" == "linux-gnu" ]; then 11 | ptrace_scope=`cat /proc/sys/kernel/yama/ptrace_scope` 12 | if [ "$ptrace_scope" != 0 ]; then 13 | echo "WARNING: ptrace_scope set to value other than 0, this might prevent debugger from connecting, try writing \"0\" to /proc/sys/kernel/yama/ptrace_scope. 14 | Read more at https://www.kernel.org/doc/Documentation/security/Yama.txt" 15 | read -p "Set ptrace_scope to 0? y/N (default N)" set_ptrace_input 16 | if [ "$set_ptrace_input" == "y" ] || [ "$set_ptrace_input" == "Y" ]; then 17 | echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope 18 | fi 19 | fi 20 | fi 21 | 22 | PLUGIN_PID=`pgrep ${PLUGIN_NAME}` 23 | export GOFLAGS="-ldflags=-compressdwarf=false"; dlv attach ${PLUGIN_PID} --headless --listen=:${PORT} --api-version 2 --log 24 | dlv 25 | #pkill dlv 26 | -------------------------------------------------------------------------------- /scripts/debug-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # so mage is in the path 4 | export PATH=$(go env GOPATH)/bin:$PATH 5 | 6 | mage -v build:debug 7 | mage -v reloadPlugin 8 | -------------------------------------------------------------------------------- /scripts/restart-plugin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | make 4 | cid=$(docker ps -aqf "name=grafana-splunk-datasource_grafana_1") 5 | docker exec -u 0 -it $cid bash > pkill -f "/var/lib/grafana/plugins/grafana-splunk-datasource/dist/grafana-splunk-datasource_linux_amd64" 6 | 7 | -------------------------------------------------------------------------------- /scripts/revive.toml: -------------------------------------------------------------------------------- 1 | ignoreGeneratedHeader = false 2 | severity = "error" 3 | confidence = 0.8 4 | errorCode = 0 5 | 6 | [rule.context-as-argument] 7 | [rule.error-return] 8 | [rule.package-comments] 9 | [rule.range] 10 | [rule.superfluous-else] 11 | [rule.modifies-parameter] 12 | [rule.indent-error-flow] 13 | [rule.error-strings] 14 | [rule.error-naming] 15 | 16 | # This can be checked by other tools like megacheck 17 | [rule.unreachable-code] 18 | 19 | # Those are probably should be enabled at some point 20 | [rule.unexported-return] 21 | [rule.exported] 22 | [rule.var-naming] 23 | # [rule.dot-imports] 24 | # [rule.receiver-naming] 25 | # [rule.blank-imports] 26 | -------------------------------------------------------------------------------- /src/DataSource.ts: -------------------------------------------------------------------------------- 1 | import { DataSourceInstanceSettings, MetricFindValue, DataQueryRequest, DataQueryResponse } from '@grafana/data'; 2 | import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime'; 3 | import { InfluxQuery, InfluxOptions } from './types'; 4 | import { Observable } from 'rxjs'; 5 | 6 | export class DataSource extends DataSourceWithBackend { 7 | constructor(private instanceSettings: DataSourceInstanceSettings) { 8 | super(instanceSettings); 9 | } 10 | 11 | query(request: DataQueryRequest): Observable { 12 | // Why? This is needed otherwise options never get passed into the query (ex: v.bucket) 13 | request.targets.forEach((target: InfluxQuery, i: number) => { 14 | request.targets[i] = { 15 | ...target, 16 | options: this.instanceSettings.jsonData, 17 | }; 18 | }); 19 | 20 | return super.query.call(this, request); 21 | } 22 | 23 | async metricFindQuery?(query: any, options?: any): Promise { 24 | return Promise.resolve([]); 25 | } 26 | 27 | applyTemplateVariables(query: InfluxQuery): InfluxQuery { 28 | if (!query || !query.query) { 29 | return query; 30 | } 31 | 32 | return { 33 | ...query, 34 | query: getTemplateSrv().replace(query.query), 35 | }; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/components/ConfigEditor.tsx: -------------------------------------------------------------------------------- 1 | import React, { PureComponent, ChangeEvent } from 'react'; 2 | import { 3 | DataSourcePluginOptionsEditorProps, 4 | onUpdateDatasourceJsonDataOption, 5 | onUpdateDatasourceResetOption, 6 | onUpdateDatasourceSecureJsonDataOption, 7 | } from '@grafana/data'; 8 | import { InfluxOptions, InfluxSecureJsonData } from '../types'; 9 | import { DataSourceHttpSettings, InlineFormLabel, Input, Button } from '@grafana/ui'; 10 | 11 | export type Props = DataSourcePluginOptionsEditorProps; 12 | 13 | export class ConfigEditor extends PureComponent { 14 | onURLChange = (event: ChangeEvent) => { 15 | this.props.onOptionsChange({ 16 | ...this.props.options, 17 | url: event.target.value, 18 | }); 19 | }; 20 | 21 | onMaxSeriesChange = (event: ChangeEvent) => { 22 | const { options } = this.props; 23 | this.props.onOptionsChange({ 24 | ...options, 25 | jsonData: { 26 | ...options.jsonData, 27 | maxSeries: +event.target.value, 28 | }, 29 | }); 30 | }; 31 | 32 | render() { 33 | const { options, onOptionsChange } = this.props; 34 | const secureJsonData = options.secureJsonData || {}; 35 | const jsonData = options.jsonData || {}; 36 | const tokenConfigured = options?.secureJsonFields?.token === true; 37 | 38 | return ( 39 | <> 40 | {false && ( 41 | 47 | )} 48 | 49 |
50 |
51 | 52 | URL 53 | 54 |
55 | 56 |
57 |
58 |
59 | 60 |
61 |
62 | 63 | Organization 64 | 65 |
66 | 72 |
73 |
74 |
75 |
76 |
77 | 78 | Default Bucket 79 | 80 |
81 | 87 |
88 |
89 |
90 |
91 |
92 | 93 | Max Series 94 | 95 |
96 | 102 |
103 |
104 |
105 |
106 |
107 | 108 | Token 109 | 110 |
111 | {tokenConfigured ? ( 112 | 113 | ) : ( 114 | 121 | )} 122 |
123 | {tokenConfigured && ( 124 |
125 |
126 | 133 |
134 |
135 | )} 136 |
137 |
138 | 139 | ); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/components/QueryEditor.test.tsx: -------------------------------------------------------------------------------- 1 | describe('QueryEditor', () => { 2 | it('should extract id from URL', () => { 3 | expect(1).toBe(1); 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /src/components/QueryEditor.tsx: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { QueryEditorProps, SelectableValue } from '@grafana/data'; 3 | import Editor from '@monaco-editor/react'; 4 | import { DataSource } from '../DataSource'; 5 | import { InfluxQuery, InfluxOptions } from '../types'; 6 | import { Select, LinkButton, InlineFormLabel } from '@grafana/ui'; 7 | // import('@influxdata/flux-lsp-browser').then(({ Server }) => { 8 | // const srv = new Server(false, true); 9 | // console.log('lsp server ready', srv); 10 | // }); 11 | 12 | type Props = QueryEditorProps; 13 | 14 | const samples: Array> = [ 15 | { label: 'Show buckets', description: 'List the avaliable buckets (table)', value: 'buckets()' }, 16 | { 17 | label: 'Simple query', 18 | description: 'filter by measurment and field', 19 | value: `from(bucket: "db/rp") 20 | |> range(start: v.timeRangeStart, stop:v.timeRangeStop) 21 | |> filter(fn: (r) => 22 | r._measurement == "example-measurement" and 23 | r._field == "example-field" 24 | )`, 25 | }, 26 | { 27 | label: 'Grouped Query', 28 | description: 'Group by (min/max/sum/median)', 29 | value: `// v.windowPeriod is a variable referring to the current optimized window period (currently: $interval) 30 | from(bucket: v.bucket) 31 | |> range(start: v.timeRangeStart, stop: v.timeRangeStop) 32 | |> filter(fn: (r) => r["_measurement"] == "measurement1" or r["_measurement"] =~ /^.*?regex.*$/) 33 | |> filter(fn: (r) => r["_field"] == "field2" or r["_field"] =~ /^.*?regex.*$/) 34 | |> aggregateWindow(every: v.windowPeriod, fn: mean|median|max|count|derivative|sum) 35 | |> yield(name: "some-name")`, 36 | }, 37 | { 38 | label: 'Filter by value', 39 | description: 'Results between a min/max', 40 | value: `// v.bucket, v.timeRangeStart, and v.timeRange stop are all variables supported by the flux plugin and influxdb 41 | from(bucket: v.bucket) 42 | |> range(start: v.timeRangeStart, stop: v.timeRangeStop) 43 | |> filter(fn: (r) => r["_value"] >= 10 and r["_value"] <= 20)`, 44 | }, 45 | { 46 | label: 'Schema Exploration: (measurements)', 47 | description: 'Get a list of measurement using flux', 48 | value: `import "influxdata/influxdb/v1" 49 | v1.measurements(bucket: v.bucket)`, 50 | }, 51 | { 52 | label: 'Schema Exploration: (fields)', 53 | description: 'Return every possible key in a single table', 54 | value: `from(bucket: v.bucket) 55 | |> range(start: -30m) 56 | |> keys() 57 | |> keep(columns: ["_value"]) 58 | |> group() 59 | |> distinct()`, 60 | }, 61 | { 62 | label: 'Schema Exploration: (tag keys)', 63 | description: 'Get a list of tag keys using flux', 64 | value: `import "influxdata/influxdb/v1" 65 | v1.tagKeys(bucket: v.bucket)`, 66 | }, 67 | { 68 | label: 'Schema Exploration: (tag values)', 69 | description: 'Get a list of tag values using flux', 70 | value: `import "influxdata/influxdb/v1" 71 | 72 | v1.tagValues( 73 | bucket: v.bucket, 74 | tag: "host", 75 | predicate: (r) => true, 76 | start: -1d 77 | )`, 78 | }, 79 | ]; 80 | 81 | export class QueryEditor extends PureComponent { 82 | getEditorValue: any | undefined; 83 | 84 | onRawQueryChange = () => { 85 | this.props.onChange({ 86 | ...this.props.query, 87 | query: this.getEditorValue(), 88 | }); 89 | this.props.onRunQuery(); 90 | }; 91 | 92 | onEditorDidMount = (getEditorValue: any) => { 93 | this.getEditorValue = getEditorValue; 94 | }; 95 | 96 | onSampleChange = (val: SelectableValue) => { 97 | this.props.onChange({ 98 | ...this.props.query, 99 | query: val.value!, 100 | }); 101 | this.props.onRunQuery(); 102 | }; 103 | 104 | render() { 105 | const { query } = this.props; 106 | return ( 107 |
108 |
109 | 116 |
117 |
118 |
119 | 120 | Help 121 | 122 |