diff --git a/.citools/src/bra/go.mod b/.citools/src/bra/go.mod index 8a75cdfef83..b37f09f62f9 100644 --- a/.citools/src/bra/go.mod +++ b/.citools/src/bra/go.mod @@ -1,6 +1,6 @@ module bra -go 1.24.3 +go 1.24.4 tool github.com/unknwon/bra diff --git a/.citools/src/cog/go.mod b/.citools/src/cog/go.mod index 1258685195e..44a7a920f35 100644 --- a/.citools/src/cog/go.mod +++ b/.citools/src/cog/go.mod @@ -1,6 +1,6 @@ module cog -go 1.24.3 +go 1.24.4 tool github.com/grafana/cog/cmd/cli diff --git a/.citools/src/cue/go.mod b/.citools/src/cue/go.mod index 31cb9301000..f59e9af9ff6 100644 --- a/.citools/src/cue/go.mod +++ b/.citools/src/cue/go.mod @@ -1,6 +1,6 @@ module cue -go 1.24.3 +go 1.24.4 tool cuelang.org/go/cmd/cue diff --git a/.citools/src/golangci-lint/go.mod b/.citools/src/golangci-lint/go.mod index 9f2b7babff3..bdc3e9bba79 100644 --- a/.citools/src/golangci-lint/go.mod +++ b/.citools/src/golangci-lint/go.mod @@ -1,6 +1,6 @@ module golangci-lint -go 1.24.3 +go 1.24.4 tool github.com/golangci/golangci-lint/v2/cmd/golangci-lint diff --git a/.citools/src/jb/go.mod b/.citools/src/jb/go.mod index 9e0ba331ae7..eec18890a50 100644 --- a/.citools/src/jb/go.mod +++ b/.citools/src/jb/go.mod @@ -1,6 +1,6 @@ module jb -go 1.24.3 +go 1.24.4 tool github.com/jsonnet-bundler/jsonnet-bundler/cmd/jb diff --git a/.citools/src/lefthook/go.mod b/.citools/src/lefthook/go.mod index 2cc1154daf6..85e4a8e5c14 100644 --- a/.citools/src/lefthook/go.mod +++ b/.citools/src/lefthook/go.mod @@ -1,6 +1,6 @@ module lefthook -go 1.24.3 +go 1.24.4 tool github.com/evilmartians/lefthook diff --git a/.citools/src/swagger/go.mod b/.citools/src/swagger/go.mod index 5c4f8438646..33ca8159a8f 100644 --- a/.citools/src/swagger/go.mod +++ b/.citools/src/swagger/go.mod @@ -1,6 +1,6 @@ module swagger -go 1.24.3 +go 1.24.4 tool github.com/go-swagger/go-swagger/cmd/swagger diff --git a/.drone.yml b/.drone.yml index 298efd04173..40e64d7e17f 100644 --- a/.drone.yml +++ b/.drone.yml @@ -25,7 +25,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: compile-build-cmd - commands: - ./bin/build verify-drone @@ -75,7 +75,7 @@ steps: - go install github.com/bazelbuild/buildtools/buildifier@latest - buildifier --lint=warn -mode=check -r . depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: lint-starlark trigger: event: @@ -219,7 +219,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -228,21 +228,21 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: wire-install - commands: - apk add --update build-base shared-mime-info shared-mime-info-lang - go list -f '{{.Dir}}/...' -m | xargs go test -short -covermode=atomic -timeout=5m depends_on: - wire-install - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: test-backend - commands: - apk add --update build-base @@ -251,7 +251,7 @@ steps: | grep -o '\(.*\)/' | sort -u) depends_on: - wire-install - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: test-backend-integration trigger: event: @@ -306,7 +306,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: compile-build-cmd - commands: - echo $(/usr/bin/github-app-external-token) > /github-app/token @@ -351,16 +351,16 @@ steps: - apk add --update make - make gen-go depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: wire-install - commands: - go run scripts/modowners/modowners.go check go.mod - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: validate-modfile - commands: - apk add --update make - make swagger-validate - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: validate-openapi-spec trigger: event: @@ -437,7 +437,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: compile-build-cmd - commands: - '# It is required that code generated from Thema/CUE be committed and in sync @@ -447,7 +447,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -456,7 +456,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: verify-gen-jsonnet - commands: - yarn install --immutable || yarn install --immutable @@ -507,7 +507,7 @@ steps: environment: _EXPERIMENTAL_DAGGER_CLOUD_TOKEN: from_secret: dagger_token - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: rgm-package pull: always volumes: @@ -892,7 +892,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: compile-build-cmd - commands: - echo $DRONE_RUNNER_NAME @@ -906,7 +906,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -915,14 +915,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: wire-install - commands: - dockerize -wait tcp://postgres:5432 -timeout 120s @@ -943,7 +943,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: postgres-integration-tests - commands: - dockerize -wait tcp://mysql80:3306 -timeout 120s @@ -964,7 +964,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql80 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: mysql-8.0-integration-tests - commands: - dockerize -wait tcp://redis:6379 -timeout 120s @@ -980,7 +980,7 @@ steps: - wait-for-redis environment: REDIS_URL: redis://redis:6379/0 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: redis-integration-tests - commands: - dockerize -wait tcp://memcached:11211 -timeout 120s @@ -996,7 +996,7 @@ steps: - wait-for-memcached environment: MEMCACHED_HOSTS: memcached:11211 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: memcached-integration-tests - commands: - dockerize -wait tcp://mimir_backend:8080 -timeout 120s @@ -1012,7 +1012,7 @@ steps: environment: AM_TENANT_ID: test AM_URL: http://mimir_backend:8080 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: remote-alertmanager-integration-tests trigger: event: @@ -1094,7 +1094,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: verify-gen-cue trigger: event: @@ -1215,7 +1215,7 @@ steps: && return 1; fi depends_on: - clone-enterprise - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: swagger-gen trigger: event: @@ -1320,7 +1320,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: compile-build-cmd - commands: - '# It is required that code generated from Thema/CUE be committed and in sync @@ -1331,7 +1331,7 @@ steps: - CODEGEN_VERIFY=1 make gen-cue depends_on: - clone-enterprise - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -1341,14 +1341,14 @@ steps: - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: - clone-enterprise - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: wire-install - commands: - apk add --update build-base @@ -1356,7 +1356,7 @@ steps: - go test -v -run=^$ -benchmem -timeout=1h -count=8 -bench=. ${GO_PACKAGES} depends_on: - wire-install - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: sqlite-benchmark-integration-tests - commands: - apk add --update build-base @@ -1368,7 +1368,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: postgres-benchmark-integration-tests - commands: - apk add --update build-base @@ -1379,7 +1379,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql80 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: mysql-8.0-benchmark-integration-tests trigger: event: @@ -1451,7 +1451,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: verify-gen-cue trigger: branch: main @@ -1500,7 +1500,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -1509,21 +1509,21 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: wire-install - commands: - apk add --update build-base shared-mime-info shared-mime-info-lang - go list -f '{{.Dir}}/...' -m | xargs go test -short -covermode=atomic -timeout=5m depends_on: - wire-install - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: test-backend - commands: - apk add --update build-base @@ -1532,7 +1532,7 @@ steps: | grep -o '\(.*\)/' | sort -u) depends_on: - wire-install - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: test-backend-integration trigger: branch: main @@ -1577,22 +1577,22 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: compile-build-cmd - commands: - apk add --update make - make gen-go depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: wire-install - commands: - go run scripts/modowners/modowners.go check go.mod - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: validate-modfile - commands: - apk add --update make - make swagger-validate - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: validate-openapi-spec - commands: - ./bin/build verify-drone @@ -1724,7 +1724,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: compile-build-cmd - commands: - '# It is required that code generated from Thema/CUE be committed and in sync @@ -1734,7 +1734,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -1743,7 +1743,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: verify-gen-jsonnet - commands: - yarn install --immutable || yarn install --immutable @@ -1793,7 +1793,7 @@ steps: environment: _EXPERIMENTAL_DAGGER_CLOUD_TOKEN: from_secret: dagger_token - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: rgm-package pull: always volumes: @@ -2253,7 +2253,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: compile-build-cmd - commands: - echo $DRONE_RUNNER_NAME @@ -2267,7 +2267,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -2276,14 +2276,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: wire-install - commands: - dockerize -wait tcp://postgres:5432 -timeout 120s @@ -2304,7 +2304,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: postgres-integration-tests - commands: - dockerize -wait tcp://mysql80:3306 -timeout 120s @@ -2325,7 +2325,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql80 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: mysql-8.0-integration-tests - commands: - dockerize -wait tcp://redis:6379 -timeout 120s @@ -2341,7 +2341,7 @@ steps: - wait-for-redis environment: REDIS_URL: redis://redis:6379/0 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: redis-integration-tests - commands: - dockerize -wait tcp://memcached:11211 -timeout 120s @@ -2357,7 +2357,7 @@ steps: - wait-for-memcached environment: MEMCACHED_HOSTS: memcached:11211 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: memcached-integration-tests - commands: - dockerize -wait tcp://mimir_backend:8080 -timeout 120s @@ -2373,7 +2373,7 @@ steps: environment: AM_TENANT_ID: test AM_URL: http://mimir_backend:8080 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: remote-alertmanager-integration-tests trigger: branch: main @@ -2641,7 +2641,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -2650,21 +2650,21 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: wire-install - commands: - apk add --update build-base shared-mime-info shared-mime-info-lang - go list -f '{{.Dir}}/...' -m | xargs go test -short -covermode=atomic -timeout=5m depends_on: - wire-install - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: test-backend - commands: - apk add --update build-base @@ -2673,7 +2673,7 @@ steps: | grep -o '\(.*\)/' | sort -u) depends_on: - wire-install - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: test-backend-integration trigger: branch: @@ -2716,22 +2716,22 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: compile-build-cmd - commands: - apk add --update make - make gen-go depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: wire-install - commands: - go run scripts/modowners/modowners.go check go.mod - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: validate-modfile - commands: - apk add --update make - make swagger-validate - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: validate-openapi-spec trigger: branch: @@ -2810,7 +2810,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: compile-build-cmd - commands: - echo $DRONE_RUNNER_NAME @@ -2824,7 +2824,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -2833,14 +2833,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: wire-install - commands: - dockerize -wait tcp://postgres:5432 -timeout 120s @@ -2861,7 +2861,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: postgres-integration-tests - commands: - dockerize -wait tcp://mysql80:3306 -timeout 120s @@ -2882,7 +2882,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql80 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: mysql-8.0-integration-tests - commands: - dockerize -wait tcp://redis:6379 -timeout 120s @@ -2898,7 +2898,7 @@ steps: - wait-for-redis environment: REDIS_URL: redis://redis:6379/0 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: redis-integration-tests - commands: - dockerize -wait tcp://memcached:11211 -timeout 120s @@ -2914,7 +2914,7 @@ steps: - wait-for-memcached environment: MEMCACHED_HOSTS: memcached:11211 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: memcached-integration-tests - commands: - dockerize -wait tcp://mimir_backend:8080 -timeout 120s @@ -2930,7 +2930,7 @@ steps: environment: AM_TENANT_ID: test AM_URL: http://mimir_backend:8080 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: remote-alertmanager-integration-tests trigger: branch: @@ -3030,7 +3030,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: compile-build-cmd - commands: - ./bin/build artifacts docker fetch --edition oss @@ -3160,7 +3160,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: compile-build-cmd - commands: - ./bin/build artifacts docker fetch --edition oss @@ -3301,7 +3301,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: compile-build-cmd - commands: - ./bin/build artifacts packages --artifacts-editions=oss --tag $${DRONE_TAG} --src-bucket @@ -3393,7 +3393,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: compile-build-cmd - commands: - yarn install --immutable || yarn install --immutable @@ -3493,7 +3493,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: compile-build-cmd - depends_on: - compile-build-cmd @@ -3590,7 +3590,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: compile-build-cmd - commands: - ./bin/build publish grafana-com --edition oss ${DRONE_TAG} @@ -3665,7 +3665,7 @@ steps: STORYBOOK_DESTINATION: from_secret: rgm_storybook_destination UBUNTU_BASE: ubuntu:22.04 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: rgm-build pull: always volumes: @@ -3742,7 +3742,7 @@ steps: STORYBOOK_DESTINATION: from_secret: rgm_storybook_destination UBUNTU_BASE: ubuntu:22.04 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: rgm-build pull: always volumes: @@ -3861,7 +3861,7 @@ steps: STORYBOOK_DESTINATION: from_secret: rgm_storybook_destination UBUNTU_BASE: ubuntu:22.04 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: rgm-build pull: always volumes: @@ -4000,7 +4000,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -4009,21 +4009,21 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: wire-install - commands: - apk add --update build-base shared-mime-info shared-mime-info-lang - go list -f '{{.Dir}}/...' -m | xargs go test -short -covermode=atomic -timeout=5m depends_on: - wire-install - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: test-backend - commands: - apk add --update build-base @@ -4032,7 +4032,7 @@ steps: | grep -o '\(.*\)/' | sort -u) depends_on: - wire-install - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: test-backend-integration trigger: cron: @@ -4100,7 +4100,7 @@ steps: STORYBOOK_DESTINATION: from_secret: rgm_storybook_destination UBUNTU_BASE: ubuntu:22.04 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: rgm-build pull: always volumes: @@ -4246,7 +4246,7 @@ steps: STORYBOOK_DESTINATION: from_secret: rgm_storybook_destination UBUNTU_BASE: ubuntu:22.04 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: rgm-publish pull: always volumes: @@ -4376,7 +4376,7 @@ steps: STORYBOOK_DESTINATION: from_secret: rgm_storybook_destination UBUNTU_BASE: ubuntu:22.04 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: rgm-build pull: always volumes: @@ -4502,7 +4502,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -4511,14 +4511,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: wire-install - commands: - dockerize -wait tcp://postgres:5432 -timeout 120s @@ -4539,7 +4539,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: postgres-integration-tests - commands: - dockerize -wait tcp://mysql80:3306 -timeout 120s @@ -4560,7 +4560,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql80 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: mysql-8.0-integration-tests - commands: - dockerize -wait tcp://redis:6379 -timeout 120s @@ -4576,7 +4576,7 @@ steps: - wait-for-redis environment: REDIS_URL: redis://redis:6379/0 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: redis-integration-tests - commands: - dockerize -wait tcp://memcached:11211 -timeout 120s @@ -4592,7 +4592,7 @@ steps: - wait-for-memcached environment: MEMCACHED_HOSTS: memcached:11211 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: memcached-integration-tests - commands: - dockerize -wait tcp://mimir_backend:8080 -timeout 120s @@ -4608,7 +4608,7 @@ steps: environment: AM_TENANT_ID: test AM_URL: http://mimir_backend:8080 - image: golang:1.24.3-alpine + image: golang:1.24.4-alpine name: remote-alertmanager-integration-tests trigger: event: @@ -4911,7 +4911,7 @@ steps: - commands: - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM docker:27-cli - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM alpine/git:2.40.1 - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM golang:1.24.3-alpine + - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM golang:1.24.4-alpine - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM node:22.11.0-alpine - 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 @@ -4949,7 +4949,7 @@ steps: - commands: - trivy --exit-code 1 --severity HIGH,CRITICAL docker:27-cli - trivy --exit-code 1 --severity HIGH,CRITICAL alpine/git:2.40.1 - - trivy --exit-code 1 --severity HIGH,CRITICAL golang:1.24.3-alpine + - trivy --exit-code 1 --severity HIGH,CRITICAL golang:1.24.4-alpine - trivy --exit-code 1 --severity HIGH,CRITICAL node:22.11.0-alpine - trivy --exit-code 1 --severity HIGH,CRITICAL node:22-bookworm - trivy --exit-code 1 --severity HIGH,CRITICAL google/cloud-sdk:431.0.0 @@ -5206,6 +5206,6 @@ kind: secret name: gcr_credentials --- kind: signature -hmac: e430c51190ba46dbd426d13f47c13c275f4d05b6b1a26d1d9f67b5a8f09ecb41 +hmac: 7f318d682f431d61add0d60afcb9360f390217961709adeb88c34f11feec35b0 ... diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1c198645369..cde4501d4b2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -499,28 +499,28 @@ playwright.config.ts @grafana/plugins-platform-frontend /public/app/features/canvas/ @grafana/dataviz-squad /public/app/features/geo/ @grafana/dataviz-squad /public/app/features/visualization/data-hover/ @grafana/dataviz-squad -/public/app/features/commandPalette/ @grafana/grafana-frontend-platform +/public/app/features/commandPalette/ @grafana/grafana-search-navigate-organise /public/app/features/connections/ @grafana/plugins-platform-frontend /public/app/features/correlations/ @grafana/datapro /public/app/features/dashboard/ @grafana/dashboards-squad /public/app/features/dashboard/components/TransformationsEditor/ @grafana/datapro /public/app/features/dashboard-scene/ @grafana/dashboards-squad -/public/app/features/scopes/ @grafana/dashboards-squad +/public/app/features/scopes/ @grafana/grafana-operator-experience-squad /public/app/features/datasources/ @grafana/plugins-platform-frontend /public/app/features/dimensions/ @grafana/dataviz-squad /public/app/features/dataframe-import/ @grafana/dataviz-squad /public/app/features/explore/ @grafana/observability-traces-and-profiling /public/app/features/expressions/ @grafana/grafana-datasources-core-services -/public/app/features/folders/ @grafana/grafana-frontend-platform +/public/app/features/folders/ @grafana/grafana-search-navigate-organise /public/app/features/inspector/ @grafana/dashboards-squad -/public/app/features/invites/ @grafana/grafana-frontend-platform +/public/app/features/invites/ @grafana/sharing-squad /public/app/features/library-panels/ @grafana/dashboards-squad /public/app/features/logs/ @grafana/observability-logs /public/app/features/live/ @grafana/dashboards-squad /public/app/features/apiserver/ @grafana/grafana-app-platform-squad /public/app/features/manage-dashboards/ @grafana/dashboards-squad -/public/app/features/notifications/ @grafana/grafana-frontend-platform -/public/app/features/org/ @grafana/grafana-frontend-platform +/public/app/features/notifications/ @grafana/grafana-search-navigate-organise +/public/app/features/org/ @grafana/grafana-search-navigate-organise /public/app/features/panel/ @grafana/dashboards-squad /public/app/features/playlist/ @grafana/dashboards-squad /public/app/features/plugins/ @grafana/plugins-platform-frontend @@ -528,8 +528,8 @@ playwright.config.ts @grafana/plugins-platform-frontend /public/app/features/runtime/ @ryantxu /public/app/features/query/ @grafana/dashboards-squad /public/app/features/sandbox/ @grafana/grafana-frontend-platform -/public/app/features/browse-dashboards/ @grafana/grafana-frontend-platform -/public/app/features/search/ @grafana/grafana-frontend-platform +/public/app/features/browse-dashboards/ @grafana/grafana-search-navigate-organise +/public/app/features/search/ @grafana/grafana-search-navigate-organise /public/app/features/serviceaccounts/ @grafana/identity-squad /public/app/features/teams/ @grafana/access-squad /public/app/features/templating/ @grafana/dashboards-squad @@ -539,16 +539,16 @@ playwright.config.ts @grafana/plugins-platform-frontend /public/app/features/users/ @grafana/access-squad /public/app/features/variables/ @grafana/dashboards-squad /public/app/features/preferences/ @grafana/grafana-frontend-platform -/public/app/features/bookmarks/ @grafana/grafana-frontend-platform +/public/app/features/bookmarks/ @grafana/grafana-search-navigate-organise /public/app/plugins/panel/alertlist/ @grafana/alerting-frontend -/public/app/plugins/panel/annolist/ @grafana/grafana-frontend-platform +/public/app/plugins/panel/annolist/ @grafana/dashboards-squad /public/app/plugins/panel/barchart/ @grafana/dataviz-squad /public/app/plugins/panel/bargauge/ @grafana/dataviz-squad -/public/app/plugins/panel/dashlist/ @grafana/grafana-frontend-platform +/public/app/plugins/panel/dashlist/ @grafana/grafana-search-navigate-organise /public/app/plugins/panel/debug/ @ryantxu /public/app/plugins/panel/datagrid/ @grafana/dataviz-squad /public/app/plugins/panel/gauge/ @grafana/dataviz-squad -/public/app/plugins/panel/gettingstarted/ @grafana/grafana-frontend-platform +/public/app/plugins/panel/gettingstarted/ @grafana/grafana-search-navigate-organise /public/app/plugins/panel/heatmap/ @grafana/dataviz-squad /public/app/plugins/panel/histogram/ @grafana/dataviz-squad /public/app/plugins/panel/logs/ @grafana/observability-logs @@ -567,12 +567,12 @@ playwright.config.ts @grafana/plugins-platform-frontend /public/app/plugins/panel/canvas/ @grafana/dataviz-squad /public/app/plugins/panel/candlestick/ @grafana/dataviz-squad /public/app/plugins/panel/live/ @grafana/dashboards-squad -/public/app/plugins/panel/news/ @grafana/grafana-frontend-platform +/public/app/plugins/panel/news/ @grafana/dataviz-squad /public/app/plugins/panel/stat/ @grafana/dataviz-squad -/public/app/plugins/panel/text/ @grafana/grafana-frontend-platform -/public/app/plugins/panel/welcome/ @grafana/grafana-frontend-platform +/public/app/plugins/panel/text/ @grafana/dataviz-squad +/public/app/plugins/panel/welcome/ @grafana/grafana-search-navigate-organise /public/app/plugins/panel/xychart/ @grafana/dataviz-squad -/public/app/routes/ @grafana/grafana-frontend-platform +/public/app/routes/ @grafana/grafana-search-navigate-organise /public/app/store/ @grafana/grafana-frontend-platform /public/app/types/ @grafana/grafana-frontend-platform /public/app/types/alerting.ts @grafana/alerting-frontend @@ -600,7 +600,6 @@ playwright.config.ts @grafana/plugins-platform-frontend /public/app/features/explore/NodeGraph/ @grafana/observability-traces-and-profiling /public/app/features/explore/FlameGraph/ @grafana/observability-traces-and-profiling /public/app/features/explore/TraceView/ @grafana/observability-traces-and-profiling -/public/app/features/explore/QueryLibrary/ @grafana/grafana-frontend-platform /public/api-merged.json @grafana/grafana-backend-group /public/api-enterprise-spec.json @grafana/grafana-backend-group @@ -642,7 +641,7 @@ playwright.config.ts @grafana/plugins-platform-frontend /scripts/levitate-parse-json-report.js @grafana/plugins-platform-frontend /scripts/levitate-show-affected-plugins.js @grafana/plugins-platform-frontend /scripts/codemods/explicit-barrel-imports.cjs @grafana/frontend-ops -/scripts/rtk-client-generator/ @grafana/grafana-frontend-platform +/scripts/rtk-client-generator/ @grafana/grafana-search-navigate-organise /scripts/**/generate-transformations* @grafana/datapro /scripts/webpack/ @grafana/frontend-ops @@ -689,6 +688,7 @@ playwright.config.ts @grafana/plugins-platform-frontend /pkg/services/dashboardsnapshots/ @grafana/sharing-squad /pkg/services/publicdashboards/ @grafana/sharing-squad /pkg/services/rendering/ @grafana/sharing-squad +/public/app/features/explore/QueryLibrary/ @grafana/sharing-squad # SSE - Server Side Expressions /pkg/expr/ @grafana/grafana-datasources-core-services diff --git a/Dockerfile b/Dockerfile index 966d1753ad3..e7f06079c7e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ ARG BASE_IMAGE=alpine:3.21 ARG JS_IMAGE=node:22-alpine ARG JS_PLATFORM=linux/amd64 -ARG GO_IMAGE=golang:1.24.3-alpine +ARG GO_IMAGE=golang:1.24.4-alpine # Default to building locally ARG GO_SRC=go-builder diff --git a/Makefile b/Makefile index 9be96ca3248..469706de274 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ include .bingo/Variables.mk include .citools/Variables.mk GO = go -GO_VERSION = 1.24.3 +GO_VERSION = 1.24.4 GO_LINT_FILES ?= $(shell ./scripts/go-workspace/golangci-lint-includes.sh) GO_TEST_FILES ?= $(shell ./scripts/go-workspace/test-includes.sh) SH_FILES ?= $(shell find ./scripts -name *.sh) diff --git a/apps/advisor/go.mod b/apps/advisor/go.mod index ef3c2710ba5..1045673d51f 100644 --- a/apps/advisor/go.mod +++ b/apps/advisor/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/apps/advisor -go 1.24.3 +go 1.24.4 require ( github.com/grafana/grafana-app-sdk v0.38.2 diff --git a/apps/advisor/pkg/app/checkregistry/checkregistry.go b/apps/advisor/pkg/app/checkregistry/checkregistry.go index 9fbf0ab66ef..1ec5f075fc7 100644 --- a/apps/advisor/pkg/app/checkregistry/checkregistry.go +++ b/apps/advisor/pkg/app/checkregistry/checkregistry.go @@ -5,6 +5,7 @@ import ( "github.com/grafana/grafana/apps/advisor/pkg/app/checks/authchecks" "github.com/grafana/grafana/apps/advisor/pkg/app/checks/configchecks" "github.com/grafana/grafana/apps/advisor/pkg/app/checks/datasourcecheck" + "github.com/grafana/grafana/apps/advisor/pkg/app/checks/instancechecks" "github.com/grafana/grafana/apps/advisor/pkg/app/checks/plugincheck" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/repo" @@ -81,6 +82,7 @@ func (s *Service) Checks() []checks.Check { ), authchecks.New(s.ssoSettingsSvc), configchecks.New(s.cfg), + instancechecks.New(s.cfg), } } diff --git a/apps/advisor/pkg/app/checks/instancechecks/check.go b/apps/advisor/pkg/app/checks/instancechecks/check.go new file mode 100644 index 00000000000..34b606d1c37 --- /dev/null +++ b/apps/advisor/pkg/app/checks/instancechecks/check.go @@ -0,0 +1,72 @@ +package instancechecks + +import ( + "context" + "time" + + "github.com/google/go-github/v70/github" + "github.com/grafana/grafana/apps/advisor/pkg/app/checks" + "github.com/grafana/grafana/pkg/setting" +) + +var _ checks.Check = (*check)(nil) + +type check struct { + cfg *setting.Cfg + isCloudInstance bool +} + +func New(cfg *setting.Cfg) checks.Check { + return &check{ + cfg: cfg, + isCloudInstance: cfg.StackID != "", + } +} + +func (c *check) ID() string { + return "instance" +} + +func (c *check) Name() string { + return "instance attribute" +} + +func (c *check) Items(ctx context.Context) ([]any, error) { + if c.isCloudInstance { + return []any{ + pinnedVersion, // pinned version check + }, nil + } + + return []any{ + outOfSupportVersion, // out of support version check + }, nil +} + +func (c *check) Item(ctx context.Context, id string) (any, error) { + return id, nil +} + +func (c *check) Init(ctx context.Context) error { + return nil +} + +func (c *check) Steps() []checks.Step { + // If running in cloud, we need to check if the version is pinned + if c.isCloudInstance { + return []checks.Step{ + &pinnedVersionStep{ + BuildBranch: c.cfg.BuildBranch, + }, + } + } + + // If running in self-managed, we need to check if the version is out of support + return []checks.Step{ + &outOfSupportVersionStep{ + GrafanaVersion: c.cfg.BuildVersion, + BuildDate: time.Unix(c.cfg.BuildStamp, 0).UTC(), + ghClient: github.NewClient(nil).Repositories, + }, + } +} diff --git a/apps/advisor/pkg/app/checks/instancechecks/check_test.go b/apps/advisor/pkg/app/checks/instancechecks/check_test.go new file mode 100644 index 00000000000..00c38c4b0a5 --- /dev/null +++ b/apps/advisor/pkg/app/checks/instancechecks/check_test.go @@ -0,0 +1,63 @@ +package instancechecks + +import ( + "context" + "testing" + "time" + + "github.com/grafana/grafana/pkg/setting" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCheck_IsCloudInstance_Logic(t *testing.T) { + tests := []struct { + name string + stackID string + expectedCloud bool + expectedStepID string + expectedItemID string + }{ + { + name: "empty stackID should run out of support check", + stackID: "", + expectedCloud: false, + expectedStepID: outOfSupportVersion, + expectedItemID: outOfSupportVersion, + }, + { + name: "non-empty stackID should run pinned version check", + stackID: "12345", + expectedCloud: true, + expectedStepID: pinnedVersion, + expectedItemID: pinnedVersion, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := &setting.Cfg{ + StackID: tt.stackID, + BuildBranch: "v10.0.0", + BuildVersion: "10.0.0", + BuildStamp: time.Now().Unix(), + } + + check := New(cfg).(*check) + + // Verify isCloudInstance field is set correctly + assert.Equal(t, tt.expectedCloud, check.isCloudInstance) + + // Verify Steps() returns the correct step type + steps := check.Steps() + require.Len(t, steps, 1) + assert.Equal(t, tt.expectedStepID, steps[0].ID()) + + // Verify Items() returns the correct item type + items, err := check.Items(context.Background()) + require.NoError(t, err) + require.Len(t, items, 1) + assert.Equal(t, tt.expectedItemID, items[0]) + }) + } +} diff --git a/apps/advisor/pkg/app/checks/instancechecks/out_of_support_step.go b/apps/advisor/pkg/app/checks/instancechecks/out_of_support_step.go new file mode 100644 index 00000000000..5a15d775eb0 --- /dev/null +++ b/apps/advisor/pkg/app/checks/instancechecks/out_of_support_step.go @@ -0,0 +1,122 @@ +package instancechecks + +import ( + "context" + "fmt" + "time" + + "github.com/Masterminds/semver/v3" + "github.com/google/go-github/v70/github" + "github.com/grafana/grafana-app-sdk/logging" + advisor "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1" + "github.com/grafana/grafana/apps/advisor/pkg/app/checks" +) + +var _ checks.Step = &outOfSupportVersionStep{} + +const ( + outOfSupportVersion = "out_of_support_version" +) + +type outOfSupportVersionStep struct { + GrafanaVersion string + BuildDate time.Time + ghClient gitHubClient +} + +func (s *outOfSupportVersionStep) Title() string { + return "Grafana version check" +} + +func (s *outOfSupportVersionStep) Description() string { + return "Check if the current Grafana version is out of support." +} + +func (s *outOfSupportVersionStep) Resolution() string { + return "Out of support versions will not receive security updates or bug fixes. " + + "Upgrade to a more recent version. " + + "" + + "Learn more about version support." +} + +func (s *outOfSupportVersionStep) ID() string { + return outOfSupportVersion +} + +func (s *outOfSupportVersionStep) Run(ctx context.Context, log logging.Logger, _ *advisor.CheckSpec, it any) ([]advisor.CheckReportFailure, error) { + item, ok := it.(string) + if !ok { + return nil, fmt.Errorf("invalid item type %T", it) + } + if item != outOfSupportVersion { + // Not interested in this item + return nil, nil + } + + // If the build date is less than 9 months old, it's supported + if s.BuildDate.After(time.Now().AddDate(0, -9, 0)) { + return nil, nil + } + + // If the build date is older than 15 months, it's not supported + isOutOfSupport := false + if s.BuildDate.Before(time.Now().AddDate(0, -15, 0)) { + isOutOfSupport = true + } else { + // In other cases, we need to check if the version is out of support. + // Minor versions are generally supported for 9 months but the last + // minor version for a major version is supported for 15 months. + // This is the case when we keep doing releases for old minor versions (e.g. 11.x when 12.x is out). + // https://grafana.com/docs/grafana/latest/upgrade-guide/when-to-upgrade/#what-to-know-about-version-support + + // Parse the current version using semver + version, err := semver.NewVersion(s.GrafanaVersion) + if err != nil { + // Unable to parse the version so unable to check if it's out of support + log.Error("Unable to parse the version", "version", s.GrafanaVersion, "error", err) + return nil, nil + } + + // To verify if the current version is the latest minor version for this major version, + // we try to get the version vX.Y+1.0 from GitHub + nextMinorVersion := fmt.Sprintf("v%d.%d.0", version.Major(), version.Minor()+1) + _, res, err := s.ghClient.GetReleaseByTag(ctx, "grafana", "grafana", nextMinorVersion) + if err != nil && res.StatusCode != 404 { + // Unable to get the release info so unable to check if it's out of support + log.Error("Unable to get the release info", "version", s.GrafanaVersion, "error", err.Error()) + return nil, nil + } + latestMinor := false + if res.StatusCode == 404 { + // No next minor version found, so the current version is the latest minor version + latestMinor = true + } + + // Calculate support end date + supportMonths := 9 + if latestMinor { + supportMonths = 15 // Extended support for last minor version + } + + supportEndDate := s.BuildDate.AddDate(0, supportMonths, 0) + if time.Now().After(supportEndDate) { + isOutOfSupport = true + } + } + + if isOutOfSupport { + return []advisor.CheckReportFailure{checks.NewCheckReportFailure( + advisor.CheckReportFailureSeverityHigh, + s.ID(), + fmt.Sprintf("Grafana version %s is out of support", s.GrafanaVersion), + outOfSupportVersion, + []advisor.CheckErrorLink{}, + )}, nil + } + return nil, nil +} + +// gitHubClient is a mockable interface for the GitHub client +type gitHubClient interface { + GetReleaseByTag(ctx context.Context, owner, repo, tag string) (*github.RepositoryRelease, *github.Response, error) +} diff --git a/apps/advisor/pkg/app/checks/instancechecks/out_of_support_step_test.go b/apps/advisor/pkg/app/checks/instancechecks/out_of_support_step_test.go new file mode 100644 index 00000000000..9785fbfd196 --- /dev/null +++ b/apps/advisor/pkg/app/checks/instancechecks/out_of_support_step_test.go @@ -0,0 +1,157 @@ +package instancechecks + +import ( + "context" + "net/http" + "testing" + "time" + + "github.com/google/go-github/v70/github" + "github.com/grafana/grafana-app-sdk/logging" + advisor "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestOutOfSupportVersionStep(t *testing.T) { + now := time.Now() + oldDate := now.AddDate(0, -16, 0) // 16 months ago + recentDate := now.AddDate(0, -8, 0) // 8 months ago + oldDateButSupportedIfLatestMinor := now.AddDate(0, -10, 0) // 10 months ago + + tests := []struct { + name string + step *outOfSupportVersionStep + input any + wantFailure bool + wantErr bool + }{ + { + name: "should return error for invalid input type", + step: &outOfSupportVersionStep{ + GrafanaVersion: "9.5.0", + BuildDate: now, + ghClient: &mockGitHubClient{}, + }, + input: 123, // invalid type + wantErr: true, + }, + { + name: "should return nil for non-out-of-support item", + step: &outOfSupportVersionStep{ + GrafanaVersion: "9.5.0", + BuildDate: now, + ghClient: &mockGitHubClient{}, + }, + input: "other_item", + wantFailure: false, + wantErr: false, + }, + { + name: "should return nil for recent build date", + step: &outOfSupportVersionStep{ + GrafanaVersion: "11.5.0", + BuildDate: recentDate, + ghClient: &mockGitHubClient{}, + }, + input: outOfSupportVersion, + wantFailure: false, + wantErr: false, + }, + { + name: "should return failure for old build date", + step: &outOfSupportVersionStep{ + GrafanaVersion: "9.5.0", + BuildDate: oldDate, + ghClient: &mockGitHubClient{}, + }, + input: outOfSupportVersion, + wantFailure: true, + wantErr: false, + }, + { + name: "should return nil for invalid version format", + step: &outOfSupportVersionStep{ + GrafanaVersion: "invalid-version", + BuildDate: oldDateButSupportedIfLatestMinor, + ghClient: &mockGitHubClient{}, + }, + input: outOfSupportVersion, + wantFailure: false, + wantErr: false, + }, + { + name: "should return nil for GitHub API error", + step: &outOfSupportVersionStep{ + GrafanaVersion: "9.5.0", + BuildDate: oldDateButSupportedIfLatestMinor, + ghClient: &mockGitHubClient{ + errCode: http.StatusTooManyRequests, + }, + }, + input: outOfSupportVersion, + wantFailure: false, + wantErr: false, + }, + { + name: "should return failure for non-latest minor version", + step: &outOfSupportVersionStep{ + GrafanaVersion: "9.5.0", + BuildDate: oldDateButSupportedIfLatestMinor, + ghClient: &mockGitHubClient{ + release: &github.RepositoryRelease{}, + }, + }, + input: outOfSupportVersion, + wantFailure: true, + wantErr: false, + }, + { + name: "should return nil for latest minor version", + step: &outOfSupportVersionStep{ + GrafanaVersion: "9.5.0", + BuildDate: oldDateButSupportedIfLatestMinor, + ghClient: &mockGitHubClient{ + errCode: http.StatusNotFound, + }, + }, + input: outOfSupportVersion, + wantFailure: false, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + failures, err := tt.step.Run(context.Background(), logging.DefaultLogger, &advisor.CheckSpec{}, tt.input) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + + if tt.wantFailure { + require.Len(t, failures, 1) + assert.Equal(t, advisor.CheckReportFailureSeverityHigh, failures[0].Severity) + assert.Equal(t, outOfSupportVersion, failures[0].ItemID) + assert.Equal(t, "Grafana version "+tt.step.GrafanaVersion+" is out of support", failures[0].Item) + } else { + assert.Empty(t, failures) + } + }) + } +} + +type mockGitHubClient struct { + release *github.RepositoryRelease + errCode int +} + +func (m *mockGitHubClient) GetReleaseByTag(ctx context.Context, owner, repo, tag string) (*github.RepositoryRelease, *github.Response, error) { + if m.errCode != 0 { + return nil, &github.Response{Response: &http.Response{StatusCode: m.errCode}}, assert.AnError + } + return m.release, &github.Response{Response: &http.Response{StatusCode: http.StatusOK}}, nil +} diff --git a/apps/advisor/pkg/app/checks/instancechecks/pinned_version_step.go b/apps/advisor/pkg/app/checks/instancechecks/pinned_version_step.go new file mode 100644 index 00000000000..a07b772625e --- /dev/null +++ b/apps/advisor/pkg/app/checks/instancechecks/pinned_version_step.go @@ -0,0 +1,62 @@ +package instancechecks + +import ( + "context" + "fmt" + + "github.com/grafana/grafana-app-sdk/logging" + advisor "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1" + "github.com/grafana/grafana/apps/advisor/pkg/app/checks" +) + +var _ checks.Step = &pinnedVersionStep{} + +const ( + pinnedVersion = "pinned_version" +) + +type pinnedVersionStep struct { + BuildBranch string +} + +func (s *pinnedVersionStep) Title() string { + return "Grafana Cloud version check" +} + +func (s *pinnedVersionStep) Description() string { + return "Check if the Grafana version is pinned." +} + +func (s *pinnedVersionStep) Resolution() string { + return "You may miss out on security updates and bug fixes if you use a pinned version. " + + "Contact your Grafana administrator and open a " + + "support ticket to help you get unpinned." +} + +func (s *pinnedVersionStep) ID() string { + return pinnedVersion +} + +func (s *pinnedVersionStep) Run(ctx context.Context, log logging.Logger, _ *advisor.CheckSpec, it any) ([]advisor.CheckReportFailure, error) { + item, ok := it.(string) + if !ok { + return nil, fmt.Errorf("invalid item type %T", it) + } + if item != pinnedVersion { + // Not interested in this item + return nil, nil + } + + if s.BuildBranch == "HEAD" { + // Not a pinned version + return nil, nil + } + + return []advisor.CheckReportFailure{checks.NewCheckReportFailure( + advisor.CheckReportFailureSeverityLow, + s.ID(), + "Grafana version is pinned", + "pinned_version", + []advisor.CheckErrorLink{}, + )}, nil +} diff --git a/apps/advisor/pkg/app/checks/instancechecks/pinned_version_step_test.go b/apps/advisor/pkg/app/checks/instancechecks/pinned_version_step_test.go new file mode 100644 index 00000000000..f6dbda63a30 --- /dev/null +++ b/apps/advisor/pkg/app/checks/instancechecks/pinned_version_step_test.go @@ -0,0 +1,79 @@ +package instancechecks + +import ( + "context" + "testing" + + "github.com/grafana/grafana-app-sdk/logging" + advisor "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPinnedVersionStep(t *testing.T) { + tests := []struct { + name string + step *pinnedVersionStep + input any + wantFailure bool + wantErr bool + }{ + { + name: "should return error for invalid input type", + step: &pinnedVersionStep{ + BuildBranch: "test-branch", + }, + input: 123, // invalid type + wantErr: true, + }, + { + name: "should return nil for non-pinned version item", + step: &pinnedVersionStep{ + BuildBranch: "test-branch", + }, + input: "other_item", + wantFailure: false, + wantErr: false, + }, + { + name: "should return nil for HEAD build branch", + step: &pinnedVersionStep{ + BuildBranch: "HEAD", + }, + input: pinnedVersion, + wantFailure: false, + wantErr: false, + }, + { + name: "should return failure for pinned version", + step: &pinnedVersionStep{ + BuildBranch: "v9.5.0", + }, + input: pinnedVersion, + wantFailure: true, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + failures, err := tt.step.Run(context.Background(), logging.DefaultLogger, &advisor.CheckSpec{}, tt.input) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + + if tt.wantFailure { + require.Len(t, failures, 1) + assert.Equal(t, advisor.CheckReportFailureSeverityLow, failures[0].Severity) + assert.Equal(t, "pinned_version", failures[0].ItemID) + assert.Equal(t, "Grafana version is pinned", failures[0].Item) + } else { + assert.Empty(t, failures) + } + }) + } +} diff --git a/apps/alerting/notifications/go.mod b/apps/alerting/notifications/go.mod index aac76670d3b..9878a4e39ff 100644 --- a/apps/alerting/notifications/go.mod +++ b/apps/alerting/notifications/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/apps/alerting/notifications -go 1.24.3 +go 1.24.4 require ( github.com/grafana/grafana-app-sdk v0.38.2 diff --git a/apps/dashboard/go.mod b/apps/dashboard/go.mod index dd46e08e462..846883be4e8 100644 --- a/apps/dashboard/go.mod +++ b/apps/dashboard/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/apps/dashboard -go 1.24.3 +go 1.24.4 require ( cuelang.org/go v0.11.1 diff --git a/apps/folder/go.mod b/apps/folder/go.mod index 84685631d82..cfe8561a6b8 100644 --- a/apps/folder/go.mod +++ b/apps/folder/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/apps/folder -go 1.24.3 +go 1.24.4 require ( github.com/grafana/grafana-app-sdk v0.38.2 diff --git a/apps/iam/go.mod b/apps/iam/go.mod index 4501e755d56..c118cacd1dc 100644 --- a/apps/iam/go.mod +++ b/apps/iam/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/apps/iam -go 1.24.3 +go 1.24.4 require ( github.com/grafana/grafana-app-sdk v0.38.2 diff --git a/apps/investigations/go.mod b/apps/investigations/go.mod index a82f1b8b0ba..057daa3fef4 100644 --- a/apps/investigations/go.mod +++ b/apps/investigations/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/apps/investigations -go 1.24.3 +go 1.24.4 require ( github.com/grafana/grafana-app-sdk v0.38.2 diff --git a/apps/playlist/go.mod b/apps/playlist/go.mod index fd759ee3fdd..59a4f465360 100644 --- a/apps/playlist/go.mod +++ b/apps/playlist/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/apps/playlist -go 1.24.3 +go 1.24.4 require ( github.com/grafana/grafana-app-sdk v0.38.2 diff --git a/devenv/docker/blocks/prometheus_high_card/go.mod b/devenv/docker/blocks/prometheus_high_card/go.mod index 6706105c5cc..32a39000351 100644 --- a/devenv/docker/blocks/prometheus_high_card/go.mod +++ b/devenv/docker/blocks/prometheus_high_card/go.mod @@ -1,6 +1,6 @@ module high-card -go 1.24.3 +go 1.24.4 require github.com/prometheus/client_golang v1.22.0 diff --git a/devenv/docker/blocks/prometheus_utf8/go.mod b/devenv/docker/blocks/prometheus_utf8/go.mod index 8e05b29a4ff..ed5e88f860d 100644 --- a/devenv/docker/blocks/prometheus_utf8/go.mod +++ b/devenv/docker/blocks/prometheus_utf8/go.mod @@ -1,6 +1,6 @@ module utf8-support -go 1.24.3 +go 1.24.4 require ( github.com/prometheus/client_golang v1.22.0 diff --git a/devenv/docker/blocks/stateful_webhook/Dockerfile b/devenv/docker/blocks/stateful_webhook/Dockerfile index 4cac8ca98dc..42a1610a9a2 100644 --- a/devenv/docker/blocks/stateful_webhook/Dockerfile +++ b/devenv/docker/blocks/stateful_webhook/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.24.3 +FROM golang:1.24.4 ADD main.go /go/src/webhook/main.go diff --git a/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md b/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md index 10f2af5b991..2e1490822fc 100644 --- a/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md +++ b/docs/sources/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/index.md @@ -59,7 +59,7 @@ The following tables list permissions associated with basic and fixed roles. Thi | Grafana Admin | `basic_grafana_admin` | fixed:roles:writer`
`fixed:users:writer`
`fixed:org.users:writer`
`fixed:ldap:writer`
`fixed:stats:reader`
`fixed:settings:writer`
`fixed:provisioning:writer`
`fixed:organization:maintainer`
`fixed:licensing:writer`
`fixed:plugins:maintainer`
`fixed:authentication.config:writer`
`fixed:migrationassistant:migrator` | Default [Grafana server administrator](/docs/grafana//administration/roles-and-permissions/#grafana-server-administrators) assignments. | | Admin | `basic_admin` | All roles assigned to Editor and `fixed:reports:writer`
`fixed:datasources:writer`
`fixed:organization:writer`
`fixed:datasources.permissions:writer`
`fixed:teams:writer`
`fixed:dashboards:writer`
`fixed:dashboards.permissions:writer`
`fixed:dashboards.public:writer`
`fixed:folders:writer`
`fixed:folders.permissions:writer`
`fixed:alerting:writer`
`fixed:apikeys:writer`
`fixed:alerting.provisioning.secrets:reader`
`fixed:alerting.provisioning:writer`
`fixed:datasources.caching:writer`
`fixed:plugins:writer`
`fixed:library.panels:writer` | Default [Grafana organization administrator](ref:rbac-basic-roles) assignments. | | Editor | `basic_editor` | All roles assigned to Viewer and `fixed:datasources:explorer`
`fixed:dashboards:creator`
`fixed:folders:creator`
`fixed:annotations:writer`
`fixed:alerting:writer`
`fixed:library.panels:creator`
`fixed:library.panels:general.writer`
`fixed:alerting.provisioning.status:writer` | Default [Editor](ref:rbac-basic-roles) assignments. | -| Viewer | `basic_viewer` | `fixed:datasources.id:reader`
`fixed:organization:reader`
`fixed:annotations:reader`
`fixed:annotations.dashboard:writer`
`fixed:alerting:reader`
`fixed:plugins.app:reader`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader`
`fixed:library.panels:general.reader`
`fixed:folders.general:reader` | Default [Viewer](ref:rbac-basic-roles) assignments. | +| Viewer | `basic_viewer` | `fixed:datasources.id:reader`
`fixed:organization:reader`
`fixed:annotations:reader`
`fixed:annotations.dashboard:writer`
`fixed:alerting:reader`
`fixed:plugins.app:reader`
`fixed:dashboards.insights:reader`
`fixed:datasources.insights:reader`
`fixed:library.panels:general.reader`
`fixed:folders.general:reader`
`fixed:datasources.builtin:reader` | Default [Viewer](ref:rbac-basic-roles) assignments. | | No Basic Role | n/a | | Default [No Basic Role](ref:rbac-basic-roles) | ## Fixed role definitions @@ -103,6 +103,7 @@ To learn how to use the roles API to determine the role UUIDs, refer to [Manage | `fixed:datasources:explorer` | `fixed_qDzW9mzx9yM91T5Bi8dHUM2muTw` | `datasources:explore` | Enable the Explore feature. Data source permissions still apply, you can only query data sources for which you have query permissions. | | `fixed:datasources:reader` | `fixed_C2x8IxkiBc1KZVjyYH775T9jNMQ` | `datasources:read`
`datasources:query` | Read and query data sources. | | `fixed:datasources:writer` | `fixed_q8HXq8kjjA5IlHHgBJlKlUyaNik` | All permissions from `fixed:datasources:reader` and
`datasources:create`
`datasources:write`
`datasources:delete` | Read, query, create, delete, or update a data source. | +| `fixed:datasources.builtin:reader` | `fixed_q8HXq8kjjA5IlHHgBJlKlUyaNik` | `datasources:read` and `datasources:query` scoped to `datasources:uid:grafana` | An internal role used to grant Viewers access to the builtin example data source in Grafana. | | `fixed:datasources.caching:reader` | `fixed_D2ddpGxJYlw0mbsTS1ek9fj0kj4` | `datasources.caching:read` | Read data source query caching settings. | | `fixed:datasources.caching:writer` | `fixed_JtFjHr7jd7hSqUYcktKvRvIOGRE` | `datasources.caching:read`
`datasources.caching:write` | Enable, disable, or update query caching settings. | | `fixed:datasources.id:reader` | `fixed_entg--fHmDqWY2-69N0ocawK0Os` | `datasources.id:read` | Read the ID of a data source based on its name. | @@ -112,6 +113,7 @@ To learn how to use the roles API to determine the role UUIDs, refer to [Manage | `fixed:folders:creator` | `fixed_gGLRbZGAGB6n9uECqSh_W382RlQ` | `folders:create` | Create folders in the root level. | | `fixed:folders:reader` | `fixed_yeW-5QPeo-i5PZUIUXMlAA97GnQ` | `folders:read`
`dashboards:read` | Read all folders and dashboards. | | `fixed:folders:writer` | `fixed_wJXLoTzgE7jVuz90dryYoiogL0o` | All permissions from `fixed:dashboards:writer` and
`folders:read`
`folders:write`
`folders:create`
`folders:delete`
`folders.permissions:read`
`folders.permissions:write` | Read, update, and delete all folders and dashboards. Create folders and subfolders. | +| `fixed:folders.general:reader` | `fixed_rSASbkg8DvpG_gTX5s41d7uxRvI` | `folders:read` scoped to `folders:uid:general` | An internal role used to correctly display access to the folder tree for Viewer role. | | `fixed:folders.permissions:reader` | `fixed_E06l4cx0JFm47EeLBE4nmv3pnSo` | `folders.permissions:read` | Read all folder permissions. | | `fixed:folders.permissions:writer` | `fixed_3GAgpQ_hWG8o7-lwNb86_VB37eI` | All permissions from `fixed:folders.permissions:reader` and
`folders.permissions:write` | Read and update all folder permissions. | | `fixed:ldap:reader` | `fixed_lMcOPwSkxKY-qCK8NMJc5k6izLE` | `ldap.user:read`
`ldap.status:read` | Read the LDAP configuration and LDAP status information. | diff --git a/docs/sources/cli.md b/docs/sources/cli.md index 301741d644a..e5e3ac6f2b5 100644 --- a/docs/sources/cli.md +++ b/docs/sources/cli.md @@ -229,7 +229,7 @@ To correct this, use the `--homepath` global option to specify the Grafana defau grafana cli --homepath "/usr/share/grafana" admin reset-admin-password ``` -If you have not lost the admin password, we recommend that you change the user password either in the User Preferences or in the Server Admin > User tab. +If you have not lost the admin password, we recommend that you change the user password either in the User Preferences or in the **Server Admin > User** tab. If you need to set the password in a script, then you can use the [Grafana User API](../developers/http_api/user/#change-password). @@ -241,6 +241,19 @@ If you installed Grafana using Homebrew, you can reset the admin password using /opt/homebrew/opt/grafana/bin/grafana cli --config /opt/homebrew/etc/grafana/grafana.ini --homepath /opt/homebrew/opt/grafana/share/grafana --configOverrides cfg:default.paths.data=/opt/homebrew/var/lib/grafana admin reset-admin-password ``` +#### Reset admin password for Grafana deployed with Grafana Operator and using an external database + +If you deploy Grafana with Grafana Operator and configure Grafana to use an external PostgreSQL or MySQL database, specify both the homepath and configuration file in your command: + +```bash +grafana cli --homepath /usr/share/grafana --config /etc/grafana/grafana.ini admin reset-admin-password +``` + +If you don't do this, the CLI: + +- Won't see the database connection information since it's in a directory outside of the Grafana homepath +- Will configure the default SQLite database in `/var/lib/grafana` and reset that password instead of for your external database + ### Migrate data and encrypt passwords `data-migration` runs a script that migrates or cleans up data in your database. diff --git a/docs/sources/introduction/_index.md b/docs/sources/introduction/_index.md index 9aba0f1cd48..3c71f685351 100644 --- a/docs/sources/introduction/_index.md +++ b/docs/sources/introduction/_index.md @@ -84,7 +84,7 @@ In addition to Grafana, Grafana Labs also provides the following open source pro **Grafana Beyla:** Grafana Beyla is an eBPF-based application auto-instrumentation tool for application observability. eBPF is used to automatically inspect application executables and the OS networking layer as well as capture basic trace spans related to web transactions and Rate-Errors-Duration (RED) metrics for Linux HTTP/S and gRPC services. All data capture occurs without any modifications to application code or configuration. For more information about Grafana Beyla, refer to [Grafana Beyla documentation](/docs/beyla/latest/). -**Grafana Alloy:** Grafana Alloy is a flexible, high performance, vendor-neutral distribution of the [OpenTelemetry][] (OTel) Collector. +**Grafana Alloy:** Grafana Alloy is a flexible, high performance, vendor-neutral distribution of the [OpenTelemetry](https://opentelemetry.io/) (OTel) Collector. It's fully compatible with the most popular open source observability standards such as OpenTelemetry (OTel) and Prometheus. For more information about Grafana Alloy, refer to the [Grafana Alloy documentation](https://grafana.com/docs/alloy/latest/). diff --git a/docs/sources/observability-as-code/foundation-sdk/dashboard-automation.md b/docs/sources/observability-as-code/foundation-sdk/dashboard-automation.md index 30e1df3223e..341fbd2af8c 100644 --- a/docs/sources/observability-as-code/foundation-sdk/dashboard-automation.md +++ b/docs/sources/observability-as-code/foundation-sdk/dashboard-automation.md @@ -164,10 +164,10 @@ jobs: - name: Checkout code uses: actions/checkout@v3 - - name: Set up Go 1.24.3 + - name: Set up Go uses: actions/setup-go@v5 with: - go-version: 1.24.3 + go-version: 1.24.4 - name: Verify Go version run: go version @@ -208,7 +208,7 @@ This GitHub Action automates the deployment of a Grafana dashboard using the Fou The first few steps: - Check out the repository to access the project code. -- Install Go 1.24.3 using the `actions/setup-go` action. +- Install Go 1.24.4 using the `actions/setup-go` action. - Verify Go is properly installed. ### 2. Download and install `grafanactl` diff --git a/e2e/dashboard-new-layouts/dashboard-group-panels.spec.ts b/e2e/dashboard-new-layouts/dashboard-group-panels.spec.ts index c74d43fbd8d..f3d74c171b5 100644 --- a/e2e/dashboard-new-layouts/dashboard-group-panels.spec.ts +++ b/e2e/dashboard-new-layouts/dashboard-group-panels.spec.ts @@ -5,10 +5,6 @@ describe('Grouping panels', () => { e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); }); - after(() => { - e2e.flows.revertAllChanges(); - }); - /* * Rows */ diff --git a/e2e/dashboard-new-layouts/dashboard-outline.spec.ts b/e2e/dashboard-new-layouts/dashboard-outline.spec.ts new file mode 100644 index 00000000000..6068152514c --- /dev/null +++ b/e2e/dashboard-new-layouts/dashboard-outline.spec.ts @@ -0,0 +1,26 @@ +import { e2e } from '../utils'; + +const PAGE_UNDER_TEST = 'edediimbjhdz4b/a-tall-dashboard'; + +describe('Dashboard Outline', () => { + beforeEach(() => { + e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); + }); + + it('can use dashboard outline', () => { + e2e.flows.openDashboard({ uid: `${PAGE_UNDER_TEST}?orgId=1` }); + + e2e.flows.scenes.toggleEditMode(); + + e2e.components.PanelEditor.Outline.section().click(); + + // Should be able to click Variables item in outline to see add variable button + e2e.components.PanelEditor.Outline.item('Variables').click(); + e2e.components.PanelEditor.ElementEditPane.addVariableButton().should('exist'); + + // Clicking a panel should scroll that panel in view + cy.contains('Dashboard panel 48').should('not.exist'); + e2e.components.PanelEditor.Outline.item('Panel #48').click(); + cy.contains('Dashboard panel 48').should('exist'); + }); +}); diff --git a/e2e/dashboard-new-layouts/dashboards-panel-layouts.spec.ts b/e2e/dashboard-new-layouts/dashboards-panel-layouts.spec.ts new file mode 100644 index 00000000000..49f4e1bcc3e --- /dev/null +++ b/e2e/dashboard-new-layouts/dashboards-panel-layouts.spec.ts @@ -0,0 +1,309 @@ +import { e2e } from '../utils'; + +describe('Dashboard', () => { + beforeEach(() => { + e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); + }); + + it('can switch to auto grid layout', () => { + e2e.flows.scenes.importV2Dashboard({ title: 'Switch to auto grid' }); + + e2e.components.NavToolbar.editDashboard.editButton().click(); + + e2e.components.Panels.Panel.title('New panel').should('have.length', 3); + + e2e.components.OptionsGroup.toggle('grid-layout-category').click(); + + e2e.flows.scenes.selectAutoGridLayout(); + + e2e.components.Panels.Panel.title('New panel').should('have.length', 3); + + const checkInputs = () => { + e2e.components.PanelEditor.ElementEditPane.AutoGridLayout.minColumnWidth().should('be.visible'); + e2e.components.PanelEditor.ElementEditPane.AutoGridLayout.maxColumns().should('be.visible'); + e2e.components.PanelEditor.ElementEditPane.AutoGridLayout.rowHeight().should('be.visible'); + e2e.components.PanelEditor.ElementEditPane.AutoGridLayout.fillScreen().should('exist'); + }; + + checkInputs(); + + e2e.flows.scenes.saveDashboard(); + cy.reload(); + + e2e.components.Panels.Panel.title('New panel').should('have.length', 3); + + e2e.components.NavToolbar.editDashboard.editButton().click(); + + checkInputs(); + }); + + it('can change min column width in auto grid layout', () => { + e2e.flows.scenes.importV2Dashboard({ title: 'Set min column width' }); + + e2e.components.NavToolbar.editDashboard.editButton().click(); + + e2e.components.Panels.Panel.title('New panel').should('have.length', 3); + + e2e.components.OptionsGroup.toggle('grid-layout-category').click(); + + e2e.flows.scenes.selectAutoGridLayout(); + + let firstStandardPanelTopOffset = 0; + + // standard min column width will have 1 panel on a second row in edit mode + e2e.components.Panels.Panel.title('New panel') + .first() + .then((el) => { + firstStandardPanelTopOffset = el.offset().top; + }); + + e2e.components.Panels.Panel.title('New panel') + .last() + .then((el) => { + expect(el.offset().top).to.be.greaterThan(firstStandardPanelTopOffset); + }); + + e2e.components.PanelEditor.ElementEditPane.AutoGridLayout.minColumnWidth().should('be.visible').click(); + cy.get('[id=combobox-option-narrow]').click(); + + const checkOffset = () => { + // narrow min column width will have all panels on the same row + let narrowPanelTopOffset = 0; + + e2e.components.Panels.Panel.title('New panel') + .first() + .then((el) => { + narrowPanelTopOffset = el.offset().top; + }); + + e2e.components.Panels.Panel.title('New panel') + .last() + .then((el) => { + expect(el.offset().top).to.eq(narrowPanelTopOffset); + }); + }; + + checkOffset(); + + e2e.flows.scenes.saveDashboard(); + cy.reload(); + + e2e.components.NavToolbar.editDashboard.editButton().click(); + + e2e.components.PanelEditor.ElementEditPane.AutoGridLayout.minColumnWidth().should('have.value', 'Narrow'); + + checkOffset(); + }); + + it('can change to custom min column width in auto grid layout', () => { + e2e.flows.scenes.importV2Dashboard({ title: 'Set custom min column width' }); + + e2e.components.NavToolbar.editDashboard.editButton().click(); + + e2e.components.Panels.Panel.title('New panel').should('have.length', 3); + + e2e.components.OptionsGroup.toggle('grid-layout-category').click(); + + e2e.flows.scenes.selectAutoGridLayout(); + + e2e.components.PanelEditor.ElementEditPane.AutoGridLayout.minColumnWidth().should('be.visible').click(); + cy.get('[id=combobox-option-custom]').click(); + + e2e.components.PanelEditor.ElementEditPane.AutoGridLayout.customMinColumnWidth() + .should('be.visible') + .clear() + .type('900') + .blur(); + + cy.wait(100); // cy too fast and executes next command before resizing is done + + // // changing to 900 custom width to have each panel span the whole row to verify offset + e2e.flows.scenes.verifyPanelsStackedVertically(); + + e2e.flows.scenes.saveDashboard(); + cy.reload(); + + e2e.flows.scenes.verifyPanelsStackedVertically(); + + e2e.components.NavToolbar.editDashboard.editButton().click(); + e2e.components.PanelEditor.ElementEditPane.AutoGridLayout.customMinColumnWidth().should('have.value', '900'); + + e2e.flows.scenes.verifyPanelsStackedVertically(); + + e2e.components.PanelEditor.ElementEditPane.AutoGridLayout.clearCustomMinColumnWidth().should('be.visible').click(); + e2e.components.PanelEditor.ElementEditPane.AutoGridLayout.minColumnWidth().should('have.value', 'Standard'); + }); + + it('can change max columns in auto grid layout', () => { + e2e.flows.scenes.importV2Dashboard({ title: 'Set max columns' }); + + e2e.components.NavToolbar.editDashboard.editButton().click(); + + e2e.components.Panels.Panel.title('New panel').should('have.length', 3); + + e2e.components.OptionsGroup.toggle('grid-layout-category').click(); + + e2e.flows.scenes.selectAutoGridLayout(); + + e2e.components.PanelEditor.ElementEditPane.AutoGridLayout.maxColumns().should('be.visible').click(); + cy.get('[id=combobox-option-1]').click(); + + // changing to 1 max column to have each panel span the whole row to verify offset + e2e.flows.scenes.verifyPanelsStackedVertically(); + + e2e.flows.scenes.saveDashboard(); + cy.reload(); + + e2e.flows.scenes.verifyPanelsStackedVertically(); + + e2e.components.NavToolbar.editDashboard.editButton().click(); + + e2e.components.PanelEditor.ElementEditPane.AutoGridLayout.maxColumns().should('have.value', '1'); + + e2e.flows.scenes.verifyPanelsStackedVertically(); + }); + + it('can change row height in auto grid layout', () => { + e2e.flows.scenes.importV2Dashboard({ title: 'Set row height' }); + + e2e.components.NavToolbar.editDashboard.editButton().click(); + + e2e.components.Panels.Panel.title('New panel').should('have.length', 3); + + e2e.components.OptionsGroup.toggle('grid-layout-category').click(); + + e2e.flows.scenes.selectAutoGridLayout(); + + let regularRowHeight = 0; + + e2e.components.Panels.Panel.title('New panel') + .first() + .then((el) => { + regularRowHeight = el.height(); + }); + + e2e.components.PanelEditor.ElementEditPane.AutoGridLayout.rowHeight().should('be.visible').click(); + cy.get('[id=combobox-option-short]').click(); + + e2e.components.Panels.Panel.title('New panel') + .first() + .then((el) => { + expect(el.height()).to.be.lessThan(regularRowHeight); + }); + + e2e.components.PanelEditor.ElementEditPane.AutoGridLayout.rowHeight().should('be.visible').click(); + cy.get('[id=combobox-option-tall]').click(); + + const checkHeight = () => { + e2e.components.Panels.Panel.title('New panel') + .first() + .then((el) => { + expect(el.height()).to.be.greaterThan(regularRowHeight); + }); + }; + + checkHeight(); + + e2e.flows.scenes.saveDashboard(); + cy.reload(); + + checkHeight(); + + e2e.components.NavToolbar.editDashboard.editButton().click(); + + e2e.components.PanelEditor.ElementEditPane.AutoGridLayout.rowHeight().should('have.value', 'Tall'); + + checkHeight(); + }); + + it('can change to custom row height in auto grid layout', () => { + e2e.flows.scenes.importV2Dashboard({ title: 'Set custom row height' }); + + e2e.components.NavToolbar.editDashboard.editButton().click(); + + e2e.components.Panels.Panel.title('New panel').should('have.length', 3); + + e2e.components.OptionsGroup.toggle('grid-layout-category').click(); + + e2e.flows.scenes.selectAutoGridLayout(); + + let regularRowHeight = 0; + + e2e.components.Panels.Panel.title('New panel') + .first() + .then((el) => { + regularRowHeight = el.height(); + }); + + e2e.components.PanelEditor.ElementEditPane.AutoGridLayout.rowHeight().should('be.visible').click(); + cy.get('[id=combobox-option-custom]').click(); + e2e.components.PanelEditor.ElementEditPane.AutoGridLayout.customRowHeight().clear().type('800').blur(); + cy.wait(100); // cy too fast and executes next command before resizing is done + + e2e.components.Panels.Panel.title('New panel') + .first() + .then((el) => { + const elHeight = el.height(); + expect(elHeight).be.closeTo(800, 5); // some flakyness and get 798 sometimes + expect(elHeight).to.be.greaterThan(regularRowHeight); + }); + + e2e.flows.scenes.saveDashboard(); + cy.reload(); + + e2e.components.Panels.Panel.title('New panel') + .first() + .then((el) => { + expect(el.height()).be.closeTo(800, 5); // some flakyness and get 798 sometimes + }); + + e2e.components.NavToolbar.editDashboard.editButton().click(); + + e2e.components.PanelEditor.ElementEditPane.AutoGridLayout.customRowHeight().should('have.value', '800'); + e2e.components.PanelEditor.ElementEditPane.AutoGridLayout.clearCustomRowHeight().should('be.visible').click(); + e2e.components.PanelEditor.ElementEditPane.AutoGridLayout.rowHeight().should('have.value', 'Standard'); + }); + + it('can change fill screen in auto grid layout', () => { + e2e.flows.scenes.importV2Dashboard({ title: 'Set fill screen' }); + + e2e.components.NavToolbar.editDashboard.editButton().click(); + + e2e.components.Panels.Panel.title('New panel').should('have.length', 3); + + e2e.components.OptionsGroup.toggle('grid-layout-category').click(); + + e2e.flows.scenes.selectAutoGridLayout(); + + e2e.components.PanelEditor.ElementEditPane.AutoGridLayout.minColumnWidth().should('be.visible').click(); + cy.get('[id=combobox-option-narrow]').click(); + + let initialHeight = 0; + + e2e.components.Panels.Panel.title('New panel') + .first() + .then((el) => { + initialHeight = el.height(); + }); + + e2e.components.PanelEditor.ElementEditPane.AutoGridLayout.fillScreen().click({ force: true }); + + const checkHeight = () => { + e2e.components.Panels.Panel.title('New panel') + .first() + .then((el) => { + expect(el.height()).to.be.greaterThan(initialHeight); + }); + }; + + checkHeight(); + e2e.flows.scenes.saveDashboard(); + cy.reload(); + + checkHeight(); + e2e.components.NavToolbar.editDashboard.editButton().click(); + e2e.components.PanelEditor.ElementEditPane.AutoGridLayout.fillScreen().should('be.checked'); + + checkHeight(); + }); +}); diff --git a/e2e/utils/flows/scenes/editPaneActions.ts b/e2e/utils/flows/scenes/editPaneActions.ts index bba3f40578e..04bb7e8fa05 100644 --- a/e2e/utils/flows/scenes/editPaneActions.ts +++ b/e2e/utils/flows/scenes/editPaneActions.ts @@ -12,6 +12,14 @@ export const selectRowsLayout = () => { clickGroupLayoutButton('Rows'); }; +export const selectCustomGridLayout = () => { + clickGroupLayoutButton('Custom'); +}; + +export const selectAutoGridLayout = () => { + clickGroupLayoutButton('Auto grid'); +}; + const editPaneCopyOrDuplicate = (buttonLabel: string) => { e2e.components.EditPaneHeader.copyDropdown().click(); cy.get('[role="menu"]').within(() => { diff --git a/e2e/utils/flows/scenes/index.ts b/e2e/utils/flows/scenes/index.ts index 9a5562237dc..3a8d6fa55a7 100644 --- a/e2e/utils/flows/scenes/index.ts +++ b/e2e/utils/flows/scenes/index.ts @@ -8,3 +8,4 @@ export * from './saveDashboard'; export * from './importDashboard'; export * from './editPaneActions'; export * from './selectPanel'; +export * from './panelPosition'; diff --git a/e2e/utils/flows/scenes/panelPosition.ts b/e2e/utils/flows/scenes/panelPosition.ts new file mode 100644 index 00000000000..63579422346 --- /dev/null +++ b/e2e/utils/flows/scenes/panelPosition.ts @@ -0,0 +1,14 @@ +import { e2e } from '../..'; + +export const verifyPanelsStackedVertically = () => { + let initialOffset = 0; + e2e.components.Panels.Panel.title('New panel').each((el) => { + if (!initialOffset) { + initialOffset = el.offset().top; + } else { + const elOffset = el.offset().top; + expect(elOffset).to.be.greaterThan(initialOffset); + initialOffset = elOffset; + } + }); +}; diff --git a/go.mod b/go.mod index 1d956b329fb..6f85332903a 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana -go 1.24.3 +go 1.24.4 require ( buf.build/gen/go/parca-dev/parca/connectrpc/go v1.17.0-20240902100956-02fd72488966.1 // @grafana/observability-traces-and-profiling diff --git a/go.work b/go.work index f9de6e23daa..044c12b9a93 100644 --- a/go.work +++ b/go.work @@ -1,4 +1,4 @@ -go 1.24.3 +go 1.24.4 // The `skip:golangci-lint` comment tag is used to exclude the package from the `golangci-lint` GitHub Action. // The module at the root of the repo (`.`) is excluded because ./pkg/... is included manually in the `golangci-lint` configuration. diff --git a/hack/go.mod b/hack/go.mod index 08fa4910871..eb429b002ec 100644 --- a/hack/go.mod +++ b/hack/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/hack -go 1.24.3 +go 1.24.4 require k8s.io/code-generator v0.33.1 diff --git a/package.json b/package.json index e5152879e2f..9897b90f1fe 100644 --- a/package.json +++ b/package.json @@ -235,7 +235,6 @@ "react-select-event": "5.5.1", "redux-mock-store": "1.5.5", "rimraf": "6.0.1", - "rudder-sdk-js": "2.48.44", "sass": "1.83.4", "sass-loader": "16.0.4", "smtp-tester": "^2.1.0", diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index 343dc58efcb..f0e51f64da6 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -606,6 +606,32 @@ export const versionedComponents = { variableLabelInput: { '12.0.0': 'data-testid variable label input', }, + AutoGridLayout: { + minColumnWidth: { + '12.1.0': 'data-testid min column width selector', + }, + customMinColumnWidth: { + '12.1.0': 'data-testid custom min column width input', + }, + clearCustomMinColumnWidth: { + '12.1.0': 'data-testid clear custom min column width input', + }, + maxColumns: { + '12.1.0': 'data-testid max columns selector', + }, + rowHeight: { + '12.1.0': 'data-testid row height selector', + }, + customRowHeight: { + '12.1.0': 'data-testid custom row height input', + }, + clearCustomRowHeight: { + '12.1.0': 'data-testid clear custom row height input', + }, + fillScreen: { + '12.1.0': 'data-testid fill screen switch', + }, + }, }, }, PanelInspector: { diff --git a/packages/grafana-i18n/src/i18n.tsx b/packages/grafana-i18n/src/i18n.tsx index 8aeb19681ec..343061a6c42 100644 --- a/packages/grafana-i18n/src/i18n.tsx +++ b/packages/grafana-i18n/src/i18n.tsx @@ -30,17 +30,20 @@ export async function loadPluginResources(id: string, language: string, loaders? } // exported for testing -export async function initDefaultI18nInstance() { +export function initDefaultI18nInstance() { // If the resources are not an object, we need to initialize the plugin translations if (getI18nInstance().options?.resources && typeof getI18nInstance().options.resources === 'object') { return; } - await getI18nInstance().use(initReactI18next).init({ + const initPromise = getI18nInstance().use(initReactI18next).init({ resources: {}, returnEmptyString: false, lng: DEFAULT_LANGUAGE, // this should be the locale of the phrases in our source JSX }); + tFunc = getI18nInstance().t; + transComponent = (props: TransProps) => ; + return initPromise; } // exported for testing @@ -171,6 +174,7 @@ export function addResourceBundle(language: string, namespace: string, resources } export const t: TFunction = (id: string, defaultMessage: string, values?: Record) => { + initDefaultI18nInstance(); if (!tFunc) { if (process.env.NODE_ENV !== 'test') { console.warn( @@ -189,10 +193,12 @@ export const t: TFunction = (id: string, defaultMessage: string, values?: Record }; export function useTranslate() { + initDefaultI18nInstance(); return { t }; } export function Trans(props: TransProps) { + initDefaultI18nInstance(); const Component = transComponent ?? I18NextTrans; return ; } diff --git a/packages/grafana-sql/src/components/visual-query-builder/SQLGroupByRow.tsx b/packages/grafana-sql/src/components/visual-query-builder/SQLGroupByRow.tsx index 722a0bad3f9..ef16d8b50d0 100644 --- a/packages/grafana-sql/src/components/visual-query-builder/SQLGroupByRow.tsx +++ b/packages/grafana-sql/src/components/visual-query-builder/SQLGroupByRow.tsx @@ -2,6 +2,7 @@ import { SelectableValue } from '@grafana/data'; import { QueryWithDefaults } from '../../defaults'; import { DB, SQLQuery } from '../../types'; +import { getColumnsWithIndices } from '../../utils/getColumnsWithIndices'; import { useSqlChange } from '../../utils/useSqlChange'; import { GroupByRow } from './GroupByRow'; @@ -15,6 +16,6 @@ interface SQLGroupByRowProps { export function SQLGroupByRow({ fields, query, onQueryChange, db }: SQLGroupByRowProps) { const { onSqlChange } = useSqlChange({ query, onQueryChange, db }); - - return ; + let columnsWithIndices: SelectableValue[] = getColumnsWithIndices(query, fields); + return ; } diff --git a/packages/grafana-sql/src/components/visual-query-builder/SQLOrderByRow.tsx b/packages/grafana-sql/src/components/visual-query-builder/SQLOrderByRow.tsx index 8ef0d6f5356..b20ec915c54 100644 --- a/packages/grafana-sql/src/components/visual-query-builder/SQLOrderByRow.tsx +++ b/packages/grafana-sql/src/components/visual-query-builder/SQLOrderByRow.tsx @@ -2,6 +2,7 @@ import { SelectableValue } from '@grafana/data'; import { QueryWithDefaults } from '../../defaults'; import { DB, SQLQuery } from '../../types'; +import { getColumnsWithIndices } from '../../utils/getColumnsWithIndices'; import { useSqlChange } from '../../utils/useSqlChange'; import { OrderByRow } from './OrderByRow'; @@ -15,26 +16,6 @@ type SQLOrderByRowProps = { export function SQLOrderByRow({ fields, query, onQueryChange, db }: SQLOrderByRowProps) { const { onSqlChange } = useSqlChange({ query, onQueryChange, db }); - let columnsWithIndices: SelectableValue[] = []; - - if (fields) { - const options = query.sql?.columns?.map((c, i) => { - const value = c.name ? `${c.name}(${c.parameters?.map((p) => p.name)})` : c.parameters?.map((p) => p.name); - return { - value, - label: `${i + 1} - ${value}`, - }; - }); - columnsWithIndices = [ - { - value: '', - label: 'Selected columns', - options, - expanded: true, - }, - ...fields, - ]; - } - + let columnsWithIndices: SelectableValue[] = getColumnsWithIndices(query, fields); return ; } diff --git a/packages/grafana-sql/src/utils/getColumnsWithIndices.ts b/packages/grafana-sql/src/utils/getColumnsWithIndices.ts new file mode 100644 index 00000000000..6c7dccbff9e --- /dev/null +++ b/packages/grafana-sql/src/utils/getColumnsWithIndices.ts @@ -0,0 +1,29 @@ +import { SelectableValue } from '@grafana/data'; + +import { SQLQuery } from '../types'; + +export function getColumnsWithIndices(query: SQLQuery, fields: SelectableValue[]): SelectableValue[] { + if (!fields || !query.sql?.columns) { + return fields; + } + + const options = query.sql.columns.map((c, i) => { + const value = c.name + ? `${c.name}(${c.parameters?.map((p) => p.name).join(', ')})` + : c.parameters?.map((p) => p.name).join(', '); + return { + value, + label: `${i + 1} - ${value}`, + }; + }); + + return [ + { + value: '', + label: 'Selected columns', + options, + expanded: true, + }, + ...fields, + ]; +} diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/CalendarBody.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/CalendarBody.tsx index e8dcd066de9..5279342e6d6 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/CalendarBody.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/CalendarBody.tsx @@ -105,6 +105,7 @@ export const getBodyStyles = (theme: GrafanaTheme2) => { '&:disabled': { color: theme.colors.action.disabledText, + cursor: 'not-allowed', }, }), body: css({ @@ -153,13 +154,18 @@ export const getBodyStyles = (theme: GrafanaTheme2) => { outline: 0, }, - [`${hasActiveSelector}, .react-calendar__tile--active, .react-calendar__tile--active:hover`]: { + [`${hasActiveSelector}, .react-calendar__tile--active`]: { color: theme.colors.primary.contrastText, fontWeight: theme.typography.fontWeightMedium, background: theme.colors.primary.main, border: '0px', }, + '.react-calendar__tile:hover:not(.react-calendar__tile--active):not(.react-calendar__tile--rangeEnd):not(.react-calendar__tile--rangeStart)': + { + backgroundColor: theme.colors.action.hover, + }, + '.react-calendar__tile--rangeEnd, .react-calendar__tile--rangeStart': { padding: 0, border: '0px', diff --git a/pkg/aggregator/go.mod b/pkg/aggregator/go.mod index b496f90c925..8fca2a719a8 100644 --- a/pkg/aggregator/go.mod +++ b/pkg/aggregator/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/pkg/aggregator -go 1.24.3 +go 1.24.4 require ( github.com/emicklei/go-restful/v3 v3.11.0 diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index e770bec0c9f..8a9af8a7b6f 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/pkg/apimachinery -go 1.24.3 +go 1.24.4 require ( github.com/grafana/authlib v0.0.0-20250515162837-2f4a8263eabb // @grafana/identity-access-team diff --git a/pkg/apis/secret/go.mod b/pkg/apis/secret/go.mod index 8a45e743ff1..d9d4f23ce31 100644 --- a/pkg/apis/secret/go.mod +++ b/pkg/apis/secret/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/pkg/apis/secret -go 1.24.3 +go 1.24.4 require ( github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index 56de9774c53..9a3a8e3c1ca 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/pkg/apiserver -go 1.24.3 +go 1.24.4 require ( github.com/google/go-cmp v0.7.0 diff --git a/pkg/build/go.mod b/pkg/build/go.mod index ace3ff91767..0df8e09498c 100644 --- a/pkg/build/go.mod +++ b/pkg/build/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/pkg/build -go 1.24.3 +go 1.24.4 // Override docker/docker to avoid: // go: github.com/drone-runners/drone-runner-docker@v1.8.2 requires diff --git a/pkg/build/wire/go.mod b/pkg/build/wire/go.mod index 3b1d875b3fd..f2286a2c311 100644 --- a/pkg/build/wire/go.mod +++ b/pkg/build/wire/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/pkg/build/wire -go 1.24.3 +go 1.24.4 require ( github.com/google/go-cmp v0.7.0 diff --git a/pkg/codegen/go.mod b/pkg/codegen/go.mod index 6eea447e2e1..d56a680547a 100644 --- a/pkg/codegen/go.mod +++ b/pkg/codegen/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/pkg/codegen -go 1.24.3 +go 1.24.4 require ( cuelang.org/go v0.11.1 diff --git a/pkg/expr/graph.go b/pkg/expr/graph.go index 4d760dff930..e19b73dd15e 100644 --- a/pkg/expr/graph.go +++ b/pkg/expr/graph.go @@ -328,14 +328,6 @@ func buildGraphEdges(dp *simple.DirectedGraph, registry map[string]Node) error { for _, neededVar := range cmdNode.Command.NeedsVars() { neededNode, ok := registry[neededVar] if !ok { - _, ok := cmdNode.Command.(*SQLCommand) - // If the SSE is a SQL expression, and the node can't be found, it might be a CTE table name - // CTEs are calculated during the evaluation of the SQL, so we won't have a node for them - // So we `continue` in order to support CTE functionality - // TODO: remove CTE table names from the list of table names during parsing of the SQL - if ok { - continue - } return fmt.Errorf("unable to find dependent node '%v'", neededVar) } diff --git a/pkg/expr/sql/parser.go b/pkg/expr/sql/parser.go index c65e4b89131..0e401c94a15 100644 --- a/pkg/expr/sql/parser.go +++ b/pkg/expr/sql/parser.go @@ -3,6 +3,7 @@ package sql import ( "fmt" "sort" + "strings" "github.com/dolthub/vitess/go/vt/sqlparser" "github.com/grafana/grafana/pkg/infra/log" @@ -10,7 +11,8 @@ import ( var logger = log.New("sql_expr") -// TablesList returns a list of tables for the sql statement +// TablesList returns a list of tables for the sql statement excluding +// CTEs and the 'dual' table. The list is sorted alphabetically. func TablesList(rawSQL string) ([]string, error) { stmt, err := sqlparser.Parse(rawSQL) if err != nil { @@ -19,10 +21,18 @@ func TablesList(rawSQL string) ([]string, error) { } tables := make(map[string]struct{}) + cteNames := make(map[string]struct{}) walkSubtree := func(node sqlparser.SQLNode) error { err = sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) { switch v := node.(type) { + case *sqlparser.CommonTableExpr: + // Track CTE name from the As field + cteName := v.As.String() + if cteName != "" { + cteNames[strings.ToLower(cteName)] = struct{}{} + } + case *sqlparser.AliasedTableExpr: if tableName, ok := v.Expr.(sqlparser.TableName); ok { tables[tableName.Name.String()] = struct{}{} @@ -49,9 +59,15 @@ func TablesList(rawSQL string) ([]string, error) { // Remove 'dual' table if it exists // This is a special table in MySQL that always returns a single row with a single column // See: https://dev.mysql.com/doc/refman/5.7/en/select.html#:~:text=You%20are%20permitted%20to%20specify%20DUAL%20as%20a%20dummy%20table%20name%20in%20situations%20where%20no%20tables%20are%20referenced - if table != "dual" { - result = append(result, table) + if table == "dual" { + continue } + + // Skip CTEs + if _, ok := cteNames[strings.ToLower(table)]; ok { + continue + } + result = append(result, table) } sort.Strings(result) diff --git a/pkg/expr/sql/parser_test.go b/pkg/expr/sql/parser_test.go index 17235684388..44ac1ccb102 100644 --- a/pkg/expr/sql/parser_test.go +++ b/pkg/expr/sql/parser_test.go @@ -105,7 +105,7 @@ func TestTablesList(t *testing.T) { ) SELECT name, price FROM top_products;`, - expected: []string{"products", "top_products"}, + expected: []string{"products"}, }, { name: "with quote", diff --git a/pkg/plugins/codegen/go.mod b/pkg/plugins/codegen/go.mod index 78b31cd6f6f..9b80d5ffcbf 100644 --- a/pkg/plugins/codegen/go.mod +++ b/pkg/plugins/codegen/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/pkg/plugins/codegen -go 1.24.3 +go 1.24.4 replace github.com/grafana/grafana/pkg/codegen => ../../codegen diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index e60338003b9..2854642a28a 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/pkg/promlib -go 1.24.3 +go 1.24.4 require ( github.com/grafana/dskit v0.0.0-20250317084829-9cdd36a91f10 diff --git a/pkg/registry/apis/query/parser.go b/pkg/registry/apis/query/parser.go index 965a0a173c3..96089f763d6 100644 --- a/pkg/registry/apis/query/parser.go +++ b/pkg/registry/apis/query/parser.go @@ -167,14 +167,8 @@ func (p *queryParser) parseRequest(ctx context.Context, input *query.QueryDataRe q, ok := queryRefIDs[refId] if !ok { - _, isSQLCMD := target.Command.(*expr.SQLCommand) - if isSQLCMD { - continue - } else { - target, ok = expressions[refId] - if !ok { - return rsp, makeDependencyError(exp.RefID, refId) - } + if target, ok = expressions[refId]; !ok { + return rsp, makeDependencyError(exp.RefID, refId) } } diff --git a/pkg/registry/apis/query/parser_test.go b/pkg/registry/apis/query/parser_test.go index e3e189f7ab1..f43e5295631 100644 --- a/pkg/registry/apis/query/parser_test.go +++ b/pkg/registry/apis/query/parser_test.go @@ -201,6 +201,46 @@ func TestSqlInputs(t *testing.T) { require.Equal(t, parsedRequestInfo.SqlInputs["B"], struct{}{}) } +func TestSqlCTE(t *testing.T) { + parser := newQueryParser( + expr.NewExpressionQueryReader(featuremgmt.WithFeatures(featuremgmt.FlagSqlExpressions)), + nil, + tracing.InitializeTracerForTest(), + log.NewNopLogger(), + ) + + parsedRequestInfo, err := parser.parseRequest(context.Background(), &query.QueryDataRequest{ + QueryDataRequest: data.QueryDataRequest{ + Queries: []data.DataQuery{ + data.NewDataQuery(map[string]any{ + "refId": "A", + "datasource": &data.DataSourceRef{ + Type: "prometheus", + UID: "local-prom", + }, + }), + data.NewDataQuery(map[string]any{ + "refId": "B", + "datasource": &data.DataSourceRef{ + Type: "__expr__", + UID: "__expr__", + }, + "type": "sql", + "expression": `WITH CTE AS ( + SELECT + Month + FROM A + ) + + SELECT * FROM CTE`, + }), + }, + }, + }) + require.NoError(t, err) + require.Equal(t, parsedRequestInfo.SqlInputs["B"], struct{}{}) +} + func TestGrafanaDS(t *testing.T) { ctx := context.Background() parser := newQueryParser(expr.NewExpressionQueryReader(featuremgmt.WithFeatures()), diff --git a/pkg/semconv/go.mod b/pkg/semconv/go.mod index 40aa5ce47a3..f3a2f4b31dd 100644 --- a/pkg/semconv/go.mod +++ b/pkg/semconv/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/pkg/semconv -go 1.24.3 +go 1.24.4 require go.opentelemetry.io/otel v1.35.0 diff --git a/pkg/services/featuremgmt/codeowners.go b/pkg/services/featuremgmt/codeowners.go index 97cf2bd4b3b..e4647a59a6f 100644 --- a/pkg/services/featuremgmt/codeowners.go +++ b/pkg/services/featuremgmt/codeowners.go @@ -9,6 +9,7 @@ const ( grafanaDashboardsSquad codeowner = "@grafana/dashboards-squad" grafanaDatavizSquad codeowner = "@grafana/dataviz-squad" grafanaFrontendPlatformSquad codeowner = "@grafana/grafana-frontend-platform" + grafanaFrontendSearchNavOrganise codeowner = "@grafana/grafana-search-navigate-organise" grafanaBackendServicesSquad codeowner = "@grafana/grafana-backend-services-squad" grafanaSearchAndStorageSquad codeowner = "@grafana/search-and-storage" grafanaPluginsPlatformSquad codeowner = "@grafana/plugins-platform-backend" diff --git a/pkg/storage/unified/sql/backend.go b/pkg/storage/unified/sql/backend.go index 8bef191d99e..91b71486952 100644 --- a/pkg/storage/unified/sql/backend.go +++ b/pkg/storage/unified/sql/backend.go @@ -780,7 +780,14 @@ func (b *backend) getHistory(ctx context.Context, req *resourcepb.ListRequest, c listReq.MinRV = latestDeletedRV + 1 } - rows, err := dbutil.QueryRows(ctx, tx, sqlResourceHistoryGet, listReq) + var rows db.Rows + if listReq.Trash { + // unlike history, trash will not return an object if an object of the same name is live + // (i.e. in the resource table) + rows, err = dbutil.QueryRows(ctx, tx, sqlResourceTrash, listReq) + } else { + rows, err = dbutil.QueryRows(ctx, tx, sqlResourceHistoryGet, listReq) + } if rows != nil { defer func() { if err := rows.Close(); err != nil { diff --git a/pkg/storage/unified/sql/data/resource_history_get.sql b/pkg/storage/unified/sql/data/resource_history_get.sql index 8d9e267f741..434ae8ffa01 100644 --- a/pkg/storage/unified/sql/data/resource_history_get.sql +++ b/pkg/storage/unified/sql/data/resource_history_get.sql @@ -15,9 +15,6 @@ WHERE 1 = 1 {{ if .Key.Name }} AND {{ .Ident "name" }} = {{ .Arg .Key.Name }} {{ end }} - {{ if .Trash }} - AND {{ .Ident "action" }} = 3 - {{ end }} {{ if (gt .StartRV 0) }} {{ if .SortAscending }} AND {{ .Ident "resource_version" }} > {{ .Arg .StartRV }} diff --git a/pkg/storage/unified/sql/data/resource_trash.sql b/pkg/storage/unified/sql/data/resource_trash.sql new file mode 100644 index 00000000000..83da698f575 --- /dev/null +++ b/pkg/storage/unified/sql/data/resource_trash.sql @@ -0,0 +1,54 @@ +SELECT + h.{{ .Ident "guid" }}, + h.{{ .Ident "resource_version" }}, + h.{{ .Ident "namespace" }}, + h.{{ .Ident "group" }}, + h.{{ .Ident "resource" }}, + h.{{ .Ident "name" }}, + h.{{ .Ident "folder" }}, + h.{{ .Ident "value" }} +FROM {{ .Ident "resource_history" }} h +INNER JOIN ( + SELECT {{ .Ident "name" }}, MAX({{ .Ident "resource_version" }}) as max_rv + FROM {{ .Ident "resource_history" }} + WHERE 1 = 1 + AND {{ .Ident "namespace" }} = {{ .Arg .Key.Namespace }} + AND {{ .Ident "group" }} = {{ .Arg .Key.Group }} + AND {{ .Ident "resource" }} = {{ .Arg .Key.Resource }} + {{ if .Key.Name }} + AND {{ .Ident "name" }} = {{ .Arg .Key.Name }} + {{ end }} + AND {{ .Ident "action" }} = 3 + {{ if (gt .StartRV 0) }} + {{ if .SortAscending }} + AND {{ .Ident "resource_version" }} > {{ .Arg .StartRV }} + {{ else }} + AND {{ .Ident "resource_version" }} < {{ .Arg .StartRV }} + {{ end }} + {{ end }} + {{ if (gt .MinRV 0) }} + AND {{ .Ident "resource_version" }} >= {{ .Arg .MinRV }} + {{ end }} + {{ if (gt .ExactRV 0) }} + AND {{ .Ident "resource_version" }} = {{ .Arg .ExactRV }} + {{ end }} + GROUP BY {{ .Ident "name" }} +) max_versions ON h.{{ .Ident "name" }} = max_versions.{{ .Ident "name" }} + AND h.{{ .Ident "resource_version" }} = max_versions.max_rv +WHERE 1 = 1 + AND h.{{ .Ident "namespace" }} = {{ .Arg .Key.Namespace }} + AND h.{{ .Ident "group" }} = {{ .Arg .Key.Group }} + AND h.{{ .Ident "resource" }} = {{ .Arg .Key.Resource }} + AND h.{{ .Ident "action" }} = 3 + AND NOT EXISTS ( + SELECT 1 FROM {{ .Ident "resource" }} r + WHERE r.{{ .Ident "namespace" }} = h.{{ .Ident "namespace" }} + AND r.{{ .Ident "group" }} = h.{{ .Ident "group" }} + AND r.{{ .Ident "resource" }} = h.{{ .Ident "resource" }} + AND r.{{ .Ident "name" }} = h.{{ .Ident "name" }} + ) +{{ if .SortAscending }} +ORDER BY h.{{ .Ident "resource_version" }} ASC +{{ else }} +ORDER BY h.{{ .Ident "resource_version" }} DESC +{{ end }} diff --git a/pkg/storage/unified/sql/queries.go b/pkg/storage/unified/sql/queries.go index 52778613a82..43ae7f3a45a 100644 --- a/pkg/storage/unified/sql/queries.go +++ b/pkg/storage/unified/sql/queries.go @@ -46,6 +46,7 @@ var ( sqlResourceHistoryGet = mustTemplate("resource_history_get.sql") sqlResourceHistoryDelete = mustTemplate("resource_history_delete.sql") sqlResourceHistoryPrune = mustTemplate("resource_history_prune.sql") + sqlResourceTrash = mustTemplate("resource_trash.sql") sqlResourceInsertFromHistory = mustTemplate("resource_insert_from_history.sql") // sqlResourceLabelsInsert = mustTemplate("resource_labels_insert.sql") diff --git a/pkg/storage/unified/sql/queries_test.go b/pkg/storage/unified/sql/queries_test.go index 4d5941e25dc..dcdf64016d5 100644 --- a/pkg/storage/unified/sql/queries_test.go +++ b/pkg/storage/unified/sql/queries_test.go @@ -245,6 +245,9 @@ func TestUnifiedStorageQueries(t *testing.T) { }, }, }, + }, + + sqlResourceTrash: { { Name: "read trash", Data: &sqlGetHistoryRequest{ diff --git a/pkg/storage/unified/sql/testdata/mysql--resource_trash-read trash second page.sql b/pkg/storage/unified/sql/testdata/mysql--resource_trash-read trash second page.sql new file mode 100755 index 00000000000..374c9740ee6 --- /dev/null +++ b/pkg/storage/unified/sql/testdata/mysql--resource_trash-read trash second page.sql @@ -0,0 +1,35 @@ +SELECT + h.`guid`, + h.`resource_version`, + h.`namespace`, + h.`group`, + h.`resource`, + h.`name`, + h.`folder`, + h.`value` +FROM `resource_history` h +INNER JOIN ( + SELECT `name`, MAX(`resource_version`) as max_rv + FROM `resource_history` + WHERE 1 = 1 + AND `namespace` = 'nn' + AND `group` = 'gg' + AND `resource` = 'rr' + AND `action` = 3 + AND `resource_version` < 123456 + GROUP BY `name` +) max_versions ON h.`name` = max_versions.`name` + AND h.`resource_version` = max_versions.max_rv +WHERE 1 = 1 + AND h.`namespace` = 'nn' + AND h.`group` = 'gg' + AND h.`resource` = 'rr' + AND h.`action` = 3 + AND NOT EXISTS ( + SELECT 1 FROM `resource` r + WHERE r.`namespace` = h.`namespace` + AND r.`group` = h.`group` + AND r.`resource` = h.`resource` + AND r.`name` = h.`name` + ) +ORDER BY h.`resource_version` DESC diff --git a/pkg/storage/unified/sql/testdata/mysql--resource_trash-read trash.sql b/pkg/storage/unified/sql/testdata/mysql--resource_trash-read trash.sql new file mode 100755 index 00000000000..b5190484b02 --- /dev/null +++ b/pkg/storage/unified/sql/testdata/mysql--resource_trash-read trash.sql @@ -0,0 +1,34 @@ +SELECT + h.`guid`, + h.`resource_version`, + h.`namespace`, + h.`group`, + h.`resource`, + h.`name`, + h.`folder`, + h.`value` +FROM `resource_history` h +INNER JOIN ( + SELECT `name`, MAX(`resource_version`) as max_rv + FROM `resource_history` + WHERE 1 = 1 + AND `namespace` = 'nn' + AND `group` = 'gg' + AND `resource` = 'rr' + AND `action` = 3 + GROUP BY `name` +) max_versions ON h.`name` = max_versions.`name` + AND h.`resource_version` = max_versions.max_rv +WHERE 1 = 1 + AND h.`namespace` = 'nn' + AND h.`group` = 'gg' + AND h.`resource` = 'rr' + AND h.`action` = 3 + AND NOT EXISTS ( + SELECT 1 FROM `resource` r + WHERE r.`namespace` = h.`namespace` + AND r.`group` = h.`group` + AND r.`resource` = h.`resource` + AND r.`name` = h.`name` + ) +ORDER BY h.`resource_version` DESC diff --git a/pkg/storage/unified/sql/testdata/postgres--resource_trash-read trash second page.sql b/pkg/storage/unified/sql/testdata/postgres--resource_trash-read trash second page.sql new file mode 100755 index 00000000000..fee07069bf7 --- /dev/null +++ b/pkg/storage/unified/sql/testdata/postgres--resource_trash-read trash second page.sql @@ -0,0 +1,35 @@ +SELECT + h."guid", + h."resource_version", + h."namespace", + h."group", + h."resource", + h."name", + h."folder", + h."value" +FROM "resource_history" h +INNER JOIN ( + SELECT "name", MAX("resource_version") as max_rv + FROM "resource_history" + WHERE 1 = 1 + AND "namespace" = 'nn' + AND "group" = 'gg' + AND "resource" = 'rr' + AND "action" = 3 + AND "resource_version" < 123456 + GROUP BY "name" +) max_versions ON h."name" = max_versions."name" + AND h."resource_version" = max_versions.max_rv +WHERE 1 = 1 + AND h."namespace" = 'nn' + AND h."group" = 'gg' + AND h."resource" = 'rr' + AND h."action" = 3 + AND NOT EXISTS ( + SELECT 1 FROM "resource" r + WHERE r."namespace" = h."namespace" + AND r."group" = h."group" + AND r."resource" = h."resource" + AND r."name" = h."name" + ) +ORDER BY h."resource_version" DESC diff --git a/pkg/storage/unified/sql/testdata/postgres--resource_trash-read trash.sql b/pkg/storage/unified/sql/testdata/postgres--resource_trash-read trash.sql new file mode 100755 index 00000000000..c45f0a44cc1 --- /dev/null +++ b/pkg/storage/unified/sql/testdata/postgres--resource_trash-read trash.sql @@ -0,0 +1,34 @@ +SELECT + h."guid", + h."resource_version", + h."namespace", + h."group", + h."resource", + h."name", + h."folder", + h."value" +FROM "resource_history" h +INNER JOIN ( + SELECT "name", MAX("resource_version") as max_rv + FROM "resource_history" + WHERE 1 = 1 + AND "namespace" = 'nn' + AND "group" = 'gg' + AND "resource" = 'rr' + AND "action" = 3 + GROUP BY "name" +) max_versions ON h."name" = max_versions."name" + AND h."resource_version" = max_versions.max_rv +WHERE 1 = 1 + AND h."namespace" = 'nn' + AND h."group" = 'gg' + AND h."resource" = 'rr' + AND h."action" = 3 + AND NOT EXISTS ( + SELECT 1 FROM "resource" r + WHERE r."namespace" = h."namespace" + AND r."group" = h."group" + AND r."resource" = h."resource" + AND r."name" = h."name" + ) +ORDER BY h."resource_version" DESC diff --git a/pkg/storage/unified/sql/testdata/sqlite--resource_trash-read trash second page.sql b/pkg/storage/unified/sql/testdata/sqlite--resource_trash-read trash second page.sql new file mode 100755 index 00000000000..fee07069bf7 --- /dev/null +++ b/pkg/storage/unified/sql/testdata/sqlite--resource_trash-read trash second page.sql @@ -0,0 +1,35 @@ +SELECT + h."guid", + h."resource_version", + h."namespace", + h."group", + h."resource", + h."name", + h."folder", + h."value" +FROM "resource_history" h +INNER JOIN ( + SELECT "name", MAX("resource_version") as max_rv + FROM "resource_history" + WHERE 1 = 1 + AND "namespace" = 'nn' + AND "group" = 'gg' + AND "resource" = 'rr' + AND "action" = 3 + AND "resource_version" < 123456 + GROUP BY "name" +) max_versions ON h."name" = max_versions."name" + AND h."resource_version" = max_versions.max_rv +WHERE 1 = 1 + AND h."namespace" = 'nn' + AND h."group" = 'gg' + AND h."resource" = 'rr' + AND h."action" = 3 + AND NOT EXISTS ( + SELECT 1 FROM "resource" r + WHERE r."namespace" = h."namespace" + AND r."group" = h."group" + AND r."resource" = h."resource" + AND r."name" = h."name" + ) +ORDER BY h."resource_version" DESC diff --git a/pkg/storage/unified/sql/testdata/sqlite--resource_trash-read trash.sql b/pkg/storage/unified/sql/testdata/sqlite--resource_trash-read trash.sql new file mode 100755 index 00000000000..c45f0a44cc1 --- /dev/null +++ b/pkg/storage/unified/sql/testdata/sqlite--resource_trash-read trash.sql @@ -0,0 +1,34 @@ +SELECT + h."guid", + h."resource_version", + h."namespace", + h."group", + h."resource", + h."name", + h."folder", + h."value" +FROM "resource_history" h +INNER JOIN ( + SELECT "name", MAX("resource_version") as max_rv + FROM "resource_history" + WHERE 1 = 1 + AND "namespace" = 'nn' + AND "group" = 'gg' + AND "resource" = 'rr' + AND "action" = 3 + GROUP BY "name" +) max_versions ON h."name" = max_versions."name" + AND h."resource_version" = max_versions.max_rv +WHERE 1 = 1 + AND h."namespace" = 'nn' + AND h."group" = 'gg' + AND h."resource" = 'rr' + AND h."action" = 3 + AND NOT EXISTS ( + SELECT 1 FROM "resource" r + WHERE r."namespace" = h."namespace" + AND r."group" = h."group" + AND r."resource" = h."resource" + AND r."name" = h."name" + ) +ORDER BY h."resource_version" DESC diff --git a/pkg/storage/unified/testing/storage_backend.go b/pkg/storage/unified/testing/storage_backend.go index c8ebb8b4692..45e756436fc 100644 --- a/pkg/storage/unified/testing/storage_backend.go +++ b/pkg/storage/unified/testing/storage_backend.go @@ -35,6 +35,7 @@ const ( TestGetResourceStats = "get resource stats" TestListHistory = "list history" TestListHistoryErrorReporting = "list history error reporting" + TestListTrash = "list trash" TestCreateNewResource = "create new resource" ) @@ -79,6 +80,7 @@ func RunStorageBackendTest(t *testing.T, newBackend NewBackendFunc, opts *TestOp {TestGetResourceStats, runTestIntegrationBackendGetResourceStats}, {TestListHistory, runTestIntegrationBackendListHistory}, {TestListHistoryErrorReporting, runTestIntegrationBackendListHistoryErrorReporting}, + {TestListTrash, runTestIntegrationBackendTrash}, {TestCreateNewResource, runTestIntegrationBackendCreateNewResource}, } @@ -1128,3 +1130,95 @@ func newServer(t *testing.T, b resource.StorageBackend) resource.ResourceServer return server } + +func runTestIntegrationBackendTrash(t *testing.T, backend resource.StorageBackend, nsPrefix string) { + ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) + server := newServer(t, backend) + ns := nsPrefix + "-ns-trash" + + // item1 deleted with multiple history events + rv1, err := writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_ADDED, WithNamespace(ns)) + require.NoError(t, err) + require.Greater(t, rv1, int64(0)) + rvDelete1, err := writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_DELETED, WithNamespace(ns)) + require.NoError(t, err) + require.Greater(t, rvDelete1, rv1) + rvDelete2, err := writeEvent(ctx, backend, "item1", resourcepb.WatchEvent_DELETED, WithNamespace(ns)) + require.NoError(t, err) + require.Greater(t, rvDelete2, rvDelete1) + + // item2 deleted and recreated, should not be returned in trash + rv2, err := writeEvent(ctx, backend, "item2", resourcepb.WatchEvent_ADDED, WithNamespace(ns)) + require.NoError(t, err) + require.Greater(t, rv2, int64(0)) + rvDelete3, err := writeEvent(ctx, backend, "item2", resourcepb.WatchEvent_DELETED, WithNamespace(ns)) + require.NoError(t, err) + require.Greater(t, rvDelete3, rv2) + rv3, err := writeEvent(ctx, backend, "item2", resourcepb.WatchEvent_ADDED, WithNamespace(ns)) + require.NoError(t, err) + require.Greater(t, rv3, int64(0)) + + tests := []struct { + name string + request *resourcepb.ListRequest + expectedVersions []int64 + expectedValues []string + minExpectedHeadRV int64 + expectedContinueRV int64 + expectedSortAsc bool + }{ + { + name: "returns the latest delete event", + request: &resourcepb.ListRequest{ + Source: resourcepb.ListRequest_TRASH, + Options: &resourcepb.ListOptions{ + Key: &resourcepb.ResourceKey{ + Namespace: ns, + Group: "group", + Resource: "resource", + Name: "item1", + }, + }, + }, + expectedVersions: []int64{rvDelete2}, + expectedValues: []string{"item1 DELETED"}, + minExpectedHeadRV: rvDelete2, + expectedContinueRV: rvDelete2, + expectedSortAsc: false, + }, + { + name: "does not return a version in the resource table", + request: &resourcepb.ListRequest{ + Source: resourcepb.ListRequest_TRASH, + Options: &resourcepb.ListOptions{ + Key: &resourcepb.ResourceKey{ + Namespace: ns, + Group: "group", + Resource: "resource", + Name: "item2", + }, + }, + }, + expectedVersions: []int64{}, + expectedValues: []string{}, + minExpectedHeadRV: rv3, + expectedContinueRV: rv3, + expectedSortAsc: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + res, err := server.List(ctx, tc.request) + require.NoError(t, err) + require.Nil(t, res.Error) + expectedItemCount := len(tc.expectedVersions) + require.Len(t, res.Items, expectedItemCount) + for i := 0; i < expectedItemCount; i++ { + require.Equal(t, tc.expectedVersions[i], res.Items[i].ResourceVersion) + require.Contains(t, string(res.Items[i].Value), tc.expectedValues[i]) + } + require.GreaterOrEqual(t, res.ResourceVersion, tc.minExpectedHeadRV) + }) + } +} diff --git a/pkg/tsdb/grafana-pyroscope-datasource/instance.go b/pkg/tsdb/grafana-pyroscope-datasource/instance.go index 97e3cc7388e..978b7148bda 100644 --- a/pkg/tsdb/grafana-pyroscope-datasource/instance.go +++ b/pkg/tsdb/grafana-pyroscope-datasource/instance.go @@ -29,12 +29,12 @@ var ( ) type ProfilingClient interface { - ProfileTypes(ctx context.Context, start int64, end int64) ([]*ProfileType, http.Header, error) - LabelNames(ctx context.Context, labelSelector string, start int64, end int64) ([]string, http.Header, error) - LabelValues(ctx context.Context, label string, labelSelector string, start int64, end int64) ([]string, http.Header, error) - GetSeries(ctx context.Context, profileTypeID string, labelSelector string, start int64, end int64, groupBy []string, limit *int64, step float64) (*SeriesResponse, http.Header, error) - GetProfile(ctx context.Context, profileTypeID string, labelSelector string, start int64, end int64, maxNodes *int64) (*ProfileResponse, http.Header, error) - GetSpanProfile(ctx context.Context, profileTypeID string, labelSelector string, spanSelector []string, start int64, end int64, maxNodes *int64) (*ProfileResponse, http.Header, error) + ProfileTypes(ctx context.Context, start int64, end int64) ([]*ProfileType, error) + LabelNames(ctx context.Context, labelSelector string, start int64, end int64) ([]string, error) + LabelValues(ctx context.Context, label string, labelSelector string, start int64, end int64) ([]string, error) + GetSeries(ctx context.Context, profileTypeID string, labelSelector string, start int64, end int64, groupBy []string, limit *int64, step float64) (*SeriesResponse, error) + GetProfile(ctx context.Context, profileTypeID string, labelSelector string, start int64, end int64, maxNodes *int64) (*ProfileResponse, error) + GetSpanProfile(ctx context.Context, profileTypeID string, labelSelector string, spanSelector []string, start int64, end int64, maxNodes *int64) (*ProfileResponse, error) } // PyroscopeDatasource is a datasource for querying application performance profiles. @@ -109,7 +109,7 @@ func (d *PyroscopeDatasource) profileTypes(ctx context.Context, req *backend.Cal } } - types, respHeaders, err := d.client.ProfileTypes(ctx, start, end) + types, err := d.client.ProfileTypes(ctx, start, end) if err != nil { ctxLogger.Error("Received error from client", "error", err, "function", logEntrypoint()) return err @@ -119,7 +119,7 @@ func (d *PyroscopeDatasource) profileTypes(ctx context.Context, req *backend.Cal ctxLogger.Error("Failed to marshal response", "error", err, "function", logEntrypoint()) return err } - err = sender.Send(&backend.CallResourceResponse{Body: bodyData, Headers: respHeaders, Status: 200}) + err = sender.Send(&backend.CallResourceResponse{Body: bodyData, Status: 200}) if err != nil { ctxLogger.Error("Failed to send response", "error", err, "function", logEntrypoint()) return err @@ -146,7 +146,7 @@ func (d *PyroscopeDatasource) labelNames(ctx context.Context, req *backend.CallR return fmt.Errorf("failed parsing label selector: %v", err) } - labelNames, respHeaders, err := d.client.LabelNames(ctx, labelSelector, start, end) + labelNames, err := d.client.LabelNames(ctx, labelSelector, start, end) if err != nil { ctxLogger.Error("Received error from client", "error", err, "function", logEntrypoint()) return fmt.Errorf("error calling LabelNames: %v", err) @@ -167,7 +167,7 @@ func (d *PyroscopeDatasource) labelNames(ctx context.Context, req *backend.CallR ctxLogger.Error("Failed to marshal response", "error", err, "function", logEntrypoint()) return err } - err = sender.Send(&backend.CallResourceResponse{Body: jsonResponse, Headers: respHeaders, Status: 200}) + err = sender.Send(&backend.CallResourceResponse{Body: jsonResponse, Status: 200}) if err != nil { ctxLogger.Error("Failed to send response", "error", err, "function", logEntrypoint()) return err @@ -195,7 +195,7 @@ func (d *PyroscopeDatasource) labelValues(ctx context.Context, req *backend.Call end, _ := strconv.ParseInt(query.Get("end"), 10, 64) label := query.Get("label") - res, respHeaders, err := d.client.LabelValues(ctx, label, query.Get("query"), start, end) + res, err := d.client.LabelValues(ctx, label, query.Get("query"), start, end) if err != nil { ctxLogger.Error("Received error from client", "error", err, "function", logEntrypoint()) return fmt.Errorf("error calling LabelValues: %v", err) @@ -207,7 +207,7 @@ func (d *PyroscopeDatasource) labelValues(ctx context.Context, req *backend.Call return err } - err = sender.Send(&backend.CallResourceResponse{Body: data, Headers: respHeaders, Status: 200}) + err = sender.Send(&backend.CallResourceResponse{Body: data, Status: 200}) if err != nil { ctxLogger.Error("Failed to send response", "error", err, "function", logEntrypoint()) return err @@ -255,7 +255,7 @@ func (d *PyroscopeDatasource) CheckHealth(ctx context.Context, _ *backend.CheckH // request succeeded or failed, we set the window to be small. start := time.Unix(1, 0).UnixMilli() end := time.Unix(4, 0).UnixMilli() - if _, _, err := d.client.ProfileTypes(ctx, start, end); err != nil { + if _, err := d.client.ProfileTypes(ctx, start, end); err != nil { status = backend.HealthStatusError message = err.Error() } diff --git a/pkg/tsdb/grafana-pyroscope-datasource/pyroscopeClient.go b/pkg/tsdb/grafana-pyroscope-datasource/pyroscopeClient.go index 903c31a3602..e742af31d30 100644 --- a/pkg/tsdb/grafana-pyroscope-datasource/pyroscopeClient.go +++ b/pkg/tsdb/grafana-pyroscope-datasource/pyroscopeClient.go @@ -71,7 +71,7 @@ func NewPyroscopeClient(httpClient *http.Client, url string) *PyroscopeClient { } } -func (c *PyroscopeClient) ProfileTypes(ctx context.Context, start int64, end int64) ([]*ProfileType, http.Header, error) { +func (c *PyroscopeClient) ProfileTypes(ctx context.Context, start int64, end int64) ([]*ProfileType, error) { ctx, span := tracing.DefaultTracer().Start(ctx, "datasource.pyroscope.ProfileTypes") defer span.End() res, err := c.connectClient.ProfileTypes(ctx, connect.NewRequest(&querierv1.ProfileTypesRequest{ @@ -82,11 +82,11 @@ func (c *PyroscopeClient) ProfileTypes(ctx context.Context, start int64, end int logger.Error("Received error from client", "error", err, "function", logEntrypoint()) span.RecordError(err) span.SetStatus(codes.Error, err.Error()) - return nil, nil, err + return nil, err } if res.Msg.ProfileTypes == nil { // Let's make sure we send at least empty array if we don't have any types - return []*ProfileType{}, nil, nil + return []*ProfileType{}, nil } else { pTypes := make([]*ProfileType, len(res.Msg.ProfileTypes)) for i, pType := range res.Msg.ProfileTypes { @@ -95,11 +95,11 @@ func (c *PyroscopeClient) ProfileTypes(ctx context.Context, start int64, end int Label: pType.Name + " - " + pType.SampleType, } } - return pTypes, res.Header(), nil + return pTypes, nil } } -func (c *PyroscopeClient) GetSeries(ctx context.Context, profileTypeID string, labelSelector string, start int64, end int64, groupBy []string, limit *int64, step float64) (*SeriesResponse, http.Header, error) { +func (c *PyroscopeClient) GetSeries(ctx context.Context, profileTypeID string, labelSelector string, start int64, end int64, groupBy []string, limit *int64, step float64) (*SeriesResponse, error) { ctx, span := tracing.DefaultTracer().Start(ctx, "datasource.pyroscope.GetSeries", trace.WithAttributes(attribute.String("profileTypeID", profileTypeID), attribute.String("labelSelector", labelSelector))) defer span.End() req := connect.NewRequest(&querierv1.SelectSeriesRequest{ @@ -117,7 +117,7 @@ func (c *PyroscopeClient) GetSeries(ctx context.Context, profileTypeID string, l logger.Error("Received error from client", "error", err, "function", logEntrypoint()) span.RecordError(err) span.SetStatus(codes.Error, err.Error()) - return nil, nil, err + return nil, err } series := make([]*Series, len(resp.Msg.Series)) @@ -152,10 +152,10 @@ func (c *PyroscopeClient) GetSeries(ctx context.Context, profileTypeID string, l Series: series, Units: getUnits(profileTypeID), Label: parts[1], - }, resp.Header(), nil + }, nil } -func (c *PyroscopeClient) GetProfile(ctx context.Context, profileTypeID, labelSelector string, start, end int64, maxNodes *int64) (*ProfileResponse, http.Header, error) { +func (c *PyroscopeClient) GetProfile(ctx context.Context, profileTypeID, labelSelector string, start, end int64, maxNodes *int64) (*ProfileResponse, error) { ctx, span := tracing.DefaultTracer().Start(ctx, "datasource.pyroscope.GetProfile", trace.WithAttributes(attribute.String("profileTypeID", profileTypeID), attribute.String("labelSelector", labelSelector))) defer span.End() req := &connect.Request[querierv1.SelectMergeStacktracesRequest]{ @@ -173,18 +173,18 @@ func (c *PyroscopeClient) GetProfile(ctx context.Context, profileTypeID, labelSe logger.Error("Received error from client", "error", err, "function", logEntrypoint()) span.RecordError(err) span.SetStatus(codes.Error, err.Error()) - return nil, nil, err + return nil, err } if resp.Msg.Flamegraph == nil { // Not an error, can happen when querying data oout of range. - return nil, nil, nil + return nil, nil } - return profileQuery(resp.Msg.Flamegraph, profileTypeID), resp.Header(), nil + return profileQuery(ctx, err, span, resp.Msg.Flamegraph, profileTypeID) } -func (c *PyroscopeClient) GetSpanProfile(ctx context.Context, profileTypeID, labelSelector string, spanSelector []string, start, end int64, maxNodes *int64) (*ProfileResponse, http.Header, error) { +func (c *PyroscopeClient) GetSpanProfile(ctx context.Context, profileTypeID, labelSelector string, spanSelector []string, start, end int64, maxNodes *int64) (*ProfileResponse, error) { ctx, span := tracing.DefaultTracer().Start(ctx, "datasource.pyroscope.GetSpanProfile", trace.WithAttributes(attribute.String("profileTypeID", profileTypeID), attribute.String("labelSelector", labelSelector), attribute.String("spanSelector", strings.Join(spanSelector, ",")))) defer span.End() req := &connect.Request[querierv1.SelectMergeSpanProfileRequest]{ @@ -202,18 +202,18 @@ func (c *PyroscopeClient) GetSpanProfile(ctx context.Context, profileTypeID, lab if err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) - return nil, nil, err + return nil, err } if resp.Msg.Flamegraph == nil { // Not an error, can happen when querying data oout of range. - return nil, nil, nil + return nil, nil } - return profileQuery(resp.Msg.Flamegraph, profileTypeID), resp.Header(), nil + return profileQuery(ctx, err, span, resp.Msg.Flamegraph, profileTypeID) } -func profileQuery(flamegraph *querierv1.FlameGraph, profileTypeID string) *ProfileResponse { +func profileQuery(ctx context.Context, err error, span trace.Span, flamegraph *querierv1.FlameGraph, profileTypeID string) (*ProfileResponse, error) { levels := make([]*Level, len(flamegraph.Levels)) for i, level := range flamegraph.Levels { levels[i] = &Level{ @@ -229,7 +229,7 @@ func profileQuery(flamegraph *querierv1.FlameGraph, profileTypeID string) *Profi MaxSelf: flamegraph.MaxSelf, }, Units: getUnits(profileTypeID), - } + }, nil } func getUnits(profileTypeID string) string { @@ -244,7 +244,7 @@ func getUnits(profileTypeID string) string { return unit } -func (c *PyroscopeClient) LabelNames(ctx context.Context, labelSelector string, start int64, end int64) ([]string, http.Header, error) { +func (c *PyroscopeClient) LabelNames(ctx context.Context, labelSelector string, start int64, end int64) ([]string, error) { ctx, span := tracing.DefaultTracer().Start(ctx, "datasource.pyroscope.LabelNames") defer span.End() resp, err := c.connectClient.LabelNames(ctx, connect.NewRequest(&typesv1.LabelNamesRequest{ @@ -256,11 +256,11 @@ func (c *PyroscopeClient) LabelNames(ctx context.Context, labelSelector string, logger.Error("Received error from client", "error", err, "function", logEntrypoint()) span.RecordError(err) span.SetStatus(codes.Error, err.Error()) - return nil, nil, fmt.Errorf("error sending LabelNames request %v", err) + return nil, fmt.Errorf("error sending LabelNames request %v", err) } if resp.Msg.Names == nil { - return []string{}, nil, nil + return []string{}, nil } var filtered []string @@ -270,10 +270,10 @@ func (c *PyroscopeClient) LabelNames(ctx context.Context, labelSelector string, } } - return filtered, resp.Header(), nil + return filtered, nil } -func (c *PyroscopeClient) LabelValues(ctx context.Context, label string, labelSelector string, start int64, end int64) ([]string, http.Header, error) { +func (c *PyroscopeClient) LabelValues(ctx context.Context, label string, labelSelector string, start int64, end int64) ([]string, error) { ctx, span := tracing.DefaultTracer().Start(ctx, "datasource.pyroscope.LabelValues") defer span.End() resp, err := c.connectClient.LabelValues(ctx, connect.NewRequest(&typesv1.LabelValuesRequest{ @@ -286,12 +286,12 @@ func (c *PyroscopeClient) LabelValues(ctx context.Context, label string, labelSe logger.Error("Received error from client", "error", err, "function", logEntrypoint()) span.RecordError(err) span.SetStatus(codes.Error, err.Error()) - return nil, nil, err + return nil, err } if resp.Msg.Names == nil { - return []string{}, nil, nil + return []string{}, nil } - return resp.Msg.Names, resp.Header(), nil + return resp.Msg.Names, nil } func isPrivateLabel(label string) bool { diff --git a/pkg/tsdb/grafana-pyroscope-datasource/pyroscopeClient_test.go b/pkg/tsdb/grafana-pyroscope-datasource/pyroscopeClient_test.go index 6aa9c71939d..dbcf94aee94 100644 --- a/pkg/tsdb/grafana-pyroscope-datasource/pyroscopeClient_test.go +++ b/pkg/tsdb/grafana-pyroscope-datasource/pyroscopeClient_test.go @@ -19,7 +19,7 @@ func Test_PyroscopeClient(t *testing.T) { t.Run("GetSeries", func(t *testing.T) { limit := int64(42) - resp, _, err := client.GetSeries(context.Background(), "memory:alloc_objects:count:space:bytes", "{}", 0, 100, []string{}, &limit, 15) + resp, err := client.GetSeries(context.Background(), "memory:alloc_objects:count:space:bytes", "{}", 0, 100, []string{}, &limit, 15) require.Nil(t, err) series := &SeriesResponse{ @@ -34,7 +34,7 @@ func Test_PyroscopeClient(t *testing.T) { t.Run("GetProfile", func(t *testing.T) { maxNodes := int64(-1) - resp, _, err := client.GetProfile(context.Background(), "memory:alloc_objects:count:space:bytes", "{}", 0, 100, &maxNodes) + resp, err := client.GetProfile(context.Background(), "memory:alloc_objects:count:space:bytes", "{}", 0, 100, &maxNodes) require.Nil(t, err) series := &ProfileResponse{ @@ -56,7 +56,7 @@ func Test_PyroscopeClient(t *testing.T) { t.Run("GetProfile with empty response", func(t *testing.T) { connectClient.SendEmptyProfileResponse = true maxNodes := int64(-1) - resp, _, err := client.GetProfile(context.Background(), "memory:alloc_objects:count:space:bytes", "{}", 0, 100, &maxNodes) + resp, err := client.GetProfile(context.Background(), "memory:alloc_objects:count:space:bytes", "{}", 0, 100, &maxNodes) require.Nil(t, err) // Mainly ensuring this does not panic like before require.Nil(t, resp) diff --git a/pkg/tsdb/grafana-pyroscope-datasource/query.go b/pkg/tsdb/grafana-pyroscope-datasource/query.go index c00449c8b86..c2243c7af05 100644 --- a/pkg/tsdb/grafana-pyroscope-datasource/query.go +++ b/pkg/tsdb/grafana-pyroscope-datasource/query.go @@ -77,7 +77,7 @@ func (d *PyroscopeDatasource) query(ctx context.Context, pCtx backend.PluginCont } } logger.Debug("Sending SelectSeriesRequest", "queryModel", qm, "function", logEntrypoint()) - seriesResp, _, err := d.client.GetSeries( + seriesResp, err := d.client.GetSeries( gCtx, profileTypeId, labelSelector, @@ -114,7 +114,7 @@ func (d *PyroscopeDatasource) query(ctx context.Context, pCtx backend.PluginCont var profileResp *ProfileResponse if len(qm.SpanSelector) > 0 { logger.Debug("Calling GetSpanProfile", "queryModel", qm, "function", logEntrypoint()) - prof, _, err := d.client.GetSpanProfile(gCtx, profileTypeId, labelSelector, qm.SpanSelector, query.TimeRange.From.UnixMilli(), query.TimeRange.To.UnixMilli(), qm.MaxNodes) + prof, err := d.client.GetSpanProfile(gCtx, profileTypeId, labelSelector, qm.SpanSelector, query.TimeRange.From.UnixMilli(), query.TimeRange.To.UnixMilli(), qm.MaxNodes) if err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) @@ -124,7 +124,7 @@ func (d *PyroscopeDatasource) query(ctx context.Context, pCtx backend.PluginCont profileResp = prof } else { logger.Debug("Calling GetProfile", "queryModel", qm, "function", logEntrypoint()) - prof, _, err := d.client.GetProfile(gCtx, profileTypeId, labelSelector, query.TimeRange.From.UnixMilli(), query.TimeRange.To.UnixMilli(), qm.MaxNodes) + prof, err := d.client.GetProfile(gCtx, profileTypeId, labelSelector, query.TimeRange.From.UnixMilli(), query.TimeRange.To.UnixMilli(), qm.MaxNodes) if err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) diff --git a/pkg/tsdb/grafana-pyroscope-datasource/query_test.go b/pkg/tsdb/grafana-pyroscope-datasource/query_test.go index ffb9f8aaa7c..def783b917f 100644 --- a/pkg/tsdb/grafana-pyroscope-datasource/query_test.go +++ b/pkg/tsdb/grafana-pyroscope-datasource/query_test.go @@ -2,7 +2,6 @@ package pyroscope import ( "context" - "net/http" "testing" "time" @@ -419,7 +418,7 @@ type FakeClient struct { Args []any } -func (f *FakeClient) ProfileTypes(ctx context.Context, start int64, end int64) ([]*ProfileType, http.Header, error) { +func (f *FakeClient) ProfileTypes(ctx context.Context, start int64, end int64) ([]*ProfileType, error) { return []*ProfileType{ { ID: "type:1", @@ -429,18 +428,18 @@ func (f *FakeClient) ProfileTypes(ctx context.Context, start int64, end int64) ( ID: "type:2", Label: "memory", }, - }, http.Header{}, nil + }, nil } -func (f *FakeClient) LabelValues(ctx context.Context, label string, labelSelector string, start int64, end int64) ([]string, http.Header, error) { +func (f *FakeClient) LabelValues(ctx context.Context, label string, labelSelector string, start int64, end int64) ([]string, error) { panic("implement me") } -func (f *FakeClient) LabelNames(ctx context.Context, labelSelector string, start int64, end int64) ([]string, http.Header, error) { +func (f *FakeClient) LabelNames(ctx context.Context, labelSelector string, start int64, end int64) ([]string, error) { panic("implement me") } -func (f *FakeClient) GetProfile(ctx context.Context, profileTypeID, labelSelector string, start, end int64, maxNodes *int64) (*ProfileResponse, http.Header, error) { +func (f *FakeClient) GetProfile(ctx context.Context, profileTypeID, labelSelector string, start, end int64, maxNodes *int64) (*ProfileResponse, error) { return &ProfileResponse{ Flamebearer: &Flamebearer{ Names: []string{"foo", "bar", "baz"}, @@ -453,10 +452,10 @@ func (f *FakeClient) GetProfile(ctx context.Context, profileTypeID, labelSelecto MaxSelf: 56, }, Units: "count", - }, http.Header{}, nil + }, nil } -func (f *FakeClient) GetSpanProfile(ctx context.Context, profileTypeID, labelSelector string, spanSelector []string, start, end int64, maxNodes *int64) (*ProfileResponse, http.Header, error) { +func (f *FakeClient) GetSpanProfile(ctx context.Context, profileTypeID, labelSelector string, spanSelector []string, start, end int64, maxNodes *int64) (*ProfileResponse, error) { return &ProfileResponse{ Flamebearer: &Flamebearer{ Names: []string{"foo", "bar", "baz"}, @@ -469,10 +468,10 @@ func (f *FakeClient) GetSpanProfile(ctx context.Context, profileTypeID, labelSel MaxSelf: 56, }, Units: "count", - }, http.Header{}, nil + }, nil } -func (f *FakeClient) GetSeries(ctx context.Context, profileTypeID, labelSelector string, start, end int64, groupBy []string, limit *int64, step float64) (*SeriesResponse, http.Header, error) { +func (f *FakeClient) GetSeries(ctx context.Context, profileTypeID, labelSelector string, start, end int64, groupBy []string, limit *int64, step float64) (*SeriesResponse, error) { f.Args = []any{profileTypeID, labelSelector, start, end, groupBy, step} return &SeriesResponse{ Series: []*Series{ @@ -483,5 +482,5 @@ func (f *FakeClient) GetSeries(ctx context.Context, profileTypeID, labelSelector }, Units: "count", Label: "test", - }, http.Header{}, nil + }, nil } diff --git a/pkg/tsdb/parca/resources.go b/pkg/tsdb/parca/resources.go index c0f0400047a..08a4b0a5231 100644 --- a/pkg/tsdb/parca/resources.go +++ b/pkg/tsdb/parca/resources.go @@ -65,7 +65,7 @@ func (d *ParcaDatasource) callProfileTypes(ctx context.Context, req *backend.Cal span.SetStatus(codes.Error, err.Error()) return err } - err = sender.Send(&backend.CallResourceResponse{Body: data, Headers: res.Header(), Status: 200}) + err = sender.Send(&backend.CallResourceResponse{Body: data, Status: 200}) if err != nil { ctxLogger.Error("Failed to send data to Parca", "error", err, "function", logEntrypoint()) span.RecordError(err) @@ -98,7 +98,7 @@ func (d *ParcaDatasource) callLabelNames(ctx context.Context, req *backend.CallR span.SetStatus(codes.Error, err.Error()) return err } - err = sender.Send(&backend.CallResourceResponse{Body: data, Headers: res.Header(), Status: 200}) + err = sender.Send(&backend.CallResourceResponse{Body: data, Status: 200}) if err != nil { ctxLogger.Error("Failed to send data to Parca", "error", err, "function", logEntrypoint()) span.RecordError(err) @@ -142,7 +142,7 @@ func (d *ParcaDatasource) callLabelValues(ctx context.Context, req *backend.Call span.SetStatus(codes.Error, err.Error()) return err } - err = sender.Send(&backend.CallResourceResponse{Body: data, Headers: res.Header(), Status: 200}) + err = sender.Send(&backend.CallResourceResponse{Body: data, Status: 200}) if err != nil { ctxLogger.Error("Failed to send data to Parca", "error", err, "function", logEntrypoint()) span.RecordError(err) diff --git a/public/app/core/components/TimeSeries/utils.ts b/public/app/core/components/TimeSeries/utils.ts index 5328be7e863..b23a6a3b46d 100644 --- a/public/app/core/components/TimeSeries/utils.ts +++ b/public/app/core/components/TimeSeries/utils.ts @@ -347,48 +347,59 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn = ({ pointsFilter = (u, seriesIdx, show, gaps) => { let filtered = []; - let series = u.series[seriesIdx]; - - if (!show && gaps && gaps.length) { - const [firstIdx, lastIdx] = series.idxs!; - const xData = u.data[0]; + if (!show) { const yData = u.data[seriesIdx]; - const firstPos = Math.round(u.valToPos(xData[firstIdx], 'x', true)); - const lastPos = Math.round(u.valToPos(xData[lastIdx], 'x', true)); - if (gaps[0][0] === firstPos) { - filtered.push(firstIdx); - } + if (gaps && gaps.length) { + const firstIdx = u.posToIdx(gaps[0][0], true); - // show single points between consecutive gaps that share end/start - for (let i = 0; i < gaps.length; i++) { - let thisGap = gaps[i]; - let nextGap = gaps[i + 1]; + if (yData[firstIdx - 1] == null) { + filtered.push(firstIdx); + } - if (nextGap && thisGap[1] === nextGap[0]) { - // approx when data density is > 1pt/px, since gap start/end pixels are rounded - let approxIdx = u.posToIdx(thisGap[1], true); + // show single points between consecutive gaps that share end/start + for (let i = 0; i < gaps.length; i++) { + let thisGap = gaps[i]; + let nextGap = gaps[i + 1]; - if (yData[approxIdx] == null) { - // scan left/right alternating to find closest index with non-null value - for (let j = 1; j < 100; j++) { - if (yData[approxIdx + j] != null) { - approxIdx += j; - break; - } - if (yData[approxIdx - j] != null) { - approxIdx -= j; - break; + if (nextGap && thisGap[1] === nextGap[0]) { + // approx when data density is > 1pt/px, since gap start/end pixels are rounded + let approxIdx = u.posToIdx(thisGap[1], true); + + if (yData[approxIdx] == null) { + // scan left/right alternating to find closest index with non-null value + for (let j = 1; j < 100; j++) { + if (yData[approxIdx + j] != null) { + approxIdx += j; + break; + } + if (yData[approxIdx - j] != null) { + approxIdx -= j; + break; + } } } - } - filtered.push(approxIdx); + filtered.push(approxIdx); + } + } + + const lastIdx = u.posToIdx(gaps[gaps.length - 1][1], true); + + if (yData[lastIdx + 1] == null) { + filtered.push(lastIdx); } } - - if (gaps[gaps.length - 1][1] === lastPos) { - filtered.push(lastIdx); + // single point surrounded by nulls + else { + // meh heuristic + if (yData[0] == null && yData[yData.length - 1] == null) { + for (let i = 0; i < yData.length; i++) { + if (yData[i] != null) { + filtered.push(i); + } + } + } } } diff --git a/public/app/core/services/echo/backends/analytics/RudderstackBackend.ts b/public/app/core/services/echo/backends/analytics/RudderstackBackend.ts index c73b3788b11..5b3c6c97a03 100644 --- a/public/app/core/services/echo/backends/analytics/RudderstackBackend.ts +++ b/public/app/core/services/echo/backends/analytics/RudderstackBackend.ts @@ -1,5 +1,3 @@ -import type { apiOptions, identify, load, page, track } from 'rudder-sdk-js'; // SDK is loaded dynamically from config, so we only import types from the SDK package - import { BuildInfo, CurrentUserDTO } from '@grafana/data'; import { EchoBackend, @@ -12,11 +10,19 @@ import { import { loadScript } from '../../utils'; +type Properties = Record; + +interface RudderstackAPIOptions { + Intercom?: { + user_hash: string; + }; +} + interface Rudderstack { - identify: typeof identify; - load: typeof load; - page: typeof page; - track: typeof track; + identify: (identifier: string, traits: Properties, options?: RudderstackAPIOptions) => void; + load: (writeKey: string, dataPlaneURL: string, options: { configUrl?: string; destSDKBaseURL?: string }) => void; + page: () => void; + track: (eventName: string, properties?: Properties) => void; } declare global { @@ -76,7 +82,7 @@ export class RudderstackBackend implements EchoBackend ) : ( {t('dashboard.auto-grid.options.custom-min-width.clear', 'Clear')} @@ -141,6 +145,7 @@ function GridLayoutColumns({ layoutManager }: { layoutManager: AutoGridLayoutMan value={String(maxColumnCount)} onChange={({ value }) => layoutManager.onMaxColumnCountChanged(parseInt(value, 10))} width={6.5} + data-testid={selectors.components.PanelEditor.ElementEditPane.AutoGridLayout.maxColumns} /> @@ -217,7 +222,13 @@ function GridLayoutRows({ layoutManager }: { layoutManager: AutoGridLayoutManage className={styles.wideSelector} > {isStandardHeight ? ( - + ) : ( {t('dashboard.auto-grid.options.custom-min-height.clear', 'Clear')} @@ -247,6 +260,7 @@ function GridLayoutRows({ layoutManager }: { layoutManager: AutoGridLayoutManage id="fill-screen-toggle" value={fillScreen} onChange={() => layoutManager.onFillScreenChanged(!fillScreen)} + data-testid={selectors.components.PanelEditor.ElementEditPane.AutoGridLayout.fillScreen} /> diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx index 51d805667bf..b852f1520cd 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx @@ -273,6 +273,48 @@ describe('LokiQueryBuilderOptions', () => { }); }); }); + + describe('Step validation', () => { + it('considers empty step as valid', async () => { + setup({ expr: 'rate({foo="bar"}[5m]' }); + await userEvent.click(screen.getByRole('button', { name: /Options/ })); + expect(screen.queryByText(/Invalid step/)).not.toBeInTheDocument(); + }); + + it('considers variable step that exists in the datasource as valid', async () => { + const datasource = createLokiDatasource(); + datasource.getVariables = jest.fn().mockReturnValue(['$interval']); + setup({ expr: 'rate({foo="bar"}[5m]', step: '$interval' }, undefined, { datasource }); + await userEvent.click(screen.getByRole('button', { name: /Options/ })); + expect(screen.queryByText(/Invalid step/)).not.toBeInTheDocument(); + }); + + it('considers variable step that does not exist in the datasource as invalid', async () => { + const datasource = createLokiDatasource(); + datasource.getVariables = jest.fn().mockReturnValue(['$interval']); + setup({ expr: 'rate({foo="bar"}[5m]', step: '$custom' }, undefined, { datasource }); + await userEvent.click(screen.getByRole('button', { name: /Options/ })); + expect(screen.getByText(/Invalid step/)).toBeInTheDocument(); + }); + + it('considers valid duration step as valid', async () => { + setup({ expr: 'rate({foo="bar"}[5m]', step: '1m' }); + await userEvent.click(screen.getByRole('button', { name: /Options/ })); + expect(screen.queryByText(/Invalid step/)).not.toBeInTheDocument(); + }); + + it('considers invalid step as invalid', async () => { + setup({ expr: 'rate({foo="bar"}[5m]', step: 'invalid' }); + await userEvent.click(screen.getByRole('button', { name: /Options/ })); + expect(screen.getByText(/Invalid step/)).toBeInTheDocument(); + }); + + it('considers non-duration number as invalid', async () => { + setup({ expr: 'rate({foo="bar"}[5m]', step: '123' }); + await userEvent.click(screen.getByRole('button', { name: /Options/ })); + expect(screen.getByText(/Invalid step/)).toBeInTheDocument(); + }); + }); }); function setup(queryOverrides: Partial = {}, onChange = jest.fn(), propOverrides: Partial = {}) { diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx index f692bf9496a..d04e9fc42f6 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx @@ -138,8 +138,18 @@ export const LokiQueryBuilderOptions = React.memo( if (!query.step) { return true; } - return typeof query.step === 'string' && isValidGrafanaDuration(query.step) && !isNaN(parseInt(query.step, 10)); - }, [query.step]); + + if (typeof query.step === 'string') { + // If we use a variable as step, we consider it valid + if (datasource.getVariables().includes(query.step)) { + return true; + } + // Check if the step is a valid Grafana duration + return isValidGrafanaDuration(query.step) && !isNaN(parseInt(query.step, 10)); + } + + return false; + }, [query.step, datasource]); return ( diff --git a/public/app/plugins/datasource/tempo/traceql/QueryEditor.tsx b/public/app/plugins/datasource/tempo/traceql/QueryEditor.tsx index 72d0e9890d8..56d2ad225d5 100644 --- a/public/app/plugins/datasource/tempo/traceql/QueryEditor.tsx +++ b/public/app/plugins/datasource/tempo/traceql/QueryEditor.tsx @@ -2,9 +2,9 @@ import { css } from '@emotion/css'; import { defaults } from 'lodash'; import { useState } from 'react'; -import { GrafanaTheme2, QueryEditorProps } from '@grafana/data'; +import { CoreApp, GrafanaTheme2, QueryEditorProps } from '@grafana/data'; import { config, reportInteraction } from '@grafana/runtime'; -import { Button, InlineLabel, useStyles2 } from '@grafana/ui'; +import { Alert, Button, Icon, InlineLabel, useStyles2 } from '@grafana/ui'; import { TempoDatasource } from '../datasource'; import { defaultQuery, MyDataSourceOptions, TempoQuery } from '../types'; @@ -28,8 +28,26 @@ export function QueryEditor(props: Props) { return genQuery === query.query || genQuery === '{}'; }); + const alertingWarning = ( + + Please note that TraceQL metrics is an experimental feature and should not be used in production. Read more about + it in{' '} + + documentation + + + . + + ); + const inAlerting = props.app === CoreApp.UnifiedAlerting || props.app === CoreApp.CloudAlerting; + return ( <> + {inAlerting && alertingWarning} Build complex queries using TraceQL to select a list of traces.{' '} diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index 59437ff8e22..eaa6bed6b8e 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -4465,6 +4465,7 @@ } }, "repeat-row-select2": { + "placeholder": "Vybrat ", "variable-options": { "label": { "disable-repeating": "", @@ -5283,7 +5284,7 @@ }, "save-dashboard-form": { "aria-label-message": "zpráva", - "body-dashboard-drastically-changed": "Protože používáte nové funkce nástěnky, které jsou podporované pouze v novém formátu schématu nástěnky Grafana, nástěnka bude uložena v novém formátu. Ujistěte se, jestli chcete provést tuto akci nebo chcete uložit nástěnku jako novou kopii.", + "body-dashboard-drastically-changed": "", "cancel-button": { "cancel": "Zrušit" }, @@ -5299,7 +5300,7 @@ }, "schema-v2-library-panels-export": "Funkce dynamické nástěnky panelu je experimentální a nemá plnou funkčnost srovnatelnou se současným chováním nástěnek. Je založena na novém formátu schématu, který nepodporuje panely knihovny. To znamená, že při exportu nástěnky pro použití v jiné instanci nebudeme zahrnovat panely knihoven. Jejich podpora je naplánována v rámci <2>životního cyklu funkce.", "schema-v2-library-panels-export-title": "Schéma nástěnky V2 zatím nepodporuje export panelů knihoven pro použití v jiné instanci", - "title-dashboard-drastically-changed": "Nástěnka se podstatně změnila" + "title-dashboard-drastically-changed": "" }, "save-dashboard-form-common-options": { "save-refresh-description-current-refresh-default": "Nastaví aktuální obnovení jako nové výchozí", @@ -7767,6 +7768,8 @@ }, "enable-highlighting": "Povolit zvýraznění", "escape-newlines": "Opravit nesprávně uniklé sekvence nového řádku a záložek v řádcích protokolu", + "font-size-default": "", + "font-size-small": "", "hide-timestamps": "Skrýt časová razítka", "hide-unique-labels": "Skrýt jedinečné štítky", "newest-first": "Seřazeno od nejnovějších protokolů – kliknutím zobrazíte nejstarší protokoly jako první", diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index 41293652554..369650ceb84 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -4427,6 +4427,7 @@ } }, "repeat-row-select2": { + "placeholder": "Auswählen", "variable-options": { "label": { "disable-repeating": "", @@ -5243,7 +5244,7 @@ }, "save-dashboard-form": { "aria-label-message": "Nachricht", - "body-dashboard-drastically-changed": "Da Sie neue Dashboard-Funktionen nutzen, die nur im neuen Dashboard-Schema-Format von Grafana unterstützt werden, wird das Dashboard im neuen Format gespeichert. Bitte prüfen Sie, ob Sie diese Aktion wirklich ausführen möchten, oder ob Sie das Dashboard lieber als neue Kopie speichern möchten.", + "body-dashboard-drastically-changed": "", "cancel-button": { "cancel": "Abbrechen" }, @@ -5259,7 +5260,7 @@ }, "schema-v2-library-panels-export": "Die dynamische Dashboard-Funktionalität ist experimentell und weist keine vollständige Funktionsparität mit dem aktuellen Dashboard-Verhalten auf. Sie basiert auf einem neuen Schemaformat, das keine Bibliotheks-Panels unterstützt. Das bedeutet, dass wir bei einem Export des Dashboards für die Nutzung in einer anderen Instanz keine Bibliotheks-Panels einbeziehen. Wir planen, sie im weiteren Verlauf des <2>Lebenszyklus der Funktion zu unterstützen.", "schema-v2-library-panels-export-title": "Das Dashboard-Schema V2 unterstützt noch keinen Export von Bibliotheks-Panels für die Verwendung in einer anderen Instanz", - "title-dashboard-drastically-changed": "Dashboard erheblich verändert" + "title-dashboard-drastically-changed": "" }, "save-dashboard-form-common-options": { "save-refresh-description-current-refresh-default": "Macht die aktuelle Aktualisierung zum neuen Standard", @@ -7715,6 +7716,8 @@ }, "enable-highlighting": "Markierung aktivieren", "escape-newlines": "Falsch dargestellte Zeilenumbrüche und Tab-Sequenzen in Log-Zeilen korrigieren", + "font-size-default": "", + "font-size-small": "", "hide-timestamps": "Zeitstempel ausblenden", "hide-unique-labels": "Eindeutige Labels ausblenden", "newest-first": "Sortiert nach neuesten Logs zuerst – klicken Sie, um die ältesten zuerst anzuzeigen", diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index 11561a508f8..a6afb1a35c5 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -4427,6 +4427,7 @@ } }, "repeat-row-select2": { + "placeholder": "Elegir", "variable-options": { "label": { "disable-repeating": "", @@ -5243,7 +5244,7 @@ }, "save-dashboard-form": { "aria-label-message": "mensaje", - "body-dashboard-drastically-changed": "Como estás utilizando nuevas funciones de dashboards que solo son compatibles con el nuevo formato de esquema de dashboards de Grafana, el dashboard se guardará en el nuevo formato. Asegúrate de que quieres realizar esta acción o prefieres guardar el dashboard como una nueva copia.", + "body-dashboard-drastically-changed": "", "cancel-button": { "cancel": "Cancelar" }, @@ -5259,7 +5260,7 @@ }, "schema-v2-library-panels-export": "La funcionalidad del dashboard dinámico es experimental y no tiene una paridad de características completa con el comportamiento actual de los dashboards. Se basa en un nuevo formato de esquema, que no admite paneles de librería. Esto significa que al exportar el dashboard para usarlo en otra instancia, no se incluirán paneles de librería. Tenemos la intención de admitirlos a medida que avanzamos en el <2>ciclo de vida de la función.", "schema-v2-library-panels-export-title": "El esquema de dashboard V2 aún no admite la exportación de paneles de librería para su uso en otra instancia", - "title-dashboard-drastically-changed": "El dashboard ha cambiado drásticamente" + "title-dashboard-drastically-changed": "" }, "save-dashboard-form-common-options": { "save-refresh-description-current-refresh-default": "Hará que la actualización actual sea la nueva predeterminada", @@ -7715,6 +7716,8 @@ }, "enable-highlighting": "Activar resaltado", "escape-newlines": "Corregir las secuencias de tabulación y de nueva línea que escaparon incorrectamente en las líneas de log", + "font-size-default": "", + "font-size-small": "", "hide-timestamps": "Ocultar marcas de tiempo", "hide-unique-labels": "Ocultar etiquetas únicas", "newest-first": "Ordenado por los logs más nuevos primero: haga clic para mostrar los más antiguos primero", diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 1550720bc2e..24437051729 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -4427,6 +4427,7 @@ } }, "repeat-row-select2": { + "placeholder": "Choisir", "variable-options": { "label": { "disable-repeating": "", @@ -5243,7 +5244,7 @@ }, "save-dashboard-form": { "aria-label-message": "message", - "body-dashboard-drastically-changed": "Étant donné que vous utilisez de nouvelles fonctionnalités de tableau de bord uniquement prises en charge par le nouveau format de schéma de tableau de bord Grafana, le tableau de bord sera enregistré dans le nouveau format. Veuillez vous assurer que vous souhaitez effectuer cette action ou que vous préférez enregistrer le tableau de bord en tant que nouvelle copie.", + "body-dashboard-drastically-changed": "", "cancel-button": { "cancel": "Annuler" }, @@ -5259,7 +5260,7 @@ }, "schema-v2-library-panels-export": "La fonctionnalité de tableau de bord dynamique est expérimentale et n’a pas une parité complète de fonctionnalités avec le comportement actuel des tableaux de bord. Elle est basée sur un nouveau format de schéma, qui ne prend pas en charge les panneaux de bibliothèque. Cela signifie que lors de l’exportation du tableau de bord pour l’utiliser dans une autre instance, nous n’inclurons pas les panneaux de la bibliothèque. Nous avons l’intention de les prendre en charge au fur et à mesure de notre progression dans le <2>cycle de vie de la fonctionnalité.", "schema-v2-library-panels-export-title": "Le schéma de tableau de bord V2 ne prend pas encore en charge l’exportation de panneaux de bibliothèque à utiliser dans une autre instance", - "title-dashboard-drastically-changed": "Le tableau de bord a radicalement changé" + "title-dashboard-drastically-changed": "" }, "save-dashboard-form-common-options": { "save-refresh-description-current-refresh-default": "L’actualisation actuelle deviendra la nouvelle valeur par défaut", @@ -7715,6 +7716,8 @@ }, "enable-highlighting": "Activer la surbrillance", "escape-newlines": "Correction des séquences de sauts de ligne et de tabulations incorrectement échappées dans les lignes du journal", + "font-size-default": "", + "font-size-small": "", "hide-timestamps": "Masquer les horodatages", "hide-unique-labels": "Masquer les étiquettes uniques", "newest-first": "Trié par les journaux les plus récents en premier - Cliquez pour afficher les plus anciens en premier", diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index 0742502cef8..56c696de3c3 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -4427,6 +4427,7 @@ } }, "repeat-row-select2": { + "placeholder": "Kiválasztás", "variable-options": { "label": { "disable-repeating": "", @@ -5243,7 +5244,7 @@ }, "save-dashboard-form": { "aria-label-message": "üzenet", - "body-dashboard-drastically-changed": "Mivel olyan új irányítópult-funkciókat használ, amelyeket csak a Grafana új irányítópult-sémaformátuma támogat, az irányítópultot az új formátumban menti a rendszer. Győződjön meg róla, hogy végre kívánja hajtani ezt a műveletet, vagy inkább új példányként menti az irányítópultot.", + "body-dashboard-drastically-changed": "", "cancel-button": { "cancel": "Mégse" }, @@ -5259,7 +5260,7 @@ }, "schema-v2-library-panels-export": "A dinamikus irányítópult funkcionalitása kísérleti jellegű, és nem rendelkezik teljes funkcionalitással a jelenlegi irányítópultok viselkedéséhez képest. Új sémaformátumon alapul, amely nem támogatja a könyvtárpaneleket. Ez azt jelenti, hogy amikor az irányítópultot egy másik példányban való használathoz exportálja, nem fogja tartalmazni a könyvtárpaneleket. Támogatni kívánjuk őket a funkció <2>életciklusának előrehaladtával.", "schema-v2-library-panels-export-title": "A Dashboard Schema V2 még nem támogatja a könyvtárpanelek exportálását egy másik példányban való használathoz", - "title-dashboard-drastically-changed": "Irányítópult nagymértékben megváltozott" + "title-dashboard-drastically-changed": "" }, "save-dashboard-form-common-options": { "save-refresh-description-current-refresh-default": "Az aktuális frissítés lesz az új alapértelmezés", @@ -7715,6 +7716,8 @@ }, "enable-highlighting": "Kiemelés engedélyezése", "escape-newlines": "Helytelenül értelmezett újsor- és tabulátorkarakteres szekvenciák javítása a naplósorokban", + "font-size-default": "", + "font-size-small": "", "hide-timestamps": "Időbélyegek elrejtése", "hide-unique-labels": "Egyedi címkék elrejtése", "newest-first": "Rendezés a legújabb naplók szerint – kattintson, hogy a legrégebbi naplók jelenjenek meg elsőként", diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index 0958500de74..1d9d04451be 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -4408,6 +4408,7 @@ } }, "repeat-row-select2": { + "placeholder": "Pilih", "variable-options": { "label": { "disable-repeating": "", @@ -5223,7 +5224,7 @@ }, "save-dashboard-form": { "aria-label-message": "pesan", - "body-dashboard-drastically-changed": "Anda menggunakan fitur dasbor baru yang hanya didukung pada format skema dasbor Grafana baru, jadi dasbor akan disimpan dalam format baru. Pastikan Anda ingin melakukan tindakan ini atau Anda lebih suka menyimpan dasbor sebagai salinan baru.", + "body-dashboard-drastically-changed": "", "cancel-button": { "cancel": "Batalkan" }, @@ -5239,7 +5240,7 @@ }, "schema-v2-library-panels-export": "Fungsionalitas dasbor dinamis bersifat eksperimen, dan tidak memiliki paritas fitur penuh dengan perilaku dasbor saat ini. Ini didasarkan pada format skema baru, yang tidak mendukung panel pustaka. Ini berarti ketika mengekspor dasbor untuk menggunakannya di instans lain, kami tidak akan menyertakan panel pustaka. Kami bermaksud untuk mendukung seiring fitur <2>siklus pengembangan fitur.", "schema-v2-library-panels-export-title": "Skema Dasbor V2 belum mendukung ekspor panel pustaka untuk digunakan di instans lain", - "title-dashboard-drastically-changed": "Dasbor berubah drastis" + "title-dashboard-drastically-changed": "" }, "save-dashboard-form-common-options": { "save-refresh-description-current-refresh-default": "Akan menjadikan muat ulang saat ini sebagai default baru", @@ -7689,6 +7690,8 @@ }, "enable-highlighting": "Aktifkan penyorotan", "escape-newlines": "Perbaiki escape sequence baris baru dan tab yang salah di baris log", + "font-size-default": "", + "font-size-small": "", "hide-timestamps": "Sembunyikan stempel waktu", "hide-unique-labels": "Sembunyikan label unik", "newest-first": "Diurutkan berdasarkan log terbaru lebih dulu - Klik untuk menampilkan yang paling lama lebih dulu", diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index e42366bad35..b23d35408ab 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -4427,6 +4427,7 @@ } }, "repeat-row-select2": { + "placeholder": "Scegli", "variable-options": { "label": { "disable-repeating": "", @@ -5243,7 +5244,7 @@ }, "save-dashboard-form": { "aria-label-message": "messaggio", - "body-dashboard-drastically-changed": "Dato che stai utilizzando nuove funzionalità della dashboard supportate solo nel nuovo formato grafico della dashboard Grafana, la dashboard verrà salvata nel nuovo formato. Assicurati di voler eseguire questa azione o di voler salvare la dashboard come nuova copia.", + "body-dashboard-drastically-changed": "", "cancel-button": { "cancel": "Annulla" }, @@ -5259,7 +5260,7 @@ }, "schema-v2-library-panels-export": "La funzionalità della dashboard dinamica è in fase sperimentale e non ha una parità di funzionalità completa con il comportamento attuale delle dashboard. Si basa su un nuovo formato di schema, che non supporta i pannelli della libreria. Ciò significa che quando si esporta la dashboard per utilizzarla in un'altra istanza, non includeremo i pannelli della libreria. Intendiamo supportarli man mano che progrediamo nel <2>ciclo di vita della funzionalità.", "schema-v2-library-panels-export-title": "Schema Dashboard V2 non supporta ancora l'esportazione di pannelli della libreria da utilizzare in un'altra istanza", - "title-dashboard-drastically-changed": "La dashboard è cambiata radicalmente" + "title-dashboard-drastically-changed": "" }, "save-dashboard-form-common-options": { "save-refresh-description-current-refresh-default": "Imposterà l'aggiornamento corrente come nuovo valore predefinito", @@ -7715,6 +7716,8 @@ }, "enable-highlighting": "Abilita evidenziazione", "escape-newlines": "Correggi le sequenze di nuove righe e tabulazioni non corrette nelle righe del registro", + "font-size-default": "", + "font-size-small": "", "hide-timestamps": "Nascondi marca temporale", "hide-unique-labels": "Nascondi etichette univoche", "newest-first": "Ordine: prima i registri più recenti – Fai clic per mostrare prima i meno recenti", diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index f65a2584594..983d64de58b 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -4408,6 +4408,7 @@ } }, "repeat-row-select2": { + "placeholder": "選択する", "variable-options": { "label": { "disable-repeating": "", @@ -5223,7 +5224,7 @@ }, "save-dashboard-form": { "aria-label-message": "メッセージ", - "body-dashboard-drastically-changed": "新しいGrafanaダッシュボードスキーマ形式でのみサポートされている新たなダッシュボード機能を使用しているため、ダッシュボードは新形式で保存されます。この操作を実行するか、ダッシュボードを新しいコピーとして保存するかを選択してください。", + "body-dashboard-drastically-changed": "", "cancel-button": { "cancel": "キャンセル" }, @@ -5239,7 +5240,7 @@ }, "schema-v2-library-panels-export": "動的ダッシュボード機能は実験的なもので、現在のダッシュボード動作と完全な機能互換性はありません。この機能は、ライブラリパネルをサポートしていない新しいスキーマ形式に基づいています。そのため、別のインスタンスで使用する目的でダッシュボードをエクスポートした場合、ライブラリパネルは含まれません。機能の<2>ライフサイクルが進むにつれ、これらをサポートする予定です。", "schema-v2-library-panels-export-title": "現在ダッシュボードスキーマV2では、別のインスタンスでの使用目的でのライブラリパネルのエクスポートをサポートしていません", - "title-dashboard-drastically-changed": "ダッシュボードが大幅に変更されました" + "title-dashboard-drastically-changed": "" }, "save-dashboard-form-common-options": { "save-refresh-description-current-refresh-default": "現在の更新を新しいデフォルトに設定", @@ -7689,6 +7690,8 @@ }, "enable-highlighting": "ハイライトを有効にする", "escape-newlines": "ログ行で誤ってエスケープされた改行とタブシーケンスを修正", + "font-size-default": "", + "font-size-small": "", "hide-timestamps": "タイムスタンプを非表示", "hide-unique-labels": "一意のラベルを非表示", "newest-first": "最新のログ順に並び替え - クリックして最も古いログを最初に表示", diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index 43bf9493a40..e1c26837746 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -4408,6 +4408,7 @@ } }, "repeat-row-select2": { + "placeholder": "선택", "variable-options": { "label": { "disable-repeating": "", @@ -5223,7 +5224,7 @@ }, "save-dashboard-form": { "aria-label-message": "메시지", - "body-dashboard-drastically-changed": "새로운 Grafana 대시보드 스키마 형식에서만 지원되는 새 대시보드 기능을 사용하고 있기 때문에 대시보드가 새 형식으로 저장됩니다. 이 동작을 수행할지 아니면 대시보드를 새 복사본으로 저장할지 확인하세요.", + "body-dashboard-drastically-changed": "", "cancel-button": { "cancel": "취소" }, @@ -5239,7 +5240,7 @@ }, "schema-v2-library-panels-export": "동적 대시보드 기능은 실험 단계이며 현재 대시보드 동작과 모든 기능이 동일하지는 않습니다. 이 기능은 라이브러리 패널을 지원하지 않는 새로운 스키마 형식을 기반으로 합니다. 즉, 다른 인스턴스에서 사용하기 위해 대시보드를 내보낼 때 라이브러리 패널이 포함되지 않는다는 것을 의미합니다. Grafana는 기능 <2>수명 주기가 진행됨에 따라 이를 지원할 계획입니다.", "schema-v2-library-panels-export-title": "대시보드 스키마 V2는 아직 다른 인스턴스에서 사용할 라이브러리 패널 내보내기를 지원하지 않습니다", - "title-dashboard-drastically-changed": "대시보드가 대폭 변경됨" + "title-dashboard-drastically-changed": "" }, "save-dashboard-form-common-options": { "save-refresh-description-current-refresh-default": "현재 새로 고침을 새로운 기본값으로 설정합니다", @@ -7689,6 +7690,8 @@ }, "enable-highlighting": "강조 표시 활성화", "escape-newlines": "로그 줄에서 잘못된 줄 바꿈 및 탭 시퀀스 수정", + "font-size-default": "", + "font-size-small": "", "hide-timestamps": "타임스탬프 숨기기", "hide-unique-labels": "고유 라벨 숨기기", "newest-first": "최신 로그순으로 정렬 - 클릭하여 오래된 로그순으로 표시", diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index f46f810c390..07fdfeb9acd 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -4427,6 +4427,7 @@ } }, "repeat-row-select2": { + "placeholder": "Kies", "variable-options": { "label": { "disable-repeating": "", @@ -5243,7 +5244,7 @@ }, "save-dashboard-form": { "aria-label-message": "bericht", - "body-dashboard-drastically-changed": "Omdat je nieuwe dashboardfuncties gebruikt die alleen worden ondersteund in de nieuwe indeling van het Grafana-dashboardschema, wordt het dashboard opgeslagen in de nieuwe indeling. Beslis of je deze actie wilt uitvoeren of dat je het dashboard liever als een nieuwe kopie opslaat.", + "body-dashboard-drastically-changed": "", "cancel-button": { "cancel": "Annuleren" }, @@ -5259,7 +5260,7 @@ }, "schema-v2-library-panels-export": "De dynamische dashboardfunctionaliteit is experimenteel en heeft geen volledige functiepariteit met het huidige gedrag van dashboards. Het is gebaseerd op een nieuw schemaformaat, dat geen bibliotheekpanelen ondersteunt. Dit betekent dat we bij het exporteren van het dashboard om het in een andere instantie te gebruiken, geen bibliotheekpanelen opnemen. We zijn van plan om ze te ondersteunen naarmate we verder komen in de <2>levenscyclus van de functie.", "schema-v2-library-panels-export-title": "Dashboardschema V2 biedt nog geen ondersteuning voor het exporteren van bibliotheekpanelen die in een andere instantie kunnen worden gebruikt", - "title-dashboard-drastically-changed": "Dashboard is drastisch gewijzigd" + "title-dashboard-drastically-changed": "" }, "save-dashboard-form-common-options": { "save-refresh-description-current-refresh-default": "Maakt de huidige vernieuwing de nieuwe standaard", @@ -7715,6 +7716,8 @@ }, "enable-highlighting": "Markeren inschakelen", "escape-newlines": "Corrigeer onjuist escaped nieuwe lijn en tabbladsequenties in logregels", + "font-size-default": "", + "font-size-small": "", "hide-timestamps": "Tijdstempels verbergen", "hide-unique-labels": "Unieke labels verbergen", "newest-first": "Gesorteerd op nieuwste logboeken eerst - klik om oudste eerst weer te geven", diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index 01ed2a83213..57b8b88d424 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -4465,6 +4465,7 @@ } }, "repeat-row-select2": { + "placeholder": "Wybierz", "variable-options": { "label": { "disable-repeating": "", @@ -5283,7 +5284,7 @@ }, "save-dashboard-form": { "aria-label-message": "Wiadomość", - "body-dashboard-drastically-changed": "Ponieważ używasz nowych funkcji obsługiwanych tylko w nowym formacie schematu pulpitu Grafany, pulpit zostanie zapisany w nowym formacie. Zdecyduj, czy chcesz wykonać tę czynność, czy wolisz zapisać pulpit jako nową kopię.", + "body-dashboard-drastically-changed": "", "cancel-button": { "cancel": "Anuluj" }, @@ -5299,7 +5300,7 @@ }, "schema-v2-library-panels-export": "Funkcja pulpitu dynamicznego jest eksperymentalna i nie odpowiada w pełni obecnemu sposobowi działania pulpitów. Opiera się na nowym formacie schematu, który nie obsługuje paneli biblioteki. Oznacza to, że przy eksporcie pulpitu, aby użyć go w innej instancji, nie uwzględnimy paneli biblioteki. Zamierzamy dodać obsługę tych paneli w miarę <2>rozwoju tej funkcji.", "schema-v2-library-panels-export-title": "Schemat pulpitu w wersji 2 nie obsługuje jeszcze eksportowania paneli biblioteki do użytku w innej instancji", - "title-dashboard-drastically-changed": "Panel znacznie się zmienił" + "title-dashboard-drastically-changed": "" }, "save-dashboard-form-common-options": { "save-refresh-description-current-refresh-default": "Bieżące odświeżenie stanie się nową wartością domyślną", @@ -7767,6 +7768,8 @@ }, "enable-highlighting": "Włącz wyróżnianie", "escape-newlines": "Napraw nieprawidłowe sekwencje znaków nowego wiersza i tabulacji we wpisach logów", + "font-size-default": "", + "font-size-small": "", "hide-timestamps": "Ukryj znaczniki czasu", "hide-unique-labels": "Ukryj unikalne etykiety", "newest-first": "Sortowanie od najnowszych wpisów dziennika – kliknij, aby wyświetlić najpierw najstarsze", diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index b915ddd779e..2e66bdf9632 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -4427,6 +4427,7 @@ } }, "repeat-row-select2": { + "placeholder": "Escolher", "variable-options": { "label": { "disable-repeating": "", @@ -5243,7 +5244,7 @@ }, "save-dashboard-form": { "aria-label-message": "mensagem", - "body-dashboard-drastically-changed": "Como você está usando novos recursos de painéis compatíveis apenas com o novo formato de esquema de painel da Grafana, o painel será salvo no novo formato. Certifique-se de que você deseja executar esta ação ou prefere salvar o painel como uma nova cópia.", + "body-dashboard-drastically-changed": "", "cancel-button": { "cancel": "Cancelar" }, @@ -5259,7 +5260,7 @@ }, "schema-v2-library-panels-export": "A funcionalidade de painel dinâmico está em testes e não possui todos os recursos presentes no comportamento atual dos painéis. Ele se baseia em um novo formato de esquema, que não aceita painéis da biblioteca. Isso significa que, ao exportar o painel para usá-lo em outra instância, não incluiremos painéis da biblioteca. Pretendemos aceitá-los à medida que avançamos no <2>ciclo de vida do recurso.", "schema-v2-library-panels-export-title": "O esquema de painel V2 ainda não aceita a exportação de painéis da biblioteca para uso em outra instância", - "title-dashboard-drastically-changed": "O painel foi alterado drasticamente" + "title-dashboard-drastically-changed": "" }, "save-dashboard-form-common-options": { "save-refresh-description-current-refresh-default": "Fará com que a atualização atual seja o novo padrão", @@ -7715,6 +7716,8 @@ }, "enable-highlighting": "Ativar destaque", "escape-newlines": "Corrigir sequências de tabulação e quebra de linha adicionadas incorretamente por escape nas linhas de log", + "font-size-default": "", + "font-size-small": "", "hide-timestamps": "Ocultar data e hora", "hide-unique-labels": "Ocultar rótulos exclusivos", "newest-first": "Organizado por logs mais recentes primeiro: clique para exibir os mais antigos primeiro", diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index 423138a47e4..f701ea99c9c 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -4427,6 +4427,7 @@ } }, "repeat-row-select2": { + "placeholder": "Escolher", "variable-options": { "label": { "disable-repeating": "", @@ -5243,7 +5244,7 @@ }, "save-dashboard-form": { "aria-label-message": "mensagem", - "body-dashboard-drastically-changed": "Como está a utilizar novas funcionalidades de painéis de controlo suportadas apenas no novo formato de esquema de painel de controlo da Grafana, este será guardado no novo formato. Certifique-se de que pretende realizar esta ação ou se prefere guardar o painel de controlo como uma nova cópia.", + "body-dashboard-drastically-changed": "", "cancel-button": { "cancel": "Cancelar" }, @@ -5259,7 +5260,7 @@ }, "schema-v2-library-panels-export": "A funcionalidade do painel de controlo dinâmico é experimental e não tem paridade de funcionalidade total com o comportamento atual dos painéis de controlo. Baseia-se num novo formato de esquema, que não suporta painéis de biblioteca. Isto significa que, ao exportar o painel de controlo para o utilizar noutra instância, não incluiremos painéis de biblioteca. Tencionamos suportá-los à medida que avançamos no <2>ciclo de vida da funcionalidade.", "schema-v2-library-panels-export-title": "O Esquema de Painel de Controlo V2 ainda não suporta a exportação de painéis de biblioteca para serem utilizados noutra instância", - "title-dashboard-drastically-changed": "Painel de controlo alterado drasticamente" + "title-dashboard-drastically-changed": "" }, "save-dashboard-form-common-options": { "save-refresh-description-current-refresh-default": "Tornará a atualização atual a nova predefinição", @@ -7715,6 +7716,8 @@ }, "enable-highlighting": "Ativar destaque", "escape-newlines": "Corrigir sequências de novas linhas e tabulações ignoradas incorretamente nas linhas de registo", + "font-size-default": "", + "font-size-small": "", "hide-timestamps": "Ocultar registos de hora/data", "hide-unique-labels": "Ocultar etiquetas únicas", "newest-first": "Ordenado por registos mais recentes primeiro - Clique para mostrar os mais antigos primeiro", diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index 60f01244e92..fff7cb7c230 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -4465,6 +4465,7 @@ } }, "repeat-row-select2": { + "placeholder": "Выбрать", "variable-options": { "label": { "disable-repeating": "", @@ -5283,7 +5284,7 @@ }, "save-dashboard-form": { "aria-label-message": "сообщение", - "body-dashboard-drastically-changed": "Поскольку вы используете новые функции дашбордов, поддерживаемые только в новом формате схемы дашбордов Grafana, дашборд будет сохранен в новом формате. Выполните это действие, если оно действительно необходимо, или сохраните дашборд как новую копию.", + "body-dashboard-drastically-changed": "", "cancel-button": { "cancel": "Отмена" }, @@ -5299,7 +5300,7 @@ }, "schema-v2-library-panels-export": "Функциональность динамического дашборда является экспериментальной и не имеет полного паритета функциональных возможностей с текущим поведением дашбордов. Она основана на новом формате схемы, который не поддерживает панели библиотеки. Таким образом, экспорт дашборда для использования в другом экземпляре не будет включать панели библиотеки. Мы намерены поддерживать их по мере прохождения <2>жизненного цикла функции.", "schema-v2-library-panels-export-title": "Дашборды схемы V2 пока не поддерживают экспорт панелей библиотеки для использования в другом экземпляре", - "title-dashboard-drastically-changed": "Дашборд существенно изменен" + "title-dashboard-drastically-changed": "" }, "save-dashboard-form-common-options": { "save-refresh-description-current-refresh-default": "Текущее обновление станет новым значением по умолчанию", @@ -7767,6 +7768,8 @@ }, "enable-highlighting": "Включить выделение", "escape-newlines": "Исправить неправильно экранированные последовательности новой строки и табуляции в строках журнала", + "font-size-default": "", + "font-size-small": "", "hide-timestamps": "Скрыть метки времени", "hide-unique-labels": "Скрыть уникальные метки", "newest-first": "Сначала отображаются самые новые журналы. Нажмите, чтобы показать сначала самые старые", diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index 5f2a1d0eb85..a4af1a97a34 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -4427,6 +4427,7 @@ } }, "repeat-row-select2": { + "placeholder": "Välj", "variable-options": { "label": { "disable-repeating": "", @@ -5243,7 +5244,7 @@ }, "save-dashboard-form": { "aria-label-message": "meddelande", - "body-dashboard-drastically-changed": "Eftersom du använder nya instrumentpanelsfunktioner som endast stöds på det nya schemaformatet för Grafana-instrumentpaneler kommer instrumentpanelen att sparas i det nya formatet. Utför denna åtgärd om du verkligen vill göra det eller vill spara instrumentpanelen som en ny kopia.", + "body-dashboard-drastically-changed": "", "cancel-button": { "cancel": "Avbryt" }, @@ -5259,7 +5260,7 @@ }, "schema-v2-library-panels-export": "Funktionen för dynamiska instrumentpaneler är experimentell och har ännu inte fullständig funktionsparitet med nuvarande instrumentpanelbeteende. Det är baserat på ett nytt schemaformat som saknar stöd för bibliotekspaneler. Det innebär att om du vill använda instrumentpanelen i en annan instans och exporterar den kommer inte bibliotekspaneler att inkluderas under exporten. Vi har för avsikt att inkludera stöd för dem när vi går vidare med funktionens <2>livscykel.", "schema-v2-library-panels-export-title": "Instrumentpanelschema V2 saknar för närvarande stöd för export av bibliotekspaneler för användning i en annan instans", - "title-dashboard-drastically-changed": "Instrumentpanelen har ändrats drastiskt" + "title-dashboard-drastically-changed": "" }, "save-dashboard-form-common-options": { "save-refresh-description-current-refresh-default": "Kommer att göra den aktuella uppdateringen till ny standard", @@ -7715,6 +7716,8 @@ }, "enable-highlighting": "Aktivera markering", "escape-newlines": "Åtgärda felaktiga undantagstecken för radbrytningar och tabbar i loggrader", + "font-size-default": "", + "font-size-small": "", "hide-timestamps": "Dölj tidsstämplar", "hide-unique-labels": "Dölj unika etiketter", "newest-first": "Sorterat efter nyaste loggar först – klicka om du vill visa äldsta först", diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index 74de420a8fd..6bae3fef3ff 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -4427,6 +4427,7 @@ } }, "repeat-row-select2": { + "placeholder": "Seçin", "variable-options": { "label": { "disable-repeating": "", @@ -5243,7 +5244,7 @@ }, "save-dashboard-form": { "aria-label-message": "mesaj", - "body-dashboard-drastically-changed": "Yalnızca yeni Grafana pano şema biçiminde desteklenen yeni pano özelliklerini kullandığınız için pano yeni biçimde kaydedilecektir. Lütfen bu işlemi yapmak istediğinizden emin olun veya panoyu yeni bir kopya olarak kaydetmeyi tercih edin.", + "body-dashboard-drastically-changed": "", "cancel-button": { "cancel": "İptal" }, @@ -5259,7 +5260,7 @@ }, "schema-v2-library-panels-export": "Dinamik pano işlevi henüz test aşamasındadır ve mevcut panoların tüm işlevlerini tam olarak karşılamamaktadır. Yeni şema biçimi henüz kütüphane panellerini desteklemiyor. Bu nedenle, dışa aktarılan panolarda kütüphane panelleri yer almayacaktır. Özellik <2>geliştirme süreci ilerledikçe bu desteğin eklenmesi planlanmaktadır.", "schema-v2-library-panels-export-title": "Pano Şeması V2, kütüphane panellerinin başka bir örnekte kullanılmak üzere dışa aktarılmasını henüz desteklememektedir", - "title-dashboard-drastically-changed": "Pano büyük ölçüde değişti" + "title-dashboard-drastically-changed": "" }, "save-dashboard-form-common-options": { "save-refresh-description-current-refresh-default": "Mevcut yenilemeyi yeni varsayılan yapacak", @@ -7715,6 +7716,8 @@ }, "enable-highlighting": "Vurgulamayı etkinleştir", "escape-newlines": "Günlük kaydı satırlarındaki kaçış karakterli satır sonu ve sekme dizilerini düzelt", + "font-size-default": "", + "font-size-small": "", "hide-timestamps": "Zaman damgalarını gizle", "hide-unique-labels": "Benzersiz etiketleri gizle", "newest-first": "Günlükler yeniden eskiye sıralandı: En eskileri göstermek için tıklayın", diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index b1902f5aaab..da0e9c729f1 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -4408,6 +4408,7 @@ } }, "repeat-row-select2": { + "placeholder": "选择", "variable-options": { "label": { "disable-repeating": "", @@ -5223,7 +5224,7 @@ }, "save-dashboard-form": { "aria-label-message": "消息", - "body-dashboard-drastically-changed": "由于您正在使用的新数据面板功能仅支持新的 Grafana 数据面板架构格式,因此数据面板将以新格式保存。请确保您要执行此操作,或者您希望将数据面板保存为新副本。", + "body-dashboard-drastically-changed": "", "cancel-button": { "cancel": "取消" }, @@ -5239,7 +5240,7 @@ }, "schema-v2-library-panels-export": "动态数据面板功能是实验性的,并且与当前数据面板行为没有完全的功能对称性。它基于新的模式格式,且不支持库面板。这意味着,在导出数据面板以便在其他实例中使用时,其中不会包含库面板。我们打算在<2>生命周期功能中逐步支持它们。", "schema-v2-library-panels-export-title": "数据面板模式 V2 暂不支持导出库面板以便在其他实例中使用", - "title-dashboard-drastically-changed": "数据面板发生了重大变化" + "title-dashboard-drastically-changed": "" }, "save-dashboard-form-common-options": { "save-refresh-description-current-refresh-default": "将使当前刷新成为新的默认值", @@ -7689,6 +7690,8 @@ }, "enable-highlighting": "启用突出显示", "escape-newlines": "修复日志行中错误转义的换行和制表符序列", + "font-size-default": "", + "font-size-small": "", "hide-timestamps": "隐藏时间戳", "hide-unique-labels": "隐藏唯一标签", "newest-first": "先显示最新日志 - 点击以先显示最旧日志", diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index 29108535604..851bc9e0b19 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -4408,6 +4408,7 @@ } }, "repeat-row-select2": { + "placeholder": "選擇", "variable-options": { "label": { "disable-repeating": "", @@ -5223,7 +5224,7 @@ }, "save-dashboard-form": { "aria-label-message": "訊息", - "body-dashboard-drastically-changed": "由於您正在使用僅在新的 Grafana 儀表板架構格式中支援的新儀表板功能,因此儀表板將以新格式儲存。請確認您要執行此操作,還是要將儀表板儲存為新副本。", + "body-dashboard-drastically-changed": "", "cancel-button": { "cancel": "取消" }, @@ -5239,7 +5240,7 @@ }, "schema-v2-library-panels-export": "動態儀表板功能僅供試驗,並未與目前的儀表板行為具有完全的功能平等性。它以新的模式格式為基礎,不支援資料庫面板。這表示在匯出儀表板以便在另一個執行個體中使用時,我們不會包含資料庫面板。隨著我們在功能<2>生命週期中取得進展,我們計劃為其提供支援。", "schema-v2-library-panels-export-title": "儀表板架構 V2 尚不支援為了在另一個執行個體中使用而匯出資料庫面板", - "title-dashboard-drastically-changed": "儀表板已大幅變更" + "title-dashboard-drastically-changed": "" }, "save-dashboard-form-common-options": { "save-refresh-description-current-refresh-default": "將使目前的重新整理成為新的預設值", @@ -7689,6 +7690,8 @@ }, "enable-highlighting": "啟用醒目提示", "escape-newlines": "修復紀錄行中新行和分頁序列的錯誤轉義", + "font-size-default": "", + "font-size-small": "", "hide-timestamps": "隱藏時間戳記", "hide-unique-labels": "隱藏唯一標籤", "newest-first": "按最新紀錄排序 - 按一下以顯示最舊紀錄", diff --git a/scripts/drone/variables.star b/scripts/drone/variables.star index d5abeb28022..d0ed5fbd63e 100644 --- a/scripts/drone/variables.star +++ b/scripts/drone/variables.star @@ -3,7 +3,7 @@ global variables """ grabpl_version = "v3.1.2" -golang_version = "1.24.3" +golang_version = "1.24.4" # nodejs_version should match what's in ".nvmrc", but without the v prefix. nodejs_version = "22.11.0" diff --git a/scripts/go-workspace/go.mod b/scripts/go-workspace/go.mod index bc39d670ccd..5e23c2f70b2 100644 --- a/scripts/go-workspace/go.mod +++ b/scripts/go-workspace/go.mod @@ -1,5 +1,5 @@ module github.com/grafana/grafana/scripts/go-workspace -go 1.24.3 +go 1.24.4 require golang.org/x/mod v0.24.0 diff --git a/scripts/modowners/go.mod b/scripts/modowners/go.mod index 1b2315dde57..10b70c57884 100644 --- a/scripts/modowners/go.mod +++ b/scripts/modowners/go.mod @@ -1,5 +1,5 @@ module github.com/grafana/grafana/scripts/modowners -go 1.24.3 +go 1.24.4 require golang.org/x/mod v0.24.0 diff --git a/yarn.lock b/yarn.lock index 6a139363016..8b95b41edca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18292,7 +18292,6 @@ __metadata: regenerator-runtime: "npm:0.14.1" reselect: "npm:5.1.1" rimraf: "npm:6.0.1" - rudder-sdk-js: "npm:2.48.44" rxjs: "npm:7.8.2" sass: "npm:1.83.4" sass-loader: "npm:16.0.4" @@ -27971,13 +27970,6 @@ __metadata: languageName: node linkType: hard -"rudder-sdk-js@npm:2.48.44": - version: 2.48.44 - resolution: "rudder-sdk-js@npm:2.48.44" - checksum: 10/538bc405c0cb7faf33b910a39d51bd6df53fbdb176d4e83c33821e7a454178b65ced216ad0f055b6dffd201ccc45cead814138e20683e15424a02fb54b894e15 - languageName: node - linkType: hard - "run-applescript@npm:^7.0.0": version: 7.0.0 resolution: "run-applescript@npm:7.0.0"