Compare commits

..

6 Commits

Author SHA1 Message Date
Guilherme Caulada
d2bfe3cc0b [v9.5.x] CI: Fix verify-prerelease-assets pipeline (#72231)
CI: Fix verify-prerelease-assets pipeline (#72226)

* Fix verify-prerelease-assets pipeline

* Comment out unused assets

(cherry picked from commit 142cbc2ec6)
2023-07-25 15:32:26 -03:00
Kevin Minehart
7bf8e4d461 [v9.5.x] CI: Run only Grafana builds on RGM for grafana/grafana (#72160)
* CI: Try setting the vault secret for 'grafana_api_key' (#71981)

* Try setting the vault secret

* Maybe?

* forgot .drone.yml

* CI: Use GCP keys in vault and not drone secrets (#72023)

* CI: Run only Grafana builds on RGM for grafana/grafana (#72144)

* Run only OSS builds on RGM for grafana/grafana

* Update script name

* Ignore windows step failure for RGM

---------

Co-authored-by: Guilherme Caulada <guilherme.caulada@grafana.com>
2023-07-25 15:32:26 -03:00
Guilherme Caulada
95f0deadc0 [v9.5.x] CI: Allow RGM to create msi for multiple editions (#71893)
CI: Allow RGM to create msi for multiple editions (#71883)

* Allow RGM to create msi for multiple editions

* Remove suffix from build-windows-installer step

* Invert exit code on verify_release_pipeline

(cherry picked from commit 4c80bcf35f)
2023-07-25 15:32:25 -03:00
Guilherme Caulada
08fac0d925 [v9.5.x] CI: Add rgm compatible windows msi pipeline (#71868)
CI: Add rgm compatible windows msi pipeline (#71678)

* Add rgm compatible windows msi pipeline

* Fix dependencies and platform

* Fix missing arg comment

* Fix linting issues

* Try to fix version on file path

* Fix dependency on verify release pipeline

(cherry picked from commit 4caa773401)
2023-07-25 15:32:25 -03:00
Guilherme Caulada
edaca16642 [v9.5.x] CI: Removes enterprise specific pipelines and steps (#71780)
[WIP] CI: Removes enterprise specific pipelines and steps (#70815)

* Removes enterprise specific pipelines and steps (#123)

* Comment out enterprise related pipelines and steps

* Suppress unused variable warning

* Removes all edition arguments

* Remove leftover comments

* Remove redundant oss on pipelines and steps names

* Remove leftover unused variable

* Remove leftovers

* Remove pipeline dependencies

* Rename pipelines

* Fix starlark

---------

Co-authored-by: dsotirakis <dimitrios.sotirakis@grafana.com>
(cherry picked from commit 642a81ba75e79138246797302aba5c35575f030d)

* Add editions for static assets

(cherry picked from commit b13939b9af)

Co-authored-by: Dimitris Sotirakis <dimitrios.sotirakis@grafana.com>
2023-07-25 15:32:25 -03:00
grafana-delivery-bot[bot]
d8fc9d0642 [v9.5.x] Alerting: Sort NumberCaptureValues in EvaluationString (#71930)
Alerting: Sort NumberCaptureValues in EvaluationString (#71927)

This commit changes extractEvalString to sort NumberCaptureValues
in ascending order of Var before building the output string. This
means that users will see EvaluationString in a consistent order,
but also make it possible to assert its output in tests.

(cherry picked from commit 89dcaaf049)

Co-authored-by: George Robinson <george.robinson@grafana.com>
2023-07-20 09:25:50 +02:00
333 changed files with 14950 additions and 8273 deletions

View File

@@ -1,4 +0,0 @@
aks
eror
iam
wan

View File

@@ -7,49 +7,57 @@
This module returns a Drone configuration including pipelines and secrets.
"""
load("scripts/drone/events/cron.star", "cronjobs")
load("scripts/drone/events/main.star", "main_pipelines")
load("scripts/drone/events/pr.star", "pr_pipelines")
load("scripts/drone/events/main.star", "main_pipelines")
load(
"scripts/drone/events/release.star",
"integration_test_pipelines",
"oss_pipelines",
"publish_artifacts_pipelines",
"publish_npm_pipelines",
"publish_packages_pipeline",
"verify_release_pipeline",
)
load(
"scripts/drone/pipelines/ci_images.star",
"publish_ci_windows_test_image_pipeline",
"scripts/drone/rgm.star",
"rgm",
)
load(
"scripts/drone/pipelines/publish_images.star",
"publish_image_pipelines_public",
)
load(
"scripts/drone/pipelines/ci_images.star",
"publish_ci_build_container_image_pipeline",
"publish_ci_windows_test_image_pipeline",
)
load(
"scripts/drone/pipelines/windows.star",
"windows_test_backend",
)
load(
"scripts/drone/rgm.star",
"rgm",
)
load("scripts/drone/version.star", "version_branch_pipelines")
load("scripts/drone/events/cron.star", "cronjobs")
load("scripts/drone/vault.star", "secrets")
def main(_ctx):
return (
pr_pipelines() +
main_pipelines() +
oss_pipelines() +
publish_image_pipelines_public() +
publish_artifacts_pipelines("public") +
publish_npm_pipelines() +
publish_packages_pipeline() +
rgm() +
[verify_release_pipeline()] +
[windows_test_backend({
"event": ["promote"],
"target": ["test-windows"],
}, "oss", "testing")] +
rgm() +
version_branch_pipelines() +
integration_test_pipelines() +
publish_ci_windows_test_image_pipeline() +
publish_ci_build_container_image_pipeline() +
cronjobs() +
secrets()
)

3452
.drone.yml

File diff suppressed because it is too large Load Diff

2
.github/CODEOWNERS vendored
View File

@@ -605,7 +605,7 @@ embed.go @grafana/grafana-as-code
/.github/workflows/stale.yml @grafana/grafana-frontend-platform
/.github/workflows/update-changelog.yml @grafana/grafana-delivery
/.github/workflows/snyk.yml @grafana/security-team
/.github/workflows/create-security-patch-from-security-mirror.yml @grafana/grafana-delivery
# Conf
/conf/defaults.ini @torkelo

View File

@@ -2,8 +2,6 @@ name: Auto-milestone
on:
pull_request:
types:
- opened
- reopened
- closed
jobs:

View File

@@ -44,7 +44,7 @@ jobs:
name: Set go version
uses: actions/setup-go@v3
with:
go-version: '1.21.5'
go-version: '1.20.6'
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL

View File

@@ -1,28 +0,0 @@
# Owned by grafana-delivery-squad
# Intended to be dropped into the base repo (Ex: grafana/grafana) for use in the security mirror.
name: Create security patch
run-name: create-security-patch
on:
pull_request:
types:
- opened
- reopened
- synchronize
branches:
- "main"
- "v*.*.*"
# This is run before the pull request has been merged, so we'll run against the src branch
jobs:
trigger_downstream_create_security_patch:
concurrency: create-patch-${{ github.ref_name }}
uses: grafana/security-patch-actions/.github/workflows/create-patch.yml@main
if: github.repository == 'grafana/grafana-security-mirror'
with:
repo: "${{ github.repository }}"
src_ref: "${{ github.head_ref }}" # this is the source branch name, Ex: "feature/newthing"
patch_ref: "${{ github.base_ref }}" # this is the target branch name, Ex: "main"
patch_repo: "grafana/grafana-security-patches"
patch_prefix: "${{ github.event.pull_request.number }}"
secrets: inherit

View File

@@ -23,7 +23,7 @@ jobs:
- name: Set go version
uses: actions/setup-go@v3
with:
go-version: '1.21.5'
go-version: '1.20.6'
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL

View File

@@ -3,11 +3,9 @@
name: Check for patch conflicts
run-name: check-patch-conflicts-${{ github.base_ref }}-${{ github.head_ref }}
on:
pull_request:
pull_request_target:
types:
- opened
- reopened
- synchronize
branches:
- "main"
- "v*.*.*"

View File

@@ -1,7 +1,7 @@
# Owned by grafana-delivery-squad
# Intended to be dropped into the base repo, Ex: grafana/grafana
name: Sync to mirror
run-name: sync-to-mirror-${{ github.ref_name }}
run-name: sync-to-mirror-${{ github.base_ref }}-${{ github.head_ref }}
on:
workflow_dispatch:
push:
@@ -13,7 +13,7 @@ on:
# This is run after the pull request has been merged, so we'll run against the target branch
jobs:
trigger_downstream_patch_mirror:
concurrency: patch-mirror-${{ github.ref_name }}
concurrency: patch-mirror-${{ github.ref }}
uses: grafana/security-patch-actions/.github/workflows/mirror-branch-and-apply-patches.yml@main
if: github.repository == 'grafana/grafana'
with:

View File

@@ -80,7 +80,7 @@ var config = {
{
url: '${HOST}/?orgId=1',
wait: 500,
threshold: 3,
threshold: 0,
},
{
url: '${HOST}/d/O6f11TZWk/panel-tests-bar-gauge',
@@ -93,7 +93,7 @@ var config = {
url: '${HOST}/?orgId=1&search=open',
wait: 500,
rootElement: '.main-view',
threshold: 3,
threshold: 0,
},
{
url: '${HOST}/alerting/list',

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
# syntax=docker/dockerfile:1
ARG BASE_IMAGE=alpine:3.18.3
ARG JS_IMAGE=node:18-alpine3.18
ARG BASE_IMAGE=alpine:3.17
ARG JS_IMAGE=node:18-alpine3.17
ARG JS_PLATFORM=linux/amd64
ARG GO_IMAGE=golang:1.21.5-alpine3.18
ARG GO_IMAGE=golang:1.20.6-alpine3.17
ARG GO_SRC=go-builder
ARG JS_SRC=js-builder
@@ -64,7 +64,6 @@ COPY pkg pkg
COPY scripts scripts
COPY conf conf
COPY .github .github
COPY LICENSE ./
ENV COMMIT_SHA=${COMMIT_SHA}
ENV BUILD_BRANCH=${BUILD_BRANCH}
@@ -111,7 +110,7 @@ RUN if grep -i -q alpine /etc/issue; then \
elif grep -i -q ubuntu /etc/issue; then \
DEBIAN_FRONTEND=noninteractive && \
apt-get update && \
apt-get install -y ca-certificates curl tzdata musl && \
apt-get install -y ca-certificates curl tzdata && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/*; \
else \
@@ -166,7 +165,6 @@ RUN if [ ! $(getent group "$GF_GID") ]; then \
COPY --from=go-src /tmp/grafana/bin/grafana* /tmp/grafana/bin/*/grafana* ./bin/
COPY --from=js-src /tmp/grafana/public ./public
COPY --from=go-src /tmp/grafana/LICENSE ./
EXPOSE 3000

View File

@@ -13,12 +13,11 @@ GO = go
GO_FILES ?= ./pkg/...
SH_FILES ?= $(shell find ./scripts -name *.sh)
GO_BUILD_FLAGS += $(if $(GO_BUILD_DEV),-dev)
GO_BUILD_FLAGS += $(if $(GO_BUILD_DEV),-dev)
GO_BUILD_FLAGS += $(if $(GO_BUILD_TAGS),-build-tags=$(GO_BUILD_TAGS))
targets := $(shell echo '$(sources)' | tr "," " ")
GO_INTEGRATION_TESTS := $(shell find ./pkg -type f -name '*_test.go' -exec grep -l '^func TestIntegration' '{}' '+' | grep -o '\(.*\)/' | sort -u)
all: deps build
##@ Dependencies
@@ -36,68 +35,36 @@ node_modules: package.json yarn.lock ## Install node modules.
##@ Swagger
SPEC_TARGET = public/api-spec.json
ENTERPRISE_SPEC_TARGET = public/api-enterprise-spec.json
MERGED_SPEC_TARGET = public/api-merged.json
MERGED_SPEC_TARGET := public/api-merged.json
NGALERT_SPEC_TARGET = pkg/services/ngalert/api/tooling/api.json
$(NGALERT_SPEC_TARGET):
+$(MAKE) -C pkg/services/ngalert/api/tooling api.json
$(MERGED_SPEC_TARGET): swagger-oss-gen swagger-enterprise-gen $(NGALERT_SPEC_TARGET) $(SWAGGER) ## Merge generated and ngalert API specs
$(MERGED_SPEC_TARGET): $(SPEC_TARGET) $(NGALERT_SPEC_TARGET) $(SWAGGER) ## Merge generated and ngalert API specs
# known conflicts DsPermissionType, AddApiKeyCommand, Json, Duration (identical models referenced by both specs)
$(SWAGGER) mixin $(SPEC_TARGET) $(ENTERPRISE_SPEC_TARGET) $(NGALERT_SPEC_TARGET) --ignore-conflicts -o $(MERGED_SPEC_TARGET)
$(SWAGGER) mixin $(SPEC_TARGET) $(NGALERT_SPEC_TARGET) --ignore-conflicts -o $(MERGED_SPEC_TARGET)
swagger-oss-gen: $(SWAGGER) ## Generate API Swagger specification
@echo "re-generating swagger for OSS"
rm -f $(SPEC_TARGET)
$(SPEC_TARGET): $(SWAGGER) ## Generate API Swagger specification
SWAGGER_GENERATE_EXTENSION=false $(SWAGGER) generate spec -m -w pkg/server -o $(SPEC_TARGET) \
-x "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" \
-x "github.com/prometheus/alertmanager" \
-i pkg/api/swagger_tags.json \
--exclude-tag=alpha \
--exclude-tag=enterprise
--exclude-tag=alpha
go run pkg/services/ngalert/api/tooling/cmd/clean-swagger/main.go -if $@ -of $@
# this file only exists if enterprise is enabled
ENTERPRISE_EXT_FILE = pkg/extensions/ext.go
ifeq ("$(wildcard $(ENTERPRISE_EXT_FILE))","") ## if enterprise is not enabled
swagger-enterprise-gen:
@echo "skipping re-generating swagger for enterprise: not enabled"
else
swagger-enterprise-gen: $(SWAGGER) ## Generate API Swagger specification
@echo "re-generating swagger for enterprise"
rm -f $(ENTERPRISE_SPEC_TARGET)
SWAGGER_GENERATE_EXTENSION=false $(SWAGGER) generate spec -m -w pkg/server -o $(ENTERPRISE_SPEC_TARGET) \
-x "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" \
-x "github.com/prometheus/alertmanager" \
-i pkg/api/swagger_tags.json \
--exclude-tag=alpha \
--include-tag=enterprise
endif
swagger-api-spec: gen-go $(SPEC_TARGET) $(MERGED_SPEC_TARGET) validate-api-spec
swagger-gen: gen-go $(MERGED_SPEC_TARGET) swagger-validate
swagger-validate: $(MERGED_SPEC_TARGET) $(SWAGGER) ## Validate API spec
validate-api-spec: $(MERGED_SPEC_TARGET) $(SWAGGER) ## Validate API spec
$(SWAGGER) validate $(<)
swagger-clean:
clean-api-spec:
rm -f $(SPEC_TARGET) $(MERGED_SPEC_TARGET) $(OAPI_SPEC_TARGET)
.PHONY: cleanup-old-git-hooks
cleanup-old-git-hooks:
./scripts/cleanup-husky.sh
.PHONY: lefthook-install
lefthook-install: cleanup-old-git-hooks $(LEFTHOOK) # install lefthook for pre-commit hooks
$(LEFTHOOK) install -f
.PHONY: lefthook-uninstall
lefthook-uninstall: $(LEFTHOOK)
$(LEFTHOOK) uninstall
##@ OpenAPI 3
OAPI_SPEC_TARGET = public/openapi3.json
openapi3-gen: swagger-gen ## Generates OpenApi 3 specs from the Swagger 2 already generated
openapi3-gen: swagger-api-spec ## Generates OpenApi 3 specs from the Swagger 2 already generated
$(GO) run scripts/openapi3/openapi3conv.go $(MERGED_SPEC_TARGET) $(OAPI_SPEC_TARGET)
##@ Building
@@ -108,7 +75,7 @@ gen-cue: ## Do all CUE/Thema code generation
go generate ./public/app/plugins/gen.go
go generate ./pkg/kindsys/report.go
gen-go: $(WIRE)
gen-go: $(WIRE) gen-cue
@echo "generate go files"
$(WIRE) gen -tags $(WIRE_TAGS) ./pkg/server
@@ -162,39 +129,19 @@ test-go-unit: ## Run unit tests for backend with flags.
.PHONY: test-go-integration
test-go-integration: ## Run integration tests for backend with flags.
@echo "test backend integration tests"
$(GO) test -count=1 -run "^TestIntegration" -covermode=atomic -timeout=5m $(GO_INTEGRATION_TESTS)
.PHONY: test-go-integration-alertmanager
test-go-integration-alertmanager: ## Run integration tests for the remote alertmanager (config taken from the mimir_backend block).
@echo "test remote alertmanager integration tests"
$(GO) clean -testcache
AM_URL=http://localhost:8080 AM_TENANT_ID=test AM_PASSWORD=test \
$(GO) test -count=1 -run "^TestIntegrationRemoteAlertmanager" -covermode=atomic -timeout=5m ./pkg/services/ngalert/notifier/...
$(GO) test -run Integration -covermode=atomic -timeout=30m ./pkg/...
.PHONY: test-go-integration-postgres
test-go-integration-postgres: devenv-postgres ## Run integration tests for postgres backend with flags.
@echo "test backend integration postgres tests"
$(GO) clean -testcache
GRAFANA_TEST_DB=postgres \
$(GO) test -p=1 -count=1 -run "^TestIntegration" -covermode=atomic -timeout=10m $(GO_INTEGRATION_TESTS)
$(GO) list './pkg/...' | xargs -I {} sh -c 'GRAFANA_TEST_DB=postgres go test -run Integration -covermode=atomic -timeout=2m {}'
.PHONY: test-go-integration-mysql
test-go-integration-mysql: devenv-mysql ## Run integration tests for mysql backend with flags.
@echo "test backend integration mysql tests"
GRAFANA_TEST_DB=mysql \
$(GO) test -p=1 -count=1 -run "^TestIntegration" -covermode=atomic -timeout=10m $(GO_INTEGRATION_TESTS)
.PHONY: test-go-integration-redis
test-go-integration-redis: ## Run integration tests for redis cache.
@echo "test backend integration redis tests"
$(GO) clean -testcache
REDIS_URL=localhost:6379 $(GO) test -run IntegrationRedis -covermode=atomic -timeout=2m $(GO_INTEGRATION_TESTS)
.PHONY: test-go-integration-memcached
test-go-integration-memcached: ## Run integration tests for memcached cache.
@echo "test backend integration memcached tests"
$(GO) clean -testcache
MEMCACHED_HOSTS=localhost:11211 $(GO) test -run IntegrationMemcached -covermode=atomic -timeout=2m $(GO_INTEGRATION_TESTS)
$(GO) list './pkg/...' | xargs -I {} sh -c 'GRAFANA_TEST_DB=mysql go test -run Integration -covermode=atomic -timeout=2m {}'
test-js: ## Run tests for frontend.
@echo "test frontend"
@@ -229,7 +176,7 @@ build-docker-full: ## Build Docker image for development.
--build-arg BINGO=false \
--build-arg GO_BUILD_TAGS=$(GO_BUILD_TAGS) \
--build-arg WIRE_TAGS=$(WIRE_TAGS) \
--build-arg COMMIT_SHA=$$(git rev-parse HEAD) \
--build-arg COMMIT_SHA=$$(git rev-parse --short HEAD) \
--build-arg BUILD_BRANCH=$$(git rev-parse --abbrev-ref HEAD) \
--tag grafana/grafana$(TAG_SUFFIX):dev \
$(DOCKER_BUILD_ARGS)
@@ -242,10 +189,10 @@ build-docker-full-ubuntu: ## Build Docker image based on Ubuntu for development.
--build-arg BINGO=false \
--build-arg GO_BUILD_TAGS=$(GO_BUILD_TAGS) \
--build-arg WIRE_TAGS=$(WIRE_TAGS) \
--build-arg COMMIT_SHA=$$(git rev-parse HEAD) \
--build-arg COMMIT_SHA=$$(git rev-parse --short HEAD) \
--build-arg BUILD_BRANCH=$$(git rev-parse --abbrev-ref HEAD) \
--build-arg BASE_IMAGE=ubuntu:22.04 \
--build-arg GO_IMAGE=golang:1.21.5 \
--build-arg BASE_IMAGE=ubuntu:20.04 \
--build-arg GO_IMAGE=golang:1.20.6 \
--tag grafana/grafana$(TAG_SUFFIX):dev-ubuntu \
$(DOCKER_BUILD_ARGS)
@@ -290,9 +237,6 @@ devenv-mysql:
protobuf: ## Compile protobuf definitions
bash scripts/protobuf-check.sh
bash pkg/plugins/backendplugin/pluginextensionv2/generate.sh
bash pkg/plugins/backendplugin/secretsmanagerplugin/generate.sh
bash pkg/services/store/entity/generate.sh
bash pkg/infra/grn/generate.sh
clean: ## Clean up intermediate build artifacts.
@echo "cleaning"
@@ -318,7 +262,7 @@ scripts/drone/TAGS: $(shell find scripts/drone -name '*.star')
etags --lang none --regex="/def \(\w+\)[^:]+:/\1/" --regex="/\s*\(\w+\) =/\1/" $^ -o $@
format-drone:
buildifier --lint=fix -r scripts/drone
buildifier -r scripts/drone
help: ## Display this help.
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

View File

@@ -534,17 +534,6 @@ azure_auth_enabled = false
# Use email lookup in addition to the unique ID provided by the IdP
oauth_allow_insecure_email_lookup = false
# Set to true to include id of identity as a response header
id_response_header_enabled = false
# Prefix used for the id response header, X-Grafana-Identity-Id
id_response_header_prefix = X-Grafana
# List of identity namespaces to add id response headers for, separated by space.
# Available namespaces are user, api-key and service-account.
# The header value will encode the namespace ("user:<id>", "api-key:<id>", "service-account:<id>")
id_response_header_namespaces = user api-key service-account
#################################### Anonymous Auth ######################
[auth.anonymous]
# enable anonymous access
@@ -794,24 +783,6 @@ managed_identity_enabled = false
# Should be set for user-assigned identity and should be empty for system-assigned identity
managed_identity_client_id =
# Specifies whether Azure AD Workload Identity authentication should be enabled in datasources that support it
# For more documentation on Azure AD Workload Identity, review this documentation:
# https://azure.github.io/azure-workload-identity/docs/
# Disabled by default, needs to be explicitly enabled
workload_identity_enabled = false
# Tenant ID of the Azure AD Workload Identity
# Allows to override default tenant ID of the Azure AD identity associated with the Kubernetes service account
workload_identity_tenant_id =
# Client ID of the Azure AD Workload Identity
# Allows to override default client ID of the Azure AD identity associated with the Kubernetes service account
workload_identity_client_id =
# Custom path to token file for the Azure AD Workload Identity
# Allows to set a custom path to the projected service account token file
workload_identity_token_file =
#################################### Role-based Access Control ###########
[rbac]
# If enabled, cache permissions in a in memory cache
@@ -1027,18 +998,13 @@ execute_alerts = true
# The timeout string is a possibly signed sequence of decimal numbers, followed by a unit suffix (ms, s, m, h, d), e.g. 30s or 1m.
evaluation_timeout = 30s
# Number of times we'll attempt to evaluate an alert rule before giving up on that evaluation. The default value is 1.
max_attempts = 1
# Number of times we'll attempt to evaluate an alert rule before giving up on that evaluation. This option has a legacy version in the `[alerting]` section that takes precedence.
max_attempts = 3
# Minimum interval to enforce between rule evaluations. Rules will be adjusted if they are less than this value or if they are not multiple of the scheduler interval (10s). Higher values can help with resource management as we'll schedule fewer evaluations over time. This option has a legacy version in the `[alerting]` section that takes precedence.
# The interval string is a possibly signed sequence of decimal numbers, followed by a unit suffix (ms, s, m, h, d), e.g. 30s or 1m.
min_interval = 10s
# This is an experimental option to add parallelization to saving alert states in the database.
# It configures the maximum number of concurrent queries per rule evaluated. The default value is 1
# (concurrent queries per rule disabled).
max_state_save_concurrency = 1
[unified_alerting.screenshots]
# Enable screenshots in notifications. You must have either installed the Grafana image rendering
# plugin, or set up Grafana to use a remote rendering service.

View File

@@ -523,17 +523,6 @@
# Use email lookup in addition to the unique ID provided by the IdP
;oauth_allow_insecure_email_lookup = false
# Set to true to include id of identity as a response header
;id_response_header_enabled = false
# Prefix used for the id response header, X-Grafana-Identity-Id
;id_response_header_prefix = X-Grafana
# List of identity namespaces to add id response headers for, separated by space.
# Available namespaces are user, api-key and service-account.
# The header value will encode the namespace ("user:<id>", "api-key:<id>", "service-account:<id>")
;id_response_header_namespaces = user api-key service-account
#################################### Anonymous Auth ######################
[auth.anonymous]
# enable anonymous access
@@ -765,24 +754,6 @@
# Should be set for user-assigned identity and should be empty for system-assigned identity
;managed_identity_client_id =
# Specifies whether Azure AD Workload Identity authentication should be enabled in datasources that support it
# For more documentation on Azure AD Workload Identity, review this documentation:
# https://azure.github.io/azure-workload-identity/docs/
# Disabled by default, needs to be explicitly enabled
;workload_identity_enabled = false
# Tenant ID of the Azure AD Workload Identity
# Allows to override default tenant ID of the Azure AD identity associated with the Kubernetes service account
;workload_identity_tenant_id =
# Client ID of the Azure AD Workload Identity
# Allows to override default client ID of the Azure AD identity associated with the Kubernetes service account
;workload_identity_client_id =
# Custom path to token file for the Azure AD Workload Identity
# Allows to set a custom path to the projected service account token file
;workload_identity_token_file =
#################################### Role-based Access Control ###########
[rbac]
;permission_cache = true
@@ -996,8 +967,8 @@
# The timeout string is a possibly signed sequence of decimal numbers, followed by a unit suffix (ms, s, m, h, d), e.g. 30s or 1m.
;evaluation_timeout = 30s
# Number of times we'll attempt to evaluate an alert rule before giving up on that evaluation. The default value is 1.
;max_attempts = 1
# Number of times we'll attempt to evaluate an alert rule before giving up on that evaluation. This option has a legacy version in the `[alerting]` section that takes precedence.
;max_attempts = 3
# Minimum interval to enforce between rule evaluations. Rules will be adjusted if they are less than this value or if they are not multiple of the scheduler interval (10s). Higher values can help with resource management as we'll schedule fewer evaluations over time. This option has a legacy version in the `[alerting]` section that takes precedence.
# The interval string is a possibly signed sequence of decimal numbers, followed by a unit suffix (ms, s, m, h, d), e.g. 30s or 1m.

View File

@@ -1,22 +0,0 @@
# Upgrading Go Version
Notes on upgrading Go version.
Example PR: https://github.com/grafana/grafana/pull/79329
## The main areas that need to change during the upgrade are:
- https://github.com/grafana/grafana/blob/d8ecea4ed93efb2e4d64a5ee24bc08f3805f413d/scripts/drone/variables.star#L6
- https://github.com/grafana/grafana/blob/d8ecea4ed93efb2e4d64a5ee24bc08f3805f413d/Makefile#L264
- https://github.com/grafana/grafana/blob/d8ecea4ed93efb2e4d64a5ee24bc08f3805f413d/Dockerfile#L6
Make sure to run `make drone` so that changes to `.star` files are reflected and `drone.yml` is generated.
### Additional files to change
- Take a look in `.github/workflows` folder for what `go` version is being used there in various workflows.
- Make sure to create a PR with the corresponding changes in `grafana/grafana-enterprise` repository.
## Updating the go.mod file
Please avoid updating the `go.mod` to the newest version unless really necessary. This ensures backwards compatibility and introduces less breaking changes. Always upgrade Go version in the runtime files above first, let them run for a couple of weeks and only then consider updating the `go.mod` file if necessary.

View File

@@ -1,5 +1,5 @@
# The source of this file is https://raw.githubusercontent.com/grafana/writers-toolkit/main/docs/docs.mk.
# A changelog is included in the head of the `make-docs` script.
# 4.0.0 (2023-06-06)
include variables.mk
-include variables.mk.local
@@ -76,11 +76,11 @@ docs-rm: ## Remove the docs container.
.PHONY: docs-pull
docs-pull: ## Pull documentation base image.
$(PODMAN) pull -q $(DOCS_IMAGE)
$(PODMAN) pull $(DOCS_IMAGE)
make-docs: ## Fetch the latest make-docs script.
make-docs:
if [[ ! -f "$(CURDIR)/make-docs" ]]; then
if [[ ! -f "$(PWD)/make-docs" ]]; then
echo 'WARN: No make-docs script found in the working directory. Run `make update` to download it.' >&2
exit 1
fi
@@ -88,27 +88,27 @@ make-docs:
.PHONY: docs
docs: ## Serve documentation locally, which includes pulling the latest `DOCS_IMAGE` (default: `grafana/docs-base:latest`) container image. See also `docs-no-pull`.
docs: docs-pull make-docs
$(CURDIR)/make-docs $(PROJECTS)
$(PWD)/make-docs $(PROJECTS)
.PHONY: docs-no-pull
docs-no-pull: ## Serve documentation locally without pulling the `DOCS_IMAGE` (default: `grafana/docs-base:latest`) container image.
docs-no-pull: make-docs
$(CURDIR)/make-docs $(PROJECTS)
$(PWD)/make-docs $(PROJECTS)
.PHONY: docs-debug
docs-debug: ## Run Hugo web server with debugging enabled. TODO: support all SERVER_FLAGS defined in website Makefile.
docs-debug: make-docs
WEBSITE_EXEC='hugo server --bind 0.0.0.0 --port 3002 --debug' $(CURDIR)/make-docs $(PROJECTS)
WEBSITE_EXEC='hugo server --bind 0.0.0.0 --port 3002 --debug' $(PWD)/make-docs $(PROJECTS)
.PHONY: doc-validator
doc-validator: ## Run doc-validator on the entire docs folder.
doc-validator: make-docs
DOCS_IMAGE=$(DOC_VALIDATOR_IMAGE) $(CURDIR)/make-docs $(PROJECTS)
DOCS_IMAGE=$(DOC_VALIDATOR_IMAGE) $(PWD)/make-docs $(PROJECTS)
.PHONY: vale
vale: ## Run vale on the entire docs folder.
vale: make-docs
DOCS_IMAGE=$(VALE_IMAGE) $(CURDIR)/make-docs $(PROJECTS)
DOCS_IMAGE=$(VALE_IMAGE) $(PWD)/make-docs $(PROJECTS)
.PHONY: update
update: ## Fetch the latest version of this Makefile and the `make-docs` script from Writers' Toolkit.

View File

@@ -1,133 +1,6 @@
#!/bin/sh
# The source of this file is https://raw.githubusercontent.com/grafana/writers-toolkit/main/docs/make-docs.
# # `make-docs` procedure changelog
#
# Updates should conform to the guidelines in https://keepachangelog.com/en/1.1.0/.
# [Semantic versioning](https://semver.org/) is used to help the reader identify the significance of changes.
# Changes are relevant to this script and the support docs.mk GNU Make interface.
# ## 4.2.1 (2023-09-13)
# ## Fixed
# - Improved consistency of the webserver request loop by polling the Hugo port rather than the proxy port.
# ## 4.2.0 (2023-09-01)
# ### Added
# - Retry the initial webserver request up to ten times to allow for the process to start.
# If it is still failing after ten seconds, an error message is logged.
# ## 4.1.1 (2023-07-20)
# ### Fixed
# - Replaced use of `realpath` with POSIX compatible alternative to determine default value for REPOS_PATH.
# ## 4.1.0 (2023-06-16)
# ### Added
# - Mounts of `layouts` and `config` directories for the `website` project.
# Ensures that local changes to mounts or shortcodes are reflected in the development server.
# ### Fixed
# - Version inference for versioned docs pages.
# Pages in versioned projects now have the `versioned: true` front matter set to ensure that "version" in $.Page.Scratch is set on builds.
# ## 4.0.0 (2023-06-06)
# ### Removed
# - `doc-validator/%` target.
# The behavior of the target was not as described.
# Instead, to limit `doc-validator` to only specific files, refer to https://grafana.com/docs/writers-toolkit/writing-guide/tooling-and-workflows/validate-technical-documentation/#run-on-specific-files.
# ## 3.0.0 (2023-05-18)
# ### Fixed
# - Compatibility with the updated Make targets in the `website` repository.
# `docs` now runs this script itself, `server-docs` builds the site with the `docs` Hugo environment.
# ## 2.0.0 (2023-05-18)
# ### Added
# - Support for the grafana-cloud/frontend-observability/faro-web-sdk project.
# - Use of `doc-validator` v2.0.x which includes breaking changes to command line options.
# ### Fixed
# - Source grafana-cloud project from website repository.
# ### Added
# - Support for running the Vale linter with `make vale`.
# ## 1.2.1 (2023-05-05)
# ### Fixed
# - Use `latest` tag of `grafana/vale` image by default instead of hardcoded older version.
# - Fix mounting multiple projects broken by the changes in 1.0.1
# ## 1.2.0 (2023-05-05)
# ### Added
# - Support for running the Vale linter with `make vale`.
# ### Fixed
# ## 1.1.0 (2023-05-05)
# ### Added
# - Rewrite error output so it can be followed by text editors.
# ### Fixed
# - Fix `docs-debug` container process port.
# ## 1.0.1 (2023-05-04)
# ### Fixed
# - Ensure complete section hierarchy so that all projects have a visible menu.
# ## 1.0.0 (2023-05-04)
# ### Added
# - Build multiple projects simultaneously if all projects are checked out locally.
# - Run [`doc-validator`](https://github.com/grafana/technical-documentation/tree/main/tools/cmd/doc-validator) over projects.
# - Redirect project root to mounted version.
# For example redirect `/docs/grafana/` to `/docs/grafana/latest/`.
# - Support for Podman or Docker containers with `PODMAN` environment variable.
# - Support for projects:
# - agent
# - enterprise-logs
# - enterprise-metrics
# - enterprise-traces
# - grafana
# - grafana-cloud
# - grafana-cloud/machine-learning
# - helm-charts/mimir-distributed
# - helm-charts/tempo-distributed
# - incident
# - loki
# - mimir
# - oncall
# - opentelemetry
# - phlare
# - plugins
# - slo
# - tempo
# - writers-toolkit
# 4.1.0 (2023-06-16)
set -ef
@@ -146,21 +19,6 @@ readonly WEBSITE_MOUNTS="${WEBSITE_MOUNTS:-}"
PODMAN="$(if command -v podman >/dev/null 2>&1; then echo podman; else echo docker; fi)"
if ! command -v curl >/dev/null 2>&1; then
if ! command -v wget >/dev/null 2>&1; then
errr 'either `curl` or `wget` must be installed for this script to work.'
exit 1
fi
fi
if ! command -v "${PODMAN}" >/dev/null 2>&1; then
errr 'either `podman` or `docker` must be installed for this script to work.'
exit 1
fi
about() {
cat <<EOF
Test documentation locally with multiple source repositories.
@@ -190,7 +48,7 @@ EOF
exit 1
fi
readonly REPOS_PATH="${REPOS_PATH:-$(cd "$(git rev-parse --show-toplevel)/.." && echo "${PWD}")}"
readonly REPOS_PATH="${REPOS_PATH:-$(realpath "$(git rev-parse --show-toplevel)/..")}"
if [ -z "${REPOS_PATH}" ]; then
cat <<EOF >&2
@@ -205,23 +63,20 @@ SOURCES_as_code='as-code-docs'
SOURCES_enterprise_metrics='backend-enterprise'
SOURCES_enterprise_metrics_='backend-enterprise'
SOURCES_grafana_cloud='website'
SOURCES_grafana_cloud_alerting_and_irm_machine_learning='machine-learning'
SOURCES_grafana_cloud_alerting_and_irm_slo='slo'
SOURCES_grafana_cloud_k6='k6-docs'
SOURCES_grafana_cloud_data_configuration_integrations='cloud-onboarding'
SOURCES_grafana_cloud_frontend_observability_faro_web_sdk='faro-web-sdk'
SOURCES_grafana_cloud_machine_learning='machine-learning'
SOURCES_helm_charts_mimir_distributed='mimir'
SOURCES_helm_charts_tempo_distributed='tempo'
SOURCES_opentelemetry='opentelemetry-docs'
SOURCES_plugins_grafana_splunk_datasource='splunk-datasource'
VERSIONS_as_code='UNVERSIONED'
VERSIONS_grafana_cloud='UNVERSIONED'
VERSIONS_grafana_cloud_alerting_and_irm_machine_learning='UNVERSIONED'
VERSIONS_grafana_cloud_alerting_and_irm_slo='UNVERSIONED'
VERSIONS_grafana_cloud_k6='UNVERSIONED'
VERSIONS_grafana_cloud_data_configuration_integrations='UNVERSIONED'
VERSIONS_grafana_cloud_frontend_observability_faro_web_sdk='UNVERSIONED'
VERSIONS_grafana_cloud_machine_learning='UNVERSIONED'
VERSIONS_opentelemetry='UNVERSIONED'
VERSIONS_technical_documentation='UNVERSIONED'
VERSIONS_website='UNVERSIONED'
@@ -359,10 +214,9 @@ repo_path() {
done
unset IFS
errr "could not find project '${_repo}' in any of the paths in REPOS_PATH '${REPOS_PATH}'."
note "you must have a checkout of the project '${_repo}' at '${REPOS_PATH##:*}/${_repo}'."
note "if you have cloned the repository into a directory with a different name, consider changing it to ${_repo}."
echo "ERRR: could not find project '${_repo}' in any of the paths in REPOS_PATH '${REPOS_PATH}'." >&2
echo "NOTE: you must have a checkout of the project '${_repo}' at '${REPOS_PATH##:*}/${_repo}'." >&2
echo "NOTE: if you have cloned the repository into a directory with a different name, consider changing it to ${_repo}." >&2
unset _repo
exit 1
}
@@ -448,61 +302,6 @@ POSIX_HERESTRING
unset _project _version _repo _path
}
await_build() {
url="$1"
req="$(if command -v curl >/dev/null 2>&1; then echo 'curl -s -o /dev/null'; else echo 'wget -q'; fi)"
i=1
max=10
while [ "${i}" -ne "${max}" ]
do
sleep 1
debg "Retrying request to webserver assuming the process is still starting up."
i=$((i + 1))
if ${req} "${url}"; then
echo
echo "View documentation locally:"
for x in ${url_src_dst_vers}; do
IFS='^' read -r url _ _ <<POSIX_HERESTRING
$x
POSIX_HERESTRING
if [ -n "${url}" ]; then
if [ "${_url}" != "arbitrary" ]; then
echo " ${url}"
fi
fi
done
echo
echo 'Press Ctrl+c to stop the server'
unset i max req url
return
fi
done
echo
errr 'The build was interrupted or a build error occurred, check the previous logs for possible causes.'
note 'You might need to use Ctrl+c to end the process.'
unset i max req url
}
debg() {
if [ -n "${DEBUG}" ]; then
echo "DEBG: $1" >&2
fi
}
errr() {
echo "ERRR: $1" >&2
}
note() {
echo "NOTE: $1" >&2
}
url_src_dst_vers="$(url_src_dst_vers "$@")"
volumes=""
@@ -528,14 +327,13 @@ POSIX_HERESTRING
if [ "${_url}" != "arbitrary" ]; then
if [ ! -f "${_src}/_index.md" ]; then
errr "Index file '${_src}/_index.md' does not exist."
note "Is '${_src}' the correct source directory?"
echo "ERRR: Index file '${_src}/_index.md' does not exist." >&2
echo "Is '${_src}' the correct source directory?" >&2
exit 1
fi
fi
debg "DEBG: Mounting '${_src}' at container path '${_dst}'"
echo "DEBG: Mounting '${_src}' at container path '${_dst}'" >&2
if [ -z "${volumes}" ]; then
volumes="--volume=${_src}:${_dst}"
else
@@ -561,35 +359,35 @@ case "${image}" in
proj="$(new_proj "$1")"
echo
"${PODMAN}" run \
--init \
--interactive \
--name "${DOCS_CONTAINER}" \
--platform linux/amd64 \
--rm \
--tty \
${volumes} \
"${DOCS_IMAGE}" \
"--include=${DOC_VALIDATOR_INCLUDE}" \
"--skip-checks=${DOC_VALIDATOR_SKIP_CHECKS}" \
/hugo/content/docs \
"$(proj_canonical "${proj}")" | sed "s#$(proj_dst "${proj}")#sources#"
--init \
--interactive \
--name "${DOCS_CONTAINER}" \
--platform linux/amd64 \
--rm \
--tty \
${volumes} \
"${DOCS_IMAGE}" \
"--include=${DOC_VALIDATOR_INCLUDE}" \
"--skip-checks=${DOC_VALIDATOR_SKIP_CHECKS}" \
/hugo/content/docs \
"$(proj_canonical "${proj}")" | sed "s#$(proj_dst "${proj}")#sources#"
;;
'grafana/vale')
proj="$(new_proj "$1")"
echo
"${PODMAN}" run \
--init \
--interactive \
--name "${DOCS_CONTAINER}" \
--platform linux/amd64 \
--rm \
--tty \
${volumes} \
"${DOCS_IMAGE}" \
"--minAlertLevel=${VALE_MINALERTLEVEL}" \
--config=/etc/vale/.vale.ini \
--output=line \
/hugo/content/docs | sed "s#$(proj_dst "${proj}")#sources#"
--init \
--interactive \
--name "${DOCS_CONTAINER}" \
--platform linux/amd64 \
--rm \
--tty \
${volumes} \
"${DOCS_IMAGE}" \
"--minAlertLevel=${VALE_MINALERTLEVEL}" \
--config=/etc/vale/.vale.ini \
--output=line \
/hugo/content/docs | sed "s#$(proj_dst "${proj}")#sources#"
;;
*)
tempfile="$(mktemp -t make-docs.XXX)"
@@ -616,41 +414,36 @@ fi
${WEBSITE_EXEC}
EOF
chmod +x "${tempfile}"
volumes="${volumes} --volume=${tempfile}:/entrypoint"
volumes="${volumes} --volume=$(realpath "${tempfile}"):/entrypoint"
readonly volumes
IFS='' read -r cmd <<EOF
${PODMAN} run \
--env=HUGO_REFLINKSERRORLEVEL=${HUGO_REFLINKSERRORLEVEL} \
--init \
--interactive \
--name=${DOCS_CONTAINER} \
--platform=linux/amd64 \
--publish=${DOCS_HOST_PORT}:3002 \
--publish=3003:3003 \
--rm \
--tty \
${volumes} \
${DOCS_IMAGE} \
/entrypoint
EOF
await_build http://localhost:3003 &
echo
echo "Documentation will be served at the following URLs:"
for x in ${url_src_dst_vers}; do
IFS='^' read -r url _ _ <<POSIX_HERESTRING
$x
POSIX_HERESTRING
if [ -n "${DEBUG}" ]; then
${cmd}
else
${cmd} 2>&1| sed \
-e '/Web Server is available at http:\/\/localhost:3003\/ (bind address 0.0.0.0)/ d' \
-e '/^hugo server/ d' \
-e '/fatal: not a git repository (or any parent up to mount point \/)/ d' \
-e '/Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set)./ d' \
-e "/Makefile:[0-9]*: warning: overriding recipe for target 'docs'/ d" \
-e "/docs.mk:[0-9]*: warning: ignoring old recipe for target 'docs'/ d" \
-e '/\/usr\/bin\/make -j 2 proxy hserver-docs HUGO_PORT=3003/ d' \
-e '/website-proxy/ d' \
-e '/rm -rf dist*/ d' \
-e '/Press Ctrl+C to stop/ d' \
-e '/make/ d' || echo
fi
if [ -n "${url}" ]; then
if [ "${_url}" != "arbitrary" ]; then
echo " ${url}"
fi
fi
done
echo
"${PODMAN}" run \
--env "HUGO_REFLINKSERRORLEVEL=${HUGO_REFLINKSERRORLEVEL}" \
--init \
--interactive \
--name "${DOCS_CONTAINER}" \
--platform linux/amd64 \
--publish "${DOCS_HOST_PORT}:3002" \
--publish "3003:3003" \
--rm \
--tty \
${volumes} \
"${DOCS_IMAGE}" \
/entrypoint
;;
esac

View File

@@ -226,7 +226,7 @@ title: Grafana documentation
</div>
<h5>Grafana Cloud</h5>
</a>
<a href="https://grafana.com/grafana/download/nightly?edition=oss" class="nav-cards__item nav-cards__item--install">
<a href="https://grafana.com/grafana/nightly?edition=oss" class="nav-cards__item nav-cards__item--install">
<div class="nav-cards__icon fa fa-moon-o">
</div>
<h5>Nightly builds</h5>

View File

@@ -127,7 +127,7 @@ Complete the following steps to migrate from API keys to service accounts using
This action generates a service account token.
1. Store the ID and secret that the system returns to you.
1. Pass the token in the `Authorization` header, prefixed with `Bearer`.
1. Pass the token in the `Authrorization` header, prefixed with `Bearer`.
This action authenticates API requests.

View File

@@ -1,15 +0,0 @@
---
description: Describes how to back up a locally provisioned Grafana instance.
keywords:
- grafana
- backup
labels:
products:
- enterprise
- oss
title: Back up Grafana
weight: 80
menuTitle: Back up Grafana
---
{{< docs/shared lookup="back-up/back-up-grafana.md" source="grafana" version="<GRAFANA VERSION>" >}}

View File

@@ -13,7 +13,7 @@ weight: 600
Besides the wide range of visualizations and data sources that are available immediately after you install Grafana, you can extend your Grafana experience with _plugins_.
You can [install]({{< relref "#install-a-plugin" >}}) one of the plugins built by the Grafana community, or [build one yourself](https://grafana.com/developers/plugin-tools).
You can [install]({{< relref "#install-a-plugin" >}}) one of the plugins built by the Grafana community, or [build one yourself]({{< relref "../../developers/plugins/" >}}).
Grafana supports three types of plugins: [panels](/grafana/plugins?type=panel), [data sources](/plugins?type=datasource), and [apps](/grafana/plugins?type=app).
@@ -165,7 +165,7 @@ Grafana also writes an error message to the server log:
WARN[05-26|12:00:00] Some plugin scanning errors were found errors="plugin '<plugin id>' is unsigned, plugin '<plugin id>' has an invalid signature"
```
If you are a plugin developer and want to know how to sign your plugin, refer to [Sign a plugin](https://grafana.com/developers/plugin-tools/publish-a-plugin/sign-a-plugin).
If you are a plugin developer and want to know how to sign your plugin, refer to [Sign a plugin]({{< relref "../../developers/plugins/sign-a-plugin/" >}}).
| Signature status | Description |
| ------------------ | ------------------------------------------------------------------------------- |

View File

@@ -56,7 +56,7 @@ Currently we do not provide any scripts/manifests for configuring Grafana. Rathe
| Tool | Project |
| --------- | -------------------------------------------------------------------------------------------------------------- |
| Puppet | [https://forge.puppet.com/puppet/grafana](https://forge.puppet.com/puppet/grafana) |
| Ansible | [https://github.com/grafana/grafana-ansible-collection](https://github.com/grafana/grafana-ansible-collection) |
| Ansible | [https://github.com/cloudalchemy/ansible-grafana](https://github.com/cloudalchemy/ansible-grafana) |
| Chef | [https://github.com/sous-chefs/chef-grafana](https://github.com/sous-chefs/chef-grafana) |
| Saltstack | [https://github.com/salt-formulas/salt-formula-grafana](https://github.com/salt-formulas/salt-formula-grafana) |
| Jsonnet | [https://github.com/grafana/grafonnet-lib/](https://github.com/grafana/grafonnet-lib/) |
@@ -351,8 +351,8 @@ When Grafana starts, it updates/inserts all dashboards available in the configur
By default, Grafana deletes dashboards in the database if the file is removed. You can disable this behavior using the `disableDeletion` setting.
> **Note:** Provisioning allows you to overwrite existing dashboards
> which leads to problems if you reuse settings that are supposed to be unique.
> Be careful not to reuse the same `title` multiple times within a folder
> which leads to problems if you re-use settings that are supposed to be unique.
> Be careful not to re-use the same `title` multiple times within a folder
> or `uid` within the same installation as this will cause weird behaviors.
### Provision folders structure from filesystem to Grafana

View File

@@ -8,7 +8,7 @@ weight: 500
# Manage dashboard permissions
Dashboard and dashboard folder permissions enable you to grant a viewer the ability to edit and save dashboard changes, or limit an editor's permission to modify a dashboard.
Dashboard and dasboard folder permissions enable you to grant a viewer the ability to edit and save dashboard changes, or limit an editor's permission to modify a dashboard.
For more information about dashboard permissions, refer to [Dashboard permissions]({{< relref "../../roles-and-permissions/#dashboard-permissions" >}}).

View File

@@ -53,11 +53,11 @@ Alert summary:
You can use any of the following built-in template options to embed custom templates.
| Name | Notes |
| ----------------------- | ------------------------------------------------------------ |
| `default.title` | Displays high-level status information. |
| `default.message` | Provides a formatted summary of firing and resolved alerts. |
| `teams.default.message` | Similar to `default.message`, formatted for Microsoft Teams. |
| Name | Notes |
| ----------------------- | ------------------------------------------------------------- |
| `default.title` | Displays high-level status information. |
| `default.message` | Provides a formatted summary of firing and resolved alerts. |
| `teams.default.message` | Similar to `default.messsage`, formatted for Microsoft Teams. |
### HTML in notification templates

View File

@@ -1,8 +1,8 @@
---
aliases:
- ../contact-points/
- ../unified-alerting/contact-points/
- contact-point-types/
- /docs/grafana/latest/alerting/contact-points/
- /docs/grafana/latest/alerting/unified-alerting/contact-points/
- /docs/grafana/latest/alerting/fundamentals/contact-points/contact-point-types/
description: Create or edit contact point
keywords:
- grafana
@@ -54,6 +54,6 @@ The following table lists the contact point integrations supported by Grafana.
## Useful links
[Manage contact points](/docs/grafana/v9.5/alerting/manage-notifications/manage-contact-points/)
[Manage contact points](/docs/grafana/latest/alerting/manage-notifications/create-contact-point/)
[Create and edit notification templates](/docs/grafana/v9.5/alerting/manage-notifications/template-notifications/create-notification-templates/)
[Create and edit notification templates](/docs/grafana/latest/alerting/manage-notifications/template-notifications/create-notification-templates/)

View File

@@ -23,7 +23,7 @@ Grafana managed alerts query the following backend data sources that have alerti
- built-in data sources or those developed and maintained by Grafana: `Graphite`, `Prometheus`, `Loki`, `InfluxDB`, `Elasticsearch`,
`Google Cloud Monitoring`, `Cloudwatch`, `Azure Monitor`, `MySQL`, `PostgreSQL`, `MSSQL`, `OpenTSDB`, `Oracle`, and `Azure Monitor`
- community developed backend data sources with alerting enabled (`backend` and `alerting` properties are set in the [plugin.json](https://grafana.com/developers/plugin-tools/reference-plugin-json)
- community developed backend data sources with alerting enabled (`backend` and `alerting` properties are set in the [plugin.json]({{< relref "/docs/grafana/latest/developers/plugins/metadata" >}}))
### Metrics from the alerting engine

View File

@@ -19,7 +19,7 @@ Users who are still using legacy alerting are encouraged to migrate their alerts
However, we will still patch CVEs until legacy alerting is completely removed in Grafana 11; honoring our commitment to building and distributing secure software.
We have provided [instructions](https://grafana.com/docs/grafana/v9.5/alerting/migrating-alerts/) on how to migrate to the new alerting system, making the process as easy as possible for users.
We have provided [instructions](https://grafana.com/docs/grafana/latest/alerting/migrating-alerts/) on how to migrate to the new alerting system, making the process as easy as possible for users.
## Why are we deprecating legacy alerting?
@@ -29,18 +29,18 @@ The new system is based on Prometheus Alertmanager, which offers a more comprehe
Overall, the new alerting system in Grafana is a major improvement over the legacy alerting feature, providing users with a more powerful and flexible alerting experience.
Additionally, legacy alerting still requires Angular to function and we are [planning to remove support for it](https://grafana.com/docs/grafana/v9.5/developers/angular_deprecation/) in Grafana 11.
Additionally, legacy alerting still requires Angular to function and we are [planning to remove support for it](https://grafana.com/docs/grafana/latest/developers/angular_deprecation/) in Grafana 11.
## When will we remove legacy alerting completely?
Legacy alerting will be removed from the code-base in Grafana 11, following the same timeline as the [Angular deprecation](https://grafana.com/docs/grafana/v9.5/developers/angular_deprecation/).
Legacy alerting will be removed from the code-base in Grafana 11, following the same timeline as the [Angular deprecation](https://grafana.com/docs/grafana/latest/developers/angular_deprecation/).
## How do I migrate to the new Grafana alerting?
Refer to our [migration instructions](https://grafana.com/docs/grafana/v9.5/alerting/migrating-alerts/opt-in/).
Refer to our [migration instructions](https://grafana.com/docs/grafana/latest/alerting/migrating-alerts/opt-in/).
### Links
- [Upgrade Alerting](https://grafana.com/docs/grafana/v9.5/alerting/migrating-alerts/)
- [Legacy alerting differences and limitations](https://grafana.com/docs/grafana/v9.5/alerting/migrating-alerts/migrating-legacy-alerts/)
- [Angular support deprecation](https://grafana.com/docs/grafana/v9.5/developers/angular_deprecation/)
- [Upgrade Alerting](https://grafana.com/docs/grafana/latest/alerting/migrating-alerts/)
- [Legacy alerting differences and limitations](https://grafana.com/docs/grafana/latest/alerting/migrating-alerts/migrating-legacy-alerts/)
- [Angular support deprecation](https://grafana.com/docs/grafana/latest/developers/angular_deprecation/)

View File

@@ -20,6 +20,8 @@ A mute timing is a recurring interval of time when no new notifications for a po
Similar to silences, mute timings do not prevent alert rules from being evaluated, nor do they stop alert instances from being shown in the user interface. They only prevent notifications from being created.
You can configure Grafana managed mute timings as well as mute timings for an [external Alertmanager data source]({{< relref "/docs/grafana/latest/datasources/alertmanager" >}}). For more information, refer to [Alertmanager documentation]({{< relref "/docs/grafana/latest/alerting/manage-notifications/alertmanager" >}}).
## Mute timings vs silences
The following table highlights the key differences between mute timings and silences.

View File

@@ -47,4 +47,4 @@ Grafana cannot be used to receive external alerts. You can only send alerts to t
You have the option to send Grafana managed alerts to an external Alertmanager, you can find this option in the admin tab on the Alerting page.
For more information, refer to [this GitHub discussion](https://github.com/grafana/grafana/discussions/45773).
For more information, refer to [this GitHub discussion](https://github.com/grafana/grafana/discussions/45773). For more information on the different Alertmanagers, refer to [Alertmanager]([{{< relref "../manage-notifications/alertmanager/" >}}](https://grafana.com/docs/grafana/next/alerting/manage-notifications/alertmanager/).

View File

@@ -11,4 +11,4 @@ Configure the features and integrations that you need to create and manage your
- [Configure Alertmanager](https://grafana.com/docs/grafana/latest/alerting/set-up/configure-alertmanager/)
- [Provision Grafana Alerting resources](https://grafana.com/docs/grafana/latest/alerting/set-up/provision-alerting-resources/)
- [Connect Grafana Alerting to Grafana OnCall](https://grafana.com/docs/oncall/latest/integrations/grafana-alerting/)
- [Connect Grafana Alerting to Grafana OnCall](https://grafana.com/docs/oncall/latest/integrations/available-integrations/add-grafana-alerting/)

View File

@@ -28,7 +28,7 @@ Since gossiping of notifications and silences uses both TCP and UDP port `9094`,
1. In your custom configuration file ($WORKING_DIR/conf/custom.ini), go to the `[unified_alerting]` section.
2. Set `[ha_peers]` to the number of hosts for each Grafana instance in the cluster (using a format of host:port), for example, `ha_peers=10.0.0.5:9094,10.0.0.6:9094,10.0.0.7:9094`.
You must have at least one (1) Grafana instance added to the `ha_peers` section.
You must have at least one (1) Grafana instance added to the [`[ha_peer]` section.
3. Set `[ha_listen_address]` to the instance IP address using a format of `host:port` (or the [Pod's](https://kubernetes.io/docs/concepts/workloads/pods/) IP in the case of using Kubernetes).
By default, it is set to listen to all interfaces (`0.0.0.0`).

View File

@@ -66,7 +66,7 @@ groups:
# <string, required> which query should be used for the condition
condition: A
# <list, required> list of query objects that should be executed on each
# evaluation - should be obtained through the API
# evaluation - should be obtained trough the API
data:
- refId: A
datasourceUid: '__expr__'

View File

@@ -110,7 +110,7 @@ You cannot edit resources provisioned via Terraform from the UI. This ensures th
**Note:**
You can reuse the same templates across many contact points. In the example above, a shared template ie embedded using the statement `{{ template “Alert Instance Template” . }}`
You can re-use the same templates across many contact points. In the example above, a shared template ie embedded using the statement `{{ template “Alert Instance Template” . }}`
This fragment can then be managed separately in Terraform:

View File

@@ -83,23 +83,10 @@ To change _recent_ to something other than the past 10 minutes, edit the [config
```ini
[analytics.views]
# Set age for recent active users to 10 minutes
# Set age for recent active users
recent_users_age = 10m
```
To disable the presence indicator, edit the [configuration][] file as follows:
```ini
[analytics.views]
# Disables the presence indicator
recent_users_age = 0
```
The dashboard won't show any avatars and thus no recent user activity.
## Sort dashboards by using insights data
In the search view, you can use insights data to help you find most-used, broken, and unused dashboards.

View File

@@ -120,7 +120,7 @@ You can stop annotations from being fetched and drawn by taking the following st
1. Find and click the **Annotations & Alerts (Built-in)** query to open it.
1. Click the **Enabled** toggle to turn it off.
When you copy a dashboard using the **Save As** feature it will get a new dashboard id, so annotations created on the source dashboard will no longer be visible on the copy. You can still show them if you add a new **Annotation Query** and filter by tags. However, this only works if the annotations on the source dashboard had tags to filter by.
When you copy a dashboard using the **Save As** feature it will get a new dashboard id, so annotations created on source dashboard will no longer be visible on the copy. You can still show them if you add a new **Annotation Query** and filter by tags. However, this only works if the annotations on the source dashboard had tags to filter by.
### Filter queries by tag

View File

@@ -53,8 +53,6 @@ Folders help you organize and group dashboards, which is useful when you have ma
1. On the Dashboards page, click **New** and select **New folder** in the dropdown.
1. Enter a unique name and click **Create**.
Do not use the name of a folder that has already been provisioned (such as "General") and avoid special characters (except underscores and hyphens).
When you save a dashboard, you can either select a folder for the dashboard to be saved in or create a new folder.
## Manage dashboards

View File

@@ -58,6 +58,3 @@ The following dashboards in Grafana Play provide examples of template variables:
- Variable drop-down lists are displayed in the order they are listed in the variable list in Dashboard settings.
- Put the variables that you will change often at the top, so they will be shown first (far left on the dashboard).
- By default, variables don't have a default value. This means that the topmost value in the drop-down is always preselected. If you want to pre-populate a variable with an empty value, you can use the following workaround in the variable settings:
1. Select the **Include All Option** checkbox.
2. In the **Custom all value** field, enter a value like `+`.

View File

@@ -61,7 +61,7 @@ The following table lists the types of variables shipped with Grafana.
You must enter general options for any type of variable that you create.
1. Navigate to the dashboard you want to make a variable for and click the **Dashboard settings** (gear) icon at the top of the page.
1. On the **Variables** tab, click **New variable**.
1. On the **Variables** tab, click **New**.
1. Enter a **Name** for the variable.
1. In the **Type** list, select **Query**.
1. (Optional) In **Label**, enter the display name of the variable dropdown.
@@ -87,10 +87,9 @@ Query expressions can contain references to other variables and in effect create
1. In the **Data source** list, select the target data source for the query. For more information about data sources, refer to [Add a data source]({{< relref "../../../administration/data-source-management#add-a-data-source" >}}).
1. In the **Refresh** list, select when the variable should update options.
- **On Dashboard Load:** Queries the data source every time the dashboard loads. This slows down dashboard loading, because the variable query needs to be completed before dashboard can be initialized.
- **On Time Range Change:** Queries the data source every time the dashboard loads and when the dashboard time range changes. Use this option if your variable options query contains a time range filter or is dependent on the dashboard time range.
- **On Time Range Change:** Queries the data source when the dashboard time range changes. Only use this option if your variable options query contains a time range filter or is dependent on the dashboard time range.
1. In the **Query** field, enter a query.
- The query field varies according to your data source. Some data sources have custom query editors.
- Make sure that the query returns values named `__text` and `__value` as appropriate in your query syntax. For example, in SQL, you can use a query such as `SELECT hostname AS __text, id AS __value FROM MyTable`. Queries for other languages will vary depending on syntax.
- If you need more room in a single input field query editor, then hover your cursor over the lines in the lower right corner of the field and drag downward to expand.
1. (Optional) In the **Regex** field, type a regex expression to filter or capture specific parts of the names returned by your data source query. To see examples, refer to [Filter variables with regex]({{< relref "#filter-variables-with-regex" >}}).
1. In the **Sort** list, select the sort order for values to be displayed in the dropdown list. The default option, **Disabled**, means that the order of options returned by your data source query will be used.
@@ -329,7 +328,7 @@ Extremely complex linked templated dashboards are possible, 5 or 10 levels deep.
The following Grafana Play dashboards contain fairly simple chained variables, only two layers deep. To view the variables and their settings, click **Dashboard settings** (gear icon) and then click **Variables**. Both examples are expanded in the following section.
- [Graphite Templated Nested](https://play.grafana.org/d/000000056/graphite-templated-nested?orgId=1&var-app=country&var-server=All&var-interval=1h)
- [InfluxDB Templated](https://play.grafana.org/d/e7bad3ef-db0c-4bbd-8245-b85c0b2ca2b9/influx-2-73a-hourly-electric-grid-monitor-for-us?orgId=1&refresh=1m)
- [InfluxDB Templated](https://play.grafana.org/d/000000002/influxdb-templated?orgId=1)
### Examples explained
@@ -401,7 +400,7 @@ apps.fakesite.web_server_01.cpu.*
#### InfluxDB example
In this example, you have several data centers. Each data center has a different subset of hosts. It is based on the [InfluxDB Templated](https://play.grafana.org/d/e7bad3ef-db0c-4bbd-8245-b85c0b2ca2b9/influx-2-73a-hourly-electric-grid-monitor-for-us?orgId=1&refresh=1m) dashboard.
In this example, you have several data centers. Each data center has a different subset of hosts. It is based on the [InfluxDB Templated](https://play.grafana.org/d/000000002/influxdb-templated?orgId=1) dashboard.
In this example, when the user changes the value of the `datacenter` variable, it changes the dropdown options returned by the `host` variable. The `host` variable uses the **Multi-value** option and **Include all option**, allowing users to select some or all options presented at any time. The `datacenter` does not use either option, so you can only select one data center at a time.

View File

@@ -2,7 +2,6 @@
aliases:
- data-sources/
- overview/
- ./features/datasources/
title: Data sources
weight: 60
---
@@ -24,7 +23,7 @@ After you add and configure a data source, you can use it as an input for many o
This documentation describes how to manage data sources in general,
and how to configure or query the built-in data sources.
For other data sources, refer to the list of [datasource plugins](/grafana/plugins/).
To develop a custom plugin, refer to [Build a plugin](https://grafana.com/developers/plugin-tools).
To develop a custom plugin, refer to [Build a plugin]({{< relref "../developers/plugins/" >}}).
## Manage data sources
@@ -47,7 +46,7 @@ For example, this video demonstrates the visual Prometheus query builder:
{{< vimeo 720004179 >}}
For general information about querying in Grafana, and common options and user interface elements across all query editors, refer to [Query and transform data]({{< relref "../panels-visualizations/query-transform-data" >}}).
For general information about querying in Grafana, and common options and user interface elements across all query editors, refer to [Query and transform data]({{< relref "../panels-visualizations/query-transform-data/" >}}).
## Special data sources

View File

@@ -58,9 +58,6 @@ For more information, refer to [Azure documentation for role assignments](https:
If you host Grafana in Azure, such as in App Service or Azure Virtual Machines, you can configure the Azure Monitor data source to use Managed Identity for secure authentication without entering credentials into Grafana.
For details, refer to [Configuring using Managed Identity]({{< relref "#configuring-using-managed-identity" >}}).
You can configure the Azure Monitor data source to use Workload Identity for secure authentication without entering credentials into Grafana if you host Grafana in a Kubernetes environment, such as AKS, and require access to Azure resources.
For details, refer to [Configuring using Workload Identity](#configuring-using-workload-identity).
| Name | Description |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Authentication** | Enables Managed Identity. Selecting Managed Identity hides many of the other fields. For details, see [Configuring using Managed Identity](#configuring-using-managed-identity). |
@@ -112,21 +109,6 @@ datasources:
version: 1
```
**Workload Identity:**
```yaml
apiVersion: 1 # config file version
datasources:
- name: Azure Monitor
type: grafana-azure-monitor-datasource
access: proxy
jsonData:
azureAuthType: workloadidentity
subscriptionId: <subscription-id> # Optional, default subscription
version: 1
```
#### Supported cloud names
| Azure Cloud | `cloudName` Value |
@@ -137,8 +119,8 @@ datasources:
### Configure Managed Identity
You can use managed identity to configure Azure Monitor in Grafana if you host Grafana in Azure (such as an App Service or with Azure Virtual Machines) and have managed identity enabled on your VM.
This lets you securely authenticate data sources without manually configuring credentials via Azure AD App Registrations.
If you host Grafana in Azure, such as an App Service or with Azure Virtual Machines, and have managed identity enabled on your VM, you can use managed identity to configure Azure Monitor in Grafana.
This lets you securely authenticate data sources without manually configuring credentials via Azure AD App Registrations for each.
For details on Azure managed identities, refer to the [Azure documentation](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview).
**To enable managed identity for Grafana:**
@@ -154,46 +136,7 @@ For details on Azure managed identities, refer to the [Azure documentation](http
This hides the directory ID, application ID, and client secret fields, and the data source uses managed identity to authenticate to Azure Monitor Metrics and Logs, and Azure Resource Graph.
{{< figure src="/media/docs/grafana/data-sources/screenshot-managed-identity-2.png" max-width="800px" class="docs-image--no-shadow" caption="Azure Monitor screenshot showing Managed Identity authentication" >}}
3. You can set the `managed_identity_client_id` field in the `[azure]` section of the [Grafana server configuration][configure-grafana-azure] to allow a user-assigned managed identity to be used instead of the default system-assigned identity.
```ini
[azure]
managed_identity_enabled = true
managed_identity_client_id = USER_ASSIGNED_IDENTITY_CLIENT_ID
```
### Configure Workload Identity
You can use workload identity to configure Azure Monitor in Grafana if you host Grafana in a Kubernetes environment, such as AKS, in conjunction with managed identities.
This lets you securely authenticate data sources without manually configuring credentials via Azure AD App Registrations.
For details on workload identity, refer to the [Azure workload identity documentation](https://azure.github.io/azure-workload-identity/docs/).
**To enable workload identity for Grafana:**
1. Set the `workload_identity_enabled` flag in the `[azure]` section of the [Grafana server configuration][configure-grafana-azure].
```ini
[azure]
workload_identity_enabled = true
```
2. In the Azure Monitor data source configuration, set **Authentication** to **Workload Identity**.
This hides the directory ID, application ID, and client secret fields, and the data source uses workload identity to authenticate to Azure Monitor Metrics and Logs, and Azure Resource Graph.
{{< figure src="/media/docs/grafana/data-sources/screenshot-workload-identity.png" max-width="800px" class="docs-image--no-shadow" caption="Azure Monitor screenshot showing Workload Identity authentication" >}}
3. There are additional configuration variables that can control the authentication method.`workload_identity_tenant_id` represents the Azure AD tenant that contains the managed identity, `workload_identity_client_id` represents the client ID of the managed identity if it differs from the default client ID, `workload_identity_token_file` represents the path to the token file. Refer to the [documentation](https://azure.github.io/azure-workload-identity/docs/) for more information on what values these variables should use, if any.
```ini
[azure]
workload_identity_enabled = true
workload_identity_tenant_id = IDENTITY_TENANT_ID
workload_identity_client_id = IDENTITY_CLIENT_ID
workload_identity_token_file = TOKEN_FILE_PATH
```
{{< figure src="/media/docs/grafana/data-sources/screenshot-managed-identity.png" max-width="800px" class="docs-image--no-shadow" caption="Azure Monitor Metrics screenshot showing Dimensions" >}}
## Query the data source

View File

@@ -134,7 +134,7 @@ The default values for "cloud monitoring auto" are:
The other automatic option is "grafana auto", which automatically sets the Group By time depending on the time range chosen and width of the time series panel.
For more information about "grafana auto", refer to [Interval variable]({{< relref "../../../dashboards/variables/add-template-variables#add-an-interval-variable" >}}).
For more information about "grafana auto", refer to [Interval variable]({{< relref "../../../dashboards/variables/add-template-variables/#add-an-interval-variable" >}}).
You can also choose fixed time intervals to group by, like `1h` or `1d`.

View File

@@ -17,7 +17,7 @@ weight: 700
# InfluxDB data source
{{< docs/shared lookup="influxdb/intro.md" source="grafana" version="<GRAFANA VERSION>" >}}
{{< docs/shared "influxdb/intro.md" >}}
Grafana includes built-in support for InfluxDB.
This topic explains options, variables, querying, and other features specific to the InfluxDB data source, which include its [feature-rich code editor for queries and visual query builder]({{< relref "./query-editor/" >}}).

View File

@@ -47,29 +47,3 @@ For more information, refer to [Add ad hoc filters]({{< relref "../../../dashboa
You can use some global built-in variables in query variables: `$__interval`, `$__interval_ms`, `$__range`, `$__range_s`, and `$__range_ms`.
For more information, refer to [Global built-in variables]({{< relref "../../../dashboards/variables/add-template-variables#global-variables" >}}).
## Label extraction and indexing in Loki
Labels play a fundamental role in Loki's log aggregation and querying capabilities. When logs are ingested into Loki, they are often accompanied by metadata called labels, which provide contextual information about the log entries. These labels consist of key-value pairs and are essential for organizing, filtering, and searching log data efficiently.
### 1. Label extraction
During the ingestion process, Loki performs label extraction from the log lines. Loki's approach to label extraction is based on regular expressions, allowing users to specify custom patterns for parsing log lines and extracting relevant label key-value pairs. This flexibility enables Loki to adapt to various log formats and schemas.
For example, suppose you have log lines in the following format:
**2023-07-25 12:34:56 INFO: Request from IP A.B.C.D to endpoint /api/data**
To extract labels from this log format, you could define a regular expression to extract the log level ("INFO"), IP address ("A.B.C.D"), and endpoint ("/api/data") as labels. These labels can later be used to filter and aggregate log entries.
### 2. Indexing labels
Once labels are extracted, Loki efficiently indexes them. The index serves as a lookup mechanism that maps labels to the corresponding log entries. This indexing process enables faster retrieval of logs based on specific label criteria, significantly enhancing query performance.
For instance, if you have a label "job" that represents different services in your application, Loki will index the logs for each job separately. This indexing allows you to quickly query and analyze logs for individual jobs without the need to scan the entire log dataset.
By effectively extracting and indexing labels, Loki enables users to perform complex and targeted log queries without compromising on query speed and resource consumption.
Utilizing Loki's indexed labels in combination with Grafana's template variables provides a powerful way to interactively explore and visualize log data. Template variables allow users to create dynamic queries, selecting and filtering logs based on various labels, such as job names, instance IDs, severity levels, or any other contextual information attached to the log entries.
In conclusion, Loki's label extraction and indexing mechanisms are key components that contribute to its ability to handle vast amounts of log data efficiently. By making use of labels and template variables, users can easily gain valuable insights from their log data and troubleshoot issues effectively.

View File

@@ -220,7 +220,7 @@ The resulting table panel:
If you set the **Format** setting in the query editor to **Time series**, then the query must have a column named `time` that returns either a SQL datetime or any numeric datatype representing Unix epoch in seconds.
Result sets of time series queries must also be sorted by time for panels to properly visualize the result.
A time series query result is returned in a [wide data frame format](https://grafana.com/developers/plugin-tools/introduction/data-frames#wide-format).
A time series query result is returned in a [wide data frame format]({{< relref "../../../developers/plugins/data-frames#wide-format" >}}).
Any column except time or of type string transforms into value fields in the data frame query result.
Any string column transforms into field labels in the data frame query result.

View File

@@ -114,7 +114,7 @@ datasources:
password: ${GRAFANA_MYSQL_PASSWORD}
```
##### Using TLS verification
##### Using TLS Verificaiton
```yaml
apiVersion: 1
@@ -273,7 +273,7 @@ The resulting table panel:
If you set Format as to _Time series_, then the query must have a column named time that returns either a SQL datetime or any numeric datatype representing Unix epoch in seconds. In addition, result sets of time series queries must be sorted by time for panels to properly visualize the result.
A time series query result is returned in a [wide data frame format](https://grafana.com/developers/plugin-tools/introduction/data-frames#wide-format). Any column except time or of type string transforms into value fields in the data frame query result. Any string column transforms into field labels in the data frame query result.
A time series query result is returned in a [wide data frame format]({{< relref "../../developers/plugins/data-frames#wide-format" >}}). Any column except time or of type string transforms into value fields in the data frame query result. Any string column transforms into field labels in the data frame query result.
> For backward compatibility, there's an exception to the above rule for queries that return three columns including a string column named metric. Instead of transforming the metric column into field labels, it becomes the field name, and then the series name is formatted as the value of the metric column. See the example with the metric column below.
@@ -460,7 +460,7 @@ ORDER BY atimestamp ASC
#### Disabling Quoting for Multi-value Variables
Grafana automatically creates a quoted, comma-separated string for multi-value variables. For example: if `server01` and `server02` are selected then it will be formatted as: `'server01', 'server02'`. To disable quoting, use the csv formatting option for variables:
Grafana automatically creates a quoted, comma-separated string for multi-value variables. For example: if `server01` and `server02` are selected then it will be formatted as: `'server01', 'server02'`. Do disable quoting, use the csv formatting option for variables:
`${servers:csv}`

View File

@@ -225,7 +225,7 @@ The resulting table panel:
If you set Format as to _Time series_, then the query must have a column named time that returns either a SQL datetime or any numeric datatype representing Unix epoch in seconds. In addition, result sets of time series queries must be sorted by time for panels to properly visualize the result.
A time series query result is returned in a [wide data frame format](https://grafana.com/developers/plugin-tools/introduction/data-frames#wide-format). Any column except time or of type string transforms into value fields in the data frame query result. Any string column transforms into field labels in the data frame query result.
A time series query result is returned in a [wide data frame format]({{< relref "../../developers/plugins/data-frames#wide-format" >}}). Any column except time or of type string transforms into value fields in the data frame query result. Any string column transforms into field labels in the data frame query result.
> For backward compatibility, there's an exception to the above rule for queries that return three columns including a string column named metric. Instead of transforming the metric column into field labels, it becomes the field name, and then the series name is formatted as the value of the metric column. See the example with the metric column below.

View File

@@ -75,7 +75,7 @@ You can create TraceQL queries using the Query editor or using Search query tab
[//]: # 'Include content for preview of Search tab featuring TraceQL query builder'
{{< docs/shared source="grafana" lookup="datasources/tempo-search-traceql.md" leveloffset="+1" version="<GRAFANA VERSION>" >}}
{{< docs/shared source="grafana" lookup="datasources/tempo-search-traceql.md" leveloffset="+1" >}}
## Query Loki for traces

View File

@@ -36,7 +36,7 @@ We encourage you to locate the repository of the corresponding plugin and create
### Links
- [Migrate Angular to React](https://grafana.com/developers/plugin-tools/migration-guides/migrate-angularjs-to-react)
- [Migrate Angular to React]({{< relref "../plugins/migration-guide/angular-react/" >}})
- [Build a panel plugin](https://grafana.com/tutorials/build-a-panel-plugin/)
- [Build a data source plugin](https://grafana.com/tutorials/build-a-data-source-plugin/)
- [List of current Angular plugins]({{< relref "./angular-plugins/" >}})

View File

@@ -31,7 +31,7 @@ We also list the year in which the plugin was last updated in the catalog and wh
We are greatly appreciative of the developers who have contributed plugins to the Grafana ecosystem, your work has helped support millions of users to gain insights into their data. A plugin being listed below is no reflection on its quality, and is purely to help users understand the impact of the removal of Angular support in Grafana.
Guidance on migrating a plugin to React can be found in our [migration guide](https://grafana.com/developers/plugin-tools/migration-guides/migrate-angularjs-to-react). If you would like to add any specific migration guidance for your plugin here or update our assessment, please open a PR by clicking the `Suggest an edit` button at the bottom of this page.
Guidance on migrating a plugin to React can be found in our [migration guide]({{< relref "../plugins/migration-guide/angular-react/" >}}). If you would like to add any specific migration guidance for your plugin here or update our assessment, please open a PR by clicking the `Suggest an edit` button at the bottom of this page.
# Current AngularJS based plugins

View File

@@ -17,7 +17,7 @@ title: RBAC HTTP API
# RBAC API
> Role-based access control API is only available in Grafana Cloud or Grafana Enterprise. Read more about [Grafana Enterprise]({{< relref "/docs/grafana/latest/introduction/grafana-enterprise" >}}).
> Role-based access control API is only available in Grafana Enterprise. Read more about [Grafana Enterprise]({{< relref "/docs/grafana/latest/introduction/grafana-enterprise" >}}).
The API can be used to create, update, delete, get, and list roles.
@@ -529,7 +529,7 @@ Content-Type: application/json; charset=UTF-8
For example, if a user does not have required permissions for creating users, they won't be able to unassign a role which will allow to do that. This is done to prevent escalation of privileges.
| Action | Scope |
| ------------------ | ------------------------- |
| ------------------ | ------------------------- |
| users.roles:remove | permissions:type:delegate |
#### Query parameters

View File

@@ -57,8 +57,10 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
- **dashboard.id** id = null to create a new dashboard.
- **dashboard.uid** Optional unique identifier when creating a dashboard. uid = null will generate a new uid.
- **folderId** The id of the folder to save the dashboard in.
- **folderUid** The UID of the folder to save the dashboard in. Overrides the `folderId`.
- **overwrite** Set to true if you want to overwrite existing dashboard with newer version, same dashboard title in folder or same dashboard uid.
- **message** - Set a commit message for the version history.
- **refresh** - Set the dashboard refresh interval. If this is lower than [the minimum refresh interval]({{< relref "/docs/grafana/latest/setup-grafana/configure-grafana#min_refresh_interval" >}}), then Grafana will ignore it and will enforce the minimum refresh interval.
For adding or updating an alert rule for a dashboard panel the user should declare a
`dashboard.panels.alert` block.
@@ -67,36 +69,169 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
```http
HTTP/1.1 200 OK
Content-Type: application/json
Content-Type: application/json; charset=UTF-8
Content-Length: 78
```
```
**Example Response**:
```http
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Content-Type: application/json; charset=UTF-8
Content-Length: 78
```
Status Codes:
```
Status Codes:
- **200** Created
- **400** Errors (invalid json, missing or invalid fields, etc)
- **401** Unauthorized
- **403** Access denied
- **401** Unauthorized
- **403** Access denied
- **412** Precondition failed
The **412** status code is used for explaining that you cannot create the dashboard and why.
There can be different reasons for this:
- The dashboard has been changed by someone else, `status=version-mismatch`
- A dashboard with the same name in the folder already exists, `status=name-exists`
- A dashboard with the same uid already exists, `status=name-exists`
- The dashboard belongs to plugin `<plugin title>`, `status=plugin-dashboard`
- **412** Precondition failed
The **412** status code is used for explaining that you cannot create the dashboard and why.
There can be different reasons for this:
- The dashboard has been changed by someone else, `status=version-mismatch`
- A dashboard with the same name in the folder already exists, `status=name-exists`
- A dashboard with the same uid already exists, `status=name-exists`
- The dashboard belongs to plugin `<plugin title>`, `status=plugin-dashboard`
The response body will have the following properties:
```http
HTTP/1.1 412 Precondition Failed
Content-Type: application/json; charset=UTF-8
Content-Length: 97
```
In case of title already exists the `status` property will be `name-exists`.
## Get dashboard by uid
`GET /api/dashboards/uid/:uid`
Will return the dashboard given the dashboard unique identifier (uid). Information about the unique identifier of a folder containing the requested dashboard might be found in the metadata.
**Required permissions**
See note in the [introduction]({{< ref "#dashboard-api" >}}) for an explanation.
| Action | Scope |
| ----------------- | -------------- |
| `dashboards:read` | `dashboards:*` |
**Example Request**:
```http
GET /api/dashboards/uid/cIBgcSjkk HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
```
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
```
Status Codes:
- **200** Found
- **401** Unauthorized
- **403** Access denied
- **404** Not found
## Delete dashboard by uid
`DELETE /api/dashboards/uid/:uid`
Will delete the dashboard given the specified unique identifier (uid).
**Required permissions**
See note in the [introduction]({{< ref "#dashboard-api" >}}) for an explanation.
| Action | Scope |
| ------------------- | ----------------------------- |
| `dashboards:delete` | `dashboards:*`<br>`folders:*` |
**Example Request**:
```http
DELETE /api/dashboards/uid/cIBgcSjkk HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
```
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
```
Status Codes:
- **200** Deleted
- **401** Unauthorized
- **403** Access denied
- **404** Not found
## Gets the home dashboard
`GET /api/dashboards/home`
Will return the home dashboard.
**Example Request**:
```http
GET /api/dashboards/home HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
```
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
```
## Tags for Dashboard
`GET /api/dashboards/tags`
Get all tags of dashboards
**Example Request**:
```http
GET /api/dashboards/tags HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
```
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
```
## Dashboard Search
See [Folder/Dashboard Search API]({{< relref "folder_dashboard_search/" >}}).
@@ -108,12 +243,12 @@ Content-Type: application/json; charset=UTF-8
Content-Length: 78
{
## Get dashboard by uid
`GET /api/dashboards/uid/:uid`
Will return the dashboard given the dashboard unique identifier (uid). Information about the unique identifier of a folder containing the requested dashboard might be found in the metadata.
"id": 1,
"uid": "cIBgcSjkk",
"url": "/d/cIBgcSjkk/production-overview",
"status": "success",
"version": 1,
"slug": "production-overview" //deprecated in Grafana v5.0
}
```

View File

@@ -30,14 +30,14 @@ Returns a list of all library elements the authenticated user has permission to
Query parameters:
- `searchString`: Part of the name or description searched for.
- `kind`: Kind of element to search for. Use `1` for library panels or `2` for library variables.
- `sortDirection`: Sort order of elements. Use `alpha-asc` for ascending and `alpha-desc` for descending sort order.
- `typeFilter`: A comma separated list of types to filter the elements by.
- `excludeUid`: Element UID to exclude from search results.
- `folderFilter`: A comma separated list of folder IDs to filter the elements by.
- `perPage`: The number of results per page; default is 100.
- `page`: The page for a set of records, given that only `perPage` records are returned at a time. Numbering starts at `1`.
- **searchString** Part of the name or description searched for.
- **kind** Kind of element to search for. Use `1` for library panels or `2` for library variables.
- **sortDirection** Sort order of elements. Use `alpha-asc` for ascending and `alpha-desc` for descending sort order.
- **typeFilter** A comma separated list of types to filter the elements by.
- **excludeUid** Element UID to exclude from search results.
- **folderFilter** A comma separated list of folder ID(s) to filter the elements by.
- **perPage** The number of results per page; default is 100.
- **page** The page for a set of records, given that only `perPage` records are returned at a time. Numbering starts at `1`.
**Example Request**:
@@ -98,8 +98,8 @@ Content-Type: application/json
**Example Request**:
```http
GET /api/library-elements/name/API docs Example HTTP/1.1
```http
GET /api/library-elements/name/API docs Example HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
@@ -157,9 +157,9 @@ Content-Type: application/json
JSON Body schema:
- **folderId** ID of the folder where the library element is stored. It is deprecated since Grafana v9
- `folderUid`: Optional, the UID of the folder where the library element is stored, empty string when it is General folder
- `name`: Optional, the name of the library element.
- `model`: The JSON model for the library element.
- **folderUid** Optional, the UID of the folder where the library element is stored, empty string when it is General folder
- **name** Optional, the name of the library element.
- **model** The JSON model for the library element.
- **kind** Kind of element to create, Use `1` for library panels or `2` for library variables.
- **uid** Optional, the [unique identifier](/http_api/library_element/#identifier-id-vs-unique-identifier-uid).
@@ -219,9 +219,9 @@ Content-Type: application/json
```http
HTTP/1.1 200
Content-Type: application/json
```
```
Status Codes:
- **200** Updated
@@ -265,9 +265,9 @@ Content-Type: application/json
- **403** Access denied
- **404** Library element not found
- `200`: Found
- `401`: Unauthorized
- `404`: Library element not found
- **200** Found
- **401** Unauthorized
- **404** Library element not found
## Create library element
@@ -277,12 +277,12 @@ Creates a new library element.
JSON Body schema:
- `folderId`: ID of the folder where the library element is stored. It is deprecated since Grafana v9
- `folderUid`: Optional, the UID of the folder where the library element is stored, empty string when it is General folder
- `name`: Optional, the name of the library element.
- `model`: The JSON model for the library element.
- `kind`: Kind of element to create, Use `1` for library panels or `2` for library variables.
- `uid`: Optional, the [unique identifier](/http_api/library_element/#identifier-id-vs-unique-identifier-uid).
- **folderId** ID of the folder where the library element is stored. It is deprecated since Grafana v9
- **folderUid** Optional, the UID of the folder where the library element is stored, empty string when it is General folder
- **name** Optional, the name of the library element.
- **model** The JSON model for the library element.
- **kind** Kind of element to create, Use `1` for library panels or `2` for library variables.
- **uid** Optional, the [unique identifier](/http_api/library_element/#identifier-id-vs-unique-identifier-uid).
**Example Request**:
@@ -343,10 +343,10 @@ Content-Type: application/json
Status Codes:
- `200`: Created
- `400`: Errors (for example, name or UID already exists, invalid JSON, missing or invalid fields, and so on).
- `401`: Unauthorized
- `403`: Access denied
- **200** Created
- **400** Errors (for example, name or UID already exists, invalid JSON, missing or invalid fields, and so on).
- **401** Unauthorized
- **403** Access denied
## Update library element
@@ -356,13 +356,13 @@ Updates an existing library element identified by uid.
JSON Body schema:
- `folderId`: ID of the folder where the library element is stored. It is deprecated since Grafana v9
- `folderUid`: UID of the folder where the library element is stored, empty string when it is General folder.
- `name`: Name of the library element.
- `model`: The JSON model for the library element.
- `kind`: Kind of element to create. Use `1` for library panels or `2` for library variables.
- `version`: Version of the library element you are updating.
- `uid`: Optional, the [unique identifier](/http_api/library_element/#identifier-id-vs-unique-identifier-uid).
- **folderId** ID of the folder where the library element is stored. It is deprecated since Grafana v9
- **folderUid** UID of the folder where the library element is stored, empty string when it is General folder.
- **name** Name of the library element.
- **model** The JSON model for the library element.
- **kind** Kind of element to create. Use `1` for library panels or `2` for library variables.
- **version** Version of the library element you are updating.
- **uid** Optional, the [unique identifier](/http_api/library_element/#identifier-id-vs-unique-identifier-uid).
**Example Request**:
@@ -424,12 +424,12 @@ Content-Type: application/json
Status Codes:
- `200`: Updated
- `400`: Errors (for example, name or UID already exists, invalid JSON, missing or invalid fields, and so on).
- `401`: Unauthorized
- `403`: Access denied
- `404`: Library element not found
- `412`: Version mismatch
- **200** Updated
- **400** Errors (for example, name or UID already exists, invalid JSON, missing or invalid fields, and so on).
- **401** Unauthorized
- **403** Access denied
- **404** Library element not found
- **412** Version mismatch
## Delete library element
@@ -463,8 +463,8 @@ Content-Type: application/json
Status Codes:
- `200`: Deleted
- `401`: Unauthorized
- `400`: Bad request
- `403`: Access denied
- `404`: Library element not found
- **200** Deleted
- **401** Unauthorized
- **400** Bad request
- **403** Access denied
- **404** Library element not found

View File

@@ -0,0 +1,52 @@
---
aliases:
- ../plugins/developing/
description: Resources for creating Grafana plugins
title: Plugin developer's guide
weight: 200
---
# Grafana plugin developer's guide
You can extend Grafana's built-in capabilities with plugins. Plugins enable Grafana to accomplish specialized tasks, custom-tailored to your requirements. By making a plugin for your organization, you can connect Grafana to other data sources, ticketing tools, and CI/CD tooling.
You can create plugins for private use or contribute them to the open source community by publishing to the [Grafana plugin catalog](https://grafana.com/grafana/plugins/). This catalog has hundreds of other community and commercial plugins.
If you are a Grafana plugin developer or want to become one, this plugin developer's guide contains the tutorials and reference materials to help you get started.
## Plugin basics
You can create several types of plugins, including:
- **Panel plugins** - Visualize data and navigate between dashboards.
- **Data source plugins** - Link to new databases or other sources of data.
- **App plugins** - Create rich applications for custom out-of-the-box experiences.
> **Note:** To learn more about the types of plugins you can build, refer to the [Plugin management]({{< relref "../../administration/plugin-management" >}}) documentation.
## Contents of this developer's guide
The following topics are covered in this guide:
- **[Get started with plugins]({{< relref "./get-started-with-plugins" >}})** - Start developing Grafana plugins with the [Create-plugin](https://www.npmjs.com/package/@grafana/create-plugin) tool.
- **[Introduction to plugin development]({{< relref "./introduction-to-plugin-development" >}})** - Learn the fundamentals of Grafana plugin development: frontend and backend development processes, data frames, error handling, and more.
- **[Create a Grafana plugin]({{< relref "./create-a-grafana-plugin" >}})** - If you're familiar with plugin creation, use the tutorials for creating panel plugins, data source plugins, and more to deepen your knowledge.
- **[Migrate a plugin]({{< relref "./migration-guide" >}})** - Learn how to upgrade from a previous version of a Grafana plugin, rewrite an old Angular plugin in React, or update to a newer version.
- **[Publish a Grafana plugin]({{< relref "./publish-a-plugin" >}})** - Learn about publishing a plugin to the Grafana plugin catalog, including publishing criteria, packaging, and deployment.
- **[Reference]({{< relref "metadata.md" >}})** - Description of the `plugin.json` schema and plugin metadata.
## Go further
Learn more about additional tools and see plugin type examples.
### User interface creation
Explore the many UI components in our [Grafana UI library](https://developers.grafana.com/ui).
### Plugin examples
Grafana Labs provides a number of best practice example plugins for different use cases to help you quickly get started. Browse our [plugin examples](https://github.com/grafana/grafana-plugin-examples).
### SDK
Learn more about [Grafana Plugin SDK for Go]({{< relref "./backend/grafana-plugin-sdk-for-go" >}}).

View File

@@ -0,0 +1,166 @@
---
title: Add anonymous usage reporting
---
# Add anonymous usage reporting
Add anonymous usage tracking to your plugin to send [reporting events]({{< relref "../../setup-grafana/configure-grafana/#reporting_enabled" >}}) that describe how your plugin is being used to a tracking system configured by your Grafana server administrator.
## Event reporting
In this section, we show an example of tracking usage data from a query editor and receiving a report back from the analytics service.
### Sample query editor
Let's say you have a `QueryEditor` that looks similar to the example below. It has a `CodeEditor` field where you can write your query and a query type selector so you can select the kind of query result that you expect to return:
```ts
import React, { ReactElement } from 'react';
import { InlineFieldRow, InlineField, Select, CodeEditor } from '@grafana/ui';
import type { EditorProps } from './types';
export function QueryEditor(props: EditorProps): ReactElement {
const { datasource, query, onChange, onRunQuery } = props;
const queryType = { value: query.value ?? 'timeseries' };
const queryTypes = [
{
label: 'Timeseries',
value: 'timeseries',
},
{
label: 'Table',
value: 'table',
},
];
const onChangeQueryType = (type: string) => {
onChange({
...query,
queryType: type,
});
runQuery();
};
const onChangeRawQuery = (rawQuery: string) => {
onChange({
...query,
rawQuery: type,
});
runQuery();
};
return (
<>
<div>
<CodeEditor
height="200px"
showLineNumbers={true}
language="sql"
onBlur={onChangeRawQuery}
value={query.rawQuery}
/>
</div>
<InlineFieldRow>
<InlineField label="Query type" grow>
<Select options={queryTypes} onChange={onChangeQueryType} value={queryType} />
</InlineField>
</InlineFieldRow>
</>
);
}
```
### Track usage with `usePluginInteractionReporter`
Let's say that you want to track how the usage looks between time series and table queries.
What you want to do is to add the `usePluginInteractionReporter` to fetch a report function that takes two arguments:
- Required: An event name that begins with `grafana_plugin_`. It is used to identify the interaction being made.
- Optional: Attached contextual data. In our example, that is the query type.
```ts
import React, { ReactElement } from 'react';
import { InlineFieldRow, InlineField, Select, CodeEditor } from '@grafana/ui';
import { usePluginInteractionReporter } from '@grafana/runtime';
import type { EditorProps } from './types';
export function QueryEditor(props: EditorProps): ReactElement {
const { datasource, query, onChange, onRunQuery } = props;
const report = usePluginInteractionReporter();
const queryType = { value: query.value ?? 'timeseries' };
const queryTypes = [
{
label: 'Timeseries',
value: 'timeseries',
},
{
label: 'Table',
value: 'table',
},
];
const onChangeQueryType = (type: string) => {
onChange({
...query,
queryType: type,
});
runQuery();
};
const onChangeRawQuery = (rawQuery: string) => {
onChange({
...query,
rawQuery: type,
});
report('grafana_plugin_executed_query', {
query_type: queryType.value,
});
runQuery();
};
return (
<>
<div>
<CodeEditor
height="200px"
showLineNumbers={true}
language="sql"
onBlur={onChangeRawQuery}
value={query.rawQuery}
/>
</div>
<InlineFieldRow>
<InlineField label="Query type" grow>
<Select options={queryTypes} onChange={onChangeQueryType} value={queryType} />
</InlineField>
</InlineFieldRow>
</>
);
}
```
### Data returned from the analytics service
When you use `usePluginInteractionReporter`, the report function that is handed back to you automatically attaches contextual data about the plugin you are tracking to the events.
In our example, the following information is sent to the analytics service configured by the Grafana server administrator:
```ts
{
type: 'interaction',
payload: {
interactionName: 'grafana_plugin_executed_query',
grafana_version: '9.2.1',
plugin_type: 'datasource',
plugin_version: '1.0.0',
plugin_id: 'grafana-example-datasource',
plugin_name: 'Example',
datasource_uid: 'qeSI8VV7z', // will only be added for datasources
query_type: 'timeseries'
}
}
```

View File

@@ -0,0 +1,459 @@
---
aliases:
- ../../plugins/developing/auth-for-datasources/
- /docs/grafana/next/developers/plugins/authentication/
title: Add authentication for data source plugins
---
# Add authentication for data source plugins
Grafana plugins can perform authenticated requests against a third-party API by using the _data source proxy_ or through a custom a _backend plugin_.
## Choose an authentication method
Configure your data source plugin to authenticate against a third-party API in one of either of two ways:
- Use the [_data source proxy_](#authenticate-using-the-data-source-proxy) method, or
- Build a [_backend plugin_](#authenticate-using-a-backend-plugin).
| Case | Use |
| ----------------------------------------------------------------------------------------------- | ------------------------------- |
| Do you need to authenticate your plugin using Basic Auth or API keys? | Use the data source proxy. |
| Does your API support OAuth 2.0 using client credentials? | Use the data source proxy. |
| Does your API use a custom authentication method that isn't supported by the data source proxy? | Use a backend plugin. |
| Does your API communicate over a protocol other than HTTP? | Build and use a backend plugin. |
| Does your plugin require alerting support? | Build and use a backend plugin. |
## Encrypt data source configuration
Data source plugins have two ways of storing custom configuration: `jsonData` and `secureJsonData`.
Users with the Viewer role can access data source configuration such as the contents of `jsonData` in cleartext. If you've enabled anonymous access, anyone who can access Grafana in their browser can see the contents of `jsonData`.
Users of [Grafana Enterprise](https://grafana.com/products/enterprise/grafana/) can restrict access to data sources to specific users and teams. For more information, refer to [Data source permissions](https://grafana.com/docs/grafana/latest/enterprise/datasource_permissions).
> **Important:** Do not use `jsonData` with sensitive data such as password, tokens, and API keys. If you need to store sensitive information, use `secureJsonData` instead.
> **Note:** You can see the settings that the current user has access to by entering `window.grafanaBootData` in the developer console of your browser.
### Store configuration in `secureJsonData`
If you need to store sensitive information, use `secureJsonData` instead of `jsonData`. Whenever the user saves the data source configuration, the secrets in `secureJsonData` are sent to the Grafana server and encrypted before they're stored.
Once you have encrypted the secure configuration, it can no longer be accessed from the browser. The only way to access secrets after they've been saved is by using the [_data source proxy_](#authenticate-using-the-data-source-proxy).
### Add secret configuration to your data source plugin
To demonstrate how you can add secrets to a data source plugin, let's add support for configuring an API key.
1. Create a new interface in `types.ts` to hold the API key:
```ts
export interface MySecureJsonData {
apiKey?: string;
}
```
1. Add type information to your `secureJsonData` object by updating the props for your `ConfigEditor` to accept the interface as a second type parameter. Access the value of the secret from the `options` prop inside your `ConfigEditor`:
```ts
interface Props extends DataSourcePluginOptionsEditorProps<MyDataSourceOptions, MySecureJsonData> {}
```
```ts
const { secureJsonData, secureJsonFields } = options;
const { apiKey } = secureJsonData;
```
> **Note:** You can do this until the user saves the configuration; when the user saves the configuration, Grafana clears the value. After that, you can use `secureJsonFields` to determine whether the property has been configured.
1. To securely update the secret in your plugin's configuration editor, update the `secureJsonData` object using the `onOptionsChange` prop:
```ts
const onAPIKeyChange = (event: ChangeEvent<HTMLInputElement>) => {
onOptionsChange({
...options,
secureJsonData: {
apiKey: event.target.value,
},
});
};
```
1. Define a component that can accept user input:
```ts
<Input
type="password"
placeholder={secureJsonFields?.apiKey ? 'configured' : ''}
value={secureJsonData.apiKey ?? ''}
onChange={onAPIKeyChange}
/>
```
1. Optional: If you want the user to be able to reset the API key, then you need to set the property to `false` in the `secureJsonFields` object:
```ts
const onResetAPIKey = () => {
onOptionsChange({
...options,
secureJsonFields: {
...options.secureJsonFields,
apiKey: false,
},
secureJsonData: {
...options.secureJsonData,
apiKey: '',
},
});
};
```
Now that users can configure secrets, the next step is to see how we can add them to our requests.
## Authenticate using the data source proxy
Once the user has saved the configuration for a data source, the secret data source configuration will no longer be available in the browser. Encrypted secrets can only be accessed on the server. So how do you add them to your request?
The Grafana server comes with a proxy that lets you define templates for your requests: _proxy routes_. Grafana sends the proxy route to the server, decrypts the secrets along with other configuration, and adds them to the request before sending it.
> **Note:** Be sure not to confuse the data source proxy with the [auth proxy]({{< relref "../../setup-grafana/configure-security/configure-authentication/auth-proxy/" >}}). The data source proxy is used to authenticate a data source, while the auth proxy is used to log into Grafana itself.
### Add a proxy route to your plugin
To forward requests through the Grafana proxy, you need to configure one or more _proxy routes_. A proxy route is a template for any outgoing request that is handled by the proxy. You can configure proxy routes in the [plugin.json](https://grafana.com/docs/grafana/latest/developers/plugins/metadata/) file.
1. Add the route to `plugin.json`:
```json
"routes": [
{
"path": "example",
"url": "https://api.example.com"
}
]
```
> **Note:** You need to restart the Grafana server every time you make a change to your `plugin.json` file.
1. In the `DataSource`, extract the proxy URL from `instanceSettings` to a class property called `url`:
```ts
export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
url?: string;
constructor(instanceSettings: DataSourceInstanceSettings<MyDataSourceOptions>) {
super(instanceSettings);
this.url = instanceSettings.url;
}
// ...
}
```
1. In the `query` method, make a request using `BackendSrv`. The first section of the URL path needs to match the `path` of your proxy route. The data source proxy replaces `this.url + routePath` with the `url` of the route. Based on our example, the URL for the request would be `https://api.example.com/v1/users`:
```ts
import { getBackendSrv } from '@grafana/runtime';
```
```ts
const routePath = '/example';
getBackendSrv().datasourceRequest({
url: this.url + routePath + '/v1/users',
method: 'GET',
});
```
### Add a dynamic proxy route to your plugin
Grafana sends the proxy route to the server, where the data source proxy decrypts any sensitive data and interpolates the template variables with the decrypted data before making the request.
To add user-defined configuration to your routes:
- Use `.JsonData` for configuration stored in `jsonData`. For example, where `projectId` is the name of a property in the `jsonData` object:
```json
"routes": [
{
"path": "example",
"url": "https://api.example.com/projects/{{ .JsonData.projectId }}"
}
]
```
- Use `.SecureJsonData` for sensitive data stored in `secureJsonData`. For example, where `password` is the name of a property in the `secureJsonData` object:
```json
"routes": [
{
"path": "example",
"url": "https://{{ .JsonData.username }}:{{ .SecureJsonData.password }}@api.example.com"
}
]
```
In addition to adding the URL to the proxy route, you can also add headers, URL parameters, and a request body.
#### Add HTTP headers to a proxy route
Here's an example of adding `name` and `content` as HTTP headers:
```json
"routes": [
{
"path": "example",
"url": "https://api.example.com",
"headers": [
{
"name": "Authorization",
"content": "Bearer {{ .SecureJsonData.apiToken }}"
}
]
}
]
```
#### Add URL parameters to a proxy route
Here's an example of adding `name` and `content` as URL parameters:
```json
"routes": [
{
"path": "example",
"url": "http://api.example.com",
"urlParams": [
{
"name": "apiKey",
"content": "{{ .SecureJsonData.apiKey }}"
}
]
}
]
```
#### Add a request body to a proxy route
Here's an example of adding `username` and `password` to the request body:
```json
"routes": [
{
"path": "example",
"url": "http://api.example.com",
"body": {
"username": "{{ .JsonData.username }}",
"password": "{{ .SecureJsonData.password }}"
}
}
]
```
### Add an OAuth 2.0 proxy route to your plugin
Since your request to each route is made server-side with OAuth 2.0 authentication, only machine-to-machine requests are supported. In order words, if you need to use a different grant than client credentials, you need to implement it yourself.
To authenticate using OAuth 2.0, add a `tokenAuth` object to the proxy route definition. If necessary, Grafana performs a request to the URL defined in `tokenAuth` to retrieve a token before making the request to the URL in your proxy route. Grafana automatically renews the token when it expires.
Any parameters defined in `tokenAuth.params` are encoded as `application/x-www-form-urlencoded` and sent to the token URL.
```json
{
"routes": [
{
"path": "api",
"url": "https://api.example.com/v1",
"tokenAuth": {
"url": "https://api.example.com/v1/oauth/token",
"params": {
"grant_type": "client_credentials",
"client_id": "{{ .SecureJsonData.clientId }}",
"client_secret": "{{ .SecureJsonData.clientSecret }}"
}
}
}
]
}
```
## Authenticate using a backend plugin
While the data source proxy supports the most common authentication methods for HTTP APIs, using proxy routes has a few limitations:
- Proxy routes only support HTTP or HTTPS.
- Proxy routes don't support custom token authentication.
If any of these limitations apply to your plugin, you need to add a [backend plugin]({{< relref "backend/" >}}). Because backend plugins run on the server, they can access decrypted secrets, which makes it easier to implement custom authentication methods.
The decrypted secrets are available from the `DecryptedSecureJSONData` field in the instance settings.
```go
func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
instanceSettings := req.PluginContext.DataSourceInstanceSettings
if apiKey, exists := settings.DecryptedSecureJSONData["apiKey"]; exists {
// Use the decrypted API key.
}
// ...
}
```
## Forward OAuth identity for the logged-in user
If your data source uses the same OAuth provider as Grafana itself, for example using [Generic OAuth Authentication]({{< relref "../../setup-grafana/configure-security/configure-authentication/generic-oauth/" >}}), then your data source plugin can reuse the access token for the logged-in Grafana user.
To allow Grafana to pass the access token to the plugin, update the data source configuration and set the `jsonData.oauthPassThru` property to `true`. The [DataSourceHttpSettings](https://developers.grafana.com/ui/latest/index.html?path=/story/data-source-datasourcehttpsettings--basic) settings provide a toggle, the **Forward OAuth Identity** option, for this. You can also build an appropriate toggle to set `jsonData.oauthPassThru` in your data source configuration page UI.
When configured, Grafana can forward authorization HTTP headers such as `Authorization` or `X-ID-Token` to a backend data source. This information is available across the `QueryData`, `CallResource` and `CheckHealth` requests.
To get Grafana to forward the headers, create a HTTP client using the [Grafana plugin SDK for Go](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go/backend/httpclient) and set the `ForwardHTTPHeaders` option to `true` (by default, it's set to `false`). This package exposes request information which can be subsequently forwarded downstream and/or used directly within the plugin.
```go
func NewDatasource(settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
opts, err := settings.HTTPClientOptions()
if err != nil {
return nil, fmt.Errorf("http client options: %w", err)
}
// Important: Reuse the same client for each query to avoid using all available connections on a host.
opts.ForwardHTTPHeaders = true
cl, err := httpclient.New(opts)
if err != nil {
return nil, fmt.Errorf("httpclient new: %w", err)
}
return &Datasource{
httpClient: cl,
}, nil
}
func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
// Necessary to keep the Context, since the injected middleware is configured there
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://some-url", nil)
if err != nil {
return nil, fmt.Errorf("new request with context: %w", err)
}
// Authorization header will be automatically injected if oauthPassThru is configured
resp, err := ds.httpClient.Do(req)
// ...
}
```
You can see a full working plugin example here: [datasource-http-backend](https://github.com/grafana/grafana-plugin-examples/tree/main/examples/datasource-http-backend).
### Extract a header from an HTTP request
If you need to access the HTTP header information directly, you can also extract that information from the request:
```go
func (ds *dataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
token := strings.Fields(req.GetHTTPHeader(backend.OAuthIdentityTokenHeaderName))
var (
tokenType = token[0]
accessToken = token[1]
)
idToken := req.GetHTTPHeader(backend.OAuthIdentityIDTokenHeaderName) // present if user's token includes an ID token
// ...
return &backend.CheckHealthResult{Status: backend.HealthStatusOk}, nil
}
func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
token := strings.Fields(req.GetHTTPHeader(backend.OAuthIdentityTokenHeaderName))
var (
tokenType = token[0]
accessToken = token[1]
)
idToken := req.GetHTTPHeader(backend.OAuthIdentityIDTokenHeaderName)
for _, q := range req.Queries {
// ...
}
}
func (ds *dataSource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
token := req.GetHTTPHeader(backend.OAuthIdentityTokenHeaderName)
idToken := req.GetHTTPHeader(backend.OAuthIdentityIDTokenHeaderName)
// ...
}
```
## Work with cookies
### Forward cookies for the logged-in user
Your data source plugin can forward cookies for the logged-in Grafana user to the data source. Use the [DataSourceHttpSettings](https://developers.grafana.com/ui/latest/index.html?path=/story/data-source-datasourcehttpsettings--basic) component on the data source's configuration page. It provides the **Allowed cookies** option, where you can specify the cookie names.
When configured, as with [authorization headers](#forward-oauth-identity-for-the-logged-in-user), these cookies are automatically injected if you use the SDK HTTP client.
### Extract cookies for the logged-in user
You can also extract the cookies in the `QueryData`, `CallResource` and `CheckHealth` requests if required.
**`QueryData`**
```go
func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
cookies:= req.GetHTTPHeader(backend.CookiesHeaderName)
// ...
}
```
**`CallResource`**
```go
func (ds *dataSource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
cookies:= req.GetHTTPHeader(backend.CookiesHeaderName)
// ...
}
```
**`CheckHealth`**
```go
func (ds *dataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
cookies:= req.GetHTTPHeader(backend.CookiesHeaderName)
// ...
}
```
## Forward user header for the logged-in user
When [send_user_header]({{< relref "../../setup-grafana/configure-grafana/_index.md#send_user_header" >}}) is enabled, Grafana passes the user header to the plugin using the `X-Grafana-User` header. You can forward this header as well as [authorization headers](#forward-oauth-identity-for-the-logged-in-user) or [configured cookies](#forward-cookies-for-the-logged-in-user).
**`QueryData`**
```go
func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
u := req.GetHTTPHeader("X-Grafana-User")
// ...
}
```
**`CallResource`**
```go
func (ds *dataSource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
u := req.GetHTTPHeader("X-Grafana-User")
// ...
}
```
**`CheckHealth`**
```go
func (ds *dataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
u := req.GetHTTPHeader("X-Grafana-User")
// ...
}
```

View File

@@ -0,0 +1,110 @@
---
title: Add distributed tracing for backend plugins
---
# Add distributed tracing for backend plugins
> **Note:** This feature requires at least Grafana 9.5.0, and your plugin needs to be built at least with grafana-plugins-sdk-go v0.157.0. If you run a plugin with tracing features on an older version of Grafana, tracing is disabled.
Distributed tracing allows backend plugin developers to create custom spans in their plugins, and send them to the same endpoint and with the same propagation format as the main Grafana instance. The tracing context is also propagated from the Grafana instance to the plugin, so the plugin's spans will be correlated to the correct trace.
## Plugin configuration
Plugin tracing must be enabled manually on a per-plugin basis, by specifying `tracing = true` in the plugin's config section:
```ini
[plugin.myorg-myplugin-datasource]
tracing = true
```
## OpenTelemetry configuration in Grafana
Grafana supports [OpenTelemetry](https://opentelemetry.io/) for distributed tracing. If Grafana is configured to use a deprecated tracing system (Jaeger or OpenTracing), then tracing is disabled in the plugin provided by the SDK and configured when calling `datasource.Manage | app.Manage`.
OpenTelemetry must be enabled and configured for the Grafana instance. Please refer to the [Grafana configuration documentation](
{{< relref "../../setup-grafana/configure-grafana/#tracingopentelemetry" >}}) for more information.
Refer to the [OpenTelemetry Go SDK](https://pkg.go.dev/go.opentelemetry.io/otel) for in-depth documentation about all the features provided by OpenTelemetry.
> **Note:** If tracing is disabled in Grafana, `backend.DefaultTracer()` returns a no-op tracer.
## Implement tracing in your plugin
> **Note:** Make sure you are using at least grafana-plugin-sdk-go v0.157.0. You can update with `go get -u github.com/grafana/grafana-plugin-sdk-go`.
### Configure a global tracer
When OpenTelemetry tracing is enabled on the main Grafana instance and tracing is enabled for a plugin, the OpenTelemetry endpoint address and propagation format is passed to the plugin during startup. These parameters are used to configure a global tracer.
1. Use `datasource.Manage` or `app.Manage` to run your plugin to automatically configure the global tracer. Specify any custom attributes for the default tracer using `CustomAttributes`:
```go
func main() {
if err := datasource.Manage("MY_PLUGIN_ID", plugin.NewDatasource, datasource.ManageOpts{
TracingOpts: tracing.Opts{
// Optional custom attributes attached to the tracer's resource.
// The tracer will already have some SDK and runtime ones pre-populated.
CustomAttributes: []attribute.KeyValue{
attribute.String("my_plugin.my_attribute", "custom value"),
},
},
}); err != nil {
log.DefaultLogger.Error(err.Error())
os.Exit(1)
}
}
```
1. Once you have configured tracing, use the global tracer like this:
```go
tracing.DefaultTracer()
```
This returns an [OpenTelemetry `trace.Tracer`](https://pkg.go.dev/go.opentelemetry.io/otel/trace#Tracer) for creating spans.
**Example:**
```go
func (d *Datasource) query(ctx context.Context, pCtx backend.PluginContext, query backend.DataQuery) (backend.DataResponse, error) {
ctx, span := tracing.DefaultTracer().Start(
ctx,
"query processing",
trace.WithAttributes(
attribute.String("query.ref_id", query.RefID),
attribute.String("query.type", query.QueryType),
attribute.Int64("query.max_data_points", query.MaxDataPoints),
attribute.Int64("query.interval_ms", query.Interval.Milliseconds()),
attribute.Int64("query.time_range.from", query.TimeRange.From.Unix()),
attribute.Int64("query.time_range.to", query.TimeRange.To.Unix()),
),
)
defer span.End()
log.DefaultLogger.Debug("query", "traceID", trace.SpanContextFromContext(ctx).TraceID())
// ...
}
```
### Tracing gRPC calls
When tracing is enabled, a new span is created automatically for each gRPC call (`QueryData`, `CheckHealth`, etc.), both on Grafana's side and on the plugin's side. The plugin SDK also injects the trace context into the `context.Context` that is passed to those methods.
You can retrieve the [trace.SpanContext](https://pkg.go.dev/go.opentelemetry.io/otel/trace#SpanContext) with `tracing.SpanContextFromContext` by passing the original `context.Context` to it:
```go
func (d *Datasource) query(ctx context.Context, pCtx backend.PluginContext, query backend.DataQuery) (backend.DataResponse, error) {
spanCtx := trace.SpanContextFromContext(ctx)
traceID := spanCtx.TraceID()
// ...
}
```
### Tracing HTTP requests
When tracing is enabled, a `TracingMiddleware` is also added to the default middleware stack to all HTTP clients created using the `httpclient.New` or `httpclient.NewProvider`, unless you specify custom middleware. This middleware creates spans for each outgoing HTTP request and provides some useful attributes and events related to the request's lifecycle.
## Plugin example
Refer to the [datasource-http-backend plugin example](https://github.com/grafana/grafana-plugin-examples/tree/main/examples/datasource-http-backend) for a complete example of a plugin with full distributed tracing support.

View File

@@ -0,0 +1,73 @@
---
title: Add query editor help
---
# Add query editor help
Query editors support the addition of a help component to display examples of potential queries. When the user clicks on one of the examples, the query editor is automatically updated. This helps the user to make faster queries.
1. In the `src` directory of your plugin, create a file `QueryEditorHelp.tsx` with the following content:
```ts
import React from 'react';
import { QueryEditorHelpProps } from '@grafana/data';
export default (props: QueryEditorHelpProps) => {
return <h2>My cheat sheet</h2>;
};
```
1. Configure the plugin to use `QueryEditorHelp`:
```ts
import QueryEditorHelp from './QueryEditorHelp';
```
```ts
export const plugin = new DataSourcePlugin<DataSource, MyQuery, MyDataSourceOptions>(DataSource)
.setConfigEditor(ConfigEditor)
.setQueryEditor(QueryEditor)
.setQueryEditorHelp(QueryEditorHelp);
```
1. Create a few examples of potential queries:
```ts
import React from 'react';
import { QueryEditorHelpProps, DataQuery } from '@grafana/data';
const examples = [
{
title: 'Addition',
expression: '1 + 2',
label: 'Add two integers',
},
{
title: 'Subtraction',
expression: '2 - 1',
label: 'Subtract an integer from another',
},
];
export default (props: QueryEditorHelpProps) => {
return (
<div>
<h2>Cheat Sheet</h2>
{examples.map((item, index) => (
<div className="cheat-sheet-item" key={index}>
<div className="cheat-sheet-item__title">{item.title}</div>
{item.expression ? (
<div
className="cheat-sheet-item__example"
onClick={(e) => props.onClickExample({ refId: 'A', queryText: item.expression } as DataQuery)}
>
<code>{item.expression}</code>
</div>
) : null}
<div className="cheat-sheet-item__label">{item.label}</div>
</div>
))}
</div>
);
};
```

View File

@@ -0,0 +1,30 @@
---
title: Add support for annotations
---
# Add support for annotations
You can add support to your plugin for annotations that will insert information into Grafana alerts. This guide explains how to add support for [annotations]({{< relref "../../dashboards/build-dashboards/annotate-visualizations/#querying-other-data-sources " >}}) to a data source plugin.
## Support annotations in your data source plugin
To enable annotations, simply add two lines of code to your plugin. Grafana uses your default query editor for editing annotation queries.
1. Add `"annotations": true` to the [plugin.json]({{< relref "metadata/" >}}) file to let Grafana know that your plugin supports annotations.
**In `plugin.json`:**
```json
{
"annotations": true
}
```
2. In `datasource.ts`, override the `annotations` property from `DataSourceApi` (or `DataSourceWithBackend` for backend data sources). For the default behavior, set `annotations` to an empty object.
**In `datasource.ts`:**
```ts
annotations: {
}
```

View File

@@ -0,0 +1,70 @@
---
title: Add features to Explore queries
---
# Add features for Explore queries
[Explore]({{< relref "../../explore/" >}}) allows users can make ad-hoc queries without the use of a dashboard. This is useful when they want to troubleshoot or learn more about the data.
Your data source supports Explore by default and uses the existing query editor for the data source. This guide explains how to extend functionality for Explore queries in a data source plugin.
## Add an Explore-specific query editor
To extend Explore functionality for your data source, define an Explore-specific query editor.
1. Create a file `ExploreQueryEditor.tsx` in the `src` directory of your plugin, with content similar to this:
```ts
import React from 'react';
import { QueryEditorProps } from '@grafana/data';
import { QueryField } from '@grafana/ui';
import { DataSource } from './DataSource';
import { MyQuery, MyDataSourceOptions } from './types';
type Props = QueryEditorProps<DataSource, MyQuery, MyDataSourceOptions>;
export default (props: Props) => {
return <h2>My Explore-specific query editor</h2>;
};
```
1. Modify your base query editor in `QueryEditor.tsx` to render the Explore-specific query editor. For example:
```ts
// [...]
import { CoreApp } from '@grafana/data';
import ExploreQueryEditor from './ExploreQueryEditor';
type Props = QueryEditorProps<DataSource, MyQuery, MyDataSourceOptions>;
export default (props: Props) => {
const { app } = props;
switch (app) {
case CoreApp.Explore:
return <ExploreQueryEditor {...props} />;
default:
return <div>My base query editor</div>;
}
};
```
## Select a preferred visualization type
By default, Explore should select an appropriate and useful visualization for your data. It can figure out whether the returned data is time series data or logs or something else, and creates the right type of visualization.
However, if you want a custom visualization, you can add a hint to your returned data frame by setting the `meta' attribute to `preferredVisualisationType`.
Construct a data frame with specific metadata like this:
```
const firstResult = new MutableDataFrame({
fields: [...],
meta: {
preferredVisualisationType: 'logs',
},
});
```
For possible options, refer to [PreferredVisualisationType](https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/types/data.ts#L25).

View File

@@ -0,0 +1,198 @@
---
title: Add support for variables in plugins
---
# Add support for variables in plugins
Variables are placeholders for values, and you can use them to create templated queries, and dashboard or panel links. For more information on variables, refer to [Templates and variables]({{< relref "../../dashboards/variables/" >}}).
In this guide, you'll see how you can turn a query string like this:
```sql
SELECT * FROM services WHERE id = "$service"
```
into
```sql
SELECT * FROM services WHERE id = "auth-api"
```
Grafana provides a couple of helper functions to interpolate variables in a string template. Let's see how you can use them in your plugin.
## Interpolate variables in panel plugins
For panels, the `replaceVariables` function is available in the `PanelProps`.
Add `replaceVariables` to the argument list, and pass a user-defined template string to it:
```ts
export function SimplePanel({ options, data, width, height, replaceVariables }: Props) {
const query = replaceVariables('Now displaying $service');
return <div>{query}</div>;
}
```
## Interpolate variables in data source plugins
For data sources, you need to use the `getTemplateSrv`, which returns an instance of `TemplateSrv`.
1. Import `getTemplateSrv` from the `runtime` package:
```ts
import { getTemplateSrv } from '@grafana/runtime';
```
1. In your `query` method, call the `replace` method with a user-defined template string:
```ts
async query(options: DataQueryRequest<MyQuery>): Promise<DataQueryResponse> {
const query = getTemplateSrv().replace('SELECT * FROM services WHERE id = "$service"', options.scopedVars);
const data = makeDbQuery(query);
return { data };
}
```
## Format multi-value variables
When a user selects multiple values for a variable, the value of the interpolated variable depends on the [variable format]({{< relref "../../dashboards/variables/variable-syntax/#advanced-variable-format-options" >}}).
A data source plugin can define the default format option when no format is specified by adding a third argument to the interpolation function.
Let's change the SQL query to use CSV format by default:
```ts
getTemplateSrv().replace('SELECT * FROM services WHERE id IN ($service)', options.scopedVars, 'csv');
```
Now, when users write `$service`, the query looks like this:
```sql
SELECT * FROM services WHERE id IN (admin,auth,billing)
```
For more information on the available variable formats, refer to [Advanced variable format options]({{< relref "../../dashboards/variables/variable-syntax/#advanced-variable-format-options" >}}).
## Set a variable from your plugin
Not only can you read the value of a variable, you can also update the variable from your plugin. Use `locationService.partial(query, replace)`.
The following example shows how to update a variable called `service`.
- `query` contains the query parameters you want to update. The query parameters that control variables are prefixed with `var-`.
- `replace: true` tells Grafana to update the current URL state rather than creating a new history entry.
```ts
import { locationService } from '@grafana/runtime';
```
```ts
locationService.partial({ 'var-service': 'billing' }, true);
```
> **Note:** Grafana queries your data source whenever you update a variable. Excessive updates to variables can slow down Grafana and lead to a poor user experience.
## Add support for query variables to your data source
A [query variable]({{< relref "../../dashboards/variables/add-template-variables/#add-a-query-variable" >}}) is a type of variable that allows you to query a data source for the values. By adding support for query variables to your data source plugin, users can create dynamic dashboards based on data from your data source.
Let's start by defining a query model for the variable query:
```ts
export interface MyVariableQuery {
namespace: string;
rawQuery: string;
}
```
For a data source to support query variables, override the `metricFindQuery` in your `DataSourceApi` class. The `metricFindQuery` function returns an array of `MetricFindValue` which has a single property, `text`:
```ts
async metricFindQuery(query: MyVariableQuery, options?: any) {
// Retrieve DataQueryResponse based on query.
const response = await this.fetchMetricNames(query.namespace, query.rawQuery);
// Convert query results to a MetricFindValue[]
const values = response.data.map(frame => ({ text: frame.name }));
return values;
}
```
> **Note:** By default, Grafana provides a basic query model and editor for simple text queries. If that's all you need, then leave the query type as `string`:
```ts
async metricFindQuery(query: string, options?: any)
```
Let's create a custom query editor to allow the user to edit the query model.
1. Create a `VariableQueryEditor` component:
```ts
import React, { useState } from 'react';
import { MyVariableQuery } from './types';
interface VariableQueryProps {
query: MyVariableQuery;
onChange: (query: MyVariableQuery, definition: string) => void;
}
export const VariableQueryEditor = ({ onChange, query }: VariableQueryProps) => {
const [state, setState] = useState(query);
const saveQuery = () => {
onChange(state, `${state.query} (${state.namespace})`);
};
const handleChange = (event: React.FormEvent<HTMLInputElement>) =>
setState({
...state,
[event.currentTarget.name]: event.currentTarget.value,
});
return (
<>
<div className="gf-form">
<span className="gf-form-label width-10">Namespace</span>
<input
name="namespace"
className="gf-form-input"
onBlur={saveQuery}
onChange={handleChange}
value={state.namespace}
/>
</div>
<div className="gf-form">
<span className="gf-form-label width-10">Query</span>
<input
name="rawQuery"
className="gf-form-input"
onBlur={saveQuery}
onChange={handleChange}
value={state.rawQuery}
/>
</div>
</>
);
};
```
Grafana saves the query model whenever one of the text fields loses focus (`onBlur`) and then previews the values returned by `metricFindQuery`.
The second argument to `onChange` allows you to set a text representation of the query that will appear next to the name of the variable in the variables list.
1. Configure your plugin to use the query editor:
```ts
import { VariableQueryEditor } from './VariableQueryEditor';
export const plugin = new DataSourcePlugin<DataSource, MyQuery, MyDataSourceOptions>(DataSource)
.setQueryEditor(QueryEditor)
.setVariableQueryEditor(VariableQueryEditor);
```
That's it! You can now try out the plugin by adding a [query variable]({{< relref "../../dashboards/variables/add-template-variables/#add-a-query-variable" >}}) to your dashboard.

View File

@@ -0,0 +1,97 @@
---
aliases:
- ../../plugins/developing/backend-plugins-guide/
keywords:
- grafana
- plugins
- backend
- plugin
- backend-plugins
- documentation
title: Backend plugins
---
# Backend plugins
The Grafana plugin system for backend development allows you to integrate Grafana with virtually anything and offer custom visualizations. This document explains the system's background, use cases, and key features.
## Background
Grafana added support for _frontend plugins_ in version 3.0 so that the Grafana community could create custom panels and data sources. It was wildly successful and has made Grafana much more useful for our user community.
However, one limitation of these plugins is that they run on the client side, in the browser. Therefore, they can't support use cases that require server-side features.
Since Grafana v7.0, we have supported server-side plugins that remove this limitation. We use the term _backend plugin_ to denote that a plugin has a backend component. A backend plugin usually requires frontend components as well. For example, some backend data source plugins need query editor components on the frontend.
## Use cases for implementing a backend plugin
The following examples give some common use cases for backend plugins:
- Enable [Grafana Alerting]({{< relref "../../../alerting/" >}}) for data sources.
- Connect to SQL database servers and other non-HTTP services that normally can't be connected to from a browser.
- Keep state between users, for example, by query caching for data sources.
- Use custom authentication methods and/or authorization checks that aren't supported in Grafana.
- Use a custom data source request proxy (refer to [Resources]({{< relref "#resources" >}}) for more information).
## Grafana backend plugin system
The Grafana backend plugin system is based on HashiCorp's [Go Plugin System over RPC](https://github.com/hashicorp/go-plugin). Our implementation of the Grafana server launches each backend plugin as a subprocess and communicates with it over [gRPC](https://grpc.io/).
### Benefits for plugin development
Grafana's approach has benefits for developers:
- **Stability:** Plugins can't crash your Grafana process: a panic in a plugin doesn't panic the server.
- **Ease of development:** Plugins can be written in any language that supports gRPC (for example, write a Go application and run `go build`).
- **Security:** Plugins only have access to the interfaces and arguments given to them, not to the entire memory space of the process.
### Capabilities of the backend plugin system
Grafana's backend plugin system exposes several key capabilities, or building blocks, that your backend plugin can implement:
- Query data
- Resources
- Health checks
- Collect metrics
- Streaming
#### Query data
The query data capability allows a backend plugin to handle data source queries that are submitted from a [dashboard]({{< relref "../../../dashboards/" >}}), [Explore]({{< relref "../../../explore/" >}}) or [Grafana Alerting]({{< relref "../../../alerting/" >}}). The response contains [data frames]({{< relref "../data-frames/" >}}), which are used to visualize metrics, logs, and traces.
{{% admonition type="note" %}} Backend data source plugins are required to implement the query data capability.{{%
/admonition %}}
#### Resources
The resources capability allows a backend plugin to handle custom HTTP requests sent to the Grafana HTTP API and respond with custom HTTP responses. Here, the request and response formats can vary. For example, you can use JSON, plain text, HTML, or static resources such as images and files, and so on.
Compared to the query data capability, where the response contains data frames, the resources capability gives the plugin developer more flexibility for extending and opening up Grafana for new and interesting use cases.
### Use cases
Examples of use cases for implementing resources:
- Implement a custom data source proxy to provide certain authentication, authorization, or other requirements that are not supported in Grafana's [built-in data proxy]({{< relref "../../http_api/data_source/#data-source-proxy-calls" >}}).
- Return data or information in a format suitable for use within a data source query editor to provide auto-complete functionality.
- Return static resources such as images or files.
- Send a command to a device, such as a microcontroller or IoT device.
- Request information from a device, such as a microcontroller or IoT device.
- Extend Grafana's HTTP API with custom resources, methods and actions.
- Use [chunked transfer encoding](https://en.wikipedia.org/wiki/Chunked_transfer_encoding) to return large data responses in chunks or to enable certain streaming capabilities.
#### Health checks
The health checks capability allows a backend plugin to return the status of the plugin. For data source backend plugins, the health check is automatically called when a user edits a data source and selects _Save & Test_ in the UI.
A plugin's health check endpoint is exposed in the Grafana HTTP API and allows external systems to continuously poll the plugin's health to make sure that it's running and working as expected.
#### Collect metrics
A backend plugin can collect and return runtime, process, and custom metrics using the text-based Prometheus [exposition format](https://prometheus.io/docs/instrumenting/exposition_formats/). If you're using the [Grafana Plugin SDK for Go]({{< relref "grafana-plugin-sdk-for-go/" >}}) to implement your backend plugin, then the [Prometheus instrumentation library for Go applications](https://github.com/prometheus/client_golang) is built-in. This SDK gives you Go runtime metrics and process metrics out of the box. You can use the [Prometheus instrumentation library](https://github.com/prometheus/client_golang) to add custom metrics to instrument your backend plugin.
The Grafana HTTP API offers an endpoint (`/api/plugins/<plugin id>/metrics`) that allows you to configure a Prometheus instance to scrape the metrics.
#### Streaming
The streaming capability allows a backend plugin to handle data source queries that are streaming. For more information, refer to [Build a streaming data source plugin]({{<relref "../build-a-streaming-data-source-plugin.md">}})

View File

@@ -0,0 +1,28 @@
---
keywords:
- grafana
- plugins
- backend
- plugin
- backend-plugins
- sdk
- documentation
title: Grafana Plugin SDK for Go
---
# Grafana plugin SDK for Go
The [Grafana plugin SDK for Go](https://pkg.go.dev/mod/github.com/grafana/grafana-plugin-sdk-go?tab=overview) is a [Go](https://golang.org/) module that provides a set of [packages](https://pkg.go.dev/mod/github.com/grafana/grafana-plugin-sdk-go?tab=packages) that you can use to implement a backend plugin.
The plugin SDK provides a high-level framework with APIs, utilities, and tooling. By using the SDK, you can avoid the need to learn the details of the [plugin protocol]({{< relref "plugin-protocol/" >}}) and RPC communication protocol, so you don't have to manage either one.
## Versioning
The Grafana plugin Go SDK is still in development. It is based on the [plugin protocol]({{< relref "plugin-protocol/" >}}), which is versioned separately and is considered stable. However, from time to time, we might introduce breaking changes in the SDK.
When we update the plugin SDK, those plugins that use an older version of the SDK should still work with Grafana. However, these older plugins may be unable to use the new features and capabilities we introduce in updated SDK versions.
## See also
- [SDK source code](https://github.com/grafana/grafana-plugin-sdk-go)
- [Go reference documentation](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go)

View File

@@ -0,0 +1,45 @@
---
keywords:
- grafana
- plugins
- backend
- plugin
- backend-plugins
- documentation
title: Plugin protocol
---
# Plugin protocol
The Grafana server uses a physical wire protocol to communicate with backend plugins. This protocol establishes a contract between Grafana and backend plugins to allow them to communicate with each other.
## Developing with the plugin protocol
{{% admonition type="caution" %}} We strongly recommend that backend plugin development not be implemented directly against the protocol. Instead, we prefer that you use the [Grafana Plugin SDK for Go]({{< relref "grafana-plugin-sdk-for-go/" >}}) that implements this protocol and provides higher-level APIs. {{%
/admonition %}}
If you choose to develop against the plugin protocol directly, you can do so using [Protocol Buffers](https://developers.google.com/protocol-buffers) (that is, protobufs) with [gRPC](https://grpc.io/).
Grafana's plugin protocol protobufs are available in the [GitHub repository](https://github.com/grafana/grafana-plugin-sdk-go/blob/master/proto/backend.proto).
{{% admonition type="note" %}}
The plugin protocol lives in the [Grafana Plugin SDK for Go]({{< relref "grafana-plugin-sdk-for-go/" >}}) because Grafana itself uses parts of the SDK as a dependency.
{{% /admonition %}}
## Versioning
From time to time, Grafana will offer additions of services, messages, and fields in the latest version of the plugin protocol. We don't expect these updates to introduce any breaking changes. However, if we must introduce breaking changes to the plugin protocol, we'll create a new major version of the plugin protocol.
Grafana will release new major versions of the plugin protocol alongside new major Grafana releases. When this happens, we'll support both the old and the new plugin protocol for some time to make sure existing backend plugins continue to work.
The plugin protocol attempts to follow Grafana's versioning. However, that doesn't mean we will automatically create a new major version of the plugin protocol when a new major release of Grafana is released.
## Writing plugins without Go
If you want to write a backend plugin in a language other than Go, then it's possible as long as the language supports gRPC. However, we recommend that you develop your plugin in Go for several reasons:
- We offer an official plugin SDK.
- The compiled output is a single binary.
- Writing for multiple platforms is easy. Typically, no additional dependencies must be installed on the target platform.
- Small footprint for binary size.
- Small footprint for resource usage.

View File

@@ -0,0 +1,148 @@
---
title: Build a logs data source plugin
---
# Build a logs data source plugin
Grafana data source plugins support metrics, logs, and other data types. The steps to build a logs data source plugin are largely the same as for a metrics data source, but there are a few differences which we will explain in this guide.
## Before you begin
This guide assumes that you're already familiar with how to [Build a data source plugin](/tutorials/build-a-data-source-plugin/) for metrics. We recommend that you review this material before continuing.
## Add logs support to your data source
To add logs support to an existing data source, you need to:
1. Enable logs support
1. Construct the log data
When these steps are done, then you can improve the user experience with one or more [optional features](#enhance-your-logs-data-source-plugin-with-optional-features).
### Step 1: Enable logs support
Tell Grafana that your data source plugin can return log data, by adding `"logs": true` to the [plugin.json]({{< relref "metadata/" >}}) file.
```json
{
"logs": true
}
```
### Step 2: Construct the log data
As it does with metrics data, Grafana expects your plugin to return log data as a [data frame]({{< relref "data-frames/" >}}).
To return log data, return a data frame with at least one time field and one text field from the data source's `query` method.
**Example:**
```ts
const frame = new MutableDataFrame({
refId: query.refId,
fields: [
{ name: 'time', type: FieldType.time },
{ name: 'content', type: FieldType.string },
],
});
frame.add({ time: 1589189388597, content: 'user registered' });
frame.add({ time: 1589189406480, content: 'user logged in' });
```
That's all you need to start returning log data from your data source. Go ahead and try it out in [Explore]({{< relref "../../explore/" >}}) or by adding a [Logs panel]({{< relref "../../panels-visualizations/visualizations/logs/" >}}).
Congratulations, you just wrote your first logs data source plugin! Next, let's look at a couple of features that can further improve the experience for the user.
## Enhance your logs data source plugin with optional features
Add visualization type hints, labels, and other optional features to logs.
### Add a preferred visualization type hint to the data frame
To make sure Grafana recognizes data as logs and shows logs visualization automatically in Explore, set `meta.preferredVisualisationType` to `'logs'` in the returned data frame. See [Selecting preferred visualisation section]({{< relref "add-support-for-explore-queries/#selecting-preferred-visualisation" >}})
**Example:**
```ts
const frame = new MutableDataFrame({
refId: query.refId,
meta: {
preferredVisualisationType: 'logs',
},
fields: [
{ name: 'time', type: FieldType.time },
{ name: 'content', type: FieldType.string },
],
});
```
### Add labels to your logs
Many log systems let you query logs based on metadata, or _labels_, to help filter log lines.
Add labels to a stream of logs by setting the `labels` property on the Field.
**Example**:
```ts
const frame = new MutableDataFrame({
refId: query.refId,
fields: [
{ name: 'time', type: FieldType.time },
{ name: 'content', type: FieldType.string, labels: { filename: 'file.txt' } },
],
});
frame.add({ time: 1589189388597, content: 'user registered' });
frame.add({ time: 1589189406480, content: 'user logged in' });
```
### Extract detected fields from your logs
Add additional information about each log line by supplying more data frame fields.
If a data frame has more than one text field, then Grafana assumes the first field in the data frame to be the actual log line. Grafana treats subsequent text fields as [detected fields]({{< relref "../../explore/#labels-and-detected-fields" >}}).
Any number of custom fields can be added to your data frame; Grafana comes with two dedicated fields: `levels` and `id`.
#### Levels
To set the level for each log line, add a `level` field.
**Example:**
```ts
const frame = new MutableDataFrame({
refId: query.refId,
fields: [
{ name: 'time', type: FieldType.time },
{ name: 'content', type: FieldType.string, labels: { filename: 'file.txt' } },
{ name: 'level', type: FieldType.string },
],
});
frame.add({ time: 1589189388597, content: 'user registered', level: 'info' });
frame.add({ time: 1589189406480, content: 'unknown error', level: 'error' });
```
#### 'id' for assigning unique identifiers to log lines
By default, Grafana offers basic support for deduplicating log lines. You can improve the support by adding an `id` field to explicitly assign identifiers to each log line.
**Example:**
```ts
const frame = new MutableDataFrame({
refId: query.refId,
fields: [
{ name: 'time', type: FieldType.time },
{ name: 'content', type: FieldType.string, labels: { filename: 'file.txt' } },
{ name: 'level', type: FieldType.string },
{ name: 'id', type: FieldType.string },
],
});
frame.add({ time: 1589189388597, content: 'user registered', level: 'info', id: 'd3b07384d113edec49eaa6238ad5ff00' });
frame.add({ time: 1589189406480, content: 'unknown error', level: 'error', id: 'c157a79031e1c40f85931829bc5fc552' });
```

View File

@@ -0,0 +1,151 @@
---
title: Build a streaming data source plugin
---
# Build a streaming data source plugin
In Grafana, you can set your dashboards to automatically refresh at a certain interval, no matter what data source you use. Unfortunately, this means that your queries are requesting all the data to be sent again, regardless of whether the data has actually changed. Adding streaming to a plugin helps reduce queries so your dashboard is only updated when new data becomes available.
## Before you begin
This guide assumes that you're already familiar with how to [Build a data source plugin](/tutorials/build-a-data-source-plugin/).
Grafana uses [RxJS](https://rxjs.dev/) to continuously send data from a data source to a panel visualization.
> **Note:** To learn more about RxJs, refer to the [RxJS documentation](https://rxjs.dev/guide/overview).
## Add streaming to your data source
Enable streaming for your data source plugin to update your dashboard when new data becomes available.
For example, a streaming data source plugin can connect to a websocket, or subscribe to a message bus, and update the visualization whenever a new message is available.
### Step 1: Edit the `plugin.json` file
Enable streaming for your data source in the `plugin.json` file.
```json
{
"streaming": true
}
```
### Step 2: Change the signature of the `query` method
Modify the signature of the `query` method to return an `Observable` from the `rxjs` package. Make sure you remove the `async` keyword.
```ts
import { Observable } from 'rxjs';
```
```ts
query(options: DataQueryRequest<MyQuery>): Observable<DataQueryResponse> {
// ...
}
```
### Step 3: Create an `Observable` instance for each query
Create an `Observable` instance for each query, and then combine them all using the `merge` function from the `rxjs` package.
```ts
import { Observable, merge } from 'rxjs';
```
```ts
const observables = options.targets.map((target) => {
return new Observable<DataQueryResponse>((subscriber) => {
// ...
});
});
return merge(...observables);
```
### Step 4: Create a `CircularDataFrame` instance
In the `subscribe` function, create a `CircularDataFrame` instance.
```ts
import { CircularDataFrame } from '@grafana/data';
```
```ts
const frame = new CircularDataFrame({
append: 'tail',
capacity: 1000,
});
frame.refId = query.refId;
frame.addField({ name: 'time', type: FieldType.time });
frame.addField({ name: 'value', type: FieldType.number });
```
Circular data frames have a limited capacity. When a circular data frame reaches its capacity, the oldest data point is removed.
### Step 5: Send the updated data frame
Use `subscriber.next()` to send the updated data frame whenever you receive new updates.
```ts
import { LoadingState } from '@grafana/data';
```
```ts
const intervalId = setInterval(() => {
frame.add({ time: Date.now(), value: Math.random() });
subscriber.next({
data: [frame],
key: query.refId,
state: LoadingState.Streaming,
});
}, 500);
return () => {
clearInterval(intervalId);
};
```
> **Note:** In practice, you'd call `subscriber.next` as soon as you receive new data from a websocket or a message bus. In the example above, data is being received every 500 milliseconds.
### Example code for final `query` method
```ts
query(options: DataQueryRequest<MyQuery>): Observable<DataQueryResponse> {
const streams = options.targets.map(target => {
const query = defaults(target, defaultQuery);
return new Observable<DataQueryResponse>(subscriber => {
const frame = new CircularDataFrame({
append: 'tail',
capacity: 1000,
});
frame.refId = query.refId;
frame.addField({ name: 'time', type: FieldType.time });
frame.addField({ name: 'value', type: FieldType.number });
const intervalId = setInterval(() => {
frame.add({ time: Date.now(), value: Math.random() });
subscriber.next({
data: [frame],
key: query.refId,
state: LoadingState.Streaming,
});
}, 100);
return () => {
clearInterval(intervalId);
};
});
});
return merge(...streams);
}
```
One limitation with this example is that the panel visualization is cleared every time you update the dashboard. If you have access to historical data, you can add it, or _backfill_ it, to the data frame before the first call to `subscriber.next()`.
For another example of a streaming plugin, refer to the [streaming websocket example](https://github.com/grafana/grafana-plugin-examples/tree/main/examples/datasource-streaming-websocket) on GitHub.

View File

@@ -0,0 +1,27 @@
---
description: How-to topics for plugin development
title: Create a plugin
---
# Create a Grafana plugin
This section contains how-to topics for developing Grafana plugins.
- [Build a Grafana plugin](https://grafana.github.io/plugin-tools/docs/creating-a-plugin)
- [Build a panel plugin](https://grafana.com/tutorials/build-a-panel-plugin/)
- [Build a data source plugin](https://grafana.com/tutorials/build-a-data-source-plugin/)
- [Build a data source backend plugin](https://grafana.com/tutorials/build-a-data-source-backend-plugin/)
- [Build a logs data source plugin]({{< relref "../build-a-logs-data-source-plugin.md">}})
- [Build a streaming data source plugin]({{< relref "../build-a-streaming-data-source-plugin.md">}})
- Extend a Grafana plugin
- [Add annotations]({{< relref "add-support-for-annotations.md">}})
- [Add anonymous usage reporting]({{< relref "add-anonymous-usage-reporting.md">}})
- [Add authentication for a data source plugin]({{< relref "add-authentication-for-data-source-plugins.md">}})
- [Add Explore queries]({{< relref "add-support-for-explore-queries.md">}})
- [Add query editor help]({{< relref "add-query-editor-help.md">}})
- [Add variables]({{< relref "add-support-for-variables.md">}})
- [Create panel option editors]({{< relref "custom-panel-option-editors.md">}})
- [Sign a plugin]({{< relref "sign-a-plugin.md">}})
- [Automate development with CI](https://grafana.github.io/plugin-tools/docs/ci)
- [Create nested plugins](https://grafana.github.io/plugin-tools/docs/nested-plugins)
- [Extend configurations](https://grafana.github.io/plugin-tools/docs/advanced-configuration)

View File

@@ -0,0 +1,74 @@
---
title: Work with cross-plugin links
description: Learn how to add plugin links to a Grafana app plugin
---
# Work with cross-plugin links
With the Plugins extension API, app plugins can register extension points of their own to display other plugins links. This is called _cross-plugin linking_, and you can use it to create more immersive user experiences with installed plugins.
## Available extension points within plugins
An extension point is a location in another plugin's UI where your plugin can insert links. All extension point IDs within plugins should follow the naming convention `plugins/<plugin-id>/<extension-point-id>`.
## How to create an extension point within a plugin
Use the `getPluginExtensions` method in `@grafana/runtime` to create an extension point within your plugin. An extension point is a way to specify where in the plugin UI other plugins links are rendered.
{{% admonition type="note" %}}
Creating an extension point in a plugin creates a public interface for other plugins to interact with. Changes to the extension point ID or its context could break any plugin that attempts to register a link inside your plugin.
{{% /admonition %}}
The `getPluginExtensions` method takes an object consisting of the `extensionPointId`, which must begin `plugin/<pluginId>`, and any contextual information that you want to provide. The `getPluginExtensions` method returns a list of `extensionLinks` that your program can loop over:
```typescript
import { getPluginExtensions } from '@grafana/runtime';
import { isPluginExtensionLink } from '@grafana/data';
import { LinkButton } from '@grafana/ui';
function AppExtensionPointExample() {
const { extensions } = getPluginExtensions({
extensionPointId: 'plugin/another-app-plugin/menu',
context: {
pluginId: 'another-app-plugin',
},
});
if (extensions.length === 0) {
return null;
}
return (
<div>
{extensions.map((extension) => {
if (isPluginExtensionLink(extension)) {
return (
<LinkButton href={extension.path} title={extension.description} key={extension.key}>
{extension.title}
</LinkButton>
);
}
return null;
})}
</div>
);
}
```
The preceding example shows a component that renders `<LinkButton />` components for all link extensions that other plugins registered for the `plugin/another-app-plugin/menu` extension point ID. The context is passed as the second parameter to `getPluginExtensions`, which uses `Object.freeze` to make the context immutable before passing it to other plugins.
## Insert links into another plugin
Create links for other plugins in the same way you [extend the Grafana application UI]({{< relref "./extend-the-grafana-ui-with-links" >}}) with a link. Don't specify a `grafana/...` extension point. Instead, specify the plugin extension point `plugin/<pluginId>/<extensionPointId>`.
Given the preceding example, use a plugin link such as the following:
```typescript
new AppPlugin().configureExtensionLink({
title: 'Go to basic app',
description: 'Will navigate the user to the basic app',
extensionPointId: 'plugin/another-app-plugin/menu',
path: '/a/myorg-basic-app/one',
});
```

View File

@@ -0,0 +1,121 @@
---
title: Build a custom panel option editor
---
# Build a custom panel option editor
The Grafana plugin platform comes with a range of editors that allow your users to customize a panel. The standard editors cover the most common types of options, such as text input and boolean switches. If you don't find the editor you're looking for, you can build your own.
## Panel option editor basics
The simplest editor is a React component that accepts two props:
- **`value`**: the current value of the option
- **`onChange`**: updates the option's value
The editor in the example below lets the user toggle a boolean value by clicking a button:
**SimpleEditor.tsx**
```ts
import React from 'react';
import { Button } from '@grafana/ui';
import { StandardEditorProps } from '@grafana/data';
export const SimpleEditor = ({ value, onChange }: StandardEditorProps<boolean>) => {
return <Button onClick={() => onChange(!value)}>{value ? 'Disable' : 'Enable'}</Button>;
};
```
To use a custom panel option editor, use the `addCustomEditor` on the `OptionsUIBuilder` object in your `module.ts` file and set the `editor` property to the name of your custom editor component.
**module.ts**
```ts
export const plugin = new PanelPlugin<SimpleOptions>(SimplePanel).setPanelOptions((builder) => {
return builder.addCustomEditor({
id: 'label',
path: 'label',
name: 'Label',
editor: SimpleEditor,
});
});
```
## Add settings to your panel option editor
You can use your custom editor to customize multiple possible settings. To add settings to your editor, set the second template variable of `StandardEditorProps` to an interface that contains the settings you want to configure. Access the editor settings through the `item` prop.
Here's an example of an editor that populates a drop-down with a range of numbers. The `Settings` interface defines the range of the `from` and `to` properties.
**SimpleEditor.tsx**
```ts
interface Settings {
from: number;
to: number;
}
type Props = StandardEditorProps<number, Settings>;
export const SimpleEditor = ({ item, value, onChange }: Props) => {
const options: Array<SelectableValue<number>> = [];
// Default values
const from = item.settings?.from ?? 1;
const to = item.settings?.to ?? 10;
for (let i = from; i <= to; i++) {
options.push({
label: i.toString(),
value: i,
});
}
return <Select options={options} value={value} onChange={(selectableValue) => onChange(selectableValue.value)} />;
};
```
You can now configure the editor for each option by configuring the `settings` property to call `addCustomEditor`:
```ts
export const plugin = new PanelPlugin<SimpleOptions>(SimplePanel).setPanelOptions((builder) => {
return builder.addCustomEditor({
id: 'index',
path: 'index',
name: 'Index',
editor: SimpleEditor,
settings: {
from: 1,
to: 10,
},
});
});
```
## Use query results in your panel option editor
Option editors can access the results from the last query. This lets you update your editor dynamically based on the data returned by the data source.
The editor context is available through the `context` prop. The data frames returned by the data source are available under `context.data`.
**SimpleEditor.tsx**
```ts
export const SimpleEditor = ({ item, value, onChange, context }: StandardEditorProps<string>) => {
const options: SelectableValue<string>[] = [];
if (context.data) {
const frames = context.data;
for (let i = 0; i < frames.length; i++) {
options.push({
label: frames[i].name,
value: frames[i].name,
});
}
}
return <Select options={options} value={value} onChange={(selectableValue) => onChange(selectableValue.value)} />;
};
```

View File

@@ -0,0 +1,182 @@
---
title: Data frames
---
# Data frames
Grafana supports a variety of different data sources, each with its own data model. To make this possible, Grafana consolidates the query results from each of these data sources into one unified data structure called a _data frame_.
The data frame structure is a concept that's borrowed from data analysis tools like the [R programming language](https://www.r-project.org) and [Pandas](https://pandas.pydata.org/).
> Data frames are available in Grafana 7.0+, and replaced the Time series and Table structures with a more generic data structure that can support a wider range of data types.
This document gives an overview of the data frame structure, and of how data is handled within Grafana.
## The data frame
A data frame is a columnar-oriented table structure, which means it stores data by column and not by row. To understand what this means, lets look at the TypeScript definition used by Grafana:
```ts
interface DataFrame {
name?: string;
// reference to query that create the frame
refId?: string;
fields: []Field;
}
```
In essence, a data frame is a collection of _fields_, where each field corresponds to a column. Each field, in turn, consists of a collection of values, along with meta information, such as the data type of those values.
```ts
interface Field {
name: string;
// Prometheus like Labels / Tags
labels?: Record<string, string>;
// For example string, number, time (or more specific primitives in the backend)
type: FieldType;
// Array of values all of the same type
values: Vector<T>;
// Optional display data for the field (e.g. unit, name over-ride, etc)
config: FieldConfig;
}
```
Let's look at an example. The table below demonstrates a data frame with two fields, _time_ and _temperature_.
| time | temperature |
| ------------------- | ----------- |
| 2020-01-02 03:04:00 | 45.0 |
| 2020-01-02 03:05:00 | 47.0 |
| 2020-01-02 03:06:00 | 48.0 |
Each field has three values, and each value in a field must share the same type. In this case, all values in the time field are timestamps, and all values in the temperature field are numbers.
One restriction on data frames is that all fields in the frame must be of the same length to be a valid data frame.
### Field configuration
Each field in a data frame contains optional information about the values in the field, such as units, scaling, and so on.
By adding field configurations to a data frame, Grafana can configure visualizations automatically. For example, you could configure Grafana to automatically set the unit provided by the data source.
## Transformations
Along with the type information, field configs enable _data transformations_ within Grafana.
A data transformation is any function that accepts a data frame as input, and returns another data frame as output. By using data frames in your plugin, you get a range of transformations for free.
## Data frames as time series
A data frame with at least one time field is considered a _time series_.
For more information on time series, refer to our [Introduction to time series]({{< relref "../../fundamentals/timeseries/" >}}).
### Wide format
When a collection of time series shares the same _time index_—the time fields in each time series are identical—they can be stored together, in a _wide_ format. By reusing the time field, we can reduce the amount of data being sent to the browser.
In this example, the `cpu` usage from each host share the time index, so we can store them in the same data frame.
```text
Name: Wide
Dimensions: 3 fields by 2 rows
+---------------------+-----------------+-----------------+
| Name: time | Name: cpu | Name: cpu |
| Labels: | Labels: host=a | Labels: host=b |
| Type: []time.Time | Type: []float64 | Type: []float64 |
+---------------------+-----------------+-----------------+
| 2020-01-02 03:04:00 | 3 | 4 |
| 2020-01-02 03:05:00 | 6 | 7 |
+---------------------+-----------------+-----------------+
```
However, if the two time series don't share the same time values, they are represented as two distinct data frames.
```text
Name: cpu
Dimensions: 2 fields by 2 rows
+---------------------+-----------------+
| Name: time | Name: cpu |
| Labels: | Labels: host=a |
| Type: []time.Time | Type: []float64 |
+---------------------+-----------------+
| 2020-01-02 03:04:00 | 3 |
| 2020-01-02 03:05:00 | 6 |
+---------------------+-----------------+
Name: cpu
Dimensions: 2 fields by 2 rows
+---------------------+-----------------+
| Name: time | Name: cpu |
| Labels: | Labels: host=b |
| Type: []time.Time | Type: []float64 |
+---------------------+-----------------+
| 2020-01-02 03:04:01 | 4 |
| 2020-01-02 03:05:01 | 7 |
+---------------------+-----------------+
```
The wide format can typically be used when multiple time series are collected by the same process. In this case, every measurement is made at the same interval and will therefore share the same time values.
### Long format
Some data sources return data in a _long_ format (also called _narrow_ format). This is common format returned by, for example, SQL databases.
In long format, string values are represented as separate fields rather than as labels. As a result, a data form in long form may have duplicated time values.
Grafana can detect and convert data frames in long format into wide format.
> **Note:** Long format is currently only supported in the backend: [Grafana Issue #22219](https://github.com/grafana/grafana/issues/22219).
For example, the following data frame in long format:
```text
Name: Long
Dimensions: 4 fields by 4 rows
+---------------------+-----------------+-----------------+----------------+
| Name: time | Name: aMetric | Name: bMetric | Name: host |
| Labels: | Labels: | Labels: | Labels: |
| Type: []time.Time | Type: []float64 | Type: []float64 | Type: []string |
+---------------------+-----------------+-----------------+----------------+
| 2020-01-02 03:04:00 | 2 | 10 | foo |
| 2020-01-02 03:04:00 | 5 | 15 | bar |
| 2020-01-02 03:05:00 | 3 | 11 | foo |
| 2020-01-02 03:05:00 | 6 | 16 | bar |
+---------------------+-----------------+-----------------+----------------+
```
can be converted into a data frame in wide format:
```text
Name: Wide
Dimensions: 5 fields by 2 rows
+---------------------+------------------+------------------+------------------+------------------+
| Name: time | Name: aMetric | Name: bMetric | Name: aMetric | Name: bMetric |
| Labels: | Labels: host=foo | Labels: host=foo | Labels: host=bar | Labels: host=bar |
| Type: []time.Time | Type: []float64 | Type: []float64 | Type: []float64 | Type: []float64 |
+---------------------+------------------+------------------+------------------+------------------+
| 2020-01-02 03:04:00 | 2 | 10 | 5 | 15 |
| 2020-01-02 03:05:00 | 3 | 11 | 6 | 16 |
+---------------------+------------------+------------------+------------------+------------------+
```
> **Note:** Not all panels support the wide time series data frame format. To keep full backward compatibility we've introduced a transformation that can be used to convert from the wide to the long format. For usage information, refer to the [Prepare time series-transformation]({{< relref "../../panels-visualizations/query-transform-data/transform-data/#prepare-time-series" >}}).
## Technical references
This section contains links to technical reference and implementations of data frames.
### Apache Arrow
The data frame structure is inspired by, and uses the [Apache Arrow Project](https://arrow.apache.org/). Javascript Data frames use Arrow Tables as the underlying structure, and the backend Go code serializes its Frames in Arrow Tables for transmission.
### Javascript
The Javascript implementation of data frames is in the [`/src/dataframe` folder](https://github.com/grafana/grafana/tree/main/packages/grafana-data/src/dataframe) and [`/src/types/dataframe.ts`](https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/types/dataFrame.ts) of the [`@grafana/data` package](https://github.com/grafana/grafana/tree/main/packages/grafana-data).
### Go
For documentation on the Go implementation of data frames, refer to the [github.com/grafana/grafana-plugin-sdk-go/data package](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go/data?tab=doc).

View File

@@ -0,0 +1,178 @@
---
title: Develop with a local environment
---
# Develop with a local environment
Follow the steps in this guide to set up a development environment where you run Grafana and your plugin locally. With this setup, you can see your changes as you add them.
## Run Grafana in your host
To clone and run Grafana locally:
1. Download and set up Grafana. Refer to the [developer-guide](https://github.com/grafana/grafana/blob/HEAD/contribute/developer-guide.md).
2. Grafana looks for plugins, by default, in its `data/plugins` directory. You can create a symbolic link to your plugin repository to detect new changes:
```bash
ln -s <plugin-path>/dist data/plugins/<plugin-name>
```
3. Optional: If the preceding step doesn't work for you (for example, if you are running on Windows), then modify the default path in the Grafana configuration. Find the default path at `conf/custom.ini`) and point it to your plugin's directory:
```ini
[paths]
plugins = <path-to-your-plugin-parent-directory>
```
## Run Grafana with docker-compose
Another option is to run Grafana with docker-compose so that it runs in a container. To do so, create the `docker-compose` file in your plugin directory.
{{% admonition type="note" %}}
If your plugin already includes a docker-compose file, then skip this step.
{{% /admonition %}}
```yaml
version: '3.7'
services:
grafana:
# Change latest with your target version, if needed
image: grafana/grafana:latest
ports:
- 3000:3000/tcp
volumes:
# Use your plugin folder (for example, redshift-datasource)
- ./dist:/var/lib/grafana/plugins/<plugin-folder>
- ./provisioning:/etc/grafana/provisioning
environment:
- TERM=linux
- GF_LOG_LEVEL=debug
- GF_DATAPROXY_LOGGING=true
- GF_DEFAULT_APP_MODE=development
```
## Run your plugin in development mode
Finally, start your plugin in development mode. Go to your plugin's root directory and follow these steps:
1. Build your plugin backend and start the frontend in watch mode:
```bash
mage -v
yarn watch
```
2. Start the Grafana backend and frontend:
1. For a local copy of Grafana, go to the directory with Grafana source code and run:
```bash
make run
```
```bash
yarn start
```
2. Or, with docker-compose, in your plugin directory, run:
```bash
docker-compose up
```
After this, you should be able to see your plugin listed in Grafana, and then you can test your changes.
If you make a change in the frontend, you must refresh your browser. However, changes in the backend may require that you rebuild your plugin binaries and reload the plugin (`mage && mage reloadPlugin` for local development, or run `docker-compose up` again if you are using docker-compose).
## Run your backend plugin with a debugger
{{% admonition type="note" %}}
The following method only works with a local Grafana instance and currently doesn't work with Docker.
{{% /admonition %}}
Running a backend plugin with a debugger is supported in Visual Studio Code and GoLand out of the box, but it can also work with any other IDE or debugger.
You can run a backend plugin and attach a debugger to it, which allows you to set breakpoints and debug your backend plugin directly from your IDE of choice:
1. Go to your plugin's folder.
1. Check your `go.mod` to make sure `grafana-plugin-sdk-go` are at least on `v0.156.0`
- If not, update it to the latest version:
```
go get -u github.com/grafana/grafana-plugin-sdk-go
```
1. Build your plugin at least once:
```
yarn build && mage
```
1. Install your plugin into your local Grafana instance.
Now that your plugin is ready to run, follow the instructions bellow for your IDE of choice.
### Visual Studio Code
1. If it's not already present, go to your plugin's folder and place the following file inside `.vscode/launch.json`:
```json
{
"version": "0.2.0",
"configurations": [
{
"name": "Standalone debug mode",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}/pkg",
"env": {},
"args": ["-standalone"]
}
]
}
```
1. Press `F5` to run your plugin in debug mode.
1. If Grafana isn't already running, run it.
> If you re-run the configuration, Grafana automatically reloads the plugin.
### GoLand
1. Create a new Run/Debug configuration:
- **Run kind**: Package
- **Package path**: your `pkg` package
- **Program arguments**: `-standalone`
1. Run the config (with or without the debugger).
1. If Grafana isn't already running, run it.
{{% admonition type="note" %}}
If you re-run the configuration, Grafana automatically reloads the plugin.
{{% /admonition %}}
### Other IDEs
Configure your code editor to run the following steps:
1. Build the executable file with debug flags.
```
mage build:debug
```
1. Run the plugin's executable file (inside `dist`) with `-standalone -debug` flags.
```
./gpx_xyz_linux_amd64 -standalone -debug
```
1. Attach a debugger to the process.
1. If Grafana isn't already running, run it.
> If you re-run the configuration, Grafana automatically reloads the plugin.
### Notes
- All logs are printed in the plugin's `stdout` rather than in Grafana logs.
- If the backend plugin doesn't serve requests after you turn off debug mode, you can force a reset to the standalone mode. To do so, delete the files `dist/standalone.txt`, `dist/pid.txt`, and the executable file, and then restart Grafana.
- Grafana doesn't support debugging backend plugins running inside Docker. But this is a [planned enhancement](https://github.com/grafana/grafana-plugin-sdk-go/issues/685).

View File

@@ -0,0 +1,63 @@
---
title: Error handling
---
# Error handling
This guide explains how to handle errors in plugins.
## Provide usable defaults
Allow the user to learn your plugin in small steps. Provide a useful default configuration so that:
- The user can get started right away.
- You can avoid unnecessary error messages.
For example, by selecting the first field of an expected type, the panel can display a visualization without any user configuration. If a user explicitly selects a field, then use that one. Otherwise, default to the first field of type `string`:
```ts
const numberField = frame.fields.find((field) =>
options.numberFieldName ? field.name === options.numberFieldName : field.type === FieldType.number
);
```
## Display error messages
To display an error message to the user, `throw` an `Error` with the message you want to display:
```ts
throw new Error('An error occurred');
```
Grafana displays the error message in the top-left corner of the panel.
{{< figure src="/static/img/docs/panel_error.png" class="docs-image--no-shadow" max-width="850px" >}}
Avoid displaying overly-technical error messages to the user. If you want to let technical users report an error, consider logging it instead.
```ts
try {
failingFunction();
} catch (err) {
console.error(err);
throw new Error('Something went wrong');
}
```
> **Note:** Grafana displays the exception message in the UI as written, so we recommend using grammatically correct sentences. For more information, refer to the [Documentation style guide](https://github.com/grafana/grafana/blob/main/contribute/style-guides/documentation-style-guide.md).
Here are some examples of situations where you might want to display an error to the user.
### Invalid query response
Users have full freedom when they create data source queries for panels. If your panel plugin requires a specific format for the query response, then use the panel canvas to guide the user.
```ts
if (!numberField) {
throw new Error('Query result is missing a number field');
}
if (frame.length === 0) {
throw new Error('Query returned an empty result');
}
```

View File

@@ -0,0 +1,132 @@
---
title: Use extensions to add links to app plugins
description: Learn how to add links to the Grafana user interface from an app plugin
---
# Use extensions to add links to app plugins
You can use the Plugin extensions API with your Grafana app plugins to add links to the Grafana UI. This feature lets you send users to your plugin's pages from other spots in the Grafana application.
## Before you begin
Be sure your plugin meets the following requirements before proceeding:
- It must be an app plugin.
- It must be preloaded (by setting the [preload property]({{< relref "./metadata" >}}) to `true` in the `plugin.json`
- It must be installed and enabled.
## Available extension points within Grafana
An _extension point_ is a location within the Grafana UI where a plugin can insert links. The IDs of all extension points within Grafana start with `grafana/`. For example, you can use the following extension point ID:
- `grafana/dashboard/panel/menu`: extension point for all panel dropdown menus in dashboards
## Add a link extension within a Grafana dashboard panel menu
To add a link extension within a Grafana dashboard panel menu, complete the following steps:
1. Define the link extension in your plugin's `module.ts` file.
1. Define a new instance of the `AppPlugin` class by using the `configureExtensionLink` method. This method requires:
- an object that describes your link extension, including a `title` property for the link text
- an `extensionPointId` method that tells Grafana where the link should appear
- a `path` for the user to go to your plugin
```typescript
new AppPlugin().configureExtensionLink({
title: 'Go to basic app',
description: 'Will send the user to the basic app',
extensionPointId: 'grafana/dashboard/panel/menu',
path: '/a/myorg-basic-app/one', // Must start with "/a/<PLUGIN_ID>/"
});
```
Your link will now appear in dashboard panel menus. When the user clicks the link, they will be sent to the path you defined earlier.
{{% admonition type="note" %}} Each plugin is limited to a maximum of two links per extension point.{{%
/admonition %}}
## Add a link extension using context within Grafana
The above example works for simple cases. However, you may want to act on information from the app's panel from which the user is navigating.
To do this, use the `configure` property on the object that is passed to `configureExtensionLink()`. This property takes a function and returns an object that consists of a `title` property for the link text and a `path` to send the user to your plugin.
Alternatively, if you need to hide the link for certain scenarios, define the function to return _undefined_:
```typescript
new AppPlugin().configureExtensionLink({
title: 'Go to basic app',
description: 'Will send the user to the basic app',
extensionPointId: 'grafana/dashboard/panel/menu',
path: '/a/myorg-basic-app/one',
configure: (context: PanelContext) => {
switch (context?.pluginId) {
case 'timeseries':
return {
title: 'Go to page one',
description: 'hello',
path: '/a/myorg-basic-app/one',
};
case 'piechart':
return {
title: 'Go to page two',
path: '/a/myorg-basic-app/two',
};
// Returning undefined tells Grafana to hide the link
default:
return undefined;
}
},
});
```
The above example demonstrates how to return a different `path` based on which plugin the dashboard panel is using. If the clicked-upon panel is neither a time series nor a pie chart panel, then the `configure()` function returns _undefined_. When this happens, Grafana doesn't render the link.
{{% admonition type="note" %}} The context passed to the `configure()` function is bound by the `extensionPointId` into which you insert the link. Different extension points contain different contexts.{{%
/admonition %}}
## Add an event handler to a link
Link extensions give you the means to direct users to a plugin page via href links within the Grafana UI. You can also use them to trigger `onClick` events to perform dynamic actions when clicked.
To add an event handler to a link in a panel menu, complete the following steps:
1. Define the link extension in the plugin's `module.ts` file.
1. Create a new instance of the `AppPlugin` class, again using the `configureExtensionLink` method. This time, add an `onClick` property which takes a function. This function receives the click event and an object consisting of the `context` and an `openModal` function.
In the following example, we open a dialog.
```typescript
new AppPlugin().configureExtensionLink({
title: 'Go to basic app',
description: 'Will send the user to the basic app',
extensionPointId: 'grafana/dashboard/panel/menu',
path: '/a/myorg-basic-app/one',
onClick: (event, { context, openModal }) => {
event.preventDefault();
openModal({
title: 'My plugin dialog',
body: ({ onDismiss }) => <SampleModal onDismiss={onDismiss} pluginId={context?.pluginId} />,
});
},
});
type Props = {
onDismiss: () => void;
pluginId?: string;
};
const SampleModal = ({ onDismiss, pluginId }: Props) => {
return (
<VerticalGroup spacing="sm">
<p>This dialog was opened via the plugin extensions API.</p>
<p>The panel is using a {pluginId} plugin to display data.</p>
</VerticalGroup>
);
};
```
As you can see, the plugin extensions API enables you to insert links into the UI of Grafana applications that send users to plugin features or trigger actions based on where the user clicked. The plugins extension API can also be used for [cross-plugin linking]({{< relref "./cross-plugin-linking" >}}).

View File

@@ -0,0 +1,12 @@
---
description: Get started guide
title: Get started with plugins
---
# Get started with Grafana plugins
This section contains guidance for building plugins.
- [Get started with creating a plugin](https://grafana.github.io/plugin-tools/docs/getting-started)
- [Types of Grafana plugins](https://grafana.com/docs/grafana/latest/administration/plugin-management/)
- [Set up your development environment](https://grafana.github.io/plugin-tools/docs/docker)

View File

@@ -0,0 +1,13 @@
---
description: Conceptual topics for plugin development
title: Introduction to plugin development
---
# Introduction to Grafana plugin development
This section contains topics related to the key concepts for Grafana plugin development.
- [Backend plugins]({{< relref "../backend/" >}})
- [Grafana plugin SDK for Go]({{< relref "../backend/grafana-plugin-sdk-for-go.md" >}})
- [Plugin protocol]({{< relref "../backend/plugin-protocol.md" >}})
- [Data frames]({{< relref "data-frames.md">}})

View File

@@ -0,0 +1,132 @@
---
aliases:
- ../../plugins/development/
- /docs/grafana/next/plugins/apps/
- /docs/grafana/next/plugins/datasources/
- /docs/grafana/next/plugins/developing/development/
- /docs/grafana/next/plugins/panels/
title: Legacy plugins
---
# Legacy plugins
> **Note:** Since Grafana 7.0, writing plugins using Angular is no longer recommended. If you're looking to build a new plugin, refer to [Plugins]({{< relref "../" >}}).
You can extend Grafana by writing your own plugins and then share them with other users in [our plugin repository](https://grafana.com/plugins).
Grafana already has a strong community of contributors and plugin developers. By making it easier to develop and install plugins with resources such as this guide, we hope that the community can grow even stronger and develop new plugins that we would never think about.
## Short version
1. [Set up Grafana](https://github.com/grafana/grafana/blob/main/contribute/developer-guide.md)
1. Clone an example plugin into `/var/lib/grafana/plugins` or `data/plugins` (relative to grafana git repo if you're running development version from source dir)
1. Use one of our example plugins as a starting point
Example plugins
- ["Hello World" panel using Angular](https://github.com/grafana/simple-angular-panel)
- ["Hello World" panel using React](https://github.com/grafana/simple-react-panel)
- [Simple json data source](https://github.com/grafana/simple-json-datasource)
- [Clock panel](https://github.com/grafana/clock-panel)
- [Pie chart panel](https://github.com/grafana/piechart-panel)
You might also be interested in the available tutorials around authoring a plugin.
- [Grafana Tutorials](https://grafana.com/tutorials/)
## What languages?
Since everything turns into JavaScript, it's up to you to choose which language you want. That said, it's probably a good idea to choose es6 or TypeScript, because we use es6 classes in Grafana. So it's easier to get inspiration from the Grafana repo if you choose one of those languages.
## Buildscript
You can use any build system that supports systemjs. All the built content should end up in a folder named `dist` and be committed to the repository. By committing the dist folder, the person who installs your plugin does not have to run any build script. All of our example plugins have a build script configured.
## Keep your plugin up to date
New versions of Grafana can sometimes cause plugins to break. See our [documentation](https://grafana.com/docs/grafana/latest/developers/plugins/migration-guide/) for changes in
Grafana that can impact your plugin.
## Metadata
See the [coding styleguide]({{< relref "style-guide/" >}}) for details on the metadata.
## module.(js|ts)
This is the entry point for every plugin. This is the place where you should export
your plugin implementation. Depending on what kind of plugin you are developing you
will be expected to export different things. You can find what's expected for [datasource]({{< relref "data-sources/" >}}), [panels]({{< relref "panels/" >}})
and [apps]({{< relref "apps/" >}}) plugins in the documentation.
The Grafana SDK is quite small so far and can be found here:
- [SDK file in Grafana](https://github.com/grafana/grafana/blob/main/public/app/plugins/sdk.ts)
The SDK contains three different plugin classes: PanelCtrl, MetricsPanelCtrl and QueryCtrl. For plugins of the panel type, the module.js file should export one of these. There are some extra classes for [data sources]({{< relref "data-sources/" >}}).
Example:
```javascript
import { ClockCtrl } from './clock_ctrl';
export { ClockCtrl as PanelCtrl };
```
The module class is also where css for the dark and light themes is imported:
```javascript
import { loadPluginCss } from 'app/plugins/sdk';
import WorldmapCtrl from './worldmap_ctrl';
loadPluginCss({
dark: 'plugins/grafana-worldmap-panel/css/worldmap.dark.css',
light: 'plugins/grafana-worldmap-panel/css/worldmap.light.css',
});
export { WorldmapCtrl as PanelCtrl };
```
## Start developing your plugin
There are three ways that you can start developing a Grafana plugin.
1. Set up a Grafana development environment. [(described here)](https://github.com/grafana/grafana/blob/main/contribute/developer-guide.md) and place your plugin in the `data/plugins` folder.
1. Install Grafana and place your plugin in the plugins directory which is set in your [config file](/administration/configuration). By default this is `/var/lib/grafana/plugins` on Linux systems.
1. Place your plugin directory anywhere you like and specify it grafana.ini.
We encourage people to set up the full Grafana environment so that you can get inspiration from the rest of the Grafana code base.
When Grafana starts, it scans the plugin folders and mounts every folder that contains a plugin.json file unless
the folder contains a subfolder named dist. In that case, Grafana mounts the dist folder instead.
This makes it possible to have both built and src content in the same plugin Git repo.
## Grafana Events
There are a number of Grafana events that a plugin can hook into:
- `init-edit-mode` can be used to add tabs when editing a panel
- `panel-teardown` can be used for clean up
- `data-received` is an event in that is triggered on data refresh and can be hooked into
- `data-snapshot-load` is an event triggered to load data when in snapshot mode.
- `data-error` is used to handle errors on dashboard refresh.
If a panel receives data and hooks into the `data-received` event then it should handle snapshot mode too. Otherwise the panel will not work if saved as a snapshot. [Getting Plugins to work in Snapshot Mode]({{< relref "snapshot-mode/" >}}) describes how to add support for this.
## Examples
We have three different examples that you can fork/download to get started developing your Grafana plugin.
- [simple-json-datasource](https://github.com/grafana/simple-json-datasource) (small data source plugin for querying json data from backends)
- [simple-app-plugin](https://github.com/grafana/simple-app-plugin)
- [clock-panel](https://github.com/grafana/clock-panel)
- [singlestat-panel](https://github.com/grafana/grafana/tree/main/public/app/plugins/panel/singlestat)
- [piechart-panel](https://github.com/grafana/piechart-panel)
## Other Articles
- [Getting Plugins to work in Snapshot Mode]({{< relref "snapshot-mode/" >}})
- [Plugin Defaults and Editor Mode]({{< relref "defaults-and-editor-mode/" >}})
- [Grafana Plugin Code Styleguide]({{< relref "style-guide/" >}})
- [Grafana Apps]({{< relref "apps/" >}})
- [Grafana Data Sources]({{< relref "data-sources/" >}})
- [plugin.json Schema]({{< relref "../metadata/" >}})

View File

@@ -0,0 +1,56 @@
---
aliases:
- ../../../plugins/developing/apps/
keywords:
- grafana
- plugins
- documentation
title: Legacy app plugins
---
# Legacy app plugins
App plugins are Grafana plugins that can bundle data source and panel plugins within one package. They also enable the plugin author to create custom pages within Grafana. The custom pages enable the plugin author to include things like documentation, sign-up forms, or to control other services with HTTP requests.
Data source and panel plugins will show up like normal plugins. The app pages will be available in the main menu.
## Enabling app plugins
After installing an app, it has to be enabled before it shows up as a data source or panel. You can do that on the app page in the configuration tab.
## Developing an App Plugin
An App is a bundle of panels, dashboards and/or data source(s). There is nothing different about developing panels and data sources for an app.
Apps have to be enabled in Grafana and should import any included dashboards when the user enables it. A ConfigCtrl class should be created and the dashboards imported in the postUpdate hook. See example below:
```javascript
export class ConfigCtrl {
/** @ngInject */
constructor($scope, $injector, $q) {
this.$q = $q;
this.enabled = false;
this.appEditCtrl.setPostUpdateHook(this.postUpdate.bind(this));
}
postUpdate() {
if (!this.appModel.enabled) {
return;
}
// TODO, whatever you want
console.log('Post Update:', this);
}
}
ConfigCtrl.templateUrl = 'components/config/config.html';
```
If possible, a link to a dashboard or custom page should be shown after enabling the app to guide the user to the appropriate place.
{{< figure class="float-right" src="/static/img/docs/app_plugin_after_enable.png" caption="After enabling" >}}
### Develop your own App
> Our goal is not to have a very extensive documentation but rather have actual
> code that people can look at. An example implementation of an app can be found
> in this [example app repo](https://github.com/grafana/simple-app-plugin)

View File

@@ -0,0 +1,184 @@
---
aliases:
- ../../../plugins/developing/datasources/
keywords:
- grafana
- plugins
- documentation
title: Legacy data source plugins
---
# Legacy data source plugins
Data source plugins enable people to develop plugins for any database that
communicates over HTTP. Its up to the plugin to transform the data into
time series data so that any grafana panel can then show it.
## Data source development
> Our goal is not to have a very extensive documentation but rather have actual
> code that people can look at. Example implementations of a data source can be
> found in these repos:
> - [simple-json-datasource](https://github.com/grafana/simple-json-datasource)
> - [simple-datasource](https://github.com/grafana/simple-datasource)
> - [simple-json-backend-datasource](https://github.com/grafana/simple-json-backend-datasource)
To interact with the rest of grafana the plugins module file can export 4 different components.
- Datasource (Required)
- QueryCtrl (Required)
- ConfigCtrl (Required)
- AnnotationsQueryCtrl
## Plugin json
There are two data source specific settings for the plugin.json
```json
"metrics": true,
"annotations": false,
```
These settings indicate what kind of data the plugin can deliver. At least one of them has to be true.
## Data source
The javascript object that communicates with the database and transforms data to times series.
The Data source should contain the following functions:
```javascript
query(options); // used by panels to get data
testDatasource(); // used by data source configuration page to make sure the connection is working
annotationQuery(options); // used by dashboards to get annotations
metricFindQuery(options); // used by query editor to get metric suggestions.
```
### testDatasource
When a user clicks on the _Save & Test_ button when adding a new data source, the details are first saved to the database and then the `testDatasource` function that is defined in your data source plugin will be called. It is recommended that this function makes a query to the data source that will also test that the authentication details are correct. This is so the data source is correctly configured when the user tries to write a query in a new dashboard.
### Query
Request object passed to datasource.query function:
```json
{
"range": { "from": "2015-12-22T03:06:13.851Z", "to": "2015-12-22T06:48:24.137Z" },
"interval": "5s",
"targets": [
{ "refId": "B", "target": "upper_75" },
{ "refId": "A", "target": "upper_90" }
],
"format": "json",
"maxDataPoints": 2495 // decided by the panel
}
```
There are two different kinds of results for data sources:
time series and table. Time series is the most common format and is supported by all data sources and panels. Table format is only supported by the InfluxDB data source and table panel. But we might see more of this in the future.
Time series response from datasource.query.
An array of:
```json
[
{
"target": "upper_75",
"datapoints": [
[622, 1450754160000],
[365, 1450754220000]
]
},
{
"target": "upper_90",
"datapoints": [
[861, 1450754160000],
[767, 1450754220000]
]
}
]
```
Table response from datasource.query.
An array of:
```json
[
{
"columns": [
{
"text": "Time",
"type": "time",
"sort": true,
"desc": true
},
{
"text": "mean"
},
{
"text": "sum"
}
],
"rows": [
[1457425380000, null, null],
[1457425370000, 1002.76215352, 1002.76215352]
],
"type": "table"
}
]
```
### Annotation Query
Request object passed to datasource.annotationQuery function:
```json
{
"range": { "from": "2016-03-04T04:07:55.144Z", "to": "2016-03-04T07:07:55.144Z" },
"rangeRaw": { "from": "now-3h", "to": "now" },
"annotation": {
"datasource": "generic datasource",
"enable": true,
"name": "annotation name"
},
"dashboard": DashboardModel
}
```
Expected result from datasource.annotationQuery:
```json
[
{
"annotation": {
"name": "annotation name", //should match the annotation name in grafana
"enabled": true,
"datasource": "generic datasource"
},
"title": "Cluster outage",
"time": 1457075272576,
"text": "Joe causes brain split",
"tags": ["joe", "cluster", "failure"]
}
]
```
## QueryCtrl
A JavaScript class that will be instantiated and treated as an Angular controller when the user edits metrics in a panel. This class has to inherit from the `app/plugins/sdk.QueryCtrl` class.
Requires a static template or `templateUrl` variable which will be rendered as the view for this controller.
## ConfigCtrl
A JavaScript class that will be instantiated and treated as an Angular controller when a user tries to edit or create a new data source of this type.
Requires a static template or `templateUrl` variable which will be rendered as the view for this controller.
## AnnotationsQueryCtrl
A JavaScript class that will be instantiated and treated as an Angular controller when the user chooses this type of data source in the templating menu in the dashboard.
Requires a static template or `templateUrl` variable which will be rendered as the view for this controller. The fields that are bound to this controller are then sent to the Database objects annotationQuery function.

View File

@@ -0,0 +1,136 @@
---
aliases:
- ../../../plugins/developing/defaults-and-editor-mode/
title: Legacy defaults and editor mode
---
# Legacy defaults and editor mode
Most plugins allow users to customize the behavior by changing settings on an editor tab. These setting fields are saved in the dashboard json.
## Defaults
We define fields to be saved in Grafana by creating values on the panel object of the controller. You can see these values for any panel by choosing View JSON from the settings menu in Grafana. Here is an excerpt from the clock panel json (with some fields removed), the panel data is saved in the panels array:
```json
{
"id": 4,
"title": "Clock",
...
"rows": [
{
...
"panels": [
{
"bgColor": "rgb(132, 151, 130)",
"clockType": "24 hour",
```
You can define panel data by first creating a variable with default values for the fields and then setting them on the panel object:
```javascript
const panelDefaults = {
clockType: '24 hour',
fontSize: '60px',
fontWeight: 'normal',
bgColor: null
};
constructor($scope, $injector) {
super($scope, $injector);
_.defaults(this.panel, panelDefaults);
this.updateClock();
}
```
The Lodash function [defaults](https://lodash.com/docs/4.17.4#defaults), which is called in the code above: `_.defaults`, sets a default value only if the value is not already set. This way values that have been changed by the user will not be overwritten.
These panel fields can be used in the controller or module.html template:
```html
<h2 style="font-size: {{ctrl.panel.fontSize}};">{{ctrl.time}}</h2>
```
If you want your users to be able to change these panel values then you need to expose them in the Grafana editor.
## Editor Mode
Editor mode is when a user clicks Edit on a panel. Every panel has a general tab where you change the title and width and some panels have more inbuilt tabs like the Metrics tab or Time Range tab. A panel plugin can add its own tab(s) so that a user can customize the panel.
Grafana conventions mean all you need to do is to hook up an Angular template with input fields and Grafana will automatically save the values to the dashboard json and load them on dashboard load.
## Using Events
To add an editor tab you need to hook into the event model so that the tab is added when the _init-edit-mode_ event is triggered. The following code should be added to the constructor of the plugin Ctrl class:
```javascript
this.events.on('init-edit-mode', this.onInitEditMode.bind(this));
```
Then you need to create a handler function that is bound to the event. In the example above, the handler is called onInitEditMode. The tab is added by calling the controller function, _addEditorTab_. This function has three parameters; the tab name, the path to a html template for the new editor tab and the tab number. It can be a bit tricky to figure out the path, the path name will be based on the id that is specified in the plugin.json file - for example **grafana-clock-panel**. The code below hooks up an Angular template called editor.html that is located in the `src/partials` directory.
```javascript
onInitEditMode() {
this.addEditorTab('Options', 'public/plugins/grafana-clock-panel/editor.html', 2);
}
```
## Editor HTML and CSS
For editor tabs html, it is best to use Grafana css styles rather than custom styles. This is to preserve the look and feel of other tabs in Grafana.
Most editor tabs should use the [gf-form css class](https://github.com/grafana/grafana/blob/main/public/sass/components/_gf-form.scss) from Grafana. The example below has one row with a couple of columns and each column is wrapped in a div like this:
```html
<div class="section gf-form-group"></div>
```
Then each pair, label and field is wrapped in a div with a gf-form class.
```html
<div class="gf-form">
<label class="gf-form-label width-8">Font Size</label>
<input
type="text"
class="gf-form-input width-4"
ng-model="ctrl.panel.fontSize"
ng-change="ctrl.render()"
ng-model-onblur
/>
</div>
```
Note that there are some Angular attributes here. _ng-model_ will update the panel data. _ng-change_ will render the panel when you change the value. This change will occur on the onblur event due to the _ng-model-onblur_ attribute. This means you can see the effect of your changes on the panel while editing.
{{< figure class="float-right" src="/assets/img/blog/clock-panel-editor.png" caption="Panel Editor" >}}
On the editor tab we use a drop-down for 12/24 hour clock, an input field for font size and a color picker for the background color.
The drop-down/select has its own _gf-form-select-wrapper_ css class and looks like this:
```html
<div class="gf-form">
<label class="gf-form-label width-9">12 or 24 hour</label>
<div class="gf-form-select-wrapper max-width-9">
<select
class="input-small gf-form-input"
ng-model="ctrl.panel.clockType"
ng-options="t for t in ['12 hour', '24 hour', 'custom']"
ng-change="ctrl.render()"
></select>
</div>
</div>
```
The color picker (or spectrum picker) is a component that already exists in Grafana. We use it like this for the background color:
```html
<spectrum-picker class="gf-form-input" ng-model="ctrl.panel.bgColor" ng-change="ctrl.render()"></spectrum-picker>
```
## Editor Tab Finished
To reiterate, this all ties together quite neatly. We specify properties and panel defaults in the constructor for the panel controller and these can then be changed in the editor. Grafana takes care of saving the changes.
One thing to be aware of is that panel defaults are used the first time a panel is created to set the initial values of the panel properties. After the panel is saved then the saved value will be used instead. So beware if you update panel defaults they will not automatically update the property in an existing panel. For example, if you set the default font size to 60px first and then in version 2 of the plugin change it to 50px, existing panels will still have 60px and only new panels will get the new 50px value.

View File

@@ -0,0 +1,33 @@
---
aliases:
- ../../../plugins/developing/panels/
keywords:
- grafana
- plugins
- panel
- documentation
title: Legacy panel plugins
---
# Legacy panel plugins
Panels are the main building blocks of dashboards.
## Panel development
### Scrolling
The grafana dashboard framework controls the panel height. To enable a scrollbar within the panel the PanelCtrl needs to set the scrollable static variable:
```javascript
export class MyPanelCtrl extends PanelCtrl {
static scrollable = true;
...
```
In this case, make sure the template has a single `<div>...</div>` root. The plugin loader will modify that element adding a scrollbar.
### Examples
- [clock-panel](https://github.com/grafana/clock-panel)
- [singlestat-panel](https://github.com/grafana/grafana/tree/main/public/app/plugins/panel/singlestat)

View File

@@ -0,0 +1,181 @@
---
aliases:
- ../../../plugins/developing/plugin-review-guidelines/
title: Legacy review guidelines
---
# Legacy review guidelines
The Grafana team reviews all plugins that are published on Grafana.com. There are two areas we review, the metadata for the plugin and the plugin functionality.
## Metadata
The plugin metadata consists of a `plugin.json` file and the README.md file. The `plugin.json` file is used by Grafana to load the plugin, and the README.md file is shown in the plugins section of Grafana and the plugins section of https://grafana.com.
### README.md
The README.md file is shown on the plugins page in Grafana and the plugin page on Grafana.com. There are some differences between the GitHub markdown and the markdown allowed in Grafana/Grafana.com:
- Cannot contain inline HTML.
- Any image links should be absolute links. For example: https://raw.githubusercontent.com/grafana/azure-monitor-datasource/master/dist/img/grafana_cloud_install.png
The README should:
- describe the purpose of the plugin.
- contain steps on how to get started.
### Plugin.json
The `plugin.json` file is the same concept as the `package.json` file for an npm package. When the Grafana server starts it will scan the plugin folders (all folders in the data/plugins subfolder) and load every folder that contains a `plugin.json` file unless the folder contains a subfolder named `dist`. In that case, the Grafana server will load the `dist` folder instead.
A minimal `plugin.json` file:
```json
{
"type": "panel",
"name": "Clock",
"id": "yourorg-clock-panel",
"info": {
"description": "Clock panel for grafana",
"author": {
"name": "Author Name",
"url": "http://yourwebsite.com"
},
"keywords": ["clock", "panel"],
"version": "1.0.0",
"updated": "2018-03-24"
},
"dependencies": {
"grafanaVersion": "3.x.x",
"plugins": []
}
}
```
- The convention for the plugin id is **[grafana.com username/org]-[plugin name]-[datasource|app|panel]** and it has to be unique. The org **cannot** be `grafana` unless it is a plugin created by the Grafana core team.
Examples:
- raintank-worldping-app
- ryantxu-ajax-panel
- alexanderzobnin-zabbix-app
- hawkular-datasource
- The `type` field should be either `datasource` `app` or `panel`.
- The `version` field should be in the form: x.x.x e.g. `1.0.0` or `0.4.1`.
The full file format for `plugin.json` file is in [plugin.json](http://docs.grafana.org/plugins/developing/plugin.json/).
## Plugin Language
JavaScript, TypeScript, ES6 (or any other language) are all fine as long as the contents of the `dist` subdirectory are transpiled to JavaScript (ES5).
## File and Directory Structure Conventions
Here is a typical directory structure for a plugin.
```bash
johnnyb-awesome-datasource
|-- dist
|-- src
| |-- img
| | |-- logo.svg
| |-- partials
| | |-- annotations.editor.html
| | |-- config.html
| | |-- query.editor.html
| |-- datasource.js
| |-- module.js
| |-- plugin.json
| |-- query_ctrl.js
|-- Gruntfile.js
|-- LICENSE
|-- package.json
|-- README.md
```
Most JavaScript projects have a build step. The generated JavaScript should be placed in the `dist` directory and the source code in the `src` directory. We recommend that the plugin.json file be placed in the src directory and then copied over to the dist directory when building. The `README.md` can be placed in the root or in the dist directory.
Directories:
- `src/` contains plugin source files.
- `src/partials` contains html templates.
- `src/img` contains plugin logos and other images.
- `dist/` contains built content.
## HTML and CSS
For the HTML on editor tabs, we recommend using the inbuilt Grafana styles rather than defining your own. This makes plugins feel like a more natural part of Grafana. If done correctly, the html will also be responsive and adapt to smaller screens. The `gf-form` css classes should be used for labels and inputs.
Below is a minimal example of an editor row with one form group and two fields, a dropdown and a text input:
```html
<div class="editor-row">
<div class="section gf-form-group">
<h5 class="section-heading">My Plugin Options</h5>
<div class="gf-form">
<label class="gf-form-label width-10">Label1</label>
<div class="gf-form-select-wrapper max-width-10">
<select
class="input-small gf-form-input"
ng-model="ctrl.panel.mySelectProperty"
ng-options="t for t in ['option1', 'option2', 'option3']"
ng-change="ctrl.onSelectChange()"
></select>
</div>
<div class="gf-form">
<label class="gf-form-label width-10">Label2</label>
<input
type="text"
class="input-small gf-form-input width-10"
ng-model="ctrl.panel.myProperty"
ng-change="ctrl.onFieldChange()"
placeholder="suggestion for user"
ng-model-onblur
/>
</div>
</div>
</div>
</div>
```
Use the `width-x` and `max-width-x` classes to control the width of your labels and input fields. Try to get labels and input fields to line up neatly by having the same width for all the labels in a group and the same width for all inputs in a group if possible.
## Data Sources
For more information about data sources, refer to the [basic guide for data sources](http://docs.grafana.org/plugins/developing/datasources/).
### Configuration Page Guidelines
- It should be as easy as possible for a user to configure a URL. If the data source is using the `datasource-http-settings` component, it should use the `suggest-url` attribute to suggest the default URL or a URL that is similar to what it should be (especially important if the URL refers to a REST endpoint that is not common knowledge for most users e.g. `https://yourserver:4000/api/custom-endpoint`).
```html
<datasource-http-settings current="ctrl.current" suggest-url="http://localhost:8080"> </datasource-http-settings>
```
- The `testDatasource` function should make a query to the data source that will also test that the authentication details are correct. This is so the data source is correctly configured when the user tries to write a query in a new dashboard.
#### Password Security
If possible, any passwords or secrets should be saved in the `secureJsonData` blob. To encrypt sensitive data, the Grafana server's proxy feature must be used. The Grafana server has support for token authentication (OAuth) and HTTP Header authentication. If the calls have to be sent directly from the browser to a third-party API, this will not be possible and sensitive data will not be encrypted.
Read more here about how [authentication for data sources]({{< relref "../add-authentication-for-data-source-plugins/" >}}) works.
If using the proxy feature, the Configuration page should use the `secureJsonData` blob like this:
- good: `<input type="password" class="gf-form-input" ng-model='ctrl.current.secureJsonData.password' placeholder="password"></input>`
- bad: `<input type="password" class="gf-form-input" ng-model='ctrl.current.password' placeholder="password"></input>`
### Query Editor
Each query editor is unique and can have a unique style. It should be adapted to what the users of the data source are used to.
- Should use the Grafana CSS `gf-form` classes.
- Should be neat and tidy. Labels and fields in columns should be aligned and should be the same width if possible.
- The data source should be able to handle when a user toggles a query (by clicking on the eye icon) and not execute the query. This is done by checking the `hide` property - an [example](https://github.com/grafana/grafana/blob/e75840737e81f70b6d169df21eca86a624d4bdc4/public/app/plugins/datasource/postgres/datasource.ts#L73).
- Should not execute queries if fields in the Query Editor are empty and the query will throw an exception (defensive programming).
- Should handle errors. There are two main ways to do this:
- use the notification system in Grafana to show a toaster pop-up with the error message. For an example of a pop-up with the error message, refer to [code in triggers_panel_ctrl](https://github.com/alexanderzobnin/grafana-zabbix/blob/fdbbba2fb03f5f2a4b3b0715415e09d5a4cf6cde/src/panel-triggers/triggers_panel_ctrl.js#L467-L471).
- provide an error notification in the query editor like the MySQL/Postgres data sources do. For an example of error notification in the query editor, refer to [code in query_ctrl](https://github.com/grafana/azure-monitor-datasource/blob/b184d077f082a69f962120ef0d1f8296a0d46f03/src/query_ctrl.ts#L36-L51) and in the [html](https://github.com/grafana/azure-monitor-datasource/blob/b184d077f082a69f962120ef0d1f8296a0d46f03/src/partials/query.editor.html#L190-L193).

View File

@@ -0,0 +1,76 @@
---
aliases:
- ../../../plugins/developing/snapshot-mode/
title: Legacy snapshot mode
---
# Legacy snapshot mode
{{< figure class="float-right" src="/static/img/docs/Grafana-snapshot-example.png" caption="A dashboard using snapshot data and not live data." >}}
Grafana has this great feature where you can [save a snapshot of your dashboard]({{< relref "../../../dashboards/build-dashboards/view-dashboard-json-model" >}}). Instead of sending a screenshot of a dashboard to someone, you can send them a working, interactive Grafana dashboard with the snapshot data embedded inside it. The snapshot can be saved on your Grafana server and is available to all your co-workers. Raintank also hosts a [snapshot server](https://snapshots.raintank.io) if you want to send the snapshot to someone who does not have access to your Grafana server.
{{< figure class="float-right" src="/static/img/docs/animated_gifs/snapshots.gif" caption="Selecting a snapshot" >}}
This all works because Grafana saves a snapshot of the current data in the dashboard json instead of fetching the data from a data source. However, if you are building a custom panel plugin then this will not work straight out of the box. You will need to make some small (and easy!) changes first.
## Enabling support for loading snapshot data
Grafana automatically saves data from data sources in the dashboard json when the snapshot is created so we do not have to write any code for that. Enabling snapshot support for reading time series data is very simple. First in the constructor, we need to add an event handler for `data-snapshot-load`. This event is triggered by Grafana when the snapshot data is loaded from the dashboard json.
```javascript
constructor($scope, $injector, contextSrv) {
super($scope, $injector);
...
this.events.on('init-edit-mode', this.onInitEditMode.bind(this));
this.events.on('data-received', this.onDataReceived.bind(this));
this.events.on('panel-teardown', this.onPanelTeardown.bind(this));
this.events.on('data-snapshot-load', this.onDataSnapshotLoad.bind(this));
```
Then we need to create a simple event handler that just forwards the data on to our regular `data-received` handler:
```javascript
onDataSnapshotLoad(snapshotData) {
this.onDataReceived(snapshotData);
}
```
This will cover most use cases for snapshot support. Sometimes you will want to save data that is not time series data from a Grafana data source and then you have to do a bit more work to get snapshot support.
## Saving custom data for snapshots
Data that is not time series data from a Grafana data source is not saved automatically by Grafana. Saving custom data for snapshot mode has to be done manually.
{{< figure class="float-right" src="/static/img/docs/Grafana-save-snapshot.png" caption="Save snapshot" >}}
Grafana gives us a chance to save data to the dashboard json when it is creating a snapshot. In the 'data-received' event handler, you can check the snapshot flag on the dashboard object. If this is true, then Grafana is creating a snapshot and you can manually save custom data to the panel json. In the example, a new field called snapshotLocationData in the panel json is initialized with a snapshot of the custom data.
```javascript
onDataReceived(dataList) {
if (!dataList) return;
if (this.dashboard.snapshot && this.locations) {
this.panel.snapshotLocationData = this.locations;
}
```
Now the location data is saved in the dashboard json but we will have to load it manually as well.
## Loading custom data for snapshots
The example below shows a function that loads the custom data. The data source for the custom data (an external API in this case) is not available in snapshot mode so a guard check is made to see if there is any snapshot data available first. If there is, then the snapshot data is used instead of trying to load the data from the external API.
```javascript
loadLocationDataFromFile(reload) {
if (this.map && !reload) return;
if (this.panel.snapshotLocationData) {
this.locations = this.panel.snapshotLocationData;
return;
}
```
It is really easy to forget to add this support but it enables a great feature and can be used to demo your panel.
If there is a panel plugin that you would like to be installed on the Raintank Snapshot server then please contact us via [Slack](https://slack.grafana.com) or [GitHub](https://github.com/grafana/grafana).

View File

@@ -0,0 +1,193 @@
---
aliases:
- ../../../plugins/developing/code-styleguide/
title: Legacy code style guide
---
# Legacy code style guide
This guide has two parts. The first part describes the metadata and the second part is a styleguide for HTML/CSS and JavaScript in Grafana plugins and applies if you are using ES6 in your plugin. If using TypeScript then the [Angular TypeScript styleguide](https://angular.io/styleguide) is recommended.
## Metadata
The plugin metadata consists of a plugin.json file and the README.md file. These two files are used by Grafana and Grafana.com.
### Plugin.json (mandatory)
The plugin.json file is the same concept as the package.json file for an npm package. When Grafana starts it will scan the plugin folders and mount every folder that contains a plugin.json file unless the folder contains a subfolder named `dist`. In that case grafana will mount the `dist` folder instead.
The most important fields are the first three, especially the id. The convention for the plugin id is **[github username/org]-[plugin name]-[datasource|app|panel]** and it has to be unique.
Examples:
```bash
raintank-worldping-app
grafana-simple-json-datasource
grafana-piechart-panel
mtanda-histogram-panel
```
For more information about the file format for `plugin.json` file, refer to [metadata]({{< relref "../metadata/" >}}).
Minimal plugin.json:
```javascript
{
"type": "panel",
"name": "Clock",
"id": "yourorg-clock-panel",
"info": {
"description": "Clock panel for grafana",
"author": {
"name": "Grafana Labs",
"url": "https://grafana.com"
},
"keywords": ["clock", "panel"],
"version": "1.0.0",
"updated": "2015-03-24"
},
"dependencies": {
"grafanaVersion": "3.x.x",
"plugins": [ ]
}
}
```
### README.md
The README.md file is rendered both in the grafana.com plugins page, and within the Grafana application. The only difference from how GitHub renders markdown is that html is not allowed.
## File and Directory Structure Conventions
Here is a typical directory structure for a plugin.
```bash
johnnyb-awesome-datasource
|-- dist
|-- spec
| |-- datasource_spec.js
| |-- query_ctrl_spec.js
| |-- test-main.js
|-- src
| |-- img
| | |-- logo.svg
| |-- partials
| | |-- annotations.editor.html
| | |-- config.html
| | |-- query.editor.html
| |-- datasource.js
| |-- module.js
| |-- plugin.json
| |-- query_ctrl.js
|-- Gruntfile.js
|-- LICENSE
|-- package.json
|-- README.md
```
Most JavaScript projects have a build step and most Grafana plugins are built using Babel and ES6. The generated JavaScript should be placed in the `dist` directory and the source code in the `src` directory. We recommend that the plugin.json file be placed in the src directory and then copied over to the dist directory when building. The `README.md` can be placed in the root or in the dist directory.
Directories:
- `src/` contains plugin source files.
- `src/partials` contains html templates.
- `src/img` contains plugin logos and other images.
- `spec/` contains tests (optional).
- `dist/` contains built content.
## HTML and CSS
For the HTML on editor tabs, we recommend using the inbuilt Grafana styles rather than defining your own. This makes plugins feel like a more natural part of Grafana. If done correctly, the html will also be responsive and adapt to smaller screens. The `gf-form` css classes should be used for labels and inputs.
Below is a minimal example of an editor row with one form group and two fields, a dropdown and a text input:
```html
<div class="editor-row">
<div class="section gf-form-group">
<h5 class="section-heading">My Plugin Options</h5>
<div class="gf-form">
<label class="gf-form-label width-10">Label1</label>
<div class="gf-form-select-wrapper max-width-10">
<select
class="input-small gf-form-input"
ng-model="ctrl.panel.mySelectProperty"
ng-options="t for t in ['option1', 'option2', 'option3']"
ng-change="ctrl.onSelectChange()"
></select>
</div>
<div class="gf-form">
<label class="gf-form-label width-10">Label2</label>
<input
type="text"
class="input-small gf-form-input width-10"
ng-model="ctrl.panel.myProperty"
ng-change="ctrl.onFieldChange()"
placeholder="suggestion for user"
ng-model-onblur
/>
</div>
</div>
</div>
</div>
```
Use the `width-x` and `max-width-x` classes to control the width of your labels and input fields. Try to get labels and input fields to line up neatly by having the same width for all the labels in a group and the same width for all inputs in a group if possible.
## Build Scripts
Our recommendation is to use whatever you usually use - Grunt, Gulp or npm scripts. Most plugins seems to use Grunt so that is probably the easiest to get started with if you do not have a preferred build system. The only requirement is that it supports systemjs which is required by Grafana to load plugins.
## Linting
We recommend that you use a linter for your JavaScript. For ES6, the standard linter is [eslint](http://eslint.org/). Rules for linting are described in an .eslintrc that is placed in the root directory. For an example of linting rules in a plugin, refer to [.eslintrc](https://github.com/grafana/worldmap-panel/blob/master/.eslintrc).
### ES6 features
1. Use `const` if a variable is not going to be reassigned.
1. Prefer to use `let` instead `var` ([Exploring ES6](http://exploringjs.com/es6/ch_core-features.html#_from-var-to-letconst))
1. Use arrow functions, which dont shadow `this` ([Exploring ES6](http://exploringjs.com/es6/ch_core-features.html#_from-function-expressions-to-arrow-functions)):
```js
testDatasource() {
return this.getServerStatus()
.then(status => {
return this.doSomething(status);
})
}
```
better than
```js
testDatasource() {
var self = this;
return this.getServerStatus()
.then(function(status) {
return self.doSomething(status);
})
}
```
1. Use native _Promise_ object:
```js
metricFindQuery(query) {
if (!query) {
return Promise.resolve([]);
}
}
```
better than
```js
metricFindQuery(query) {
if (!query) {
return this.$q.when([]);
}
}
```
1. If using Lodash, then be consistent and prefer that to the native ES6 array functions.

View File

@@ -0,0 +1,252 @@
---
aliases:
- ../../plugins/developing/plugin.json/
keywords:
- grafana
- plugins
- documentation
title: plugin.json
---
# plugin.json
The plugin.json file is required for all plugins. When Grafana starts, it scans the plugin folders and mounts every folder that contains a plugin.json file unless the folder contains a subfolder named dist. In that case, Grafana mounts the dist folder instead.
## Properties
| Property | Type | Required | Description |
| -------------------- | ----------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | string | **Yes** | Unique name of the plugin. If the plugin is published on grafana.com, then the plugin `id` should follow the Grafana naming convention. |
| `name` | string | **Yes** | Human-readable name of the plugin that is shown to the user in the UI. |
| `type` | string | **Yes** | Plugin type. Possible values are: `app`, `datasource`, `panel`, `renderer`, `secretsmanager`. |
| `info` | [object](#info) | **Yes** | Metadata for the plugin. Some fields are used on the plugins page in Grafana and others on grafana.com if the plugin is published. |
| `dependencies` | [object](#dependencies) | **Yes** | Dependency information related to Grafana and other plugins. |
| `$schema` | string | No | Schema definition for the plugin.json file. Used primarily for schema validation. |
| `alerting` | boolean | No | For data source plugins, if the plugin supports alerting. Requires `backend` to be set to `true`. |
| `annotations` | boolean | No | For data source plugins, if the plugin supports annotation queries. |
| `autoEnabled` | boolean | No | Set to true for app plugins that should be enabled and pinned to the navigation bar in all orgs. |
| `backend` | boolean | No | If the plugin has a backend component. |
| `category` | string | No | Plugin category used on the "Add data source" page. Possible values are: `tsdb`, `logging`, `cloud`, `tracing`, `profiling`, `sql`, `enterprise`, `iot`, `other`. |
| `enterpriseFeatures` | [object](#enterprisefeatures) | No | Grafana Enterprise specific features |
| `executable` | string | No | The first part of the file name of the backend component executable. There can be multiple executables built for different operating system and architecture. Grafana will check for executables named `<executable>_<$GOOS>_<lower case $GOARCH><.exe for Windows>`, e.g. `plugin_linux_amd64`. Combination of $GOOS and $GOARCH can be found here: https://golang.org/doc/install/source#environment. |
| `includes` | [object](#includes)[] | No | Resources to include in plugin. |
| `logs` | boolean | No | For data source plugins, if the plugin supports logs. It may be used to filter logs only features. |
| `metrics` | boolean | No | For data source plugins, if the plugin supports metric queries. Used to enable the plugin in the panel editor. |
| `preload` | boolean | No | Initialize plugin on startup. By default, the plugin initializes on first use. Useful for app plugins that should load without user interaction. |
| `queryOptions` | [object](#queryoptions) | No | For data source plugins. There is a query options section in the plugin's query editor and these options can be turned on if needed. |
| `routes` | [object](#routes)[] | No | For data source plugins. Proxy routes used for plugin authentication and adding headers to HTTP requests made by the plugin. For more information, refer to [Authentication for data source plugins](https://grafana.com/docs/grafana/latest/developers/plugins/authentication/). |
| `skipDataQuery` | boolean | No | For panel plugins. Hides the query editor. |
| `state` | string | No | Marks a plugin as a pre-release. Possible values are: `alpha`, `beta`. |
| `streaming` | boolean | No | For data source plugins, if the plugin supports streaming. Used in Explore to start live streaming. |
| `tracing` | boolean | No | For data source plugins, if the plugin supports tracing. Used for example to link logs (e.g. Loki logs) with tracing plugins. |
## dependencies
Dependencies needed by the plugin.
### Properties
| Property | Type | Required | Description |
| ------------------- | -------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `grafanaDependency` | string | **Yes** | Required Grafana version for this plugin. Validated using https://github.com/npm/node-semver. |
| `grafanaVersion` | string | No | (Deprecated) Required Grafana version for this plugin, e.g. `6.x.x 7.x.x` to denote plugin requires Grafana v6.x.x or v7.x.x. |
| `plugins` | [object](#plugins)[] | No | An array of required plugins on which this plugin depends. |
### plugins
Plugin dependency. Used to display information about plugin dependencies in the Grafana UI.
#### Properties
| Property | Type | Required | Description |
| --------- | ------ | -------- | -------------------------------------------------- |
| `id` | string | **Yes** | |
| `name` | string | **Yes** | |
| `type` | string | **Yes** | Possible values are: `app`, `datasource`, `panel`. |
| `version` | string | **Yes** | |
## enterpriseFeatures
Grafana Enterprise specific features.
### Properties
| Property | Type | Required | Description |
| ------------------------- | ------- | -------- | ------------------------------------------------------------------- |
| `healthDiagnosticsErrors` | boolean | No | Enable/Disable health diagnostics errors. Requires Grafana >=7.5.5. |
## includes
### Properties
| Property | Type | Required | Description |
| ------------ | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `addToNav` | boolean | No | Add the include to the side menu. |
| `component` | string | No | (Legacy) The Angular component to use for a page. |
| `defaultNav` | boolean | No | Page or dashboard when user clicks the icon in the side menu. |
| `icon` | string | No | Icon to use in the side menu. For information on available icon, refer to [Icons Overview](https://developers.grafana.com/ui/latest/index.html?path=/story/docs-overview-icon--icons-overview). |
| `name` | string | No | |
| `path` | string | No | Used for app plugins. |
| `role` | string | No | Possible values are: `Admin`, `Editor`, `Viewer`. |
| `type` | string | No | Possible values are: `dashboard`, `page`, `panel`, `datasource`. |
| `uid` | string | No | Unique identifier of the included resource |
## info
Metadata for the plugin. Some fields are used on the plugins page in Grafana and others on grafana.com if the plugin is published.
### Properties
| Property | Type | Required | Description |
| ------------- | ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `keywords` | string[] | **Yes** | Array of plugin keywords. Used for search on grafana.com. |
| `logos` | [object](#logos) | **Yes** | SVG images that are used as plugin icons. |
| `updated` | string | **Yes** | Date when this plugin was built. |
| `version` | string | **Yes** | Project version of this commit, e.g. `6.7.x`. |
| `author` | [object](#author) | No | Information about the plugin author. |
| `build` | [object](#build) | No | Build information |
| `description` | string | No | Description of plugin. Used on the plugins page in Grafana and for search on grafana.com. |
| `links` | [object](#links)[] | No | An array of link objects to be displayed on this plugin's project page in the form `{name: 'foo', url: 'http://example.com'}` |
| `screenshots` | [object](#screenshots)[] | No | An array of screenshot objects in the form `{name: 'bar', path: 'img/screenshot.png'}` |
### author
Information about the plugin author.
#### Properties
| Property | Type | Required | Description |
| -------- | ------ | -------- | ------------------------- |
| `email` | string | No | Author's name. |
| `name` | string | No | Author's name. |
| `url` | string | No | Link to author's website. |
### build
Build information
#### Properties
| Property | Type | Required | Description |
| -------- | ------ | -------- | ---------------------------------------------------- |
| `branch` | string | No | Git branch the plugin was built from. |
| `hash` | string | No | Git hash of the commit the plugin was built from |
| `number` | number | No | |
| `pr` | number | No | GitHub pull request the plugin was built from |
| `repo` | string | No | |
| `time` | number | No | Time when the plugin was built, as a Unix timestamp. |
### links
#### Properties
| Property | Type | Required | Description |
| -------- | ------ | -------- | ----------- |
| `name` | string | No | |
| `url` | string | No | |
### logos
SVG images that are used as plugin icons.
#### Properties
| Property | Type | Required | Description |
| -------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `large` | string | **Yes** | Link to the "large" version of the plugin logo, which must be an SVG image. "Large" and "small" logos can be the same image. |
| `small` | string | **Yes** | Link to the "small" version of the plugin logo, which must be an SVG image. "Large" and "small" logos can be the same image. |
### screenshots
#### Properties
| Property | Type | Required | Description |
| -------- | ------ | -------- | ----------- |
| `name` | string | No | |
| `path` | string | No | |
## queryOptions
For data source plugins. There is a query options section in the plugin's query editor and these options can be turned on if needed.
### Properties
| Property | Type | Required | Description |
| --------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |
| `cacheTimeout` | boolean | No | For data source plugins. If the `cache timeout` option should be shown in the query options section in the query editor. |
| `maxDataPoints` | boolean | No | For data source plugins. If the `max data points` option should be shown in the query options section in the query editor. |
| `minInterval` | boolean | No | For data source plugins. If the `min interval` option should be shown in the query options section in the query editor. |
## routes
For data source plugins. Proxy routes used for plugin authentication and adding headers to HTTP requests made by the plugin. For more information, refer to [Authentication for data source plugins](https://grafana.com/docs/grafana/latest/developers/plugins/authentication/).
### Properties
| Property | Type | Required | Description |
| -------------- | ----------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `body` | [object](#body) | No | For data source plugins. Route headers set the body content and length to the proxied request. |
| `headers` | array | No | For data source plugins. Route headers adds HTTP headers to the proxied request. |
| `jwtTokenAuth` | [object](#jwttokenauth) | No | For data source plugins. Token authentication section used with an JWT OAuth API. |
| `method` | string | No | For data source plugins. Route method matches the HTTP verb like GET or POST. Multiple methods can be provided as a comma-separated list. |
| `path` | string | No | For data source plugins. The route path that is replaced by the route URL field when proxying the call. |
| `reqRole` | string | No | |
| `reqSignedIn` | boolean | No | |
| `tokenAuth` | [object](#tokenauth) | No | For data source plugins. Token authentication section used with an OAuth API. |
| `url` | string | No | For data source plugins. Route URL is where the request is proxied to. |
### body
For data source plugins. Route headers set the body content and length to the proxied request.
| Property | Type | Required | Description |
| -------- | ---- | -------- | ----------- |
### jwtTokenAuth
For data source plugins. Token authentication section used with an JWT OAuth API.
#### Properties
| Property | Type | Required | Description |
| -------- | ----------------- | -------- | --------------------------------------------------------------------- |
| `params` | [object](#params) | No | Parameters for the JWT token authentication request. |
| `scopes` | string[] | No | The list of scopes that your application should be granted access to. |
| `url` | string | No | URL to fetch the JWT token. |
#### params
Parameters for the JWT token authentication request.
##### Properties
| Property | Type | Required | Description |
| -------------- | ------ | -------- | ----------- |
| `client_email` | string | No | |
| `private_key` | string | No | |
| `token_uri` | string | No | |
### tokenAuth
For data source plugins. Token authentication section used with an OAuth API.
#### Properties
| Property | Type | Required | Description |
| -------- | ----------------- | -------- | --------------------------------------------------------------------- |
| `params` | [object](#params) | No | Parameters for the token authentication request. |
| `scopes` | string[] | No | The list of scopes that your application should be granted access to. |
| `url` | string | No | URL to fetch the authentication token. |
#### params
Parameters for the token authentication request.
##### Properties
| Property | Type | Required | Description |
| --------------- | ------ | -------- | ----------------------------------------------------------------------------------------- |
| `client_id` | string | No | OAuth client ID |
| `client_secret` | string | No | OAuth client secret. Usually populated by decrypting the secret from the SecureJson blob. |
| `grant_type` | string | No | OAuth grant type |
| `resource` | string | No | OAuth resource |

View File

@@ -0,0 +1,10 @@
+++
# -------------------------------------------------------------------------
# Do not edit this file. It is automatically generated by json-schema-docs.
# -------------------------------------------------------------------------
title = "plugin.json"
keywords = ["grafana", "plugins", "documentation"]
aliases = ["/docs/grafana/latest/plugins/developing/plugin.json/"]
+++
{{ .Markdown 1 }}

View File

@@ -0,0 +1,954 @@
---
title: Plugin migration guide
---
# Plugin migration guide
## Introduction
This guide helps you identify the steps required to update a plugin from the Grafana version it currently supports to newer versions of Grafana.
> **Note:** If you've successfully migrated your plugin using this guide, then share your experiences with us! If you find missing information, then we encourage you to [submit an issue on GitHub](https://github.com/grafana/grafana/issues/new?title=Docs%20feedback:%20/developers/plugins/migration-guide.md) so that we can improve this guide!
## Table of contents
- [Plugin migration guide](#plugin-migration-guide)
- [Introduction](#introduction)
- [Table of contents](#table-of-contents)
- [From version 9.3.x to 9.4.x](#from-version-93x-to-94x)
- [Supporting new navigation layout](#supporting-new-navigation-layout)
- [Forwarded HTTP headers in grafana-plugin-sdk-go](#forwarded-http-headers-in-grafana-plugin-sdk-go)
- [From version 9.1.x to 9.2.x](#from-version-91x-to-92x)
- [React and React-dom as peer dependencies](#react-and-react-dom-as-peer-dependencies)
- [NavModelItem requires a valid icon name](#navmodelitem-requires-a-valid-icon-name)
- [Additional type availability](#additional-type-availability)
- [From version 8.x to 9.x](#from-version-8x-to-9x)
- [9.0 breaking changes](#90-breaking-changes)
- [theme.visualization.getColorByName replaces getColorForTheme](#themevisualizationgetcolorbyname-replaces-getcolorfortheme)
- [VizTextDisplayOptions replaces TextDisplayOptions](#viztextdisplayoptions-replaces-textdisplayoptions)
- [Changes in the internal of `backendSrv.fetch()`](#changes-in-the-internal-of-backendsrvfetch)
- [GrafanaTheme2 and useStyles2 replaces getFormStyles](#grafanatheme2-and-usestyles2-replaces-getformstyles)
- [/api/ds/query replaces /api/tsdb/query](#apidsquery-replaces-apitsdbquery)
- [selectOptionInTest has been removed](#selectoptionintest-has-been-removed)
- [Toolkit 9 and webpack](#toolkit-9-and-webpack)
- [From version 8.3.x to 8.4.x](#from-version-83x-to-84x)
- [Value Mapping Editor has been removed from @grafana-ui library](#value-mapping-editor-has-been-removed-from-grafana-ui-library)
- [Thresholds Editor has been removed from @grafana-ui library](#thresholds-editor-has-been-removed-from-grafana-ui-library)
- [8.4 deprecations](#84-deprecations)
- [LocationService replaces getLocationSrv](#locationservice-replaces-getlocationsrv)
- [From version 7.x.x to 8.x.x](#from-version-7xx-to-8xx)
- [Backend plugin v1 support has been dropped](#backend-plugin-v1-support-has-been-dropped)
- [1. Add dependency on grafana-plugin-sdk-go](#1-add-dependency-on-grafana-plugin-sdk-go)
- [2. Update the way you bootstrap your plugin](#2-update-the-way-you-bootstrap-your-plugin)
- [3. Update the plugin package](#3-update-the-plugin-package)
- [Sign and load backend plugins](#sign-and-load-backend-plugins)
- [Update react-hook-form from v6 to v7](#update-react-hook-form-from-v6-to-v7)
- [Update the plugin.json](#update-the-pluginjson)
- [Update imports to match emotion 11](#update-imports-to-match-emotion-11)
- [Update needed for app plugins using dashboards](#update-needed-for-app-plugins-using-dashboards)
- [8.0 deprecations](#80-deprecations)
- [Grafana theme v1](#grafana-theme-v1)
- [From version 6.2.x to 7.4.0](#from-version-62x-to-740)
- [Legend components](#legend-components)
- [From version 6.5.x to 7.3.0](#from-version-65x-to-730)
- [getColorForTheme changes](#getcolorfortheme-changes)
- [From version 6.x.x to 7.0.0](#from-version-6xx-to-700)
- [What's new in Grafana 7.0?](#whats-new-in-grafana-70)
- [New data format](#new-data-format)
- [Improved TypeScript support](#improved-typescript-support)
- [Grafana Toolkit](#grafana-toolkit)
- [Field options](#field-options)
- [Backend plugins](#backend-plugins)
- [Migrate a plugin from Angular to React](#migrate-a-plugin-from-angular-to-react)
- [Migrate a panel plugin](#migrate-a-panel-plugin)
- [Migrate a data source plugin](#migrate-a-data-source-plugin)
- [Migrate to data frames](#migrate-to-data-frames)
- [Troubleshoot plugin migration](#troubleshoot-plugin-migration)
## From version 9.3.x to 9.4.x
### Supporting new navigation layout
First, enable the `topnav` feature flag in `custom.ini` to check how your plugin renders in the new navigation layout:
```ini
[feature_toggles]
enable = topnav
```
#### Migrate from `onNavChanged`
If your plugin uses the `onNavChanged` callback to inform Grafana of its nav model & sub pages, you should see that this results in duplicated navigation elements. If you disable `topnav` it should look just as before.
When `topnav` is enabled we need to update the plugin to take advantage of the new `PluginPage` component and not call `onNavChanged`. `onNavChanged` is now deprecated.
#### Switch to `PluginPage` component
Grafana now exposes a new `PluginPage` component from `@grafana/runtime` that hooks into the new navigation and page layouts and supports the old page layouts when the `topnav` feature is disabled. This new component will also handle rendering the section navigation. The section navigation can include other core sections and other plugins. To control what pages are displayed in the section navigation for a specific plugin, Grafana will use the pages added in `plugin.json` that have `addToNav` set to `true`.
This component is very easy to use. Simply wrap it around your page content:
```tsx
import { PluginPage } from '@grafana/runtime';
...
return (
<PluginPage>
{your page content here}
</PluginPage>
);
```
Grafana will look at the URL to know what plugin and page should be active in the section nav, so this only works for pages you have specified in `plugin.json`. `PluginPage` will then render a page header based on the page name specified in `plugin.json`.
#### Using `PluginPage` for pages not defined in `plugin.json`
The `PluginPage` component also exposes a `pageNav` property that is a `NavModelItem`. This `pageNav` property is useful for pages that are not defined in `plugin.json` (e.g. individual item pages). The `text` and `description` you specify in the `pageNav` model will be used to populate the breadcrumbs and page header.
Example:
```tsx
const pageNav = {
text: 'Write errors cortex-prod-04',
description: 'Incident timeline and details'
};
return (
<PluginPage pageNav={pageNav}>
{your page content here}
</PluginPage>
);
```
The way the active page is matched in the breadcrumbs and section nav relies on the page routes being hierarchical. If you have a list page and an item page, the item page needs to be a subroute of the list page and the list page url needs to be specified in your `plugin.json`. For example, you might have a list of users at `/users`. This means that the item page for a specific user needs to be at `/users/:id`. This may require some refactoring of your routes.
#### Using `PluginPage` with tabs
You can also create a further layer of hierarchy by specifying `children` in the `pageNav` model to created a page with tabbed navigation.
Example:
```tsx
const pageNav = {
text: 'My page',
description: 'Incident timeline and details',
url: '/a/myorgid-pluginname-app',
children: [
{
url: '/a/myorgid-pluginname-app/tab1',
text: 'Tab1',
active: true,
},
{
url: '/a/myorgid-pluginname-app/tab2',
text: 'Tab1',
},
],
};
return (
<PluginPage pageNav={pageNav}>
{your page content here}
</PluginPage>
);
```
#### Using `PluginPage` in a backwards-compatible way
If you want to maintain backwards-compatibility with older versions of Grafana, one way is to implement a `PluginPage` wrapper. If `PluginPage` is available and the `topnav` feature is enabled then use the real `PluginPage`, otherwise fallback to whatever each plugin is doing today (including calling `onNavChanged`).
Example:
```tsx
import { PluginPageProps, PluginPage as RealPluginPage, config } from '@grafana/runtime';
export const PluginPage = RealPluginPage && config.featureToggles.topnav ? RealPluginPage : PluginPageFallback;
function PluginPageFallback(props: PluginPageProps) {
return props.children;
}
```
Theres an additional step (and if block) needed to hide/show tabs depending on if `config.features.topnav` is `true`. These changes will need to be made in the `useNavModel.ts` file in your plugin:
```tsx
// useNavModel.ts
import { config } from '@grafana/runtime';
...
export function useNavModel({ meta, rootPath, onNavChanged }: Args) {
const { pathname, search } = useLocation();
useEffect(() => {
if (config.featureToggles.topnav) {
return;
}
}, [config]);
...
```
### Forwarded HTTP headers in grafana-plugin-sdk-go
It's recommended to use the `<request>.GetHTTPHeader` or `<request>.GetHTTPHeaders` methods when retrieving forwarded HTTP headers. See [Forward OAuth identity for the logged-in user]({{< relref "add-authentication-for-data-source-plugins.md#forward-oauth-identity-for-the-logged-in-user" >}}), [Forward cookies for the logged-in user
]({{< relref "add-authentication-for-data-source-plugins.md#forward-cookies-for-the-logged-in-user" >}}) or [Forward user header for the logged-in user]({{< relref "add-authentication-for-data-source-plugins.md#forward-user-header-for-the-logged-in-user" >}}) for example usages.
#### Technical details
The grafana-plugin-sdk-go [v0.147.0](https://github.com/grafana/grafana-plugin-sdk-go/releases/tag/v0.147.0) introduces a new interface [ForwardHTTPHeaders](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go@v0.147.0/backend#ForwardHTTPHeaders) that `QueryDataRequest`, `CheckHealthRequest` and `CallResourceRequest` implements.
Newly introduced forwarded HTTP headers in Grafana v9.4.0 are `X-Grafana-User`, `X-Panel-Id`, `X-Dashboard-Uid`, `X-Datasource-Uid` and `X-Grafana-Org-Id`. Internally these are prefixed with `http_` and sent as `http_<HTTP header name>` in [CheckHealthRequest.Headers](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go@v0.147.0/backend#CheckHealthRequest) and [QueryDataRequest.Headers](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go@v0.147.0/backend#QueryDataRequest). By using the [ForwardHTTPHeaders](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go@v0.147.0/backend#ForwardHTTPHeaders) methods you're guaranteed to be able to operate on HTTP headers without using the prefix, i.e. `X-Grafana-User`, `X-Panel-Id`, `X-Dashboard-Uid`, `X-Datasource-Uid` and `X-Grafana-Org-Id`.
## From version 9.1.x to 9.2.x
### React and React-dom as peer dependencies
In earlier versions of Grafana packages `react` and `react-dom` were installed during a `yarn install` regardless of a plugins dependencies. In 9.2.0 the `@grafana` packages declare these react packages as peerDependencies and will need adding to a plugins `package.json` file for test commands to continue to run successfully.
Example:
```json
// before
"dependencies": {
"@grafana/data": "9.1.0",
"@grafana/ui": "9.1.0",
},
// after
"dependencies": {
"@grafana/data": "9.2.0",
"@grafana/ui": "9.2.0",
"react": "17.0.2",
"react-dom": "17.0.2"
},
```
### NavModelItem requires a valid icon name
The typings of the `NavModelItem` have improved to only allow a valid `IconName` for the icon property. You can find the complete list of valid icons [here](https://github.com/grafana/grafana/blob/v9.2.0-beta1/packages/grafana-data/src/types/icon.ts). The icons specified in the list will work for older versions of Grafana 9.
Example:
```ts
// before
const model: NavModelItem = {
id: 'settings',
text: 'Settings',
icon: 'fa fa-cog',
url: `${baseUrl}/settings`,
};
// after
const model: NavModelItem = {
id: 'settings',
text: 'Settings',
icon: 'cog',
url: `${baseUrl}/settings`,
};
```
### Additional type availability
FieldProps, ModalProps, and QueryFieldProps are now exposed from `@grafana/ui`. They can be imported in the same way as other types.
Example:
```ts
import { FieldProps, ModalProps, QueryFieldProps } from '@grafana/ui';
```
## From version 8.x to 9.x
### 9.0 breaking changes
#### theme.visualization.getColorByName replaces getColorForTheme
`getColorForTheme` was removed, use `theme.visualization.getColorByName` instead
Example:
```ts
// before
fillColor: getColorForTheme(panel.sparkline.fillColor, config.theme)
// after
fillColor: config.theme.visualization.getColorByName(panel.sparkline.fillColor),
```
#### VizTextDisplayOptions replaces TextDisplayOptions
`TextDisplayOptions` was removed, use `VizTextDisplayOptions` instead
Example:
```ts
// before
interface Options {
...
text?: TextDisplayOptions;
...
}
// after
interface Options {
...
text?: VizTextDisplayOptions;
...
}
```
#### Changes in the internal of `backendSrv.fetch()`
We have changed the internals of `backendSrv.fetch()` to throw an error when the response is an incorrect JSON. Make sure to handle possible errors on the callsite where using `backendSrv.fetch()` (or any other `backendSrv` methods)
```ts
// PREVIOUSLY: this was returning with an empty object {} - in case the response is an invalid JSON
return await getBackendSrv().post(`${API_ROOT}/${id}/install`);
// AFTER THIS CHANGE: the following will throw an error - in case the response is an invalid JSON
return await getBackendSrv().post(`${API_ROOT}/${id}/install`);
```
#### GrafanaTheme2 and useStyles2 replaces getFormStyles
We have removed the deprecated `getFormStyles` function from [grafana-ui](https://www.npmjs.com/package/@grafana/ui). Use `GrafanaTheme2` and the `useStyles2` hook instead
#### /api/ds/query replaces /api/tsdb/query
We have removed the deprecated `/api/tsdb/query` metrics endpoint. Use [/api/ds/query]({{< relref "../http_api/data_source/#query-a-data-source" >}}) instead
#### selectOptionInTest has been removed
The `@grafana/ui` package helper function `selectOptionInTest` used in frontend tests has been removed as it caused testing libraries to be bundled in the production code of Grafana. If you were using this helper function in your tests please update your code accordingly:
```ts
// before
import { selectOptionInTest } from '@grafana/ui';
// ...test usage
await selectOptionInTest(selectEl, 'Option 2');
// after
import { select } from 'react-select-event';
// ...test usage
await select(selectEl, 'Option 2', { container: document.body });
```
#### Toolkit 9 and webpack
Plugins using custom Webpack configs could potentially break due to the changes between webpack@4 and webpack@5. Please refer to the [official migration guide](https://webpack.js.org/migrate/5/) for assistance.
Webpack 5 does not include polyfills for node.js core modules by default (e.g. `buffer`, `stream`, `os`). This can result in failed builds for plugins. If polyfills are required it is recommended to create a custom webpack config in the root of the plugin repo and add the required fallbacks:
```js
// webpack.config.js
module.exports.getWebpackConfig = (config, options) => ({
...config,
resolve: {
...config.resolve,
fallback: {
os: require.resolve('os-browserify/browser'),
stream: require.resolve('stream-browserify'),
timers: require.resolve('timers-browserify'),
},
},
});
```
Please refer to the webpack build error messages or the [official migration guide](https://webpack.js.org/migrate/5/) for assistance with fallbacks.
## From version 8.3.x to 8.4.x
This section explains how to migrate Grafana v8.3.x plugins to the updated plugin system available in Grafana v8.4.x. Depending on your plugin, you need to perform one or more of the following steps.
### Value Mapping Editor has been removed from @grafana-ui library
Removed due to being an internal component.
### Thresholds Editor has been removed from @grafana-ui library
Removed due to being an internal component.
### 8.4 deprecations
#### LocationService replaces getLocationSrv
In a previous release, we migrated to use a new routing system and introduced a new service for managing locations, navigation, and related information. In this release, we are making that new service the primary service.
**Example:** Import the service.
```ts
// before
import { getLocationSrv } from '@grafana/runtime';
// after
import { locationService } from '@grafana/runtime';
```
**Example:** Navigate to a path and add a new record in the navigation history so that you can navigate back to the previous one.
```ts
// before
getLocationSrv.update({
path: '/route-to-navigate-to',
replace: false,
});
// after
locationService.push('/route-to-navigate-to');
```
**Example:** Navigate to a path and replace the current record in the navigation history.
```ts
// before
getLocationSrv.update({
path: '/route-to-navigate-to',
replace: true,
});
// after
locationService.replace('/route-to-navigate-to');
```
**Example:** Update the search or query parameter for the current route and add a new record in the navigation history so that you can navigate back to the previous one.
```ts
// How to navigate to a new path
// before
getLocationSrv.update({
query: {
value: 1,
},
partial: true,
replace: false,
});
// after
locationService.partial({ value: 1 });
```
**Example:** Update the search or query parameter for the current route and add replacing it in the navigation history.
```ts
// before
getLocationSrv.update({
query: {
'var-variable': 1,
},
partial: true,
replace: true,
});
// after
locationService.partial({ 'var-variable': 1 }, true);
```
## From version 7.x.x to 8.x.x
This section explains how to migrate Grafana v7.x.x plugins to the updated plugin system available in Grafana v8.x.x. Depending on your plugin, you need to perform one or more of the following steps. We have documented the breaking changes in Grafana v8.x.x and the steps you need to take to upgrade your plugin.
### Backend plugin v1 support has been dropped
Use the new [plugin sdk](https://github.com/grafana/grafana-plugin-sdk-go) to run your backend plugin running in Grafana 8.
#### 1. Add dependency on grafana-plugin-sdk-go
Add a dependency on the `https://github.com/grafana/grafana-plugin-sdk-go`. We recommend using [go modules](https://go.dev/blog/using-go-modules) to manage your dependencies.
#### 2. Update the way you bootstrap your plugin
Update your `main` package to bootstrap via the new plugin sdk.
```go
// before
package main
import (
"github.com/grafana/grafana_plugin_model/go/datasource"
hclog "github.com/hashicorp/go-hclog"
plugin "github.com/hashicorp/go-plugin"
"github.com/myorgid/datasource/pkg/plugin"
)
func main() {
pluginLogger.Debug("Running GRPC server")
ds, err := NewSampleDatasource(pluginLogger);
if err != nil {
pluginLogger.Error("Unable to create plugin");
}
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: plugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "grafana_plugin_type",
MagicCookieValue: "datasource",
},
Plugins: map[string]plugin.Plugin{
"myorgid-datasource": &datasource.DatasourcePluginImpl{Plugin: ds},
},
GRPCServer: plugin.DefaultGRPCServer,
})
}
// after
package main
import (
"os"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
"github.com/myorgid/datasource/pkg/plugin"
)
func main() {
log.DefaultLogger.Debug("Running GRPC server")
if err := datasource.Manage("myorgid-datasource", NewSampleDatasource, datasource.ManageOpts{}); err != nil {
log.DefaultLogger.Error(err.Error())
os.Exit(1)
}
}
```
#### 3. Update the plugin package
Update your `plugin` package to use the new plugin sdk.
```go
// before
package plugin
import (
"context"
"github.com/grafana/grafana_plugin_model/go/datasource"
"github.com/hashicorp/go-hclog"
)
func NewSampleDatasource(pluginLogger hclog.Logger) (*SampleDatasource, error) {
return &SampleDatasource{
logger: pluginLogger,
}, nil
}
type SampleDatasource struct{
logger hclog.Logger
}
func (d *SampleDatasource) Query(ctx context.Context, tsdbReq *datasource.DatasourceRequest) (*datasource.DatasourceResponse, error) {
d.logger.Info("QueryData called", "request", req)
// logic for querying your datasource.
}
// after
package plugin
import (
"context"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana-plugin-sdk-go/data"
)
func NewSampleDatasource(_ backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
return &SampleDatasource{}, nil
}
type SampleDatasource struct{}
func (d *SampleDatasource) Dispose() {
// Clean up datasource instance resources.
}
func (d *SampleDatasource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
log.DefaultLogger.Info("QueryData called", "request", req)
// logic for querying your datasource.
}
func (d *SampleDatasource) CheckHealth(_ context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
log.DefaultLogger.Info("CheckHealth called", "request", req)
// The main use case for these health checks is the test button on the
// datasource configuration page which allows users to verify that
// a datasource is working as expected.
}
```
### Sign and load backend plugins
We strongly recommend that you not allow unsigned plugins in your Grafana installation. By allowing unsigned plugins, we cannot guarantee the authenticity of the plugin, which could compromise the security of your Grafana installation.
To sign your plugin, see [Sign a plugin](https://grafana.com/docs/grafana/latest/developers/plugins/sign-a-plugin/#sign-a-plugin).
You can still run and develop an unsigned plugin by running your Grafana instance in [development mode](https://grafana.com/docs/grafana/latest/administration/configuration/#app_mode). Alternatively, you can use the [allow_loading_unsigned_plugins]({{< relref "../../setup-grafana/configure-grafana/#allow_loading_unsigned_plugins" >}}) configuration setting.
### Update react-hook-form from v6 to v7
We have upgraded react-hook-form from version 6 to version 7. To make your forms compatible with version 7, refer to the [react-hook-form-migration-guide](https://react-hook-form.com/migrate-v6-to-v7/).
### Update the plugin.json
The property that defines which Grafana version your plugin supports has been renamed and now it is a range instead of a specific version.
```json
// before
{
"dependencies": {
"grafanaVersion": "7.5.x",
"plugins": []
}
}
// after
{
"dependencies": {
"grafanaDependency": ">=8.0.0",
"plugins": []
}
}
```
### Update imports to match emotion 11
Grafana uses Emotion library to manage frontend styling. We have updated the Emotion package and this can affect your frontend plugin if you have custom styling. You only need to update the import statements to get it working in Grafana 8.
```ts
// before
import { cx, css } from 'emotion';
// after
import { cx, css } from '@emotion/css';
```
### Update needed for app plugins using dashboards
To make side navigation work properly - app plugins targeting Grafana `8.+` and integrating into the side menu via [addToNav]({{< relref "metadata/#properties-4" >}}) property need to adjust their `plugin.json` and all dashboard json files to have a matching `uid`.
**`plugin.json`**
```json "linenos=inline,hl_lines=7,linenostart=1"
{
"id": "plugin-id",
// ...
"includes": [
{
"type": "dashboard",
"name": "(Team) Situation Overview",
"path": "dashboards/example-dashboard.json",
"addToNav": true,
"defaultNav": false,
"uid": "l3KqBxCMz"
}
]
// ...
}
```
**`dashboards/example-dashboard.json`**
```json
{
// ...
"title": "Example Dashboard",
"uid": "l3KqBxCMz",
"version": 1
// ...
}
```
### 8.0 deprecations
#### Grafana theme v1
In Grafana 8 we have introduced a new improved version of our theming system. The previous version of the theming system is still available but is deprecated and will be removed in the next major version of Grafana.
You can find more detailed information on how to apply the v2 theme [here](https://github.com/grafana/grafana/blob/main/contribute/style-guides/themes.md#theming-grafana).
**How to style a functional component**
The `useStyles` hook is the preferred way to access the theme when styling. It provides basic memoization and access to the theme object.
```ts
// before
import React, { ReactElement } from 'react';
import css from 'emotion';
import { GrafanaTheme } from '@grafana/data';
import { useStyles } from '@grafana/ui';
function Component(): ReactElement | null {
const styles = useStyles(getStyles);
}
const getStyles = (theme: GrafanaTheme) => ({
myStyle: css`
background: ${theme.colors.bodyBg};
display: flex;
`,
});
// after
import React, { ReactElement } from 'react';
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '@grafana/ui';
function Component(): ReactElement | null {
const theme = useStyles2(getStyles);
}
const getStyles = (theme: GrafanaTheme2) => ({
myStyle: css`
background: ${theme.colors.background.canvas};
display: flex;
`,
});
```
**How to use the theme in a functional component**
```ts
// before
import React, { ReactElement } from 'react';
import { useTheme } from '@grafana/ui';
function Component(): ReactElement | null {
const theme = useTheme();
}
// after
import React, { ReactElement } from 'react';
import { useTheme2 } from '@grafana/ui';
function Component(): ReactElement | null {
const theme = useTheme2();
// Your component has access to the theme variables now
}
```
**How to use the theme in a class component**
```ts
// before
import React from 'react';
import { Themeable, withTheme } from '@grafana/ui';
type Props = {} & Themeable;
class Component extends React.Component<Props> {
render() {
const { theme } = this.props;
// Your component has access to the theme variables now
}
}
export default withTheme(Component);
// after
import React from 'react';
import { Themeable2, withTheme2 } from '@grafana/ui';
type Props = {} & Themeable2;
class Component extends React.Component<Props> {
render() {
const { theme } = this.props;
// Your component has access to the theme variables now
}
}
export default withTheme2(Component);
```
**Gradually migrating components**
If you need to use both the v1 and v2 themes due to using migrated and non-migrated components in the same context, use the `v1` property on the `v2` theme as described in the following example.
```ts
function Component(): ReactElement | null {
const theme = useTheme2();
return (
<NonMigrated theme={theme.v1}>
<Migrated theme={theme] />
</NonMigrate>
);
};
```
## From version 6.2.x to 7.4.0
### Legend components
The Legend components have been refactored and introduced the following changes within the `@grafana/ui` package.
```ts
// before
import { LegendItem, LegendOptions, GraphLegend } from '@grafana/ui';
// after
import { VizLegendItem, VizLegendOptions, VizLegend } from '@grafana/ui';
```
- `LegendPlacement` has been updated from `'under' | 'right' | 'over'` to `'bottom' | 'right'` so you can not place the legend above the visualization anymore.
- The `isVisible` in the `LegendItem` has been renamed to `disabled` in `VizLegendItem`.
## From version 6.5.x to 7.3.0
### getColorForTheme changes
The `getColorForTheme` function arguments have changed from `(color: ColorDefinition, theme?: GrafanaThemeType)` to `(color: string, theme: GrafanaTheme)`.
```ts
// before
const color: ColorDefinition = {
hue: 'green';
name: 'dark-green';
variants: {
light: '#19730E'
dark: '#37872D'
};
}
const themeType: GrafanaThemeType = 'dark';
const themeColor = getColorForTheme(color, themeType);
// after
const color = 'green';
const theme: GrafanaTheme = useTheme();
const themeColor = getColorForTheme(color, theme);
```
## From version 6.x.x to 7.0.0
### What's new in Grafana 7.0?
Grafana 7.0 introduced a whole new plugin platform based on React. The new platform supersedes the previous Angular-based plugin platform.
Plugins built using Angular still work for the foreseeable future, but we encourage new plugin authors to develop with the new platform.
#### New data format
Along with the move to React, the new plugin platform introduced a new internal data format called [data frames](data-frames.md).
Previously, data source plugins could send data either as time series or tables. With data frames, data sources can send any data in a table-like structure. This gives you more flexibility to visualize your data in Grafana.
#### Improved TypeScript support
While the previous Angular-based plugin SDK did support TypeScript, for the React platform, weve greatly improved the support. All our APIs are now TypeScript, which might require existing code to update to the new stricter type definitions. Grafana 7.0 also introduced several new APIs for plugin developers that take advantage of many of the new features in Grafana 7.0.
#### Grafana Toolkit
With Grafana 7.0, we released a new tool for making it easier to develop plugins. Before, youd use Gulp, Grunt, or similar tools to generate the minified assets. Grafana Toolkit takes care of building and testing your plugin without complicated configuration files.
For more information, refer to [@grafana/toolkit](https://www.npmjs.com/package/@grafana/toolkit).
#### Field options
Grafana 7.0 introduced the concept of _field options_, a new way of configuring your data before it gets visualized. Since this was not available in previous versions, any plugin that enables field-based configuration will not work in previous versions of Grafana.
For plugins prior to Grafana 7.0, all options are considered _Display options_. The tab for field configuration isn't available.
#### Backend plugins
While backend plugins were available as an experimental feature in previous versions of Grafana, the support has been greatly improved for Grafana 7. Backend plugins for Grafana 7.0 are backwards-compatible and will continue to work. However, the old backend plugin system has been deprecated, and we recommend that you use the new SDK for backend plugins.
Since Grafana 7.0 introduced signing of backend plugins, community plugins wont load by default if theyre unsigned.
### Migrate a plugin from Angular to React
If youre looking to migrate a plugin to the new plugin platform, then we recommend that you release it under a new major version. Consider keeping a release branch for the previous version to be able to roll out patch releases for versions prior to Grafana 7.
While there's no 1-to-1 migration path from an Angular plugin to the new React platform, from early adopters, weve learned that one of the easiest ways to migrate is to:
1. Create a new branch called `migrate-to-react`.
1. Start from scratch with one of the templates provided by Grafana Toolkit.
1. Move the existing code into the new plugin incrementally, one component at a time.
#### Migrate a panel plugin
Prior to Grafana 7.0, you would export a MetricsPanelCtrl from module.ts.
**src/module.ts**
```ts
import { MetricsPanelCtrl } from 'grafana/app/plugins/sdk';
class MyPanelCtrl extends MetricsPanelCtrl {
// ...
}
export { MyPanelCtrl as PanelCtrl };
```
Starting with 7.0, plugins now export a PanelPlugin from module.ts where MyPanel is a React component containing the props from PanelProps.
**src/module.ts**
```ts
import { PanelPlugin } from '@grafana/data';
export const plugin = new PanelPlugin<MyOptions>(MyPanel);
```
**src/MyPanel.tsx**
```ts
import { PanelProps } from '@grafana/data';
interface Props extends PanelProps<SimpleOptions> {}
export function MyPanel({ options, data, width, height }: Props) {
// ...
}
```
#### Migrate a data source plugin
While all plugins are different, we'd like to share a migration process that has worked for some of our users.
1. Define your configuration model and `ConfigEditor`. For many plugins, the configuration editor is the simplest component so it's a good candidate to start with.
1. Implement the `testDatasource()` method on the class that extends `DataSourceApi` using the settings in your configuration model to make sure you can successfully configure and access the external API.
1. Implement the `query()` method. At this point, you can hard-code your query, because we havent yet implemented the query editor. The `query()` method supports both the new data frame response and the old TimeSeries response, so dont worry about converting to the new format just yet.
1. Implement the `QueryEditor`. How much work this requires depends on how complex your query model is.
By now, you should be able to release your new version.
To fully migrate to the new plugin platform, convert the time series response to a data frame response.
#### Migrate to data frames
Before 7.0, data source and panel plugins exchanged data using either time series or tables. Starting with 7.0, plugins use the new data frame format to pass data from data sources to panels.
Grafana 7.0 is backward compatible with the old data format used in previous versions. Panels and data sources using the old format will still work with plugins using the new data frame format.
The `DataQueryResponse` returned by the `query` method can be either a [LegacyResponseData](https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/types/datasource.ts#L419) or a [DataFrame](https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/types/dataFrame.ts#L200).
The [toDataFrame()](https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/dataframe/processDataFrame.ts#L309) function converts a legacy response, such as `TimeSeries` or `Table`, to a `DataFrame`. Use it to gradually move your code to the new format.
```ts
import { toDataFrame } from '@grafana/data';
```
```ts
async query(options: DataQueryRequest<MyQuery>): Promise<DataQueryResponse> {
return {
data: options.targets.map(query => {
const timeSeries: TimeSeries = await doLegacyRequest(query);
return toDataFrame(timeSeries);
}
};
}
```
### Troubleshoot plugin migration
As of Grafana 7.0, backend plugins can now be cryptographically signed to verify their origin. By default, Grafana ignores unsigned plugins. For more information, refer to [Allow unsigned plugins]({{< relref "../../administration/plugin-management/#allow-unsigned-plugins" >}}).

View File

@@ -0,0 +1,73 @@
---
title: Configuring plugin spellcheck
description: Internal docs on how to setup the plugin spellcheck
draft: true
---
# Configuring plugin spellcheck
> This process is applicable only for grafana maintained plugins and only if the plugins are activated in drone.grafana.net for CI process.
## What it is and why it is required
The spellcheck CI step performs basic spellcheck against the plugin code and documentation.\
It helps us to avoid showing things like this to our customers:
![image](https://user-images.githubusercontent.com/1436174/208397307-4270cb57-b538-4c68-8b0f-67ab5d3b8dad.png)
At the moment spellcheck is mandatory for all internal plugins.
Under the hood, the pipeline uses [cspell npm package](https://www.npmjs.com/package/cspell) to perform the spellcheck.
## Steps to configure spellcheck
If you ended up here following the link from the failing CI then most probably you don't have spellcheck configured for your plugin. Follow the below steps to set it up.
1. Install cspell package to your plugin's repo:
```bash
yarn add -D cspell@6.13.3
```
3. Add `spellcheck` command to the `scripts` section in `package.json` of your plugin:
```
"spellcheck": "cspell -c cspell.config.json \"**/*.{ts,tsx,js,go,md,mdx,yml,yaml,json,scss,css}\""
```
3. Create a `cspell.config.json` file in the repo root folder and add a basic config there:
```json
{
"ignorePaths": [
"coverage/**",
"cypress/**",
"dist/**",
"go.sum",
"mage_output_file.go",
"node_modules/**",
"provisioning/**/*.yaml",
"src/dashboards/*.json",
"**/testdata/**/*.json",
"**/testdata/**/*.jsonc",
"vendor/**",
"cspell.config.json",
"package.json",
"yarn.lock",
"docker-compose*.yaml",
"docker-compose*.yml"
],
"ignoreRegExpList": [
// ignore multiline imports
"import\\s*\\((.|[\r\n])*?\\)",
// ignore single line imports
"import\\s*.*\".*?\""
],
"words": ["grafana", "datasource", "datasources"]
}
```
4. Run `yarn spellcheck` to see if there are any misspellings
5. If errors found, either fix them or add to `ignorePaths` or `words` section of the `cspell.config.json` created earlier
Sample PR to add spellcheck to your repo: https://github.com/grafana/athena-datasource/pull/185/files

View File

@@ -0,0 +1,11 @@
---
description: Topics related to plugin publishing
title: Publish a plugin
---
# Publish a plugin
This section contains topics related to publishing Grafana plugins.
- [Publishing and signing criteria]({{< relref "../publishing-and-signing-criteria.md">}})
- [Publish or update a plugin]({{< relref "../publish-or-update-a-plugin.md">}})

View File

@@ -0,0 +1,131 @@
---
aliases:
- share-a-plugin/
description: Learn how to package and share your plugin.
title: Publish or update a plugin
type: docs
---
# Publish or update a plugin
You've just built your plugin; now you want to share it with the world.
In this guide, you'll learn how to package and share your plugin with others.
The best way to share your plugin with the world is to publish it in the [Grafana plugin catalog]{{< relref "/plugins" >}}).
By having your plugin published on Grafana.com, more users will be able to discover your plugin.
## Before you begin
When you build a plugin from source, a `dist` folder is created. This folder contains the production build or _plugin assets_ for your plugin.
To package a plugin, refer to [Package a plugin]{{< relref "package-a-plugin.md" >}}).
### Follow our guidelines
Get familiar with our plugin [publishing and signing criteria]({{< relref "./publishing-and-signing-criteria" >}})
### Do this for best results
To speed up the time it takes to review your plugin:
- Check that your plugin is ready for review using the [plugin validator](https://github.com/grafana/plugin-validator).
- Read our [6 tips for improving your Grafana plugin before you publish](/blog/2021/01/21/6-tips-for-improving-your-grafana-plugin-before-you-publish/).
- Refer to [plugin-examples](https://github.com/grafana/grafana-plugin-examples) to review best practices for building your plugin.
## Publish your plugin
Follow these steps to publish your plugin for the first time.
1. [Sign in](/auth/sign-in) to your Grafana Cloud account.
1. In the left menu, under **Org settings**, click **My Plugins**.
1. Click **Submit Plugin**. The Create Plugin Submission form appears.
{{< figure src="/static/img/docs/plugins/plugins-submission-create2.png" class="docs-image--no-shadow" max-width="650px" >}}
1. Enter the information requested by the form.
- **OS & Architecture:**
- Select **Single** if your plugin archive contains binaries for multiple architectures.
- Select **Multiple** if you'd like to submit separate plugin archives for each architecture.
This can lead to faster downloads since users can select the specific architecture on which they want to install the plugin.
- **URL:** A URL that points to a ZIP archive of your packaged plugin.
- **Source Code URL:** A URL that points to a public Git repository or ZIP archive of your complete plugin source code.
- **MD5:** The MD5 hash of the plugin specified by the **URL**.
- The remaining questions help us determine the [signature level]({{< relref "./sign-a-plugin#plugin-signature-levels" >}}) for your plugin.
1. Click **Submit**.
After you submit your plugin, we run an automated validation to make sure it adheres to our guidelines.
Your submission is placed in the review queue after the submission is validated.
All submissions are manually inspected by a plugin reviewer.
For every new plugin, we perform a manual review that includes the following checks:
- **Code review:** For quality and security purposes, we review the source code for the plugin.
If you're unable to make the source code publicly available, let us know in a comment on your plugin submission.
- **Tests:** We install your plugin on one of our Grafana instances to test it for basic use.
For more advanced plugins, we may ask you to assist us in configuring a test environment for the plugin.
We use the test environment whenever you submit a plugin update.
## Update your plugin
To submit an **update** for an already published plugin:
1. [Sign in](/auth/sign-in) to your Grafana Cloud account.
1. In the left menu, under **Org settings**, click **My Plugins**.
1. Click **Submit Update** for the plugin you want to update. The Create Plugin Submission form appears.
{{< figure src="/static/img/docs/plugins/plugins-submission-create2.png" class="docs-image--no-shadow" max-width="650px" >}}
1. Enter the information requested by the form.
- **OS & Architecture:**
- Select **Single** if your plugin archive contains binaries for multiple architectures.
- Select **Multiple** if you'd like to submit separate plugin archives for each architecture.
This can lead to faster downloads since users can select the specific architecture they want to install the plugin on.
- **URL:** A URL that points to a ZIP archive of your packaged plugin.
- **Source Code URL:** A URL that points to a public Git repository or ZIP archive of your complete plugin source code. See [examples](#what-source-code-url-formats-are-supported).
- **MD5:** The MD5 hash of the plugin specified by the **URL**.
1. Click **Submit**.
## Frequently asked questions
### Do I need to submit a private plugin?
- No. Please only submit plugins that you wish to make publicly available for the Grafana community.
### How long does it take to review my submission?
- We're not able to give an estimate at this time, though we're constantly working on improving the time it takes to review a plugin.
### Can I decide a date when my plugin will be published?
- No. We cannot guarantee specific publishing dates, as plugins are immediately published after a review based on our internal prioritization.
### Can I see metrics of my plugin installs, downloads or usage?
- No. We don't offer this information at the moment to plugin authors.
### How can I update my plugin's catalog page?
- The plugin's catalog page content is extracted from the plugin README file.
To update the plugin's catalog page, submit an updated plugin with the new content included in the README file.
### Can I unlist my plugin from the Grafana plugin catalog in case of a bug?
- In the event of a bug, unlisting the plugin from our catalog may be possible in exceptional cases, such as security concerns. However, we don't have control over the specific instances where the plugin is installed.
### Can I distribute my plugin somewhere else other than the Grafana plugin catalog?
- The official method for distributing Grafana plugins is through our catalog. Alternative methods, such as installing private or development plugins on local Grafana instances, are available as per the guidelines provided in [this guide]({{< relref "../../administration/plugin-management#install-plugin-on-local-grafana" >}}).
### Can I still use Angular for my plugin?
- No. We will not accept any new plugin submissions written in Angular. For more information, refer to our [Angular support deprecation documentation]({{< relref "../angular_deprecation" >}}).
### Do plugin signatures expire?
- Plugin signatures do not currently expire.
### What source code URL formats are supported?
- Using a tag or branch: `https://github.com/grafana/clock-panel/tree/v2.1.3`
- A tag or branch and the code is in a subdirectory (important for mono repos): `https://github.com/grafana/clock-panel/tree/v2.1.3/plugin/` (here, the plugin contains the plugin code)
- Using the latest main or master branch commit: `https://github.com/grafana/clock-panel/` (not recommended, it's better to pass a tag or branch)

View File

@@ -0,0 +1,22 @@
---
title: Plugin publishing and signing criteria
---
# Plugin publishing and signing criteria
Grafana plugins must adhere to the Grafana Labs [Plugin Policy](https://grafana.com/legal/plugins/). Our review process for publishing and signing will examine your compliance with this policy.
> **Important:** Grafana Labs reserves the right to decline or remove any plugin at its discretion. Failure to comply with publishing and signing criteria may result in immediate removal from the Grafana plugin catalog.
## Plugin licensing
Plugins must be licensed under one of the following AGPL compliant licenses for publishing to the Grafana plugin catalog:
- AGPL-3.0
- Apache-2.0
- BSD
- GPL-3.0
- LGPL-3.0
- MIT
If contributing a plugin on behalf of an organization, be sure to seek guidance from your legal team.

View File

@@ -0,0 +1,10 @@
---
description: Reference topics.
title: Reference
---
# Reference
This section contains a metadata reference for plugin development.
- [{{< relref "plugin.json">}}](metadata.md)

View File

@@ -0,0 +1,126 @@
---
title: Sign a plugin
---
# Sign a plugin
Grafana requires all plugins to be signed so that we can verify their authenticity with [signature verification]({{< relref "../../administration/plugin-management#plugin-signatures" >}}).
All Grafana Labs-authored backend plugins, including Enterprise plugins, are signed. By [default]({{< relref "../../administration/plugin-management#allow-unsigned-plugins" >}}), Grafana **requires** all plugins to be signed in order for them to be loaded.
Before you can sign your plugin, you need to decide whether you want to sign it as a _public_ or a _private_ plugin.
To make your plugin publicly available outside of your organization, sign your plugin under a _community_ or _commercial_ [signature level](#plugin-signature-levels). Public plugins are available from the [Grafana plugin catalog](https://grafana.com/plugins) and can be installed by anyone.
If you intend to only use the plugin within your organization, sign it under a _private_ [signature level](#plugin-signature-levels).
## Generate an API key
To verify ownership of your plugin, generate an API key that you'll use every time you need to sign a new version of your plugin.
1. [Create a Grafana Cloud account](https://grafana.com/signup).
1. Make sure that the first part of the plugin ID matches the slug of your Grafana Cloud account.
You can find the plugin ID in the `plugin.json` file inside your plugin directory. For example, if your account slug is `acmecorp`, you need to prefix the plugin ID with `acmecorp-`.
1. [Create a Grafana Cloud API key](https://grafana.com/docs/grafana-cloud/reference/create-api-key/) with the **PluginPublisher** role.
## Sign a public plugin
Public plugins need to be reviewed by the Grafana team before you can sign them.
1. Submit your plugin for [review]({{< relref "publish-a-plugin/#publishing-your-plugin-for-the-first-time" >}})
1. If we approve your plugin, you're granted a plugin signature level. You need this signature level to proceed.
1. In your plugin directory, sign the plugin with the API key you just created. Grafana Sign Plugin creates a [MANIFEST.txt](#plugin-manifest) file in the `dist` directory of your plugin:
```bash
export GRAFANA_API_KEY=<YOUR_API_KEY>
npx @grafana/sign-plugin@latest
```
## Sign a private plugin
1. In your plugin directory, sign the plugin with the API key you just created. Grafana Sign Plugin creates a [MANIFEST.txt](#plugin-manifest) file in the `dist` directory of your plugin.
```bash
export GRAFANA_API_KEY=<YOUR_API_KEY>
npx @grafana/sign-plugin@latest --rootUrls https://example.com/grafana
```
1. After the `rootUrls` flag, enter a comma-separated list of URLs for the Grafana instances where you intend to install the plugin.
## Plugin signature levels
To sign a plugin, you need to select the _signature level_ that you want to sign it under. The signature level of your plugin determines how you can distribute it.
You can sign your plugin under three different _signature levels_: _private_, _community_, and _commercial_.
| **Plugin Level** | **Paid Subscription Required?** | **Description** |
| ---------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Private | No;<br>Free of charge | <p>You can create and sign a Private plugin for any technology at no charge.</p><p>Private plugins are intended for use on your own installation of Grafana. They may not be distributed to the Grafana community, and they are not published in the Grafana plugin catalog.</p> |
| Community | No;<br>Free of charge | <p>You can create, sign, and distribute plugins at no charge, provided that all dependent technologies are open source and not for profit.</p><p>Community plugins are published in the official Grafana catalog, and are available to the entire Grafana community.</p> |
| Commercial | Yes;<br>Commercial plugin subscription required | <p>You can create, sign, and distribute plugins with dependent technologies that are closed source or commercially backed. To do so, enter into a Commercial plugin subscription with Grafana Labs.</p><p>Commercial plugins are published on the Grafana plugin catalog, and are available to the entire Grafana community.</p> |
For instructions on how to sign a plugin under the Community and Commercial signature level, refer to [Sign a public plugin](#sign-a-public-plugin).
For instructions on how to sign a plugin under the Private signature level, refer to [Sign a private plugin](#sign-a-private-plugin).
## Plugin manifest
For Grafana to verify the digital signature of a plugin, the plugin must include a signed manifest file, `MANIFEST.txt`. The signed manifest file contains two sections:
- **Signed message -** Contains plugin metadata and plugin files with their respective checksums (SHA256).
- **Digital signature -** Created by encrypting the signed message using a private key. Grafana has a public key built-in that can be used to verify that the digital signature has been encrypted using the expected private key.
**Example**
```txt
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
{
"manifestVersion": "2.0.0",
"signatureType": "community",
"signedByOrg": "myorgid",
"signedByOrgName": "My Org",
"plugin": "myorgid-simple-panel",
"version": "1.0.0",
"time": 1602753404133,
"keyId": "7e4d0c6a708866e7",
"files": {
"LICENSE": "12ab7a0961275f5ce7a428e662279cf49bab887d12b2ff7bfde738346178c28c",
"module.js.LICENSE.txt": "0d8f66cd4afb566cb5b7e1540c68f43b939d3eba12ace290f18abc4f4cb53ed0",
"module.js.map": "8a4ede5b5847dec1c6c30008d07bef8a049408d2b1e862841e30357f82e0fa19",
"plugin.json": "13be5f2fd55bee787c5413b5ba6a1fae2dfe8d2df6c867dadc4657b98f821f90",
"README.md": "2d90145b28f22348d4f50a81695e888c68ebd4f8baec731fdf2d79c8b187a27f",
"module.js": "b4b6945bbf3332b08e5e1cb214a5b85c82557b292577eb58c8eb1703bc8e4577"
}
}
-----BEGIN PGP SIGNATURE-----
Version: OpenPGP.js v4.10.1
Comment: https://openpgpjs.org
wqEEARMKAAYFAl+IE3wACgkQfk0ManCIZudpdwIHTCqjVzfm7DechTa7BTbd
+dNIQtwh8Tv2Q9HksgN6c6M9nbQTP0xNHwxSxHOI8EL3euz/OagzWoiIWulG
7AQo7FYCCQGucaLPPK3tsWaeFqVKy+JtQhrJJui23DAZLSYQYZlKQ+nFqc9x
T6scfmuhWC/TOcm83EVoCzIV3R5dOTKHqkjIUg==
=GdNq
-----END PGP SIGNATURE-----
```
## Troubleshooting
### Why do I get a "Modified signature" error?
In some cases an invalid `MANIFEST.txt` is generated because of an issue when signing the plugin on Windows. You can fix this by replacing all double backslashes, `\\`, with a forward slash, `/`, in the `MANIFEST.txt` file. You need to do this every time you sign your plugin.
### Why do I get a "Field is required: `rootUrls`" error for my public plugin?
With a **public** plugin, your plugin doesn't have a plugin signature level assigned to it yet. A Grafana team member will assign a signature level to your plugin once it has been reviewed and approved. For more information, refer to [Sign a public plugin](#sign-a-public-plugin).
### Why do I get a "Field is required: `rootUrls`" error for my private plugin?
With a **private** plugin, you need to add a `rootUrls` flag to the `plugin:sign` command. The `rootUrls` must match the [root_url]({{< relref "../../setup-grafana/configure-grafana/#root_url" >}}) configuration. For more information, refer to [Sign a private plugin](#sign-a-private-plugin).
If you still get this error, make sure that the API key was generated by a Grafana Cloud account that matches the first part of the plugin ID.

View File

@@ -0,0 +1,121 @@
---
title: Working with data frames
---
# Working with data frames
The data frame is a columnar data structure which allows efficient querying of large amounts of data. Since data frames are a central concept when developing plugins for Grafana, in this guide we'll look at some ways you can use them.
The DataFrame interface contains a `name` and an array of `fields` where each field contains the name, type, and the values for the field.
> **Note:** If you're looking to migrate an existing plugin to use the data frame format, refer to [Migrate to data frames]({{< relref "migration-guide/#migrate-to-data-frames" >}}).
## Create a data frame
If you build a data source plugin, then you'll most likely want to convert a response from an external API to a data frame. Let's look at how to create a data frame.
Let's start with creating a simple data frame that represents a time series. The easiest way to create a data frame is to use the toDataFrame function.
```ts
// Need to be of the same length.
const timeValues = [1599471973065, 1599471975729];
const numberValues = [12.3, 28.6];
// Create data frame from values.
const frame = toDataFrame({
name: 'http_requests_total',
fields: [
{ name: 'Time', type: FieldType.time, values: timeValues },
{ name: 'Value', type: FieldType.number, values: numberValues },
],
});
```
> **Note:** Data frames representing time series contain at least a `time` field, and a `number` field. By convention, built-in plugins use `Time` and `Value` as field names for data frames containing time series data.
As you can see from the example, creating data frames like this requires that your data is already stored as columnar data. If you already have the records in the form of an array of objects, then you can pass it to `toDataFrame` which tries to guess the schema based on the types and names of the objects in the array. If you're creating complex data frames this way, then be sure to verify that you get the schema you expect.
```ts
const series = [
{ Time: 1599471973065, Value: 12.3 },
{ Time: 1599471975729, Value: 28.6 },
];
const frame = toDataFrame(series);
frame.name = 'http_requests_total';
```
## Read values from a data frame
When you're building a panel plugin, the data frames returned by the data source are available from the `data` prop in your panel component.
```ts
function SimplePanel({ data: Props }) {
const frame = data.series[0];
// ...
}
```
Before you start reading the data, think about what data you expect. For example, to visualize a time series we'd need at least one time field, and one number field.
```ts
const timeField = frame.fields.find((field) => field.type === FieldType.time);
const valueField = frame.fields.find((field) => field.type === FieldType.number);
```
Other types of visualizations might need multiple dimensions. For example, a bubble chart that uses three numeric fields: the X-axis, Y-axis, and one for the radius of each bubble. In this case, instead of hard coding the field names, we recommend that you let the user choose the field to use for each dimension.
```ts
const x = frame.fields.find((field) => field.name === xField);
const y = frame.fields.find((field) => field.name === yField);
const size = frame.fields.find((field) => field.name === sizeField);
for (let i = 0; i < frame.length; i++) {
const row = [x?.values.get(i), y?.values.get(i), size?.values.get(i)];
// ...
}
```
Alternatively, you can use the DataFrameView, which gives you an array of objects that contain a property for each field in the frame.
```ts
const view = new DataFrameView(frame);
view.forEach((row) => {
console.log(row[options.xField], row[options.yField], row[options.sizeField]);
});
```
## Display values from a data frame
Field options let the user control how Grafana displays the data in a data frame.
To apply the field options to a value, use the `display` method on the corresponding field. The result contains information such as the color and suffix to use when display the value.
```ts
const valueField = frame.fields.find((field) => field.type === FieldType.number);
return (
<div>
{valueField
? valueField.values.toArray().map((value) => {
const displayValue = valueField.display!(value);
return (
<p style={{ color: displayValue.color }}>
{displayValue.text} {displayValue.suffix ? displayValue.suffix : ''}
</p>
);
})
: null}
</div>
);
```
To apply field options to the name of a field, use getFieldDisplayName.
```ts
const valueField = frame.fields.find((field) => field.type === FieldType.number);
const valueFieldName = getFieldDisplayName(valueField, frame);
```

View File

@@ -9,8 +9,8 @@ weight: 8
This section provides basic information about observability topics in general and Grafana in particular. These topics will help people who are just starting out with observability and monitoring.
{{< docs/shared lookup="basics/what-is-grafana.md" source="grafana" version="<GRAFANA VERSION>" >}}
{{< docs/shared "basics/what-is-grafana.md" >}}
{{< docs/shared lookup="basics/grafana-cloud.md" source="grafana" version="<GRAFANA VERSION>" >}}
{{< docs/shared "basics/grafana-cloud.md" >}}
{{< docs/shared lookup="basics/grafana-enterprise.md" source="grafana" version="<GRAFANA VERSION>" >}}
{{< docs/shared "basics/grafana-enterprise.md" >}}

View File

@@ -34,11 +34,11 @@ It can be difficult to understand the distinctions between different data source
## Plugins
A Grafana plugin is software that adds new capabilities to Grafana. They come in many types, but right now we'll address _data source plugins_. The job of a Grafana data source plugin is to take a query you want answered, retrieve the data from the data source, and reconcile the differences between the data model of the data source and the data model of Grafana dashboards. It does this using a unified data structure called a [data frame](https://grafana.com/developers/plugin-tools/introduction/data-frames).
A Grafana plugin is software that adds new capabilities to Grafana. They come in many types, but right now we'll address _data source plugins_. The job of a Grafana data source plugin is to take a query you want answered, retrieve the data from the data source, and reconcile the differences between the data model of the data source and the data model of Grafana dashboards. It does this using a unified data structure called a [data frame][data-frames].
The data coming into the plugin from the data source might be in many different formats (such as JSON, rows and columns, or CSV), but when it leaves the plugin and moves through the rest of the gates toward a visualization, it's always in data frames.
Currently, Grafana offers a diverse range of 155 data sources that you can use. The most commonly used options are already pre-installed and accessible. Before exploring other options, look for an existing data source that matches your requirements. Grafana constantly updates the list, but if you don't find a suitable data source, you can browse through the [plugin catalog](/grafana/plugins/?type=datasource) or [create a plugin](https://grafana.com/developers/plugin-tools/).
Currently, Grafana offers a diverse range of 155 data sources that you can use. The most commonly used options are already pre-installed and accessible. Before exploring other options, look for an existing data source that matches your requirements. Grafana constantly updates the list, but if you don't find a suitable data source, you can browse through the [plugin catalog](/grafana/plugins/?type=datasource) or [create a plugin](/tutorials/build-a-data-source-plugin/).
## Queries
@@ -90,6 +90,9 @@ And with the data source identified and the plugin installed, you can write your
This component architecture is part of what makes Grafana so powerful and general. Given the data source plugin and data frame abstraction, any data source you can access can work with the same general approach.
{{% docs/reference %}}
[data-frames]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/developers/plugins/data-frames"
[data-frames]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/developers/plugins/data-frames"
[transform-data]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/panels-visualizations/query-transform-data/transform-data"
[transform-data]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/panels-visualizations/query-transform-data/transform-data"
{{% /docs/reference %}}

View File

@@ -59,7 +59,7 @@ The following gauge visualization displays the total RAM usage on a computer.
The third metric type is called a `histogram`, which counts observations and organizes them into configurable groups. The following example displays floating-point numbers grouped into ranges that display how frequently each occurred.
{{< figure src="/media/docs/grafana/intro-prometheus/histogram-example.png" max-width="750px" caption="Histogram visualization" >}}
{{< figure src="/media/docs/grafana/intro-prometheus/histogram-example.png" max-width="750px" caption="Historgram visualization" >}}
These core concepts of time series, metrics, labels, and aggregation functions are foundational to Grafana and observability.

View File

@@ -94,11 +94,15 @@ For more information, see the documentation on [creating alerts with multiple se
In the case of SQL-like data sources, more than one numeric column can be selected, with or without additional string columns to be used as dimensions. For example, `AVG(Temperature) AS AvgTemp, MAX(Temperature) AS MaxTemp`. This, if combined with multiple dimensions, can result in a lot of series. Selecting multiple values is currently only designed to be used with visualization.
Additional technical information on tabular time series formats and how dimensions are extracted can be found in [the developer documentation on data frames as time series](https://grafana.com/developers/plugin-tools/introduction/data-frames#data-frames-as-time-series).
Additional technical information on tabular time series formats and how dimensions are extracted can be found in [the developer documentation on data frames as time series][data-frames-as-time-series].
{{% docs/reference %}}
[create-grafana-managed-rule]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/alerting/alerting-rules/create-grafana-managed-rule#single-and-multi-dimensional-rule"
[create-grafana-managed-rule]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/alerting/alerting-rules/create-grafana-managed-rule#single-and-multi-dimensional-rule"
[data-frames-as-time-series]: "/docs/grafana/ -> docs/grafana/<GRAFANA VERSION>/developers/plugins/data-frames#data-frames-as-time-series"
[data-frames-as-time-series]: "/docs/grafana-cloud/ -> docs/grafana/<GRAFANA VERSION>/developers/plugins/data-frames#data-frames-as-time-series"
[time-series-databases]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/fundamentals/timeseries#time-series-databases"
[time-series-databases]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/fundamentals/timeseries#time-series-databases"
{{% /docs/reference %}}

View File

@@ -7,6 +7,6 @@ weight: 9
# Get started
This section provides guidance on how build your first dashboard after you have installed Grafana. It also provides step-by-step instructions on how to add a Prometheus, InfluxDB, or an MS SQL Server data source. Refer to [Data sources]({{< relref "../datasources" >}}) for a list of all supported data sources.
This section provides guidance on how build your first dashboard after you have installed Grafana. It also provides step-by-step instructions on how to add a Prometheus, InfluxDB, or an MS SQL Server data source. Refer to [Data sources]({{< relref "../administration/data-source-management/" >}}) for a list of all supported data sources.
{{< section >}}

View File

@@ -8,7 +8,7 @@ weight: 400
# Get started with Grafana and InfluxDB
{{< docs/shared lookup="influxdb/intro.md" source="grafana" version="<GRAFANA VERSION>" >}}
{{< docs/shared "influxdb/intro.md" >}}
#### Get InfluxDB

Some files were not shown because too many files have changed in this diff Show More