Merge remote-tracking branch 'origin/main' into grafana-git-ui-sync

This commit is contained in:
Mariell Hoversholm committed 2025-02-18 14:09:31 +01:00
commit 77a11efe0c
187 files changed
+8864 -4317

No files matched your search

+21 -5
View File
@@ -1548,7 +1548,7 @@ exports[`better eslint`] = {
"public/app/features/alerting/unified/GrafanaRuleQueryViewer.tsx:5381": [
[0, 0, 0, "\'@grafana/data/src/datetime/rangeutil\' import is restricted from being used by a pattern. Import from the public export instead.", "0"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings in text props in text props. Wrap text with <Trans /> or use t() or use t()", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"],
@@ -1561,7 +1561,8 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "12"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "13"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "14"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "15"]
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "15"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "16"]
],
"public/app/features/alerting/unified/NotificationPoliciesPage.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
@@ -2342,6 +2343,9 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "10"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "11"]
],
"public/app/features/alerting/unified/components/rule-editor/dag.test.ts:5381": [
[0, 0, 0, "\'@grafana/runtime/src/utils/DataSourceWithBackend\' import is restricted from being used by a pattern. Import from the public export instead.", "0"]
],
"public/app/features/alerting/unified/components/rule-editor/labels/LabelsButtons.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"]
@@ -2466,9 +2470,18 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"]
],
"public/app/features/alerting/unified/components/rule-viewer/tabs/Query.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"]
"public/app/features/alerting/unified/components/rule-viewer/tabs/Details.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "6"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "7"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "8"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "9"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "10"]
],
"public/app/features/alerting/unified/components/rule-viewer/tabs/Routing.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
@@ -2922,6 +2935,9 @@ exports[`better eslint`] = {
"public/app/features/alerting/unified/rule-list/components/RuleListIcon.tsx:5381": [
[0, 0, 0, "\'@grafana/ui/src/components/Text/Text\' import is restricted from being used by a pattern. Import from the public export instead.", "0"]
],
"public/app/features/alerting/unified/state/AlertingQueryRunner.test.ts:5381": [
[0, 0, 0, "\'@grafana/runtime/src/utils/DataSourceWithBackend\' import is restricted from being used by a pattern. Import from the public export instead.", "0"]
],
"public/app/features/alerting/unified/state/actions.ts:5381": [
[0, 0, 0, "\'@grafana/runtime/src/utils/logging\' import is restricted from being used by a pattern. Import from the public export instead.", "0"]
],
+5
View File
@@ -5,6 +5,10 @@ GOPATH ?= $(shell go env GOPATH)
GOBIN ?= $(firstword $(subst :, ,${GOPATH}))/bin
GO ?= $(shell which go)
# Add this near the top of the file, after the initial variable definitions
ifndef VARIABLES_MK
VARIABLES_MK := 1
# Below generated variables ensure that every time a tool under each variable is invoked, the correct version
# will be used; reinstalling only if needed.
# For example for bra variable:
@@ -65,3 +69,4 @@ $(SWAGGER): $(BINGO_DIR)/swagger.mod
@echo "(re)installing $(GOBIN)/swagger-v0.30.6-0.20240310114303-db51e79a0e37"
@cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=swagger.mod -o=$(GOBIN)/swagger-v0.30.6-0.20240310114303-db51e79a0e37 "github.com/go-swagger/go-swagger/cmd/swagger"
endif
+41 -41
View File
@@ -18,7 +18,7 @@ services: []
steps:
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd
@@ -69,7 +69,7 @@ services: []
steps:
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- go install github.com/bazelbuild/buildtools/buildifier@latest
@@ -112,7 +112,7 @@ services: []
steps:
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- yarn install --immutable || yarn install --immutable
@@ -170,7 +170,7 @@ services: []
steps:
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- yarn install --immutable || yarn install --immutable
@@ -309,7 +309,7 @@ steps:
path: /github-app
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- yarn install --immutable || yarn install --immutable
@@ -427,7 +427,7 @@ steps:
path: /github-app
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- '# It is required that code generated from Thema/CUE be committed and in sync
@@ -517,7 +517,7 @@ services: []
steps:
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd
@@ -642,7 +642,7 @@ steps:
path: /github-app
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- mkdir -p bin
@@ -713,7 +713,7 @@ steps:
-a targz:grafana:linux/arm/v7 -a docker:grafana:linux/amd64 -a docker:grafana:linux/amd64:ubuntu
-a docker:grafana:linux/arm64 -a docker:grafana:linux/arm64:ubuntu -a docker:grafana:linux/arm/v7
-a docker:grafana:linux/arm/v7:ubuntu --go-version=1.23.5 --yarn-cache=$$YARN_CACHE_FOLDER
--build-id=$$DRONE_BUILD_NUMBER --ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.20.5
--build-id=$$DRONE_BUILD_NUMBER --ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.20.6
--tag-format='{{ .version_base }}-{{ .buildID }}-{{ .arch }}' --ubuntu-tag-format='{{
.version_base }}-{{ .buildID }}-ubuntu-{{ .arch }}' --verify='false' --grafana-dir=$$PWD
> packages.txt
@@ -770,7 +770,7 @@ steps:
GF_APP_MODE: development
GF_SERVER_HTTP_PORT: "3001"
GF_SERVER_ROUTER_LOGGING: "1"
image: alpine:3.20.5
image: alpine:3.20.6
name: grafana-server
- commands:
- ./bin/build e2e-tests --port 3001 --suite dashboards-suite
@@ -1114,7 +1114,7 @@ steps:
name: compile-build-cmd
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- '# It is required that code generated from Thema/CUE be committed and in sync
@@ -1280,7 +1280,7 @@ services: []
steps:
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- yarn install --immutable || yarn install --immutable
@@ -1637,7 +1637,7 @@ services: []
steps:
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- yarn install --immutable || yarn install --immutable
@@ -1708,7 +1708,7 @@ services: []
steps:
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- yarn install --immutable || yarn install --immutable
@@ -1766,7 +1766,7 @@ services: []
steps:
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- yarn install --immutable || yarn install --immutable
@@ -1842,7 +1842,7 @@ services: []
steps:
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- '# It is required that code generated from Thema/CUE be committed and in sync
@@ -1922,7 +1922,7 @@ services: []
steps:
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd
@@ -1988,7 +1988,7 @@ services: []
steps:
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- yarn install --immutable || yarn install --immutable
@@ -2063,7 +2063,7 @@ steps:
path: /github-app
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- mkdir -p bin
@@ -2133,7 +2133,7 @@ steps:
-a targz:grafana:linux/arm/v7 -a docker:grafana:linux/amd64 -a docker:grafana:linux/amd64:ubuntu
-a docker:grafana:linux/arm64 -a docker:grafana:linux/arm64:ubuntu -a docker:grafana:linux/arm/v7
-a docker:grafana:linux/arm/v7:ubuntu --go-version=1.23.5 --yarn-cache=$$YARN_CACHE_FOLDER
--build-id=$$DRONE_BUILD_NUMBER --ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.20.5
--build-id=$$DRONE_BUILD_NUMBER --ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.20.6
--tag-format='{{ .version_base }}-{{ .buildID }}-{{ .arch }}' --ubuntu-tag-format='{{
.version_base }}-{{ .buildID }}-ubuntu-{{ .arch }}' --verify='false' --grafana-dir=$$PWD
> packages.txt
@@ -2194,7 +2194,7 @@ steps:
GF_APP_MODE: development
GF_SERVER_HTTP_PORT: "3001"
GF_SERVER_ROUTER_LOGGING: "1"
image: alpine:3.20.5
image: alpine:3.20.6
name: grafana-server
- commands:
- ./bin/build e2e-tests --port 3001 --suite dashboards-suite
@@ -2611,7 +2611,7 @@ steps:
name: compile-build-cmd
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- '# It is required that code generated from Thema/CUE be committed and in sync
@@ -2856,7 +2856,7 @@ services: []
steps:
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- yarn install --immutable || yarn install --immutable
@@ -2912,7 +2912,7 @@ services: []
steps:
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- yarn install --immutable || yarn install --immutable
@@ -2986,7 +2986,7 @@ services: []
steps:
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- '# It is required that code generated from Thema/CUE be committed and in sync
@@ -3064,7 +3064,7 @@ services: []
steps:
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd
@@ -3169,7 +3169,7 @@ steps:
name: compile-build-cmd
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- '# It is required that code generated from Thema/CUE be committed and in sync
@@ -3372,7 +3372,7 @@ services: []
steps:
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- mkdir -p bin
@@ -3504,7 +3504,7 @@ services: []
steps:
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- mkdir -p bin
@@ -3994,7 +3994,7 @@ steps:
environment:
_EXPERIMENTAL_DAGGER_CLOUD_TOKEN:
from_secret: dagger_token
ALPINE_BASE: alpine:3.20.5
ALPINE_BASE: alpine:3.20.6
CDN_DESTINATION:
from_secret: rgm_cdn_destination
DESTINATION:
@@ -4069,7 +4069,7 @@ steps:
environment:
_EXPERIMENTAL_DAGGER_CLOUD_TOKEN:
from_secret: dagger_token
ALPINE_BASE: alpine:3.20.5
ALPINE_BASE: alpine:3.20.6
CDN_DESTINATION:
from_secret: rgm_cdn_destination
DESTINATION:
@@ -4231,7 +4231,7 @@ steps:
environment:
_EXPERIMENTAL_DAGGER_CLOUD_TOKEN:
from_secret: dagger_token
ALPINE_BASE: alpine:3.20.5
ALPINE_BASE: alpine:3.20.6
CDN_DESTINATION:
from_secret: rgm_cdn_destination
DESTINATION:
@@ -4333,7 +4333,7 @@ services: []
steps:
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- yarn install --immutable || yarn install --immutable
@@ -4387,7 +4387,7 @@ services: []
steps:
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- '# It is required that code generated from Thema/CUE be committed and in sync
@@ -4468,7 +4468,7 @@ steps:
environment:
_EXPERIMENTAL_DAGGER_CLOUD_TOKEN:
from_secret: dagger_token
ALPINE_BASE: alpine:3.20.5
ALPINE_BASE: alpine:3.20.6
CDN_DESTINATION:
from_secret: rgm_cdn_destination
DESTINATION:
@@ -4612,7 +4612,7 @@ steps:
environment:
_EXPERIMENTAL_DAGGER_CLOUD_TOKEN:
from_secret: dagger_token
ALPINE_BASE: alpine:3.20.5
ALPINE_BASE: alpine:3.20.6
CDN_DESTINATION:
from_secret: rgm_cdn_destination
DESTINATION:
@@ -4740,7 +4740,7 @@ steps:
environment:
_EXPERIMENTAL_DAGGER_CLOUD_TOKEN:
from_secret: dagger_token
ALPINE_BASE: alpine:3.20.5
ALPINE_BASE: alpine:3.20.6
CDN_DESTINATION:
from_secret: rgm_cdn_destination
DESTINATION:
@@ -4879,7 +4879,7 @@ steps:
name: grabpl
- commands:
- echo $DRONE_RUNNER_NAME
image: alpine:3.20.5
image: alpine:3.20.6
name: identify-runner
- commands:
- '# It is required that code generated from Thema/CUE be committed and in sync
@@ -5303,7 +5303,7 @@ steps:
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM node:22-bookworm
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM google/cloud-sdk:431.0.0
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/grafana-ci-deploy:1.3.3
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM alpine:3.20.5
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM alpine:3.20.6
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM ubuntu:22.04
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM byrnedo/alpine-curl:0.1.8
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM plugins/slack
@@ -5341,7 +5341,7 @@ steps:
- trivy --exit-code 1 --severity HIGH,CRITICAL node:22-bookworm
- trivy --exit-code 1 --severity HIGH,CRITICAL google/cloud-sdk:431.0.0
- trivy --exit-code 1 --severity HIGH,CRITICAL grafana/grafana-ci-deploy:1.3.3
- trivy --exit-code 1 --severity HIGH,CRITICAL alpine:3.20.5
- trivy --exit-code 1 --severity HIGH,CRITICAL alpine:3.20.6
- trivy --exit-code 1 --severity HIGH,CRITICAL ubuntu:22.04
- trivy --exit-code 1 --severity HIGH,CRITICAL byrnedo/alpine-curl:0.1.8
- trivy --exit-code 1 --severity HIGH,CRITICAL plugins/slack
@@ -5605,6 +5605,6 @@ kind: secret
name: gcr_credentials
---
kind: signature
hmac: ed11784cafc81bbb617e931f1aa392a9de8bde04dd5d14e51baf856a3942dd91
hmac: a1d8f3defdf52b76049d8a8df1c21dd13cac9a2a9f80566abceffb4acd1b49d2
...
+2 -1
View File
@@ -73,7 +73,7 @@
/apps/alerting/ @grafana/alerting-backend
/apps/playlist/ @grafana/grafana-app-platform-squad
/apps/investigation/ @fcjack @matryer
/apps/investigations/ @fcjack @matryer @svennergr
/apps/advisor/ @grafana/plugins-platform-backend
/pkg/api/ @grafana/grafana-backend-group
/pkg/apis/ @grafana/grafana-app-platform-squad
@@ -446,6 +446,7 @@
/.betterer.eslint.config.js @grafana/frontend-ops
/.gitattributes @grafana/frontend-ops
/.gitignore @grafana/frontend-ops
/.ignore @grafana/frontend-ops
/.nvmrc @grafana/frontend-ops
/.prettierignore @grafana/frontend-ops
/.yarn @grafana/frontend-ops
+1 -1
View File
@@ -8,7 +8,7 @@ updates:
directories:
- "/"
- "/apps/playlist"
- "/apps/investigation"
- "/apps/investigations"
- "/pkg/aggregator"
- "/pkg/apimachinery"
- "/pkg/apiserver"
+11 -5
View File
@@ -40,6 +40,7 @@ __debug_bin*
# This is the new place of the block, but I leave the previous here for a while
/devenv/docker/blocks/auth/saml-enterprise
/devenv/docker/blocks/auth/signer
/devenv/docker/blocks/spanner_tests
/tmp
tools/phantomjs/phantomjs
@@ -106,14 +107,18 @@ profile.cov
/pkg/cmd/grafana-cli/grafana-cli
/pkg/cmd/grafana-server/grafana-server
/pkg/cmd/grafana-server/debug
/pkg/extensions/*
/pkg/build/cmd/artifactspage.go
/pkg/build/cmd/artifactspage.tmpl.html
/pkg/build/cmd/exportversion.go
/pkg/server/wireexts_enterprise.go
# Extensions
/pkg/cmd/grafana-cli/runner/wireexts_enterprise.go
/pkg/server/wireexts_enterprise.go
/pkg/build/cmd/enterprise.go
/pkg/extensions/*
!/pkg/extensions/.keep
!/pkg/extensions/main.go
/public/app/extensions
!/public/app/extensions/.keep
debug.test
/examples/*/dist
/packaging/**/*.rpm
@@ -173,6 +178,7 @@ compilation-stats.json
/e2e/benchmarks/**/results
/e2e/build_results.zip
/e2e/extensions
!/e2e/extensions/.keep
/e2e/extensions-suite
/test-results/
/playwright-report/
+3 -3
View File
@@ -191,10 +191,10 @@ linters-settings:
allow: []
deny:
- pkg: github.com/grafana/grafana/pkg
desc: apps/investigation is not allowed to import grafana core
desc: apps/investigations is not allowed to import grafana core
files:
- ./apps/investigation/*
- ./apps/investigation/**/*
- ./apps/investigations/*
- ./apps/investigations/**/*
gocritic:
enabled-checks:
- ruleguard
+22
View File
@@ -0,0 +1,22 @@
# This is a trick to ignore files only by git but not by other tools
!/public/app/extensions
!/pkg/extensions/*
!/pkg/cmd/grafana-cli/runner/wireexts_enterprise.go
!/pkg/server/wireexts_enterprise.go
!/pkg/build/cmd/enterprise.go
!/pkg/extensions/*
# Enterprise emails
!/emails/templates/enterprise_*
!/public/emails/enterprise_*
# Enterprise reporting fonts
!/public/fonts/dejavu
# Enterprise devenv
!/devenv/docker/blocks/grafana-enterprise
!/devenv/docker/blocks/saml-enterprise
# This is the new place of the block, but I leave the previous here for a while
!/devenv/docker/blocks/auth/saml-enterprise
!/devenv/docker/blocks/auth/signer
!/devenv/docker/blocks/spanner_tests
+1 -1
View File
@@ -73,7 +73,7 @@ COPY pkg/storage/unified/apistore pkg/storage/unified/apistore
COPY pkg/semconv pkg/semconv
COPY pkg/aggregator pkg/aggregator
COPY apps/playlist apps/playlist
COPY apps/investigation apps/investigation
COPY apps/investigations apps/investigations
COPY apps/advisor apps/advisor
COPY apps apps
COPY kindsv2 kindsv2
@@ -1,2 +0,0 @@
module: "github.com/grafana/grafana/apps/investigation/kinds"
language: version: "v0.8.2"
@@ -1,87 +0,0 @@
package investigation
// This is our Investigation definition, which contains metadata about the kind, and the kind's schema
investigation: {
kind: "Investigation"
pluralName: "Investigations"
current: "v1alpha1"
versions: {
"v1alpha1": {
codegen: {
frontend: false
backend: true
}
schema: {
#InvestigationSpec: {
title: string
status: "open" | "closed"
items: [...#InvestigationItem]
}
// InvestigationItem is an item in an investigation.
#InvestigationItem: {
id: string
title: string
// type is the type of the item "timeseries", "heatmap", "log-table" (not an enum to allow for future extensions).
type: string
// url is the URL to the item.
url: string
// origin is where the item was created from.
origin: string // "explore-metrics", "explore-logs", "explore-traces" (not an enum to allow for future extensions)
// iconPath (optional) is the path to the icon for the item.
iconPath?: string
// timeRange (optional) is the time range of the item.
timeRange: #AbsoluteTimeRange
// note (optional) is a comment on the item.
note?: [...#Comment]
// queryType is the type of the query used to generate this item.
queryType: "logs" | "metrics"
// dataQuery contains the query used to generate this item.
dataQuery: #DataQueryLogs | #DataQueryMetrics
}
// DataQueryLogs is a data query for logs.
#DataQueryLogs: {
// refId is the reference ID of the query.
refId: string
// datasource is the datasource of the query.
datasource: #DatasourceRef
// expr is the expression of the query.
expr: string
// maxLines (optional) is used to limit the number of log rows returned.
maxLines?: int64
}
// DataQueryMetrics is a data query for metrics.
#DataQueryMetrics: {
refId: string
datasource: #DatasourceRef
expr: string
}
// Comment is a comment on an investigation item.
#Comment: {
authorUserID: string
bodyMarkdown: string
}
// DatasourceRef is a reference to a datasource.
#DatasourceRef: {
uid: string
type: string
apiVersion: string
name: string
}
// AbsoluteTimeRange is a time range specified by absolute timestamps.
#AbsoluteTimeRange: {
from: number
to: number
}
// spec is the schema of our resource. The spec should include all the user-ediable information for the kind.
spec: #InvestigationSpec
}
}
}
}
-9
View File
@@ -1,9 +0,0 @@
package investigation
manifest: {
appName: "investigation"
groupOverride: "investigation.grafana.app"
kinds: [
investigation,
]
}
@@ -1,136 +0,0 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
package v1alpha1
// AbsoluteTimeRange is a time range specified by absolute timestamps.
// +k8s:openapi-gen=true
type InvestigationAbsoluteTimeRange struct {
From float64 `json:"from"`
To float64 `json:"to"`
}
// NewInvestigationAbsoluteTimeRange creates a new InvestigationAbsoluteTimeRange object.
func NewInvestigationAbsoluteTimeRange() *InvestigationAbsoluteTimeRange {
return &InvestigationAbsoluteTimeRange{}
}
// Comment is a comment on an investigation item.
// +k8s:openapi-gen=true
type InvestigationComment struct {
AuthorUserID string `json:"authorUserID"`
BodyMarkdown string `json:"bodyMarkdown"`
}
// NewInvestigationComment creates a new InvestigationComment object.
func NewInvestigationComment() *InvestigationComment {
return &InvestigationComment{}
}
// DatasourceRef is a reference to a datasource.
// +k8s:openapi-gen=true
type InvestigationDatasourceRef struct {
Uid string `json:"uid"`
Type string `json:"type"`
ApiVersion string `json:"apiVersion"`
Name string `json:"name"`
}
// NewInvestigationDatasourceRef creates a new InvestigationDatasourceRef object.
func NewInvestigationDatasourceRef() *InvestigationDatasourceRef {
return &InvestigationDatasourceRef{}
}
// DataQueryLogs is a data query for logs.
// +k8s:openapi-gen=true
type InvestigationDataQueryLogs struct {
// refId is the reference ID of the query.
RefId string `json:"refId"`
// datasource is the datasource of the query.
Datasource InvestigationDatasourceRef `json:"datasource"`
// expr is the expression of the query.
Expr string `json:"expr"`
// maxLines (optional) is used to limit the number of log rows returned.
MaxLines *int64 `json:"maxLines,omitempty"`
}
// NewInvestigationDataQueryLogs creates a new InvestigationDataQueryLogs object.
func NewInvestigationDataQueryLogs() *InvestigationDataQueryLogs {
return &InvestigationDataQueryLogs{
Datasource: *NewInvestigationDatasourceRef(),
}
}
// DataQueryMetrics is a data query for metrics.
// +k8s:openapi-gen=true
type InvestigationDataQueryMetrics struct {
RefId string `json:"refId"`
Datasource InvestigationDatasourceRef `json:"datasource"`
Expr string `json:"expr"`
}
// NewInvestigationDataQueryMetrics creates a new InvestigationDataQueryMetrics object.
func NewInvestigationDataQueryMetrics() *InvestigationDataQueryMetrics {
return &InvestigationDataQueryMetrics{
Datasource: *NewInvestigationDatasourceRef(),
}
}
// InvestigationItem is an item in an investigation.
// +k8s:openapi-gen=true
type InvestigationInvestigationItem struct {
Id string `json:"id"`
Title string `json:"title"`
// type is the type of the item "timeseries", "heatmap", "log-table" (not an enum to allow for future extensions).
Type string `json:"type"`
// url is the URL to the item.
Url string `json:"url"`
// origin is where the item was created from.
// "explore-metrics", "explore-logs", "explore-traces" (not an enum to allow for future extensions)
Origin string `json:"origin"`
// iconPath (optional) is the path to the icon for the item.
IconPath *string `json:"iconPath,omitempty"`
// timeRange (optional) is the time range of the item.
TimeRange InvestigationAbsoluteTimeRange `json:"timeRange"`
// note (optional) is a comment on the item.
Note []InvestigationComment `json:"note,omitempty"`
// queryType is the type of the query used to generate this item.
QueryType InvestigationInvestigationItemQueryType `json:"queryType"`
// dataQuery contains the query used to generate this item.
DataQuery interface{} `json:"dataQuery"`
}
// NewInvestigationInvestigationItem creates a new InvestigationInvestigationItem object.
func NewInvestigationInvestigationItem() *InvestigationInvestigationItem {
return &InvestigationInvestigationItem{
TimeRange: *NewInvestigationAbsoluteTimeRange(),
}
}
// spec is the schema of our resource. The spec should include all the user-ediable information for the kind.
// +k8s:openapi-gen=true
type InvestigationSpec struct {
Title string `json:"title"`
Status InvestigationSpecStatus `json:"status"`
Items []InvestigationInvestigationItem `json:"items"`
}
// NewInvestigationSpec creates a new InvestigationSpec object.
func NewInvestigationSpec() *InvestigationSpec {
return &InvestigationSpec{}
}
// +k8s:openapi-gen=true
type InvestigationInvestigationItemQueryType string
const (
InvestigationInvestigationItemQueryTypeLogs InvestigationInvestigationItemQueryType = "logs"
InvestigationInvestigationItemQueryTypeMetrics InvestigationInvestigationItemQueryType = "metrics"
)
// +k8s:openapi-gen=true
type InvestigationSpecStatus string
const (
InvestigationSpecStatusOpen InvestigationSpecStatus = "open"
InvestigationSpecStatusClosed InvestigationSpecStatus = "closed"
)
@@ -1,537 +0,0 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
// Code generated by grafana-app-sdk. DO NOT EDIT.
package v1alpha1
import (
common "k8s.io/kube-openapi/pkg/common"
spec "k8s.io/kube-openapi/pkg/validation/spec"
)
func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
return map[string]common.OpenAPIDefinition{
"github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.Investigation": schema_pkg_apis_investigation_v1alpha1_Investigation(ref),
"github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationAbsoluteTimeRange": schema_pkg_apis_investigation_v1alpha1_InvestigationAbsoluteTimeRange(ref),
"github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationComment": schema_pkg_apis_investigation_v1alpha1_InvestigationComment(ref),
"github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationDataQueryLogs": schema_pkg_apis_investigation_v1alpha1_InvestigationDataQueryLogs(ref),
"github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationDataQueryMetrics": schema_pkg_apis_investigation_v1alpha1_InvestigationDataQueryMetrics(ref),
"github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationDatasourceRef": schema_pkg_apis_investigation_v1alpha1_InvestigationDatasourceRef(ref),
"github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationInvestigationItem": schema_pkg_apis_investigation_v1alpha1_InvestigationInvestigationItem(ref),
"github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationList": schema_pkg_apis_investigation_v1alpha1_InvestigationList(ref),
"github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationSpec": schema_pkg_apis_investigation_v1alpha1_InvestigationSpec(ref),
"github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationStatus": schema_pkg_apis_investigation_v1alpha1_InvestigationStatus(ref),
"github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationstatusOperatorState": schema_pkg_apis_investigation_v1alpha1_InvestigationstatusOperatorState(ref),
}
}
func schema_pkg_apis_investigation_v1alpha1_Investigation(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"metadata": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"),
},
},
"spec": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationSpec"),
},
},
"status": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationStatus"),
},
},
},
Required: []string{"metadata", "spec", "status"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationSpec", "github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}
func schema_pkg_apis_investigation_v1alpha1_InvestigationAbsoluteTimeRange(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "AbsoluteTimeRange is a time range specified by absolute timestamps.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"from": {
SchemaProps: spec.SchemaProps{
Default: 0,
Type: []string{"number"},
Format: "double",
},
},
"to": {
SchemaProps: spec.SchemaProps{
Default: 0,
Type: []string{"number"},
Format: "double",
},
},
},
Required: []string{"from", "to"},
},
},
}
}
func schema_pkg_apis_investigation_v1alpha1_InvestigationComment(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "Comment is a comment on an investigation item.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"authorUserID": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"bodyMarkdown": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"authorUserID", "bodyMarkdown"},
},
},
}
}
func schema_pkg_apis_investigation_v1alpha1_InvestigationDataQueryLogs(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "DataQueryLogs is a data query for logs.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"refId": {
SchemaProps: spec.SchemaProps{
Description: "refId is the reference ID of the query.",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"datasource": {
SchemaProps: spec.SchemaProps{
Description: "datasource is the datasource of the query.",
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationDatasourceRef"),
},
},
"expr": {
SchemaProps: spec.SchemaProps{
Description: "expr is the expression of the query.",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"maxLines": {
SchemaProps: spec.SchemaProps{
Description: "maxLines (optional) is used to limit the number of log rows returned.",
Type: []string{"integer"},
Format: "int64",
},
},
},
Required: []string{"refId", "datasource", "expr"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationDatasourceRef"},
}
}
func schema_pkg_apis_investigation_v1alpha1_InvestigationDataQueryMetrics(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "DataQueryMetrics is a data query for metrics.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"refId": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"datasource": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationDatasourceRef"),
},
},
"expr": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"refId", "datasource", "expr"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationDatasourceRef"},
}
}
func schema_pkg_apis_investigation_v1alpha1_InvestigationDatasourceRef(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "DatasourceRef is a reference to a datasource.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"uid": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"type": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"name": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"uid", "type", "apiVersion", "name"},
},
},
}
}
func schema_pkg_apis_investigation_v1alpha1_InvestigationInvestigationItem(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "InvestigationItem is an item in an investigation.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"id": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"title": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"type": {
SchemaProps: spec.SchemaProps{
Description: "type is the type of the item \"timeseries\", \"heatmap\", \"log-table\" (not an enum to allow for future extensions).",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"url": {
SchemaProps: spec.SchemaProps{
Description: "url is the URL to the item.",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"origin": {
SchemaProps: spec.SchemaProps{
Description: "origin is where the item was created from. \"explore-metrics\", \"explore-logs\", \"explore-traces\" (not an enum to allow for future extensions)",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"iconPath": {
SchemaProps: spec.SchemaProps{
Description: "iconPath (optional) is the path to the icon for the item.",
Type: []string{"string"},
Format: "",
},
},
"timeRange": {
SchemaProps: spec.SchemaProps{
Description: "timeRange (optional) is the time range of the item.",
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationAbsoluteTimeRange"),
},
},
"note": {
SchemaProps: spec.SchemaProps{
Description: "note (optional) is a comment on the item.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationComment"),
},
},
},
},
},
"queryType": {
SchemaProps: spec.SchemaProps{
Description: "queryType is the type of the query used to generate this item.",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"dataQuery": {
SchemaProps: spec.SchemaProps{
Description: "dataQuery contains the query used to generate this item.",
Type: []string{"object"},
Format: "",
},
},
},
Required: []string{"id", "title", "type", "url", "origin", "timeRange", "queryType", "dataQuery"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationAbsoluteTimeRange", "github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationComment"},
}
}
func schema_pkg_apis_investigation_v1alpha1_InvestigationList(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"metadata": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
},
},
"items": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.Investigation"),
},
},
},
},
},
},
Required: []string{"metadata", "items"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.Investigation", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
}
}
func schema_pkg_apis_investigation_v1alpha1_InvestigationSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "spec is the schema of our resource. The spec should include all the user-ediable information for the kind.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"title": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"status": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"items": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationInvestigationItem"),
},
},
},
},
},
},
Required: []string{"title", "status", "items"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationInvestigationItem"},
}
}
func schema_pkg_apis_investigation_v1alpha1_InvestigationStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"operatorStates": {
SchemaProps: spec.SchemaProps{
Description: "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.",
Type: []string{"object"},
AdditionalProperties: &spec.SchemaOrBool{
Allows: true,
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationstatusOperatorState"),
},
},
},
},
},
"additionalFields": {
SchemaProps: spec.SchemaProps{
Description: "additionalFields is reserved for future use",
Type: []string{"object"},
AdditionalProperties: &spec.SchemaOrBool{
Allows: true,
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Format: "",
},
},
},
},
},
},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1.InvestigationstatusOperatorState"},
}
}
func schema_pkg_apis_investigation_v1alpha1_InvestigationstatusOperatorState(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"lastEvaluation": {
SchemaProps: spec.SchemaProps{
Description: "lastEvaluation is the ResourceVersion last evaluated",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"state": {
SchemaProps: spec.SchemaProps{
Description: "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"descriptiveState": {
SchemaProps: spec.SchemaProps{
Description: "descriptiveState is an optional more descriptive state field which has no requirements on format",
Type: []string{"string"},
Format: "",
},
},
"details": {
SchemaProps: spec.SchemaProps{
Description: "details contains any extra information that is operator-specific",
Type: []string{"object"},
AdditionalProperties: &spec.SchemaOrBool{
Allows: true,
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Format: "",
},
},
},
},
},
},
Required: []string{"lastEvaluation", "state"},
},
},
}
}
@@ -1,50 +0,0 @@
//
// This file is generated by grafana-app-sdk
// DO NOT EDIT
//
package apis
import (
"encoding/json"
"github.com/grafana/grafana-app-sdk/app"
)
var (
rawSchemaInvestigationv1alpha1 = []byte(`{"spec":{"description":"spec is the schema of our resource. The spec should include all the user-ediable information for the kind.","properties":{"items":{"items":{"properties":{"dataQuery":{"description":"dataQuery contains the query used to generate this item.","oneOf":[{"allOf":[{"required":["refId","datasource","expr"]},{"not":{"anyOf":[{"required":["refId","datasource","expr"]}]}}]},{"allOf":[{"required":["refId","datasource","expr"]},{"not":{"anyOf":[{"required":["refId","datasource","expr"]}]}}]}],"properties":{"datasource":{"description":"datasource is the datasource of the query.","properties":{"apiVersion":{"type":"string"},"name":{"type":"string"},"type":{"type":"string"},"uid":{"type":"string"}},"required":["uid","type","apiVersion","name"],"type":"object"},"expr":{"description":"expr is the expression of the query.","type":"string"},"maxLines":{"description":"maxLines (optional) is used to limit the number of log rows returned.","format":"int64","type":"integer"},"refId":{"description":"refId is the reference ID of the query.","type":"string"}},"type":"object"},"iconPath":{"description":"iconPath (optional) is the path to the icon for the item.","type":"string"},"id":{"type":"string"},"note":{"description":"note (optional) is a comment on the item.","items":{"properties":{"authorUserID":{"type":"string"},"bodyMarkdown":{"type":"string"}},"required":["authorUserID","bodyMarkdown"],"type":"object"},"type":"array"},"origin":{"description":"origin is where the item was created from.","type":"string"},"queryType":{"description":"queryType is the type of the query used to generate this item.","enum":["logs","metrics"],"type":"string"},"timeRange":{"description":"timeRange (optional) is the time range of the item.","properties":{"from":{"type":"number"},"to":{"type":"number"}},"required":["from","to"],"type":"object"},"title":{"type":"string"},"type":{"description":"type is the type of the item \"timeseries\", \"heatmap\", \"log-table\" (not an enum to allow for future extensions).","type":"string"},"url":{"description":"url is the URL to the item.","type":"string"}},"required":["id","title","type","url","origin","timeRange","queryType","dataQuery"],"type":"object"},"type":"array"},"status":{"enum":["open","closed"],"type":"string"},"title":{"type":"string"}},"required":["title","status","items"],"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"operatorStates":{"additionalProperties":{"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"description":"details contains any extra information that is operator-specific","type":"object","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object","x-kubernetes-preserve-unknown-fields":true}}`)
versionSchemaInvestigationv1alpha1 app.VersionSchema
_ = json.Unmarshal(rawSchemaInvestigationv1alpha1, &versionSchemaInvestigationv1alpha1)
)
var appManifestData = app.ManifestData{
AppName: "investigation",
Group: "investigation.grafana.app",
Kinds: []app.ManifestKind{
{
Kind: "Investigation",
Scope: "Namespaced",
Conversion: false,
Versions: []app.ManifestKindVersion{
{
Name: "v1alpha1",
Schema: &versionSchemaInvestigationv1alpha1,
},
},
},
},
}
func jsonToMap(j string) map[string]any {
m := make(map[string]any)
json.Unmarshal([]byte(j), &j)
return m
}
func LocalManifest() app.Manifest {
return app.NewEmbeddedManifest(appManifestData)
}
func RemoteManifest() app.Manifest {
return app.NewAPIServerManifest("investigation")
}
File renamed without changes.
+152
View File
@@ -0,0 +1,152 @@
[
{
"id": "896312ce-65b0-4b50-ade1-e7f04fa22c66",
"title": "Thursday morning investigation",
"hasCustomName": false,
"isFavorite": false,
"collectables": [
{
"origin": "Explore Logs",
"type": "timeseries",
"queries": [
{
"refId": "LABEL_BREAKDOWN_VALUES",
"queryType": "range",
"editorMode": "code",
"supportingQueryType": "grafana-lokiexplore-app",
"legendFormat": "{{detected_level}}",
"expr": "sum(count_over_time({service_name=\"web_app_1\"} | detected_level != \"\"[$__auto])) by (detected_level)"
}
],
"timeRange": {
"to": "2025-02-13T11:31:20.536Z",
"from": "2025-02-13T11:16:20.536Z",
"raw": {
"from": "now-15m",
"to": "now"
}
},
"datasource": {
"uid": "fe9k7u07b1a0wc"
},
"url": "http://localhost:3000/a/grafana-lokiexplore-app/explore/service/web_app_1/labels?patterns=%5B%5D&from=now-15m&to=now&var-ds=fe9k7u07b1a0wc&var-filters=service_name%7C%3D%7Cweb_app_1&var-fields=&var-levels=&var-metadata=&var-patterns=&var-lineFilterV2=&var-lineFilters=&urlColumns=%5B%5D&visualizationType=%22logs%22&displayedFields=%5B%5D&timezone=browser&var-all-fields=&var-labelBy=$__all",
"id": "LABEL_BREAKDOWN_VALUES_detected_level",
"title": "detected_level",
"logoPath": "public/plugins/grafana-lokiexplore-app/img/img/logo.svg",
"createdAt": "2025-02-13T11:31:23.637Z"
}
],
"createdAt": "2025-02-13T11:31:23.636Z",
"updatedAt": "2025-02-13T11:31:23.637Z",
"viewMode": {
"mode": "compact",
"showComments": true,
"showTooltips": false
}
},
{
"id": "e9cf1958-d0ed-46b7-b597-9052c7648656",
"title": "Thursday morning investigation",
"hasCustomName": false,
"isFavorite": false,
"collectables": [
{
"origin": "Explore Logs",
"type": "timeseries",
"queries": [
{
"refId": "LABEL_BREAKDOWN_VALUES",
"queryType": "range",
"editorMode": "code",
"supportingQueryType": "grafana-lokiexplore-app",
"legendFormat": "{{detected_level}}",
"expr": "sum(count_over_time({service_name=\"web_app_1\"} | detected_level != \"\"[$__auto])) by (detected_level)"
}
],
"timeRange": {
"to": "2025-02-13T11:31:20.536Z",
"from": "2025-02-13T11:16:20.536Z",
"raw": {
"from": "now-15m",
"to": "now"
}
},
"datasource": {
"uid": "fe9k7u07b1a0wc"
},
"url": "http://localhost:3000/a/grafana-lokiexplore-app/explore/service/web_app_1/labels?patterns=%5B%5D&from=now-15m&to=now&var-ds=fe9k7u07b1a0wc&var-filters=service_name%7C%3D%7Cweb_app_1&var-fields=&var-levels=&var-metadata=&var-patterns=&var-lineFilterV2=&var-lineFilters=&urlColumns=%5B%5D&visualizationType=%22logs%22&displayedFields=%5B%5D&timezone=browser&var-all-fields=&var-labelBy=$__all",
"id": "LABEL_BREAKDOWN_VALUES_detected_level",
"title": "detected_level",
"logoPath": "public/plugins/grafana-lokiexplore-app/img/img/logo.svg",
"createdAt": "2025-02-13T11:31:23.638Z"
},
{
"origin": "Explore Logs",
"type": "timeseries",
"queries": [
{
"refId": "LABEL_BREAKDOWN_VALUES",
"queryType": "range",
"editorMode": "code",
"supportingQueryType": "grafana-lokiexplore-app",
"legendFormat": "{{service_name}}",
"expr": "sum(count_over_time({service_name=\"web_app_1\",service_name != \"\"} [$__auto])) by (service_name)"
}
],
"timeRange": {
"to": "2025-02-13T11:31:20.536Z",
"from": "2025-02-13T11:16:20.536Z",
"raw": {
"from": "now-15m",
"to": "now"
}
},
"datasource": {
"uid": "fe9k7u07b1a0wc"
},
"url": "http://localhost:3000/a/grafana-lokiexplore-app/explore/service/web_app_1/labels?patterns=%5B%5D&from=now-15m&to=now&var-ds=fe9k7u07b1a0wc&var-filters=service_name%7C%3D%7Cweb_app_1&var-fields=&var-levels=&var-metadata=&var-patterns=&var-lineFilterV2=&var-lineFilters=&urlColumns=%5B%5D&visualizationType=%22logs%22&displayedFields=%5B%5D&timezone=browser&var-all-fields=&var-labelBy=$__all",
"id": "LABEL_BREAKDOWN_VALUES_service_name",
"title": "service_name",
"logoPath": "public/plugins/grafana-lokiexplore-app/img/img/logo.svg",
"createdAt": "2025-02-13T11:31:41.507Z"
},
{
"origin": "Explore Logs",
"type": "timeseries",
"queries": [
{
"refId": "LABEL_BREAKDOWN_VALUES",
"queryType": "range",
"editorMode": "code",
"supportingQueryType": "grafana-lokiexplore-app",
"legendFormat": "{{service}}",
"expr": "sum(count_over_time({service_name=\"web_app_1\",service != \"\"} [$__auto])) by (service)"
}
],
"timeRange": {
"to": "2025-02-13T11:31:20.536Z",
"from": "2025-02-13T11:16:20.536Z",
"raw": {
"from": "now-15m",
"to": "now"
}
},
"datasource": {
"uid": "fe9k7u07b1a0wc"
},
"url": "http://localhost:3000/a/grafana-lokiexplore-app/explore/service/web_app_1/labels?patterns=%5B%5D&from=now-15m&to=now&var-ds=fe9k7u07b1a0wc&var-filters=service_name%7C%3D%7Cweb_app_1&var-fields=&var-levels=&var-metadata=&var-patterns=&var-lineFilterV2=&var-lineFilters=&urlColumns=%5B%5D&visualizationType=%22logs%22&displayedFields=%5B%5D&timezone=browser&var-all-fields=&var-labelBy=$__all",
"id": "LABEL_BREAKDOWN_VALUES_service",
"title": "service",
"logoPath": "public/plugins/grafana-lokiexplore-app/img/img/logo.svg",
"createdAt": "2025-02-13T11:31:43.698Z"
}
],
"createdAt": "2025-02-13T11:31:23.637Z",
"updatedAt": "2025-02-13T11:31:43.698Z",
"viewMode": {
"mode": "compact",
"showComments": true,
"showTooltips": false
}
}
]
@@ -1,4 +1,4 @@
module github.com/grafana/grafana/apps/investigation
module github.com/grafana/grafana/apps/investigations
go 1.23.4
File renamed without changes.
+51
View File
@@ -0,0 +1,51 @@
package investigations
// Collectable represents an item collected during investigation
#Collectable: {
id: string
createdAt: string
title: string
origin: string
type: string
queries: [...#Query] // +listType=atomic
timeRange: #TimeRange
datasource: #DatasourceRef
url: string
logoPath?: string
note: string
noteUpdatedAt: string
}
#CollectableSummary: {
id: string
title: string
logoPath: string
origin: string
}
// Query represents a data query
#Query: {
refId: string
queryType: string
editorMode: string
supportingQueryType: string
legendFormat: string
expr: string
}
// TimeRange represents a time range with both absolute and relative values
#TimeRange: {
from: string
to: string
raw: {
from: string
to: string
}
}
// DatasourceRef is a reference to a datasource
#DatasourceRef: {
uid: string
}
@@ -0,0 +1 @@
module: "github.com/grafana/grafana/apps/investigations"
@@ -0,0 +1,64 @@
package investigations
// This is our Investigation definition, which contains metadata about the kind, and the kind's schema
investigation: {
kind: "Investigation"
group: "investigations.grafana.app"
apiResource: {
groupOverride: "investigations.grafana.app"
}
pluralName: "Investigations"
current: "v0alpha1"
versions: {
"v0alpha1": {
codegen: {
frontend: true
backend: true
options: {
generateObjectMeta: true
generateClient: true
k8sLike: true
package: "github.com/grafana/grafana/apps/investigations"
}
}
schema: {
// spec is the schema of our resource
spec: {
title: string
createdByProfile: #Person
hasCustomName: bool
isFavorite: bool
overviewNote: string
overviewNoteUpdatedAt: string
collectables: [...#Collectable] // +listType=atomic
viewMode: #ViewMode
}
}
}
}
}
// Type definition for investigation summaries
#InvestigationSummary: {
title: string
createdByProfile: #Person
hasCustomName: bool
isFavorite: bool
overviewNote: string
overviewNoteUpdatedAt: string
viewMode: #ViewMode
collectableSummaries: [...#CollectableSummary] // +listType=atomic
}
// Person represents a user profile with basic information
#Person: {
uid: string // Unique identifier for the user
name: string // Display name of the user
gravatarUrl: string // URL to user's Gravatar image
}
#ViewMode: {
mode: "compact" | "full"
showComments: bool
showTooltips: bool
}
@@ -0,0 +1,37 @@
package investigations
investigationIndex: {
kind: "InvestigationIndex"
group: "investigations.grafana.app"
apiResource: {
groupOverride: "investigations.grafana.app"
}
pluralName: "InvestigationIndexes"
current: "v0alpha1"
versions: {
"v0alpha1": {
codegen: {
frontend: true
backend: true
options: {
generateObjectMeta: true
generateClient: true
k8sLike: true
package: "github.com/grafana/grafana/apps/investigations"
}
}
schema: {
spec: {
// Title of the index, e.g. 'Favorites' or 'My Investigations'
title: string
// The Person who owns this investigation index
owner: #Person
// Array of investigation summaries
investigationSummaries: [...#InvestigationSummary] // +listType=atomic
}
}
}
}
}
+10
View File
@@ -0,0 +1,10 @@
package investigations
manifest: {
appName: "investigations"
groupOverride: "investigations.grafana.app"
kinds: [
investigation,
investigationIndex,
]
}
@@ -1,12 +1,12 @@
package v1alpha1
package v0alpha1
import "k8s.io/apimachinery/pkg/runtime/schema"
const (
// Group is the API group used by all kinds in this package
Group = "investigation.grafana.app"
Group = "investigations.grafana.app"
// Version is the API version used by all kinds in this package
Version = "v1alpha1"
Version = "v0alpha1"
)
var (
@@ -2,7 +2,7 @@
// Code generated by grafana-app-sdk. DO NOT EDIT.
//
package v1alpha1
package v0alpha1
import (
"encoding/json"
@@ -1,6 +1,6 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
package v1alpha1
package v0alpha1
import (
time "time"
@@ -2,7 +2,7 @@
// Code generated by grafana-app-sdk. DO NOT EDIT.
//
package v1alpha1
package v0alpha1
import (
"fmt"
@@ -2,7 +2,7 @@
// Code generated by grafana-app-sdk. DO NOT EDIT.
//
package v1alpha1
package v0alpha1
import (
"github.com/grafana/grafana-app-sdk/resource"
@@ -10,7 +10,7 @@ import (
// schema is unexported to prevent accidental overwrites
var (
schemaInvestigation = resource.NewSimpleSchema("investigation.grafana.app", "v1alpha1", &Investigation{}, &InvestigationList{}, resource.WithKind("Investigation"),
schemaInvestigation = resource.NewSimpleSchema("investigations.grafana.app", "v0alpha1", &Investigation{}, &InvestigationList{}, resource.WithKind("Investigation"),
resource.WithPlural("investigations"), resource.WithScope(resource.NamespacedScope))
kindInvestigation = resource.Kind{
Schema: schemaInvestigation,
@@ -0,0 +1,140 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
package v0alpha1
// Person represents a user profile with basic information
// +k8s:openapi-gen=true
type InvestigationPerson struct {
// Unique identifier for the user
Uid string `json:"uid"`
// Display name of the user
Name string `json:"name"`
// URL to user's Gravatar image
GravatarUrl string `json:"gravatarUrl"`
}
// NewInvestigationPerson creates a new InvestigationPerson object.
func NewInvestigationPerson() *InvestigationPerson {
return &InvestigationPerson{}
}
// Collectable represents an item collected during investigation
// +k8s:openapi-gen=true
type InvestigationCollectable struct {
Id string `json:"id"`
CreatedAt string `json:"createdAt"`
Title string `json:"title"`
Origin string `json:"origin"`
Type string `json:"type"`
// +listType=atomic
Queries []InvestigationQuery `json:"queries"`
TimeRange InvestigationTimeRange `json:"timeRange"`
Datasource InvestigationDatasourceRef `json:"datasource"`
Url string `json:"url"`
LogoPath *string `json:"logoPath,omitempty"`
Note string `json:"note"`
NoteUpdatedAt string `json:"noteUpdatedAt"`
}
// NewInvestigationCollectable creates a new InvestigationCollectable object.
func NewInvestigationCollectable() *InvestigationCollectable {
return &InvestigationCollectable{
TimeRange: *NewInvestigationTimeRange(),
Datasource: *NewInvestigationDatasourceRef(),
}
}
// Query represents a data query
// +k8s:openapi-gen=true
type InvestigationQuery struct {
RefId string `json:"refId"`
QueryType string `json:"queryType"`
EditorMode string `json:"editorMode"`
SupportingQueryType string `json:"supportingQueryType"`
LegendFormat string `json:"legendFormat"`
Expr string `json:"expr"`
}
// NewInvestigationQuery creates a new InvestigationQuery object.
func NewInvestigationQuery() *InvestigationQuery {
return &InvestigationQuery{}
}
// TimeRange represents a time range with both absolute and relative values
// +k8s:openapi-gen=true
type InvestigationTimeRange struct {
From string `json:"from"`
To string `json:"to"`
Raw InvestigationV0alpha1TimeRangeRaw `json:"raw"`
}
// NewInvestigationTimeRange creates a new InvestigationTimeRange object.
func NewInvestigationTimeRange() *InvestigationTimeRange {
return &InvestigationTimeRange{
Raw: *NewInvestigationV0alpha1TimeRangeRaw(),
}
}
// DatasourceRef is a reference to a datasource
// +k8s:openapi-gen=true
type InvestigationDatasourceRef struct {
Uid string `json:"uid"`
}
// NewInvestigationDatasourceRef creates a new InvestigationDatasourceRef object.
func NewInvestigationDatasourceRef() *InvestigationDatasourceRef {
return &InvestigationDatasourceRef{}
}
// +k8s:openapi-gen=true
type InvestigationViewMode struct {
Mode InvestigationViewModeMode `json:"mode"`
ShowComments bool `json:"showComments"`
ShowTooltips bool `json:"showTooltips"`
}
// NewInvestigationViewMode creates a new InvestigationViewMode object.
func NewInvestigationViewMode() *InvestigationViewMode {
return &InvestigationViewMode{}
}
// spec is the schema of our resource
// +k8s:openapi-gen=true
type InvestigationSpec struct {
Title string `json:"title"`
CreatedByProfile InvestigationPerson `json:"createdByProfile"`
HasCustomName bool `json:"hasCustomName"`
IsFavorite bool `json:"isFavorite"`
OverviewNote string `json:"overviewNote"`
OverviewNoteUpdatedAt string `json:"overviewNoteUpdatedAt"`
// +listType=atomic
Collectables []InvestigationCollectable `json:"collectables"`
ViewMode InvestigationViewMode `json:"viewMode"`
}
// NewInvestigationSpec creates a new InvestigationSpec object.
func NewInvestigationSpec() *InvestigationSpec {
return &InvestigationSpec{
CreatedByProfile: *NewInvestigationPerson(),
ViewMode: *NewInvestigationViewMode(),
}
}
// +k8s:openapi-gen=true
type InvestigationV0alpha1TimeRangeRaw struct {
From string `json:"from"`
To string `json:"to"`
}
// NewInvestigationV0alpha1TimeRangeRaw creates a new InvestigationV0alpha1TimeRangeRaw object.
func NewInvestigationV0alpha1TimeRangeRaw() *InvestigationV0alpha1TimeRangeRaw {
return &InvestigationV0alpha1TimeRangeRaw{}
}
// +k8s:openapi-gen=true
type InvestigationViewModeMode string
const (
InvestigationViewModeModeCompact InvestigationViewModeMode = "compact"
InvestigationViewModeModeFull InvestigationViewModeMode = "full"
)
@@ -1,6 +1,6 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
package v1alpha1
package v0alpha1
// +k8s:openapi-gen=true
type InvestigationstatusOperatorState struct {
@@ -0,0 +1,28 @@
//
// Code generated by grafana-app-sdk. DO NOT EDIT.
//
package v0alpha1
import (
"encoding/json"
"io"
"github.com/grafana/grafana-app-sdk/resource"
)
// InvestigationIndexJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding
type InvestigationIndexJSONCodec struct{}
// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into`
func (*InvestigationIndexJSONCodec) Read(reader io.Reader, into resource.Object) error {
return json.NewDecoder(reader).Decode(into)
}
// Write writes JSON-encoded bytes into `writer` marshaled from `from`
func (*InvestigationIndexJSONCodec) Write(writer io.Writer, from resource.Object) error {
return json.NewEncoder(writer).Encode(from)
}
// Interface compliance checks
var _ resource.Codec = &InvestigationIndexJSONCodec{}
@@ -0,0 +1,28 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
package v0alpha1
import (
time "time"
)
// metadata contains embedded CommonMetadata and can be extended with custom string fields
// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
// without external reference as using the CommonMetadata reference breaks thema codegen.
type InvestigationIndexMetadata struct {
UpdateTimestamp time.Time `json:"updateTimestamp"`
CreatedBy string `json:"createdBy"`
Uid string `json:"uid"`
CreationTimestamp time.Time `json:"creationTimestamp"`
DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"`
Finalizers []string `json:"finalizers"`
ResourceVersion string `json:"resourceVersion"`
Generation int64 `json:"generation"`
UpdatedBy string `json:"updatedBy"`
Labels map[string]string `json:"labels"`
}
// NewInvestigationIndexMetadata creates a new InvestigationIndexMetadata object.
func NewInvestigationIndexMetadata() *InvestigationIndexMetadata {
return &InvestigationIndexMetadata{}
}
@@ -0,0 +1,266 @@
//
// Code generated by grafana-app-sdk. DO NOT EDIT.
//
package v0alpha1
import (
"fmt"
"github.com/grafana/grafana-app-sdk/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"time"
)
// +k8s:openapi-gen=true
type InvestigationIndex struct {
metav1.TypeMeta `json:",inline" yaml:",inline"`
metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
Spec InvestigationIndexSpec `json:"spec" yaml:"spec"`
InvestigationIndexStatus InvestigationIndexStatus `json:"status" yaml:"status"`
}
func (o *InvestigationIndex) GetSpec() any {
return o.Spec
}
func (o *InvestigationIndex) SetSpec(spec any) error {
cast, ok := spec.(InvestigationIndexSpec)
if !ok {
return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec)
}
o.Spec = cast
return nil
}
func (o *InvestigationIndex) GetSubresources() map[string]any {
return map[string]any{
"status": o.InvestigationIndexStatus,
}
}
func (o *InvestigationIndex) GetSubresource(name string) (any, bool) {
switch name {
case "status":
return o.InvestigationIndexStatus, true
default:
return nil, false
}
}
func (o *InvestigationIndex) SetSubresource(name string, value any) error {
switch name {
case "status":
cast, ok := value.(InvestigationIndexStatus)
if !ok {
return fmt.Errorf("cannot set status type %#v, not of type InvestigationIndexStatus", value)
}
o.InvestigationIndexStatus = cast
return nil
default:
return fmt.Errorf("subresource '%s' does not exist", name)
}
}
func (o *InvestigationIndex) GetStaticMetadata() resource.StaticMetadata {
gvk := o.GroupVersionKind()
return resource.StaticMetadata{
Name: o.ObjectMeta.Name,
Namespace: o.ObjectMeta.Namespace,
Group: gvk.Group,
Version: gvk.Version,
Kind: gvk.Kind,
}
}
func (o *InvestigationIndex) SetStaticMetadata(metadata resource.StaticMetadata) {
o.Name = metadata.Name
o.Namespace = metadata.Namespace
o.SetGroupVersionKind(schema.GroupVersionKind{
Group: metadata.Group,
Version: metadata.Version,
Kind: metadata.Kind,
})
}
func (o *InvestigationIndex) GetCommonMetadata() resource.CommonMetadata {
dt := o.DeletionTimestamp
var deletionTimestamp *time.Time
if dt != nil {
deletionTimestamp = &dt.Time
}
// Legacy ExtraFields support
extraFields := make(map[string]any)
if o.Annotations != nil {
extraFields["annotations"] = o.Annotations
}
if o.ManagedFields != nil {
extraFields["managedFields"] = o.ManagedFields
}
if o.OwnerReferences != nil {
extraFields["ownerReferences"] = o.OwnerReferences
}
return resource.CommonMetadata{
UID: string(o.UID),
ResourceVersion: o.ResourceVersion,
Generation: o.Generation,
Labels: o.Labels,
CreationTimestamp: o.CreationTimestamp.Time,
DeletionTimestamp: deletionTimestamp,
Finalizers: o.Finalizers,
UpdateTimestamp: o.GetUpdateTimestamp(),
CreatedBy: o.GetCreatedBy(),
UpdatedBy: o.GetUpdatedBy(),
ExtraFields: extraFields,
}
}
func (o *InvestigationIndex) SetCommonMetadata(metadata resource.CommonMetadata) {
o.UID = types.UID(metadata.UID)
o.ResourceVersion = metadata.ResourceVersion
o.Generation = metadata.Generation
o.Labels = metadata.Labels
o.CreationTimestamp = metav1.NewTime(metadata.CreationTimestamp)
if metadata.DeletionTimestamp != nil {
dt := metav1.NewTime(*metadata.DeletionTimestamp)
o.DeletionTimestamp = &dt
} else {
o.DeletionTimestamp = nil
}
o.Finalizers = metadata.Finalizers
if o.Annotations == nil {
o.Annotations = make(map[string]string)
}
if !metadata.UpdateTimestamp.IsZero() {
o.SetUpdateTimestamp(metadata.UpdateTimestamp)
}
if metadata.CreatedBy != "" {
o.SetCreatedBy(metadata.CreatedBy)
}
if metadata.UpdatedBy != "" {
o.SetUpdatedBy(metadata.UpdatedBy)
}
// Legacy support for setting Annotations, ManagedFields, and OwnerReferences via ExtraFields
if metadata.ExtraFields != nil {
if annotations, ok := metadata.ExtraFields["annotations"]; ok {
if cast, ok := annotations.(map[string]string); ok {
o.Annotations = cast
}
}
if managedFields, ok := metadata.ExtraFields["managedFields"]; ok {
if cast, ok := managedFields.([]metav1.ManagedFieldsEntry); ok {
o.ManagedFields = cast
}
}
if ownerReferences, ok := metadata.ExtraFields["ownerReferences"]; ok {
if cast, ok := ownerReferences.([]metav1.OwnerReference); ok {
o.OwnerReferences = cast
}
}
}
}
func (o *InvestigationIndex) GetCreatedBy() string {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
return o.ObjectMeta.Annotations["grafana.com/createdBy"]
}
func (o *InvestigationIndex) SetCreatedBy(createdBy string) {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy
}
func (o *InvestigationIndex) GetUpdateTimestamp() time.Time {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"])
return parsed
}
func (o *InvestigationIndex) SetUpdateTimestamp(updateTimestamp time.Time) {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339)
}
func (o *InvestigationIndex) GetUpdatedBy() string {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
return o.ObjectMeta.Annotations["grafana.com/updatedBy"]
}
func (o *InvestigationIndex) SetUpdatedBy(updatedBy string) {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy
}
func (o *InvestigationIndex) Copy() resource.Object {
return resource.CopyObject(o)
}
func (o *InvestigationIndex) DeepCopyObject() runtime.Object {
return o.Copy()
}
// Interface compliance compile-time check
var _ resource.Object = &InvestigationIndex{}
// +k8s:openapi-gen=true
type InvestigationIndexList struct {
metav1.TypeMeta `json:",inline" yaml:",inline"`
metav1.ListMeta `json:"metadata" yaml:"metadata"`
Items []InvestigationIndex `json:"items" yaml:"items"`
}
func (o *InvestigationIndexList) DeepCopyObject() runtime.Object {
return o.Copy()
}
func (o *InvestigationIndexList) Copy() resource.ListObject {
cpy := &InvestigationIndexList{
TypeMeta: o.TypeMeta,
Items: make([]InvestigationIndex, len(o.Items)),
}
o.ListMeta.DeepCopyInto(&cpy.ListMeta)
for i := 0; i < len(o.Items); i++ {
if item, ok := o.Items[i].Copy().(*InvestigationIndex); ok {
cpy.Items[i] = *item
}
}
return cpy
}
func (o *InvestigationIndexList) GetItems() []resource.Object {
items := make([]resource.Object, len(o.Items))
for i := 0; i < len(o.Items); i++ {
items[i] = &o.Items[i]
}
return items
}
func (o *InvestigationIndexList) SetItems(items []resource.Object) {
o.Items = make([]InvestigationIndex, len(items))
for i := 0; i < len(items); i++ {
o.Items[i] = *items[i].(*InvestigationIndex)
}
}
// Interface compliance compile-time check
var _ resource.ListObject = &InvestigationIndexList{}
@@ -0,0 +1,34 @@
//
// Code generated by grafana-app-sdk. DO NOT EDIT.
//
package v0alpha1
import (
"github.com/grafana/grafana-app-sdk/resource"
)
// schema is unexported to prevent accidental overwrites
var (
schemaInvestigationIndex = resource.NewSimpleSchema("investigations.grafana.app", "v0alpha1", &InvestigationIndex{}, &InvestigationIndexList{}, resource.WithKind("InvestigationIndex"),
resource.WithPlural("investigationindexes"), resource.WithScope(resource.NamespacedScope))
kindInvestigationIndex = resource.Kind{
Schema: schemaInvestigationIndex,
Codecs: map[resource.KindEncoding]resource.Codec{
resource.KindEncodingJSON: &InvestigationIndexJSONCodec{},
},
}
)
// Kind returns a resource.Kind for this Schema with a JSON codec
func InvestigationIndexKind() resource.Kind {
return kindInvestigationIndex
}
// Schema returns a resource.SimpleSchema representation of InvestigationIndex
func InvestigationIndexSchema() *resource.SimpleSchema {
return schemaInvestigationIndex
}
// Interface compliance checks
var _ resource.Schema = kindInvestigationIndex
@@ -0,0 +1,92 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
package v0alpha1
// Person represents a user profile with basic information
// +k8s:openapi-gen=true
type InvestigationIndexPerson struct {
// Unique identifier for the user
Uid string `json:"uid"`
// Display name of the user
Name string `json:"name"`
// URL to user's Gravatar image
GravatarUrl string `json:"gravatarUrl"`
}
// NewInvestigationIndexPerson creates a new InvestigationIndexPerson object.
func NewInvestigationIndexPerson() *InvestigationIndexPerson {
return &InvestigationIndexPerson{}
}
// Type definition for investigation summaries
// +k8s:openapi-gen=true
type InvestigationIndexInvestigationSummary struct {
Title string `json:"title"`
CreatedByProfile InvestigationIndexPerson `json:"createdByProfile"`
HasCustomName bool `json:"hasCustomName"`
IsFavorite bool `json:"isFavorite"`
OverviewNote string `json:"overviewNote"`
OverviewNoteUpdatedAt string `json:"overviewNoteUpdatedAt"`
ViewMode InvestigationIndexViewMode `json:"viewMode"`
// +listType=atomic
CollectableSummaries []InvestigationIndexCollectableSummary `json:"collectableSummaries"`
}
// NewInvestigationIndexInvestigationSummary creates a new InvestigationIndexInvestigationSummary object.
func NewInvestigationIndexInvestigationSummary() *InvestigationIndexInvestigationSummary {
return &InvestigationIndexInvestigationSummary{
CreatedByProfile: *NewInvestigationIndexPerson(),
ViewMode: *NewInvestigationIndexViewMode(),
}
}
// +k8s:openapi-gen=true
type InvestigationIndexViewMode struct {
Mode InvestigationIndexViewModeMode `json:"mode"`
ShowComments bool `json:"showComments"`
ShowTooltips bool `json:"showTooltips"`
}
// NewInvestigationIndexViewMode creates a new InvestigationIndexViewMode object.
func NewInvestigationIndexViewMode() *InvestigationIndexViewMode {
return &InvestigationIndexViewMode{}
}
// +k8s:openapi-gen=true
type InvestigationIndexCollectableSummary struct {
Id string `json:"id"`
Title string `json:"title"`
LogoPath string `json:"logoPath"`
Origin string `json:"origin"`
}
// NewInvestigationIndexCollectableSummary creates a new InvestigationIndexCollectableSummary object.
func NewInvestigationIndexCollectableSummary() *InvestigationIndexCollectableSummary {
return &InvestigationIndexCollectableSummary{}
}
// +k8s:openapi-gen=true
type InvestigationIndexSpec struct {
// Title of the index, e.g. 'Favorites' or 'My Investigations'
Title string `json:"title"`
// The Person who owns this investigation index
Owner InvestigationIndexPerson `json:"owner"`
// Array of investigation summaries
// +listType=atomic
InvestigationSummaries []InvestigationIndexInvestigationSummary `json:"investigationSummaries"`
}
// NewInvestigationIndexSpec creates a new InvestigationIndexSpec object.
func NewInvestigationIndexSpec() *InvestigationIndexSpec {
return &InvestigationIndexSpec{
Owner: *NewInvestigationIndexPerson(),
}
}
// +k8s:openapi-gen=true
type InvestigationIndexViewModeMode string
const (
InvestigationIndexViewModeModeCompact InvestigationIndexViewModeMode = "compact"
InvestigationIndexViewModeModeFull InvestigationIndexViewModeMode = "full"
)
@@ -0,0 +1,44 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
package v0alpha1
// +k8s:openapi-gen=true
type InvestigationIndexstatusOperatorState struct {
// lastEvaluation is the ResourceVersion last evaluated
LastEvaluation string `json:"lastEvaluation"`
// state describes the state of the lastEvaluation.
// It is limited to three possible states for machine evaluation.
State InvestigationIndexStatusOperatorStateState `json:"state"`
// descriptiveState is an optional more descriptive state field which has no requirements on format
DescriptiveState *string `json:"descriptiveState,omitempty"`
// details contains any extra information that is operator-specific
Details map[string]interface{} `json:"details,omitempty"`
}
// NewInvestigationIndexstatusOperatorState creates a new InvestigationIndexstatusOperatorState object.
func NewInvestigationIndexstatusOperatorState() *InvestigationIndexstatusOperatorState {
return &InvestigationIndexstatusOperatorState{}
}
// +k8s:openapi-gen=true
type InvestigationIndexStatus struct {
// operatorStates is a map of operator ID to operator state evaluations.
// Any operator which consumes this kind SHOULD add its state evaluation information to this field.
OperatorStates map[string]InvestigationIndexstatusOperatorState `json:"operatorStates,omitempty"`
// additionalFields is reserved for future use
AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"`
}
// NewInvestigationIndexStatus creates a new InvestigationIndexStatus object.
func NewInvestigationIndexStatus() *InvestigationIndexStatus {
return &InvestigationIndexStatus{}
}
// +k8s:openapi-gen=true
type InvestigationIndexStatusOperatorStateState string
const (
InvestigationIndexStatusOperatorStateStateSuccess InvestigationIndexStatusOperatorStateState = "success"
InvestigationIndexStatusOperatorStateStateInProgress InvestigationIndexStatusOperatorStateState = "in_progress"
InvestigationIndexStatusOperatorStateStateFailed InvestigationIndexStatusOperatorStateState = "failed"
)
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,65 @@
//
// This file is generated by grafana-app-sdk
// DO NOT EDIT
//
package apis
import (
"encoding/json"
"github.com/grafana/grafana-app-sdk/app"
)
var (
rawSchemaInvestigationv0alpha1 = []byte(`{"spec":{"description":"spec is the schema of our resource","properties":{"collectables":{"items":{"properties":{"createdAt":{"type":"string"},"datasource":{"properties":{"uid":{"type":"string"}},"required":["uid"],"type":"object"},"id":{"type":"string"},"logoPath":{"type":"string"},"note":{"type":"string"},"noteUpdatedAt":{"type":"string"},"origin":{"type":"string"},"queries":{"items":{"properties":{"editorMode":{"type":"string"},"expr":{"type":"string"},"legendFormat":{"type":"string"},"queryType":{"type":"string"},"refId":{"type":"string"},"supportingQueryType":{"type":"string"}},"required":["refId","queryType","editorMode","supportingQueryType","legendFormat","expr"],"type":"object"},"type":"array"},"timeRange":{"properties":{"from":{"type":"string"},"raw":{"properties":{"from":{"type":"string"},"to":{"type":"string"}},"required":["from","to"],"type":"object"},"to":{"type":"string"}},"required":["from","to","raw"],"type":"object"},"title":{"type":"string"},"type":{"type":"string"},"url":{"type":"string"}},"required":["id","createdAt","title","origin","type","queries","timeRange","datasource","url","note","noteUpdatedAt"],"type":"object"},"type":"array"},"createdByProfile":{"properties":{"gravatarUrl":{"type":"string"},"name":{"type":"string"},"uid":{"type":"string"}},"required":["uid","name","gravatarUrl"],"type":"object"},"hasCustomName":{"type":"boolean"},"isFavorite":{"type":"boolean"},"overviewNote":{"type":"string"},"overviewNoteUpdatedAt":{"type":"string"},"title":{"type":"string"},"viewMode":{"properties":{"mode":{"enum":["compact","full"],"type":"string"},"showComments":{"type":"boolean"},"showTooltips":{"type":"boolean"}},"required":["mode","showComments","showTooltips"],"type":"object"}},"required":["title","createdByProfile","hasCustomName","isFavorite","overviewNote","overviewNoteUpdatedAt","collectables","viewMode"],"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"operatorStates":{"additionalProperties":{"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"description":"details contains any extra information that is operator-specific","type":"object","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object","x-kubernetes-preserve-unknown-fields":true}}`)
versionSchemaInvestigationv0alpha1 app.VersionSchema
_ = json.Unmarshal(rawSchemaInvestigationv0alpha1, &versionSchemaInvestigationv0alpha1)
rawSchemaInvestigationIndexv0alpha1 = []byte(`{"spec":{"properties":{"investigationSummaries":{"description":"Array of investigation summaries","items":{"properties":{"collectableSummaries":{"items":{"properties":{"id":{"type":"string"},"logoPath":{"type":"string"},"origin":{"type":"string"},"title":{"type":"string"}},"required":["id","title","logoPath","origin"],"type":"object"},"type":"array"},"createdByProfile":{"properties":{"gravatarUrl":{"type":"string"},"name":{"type":"string"},"uid":{"type":"string"}},"required":["uid","name","gravatarUrl"],"type":"object"},"hasCustomName":{"type":"boolean"},"isFavorite":{"type":"boolean"},"overviewNote":{"type":"string"},"overviewNoteUpdatedAt":{"type":"string"},"title":{"type":"string"},"viewMode":{"properties":{"mode":{"enum":["compact","full"],"type":"string"},"showComments":{"type":"boolean"},"showTooltips":{"type":"boolean"}},"required":["mode","showComments","showTooltips"],"type":"object"}},"required":["title","createdByProfile","hasCustomName","isFavorite","overviewNote","overviewNoteUpdatedAt","viewMode","collectableSummaries"],"type":"object"},"type":"array"},"owner":{"description":"The Person who owns this investigation index","properties":{"gravatarUrl":{"type":"string"},"name":{"type":"string"},"uid":{"type":"string"}},"required":["uid","name","gravatarUrl"],"type":"object"},"title":{"description":"Title of the index, e.g. 'Favorites' or 'My Investigations'","type":"string"}},"required":["title","owner","investigationSummaries"],"type":"object"},"status":{"properties":{"additionalFields":{"description":"additionalFields is reserved for future use","type":"object","x-kubernetes-preserve-unknown-fields":true},"operatorStates":{"additionalProperties":{"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"description":"details contains any extra information that is operator-specific","type":"object","x-kubernetes-preserve-unknown-fields":true},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object","x-kubernetes-preserve-unknown-fields":true}}`)
versionSchemaInvestigationIndexv0alpha1 app.VersionSchema
_ = json.Unmarshal(rawSchemaInvestigationIndexv0alpha1, &versionSchemaInvestigationIndexv0alpha1)
)
var appManifestData = app.ManifestData{
AppName: "investigations",
Group: "investigations.grafana.app",
Kinds: []app.ManifestKind{
{
Kind: "Investigation",
Scope: "Namespaced",
Conversion: false,
Versions: []app.ManifestKindVersion{
{
Name: "v0alpha1",
Schema: &versionSchemaInvestigationv0alpha1,
},
},
},
{
Kind: "InvestigationIndex",
Scope: "Namespaced",
Conversion: false,
Versions: []app.ManifestKindVersion{
{
Name: "v0alpha1",
Schema: &versionSchemaInvestigationIndexv0alpha1,
},
},
},
},
}
func jsonToMap(j string) map[string]any {
m := make(map[string]any)
json.Unmarshal([]byte(j), &j)
return m
}
func LocalManifest() app.Manifest {
return app.NewEmbeddedManifest(appManifestData)
}
func RemoteManifest() app.Manifest {
return app.NewAPIServerManifest("investigations")
}
@@ -9,7 +9,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/klog/v2"
investigationv1alpha1 "github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1"
investigationsv0alpha1 "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1"
)
func New(cfg app.Config) (app.App, error) {
@@ -24,7 +24,10 @@ func New(cfg app.Config) (app.App, error) {
},
ManagedKinds: []simple.AppManagedKind{
{
Kind: investigationv1alpha1.InvestigationKind(),
Kind: investigationsv0alpha1.InvestigationKind(),
},
{
Kind: investigationsv0alpha1.InvestigationIndexKind(),
},
},
}
@@ -44,10 +47,13 @@ func New(cfg app.Config) (app.App, error) {
func GetKinds() map[schema.GroupVersion][]resource.Kind {
gv := schema.GroupVersion{
Group: investigationv1alpha1.InvestigationKind().Group(),
Version: investigationv1alpha1.InvestigationKind().Version(),
Group: investigationsv0alpha1.InvestigationKind().Group(),
Version: investigationsv0alpha1.InvestigationKind().Version(),
}
return map[schema.GroupVersion][]resource.Kind{
gv: {investigationv1alpha1.InvestigationKind()},
gv: {
investigationsv0alpha1.InvestigationKind(),
investigationsv0alpha1.InvestigationIndexKind(),
},
}
}
@@ -0,0 +1,49 @@
/*
* This file was generated by grafana-app-sdk. DO NOT EDIT.
*/
import { Spec } from './types.spec.gen';
import { Status } from './types.status.gen';
export interface Metadata {
name: string;
namespace: string;
generateName?: string;
selfLink?: string;
uid?: string;
resourceVersion?: string;
generation?: number;
creationTimestamp?: string;
deletionTimestamp?: string;
deletionGracePeriodSeconds?: number;
labels?: Record<string, string>;
annotations?: Record<string, string>;
ownerReferences?: OwnerReference[];
finalizers?: string[];
managedFields?: ManagedFieldsEntry[];
}
export interface OwnerReference {
apiVersion: string;
kind: string;
name: string;
uid: string;
controller?: boolean;
blockOwnerDeletion?: boolean;
}
export interface ManagedFieldsEntry {
manager?: string;
operation?: string;
apiVersion?: string;
time?: string;
fieldsType?: string;
subresource?: string;
}
export interface Investigation {
kind: string;
apiVersion: string;
metadata: Metadata;
spec: Spec;
status: Status;
}
@@ -0,0 +1,30 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
// metadata contains embedded CommonMetadata and can be extended with custom string fields
// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
// without external reference as using the CommonMetadata reference breaks thema codegen.
export interface Metadata {
updateTimestamp: string;
createdBy: string;
uid: string;
creationTimestamp: string;
deletionTimestamp?: string;
finalizers: string[];
resourceVersion: string;
generation: number;
updatedBy: string;
labels: Record<string, string>;
}
export const defaultMetadata = (): Metadata => ({
updateTimestamp: "",
createdBy: "",
uid: "",
creationTimestamp: "",
finalizers: [],
resourceVersion: "",
generation: 0,
updatedBy: "",
labels: {},
});
@@ -0,0 +1,132 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
// Person represents a user profile with basic information
export interface Person {
// Unique identifier for the user
uid: string;
// Display name of the user
name: string;
// URL to user's Gravatar image
gravatarUrl: string;
}
export const defaultPerson = (): Person => ({
uid: "",
name: "",
gravatarUrl: "",
});
// Collectable represents an item collected during investigation
export interface Collectable {
id: string;
createdAt: string;
title: string;
origin: string;
type: string;
// +listType=atomic
queries: Query[];
timeRange: TimeRange;
datasource: DatasourceRef;
url: string;
logoPath?: string;
note: string;
noteUpdatedAt: string;
}
export const defaultCollectable = (): Collectable => ({
id: "",
createdAt: "",
title: "",
origin: "",
type: "",
queries: [],
timeRange: defaultTimeRange(),
datasource: defaultDatasourceRef(),
url: "",
note: "",
noteUpdatedAt: "",
});
// Query represents a data query
export interface Query {
refId: string;
queryType: string;
editorMode: string;
supportingQueryType: string;
legendFormat: string;
expr: string;
}
export const defaultQuery = (): Query => ({
refId: "",
queryType: "",
editorMode: "",
supportingQueryType: "",
legendFormat: "",
expr: "",
});
// TimeRange represents a time range with both absolute and relative values
export interface TimeRange {
from: string;
to: string;
raw: {
from: string;
to: string;
};
}
export const defaultTimeRange = (): TimeRange => ({
from: "",
to: "",
raw: {
from: "",
to: "",
},
});
// DatasourceRef is a reference to a datasource
export interface DatasourceRef {
uid: string;
}
export const defaultDatasourceRef = (): DatasourceRef => ({
uid: "",
});
export interface ViewMode {
mode: "compact" | "full";
showComments: boolean;
showTooltips: boolean;
}
export const defaultViewMode = (): ViewMode => ({
mode: "compact",
showComments: false,
showTooltips: false,
});
// spec is the schema of our resource
export interface Spec {
title: string;
createdByProfile: Person;
hasCustomName: boolean;
isFavorite: boolean;
overviewNote: string;
overviewNoteUpdatedAt: string;
// +listType=atomic
collectables: Collectable[];
viewMode: ViewMode;
}
export const defaultSpec = (): Spec => ({
title: "",
createdByProfile: defaultPerson(),
hasCustomName: false,
isFavorite: false,
overviewNote: "",
overviewNoteUpdatedAt: "",
collectables: [],
viewMode: defaultViewMode(),
});
@@ -0,0 +1,30 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
export interface OperatorState {
// lastEvaluation is the ResourceVersion last evaluated
lastEvaluation: string;
// state describes the state of the lastEvaluation.
// It is limited to three possible states for machine evaluation.
state: "success" | "in_progress" | "failed";
// descriptiveState is an optional more descriptive state field which has no requirements on format
descriptiveState?: string;
// details contains any extra information that is operator-specific
details?: Record<string, any>;
}
export const defaultOperatorState = (): OperatorState => ({
lastEvaluation: "",
state: "success",
});
export interface Status {
// operatorStates is a map of operator ID to operator state evaluations.
// Any operator which consumes this kind SHOULD add its state evaluation information to this field.
operatorStates?: Record<string, OperatorState>;
// additionalFields is reserved for future use
additionalFields?: Record<string, any>;
}
export const defaultStatus = (): Status => ({
});
@@ -0,0 +1,49 @@
/*
* This file was generated by grafana-app-sdk. DO NOT EDIT.
*/
import { Spec } from './types.spec.gen';
import { Status } from './types.status.gen';
export interface Metadata {
name: string;
namespace: string;
generateName?: string;
selfLink?: string;
uid?: string;
resourceVersion?: string;
generation?: number;
creationTimestamp?: string;
deletionTimestamp?: string;
deletionGracePeriodSeconds?: number;
labels?: Record<string, string>;
annotations?: Record<string, string>;
ownerReferences?: OwnerReference[];
finalizers?: string[];
managedFields?: ManagedFieldsEntry[];
}
export interface OwnerReference {
apiVersion: string;
kind: string;
name: string;
uid: string;
controller?: boolean;
blockOwnerDeletion?: boolean;
}
export interface ManagedFieldsEntry {
manager?: string;
operation?: string;
apiVersion?: string;
time?: string;
fieldsType?: string;
subresource?: string;
}
export interface InvestigationIndex {
kind: string;
apiVersion: string;
metadata: Metadata;
spec: Spec;
status: Status;
}
@@ -0,0 +1,30 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
// metadata contains embedded CommonMetadata and can be extended with custom string fields
// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
// without external reference as using the CommonMetadata reference breaks thema codegen.
export interface Metadata {
updateTimestamp: string;
createdBy: string;
uid: string;
creationTimestamp: string;
deletionTimestamp?: string;
finalizers: string[];
resourceVersion: string;
generation: number;
updatedBy: string;
labels: Record<string, string>;
}
export const defaultMetadata = (): Metadata => ({
updateTimestamp: "",
createdBy: "",
uid: "",
creationTimestamp: "",
finalizers: [],
resourceVersion: "",
generation: 0,
updatedBy: "",
labels: {},
});
@@ -0,0 +1,84 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
// Person represents a user profile with basic information
export interface Person {
// Unique identifier for the user
uid: string;
// Display name of the user
name: string;
// URL to user's Gravatar image
gravatarUrl: string;
}
export const defaultPerson = (): Person => ({
uid: "",
name: "",
gravatarUrl: "",
});
// Type definition for investigation summaries
export interface InvestigationSummary {
title: string;
createdByProfile: Person;
hasCustomName: boolean;
isFavorite: boolean;
overviewNote: string;
overviewNoteUpdatedAt: string;
viewMode: ViewMode;
// +listType=atomic
collectableSummaries: CollectableSummary[];
}
export const defaultInvestigationSummary = (): InvestigationSummary => ({
title: "",
createdByProfile: defaultPerson(),
hasCustomName: false,
isFavorite: false,
overviewNote: "",
overviewNoteUpdatedAt: "",
viewMode: defaultViewMode(),
collectableSummaries: [],
});
export interface ViewMode {
mode: "compact" | "full";
showComments: boolean;
showTooltips: boolean;
}
export const defaultViewMode = (): ViewMode => ({
mode: "compact",
showComments: false,
showTooltips: false,
});
export interface CollectableSummary {
id: string;
title: string;
logoPath: string;
origin: string;
}
export const defaultCollectableSummary = (): CollectableSummary => ({
id: "",
title: "",
logoPath: "",
origin: "",
});
export interface Spec {
// Title of the index, e.g. 'Favorites' or 'My Investigations'
title: string;
// The Person who owns this investigation index
owner: Person;
// Array of investigation summaries
// +listType=atomic
investigationSummaries: InvestigationSummary[];
}
export const defaultSpec = (): Spec => ({
title: "",
owner: defaultPerson(),
investigationSummaries: [],
});
@@ -0,0 +1,30 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
export interface OperatorState {
// lastEvaluation is the ResourceVersion last evaluated
lastEvaluation: string;
// state describes the state of the lastEvaluation.
// It is limited to three possible states for machine evaluation.
state: "success" | "in_progress" | "failed";
// descriptiveState is an optional more descriptive state field which has no requirements on format
descriptiveState?: string;
// details contains any extra information that is operator-specific
details?: Record<string, any>;
}
export const defaultOperatorState = (): OperatorState => ({
lastEvaluation: "",
state: "success",
});
export interface Status {
// operatorStates is a map of operator ID to operator state evaluations.
// Any operator which consumes this kind SHOULD add its state evaluation information to this field.
operatorStates?: Record<string, OperatorState>;
// additionalFields is reserved for future use
additionalFields?: Record<string, any>;
}
export const defaultStatus = (): Status => ({
});
@@ -46,11 +46,9 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
| `alertingInsights` | Show the new alerting insights landing page | Yes |
| `panelMonitoring` | Enables panel monitoring through logs and measurements | Yes |
| `formatString` | Enable format string transformer | Yes |
| `transformationsVariableSupport` | Allows using variables in transformations | Yes |
| `kubernetesPlaylists` | Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s | Yes |
| `recoveryThreshold` | Enables feature recovery threshold (aka hysteresis) for threshold server-side expression | Yes |
| `lokiStructuredMetadata` | Enables the loki data source to request structured metadata from the Loki server | Yes |
| `managedPluginsInstall` | Install managed plugins directly from plugins catalog | Yes |
| `addFieldFromCalculationStatFunctions` | Add cumulative and window functions to the add field from calculation transformation | Yes |
| `annotationPermissionUpdate` | Change the way annotation permissions work by scoping them to folders and dashboards. | Yes |
| `dashboardSceneForViewers` | Enables dashboard rendering using Scenes for viewer roles | Yes |
View File
Whitespace-only changes.
+5 -7
View File
@@ -75,7 +75,7 @@ require (
github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group
github.com/gorilla/websocket v1.5.3 // @grafana/grafana-app-platform-squad
github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038 // @grafana/alerting-backend
github.com/grafana/authlib v0.0.0-20250206063954-bf4600a17569 // @grafana/identity-access-team
github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc // @grafana/identity-access-team
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c // @grafana/identity-access-team
github.com/grafana/dashboard-linter v0.0.0-20241106223805-1e7999311752 // @grafana/grafana-app-platform-squad
github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics
@@ -207,7 +207,7 @@ require (
require (
github.com/grafana/grafana/apps/advisor v0.0.0-20250123151950-b066a6313173 // @grafana/plugins-platform-backend
github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250121113133-e747350fee2d // @grafana/alerting-backend
github.com/grafana/grafana/apps/investigation v0.0.0-20250121113133-e747350fee2d // @fcjack @matryer
github.com/grafana/grafana/apps/investigations v0.0.0-20250217190546-9df6412e9255 // @fcjack @matryer
github.com/grafana/grafana/apps/playlist v0.0.0-20250121113133-e747350fee2d // @grafana/grafana-app-platform-squad
github.com/grafana/grafana/pkg/aggregator v0.0.0-20250121113133-e747350fee2d // @grafana/grafana-app-platform-squad
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250121113133-e747350fee2d // @grafana/grafana-app-platform-squad
@@ -450,6 +450,8 @@ require (
github.com/natefinch/wrap v0.2.0 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/oapi-codegen/runtime v1.0.0 // indirect
github.com/oasdiff/yaml v0.0.0-20241210131133-6b86fb107d80 // indirect
github.com/oasdiff/yaml3 v0.0.0-20241210130736-a94c01f36349 // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/oklog/ulid/v2 v2.1.0 // indirect
@@ -560,11 +562,7 @@ require (
sigs.k8s.io/yaml v1.4.0 // indirect
)
require (
github.com/google/go-github/v64 v64.0.0 // indirect
github.com/oasdiff/yaml v0.0.0-20241210131133-6b86fb107d80 // indirect
github.com/oasdiff/yaml3 v0.0.0-20241210130736-a94c01f36349 // indirect
)
require github.com/google/go-github/v64 v64.0.0 // indirect
// Use fork of crewjam/saml with fixes for some issues until changes get merged into upstream
replace github.com/crewjam/saml => github.com/grafana/saml v0.4.15-0.20240917091248-ae3bbdad8a56
+4 -4
View File
@@ -1530,8 +1530,8 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038 h1:dG/UKAjY/KlKp9fY8aEm+gSQHHRmPm5q+9cea3hRSu8=
github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU=
github.com/grafana/authlib v0.0.0-20250206063954-bf4600a17569 h1:bQw6fdcxVdZ6xmZVhMtRgGEKIT1Zc6y+i1PWF8tMX4Q=
github.com/grafana/authlib v0.0.0-20250206063954-bf4600a17569/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8=
github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc h1:qx8avmRi3wx+SuAn6UV0pKma1fJ0/4POUYNc+2rxZkg=
github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc/go.mod h1:HamgITzYqErFp8qLwYM/JwwE3z6fXft0Z+5n0Dx1rUM=
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo=
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM=
github.com/grafana/dashboard-linter v0.0.0-20241106223805-1e7999311752 h1:pIEPQPua6kCkMib/nOn//WLgtiA4urf2RPBViSF6CcE=
@@ -1572,8 +1572,8 @@ github.com/grafana/grafana/apps/advisor v0.0.0-20250123151950-b066a6313173 h1:uO
github.com/grafana/grafana/apps/advisor v0.0.0-20250123151950-b066a6313173/go.mod h1:goSDiy3jtC2cp8wjpPZdUHRENcoSUHae1/Px/MDfddA=
github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250121113133-e747350fee2d h1:NRVOtiG1aUwOazBj9KM7X2o2shsM6TchqisezzoH1gw=
github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250121113133-e747350fee2d/go.mod h1:AvleS6icyPmcBjihtx5jYEvdzLmHGBp66NuE0AMR57A=
github.com/grafana/grafana/apps/investigation v0.0.0-20250121113133-e747350fee2d h1:oNc/aDfDucQxLbRZK25yz3Cwc+dGo1C0Xmm2LaliWUQ=
github.com/grafana/grafana/apps/investigation v0.0.0-20250121113133-e747350fee2d/go.mod h1:HQprw3MmiYj5OUV9CZnkwA1FKDZBmYACuAB3oDvUOmI=
github.com/grafana/grafana/apps/investigations v0.0.0-20250217190546-9df6412e9255 h1:4bEH/v1cbODIie8rMAqrsUlXeWLu7ht9SXNIrdvMWmA=
github.com/grafana/grafana/apps/investigations v0.0.0-20250217190546-9df6412e9255/go.mod h1:ygFcJP2McdSeMJVj/3YrKafZMc/lZBsp54HO51MtJYw=
github.com/grafana/grafana/apps/playlist v0.0.0-20250121113133-e747350fee2d h1:cJ+9jgHw8Z17rM+DCdxvyWxPqH9OB9fkPLbnWEO+QGg=
github.com/grafana/grafana/apps/playlist v0.0.0-20250121113133-e747350fee2d/go.mod h1:DjJe5osrW/BKrzN9hAAOSElNWutj1bcriExa7iDP7kA=
github.com/grafana/grafana/pkg/aggregator v0.0.0-20250121113133-e747350fee2d h1:aBD5kzsIAh50vjNqUkWK9mNpLGIBYAnKkWtUepGNAiQ=
+1 -1
View File
@@ -7,7 +7,7 @@ use (
. // skip:golangci-lint
./apps/advisor
./apps/alerting/notifications
./apps/investigation
./apps/investigations
./apps/playlist
./pkg/aggregator
./pkg/apimachinery
-1644
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -195,7 +195,7 @@
"html-loader": "5.1.0",
"html-webpack-plugin": "5.6.3",
"http-server": "14.1.1",
"i18next-parser": "9.1.0",
"i18next-parser": "9.3.0",
"ini": "^5.0.0",
"jest": "29.7.0",
"jest-canvas-mock": "2.5.2",
+1
View File
@@ -548,6 +548,7 @@ export {
type PluginExtension,
type PluginExtensionLink,
type PluginExtensionComponent,
type PluginExtensionComponentMeta,
type PluginExtensionConfig,
type PluginExtensionFunction,
type PluginExtensionLinkConfig,
@@ -1,8 +1,22 @@
import {
SceneDataNode,
SceneDataTransformer,
SceneDeactivationHandler,
SceneFlexItem,
SceneFlexLayout,
sceneGraph,
SceneObject,
SceneObjectBase,
SceneVariable,
SceneVariableSet,
TestVariable,
} from '@grafana/scenes';
import { DataTransformerConfig, LoadingState } from '@grafana/schema';
import { DataFrameView } from '../../dataframe/DataFrameView';
import { toDataFrame } from '../../dataframe/processDataFrame';
import { ScopedVars } from '../../types/ScopedVars';
import { FieldType } from '../../types/dataFrame';
import { DataTransformContext } from '../../types/transformations';
import { DataFrame, FieldType } from '../../types/dataFrame';
import { getDefaultTimeRange } from '../../types/time';
import { BinaryOperationID } from '../../utils/binaryOperators';
import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry';
import { UnaryOperationID } from '../../utils/unaryOperators';
@@ -565,51 +579,26 @@ describe('calculateField transformer w/ timeseries', () => {
replaceFields: true,
},
};
const context: DataTransformContext = {
interpolate: (target: string | undefined, scopedVars?: ScopedVars, format?: string | Function): string => {
if (!target) {
return '';
}
const variables: ScopedVars = {
var1: {
value: 'Test',
text: 'Test',
},
var2: {
value: 5,
text: '5',
},
__interval: {
value: 10000,
text: '10000',
},
};
for (const key in variables) {
if (target === `$${key}`) {
return variables[key]!.value + '';
}
}
return target;
},
};
await expect(transformDataFrame([cfg], [seriesA], context)).toEmitValuesWith((received) => {
const data = received[0];
const filtered = data[0];
const rows = new DataFrameView(filtered).toArray();
expect(rows).toMatchInlineSnapshot(`
[
{
"Test": 6,
"TheTime": 1000,
},
{
"Test": 105,
"TheTime": 2000,
},
]
`);
});
const data = setupTransformationScene(seriesA, cfg, [
new TestVariable({ name: 'var1', value: 'Test' }),
new TestVariable({ name: 'var2', value: 5 }),
]);
const filtered = data[0];
const rows = new DataFrameView(filtered).toArray();
expect(rows).toMatchInlineSnapshot(`
[
{
"Test": 6,
"TheTime": 1000,
},
{
"Test": 105,
"TheTime": 2000,
},
]
`);
});
it('calculates centered moving average on odd window size', async () => {
@@ -750,8 +739,6 @@ describe('calculateField transformer w/ timeseries', () => {
await expect(transformDataFrame([cfg], [series])).toEmitValuesWith((received) => {
const data = received[0][0];
//console.log(data.fields);
expect(data.fields.length).toEqual(2);
expect(data.fields[1].values).toEqual([1, 1, 3, 3.5, 4.5]);
});
@@ -1185,3 +1172,63 @@ describe('calculateField transformer w/ timeseries', () => {
});
});
});
function activateFullSceneTree(scene: SceneObject): SceneDeactivationHandler {
const deactivationHandlers: SceneDeactivationHandler[] = [];
// Important that variables are activated before other children
if (scene.state.$variables) {
deactivationHandlers.push(activateFullSceneTree(scene.state.$variables));
}
scene.forEachChild((child) => {
// For query runners which by default use the container width for maxDataPoints calculation we are setting a width.
// In real life this is done by the React component when VizPanel is rendered.
if ('setContainerWidth' in child) {
// @ts-expect-error
child.setContainerWidth(500);
}
deactivationHandlers.push(activateFullSceneTree(child));
});
deactivationHandlers.push(scene.activate());
return () => {
for (const handler of deactivationHandlers) {
handler();
}
};
}
function setupTransformationScene(
inputData: DataFrame,
cfg: DataTransformerConfig,
variables: SceneVariable[]
): DataFrame[] {
class TestSceneObject extends SceneObjectBase<{}> {}
const dataNode = new SceneDataNode({
data: {
state: LoadingState.Loading,
timeRange: getDefaultTimeRange(),
series: [inputData],
},
});
const transformationNode = new SceneDataTransformer({
transformations: [cfg],
});
const consumer = new TestSceneObject({
$data: transformationNode,
});
const scene = new SceneFlexLayout({
$data: dataNode,
$variables: new SceneVariableSet({ variables }),
children: [new SceneFlexItem({ body: consumer })],
});
activateFullSceneTree(scene);
return sceneGraph.getData(consumer).state.data?.series!;
}
@@ -139,10 +139,6 @@ export const calculateFieldTransformer: DataTransformerInfo<CalculateFieldTransf
? ensureColumnsTransformer.operator(null, ctx)
: noopTransformer.operator({}, ctx);
if (options.alias != null) {
options.alias = ctx.interpolate(options.alias);
}
return outerSource.pipe(
operator,
map((data) => {
@@ -573,7 +569,8 @@ function findFieldValuesWithNameOrConstant(
}
if (value.matcher && value.matcher.id === FieldMatcherID.byName) {
const name = ctx.interpolate(value.matcher.options ?? '');
const name = value.matcher.options ?? '';
for (const f of frame.fields) {
if (name === getFieldDisplayName(f, frame, allFrames)) {
if (f.type === FieldType.boolean) {
@@ -584,7 +581,7 @@ function findFieldValuesWithNameOrConstant(
}
}
const v = parseFloat(value.fixed ?? ctx.interpolate(value.matcher?.options ?? ''));
const v = parseFloat(value.fixed ?? value.matcher?.options ?? '');
if (!isNaN(v)) {
return new Array(frame.length).fill(v);
}
@@ -28,15 +28,15 @@ export const filterFieldsTransformer: DataTransformerInfo<FilterOptions> = {
}
if (typeof options.include?.options === 'string') {
options.include.options = ctx.interpolate(options.include?.options);
options.include.options = options.include?.options;
} else if (typeof options.include?.options?.pattern === 'string') {
options.include.options.pattern = ctx.interpolate(options.include?.options.pattern);
options.include.options.pattern = options.include?.options.pattern;
}
if (typeof options.exclude?.options === 'string') {
options.exclude.options = ctx.interpolate(options.exclude?.options);
options.exclude.options = options.exclude?.options;
} else if (typeof options.exclude?.options?.pattern === 'string') {
options.exclude.options.pattern = ctx.interpolate(options.exclude?.options.pattern);
options.exclude.options.pattern = options.exclude?.options.pattern;
}
return source.pipe(
@@ -1,6 +1,21 @@
import {
SceneDataNode,
SceneDataTransformer,
SceneDeactivationHandler,
SceneFlexItem,
SceneFlexLayout,
sceneGraph,
SceneObject,
SceneObjectBase,
SceneVariable,
SceneVariableSet,
TestVariable,
} from '@grafana/scenes';
import { DataTransformerConfig, LoadingState } from '@grafana/schema';
import { toDataFrame } from '../../dataframe/processDataFrame';
import { ScopedVars } from '../../types/ScopedVars';
import { FieldType } from '../../types/dataFrame';
import { DataFrame, FieldType } from '../../types/dataFrame';
import { getDefaultTimeRange } from '../../types/time';
import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry';
import { transformDataFrame } from '../transformDataFrame';
@@ -196,6 +211,7 @@ describe('filterByName transformer', () => {
expect(filtered.fields[0].name).toBe('B');
});
});
it('it can use a variable with multiple comma separated', async () => {
const cfg = {
id: DataTransformerID.filterFieldsByName,
@@ -207,31 +223,13 @@ describe('filterByName transformer', () => {
},
};
const ctx = {
interpolate: (target: string | undefined, scopedVars?: ScopedVars, format?: string | Function): string => {
if (!target) {
return '';
}
const variables: ScopedVars = {
var: {
value: 'B,D',
text: 'Test',
},
};
for (const key in variables) {
return target.replace(`$${key}`, variables[key]!.value);
}
return target;
},
};
await expect(transformDataFrame([cfg], [seriesWithNamesToMatch], ctx)).toEmitValuesWith((received) => {
const data = received[0];
const filtered = data[0];
expect(filtered.fields.length).toBe(2);
expect(filtered.fields[0].name).toBe('B');
expect(filtered.fields[1].name).toBe('D');
});
const data = setupTransformationScene(seriesWithNamesToMatch, cfg, [
new TestVariable({ name: 'var', value: 'B,D' }),
]);
const filtered = data[0];
expect(filtered.fields.length).toBe(2);
expect(filtered.fields[0].name).toBe('B');
expect(filtered.fields[1].name).toBe('D');
});
it('it can use a variable with multiple comma separated values in {}', async () => {
@@ -245,31 +243,14 @@ describe('filterByName transformer', () => {
},
};
const ctx = {
interpolate: (target: string | undefined, scopedVars?: ScopedVars, format?: string | Function): string => {
if (!target) {
return '';
}
const variables: ScopedVars = {
var: {
value: '{B,D}',
text: 'Test',
},
};
for (const key in variables) {
return target.replace(`$${key}`, variables[key]!.value);
}
return target;
},
};
const data = setupTransformationScene(seriesWithNamesToMatch, cfg, [
new TestVariable({ name: 'var', value: 'B,D' }),
]);
await expect(transformDataFrame([cfg], [seriesWithNamesToMatch], ctx)).toEmitValuesWith((received) => {
const data = received[0];
const filtered = data[0];
expect(filtered.fields.length).toBe(2);
expect(filtered.fields[0].name).toBe('B');
expect(filtered.fields[1].name).toBe('D');
});
const filtered = data[0];
expect(filtered.fields.length).toBe(2);
expect(filtered.fields[0].name).toBe('B');
expect(filtered.fields[1].name).toBe('D');
});
it('uses template variable substitution', async () => {
@@ -277,35 +258,78 @@ describe('filterByName transformer', () => {
id: DataTransformerID.filterFieldsByName,
options: {
include: {
pattern: '/^$var1/',
pattern: '/^$var/',
},
},
};
const ctx = {
interpolate: (target: string | undefined, scopedVars?: ScopedVars, format?: string | Function): string => {
if (!target) {
return '';
}
const variables: ScopedVars = {
var1: {
value: 'startsWith',
text: 'Test',
},
};
for (const key in variables) {
return target.replace(`$${key}`, variables[key]!.value);
}
return target;
},
};
const data = setupTransformationScene(seriesWithNamesToMatch, cfg, [
new TestVariable({ name: 'var', value: 'startsWith' }),
]);
await expect(transformDataFrame([cfg], [seriesWithNamesToMatch], ctx)).toEmitValuesWith((received) => {
const data = received[0];
const filtered = data[0];
expect(filtered.fields.length).toBe(2);
expect(filtered.fields[0].name).toBe('startsWithA');
});
const filtered = data[0];
expect(filtered.fields.length).toBe(2);
expect(filtered.fields[0].name).toBe('startsWithA');
});
});
});
function activateFullSceneTree(scene: SceneObject): SceneDeactivationHandler {
const deactivationHandlers: SceneDeactivationHandler[] = [];
// Important that variables are activated before other children
if (scene.state.$variables) {
deactivationHandlers.push(activateFullSceneTree(scene.state.$variables));
}
scene.forEachChild((child) => {
// For query runners which by default use the container width for maxDataPoints calculation we are setting a width.
// In real life this is done by the React component when VizPanel is rendered.
if ('setContainerWidth' in child) {
// @ts-expect-error
child.setContainerWidth(500);
}
deactivationHandlers.push(activateFullSceneTree(child));
});
deactivationHandlers.push(scene.activate());
return () => {
for (const handler of deactivationHandlers) {
handler();
}
};
}
export function setupTransformationScene(
inputData: DataFrame,
cfg: DataTransformerConfig,
variables: SceneVariable[]
): DataFrame[] {
class TestSceneObject extends SceneObjectBase<{}> {}
const dataNode = new SceneDataNode({
data: {
state: LoadingState.Loading,
timeRange: getDefaultTimeRange(),
series: [inputData],
},
});
const transformationNode = new SceneDataTransformer({
transformations: [cfg],
});
const consumer = new TestSceneObject({
$data: transformationNode,
});
const scene = new SceneFlexLayout({
$data: dataNode,
$variables: new SceneVariableSet({ variables }),
children: [new SceneFlexItem({ body: consumer })],
});
activateFullSceneTree(scene);
return sceneGraph.getData(consumer).state.data?.series!;
}
@@ -1,4 +1,4 @@
import { DataTransformContext, DataTransformerInfo, MatcherConfig } from '../../types/transformations';
import { DataTransformerInfo, MatcherConfig } from '../../types/transformations';
import { FieldMatcherID } from '../matchers/ids';
import { RegexpOrNamesMatcherOptions } from '../matchers/nameMatcher';
@@ -25,8 +25,8 @@ export const filterFieldsByNameTransformer: DataTransformerInfo<FilterFieldsByNa
source.pipe(
filterFieldsTransformer.operator(
{
include: getMatcherConfig(ctx, options.include, options.byVariable),
exclude: getMatcherConfig(ctx, options.exclude, options.byVariable),
include: getMatcherConfig(options.include, options.byVariable),
exclude: getMatcherConfig(options.exclude, options.byVariable),
},
ctx
)
@@ -35,7 +35,6 @@ export const filterFieldsByNameTransformer: DataTransformerInfo<FilterFieldsByNa
// Exported to share with other implementations, but not exported to `@grafana/data`
export const getMatcherConfig = (
ctx: DataTransformContext,
options?: RegexpOrNamesMatcherOptions,
byVariable?: boolean
): MatcherConfig | undefined => {
@@ -46,7 +45,8 @@ export const getMatcherConfig = (
const { names, pattern, variable } = options;
if (byVariable && variable) {
const stringOfNames = ctx.interpolate(variable);
const stringOfNames = variable;
if (/\{.*\}/.test(stringOfNames)) {
const namesFromString = stringOfNames.slice(1).slice(0, -1).split(',');
return { id: FieldMatcherID.byNames, options: { names: namesFromString } };
@@ -13,10 +13,6 @@ import {
FilterByValueType,
} from './filterByValue';
import { DataTransformerID } from './ids';
import * as utils from './utils';
const mockTransformationsVariableSupport = jest.spyOn(utils, 'transformationsVariableSupport');
mockTransformationsVariableSupport.mockReturnValue(false);
const seriesAWithSingleField = toDataFrame({
name: 'A',
@@ -260,139 +256,6 @@ describe('FilterByValue transformer', () => {
});
});
it('should interpolate dashboard variables', async () => {
mockTransformationsVariableSupport.mockReturnValue(true);
const lower: MatcherConfig<BasicValueMatcherOptions<string | number>> = {
id: ValueMatcherID.lower,
options: { value: 'thiswillinterpolateto6' },
};
const cfg: DataTransformerConfig<FilterByValueTransformerOptions> = {
id: DataTransformerID.filterByValue,
options: {
type: FilterByValueType.exclude,
match: FilterByValueMatch.all,
filters: [
{
fieldName: 'numbers',
config: lower,
},
],
},
};
const ctxmock = { interpolate: jest.fn(() => '6') };
await expect(transformDataFrame([cfg], [seriesAWithSingleField], ctxmock)).toEmitValuesWith((received) => {
const processed = received[0];
expect(processed.length).toEqual(1);
expect(processed[0].fields).toEqual([
{
name: 'time',
type: FieldType.time,
values: [6000, 7000],
state: {},
},
{
name: 'numbers',
type: FieldType.number,
values: [6, 7],
state: {},
},
]);
});
});
it('should interpolate dashboard variables for regex matcher', async () => {
mockTransformationsVariableSupport.mockReturnValue(true);
const regex: MatcherConfig<BasicValueMatcherOptions<string | number>> = {
id: ValueMatcherID.regex,
options: { value: '.*thiswillinterpolateto6' },
};
const cfg: DataTransformerConfig<FilterByValueTransformerOptions> = {
id: DataTransformerID.filterByValue,
options: {
type: FilterByValueType.include,
match: FilterByValueMatch.all,
filters: [
{
fieldName: 'numbers',
config: regex,
},
],
},
};
const ctxmock = { interpolate: jest.fn(() => '6') };
await expect(transformDataFrame([cfg], [seriesAWithSingleField], ctxmock)).toEmitValuesWith((received) => {
const processed = received[0];
expect(processed.length).toEqual(1);
expect(processed[0].fields).toEqual([
{
name: 'time',
type: FieldType.time,
values: [6000],
state: {},
},
{
name: 'numbers',
type: FieldType.number,
values: [6],
state: {},
},
]);
});
});
it('should not interpolate dashboard variables when feature toggle is off', async () => {
mockTransformationsVariableSupport.mockReturnValue(false);
const lower: MatcherConfig<BasicValueMatcherOptions<number | string>> = {
id: ValueMatcherID.lower,
options: { value: 'notinterpolating' },
};
const cfg: DataTransformerConfig<FilterByValueTransformerOptions> = {
id: DataTransformerID.filterByValue,
options: {
type: FilterByValueType.exclude,
match: FilterByValueMatch.all,
filters: [
{
fieldName: 'numbers',
config: lower,
},
],
},
};
await expect(transformDataFrame([cfg], [seriesAWithSingleField])).toEmitValuesWith((received) => {
const processed = received[0];
expect(processed.length).toEqual(1);
expect(processed[0].fields).toEqual([
{
name: 'time',
type: FieldType.time,
values: [1000, 2000, 3000, 4000, 5000, 6000, 7000],
state: {},
},
{
name: 'numbers',
type: FieldType.number,
values: [1, 2, 3, 4, 5, 6, 7],
state: {},
},
]);
});
});
it('should match any condition', async () => {
const lowerOrEqual: MatcherConfig<BasicValueMatcherOptions<number>> = {
id: ValueMatcherID.lowerOrEqual,
@@ -4,11 +4,9 @@ import { getFieldDisplayName } from '../../field/fieldState';
import { DataFrame, Field } from '../../types/dataFrame';
import { DataTransformerInfo, MatcherConfig } from '../../types/transformations';
import { getValueMatcher } from '../matchers';
import { ValueMatcherID } from '../matchers/ids';
import { DataTransformerID } from './ids';
import { noopTransformer } from './noop';
import { transformationsVariableSupport } from './utils';
export enum FilterByValueType {
exclude = 'exclude',
@@ -50,50 +48,6 @@ export const filterByValueTransformer: DataTransformerInfo<FilterByValueTransfor
return source.pipe(noopTransformer.operator({}, ctx));
}
const interpolatedFilters: FilterByValueFilter[] = [];
if (transformationsVariableSupport()) {
interpolatedFilters.push(
...filters.map((filter) => {
if (filter.config.id === ValueMatcherID.between) {
let valueFrom = filter.config.options.from;
let valueTo = filter.config.options.to;
if (typeof filter.config.options.from === 'string') {
valueFrom = ctx.interpolate(valueFrom);
}
if (typeof filter.config.options.to === 'string') {
valueTo = ctx.interpolate(valueTo);
}
return {
...filter,
config: {
...filter.config,
options: {
...filter.config.options,
to: valueTo,
from: valueFrom,
},
},
};
} else if (filter.config.options.value) {
let value = filter.config.options.value;
if (typeof filter.config.options.value === 'string') {
value = ctx.interpolate(value);
}
return {
...filter,
config: { ...filter.config, options: { ...filter.config.options, value } },
};
}
return filter;
})
);
}
return source.pipe(
map((data) => {
if (data.length === 0) {
@@ -106,12 +60,7 @@ export const filterByValueTransformer: DataTransformerInfo<FilterByValueTransfor
const rows = new Set<number>();
const fieldIndexByName = groupFieldIndexByName(frame, data);
let matchers;
if (transformationsVariableSupport()) {
matchers = createFilterValueMatchers(interpolatedFilters, fieldIndexByName);
} else {
matchers = createFilterValueMatchers(filters, fieldIndexByName);
}
const matchers = createFilterValueMatchers(filters, fieldIndexByName);
for (let index = 0; index < frame.length; index++) {
if (rows.has(index)) {
@@ -65,9 +65,9 @@ export const groupingToMatrixTransformer: DataTransformerInfo<GroupingToMatrixTr
operator: (options: GroupingToMatrixTransformerOptions, ctx: DataTransformContext) => (source) =>
source.pipe(
map((data) => {
const columnFieldMatch = ctx.interpolate(options.columnField || DEFAULT_COLUMN_FIELD);
const rowFieldMatch = ctx.interpolate(options.rowField || DEFAULT_ROW_FIELD);
const valueFieldMatch = ctx.interpolate(options.valueField || DEFAULT_VALUE_FIELD);
const columnFieldMatch = options.columnField || DEFAULT_COLUMN_FIELD;
const rowFieldMatch = options.rowField || DEFAULT_ROW_FIELD;
const valueFieldMatch = options.valueField || DEFAULT_VALUE_FIELD;
const emptyValue = options.emptyValue || DEFAULT_EMPTY_VALUE;
// Accept only single queries
@@ -11,7 +11,6 @@ import { roundDecimals } from '../../utils/numbers';
import { DataTransformerID } from './ids';
import { AlignedData, join } from './joinDataFrames';
import { nullToValueField } from './nulls/nullToValue';
import { transformationsVariableSupport } from './utils';
/**
* @internal
@@ -112,9 +111,6 @@ export const histogramTransformer: SynchronousDataTransformerInfo<HistogramTrans
bucketOffset: number | undefined = undefined;
if (options.bucketSize) {
if (transformationsVariableSupport()) {
options.bucketSize = ctx.interpolate(options.bucketSize.toString());
}
if (typeof options.bucketSize === 'string') {
bucketSize = parseFloat(options.bucketSize);
} else {
@@ -127,9 +123,6 @@ export const histogramTransformer: SynchronousDataTransformerInfo<HistogramTrans
}
if (options.bucketOffset) {
if (transformationsVariableSupport()) {
options.bucketOffset = ctx.interpolate(options.bucketOffset.toString());
}
if (typeof options.bucketOffset === 'string') {
bucketOffset = parseFloat(options.bucketOffset);
} else {
@@ -38,7 +38,7 @@ export const joinByFieldTransformer: SynchronousDataTransformerInfo<JoinByFieldO
return (data: DataFrame[]) => {
if (data.length > 1) {
if (options.byField && !joinBy) {
joinBy = fieldMatchers.get(FieldMatcherID.byName).get(ctx.interpolate(options.byField));
joinBy = fieldMatchers.get(FieldMatcherID.byName).get(options.byField);
}
const joined = joinDataFrames({ frames: data, joinBy, mode: options.mode });
if (joined) {
@@ -3,7 +3,6 @@ import { map } from 'rxjs/operators';
import { DataTransformerInfo } from '../../types/transformations';
import { DataTransformerID } from './ids';
import { transformationsVariableSupport } from './utils';
export interface LimitTransformerOptions {
limitField?: number | string;
@@ -25,11 +24,7 @@ export const limitTransformer: DataTransformerInfo<LimitTransformerOptions> = {
let limit = DEFAULT_LIMIT_FIELD;
if (options.limitField !== undefined) {
if (typeof options.limitField === 'string') {
if (transformationsVariableSupport()) {
limit = parseInt(ctx.interpolate(options.limitField), 10);
} else {
limit = parseInt(options.limitField, 10);
}
limit = parseInt(options.limitField, 10);
} else {
limit = options.limitField;
}
@@ -6,7 +6,6 @@ import { DataFrame } from '../../types/dataFrame';
import { DataTransformContext, DataTransformerInfo } from '../../types/transformations';
import { DataTransformerID } from './ids';
import { transformationsVariableSupport } from './utils';
export interface SortByField {
field: string;
@@ -59,12 +58,7 @@ function attachFieldIndex(frame: DataFrame, sort: SortByField[], ctx: DataTransf
// null or undefined
return s;
}
if (transformationsVariableSupport()) {
return {
...s,
index: frame.fields.findIndex((f) => ctx.interpolate(s.field) === getFieldDisplayName(f, frame)),
};
}
return {
...s,
index: frame.fields.findIndex((f) => s.field === getFieldDisplayName(f, frame)),
@@ -6,11 +6,6 @@ declare global {
grafanaBootData?: BootData;
}
}
export const transformationsVariableSupport = () => {
return window?.grafanaBootData?.settings?.featureToggles?.transformationsVariableSupport;
};
/**
* Retrieve the maximum number of fields in a series of a dataframe.
*/
@@ -105,7 +105,6 @@ export interface FeatureToggles {
enableNativeHTTPHistogram?: boolean;
disableClassicHTTPHistogram?: boolean;
formatString?: boolean;
transformationsVariableSupport?: boolean;
kubernetesPlaylists?: boolean;
kubernetesSnapshots?: boolean;
kubernetesDashboards?: boolean;
@@ -121,7 +120,6 @@ export interface FeatureToggles {
lokiStructuredMetadata?: boolean;
teamHttpHeaders?: boolean;
cachingOptimizeSerializationMemoryUsage?: boolean;
managedPluginsInstall?: boolean;
prometheusPromQAIL?: boolean;
prometheusCodeModeMetricNamesSearch?: boolean;
addFieldFromCalculationStatFunctions?: boolean;
@@ -260,4 +258,6 @@ export interface FeatureToggles {
grafanaconThemes?: boolean;
pluginsCDNSyncLoader?: boolean;
alertingJiraIntegration?: boolean;
alertingRuleVersionHistoryRestore?: boolean;
newShareReportDrawer?: boolean;
}
@@ -32,6 +32,8 @@ export type PluginExtensionLink = PluginExtensionBase & {
category?: string;
};
export type PluginExtensionComponentMeta = Omit<PluginExtensionComponent, 'component'>;
export type PluginExtensionComponent<Props = {}> = PluginExtensionBase & {
type: PluginExtensionTypes.component;
component: React.ComponentType<Props>;
@@ -193,7 +195,10 @@ export type PluginExtensionPanelContext = {
data?: PanelData;
};
export type PluginExtensionDataSourceConfigContext<JsonData extends DataSourceJsonData = DataSourceJsonData> = {
export type PluginExtensionDataSourceConfigContext<
JsonData extends DataSourceJsonData = DataSourceJsonData,
SecureJsonData = {},
> = {
// The current datasource settings
dataSource: DataSourceSettings<JsonData>;
@@ -209,6 +214,7 @@ export type PluginExtensionDataSourceConfigContext<JsonData extends DataSourceJs
// Can be used to update the `jsonData` field on the datasource
// (Only updates the form, it still needs to be saved by the user)
setJsonData: (jsonData: JsonData) => void;
setSecureJsonData: (secureJsonData: SecureJsonData) => void;
};
export type PluginExtensionCommandPaletteContext = {};
+7 -1
View File
@@ -8,5 +8,11 @@
},
"exclude": ["dist/**/*"],
"extends": "@grafana/tsconfig",
"include": ["src/**/*.ts*", "typings/jest", "../../public/app/types/jquery/*.ts", "../../public/app/types/*.d.ts"]
"include": [
"src/**/*.ts*",
"typings/jest",
"../../public/app/types/jquery/*.ts",
"../../public/app/types/*.d.ts",
"../grafana-ui/src/types/react-table-config.d.ts"
]
}
@@ -3,6 +3,7 @@ import type {
PluginExtensionLink,
PluginExtensionComponent,
PluginExtensionFunction,
PluginExtensionComponentMeta,
} from '@grafana/data';
import { isPluginExtensionComponent, isPluginExtensionLink } from './utils';
@@ -42,7 +43,7 @@ export type UsePluginComponentResult<Props = {}> = {
};
export type UsePluginComponentsResult<Props = {}> = {
components: Array<React.ComponentType<Props>>;
components: Array<React.ComponentType<Props> & { meta: PluginExtensionComponentMeta }>;
isLoading: boolean;
};
+1 -1
View File
@@ -425,7 +425,7 @@ func (hs *HTTPServer) registerRoutes() {
apiRoute.Any("/plugin-proxy/:pluginId/*", requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow), authorize(ac.EvalPermission(pluginaccesscontrol.ActionAppAccess, pluginIDScope)), checkAppEnabled(hs.pluginStore, hs.PluginSettings), hs.ProxyPluginRequest)
apiRoute.Any("/plugin-proxy/:pluginId", requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow), authorize(ac.EvalPermission(pluginaccesscontrol.ActionAppAccess, pluginIDScope)), checkAppEnabled(hs.pluginStore, hs.PluginSettings), hs.ProxyPluginRequest)
if hs.Cfg.PluginAdminEnabled && (hs.Features.IsEnabledGlobally(featuremgmt.FlagManagedPluginsInstall) || !hs.Cfg.PluginAdminExternalManageEnabled) {
if hs.Cfg.PluginAdminEnabled {
apiRoute.Group("/plugins", func(pluginRoute routing.RouteRegister) {
pluginRoute.Post("/:pluginId/install", authorizeInOrg(ac.UseGlobalOrSingleOrg(hs.Cfg), ac.EvalPermission(pluginaccesscontrol.ActionInstall)), routing.Wrap(hs.InstallPlugin))
pluginRoute.Post("/:pluginId/uninstall", authorizeInOrg(ac.UseGlobalOrSingleOrg(hs.Cfg), ac.EvalPermission(pluginaccesscontrol.ActionInstall)), routing.Wrap(hs.UninstallPlugin))
+2 -1
View File
@@ -250,7 +250,8 @@ func (hs *HTTPServer) getThemeForIndexData(themePrefId string, themeURLParam str
if pref.IsValidThemeID(themePrefId) {
theme := pref.GetThemeByID(themePrefId)
if !theme.IsExtra || hs.Features.IsEnabledGlobally(featuremgmt.FlagExtraThemes) {
// TODO refactor
if !theme.IsExtra || hs.Features.IsEnabledGlobally(featuremgmt.FlagExtraThemes) || hs.Features.IsEnabledGlobally(featuremgmt.FlagGrafanaconThemes) {
return theme
}
}
+1 -1
View File
@@ -73,7 +73,7 @@ func Test_PluginsInstallAndUninstall(t *testing.T) {
preInstalledPlugin bool
}
tcs := []testCase{
{expectedCode: http.StatusNotFound, permissionOrg: globalOrg, permissions: canInstall, pluginAdminEnabled: true, pluginAdminExternalManageEnabled: true},
{expectedCode: http.StatusOK, permissionOrg: globalOrg, permissions: canInstall, pluginAdminEnabled: true, pluginAdminExternalManageEnabled: true},
{expectedCode: http.StatusNotFound, permissionOrg: globalOrg, permissions: canInstall, pluginAdminEnabled: false, pluginAdminExternalManageEnabled: true},
{expectedCode: http.StatusNotFound, permissionOrg: globalOrg, permissions: canInstall, pluginAdminEnabled: false, pluginAdminExternalManageEnabled: false},
{expectedCode: http.StatusForbidden, permissionOrg: globalOrg, permissions: cannotInstall, pluginAdminEnabled: true, pluginAdminExternalManageEnabled: false},
+1 -1
View File
@@ -3,7 +3,7 @@ module github.com/grafana/grafana/pkg/apimachinery
go 1.23.1
require (
github.com/grafana/authlib v0.0.0-20250206063954-bf4600a17569 // @grafana/identity-access-team
github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc // @grafana/identity-access-team
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c // @grafana/identity-access-team
github.com/stretchr/testify v1.10.0
k8s.io/apimachinery v0.32.1
+2 -2
View File
@@ -32,8 +32,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grafana/authlib v0.0.0-20250206063954-bf4600a17569 h1:bQw6fdcxVdZ6xmZVhMtRgGEKIT1Zc6y+i1PWF8tMX4Q=
github.com/grafana/authlib v0.0.0-20250206063954-bf4600a17569/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8=
github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc h1:qx8avmRi3wx+SuAn6UV0pKma1fJ0/4POUYNc+2rxZkg=
github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc/go.mod h1:HamgITzYqErFp8qLwYM/JwwE3z6fXft0Z+5n0Dx1rUM=
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo=
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
-4
View File
@@ -21,10 +21,6 @@ var (
Usage: "Specify number of tries before failing",
Value: 1,
}
dryRunFlag = cli.BoolFlag{
Name: "dry-run",
Usage: "Only simulate actions",
}
tagFlag = cli.StringFlag{
Name: "tag",
Usage: "Grafana version tag",
+6 -5
View File
@@ -5,6 +5,7 @@ import (
"os"
"github.com/grafana/grafana/pkg/build"
"github.com/grafana/grafana/pkg/build/cmd/util"
"github.com/urfave/cli/v2"
)
@@ -66,7 +67,7 @@ func main() {
Name: "publish-metrics",
Usage: "Publish a set of metrics from stdin",
ArgsUsage: "<api-key>",
Action: MaxArgCountWrapper(1, PublishMetrics),
Action: util.MaxArgCountWrapper(1, PublishMetrics),
},
{
Name: "verify-drone",
@@ -178,7 +179,7 @@ func main() {
Name: "fetch",
Usage: "Fetch Grafana Docker images",
ArgsUsage: "[version]",
Action: MaxArgCountWrapper(1, FetchImages),
Action: util.MaxArgCountWrapper(1, FetchImages),
Flags: []cli.Flag{
&editionFlag,
},
@@ -229,7 +230,7 @@ func main() {
Flags: []cli.Flag{
&editionFlag,
&buildIDFlag,
&dryRunFlag,
&util.DryRunFlag,
&cli.StringFlag{
Name: "src-bucket",
Value: "grafana-downloads",
@@ -242,7 +243,7 @@ func main() {
Usage: "Publish packages to GitHub releases",
Action: PublishGithub,
Flags: []cli.Flag{
&dryRunFlag,
&util.DryRunFlag,
&cli.StringFlag{
Name: "path",
Usage: "Path to the asset to be published",
@@ -267,7 +268,7 @@ func main() {
Usage: "Publish image to AWS Marketplace releases",
Action: PublishAwsMarketplace,
Flags: []cli.Flag{
&dryRunFlag,
&util.DryRunFlag,
&cli.StringFlag{
Name: "version",
Usage: "Release version (default from metadata)",
+5 -1
View File
@@ -15,6 +15,7 @@ import (
"github.com/aws/aws-sdk-go/service/ecr"
"github.com/aws/aws-sdk-go/service/marketplacecatalog"
"github.com/docker/docker/api/types/image"
"github.com/grafana/grafana/pkg/build/cmd/util"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v2"
)
@@ -138,7 +139,7 @@ func setupPublishAwsMarketplaceTests(t *testing.T) *cli.App {
testApp := cli.NewApp()
testApp.Action = PublishAwsMarketplace
testApp.Flags = []cli.Flag{
&dryRunFlag,
&util.DryRunFlag,
&cli.StringFlag{
Name: "version",
Usage: "Release version (default from metadata)",
@@ -171,9 +172,11 @@ type mockAwsMarketplaceDocker struct {
func (m *mockAwsMarketplaceDocker) ImagePull(ctx context.Context, refStr string, options image.PullOptions) (io.ReadCloser, error) {
return io.NopCloser(bytes.NewReader([]byte(""))), m.ImagePullError
}
func (m *mockAwsMarketplaceDocker) ImageTag(ctx context.Context, source string, target string) error {
return m.ImageTagError
}
func (m *mockAwsMarketplaceDocker) ImagePush(ctx context.Context, image string, options image.PushOptions) (io.ReadCloser, error) {
return io.NopCloser(bytes.NewReader([]byte(""))), m.ImagePushError
}
@@ -202,6 +205,7 @@ func (m *mockAwsMarketplaceCatalog) DescribeEntityWithContext(ctx context.Contex
EntityIdentifier: aws.String("productid"),
}, m.DescribeEntityWithContextError
}
func (m *mockAwsMarketplaceCatalog) StartChangeSetWithContext(ctx context.Context, input *marketplacecatalog.StartChangeSetInput, opts ...request.Option) (*marketplacecatalog.StartChangeSetOutput, error) {
return &marketplacecatalog.StartChangeSetOutput{}, m.StartChangeSetWithContextError
}
+2 -1
View File
@@ -10,6 +10,7 @@ import (
"testing"
"github.com/google/go-github/github"
"github.com/grafana/grafana/pkg/build/cmd/util"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v2"
)
@@ -158,7 +159,7 @@ func setupPublishGithubTests(t *testing.T) (*cli.App, string) {
testApp := cli.NewApp()
testApp.Action = PublishGithub
testApp.Flags = []cli.Flag{
&dryRunFlag,
&util.DryRunFlag,
&cli.StringFlag{
Name: "path",
Required: true,
@@ -1,4 +1,4 @@
package main
package util
import "github.com/urfave/cli/v2"
+8
View File
@@ -0,0 +1,8 @@
package util
import "github.com/urfave/cli/v2"
var DryRunFlag = cli.BoolFlag{
Name: "dry-run",
Usage: "Only simulate actions",
}
View File
Whitespace-only changes.
@@ -91,7 +91,7 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour
}
// if searching for tags, get those instead of the dashboards or folders
for facet, _ := range req.Facet {
for facet := range req.Facet {
if facet == resource.SEARCH_FIELD_TAGS {
tags, err := c.dashboardStore.GetDashboardTags(ctx, &dashboards.GetDashboardTagsQuery{
OrgID: user.GetOrgID(),
@@ -102,7 +102,7 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour
list := &resource.ResourceSearchResponse{
Results: &resource.ResourceTable{},
Facet: map[string]*resource.ResourceSearchResponse_Facet{
"tags": &resource.ResourceSearchResponse_Facet{
"tags": {
Terms: []*resource.ResourceSearchResponse_TermFacet{},
},
},
@@ -213,7 +213,7 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour
Key: getResourceKey(&dashboards.DashboardSearchProjection{
UID: dashboard.UID,
}, req.Options.Key.Namespace),
Cells: [][]byte{[]byte(dashboard.Title), []byte(dashboard.FolderUID), []byte{}},
Cells: [][]byte{[]byte(dashboard.Title), []byte(dashboard.FolderUID), {}},
})
}
@@ -115,7 +115,7 @@ func TestDashboardSearchClient_Search(t *testing.T) {
require.Equal(t, &resource.ResourceSearchResponse{
Results: &resource.ResourceTable{},
Facet: map[string]*resource.ResourceSearchResponse_Facet{
"tags": &resource.ResourceSearchResponse_Facet{
"tags": {
Terms: []*resource.ResourceSearchResponse_TermFacet{
{
Term: "tag1",
+5 -4
View File
@@ -7,7 +7,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/registry/apps/advisor"
"github.com/grafana/grafana/pkg/registry/apps/investigation"
"github.com/grafana/grafana/pkg/registry/apps/investigations"
"github.com/grafana/grafana/pkg/registry/apps/playlist"
"github.com/grafana/grafana/pkg/services/apiserver"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
@@ -31,7 +31,7 @@ func ProvideRegistryServiceSink(
restConfigProvider apiserver.RestConfigProvider,
features featuremgmt.FeatureToggles,
playlistAppProvider *playlist.PlaylistAppProvider,
investigationAppProvider *investigation.InvestigationAppProvider,
investigationAppProvider *investigations.InvestigationsAppProvider,
advisorAppProvider *advisor.AdvisorAppProvider,
) (*Service, error) {
cfgWrapper := func(ctx context.Context) *rest.Config {
@@ -47,11 +47,12 @@ func ProvideRegistryServiceSink(
RestConfigGetter: cfgWrapper,
APIRegistrar: registrar,
}
logger := log.New("app-registry")
var apiGroupRunner *runner.APIGroupRunner
var err error
providers := []app.Provider{playlistAppProvider}
if features.IsEnabledGlobally(featuremgmt.FlagInvestigationsBackend) {
logger.Debug("Investigations backend is enabled")
providers = append(providers, investigationAppProvider)
}
if features.IsEnabledGlobally(featuremgmt.FlagGrafanaAdvisor) {
@@ -62,7 +63,7 @@ func ProvideRegistryServiceSink(
if err != nil {
return nil, err
}
return &Service{runner: apiGroupRunner, log: log.New("app-registry")}, nil
return &Service{runner: apiGroupRunner, log: logger}, nil
}
func (s *Service) Run(ctx context.Context) error {
@@ -1,28 +1,28 @@
package investigation
package investigations
import (
"github.com/grafana/grafana-app-sdk/app"
"github.com/grafana/grafana-app-sdk/simple"
"github.com/grafana/grafana/apps/investigation/pkg/apis"
investigationv1alpha1 "github.com/grafana/grafana/apps/investigation/pkg/apis/investigation/v1alpha1"
investigationapp "github.com/grafana/grafana/apps/investigation/pkg/app"
"github.com/grafana/grafana/apps/investigations/pkg/apis"
investigationv0alpha1 "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1"
investigationapp "github.com/grafana/grafana/apps/investigations/pkg/app"
"github.com/grafana/grafana/pkg/services/apiserver/builder/runner"
"github.com/grafana/grafana/pkg/setting"
)
type InvestigationAppProvider struct {
type InvestigationsAppProvider struct {
app.Provider
cfg *setting.Cfg
}
func RegisterApp(
cfg *setting.Cfg,
) *InvestigationAppProvider {
provider := &InvestigationAppProvider{
) *InvestigationsAppProvider {
provider := &InvestigationsAppProvider{
cfg: cfg,
}
appCfg := &runner.AppBuilderConfig{
OpenAPIDefGetter: investigationv1alpha1.GetOpenAPIDefinitions,
OpenAPIDefGetter: investigationv0alpha1.GetOpenAPIDefinitions,
ManagedKinds: investigationapp.GetKinds(),
}
provider.Provider = simple.NewAppProvider(apis.LocalManifest(), appCfg, investigationapp.New)
+2 -2
View File
@@ -5,14 +5,14 @@ import (
"github.com/grafana/grafana/apps/advisor/pkg/app/checkregistry"
"github.com/grafana/grafana/pkg/registry/apps/advisor"
"github.com/grafana/grafana/pkg/registry/apps/investigation"
"github.com/grafana/grafana/pkg/registry/apps/investigations"
"github.com/grafana/grafana/pkg/registry/apps/playlist"
)
var WireSet = wire.NewSet(
ProvideRegistryServiceSink,
playlist.RegisterApp,
investigation.RegisterApp,
investigations.RegisterApp,
advisor.RegisterApp,
checkregistry.ProvideService,
wire.Bind(new(checkregistry.CheckService), new(*checkregistry.Service)),
-66
View File
@@ -1,66 +0,0 @@
package authz
import (
"fmt"
"github.com/grafana/grafana/pkg/setting"
)
type Mode string
func (s Mode) IsValid() bool {
switch s {
case ModeGRPC, ModeInProc, ModeCloud:
return true
}
return false
}
const (
ModeCloud Mode = "cloud"
ModeGRPC Mode = "grpc"
ModeInProc Mode = "inproc"
)
type Cfg struct {
remoteAddress string
listen bool
mode Mode
token string
tokenExchangeURL string
tokenNamespace string
allowInsecure bool
}
func ReadCfg(cfg *setting.Cfg) (*Cfg, error) {
authzSection := cfg.SectionWithEnvOverrides("authorization")
grpcClientAuthSection := cfg.SectionWithEnvOverrides("grpc_client_authentication")
mode := Mode(authzSection.Key("mode").MustString(string(ModeInProc)))
if !mode.IsValid() {
return nil, fmt.Errorf("authorization: invalid mode %q", mode)
}
token := grpcClientAuthSection.Key("token").MustString("")
tokenExchangeURL := grpcClientAuthSection.Key("token_exchange_url").MustString("")
tokenNamespace := grpcClientAuthSection.Key("token_namespace").MustString("stacks-" + cfg.StackID)
// When running in cloud mode, the token and tokenExchangeURL are required.
if mode == ModeCloud {
if token == "" || tokenExchangeURL == "" {
return nil, fmt.Errorf("authorization: missing token or tokenExchangeUrl")
}
}
return &Cfg{
remoteAddress: authzSection.Key("remote_address").MustString(""),
listen: authzSection.Key("listen").MustBool(false),
mode: mode,
token: token,
tokenExchangeURL: tokenExchangeURL,
tokenNamespace: tokenNamespace,
allowInsecure: cfg.Env == setting.Dev,
}, nil
}
+38 -106
View File
@@ -7,7 +7,6 @@ import (
"net/http"
"time"
"github.com/fullstorydev/grpchan"
"github.com/fullstorydev/grpchan/inprocgrpc"
grpcAuth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
"github.com/prometheus/client_golang/prometheus"
@@ -26,7 +25,6 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/iam/legacy"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver"
authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
"github.com/grafana/grafana/pkg/services/authz/rbac"
"github.com/grafana/grafana/pkg/services/authz/rbac/store"
"github.com/grafana/grafana/pkg/services/featuremgmt"
@@ -48,21 +46,18 @@ func ProvideAuthZClient(
db db.DB,
acService accesscontrol.Service,
) (authlib.AccessClient, error) {
authCfg, err := ReadCfg(cfg)
authCfg, err := readAuthzClientSettings(cfg)
if err != nil {
return nil, err
}
isRemoteServer := authCfg.mode == ModeCloud || authCfg.mode == ModeGRPC
if !features.IsEnabledGlobally(featuremgmt.FlagAuthZGRPCServer) && isRemoteServer {
if !features.IsEnabledGlobally(featuremgmt.FlagAuthZGRPCServer) && authCfg.mode == clientModeCloud {
return nil, errors.New("authZGRPCServer feature toggle is required for cloud and grpc mode")
}
switch authCfg.mode {
case ModeGRPC:
return newGrpcLegacyClient(authCfg, tracer)
case ModeCloud:
return newCloudLegacyClient(authCfg, tracer)
case clientModeCloud:
return newRemoteRBACClient(authCfg, tracer)
default:
sql := legacysql.NewDatabaseProvider(db)
@@ -83,7 +78,18 @@ func ProvideAuthZClient(
reg,
cache.NewLocalCache(cache.Config{Expiry: 5 * time.Minute, CleanupInterval: 10 * time.Minute}),
)
return newInProcLegacyClient(server, tracer)
channel := &inprocgrpc.Channel{}
channel.WithServerUnaryInterceptor(grpcAuth.UnaryServerInterceptor(func(ctx context.Context) (context.Context, error) {
ctx = authlib.WithAuthInfo(ctx, authnlib.NewAccessTokenAuthInfo(authnlib.Claims[authnlib.AccessTokenClaims]{
Rest: authnlib.AccessTokenClaims{
Namespace: "*",
},
}))
return ctx, nil
}))
authzv1.RegisterAuthzServiceServer(channel, server)
return newRBACClient(channel, tracer), nil
}
}
@@ -96,119 +102,46 @@ func ProvideStandaloneAuthZClient(
return nil, nil
}
authCfg, err := ReadCfg(cfg)
authCfg, err := readAuthzClientSettings(cfg)
if err != nil {
return nil, err
}
if authCfg.mode == ModeGRPC {
return newGrpcLegacyClient(authCfg, tracer)
}
return newCloudLegacyClient(authCfg, tracer)
return newRemoteRBACClient(authCfg, tracer)
}
func newInProcLegacyClient(server *rbac.Service, tracer tracing.Tracer) (authlib.AccessClient, error) {
// For in-proc use-case authorize add fake service claims - it should be able to access every namespace, as there is only one
staticAuth := func(ctx context.Context) (context.Context, error) {
ctx = authlib.WithAuthInfo(ctx, authnlib.NewAccessTokenAuthInfo(authnlib.Claims[authnlib.AccessTokenClaims]{
Rest: authnlib.AccessTokenClaims{
Namespace: "*",
},
}))
return ctx, nil
func newRemoteRBACClient(clientCfg *authzClientSettings, tracer tracing.Tracer) (authlib.AccessClient, error) {
tokenClient, err := authnlib.NewTokenExchangeClient(authnlib.TokenExchangeConfig{
Token: clientCfg.token,
TokenExchangeURL: clientCfg.tokenExchangeURL,
})
if err != nil {
return nil, fmt.Errorf("failed to initialize token exchange client: %w", err)
}
channel := &inprocgrpc.Channel{}
channel.RegisterService(
grpchan.InterceptServer(
&authzv1.AuthzService_ServiceDesc,
grpcAuth.UnaryServerInterceptor(staticAuth),
grpcAuth.StreamServerInterceptor(staticAuth),
conn, err := grpc.NewClient(
clientCfg.remoteAddress,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithPerRPCCredentials(
newGRPCTokenAuth(authzServiceAudience, clientCfg.tokenNamespace, tokenClient),
),
server,
)
if err != nil {
return nil, fmt.Errorf("failed to create authz client to remote server: %w", err)
}
return newRBACClient(conn, tracer), nil
}
func newRBACClient(conn grpc.ClientConnInterface, tracer tracing.Tracer) authlib.AccessClient {
return authzlib.NewClient(
&authzlib.ClientConfig{},
authzlib.WithGrpcConnectionClientOption(channel),
authzlib.WithDisableAccessTokenClientOption(),
authzlib.WithTracerClientOption(tracer),
authzlib.WithCacheClientOption(cache.NewLocalCache(cache.Config{
Expiry: 30 * time.Second,
CleanupInterval: 2 * time.Minute,
})),
)
}
func newGrpcLegacyClient(authCfg *Cfg, tracer tracing.Tracer) (authlib.AccessClient, error) {
// This client interceptor is a noop, as we don't send an access token
clientConfig := authnlib.GrpcClientConfig{}
clientInterceptor, err := authnlib.NewGrpcClientInterceptor(
&clientConfig,
authnlib.WithDisableAccessTokenOption(),
authnlib.WithTracerOption(tracer),
)
if err != nil {
return nil, err
}
cfg := authzlib.ClientConfig{RemoteAddress: authCfg.remoteAddress}
client, err := authzlib.NewClient(&cfg,
authzlib.WithGrpcDialOptionsClientOption(
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithUnaryInterceptor(clientInterceptor.UnaryClientInterceptor),
grpc.WithStreamInterceptor(clientInterceptor.StreamClientInterceptor),
),
authzlib.WithTracerClientOption(tracer),
authzlib.WithCacheClientOption(cache.NewLocalCache(cache.Config{
Expiry: 30 * time.Second,
CleanupInterval: 2 * time.Minute,
})),
// TODO: remove this once access tokens are supported on-prem
authzlib.WithDisableAccessTokenClientOption(),
)
if err != nil {
return nil, err
}
return client, nil
}
func newCloudLegacyClient(authCfg *Cfg, tracer tracing.Tracer) (authlib.AccessClient, error) {
grpcClientConfig := authnlib.GrpcClientConfig{
TokenClientConfig: &authnlib.TokenExchangeConfig{
Token: authCfg.token,
TokenExchangeURL: authCfg.tokenExchangeURL,
},
TokenRequest: &authnlib.TokenExchangeRequest{
Namespace: authCfg.tokenNamespace,
Audiences: []string{authzServiceAudience},
},
}
clientInterceptor, err := authnlib.NewGrpcClientInterceptor(&grpcClientConfig, authnlib.WithTracerOption(tracer))
if err != nil {
return nil, err
}
clientCfg := authzlib.ClientConfig{RemoteAddress: authCfg.remoteAddress}
client, err := authzlib.NewClient(&clientCfg,
authzlib.WithGrpcDialOptionsClientOption(
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithUnaryInterceptor(clientInterceptor.UnaryClientInterceptor),
grpc.WithStreamInterceptor(clientInterceptor.StreamClientInterceptor),
),
conn,
authzlib.WithCacheClientOption(cache.NewLocalCache(cache.Config{
Expiry: 30 * time.Second,
CleanupInterval: 2 * time.Minute,
})),
authzlib.WithTracerClientOption(tracer),
)
if err != nil {
return nil, err
}
return client, nil
}
func RegisterRBACAuthZService(
@@ -251,7 +184,6 @@ func RegisterRBACAuthZService(
srv := handler.GetServer()
authzv1.RegisterAuthzServiceServer(srv, server)
authzextv1.RegisterAuthzExtentionServiceServer(srv, server)
}
var _ http.RoundTripper = tokenExhangeRoundTripper{}
-2
View File
@@ -24,7 +24,6 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/iam/common"
"github.com/grafana/grafana/pkg/registry/apis/iam/legacy"
"github.com/grafana/grafana/pkg/services/accesscontrol"
authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
"github.com/grafana/grafana/pkg/services/authz/rbac/store"
"github.com/grafana/grafana/pkg/storage/legacysql"
)
@@ -38,7 +37,6 @@ const (
type Service struct {
authzv1.UnimplementedAuthzServiceServer
authzextv1.UnimplementedAuthzExtentionServiceServer
store store.Store
folderStore store.FolderStore
+59
View File
@@ -0,0 +1,59 @@
package authz
import (
"fmt"
"github.com/grafana/grafana/pkg/setting"
)
type clientMode string
func (s clientMode) IsValid() bool {
switch s {
case clientModeInproc, clientModeCloud:
return true
}
return false
}
const (
clientModeCloud clientMode = "cloud"
clientModeInproc clientMode = "inproc"
)
type authzClientSettings struct {
remoteAddress string
mode clientMode
token string
tokenExchangeURL string
tokenNamespace string
}
func readAuthzClientSettings(cfg *setting.Cfg) (*authzClientSettings, error) {
authzSection := cfg.SectionWithEnvOverrides("authorization")
grpcClientAuthSection := cfg.SectionWithEnvOverrides("grpc_client_authentication")
mode := clientMode(authzSection.Key("mode").MustString(string(clientModeInproc)))
if !mode.IsValid() {
return nil, fmt.Errorf("authorization: invalid mode %q", mode)
}
s := &authzClientSettings{}
s.mode = mode
if s.mode == clientModeInproc {
return s, nil
}
s.remoteAddress = authzSection.Key("remote_address").MustString("")
s.token = grpcClientAuthSection.Key("token").MustString("")
s.tokenNamespace = grpcClientAuthSection.Key("token_namespace").MustString("stacks-" + cfg.StackID)
s.tokenExchangeURL = grpcClientAuthSection.Key("token_exchange_url").MustString("")
// When running in cloud mode, the token and tokenExchangeURL are required.
if s.token == "" || s.tokenExchangeURL == "" {
return nil, fmt.Errorf("authorization: missing token or tokenExchangeUrl")
}
return s, nil
}
+31
View File
@@ -0,0 +1,31 @@
package authz
import (
"context"
"github.com/grafana/authlib/authn"
)
func newGRPCTokenAuth(audience, namespace string, tc authn.TokenExchanger) *tokenAuth {
return &tokenAuth{audience, namespace, tc}
}
type tokenAuth struct {
audience string
namespace string
tokenClient authn.TokenExchanger
}
func (t *tokenAuth) GetRequestMetadata(ctx context.Context, _ ...string) (map[string]string, error) {
token, err := t.tokenClient.Exchange(ctx, authn.TokenExchangeRequest{
Namespace: t.namespace,
Audiences: []string{t.audience},
})
if err != nil {
return nil, err
}
return map[string]string{authn.DefaultAccessTokenMetadataKey: token.Token}, nil
}
func (t *tokenAuth) RequireTransportSecurity() bool { return false }
+3 -32
View File
@@ -53,18 +53,13 @@ func ProvideZanzana(cfg *setting.Cfg, db db.DB, features featuremgmt.FeatureTogg
if cfg.StackID == "" {
return nil, fmt.Errorf("missing stack ID")
}
namespace := fmt.Sprintf("stacks-%s", cfg.StackID)
tokenAuthCred := &tokenAuth{
cfg: cfg,
namespace: namespace,
tokenClient: tokenClient,
}
dialOptions := []grpc.DialOption{
// TODO: add TLS support
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithPerRPCCredentials(tokenAuthCred),
grpc.WithPerRPCCredentials(
newGRPCTokenAuth(authzServiceAudience, fmt.Sprintf("stacks-%s", cfg.StackID), tokenClient),
),
}
conn, err := grpc.NewClient(cfg.ZanzanaClient.Addr, dialOptions...)
@@ -244,27 +239,3 @@ func (z *Zanzana) stopping(err error) error {
}
return nil
}
type tokenAuth struct {
cfg *setting.Cfg
namespace string
tokenClient *authnlib.TokenExchangeClient
}
func (t *tokenAuth) GetRequestMetadata(ctx context.Context, _ ...string) (map[string]string, error) {
token, err := t.tokenClient.Exchange(ctx, authnlib.TokenExchangeRequest{
Namespace: t.namespace,
Audiences: []string{authzServiceAudience},
})
if err != nil {
return nil, err
}
return map[string]string{
authnlib.DefaultAccessTokenMetadataKey: token.Token,
}, nil
}
func (t *tokenAuth) RequireTransportSecurity() bool {
return false
}
+1
View File
@@ -123,6 +123,7 @@ func (_m *FakeDashboardStore) CountInOrg(ctx context.Context, orgID int64) (int6
return r0, r1
}
// DeleteAllDashboards provides a mock function with given fields: ctx, orgID
func (_m *FakeDashboardStore) DeleteAllDashboards(ctx context.Context, orgID int64) error {
ret := _m.Called(ctx, orgID)
+17 -16
View File
@@ -658,14 +658,6 @@ var (
Owner: grafanaDatavizSquad,
Expression: "true", // enabled by default
},
{
Name: "transformationsVariableSupport",
Description: "Allows using variables in transformations",
FrontendOnly: true,
Stage: FeatureStageGeneralAvailability,
Owner: grafanaDatavizSquad,
Expression: "true", // Enabled by default
},
{
Name: "kubernetesPlaylists",
Description: "Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s",
@@ -773,14 +765,6 @@ var (
Owner: grafanaOperatorExperienceSquad,
FrontendOnly: false,
},
{
Name: "managedPluginsInstall",
Description: "Install managed plugins directly from plugins catalog",
Stage: FeatureStageGeneralAvailability,
RequiresDevMode: false,
Owner: grafanaPluginsPlatformSquad,
Expression: "true", // enabled by default
},
{
Name: "prometheusPromQAIL",
Description: "Prometheus and AI/ML to assist users in creating a query",
@@ -1817,6 +1801,23 @@ var (
FrontendOnly: true,
HideFromDocs: true,
},
{
Name: "alertingRuleVersionHistoryRestore",
Description: "Enables the alert rule version history restore feature",
FrontendOnly: true,
Stage: FeatureStageExperimental,
Owner: grafanaAlertingSquad,
HideFromAdminPage: true,
HideFromDocs: true,
},
{
Name: "newShareReportDrawer",
Description: "Enables the report creation drawer in a dashboard",
Stage: FeatureStageExperimental,
Owner: grafanaSharingSquad,
HideFromAdminPage: true,
HideFromDocs: true,
},
}
)
+2 -2
View File
@@ -86,7 +86,6 @@ panelMonitoring,GA,@grafana/dataviz-squad,false,false,true
enableNativeHTTPHistogram,experimental,@grafana/grafana-backend-services-squad,false,true,false
disableClassicHTTPHistogram,experimental,@grafana/grafana-backend-services-squad,false,true,false
formatString,GA,@grafana/dataviz-squad,false,false,true
transformationsVariableSupport,GA,@grafana/dataviz-squad,false,false,true
kubernetesPlaylists,GA,@grafana/grafana-app-platform-squad,false,true,false
kubernetesSnapshots,experimental,@grafana/grafana-app-platform-squad,false,true,false
kubernetesDashboards,experimental,@grafana/grafana-app-platform-squad,false,false,true
@@ -102,7 +101,6 @@ recoveryThreshold,GA,@grafana/alerting-squad,false,true,false
lokiStructuredMetadata,GA,@grafana/observability-logs,false,false,false
teamHttpHeaders,preview,@grafana/identity-access-team,false,false,false
cachingOptimizeSerializationMemoryUsage,experimental,@grafana/grafana-operator-experience-squad,false,false,false
managedPluginsInstall,GA,@grafana/plugins-platform-backend,false,false,false
prometheusPromQAIL,experimental,@grafana/oss-big-tent,false,false,true
prometheusCodeModeMetricNamesSearch,experimental,@grafana/oss-big-tent,false,false,true
addFieldFromCalculationStatFunctions,GA,@grafana/dataviz-squad,false,false,true
@@ -241,3 +239,5 @@ newLogsPanel,experimental,@grafana/observability-logs,false,false,true
grafanaconThemes,experimental,@grafana/grafana-frontend-platform,false,true,false
pluginsCDNSyncLoader,experimental,@grafana/plugins-platform-backend,false,false,false
alertingJiraIntegration,experimental,@grafana/alerting-squad,false,false,true
alertingRuleVersionHistoryRestore,experimental,@grafana/alerting-squad,false,false,true
newShareReportDrawer,experimental,@grafana/sharing-squad,false,false,false
1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
86 enableNativeHTTPHistogram experimental @grafana/grafana-backend-services-squad false true false
87 disableClassicHTTPHistogram experimental @grafana/grafana-backend-services-squad false true false
88 formatString GA @grafana/dataviz-squad false false true
transformationsVariableSupport GA @grafana/dataviz-squad false false true
89 kubernetesPlaylists GA @grafana/grafana-app-platform-squad false true false
90 kubernetesSnapshots experimental @grafana/grafana-app-platform-squad false true false
91 kubernetesDashboards experimental @grafana/grafana-app-platform-squad false false true
101 lokiStructuredMetadata GA @grafana/observability-logs false false false
102 teamHttpHeaders preview @grafana/identity-access-team false false false
103 cachingOptimizeSerializationMemoryUsage experimental @grafana/grafana-operator-experience-squad false false false
managedPluginsInstall GA @grafana/plugins-platform-backend false false false
104 prometheusPromQAIL experimental @grafana/oss-big-tent false false true
105 prometheusCodeModeMetricNamesSearch experimental @grafana/oss-big-tent false false true
106 addFieldFromCalculationStatFunctions GA @grafana/dataviz-squad false false true
239 grafanaconThemes experimental @grafana/grafana-frontend-platform false true false
240 pluginsCDNSyncLoader experimental @grafana/plugins-platform-backend false false false
241 alertingJiraIntegration experimental @grafana/alerting-squad false false true
242 alertingRuleVersionHistoryRestore experimental @grafana/alerting-squad false false true
243 newShareReportDrawer experimental @grafana/sharing-squad false false false
+8 -8
View File
@@ -355,10 +355,6 @@ const (
// Enable format string transformer
FlagFormatString = "formatString"
// FlagTransformationsVariableSupport
// Allows using variables in transformations
FlagTransformationsVariableSupport = "transformationsVariableSupport"
// FlagKubernetesPlaylists
// Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s
FlagKubernetesPlaylists = "kubernetesPlaylists"
@@ -419,10 +415,6 @@ const (
// If enabled, the caching backend gradually serializes query responses for the cache, comparing against the configured `[caching]max_value_mb` value as it goes. This can can help prevent Grafana from running out of memory while attempting to cache very large query responses.
FlagCachingOptimizeSerializationMemoryUsage = "cachingOptimizeSerializationMemoryUsage"
// FlagManagedPluginsInstall
// Install managed plugins directly from plugins catalog
FlagManagedPluginsInstall = "managedPluginsInstall"
// FlagPrometheusPromQAIL
// Prometheus and AI/ML to assist users in creating a query
FlagPrometheusPromQAIL = "prometheusPromQAIL"
@@ -974,4 +966,12 @@ const (
// FlagAlertingJiraIntegration
// Enables the new Jira integration for contact points in cloud alert managers.
FlagAlertingJiraIntegration = "alertingJiraIntegration"
// FlagAlertingRuleVersionHistoryRestore
// Enables the alert rule version history restore feature
FlagAlertingRuleVersionHistoryRestore = "alertingRuleVersionHistoryRestore"
// FlagNewShareReportDrawer
// Enables the report creation drawer in a dashboard
FlagNewShareReportDrawer = "newShareReportDrawer"
)
Loaded 100 of 187 files, more files were not shown because too many files have changed in this diff. Show more