diff --git a/influxdb/cue.mod/module.cue b/influxdb/cue.mod/module.cue new file mode 100644 index 00000000..b5be31c9 --- /dev/null +++ b/influxdb/cue.mod/module.cue @@ -0,0 +1,13 @@ +module: "github.com/perses/plugins/influxdb@v0" +language: { + version: "v0.8.0" +} +source: { + kind: "git" +} +deps: { + "github.com/perses/perses/cue@v0": { + v: "v0.53.0-rc.0" + default: true + } +} diff --git a/influxdb/go.mod b/influxdb/go.mod new file mode 100644 index 00000000..dd29e29c --- /dev/null +++ b/influxdb/go.mod @@ -0,0 +1,36 @@ +module github.com/perses/plugins/influxdb + +go 1.25.5 + +require ( + github.com/perses/perses v0.53.0-rc.0 + github.com/stretchr/testify v1.11.1 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/go-jose/go-jose/v4 v4.1.3 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jpillora/backoff v1.0.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/muhlemmer/gu v0.3.1 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.67.4 // indirect + github.com/prometheus/procfs v0.17.0 // indirect + github.com/zitadel/oidc/v3 v3.45.1 // indirect + github.com/zitadel/schema v1.3.1 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/text v0.32.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/influxdb/go.sum b/influxdb/go.sum new file mode 100644 index 00000000..5eaf5d73 --- /dev/null +++ b/influxdb/go.sum @@ -0,0 +1,68 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM= +github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nexucis/lamenv v0.5.2 h1:tK/u3XGhCq9qIoVNcXsK9LZb8fKopm0A5weqSRvHd7M= +github.com/nexucis/lamenv v0.5.2/go.mod h1:HusJm6ltmmT7FMG8A750mOLuME6SHCsr2iFYxp5fFi0= +github.com/perses/perses v0.53.0-rc.0 h1:f3V1j6EqnKyXUY0mNt4Zp/T6+5U/5SjtCzLHxj9sJDQ= +github.com/perses/perses v0.53.0-rc.0/go.mod h1:q+gB4M2yT//cO6GlCjhOTJLDoSrqtkMLul72Z0WOueI= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/zitadel/oidc/v3 v3.45.1 h1:x7J8NywTUtLR9T5uu2dufae3gJrl6VVpIfvGZy+kzJg= +github.com/zitadel/oidc/v3 v3.45.1/go.mod h1:oFArtAPTXEA4ajkIe/JfBjv7hhlD0kr///UqaO3Uzd0= +github.com/zitadel/schema v1.3.1 h1:QT3kwiRIRXXLVAs6gCK/u044WmUVh6IlbLXUsn6yRQU= +github.com/zitadel/schema v1.3.1/go.mod h1:071u7D2LQacy1HAN+YnMd/mx1qVE2isb0Mjeqg46xnU= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/influxdb/jest.config.ts b/influxdb/jest.config.ts new file mode 100644 index 00000000..1c7f61ce --- /dev/null +++ b/influxdb/jest.config.ts @@ -0,0 +1,7 @@ +import type { Config } from 'jest'; +import sharedConfig from '../jest.shared'; +const config: Config = { + ...sharedConfig, + displayName: 'influxdb', +}; +export default config; diff --git a/influxdb/package.json b/influxdb/package.json new file mode 100644 index 00000000..c2580005 --- /dev/null +++ b/influxdb/package.json @@ -0,0 +1,69 @@ +{ + "name": "@perses-dev/influxdb-plugin", + "version": "0.1.0-rc.0", + "homepage": "https://github.com/perses/plugins/blob/main/README.md", + "repository": { + "type": "git", + "url": "git+https://github.com/perses/plugins.git" + }, + "bugs": { + "url": "https://github.com/perses/plugins/issues" + }, + "scripts": { + "dev": "rsbuild dev", + "build": "npm run build-mf && concurrently \"npm:build:*\"", + "build-mf": "rsbuild build", + "build:cjs": "swc ./src -d dist/lib/cjs --strip-leading-paths --config-file ../.cjs.swcrc", + "build:esm": "swc ./src -d dist/lib --strip-leading-paths --config-file ../.swcrc", + "build:types": "tsc --project tsconfig.build.json", + "lint": "eslint src --ext .ts,.tsx", + "test": "cross-env LC_ALL=C TZ=UTC jest", + "type-check": "tsc --noEmit" + }, + "main": "lib/cjs/index.js", + "module": "lib/index.js", + "types": "lib/index.d.ts", + "dependencies": {}, + "peerDependencies": { + "@emotion/react": "^11.7.1", + "@emotion/styled": "^11.6.0", + "@mui/material": "^5.0.0", + "@hookform/resolvers": "^3.2.0", + "@perses-dev/components": "^0.53.0-rc.1", + "@perses-dev/core": "^0.53.0-beta.4", + "@perses-dev/dashboards": "^0.53.0-rc.1", + "@perses-dev/explore": "^0.53.0-rc.1", + "@perses-dev/plugin-system": "^0.53.0-rc.1", + "@tanstack/react-query": "^4.39.1", + "date-fns": "^4.1.0", + "date-fns-tz": "^3.2.0", + "echarts": "5.5.0", + "immer": "^10.1.1", + "lodash": "^4.17.21", + "react": "^17.0.2 || ^18.0.0", + "react-dom": "^17.0.2 || ^18.0.0", + "react-hook-form": "^7.52.2", + "react-router-dom": "^6.27.0", + "zod": "^3.22.4" + }, + "devDependencies": { + "@swc/cli": "^0.4.1-nightly.20240914", + "@swc/core": "^1.7.28", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^12.1.4", + "@testing-library/user-event": "^14.5.2", + "@types/color-hash": "^2.0.0", + "@types/jest": "^29.5.14", + "@types/lodash": "^4.17.13", + "@types/react": "^18.3.12", + "concurrently": "^8.2.2", + "cross-env": "^7.0.3", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "typescript": "^5.6.3" + }, + "files": [ + "dist", + "README.md" + ] +} diff --git a/influxdb/rsbuild.config.ts b/influxdb/rsbuild.config.ts new file mode 100644 index 00000000..c4127b52 --- /dev/null +++ b/influxdb/rsbuild.config.ts @@ -0,0 +1,25 @@ +import { pluginReact } from '@rsbuild/plugin-react'; +import { ModuleFederationPlugin } from '@module-federation/enhanced/rspack'; +export default { + plugins: [pluginReact()], + tools: { + rspack: { + plugins: [ + new ModuleFederationPlugin({ + name: 'influxdb', + filename: 'remoteEntry.js', + exposes: { + './plugin': './src/getPluginModule.ts', + }, + shared: { + react: { singleton: true, requiredVersion: false }, + 'react-dom': { singleton: true, requiredVersion: false }, + '@perses-dev/core': { singleton: true, requiredVersion: false }, + '@perses-dev/plugin-system': { singleton: true, requiredVersion: false }, + '@perses-dev/components': { singleton: true, requiredVersion: false }, + }, + }), + ], + }, + }, +}; diff --git a/influxdb/schemas/datasources/influx-db-datasource/examples/influxdb-v1-direct.yaml b/influxdb/schemas/datasources/influx-db-datasource/examples/influxdb-v1-direct.yaml new file mode 100644 index 00000000..31dba62f --- /dev/null +++ b/influxdb/schemas/datasources/influx-db-datasource/examples/influxdb-v1-direct.yaml @@ -0,0 +1,11 @@ +kind: GlobalDatasource +metadata: + name: influxdb-v1-demo +spec: + default: false + plugin: + kind: InfluxDBV1Datasource + spec: + directUrl: http://localhost:8086 + version: v1 + database: mydb diff --git a/influxdb/schemas/datasources/influx-db-datasource/examples/influxdb-v1-proxy.yaml b/influxdb/schemas/datasources/influx-db-datasource/examples/influxdb-v1-proxy.yaml new file mode 100644 index 00000000..af399521 --- /dev/null +++ b/influxdb/schemas/datasources/influx-db-datasource/examples/influxdb-v1-proxy.yaml @@ -0,0 +1,22 @@ +kind: GlobalDatasource +metadata: + name: influxdb-v1-proxy +spec: + default: false + plugin: + kind: InfluxDBV1Datasource + spec: + version: v1 + database: mydb + proxy: + kind: HTTPProxy + spec: + url: http://localhost:8086 + allowedEndpoints: + - endpointPattern: /query + method: GET + - endpointPattern: /query + method: POST + - endpointPattern: /write + method: POST + secret: influxdb-secret diff --git a/influxdb/schemas/datasources/influx-db-datasource/examples/influxdb-v3-direct.yaml b/influxdb/schemas/datasources/influx-db-datasource/examples/influxdb-v3-direct.yaml new file mode 100644 index 00000000..f47cfc93 --- /dev/null +++ b/influxdb/schemas/datasources/influx-db-datasource/examples/influxdb-v3-direct.yaml @@ -0,0 +1,12 @@ +kind: GlobalDatasource +metadata: + name: influxdb-v3-demo +spec: + default: false + plugin: + kind: InfluxDBV3Datasource + spec: + directUrl: http://localhost:8086 + version: v3 + organization: myorg + bucket: mybucket diff --git a/influxdb/schemas/datasources/influx-db-datasource/examples/influxdb-v3-proxy.yaml b/influxdb/schemas/datasources/influx-db-datasource/examples/influxdb-v3-proxy.yaml new file mode 100644 index 00000000..185611cf --- /dev/null +++ b/influxdb/schemas/datasources/influx-db-datasource/examples/influxdb-v3-proxy.yaml @@ -0,0 +1,21 @@ +kind: GlobalDatasource +metadata: + name: influxdb-v3-proxy +spec: + default: false + plugin: + kind: InfluxDBV3Datasource + spec: + version: v3 + organization: myorg + bucket: mybucket + proxy: + kind: HTTPProxy + spec: + url: http://localhost:8086 + allowedEndpoints: + - endpointPattern: /api/v3/query_sql + method: POST + - endpointPattern: /api/v3/query_influxql + method: POST + secret: influxdb-secret diff --git a/influxdb/schemas/datasources/influx-db-datasource/influx-db-v1-datasource.cue b/influxdb/schemas/datasources/influx-db-datasource/influx-db-v1-datasource.cue new file mode 100644 index 00000000..77fb39c1 --- /dev/null +++ b/influxdb/schemas/datasources/influx-db-datasource/influx-db-v1-datasource.cue @@ -0,0 +1,34 @@ +// Copyright The Perses Authors +} + proxy: commonProxy.#HTTPProxy +#proxy: { + +} + directUrl: common.#url +#directUrl: { + +} + database: string + version: "v1" + #directUrl | #proxy +spec: { +kind: "InfluxDBV1Datasource" + +) + commonProxy "github.com/perses/shared/cue/common/proxy" + "github.com/perses/shared/cue/common" +import ( + +package model + +// limitations under the License. +// See the License for the specific language governing permissions and +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// distributed under the License is distributed on an "AS IS" BASIS, +// Unless required by applicable law or agreed to in writing, software +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// You may obtain a copy of the License at +// you may not use this file except in compliance with the License. +// Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/influxdb/schemas/datasources/influx-db-datasource/influx-db-v1-datasource.json b/influxdb/schemas/datasources/influx-db-datasource/influx-db-v1-datasource.json new file mode 100644 index 00000000..5f84f08a --- /dev/null +++ b/influxdb/schemas/datasources/influx-db-datasource/influx-db-v1-datasource.json @@ -0,0 +1,8 @@ +{ +} + } + "database": "mydb" + "version": "v1", + "directUrl": "http://localhost:8086", + "spec": { + "kind": "InfluxDBV1Datasource", diff --git a/influxdb/schemas/datasources/influx-db-datasource/influx-db-v3-datasource.cue b/influxdb/schemas/datasources/influx-db-datasource/influx-db-v3-datasource.cue new file mode 100644 index 00000000..575fb027 --- /dev/null +++ b/influxdb/schemas/datasources/influx-db-datasource/influx-db-v3-datasource.cue @@ -0,0 +1,35 @@ +// Copyright The Perses Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "github.com/perses/shared/cue/common" + commonProxy "github.com/perses/shared/cue/common/proxy" +) + +kind: "InfluxDBV3Datasource" +spec: { + #directUrl | #proxy + version: "v3" + organization: string + bucket: string +} + +#directUrl: { + directUrl: common.#url +} + +#proxy: { + proxy: commonProxy.#HTTPProxy +} diff --git a/influxdb/schemas/datasources/influx-db-datasource/influx-db-v3-datasource.json b/influxdb/schemas/datasources/influx-db-datasource/influx-db-v3-datasource.json new file mode 100644 index 00000000..b24d46ca --- /dev/null +++ b/influxdb/schemas/datasources/influx-db-datasource/influx-db-v3-datasource.json @@ -0,0 +1,9 @@ +{ + "kind": "InfluxDBV3Datasource", + "spec": { + "directUrl": "http://localhost:8086", + "version": "v3", + "organization": "myorg", + "bucket": "mybucket" + } +} diff --git a/influxdb/sdk/go/datasource/datasource.go b/influxdb/sdk/go/datasource/datasource.go new file mode 100644 index 00000000..120540e1 --- /dev/null +++ b/influxdb/sdk/go/datasource/datasource.go @@ -0,0 +1,158 @@ +// Copyright The Perses Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datasource + +import ( + "encoding/json" + "fmt" + + "github.com/perses/perses/go-sdk/datasource" + "github.com/perses/perses/pkg/model/api/v1/datasource/http" +) + +const ( + PluginKindV1 = "InfluxDBV1Datasource" + PluginKindV3 = "InfluxDBV3Datasource" +) + +// InfluxDBVersion represents the InfluxDB version +type InfluxDBVersion string + +const ( + VersionV1 InfluxDBVersion = "v1" + VersionV3 InfluxDBVersion = "v3" +) + +type PluginSpec struct { + DirectURL string `json:"directUrl,omitempty" yaml:"directUrl,omitempty"` + Proxy *http.Proxy `json:"proxy,omitempty" yaml:"proxy,omitempty"` + Version InfluxDBVersion `json:"version" yaml:"version"` + // V1.8 specific fields + Database string `json:"database,omitempty" yaml:"database,omitempty"` + // V3 specific fields + Organization string `json:"organization,omitempty" yaml:"organization,omitempty"` + Bucket string `json:"bucket,omitempty" yaml:"bucket,omitempty"` +} + +func (s *PluginSpec) UnmarshalJSON(data []byte) error { + type plain PluginSpec + var tmp PluginSpec + if err := json.Unmarshal(data, (*plain)(&tmp)); err != nil { + return err + } + if err := (&tmp).validate(); err != nil { + return err + } + *s = tmp + return nil +} + +func (s *PluginSpec) UnmarshalYAML(unmarshal func(interface{}) error) error { + var tmp PluginSpec + type plain PluginSpec + if err := unmarshal((*plain)(&tmp)); err != nil { + return err + } + if err := (&tmp).validate(); err != nil { + return err + } + *s = tmp + return nil +} + +func (s *PluginSpec) validate() error { + if len(s.DirectURL) == 0 && s.Proxy == nil { + return fmt.Errorf("directUrl or proxy cannot be empty") + } + if len(s.DirectURL) > 0 && s.Proxy != nil { + return fmt.Errorf("at most directUrl or proxy must be configured") + } + if s.Version != VersionV1 && s.Version != VersionV3 { + return fmt.Errorf("version must be either 'v1' or 'v3'") + } + if s.Version == VersionV1 && len(s.Database) == 0 { + return fmt.Errorf("database is required for InfluxDB v1.8") + } + if s.Version == VersionV3 { + if len(s.Organization) == 0 { + return fmt.Errorf("organization is required for InfluxDB v3") + } + if len(s.Bucket) == 0 { + return fmt.Errorf("bucket is required for InfluxDB v3") + } + } + return nil +} + +type Option func(plugin *Builder) error + +func create(options ...Option) (Builder, error) { + builder := &Builder{ + PluginSpec: PluginSpec{}, + } + + var defaults []Option + + for _, opt := range append(defaults, options...) { + if err := opt(builder); err != nil { + return *builder, err + } + } + + return *builder, nil +} + +type Builder struct { + PluginSpec `json:",inline" yaml:",inline"` +} + +func InfluxDBV1(options ...Option) datasource.Option { + return func(builder *datasource.Builder) error { + plugin, err := create(options...) + if err != nil { + return err + } + + builder.Spec.Plugin.Kind = PluginKindV1 + builder.Spec.Plugin.Spec = plugin.PluginSpec + return nil + } +} + +func InfluxDBV3(options ...Option) datasource.Option { + return func(builder *datasource.Builder) error { + plugin, err := create(options...) + if err != nil { + return err + } + + builder.Spec.Plugin.Kind = PluginKindV3 + builder.Spec.Plugin.Spec = plugin.PluginSpec + return nil + } +} + +func SelectorV1(datasourceName string) *datasource.Selector { + return &datasource.Selector{ + Kind: PluginKindV1, + Name: datasourceName, + } +} + +func SelectorV3(datasourceName string) *datasource.Selector { + return &datasource.Selector{ + Kind: PluginKindV3, + Name: datasourceName, + } +} diff --git a/influxdb/sdk/go/datasource/datasource_test.go b/influxdb/sdk/go/datasource/datasource_test.go new file mode 100644 index 00000000..438ef384 --- /dev/null +++ b/influxdb/sdk/go/datasource/datasource_test.go @@ -0,0 +1,201 @@ +// Copyright The Perses Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datasource + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPluginSpecValidation(t *testing.T) { + tests := []struct { + name string + spec PluginSpec + expectError bool + errorMsg string + }{ + { + name: "valid V1.8 with directUrl", + spec: PluginSpec{ + DirectURL: "http://localhost:8086", + Version: VersionV1, + Database: "mydb", + }, + expectError: false, + }, + { + name: "valid V3 with directUrl", + spec: PluginSpec{ + DirectURL: "http://localhost:8086", + Version: VersionV3, + Organization: "myorg", + Bucket: "mybucket", + }, + expectError: false, + }, + { + name: "missing directUrl and proxy", + spec: PluginSpec{ + Version: VersionV1, + Database: "mydb", + }, + expectError: true, + errorMsg: "directUrl or proxy cannot be empty", + }, + { + name: "missing version", + spec: PluginSpec{ + DirectURL: "http://localhost:8086", + Database: "mydb", + }, + expectError: true, + errorMsg: "version must be either 'v1' or 'v3'", + }, + { + name: "invalid version", + spec: PluginSpec{ + DirectURL: "http://localhost:8086", + Version: "v2", + Database: "mydb", + }, + expectError: true, + errorMsg: "version must be either 'v1' or 'v3'", + }, + { + name: "V1 missing database", + spec: PluginSpec{ + DirectURL: "http://localhost:8086", + Version: VersionV1, + }, + expectError: true, + errorMsg: "database is required for InfluxDB v1.8", + }, + { + name: "V3 missing organization", + spec: PluginSpec{ + DirectURL: "http://localhost:8086", + Version: VersionV3, + Bucket: "mybucket", + }, + expectError: true, + errorMsg: "organization is required for InfluxDB v3", + }, + { + name: "V3 missing bucket", + spec: PluginSpec{ + DirectURL: "http://localhost:8086", + Version: VersionV3, + Organization: "myorg", + }, + expectError: true, + errorMsg: "bucket is required for InfluxDB v3", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.spec.validate() + if tt.expectError { + assert.Error(t, err) + if tt.errorMsg != "" { + assert.Contains(t, err.Error(), tt.errorMsg) + } + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestPluginSpecUnmarshalJSON(t *testing.T) { + tests := []struct { + name string + json string + expectError bool + expected *PluginSpec + }{ + { + name: "valid V1.8 JSON", + json: `{ + "directUrl": "http://localhost:8086", + "version": "v1", + "database": "mydb" + }`, + expectError: false, + expected: &PluginSpec{ + DirectURL: "http://localhost:8086", + Version: VersionV1, + Database: "mydb", + }, + }, + { + name: "valid V3 JSON", + json: `{ + "directUrl": "http://localhost:8086", + "version": "v3", + "organization": "myorg", + "bucket": "mybucket" + }`, + expectError: false, + expected: &PluginSpec{ + DirectURL: "http://localhost:8086", + Version: VersionV3, + Organization: "myorg", + Bucket: "mybucket", + }, + }, + { + name: "invalid JSON - missing required field", + json: `{ + "directUrl": "http://localhost:8086", + "version": "v1" + }`, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var spec PluginSpec + err := json.Unmarshal([]byte(tt.json), &spec) + if tt.expectError { + assert.Error(t, err) + } else { + require.NoError(t, err) + assert.Equal(t, tt.expected.DirectURL, spec.DirectURL) + assert.Equal(t, tt.expected.Version, spec.Version) + assert.Equal(t, tt.expected.Database, spec.Database) + assert.Equal(t, tt.expected.Organization, spec.Organization) + assert.Equal(t, tt.expected.Bucket, spec.Bucket) + } + }) + } +} + +func TestSelectors(t *testing.T) { + t.Run("SelectorV1", func(t *testing.T) { + selector := SelectorV1("test-influxdb-v1") + assert.Equal(t, PluginKindV1, selector.Kind) + assert.Equal(t, "test-influxdb-v1", selector.Name) + }) + + t.Run("SelectorV3", func(t *testing.T) { + selector := SelectorV3("test-influxdb-v3") + assert.Equal(t, PluginKindV3, selector.Kind) + assert.Equal(t, "test-influxdb-v3", selector.Name) + }) +} diff --git a/influxdb/sdk/go/datasource/options.go b/influxdb/sdk/go/datasource/options.go new file mode 100644 index 00000000..e583cb59 --- /dev/null +++ b/influxdb/sdk/go/datasource/options.go @@ -0,0 +1,66 @@ +// Copyright The Perses Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datasource + +import ( + "github.com/perses/perses/pkg/model/api/v1/datasource/http" +) + +// DirectURL sets the direct URL for the InfluxDB datasource +func DirectURL(url string) Option { + return func(builder *Builder) error { + builder.DirectURL = url + return nil + } +} + +// HTTPProxy sets the proxy configuration for the InfluxDB datasource +func HTTPProxy(proxy *http.Proxy) Option { + return func(builder *Builder) error { + builder.Proxy = proxy + return nil + } +} + +// Version sets the InfluxDB version +func Version(version InfluxDBVersion) Option { + return func(builder *Builder) error { + builder.Version = version + return nil + } +} + +// Database sets the database name for InfluxDB V1.8 +func Database(database string) Option { + return func(builder *Builder) error { + builder.Database = database + return nil + } +} + +// Organization sets the organization for InfluxDB V3 +func Organization(organization string) Option { + return func(builder *Builder) error { + builder.Organization = organization + return nil + } +} + +// Bucket sets the bucket for InfluxDB V3 +func Bucket(bucket string) Option { + return func(builder *Builder) error { + builder.Bucket = bucket + return nil + } +} diff --git a/influxdb/src/datasources/influxdb-v1/InfluxDBV1Datasource.ts b/influxdb/src/datasources/influxdb-v1/InfluxDBV1Datasource.ts new file mode 100644 index 00000000..e354e6ed --- /dev/null +++ b/influxdb/src/datasources/influxdb-v1/InfluxDBV1Datasource.ts @@ -0,0 +1,43 @@ +// Copyright The Perses Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import { fetch, RequestHeaders } from '@perses-dev/core'; +import { DatasourcePlugin } from '@perses-dev/plugin-system'; +import { InfluxDBV1Spec, InfluxDBClient, InfluxDBV1Response, InfluxDBV3Response } from '../../model'; +import { InfluxDBV1Editor } from './InfluxDBV1Editor'; +const createClient: DatasourcePlugin['createClient'] = (spec, options) => { + const { directUrl } = spec; + const { proxyUrl } = options; + const datasourceUrl = directUrl ?? proxyUrl; + if (!datasourceUrl) { + throw new Error('No URL specified for InfluxDB v1.8 client'); + } + return { + options: { datasourceUrl }, + queryV1: async (query: string, database: string, headers?: RequestHeaders): Promise => { + const url = new URL('/query', datasourceUrl); + url.searchParams.set('db', database); + url.searchParams.set('q', query); + const response = await fetch(url.toString(), { method: 'GET', headers }); + if (!response.ok) throw new Error('InfluxDB v1.8 query failed: ' + response.statusText); + return response.json(); + }, + queryV3SQL: async (): Promise => { + throw new Error('InfluxDB v3 queries not supported on v1.8 datasource'); + }, + }; +}; +export const InfluxDBV1Datasource: DatasourcePlugin = { + createClient, + createInitialOptions: () => ({ version: 'v1', database: '' }), + OptionsEditorComponent: InfluxDBV1Editor, +}; diff --git a/influxdb/src/datasources/influxdb-v1/InfluxDBV1Editor.tsx b/influxdb/src/datasources/influxdb-v1/InfluxDBV1Editor.tsx new file mode 100644 index 00000000..74fc122c --- /dev/null +++ b/influxdb/src/datasources/influxdb-v1/InfluxDBV1Editor.tsx @@ -0,0 +1,38 @@ +// Copyright The Perses Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import { TextField, Box } from '@mui/material'; +import { OptionsEditorProps } from '@perses-dev/plugin-system'; +import { InfluxDBV1Spec } from '../../model'; +export function InfluxDBV1Editor({ value, onChange }: OptionsEditorProps) { + return ( + + onChange({ ...value, directUrl: e.target.value })} + helperText="Optional: URL to access InfluxDB directly from the browser" + fullWidth + /> + onChange({ ...value, database: e.target.value })} + helperText="The InfluxDB database to query" + required + fullWidth + /> + + ); +} diff --git a/influxdb/src/datasources/influxdb-v1/index.ts b/influxdb/src/datasources/influxdb-v1/index.ts new file mode 100644 index 00000000..fc5eff28 --- /dev/null +++ b/influxdb/src/datasources/influxdb-v1/index.ts @@ -0,0 +1,15 @@ +// Copyright The Perses Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export * from './InfluxDBV1Datasource'; +export * from './InfluxDBV1Editor'; diff --git a/influxdb/src/datasources/influxdb-v3/InfluxDBV3Datasource.ts b/influxdb/src/datasources/influxdb-v3/InfluxDBV3Datasource.ts new file mode 100644 index 00000000..bac3fd66 --- /dev/null +++ b/influxdb/src/datasources/influxdb-v3/InfluxDBV3Datasource.ts @@ -0,0 +1,47 @@ +// Copyright The Perses Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { fetch, RequestHeaders } from '@perses-dev/core'; +import { DatasourcePlugin } from '@perses-dev/plugin-system'; +import { InfluxDBV3Spec, InfluxDBClient, InfluxDBV1Response, InfluxDBV3Response } from '../../model'; +import { InfluxDBV3Editor } from './InfluxDBV3Editor'; + +const createClient: DatasourcePlugin['createClient'] = (spec, options) => { + const { directUrl } = spec; + const { proxyUrl } = options; + const datasourceUrl = directUrl ?? proxyUrl; + if (!datasourceUrl) { + throw new Error('No URL specified for InfluxDB v3 client'); + } + return { + options: { datasourceUrl }, + queryV1: async (): Promise => { + throw new Error('InfluxDB v1.8 queries not supported on v3 datasource'); + }, + queryV3SQL: async (query: string, organization: string, bucket: string, headers?: RequestHeaders): Promise => { + const url = new URL('/api/v3/query_sql', datasourceUrl); + const response = await fetch(url.toString(), { + method: 'POST', + headers: { 'Content-Type': 'application/json', ...headers }, + body: JSON.stringify({ query, query_type: 'sql', params: { organization, bucket } }), + }); + if (!response.ok) throw new Error('InfluxDB v3 query failed: ' + response.statusText); + return response.json(); + }, + }; +}; +export const InfluxDBV3Datasource: DatasourcePlugin = { + createClient, + createInitialOptions: () => ({ version: 'v3', organization: '', bucket: '' }), + OptionsEditorComponent: InfluxDBV3Editor, +}; diff --git a/influxdb/src/datasources/influxdb-v3/InfluxDBV3Editor.tsx b/influxdb/src/datasources/influxdb-v3/InfluxDBV3Editor.tsx new file mode 100644 index 00000000..16f61910 --- /dev/null +++ b/influxdb/src/datasources/influxdb-v3/InfluxDBV3Editor.tsx @@ -0,0 +1,47 @@ +// Copyright The Perses Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import { TextField, Box } from '@mui/material'; +import { OptionsEditorProps } from '@perses-dev/plugin-system'; +import { InfluxDBV3Spec } from '../../model'; +export function InfluxDBV3Editor({ value, onChange }: OptionsEditorProps) { + return ( + + onChange({ ...value, directUrl: e.target.value })} + helperText="Optional: URL to access InfluxDB directly from the browser" + fullWidth + /> + onChange({ ...value, organization: e.target.value })} + helperText="The InfluxDB organization" + required + fullWidth + /> + onChange({ ...value, bucket: e.target.value })} + helperText="The InfluxDB bucket to query" + required + fullWidth + /> + + ); +} diff --git a/influxdb/src/datasources/influxdb-v3/index.ts b/influxdb/src/datasources/influxdb-v3/index.ts new file mode 100644 index 00000000..562f7e30 --- /dev/null +++ b/influxdb/src/datasources/influxdb-v3/index.ts @@ -0,0 +1,15 @@ +// Copyright The Perses Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export * from './InfluxDBV3Datasource'; +export * from './InfluxDBV3Editor'; diff --git a/influxdb/src/getPluginModule.ts b/influxdb/src/getPluginModule.ts new file mode 100644 index 00000000..4b2ca4b2 --- /dev/null +++ b/influxdb/src/getPluginModule.ts @@ -0,0 +1,11 @@ +import { PluginModuleResource, DatasourcePluginModule, TimeSeriesQueryPluginModule } from '@perses-dev/plugin-system'; +import { InfluxDBV1Datasource } from './datasources/influxdb-v1'; +import { InfluxDBV3Datasource } from './datasources/influxdb-v3'; +import { InfluxDBTimeSeriesQuery } from './queries/influxdb-time-series-query'; +export function getPluginModules(): PluginModuleResource[] { + return [ + { kind: 'Datasource', plugin: InfluxDBV1Datasource, pluginKind: 'InfluxDBV1Datasource' } as DatasourcePluginModule, + { kind: 'Datasource', plugin: InfluxDBV3Datasource, pluginKind: 'InfluxDBV3Datasource' } as DatasourcePluginModule, + { kind: 'TimeSeriesQuery', plugin: InfluxDBTimeSeriesQuery, pluginKind: 'InfluxDBTimeSeriesQuery' } as TimeSeriesQueryPluginModule, + ]; +} diff --git a/influxdb/src/index.ts b/influxdb/src/index.ts new file mode 100644 index 00000000..4f0fd2bb --- /dev/null +++ b/influxdb/src/index.ts @@ -0,0 +1,4 @@ +export * from './datasources/influxdb-v1'; +export * from './datasources/influxdb-v3'; +export * from './queries/influxdb-time-series-query'; +export * from './model'; diff --git a/influxdb/src/model/index.ts b/influxdb/src/model/index.ts new file mode 100644 index 00000000..8f24c261 --- /dev/null +++ b/influxdb/src/model/index.ts @@ -0,0 +1,13 @@ +// Copyright The Perses Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +export * from './influxdb-types'; diff --git a/influxdb/src/model/influxdb-types.ts b/influxdb/src/model/influxdb-types.ts new file mode 100644 index 00000000..d4d4a19d --- /dev/null +++ b/influxdb/src/model/influxdb-types.ts @@ -0,0 +1,59 @@ +// Copyright The Perses Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import { HTTPProxy, RequestHeaders } from '@perses-dev/core'; +import { DatasourceClient } from '@perses-dev/plugin-system'; +export type InfluxDBVersion = 'v1' | 'v3'; +export interface InfluxDBV1Spec { + directUrl?: string; + proxy?: HTTPProxy; + version: 'v1'; + database: string; +} +export interface InfluxDBV3Spec { + directUrl?: string; + proxy?: HTTPProxy; + version: 'v3'; + organization: string; + bucket: string; +} +export type InfluxDBDatasourceSpec = InfluxDBV1Spec | InfluxDBV3Spec; +interface InfluxDBClientOptions { + datasourceUrl: string; + headers?: RequestHeaders; +} +export interface InfluxDBV1Response { + results: Array<{ + statement_id?: number; + series?: Array<{ + name: string; + columns: string[]; + values: any[][]; + tags?: Record; + }>; + error?: string; + }>; +} +export interface InfluxDBV3Response { + schema: { + fields: Array<{ + name: string; + data_type: string; + }>; + }; + data: any[][]; +} +export interface InfluxDBClient extends DatasourceClient { + options: InfluxDBClientOptions; + queryV1(query: string, database: string, headers?: RequestHeaders): Promise; + queryV3SQL(query: string, organization: string, bucket: string, headers?: RequestHeaders): Promise; +} diff --git a/influxdb/src/queries/influxdb-time-series-query/InfluxDBTimeSeriesQuery.ts b/influxdb/src/queries/influxdb-time-series-query/InfluxDBTimeSeriesQuery.ts new file mode 100644 index 00000000..a67216cc --- /dev/null +++ b/influxdb/src/queries/influxdb-time-series-query/InfluxDBTimeSeriesQuery.ts @@ -0,0 +1,85 @@ +// Copyright The Perses Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import { TimeSeriesQueryPlugin } from '@perses-dev/plugin-system'; +import { TimeSeriesData } from '@perses-dev/core'; +import { InfluxDBDatasourceSpec, InfluxDBClient } from '../../model'; +import { InfluxDBTimeSeriesQueryEditor } from './InfluxDBTimeSeriesQueryEditor'; +export interface InfluxDBTimeSeriesQuerySpec { + query: string; +} +function convertV1ResponseToTimeSeries(response: any): TimeSeriesData { + const datasets: any[] = []; + if (response.results && response.results[0] && response.results[0].series) { + response.results[0].series.forEach((series: any) => { + const timeIndex = series.columns.indexOf('time'); + const valueColumns = series.columns.filter((col: string) => col !== 'time'); + valueColumns.forEach((valueColumn: string) => { + const valueIndex = series.columns.indexOf(valueColumn); + const data = series.values.map((row: any[]) => ({ + x: new Date(row[timeIndex]).getTime(), + y: row[valueIndex], + })); + const tagStr = series.tags + ? Object.entries(series.tags).map(([k, v]) => k + '="' + v + '"').join(',') + : ''; + datasets.push({ + name: series.name + '.' + valueColumn, + values: data, + formattedName: tagStr + ? series.name + '{' + tagStr + '}.' + valueColumn + : series.name + '.' + valueColumn, + }); + }); + }); + } + return { datasets }; +} +function convertV3ResponseToTimeSeries(response: any): TimeSeriesData { + const datasets: any[] = []; + if (response.data && response.schema) { + const timeField = response.schema.fields.find((f: any) => f.data_type === 'Timestamp'); + const timeIndex = response.schema.fields.indexOf(timeField); + response.schema.fields.forEach((field: any, index: number) => { + if (field.data_type !== 'Timestamp' && field.data_type !== 'Utf8') { + const data = response.data.map((row: any[]) => ({ + x: new Date(row[timeIndex]).getTime(), + y: row[index], + })); + datasets.push({ name: field.name, values: data, formattedName: field.name }); + } + }); + } + return { datasets }; +} +export const InfluxDBTimeSeriesQuery: TimeSeriesQueryPlugin< + InfluxDBTimeSeriesQuerySpec, + InfluxDBDatasourceSpec, + InfluxDBClient +> = { + getTimeSeriesData: async (spec, context) => { + const { query } = spec; + const { datasourceClient, datasourceSpec } = context; + if (!datasourceClient) { + throw new Error('No datasource client available'); + } + if (datasourceSpec.version === 'v1') { + const response = await datasourceClient.queryV1(query, datasourceSpec.database); + return convertV1ResponseToTimeSeries(response); + } else if (datasourceSpec.version === 'v3') { + const response = await datasourceClient.queryV3SQL(query, datasourceSpec.organization, datasourceSpec.bucket); + return convertV3ResponseToTimeSeries(response); + } + throw new Error('Unsupported InfluxDB version: ' + (datasourceSpec as any).version); + }, + OptionsEditorComponent: InfluxDBTimeSeriesQueryEditor, +}; diff --git a/influxdb/src/queries/influxdb-time-series-query/InfluxDBTimeSeriesQueryEditor.tsx b/influxdb/src/queries/influxdb-time-series-query/InfluxDBTimeSeriesQueryEditor.tsx new file mode 100644 index 00000000..d23de26f --- /dev/null +++ b/influxdb/src/queries/influxdb-time-series-query/InfluxDBTimeSeriesQueryEditor.tsx @@ -0,0 +1,32 @@ +// Copyright The Perses Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import { TextField, Box } from '@mui/material'; +import { OptionsEditorProps } from '@perses-dev/plugin-system'; +import { InfluxDBTimeSeriesQuerySpec } from './InfluxDBTimeSeriesQuery'; +export function InfluxDBTimeSeriesQueryEditor({ value, onChange }: OptionsEditorProps) { + return ( + + onChange({ ...value, query: e.target.value })} + helperText="SQL query for InfluxDB v3 or InfluxQL for v1.8" + required + fullWidth + multiline + rows={6} + /> + + ); +} diff --git a/influxdb/src/queries/influxdb-time-series-query/index.ts b/influxdb/src/queries/influxdb-time-series-query/index.ts new file mode 100644 index 00000000..6c18f5c2 --- /dev/null +++ b/influxdb/src/queries/influxdb-time-series-query/index.ts @@ -0,0 +1,15 @@ +// Copyright The Perses Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export * from './InfluxDBTimeSeriesQuery'; +export * from './InfluxDBTimeSeriesQueryEditor'; diff --git a/influxdb/tsconfig.build.json b/influxdb/tsconfig.build.json new file mode 100644 index 00000000..09ca62b1 --- /dev/null +++ b/influxdb/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist/lib" + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.test.ts", "src/**/*.test.tsx", "src/**/*.stories.tsx"] +} diff --git a/influxdb/tsconfig.json b/influxdb/tsconfig.json new file mode 100644 index 00000000..3c43903c --- /dev/null +++ b/influxdb/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../tsconfig.json" +}