diff --git a/.drone.yml b/.drone.yml index fe38edfddd9..295dcc35e18 100644 --- a/.drone.yml +++ b/.drone.yml @@ -17,7 +17,7 @@ steps: image: grafana/build-container:1.3.2 commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.38/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.43/grabpl - chmod +x bin/grabpl - ./bin/grabpl verify-drone - curl -fLO https://github.com/jwilder/dockerize/releases/download/v$${DOCKERIZE_VERSION}/dockerize-linux-amd64-v$${DOCKERIZE_VERSION}.tar.gz @@ -259,7 +259,7 @@ steps: image: grafana/build-container:1.3.2 commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.38/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.43/grabpl - chmod +x bin/grabpl - ./bin/grabpl verify-drone - curl -fLO https://github.com/jwilder/dockerize/releases/download/v$${DOCKERIZE_VERSION}/dockerize-linux-amd64-v$${DOCKERIZE_VERSION}.tar.gz @@ -591,7 +591,7 @@ steps: image: grafana/ci-wix:0.1.1 commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.38/windows/grabpl.exe -OutFile grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.43/windows/grabpl.exe -OutFile grabpl.exe - .\grabpl.exe verify-drone - name: build-windows-installer @@ -641,7 +641,7 @@ steps: image: grafana/build-container:1.3.2 commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.38/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.43/grabpl - chmod +x bin/grabpl - ./bin/grabpl verify-drone environment: @@ -726,7 +726,7 @@ steps: image: grafana/build-container:1.3.2 commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.38/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.43/grabpl - chmod +x bin/grabpl - ./bin/grabpl verify-drone - ./bin/grabpl verify-version ${DRONE_TAG} @@ -1033,7 +1033,7 @@ steps: image: grafana/ci-wix:0.1.1 commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.38/windows/grabpl.exe -OutFile grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.43/windows/grabpl.exe -OutFile grabpl.exe - .\grabpl.exe verify-drone - name: build-windows-installer @@ -1084,7 +1084,7 @@ steps: image: grafana/build-container:1.3.2 commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.38/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.43/grabpl - chmod +x bin/grabpl - git clone "https://$${GITHUB_TOKEN}@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise @@ -1474,7 +1474,7 @@ steps: image: grafana/ci-wix:0.1.1 commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.38/windows/grabpl.exe -OutFile grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.43/windows/grabpl.exe -OutFile grabpl.exe - git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise - git checkout ${DRONE_TAG} @@ -1540,7 +1540,7 @@ steps: image: grafana/build-container:1.3.2 commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.38/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.43/grabpl - chmod +x bin/grabpl - ./bin/grabpl verify-drone - ./bin/grabpl verify-version ${DRONE_TAG} @@ -1645,7 +1645,7 @@ steps: image: grafana/build-container:1.3.2 commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.38/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.43/grabpl - chmod +x bin/grabpl - ./bin/grabpl verify-drone - ./bin/grabpl verify-version v7.3.0-test @@ -1941,7 +1941,7 @@ steps: image: grafana/ci-wix:0.1.1 commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.38/windows/grabpl.exe -OutFile grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.43/windows/grabpl.exe -OutFile grabpl.exe - .\grabpl.exe verify-drone - name: build-windows-installer @@ -1992,7 +1992,7 @@ steps: image: grafana/build-container:1.3.2 commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.38/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.43/grabpl - chmod +x bin/grabpl - git clone "https://$${GITHUB_TOKEN}@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise @@ -2376,7 +2376,7 @@ steps: image: grafana/ci-wix:0.1.1 commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.38/windows/grabpl.exe -OutFile grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.43/windows/grabpl.exe -OutFile grabpl.exe - git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise - git checkout master @@ -2442,7 +2442,7 @@ steps: image: grafana/build-container:1.3.2 commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.38/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.43/grabpl - chmod +x bin/grabpl - ./bin/grabpl verify-drone - ./bin/grabpl verify-version v7.3.0-test @@ -2547,7 +2547,7 @@ steps: image: grafana/build-container:1.3.2 commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.38/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.43/grabpl - chmod +x bin/grabpl - ./bin/grabpl verify-drone - curl -fLO https://github.com/jwilder/dockerize/releases/download/v$${DOCKERIZE_VERSION}/dockerize-linux-amd64-v$${DOCKERIZE_VERSION}.tar.gz @@ -2818,7 +2818,7 @@ steps: image: grafana/ci-wix:0.1.1 commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.38/windows/grabpl.exe -OutFile grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.43/windows/grabpl.exe -OutFile grabpl.exe - .\grabpl.exe verify-drone - name: build-windows-installer @@ -2865,7 +2865,7 @@ steps: image: grafana/build-container:1.3.2 commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.38/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.43/grabpl - chmod +x bin/grabpl - git clone "https://$${GITHUB_TOKEN}@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise @@ -3252,7 +3252,7 @@ steps: image: grafana/ci-wix:0.1.1 commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.38/windows/grabpl.exe -OutFile grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.43/windows/grabpl.exe -OutFile grabpl.exe - git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise - git checkout $$env:DRONE_BRANCH diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6360d22fa8c..ae2fcfca86b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -27,6 +27,9 @@ go.sum @grafana/backend-platform /pkg/tsdb/azuremonitor @grafana/cloud-datasources /pkg/tsdb/cloudmonitoring @grafana/cloud-datasources +# Database migrations +/pkg/services/sqlstore/migrations @grafana/backend-platform @grafana/hosted-grafana-team +*_mig.go @grafana/backend-platform @grafana/hosted-grafana-team # Backend code docs /contribute/style-guides/backend.md @grafana/backend-platform diff --git a/Makefile b/Makefile index 5d8ec695a41..8fb27210cb4 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ -include local/Makefile -.PHONY: all deps-go deps-js deps build-go build-server build-cli build-js build build-docker-dev build-docker-full lint-go revive golangci-lint tidy-check test-go test-js test run run-frontend clean devenv devenv-down revive-strict protobuf help +.PHONY: all deps-go deps-js deps build-go build-server build-cli build-js build build-docker-dev build-docker-full lint-go revive golangci-lint test-go test-js test run run-frontend clean devenv devenv-down revive-strict protobuf help GO = GO111MODULE=on go GO_FILES ?= ./pkg/... @@ -95,11 +95,7 @@ golangci-lint: scripts/go/bin/golangci-lint --config ./scripts/go/configs/.golangci.toml \ $(GO_FILES) -tidy-check: - @echo "check whether go.mod and go.sum are consistent" - @scripts/tidy-check.sh - -lint-go: golangci-lint revive revive-strict tidy-check # Run all code checks for backend. +lint-go: golangci-lint revive revive-strict # Run all code checks for backend. # with disabled SC1071 we are ignored some TCL,Expect `/usr/bin/env expect` scripts shellcheck: $(SH_FILES) ## Run checks for shell scripts. diff --git a/devenv/datasources.yaml b/devenv/datasources.yaml index 405c69436ab..23d6e6b900c 100644 --- a/devenv/datasources.yaml +++ b/devenv/datasources.yaml @@ -32,6 +32,19 @@ datasources: timeInterval: "15s" secureJsonData: password: grafana + + - name: gdev-influxdb2 + type: influxdb + access: proxy + url: http://localhost:8087 + user: grafana + secureJsonData: + token: mytoken + password: grafana12345 + jsonData: + version: Flux + organization: myorg + defaultBucket: mybucket - name: gdev-influxdb-telegraf type: influxdb @@ -43,7 +56,7 @@ datasources: timeInterval: "10s" secureJsonData: password: grafana - + - name: gdev-opentsdb type: opentsdb access: proxy diff --git a/devenv/datasources_docker.yaml b/devenv/datasources_docker.yaml index d7abd11fb3b..d99a37ef7bb 100644 --- a/devenv/datasources_docker.yaml +++ b/devenv/datasources_docker.yaml @@ -34,6 +34,17 @@ datasources: secureJsonData: password: grafana + - name: gdev-influxdb2 + type: influxdb + access: proxy + url: http://influxdb2:8086 + secureJsonData: + token: mytoken + jsonData: + version: Flux + organization: myorg + defaultBucket: mybucket + - name: gdev-influxdb-telegraf type: influxdb access: proxy diff --git a/devenv/docker/blocks/influxdb2/docker-compose.yaml b/devenv/docker/blocks/influxdb2/docker-compose.yaml index a9c80830980..dc3bcee88a2 100644 --- a/devenv/docker/blocks/influxdb2/docker-compose.yaml +++ b/devenv/docker/blocks/influxdb2/docker-compose.yaml @@ -1,39 +1,40 @@ - influxdb: + influxdb2: image: quay.io/influxdb/influxdb:v2.0.3 container_name: influxdb2 ports: - - '8086:8086' + - '8087:8086' environment: INFLUXDB_REPORTING_DISABLED: 'true' volumes: - ./docker/blocks/influxdb2/influxdb.conf:/etc/influxdb/influxdb.conf # Use the influx cli to set up an influxdb instance. - influxdb_cli: + influxdb2_cli: links: - - influxdb + - influxdb2 image: quay.io/influxdb/influxdb:v2.0.3 # Use these same configurations parameters in your telegraf configuration, mytelegraf.conf. - entrypoint: influx setup --bucket mybucket -t mytoken -o myorg --username=grafana --password=grafana12345 --host=http://influxdb:8086 -f + entrypoint: influx setup --bucket mybucket -t mytoken -o myorg --username=grafana --password=grafana12345 --host=http://influxdb2:8086 -f # Wait for the influxd service in the influxdb container has fully bootstrapped before trying to setup an influxdb instance with the influxdb_cli service. restart: on-failure:10 depends_on: - - influxdb + - influxdb2 - fake-influxdb-data: + fake-influxdb2-data: image: grafana/fake-data-gen links: - - influxdb + - influxdb2 environment: + FD_SERVER: influxdb2 FD_DATASOURCE: influxdb FD_PORT: 8086 - telegraf: + telegraf2: image: telegraf links: - - influxdb + - influxdb2 depends_on: - - influxdb_cli + - influxdb2_cli volumes: - ./docker/blocks/influxdb2/telegraf.conf:/etc/telegraf/telegraf.conf:ro - /var/log:/var/log diff --git a/devenv/docker/blocks/influxdb2/telegraf.conf b/devenv/docker/blocks/influxdb2/telegraf.conf index 0036556b41f..6799080c089 100644 --- a/devenv/docker/blocks/influxdb2/telegraf.conf +++ b/devenv/docker/blocks/influxdb2/telegraf.conf @@ -90,7 +90,7 @@ ## urls will be written to each interval. # urls = ["unix:///var/run/influxdb.sock"] # urls = ["udp://127.0.0.1:8089"] - urls = ["http://influxdb:8086"] + urls = ["http://influxdb2:8086"] token = "mytoken" organization = "myorg" bucket = "mybucket" diff --git a/docs/sources/alerting/notifications.md b/docs/sources/alerting/notifications.md index e8bb10aa5ed..cf3e06a5a42 100644 --- a/docs/sources/alerting/notifications.md +++ b/docs/sources/alerting/notifications.md @@ -217,10 +217,6 @@ Once these two properties are set, you can send the alerts to Kafka for further Notifications can be sent by setting up an incoming webhook in Google Hangouts chat. For more information about configuring a webhook, refer to [webhooks](https://developers.google.com/hangouts/chat/how-tos/webhooks). -### Squadcast - -Squadcast helps you get alerted via Phone call, SMS, Email and Push notifications and lets you take actions on those alerts. Grafana notifications can be sent to Squadcast via a simple incoming webhook. Refer the official [Squadcast support documentation](https://support.squadcast.com/docs/grafana) for configuring these webhooks. - ### Prometheus Alertmanager Alertmanager handles alerts sent by client applications such as Prometheus server or Grafana. It takes care of deduplicating, grouping, and routing them to the correct receiver. Grafana notifications can be sent to Alertmanager via a simple incoming webhook. Refer to the official [Prometheus Alertmanager documentation](https://prometheus.io/docs/alerting/alertmanager) for configuration information. diff --git a/packages/grafana-runtime/src/index.ts b/packages/grafana-runtime/src/index.ts index 22d865593cb..a8f139042df 100644 --- a/packages/grafana-runtime/src/index.ts +++ b/packages/grafana-runtime/src/index.ts @@ -9,5 +9,6 @@ export * from './types'; export * from './measurement'; export { loadPluginCss, SystemJS, PluginCssOptions } from './utils/plugin'; export { reportMetaAnalytics } from './utils/analytics'; +export { logInfo, logDebug, logWarning, logError } from './utils/logging'; export { DataSourceWithBackend, HealthCheckResult, HealthStatus } from './utils/DataSourceWithBackend'; export { toDataQueryError, toDataQueryResponse, frameToMetricFindValue } from './utils/queryResponse'; diff --git a/packages/grafana-runtime/src/utils/logging.ts b/packages/grafana-runtime/src/utils/logging.ts new file mode 100644 index 00000000000..0f8051a54f6 --- /dev/null +++ b/packages/grafana-runtime/src/utils/logging.ts @@ -0,0 +1,50 @@ +import { captureMessage, captureException, Severity as LogLevel } from '@sentry/browser'; +export { LogLevel }; + +// a bit stricter than what Sentry allows +type Contexts = Record>>; + +/** + * Log a message at INFO level. Depending on configuration might be forwarded to backend and logged to stdout or sent to Sentry + * + * @public + */ +export function logInfo(message: string, contexts?: Contexts) { + captureMessage(message, { + level: LogLevel.Info, + contexts, + }); +} + +/** + * Log a message at WARNING level. Depending on configuration might be forwarded to backend and logged to stdout or sent to Sentry + * + * @public + */ +export function logWarning(message: string, contexts?: Contexts) { + captureMessage(message, { + level: LogLevel.Warning, + contexts, + }); +} + +/** + * Log a message at DEBUG level. Depending on configuration might be forwarded to backend and logged to stdout or sent to Sentry + * + * @public + */ +export function logDebug(message: string, contexts?: Contexts) { + captureMessage(message, { + level: LogLevel.Debug, + contexts, + }); +} + +/** + * Log an error. Depending on configuration might be forwarded to backend and logged to stdout or sent to Sentry + * + * @public + */ +export function logError(err: Error, contexts?: Contexts) { + captureException(err, { contexts }); +} diff --git a/packages/grafana-ui/.storybook/preview.ts b/packages/grafana-ui/.storybook/preview.ts index 50efa10cb74..9e40a52a344 100644 --- a/packages/grafana-ui/.storybook/preview.ts +++ b/packages/grafana-ui/.storybook/preview.ts @@ -59,5 +59,3 @@ export const parameters = { escapeHTML: false, }, }; - -export const NOOP_CONTROL = { control: { disable: true } }; diff --git a/packages/grafana-ui/src/components/BarGauge/BarGauge.story.tsx b/packages/grafana-ui/src/components/BarGauge/BarGauge.story.tsx index 6349d680fb2..622a9220320 100644 --- a/packages/grafana-ui/src/components/BarGauge/BarGauge.story.tsx +++ b/packages/grafana-ui/src/components/BarGauge/BarGauge.story.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Story } from '@storybook/react'; import { BarGauge, BarGaugeDisplayMode } from '@grafana/ui'; -import { NOOP_CONTROL } from '@grafana/ui/.storybook/preview'; +import { NOOP_CONTROL } from '../../utils/storybook/noopControl'; import { VizOrientation, ThresholdsMode, Field, FieldType, getDisplayProcessor } from '@grafana/data'; import { Props } from './BarGauge'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; diff --git a/packages/grafana-ui/src/components/BigValue/BigValue.story.tsx b/packages/grafana-ui/src/components/BigValue/BigValue.story.tsx index c74e9a82d70..88e014298fc 100644 --- a/packages/grafana-ui/src/components/BigValue/BigValue.story.tsx +++ b/packages/grafana-ui/src/components/BigValue/BigValue.story.tsx @@ -11,7 +11,7 @@ import { import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import mdx from './BigValue.mdx'; import { useTheme } from '../../themes'; -import { NOOP_CONTROL } from '@grafana/ui/.storybook/preview'; +import { NOOP_CONTROL } from '../../utils/storybook/noopControl'; import { ArrayVector, FieldSparkline, FieldType } from '@grafana/data'; export default { diff --git a/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.story.tsx b/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.story.tsx index 2ff6f650f12..3652335f8eb 100644 --- a/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.story.tsx +++ b/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.story.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Story } from '@storybook/react'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { ButtonCascader } from '@grafana/ui'; -import { NOOP_CONTROL } from '@grafana/ui/.storybook/preview'; +import { NOOP_CONTROL } from '../../utils/storybook/noopControl'; import { ButtonCascaderProps } from './ButtonCascader'; export default { diff --git a/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.story.internal.tsx b/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.story.internal.tsx index 37f9d16b13b..f5ccd36b88b 100644 --- a/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.story.internal.tsx +++ b/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.story.internal.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { renderComponentWithTheme } from '../../utils/storybook/withTheme'; import { CallToActionCard, CallToActionCardProps } from './CallToActionCard'; -import { NOOP_CONTROL } from '../../../.storybook/preview'; +import { NOOP_CONTROL } from '../../utils/storybook/noopControl'; import { Story } from '@storybook/react'; import { Button } from '../Button/Button'; import { action } from '@storybook/addon-actions'; diff --git a/packages/grafana-ui/src/components/Cascader/Cascader.story.tsx b/packages/grafana-ui/src/components/Cascader/Cascader.story.tsx index f62024862f8..53fbc46cc73 100644 --- a/packages/grafana-ui/src/components/Cascader/Cascader.story.tsx +++ b/packages/grafana-ui/src/components/Cascader/Cascader.story.tsx @@ -1,6 +1,6 @@ import { Story } from '@storybook/react'; import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; -import { NOOP_CONTROL } from '../../../.storybook/preview'; +import { NOOP_CONTROL } from '../../utils/storybook/noopControl'; import { Cascader } from '@grafana/ui'; import { CascaderProps } from './Cascader'; import mdx from './Cascader.mdx'; diff --git a/packages/grafana-ui/src/utils/storybook/noopControl.tsx b/packages/grafana-ui/src/utils/storybook/noopControl.tsx new file mode 100644 index 00000000000..d433da2c904 --- /dev/null +++ b/packages/grafana-ui/src/utils/storybook/noopControl.tsx @@ -0,0 +1 @@ +export const NOOP_CONTROL = { control: { disable: true } }; diff --git a/packages/grafana-ui/tsconfig.build.json b/packages/grafana-ui/tsconfig.build.json index fefe5556971..6134388bd9d 100644 --- a/packages/grafana-ui/tsconfig.build.json +++ b/packages/grafana-ui/tsconfig.build.json @@ -1,12 +1,4 @@ { - "exclude": [ - "**/*.story.tsx", - "**/*.story.internal.tsx", - "**/*.test.ts*", - "**/*.tmpl.ts", - "dist", - "node_modules", - "src/utils/storybook" - ], + "exclude": ["**/*.story.tsx", "**/*.test.ts*", "**/*.tmpl.ts", "dist", "node_modules", "src/utils/storybook"], "extends": "./tsconfig.json" } diff --git a/pkg/api/pluginproxy/ds_proxy.go b/pkg/api/pluginproxy/ds_proxy.go index fc79359b2a9..4ba6a511833 100644 --- a/pkg/api/pluginproxy/ds_proxy.go +++ b/pkg/api/pluginproxy/ds_proxy.go @@ -245,8 +245,8 @@ func (proxy *DataSourceProxy) validateRequest() error { if proxy.ctx.Req.Request.Method == "PUT" { return errors.New("puts not allowed on proxied Prometheus datasource") } - if proxy.ctx.Req.Request.Method == "POST" && !(proxy.proxyPath == "api/v1/query" || proxy.proxyPath == "api/v1/query_range") { - return errors.New("posts not allowed on proxied Prometheus datasource except on /query and /query_range") + if proxy.ctx.Req.Request.Method == "POST" && !(proxy.proxyPath == "api/v1/query" || proxy.proxyPath == "api/v1/query_range" || proxy.proxyPath == "api/v1/series" || proxy.proxyPath == "api/v1/labels") { + return errors.New("posts not allowed on proxied Prometheus datasource except on /query, /query_range, /series and /labels") } } diff --git a/pkg/tsdb/cloudwatch/metric_find_query.go b/pkg/tsdb/cloudwatch/metric_find_query.go index e5737b017c7..1b379d29bd8 100644 --- a/pkg/tsdb/cloudwatch/metric_find_query.go +++ b/pkg/tsdb/cloudwatch/metric_find_query.go @@ -60,7 +60,7 @@ var metricsMap = map[string][]string{ "AWS/Cognito": {"AccountTakeOverRisk", "CompromisedCredentialsRisk", "NoRisk", "OverrideBlock", "Risk"}, "AWS/Connect": {"CallBackNotDialableNumber", "CallRecordingUploadError", "CallsBreachingConcurrencyQuota", "CallsPerInterval", "ConcurrentCalls", "ConcurrentCallsPercentage", "ContactFlowErrors", "ContactFlowFatalErrors", "LongestQueueWaitTime", "MisconfiguredPhoneNumbers", "MissedCalls", "PublicSigningKeyUsage", "QueueCapacityExceededError", "QueueSize", "ThrottledCalls", "ToInstancePacketLossRate"}, "AWS/DataSync": {"BytesVerifiedSource", "BytesPreparedSource", "FilesVerifiedSource", "FilesPreparedSource", "BytesVerifiedDestination", "BytesPreparedDestination", "FilesVerifiedDestination", "FilesPreparedDestination", "FilesTransferred", "BytesTransferred", "BytesWritten"}, - "AWS/DDoSProtection": {"DDoSDetected", "DDoSAttackBitsPerSecond", "DDoSAttackPacketsPerSecond", "DDoSAttackRequestsPerSecond"}, + "AWS/DDoSProtection": {"DDoSDetected", "DDoSAttackBitsPerSecond", "DDoSAttackPacketsPerSecond", "DDoSAttackRequestsPerSecond", "VolumeBitsPerSecond", "VolumePacketsPerSecond"}, "AWS/DMS": {"CDCChangesDiskSource", "CDCChangesDiskTarget", "CDCChangesMemorySource", "CDCChangesMemoryTarget", "CDCIncomingChanges", "CDCLatencySource", "CDCLatencyTarget", "CDCThroughputBandwidthSource", "CDCThroughputBandwidthTarget", "CDCThroughputRowsSource", "CDCThroughputRowsTarget", "CPUUtilization", "FreeStorageSpace", "FreeableMemory", "FullLoadThroughputBandwidthSource", "FullLoadThroughputBandwidthTarget", "FullLoadThroughputRowsSource", "FullLoadThroughputRowsTarget", "NetworkReceiveThroughput", "NetworkTransmitThroughput", "ReadIOPS", "ReadLatency", "ReadThroughput", "SwapUsage", "WriteIOPS", "WriteLatency", "WriteThroughput"}, "AWS/DocDB": {"BackupRetentionPeriodStorageUsed", "BufferCacheHitRatio", "ChangeStreamLogSize", "CPUUtilization", "DatabaseConnections", "DBInstanceReplicaLag", "DBClusterReplicaLagMaximum", "DBClusterReplicaLagMinimum", "DiskQueueDepth", "EngineUptime", "FreeableMemory", "FreeLocalStorage", "NetworkReceiveThroughput", "NetworkThroughput", "NetworkTransmitThroughput", "ReadIOPS", "ReadLatency", "ReadThroughput", "SnapshotStorageUsed", "SwapUsage", "TotalBackupStorageBilled", "VolumeBytesUsed", "VolumeReadIOPs", "VolumeWriteIOPs", "WriteIOPS", "WriteLatency", "WriteThroughput"}, "AWS/DX": {"ConnectionBpsEgress", "ConnectionBpsIngress", "ConnectionErrorCount", "ConnectionCRCErrorCount", "ConnectionLightLevelRx", "ConnectionLightLevelTx", "ConnectionPpsEgress", "ConnectionPpsIngress", "ConnectionState", "VirtualInterfaceBpsEgress", "VirtualInterfaceBpsIngress", "VirtualInterfacePpsEgress", "VirtualInterfacePpsIngress"}, @@ -156,7 +156,7 @@ var dimensionsMap = map[string][]string{ "AWS/Cognito": {"Operation", "RiskLevel", "UserPoolId"}, "AWS/Connect": {"InstanceId", "MetricGroup", "Participant", "QueueName", "Stream Type", "Type of Connection"}, "AWS/DataSync": {"AgentId", "TaskId"}, - "AWS/DDoSProtection": {"ResourceArn", "AttackVector"}, + "AWS/DDoSProtection": {"ResourceArn", "AttackVector", "MitigationAction", "Protocol", "SourcePort", "DestinationPort", "SourceIp", "SourceAsn", "TcpFlags"}, "AWS/DMS": {"ReplicationInstanceIdentifier", "ReplicationTaskIdentifier"}, "AWS/DocDB": {"DBClusterIdentifier", "DBInstanceIdentifier", "Role"}, "AWS/DX": {"ConnectionId", "OpticalLaneNumber", "VirtualInterfaceId"}, diff --git a/pkg/tsdb/influxdb/flux/builder.go b/pkg/tsdb/influxdb/flux/builder.go index f9358972ffc..2b8b9dd7fff 100644 --- a/pkg/tsdb/influxdb/flux/builder.go +++ b/pkg/tsdb/influxdb/flux/builder.go @@ -30,18 +30,19 @@ type columnInfo struct { // frameBuilder is an interface to help testing. type frameBuilder struct { - tableID int64 - active *data.Frame - frames []*data.Frame - value *data.FieldConverter - columns []columnInfo - labels []string - maxPoints int // max points in a series - maxSeries int // max number of series - totalSeries int - isTimeSeries bool - timeColumn string // sometimes it is not `_time` - timeDisplay string + currentGroupKey []interface{} + groupKeyColumnNames []string + active *data.Frame + frames []*data.Frame + value *data.FieldConverter + columns []columnInfo + labels []string + maxPoints int // max points in a series + maxSeries int // max number of series + totalSeries int + isTimeSeries bool + timeColumn string // sometimes it is not `_time` + timeDisplay string } func isTag(schk string) bool { @@ -95,11 +96,18 @@ func getConverter(t string) (*data.FieldConverter, error) { func (fb *frameBuilder) Init(metadata *query.FluxTableMetadata) error { columns := metadata.Columns() fb.frames = make([]*data.Frame, 0) - fb.tableID = -1 + fb.currentGroupKey = nil fb.value = nil fb.columns = make([]columnInfo, 0) fb.isTimeSeries = false fb.timeColumn = "" + fb.groupKeyColumnNames = make([]string, 0) + + for _, col := range columns { + if col.IsGroup() { + fb.groupKeyColumnNames = append(fb.groupKeyColumnNames, col.Name()) + } + } for _, col := range columns { switch { @@ -175,14 +183,52 @@ func getTimeSeriesTimeColumn(columns []*query.FluxColumn) *query.FluxColumn { return nil } +func getTableID(record *query.FluxRecord, groupColumns []string) []interface{} { + result := make([]interface{}, len(groupColumns)) + + // Flux does not allow duplicate column-names, + // so we can be sure there is no confusion in the record. + // + // ( it does allow for a column named "table" to exist, + // and shadow the table-id "table" column, but the potentially + // shadowed table-id column is not a part of the group-key, + // so we should be safe ) + + for i, colName := range groupColumns { + result[i] = record.ValueByKey(colName) + } + + return result +} + +func isTableIDEqual(id1 []interface{}, id2 []interface{}) bool { + if (id1 == nil) || (id2 == nil) { + return false + } + + if len(id1) != len(id2) { + return false + } + + for i, id1Val := range id1 { + id2Val := id2[i] + + if id1Val != id2Val { + return false + } + } + + return true +} + // Append appends a single entry from an influxdb2 record to a data frame // Values are appended to _value // Tags are appended as labels // _measurement holds the dataframe name // _field holds the field name. func (fb *frameBuilder) Append(record *query.FluxRecord) error { - table, ok := record.ValueByKey("table").(int64) - if ok && table != fb.tableID { + table := getTableID(record, fb.groupKeyColumnNames) + if (fb.currentGroupKey == nil) || !isTableIDEqual(table, fb.currentGroupKey) { fb.totalSeries++ if fb.totalSeries > fb.maxSeries { return fmt.Errorf("results are truncated, max series reached (%d)", fb.maxSeries) @@ -226,7 +272,7 @@ func (fb *frameBuilder) Append(record *query.FluxRecord) error { } fb.frames = append(fb.frames, fb.active) - fb.tableID = table + fb.currentGroupKey = table } if fb.isTimeSeries { diff --git a/pkg/tsdb/influxdb/flux/executor_test.go b/pkg/tsdb/influxdb/flux/executor_test.go index 46f92bc3e3b..f623a164814 100644 --- a/pkg/tsdb/influxdb/flux/executor_test.go +++ b/pkg/tsdb/influxdb/flux/executor_test.go @@ -103,6 +103,11 @@ func TestExecuteMultiple(t *testing.T) { fmt.Println("----------------------") } +func TestExecuteColumnNamedTable(t *testing.T) { + dr := verifyGoldenResponse(t, "table") + require.Len(t, dr.Frames, 1) +} + func TestExecuteGrouping(t *testing.T) { dr := verifyGoldenResponse(t, "grouping") require.Len(t, dr.Frames, 3) diff --git a/pkg/tsdb/influxdb/flux/testdata/table.csv b/pkg/tsdb/influxdb/flux/testdata/table.csv new file mode 100644 index 00000000000..927b9166598 --- /dev/null +++ b/pkg/tsdb/influxdb/flux/testdata/table.csv @@ -0,0 +1,6 @@ +#group,false,false,true,true,false,false,true +#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,long,string +#default,last,,,,,, +,result,table,_start,_stop,_time,_value,table +,,0,2021-02-05T14:02:04.7009051Z,2021-02-05T14:03:04.7009051Z,2021-02-05T14:03:04.7009051Z,42,something + diff --git a/pkg/tsdb/influxdb/flux/testdata/table.golden.txt b/pkg/tsdb/influxdb/flux/testdata/table.golden.txt new file mode 100644 index 00000000000..82bf6e4a7db --- /dev/null +++ b/pkg/tsdb/influxdb/flux/testdata/table.golden.txt @@ -0,0 +1,16 @@ +🌟 This was machine generated. Do not edit. 🌟 + +Frame[0] {} +Name: +Dimensions: 2 Fields by 1 Rows ++---------------------------------------+----------------+ +| Name: Time | Name: | +| Labels: | Labels: | +| Type: []time.Time | Type: []*int64 | ++---------------------------------------+----------------+ +| 2021-02-05 14:03:04.7009051 +0000 UTC | 42 | ++---------------------------------------+----------------+ + + +====== TEST DATA RESPONSE (arrow base64) ====== +FRAME=QVJST1cxAAD/////yAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAHQAAAADAAAATAAAACgAAAAEAAAAzP7//wgAAAAMAAAAAAAAAAAAAAAFAAAAcmVmSWQAAADs/v//CAAAAAwAAAAAAAAAAAAAAAQAAABuYW1lAAAAAAz///8IAAAADAAAAAIAAAB7fQAABAAAAG1ldGEAAAAAAgAAALAAAAAYAAAAAAASABgAFAATABIADAAAAAgABAASAAAAFAAAAFwAAABkAAAAAAACAWgAAAACAAAAKAAAAAQAAABw////CAAAAAwAAAAAAAAAAAAAAAQAAABuYW1lAAAAAJD///8IAAAADAAAAAIAAAB7fQAABgAAAGxhYmVscwAAAAAAAAgADAAIAAcACAAAAAAAAAFAAAAAAAAAAAAAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAAAAAAAP////+4AAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAEAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAWAAAAAEAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAIAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAIzmaQHv3mAWKgAAAAAAAAAQAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAOAAAAAAAAwABAAAA2AEAAAAAAADAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAHQAAAADAAAATAAAACgAAAAEAAAAzP7//wgAAAAMAAAAAAAAAAAAAAAFAAAAcmVmSWQAAADs/v//CAAAAAwAAAAAAAAAAAAAAAQAAABuYW1lAAAAAAz///8IAAAADAAAAAIAAAB7fQAABAAAAG1ldGEAAAAAAgAAALAAAAAYAAAAAAASABgAFAATABIADAAAAAgABAASAAAAFAAAAFwAAABkAAAAAAACAWgAAAACAAAAKAAAAAQAAABw////CAAAAAwAAAAAAAAAAAAAAAQAAABuYW1lAAAAAJD///8IAAAADAAAAAIAAAB7fQAABgAAAGxhYmVscwAAAAAAAAgADAAIAAcACAAAAAAAAAFAAAAAAAAAAAAAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAATAAAAAAAAApMAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABUaW1lAAAAAAQAAABuYW1lAAAAAAAAAAAAAAYACAAGAAYAAAAAAAMABAAAAFRpbWUAAAAA8AEAAEFSUk9XMQ== diff --git a/public/app/plugins/datasource/prometheus/datasource.test.ts b/public/app/plugins/datasource/prometheus/datasource.test.ts index 7293eec54df..f591527ee87 100644 --- a/public/app/plugins/datasource/prometheus/datasource.test.ts +++ b/public/app/plugins/datasource/prometheus/datasource.test.ts @@ -105,8 +105,7 @@ describe('PrometheusDatasource', () => { expect(fetchMock.mock.calls.length).toBe(1); expect(fetchMock.mock.calls[0][0].method).toBe('GET'); }); - - it('should still perform a GET request with the DS HTTP method set to POST', () => { + it('should still perform a GET request with the DS HTTP method set to POST and not POST-friendly endpoint', () => { const postSettings = _.cloneDeep(instanceSettings); postSettings.jsonData.httpMethod = 'POST'; const promDs = new PrometheusDatasource(postSettings, templateSrvStub as any, timeSrvStub as any); @@ -114,6 +113,14 @@ describe('PrometheusDatasource', () => { expect(fetchMock.mock.calls.length).toBe(1); expect(fetchMock.mock.calls[0][0].method).toBe('GET'); }); + it('should try to perform a POST request with the DS HTTP method set to POST and POST-friendly endpoint', () => { + const postSettings = _.cloneDeep(instanceSettings); + postSettings.jsonData.httpMethod = 'POST'; + const promDs = new PrometheusDatasource(postSettings, templateSrvStub as any, timeSrvStub as any); + promDs.metadataRequest('api/v1/series'); + expect(fetchMock.mock.calls.length).toBe(1); + expect(fetchMock.mock.calls[0][0].method).toBe('POST'); + }); }); describe('When using customQueryParams', () => { diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index 8a7f2ebc505..b740bceaadf 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -44,6 +44,7 @@ import { PrometheusVariableSupport } from './variables'; import PrometheusMetricFindQuery from './metric_find_query'; export const ANNOTATION_QUERY_STEP_DEFAULT = '60s'; +const GET_AND_POST_MEDATADATA_ENDPOINTS = ['api/v1/query', 'api/v1/query_range', 'api/v1/series', 'api/v1/labels']; export class PrometheusDatasource extends DataSourceApi { type: string; @@ -136,7 +137,7 @@ export class PrometheusDatasource extends DataSourceApi } // Use this for tab completion features, wont publish response to other components - metadataRequest(url: string) { + async metadataRequest(url: string) { const data: any = {}; for (const [key, value] of this.customQueryParameters) { if (data[key] == null) { @@ -144,7 +145,21 @@ export class PrometheusDatasource extends DataSourceApi } } - return this._request(url, data, { method: 'GET', hideFromInspector: true }).toPromise(); // toPromise until we change getTagValues, getTagKeys to Observable + // If URL includes endpoint that supports POST and GET method, try to use configured method. This might fail as POST is supported only in v2.10+. + if (GET_AND_POST_MEDATADATA_ENDPOINTS.some((endpoint) => url.includes(endpoint))) { + try { + return await this._request(url, data, { method: this.httpMethod, hideFromInspector: true }).toPromise(); + } catch (err) { + // If status code of error is Method Not Allowed (405) and HTTP method is POST, retry with GET + if (this.httpMethod === 'POST' && err.status === 405) { + console.warn(`Couldn't use configured POST HTTP method for this request. Trying to use GET method instead.`); + } else { + throw err; + } + } + } + + return await this._request(url, data, { method: 'GET', hideFromInspector: true }).toPromise(); // toPromise until we change getTagValues, getTagKeys to Observable } interpolateQueryExpr(value: string | string[] = [], variable: any) { diff --git a/scripts/lib.star b/scripts/lib.star index 65130309f60..5dc88260e5a 100644 --- a/scripts/lib.star +++ b/scripts/lib.star @@ -1,4 +1,4 @@ -grabpl_version = '0.5.38' +grabpl_version = '0.5.43' build_image = 'grafana/build-container:1.3.2' publish_image = 'grafana/grafana-ci-deploy:1.3.1' grafana_docker_image = 'grafana/drone-grafana-docker:0.3.2' diff --git a/scripts/tidy-check.sh b/scripts/tidy-check.sh deleted file mode 100755 index 61aec1c898c..00000000000 --- a/scripts/tidy-check.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash -set -eo pipefail - -# Verify that Go is properly installed and available -command -v go >/dev/null 2>&1 || { echo 'please install Go or use an image that has it'; exit 1; } - -backup_go_mod_files() -{ - mod=$(mktemp) - cp go.mod "$mod" - - sum=$(mktemp) - cp go.sum "$sum" -} - -restore_go_mod_files() -{ - cp "$mod" go.mod - rm "$mod" - - cp "$sum" go.sum - rm "$sum" -} - -# Backup current go.mod and go.sum files -backup_go_mod_files - -# Defer the go.mod and go.sum files backup recovery -trap restore_go_mod_files EXIT - -# Tidy go.mod and go.sum files -go mod tidy - -diff "$mod" go.mod || { echo "your go.mod is inconsistent"; exit 1; } -diff "$sum" go.sum || { echo "your go.sum is inconsistent"; exit 1; }