Compare commits
146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| afdbc63250 | |||
| f67bd022be | |||
| ad989ae200 | |||
| fe6c2cdfee | |||
| 84b081ce37 | |||
| d4ae044801 | |||
| c9a14f1774 | |||
| d2b788eb53 | |||
| dffae66fdc | |||
| 5dbbe8164b | |||
| d1064da4cd | |||
| b57b8d4359 | |||
| 5219ccddb6 | |||
| c95e3da2d5 | |||
| 43d9fbc056 | |||
| 7b80c44ac7 | |||
| 7038ced64e | |||
| 98f271f345 | |||
| 60c4fab063 | |||
| f117691340 | |||
| c99eb8c62e | |||
| ce8663ac24 | |||
| 5dd9a14903 | |||
| 68bf19d840 | |||
| 220c29de89 | |||
| 91ab753368 | |||
| 250ca7985f | |||
| b57ed32484 | |||
| d0217588a3 | |||
| ce9ab6a89a | |||
| 8c8efd2494 | |||
| 69ccfd6bfc | |||
| 53aa5e8f7f | |||
| 69bf3068b3 | |||
| 1263a3d364 | |||
| e4b79e2fc8 | |||
| 0d1ec94548 | |||
| 23a51ec9c5 | |||
| 51dcdd3499 | |||
| 880bc23c85 | |||
| 6dc604c2ea | |||
| 77c500dc01 | |||
| bec4d225b3 | |||
| b91ca14f48 | |||
| 2aedbdb76f | |||
| 0d7f46c08a | |||
| 1b52718c23 | |||
| e61e406440 | |||
| 5cb4c311dc | |||
| 586410d8b5 | |||
| a0e894c6d8 | |||
| e4796b1de3 | |||
| 86a3aae204 | |||
| e0ad4eb7ed | |||
| f0c95a0a10 | |||
| 0b46123300 | |||
| 81b868ae91 | |||
| 2a6a48ac39 | |||
| f581a5a69b | |||
| 5ff18c0802 | |||
| 0c8c886930 | |||
| ba196958cd | |||
| 622c75af6d | |||
| e983aac141 | |||
| c42421e616 | |||
| e4ba1c1a6d | |||
| 909ed02218 | |||
| ad3763f04d | |||
| ca6ab973b4 | |||
| 9cd811b9e6 | |||
| 52cd096d92 | |||
| 98453fbcff | |||
| ccdafc3fb2 | |||
| 12abbd5a15 | |||
| 1c5caeb987 | |||
| 71a65e1f80 | |||
| ec12176220 | |||
| 0cf4f7c4de | |||
| b0785e506f | |||
| 5f8668b3aa | |||
| 368762c026 | |||
| a56fa3c7b5 | |||
| f5f9a66fa8 | |||
| eb6c22af36 | |||
| 125cc5fddd | |||
| 45c25ab1d9 | |||
| 7f34fae439 | |||
| f028b9dbdb | |||
| e95f8bf843 | |||
| f669bc4448 | |||
| a79cda3328 | |||
| c7986976e4 | |||
| 65cdf6cd45 | |||
| 7be93d9af4 | |||
| 347075bffe | |||
| 0db188e95d | |||
| f38df468b5 | |||
| c78c2d7231 | |||
| 8f4fa9ed05 | |||
| 0aae7e01bc | |||
| e4009a42a1 | |||
| 58e9e4a56d | |||
| 06d11d739b | |||
| dff9bea3e8 | |||
| 19cfab89f3 | |||
| 088bab8b38 | |||
| 9e8bdee283 | |||
| bb5bb00e4d | |||
| 5fcc67837a | |||
| 79f2016a66 | |||
| 7858dcb9c1 | |||
| 27eb488a96 | |||
| 97af86efb2 | |||
| f58ab2a6a1 | |||
| b96a1ae722 | |||
| a53875e621 | |||
| 9598ae6434 | |||
| ab0b05550f | |||
| 4518add556 | |||
| 00b89b0d29 | |||
| a3eedfeb73 | |||
| 1e8f1f74ea | |||
| 66b05914e2 | |||
| 0c60d356d1 | |||
| 41d7213d7e | |||
| efad6c7be0 | |||
| 74548dbb73 | |||
| e116254f32 | |||
| 751a399b03 | |||
| c9e044b2c7 | |||
| 92041e5a05 | |||
| 6ee1a6ea7f | |||
| 4f66b1df5a | |||
| 01f959be97 | |||
| f81deced02 | |||
| ca3bce54a8 | |||
| 3d3eeb4472 | |||
| 2b6e2c5737 | |||
| 306aee16a5 | |||
| 8319f62ef4 | |||
| b8b792f78a | |||
| 8a0f2fa9f3 | |||
| 9d980a9244 | |||
| e442720cdc | |||
| e616d04010 | |||
| 8e9675ce1c |
+3
-1
@@ -94,7 +94,6 @@
|
|||||||
/apps/shorturl/ @grafana/sharing-squad
|
/apps/shorturl/ @grafana/sharing-squad
|
||||||
/apps/secret/ @grafana/grafana-operator-experience-squad
|
/apps/secret/ @grafana/grafana-operator-experience-squad
|
||||||
/apps/scope/ @grafana/grafana-operator-experience-squad
|
/apps/scope/ @grafana/grafana-operator-experience-squad
|
||||||
/apps/investigations/ @fcjack @matryer @svennergr
|
|
||||||
/apps/advisor/ @grafana/plugins-platform-backend
|
/apps/advisor/ @grafana/plugins-platform-backend
|
||||||
/apps/iam/ @grafana/access-squad
|
/apps/iam/ @grafana/access-squad
|
||||||
/apps/sdk.mk @grafana/grafana-app-platform-squad
|
/apps/sdk.mk @grafana/grafana-app-platform-squad
|
||||||
@@ -102,6 +101,7 @@
|
|||||||
/apps/example/ @grafana/grafana-app-platform-squad
|
/apps/example/ @grafana/grafana-app-platform-squad
|
||||||
/apps/logsdrilldown/ @grafana/observability-logs
|
/apps/logsdrilldown/ @grafana/observability-logs
|
||||||
/apps/annotation/ @grafana/grafana-backend-services-squad
|
/apps/annotation/ @grafana/grafana-backend-services-squad
|
||||||
|
/apps/dashvalidator/ @grafana/sharing-squad
|
||||||
/pkg/api/ @grafana/grafana-backend-group
|
/pkg/api/ @grafana/grafana-backend-group
|
||||||
/pkg/apis/ @grafana/grafana-app-platform-squad
|
/pkg/apis/ @grafana/grafana-app-platform-squad
|
||||||
/pkg/apis/query @grafana/grafana-datasources-core-services
|
/pkg/apis/query @grafana/grafana-datasources-core-services
|
||||||
@@ -1191,6 +1191,7 @@ embed.go @grafana/grafana-as-code
|
|||||||
/pkg/registry/apps/advisor @grafana/plugins-platform-backend
|
/pkg/registry/apps/advisor @grafana/plugins-platform-backend
|
||||||
/pkg/registry/apps/alerting @grafana/alerting-backend
|
/pkg/registry/apps/alerting @grafana/alerting-backend
|
||||||
/pkg/registry/apps/plugins @grafana/plugins-platform-backend
|
/pkg/registry/apps/plugins @grafana/plugins-platform-backend
|
||||||
|
/pkg/registry/apps/dashvalidator @grafana/sharing-squad
|
||||||
/pkg/codegen/ @grafana/grafana-as-code
|
/pkg/codegen/ @grafana/grafana-as-code
|
||||||
/pkg/codegen/generators @grafana/grafana-as-code
|
/pkg/codegen/generators @grafana/grafana-as-code
|
||||||
/pkg/kinds/*/*_gen.go @grafana/grafana-as-code
|
/pkg/kinds/*/*_gen.go @grafana/grafana-as-code
|
||||||
@@ -1276,6 +1277,7 @@ embed.go @grafana/grafana-as-code
|
|||||||
/.github/workflows/i18n-crowdin-download.yml @grafana/grafana-frontend-platform
|
/.github/workflows/i18n-crowdin-download.yml @grafana/grafana-frontend-platform
|
||||||
/.github/workflows/i18n-crowdin-create-tasks.yml @grafana/grafana-frontend-platform
|
/.github/workflows/i18n-crowdin-create-tasks.yml @grafana/grafana-frontend-platform
|
||||||
/.github/workflows/i18n-verify.yml @grafana/grafana-frontend-platform
|
/.github/workflows/i18n-verify.yml @grafana/grafana-frontend-platform
|
||||||
|
/.github/workflows/deploy-storybook.yml @grafana/grafana-frontend-platform
|
||||||
/.github/workflows/deploy-storybook-preview.yml @grafana/grafana-frontend-platform
|
/.github/workflows/deploy-storybook-preview.yml @grafana/grafana-frontend-platform
|
||||||
/.github/workflows/scripts/crowdin/create-tasks.ts @grafana/grafana-frontend-platform
|
/.github/workflows/scripts/crowdin/create-tasks.ts @grafana/grafana-frontend-platform
|
||||||
/.github/workflows/scripts/publish-frontend-metrics.mts @grafana/grafana-frontend-platform
|
/.github/workflows/scripts/publish-frontend-metrics.mts @grafana/grafana-frontend-platform
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ outputs:
|
|||||||
frontend:
|
frontend:
|
||||||
description: Whether the frontend or self has changed in any way
|
description: Whether the frontend or self has changed in any way
|
||||||
value: ${{ steps.changed-files.outputs.frontend_any_changed || 'true' }}
|
value: ${{ steps.changed-files.outputs.frontend_any_changed || 'true' }}
|
||||||
|
frontend-packages:
|
||||||
|
description: Whether any frontend packages have changed
|
||||||
|
value: ${{ steps.changed-files.outputs.frontend_packages_any_changed || 'true' }}
|
||||||
e2e:
|
e2e:
|
||||||
description: Whether the e2e tests or self have changed in any way
|
description: Whether the e2e tests or self have changed in any way
|
||||||
value: ${{ steps.changed-files.outputs.e2e_any_changed == 'true' ||
|
value: ${{ steps.changed-files.outputs.e2e_any_changed == 'true' ||
|
||||||
@@ -97,6 +100,12 @@ runs:
|
|||||||
- '.yarn/**'
|
- '.yarn/**'
|
||||||
- 'apps/dashboard/pkg/migration/**'
|
- 'apps/dashboard/pkg/migration/**'
|
||||||
- '${{ inputs.self }}'
|
- '${{ inputs.self }}'
|
||||||
|
frontend_packages:
|
||||||
|
- '.github/actions/checkout/**'
|
||||||
|
- '.github/actions/change-detection/**'
|
||||||
|
- 'packages/**'
|
||||||
|
- './scripts/validate-npm-packages.sh'
|
||||||
|
- '${{ inputs.self }}'
|
||||||
e2e:
|
e2e:
|
||||||
- 'e2e/**'
|
- 'e2e/**'
|
||||||
- 'e2e-playwright/**'
|
- 'e2e-playwright/**'
|
||||||
@@ -153,6 +162,8 @@ runs:
|
|||||||
echo " --> ${{ steps.changed-files.outputs.backend_all_changed_files }}"
|
echo " --> ${{ steps.changed-files.outputs.backend_all_changed_files }}"
|
||||||
echo "Frontend: ${{ steps.changed-files.outputs.frontend_any_changed || 'true' }}"
|
echo "Frontend: ${{ steps.changed-files.outputs.frontend_any_changed || 'true' }}"
|
||||||
echo " --> ${{ steps.changed-files.outputs.frontend_all_changed_files }}"
|
echo " --> ${{ steps.changed-files.outputs.frontend_all_changed_files }}"
|
||||||
|
echo "Frontend packages: ${{ steps.changed-files.outputs.frontend_packages_any_changed || 'true' }}"
|
||||||
|
echo " --> ${{ steps.changed-files.outputs.frontend_packages_all_changed_files }}"
|
||||||
echo "E2E: ${{ steps.changed-files.outputs.e2e_any_changed || 'true' }}"
|
echo "E2E: ${{ steps.changed-files.outputs.e2e_any_changed || 'true' }}"
|
||||||
echo " --> ${{ steps.changed-files.outputs.e2e_all_changed_files }}"
|
echo " --> ${{ steps.changed-files.outputs.e2e_all_changed_files }}"
|
||||||
echo " --> ${{ steps.changed-files.outputs.backend_all_changed_files }}"
|
echo " --> ${{ steps.changed-files.outputs.backend_all_changed_files }}"
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ description: Sets up a node.js environment with presets for the Grafana reposito
|
|||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
cache-dependency-path: 'yarn.lock'
|
cache-dependency-path: 'yarn.lock'
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ updates:
|
|||||||
- "/apps/dashboard"
|
- "/apps/dashboard"
|
||||||
- "/apps/folder"
|
- "/apps/folder"
|
||||||
- "/apps/iam"
|
- "/apps/iam"
|
||||||
- "/apps/investigations"
|
|
||||||
- "/apps/playlist"
|
- "/apps/playlist"
|
||||||
- "/apps/plugins"
|
- "/apps/plugins"
|
||||||
- "/apps/preferences"
|
- "/apps/preferences"
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
name: Deploy Storybook
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
# push:
|
||||||
|
# branches:
|
||||||
|
# - main
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
detect-changes:
|
||||||
|
# Only run in grafana/grafana
|
||||||
|
if: github.repository == 'grafana/grafana'
|
||||||
|
name: Detect whether code changed
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
outputs:
|
||||||
|
changed-frontend-packages: ${{ steps.detect-changes.outputs.frontend-packages }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
persist-credentials: true # required to get more history in the changed-files action
|
||||||
|
fetch-depth: 2
|
||||||
|
- name: Detect changes
|
||||||
|
id: detect-changes
|
||||||
|
uses: ./.github/actions/change-detection
|
||||||
|
with:
|
||||||
|
self: .github/workflows/deploy-storybook.yml
|
||||||
|
deploy-storybook:
|
||||||
|
name: Deploy Storybook
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: detect-changes
|
||||||
|
# Only run in grafana/grafana
|
||||||
|
if: github.repository == 'grafana/grafana' && needs.detect-changes.outputs.changed-frontend-packages == 'true'
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
BUCKET_NAME: grafana-storybook
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: ./.github/actions/setup-node
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn install --immutable
|
||||||
|
|
||||||
|
- name: Build storybook
|
||||||
|
run: yarn storybook:build
|
||||||
|
|
||||||
|
# Create the GCS folder name
|
||||||
|
# Right now, this just returns "canary"
|
||||||
|
# But we'll expand this to work for "latest" as well in the future
|
||||||
|
- name: Create deploy name
|
||||||
|
id: create-deploy-name
|
||||||
|
run: |
|
||||||
|
echo "deploy-name=canary" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Upload Storybook
|
||||||
|
uses: grafana/shared-workflows/actions/push-to-gcs@main
|
||||||
|
with:
|
||||||
|
environment: prod
|
||||||
|
bucket: ${{ env.BUCKET_NAME }}
|
||||||
|
bucket_path: ${{ steps.create-deploy-name.outputs.deploy-name }}
|
||||||
|
path: packages/grafana-ui/dist/storybook
|
||||||
|
service_account: github-gf-storybook-deploy@grafanalabs-workload-identity.iam.gserviceaccount.com
|
||||||
|
parent: false
|
||||||
@@ -17,6 +17,7 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
changed: ${{ steps.detect-changes.outputs.frontend }}
|
changed: ${{ steps.detect-changes.outputs.frontend }}
|
||||||
prettier: ${{ steps.detect-changes.outputs.frontend == 'true' || steps.detect-changes.outputs.docs == 'true' }}
|
prettier: ${{ steps.detect-changes.outputs.frontend == 'true' || steps.detect-changes.outputs.docs == 'true' }}
|
||||||
|
changed-frontend-packages: ${{ steps.detect-changes.outputs.frontend-packages }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
@@ -42,11 +43,8 @@ jobs:
|
|||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-node@v6
|
- name: Setup Node
|
||||||
with:
|
uses: ./.github/actions/setup-node
|
||||||
node-version-file: '.nvmrc'
|
|
||||||
cache: 'yarn'
|
|
||||||
cache-dependency-path: 'yarn.lock'
|
|
||||||
- run: yarn install --immutable --check-cache
|
- run: yarn install --immutable --check-cache
|
||||||
- run: yarn run prettier:check
|
- run: yarn run prettier:check
|
||||||
- run: yarn run lint
|
- run: yarn run lint
|
||||||
@@ -63,11 +61,8 @@ jobs:
|
|||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-node@v6
|
- name: Setup Node
|
||||||
with:
|
uses: ./.github/actions/setup-node
|
||||||
node-version-file: '.nvmrc'
|
|
||||||
cache: 'yarn'
|
|
||||||
cache-dependency-path: 'yarn.lock'
|
|
||||||
- name: Setup Enterprise
|
- name: Setup Enterprise
|
||||||
uses: ./.github/actions/setup-enterprise
|
uses: ./.github/actions/setup-enterprise
|
||||||
with:
|
with:
|
||||||
@@ -89,11 +84,8 @@ jobs:
|
|||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-node@v6
|
- name: Setup Node
|
||||||
with:
|
uses: ./.github/actions/setup-node
|
||||||
node-version-file: '.nvmrc'
|
|
||||||
cache: 'yarn'
|
|
||||||
cache-dependency-path: 'yarn.lock'
|
|
||||||
- run: yarn install --immutable --check-cache
|
- run: yarn install --immutable --check-cache
|
||||||
- run: yarn run typecheck
|
- run: yarn run typecheck
|
||||||
lint-frontend-typecheck-enterprise:
|
lint-frontend-typecheck-enterprise:
|
||||||
@@ -109,11 +101,8 @@ jobs:
|
|||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-node@v6
|
- name: Setup Node
|
||||||
with:
|
uses: ./.github/actions/setup-node
|
||||||
node-version-file: '.nvmrc'
|
|
||||||
cache: 'yarn'
|
|
||||||
cache-dependency-path: 'yarn.lock'
|
|
||||||
- name: Setup Enterprise
|
- name: Setup Enterprise
|
||||||
uses: ./.github/actions/setup-enterprise
|
uses: ./.github/actions/setup-enterprise
|
||||||
with:
|
with:
|
||||||
@@ -133,11 +122,8 @@ jobs:
|
|||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-node@v6
|
- name: Setup Node
|
||||||
with:
|
uses: ./.github/actions/setup-node
|
||||||
node-version-file: '.nvmrc'
|
|
||||||
cache: 'yarn'
|
|
||||||
cache-dependency-path: 'yarn.lock'
|
|
||||||
- run: yarn install --immutable --check-cache
|
- run: yarn install --immutable --check-cache
|
||||||
- name: Generate API clients
|
- name: Generate API clients
|
||||||
run: |
|
run: |
|
||||||
@@ -164,11 +150,8 @@ jobs:
|
|||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-node@v6
|
- name: Setup Node
|
||||||
with:
|
uses: ./.github/actions/setup-node
|
||||||
node-version-file: '.nvmrc'
|
|
||||||
cache: 'yarn'
|
|
||||||
cache-dependency-path: 'yarn.lock'
|
|
||||||
- name: Setup Enterprise
|
- name: Setup Enterprise
|
||||||
uses: ./.github/actions/setup-enterprise
|
uses: ./.github/actions/setup-enterprise
|
||||||
with:
|
with:
|
||||||
@@ -187,3 +170,26 @@ jobs:
|
|||||||
echo "${uncommited_error_message}"
|
echo "${uncommited_error_message}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
lint-frontend-packed-packages:
|
||||||
|
needs: detect-changes
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
if: github.event_name == 'pull_request' && needs.detect-changes.outputs.changed-frontend-packages == 'true'
|
||||||
|
name: Verify packed frontend packages
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout build commit
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
- name: Setup Node
|
||||||
|
uses: ./.github/actions/setup-node
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn install --immutable
|
||||||
|
- name: Build and pack packages
|
||||||
|
run: |
|
||||||
|
yarn run packages:build
|
||||||
|
yarn run packages:pack
|
||||||
|
- name: Validate packages
|
||||||
|
run: ./scripts/validate-npm-packages.sh
|
||||||
|
|||||||
@@ -67,14 +67,6 @@ linters:
|
|||||||
deny:
|
deny:
|
||||||
- pkg: github.com/grafana/grafana/pkg
|
- pkg: github.com/grafana/grafana/pkg
|
||||||
desc: apiserver is not allowed to import grafana core
|
desc: apiserver is not allowed to import grafana core
|
||||||
apps-investigation:
|
|
||||||
list-mode: lax
|
|
||||||
files:
|
|
||||||
- ./apps/investigations/*
|
|
||||||
- ./apps/investigations/**/*
|
|
||||||
deny:
|
|
||||||
- pkg: github.com/grafana/grafana/pkg
|
|
||||||
desc: apps/investigations is not allowed to import grafana core
|
|
||||||
apps-playlist:
|
apps-playlist:
|
||||||
list-mode: lax
|
list-mode: lax
|
||||||
files:
|
files:
|
||||||
|
|||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
diff --git a/dist/builder-manager/index.js b/dist/builder-manager/index.js
|
diff --git a/dist/builder-manager/index.js b/dist/builder-manager/index.js
|
||||||
index 3d7f9b213dae1801bda62b31db31b9113e382ccd..212501c63d20146c29db63fb0f6300c6779eecb5 100644
|
index ac8ac6a5f6a3b7852c4064e93dc9acd3201289e6..34a0a5a5c38dd7fe525c9ebd382a10a451d4d4f3 100644
|
||||||
--- a/dist/builder-manager/index.js
|
--- a/dist/builder-manager/index.js
|
||||||
+++ b/dist/builder-manager/index.js
|
+++ b/dist/builder-manager/index.js
|
||||||
@@ -1970,7 +1970,7 @@ var pa = /^\/($|\?)/, G, C, xt = /* @__PURE__ */ o(async (e) => {
|
@@ -1974,7 +1974,7 @@ var pa = /^\/($|\?)/, G, C, xt = /* @__PURE__ */ o(async (e) => {
|
||||||
bundle: !0,
|
bundle: !0,
|
||||||
minify: !0,
|
minify: !0,
|
||||||
sourcemap: !1,
|
sourcemap: !1,
|
||||||
+2
-1
@@ -91,6 +91,7 @@ COPY pkg/storage/unified/resource pkg/storage/unified/resource
|
|||||||
COPY pkg/storage/unified/resourcepb pkg/storage/unified/resourcepb
|
COPY pkg/storage/unified/resourcepb pkg/storage/unified/resourcepb
|
||||||
COPY pkg/storage/unified/apistore pkg/storage/unified/apistore
|
COPY pkg/storage/unified/apistore pkg/storage/unified/apistore
|
||||||
COPY pkg/semconv pkg/semconv
|
COPY pkg/semconv pkg/semconv
|
||||||
|
COPY pkg/plugins pkg/plugins
|
||||||
COPY pkg/aggregator pkg/aggregator
|
COPY pkg/aggregator pkg/aggregator
|
||||||
COPY apps/playlist apps/playlist
|
COPY apps/playlist apps/playlist
|
||||||
COPY apps/quotas apps/quotas
|
COPY apps/quotas apps/quotas
|
||||||
@@ -103,10 +104,10 @@ COPY apps/collections apps/collections
|
|||||||
COPY apps/provisioning apps/provisioning
|
COPY apps/provisioning apps/provisioning
|
||||||
COPY apps/secret apps/secret
|
COPY apps/secret apps/secret
|
||||||
COPY apps/scope apps/scope
|
COPY apps/scope apps/scope
|
||||||
COPY apps/investigations apps/investigations
|
|
||||||
COPY apps/logsdrilldown apps/logsdrilldown
|
COPY apps/logsdrilldown apps/logsdrilldown
|
||||||
COPY apps/advisor apps/advisor
|
COPY apps/advisor apps/advisor
|
||||||
COPY apps/dashboard apps/dashboard
|
COPY apps/dashboard apps/dashboard
|
||||||
|
COPY apps/dashvalidator apps/dashvalidator
|
||||||
COPY apps/folder apps/folder
|
COPY apps/folder apps/folder
|
||||||
COPY apps/iam apps/iam
|
COPY apps/iam apps/iam
|
||||||
COPY apps apps
|
COPY apps apps
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ i18n-extract-enterprise:
|
|||||||
@echo "Skipping i18n extract for Enterprise: not enabled"
|
@echo "Skipping i18n extract for Enterprise: not enabled"
|
||||||
else
|
else
|
||||||
i18n-extract-enterprise:
|
i18n-extract-enterprise:
|
||||||
@echo "Extracting i18n strings for Enterprise"
|
@echo "Extracting i18n strings for Enterprise"
|
||||||
cd public/locales/enterprise && yarn run i18next-cli extract --sync-primary
|
cd public/locales/enterprise && yarn run i18next-cli extract --sync-primary
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -227,6 +227,10 @@ fix-cue:
|
|||||||
gen-jsonnet:
|
gen-jsonnet:
|
||||||
go generate ./devenv/jsonnet
|
go generate ./devenv/jsonnet
|
||||||
|
|
||||||
|
.PHONY: gen-themes
|
||||||
|
gen-themes:
|
||||||
|
go generate ./pkg/services/preference
|
||||||
|
|
||||||
.PHONY: update-workspace
|
.PHONY: update-workspace
|
||||||
update-workspace: gen-go
|
update-workspace: gen-go
|
||||||
@echo "updating workspace"
|
@echo "updating workspace"
|
||||||
@@ -244,6 +248,7 @@ build-go-fast: ## Build all Go binaries without updating workspace.
|
|||||||
.PHONY: build-backend
|
.PHONY: build-backend
|
||||||
build-backend: ## Build Grafana backend.
|
build-backend: ## Build Grafana backend.
|
||||||
@echo "build backend"
|
@echo "build backend"
|
||||||
|
$(MAKE) gen-themes
|
||||||
$(GO) run build.go $(GO_BUILD_FLAGS) build-backend
|
$(GO) run build.go $(GO_BUILD_FLAGS) build-backend
|
||||||
|
|
||||||
.PHONY: build-air
|
.PHONY: build-air
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ go 1.25.5
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-kit/log v0.2.1
|
github.com/go-kit/log v0.2.1
|
||||||
github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f
|
github.com/grafana/alerting v0.0.0-20260112172717-98a49ed9557f
|
||||||
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4
|
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4
|
||||||
github.com/grafana/grafana-app-sdk v0.48.7
|
github.com/grafana/grafana-app-sdk v0.48.7
|
||||||
github.com/grafana/grafana-app-sdk/logging v0.48.7
|
github.com/grafana/grafana-app-sdk/logging v0.48.7
|
||||||
|
|||||||
@@ -243,8 +243,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f h1:Br4SaUL3dnVopKKNhDavCLgehw60jdtl/sIxdfzmVts=
|
github.com/grafana/alerting v0.0.0-20260112172717-98a49ed9557f h1:3bXOyht68qkfvD6Y8z8XoenFbytSSOIkr/s+AqRzj0o=
|
||||||
github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU=
|
github.com/grafana/alerting v0.0.0-20260112172717-98a49ed9557f/go.mod h1:Ji0SfJChcwjgq8ljy6Y5CcYfHfAYKXjKYeysOoDS/6s=
|
||||||
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI=
|
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI=
|
||||||
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4=
|
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4=
|
||||||
github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo=
|
github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo=
|
||||||
|
|||||||
+287
@@ -0,0 +1,287 @@
|
|||||||
|
{
|
||||||
|
"kind": "Dashboard",
|
||||||
|
"apiVersion": "dashboard.grafana.app/v1beta1",
|
||||||
|
"metadata": {
|
||||||
|
"name": "legacy-ds-ref"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"annotations": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"builtIn": 1,
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana",
|
||||||
|
"uid": "-- Grafana --"
|
||||||
|
},
|
||||||
|
"enable": true,
|
||||||
|
"hide": true,
|
||||||
|
"iconColor": "rgba(0, 211, 255, 1)",
|
||||||
|
"name": "Annotations & Alerts",
|
||||||
|
"type": "dashboard"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"datasource": "${datasource}",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"showValues": false,
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Minimum cluster size"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "color",
|
||||||
|
"value": {
|
||||||
|
"fixedColor": "red",
|
||||||
|
"mode": "fixed"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "custom.lineStyle",
|
||||||
|
"value": {
|
||||||
|
"dash": [10, 10],
|
||||||
|
"fill": "dash"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "custom.lineWidth",
|
||||||
|
"value": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 9,
|
||||||
|
"w": 8,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 16,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"timeCompare": false,
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": "${datasource}",
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "count by (version) (alloy_build_info{cluster=~\"$cluster\", namespace=~\"$namespace\", job=~\"$job\"})",
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "{{version}}",
|
||||||
|
"range": true,
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Number of Alloy Instances",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": "${datasource}",
|
||||||
|
"description": "CPU usage of the Alloy process relative to 1 CPU core.\n\nFor example, 100% means using one entire CPU core.\n",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"showValues": false,
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "percentunit"
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"__systemRef": "hideSeriesFrom",
|
||||||
|
"matcher": {
|
||||||
|
"id": "byNames",
|
||||||
|
"options": {
|
||||||
|
"mode": "exclude",
|
||||||
|
"names": [
|
||||||
|
"Total"
|
||||||
|
],
|
||||||
|
"prefix": "All except:",
|
||||||
|
"readOnly": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.hideFrom",
|
||||||
|
"value": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": true,
|
||||||
|
"viz": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 9,
|
||||||
|
"w": 8,
|
||||||
|
"x": 8,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 17,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"timeCompare": false,
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": "${datasource}",
|
||||||
|
"expr": "rate(alloy_resources_process_cpu_seconds_total{cluster=~\"$cluster\", namespace=~\"$namespace\", job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])\n",
|
||||||
|
"hide": true,
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "{{instance}}",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": "${datasource}",
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "sum(rate(alloy_resources_process_cpu_seconds_total{cluster=~\"$cluster\", namespace=~\"$namespace\", job=~\"$job\", instance=~\"$instance\"}[$__rate_interval]))",
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "Total",
|
||||||
|
"range": true,
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "CPU usage",
|
||||||
|
"type": "timeseries"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": {
|
||||||
|
"from": "now-90m",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timezone": "utc",
|
||||||
|
"title": "Legacy DS Panel Query Ref",
|
||||||
|
"weekStart": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
+206
@@ -852,6 +852,194 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"panel-7": {
|
||||||
|
"kind": "Panel",
|
||||||
|
"spec": {
|
||||||
|
"id": 7,
|
||||||
|
"title": "Single Dashboard DS Query",
|
||||||
|
"description": "Panel with a single -- Dashboard -- datasource query",
|
||||||
|
"links": [],
|
||||||
|
"data": {
|
||||||
|
"kind": "QueryGroup",
|
||||||
|
"spec": {
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"kind": "PanelQuery",
|
||||||
|
"spec": {
|
||||||
|
"query": {
|
||||||
|
"kind": "datasource",
|
||||||
|
"spec": {
|
||||||
|
"panelId": 1,
|
||||||
|
"withTransforms": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"refId": "A",
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"transformations": [],
|
||||||
|
"queryOptions": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vizConfig": {
|
||||||
|
"kind": "stat",
|
||||||
|
"spec": {
|
||||||
|
"pluginVersion": "12.1.0-pre",
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"value": 0,
|
||||||
|
"color": "green"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"panel-8": {
|
||||||
|
"kind": "Panel",
|
||||||
|
"spec": {
|
||||||
|
"id": 8,
|
||||||
|
"title": "Multiple Dashboard DS Queries",
|
||||||
|
"description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
|
||||||
|
"links": [],
|
||||||
|
"data": {
|
||||||
|
"kind": "QueryGroup",
|
||||||
|
"spec": {
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"kind": "PanelQuery",
|
||||||
|
"spec": {
|
||||||
|
"query": {
|
||||||
|
"kind": "datasource",
|
||||||
|
"spec": {
|
||||||
|
"panelId": 1,
|
||||||
|
"withTransforms": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"refId": "A",
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "PanelQuery",
|
||||||
|
"spec": {
|
||||||
|
"query": {
|
||||||
|
"kind": "datasource",
|
||||||
|
"spec": {
|
||||||
|
"panelId": 2,
|
||||||
|
"withTransforms": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"refId": "B",
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "PanelQuery",
|
||||||
|
"spec": {
|
||||||
|
"query": {
|
||||||
|
"kind": "datasource",
|
||||||
|
"spec": {
|
||||||
|
"panelId": 3,
|
||||||
|
"withTransforms": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"refId": "C",
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"transformations": [],
|
||||||
|
"queryOptions": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vizConfig": {
|
||||||
|
"kind": "stat",
|
||||||
|
"spec": {
|
||||||
|
"pluginVersion": "12.1.0-pre",
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"value": 0,
|
||||||
|
"color": "green"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
@@ -914,6 +1102,24 @@
|
|||||||
"name": "panel-6"
|
"name": "panel-6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "AutoGridLayoutItem",
|
||||||
|
"spec": {
|
||||||
|
"element": {
|
||||||
|
"kind": "ElementReference",
|
||||||
|
"name": "panel-7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "AutoGridLayoutItem",
|
||||||
|
"spec": {
|
||||||
|
"element": {
|
||||||
|
"kind": "ElementReference",
|
||||||
|
"name": "panel-8"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+220
@@ -879,6 +879,200 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"panel-7": {
|
||||||
|
"kind": "Panel",
|
||||||
|
"spec": {
|
||||||
|
"id": 7,
|
||||||
|
"title": "Single Dashboard DS Query",
|
||||||
|
"description": "Panel with a single -- Dashboard -- datasource query",
|
||||||
|
"links": [],
|
||||||
|
"data": {
|
||||||
|
"kind": "QueryGroup",
|
||||||
|
"spec": {
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"kind": "PanelQuery",
|
||||||
|
"spec": {
|
||||||
|
"query": {
|
||||||
|
"kind": "DataQuery",
|
||||||
|
"group": "datasource",
|
||||||
|
"version": "v0",
|
||||||
|
"datasource": {
|
||||||
|
"name": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"panelId": 1,
|
||||||
|
"withTransforms": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"refId": "A",
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"transformations": [],
|
||||||
|
"queryOptions": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vizConfig": {
|
||||||
|
"kind": "VizConfig",
|
||||||
|
"group": "stat",
|
||||||
|
"version": "12.1.0-pre",
|
||||||
|
"spec": {
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"value": 0,
|
||||||
|
"color": "green"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"panel-8": {
|
||||||
|
"kind": "Panel",
|
||||||
|
"spec": {
|
||||||
|
"id": 8,
|
||||||
|
"title": "Multiple Dashboard DS Queries",
|
||||||
|
"description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
|
||||||
|
"links": [],
|
||||||
|
"data": {
|
||||||
|
"kind": "QueryGroup",
|
||||||
|
"spec": {
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"kind": "PanelQuery",
|
||||||
|
"spec": {
|
||||||
|
"query": {
|
||||||
|
"kind": "DataQuery",
|
||||||
|
"group": "datasource",
|
||||||
|
"version": "v0",
|
||||||
|
"datasource": {
|
||||||
|
"name": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"panelId": 1,
|
||||||
|
"withTransforms": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"refId": "A",
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "PanelQuery",
|
||||||
|
"spec": {
|
||||||
|
"query": {
|
||||||
|
"kind": "DataQuery",
|
||||||
|
"group": "datasource",
|
||||||
|
"version": "v0",
|
||||||
|
"datasource": {
|
||||||
|
"name": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"panelId": 2,
|
||||||
|
"withTransforms": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"refId": "B",
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "PanelQuery",
|
||||||
|
"spec": {
|
||||||
|
"query": {
|
||||||
|
"kind": "DataQuery",
|
||||||
|
"group": "datasource",
|
||||||
|
"version": "v0",
|
||||||
|
"datasource": {
|
||||||
|
"name": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"panelId": 3,
|
||||||
|
"withTransforms": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"refId": "C",
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"transformations": [],
|
||||||
|
"queryOptions": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vizConfig": {
|
||||||
|
"kind": "VizConfig",
|
||||||
|
"group": "stat",
|
||||||
|
"version": "12.1.0-pre",
|
||||||
|
"spec": {
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"value": 0,
|
||||||
|
"color": "green"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
@@ -973,6 +1167,32 @@
|
|||||||
"name": "panel-6"
|
"name": "panel-6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "GridLayoutItem",
|
||||||
|
"spec": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 6,
|
||||||
|
"width": 8,
|
||||||
|
"height": 3,
|
||||||
|
"element": {
|
||||||
|
"kind": "ElementReference",
|
||||||
|
"name": "panel-7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "GridLayoutItem",
|
||||||
|
"spec": {
|
||||||
|
"x": 8,
|
||||||
|
"y": 6,
|
||||||
|
"width": 8,
|
||||||
|
"height": 3,
|
||||||
|
"element": {
|
||||||
|
"kind": "ElementReference",
|
||||||
|
"name": "panel-8"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+294
@@ -0,0 +1,294 @@
|
|||||||
|
{
|
||||||
|
"kind": "Dashboard",
|
||||||
|
"apiVersion": "dashboard.grafana.app/v0alpha1",
|
||||||
|
"metadata": {
|
||||||
|
"name": "legacy-ds-ref"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"annotations": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"builtIn": 1,
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana",
|
||||||
|
"uid": "-- Grafana --"
|
||||||
|
},
|
||||||
|
"enable": true,
|
||||||
|
"hide": true,
|
||||||
|
"iconColor": "rgba(0, 211, 255, 1)",
|
||||||
|
"name": "Annotations \u0026 Alerts",
|
||||||
|
"type": "dashboard"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"datasource": "${datasource}",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"showValues": false,
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Minimum cluster size"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "color",
|
||||||
|
"value": {
|
||||||
|
"fixedColor": "red",
|
||||||
|
"mode": "fixed"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "custom.lineStyle",
|
||||||
|
"value": {
|
||||||
|
"dash": [
|
||||||
|
10,
|
||||||
|
10
|
||||||
|
],
|
||||||
|
"fill": "dash"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "custom.lineWidth",
|
||||||
|
"value": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 9,
|
||||||
|
"w": 8,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 16,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"timeCompare": false,
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": "${datasource}",
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "count by (version) (alloy_build_info{cluster=~\"$cluster\", namespace=~\"$namespace\", job=~\"$job\"})",
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "{{version}}",
|
||||||
|
"range": true,
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Number of Alloy Instances",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": "${datasource}",
|
||||||
|
"description": "CPU usage of the Alloy process relative to 1 CPU core.\n\nFor example, 100% means using one entire CPU core.\n",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"showValues": false,
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "percentunit"
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"__systemRef": "hideSeriesFrom",
|
||||||
|
"matcher": {
|
||||||
|
"id": "byNames",
|
||||||
|
"options": {
|
||||||
|
"mode": "exclude",
|
||||||
|
"names": [
|
||||||
|
"Total"
|
||||||
|
],
|
||||||
|
"prefix": "All except:",
|
||||||
|
"readOnly": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.hideFrom",
|
||||||
|
"value": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": true,
|
||||||
|
"viz": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 9,
|
||||||
|
"w": 8,
|
||||||
|
"x": 8,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 17,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"timeCompare": false,
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": "${datasource}",
|
||||||
|
"expr": "rate(alloy_resources_process_cpu_seconds_total{cluster=~\"$cluster\", namespace=~\"$namespace\", job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])\n",
|
||||||
|
"hide": true,
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "{{instance}}",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": "${datasource}",
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "sum(rate(alloy_resources_process_cpu_seconds_total{cluster=~\"$cluster\", namespace=~\"$namespace\", job=~\"$job\", instance=~\"$instance\"}[$__rate_interval]))",
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "Total",
|
||||||
|
"range": true,
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "CPU usage",
|
||||||
|
"type": "timeseries"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": {
|
||||||
|
"from": "now-90m",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timezone": "utc",
|
||||||
|
"title": "Legacy DS Panel Query Ref",
|
||||||
|
"weekStart": ""
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"conversion": {
|
||||||
|
"failed": false,
|
||||||
|
"storedVersion": "v1beta1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+405
@@ -0,0 +1,405 @@
|
|||||||
|
{
|
||||||
|
"kind": "Dashboard",
|
||||||
|
"apiVersion": "dashboard.grafana.app/v2alpha1",
|
||||||
|
"metadata": {
|
||||||
|
"name": "legacy-ds-ref"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"annotations": [
|
||||||
|
{
|
||||||
|
"kind": "AnnotationQuery",
|
||||||
|
"spec": {
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana",
|
||||||
|
"uid": "-- Grafana --"
|
||||||
|
},
|
||||||
|
"query": {
|
||||||
|
"kind": "grafana",
|
||||||
|
"spec": {}
|
||||||
|
},
|
||||||
|
"enable": true,
|
||||||
|
"hide": true,
|
||||||
|
"iconColor": "rgba(0, 211, 255, 1)",
|
||||||
|
"name": "Annotations \u0026 Alerts",
|
||||||
|
"builtIn": true,
|
||||||
|
"legacyOptions": {
|
||||||
|
"type": "dashboard"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cursorSync": "Off",
|
||||||
|
"editable": true,
|
||||||
|
"elements": {
|
||||||
|
"panel-16": {
|
||||||
|
"kind": "Panel",
|
||||||
|
"spec": {
|
||||||
|
"id": 16,
|
||||||
|
"title": "Number of Alloy Instances",
|
||||||
|
"description": "",
|
||||||
|
"links": [],
|
||||||
|
"data": {
|
||||||
|
"kind": "QueryGroup",
|
||||||
|
"spec": {
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"kind": "PanelQuery",
|
||||||
|
"spec": {
|
||||||
|
"query": {
|
||||||
|
"kind": "",
|
||||||
|
"spec": {
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "count by (version) (alloy_build_info{cluster=~\"$cluster\", namespace=~\"$namespace\", job=~\"$job\"})",
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "{{version}}",
|
||||||
|
"range": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"datasource": {
|
||||||
|
"type": "",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"refId": "B",
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"transformations": [],
|
||||||
|
"queryOptions": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vizConfig": {
|
||||||
|
"kind": "timeseries",
|
||||||
|
"spec": {
|
||||||
|
"pluginVersion": "",
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"timeCompare": false,
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"value": 0,
|
||||||
|
"color": "green"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": 80,
|
||||||
|
"color": "red"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"showValues": false,
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Minimum cluster size"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "color",
|
||||||
|
"value": {
|
||||||
|
"fixedColor": "red",
|
||||||
|
"mode": "fixed"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "custom.lineStyle",
|
||||||
|
"value": {
|
||||||
|
"dash": [
|
||||||
|
10,
|
||||||
|
10
|
||||||
|
],
|
||||||
|
"fill": "dash"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "custom.lineWidth",
|
||||||
|
"value": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"panel-17": {
|
||||||
|
"kind": "Panel",
|
||||||
|
"spec": {
|
||||||
|
"id": 17,
|
||||||
|
"title": "CPU usage",
|
||||||
|
"description": "CPU usage of the Alloy process relative to 1 CPU core.\n\nFor example, 100% means using one entire CPU core.\n",
|
||||||
|
"links": [],
|
||||||
|
"data": {
|
||||||
|
"kind": "QueryGroup",
|
||||||
|
"spec": {
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"kind": "PanelQuery",
|
||||||
|
"spec": {
|
||||||
|
"query": {
|
||||||
|
"kind": "",
|
||||||
|
"spec": {
|
||||||
|
"expr": "rate(alloy_resources_process_cpu_seconds_total{cluster=~\"$cluster\", namespace=~\"$namespace\", job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])\n",
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "{{instance}}",
|
||||||
|
"range": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"datasource": {
|
||||||
|
"type": "",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"refId": "A",
|
||||||
|
"hidden": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "PanelQuery",
|
||||||
|
"spec": {
|
||||||
|
"query": {
|
||||||
|
"kind": "",
|
||||||
|
"spec": {
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "sum(rate(alloy_resources_process_cpu_seconds_total{cluster=~\"$cluster\", namespace=~\"$namespace\", job=~\"$job\", instance=~\"$instance\"}[$__rate_interval]))",
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "Total",
|
||||||
|
"range": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"datasource": {
|
||||||
|
"type": "",
|
||||||
|
"uid": "${datasource}"
|
||||||
|
},
|
||||||
|
"refId": "B",
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"transformations": [],
|
||||||
|
"queryOptions": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vizConfig": {
|
||||||
|
"kind": "timeseries",
|
||||||
|
"spec": {
|
||||||
|
"pluginVersion": "",
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"timeCompare": false,
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "percentunit",
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"value": 0,
|
||||||
|
"color": "green"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": 80,
|
||||||
|
"color": "red"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"showValues": false,
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"__systemRef": "hideSeriesFrom",
|
||||||
|
"matcher": {
|
||||||
|
"id": "byNames",
|
||||||
|
"options": {
|
||||||
|
"mode": "exclude",
|
||||||
|
"names": [
|
||||||
|
"Total"
|
||||||
|
],
|
||||||
|
"prefix": "All except:",
|
||||||
|
"readOnly": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.hideFrom",
|
||||||
|
"value": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": true,
|
||||||
|
"viz": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"layout": {
|
||||||
|
"kind": "GridLayout",
|
||||||
|
"spec": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"kind": "GridLayoutItem",
|
||||||
|
"spec": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 8,
|
||||||
|
"height": 9,
|
||||||
|
"element": {
|
||||||
|
"kind": "ElementReference",
|
||||||
|
"name": "panel-16"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "GridLayoutItem",
|
||||||
|
"spec": {
|
||||||
|
"x": 8,
|
||||||
|
"y": 0,
|
||||||
|
"width": 8,
|
||||||
|
"height": 9,
|
||||||
|
"element": {
|
||||||
|
"kind": "ElementReference",
|
||||||
|
"name": "panel-17"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"links": [],
|
||||||
|
"liveNow": false,
|
||||||
|
"preload": false,
|
||||||
|
"tags": [],
|
||||||
|
"timeSettings": {
|
||||||
|
"timezone": "utc",
|
||||||
|
"from": "now-90m",
|
||||||
|
"to": "now",
|
||||||
|
"autoRefresh": "",
|
||||||
|
"autoRefreshIntervals": [
|
||||||
|
"5s",
|
||||||
|
"10s",
|
||||||
|
"30s",
|
||||||
|
"1m",
|
||||||
|
"5m",
|
||||||
|
"15m",
|
||||||
|
"30m",
|
||||||
|
"1h",
|
||||||
|
"2h",
|
||||||
|
"1d"
|
||||||
|
],
|
||||||
|
"hideTimepicker": false,
|
||||||
|
"fiscalYearStartMonth": 0
|
||||||
|
},
|
||||||
|
"title": "Legacy DS Panel Query Ref",
|
||||||
|
"variables": []
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"conversion": {
|
||||||
|
"failed": false,
|
||||||
|
"storedVersion": "v1beta1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+411
@@ -0,0 +1,411 @@
|
|||||||
|
{
|
||||||
|
"kind": "Dashboard",
|
||||||
|
"apiVersion": "dashboard.grafana.app/v2beta1",
|
||||||
|
"metadata": {
|
||||||
|
"name": "legacy-ds-ref"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"annotations": [
|
||||||
|
{
|
||||||
|
"kind": "AnnotationQuery",
|
||||||
|
"spec": {
|
||||||
|
"query": {
|
||||||
|
"kind": "DataQuery",
|
||||||
|
"group": "grafana",
|
||||||
|
"version": "v0",
|
||||||
|
"datasource": {
|
||||||
|
"name": "-- Grafana --"
|
||||||
|
},
|
||||||
|
"spec": {}
|
||||||
|
},
|
||||||
|
"enable": true,
|
||||||
|
"hide": true,
|
||||||
|
"iconColor": "rgba(0, 211, 255, 1)",
|
||||||
|
"name": "Annotations \u0026 Alerts",
|
||||||
|
"builtIn": true,
|
||||||
|
"legacyOptions": {
|
||||||
|
"type": "dashboard"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cursorSync": "Off",
|
||||||
|
"editable": true,
|
||||||
|
"elements": {
|
||||||
|
"panel-16": {
|
||||||
|
"kind": "Panel",
|
||||||
|
"spec": {
|
||||||
|
"id": 16,
|
||||||
|
"title": "Number of Alloy Instances",
|
||||||
|
"description": "",
|
||||||
|
"links": [],
|
||||||
|
"data": {
|
||||||
|
"kind": "QueryGroup",
|
||||||
|
"spec": {
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"kind": "PanelQuery",
|
||||||
|
"spec": {
|
||||||
|
"query": {
|
||||||
|
"kind": "DataQuery",
|
||||||
|
"group": "",
|
||||||
|
"version": "v0",
|
||||||
|
"datasource": {
|
||||||
|
"name": "${datasource}"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "count by (version) (alloy_build_info{cluster=~\"$cluster\", namespace=~\"$namespace\", job=~\"$job\"})",
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "{{version}}",
|
||||||
|
"range": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"refId": "B",
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"transformations": [],
|
||||||
|
"queryOptions": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vizConfig": {
|
||||||
|
"kind": "VizConfig",
|
||||||
|
"group": "timeseries",
|
||||||
|
"version": "",
|
||||||
|
"spec": {
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"timeCompare": false,
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"value": 0,
|
||||||
|
"color": "green"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": 80,
|
||||||
|
"color": "red"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"showValues": false,
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"id": "byName",
|
||||||
|
"options": "Minimum cluster size"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "color",
|
||||||
|
"value": {
|
||||||
|
"fixedColor": "red",
|
||||||
|
"mode": "fixed"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "custom.lineStyle",
|
||||||
|
"value": {
|
||||||
|
"dash": [
|
||||||
|
10,
|
||||||
|
10
|
||||||
|
],
|
||||||
|
"fill": "dash"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "custom.lineWidth",
|
||||||
|
"value": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"panel-17": {
|
||||||
|
"kind": "Panel",
|
||||||
|
"spec": {
|
||||||
|
"id": 17,
|
||||||
|
"title": "CPU usage",
|
||||||
|
"description": "CPU usage of the Alloy process relative to 1 CPU core.\n\nFor example, 100% means using one entire CPU core.\n",
|
||||||
|
"links": [],
|
||||||
|
"data": {
|
||||||
|
"kind": "QueryGroup",
|
||||||
|
"spec": {
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"kind": "PanelQuery",
|
||||||
|
"spec": {
|
||||||
|
"query": {
|
||||||
|
"kind": "DataQuery",
|
||||||
|
"group": "",
|
||||||
|
"version": "v0",
|
||||||
|
"datasource": {
|
||||||
|
"name": "${datasource}"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"expr": "rate(alloy_resources_process_cpu_seconds_total{cluster=~\"$cluster\", namespace=~\"$namespace\", job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])\n",
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "{{instance}}",
|
||||||
|
"range": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"refId": "A",
|
||||||
|
"hidden": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "PanelQuery",
|
||||||
|
"spec": {
|
||||||
|
"query": {
|
||||||
|
"kind": "DataQuery",
|
||||||
|
"group": "",
|
||||||
|
"version": "v0",
|
||||||
|
"datasource": {
|
||||||
|
"name": "${datasource}"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "sum(rate(alloy_resources_process_cpu_seconds_total{cluster=~\"$cluster\", namespace=~\"$namespace\", job=~\"$job\", instance=~\"$instance\"}[$__rate_interval]))",
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "Total",
|
||||||
|
"range": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"refId": "B",
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"transformations": [],
|
||||||
|
"queryOptions": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vizConfig": {
|
||||||
|
"kind": "VizConfig",
|
||||||
|
"group": "timeseries",
|
||||||
|
"version": "",
|
||||||
|
"spec": {
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"timeCompare": false,
|
||||||
|
"tooltip": {
|
||||||
|
"hideZeros": false,
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "percentunit",
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"value": 0,
|
||||||
|
"color": "green"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": 80,
|
||||||
|
"color": "red"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"barWidthFactor": 0.6,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"showValues": false,
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"__systemRef": "hideSeriesFrom",
|
||||||
|
"matcher": {
|
||||||
|
"id": "byNames",
|
||||||
|
"options": {
|
||||||
|
"mode": "exclude",
|
||||||
|
"names": [
|
||||||
|
"Total"
|
||||||
|
],
|
||||||
|
"prefix": "All except:",
|
||||||
|
"readOnly": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.hideFrom",
|
||||||
|
"value": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": true,
|
||||||
|
"viz": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"layout": {
|
||||||
|
"kind": "GridLayout",
|
||||||
|
"spec": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"kind": "GridLayoutItem",
|
||||||
|
"spec": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 8,
|
||||||
|
"height": 9,
|
||||||
|
"element": {
|
||||||
|
"kind": "ElementReference",
|
||||||
|
"name": "panel-16"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "GridLayoutItem",
|
||||||
|
"spec": {
|
||||||
|
"x": 8,
|
||||||
|
"y": 0,
|
||||||
|
"width": 8,
|
||||||
|
"height": 9,
|
||||||
|
"element": {
|
||||||
|
"kind": "ElementReference",
|
||||||
|
"name": "panel-17"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"links": [],
|
||||||
|
"liveNow": false,
|
||||||
|
"preload": false,
|
||||||
|
"tags": [],
|
||||||
|
"timeSettings": {
|
||||||
|
"timezone": "utc",
|
||||||
|
"from": "now-90m",
|
||||||
|
"to": "now",
|
||||||
|
"autoRefresh": "",
|
||||||
|
"autoRefreshIntervals": [
|
||||||
|
"5s",
|
||||||
|
"10s",
|
||||||
|
"30s",
|
||||||
|
"1m",
|
||||||
|
"5m",
|
||||||
|
"15m",
|
||||||
|
"30m",
|
||||||
|
"1h",
|
||||||
|
"2h",
|
||||||
|
"1d"
|
||||||
|
],
|
||||||
|
"hideTimepicker": false,
|
||||||
|
"fiscalYearStartMonth": 0
|
||||||
|
},
|
||||||
|
"title": "Legacy DS Panel Query Ref",
|
||||||
|
"variables": []
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"conversion": {
|
||||||
|
"failed": false,
|
||||||
|
"storedVersion": "v1beta1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+140
@@ -711,6 +711,146 @@
|
|||||||
],
|
],
|
||||||
"title": "Mixed DS WITHOUT REFS",
|
"title": "Mixed DS WITHOUT REFS",
|
||||||
"type": "timeseries"
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"description": "Panel with a single -- Dashboard -- datasource query",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 9,
|
||||||
|
"w": 8,
|
||||||
|
"x": 0,
|
||||||
|
"y": 18
|
||||||
|
},
|
||||||
|
"id": 7,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.1.0-pre",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"panelId": 1,
|
||||||
|
"refId": "A",
|
||||||
|
"withTransforms": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Single Dashboard DS Query",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "mixed",
|
||||||
|
"uid": "-- Mixed --"
|
||||||
|
},
|
||||||
|
"description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 9,
|
||||||
|
"w": 8,
|
||||||
|
"x": 8,
|
||||||
|
"y": 18
|
||||||
|
},
|
||||||
|
"id": 8,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.1.0-pre",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"panelId": 1,
|
||||||
|
"refId": "A",
|
||||||
|
"withTransforms": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"panelId": 2,
|
||||||
|
"refId": "B",
|
||||||
|
"withTransforms": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"panelId": 3,
|
||||||
|
"refId": "C",
|
||||||
|
"withTransforms": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Multiple Dashboard DS Queries",
|
||||||
|
"type": "stat"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"preload": false,
|
"preload": false,
|
||||||
|
|||||||
Vendored
+140
@@ -711,6 +711,146 @@
|
|||||||
],
|
],
|
||||||
"title": "Mixed DS WITHOUT REFS",
|
"title": "Mixed DS WITHOUT REFS",
|
||||||
"type": "timeseries"
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"description": "Panel with a single -- Dashboard -- datasource query",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 9,
|
||||||
|
"w": 8,
|
||||||
|
"x": 0,
|
||||||
|
"y": 18
|
||||||
|
},
|
||||||
|
"id": 7,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.1.0-pre",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"panelId": 1,
|
||||||
|
"refId": "A",
|
||||||
|
"withTransforms": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Single Dashboard DS Query",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "mixed",
|
||||||
|
"uid": "-- Mixed --"
|
||||||
|
},
|
||||||
|
"description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 9,
|
||||||
|
"w": 8,
|
||||||
|
"x": 8,
|
||||||
|
"y": 18
|
||||||
|
},
|
||||||
|
"id": 8,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.1.0-pre",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"panelId": 1,
|
||||||
|
"refId": "A",
|
||||||
|
"withTransforms": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"panelId": 2,
|
||||||
|
"refId": "B",
|
||||||
|
"withTransforms": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"panelId": 3,
|
||||||
|
"refId": "C",
|
||||||
|
"withTransforms": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Multiple Dashboard DS Queries",
|
||||||
|
"type": "stat"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"preload": false,
|
"preload": false,
|
||||||
|
|||||||
Vendored
+212
@@ -879,6 +879,200 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"panel-7": {
|
||||||
|
"kind": "Panel",
|
||||||
|
"spec": {
|
||||||
|
"id": 7,
|
||||||
|
"title": "Single Dashboard DS Query",
|
||||||
|
"description": "Panel with a single -- Dashboard -- datasource query",
|
||||||
|
"links": [],
|
||||||
|
"data": {
|
||||||
|
"kind": "QueryGroup",
|
||||||
|
"spec": {
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"kind": "PanelQuery",
|
||||||
|
"spec": {
|
||||||
|
"query": {
|
||||||
|
"kind": "DataQuery",
|
||||||
|
"group": "datasource",
|
||||||
|
"version": "v0",
|
||||||
|
"datasource": {
|
||||||
|
"name": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"panelId": 1,
|
||||||
|
"withTransforms": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"refId": "A",
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"transformations": [],
|
||||||
|
"queryOptions": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vizConfig": {
|
||||||
|
"kind": "VizConfig",
|
||||||
|
"group": "stat",
|
||||||
|
"version": "12.1.0-pre",
|
||||||
|
"spec": {
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"value": 0,
|
||||||
|
"color": "green"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"panel-8": {
|
||||||
|
"kind": "Panel",
|
||||||
|
"spec": {
|
||||||
|
"id": 8,
|
||||||
|
"title": "Multiple Dashboard DS Queries",
|
||||||
|
"description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
|
||||||
|
"links": [],
|
||||||
|
"data": {
|
||||||
|
"kind": "QueryGroup",
|
||||||
|
"spec": {
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"kind": "PanelQuery",
|
||||||
|
"spec": {
|
||||||
|
"query": {
|
||||||
|
"kind": "DataQuery",
|
||||||
|
"group": "datasource",
|
||||||
|
"version": "v0",
|
||||||
|
"datasource": {
|
||||||
|
"name": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"panelId": 1,
|
||||||
|
"withTransforms": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"refId": "A",
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "PanelQuery",
|
||||||
|
"spec": {
|
||||||
|
"query": {
|
||||||
|
"kind": "DataQuery",
|
||||||
|
"group": "datasource",
|
||||||
|
"version": "v0",
|
||||||
|
"datasource": {
|
||||||
|
"name": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"panelId": 2,
|
||||||
|
"withTransforms": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"refId": "B",
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "PanelQuery",
|
||||||
|
"spec": {
|
||||||
|
"query": {
|
||||||
|
"kind": "DataQuery",
|
||||||
|
"group": "datasource",
|
||||||
|
"version": "v0",
|
||||||
|
"datasource": {
|
||||||
|
"name": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"panelId": 3,
|
||||||
|
"withTransforms": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"refId": "C",
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"transformations": [],
|
||||||
|
"queryOptions": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vizConfig": {
|
||||||
|
"kind": "VizConfig",
|
||||||
|
"group": "stat",
|
||||||
|
"version": "12.1.0-pre",
|
||||||
|
"spec": {
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"value": 0,
|
||||||
|
"color": "green"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
@@ -941,6 +1135,24 @@
|
|||||||
"name": "panel-6"
|
"name": "panel-6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "AutoGridLayoutItem",
|
||||||
|
"spec": {
|
||||||
|
"element": {
|
||||||
|
"kind": "ElementReference",
|
||||||
|
"name": "panel-7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "AutoGridLayoutItem",
|
||||||
|
"spec": {
|
||||||
|
"element": {
|
||||||
|
"kind": "ElementReference",
|
||||||
|
"name": "panel-8"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+140
@@ -711,6 +711,146 @@
|
|||||||
],
|
],
|
||||||
"title": "Mixed DS WITHOUT REFS",
|
"title": "Mixed DS WITHOUT REFS",
|
||||||
"type": "timeseries"
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"description": "Panel with a single -- Dashboard -- datasource query",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 3,
|
||||||
|
"w": 8,
|
||||||
|
"x": 0,
|
||||||
|
"y": 6
|
||||||
|
},
|
||||||
|
"id": 7,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.1.0-pre",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"panelId": 1,
|
||||||
|
"refId": "A",
|
||||||
|
"withTransforms": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Single Dashboard DS Query",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "mixed",
|
||||||
|
"uid": "-- Mixed --"
|
||||||
|
},
|
||||||
|
"description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 3,
|
||||||
|
"w": 8,
|
||||||
|
"x": 8,
|
||||||
|
"y": 6
|
||||||
|
},
|
||||||
|
"id": 8,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.1.0-pre",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"panelId": 1,
|
||||||
|
"refId": "A",
|
||||||
|
"withTransforms": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"panelId": 2,
|
||||||
|
"refId": "B",
|
||||||
|
"withTransforms": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"panelId": 3,
|
||||||
|
"refId": "C",
|
||||||
|
"withTransforms": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Multiple Dashboard DS Queries",
|
||||||
|
"type": "stat"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"preload": false,
|
"preload": false,
|
||||||
|
|||||||
+140
@@ -711,6 +711,146 @@
|
|||||||
],
|
],
|
||||||
"title": "Mixed DS WITHOUT REFS",
|
"title": "Mixed DS WITHOUT REFS",
|
||||||
"type": "timeseries"
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"description": "Panel with a single -- Dashboard -- datasource query",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 3,
|
||||||
|
"w": 8,
|
||||||
|
"x": 0,
|
||||||
|
"y": 6
|
||||||
|
},
|
||||||
|
"id": 7,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.1.0-pre",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"panelId": 1,
|
||||||
|
"refId": "A",
|
||||||
|
"withTransforms": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Single Dashboard DS Query",
|
||||||
|
"type": "stat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "mixed",
|
||||||
|
"uid": "-- Mixed --"
|
||||||
|
},
|
||||||
|
"description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
},
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 3,
|
||||||
|
"w": 8,
|
||||||
|
"x": 8,
|
||||||
|
"y": 6
|
||||||
|
},
|
||||||
|
"id": 8,
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "12.1.0-pre",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"panelId": 1,
|
||||||
|
"refId": "A",
|
||||||
|
"withTransforms": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"panelId": 2,
|
||||||
|
"refId": "B",
|
||||||
|
"withTransforms": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"panelId": 3,
|
||||||
|
"refId": "C",
|
||||||
|
"withTransforms": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Multiple Dashboard DS Queries",
|
||||||
|
"type": "stat"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"preload": false,
|
"preload": false,
|
||||||
|
|||||||
Vendored
+214
@@ -852,6 +852,194 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"panel-7": {
|
||||||
|
"kind": "Panel",
|
||||||
|
"spec": {
|
||||||
|
"id": 7,
|
||||||
|
"title": "Single Dashboard DS Query",
|
||||||
|
"description": "Panel with a single -- Dashboard -- datasource query",
|
||||||
|
"links": [],
|
||||||
|
"data": {
|
||||||
|
"kind": "QueryGroup",
|
||||||
|
"spec": {
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"kind": "PanelQuery",
|
||||||
|
"spec": {
|
||||||
|
"query": {
|
||||||
|
"kind": "datasource",
|
||||||
|
"spec": {
|
||||||
|
"panelId": 1,
|
||||||
|
"withTransforms": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"refId": "A",
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"transformations": [],
|
||||||
|
"queryOptions": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vizConfig": {
|
||||||
|
"kind": "stat",
|
||||||
|
"spec": {
|
||||||
|
"pluginVersion": "12.1.0-pre",
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"value": 0,
|
||||||
|
"color": "green"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"panel-8": {
|
||||||
|
"kind": "Panel",
|
||||||
|
"spec": {
|
||||||
|
"id": 8,
|
||||||
|
"title": "Multiple Dashboard DS Queries",
|
||||||
|
"description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
|
||||||
|
"links": [],
|
||||||
|
"data": {
|
||||||
|
"kind": "QueryGroup",
|
||||||
|
"spec": {
|
||||||
|
"queries": [
|
||||||
|
{
|
||||||
|
"kind": "PanelQuery",
|
||||||
|
"spec": {
|
||||||
|
"query": {
|
||||||
|
"kind": "datasource",
|
||||||
|
"spec": {
|
||||||
|
"panelId": 1,
|
||||||
|
"withTransforms": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"refId": "A",
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "PanelQuery",
|
||||||
|
"spec": {
|
||||||
|
"query": {
|
||||||
|
"kind": "datasource",
|
||||||
|
"spec": {
|
||||||
|
"panelId": 2,
|
||||||
|
"withTransforms": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"refId": "B",
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "PanelQuery",
|
||||||
|
"spec": {
|
||||||
|
"query": {
|
||||||
|
"kind": "datasource",
|
||||||
|
"spec": {
|
||||||
|
"panelId": 3,
|
||||||
|
"withTransforms": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"datasource": {
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": "-- Dashboard --"
|
||||||
|
},
|
||||||
|
"refId": "C",
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"transformations": [],
|
||||||
|
"queryOptions": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vizConfig": {
|
||||||
|
"kind": "stat",
|
||||||
|
"spec": {
|
||||||
|
"pluginVersion": "12.1.0-pre",
|
||||||
|
"options": {
|
||||||
|
"colorMode": "value",
|
||||||
|
"graphMode": "area",
|
||||||
|
"justifyMode": "auto",
|
||||||
|
"orientation": "auto",
|
||||||
|
"percentChangeColorMode": "standard",
|
||||||
|
"reduceOptions": {
|
||||||
|
"calcs": [
|
||||||
|
"lastNotNull"
|
||||||
|
],
|
||||||
|
"fields": "",
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"showPercentChange": false,
|
||||||
|
"textMode": "auto",
|
||||||
|
"wideLayout": true
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"value": 0,
|
||||||
|
"color": "green"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"mode": "thresholds"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
@@ -946,6 +1134,32 @@
|
|||||||
"name": "panel-6"
|
"name": "panel-6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "GridLayoutItem",
|
||||||
|
"spec": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 6,
|
||||||
|
"width": 8,
|
||||||
|
"height": 3,
|
||||||
|
"element": {
|
||||||
|
"kind": "ElementReference",
|
||||||
|
"name": "panel-7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "GridLayoutItem",
|
||||||
|
"spec": {
|
||||||
|
"x": 8,
|
||||||
|
"y": 6,
|
||||||
|
"width": 8,
|
||||||
|
"height": 3,
|
||||||
|
"element": {
|
||||||
|
"kind": "ElementReference",
|
||||||
|
"name": "panel-8"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,6 +88,11 @@ func ConvertDashboard_V0_to_V1beta1(in *dashv0.Dashboard, out *dashv1.Dashboard,
|
|||||||
// Which means that we have schemaVersion: 42 dashboards where datasource variable references are still strings
|
// Which means that we have schemaVersion: 42 dashboards where datasource variable references are still strings
|
||||||
normalizeTemplateVariableDatasources(out.Spec.Object)
|
normalizeTemplateVariableDatasources(out.Spec.Object)
|
||||||
|
|
||||||
|
// Normalize panel and target datasources from string to object format
|
||||||
|
// This handles legacy dashboards where panels/targets have datasource: "$datasource" (string)
|
||||||
|
// instead of datasource: { uid: "$datasource" } (object)
|
||||||
|
normalizePanelDatasources(out.Spec.Object)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,3 +139,62 @@ func isTemplateVariableRef(s string) bool {
|
|||||||
}
|
}
|
||||||
return strings.HasPrefix(s, "$") || strings.HasPrefix(s, "${")
|
return strings.HasPrefix(s, "$") || strings.HasPrefix(s, "${")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// normalizePanelDatasources converts panel and target string datasources to object format.
|
||||||
|
// Legacy dashboards may have panels/targets with datasource: "$datasource" (string).
|
||||||
|
// This normalizes them to datasource: { uid: "$datasource" } for consistent V1→V2 conversion.
|
||||||
|
func normalizePanelDatasources(dashboard map[string]interface{}) {
|
||||||
|
panels, ok := dashboard["panels"].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizePanelsDatasources(panels)
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalizePanelsDatasources normalizes datasources in a list of panels (including nested row panels)
|
||||||
|
func normalizePanelsDatasources(panels []interface{}) {
|
||||||
|
for _, panel := range panels {
|
||||||
|
panelMap, ok := panel.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle row panels with nested panels
|
||||||
|
if panelType, _ := panelMap["type"].(string); panelType == "row" {
|
||||||
|
if nestedPanels, ok := panelMap["panels"].([]interface{}); ok {
|
||||||
|
normalizePanelsDatasources(nestedPanels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize panel-level datasource
|
||||||
|
if ds := panelMap["datasource"]; ds != nil {
|
||||||
|
if dsStr, ok := ds.(string); ok && isTemplateVariableRef(dsStr) {
|
||||||
|
panelMap["datasource"] = map[string]interface{}{
|
||||||
|
"uid": dsStr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize target-level datasources
|
||||||
|
targets, ok := panelMap["targets"].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, target := range targets {
|
||||||
|
targetMap, ok := target.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ds := targetMap["datasource"]; ds != nil {
|
||||||
|
if dsStr, ok := ds.(string); ok && isTemplateVariableRef(dsStr) {
|
||||||
|
targetMap["datasource"] = map[string]interface{}{
|
||||||
|
"uid": dsStr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2059,6 +2059,12 @@ func transformPanelQueries(ctx context.Context, panelMap map[string]interface{},
|
|||||||
Uid: &dsUID,
|
Uid: &dsUID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if dsStr, ok := ds.(string); ok && isTemplateVariable(dsStr) {
|
||||||
|
// Handle legacy panel datasource as string (template variable reference e.g., "$datasource")
|
||||||
|
// Only process template variables - other string values are not supported in V2 format
|
||||||
|
panelDatasource = &dashv2alpha1.DashboardDataSourceRef{
|
||||||
|
Uid: &dsStr,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2145,6 +2151,10 @@ func transformSingleQuery(ctx context.Context, targetMap map[string]interface{},
|
|||||||
// Resolve Grafana datasource UID when type is "datasource" and UID is empty
|
// Resolve Grafana datasource UID when type is "datasource" and UID is empty
|
||||||
queryDatasourceUID = resolveGrafanaDatasourceUID(queryDatasourceType, queryDatasourceUID)
|
queryDatasourceUID = resolveGrafanaDatasourceUID(queryDatasourceType, queryDatasourceUID)
|
||||||
}
|
}
|
||||||
|
} else if dsStr, ok := targetMap["datasource"].(string); ok && isTemplateVariable(dsStr) {
|
||||||
|
// Handle legacy target datasource as string (template variable reference e.g., "$datasource")
|
||||||
|
// Only process template variables - other string values are not supported in V2 format
|
||||||
|
queryDatasourceUID = dsStr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use panel datasource if target datasource is missing or empty
|
// Use panel datasource if target datasource is missing or empty
|
||||||
|
|||||||
@@ -1195,16 +1195,36 @@ func getDataSourceForQuery(explicitDS *dashv2alpha1.DashboardDataSourceRef, quer
|
|||||||
// getPanelDatasource determines the panel-level datasource for V1.
|
// getPanelDatasource determines the panel-level datasource for V1.
|
||||||
// Returns:
|
// Returns:
|
||||||
// - Mixed datasource reference if queries use different datasources
|
// - Mixed datasource reference if queries use different datasources
|
||||||
|
// - Mixed datasource reference if multiple queries use Dashboard datasource (they fetch from different panels)
|
||||||
|
// - Dashboard datasource reference if a single query uses Dashboard datasource
|
||||||
// - First query's datasource if all queries use the same datasource
|
// - First query's datasource if all queries use the same datasource
|
||||||
// - nil if no queries exist
|
// - nil if no queries exist
|
||||||
// Compares based on V2 input without runtime resolution:
|
// Compares based on V2 input without runtime resolution:
|
||||||
// - If query has explicit datasource.uid → use that UID and type
|
// - If query has explicit datasource.uid → use that UID and type
|
||||||
// - Else → use query.Kind as type (empty UID)
|
// - Else → use query.Kind as type (empty UID)
|
||||||
func getPanelDatasource(queries []dashv2alpha1.DashboardPanelQueryKind) map[string]interface{} {
|
func getPanelDatasource(queries []dashv2alpha1.DashboardPanelQueryKind) map[string]interface{} {
|
||||||
|
const sharedDashboardQuery = "-- Dashboard --"
|
||||||
|
|
||||||
if len(queries) == 0 {
|
if len(queries) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Count how many queries use Dashboard datasource
|
||||||
|
// Multiple dashboard queries need mixed mode because they fetch from different panels
|
||||||
|
// which may have different underlying datasources
|
||||||
|
dashboardDsQueryCount := 0
|
||||||
|
for _, query := range queries {
|
||||||
|
if query.Spec.Datasource != nil && query.Spec.Datasource.Uid != nil && *query.Spec.Datasource.Uid == sharedDashboardQuery {
|
||||||
|
dashboardDsQueryCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dashboardDsQueryCount > 1 {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"type": "mixed",
|
||||||
|
"uid": "-- Mixed --",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var firstUID, firstType string
|
var firstUID, firstType string
|
||||||
var hasFirst bool
|
var hasFirst bool
|
||||||
|
|
||||||
@@ -1239,6 +1259,16 @@ func getPanelDatasource(queries []dashv2alpha1.DashboardPanelQueryKind) map[stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle case when a single query uses Dashboard datasource.
|
||||||
|
// This is needed for the frontend to properly activate and fetch data from source panels.
|
||||||
|
// See DashboardDatasourceBehaviour.tsx for more details.
|
||||||
|
if firstUID == sharedDashboardQuery {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"type": "datasource",
|
||||||
|
"uid": sharedDashboardQuery,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Not mixed - return the first query's datasource so the panel has a datasource set.
|
// Not mixed - return the first query's datasource so the panel has a datasource set.
|
||||||
// This is required because the frontend's legacy PanelModel.PanelQueryRunner.run uses panel.datasource
|
// This is required because the frontend's legacy PanelModel.PanelQueryRunner.run uses panel.datasource
|
||||||
// to resolve the datasource, and if undefined, it falls back to the default datasource
|
// to resolve the datasource, and if undefined, it falls back to the default datasource
|
||||||
|
|||||||
+22
-4
@@ -290,7 +290,7 @@
|
|||||||
],
|
],
|
||||||
"legend": {
|
"legend": {
|
||||||
"displayMode": "table",
|
"displayMode": "table",
|
||||||
"placement": "bottom",
|
"placement": "right",
|
||||||
"showLegend": true,
|
"showLegend": true,
|
||||||
"values": [
|
"values": [
|
||||||
"percent"
|
"percent"
|
||||||
@@ -304,7 +304,7 @@
|
|||||||
"fields": "",
|
"fields": "",
|
||||||
"values": false
|
"values": false
|
||||||
},
|
},
|
||||||
"showLegend": false,
|
"showLegend": true,
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 1,
|
||||||
"text": {}
|
"text": {}
|
||||||
},
|
},
|
||||||
@@ -323,6 +323,15 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"title": "Percent",
|
"title": "Percent",
|
||||||
|
"transformations": [
|
||||||
|
{
|
||||||
|
"id": "renameByRegex",
|
||||||
|
"options": {
|
||||||
|
"regex": "^Backend-(.*)$",
|
||||||
|
"renamePattern": "b-$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"type": "piechart"
|
"type": "piechart"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -366,7 +375,7 @@
|
|||||||
],
|
],
|
||||||
"legend": {
|
"legend": {
|
||||||
"displayMode": "table",
|
"displayMode": "table",
|
||||||
"placement": "bottom",
|
"placement": "right",
|
||||||
"showLegend": true,
|
"showLegend": true,
|
||||||
"values": [
|
"values": [
|
||||||
"value"
|
"value"
|
||||||
@@ -380,7 +389,7 @@
|
|||||||
"fields": "",
|
"fields": "",
|
||||||
"values": false
|
"values": false
|
||||||
},
|
},
|
||||||
"showLegend": false,
|
"showLegend": true,
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 1,
|
||||||
"text": {}
|
"text": {}
|
||||||
},
|
},
|
||||||
@@ -399,6 +408,15 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"title": "Value",
|
"title": "Value",
|
||||||
|
"transformations": [
|
||||||
|
{
|
||||||
|
"id": "renameByRegex",
|
||||||
|
"options": {
|
||||||
|
"regex": "(.*)",
|
||||||
|
"renamePattern": "$1-how-much-wood-could-a-woodchuck-chuck-if-a-woodchuck-could-chuck-wood"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"type": "piechart"
|
"type": "piechart"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,5 +6,4 @@ generate: install-app-sdk update-app-sdk
|
|||||||
--source=./kinds/ \
|
--source=./kinds/ \
|
||||||
--gogenpath=./pkg/apis \
|
--gogenpath=./pkg/apis \
|
||||||
--grouping=group \
|
--grouping=group \
|
||||||
--genoperatorstate=false \
|
|
||||||
--defencoding=none
|
--defencoding=none
|
||||||
+228
@@ -0,0 +1,228 @@
|
|||||||
|
{
|
||||||
|
"kind": "CustomResourceDefinition",
|
||||||
|
"apiVersion": "apiextensions.k8s.io/v1",
|
||||||
|
"metadata": {
|
||||||
|
"name": "dashboardcompatibilityscores.dashvalidator.ext.grafana.com"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"group": "dashvalidator.ext.grafana.com",
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"name": "v1alpha1",
|
||||||
|
"served": true,
|
||||||
|
"storage": true,
|
||||||
|
"schema": {
|
||||||
|
"openAPIV3Schema": {
|
||||||
|
"properties": {
|
||||||
|
"spec": {
|
||||||
|
"properties": {
|
||||||
|
"dashboardJson": {
|
||||||
|
"description": "Complete dashboard JSON object to validate.\nMust be a v1 dashboard schema (contains \"panels\" array).\nv2 dashboards (with \"elements\" structure) are not yet supported.",
|
||||||
|
"type": "object",
|
||||||
|
"x-kubernetes-preserve-unknown-fields": true
|
||||||
|
},
|
||||||
|
"datasourceMappings": {
|
||||||
|
"description": "Array of datasources to validate against.\nThe validator will check dashboard queries against each datasource\nand provide per-datasource compatibility results.\n\nMVP: Only single datasource supported (array length = 1), Prometheus type only.\nFuture: Will support multiple datasources for dashboards with mixed queries.",
|
||||||
|
"items": {
|
||||||
|
"description": "DataSourceMapping specifies a datasource to validate dashboard queries against.\nMaps logical datasource references in the dashboard to actual datasource instances.",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "Optional human-readable name for display in results.\nIf not provided, UID will be used in error messages.\nExample: \"Production Prometheus (US-West)\"",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"description": "Type of datasource plugin.\nMVP: Only \"prometheus\" supported.\nFuture: \"mysql\", \"postgres\", \"elasticsearch\", etc.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"uid": {
|
||||||
|
"description": "Unique identifier of the datasource instance.\nExample: \"prometheus-prod-us-west\"",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["uid", "type"],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["dashboardJson", "datasourceMappings"],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"properties": {
|
||||||
|
"additionalFields": {
|
||||||
|
"description": "additionalFields is reserved for future use",
|
||||||
|
"type": "object",
|
||||||
|
"x-kubernetes-preserve-unknown-fields": true
|
||||||
|
},
|
||||||
|
"compatibilityScore": {
|
||||||
|
"description": "Overall compatibility score across all datasources (0-100).\nCalculated as: (total found metrics / total referenced metrics) * 100\n\nScore interpretation:\n- 100: Perfect compatibility, all queries will work\n- 80-99: Excellent, minor missing metrics\n- 50-79: Fair, significant missing metrics\n- 0-49: Poor, most queries will fail",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"datasourceResults": {
|
||||||
|
"description": "Per-datasource validation results.\nArray length matches spec.datasourceMappings.\nEach element contains detailed metrics and query-level breakdown.",
|
||||||
|
"items": {
|
||||||
|
"description": "DataSourceResult contains validation results for a single datasource.\nProvides aggregate statistics and per-query breakdown of compatibility.",
|
||||||
|
"properties": {
|
||||||
|
"checkedQueries": {
|
||||||
|
"description": "Number of queries successfully validated.\nMay be less than totalQueries if some queries couldn't be parsed.",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"compatibilityScore": {
|
||||||
|
"description": "Overall compatibility score for this datasource (0-100).\nCalculated as: (foundMetrics / totalMetrics) * 100\nUsed to calculate the global compatibilityScore in status.",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"foundMetrics": {
|
||||||
|
"description": "Number of metrics that exist in the datasource schema.\nfoundMetrics \u003c= totalMetrics",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"missingMetrics": {
|
||||||
|
"description": "Array of metric names that were referenced but don't exist.\nUseful for debugging why a dashboard shows \"no data\".\nExample for Prometheus: [\"http_requests_total\", \"api_latency_seconds\"]",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"description": "Optional display name (matches DataSourceMapping.name if provided)",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"queryBreakdown": {
|
||||||
|
"description": "Per-query breakdown showing which specific queries have issues.\nOne entry per query target (refId: \"A\", \"B\", \"C\", etc.) in each panel.\nAllows pinpointing exactly which panel/query needs fixing.",
|
||||||
|
"items": {
|
||||||
|
"description": "QueryBreakdown provides compatibility details for a single query within a panel.\nGranular per-query results allow users to identify exactly which queries need fixing.\n\nNote: A panel can have multiple queries (refId: \"A\", \"B\", \"C\", etc.),\nso there may be multiple QueryBreakdown entries for the same panelID.",
|
||||||
|
"properties": {
|
||||||
|
"compatibilityScore": {
|
||||||
|
"description": "Compatibility percentage for this individual query (0-100).\nCalculated as: (foundMetrics / totalMetrics) * 100\n100 = query will work perfectly, 0 = query will return no data.",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"foundMetrics": {
|
||||||
|
"description": "Number of those metrics that exist in the datasource.\nfoundMetrics \u003c= totalMetrics",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"missingMetrics": {
|
||||||
|
"description": "Array of missing metric names specific to this query.\nHelps identify exactly which part of a query expression will fail.\nEmpty array means query is fully compatible.",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"panelID": {
|
||||||
|
"description": "Numeric panel ID from dashboard JSON.\nUsed to correlate with dashboard structure.",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"panelTitle": {
|
||||||
|
"description": "Human-readable panel title for context.\nExample: \"CPU Usage\", \"Request Rate\"",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"queryRefId": {
|
||||||
|
"description": "Query identifier within the panel.\nValues: \"A\", \"B\", \"C\", etc. (from panel.targets[].refId)\nUniquely identifies which query in a multi-query panel this refers to.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"totalMetrics": {
|
||||||
|
"description": "Number of unique metrics referenced in this specific query.\nFor Prometheus: metrics extracted from the PromQL expr.\nExample: rate(http_requests_total[5m]) references 1 metric.",
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"panelTitle",
|
||||||
|
"panelID",
|
||||||
|
"queryRefId",
|
||||||
|
"totalMetrics",
|
||||||
|
"foundMetrics",
|
||||||
|
"missingMetrics",
|
||||||
|
"compatibilityScore"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"totalMetrics": {
|
||||||
|
"description": "Total number of unique metrics/identifiers referenced across all queries.\nFor Prometheus: metric names extracted from PromQL expressions.\nFor SQL datasources: table and column names.",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"totalQueries": {
|
||||||
|
"description": "Total number of queries in the dashboard targeting this datasource.\nIncludes all panel targets/queries that reference this datasource.",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"description": "Datasource type (matches DataSourceMapping.type)",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"uid": {
|
||||||
|
"description": "Datasource UID that was validated (matches DataSourceMapping.uid)",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"uid",
|
||||||
|
"type",
|
||||||
|
"totalQueries",
|
||||||
|
"checkedQueries",
|
||||||
|
"totalMetrics",
|
||||||
|
"foundMetrics",
|
||||||
|
"missingMetrics",
|
||||||
|
"queryBreakdown",
|
||||||
|
"compatibilityScore"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"lastChecked": {
|
||||||
|
"description": "ISO 8601 timestamp of when validation was last performed.\nExample: \"2024-01-15T10:30:00Z\"",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"description": "Human-readable summary of validation result.\nExamples: \"All queries compatible\", \"3 missing metrics found\"",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"operatorStates": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"properties": {
|
||||||
|
"descriptiveState": {
|
||||||
|
"description": "descriptiveState is an optional more descriptive state field which has no requirements on format",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"details": {
|
||||||
|
"description": "details contains any extra information that is operator-specific",
|
||||||
|
"type": "object",
|
||||||
|
"x-kubernetes-preserve-unknown-fields": true
|
||||||
|
},
|
||||||
|
"lastEvaluation": {
|
||||||
|
"description": "lastEvaluation is the ResourceVersion last evaluated",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"description": "state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.",
|
||||||
|
"enum": ["success", "in_progress", "failed"],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["lastEvaluation", "state"],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"description": "operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["compatibilityScore", "datasourceResults"],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["spec"],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"subresources": {
|
||||||
|
"status": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"names": {
|
||||||
|
"kind": "DashboardCompatibilityScore",
|
||||||
|
"plural": "dashboardcompatibilityscores"
|
||||||
|
},
|
||||||
|
"scope": "Namespaced"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "apps.grafana.com/v1alpha1",
|
||||||
|
"kind": "AppManifest",
|
||||||
|
"metadata": {
|
||||||
|
"name": "dashvalidator"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"appName": "dashvalidator",
|
||||||
|
"group": "dashvalidator.ext.grafana.com",
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"name": "v1alpha1",
|
||||||
|
"served": true,
|
||||||
|
"kinds": [
|
||||||
|
{
|
||||||
|
"kind": "DashboardCompatibilityScore",
|
||||||
|
"plural": "DashboardCompatibilityScores",
|
||||||
|
"scope": "Namespaced",
|
||||||
|
"schema": {
|
||||||
|
"spec": {
|
||||||
|
"properties": {
|
||||||
|
"dashboardJson": {
|
||||||
|
"description": "Complete dashboard JSON object to validate.\nMust be a v1 dashboard schema (contains \"panels\" array).\nv2 dashboards (with \"elements\" structure) are not yet supported.",
|
||||||
|
"type": "object",
|
||||||
|
"x-kubernetes-preserve-unknown-fields": true
|
||||||
|
},
|
||||||
|
"datasourceMappings": {
|
||||||
|
"description": "Array of datasources to validate against.\nThe validator will check dashboard queries against each datasource\nand provide per-datasource compatibility results.\n\nMVP: Only single datasource supported (array length = 1), Prometheus type only.\nFuture: Will support multiple datasources for dashboards with mixed queries.",
|
||||||
|
"items": {
|
||||||
|
"description": "DataSourceMapping specifies a datasource to validate dashboard queries against.\nMaps logical datasource references in the dashboard to actual datasource instances.",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "Optional human-readable name for display in results.\nIf not provided, UID will be used in error messages.\nExample: \"Production Prometheus (US-West)\"",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"description": "Type of datasource plugin.\nMVP: Only \"prometheus\" supported.\nFuture: \"mysql\", \"postgres\", \"elasticsearch\", etc.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"uid": {
|
||||||
|
"description": "Unique identifier of the datasource instance.\nExample: \"prometheus-prod-us-west\"",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["uid", "type"],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["dashboardJson", "datasourceMappings"],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"properties": {
|
||||||
|
"additionalFields": {
|
||||||
|
"description": "additionalFields is reserved for future use",
|
||||||
|
"type": "object",
|
||||||
|
"x-kubernetes-preserve-unknown-fields": true
|
||||||
|
},
|
||||||
|
"compatibilityScore": {
|
||||||
|
"description": "Overall compatibility score across all datasources (0-100).\nCalculated as: (total found metrics / total referenced metrics) * 100\n\nScore interpretation:\n- 100: Perfect compatibility, all queries will work\n- 80-99: Excellent, minor missing metrics\n- 50-79: Fair, significant missing metrics\n- 0-49: Poor, most queries will fail",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"datasourceResults": {
|
||||||
|
"description": "Per-datasource validation results.\nArray length matches spec.datasourceMappings.\nEach element contains detailed metrics and query-level breakdown.",
|
||||||
|
"items": {
|
||||||
|
"description": "DataSourceResult contains validation results for a single datasource.\nProvides aggregate statistics and per-query breakdown of compatibility.",
|
||||||
|
"properties": {
|
||||||
|
"checkedQueries": {
|
||||||
|
"description": "Number of queries successfully validated.\nMay be less than totalQueries if some queries couldn't be parsed.",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"compatibilityScore": {
|
||||||
|
"description": "Overall compatibility score for this datasource (0-100).\nCalculated as: (foundMetrics / totalMetrics) * 100\nUsed to calculate the global compatibilityScore in status.",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"foundMetrics": {
|
||||||
|
"description": "Number of metrics that exist in the datasource schema.\nfoundMetrics \u003c= totalMetrics",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"missingMetrics": {
|
||||||
|
"description": "Array of metric names that were referenced but don't exist.\nUseful for debugging why a dashboard shows \"no data\".\nExample for Prometheus: [\"http_requests_total\", \"api_latency_seconds\"]",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"description": "Optional display name (matches DataSourceMapping.name if provided)",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"queryBreakdown": {
|
||||||
|
"description": "Per-query breakdown showing which specific queries have issues.\nOne entry per query target (refId: \"A\", \"B\", \"C\", etc.) in each panel.\nAllows pinpointing exactly which panel/query needs fixing.",
|
||||||
|
"items": {
|
||||||
|
"description": "QueryBreakdown provides compatibility details for a single query within a panel.\nGranular per-query results allow users to identify exactly which queries need fixing.\n\nNote: A panel can have multiple queries (refId: \"A\", \"B\", \"C\", etc.),\nso there may be multiple QueryBreakdown entries for the same panelID.",
|
||||||
|
"properties": {
|
||||||
|
"compatibilityScore": {
|
||||||
|
"description": "Compatibility percentage for this individual query (0-100).\nCalculated as: (foundMetrics / totalMetrics) * 100\n100 = query will work perfectly, 0 = query will return no data.",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"foundMetrics": {
|
||||||
|
"description": "Number of those metrics that exist in the datasource.\nfoundMetrics \u003c= totalMetrics",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"missingMetrics": {
|
||||||
|
"description": "Array of missing metric names specific to this query.\nHelps identify exactly which part of a query expression will fail.\nEmpty array means query is fully compatible.",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"panelID": {
|
||||||
|
"description": "Numeric panel ID from dashboard JSON.\nUsed to correlate with dashboard structure.",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"panelTitle": {
|
||||||
|
"description": "Human-readable panel title for context.\nExample: \"CPU Usage\", \"Request Rate\"",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"queryRefId": {
|
||||||
|
"description": "Query identifier within the panel.\nValues: \"A\", \"B\", \"C\", etc. (from panel.targets[].refId)\nUniquely identifies which query in a multi-query panel this refers to.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"totalMetrics": {
|
||||||
|
"description": "Number of unique metrics referenced in this specific query.\nFor Prometheus: metrics extracted from the PromQL expr.\nExample: rate(http_requests_total[5m]) references 1 metric.",
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"panelTitle",
|
||||||
|
"panelID",
|
||||||
|
"queryRefId",
|
||||||
|
"totalMetrics",
|
||||||
|
"foundMetrics",
|
||||||
|
"missingMetrics",
|
||||||
|
"compatibilityScore"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"totalMetrics": {
|
||||||
|
"description": "Total number of unique metrics/identifiers referenced across all queries.\nFor Prometheus: metric names extracted from PromQL expressions.\nFor SQL datasources: table and column names.",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"totalQueries": {
|
||||||
|
"description": "Total number of queries in the dashboard targeting this datasource.\nIncludes all panel targets/queries that reference this datasource.",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"description": "Datasource type (matches DataSourceMapping.type)",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"uid": {
|
||||||
|
"description": "Datasource UID that was validated (matches DataSourceMapping.uid)",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"uid",
|
||||||
|
"type",
|
||||||
|
"totalQueries",
|
||||||
|
"checkedQueries",
|
||||||
|
"totalMetrics",
|
||||||
|
"foundMetrics",
|
||||||
|
"missingMetrics",
|
||||||
|
"queryBreakdown",
|
||||||
|
"compatibilityScore"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"lastChecked": {
|
||||||
|
"description": "ISO 8601 timestamp of when validation was last performed.\nExample: \"2024-01-15T10:30:00Z\"",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"description": "Human-readable summary of validation result.\nExamples: \"All queries compatible\", \"3 missing metrics found\"",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"operatorStates": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"properties": {
|
||||||
|
"descriptiveState": {
|
||||||
|
"description": "descriptiveState is an optional more descriptive state field which has no requirements on format",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"details": {
|
||||||
|
"description": "details contains any extra information that is operator-specific",
|
||||||
|
"type": "object",
|
||||||
|
"x-kubernetes-preserve-unknown-fields": true
|
||||||
|
},
|
||||||
|
"lastEvaluation": {
|
||||||
|
"description": "lastEvaluation is the ResourceVersion last evaluated",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"description": "state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.",
|
||||||
|
"enum": ["success", "in_progress", "failed"],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["lastEvaluation", "state"],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"description": "operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["compatibilityScore", "datasourceResults"],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"conversion": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"preferredVersion": "v1alpha1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
module github.com/grafana/grafana/apps/dashvalidator
|
||||||
|
|
||||||
|
go 1.25.5
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/grafana/grafana v0.0.0-00010101000000-000000000000
|
||||||
|
github.com/grafana/grafana-app-sdk v0.48.7
|
||||||
|
github.com/grafana/grafana-app-sdk/logging v0.48.7
|
||||||
|
github.com/prometheus/prometheus v0.303.1
|
||||||
|
k8s.io/apimachinery v0.34.3
|
||||||
|
k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
github.com/Machiel/slugify v1.0.1 // indirect
|
||||||
|
github.com/ProtonMail/go-crypto v1.1.6 // indirect
|
||||||
|
github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f // indirect
|
||||||
|
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
|
||||||
|
github.com/apache/arrow-go/v18 v18.4.1 // indirect
|
||||||
|
github.com/armon/go-metrics v0.4.1 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.39.1 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.18.14 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.38.5 // indirect
|
||||||
|
github.com/aws/smithy-go v1.23.1 // indirect
|
||||||
|
github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df // indirect
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||||
|
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||||
|
github.com/bluele/gcache v0.0.2 // indirect
|
||||||
|
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
|
||||||
|
github.com/bwmarrin/snowflake v0.3.0 // indirect
|
||||||
|
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/cheekybits/genny v1.0.0 // indirect
|
||||||
|
github.com/cloudflare/circl v1.6.1 // indirect
|
||||||
|
github.com/coreos/go-systemd/v22 v22.6.0 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
|
github.com/dennwc/varint v1.0.0 // indirect
|
||||||
|
github.com/diegoholiveira/jsonlogic/v3 v3.7.4 // indirect
|
||||||
|
github.com/dolthub/go-icu-regex v0.0.0-20250916051405-78a38d478790 // indirect
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
|
||||||
|
github.com/fatih/color v1.18.0 // indirect
|
||||||
|
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||||
|
github.com/getkin/kin-openapi v0.133.0 // indirect
|
||||||
|
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||||
|
github.com/go-kit/log v0.2.1 // indirect
|
||||||
|
github.com/go-logfmt/logfmt v0.6.1 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/go-openapi/jsonpointer v0.22.4 // indirect
|
||||||
|
github.com/go-openapi/jsonreference v0.21.4 // indirect
|
||||||
|
github.com/go-openapi/swag v0.25.4 // indirect
|
||||||
|
github.com/go-openapi/swag/cmdutils v0.25.4 // indirect
|
||||||
|
github.com/go-openapi/swag/conv v0.25.4 // indirect
|
||||||
|
github.com/go-openapi/swag/fileutils v0.25.4 // indirect
|
||||||
|
github.com/go-openapi/swag/jsonname v0.25.4 // indirect
|
||||||
|
github.com/go-openapi/swag/jsonutils v0.25.4 // indirect
|
||||||
|
github.com/go-openapi/swag/loading v0.25.4 // indirect
|
||||||
|
github.com/go-openapi/swag/mangling v0.25.4 // indirect
|
||||||
|
github.com/go-openapi/swag/netutils v0.25.4 // indirect
|
||||||
|
github.com/go-openapi/swag/stringutils v0.25.4 // indirect
|
||||||
|
github.com/go-openapi/swag/typeutils v0.25.4 // indirect
|
||||||
|
github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.9.3 // indirect
|
||||||
|
github.com/go-stack/stack v1.8.1 // indirect
|
||||||
|
github.com/gobwas/glob v0.2.3 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
|
github.com/gogo/googleapis v1.4.1 // indirect
|
||||||
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
|
||||||
|
github.com/golang-migrate/migrate/v4 v4.7.0 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
|
github.com/google/btree v1.1.3 // indirect
|
||||||
|
github.com/google/flatbuffers v25.2.10+incompatible // indirect
|
||||||
|
github.com/google/gnostic-models v0.7.1 // indirect
|
||||||
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f // indirect
|
||||||
|
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect
|
||||||
|
github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 // indirect
|
||||||
|
github.com/grafana/dataplane/sdata v0.0.9 // indirect
|
||||||
|
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect
|
||||||
|
github.com/grafana/grafana-aws-sdk v1.3.0 // indirect
|
||||||
|
github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 // indirect
|
||||||
|
github.com/grafana/grafana-plugin-sdk-go v0.284.0 // indirect
|
||||||
|
github.com/grafana/grafana/pkg/apimachinery v0.0.0 // indirect
|
||||||
|
github.com/grafana/grafana/pkg/apiserver v0.0.0 // indirect
|
||||||
|
github.com/grafana/grafana/pkg/semconv v0.0.0-20250804150913-990f1c69ecc2 // indirect
|
||||||
|
github.com/grafana/otel-profiling-go v0.5.1 // indirect
|
||||||
|
github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect
|
||||||
|
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
|
||||||
|
github.com/grafana/sqlds/v4 v4.2.7 // indirect
|
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // indirect
|
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
|
||||||
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
|
github.com/hashicorp/go-hclog v1.6.3 // indirect
|
||||||
|
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||||
|
github.com/hashicorp/go-metrics v0.5.4 // indirect
|
||||||
|
github.com/hashicorp/go-msgpack/v2 v2.1.2 // indirect
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
|
github.com/hashicorp/go-plugin v1.7.0 // indirect
|
||||||
|
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
|
||||||
|
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
|
github.com/hashicorp/memberlist v0.5.2 // indirect
|
||||||
|
github.com/hashicorp/yamux v0.1.2 // indirect
|
||||||
|
github.com/jaegertracing/jaeger-idl v0.5.0 // indirect
|
||||||
|
github.com/jmespath-community/go-jmespath v1.1.1 // indirect
|
||||||
|
github.com/jmoiron/sqlx v1.4.0 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/jpillora/backoff v1.0.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 // indirect
|
||||||
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
|
github.com/lib/pq v1.10.9 // indirect
|
||||||
|
github.com/mailru/easyjson v0.9.1 // indirect
|
||||||
|
github.com/mattetti/filebuffer v1.0.1 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.32 // indirect
|
||||||
|
github.com/mdlayher/socket v0.4.1 // indirect
|
||||||
|
github.com/mdlayher/vsock v1.2.1 // indirect
|
||||||
|
github.com/miekg/dns v1.1.63 // indirect
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
|
github.com/mithrandie/csvq v1.18.1 // indirect
|
||||||
|
github.com/mithrandie/csvq-driver v1.7.0 // indirect
|
||||||
|
github.com/mithrandie/go-file/v2 v2.1.0 // indirect
|
||||||
|
github.com/mithrandie/go-text v1.6.0 // indirect
|
||||||
|
github.com/mithrandie/ternary v1.1.1 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||||
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
|
||||||
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
|
github.com/nikunjy/rules v1.5.0 // indirect
|
||||||
|
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
|
||||||
|
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
|
||||||
|
github.com/oklog/run v1.1.0 // indirect
|
||||||
|
github.com/oklog/ulid v1.3.1 // indirect
|
||||||
|
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||||
|
github.com/open-feature/go-sdk v1.16.0 // indirect
|
||||||
|
github.com/open-feature/go-sdk-contrib/providers/go-feature-flag v0.2.6 // indirect
|
||||||
|
github.com/open-feature/go-sdk-contrib/providers/ofrep v0.1.6 // indirect
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||||
|
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
|
github.com/prometheus/alertmanager v0.28.2 // indirect
|
||||||
|
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||||
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
|
github.com/prometheus/common v0.67.4 // indirect
|
||||||
|
github.com/prometheus/exporter-toolkit v0.14.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.19.2 // indirect
|
||||||
|
github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
|
github.com/stretchr/testify v1.11.1 // indirect
|
||||||
|
github.com/thomaspoignant/go-feature-flag v1.42.0 // indirect
|
||||||
|
github.com/tjhop/slog-gokit v0.1.5 // indirect
|
||||||
|
github.com/woodsbury/decimal128 v1.4.0 // indirect
|
||||||
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
|
github.com/zeebo/xxh3 v1.0.2 // indirect
|
||||||
|
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 // indirect
|
||||||
|
go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 // indirect
|
||||||
|
go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.39.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
||||||
|
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
||||||
|
go.uber.org/atomic v1.11.0 // indirect
|
||||||
|
go.uber.org/mock v0.6.0 // indirect
|
||||||
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
|
golang.org/x/crypto v0.46.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 // indirect
|
||||||
|
golang.org/x/mod v0.31.0 // indirect
|
||||||
|
golang.org/x/net v0.48.0 // indirect
|
||||||
|
golang.org/x/oauth2 v0.34.0 // indirect
|
||||||
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
|
golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc // indirect
|
||||||
|
golang.org/x/term v0.38.0 // indirect
|
||||||
|
golang.org/x/text v0.32.0 // indirect
|
||||||
|
golang.org/x/time v0.14.0 // indirect
|
||||||
|
golang.org/x/tools v0.40.0 // indirect
|
||||||
|
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
||||||
|
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect
|
||||||
|
google.golang.org/grpc v1.77.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
k8s.io/api v0.34.3 // indirect
|
||||||
|
k8s.io/apiextensions-apiserver v0.34.3 // indirect
|
||||||
|
k8s.io/apiserver v0.34.3 // indirect
|
||||||
|
k8s.io/client-go v0.34.3 // indirect
|
||||||
|
k8s.io/component-base v0.34.3 // indirect
|
||||||
|
k8s.io/klog/v2 v2.130.1 // indirect
|
||||||
|
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
|
||||||
|
modernc.org/libc v1.66.10 // indirect
|
||||||
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
|
modernc.org/memory v1.11.0 // indirect
|
||||||
|
modernc.org/sqlite v1.40.1 // indirect
|
||||||
|
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||||
|
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||||
|
sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect
|
||||||
|
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||||
|
xorm.io/builder v0.3.13 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
// transitive dependencies that need replaced
|
||||||
|
// TODO: stop depending on grafana core
|
||||||
|
replace github.com/grafana/grafana => ../..
|
||||||
|
|
||||||
|
replace github.com/grafana/grafana/pkg/apimachinery => ../../pkg/apimachinery
|
||||||
|
|
||||||
|
replace github.com/grafana/grafana/pkg/apiserver => ../../pkg/apiserver
|
||||||
|
|
||||||
|
replace github.com/grafana/grafana/apps/dashboard => ../dashboard
|
||||||
|
|
||||||
|
replace github.com/grafana/grafana/apps/provisioning => ../provisioning
|
||||||
|
|
||||||
|
replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
|||||||
|
module: "github.com/grafana/grafana/apps/dashvalidator/kinds"
|
||||||
|
language: version: "v0.8.2"
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
package kinds
|
||||||
|
|
||||||
|
// DashboardCompatibilityScore validates whether a dashboard's queries
|
||||||
|
// are compatible with the target datasource schema.
|
||||||
|
//
|
||||||
|
// This resource checks if metrics, tables, or other identifiers referenced
|
||||||
|
// in dashboard queries actually exist in the configured datasources,
|
||||||
|
// helping users identify dashboards that will show "no data" before deployment.
|
||||||
|
//
|
||||||
|
// MVP: Prometheus datasource only; architecture supports future datasource types.
|
||||||
|
dashboardcompatibilityscorev0alpha1: {
|
||||||
|
kind: "DashboardCompatibilityScore"
|
||||||
|
plural: "dashboardcompatibilityscores"
|
||||||
|
scope: "Namespaced"
|
||||||
|
schema: {
|
||||||
|
spec: {
|
||||||
|
// Complete dashboard JSON object to validate.
|
||||||
|
// Must be a v1 dashboard schema (contains "panels" array).
|
||||||
|
// v2 dashboards (with "elements" structure) are not yet supported.
|
||||||
|
dashboardJson: {...}
|
||||||
|
|
||||||
|
// Array of datasources to validate against.
|
||||||
|
// The validator will check dashboard queries against each datasource
|
||||||
|
// and provide per-datasource compatibility results.
|
||||||
|
//
|
||||||
|
// MVP: Only single datasource supported (array length = 1), Prometheus type only.
|
||||||
|
// Future: Will support multiple datasources for dashboards with mixed queries.
|
||||||
|
datasourceMappings: [...#DataSourceMapping]
|
||||||
|
}
|
||||||
|
status: {
|
||||||
|
// Overall compatibility score across all datasources (0-100).
|
||||||
|
// Calculated as: (total found metrics / total referenced metrics) * 100
|
||||||
|
//
|
||||||
|
// Score interpretation:
|
||||||
|
// - 100: Perfect compatibility, all queries will work
|
||||||
|
// - 80-99: Excellent, minor missing metrics
|
||||||
|
// - 50-79: Fair, significant missing metrics
|
||||||
|
// - 0-49: Poor, most queries will fail
|
||||||
|
compatibilityScore: float64
|
||||||
|
|
||||||
|
// Per-datasource validation results.
|
||||||
|
// Array length matches spec.datasourceMappings.
|
||||||
|
// Each element contains detailed metrics and query-level breakdown.
|
||||||
|
datasourceResults: [...#DataSourceResult]
|
||||||
|
|
||||||
|
// ISO 8601 timestamp of when validation was last performed.
|
||||||
|
// Example: "2024-01-15T10:30:00Z"
|
||||||
|
lastChecked?: string
|
||||||
|
|
||||||
|
// Human-readable summary of validation result.
|
||||||
|
// Examples: "All queries compatible", "3 missing metrics found"
|
||||||
|
message?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataSourceMapping specifies a datasource to validate dashboard queries against.
|
||||||
|
// Maps logical datasource references in the dashboard to actual datasource instances.
|
||||||
|
#DataSourceMapping: {
|
||||||
|
// Unique identifier of the datasource instance.
|
||||||
|
// Example: "prometheus-prod-us-west"
|
||||||
|
uid: string
|
||||||
|
|
||||||
|
// Type of datasource plugin.
|
||||||
|
// MVP: Only "prometheus" supported.
|
||||||
|
// Future: "mysql", "postgres", "elasticsearch", etc.
|
||||||
|
type: string
|
||||||
|
|
||||||
|
// Optional human-readable name for display in results.
|
||||||
|
// If not provided, UID will be used in error messages.
|
||||||
|
// Example: "Production Prometheus (US-West)"
|
||||||
|
name?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataSourceResult contains validation results for a single datasource.
|
||||||
|
// Provides aggregate statistics and per-query breakdown of compatibility.
|
||||||
|
#DataSourceResult: {
|
||||||
|
// Datasource UID that was validated (matches DataSourceMapping.uid)
|
||||||
|
uid: string
|
||||||
|
|
||||||
|
// Datasource type (matches DataSourceMapping.type)
|
||||||
|
type: string
|
||||||
|
|
||||||
|
// Optional display name (matches DataSourceMapping.name if provided)
|
||||||
|
name?: string
|
||||||
|
|
||||||
|
// Total number of queries in the dashboard targeting this datasource.
|
||||||
|
// Includes all panel targets/queries that reference this datasource.
|
||||||
|
totalQueries: int
|
||||||
|
|
||||||
|
// Number of queries successfully validated.
|
||||||
|
// May be less than totalQueries if some queries couldn't be parsed.
|
||||||
|
checkedQueries: int
|
||||||
|
|
||||||
|
// Total number of unique metrics/identifiers referenced across all queries.
|
||||||
|
// For Prometheus: metric names extracted from PromQL expressions.
|
||||||
|
// For SQL datasources: table and column names.
|
||||||
|
totalMetrics: int
|
||||||
|
|
||||||
|
// Number of metrics that exist in the datasource schema.
|
||||||
|
// foundMetrics <= totalMetrics
|
||||||
|
foundMetrics: int
|
||||||
|
|
||||||
|
// Array of metric names that were referenced but don't exist.
|
||||||
|
// Useful for debugging why a dashboard shows "no data".
|
||||||
|
// Example for Prometheus: ["http_requests_total", "api_latency_seconds"]
|
||||||
|
missingMetrics: [...string]
|
||||||
|
|
||||||
|
// Per-query breakdown showing which specific queries have issues.
|
||||||
|
// One entry per query target (refId: "A", "B", "C", etc.) in each panel.
|
||||||
|
// Allows pinpointing exactly which panel/query needs fixing.
|
||||||
|
queryBreakdown: [...#QueryBreakdown]
|
||||||
|
|
||||||
|
// Overall compatibility score for this datasource (0-100).
|
||||||
|
// Calculated as: (foundMetrics / totalMetrics) * 100
|
||||||
|
// Used to calculate the global compatibilityScore in status.
|
||||||
|
compatibilityScore: float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryBreakdown provides compatibility details for a single query within a panel.
|
||||||
|
// Granular per-query results allow users to identify exactly which queries need fixing.
|
||||||
|
//
|
||||||
|
// Note: A panel can have multiple queries (refId: "A", "B", "C", etc.),
|
||||||
|
// so there may be multiple QueryBreakdown entries for the same panelID.
|
||||||
|
#QueryBreakdown: {
|
||||||
|
// Human-readable panel title for context.
|
||||||
|
// Example: "CPU Usage", "Request Rate"
|
||||||
|
panelTitle: string
|
||||||
|
|
||||||
|
// Numeric panel ID from dashboard JSON.
|
||||||
|
// Used to correlate with dashboard structure.
|
||||||
|
panelID: int
|
||||||
|
|
||||||
|
// Query identifier within the panel.
|
||||||
|
// Values: "A", "B", "C", etc. (from panel.targets[].refId)
|
||||||
|
// Uniquely identifies which query in a multi-query panel this refers to.
|
||||||
|
queryRefId: string
|
||||||
|
|
||||||
|
// Number of unique metrics referenced in this specific query.
|
||||||
|
// For Prometheus: metrics extracted from the PromQL expr.
|
||||||
|
// Example: rate(http_requests_total[5m]) references 1 metric.
|
||||||
|
totalMetrics: int
|
||||||
|
|
||||||
|
// Number of those metrics that exist in the datasource.
|
||||||
|
// foundMetrics <= totalMetrics
|
||||||
|
foundMetrics: int
|
||||||
|
|
||||||
|
// Array of missing metric names specific to this query.
|
||||||
|
// Helps identify exactly which part of a query expression will fail.
|
||||||
|
// Empty array means query is fully compatible.
|
||||||
|
missingMetrics: [...string]
|
||||||
|
|
||||||
|
// Compatibility percentage for this individual query (0-100).
|
||||||
|
// Calculated as: (foundMetrics / totalMetrics) * 100
|
||||||
|
// 100 = query will work perfectly, 0 = query will return no data.
|
||||||
|
compatibilityScore: float64
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package kinds
|
||||||
|
|
||||||
|
manifest: {
|
||||||
|
// appName is the unique name of your app. It is used to reference the app from other config objects,
|
||||||
|
// and to generate the group used by your app in the app platform API.
|
||||||
|
appName: "dashvalidator"
|
||||||
|
// groupOverride can be used to specify a non-appName-based API group.
|
||||||
|
// By default, an app's API group is LOWER(REPLACE(appName, '-', '')).ext.grafana.com,
|
||||||
|
// but there are cases where this needs to be changed.
|
||||||
|
// Keep in mind that changing this after an app is deployed can cause problems with clients and/or kind data.
|
||||||
|
groupOverride: "dashvalidator.grafana.app"
|
||||||
|
|
||||||
|
// versions is a map of versions supported by your app. Version names should follow the format "v<integer>" or
|
||||||
|
// "v<integer>(alpha|beta)<integer>". Each version contains the kinds your app manages for that version.
|
||||||
|
// If your app needs access to kinds managed by another app, use permissions.accessKinds to allow your app access.
|
||||||
|
versions: {
|
||||||
|
"v1alpha1": v1alpha1
|
||||||
|
}
|
||||||
|
// extraPermissions contains any additional permissions your app may require to function.
|
||||||
|
// Your app will always have all permissions for each kind it manages (the items defined in 'kinds').
|
||||||
|
extraPermissions: {
|
||||||
|
// If your app needs access to additional kinds supplied by other apps, you can list them here
|
||||||
|
accessKinds: [
|
||||||
|
// Here is an example for your app accessing the playlist kind for reads and watch
|
||||||
|
// {
|
||||||
|
// group: "playlist.grafana.app"
|
||||||
|
// resource: "playlists"
|
||||||
|
// actions: ["get","list","watch"]
|
||||||
|
// }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// v1alpha1 is the v1alpha1 version of the app's API.
|
||||||
|
// It includes kinds which the v1alpha1 API serves, and (future) custom routes served globally from the v1alpha1 version.
|
||||||
|
v1alpha1: {
|
||||||
|
// kinds is the list of kinds served by this version
|
||||||
|
kinds: [dashboardcompatibilityscorev0alpha1]
|
||||||
|
// [OPTIONAL]
|
||||||
|
// served indicates whether this particular version is served by the API server.
|
||||||
|
// served should be set to false before a version is removed from the manifest entirely.
|
||||||
|
// served defaults to true if not present.
|
||||||
|
served: true
|
||||||
|
// [OPTIONAL]
|
||||||
|
// Codegen is a trait that tells the grafana-app-sdk, or other code generation tooling, how to process this kind.
|
||||||
|
// If not present, default values within the codegen trait are used.
|
||||||
|
// If you wish to specify codegen per-version, put this section in the version's object
|
||||||
|
// (for example, <no value>v1alpha1) instead.
|
||||||
|
|
||||||
|
routes: {
|
||||||
|
namespaced: {
|
||||||
|
"/check": {
|
||||||
|
"POST": {
|
||||||
|
request: {
|
||||||
|
body: {
|
||||||
|
dashboardJson: {...}
|
||||||
|
datasourceMappings: [...{
|
||||||
|
uid: string
|
||||||
|
type: string
|
||||||
|
name?: string
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response: {
|
||||||
|
compatibilityScore: number
|
||||||
|
datasourceResults: [...{
|
||||||
|
uid: string
|
||||||
|
type: string
|
||||||
|
name?: string
|
||||||
|
totalQueries: int
|
||||||
|
checkedQueries: int
|
||||||
|
totalMetrics: int
|
||||||
|
foundMetrics: int
|
||||||
|
missingMetrics: [...string]
|
||||||
|
queryBreakdown: [...{
|
||||||
|
panelTitle: string
|
||||||
|
panelID: int
|
||||||
|
queryRefId: string
|
||||||
|
totalMetrics: int
|
||||||
|
foundMetrics: int
|
||||||
|
missingMetrics: [...string]
|
||||||
|
compatibilityScore: number
|
||||||
|
}]
|
||||||
|
compatibilityScore: number
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cluser: {}
|
||||||
|
}
|
||||||
|
codegen: {
|
||||||
|
// [OPTIONAL]
|
||||||
|
// ts contains TypeScript code generation properties for the kind
|
||||||
|
ts: {
|
||||||
|
// [OPTIONAL]
|
||||||
|
// enabled indicates whether the CLI should generate front-end TypeScript code for the kind.
|
||||||
|
// Defaults to true if not present.
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
// [OPTIONAL]
|
||||||
|
// go contains go code generation properties for the kind
|
||||||
|
go: {
|
||||||
|
// [OPTIONAL]
|
||||||
|
// enabled indicates whether the CLI should generate back-end go code for the kind.
|
||||||
|
// Defaults to true if not present.
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -4,7 +4,7 @@ import "k8s.io/apimachinery/pkg/runtime/schema"
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// APIGroup is the API group used by all kinds in this package
|
// APIGroup is the API group used by all kinds in this package
|
||||||
APIGroup = "logsdrilldown.grafana.app"
|
APIGroup = "dashvalidator.grafana.app"
|
||||||
// APIVersion is the API version used by all kinds in this package
|
// APIVersion is the API version used by all kinds in this package
|
||||||
APIVersion = "v1alpha1"
|
APIVersion = "v1alpha1"
|
||||||
)
|
)
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
type CreateCheckRequestBody struct {
|
||||||
|
DashboardJson map[string]any `json:"dashboardJson"`
|
||||||
|
DatasourceMappings []CreateCheckRequestV1alpha1BodyDatasourceMappings `json:"datasourceMappings"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCreateCheckRequestBody creates a new CreateCheckRequestBody object.
|
||||||
|
func NewCreateCheckRequestBody() *CreateCheckRequestBody {
|
||||||
|
return &CreateCheckRequestBody{
|
||||||
|
DashboardJson: map[string]any{},
|
||||||
|
DatasourceMappings: []CreateCheckRequestV1alpha1BodyDatasourceMappings{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateCheckRequestV1alpha1BodyDatasourceMappings struct {
|
||||||
|
Uid string `json:"uid"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCreateCheckRequestV1alpha1BodyDatasourceMappings creates a new CreateCheckRequestV1alpha1BodyDatasourceMappings object.
|
||||||
|
func NewCreateCheckRequestV1alpha1BodyDatasourceMappings() *CreateCheckRequestV1alpha1BodyDatasourceMappings {
|
||||||
|
return &CreateCheckRequestV1alpha1BodyDatasourceMappings{}
|
||||||
|
}
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
// +k8s:openapi-gen=true
|
||||||
|
type CreateCheckBody struct {
|
||||||
|
CompatibilityScore float64 `json:"compatibilityScore"`
|
||||||
|
DatasourceResults []V1alpha1CreateCheckBodyDatasourceResults `json:"datasourceResults"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCreateCheckBody creates a new CreateCheckBody object.
|
||||||
|
func NewCreateCheckBody() *CreateCheckBody {
|
||||||
|
return &CreateCheckBody{
|
||||||
|
DatasourceResults: []V1alpha1CreateCheckBodyDatasourceResults{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:openapi-gen=true
|
||||||
|
type V1alpha1CreateCheckBodyDatasourceResultsQueryBreakdown struct {
|
||||||
|
PanelTitle string `json:"panelTitle"`
|
||||||
|
PanelID int64 `json:"panelID"`
|
||||||
|
QueryRefId string `json:"queryRefId"`
|
||||||
|
TotalMetrics int64 `json:"totalMetrics"`
|
||||||
|
FoundMetrics int64 `json:"foundMetrics"`
|
||||||
|
MissingMetrics []string `json:"missingMetrics"`
|
||||||
|
CompatibilityScore float64 `json:"compatibilityScore"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewV1alpha1CreateCheckBodyDatasourceResultsQueryBreakdown creates a new V1alpha1CreateCheckBodyDatasourceResultsQueryBreakdown object.
|
||||||
|
func NewV1alpha1CreateCheckBodyDatasourceResultsQueryBreakdown() *V1alpha1CreateCheckBodyDatasourceResultsQueryBreakdown {
|
||||||
|
return &V1alpha1CreateCheckBodyDatasourceResultsQueryBreakdown{
|
||||||
|
MissingMetrics: []string{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:openapi-gen=true
|
||||||
|
type V1alpha1CreateCheckBodyDatasourceResults struct {
|
||||||
|
Uid string `json:"uid"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
TotalQueries int64 `json:"totalQueries"`
|
||||||
|
CheckedQueries int64 `json:"checkedQueries"`
|
||||||
|
TotalMetrics int64 `json:"totalMetrics"`
|
||||||
|
FoundMetrics int64 `json:"foundMetrics"`
|
||||||
|
MissingMetrics []string `json:"missingMetrics"`
|
||||||
|
QueryBreakdown []V1alpha1CreateCheckBodyDatasourceResultsQueryBreakdown `json:"queryBreakdown"`
|
||||||
|
CompatibilityScore float64 `json:"compatibilityScore"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewV1alpha1CreateCheckBodyDatasourceResults creates a new V1alpha1CreateCheckBodyDatasourceResults object.
|
||||||
|
func NewV1alpha1CreateCheckBodyDatasourceResults() *V1alpha1CreateCheckBodyDatasourceResults {
|
||||||
|
return &V1alpha1CreateCheckBodyDatasourceResults{
|
||||||
|
MissingMetrics: []string{},
|
||||||
|
QueryBreakdown: []V1alpha1CreateCheckBodyDatasourceResultsQueryBreakdown{},
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+37
@@ -0,0 +1,37 @@
|
|||||||
|
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/grafana/grafana-app-sdk/resource"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// +k8s:openapi-gen=true
|
||||||
|
type CreateCheck struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
CreateCheckBody `json:",inline"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCreateCheck() *CreateCheck {
|
||||||
|
return &CreateCheck{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CreateCheckBody) DeepCopyInto(dst *CreateCheckBody) {
|
||||||
|
_ = resource.CopyObjectInto(dst, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *CreateCheck) DeepCopyObject() runtime.Object {
|
||||||
|
dst := NewCreateCheck()
|
||||||
|
o.DeepCopyInto(dst)
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *CreateCheck) DeepCopyInto(dst *CreateCheck) {
|
||||||
|
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
|
||||||
|
dst.TypeMeta.Kind = o.TypeMeta.Kind
|
||||||
|
o.CreateCheckBody.DeepCopyInto(&dst.CreateCheckBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ runtime.Object = NewCreateCheck()
|
||||||
Generated
+99
@@ -0,0 +1,99 @@
|
|||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana-app-sdk/resource"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DashboardCompatibilityScoreClient struct {
|
||||||
|
client *resource.TypedClient[*DashboardCompatibilityScore, *DashboardCompatibilityScoreList]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDashboardCompatibilityScoreClient(client resource.Client) *DashboardCompatibilityScoreClient {
|
||||||
|
return &DashboardCompatibilityScoreClient{
|
||||||
|
client: resource.NewTypedClient[*DashboardCompatibilityScore, *DashboardCompatibilityScoreList](client, DashboardCompatibilityScoreKind()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDashboardCompatibilityScoreClientFromGenerator(generator resource.ClientGenerator) (*DashboardCompatibilityScoreClient, error) {
|
||||||
|
c, err := generator.ClientFor(DashboardCompatibilityScoreKind())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewDashboardCompatibilityScoreClient(c), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DashboardCompatibilityScoreClient) Get(ctx context.Context, identifier resource.Identifier) (*DashboardCompatibilityScore, error) {
|
||||||
|
return c.client.Get(ctx, identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DashboardCompatibilityScoreClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*DashboardCompatibilityScoreList, error) {
|
||||||
|
return c.client.List(ctx, namespace, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DashboardCompatibilityScoreClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*DashboardCompatibilityScoreList, error) {
|
||||||
|
resp, err := c.client.List(ctx, namespace, resource.ListOptions{
|
||||||
|
ResourceVersion: opts.ResourceVersion,
|
||||||
|
Limit: opts.Limit,
|
||||||
|
LabelFilters: opts.LabelFilters,
|
||||||
|
FieldSelectors: opts.FieldSelectors,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for resp.GetContinue() != "" {
|
||||||
|
page, err := c.client.List(ctx, namespace, resource.ListOptions{
|
||||||
|
Continue: resp.GetContinue(),
|
||||||
|
ResourceVersion: opts.ResourceVersion,
|
||||||
|
Limit: opts.Limit,
|
||||||
|
LabelFilters: opts.LabelFilters,
|
||||||
|
FieldSelectors: opts.FieldSelectors,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp.SetContinue(page.GetContinue())
|
||||||
|
resp.SetResourceVersion(page.GetResourceVersion())
|
||||||
|
resp.SetItems(append(resp.GetItems(), page.GetItems()...))
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DashboardCompatibilityScoreClient) Create(ctx context.Context, obj *DashboardCompatibilityScore, opts resource.CreateOptions) (*DashboardCompatibilityScore, error) {
|
||||||
|
// Make sure apiVersion and kind are set
|
||||||
|
obj.APIVersion = GroupVersion.Identifier()
|
||||||
|
obj.Kind = DashboardCompatibilityScoreKind().Kind()
|
||||||
|
return c.client.Create(ctx, obj, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DashboardCompatibilityScoreClient) Update(ctx context.Context, obj *DashboardCompatibilityScore, opts resource.UpdateOptions) (*DashboardCompatibilityScore, error) {
|
||||||
|
return c.client.Update(ctx, obj, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DashboardCompatibilityScoreClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*DashboardCompatibilityScore, error) {
|
||||||
|
return c.client.Patch(ctx, identifier, req, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DashboardCompatibilityScoreClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus DashboardCompatibilityScoreStatus, opts resource.UpdateOptions) (*DashboardCompatibilityScore, error) {
|
||||||
|
return c.client.Update(ctx, &DashboardCompatibilityScore{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: DashboardCompatibilityScoreKind().Kind(),
|
||||||
|
APIVersion: GroupVersion.Identifier(),
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
ResourceVersion: opts.ResourceVersion,
|
||||||
|
Namespace: identifier.Namespace,
|
||||||
|
Name: identifier.Name,
|
||||||
|
},
|
||||||
|
Status: newStatus,
|
||||||
|
}, resource.UpdateOptions{
|
||||||
|
Subresource: "status",
|
||||||
|
ResourceVersion: opts.ResourceVersion,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DashboardCompatibilityScoreClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
|
||||||
|
return c.client.Delete(ctx, identifier, opts)
|
||||||
|
}
|
||||||
+5
-5
@@ -11,18 +11,18 @@ import (
|
|||||||
"github.com/grafana/grafana-app-sdk/resource"
|
"github.com/grafana/grafana-app-sdk/resource"
|
||||||
)
|
)
|
||||||
|
|
||||||
// JSONCodec is an implementation of resource.Codec for kubernetes JSON encoding
|
// DashboardCompatibilityScoreJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding
|
||||||
type JSONCodec struct{}
|
type DashboardCompatibilityScoreJSONCodec struct{}
|
||||||
|
|
||||||
// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into`
|
// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into`
|
||||||
func (*JSONCodec) Read(reader io.Reader, into resource.Object) error {
|
func (*DashboardCompatibilityScoreJSONCodec) Read(reader io.Reader, into resource.Object) error {
|
||||||
return json.NewDecoder(reader).Decode(into)
|
return json.NewDecoder(reader).Decode(into)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write writes JSON-encoded bytes into `writer` marshaled from `from`
|
// Write writes JSON-encoded bytes into `writer` marshaled from `from`
|
||||||
func (*JSONCodec) Write(writer io.Writer, from resource.Object) error {
|
func (*DashboardCompatibilityScoreJSONCodec) Write(writer io.Writer, from resource.Object) error {
|
||||||
return json.NewEncoder(writer).Encode(from)
|
return json.NewEncoder(writer).Encode(from)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interface compliance checks
|
// Interface compliance checks
|
||||||
var _ resource.Codec = &JSONCodec{}
|
var _ resource.Codec = &DashboardCompatibilityScoreJSONCodec{}
|
||||||
+4
-4
@@ -9,7 +9,7 @@ import (
|
|||||||
// metadata contains embedded CommonMetadata and can be extended with custom string fields
|
// metadata contains embedded CommonMetadata and can be extended with custom string fields
|
||||||
// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
|
// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
|
||||||
// without external reference as using the CommonMetadata reference breaks thema codegen.
|
// without external reference as using the CommonMetadata reference breaks thema codegen.
|
||||||
type Metadata struct {
|
type DashboardCompatibilityScoreMetadata struct {
|
||||||
UpdateTimestamp time.Time `json:"updateTimestamp"`
|
UpdateTimestamp time.Time `json:"updateTimestamp"`
|
||||||
CreatedBy string `json:"createdBy"`
|
CreatedBy string `json:"createdBy"`
|
||||||
Uid string `json:"uid"`
|
Uid string `json:"uid"`
|
||||||
@@ -22,9 +22,9 @@ type Metadata struct {
|
|||||||
Labels map[string]string `json:"labels"`
|
Labels map[string]string `json:"labels"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMetadata creates a new Metadata object.
|
// NewDashboardCompatibilityScoreMetadata creates a new DashboardCompatibilityScoreMetadata object.
|
||||||
func NewMetadata() *Metadata {
|
func NewDashboardCompatibilityScoreMetadata() *DashboardCompatibilityScoreMetadata {
|
||||||
return &Metadata{
|
return &DashboardCompatibilityScoreMetadata{
|
||||||
Finalizers: []string{},
|
Finalizers: []string{},
|
||||||
Labels: map[string]string{},
|
Labels: map[string]string{},
|
||||||
}
|
}
|
||||||
+58
-51
@@ -15,22 +15,29 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
// +k8s:openapi-gen=true
|
||||||
type LogsDrilldownDefaults struct {
|
type DashboardCompatibilityScore struct {
|
||||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||||
metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
|
metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
|
||||||
|
|
||||||
// Spec is the spec of the LogsDrilldownDefaults
|
// Spec is the spec of the DashboardCompatibilityScore
|
||||||
Spec Spec `json:"spec" yaml:"spec"`
|
Spec DashboardCompatibilityScoreSpec `json:"spec" yaml:"spec"`
|
||||||
|
|
||||||
Status Status `json:"status" yaml:"status"`
|
Status DashboardCompatibilityScoreStatus `json:"status" yaml:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaults) GetSpec() any {
|
func NewDashboardCompatibilityScore() *DashboardCompatibilityScore {
|
||||||
|
return &DashboardCompatibilityScore{
|
||||||
|
Spec: *NewDashboardCompatibilityScoreSpec(),
|
||||||
|
Status: *NewDashboardCompatibilityScoreStatus(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *DashboardCompatibilityScore) GetSpec() any {
|
||||||
return o.Spec
|
return o.Spec
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaults) SetSpec(spec any) error {
|
func (o *DashboardCompatibilityScore) SetSpec(spec any) error {
|
||||||
cast, ok := spec.(Spec)
|
cast, ok := spec.(DashboardCompatibilityScoreSpec)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec)
|
return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec)
|
||||||
}
|
}
|
||||||
@@ -38,13 +45,13 @@ func (o *LogsDrilldownDefaults) SetSpec(spec any) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaults) GetSubresources() map[string]any {
|
func (o *DashboardCompatibilityScore) GetSubresources() map[string]any {
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
"status": o.Status,
|
"status": o.Status,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaults) GetSubresource(name string) (any, bool) {
|
func (o *DashboardCompatibilityScore) GetSubresource(name string) (any, bool) {
|
||||||
switch name {
|
switch name {
|
||||||
case "status":
|
case "status":
|
||||||
return o.Status, true
|
return o.Status, true
|
||||||
@@ -53,12 +60,12 @@ func (o *LogsDrilldownDefaults) GetSubresource(name string) (any, bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaults) SetSubresource(name string, value any) error {
|
func (o *DashboardCompatibilityScore) SetSubresource(name string, value any) error {
|
||||||
switch name {
|
switch name {
|
||||||
case "status":
|
case "status":
|
||||||
cast, ok := value.(Status)
|
cast, ok := value.(DashboardCompatibilityScoreStatus)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("cannot set status type %#v, not of type Status", value)
|
return fmt.Errorf("cannot set status type %#v, not of type DashboardCompatibilityScoreStatus", value)
|
||||||
}
|
}
|
||||||
o.Status = cast
|
o.Status = cast
|
||||||
return nil
|
return nil
|
||||||
@@ -67,7 +74,7 @@ func (o *LogsDrilldownDefaults) SetSubresource(name string, value any) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaults) GetStaticMetadata() resource.StaticMetadata {
|
func (o *DashboardCompatibilityScore) GetStaticMetadata() resource.StaticMetadata {
|
||||||
gvk := o.GroupVersionKind()
|
gvk := o.GroupVersionKind()
|
||||||
return resource.StaticMetadata{
|
return resource.StaticMetadata{
|
||||||
Name: o.ObjectMeta.Name,
|
Name: o.ObjectMeta.Name,
|
||||||
@@ -78,7 +85,7 @@ func (o *LogsDrilldownDefaults) GetStaticMetadata() resource.StaticMetadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaults) SetStaticMetadata(metadata resource.StaticMetadata) {
|
func (o *DashboardCompatibilityScore) SetStaticMetadata(metadata resource.StaticMetadata) {
|
||||||
o.Name = metadata.Name
|
o.Name = metadata.Name
|
||||||
o.Namespace = metadata.Namespace
|
o.Namespace = metadata.Namespace
|
||||||
o.SetGroupVersionKind(schema.GroupVersionKind{
|
o.SetGroupVersionKind(schema.GroupVersionKind{
|
||||||
@@ -88,7 +95,7 @@ func (o *LogsDrilldownDefaults) SetStaticMetadata(metadata resource.StaticMetada
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaults) GetCommonMetadata() resource.CommonMetadata {
|
func (o *DashboardCompatibilityScore) GetCommonMetadata() resource.CommonMetadata {
|
||||||
dt := o.DeletionTimestamp
|
dt := o.DeletionTimestamp
|
||||||
var deletionTimestamp *time.Time
|
var deletionTimestamp *time.Time
|
||||||
if dt != nil {
|
if dt != nil {
|
||||||
@@ -120,7 +127,7 @@ func (o *LogsDrilldownDefaults) GetCommonMetadata() resource.CommonMetadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaults) SetCommonMetadata(metadata resource.CommonMetadata) {
|
func (o *DashboardCompatibilityScore) SetCommonMetadata(metadata resource.CommonMetadata) {
|
||||||
o.UID = types.UID(metadata.UID)
|
o.UID = types.UID(metadata.UID)
|
||||||
o.ResourceVersion = metadata.ResourceVersion
|
o.ResourceVersion = metadata.ResourceVersion
|
||||||
o.Generation = metadata.Generation
|
o.Generation = metadata.Generation
|
||||||
@@ -165,7 +172,7 @@ func (o *LogsDrilldownDefaults) SetCommonMetadata(metadata resource.CommonMetada
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaults) GetCreatedBy() string {
|
func (o *DashboardCompatibilityScore) GetCreatedBy() string {
|
||||||
if o.ObjectMeta.Annotations == nil {
|
if o.ObjectMeta.Annotations == nil {
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
o.ObjectMeta.Annotations = make(map[string]string)
|
||||||
}
|
}
|
||||||
@@ -173,7 +180,7 @@ func (o *LogsDrilldownDefaults) GetCreatedBy() string {
|
|||||||
return o.ObjectMeta.Annotations["grafana.com/createdBy"]
|
return o.ObjectMeta.Annotations["grafana.com/createdBy"]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaults) SetCreatedBy(createdBy string) {
|
func (o *DashboardCompatibilityScore) SetCreatedBy(createdBy string) {
|
||||||
if o.ObjectMeta.Annotations == nil {
|
if o.ObjectMeta.Annotations == nil {
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
o.ObjectMeta.Annotations = make(map[string]string)
|
||||||
}
|
}
|
||||||
@@ -181,7 +188,7 @@ func (o *LogsDrilldownDefaults) SetCreatedBy(createdBy string) {
|
|||||||
o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy
|
o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaults) GetUpdateTimestamp() time.Time {
|
func (o *DashboardCompatibilityScore) GetUpdateTimestamp() time.Time {
|
||||||
if o.ObjectMeta.Annotations == nil {
|
if o.ObjectMeta.Annotations == nil {
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
o.ObjectMeta.Annotations = make(map[string]string)
|
||||||
}
|
}
|
||||||
@@ -190,7 +197,7 @@ func (o *LogsDrilldownDefaults) GetUpdateTimestamp() time.Time {
|
|||||||
return parsed
|
return parsed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaults) SetUpdateTimestamp(updateTimestamp time.Time) {
|
func (o *DashboardCompatibilityScore) SetUpdateTimestamp(updateTimestamp time.Time) {
|
||||||
if o.ObjectMeta.Annotations == nil {
|
if o.ObjectMeta.Annotations == nil {
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
o.ObjectMeta.Annotations = make(map[string]string)
|
||||||
}
|
}
|
||||||
@@ -198,7 +205,7 @@ func (o *LogsDrilldownDefaults) SetUpdateTimestamp(updateTimestamp time.Time) {
|
|||||||
o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339)
|
o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaults) GetUpdatedBy() string {
|
func (o *DashboardCompatibilityScore) GetUpdatedBy() string {
|
||||||
if o.ObjectMeta.Annotations == nil {
|
if o.ObjectMeta.Annotations == nil {
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
o.ObjectMeta.Annotations = make(map[string]string)
|
||||||
}
|
}
|
||||||
@@ -206,7 +213,7 @@ func (o *LogsDrilldownDefaults) GetUpdatedBy() string {
|
|||||||
return o.ObjectMeta.Annotations["grafana.com/updatedBy"]
|
return o.ObjectMeta.Annotations["grafana.com/updatedBy"]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaults) SetUpdatedBy(updatedBy string) {
|
func (o *DashboardCompatibilityScore) SetUpdatedBy(updatedBy string) {
|
||||||
if o.ObjectMeta.Annotations == nil {
|
if o.ObjectMeta.Annotations == nil {
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
o.ObjectMeta.Annotations = make(map[string]string)
|
||||||
}
|
}
|
||||||
@@ -214,21 +221,21 @@ func (o *LogsDrilldownDefaults) SetUpdatedBy(updatedBy string) {
|
|||||||
o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy
|
o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaults) Copy() resource.Object {
|
func (o *DashboardCompatibilityScore) Copy() resource.Object {
|
||||||
return resource.CopyObject(o)
|
return resource.CopyObject(o)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaults) DeepCopyObject() runtime.Object {
|
func (o *DashboardCompatibilityScore) DeepCopyObject() runtime.Object {
|
||||||
return o.Copy()
|
return o.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaults) DeepCopy() *LogsDrilldownDefaults {
|
func (o *DashboardCompatibilityScore) DeepCopy() *DashboardCompatibilityScore {
|
||||||
cpy := &LogsDrilldownDefaults{}
|
cpy := &DashboardCompatibilityScore{}
|
||||||
o.DeepCopyInto(cpy)
|
o.DeepCopyInto(cpy)
|
||||||
return cpy
|
return cpy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaults) DeepCopyInto(dst *LogsDrilldownDefaults) {
|
func (o *DashboardCompatibilityScore) DeepCopyInto(dst *DashboardCompatibilityScore) {
|
||||||
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
|
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
|
||||||
dst.TypeMeta.Kind = o.TypeMeta.Kind
|
dst.TypeMeta.Kind = o.TypeMeta.Kind
|
||||||
o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta)
|
o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta)
|
||||||
@@ -237,34 +244,34 @@ func (o *LogsDrilldownDefaults) DeepCopyInto(dst *LogsDrilldownDefaults) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Interface compliance compile-time check
|
// Interface compliance compile-time check
|
||||||
var _ resource.Object = &LogsDrilldownDefaults{}
|
var _ resource.Object = &DashboardCompatibilityScore{}
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
// +k8s:openapi-gen=true
|
||||||
type LogsDrilldownDefaultsList struct {
|
type DashboardCompatibilityScoreList struct {
|
||||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||||
metav1.ListMeta `json:"metadata" yaml:"metadata"`
|
metav1.ListMeta `json:"metadata" yaml:"metadata"`
|
||||||
Items []LogsDrilldownDefaults `json:"items" yaml:"items"`
|
Items []DashboardCompatibilityScore `json:"items" yaml:"items"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultsList) DeepCopyObject() runtime.Object {
|
func (o *DashboardCompatibilityScoreList) DeepCopyObject() runtime.Object {
|
||||||
return o.Copy()
|
return o.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultsList) Copy() resource.ListObject {
|
func (o *DashboardCompatibilityScoreList) Copy() resource.ListObject {
|
||||||
cpy := &LogsDrilldownDefaultsList{
|
cpy := &DashboardCompatibilityScoreList{
|
||||||
TypeMeta: o.TypeMeta,
|
TypeMeta: o.TypeMeta,
|
||||||
Items: make([]LogsDrilldownDefaults, len(o.Items)),
|
Items: make([]DashboardCompatibilityScore, len(o.Items)),
|
||||||
}
|
}
|
||||||
o.ListMeta.DeepCopyInto(&cpy.ListMeta)
|
o.ListMeta.DeepCopyInto(&cpy.ListMeta)
|
||||||
for i := 0; i < len(o.Items); i++ {
|
for i := 0; i < len(o.Items); i++ {
|
||||||
if item, ok := o.Items[i].Copy().(*LogsDrilldownDefaults); ok {
|
if item, ok := o.Items[i].Copy().(*DashboardCompatibilityScore); ok {
|
||||||
cpy.Items[i] = *item
|
cpy.Items[i] = *item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cpy
|
return cpy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultsList) GetItems() []resource.Object {
|
func (o *DashboardCompatibilityScoreList) GetItems() []resource.Object {
|
||||||
items := make([]resource.Object, len(o.Items))
|
items := make([]resource.Object, len(o.Items))
|
||||||
for i := 0; i < len(o.Items); i++ {
|
for i := 0; i < len(o.Items); i++ {
|
||||||
items[i] = &o.Items[i]
|
items[i] = &o.Items[i]
|
||||||
@@ -272,48 +279,48 @@ func (o *LogsDrilldownDefaultsList) GetItems() []resource.Object {
|
|||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultsList) SetItems(items []resource.Object) {
|
func (o *DashboardCompatibilityScoreList) SetItems(items []resource.Object) {
|
||||||
o.Items = make([]LogsDrilldownDefaults, len(items))
|
o.Items = make([]DashboardCompatibilityScore, len(items))
|
||||||
for i := 0; i < len(items); i++ {
|
for i := 0; i < len(items); i++ {
|
||||||
o.Items[i] = *items[i].(*LogsDrilldownDefaults)
|
o.Items[i] = *items[i].(*DashboardCompatibilityScore)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultsList) DeepCopy() *LogsDrilldownDefaultsList {
|
func (o *DashboardCompatibilityScoreList) DeepCopy() *DashboardCompatibilityScoreList {
|
||||||
cpy := &LogsDrilldownDefaultsList{}
|
cpy := &DashboardCompatibilityScoreList{}
|
||||||
o.DeepCopyInto(cpy)
|
o.DeepCopyInto(cpy)
|
||||||
return cpy
|
return cpy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultsList) DeepCopyInto(dst *LogsDrilldownDefaultsList) {
|
func (o *DashboardCompatibilityScoreList) DeepCopyInto(dst *DashboardCompatibilityScoreList) {
|
||||||
resource.CopyObjectInto(dst, o)
|
resource.CopyObjectInto(dst, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interface compliance compile-time check
|
// Interface compliance compile-time check
|
||||||
var _ resource.ListObject = &LogsDrilldownDefaultsList{}
|
var _ resource.ListObject = &DashboardCompatibilityScoreList{}
|
||||||
|
|
||||||
// Copy methods for all subresource types
|
// Copy methods for all subresource types
|
||||||
|
|
||||||
// DeepCopy creates a full deep copy of Spec
|
// DeepCopy creates a full deep copy of Spec
|
||||||
func (s *Spec) DeepCopy() *Spec {
|
func (s *DashboardCompatibilityScoreSpec) DeepCopy() *DashboardCompatibilityScoreSpec {
|
||||||
cpy := &Spec{}
|
cpy := &DashboardCompatibilityScoreSpec{}
|
||||||
s.DeepCopyInto(cpy)
|
s.DeepCopyInto(cpy)
|
||||||
return cpy
|
return cpy
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopyInto deep copies Spec into another Spec object
|
// DeepCopyInto deep copies Spec into another Spec object
|
||||||
func (s *Spec) DeepCopyInto(dst *Spec) {
|
func (s *DashboardCompatibilityScoreSpec) DeepCopyInto(dst *DashboardCompatibilityScoreSpec) {
|
||||||
resource.CopyObjectInto(dst, s)
|
resource.CopyObjectInto(dst, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy creates a full deep copy of Status
|
// DeepCopy creates a full deep copy of DashboardCompatibilityScoreStatus
|
||||||
func (s *Status) DeepCopy() *Status {
|
func (s *DashboardCompatibilityScoreStatus) DeepCopy() *DashboardCompatibilityScoreStatus {
|
||||||
cpy := &Status{}
|
cpy := &DashboardCompatibilityScoreStatus{}
|
||||||
s.DeepCopyInto(cpy)
|
s.DeepCopyInto(cpy)
|
||||||
return cpy
|
return cpy
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopyInto deep copies Status into another Status object
|
// DeepCopyInto deep copies DashboardCompatibilityScoreStatus into another DashboardCompatibilityScoreStatus object
|
||||||
func (s *Status) DeepCopyInto(dst *Status) {
|
func (s *DashboardCompatibilityScoreStatus) DeepCopyInto(dst *DashboardCompatibilityScoreStatus) {
|
||||||
resource.CopyObjectInto(dst, s)
|
resource.CopyObjectInto(dst, s)
|
||||||
}
|
}
|
||||||
Generated
+34
@@ -0,0 +1,34 @@
|
|||||||
|
//
|
||||||
|
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
||||||
|
//
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/grafana/grafana-app-sdk/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
// schema is unexported to prevent accidental overwrites
|
||||||
|
var (
|
||||||
|
schemaDashboardCompatibilityScore = resource.NewSimpleSchema("dashvalidator.grafana.app", "v1alpha1", NewDashboardCompatibilityScore(), &DashboardCompatibilityScoreList{}, resource.WithKind("DashboardCompatibilityScore"),
|
||||||
|
resource.WithPlural("dashboardcompatibilityscores"), resource.WithScope(resource.NamespacedScope))
|
||||||
|
kindDashboardCompatibilityScore = resource.Kind{
|
||||||
|
Schema: schemaDashboardCompatibilityScore,
|
||||||
|
Codecs: map[resource.KindEncoding]resource.Codec{
|
||||||
|
resource.KindEncodingJSON: &DashboardCompatibilityScoreJSONCodec{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Kind returns a resource.Kind for this Schema with a JSON codec
|
||||||
|
func DashboardCompatibilityScoreKind() resource.Kind {
|
||||||
|
return kindDashboardCompatibilityScore
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schema returns a resource.SimpleSchema representation of DashboardCompatibilityScore
|
||||||
|
func DashboardCompatibilityScoreSchema() *resource.SimpleSchema {
|
||||||
|
return schemaDashboardCompatibilityScore
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface compliance checks
|
||||||
|
var _ resource.Schema = kindDashboardCompatibilityScore
|
||||||
Generated
+48
@@ -0,0 +1,48 @@
|
|||||||
|
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
// DataSourceMapping specifies a datasource to validate dashboard queries against.
|
||||||
|
// Maps logical datasource references in the dashboard to actual datasource instances.
|
||||||
|
// +k8s:openapi-gen=true
|
||||||
|
type DashboardCompatibilityScoreDataSourceMapping struct {
|
||||||
|
// Unique identifier of the datasource instance.
|
||||||
|
// Example: "prometheus-prod-us-west"
|
||||||
|
Uid string `json:"uid"`
|
||||||
|
// Type of datasource plugin.
|
||||||
|
// MVP: Only "prometheus" supported.
|
||||||
|
// Future: "mysql", "postgres", "elasticsearch", etc.
|
||||||
|
Type string `json:"type"`
|
||||||
|
// Optional human-readable name for display in results.
|
||||||
|
// If not provided, UID will be used in error messages.
|
||||||
|
// Example: "Production Prometheus (US-West)"
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDashboardCompatibilityScoreDataSourceMapping creates a new DashboardCompatibilityScoreDataSourceMapping object.
|
||||||
|
func NewDashboardCompatibilityScoreDataSourceMapping() *DashboardCompatibilityScoreDataSourceMapping {
|
||||||
|
return &DashboardCompatibilityScoreDataSourceMapping{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:openapi-gen=true
|
||||||
|
type DashboardCompatibilityScoreSpec struct {
|
||||||
|
// Complete dashboard JSON object to validate.
|
||||||
|
// Must be a v1 dashboard schema (contains "panels" array).
|
||||||
|
// v2 dashboards (with "elements" structure) are not yet supported.
|
||||||
|
DashboardJson map[string]interface{} `json:"dashboardJson"`
|
||||||
|
// Array of datasources to validate against.
|
||||||
|
// The validator will check dashboard queries against each datasource
|
||||||
|
// and provide per-datasource compatibility results.
|
||||||
|
//
|
||||||
|
// MVP: Only single datasource supported (array length = 1), Prometheus type only.
|
||||||
|
// Future: Will support multiple datasources for dashboards with mixed queries.
|
||||||
|
DatasourceMappings []DashboardCompatibilityScoreDataSourceMapping `json:"datasourceMappings"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDashboardCompatibilityScoreSpec creates a new DashboardCompatibilityScoreSpec object.
|
||||||
|
func NewDashboardCompatibilityScoreSpec() *DashboardCompatibilityScoreSpec {
|
||||||
|
return &DashboardCompatibilityScoreSpec{
|
||||||
|
DashboardJson: map[string]interface{}{},
|
||||||
|
DatasourceMappings: []DashboardCompatibilityScoreDataSourceMapping{},
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+151
@@ -0,0 +1,151 @@
|
|||||||
|
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
// DataSourceResult contains validation results for a single datasource.
|
||||||
|
// Provides aggregate statistics and per-query breakdown of compatibility.
|
||||||
|
// +k8s:openapi-gen=true
|
||||||
|
type DashboardCompatibilityScoreDataSourceResult struct {
|
||||||
|
// Datasource UID that was validated (matches DataSourceMapping.uid)
|
||||||
|
Uid string `json:"uid"`
|
||||||
|
// Datasource type (matches DataSourceMapping.type)
|
||||||
|
Type string `json:"type"`
|
||||||
|
// Optional display name (matches DataSourceMapping.name if provided)
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
// Total number of queries in the dashboard targeting this datasource.
|
||||||
|
// Includes all panel targets/queries that reference this datasource.
|
||||||
|
TotalQueries int64 `json:"totalQueries"`
|
||||||
|
// Number of queries successfully validated.
|
||||||
|
// May be less than totalQueries if some queries couldn't be parsed.
|
||||||
|
CheckedQueries int64 `json:"checkedQueries"`
|
||||||
|
// Total number of unique metrics/identifiers referenced across all queries.
|
||||||
|
// For Prometheus: metric names extracted from PromQL expressions.
|
||||||
|
// For SQL datasources: table and column names.
|
||||||
|
TotalMetrics int64 `json:"totalMetrics"`
|
||||||
|
// Number of metrics that exist in the datasource schema.
|
||||||
|
// foundMetrics <= totalMetrics
|
||||||
|
FoundMetrics int64 `json:"foundMetrics"`
|
||||||
|
// Array of metric names that were referenced but don't exist.
|
||||||
|
// Useful for debugging why a dashboard shows "no data".
|
||||||
|
// Example for Prometheus: ["http_requests_total", "api_latency_seconds"]
|
||||||
|
MissingMetrics []string `json:"missingMetrics"`
|
||||||
|
// Per-query breakdown showing which specific queries have issues.
|
||||||
|
// One entry per query target (refId: "A", "B", "C", etc.) in each panel.
|
||||||
|
// Allows pinpointing exactly which panel/query needs fixing.
|
||||||
|
QueryBreakdown []DashboardCompatibilityScoreQueryBreakdown `json:"queryBreakdown"`
|
||||||
|
// Overall compatibility score for this datasource (0-100).
|
||||||
|
// Calculated as: (foundMetrics / totalMetrics) * 100
|
||||||
|
// Used to calculate the global compatibilityScore in status.
|
||||||
|
CompatibilityScore float64 `json:"compatibilityScore"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDashboardCompatibilityScoreDataSourceResult creates a new DashboardCompatibilityScoreDataSourceResult object.
|
||||||
|
func NewDashboardCompatibilityScoreDataSourceResult() *DashboardCompatibilityScoreDataSourceResult {
|
||||||
|
return &DashboardCompatibilityScoreDataSourceResult{
|
||||||
|
MissingMetrics: []string{},
|
||||||
|
QueryBreakdown: []DashboardCompatibilityScoreQueryBreakdown{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryBreakdown provides compatibility details for a single query within a panel.
|
||||||
|
// Granular per-query results allow users to identify exactly which queries need fixing.
|
||||||
|
//
|
||||||
|
// Note: A panel can have multiple queries (refId: "A", "B", "C", etc.),
|
||||||
|
// so there may be multiple QueryBreakdown entries for the same panelID.
|
||||||
|
// +k8s:openapi-gen=true
|
||||||
|
type DashboardCompatibilityScoreQueryBreakdown struct {
|
||||||
|
// Human-readable panel title for context.
|
||||||
|
// Example: "CPU Usage", "Request Rate"
|
||||||
|
PanelTitle string `json:"panelTitle"`
|
||||||
|
// Numeric panel ID from dashboard JSON.
|
||||||
|
// Used to correlate with dashboard structure.
|
||||||
|
PanelID int64 `json:"panelID"`
|
||||||
|
// Query identifier within the panel.
|
||||||
|
// Values: "A", "B", "C", etc. (from panel.targets[].refId)
|
||||||
|
// Uniquely identifies which query in a multi-query panel this refers to.
|
||||||
|
QueryRefId string `json:"queryRefId"`
|
||||||
|
// Number of unique metrics referenced in this specific query.
|
||||||
|
// For Prometheus: metrics extracted from the PromQL expr.
|
||||||
|
// Example: rate(http_requests_total[5m]) references 1 metric.
|
||||||
|
TotalMetrics int64 `json:"totalMetrics"`
|
||||||
|
// Number of those metrics that exist in the datasource.
|
||||||
|
// foundMetrics <= totalMetrics
|
||||||
|
FoundMetrics int64 `json:"foundMetrics"`
|
||||||
|
// Array of missing metric names specific to this query.
|
||||||
|
// Helps identify exactly which part of a query expression will fail.
|
||||||
|
// Empty array means query is fully compatible.
|
||||||
|
MissingMetrics []string `json:"missingMetrics"`
|
||||||
|
// Compatibility percentage for this individual query (0-100).
|
||||||
|
// Calculated as: (foundMetrics / totalMetrics) * 100
|
||||||
|
// 100 = query will work perfectly, 0 = query will return no data.
|
||||||
|
CompatibilityScore float64 `json:"compatibilityScore"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDashboardCompatibilityScoreQueryBreakdown creates a new DashboardCompatibilityScoreQueryBreakdown object.
|
||||||
|
func NewDashboardCompatibilityScoreQueryBreakdown() *DashboardCompatibilityScoreQueryBreakdown {
|
||||||
|
return &DashboardCompatibilityScoreQueryBreakdown{
|
||||||
|
MissingMetrics: []string{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:openapi-gen=true
|
||||||
|
type DashboardCompatibilityScorestatusOperatorState struct {
|
||||||
|
// lastEvaluation is the ResourceVersion last evaluated
|
||||||
|
LastEvaluation string `json:"lastEvaluation"`
|
||||||
|
// state describes the state of the lastEvaluation.
|
||||||
|
// It is limited to three possible states for machine evaluation.
|
||||||
|
State DashboardCompatibilityScoreStatusOperatorStateState `json:"state"`
|
||||||
|
// descriptiveState is an optional more descriptive state field which has no requirements on format
|
||||||
|
DescriptiveState *string `json:"descriptiveState,omitempty"`
|
||||||
|
// details contains any extra information that is operator-specific
|
||||||
|
Details map[string]interface{} `json:"details,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDashboardCompatibilityScorestatusOperatorState creates a new DashboardCompatibilityScorestatusOperatorState object.
|
||||||
|
func NewDashboardCompatibilityScorestatusOperatorState() *DashboardCompatibilityScorestatusOperatorState {
|
||||||
|
return &DashboardCompatibilityScorestatusOperatorState{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:openapi-gen=true
|
||||||
|
type DashboardCompatibilityScoreStatus struct {
|
||||||
|
// Overall compatibility score across all datasources (0-100).
|
||||||
|
// Calculated as: (total found metrics / total referenced metrics) * 100
|
||||||
|
//
|
||||||
|
// Score interpretation:
|
||||||
|
// - 100: Perfect compatibility, all queries will work
|
||||||
|
// - 80-99: Excellent, minor missing metrics
|
||||||
|
// - 50-79: Fair, significant missing metrics
|
||||||
|
// - 0-49: Poor, most queries will fail
|
||||||
|
CompatibilityScore float64 `json:"compatibilityScore"`
|
||||||
|
// Per-datasource validation results.
|
||||||
|
// Array length matches spec.datasourceMappings.
|
||||||
|
// Each element contains detailed metrics and query-level breakdown.
|
||||||
|
DatasourceResults []DashboardCompatibilityScoreDataSourceResult `json:"datasourceResults"`
|
||||||
|
// ISO 8601 timestamp of when validation was last performed.
|
||||||
|
// Example: "2024-01-15T10:30:00Z"
|
||||||
|
LastChecked *string `json:"lastChecked,omitempty"`
|
||||||
|
// operatorStates is a map of operator ID to operator state evaluations.
|
||||||
|
// Any operator which consumes this kind SHOULD add its state evaluation information to this field.
|
||||||
|
OperatorStates map[string]DashboardCompatibilityScorestatusOperatorState `json:"operatorStates,omitempty"`
|
||||||
|
// Human-readable summary of validation result.
|
||||||
|
// Examples: "All queries compatible", "3 missing metrics found"
|
||||||
|
Message *string `json:"message,omitempty"`
|
||||||
|
// additionalFields is reserved for future use
|
||||||
|
AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDashboardCompatibilityScoreStatus creates a new DashboardCompatibilityScoreStatus object.
|
||||||
|
func NewDashboardCompatibilityScoreStatus() *DashboardCompatibilityScoreStatus {
|
||||||
|
return &DashboardCompatibilityScoreStatus{
|
||||||
|
DatasourceResults: []DashboardCompatibilityScoreDataSourceResult{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:openapi-gen=true
|
||||||
|
type DashboardCompatibilityScoreStatusOperatorStateState string
|
||||||
|
|
||||||
|
const (
|
||||||
|
DashboardCompatibilityScoreStatusOperatorStateStateSuccess DashboardCompatibilityScoreStatusOperatorStateState = "success"
|
||||||
|
DashboardCompatibilityScoreStatusOperatorStateStateInProgress DashboardCompatibilityScoreStatusOperatorStateState = "in_progress"
|
||||||
|
DashboardCompatibilityScoreStatusOperatorStateStateFailed DashboardCompatibilityScoreStatusOperatorStateState = "failed"
|
||||||
|
)
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,360 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana-app-sdk/app"
|
||||||
|
"github.com/grafana/grafana-app-sdk/logging"
|
||||||
|
"github.com/grafana/grafana-app-sdk/resource"
|
||||||
|
"github.com/grafana/grafana-app-sdk/simple"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
|
||||||
|
validatorv1alpha1 "github.com/grafana/grafana/apps/dashvalidator/pkg/apis/dashvalidator/v1alpha1"
|
||||||
|
"github.com/grafana/grafana/apps/dashvalidator/pkg/validator"
|
||||||
|
_ "github.com/grafana/grafana/apps/dashvalidator/pkg/validator/prometheus" // Register prometheus validator via init()
|
||||||
|
"github.com/grafana/grafana/pkg/infra/httpclient"
|
||||||
|
"github.com/grafana/grafana/pkg/services/datasources"
|
||||||
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DashValidatorConfig struct {
|
||||||
|
DatasourceSvc datasources.DataSourceService
|
||||||
|
PluginCtx *plugincontext.Provider
|
||||||
|
HTTPClientProvider httpclient.Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkRequest matches the CUE schema for POST /check request
|
||||||
|
type checkRequest struct {
|
||||||
|
DashboardJSON map[string]interface{} `json:"dashboardJson"`
|
||||||
|
DatasourceMappings []datasourceMapping `json:"datasourceMappings"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// datasourceMapping represents a datasource to validate against
|
||||||
|
type datasourceMapping struct {
|
||||||
|
UID string `json:"uid"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkResponse matches the CUE schema for POST /check response
|
||||||
|
type checkResponse struct {
|
||||||
|
CompatibilityScore float64 `json:"compatibilityScore"`
|
||||||
|
DatasourceResults []datasourceResult `json:"datasourceResults"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// datasourceResult contains validation results for a single datasource
|
||||||
|
type datasourceResult struct {
|
||||||
|
UID string `json:"uid"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
TotalQueries int `json:"totalQueries"`
|
||||||
|
CheckedQueries int `json:"checkedQueries"`
|
||||||
|
TotalMetrics int `json:"totalMetrics"`
|
||||||
|
FoundMetrics int `json:"foundMetrics"`
|
||||||
|
MissingMetrics []string `json:"missingMetrics"`
|
||||||
|
QueryBreakdown []queryResult `json:"queryBreakdown"`
|
||||||
|
CompatibilityScore float64 `json:"compatibilityScore"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// queryResult contains validation results for a single query
|
||||||
|
type queryResult struct {
|
||||||
|
PanelTitle string `json:"panelTitle"`
|
||||||
|
PanelID int `json:"panelID"`
|
||||||
|
QueryRefID string `json:"queryRefId"`
|
||||||
|
TotalMetrics int `json:"totalMetrics"`
|
||||||
|
FoundMetrics int `json:"foundMetrics"`
|
||||||
|
MissingMetrics []string `json:"missingMetrics"`
|
||||||
|
CompatibilityScore float64 `json:"compatibilityScore"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg app.Config) (app.App, error) {
|
||||||
|
specificConfig, ok := cfg.SpecificConfig.(*DashValidatorConfig)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid config type: expected DashValidatorConfig")
|
||||||
|
}
|
||||||
|
|
||||||
|
log := logging.DefaultLogger.With("app", "dashvalidator")
|
||||||
|
|
||||||
|
// configure our app
|
||||||
|
simpleConfig := simple.AppConfig{
|
||||||
|
Name: "dashvalidator",
|
||||||
|
KubeConfig: cfg.KubeConfig,
|
||||||
|
|
||||||
|
//Define our custom route
|
||||||
|
VersionedCustomRoutes: map[string]simple.AppVersionRouteHandlers{
|
||||||
|
"v1alpha1": {
|
||||||
|
{
|
||||||
|
Namespaced: true,
|
||||||
|
Path: "check",
|
||||||
|
Method: "POST",
|
||||||
|
}: handleCheckRoute(log, specificConfig.DatasourceSvc, specificConfig.PluginCtx, specificConfig.HTTPClientProvider),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
a, err := simple.NewApp(simpleConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create app: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// custom route handler to check dashboard compatibility
|
||||||
|
func handleCheckRoute(
|
||||||
|
log logging.Logger,
|
||||||
|
datasourceSvc datasources.DataSourceService,
|
||||||
|
pluginCtx *plugincontext.Provider,
|
||||||
|
httpClientProvider httpclient.Provider,
|
||||||
|
) func(context.Context, app.CustomRouteResponseWriter, *app.CustomRouteRequest) error {
|
||||||
|
return func(ctx context.Context, w app.CustomRouteResponseWriter, r *app.CustomRouteRequest) error {
|
||||||
|
logger := log.WithContext(ctx)
|
||||||
|
logger.Info("Received compatibility check request")
|
||||||
|
|
||||||
|
// Step 1: Parse request body
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to read request body", "error", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return json.NewEncoder(w).Encode(map[string]string{
|
||||||
|
"error": "failed to read request body",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var req checkRequest
|
||||||
|
if err := json.Unmarshal(body, &req); err != nil {
|
||||||
|
logger.Error("Failed to parse request JSON", "error", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return json.NewEncoder(w).Encode(map[string]string{
|
||||||
|
"error": "invalid JSON in request body",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// MVP: Only support single datasource validation
|
||||||
|
if len(req.DatasourceMappings) != 1 {
|
||||||
|
logger.Error("MVP only supports single datasource validation", "numDatasources", len(req.DatasourceMappings))
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return json.NewEncoder(w).Encode(map[string]string{
|
||||||
|
"error": fmt.Sprintf("MVP only supports single datasource validation, got %d datasources", len(req.DatasourceMappings)),
|
||||||
|
"code": "invalid_request",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Build validator request
|
||||||
|
validatorReq := validator.DashboardCompatibilityRequest{
|
||||||
|
DashboardJSON: req.DashboardJSON,
|
||||||
|
DatasourceMappings: make([]validator.DatasourceMapping, 0, len(req.DatasourceMappings)),
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("Processing request", "dashboardTitle", req.DashboardJSON["title"], "numMappings", len(req.DatasourceMappings))
|
||||||
|
|
||||||
|
// Get namespace from request (needed for datasource lookup)
|
||||||
|
// Namespace format is typically "org-{orgID}"
|
||||||
|
namespace := r.ResourceIdentifier.Namespace
|
||||||
|
|
||||||
|
// Extract orgID from namespace for logging context
|
||||||
|
orgID := extractOrgIDFromNamespace(namespace)
|
||||||
|
logger = logger.With("orgID", orgID, "namespace", namespace)
|
||||||
|
|
||||||
|
for _, dsMapping := range req.DatasourceMappings {
|
||||||
|
dsLogger := logger.With("datasourceUID", dsMapping.UID, "datasourceType", dsMapping.Type)
|
||||||
|
|
||||||
|
// Convert optional name pointer to string
|
||||||
|
name := ""
|
||||||
|
if dsMapping.Name != nil {
|
||||||
|
name = *dsMapping.Name
|
||||||
|
dsLogger = dsLogger.With("datasourceName", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch datasource from Grafana using app-platform method
|
||||||
|
// Parameters: namespace, name (UID), group (datasource type)
|
||||||
|
ds, err := datasourceSvc.GetDataSourceInNamespace(ctx, namespace, dsMapping.UID, dsMapping.Type)
|
||||||
|
if err != nil {
|
||||||
|
dsLogger.Error("Failed to get datasource from namespace", "error", err)
|
||||||
|
|
||||||
|
// Check if it's a not found error vs other errors
|
||||||
|
errMsg := err.Error()
|
||||||
|
statusCode := http.StatusInternalServerError
|
||||||
|
userMsg := fmt.Sprintf("failed to retrieve datasource: %s", dsMapping.UID)
|
||||||
|
|
||||||
|
if strings.Contains(errMsg, "not found") || strings.Contains(errMsg, "does not exist") {
|
||||||
|
statusCode = http.StatusNotFound
|
||||||
|
userMsg = fmt.Sprintf("datasource not found: %s (type: %s)", dsMapping.UID, dsMapping.Type)
|
||||||
|
dsLogger.Warn("Datasource not found in namespace")
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(statusCode)
|
||||||
|
return json.NewEncoder(w).Encode(map[string]string{
|
||||||
|
"error": userMsg,
|
||||||
|
"code": "datasource_error",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
dsLogger.Info("Retrieved datasource", "url", ds.URL, "actualType", ds.Type)
|
||||||
|
|
||||||
|
// Validate that the datasource type matches the expected type
|
||||||
|
if ds.Type != dsMapping.Type {
|
||||||
|
dsLogger.Error("Datasource type mismatch",
|
||||||
|
"expectedType", dsMapping.Type,
|
||||||
|
"actualType", ds.Type)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return json.NewEncoder(w).Encode(map[string]string{
|
||||||
|
"error": fmt.Sprintf("datasource %s has type %s, expected %s", dsMapping.UID, ds.Type, dsMapping.Type),
|
||||||
|
"code": "datasource_wrong_type",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that this is a supported datasource type
|
||||||
|
// For MVP, we only support Prometheus
|
||||||
|
if !isSupportedDatasourceType(ds.Type) {
|
||||||
|
dsLogger.Error("Unsupported datasource type", "type", ds.Type)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return json.NewEncoder(w).Encode(map[string]string{
|
||||||
|
"error": fmt.Sprintf("datasource type '%s' is not supported (currently only 'prometheus' is supported)", ds.Type),
|
||||||
|
"code": "datasource_unsupported_type",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get authenticated HTTP transport for this datasource
|
||||||
|
transport, err := datasourceSvc.GetHTTPTransport(ctx, ds, httpClientProvider)
|
||||||
|
if err != nil {
|
||||||
|
dsLogger.Error("Failed to get HTTP transport for datasource", "error", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return json.NewEncoder(w).Encode(map[string]string{
|
||||||
|
"error": fmt.Sprintf("failed to configure authentication for datasource: %s", dsMapping.UID),
|
||||||
|
"code": "datasource_config_error",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create HTTP client with authenticated transport
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
}
|
||||||
|
|
||||||
|
validatorReq.DatasourceMappings = append(validatorReq.DatasourceMappings, validator.DatasourceMapping{
|
||||||
|
UID: dsMapping.UID,
|
||||||
|
Type: dsMapping.Type,
|
||||||
|
Name: name,
|
||||||
|
URL: ds.URL,
|
||||||
|
HTTPClient: httpClient, // Pass authenticated client
|
||||||
|
})
|
||||||
|
|
||||||
|
dsLogger.Debug("Datasource configured successfully for validation")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Validate dashboard compatibility
|
||||||
|
result, err := validator.ValidateDashboardCompatibility(ctx, validatorReq)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Validation failed", "error", err)
|
||||||
|
|
||||||
|
// Check if it's a structured ValidationError with a specific status code
|
||||||
|
statusCode := http.StatusInternalServerError
|
||||||
|
errorCode := "validation_error"
|
||||||
|
errorMsg := fmt.Sprintf("validation failed: %v", err)
|
||||||
|
|
||||||
|
if validationErr := validator.GetValidationError(err); validationErr != nil {
|
||||||
|
statusCode = validationErr.StatusCode
|
||||||
|
errorCode = string(validationErr.Code)
|
||||||
|
errorMsg = validationErr.Message
|
||||||
|
|
||||||
|
// Log additional context from the error
|
||||||
|
for key, value := range validationErr.Details {
|
||||||
|
logger.Error("Validation error detail", key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(statusCode)
|
||||||
|
return json.NewEncoder(w).Encode(map[string]string{
|
||||||
|
"error": errorMsg,
|
||||||
|
"code": errorCode,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: Convert result to response format
|
||||||
|
response := convertToCheckResponse(result)
|
||||||
|
|
||||||
|
// Step 5: Return response
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
return json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertToCheckResponse converts validator result to API response format
|
||||||
|
func convertToCheckResponse(result *validator.DashboardCompatibilityResult) checkResponse {
|
||||||
|
response := checkResponse{
|
||||||
|
CompatibilityScore: result.CompatibilityScore,
|
||||||
|
DatasourceResults: make([]datasourceResult, 0, len(result.DatasourceResults)),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dsResult := range result.DatasourceResults {
|
||||||
|
// Convert name string to pointer
|
||||||
|
var name *string
|
||||||
|
if dsResult.Name != "" {
|
||||||
|
name = &dsResult.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert query results
|
||||||
|
queryBreakdown := make([]queryResult, 0, len(dsResult.QueryBreakdown))
|
||||||
|
for _, qr := range dsResult.QueryBreakdown {
|
||||||
|
queryBreakdown = append(queryBreakdown, queryResult{
|
||||||
|
PanelTitle: qr.PanelTitle,
|
||||||
|
PanelID: qr.PanelID,
|
||||||
|
QueryRefID: qr.QueryRefID,
|
||||||
|
TotalMetrics: qr.TotalMetrics,
|
||||||
|
FoundMetrics: qr.FoundMetrics,
|
||||||
|
MissingMetrics: qr.MissingMetrics,
|
||||||
|
CompatibilityScore: qr.CompatibilityScore,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
response.DatasourceResults = append(response.DatasourceResults, datasourceResult{
|
||||||
|
UID: dsResult.UID,
|
||||||
|
Type: dsResult.Type,
|
||||||
|
Name: name,
|
||||||
|
TotalQueries: dsResult.TotalQueries,
|
||||||
|
CheckedQueries: dsResult.CheckedQueries,
|
||||||
|
TotalMetrics: dsResult.TotalMetrics,
|
||||||
|
FoundMetrics: dsResult.FoundMetrics,
|
||||||
|
MissingMetrics: dsResult.MissingMetrics,
|
||||||
|
QueryBreakdown: queryBreakdown,
|
||||||
|
CompatibilityScore: dsResult.CompatibilityScore,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractOrgIDFromNamespace extracts the org ID from a namespace string
|
||||||
|
// Namespace format is typically "org-{orgID}"
|
||||||
|
func extractOrgIDFromNamespace(namespace string) string {
|
||||||
|
parts := strings.Split(namespace, "-")
|
||||||
|
if len(parts) >= 2 && parts[0] == "org" {
|
||||||
|
return parts[1]
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSupportedDatasourceType checks if a datasource type is supported
|
||||||
|
// For MVP, we only support Prometheus
|
||||||
|
func isSupportedDatasourceType(dsType string) bool {
|
||||||
|
supportedTypes := map[string]bool{
|
||||||
|
"prometheus": true,
|
||||||
|
}
|
||||||
|
return supportedTypes[strings.ToLower(dsType)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetKinds() map[schema.GroupVersion][]resource.Kind {
|
||||||
|
gv := schema.GroupVersion{
|
||||||
|
Group: "dashvalidator.grafana.com",
|
||||||
|
Version: "v1alpha1",
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[schema.GroupVersion][]resource.Kind{
|
||||||
|
gv: {validatorv1alpha1.DashboardCompatibilityScoreKind()},
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -4,7 +4,7 @@ import "k8s.io/apimachinery/pkg/runtime/schema"
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// APIGroup is the API group used by all kinds in this package
|
// APIGroup is the API group used by all kinds in this package
|
||||||
APIGroup = "logsdrilldown.grafana.app"
|
APIGroup = "dashvalidator.ext.grafana.com"
|
||||||
// APIVersion is the API version used by all kinds in this package
|
// APIVersion is the API version used by all kinds in this package
|
||||||
APIVersion = "v1alpha1"
|
APIVersion = "v1alpha1"
|
||||||
)
|
)
|
||||||
+16
-16
@@ -7,33 +7,33 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LogsDrilldownDefaultColumnsClient struct {
|
type DashboardCompatibilityScoreClient struct {
|
||||||
client *resource.TypedClient[*LogsDrilldownDefaultColumns, *LogsDrilldownDefaultColumnsList]
|
client *resource.TypedClient[*DashboardCompatibilityScore, *DashboardCompatibilityScoreList]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLogsDrilldownDefaultColumnsClient(client resource.Client) *LogsDrilldownDefaultColumnsClient {
|
func NewDashboardCompatibilityScoreClient(client resource.Client) *DashboardCompatibilityScoreClient {
|
||||||
return &LogsDrilldownDefaultColumnsClient{
|
return &DashboardCompatibilityScoreClient{
|
||||||
client: resource.NewTypedClient[*LogsDrilldownDefaultColumns, *LogsDrilldownDefaultColumnsList](client, Kind()),
|
client: resource.NewTypedClient[*DashboardCompatibilityScore, *DashboardCompatibilityScoreList](client, Kind()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLogsDrilldownDefaultColumnsClientFromGenerator(generator resource.ClientGenerator) (*LogsDrilldownDefaultColumnsClient, error) {
|
func NewDashboardCompatibilityScoreClientFromGenerator(generator resource.ClientGenerator) (*DashboardCompatibilityScoreClient, error) {
|
||||||
c, err := generator.ClientFor(Kind())
|
c, err := generator.ClientFor(Kind())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return NewLogsDrilldownDefaultColumnsClient(c), nil
|
return NewDashboardCompatibilityScoreClient(c), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *LogsDrilldownDefaultColumnsClient) Get(ctx context.Context, identifier resource.Identifier) (*LogsDrilldownDefaultColumns, error) {
|
func (c *DashboardCompatibilityScoreClient) Get(ctx context.Context, identifier resource.Identifier) (*DashboardCompatibilityScore, error) {
|
||||||
return c.client.Get(ctx, identifier)
|
return c.client.Get(ctx, identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *LogsDrilldownDefaultColumnsClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*LogsDrilldownDefaultColumnsList, error) {
|
func (c *DashboardCompatibilityScoreClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*DashboardCompatibilityScoreList, error) {
|
||||||
return c.client.List(ctx, namespace, opts)
|
return c.client.List(ctx, namespace, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *LogsDrilldownDefaultColumnsClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*LogsDrilldownDefaultColumnsList, error) {
|
func (c *DashboardCompatibilityScoreClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*DashboardCompatibilityScoreList, error) {
|
||||||
resp, err := c.client.List(ctx, namespace, resource.ListOptions{
|
resp, err := c.client.List(ctx, namespace, resource.ListOptions{
|
||||||
ResourceVersion: opts.ResourceVersion,
|
ResourceVersion: opts.ResourceVersion,
|
||||||
Limit: opts.Limit,
|
Limit: opts.Limit,
|
||||||
@@ -61,23 +61,23 @@ func (c *LogsDrilldownDefaultColumnsClient) ListAll(ctx context.Context, namespa
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *LogsDrilldownDefaultColumnsClient) Create(ctx context.Context, obj *LogsDrilldownDefaultColumns, opts resource.CreateOptions) (*LogsDrilldownDefaultColumns, error) {
|
func (c *DashboardCompatibilityScoreClient) Create(ctx context.Context, obj *DashboardCompatibilityScore, opts resource.CreateOptions) (*DashboardCompatibilityScore, error) {
|
||||||
// Make sure apiVersion and kind are set
|
// Make sure apiVersion and kind are set
|
||||||
obj.APIVersion = GroupVersion.Identifier()
|
obj.APIVersion = GroupVersion.Identifier()
|
||||||
obj.Kind = Kind().Kind()
|
obj.Kind = Kind().Kind()
|
||||||
return c.client.Create(ctx, obj, opts)
|
return c.client.Create(ctx, obj, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *LogsDrilldownDefaultColumnsClient) Update(ctx context.Context, obj *LogsDrilldownDefaultColumns, opts resource.UpdateOptions) (*LogsDrilldownDefaultColumns, error) {
|
func (c *DashboardCompatibilityScoreClient) Update(ctx context.Context, obj *DashboardCompatibilityScore, opts resource.UpdateOptions) (*DashboardCompatibilityScore, error) {
|
||||||
return c.client.Update(ctx, obj, opts)
|
return c.client.Update(ctx, obj, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *LogsDrilldownDefaultColumnsClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*LogsDrilldownDefaultColumns, error) {
|
func (c *DashboardCompatibilityScoreClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*DashboardCompatibilityScore, error) {
|
||||||
return c.client.Patch(ctx, identifier, req, opts)
|
return c.client.Patch(ctx, identifier, req, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *LogsDrilldownDefaultColumnsClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus Status, opts resource.UpdateOptions) (*LogsDrilldownDefaultColumns, error) {
|
func (c *DashboardCompatibilityScoreClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus Status, opts resource.UpdateOptions) (*DashboardCompatibilityScore, error) {
|
||||||
return c.client.Update(ctx, &LogsDrilldownDefaultColumns{
|
return c.client.Update(ctx, &DashboardCompatibilityScore{
|
||||||
TypeMeta: metav1.TypeMeta{
|
TypeMeta: metav1.TypeMeta{
|
||||||
Kind: Kind().Kind(),
|
Kind: Kind().Kind(),
|
||||||
APIVersion: GroupVersion.Identifier(),
|
APIVersion: GroupVersion.Identifier(),
|
||||||
@@ -94,6 +94,6 @@ func (c *LogsDrilldownDefaultColumnsClient) UpdateStatus(ctx context.Context, id
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *LogsDrilldownDefaultColumnsClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
|
func (c *DashboardCompatibilityScoreClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
|
||||||
return c.client.Delete(ctx, identifier, opts)
|
return c.client.Delete(ctx, identifier, opts)
|
||||||
}
|
}
|
||||||
+45
-38
@@ -15,21 +15,28 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
// +k8s:openapi-gen=true
|
||||||
type LogsDrilldownDefaultColumns struct {
|
type DashboardCompatibilityScore struct {
|
||||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||||
metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
|
metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
|
||||||
|
|
||||||
// Spec is the spec of the LogsDrilldownDefaultColumns
|
// Spec is the spec of the DashboardCompatibilityScore
|
||||||
Spec Spec `json:"spec" yaml:"spec"`
|
Spec Spec `json:"spec" yaml:"spec"`
|
||||||
|
|
||||||
Status Status `json:"status" yaml:"status"`
|
Status Status `json:"status" yaml:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumns) GetSpec() any {
|
func NewDashboardCompatibilityScore() *DashboardCompatibilityScore {
|
||||||
|
return &DashboardCompatibilityScore{
|
||||||
|
Spec: *NewSpec(),
|
||||||
|
Status: *NewStatus(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *DashboardCompatibilityScore) GetSpec() any {
|
||||||
return o.Spec
|
return o.Spec
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumns) SetSpec(spec any) error {
|
func (o *DashboardCompatibilityScore) SetSpec(spec any) error {
|
||||||
cast, ok := spec.(Spec)
|
cast, ok := spec.(Spec)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec)
|
return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec)
|
||||||
@@ -38,13 +45,13 @@ func (o *LogsDrilldownDefaultColumns) SetSpec(spec any) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumns) GetSubresources() map[string]any {
|
func (o *DashboardCompatibilityScore) GetSubresources() map[string]any {
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
"status": o.Status,
|
"status": o.Status,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumns) GetSubresource(name string) (any, bool) {
|
func (o *DashboardCompatibilityScore) GetSubresource(name string) (any, bool) {
|
||||||
switch name {
|
switch name {
|
||||||
case "status":
|
case "status":
|
||||||
return o.Status, true
|
return o.Status, true
|
||||||
@@ -53,7 +60,7 @@ func (o *LogsDrilldownDefaultColumns) GetSubresource(name string) (any, bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumns) SetSubresource(name string, value any) error {
|
func (o *DashboardCompatibilityScore) SetSubresource(name string, value any) error {
|
||||||
switch name {
|
switch name {
|
||||||
case "status":
|
case "status":
|
||||||
cast, ok := value.(Status)
|
cast, ok := value.(Status)
|
||||||
@@ -67,7 +74,7 @@ func (o *LogsDrilldownDefaultColumns) SetSubresource(name string, value any) err
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumns) GetStaticMetadata() resource.StaticMetadata {
|
func (o *DashboardCompatibilityScore) GetStaticMetadata() resource.StaticMetadata {
|
||||||
gvk := o.GroupVersionKind()
|
gvk := o.GroupVersionKind()
|
||||||
return resource.StaticMetadata{
|
return resource.StaticMetadata{
|
||||||
Name: o.ObjectMeta.Name,
|
Name: o.ObjectMeta.Name,
|
||||||
@@ -78,7 +85,7 @@ func (o *LogsDrilldownDefaultColumns) GetStaticMetadata() resource.StaticMetadat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumns) SetStaticMetadata(metadata resource.StaticMetadata) {
|
func (o *DashboardCompatibilityScore) SetStaticMetadata(metadata resource.StaticMetadata) {
|
||||||
o.Name = metadata.Name
|
o.Name = metadata.Name
|
||||||
o.Namespace = metadata.Namespace
|
o.Namespace = metadata.Namespace
|
||||||
o.SetGroupVersionKind(schema.GroupVersionKind{
|
o.SetGroupVersionKind(schema.GroupVersionKind{
|
||||||
@@ -88,7 +95,7 @@ func (o *LogsDrilldownDefaultColumns) SetStaticMetadata(metadata resource.Static
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumns) GetCommonMetadata() resource.CommonMetadata {
|
func (o *DashboardCompatibilityScore) GetCommonMetadata() resource.CommonMetadata {
|
||||||
dt := o.DeletionTimestamp
|
dt := o.DeletionTimestamp
|
||||||
var deletionTimestamp *time.Time
|
var deletionTimestamp *time.Time
|
||||||
if dt != nil {
|
if dt != nil {
|
||||||
@@ -120,7 +127,7 @@ func (o *LogsDrilldownDefaultColumns) GetCommonMetadata() resource.CommonMetadat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumns) SetCommonMetadata(metadata resource.CommonMetadata) {
|
func (o *DashboardCompatibilityScore) SetCommonMetadata(metadata resource.CommonMetadata) {
|
||||||
o.UID = types.UID(metadata.UID)
|
o.UID = types.UID(metadata.UID)
|
||||||
o.ResourceVersion = metadata.ResourceVersion
|
o.ResourceVersion = metadata.ResourceVersion
|
||||||
o.Generation = metadata.Generation
|
o.Generation = metadata.Generation
|
||||||
@@ -165,7 +172,7 @@ func (o *LogsDrilldownDefaultColumns) SetCommonMetadata(metadata resource.Common
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumns) GetCreatedBy() string {
|
func (o *DashboardCompatibilityScore) GetCreatedBy() string {
|
||||||
if o.ObjectMeta.Annotations == nil {
|
if o.ObjectMeta.Annotations == nil {
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
o.ObjectMeta.Annotations = make(map[string]string)
|
||||||
}
|
}
|
||||||
@@ -173,7 +180,7 @@ func (o *LogsDrilldownDefaultColumns) GetCreatedBy() string {
|
|||||||
return o.ObjectMeta.Annotations["grafana.com/createdBy"]
|
return o.ObjectMeta.Annotations["grafana.com/createdBy"]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumns) SetCreatedBy(createdBy string) {
|
func (o *DashboardCompatibilityScore) SetCreatedBy(createdBy string) {
|
||||||
if o.ObjectMeta.Annotations == nil {
|
if o.ObjectMeta.Annotations == nil {
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
o.ObjectMeta.Annotations = make(map[string]string)
|
||||||
}
|
}
|
||||||
@@ -181,7 +188,7 @@ func (o *LogsDrilldownDefaultColumns) SetCreatedBy(createdBy string) {
|
|||||||
o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy
|
o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumns) GetUpdateTimestamp() time.Time {
|
func (o *DashboardCompatibilityScore) GetUpdateTimestamp() time.Time {
|
||||||
if o.ObjectMeta.Annotations == nil {
|
if o.ObjectMeta.Annotations == nil {
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
o.ObjectMeta.Annotations = make(map[string]string)
|
||||||
}
|
}
|
||||||
@@ -190,7 +197,7 @@ func (o *LogsDrilldownDefaultColumns) GetUpdateTimestamp() time.Time {
|
|||||||
return parsed
|
return parsed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumns) SetUpdateTimestamp(updateTimestamp time.Time) {
|
func (o *DashboardCompatibilityScore) SetUpdateTimestamp(updateTimestamp time.Time) {
|
||||||
if o.ObjectMeta.Annotations == nil {
|
if o.ObjectMeta.Annotations == nil {
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
o.ObjectMeta.Annotations = make(map[string]string)
|
||||||
}
|
}
|
||||||
@@ -198,7 +205,7 @@ func (o *LogsDrilldownDefaultColumns) SetUpdateTimestamp(updateTimestamp time.Ti
|
|||||||
o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339)
|
o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumns) GetUpdatedBy() string {
|
func (o *DashboardCompatibilityScore) GetUpdatedBy() string {
|
||||||
if o.ObjectMeta.Annotations == nil {
|
if o.ObjectMeta.Annotations == nil {
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
o.ObjectMeta.Annotations = make(map[string]string)
|
||||||
}
|
}
|
||||||
@@ -206,7 +213,7 @@ func (o *LogsDrilldownDefaultColumns) GetUpdatedBy() string {
|
|||||||
return o.ObjectMeta.Annotations["grafana.com/updatedBy"]
|
return o.ObjectMeta.Annotations["grafana.com/updatedBy"]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumns) SetUpdatedBy(updatedBy string) {
|
func (o *DashboardCompatibilityScore) SetUpdatedBy(updatedBy string) {
|
||||||
if o.ObjectMeta.Annotations == nil {
|
if o.ObjectMeta.Annotations == nil {
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
o.ObjectMeta.Annotations = make(map[string]string)
|
||||||
}
|
}
|
||||||
@@ -214,21 +221,21 @@ func (o *LogsDrilldownDefaultColumns) SetUpdatedBy(updatedBy string) {
|
|||||||
o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy
|
o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumns) Copy() resource.Object {
|
func (o *DashboardCompatibilityScore) Copy() resource.Object {
|
||||||
return resource.CopyObject(o)
|
return resource.CopyObject(o)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumns) DeepCopyObject() runtime.Object {
|
func (o *DashboardCompatibilityScore) DeepCopyObject() runtime.Object {
|
||||||
return o.Copy()
|
return o.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumns) DeepCopy() *LogsDrilldownDefaultColumns {
|
func (o *DashboardCompatibilityScore) DeepCopy() *DashboardCompatibilityScore {
|
||||||
cpy := &LogsDrilldownDefaultColumns{}
|
cpy := &DashboardCompatibilityScore{}
|
||||||
o.DeepCopyInto(cpy)
|
o.DeepCopyInto(cpy)
|
||||||
return cpy
|
return cpy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumns) DeepCopyInto(dst *LogsDrilldownDefaultColumns) {
|
func (o *DashboardCompatibilityScore) DeepCopyInto(dst *DashboardCompatibilityScore) {
|
||||||
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
|
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
|
||||||
dst.TypeMeta.Kind = o.TypeMeta.Kind
|
dst.TypeMeta.Kind = o.TypeMeta.Kind
|
||||||
o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta)
|
o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta)
|
||||||
@@ -237,34 +244,34 @@ func (o *LogsDrilldownDefaultColumns) DeepCopyInto(dst *LogsDrilldownDefaultColu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Interface compliance compile-time check
|
// Interface compliance compile-time check
|
||||||
var _ resource.Object = &LogsDrilldownDefaultColumns{}
|
var _ resource.Object = &DashboardCompatibilityScore{}
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
// +k8s:openapi-gen=true
|
||||||
type LogsDrilldownDefaultColumnsList struct {
|
type DashboardCompatibilityScoreList struct {
|
||||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||||
metav1.ListMeta `json:"metadata" yaml:"metadata"`
|
metav1.ListMeta `json:"metadata" yaml:"metadata"`
|
||||||
Items []LogsDrilldownDefaultColumns `json:"items" yaml:"items"`
|
Items []DashboardCompatibilityScore `json:"items" yaml:"items"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumnsList) DeepCopyObject() runtime.Object {
|
func (o *DashboardCompatibilityScoreList) DeepCopyObject() runtime.Object {
|
||||||
return o.Copy()
|
return o.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumnsList) Copy() resource.ListObject {
|
func (o *DashboardCompatibilityScoreList) Copy() resource.ListObject {
|
||||||
cpy := &LogsDrilldownDefaultColumnsList{
|
cpy := &DashboardCompatibilityScoreList{
|
||||||
TypeMeta: o.TypeMeta,
|
TypeMeta: o.TypeMeta,
|
||||||
Items: make([]LogsDrilldownDefaultColumns, len(o.Items)),
|
Items: make([]DashboardCompatibilityScore, len(o.Items)),
|
||||||
}
|
}
|
||||||
o.ListMeta.DeepCopyInto(&cpy.ListMeta)
|
o.ListMeta.DeepCopyInto(&cpy.ListMeta)
|
||||||
for i := 0; i < len(o.Items); i++ {
|
for i := 0; i < len(o.Items); i++ {
|
||||||
if item, ok := o.Items[i].Copy().(*LogsDrilldownDefaultColumns); ok {
|
if item, ok := o.Items[i].Copy().(*DashboardCompatibilityScore); ok {
|
||||||
cpy.Items[i] = *item
|
cpy.Items[i] = *item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cpy
|
return cpy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumnsList) GetItems() []resource.Object {
|
func (o *DashboardCompatibilityScoreList) GetItems() []resource.Object {
|
||||||
items := make([]resource.Object, len(o.Items))
|
items := make([]resource.Object, len(o.Items))
|
||||||
for i := 0; i < len(o.Items); i++ {
|
for i := 0; i < len(o.Items); i++ {
|
||||||
items[i] = &o.Items[i]
|
items[i] = &o.Items[i]
|
||||||
@@ -272,25 +279,25 @@ func (o *LogsDrilldownDefaultColumnsList) GetItems() []resource.Object {
|
|||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumnsList) SetItems(items []resource.Object) {
|
func (o *DashboardCompatibilityScoreList) SetItems(items []resource.Object) {
|
||||||
o.Items = make([]LogsDrilldownDefaultColumns, len(items))
|
o.Items = make([]DashboardCompatibilityScore, len(items))
|
||||||
for i := 0; i < len(items); i++ {
|
for i := 0; i < len(items); i++ {
|
||||||
o.Items[i] = *items[i].(*LogsDrilldownDefaultColumns)
|
o.Items[i] = *items[i].(*DashboardCompatibilityScore)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumnsList) DeepCopy() *LogsDrilldownDefaultColumnsList {
|
func (o *DashboardCompatibilityScoreList) DeepCopy() *DashboardCompatibilityScoreList {
|
||||||
cpy := &LogsDrilldownDefaultColumnsList{}
|
cpy := &DashboardCompatibilityScoreList{}
|
||||||
o.DeepCopyInto(cpy)
|
o.DeepCopyInto(cpy)
|
||||||
return cpy
|
return cpy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LogsDrilldownDefaultColumnsList) DeepCopyInto(dst *LogsDrilldownDefaultColumnsList) {
|
func (o *DashboardCompatibilityScoreList) DeepCopyInto(dst *DashboardCompatibilityScoreList) {
|
||||||
resource.CopyObjectInto(dst, o)
|
resource.CopyObjectInto(dst, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interface compliance compile-time check
|
// Interface compliance compile-time check
|
||||||
var _ resource.ListObject = &LogsDrilldownDefaultColumnsList{}
|
var _ resource.ListObject = &DashboardCompatibilityScoreList{}
|
||||||
|
|
||||||
// Copy methods for all subresource types
|
// Copy methods for all subresource types
|
||||||
|
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
//
|
||||||
|
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
||||||
|
//
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/grafana/grafana-app-sdk/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
// schema is unexported to prevent accidental overwrites
|
||||||
|
var (
|
||||||
|
schemaDashboardCompatibilityScore = resource.NewSimpleSchema("dashvalidator.ext.grafana.com", "v1alpha1", NewDashboardCompatibilityScore(), &DashboardCompatibilityScoreList{}, resource.WithKind("DashboardCompatibilityScore"),
|
||||||
|
resource.WithPlural("dashboardcompatibilityscores"), resource.WithScope(resource.NamespacedScope))
|
||||||
|
kindDashboardCompatibilityScore = resource.Kind{
|
||||||
|
Schema: schemaDashboardCompatibilityScore,
|
||||||
|
Codecs: map[resource.KindEncoding]resource.Codec{
|
||||||
|
resource.KindEncodingJSON: &JSONCodec{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Kind returns a resource.Kind for this Schema with a JSON codec
|
||||||
|
func Kind() resource.Kind {
|
||||||
|
return kindDashboardCompatibilityScore
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schema returns a resource.SimpleSchema representation of DashboardCompatibilityScore
|
||||||
|
func Schema() *resource.SimpleSchema {
|
||||||
|
return schemaDashboardCompatibilityScore
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface compliance checks
|
||||||
|
var _ resource.Schema = kindDashboardCompatibilityScore
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
// DataSourceMapping specifies a datasource to validate dashboard queries against.
|
||||||
|
// Maps logical datasource references in the dashboard to actual datasource instances.
|
||||||
|
// +k8s:openapi-gen=true
|
||||||
|
type DataSourceMapping struct {
|
||||||
|
// Unique identifier of the datasource instance.
|
||||||
|
// Example: "prometheus-prod-us-west"
|
||||||
|
Uid string `json:"uid"`
|
||||||
|
// Type of datasource plugin.
|
||||||
|
// MVP: Only "prometheus" supported.
|
||||||
|
// Future: "mysql", "postgres", "elasticsearch", etc.
|
||||||
|
Type string `json:"type"`
|
||||||
|
// Optional human-readable name for display in results.
|
||||||
|
// If not provided, UID will be used in error messages.
|
||||||
|
// Example: "Production Prometheus (US-West)"
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDataSourceMapping creates a new DataSourceMapping object.
|
||||||
|
func NewDataSourceMapping() *DataSourceMapping {
|
||||||
|
return &DataSourceMapping{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:openapi-gen=true
|
||||||
|
type Spec struct {
|
||||||
|
// Complete dashboard JSON object to validate.
|
||||||
|
// Must be a v1 dashboard schema (contains "panels" array).
|
||||||
|
// v2 dashboards (with "elements" structure) are not yet supported.
|
||||||
|
DashboardJson map[string]interface{} `json:"dashboardJson"`
|
||||||
|
// Array of datasources to validate against.
|
||||||
|
// The validator will check dashboard queries against each datasource
|
||||||
|
// and provide per-datasource compatibility results.
|
||||||
|
//
|
||||||
|
// MVP: Only single datasource supported (array length = 1), Prometheus type only.
|
||||||
|
// Future: Will support multiple datasources for dashboards with mixed queries.
|
||||||
|
DatasourceMappings []DataSourceMapping `json:"datasourceMappings"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSpec creates a new Spec object.
|
||||||
|
func NewSpec() *Spec {
|
||||||
|
return &Spec{
|
||||||
|
DashboardJson: map[string]interface{}{},
|
||||||
|
DatasourceMappings: []DataSourceMapping{},
|
||||||
|
}
|
||||||
|
}
|
||||||
+151
@@ -0,0 +1,151 @@
|
|||||||
|
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
// DataSourceResult contains validation results for a single datasource.
|
||||||
|
// Provides aggregate statistics and per-query breakdown of compatibility.
|
||||||
|
// +k8s:openapi-gen=true
|
||||||
|
type DataSourceResult struct {
|
||||||
|
// Datasource UID that was validated (matches DataSourceMapping.uid)
|
||||||
|
Uid string `json:"uid"`
|
||||||
|
// Datasource type (matches DataSourceMapping.type)
|
||||||
|
Type string `json:"type"`
|
||||||
|
// Optional display name (matches DataSourceMapping.name if provided)
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
// Total number of queries in the dashboard targeting this datasource.
|
||||||
|
// Includes all panel targets/queries that reference this datasource.
|
||||||
|
TotalQueries int64 `json:"totalQueries"`
|
||||||
|
// Number of queries successfully validated.
|
||||||
|
// May be less than totalQueries if some queries couldn't be parsed.
|
||||||
|
CheckedQueries int64 `json:"checkedQueries"`
|
||||||
|
// Total number of unique metrics/identifiers referenced across all queries.
|
||||||
|
// For Prometheus: metric names extracted from PromQL expressions.
|
||||||
|
// For SQL datasources: table and column names.
|
||||||
|
TotalMetrics int64 `json:"totalMetrics"`
|
||||||
|
// Number of metrics that exist in the datasource schema.
|
||||||
|
// foundMetrics <= totalMetrics
|
||||||
|
FoundMetrics int64 `json:"foundMetrics"`
|
||||||
|
// Array of metric names that were referenced but don't exist.
|
||||||
|
// Useful for debugging why a dashboard shows "no data".
|
||||||
|
// Example for Prometheus: ["http_requests_total", "api_latency_seconds"]
|
||||||
|
MissingMetrics []string `json:"missingMetrics"`
|
||||||
|
// Per-query breakdown showing which specific queries have issues.
|
||||||
|
// One entry per query target (refId: "A", "B", "C", etc.) in each panel.
|
||||||
|
// Allows pinpointing exactly which panel/query needs fixing.
|
||||||
|
QueryBreakdown []QueryBreakdown `json:"queryBreakdown"`
|
||||||
|
// Overall compatibility score for this datasource (0-100).
|
||||||
|
// Calculated as: (foundMetrics / totalMetrics) * 100
|
||||||
|
// Used to calculate the global compatibilityScore in status.
|
||||||
|
CompatibilityScore float64 `json:"compatibilityScore"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDataSourceResult creates a new DataSourceResult object.
|
||||||
|
func NewDataSourceResult() *DataSourceResult {
|
||||||
|
return &DataSourceResult{
|
||||||
|
MissingMetrics: []string{},
|
||||||
|
QueryBreakdown: []QueryBreakdown{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryBreakdown provides compatibility details for a single query within a panel.
|
||||||
|
// Granular per-query results allow users to identify exactly which queries need fixing.
|
||||||
|
//
|
||||||
|
// Note: A panel can have multiple queries (refId: "A", "B", "C", etc.),
|
||||||
|
// so there may be multiple QueryBreakdown entries for the same panelID.
|
||||||
|
// +k8s:openapi-gen=true
|
||||||
|
type QueryBreakdown struct {
|
||||||
|
// Human-readable panel title for context.
|
||||||
|
// Example: "CPU Usage", "Request Rate"
|
||||||
|
PanelTitle string `json:"panelTitle"`
|
||||||
|
// Numeric panel ID from dashboard JSON.
|
||||||
|
// Used to correlate with dashboard structure.
|
||||||
|
PanelID int64 `json:"panelID"`
|
||||||
|
// Query identifier within the panel.
|
||||||
|
// Values: "A", "B", "C", etc. (from panel.targets[].refId)
|
||||||
|
// Uniquely identifies which query in a multi-query panel this refers to.
|
||||||
|
QueryRefId string `json:"queryRefId"`
|
||||||
|
// Number of unique metrics referenced in this specific query.
|
||||||
|
// For Prometheus: metrics extracted from the PromQL expr.
|
||||||
|
// Example: rate(http_requests_total[5m]) references 1 metric.
|
||||||
|
TotalMetrics int64 `json:"totalMetrics"`
|
||||||
|
// Number of those metrics that exist in the datasource.
|
||||||
|
// foundMetrics <= totalMetrics
|
||||||
|
FoundMetrics int64 `json:"foundMetrics"`
|
||||||
|
// Array of missing metric names specific to this query.
|
||||||
|
// Helps identify exactly which part of a query expression will fail.
|
||||||
|
// Empty array means query is fully compatible.
|
||||||
|
MissingMetrics []string `json:"missingMetrics"`
|
||||||
|
// Compatibility percentage for this individual query (0-100).
|
||||||
|
// Calculated as: (foundMetrics / totalMetrics) * 100
|
||||||
|
// 100 = query will work perfectly, 0 = query will return no data.
|
||||||
|
CompatibilityScore float64 `json:"compatibilityScore"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewQueryBreakdown creates a new QueryBreakdown object.
|
||||||
|
func NewQueryBreakdown() *QueryBreakdown {
|
||||||
|
return &QueryBreakdown{
|
||||||
|
MissingMetrics: []string{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:openapi-gen=true
|
||||||
|
type StatusOperatorState struct {
|
||||||
|
// lastEvaluation is the ResourceVersion last evaluated
|
||||||
|
LastEvaluation string `json:"lastEvaluation"`
|
||||||
|
// state describes the state of the lastEvaluation.
|
||||||
|
// It is limited to three possible states for machine evaluation.
|
||||||
|
State StatusOperatorStateState `json:"state"`
|
||||||
|
// descriptiveState is an optional more descriptive state field which has no requirements on format
|
||||||
|
DescriptiveState *string `json:"descriptiveState,omitempty"`
|
||||||
|
// details contains any extra information that is operator-specific
|
||||||
|
Details map[string]interface{} `json:"details,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStatusOperatorState creates a new StatusOperatorState object.
|
||||||
|
func NewStatusOperatorState() *StatusOperatorState {
|
||||||
|
return &StatusOperatorState{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:openapi-gen=true
|
||||||
|
type Status struct {
|
||||||
|
// Overall compatibility score across all datasources (0-100).
|
||||||
|
// Calculated as: (total found metrics / total referenced metrics) * 100
|
||||||
|
//
|
||||||
|
// Score interpretation:
|
||||||
|
// - 100: Perfect compatibility, all queries will work
|
||||||
|
// - 80-99: Excellent, minor missing metrics
|
||||||
|
// - 50-79: Fair, significant missing metrics
|
||||||
|
// - 0-49: Poor, most queries will fail
|
||||||
|
CompatibilityScore float64 `json:"compatibilityScore"`
|
||||||
|
// Per-datasource validation results.
|
||||||
|
// Array length matches spec.datasourceMappings.
|
||||||
|
// Each element contains detailed metrics and query-level breakdown.
|
||||||
|
DatasourceResults []DataSourceResult `json:"datasourceResults"`
|
||||||
|
// ISO 8601 timestamp of when validation was last performed.
|
||||||
|
// Example: "2024-01-15T10:30:00Z"
|
||||||
|
LastChecked *string `json:"lastChecked,omitempty"`
|
||||||
|
// operatorStates is a map of operator ID to operator state evaluations.
|
||||||
|
// Any operator which consumes this kind SHOULD add its state evaluation information to this field.
|
||||||
|
OperatorStates map[string]StatusOperatorState `json:"operatorStates,omitempty"`
|
||||||
|
// Human-readable summary of validation result.
|
||||||
|
// Examples: "All queries compatible", "3 missing metrics found"
|
||||||
|
Message *string `json:"message,omitempty"`
|
||||||
|
// additionalFields is reserved for future use
|
||||||
|
AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStatus creates a new Status object.
|
||||||
|
func NewStatus() *Status {
|
||||||
|
return &Status{
|
||||||
|
DatasourceResults: []DataSourceResult{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:openapi-gen=true
|
||||||
|
type StatusOperatorStateState string
|
||||||
|
|
||||||
|
const (
|
||||||
|
StatusOperatorStateStateSuccess StatusOperatorStateState = "success"
|
||||||
|
StatusOperatorStateStateInProgress StatusOperatorStateState = "in_progress"
|
||||||
|
StatusOperatorStateStateFailed StatusOperatorStateState = "failed"
|
||||||
|
)
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,568 @@
|
|||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DashboardCompatibilityRequest contains the dashboard and datasource mappings to validate
|
||||||
|
type DashboardCompatibilityRequest struct {
|
||||||
|
DashboardJSON map[string]interface{} // Dashboard JSON structure
|
||||||
|
DatasourceMappings []DatasourceMapping // List of datasources to validate against
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatasourceMapping maps a datasource UID to its type and optionally name/URL
|
||||||
|
type DatasourceMapping struct {
|
||||||
|
UID string // Datasource UID
|
||||||
|
Type string // Datasource type (prometheus, mysql, etc.)
|
||||||
|
Name string // Optional: Datasource name
|
||||||
|
URL string // Datasource URL
|
||||||
|
HTTPClient *http.Client // Authenticated HTTP client
|
||||||
|
}
|
||||||
|
|
||||||
|
// DashboardCompatibilityResult contains the validation results for a dashboard
|
||||||
|
type DashboardCompatibilityResult struct {
|
||||||
|
CompatibilityScore float64 // Overall compatibility (0.0 - 1.0)
|
||||||
|
DatasourceResults []DatasourceValidationResult // Per-datasource results
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatasourceValidationResult contains validation results for one datasource
|
||||||
|
type DatasourceValidationResult struct {
|
||||||
|
UID string
|
||||||
|
Type string
|
||||||
|
Name string
|
||||||
|
TotalQueries int
|
||||||
|
CheckedQueries int
|
||||||
|
TotalMetrics int
|
||||||
|
FoundMetrics int
|
||||||
|
MissingMetrics []string
|
||||||
|
QueryBreakdown []QueryResult
|
||||||
|
CompatibilityScore float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateDashboardCompatibility is the main entry point for validating dashboard compatibility
|
||||||
|
// It extracts queries from the dashboard, validates them against each datasource, and returns aggregated results
|
||||||
|
func ValidateDashboardCompatibility(ctx context.Context, req DashboardCompatibilityRequest) (*DashboardCompatibilityResult, error) {
|
||||||
|
// MVP: Only support single datasource validation
|
||||||
|
if len(req.DatasourceMappings) != 1 {
|
||||||
|
return nil, fmt.Errorf("MVP only supports single datasource validation, got %d datasources", len(req.DatasourceMappings))
|
||||||
|
}
|
||||||
|
|
||||||
|
singleDatasource := req.DatasourceMappings[0]
|
||||||
|
|
||||||
|
result := &DashboardCompatibilityResult{
|
||||||
|
DatasourceResults: make([]DatasourceValidationResult, 0, len(req.DatasourceMappings)),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Extract queries from dashboard JSON
|
||||||
|
queries, err := extractQueriesFromDashboard(req.DashboardJSON)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to extract queries from dashboard: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("[DEBUG] Extracted %d queries from dashboard\n", len(queries))
|
||||||
|
for i, q := range queries {
|
||||||
|
fmt.Printf("[DEBUG] Query %d: DS=%s, RefID=%s, Query=%s\n", i, q.DatasourceUID, q.RefID, q.QueryText)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Group queries by datasource UID (with variable resolution for MVP)
|
||||||
|
queriesByDatasource := groupQueriesByDatasource(queries, singleDatasource.UID, req.DashboardJSON)
|
||||||
|
|
||||||
|
fmt.Printf("[DEBUG] Grouped queries by %d datasources\n", len(queriesByDatasource))
|
||||||
|
for dsUID, dsQueries := range queriesByDatasource {
|
||||||
|
fmt.Printf("[DEBUG] Datasource %s has %d queries\n", dsUID, len(dsQueries))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Validate each datasource
|
||||||
|
var totalCompatibility float64
|
||||||
|
validatedCount := 0
|
||||||
|
|
||||||
|
for _, dsMapping := range req.DatasourceMappings {
|
||||||
|
fmt.Printf("[DEBUG] Processing datasource mapping: UID=%s, Type=%s, URL=%s\n", dsMapping.UID, dsMapping.Type, dsMapping.URL)
|
||||||
|
|
||||||
|
// Get queries for this datasource
|
||||||
|
dsQueries, ok := queriesByDatasource[dsMapping.UID]
|
||||||
|
if !ok || len(dsQueries) == 0 {
|
||||||
|
// No queries for this datasource, skip
|
||||||
|
fmt.Printf("[DEBUG] No queries found for datasource %s, skipping\n", dsMapping.UID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("[DEBUG] Found %d queries for datasource %s\n", len(dsQueries), dsMapping.UID)
|
||||||
|
|
||||||
|
// Get validator for this datasource type
|
||||||
|
v, err := GetValidator(dsMapping.Type)
|
||||||
|
if err != nil {
|
||||||
|
// Unsupported datasource type, skip but log
|
||||||
|
fmt.Printf("[DEBUG] Failed to get validator for type %s: %v\n", dsMapping.Type, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("[DEBUG] Got validator for type %s, starting validation\n", dsMapping.Type)
|
||||||
|
|
||||||
|
// Build Datasource struct
|
||||||
|
ds := Datasource{
|
||||||
|
UID: dsMapping.UID,
|
||||||
|
Type: dsMapping.Type,
|
||||||
|
Name: dsMapping.Name,
|
||||||
|
URL: dsMapping.URL,
|
||||||
|
HTTPClient: dsMapping.HTTPClient,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate queries
|
||||||
|
validationResult, err := v.ValidateQueries(ctx, dsQueries, ds)
|
||||||
|
if err != nil {
|
||||||
|
// Validation failed for this datasource - return error to caller
|
||||||
|
// This could be a connection error, auth error, or other critical failure
|
||||||
|
return nil, fmt.Errorf("validation failed for datasource %s: %w", dsMapping.UID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to DatasourceValidationResult
|
||||||
|
dsResult := DatasourceValidationResult{
|
||||||
|
UID: dsMapping.UID,
|
||||||
|
Type: dsMapping.Type,
|
||||||
|
Name: dsMapping.Name,
|
||||||
|
TotalQueries: validationResult.TotalQueries,
|
||||||
|
CheckedQueries: validationResult.CheckedQueries,
|
||||||
|
TotalMetrics: validationResult.TotalMetrics,
|
||||||
|
FoundMetrics: validationResult.FoundMetrics,
|
||||||
|
MissingMetrics: validationResult.MissingMetrics,
|
||||||
|
QueryBreakdown: validationResult.QueryBreakdown,
|
||||||
|
CompatibilityScore: validationResult.CompatibilityScore,
|
||||||
|
}
|
||||||
|
|
||||||
|
result.DatasourceResults = append(result.DatasourceResults, dsResult)
|
||||||
|
totalCompatibility += validationResult.CompatibilityScore
|
||||||
|
validatedCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: Calculate overall compatibility score
|
||||||
|
if validatedCount > 0 {
|
||||||
|
result.CompatibilityScore = totalCompatibility / float64(validatedCount)
|
||||||
|
} else {
|
||||||
|
result.CompatibilityScore = 1.0 // No datasources = perfect compatibility
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractQueriesFromDashboard parses the dashboard JSON and extracts all queries
|
||||||
|
// Supports both v1 (legacy) and v2 (new) dashboard formats
|
||||||
|
func extractQueriesFromDashboard(dashboardJSON map[string]interface{}) ([]DashboardQuery, error) {
|
||||||
|
var queries []DashboardQuery
|
||||||
|
|
||||||
|
// Debug: Print what keys we have
|
||||||
|
fmt.Printf("[DEBUG] Dashboard JSON keys: ")
|
||||||
|
for key := range dashboardJSON {
|
||||||
|
fmt.Printf("%s, ", key)
|
||||||
|
}
|
||||||
|
fmt.Printf("\n")
|
||||||
|
|
||||||
|
// Detect dashboard version (v1 uses "panels", v2 uses different structure)
|
||||||
|
// For MVP, we only support v1 (legacy format with panels array)
|
||||||
|
if !isV1Dashboard(dashboardJSON) {
|
||||||
|
fmt.Printf("[DEBUG] isV1Dashboard returned false, 'panels' key exists: %v\n", dashboardJSON["panels"] != nil)
|
||||||
|
return nil, fmt.Errorf("unsupported dashboard format: only v1 dashboards are supported in MVP")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract panels array
|
||||||
|
panels, ok := dashboardJSON["panels"].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
// No panels in dashboard, return empty array
|
||||||
|
return queries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate through all panels
|
||||||
|
for _, panelInterface := range panels {
|
||||||
|
panel, ok := panelInterface.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract queries from this panel
|
||||||
|
panelQueries := extractQueriesFromPanel(panel)
|
||||||
|
queries = append(queries, panelQueries...)
|
||||||
|
|
||||||
|
// Handle nested panels in collapsed rows
|
||||||
|
nestedPanels, hasNested := panel["panels"].([]interface{})
|
||||||
|
if hasNested {
|
||||||
|
for _, nestedPanelInterface := range nestedPanels {
|
||||||
|
nestedPanel, ok := nestedPanelInterface.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
nestedQueries := extractQueriesFromPanel(nestedPanel)
|
||||||
|
queries = append(queries, nestedQueries...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return queries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isV1Dashboard checks if a dashboard is in v1 (legacy) format
|
||||||
|
// v1 dashboards have a "panels" array at the top level
|
||||||
|
// v2 dashboards have "elements" map and "layout" structure
|
||||||
|
//
|
||||||
|
// This follows Grafana's official dashboard conversion logic which uses
|
||||||
|
// type-safe assertions to distinguish between formats.
|
||||||
|
// Reference: apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go:450
|
||||||
|
func isV1Dashboard(dashboard map[string]interface{}) bool {
|
||||||
|
// Check for v2 indicators first (positive identification)
|
||||||
|
// v2 dashboards use a map of elements, not an array
|
||||||
|
if _, hasElements := dashboard["elements"].(map[string]interface{}); hasElements {
|
||||||
|
return false // Definitely v2
|
||||||
|
}
|
||||||
|
|
||||||
|
// v2 dashboards also have a layout structure
|
||||||
|
if _, hasLayout := dashboard["layout"]; hasLayout {
|
||||||
|
return false // v2 has layout field
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for v1 panels with type assertion (must be an array)
|
||||||
|
// This is type-safe: `{"panels": "string"}` would fail this check and return false
|
||||||
|
_, hasPanels := dashboard["panels"].([]interface{})
|
||||||
|
return hasPanels
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractQueriesFromPanel extracts all queries/targets from a single panel
|
||||||
|
func extractQueriesFromPanel(panel map[string]interface{}) []DashboardQuery {
|
||||||
|
var queries []DashboardQuery
|
||||||
|
|
||||||
|
// Get panel info for context
|
||||||
|
panelTitle := getStringValue(panel, "title", "Untitled Panel")
|
||||||
|
panelID := getIntValue(panel, "id", 0)
|
||||||
|
|
||||||
|
// Extract targets array (queries)
|
||||||
|
targets, hasTargets := panel["targets"].([]interface{})
|
||||||
|
if !hasTargets {
|
||||||
|
return queries
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate through each target/query
|
||||||
|
for _, targetInterface := range targets {
|
||||||
|
target, ok := targetInterface.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract datasource UID
|
||||||
|
datasourceUID := extractDatasourceUID(target, panel)
|
||||||
|
if datasourceUID == "" {
|
||||||
|
// Skip queries without datasource
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract query text (different fields for different datasources)
|
||||||
|
queryText := extractQueryText(target)
|
||||||
|
if queryText == "" {
|
||||||
|
// Skip empty queries
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract refId (A, B, C, etc.)
|
||||||
|
refID := getStringValue(target, "refId", "")
|
||||||
|
|
||||||
|
// Build DashboardQuery
|
||||||
|
query := DashboardQuery{
|
||||||
|
DatasourceUID: datasourceUID,
|
||||||
|
RefID: refID,
|
||||||
|
QueryText: queryText,
|
||||||
|
PanelTitle: panelTitle,
|
||||||
|
PanelID: panelID,
|
||||||
|
}
|
||||||
|
|
||||||
|
queries = append(queries, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
return queries
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractDatasourceUID gets the datasource UID from a target, falling back to panel datasource
|
||||||
|
func extractDatasourceUID(target map[string]interface{}, panel map[string]interface{}) string {
|
||||||
|
// Try target-level datasource first
|
||||||
|
if ds, ok := target["datasource"]; ok {
|
||||||
|
if uid := getDatasourceUIDFromValue(ds); uid != "" {
|
||||||
|
return uid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to panel-level datasource
|
||||||
|
if ds, ok := panel["datasource"]; ok {
|
||||||
|
if uid := getDatasourceUIDFromValue(ds); uid != "" {
|
||||||
|
return uid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDatasourceUIDFromValue extracts UID from datasource value (can be string or object)
|
||||||
|
func getDatasourceUIDFromValue(ds interface{}) string {
|
||||||
|
switch v := ds.(type) {
|
||||||
|
case string:
|
||||||
|
// Direct UID string
|
||||||
|
return v
|
||||||
|
case map[string]interface{}:
|
||||||
|
// Structured datasource reference { uid: "...", type: "..." }
|
||||||
|
return getStringValue(v, "uid", "")
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isVariableReference checks if a string is a template variable reference
|
||||||
|
// Matches patterns: ${varname}, $varname, [[varname]]
|
||||||
|
// Follows Grafana's frontend regex: /\$(\w+)|\[\[(\w+?)(?::(\w+))?\]\]|\${(\w+)(?:\.([^:^\}]+))?(?::([^\}]+))?}/g
|
||||||
|
// where \w = [A-Za-z0-9_] (alphanumeric + underscore, NO dashes)
|
||||||
|
func isVariableReference(uid string) bool {
|
||||||
|
if uid == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match ${...} pattern - requires at least one \w character inside braces
|
||||||
|
if len(uid) > 3 && uid[0] == '$' && uid[1] == '{' && uid[len(uid)-1] == '}' {
|
||||||
|
// Extract content between ${ and }
|
||||||
|
content := uid[2 : len(uid)-1]
|
||||||
|
if len(content) == 0 {
|
||||||
|
return false // Empty braces ${} not allowed
|
||||||
|
}
|
||||||
|
// Check if content starts with \w+ (before any . or :)
|
||||||
|
for i, ch := range content {
|
||||||
|
if ch == '.' || ch == ':' {
|
||||||
|
// Found delimiter, check if we had at least one \w before it
|
||||||
|
return i > 0
|
||||||
|
}
|
||||||
|
// Must be alphanumeric or underscore
|
||||||
|
if !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
|
||||||
|
(ch >= '0' && ch <= '9') || ch == '_') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true // All characters were valid \w
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match $varname pattern - requires at least one \w character after $
|
||||||
|
// \w = alphanumeric + underscore (digits ARE allowed, dashes are NOT)
|
||||||
|
if uid[0] == '$' && len(uid) > 1 {
|
||||||
|
for i := 1; i < len(uid); i++ {
|
||||||
|
ch := uid[i]
|
||||||
|
// \w = [A-Za-z0-9_] only (NO dashes)
|
||||||
|
if !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
|
||||||
|
(ch >= '0' && ch <= '9') || ch == '_') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match [[varname]] pattern - requires at least one \w character inside brackets
|
||||||
|
// Also supports [[varname:format]] syntax
|
||||||
|
if len(uid) > 4 && uid[0] == '[' && uid[1] == '[' &&
|
||||||
|
uid[len(uid)-2] == ']' && uid[len(uid)-1] == ']' {
|
||||||
|
// Extract content between [[ and ]]
|
||||||
|
content := uid[2 : len(uid)-2]
|
||||||
|
if len(content) == 0 {
|
||||||
|
return false // Empty brackets [[]] not allowed
|
||||||
|
}
|
||||||
|
// Check if content starts with \w+ (before any :)
|
||||||
|
for i, ch := range content {
|
||||||
|
if ch == ':' {
|
||||||
|
// Found format delimiter, check if we had at least one \w before it
|
||||||
|
return i > 0
|
||||||
|
}
|
||||||
|
// Must be alphanumeric or underscore
|
||||||
|
if !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
|
||||||
|
(ch >= '0' && ch <= '9') || ch == '_') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true // All characters were valid \w
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractVariableName extracts the variable name from a variable reference
|
||||||
|
// Returns only the name part, excluding fieldPath (after .) and format (after :)
|
||||||
|
// Examples: ${var.field} -> "var", [[var:text]] -> "var", $datasource -> "datasource"
|
||||||
|
func extractVariableName(varRef string) string {
|
||||||
|
if !isVariableReference(varRef) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle ${varname} pattern - may include .fieldPath or :format
|
||||||
|
if len(varRef) > 3 && varRef[0] == '$' && varRef[1] == '{' && varRef[len(varRef)-1] == '}' {
|
||||||
|
content := varRef[2 : len(varRef)-1]
|
||||||
|
// Extract only up to . or :
|
||||||
|
for i, ch := range content {
|
||||||
|
if ch == '.' || ch == ':' {
|
||||||
|
return content[:i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle $varname pattern - no modifiers possible
|
||||||
|
if varRef[0] == '$' && len(varRef) > 1 {
|
||||||
|
return varRef[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle [[varname]] pattern - may include :format
|
||||||
|
if len(varRef) > 4 && varRef[0] == '[' && varRef[1] == '[' {
|
||||||
|
content := varRef[2 : len(varRef)-2]
|
||||||
|
// Extract only up to :
|
||||||
|
for i, ch := range content {
|
||||||
|
if ch == ':' {
|
||||||
|
return content[:i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// isPrometheusVariable checks if a variable reference points to a Prometheus datasource
|
||||||
|
// Looks in dashboard.__inputs for the datasource type
|
||||||
|
func isPrometheusVariable(varRef string, dashboardJSON map[string]interface{}) bool {
|
||||||
|
if !isVariableReference(varRef) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
varName := extractVariableName(varRef)
|
||||||
|
if varName == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for __inputs array in dashboard
|
||||||
|
inputs, hasInputs := dashboardJSON["__inputs"].([]interface{})
|
||||||
|
if !hasInputs {
|
||||||
|
// No __inputs, assume it might be Prometheus (MVP: single datasource)
|
||||||
|
// This is a fallback for dashboards without explicit __inputs
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for this variable in __inputs
|
||||||
|
for _, inputInterface := range inputs {
|
||||||
|
input, ok := inputInterface.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this input matches our variable name
|
||||||
|
inputName := getStringValue(input, "name", "")
|
||||||
|
inputType := getStringValue(input, "type", "")
|
||||||
|
inputPluginID := getStringValue(input, "pluginId", "")
|
||||||
|
|
||||||
|
// Match by name (case-insensitive for flexibility)
|
||||||
|
if inputName != "" && varName != "" {
|
||||||
|
if inputName == varName ||
|
||||||
|
strings.EqualFold(inputName, varName) ||
|
||||||
|
strings.Contains(strings.ToLower(varName), strings.ToLower(inputName)) {
|
||||||
|
// Check if it's a datasource input with prometheus plugin
|
||||||
|
if inputType == "datasource" && inputPluginID == "prometheus" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not found or not Prometheus
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveDatasourceUID resolves a datasource UID, handling variable references (MVP: single datasource)
|
||||||
|
// For MVP, all Prometheus variables resolve to the single datasource UID
|
||||||
|
func resolveDatasourceUID(uid string, singleDatasourceUID string, dashboardJSON map[string]interface{}) string {
|
||||||
|
// If not a variable, return as-is (concrete UID)
|
||||||
|
if !isVariableReference(uid) {
|
||||||
|
return uid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's a Prometheus variable
|
||||||
|
if isPrometheusVariable(uid, dashboardJSON) {
|
||||||
|
fmt.Printf("[DEBUG] Resolved Prometheus variable %s to %s\n", uid, singleDatasourceUID)
|
||||||
|
return singleDatasourceUID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-Prometheus variable, return as-is (will be ignored in grouping)
|
||||||
|
fmt.Printf("[DEBUG] Variable %s is not a Prometheus variable, skipping\n", uid)
|
||||||
|
return uid
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractQueryText extracts the query text from a target
|
||||||
|
// Different datasources use different field names (expr, query, rawSql, etc.)
|
||||||
|
func extractQueryText(target map[string]interface{}) string {
|
||||||
|
// Try common query field names
|
||||||
|
queryFields := []string{"expr", "query", "rawSql", "rawQuery", "target", "measurement"}
|
||||||
|
|
||||||
|
for _, field := range queryFields {
|
||||||
|
if queryText := getStringValue(target, field, ""); queryText != "" {
|
||||||
|
return queryText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// getStringValue safely extracts a string value from a map
|
||||||
|
func getStringValue(m map[string]interface{}, key string, defaultValue string) string {
|
||||||
|
if value, ok := m[key]; ok {
|
||||||
|
if s, ok := value.(string); ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// getIntValue safely extracts an int value from a map
|
||||||
|
func getIntValue(m map[string]interface{}, key string, defaultValue int) int {
|
||||||
|
if value, ok := m[key]; ok {
|
||||||
|
switch v := value.(type) {
|
||||||
|
case int:
|
||||||
|
return v
|
||||||
|
case float64:
|
||||||
|
return int(v)
|
||||||
|
case int64:
|
||||||
|
return int(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// DashboardQuery represents a query extracted from a dashboard panel
|
||||||
|
type DashboardQuery struct {
|
||||||
|
DatasourceUID string // Which datasource this query belongs to
|
||||||
|
RefID string // Query reference ID
|
||||||
|
QueryText string // The actual query
|
||||||
|
PanelTitle string // Panel title
|
||||||
|
PanelID int // Panel ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// groupQueriesByDatasource groups dashboard queries by their datasource UID
|
||||||
|
// For MVP: resolves Prometheus template variables to the single datasource UID
|
||||||
|
func groupQueriesByDatasource(queries []DashboardQuery, singleDatasourceUID string, dashboardJSON map[string]interface{}) map[string][]Query {
|
||||||
|
grouped := make(map[string][]Query)
|
||||||
|
|
||||||
|
for _, dq := range queries {
|
||||||
|
q := Query{
|
||||||
|
RefID: dq.RefID,
|
||||||
|
QueryText: dq.QueryText,
|
||||||
|
PanelTitle: dq.PanelTitle,
|
||||||
|
PanelID: dq.PanelID,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve datasource UID (handles both concrete UIDs and variables)
|
||||||
|
resolvedUID := resolveDatasourceUID(dq.DatasourceUID, singleDatasourceUID, dashboardJSON)
|
||||||
|
|
||||||
|
// Only add to grouping if we got a valid resolved UID
|
||||||
|
if resolvedUID != "" {
|
||||||
|
grouped[resolvedUID] = append(grouped[resolvedUID], q)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return grouped
|
||||||
|
}
|
||||||
@@ -0,0 +1,604 @@
|
|||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Note: extractQueryText() uses a hardcoded field priority list because
|
||||||
|
// Grafana doesn't expose datasource query schemas at runtime.
|
||||||
|
// When Grafana adds new datasource types, update the list in dashboard.go
|
||||||
|
// and add corresponding test cases here.
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Category 1: extractQueryText Tests
|
||||||
|
// Tests verify the hardcoded field priority list works correctly.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func TestExtractQueryText(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
target map[string]interface{}
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "prometheus_expr_field",
|
||||||
|
target: map[string]interface{}{
|
||||||
|
"expr": "up",
|
||||||
|
},
|
||||||
|
expected: "up",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mysql_rawSql_field",
|
||||||
|
target: map[string]interface{}{
|
||||||
|
"rawSql": "SELECT * FROM users LIMIT 100",
|
||||||
|
},
|
||||||
|
expected: "SELECT * FROM users LIMIT 100",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "generic_query_field",
|
||||||
|
target: map[string]interface{}{
|
||||||
|
"query": "show measurements",
|
||||||
|
},
|
||||||
|
expected: "show measurements",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "field_priority_order",
|
||||||
|
target: map[string]interface{}{
|
||||||
|
"expr": "rate(cpu[5m])", // First priority
|
||||||
|
"query": "show metrics", // Second priority
|
||||||
|
},
|
||||||
|
expected: "rate(cpu[5m])", // Should return expr, not query
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing_query_fields",
|
||||||
|
target: map[string]interface{}{"refId": "A", "hide": false},
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty_string_value",
|
||||||
|
target: map[string]interface{}{
|
||||||
|
"expr": "",
|
||||||
|
},
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := extractQueryText(tt.target)
|
||||||
|
require.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Category 2: getDatasourceUIDFromValue Tests (4 tests)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func TestGetDatasourceUIDFromValue(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value interface{}
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "string_datasource_uid",
|
||||||
|
value: "prom-123",
|
||||||
|
expected: "prom-123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "object_datasource_with_uid",
|
||||||
|
value: map[string]interface{}{
|
||||||
|
"uid": "prom-123",
|
||||||
|
"type": "prometheus",
|
||||||
|
},
|
||||||
|
expected: "prom-123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "variable_reference_passed_through",
|
||||||
|
value: "${DS_PROMETHEUS}",
|
||||||
|
expected: "${DS_PROMETHEUS}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil_value",
|
||||||
|
value: nil,
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := getDatasourceUIDFromValue(tt.value)
|
||||||
|
require.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Category 3: extractDatasourceUID Tests (5 tests)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func TestExtractDatasourceUID(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
target map[string]interface{}
|
||||||
|
panel map[string]interface{}
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "target_level_datasource_string",
|
||||||
|
target: map[string]interface{}{
|
||||||
|
"datasource": "target-ds-123",
|
||||||
|
},
|
||||||
|
panel: map[string]interface{}{},
|
||||||
|
expected: "target-ds-123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "target_level_datasource_object",
|
||||||
|
target: map[string]interface{}{
|
||||||
|
"datasource": map[string]interface{}{
|
||||||
|
"uid": "target-ds-456",
|
||||||
|
"type": "prometheus",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
panel: map[string]interface{}{},
|
||||||
|
expected: "target-ds-456",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "panel_level_fallback",
|
||||||
|
target: map[string]interface{}{},
|
||||||
|
panel: map[string]interface{}{
|
||||||
|
"datasource": "panel-ds-789",
|
||||||
|
},
|
||||||
|
expected: "panel-ds-789",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "target_level_takes_precedence",
|
||||||
|
target: map[string]interface{}{
|
||||||
|
"datasource": "target-ds",
|
||||||
|
},
|
||||||
|
panel: map[string]interface{}{
|
||||||
|
"datasource": "panel-ds",
|
||||||
|
},
|
||||||
|
expected: "target-ds",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "both_missing_returns_empty",
|
||||||
|
target: map[string]interface{}{},
|
||||||
|
panel: map[string]interface{}{},
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := extractDatasourceUID(tt.target, tt.panel)
|
||||||
|
require.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Category 4: extractQueriesFromPanel Tests (8 tests)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func TestExtractQueriesFromPanel(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
panel map[string]interface{}
|
||||||
|
expected []DashboardQuery
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "panel_with_single_target",
|
||||||
|
panel: map[string]interface{}{
|
||||||
|
"id": 42,
|
||||||
|
"title": "CPU Usage",
|
||||||
|
"targets": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"refId": "A",
|
||||||
|
"expr": "rate(cpu[5m])",
|
||||||
|
"datasource": "prom-main",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []DashboardQuery{
|
||||||
|
{
|
||||||
|
DatasourceUID: "prom-main",
|
||||||
|
RefID: "A",
|
||||||
|
QueryText: "rate(cpu[5m])",
|
||||||
|
PanelTitle: "CPU Usage",
|
||||||
|
PanelID: 42,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "panel_with_multiple_targets",
|
||||||
|
panel: map[string]interface{}{
|
||||||
|
"id": 10,
|
||||||
|
"title": "Metrics",
|
||||||
|
"targets": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"refId": "A",
|
||||||
|
"expr": "up",
|
||||||
|
"datasource": "prom-1",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"refId": "B",
|
||||||
|
"expr": "down",
|
||||||
|
"datasource": "prom-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []DashboardQuery{
|
||||||
|
{
|
||||||
|
DatasourceUID: "prom-1",
|
||||||
|
RefID: "A",
|
||||||
|
QueryText: "up",
|
||||||
|
PanelTitle: "Metrics",
|
||||||
|
PanelID: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DatasourceUID: "prom-1",
|
||||||
|
RefID: "B",
|
||||||
|
QueryText: "down",
|
||||||
|
PanelTitle: "Metrics",
|
||||||
|
PanelID: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "panel_with_no_targets_field",
|
||||||
|
panel: map[string]interface{}{
|
||||||
|
"id": 1,
|
||||||
|
"title": "Text Panel",
|
||||||
|
},
|
||||||
|
expected: []DashboardQuery{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "panel_with_empty_targets_array",
|
||||||
|
panel: map[string]interface{}{
|
||||||
|
"id": 2,
|
||||||
|
"title": "Empty",
|
||||||
|
"targets": []interface{}{},
|
||||||
|
},
|
||||||
|
expected: []DashboardQuery{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "target_missing_datasource_skipped",
|
||||||
|
panel: map[string]interface{}{
|
||||||
|
"id": 3,
|
||||||
|
"title": "Incomplete",
|
||||||
|
"targets": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"refId": "A",
|
||||||
|
"expr": "up",
|
||||||
|
// No datasource field
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []DashboardQuery{}, // Empty because no datasource
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "target_missing_query_text_skipped",
|
||||||
|
panel: map[string]interface{}{
|
||||||
|
"id": 4,
|
||||||
|
"title": "No Query",
|
||||||
|
"targets": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"refId": "A",
|
||||||
|
"datasource": "prom-1",
|
||||||
|
// No expr/query field
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []DashboardQuery{}, // Empty because no query text
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "panel_metadata_extraction",
|
||||||
|
panel: map[string]interface{}{
|
||||||
|
"id": 999,
|
||||||
|
"title": "Custom Title",
|
||||||
|
"targets": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"refId": "Z",
|
||||||
|
"expr": "test_metric",
|
||||||
|
"datasource": "ds-abc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []DashboardQuery{
|
||||||
|
{
|
||||||
|
DatasourceUID: "ds-abc",
|
||||||
|
RefID: "Z",
|
||||||
|
QueryText: "test_metric",
|
||||||
|
PanelTitle: "Custom Title",
|
||||||
|
PanelID: 999,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "panel_id_as_float64",
|
||||||
|
panel: map[string]interface{}{
|
||||||
|
"id": float64(123), // JSON numbers parse as float64
|
||||||
|
"title": "Float ID Panel",
|
||||||
|
"targets": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"refId": "A",
|
||||||
|
"expr": "metric",
|
||||||
|
"datasource": "ds-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []DashboardQuery{
|
||||||
|
{
|
||||||
|
DatasourceUID: "ds-1",
|
||||||
|
RefID: "A",
|
||||||
|
QueryText: "metric",
|
||||||
|
PanelTitle: "Float ID Panel",
|
||||||
|
PanelID: 123,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := extractQueriesFromPanel(tt.panel)
|
||||||
|
if len(tt.expected) == 0 {
|
||||||
|
require.Empty(t, result)
|
||||||
|
} else {
|
||||||
|
require.Equal(t, tt.expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Category 5: Helper Functions Tests
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func TestGetStringValue(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
m map[string]interface{}
|
||||||
|
key string
|
||||||
|
defaultValue string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "returns_value_if_exists",
|
||||||
|
m: map[string]interface{}{"name": "test"},
|
||||||
|
key: "name",
|
||||||
|
defaultValue: "default",
|
||||||
|
expected: "test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "returns_default_if_missing",
|
||||||
|
m: map[string]interface{}{"other": "value"},
|
||||||
|
key: "name",
|
||||||
|
defaultValue: "default",
|
||||||
|
expected: "default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "handles_non_string_type",
|
||||||
|
m: map[string]interface{}{"name": 123},
|
||||||
|
key: "name",
|
||||||
|
defaultValue: "default",
|
||||||
|
expected: "default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty_map_returns_default",
|
||||||
|
m: map[string]interface{}{},
|
||||||
|
key: "name",
|
||||||
|
defaultValue: "default",
|
||||||
|
expected: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := getStringValue(tt.m, tt.key, tt.defaultValue)
|
||||||
|
require.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetIntValue(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
m map[string]interface{}
|
||||||
|
key string
|
||||||
|
defaultValue int
|
||||||
|
expected int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "returns_int_value",
|
||||||
|
m: map[string]interface{}{"count": 42},
|
||||||
|
key: "count",
|
||||||
|
defaultValue: 0,
|
||||||
|
expected: 42,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "handles_float64_conversion",
|
||||||
|
m: map[string]interface{}{"count": float64(123)},
|
||||||
|
key: "count",
|
||||||
|
defaultValue: 0,
|
||||||
|
expected: 123,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "handles_int64_conversion",
|
||||||
|
m: map[string]interface{}{"count": int64(456)},
|
||||||
|
key: "count",
|
||||||
|
defaultValue: 0,
|
||||||
|
expected: 456,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "returns_default_for_missing",
|
||||||
|
m: map[string]interface{}{},
|
||||||
|
key: "count",
|
||||||
|
defaultValue: 99,
|
||||||
|
expected: 99,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "returns_default_for_invalid_type",
|
||||||
|
m: map[string]interface{}{"count": "not a number"},
|
||||||
|
key: "count",
|
||||||
|
defaultValue: 99,
|
||||||
|
expected: 99,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := getIntValue(tt.m, tt.key, tt.defaultValue)
|
||||||
|
require.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Category 6: Integration Tests
|
||||||
|
// Real-world dashboard panel structures
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
func TestRealisticPrometheusPanel(t *testing.T) {
|
||||||
|
// Realistic Prometheus panel from actual Grafana dashboard
|
||||||
|
panel := map[string]interface{}{
|
||||||
|
"datasource": map[string]interface{}{
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prometheus-main",
|
||||||
|
},
|
||||||
|
"gridPos": map[string]interface{}{
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
},
|
||||||
|
"id": 28,
|
||||||
|
"title": "Request Rate",
|
||||||
|
"type": "timeseries",
|
||||||
|
"targets": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"datasource": map[string]interface{}{
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prometheus-main",
|
||||||
|
},
|
||||||
|
"expr": "rate(http_requests_total{job=\"api\"}[5m])",
|
||||||
|
"refId": "A",
|
||||||
|
"legendFormat": "{{method}} {{status}}",
|
||||||
|
"interval": "",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"datasource": map[string]interface{}{
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prometheus-main",
|
||||||
|
},
|
||||||
|
"expr": "rate(http_requests_total{job=\"worker\"}[5m])",
|
||||||
|
"refId": "B",
|
||||||
|
"legendFormat": "{{method}}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := extractQueriesFromPanel(panel)
|
||||||
|
|
||||||
|
require.Len(t, result, 2)
|
||||||
|
require.Equal(t, "prometheus-main", result[0].DatasourceUID)
|
||||||
|
require.Equal(t, "A", result[0].RefID)
|
||||||
|
require.Equal(t, "rate(http_requests_total{job=\"api\"}[5m])", result[0].QueryText)
|
||||||
|
require.Equal(t, "Request Rate", result[0].PanelTitle)
|
||||||
|
require.Equal(t, 28, result[0].PanelID)
|
||||||
|
|
||||||
|
require.Equal(t, "prometheus-main", result[1].DatasourceUID)
|
||||||
|
require.Equal(t, "B", result[1].RefID)
|
||||||
|
require.Equal(t, "rate(http_requests_total{job=\"worker\"}[5m])", result[1].QueryText)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRealisticMySQLPanel(t *testing.T) {
|
||||||
|
// Realistic MySQL panel structure
|
||||||
|
panel := map[string]interface{}{
|
||||||
|
"id": 10,
|
||||||
|
"title": "Recent Users",
|
||||||
|
"type": "table",
|
||||||
|
"datasource": map[string]interface{}{
|
||||||
|
"type": "mysql",
|
||||||
|
"uid": "mysql-prod",
|
||||||
|
},
|
||||||
|
"targets": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"datasource": map[string]interface{}{
|
||||||
|
"type": "mysql",
|
||||||
|
"uid": "mysql-prod",
|
||||||
|
},
|
||||||
|
"refId": "A",
|
||||||
|
"rawSql": "SELECT id, username, email FROM users WHERE created_at > NOW() - INTERVAL 1 DAY ORDER BY created_at DESC LIMIT 100",
|
||||||
|
"format": "table",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := extractQueriesFromPanel(panel)
|
||||||
|
|
||||||
|
require.Len(t, result, 1)
|
||||||
|
require.Equal(t, "mysql-prod", result[0].DatasourceUID)
|
||||||
|
require.Equal(t, "A", result[0].RefID)
|
||||||
|
require.Contains(t, result[0].QueryText, "SELECT id, username, email FROM users")
|
||||||
|
require.Equal(t, "Recent Users", result[0].PanelTitle)
|
||||||
|
require.Equal(t, 10, result[0].PanelID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMixedDatasourcesPanel(t *testing.T) {
|
||||||
|
// Panel with targets using different datasource types
|
||||||
|
panel := map[string]interface{}{
|
||||||
|
"id": 50,
|
||||||
|
"title": "Mixed Data",
|
||||||
|
"datasource": map[string]interface{}{
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "default-prom",
|
||||||
|
},
|
||||||
|
"targets": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"datasource": map[string]interface{}{
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "prom-1",
|
||||||
|
},
|
||||||
|
"refId": "A",
|
||||||
|
"expr": "up",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"datasource": map[string]interface{}{
|
||||||
|
"type": "elasticsearch",
|
||||||
|
"uid": "elastic-1",
|
||||||
|
},
|
||||||
|
"refId": "B",
|
||||||
|
"query": "status:200",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
// Uses panel-level datasource (fallback)
|
||||||
|
"refId": "C",
|
||||||
|
"expr": "down",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := extractQueriesFromPanel(panel)
|
||||||
|
|
||||||
|
require.Len(t, result, 3)
|
||||||
|
|
||||||
|
// Prometheus query
|
||||||
|
require.Equal(t, "prom-1", result[0].DatasourceUID)
|
||||||
|
require.Equal(t, "A", result[0].RefID)
|
||||||
|
require.Equal(t, "up", result[0].QueryText)
|
||||||
|
|
||||||
|
// Elasticsearch query
|
||||||
|
require.Equal(t, "elastic-1", result[1].DatasourceUID)
|
||||||
|
require.Equal(t, "B", result[1].RefID)
|
||||||
|
require.Equal(t, "status:200", result[1].QueryText)
|
||||||
|
|
||||||
|
// Query with panel-level datasource fallback
|
||||||
|
require.Equal(t, "default-prom", result[2].DatasourceUID)
|
||||||
|
require.Equal(t, "C", result[2].RefID)
|
||||||
|
require.Equal(t, "down", result[2].QueryText)
|
||||||
|
}
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsV1Dashboard(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
dashboard map[string]interface{}
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "v1 dashboard with panels array",
|
||||||
|
dashboard: map[string]interface{}{
|
||||||
|
"panels": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"id": 1,
|
||||||
|
"title": "Panel 1",
|
||||||
|
"type": "timeseries",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "v1 dashboard with empty panels",
|
||||||
|
dashboard: map[string]interface{}{
|
||||||
|
"panels": []interface{}{},
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "v2 dashboard with elements map",
|
||||||
|
dashboard: map[string]interface{}{
|
||||||
|
"elements": map[string]interface{}{
|
||||||
|
"panel-1": map[string]interface{}{
|
||||||
|
"kind": "Panel",
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"id": 1,
|
||||||
|
"title": "Panel 1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "v2 dashboard with layout",
|
||||||
|
dashboard: map[string]interface{}{
|
||||||
|
"layout": map[string]interface{}{
|
||||||
|
"kind": "GridLayout",
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"items": []interface{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "v2 dashboard with both elements and layout",
|
||||||
|
dashboard: map[string]interface{}{
|
||||||
|
"elements": map[string]interface{}{
|
||||||
|
"panel-1": map[string]interface{}{
|
||||||
|
"kind": "Panel",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"layout": map[string]interface{}{
|
||||||
|
"kind": "GridLayout",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty dashboard",
|
||||||
|
dashboard: map[string]interface{}{},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dashboard with wrong panels type (string instead of array)",
|
||||||
|
dashboard: map[string]interface{}{
|
||||||
|
"panels": "this-should-be-array-not-string",
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dashboard with other fields only",
|
||||||
|
dashboard: map[string]interface{}{
|
||||||
|
"title": "Test Dashboard",
|
||||||
|
"uid": "test-uid",
|
||||||
|
"tags": []string{"monitoring"},
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := isV1Dashboard(tt.dashboard)
|
||||||
|
require.Equal(t, tt.expected, result, "isV1Dashboard() returned unexpected result")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtractQueriesFromDashboard_VersionValidation(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
dashboard map[string]interface{}
|
||||||
|
expectError bool
|
||||||
|
errorContains string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid v1 dashboard extracts queries successfully",
|
||||||
|
dashboard: map[string]interface{}{
|
||||||
|
"panels": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"id": 1,
|
||||||
|
"title": "CPU Usage",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": map[string]interface{}{
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
},
|
||||||
|
"targets": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"datasource": map[string]interface{}{
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "test-prometheus",
|
||||||
|
},
|
||||||
|
"expr": "rate(cpu_usage_total[5m])",
|
||||||
|
"refId": "A",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "v2 dashboard returns unsupported format error",
|
||||||
|
dashboard: map[string]interface{}{
|
||||||
|
"elements": map[string]interface{}{
|
||||||
|
"panel-1": map[string]interface{}{
|
||||||
|
"kind": "Panel",
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"id": 1,
|
||||||
|
"title": "Panel 1",
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"kind": "QueryGroup",
|
||||||
|
},
|
||||||
|
"vizConfig": map[string]interface{}{
|
||||||
|
"kind": "TimeSeriesVisualConfig",
|
||||||
|
"pluginId": "timeseries",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"layout": map[string]interface{}{
|
||||||
|
"kind": "GridLayout",
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"items": []interface{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
errorContains: "unsupported dashboard format",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid dashboard (no panels or elements) returns error",
|
||||||
|
dashboard: map[string]interface{}{
|
||||||
|
"title": "Invalid Dashboard",
|
||||||
|
"description": "This dashboard has no panels or elements",
|
||||||
|
"tags": []string{"test"},
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
errorContains: "unsupported dashboard format",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
queries, err := extractQueriesFromDashboard(tt.dashboard)
|
||||||
|
|
||||||
|
if tt.expectError {
|
||||||
|
require.Error(t, err, "Expected error but got none")
|
||||||
|
require.Contains(t, err.Error(), tt.errorContains, "Error message doesn't contain expected substring")
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err, "Expected no error but got: %v", err)
|
||||||
|
require.NotNil(t, queries, "Queries should not be nil for valid dashboard")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrorCode represents the type of error that occurred
|
||||||
|
type ErrorCode string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Datasource-related errors
|
||||||
|
ErrCodeDatasourceNotFound ErrorCode = "datasource_not_found"
|
||||||
|
ErrCodeDatasourceWrongType ErrorCode = "datasource_wrong_type"
|
||||||
|
ErrCodeDatasourceUnreachable ErrorCode = "datasource_unreachable"
|
||||||
|
ErrCodeDatasourceAuth ErrorCode = "datasource_auth_failed"
|
||||||
|
ErrCodeDatasourceConfig ErrorCode = "datasource_config_error"
|
||||||
|
|
||||||
|
// API-related errors
|
||||||
|
ErrCodeAPIUnavailable ErrorCode = "api_unavailable"
|
||||||
|
ErrCodeAPIInvalidResponse ErrorCode = "api_invalid_response"
|
||||||
|
ErrCodeAPIRateLimit ErrorCode = "api_rate_limit"
|
||||||
|
ErrCodeAPITimeout ErrorCode = "api_timeout"
|
||||||
|
|
||||||
|
// Validation errors
|
||||||
|
ErrCodeInvalidDashboard ErrorCode = "invalid_dashboard"
|
||||||
|
ErrCodeUnsupportedDashVersion ErrorCode = "unsupported_dashboard_version"
|
||||||
|
ErrCodeInvalidQuery ErrorCode = "invalid_query"
|
||||||
|
|
||||||
|
// Internal errors
|
||||||
|
ErrCodeInternal ErrorCode = "internal_error"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidationError represents a structured error with context
|
||||||
|
type ValidationError struct {
|
||||||
|
Code ErrorCode
|
||||||
|
Message string
|
||||||
|
Details map[string]interface{}
|
||||||
|
StatusCode int
|
||||||
|
Cause error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements the error interface
|
||||||
|
func (e *ValidationError) Error() string {
|
||||||
|
if e.Cause != nil {
|
||||||
|
return fmt.Sprintf("%s: %s (caused by: %v)", e.Code, e.Message, e.Cause)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s: %s", e.Code, e.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap implements error unwrapping
|
||||||
|
func (e *ValidationError) Unwrap() error {
|
||||||
|
return e.Cause
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewValidationError creates a new ValidationError
|
||||||
|
func NewValidationError(code ErrorCode, message string, statusCode int) *ValidationError {
|
||||||
|
return &ValidationError{
|
||||||
|
Code: code,
|
||||||
|
Message: message,
|
||||||
|
StatusCode: statusCode,
|
||||||
|
Details: make(map[string]interface{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCause adds the underlying error cause
|
||||||
|
func (e *ValidationError) WithCause(err error) *ValidationError {
|
||||||
|
e.Cause = err
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDetail adds contextual information
|
||||||
|
func (e *ValidationError) WithDetail(key string, value interface{}) *ValidationError {
|
||||||
|
e.Details[key] = value
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common error constructors
|
||||||
|
|
||||||
|
// NewDatasourceNotFoundError creates an error for datasource not found
|
||||||
|
func NewDatasourceNotFoundError(uid string, namespace string) *ValidationError {
|
||||||
|
return NewValidationError(
|
||||||
|
ErrCodeDatasourceNotFound,
|
||||||
|
fmt.Sprintf("datasource not found: %s", uid),
|
||||||
|
http.StatusNotFound,
|
||||||
|
).WithDetail("datasourceUID", uid).WithDetail("namespace", namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDatasourceWrongTypeError creates an error for wrong datasource type
|
||||||
|
func NewDatasourceWrongTypeError(uid string, expectedType string, actualType string) *ValidationError {
|
||||||
|
return NewValidationError(
|
||||||
|
ErrCodeDatasourceWrongType,
|
||||||
|
fmt.Sprintf("datasource %s has wrong type: expected %s, got %s", uid, expectedType, actualType),
|
||||||
|
http.StatusBadRequest,
|
||||||
|
).WithDetail("datasourceUID", uid).
|
||||||
|
WithDetail("expectedType", expectedType).
|
||||||
|
WithDetail("actualType", actualType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDatasourceUnreachableError creates an error for unreachable datasource
|
||||||
|
func NewDatasourceUnreachableError(uid string, url string, cause error) *ValidationError {
|
||||||
|
return NewValidationError(
|
||||||
|
ErrCodeDatasourceUnreachable,
|
||||||
|
fmt.Sprintf("datasource %s at %s is unreachable", uid, url),
|
||||||
|
http.StatusServiceUnavailable,
|
||||||
|
).WithDetail("datasourceUID", uid).
|
||||||
|
WithDetail("url", url).
|
||||||
|
WithCause(cause)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAPIUnavailableError creates an error for unavailable API
|
||||||
|
func NewAPIUnavailableError(statusCode int, responseBody string, cause error) *ValidationError {
|
||||||
|
return NewValidationError(
|
||||||
|
ErrCodeAPIUnavailable,
|
||||||
|
fmt.Sprintf("Prometheus API returned status %d", statusCode),
|
||||||
|
http.StatusBadGateway,
|
||||||
|
).WithDetail("upstreamStatus", statusCode).
|
||||||
|
WithDetail("responseBody", responseBody).
|
||||||
|
WithCause(cause)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAPIInvalidResponseError creates an error for invalid API response
|
||||||
|
func NewAPIInvalidResponseError(message string, cause error) *ValidationError {
|
||||||
|
return NewValidationError(
|
||||||
|
ErrCodeAPIInvalidResponse,
|
||||||
|
fmt.Sprintf("Prometheus API returned invalid response: %s", message),
|
||||||
|
http.StatusBadGateway,
|
||||||
|
).WithCause(cause)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAPITimeoutError creates an error for API timeout
|
||||||
|
func NewAPITimeoutError(url string, cause error) *ValidationError {
|
||||||
|
return NewValidationError(
|
||||||
|
ErrCodeAPITimeout,
|
||||||
|
fmt.Sprintf("request to %s timed out", url),
|
||||||
|
http.StatusGatewayTimeout,
|
||||||
|
).WithDetail("url", url).
|
||||||
|
WithCause(cause)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDatasourceAuthError creates an error for authentication failures
|
||||||
|
func NewDatasourceAuthError(uid string, statusCode int) *ValidationError {
|
||||||
|
return NewValidationError(
|
||||||
|
ErrCodeDatasourceAuth,
|
||||||
|
fmt.Sprintf("authentication failed for datasource %s (status %d)", uid, statusCode),
|
||||||
|
http.StatusUnauthorized,
|
||||||
|
).WithDetail("datasourceUID", uid).
|
||||||
|
WithDetail("upstreamStatus", statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValidationError checks if an error is a ValidationError
|
||||||
|
func IsValidationError(err error) bool {
|
||||||
|
var validationErr *ValidationError
|
||||||
|
return errors.As(err, &validationErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValidationError extracts a ValidationError from an error chain
|
||||||
|
func GetValidationError(err error) *ValidationError {
|
||||||
|
var validationErr *ValidationError
|
||||||
|
if errors.As(err, &validationErr) {
|
||||||
|
return validationErr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHTTPStatusCode returns the appropriate HTTP status code for an error
|
||||||
|
func GetHTTPStatusCode(err error) int {
|
||||||
|
if validationErr := GetValidationError(err); validationErr != nil {
|
||||||
|
return validationErr.StatusCode
|
||||||
|
}
|
||||||
|
return http.StatusInternalServerError
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewDatasourceNotFoundError(t *testing.T) {
|
||||||
|
err := NewDatasourceNotFoundError("test-uid", "org-1")
|
||||||
|
|
||||||
|
require.Equal(t, ErrCodeDatasourceNotFound, err.Code)
|
||||||
|
require.Equal(t, http.StatusNotFound, err.StatusCode)
|
||||||
|
require.Equal(t, "test-uid", err.Details["datasourceUID"])
|
||||||
|
require.Equal(t, "org-1", err.Details["namespace"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewDatasourceWrongTypeError(t *testing.T) {
|
||||||
|
err := NewDatasourceWrongTypeError("test-uid", "prometheus", "influxdb")
|
||||||
|
|
||||||
|
require.Equal(t, ErrCodeDatasourceWrongType, err.Code)
|
||||||
|
require.Equal(t, http.StatusBadRequest, err.StatusCode)
|
||||||
|
require.Equal(t, "prometheus", err.Details["expectedType"])
|
||||||
|
require.Equal(t, "influxdb", err.Details["actualType"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewDatasourceUnreachableError(t *testing.T) {
|
||||||
|
cause := errors.New("connection refused")
|
||||||
|
err := NewDatasourceUnreachableError("test-uid", "http://localhost:9090", cause)
|
||||||
|
|
||||||
|
require.Equal(t, ErrCodeDatasourceUnreachable, err.Code)
|
||||||
|
require.Equal(t, http.StatusServiceUnavailable, err.StatusCode)
|
||||||
|
require.Equal(t, cause, err.Cause)
|
||||||
|
require.Equal(t, "http://localhost:9090", err.Details["url"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewAPIUnavailableError(t *testing.T) {
|
||||||
|
err := NewAPIUnavailableError(503, "service unavailable", nil)
|
||||||
|
|
||||||
|
require.Equal(t, ErrCodeAPIUnavailable, err.Code)
|
||||||
|
require.Equal(t, http.StatusBadGateway, err.StatusCode)
|
||||||
|
require.Equal(t, 503, err.Details["upstreamStatus"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewAPIInvalidResponseError(t *testing.T) {
|
||||||
|
cause := errors.New("invalid JSON")
|
||||||
|
err := NewAPIInvalidResponseError("missing data field", cause)
|
||||||
|
|
||||||
|
require.Equal(t, ErrCodeAPIInvalidResponse, err.Code)
|
||||||
|
require.Equal(t, http.StatusBadGateway, err.StatusCode)
|
||||||
|
require.Equal(t, cause, err.Cause)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewAPITimeoutError(t *testing.T) {
|
||||||
|
cause := errors.New("context deadline exceeded")
|
||||||
|
err := NewAPITimeoutError("http://localhost:9090/api/v1/query", cause)
|
||||||
|
|
||||||
|
require.Equal(t, ErrCodeAPITimeout, err.Code)
|
||||||
|
require.Equal(t, http.StatusGatewayTimeout, err.StatusCode)
|
||||||
|
require.Equal(t, cause, err.Cause)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewDatasourceAuthError(t *testing.T) {
|
||||||
|
err := NewDatasourceAuthError("test-uid", 401)
|
||||||
|
|
||||||
|
require.Equal(t, ErrCodeDatasourceAuth, err.Code)
|
||||||
|
require.Equal(t, http.StatusUnauthorized, err.StatusCode)
|
||||||
|
require.Equal(t, 401, err.Details["upstreamStatus"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidationErrorChaining(t *testing.T) {
|
||||||
|
cause := errors.New("network error")
|
||||||
|
err := NewValidationError(ErrCodeInternal, "test error", http.StatusInternalServerError).
|
||||||
|
WithCause(cause).
|
||||||
|
WithDetail("key1", "value1").
|
||||||
|
WithDetail("key2", 123)
|
||||||
|
|
||||||
|
require.Equal(t, cause, err.Cause)
|
||||||
|
require.Equal(t, "value1", err.Details["key1"])
|
||||||
|
require.Equal(t, 123, err.Details["key2"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsValidationError(t *testing.T) {
|
||||||
|
validationErr := NewDatasourceNotFoundError("test-uid", "org-1")
|
||||||
|
regularErr := errors.New("regular error")
|
||||||
|
|
||||||
|
require.True(t, IsValidationError(validationErr), "expected IsValidationError to return true for ValidationError")
|
||||||
|
require.False(t, IsValidationError(regularErr), "expected IsValidationError to return false for regular error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetValidationError(t *testing.T) {
|
||||||
|
validationErr := NewDatasourceNotFoundError("test-uid", "org-1")
|
||||||
|
regularErr := errors.New("regular error")
|
||||||
|
|
||||||
|
retrieved := GetValidationError(validationErr)
|
||||||
|
require.NotNil(t, retrieved, "expected GetValidationError to return the ValidationError")
|
||||||
|
require.Equal(t, ErrCodeDatasourceNotFound, retrieved.Code)
|
||||||
|
|
||||||
|
retrieved = GetValidationError(regularErr)
|
||||||
|
require.Nil(t, retrieved, "expected GetValidationError to return nil for regular error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetHTTPStatusCode(t *testing.T) {
|
||||||
|
validationErr := NewDatasourceNotFoundError("test-uid", "org-1")
|
||||||
|
regularErr := errors.New("regular error")
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusNotFound, GetHTTPStatusCode(validationErr))
|
||||||
|
require.Equal(t, http.StatusInternalServerError, GetHTTPStatusCode(regularErr), "expected default status code for regular error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorUnwrap(t *testing.T) {
|
||||||
|
cause := errors.New("underlying error")
|
||||||
|
err := NewDatasourceUnreachableError("test-uid", "http://localhost:9090", cause)
|
||||||
|
|
||||||
|
require.Equal(t, cause, errors.Unwrap(err), "expected Unwrap to return the cause")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorErrorMethod(t *testing.T) {
|
||||||
|
// Test without cause
|
||||||
|
err1 := NewDatasourceNotFoundError("test-uid", "org-1")
|
||||||
|
require.NotEmpty(t, err1.Error(), "expected non-empty error message")
|
||||||
|
|
||||||
|
// Test with cause
|
||||||
|
cause := errors.New("underlying error")
|
||||||
|
err2 := NewDatasourceUnreachableError("test-uid", "http://localhost:9090", cause)
|
||||||
|
errMsg2 := err2.Error()
|
||||||
|
require.NotEmpty(t, errMsg2, "expected non-empty error message")
|
||||||
|
require.Contains(t, errMsg2, "underlying error", "error message should include cause")
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
package prometheus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/apps/dashvalidator/pkg/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fetcher fetches available metrics from a Prometheus datasource
|
||||||
|
type Fetcher struct{}
|
||||||
|
|
||||||
|
// NewFetcher creates a new Prometheus metrics fetcher
|
||||||
|
func NewFetcher() *Fetcher {
|
||||||
|
return &Fetcher{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prometheusResponse represents the Prometheus API response structure
|
||||||
|
type prometheusResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data []string `json:"data"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchMetrics queries Prometheus to get all available metric names
|
||||||
|
// It uses the /api/v1/label/__name__/values endpoint
|
||||||
|
// The provided HTTP client should have proper authentication configured
|
||||||
|
func (f *Fetcher) FetchMetrics(ctx context.Context, datasourceURL string, client *http.Client) ([]string, error) {
|
||||||
|
// Build the API URL
|
||||||
|
baseURL, err := url.Parse(datasourceURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, validator.NewValidationError(
|
||||||
|
validator.ErrCodeDatasourceConfig,
|
||||||
|
"invalid datasource URL",
|
||||||
|
http.StatusBadRequest,
|
||||||
|
).WithCause(err).WithDetail("url", datasourceURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append Prometheus API endpoint to base URL path using path.Join
|
||||||
|
// This correctly handles datasources with existing paths (e.g., /api/prom)
|
||||||
|
endpoint := "api/v1/label/__name__/values"
|
||||||
|
baseURL.Path = path.Join(baseURL.Path, endpoint)
|
||||||
|
|
||||||
|
// Create the request
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, validator.NewValidationError(
|
||||||
|
validator.ErrCodeInternal,
|
||||||
|
"failed to create HTTP request",
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
).WithCause(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the request using the provided authenticated client
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
// Check if it's a timeout error
|
||||||
|
if errors.Is(err, context.DeadlineExceeded) || strings.Contains(err.Error(), "timeout") {
|
||||||
|
return nil, validator.NewAPITimeoutError(baseURL.String(), err)
|
||||||
|
}
|
||||||
|
// Network or connection error - datasource is unreachable
|
||||||
|
return nil, validator.NewDatasourceUnreachableError("", datasourceURL, err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Read response body for error reporting
|
||||||
|
body, readErr := io.ReadAll(resp.Body)
|
||||||
|
if readErr != nil {
|
||||||
|
body = []byte("<unable to read response body>")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check HTTP status code
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case http.StatusOK:
|
||||||
|
// Success - continue to parse response
|
||||||
|
case http.StatusUnauthorized, http.StatusForbidden:
|
||||||
|
// Authentication or authorization failure
|
||||||
|
return nil, validator.NewDatasourceAuthError("", resp.StatusCode).
|
||||||
|
WithDetail("url", baseURL.String()).
|
||||||
|
WithDetail("responseBody", string(body))
|
||||||
|
case http.StatusNotFound:
|
||||||
|
// Endpoint not found - might not be a valid Prometheus instance
|
||||||
|
return nil, validator.NewAPIUnavailableError(
|
||||||
|
resp.StatusCode,
|
||||||
|
string(body),
|
||||||
|
fmt.Errorf("endpoint not found - this may not be a valid Prometheus datasource"),
|
||||||
|
).WithDetail("url", baseURL.String())
|
||||||
|
case http.StatusTooManyRequests:
|
||||||
|
// Rate limiting
|
||||||
|
return nil, validator.NewValidationError(
|
||||||
|
validator.ErrCodeAPIRateLimit,
|
||||||
|
"Prometheus API rate limit exceeded",
|
||||||
|
http.StatusTooManyRequests,
|
||||||
|
).WithDetail("url", baseURL.String()).WithDetail("responseBody", string(body))
|
||||||
|
case http.StatusServiceUnavailable, http.StatusBadGateway, http.StatusGatewayTimeout:
|
||||||
|
// Upstream service is down or unavailable
|
||||||
|
return nil, validator.NewAPIUnavailableError(resp.StatusCode, string(body), nil).
|
||||||
|
WithDetail("url", baseURL.String())
|
||||||
|
default:
|
||||||
|
// Other error status codes
|
||||||
|
return nil, validator.NewAPIUnavailableError(resp.StatusCode, string(body), nil).
|
||||||
|
WithDetail("url", baseURL.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the response JSON
|
||||||
|
var promResp prometheusResponse
|
||||||
|
if err := json.Unmarshal(body, &promResp); err != nil {
|
||||||
|
return nil, validator.NewAPIInvalidResponseError(
|
||||||
|
"response is not valid JSON",
|
||||||
|
err,
|
||||||
|
).WithDetail("url", baseURL.String()).WithDetail("responseBody", string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Prometheus API status field
|
||||||
|
if promResp.Status != "success" {
|
||||||
|
errorMsg := promResp.Error
|
||||||
|
if errorMsg == "" {
|
||||||
|
errorMsg = "unknown error"
|
||||||
|
}
|
||||||
|
return nil, validator.NewAPIInvalidResponseError(
|
||||||
|
fmt.Sprintf("Prometheus API returned error status: %s", errorMsg),
|
||||||
|
nil,
|
||||||
|
).WithDetail("url", baseURL.String()).WithDetail("prometheusError", errorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that we got data
|
||||||
|
if promResp.Data == nil {
|
||||||
|
return nil, validator.NewAPIInvalidResponseError(
|
||||||
|
"response missing 'data' field",
|
||||||
|
nil,
|
||||||
|
).WithDetail("url", baseURL.String()).WithDetail("responseBody", string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
return promResp.Data, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package prometheus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/prometheus/prometheus/promql/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parser extracts metric names from PromQL queries
|
||||||
|
type Parser struct{}
|
||||||
|
|
||||||
|
// NewParser creates a new PromQL parser
|
||||||
|
func NewParser() *Parser {
|
||||||
|
return &Parser{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractMetrics parses a PromQL query and extracts all metric names
|
||||||
|
// For example: "rate(http_requests_total[5m])" returns ["http_requests_total"]
|
||||||
|
func (p *Parser) ExtractMetrics(query string) ([]string, error) {
|
||||||
|
// Parse the PromQL expression
|
||||||
|
expr, err := parser.ParseExpr(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse PromQL query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract metric names by walking the AST
|
||||||
|
metrics := make(map[string]bool) // Use map to deduplicate
|
||||||
|
parser.Inspect(expr, func(node parser.Node, _ []parser.Node) error {
|
||||||
|
// VectorSelector represents a metric selector like "up" or "up{job="foo"}"
|
||||||
|
if vs, ok := node.(*parser.VectorSelector); ok {
|
||||||
|
metrics[vs.Name] = true
|
||||||
|
}
|
||||||
|
// MatrixSelector represents range queries like "up[5m]"
|
||||||
|
if ms, ok := node.(*parser.MatrixSelector); ok {
|
||||||
|
if vs, ok := ms.VectorSelector.(*parser.VectorSelector); ok {
|
||||||
|
metrics[vs.Name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// Convert map to slice
|
||||||
|
result := make([]string, 0, len(metrics))
|
||||||
|
for metric := range metrics {
|
||||||
|
result = append(result, metric)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
package prometheus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExtractMetrics(t *testing.T) {
|
||||||
|
parser := NewParser()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
query string
|
||||||
|
expected []string
|
||||||
|
expectError bool
|
||||||
|
errorContains string
|
||||||
|
}{
|
||||||
|
// Category 1: Basic Extraction (3 tests - covers AST node types)
|
||||||
|
{
|
||||||
|
name: "simple metric",
|
||||||
|
query: "up",
|
||||||
|
expected: []string{"up"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "metric with labels",
|
||||||
|
query: `up{job="api"}`,
|
||||||
|
expected: []string{"up"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "range selector",
|
||||||
|
query: "up[5m]",
|
||||||
|
expected: []string{"up"},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Category 2: Function Composition (2 tests - nested complexity)
|
||||||
|
{
|
||||||
|
name: "single function",
|
||||||
|
query: "rate(http_requests_total[5m])",
|
||||||
|
expected: []string{"http_requests_total"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nested functions",
|
||||||
|
query: "sum(rate(requests[5m]))",
|
||||||
|
expected: []string{"requests"},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Category 3: Binary Operations (2 tests - multiple metrics)
|
||||||
|
{
|
||||||
|
name: "two metrics",
|
||||||
|
query: "metric_a + metric_b",
|
||||||
|
expected: []string{"metric_a", "metric_b"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "three metrics nested",
|
||||||
|
query: "(a + b) / c",
|
||||||
|
expected: []string{"a", "b", "c"},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Category 4: Deduplication (1 test - critical behavior)
|
||||||
|
{
|
||||||
|
name: "duplicate metric",
|
||||||
|
query: "up + up",
|
||||||
|
expected: []string{"up"},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Category 5: Edge Cases (2 tests - boundary behaviors)
|
||||||
|
{
|
||||||
|
name: "no metrics (literals only)",
|
||||||
|
query: "1 + 1",
|
||||||
|
expected: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "built-in function without metric",
|
||||||
|
query: "time()",
|
||||||
|
expected: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "comparison operator",
|
||||||
|
query: "a > 5",
|
||||||
|
expected: []string{"a"},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Category 6: Real Dashboard Patterns (3 tests - production queries)
|
||||||
|
{
|
||||||
|
name: "binary op with function and labels",
|
||||||
|
query: `(time() - process_start_time_seconds{job="prometheus", instance=~"$node"})`,
|
||||||
|
expected: []string{"process_start_time_seconds"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "rate with regex label matcher",
|
||||||
|
query: `rate(prometheus_local_storage_ingested_samples_total{instance=~"$node"}[5m])`,
|
||||||
|
expected: []string{"prometheus_local_storage_ingested_samples_total"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "metric with negation and multiple labels",
|
||||||
|
query: `prometheus_target_interval_length_seconds{quantile!="0.01", quantile!="0.05", instance=~"$node"}`,
|
||||||
|
expected: []string{"prometheus_target_interval_length_seconds"},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Category 7: Error Handling (2 tests - validation)
|
||||||
|
{
|
||||||
|
name: "empty string",
|
||||||
|
query: "",
|
||||||
|
expectError: true,
|
||||||
|
errorContains: "parse",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "malformed expression",
|
||||||
|
query: "{{invalid}}",
|
||||||
|
expectError: true,
|
||||||
|
errorContains: "parse",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := parser.ExtractMetrics(tt.query)
|
||||||
|
|
||||||
|
// Check error expectation
|
||||||
|
if tt.expectError {
|
||||||
|
require.Error(t, err, "Expected error for query: %q", tt.query)
|
||||||
|
if tt.errorContains != "" {
|
||||||
|
require.ErrorContains(t, err, tt.errorContains,
|
||||||
|
"Error should contain %q for query: %q", tt.errorContains, tt.query)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err, "Unexpected error for query: %q", tt.query)
|
||||||
|
|
||||||
|
// Check result matches expected (order-independent for multiple metrics)
|
||||||
|
require.ElementsMatch(t, tt.expected, result,
|
||||||
|
"ExtractMetrics(%q) returned unexpected metrics", tt.query)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
package prometheus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/apps/dashvalidator/pkg/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register Prometheus validator on package import
|
||||||
|
func init() {
|
||||||
|
validator.RegisterValidator("prometheus", func() validator.DatasourceValidator {
|
||||||
|
return NewValidator()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validator implements validator.DatasourceValidator for Prometheus datasources
|
||||||
|
type Validator struct {
|
||||||
|
parser *Parser
|
||||||
|
fetcher *Fetcher
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewValidator creates a new Prometheus validator
|
||||||
|
func NewValidator() validator.DatasourceValidator {
|
||||||
|
return &Validator{
|
||||||
|
parser: NewParser(),
|
||||||
|
fetcher: NewFetcher(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateQueries validates Prometheus queries against the datasource
|
||||||
|
func (v *Validator) ValidateQueries(ctx context.Context, queries []validator.Query, datasource validator.Datasource) (*validator.ValidationResult, error) {
|
||||||
|
fmt.Printf("[DEBUG PROM] Starting validation for %d queries against datasource %s\n", len(queries), datasource.URL)
|
||||||
|
|
||||||
|
result := &validator.ValidationResult{
|
||||||
|
TotalQueries: len(queries),
|
||||||
|
QueryBreakdown: make([]validator.QueryResult, 0, len(queries)),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Parse all queries to extract metrics
|
||||||
|
allMetrics := make(map[string]bool) // Use map to deduplicate
|
||||||
|
queryMetrics := make(map[int][]string)
|
||||||
|
|
||||||
|
for i, query := range queries {
|
||||||
|
fmt.Printf("[DEBUG PROM] Parsing query %d: %s\n", i, query.QueryText)
|
||||||
|
metrics, err := v.parser.ExtractMetrics(query.QueryText)
|
||||||
|
if err != nil {
|
||||||
|
// If we can't parse the query, we still continue with others
|
||||||
|
// but we don't count this query as "checked"
|
||||||
|
fmt.Printf("[DEBUG PROM] Failed to parse query %d: %v\n", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Printf("[DEBUG PROM] Extracted %d metrics from query %d: %v\n", len(metrics), i, metrics)
|
||||||
|
result.CheckedQueries++
|
||||||
|
queryMetrics[i] = metrics
|
||||||
|
|
||||||
|
// Add to global metrics set
|
||||||
|
for _, metric := range metrics {
|
||||||
|
allMetrics[metric] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert map to slice for fetcher
|
||||||
|
metricsToCheck := make([]string, 0, len(allMetrics))
|
||||||
|
for metric := range allMetrics {
|
||||||
|
metricsToCheck = append(metricsToCheck, metric)
|
||||||
|
}
|
||||||
|
result.TotalMetrics = len(metricsToCheck)
|
||||||
|
|
||||||
|
fmt.Printf("[DEBUG PROM] Total metrics to check: %d - %v\n", len(metricsToCheck), metricsToCheck)
|
||||||
|
|
||||||
|
// Step 2: Fetch available metrics from Prometheus
|
||||||
|
fmt.Printf("[DEBUG PROM] Fetching available metrics from %s\n", datasource.URL)
|
||||||
|
availableMetrics, err := v.fetcher.FetchMetrics(ctx, datasource.URL, datasource.HTTPClient)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("[DEBUG PROM] Failed to fetch metrics: %v\n", err)
|
||||||
|
return nil, fmt.Errorf("failed to fetch metrics from Prometheus: %w", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("[DEBUG PROM] Fetched %d available metrics from Prometheus\n", len(availableMetrics))
|
||||||
|
|
||||||
|
// Build a set for O(1) lookup
|
||||||
|
availableSet := make(map[string]bool)
|
||||||
|
for _, metric := range availableMetrics {
|
||||||
|
availableSet[metric] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Calculate compatibility
|
||||||
|
missingMetricsMap := make(map[string]bool)
|
||||||
|
for _, metric := range metricsToCheck {
|
||||||
|
if !availableSet[metric] {
|
||||||
|
missingMetricsMap[metric] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.FoundMetrics = result.TotalMetrics - len(missingMetricsMap)
|
||||||
|
|
||||||
|
// Convert missing metrics map to slice
|
||||||
|
result.MissingMetrics = make([]string, 0, len(missingMetricsMap))
|
||||||
|
for metric := range missingMetricsMap {
|
||||||
|
result.MissingMetrics = append(result.MissingMetrics, metric)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: Build per-query breakdown
|
||||||
|
for i, query := range queries {
|
||||||
|
metrics, ok := queryMetrics[i]
|
||||||
|
if !ok {
|
||||||
|
// Query wasn't parsed successfully, skip
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
queryResult := validator.QueryResult{
|
||||||
|
PanelTitle: query.PanelTitle,
|
||||||
|
PanelID: query.PanelID,
|
||||||
|
QueryRefID: query.RefID,
|
||||||
|
TotalMetrics: len(metrics),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check which metrics from this query are missing
|
||||||
|
queryMissing := make([]string, 0)
|
||||||
|
for _, metric := range metrics {
|
||||||
|
if missingMetricsMap[metric] {
|
||||||
|
queryMissing = append(queryMissing, metric)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queryResult.MissingMetrics = queryMissing
|
||||||
|
queryResult.FoundMetrics = queryResult.TotalMetrics - len(queryMissing)
|
||||||
|
|
||||||
|
// Calculate query-level compatibility score
|
||||||
|
if queryResult.TotalMetrics > 0 {
|
||||||
|
queryResult.CompatibilityScore = float64(queryResult.FoundMetrics) / float64(queryResult.TotalMetrics)
|
||||||
|
} else {
|
||||||
|
queryResult.CompatibilityScore = 1.0 // No metrics = perfect compatibility
|
||||||
|
}
|
||||||
|
|
||||||
|
result.QueryBreakdown = append(result.QueryBreakdown, queryResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 5: Calculate overall compatibility score
|
||||||
|
if result.TotalMetrics > 0 {
|
||||||
|
result.CompatibilityScore = float64(result.FoundMetrics) / float64(result.TotalMetrics)
|
||||||
|
} else {
|
||||||
|
result.CompatibilityScore = 1.0 // No metrics = perfect compatibility
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("[DEBUG PROM] Validation complete! Score: %.2f, Found: %d/%d metrics\n",
|
||||||
|
result.CompatibilityScore, result.FoundMetrics, result.TotalMetrics)
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DatasourceValidator validates dashboard queries against a datasource
|
||||||
|
// Implementations exist per datasource type (Prometheus, MySQL, etc.)
|
||||||
|
type DatasourceValidator interface {
|
||||||
|
// ValidateQueries checks if queries are compatible with the datasource
|
||||||
|
ValidateQueries(ctx context.Context, queries []Query, datasource Datasource) (*ValidationResult, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query represents a dashboard query to validate
|
||||||
|
type Query struct {
|
||||||
|
RefID string // Query reference ID (A, B, C, etc.)
|
||||||
|
QueryText string // The actual query text (PromQL, SQL, etc.)
|
||||||
|
PanelTitle string // Panel title for user-friendly reporting
|
||||||
|
PanelID int // Panel ID for reference
|
||||||
|
}
|
||||||
|
|
||||||
|
// Datasource contains connection information for a datasource
|
||||||
|
type Datasource struct {
|
||||||
|
UID string // Datasource UID from dashboard
|
||||||
|
Type string // Datasource type (prometheus, mysql, etc.)
|
||||||
|
Name string // Datasource name for reporting
|
||||||
|
URL string // Datasource URL for API calls
|
||||||
|
HTTPClient *http.Client // Authenticated HTTP client for making requests
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidationResult contains validation results for a datasource
|
||||||
|
type ValidationResult struct {
|
||||||
|
TotalQueries int // Total number of queries found
|
||||||
|
CheckedQueries int // Number of queries successfully checked
|
||||||
|
TotalMetrics int // Total metrics/entities referenced
|
||||||
|
FoundMetrics int // Metrics found in datasource
|
||||||
|
MissingMetrics []string // List of missing metrics
|
||||||
|
QueryBreakdown []QueryResult // Per-query results
|
||||||
|
CompatibilityScore float64 // Overall compatibility (0.0 - 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryResult contains validation results for a single query
|
||||||
|
type QueryResult struct {
|
||||||
|
PanelTitle string // Panel title
|
||||||
|
PanelID int // Panel ID
|
||||||
|
QueryRefID string // Query reference ID
|
||||||
|
TotalMetrics int // Metrics in this query
|
||||||
|
FoundMetrics int // Metrics found
|
||||||
|
MissingMetrics []string // Missing metrics for this query
|
||||||
|
CompatibilityScore float64 // Query compatibility (0.0 - 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// validatorRegistry holds registered validator constructors
|
||||||
|
// Validators register themselves using RegisterValidator in their init() functions
|
||||||
|
var validatorRegistry = make(map[string]func() DatasourceValidator)
|
||||||
|
|
||||||
|
// RegisterValidator registers a validator constructor for a datasource type
|
||||||
|
// This is called by validator implementations in their init() functions
|
||||||
|
// Example: validator.RegisterValidator("prometheus", func() validator.DatasourceValidator { return NewValidator() })
|
||||||
|
func RegisterValidator(dsType string, constructor func() DatasourceValidator) {
|
||||||
|
validatorRegistry[dsType] = constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValidator returns a validator for the given datasource type
|
||||||
|
// Returns an error if the datasource type is not supported
|
||||||
|
func GetValidator(dsType string) (DatasourceValidator, error) {
|
||||||
|
constructor, ok := validatorRegistry[dsType]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unsupported datasource type: %s", dsType)
|
||||||
|
}
|
||||||
|
return constructor(), nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsVariableReference(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"dollar brace", "${prometheus}", true},
|
||||||
|
{"dollar simple", "$datasource", true},
|
||||||
|
{"double bracket", "[[prometheus]]", true},
|
||||||
|
{"concrete uid", "abcd1234", false},
|
||||||
|
{"empty string", "", false},
|
||||||
|
{"dollar only", "$", false},
|
||||||
|
{"empty braces", "${}", false},
|
||||||
|
{"number start", "$123", true}, // Changed: Grafana ACCEPTS digits (per \w+ regex)
|
||||||
|
{"all digits", "$999", true}, // New: All digits are valid per \w+
|
||||||
|
{"special chars dash", "$ds-name", false}, // Changed: Grafana REJECTS dashes (not in \w)
|
||||||
|
{"underscore", "$DS_PROMETHEUS", true},
|
||||||
|
{"complex variable", "${DS_PROMETHEUS}", true},
|
||||||
|
{"simple letter", "$p", true},
|
||||||
|
{"with fieldpath", "${var.field}", true}, // New: Test fieldPath syntax
|
||||||
|
{"with format", "[[var:text]]", true}, // New: Test format syntax
|
||||||
|
{"brace with format", "${var:json}", true}, // New: Test brace format syntax
|
||||||
|
{"digit in brackets", "[[123]]", true}, // New: Digits allowed in all patterns
|
||||||
|
{"empty brackets", "[[]]", false}, // New: Empty brackets rejected
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := isVariableReference(tt.input)
|
||||||
|
require.Equal(t, tt.expected, result, "isVariableReference(%q) returned unexpected result", tt.input)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtractVariableName(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"dollar brace", "${prometheus}", "prometheus"},
|
||||||
|
{"dollar simple", "$datasource", "datasource"},
|
||||||
|
{"double bracket", "[[prometheus]]", "prometheus"},
|
||||||
|
{"not variable", "concrete-uid", ""},
|
||||||
|
{"empty", "", ""},
|
||||||
|
{"complex name", "${DS_PROMETHEUS}", "DS_PROMETHEUS"},
|
||||||
|
{"with underscore", "$DS_NAME", "DS_NAME"},
|
||||||
|
{"digit variable", "$123", "123"}, // New: Digits are valid
|
||||||
|
{"with fieldpath", "${var.field}", "var"}, // Changed: Extract only name, not fieldPath
|
||||||
|
{"with format brace", "${var:json}", "var"}, // Changed: Extract only name, not format
|
||||||
|
{"with format bracket", "[[var:text]]", "var"}, // Changed: Extract only name, not format
|
||||||
|
{"fieldpath and format", "${var.field:json}", "var"}, // New: Extract only name from complex syntax
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := extractVariableName(tt.input)
|
||||||
|
require.Equal(t, tt.expected, result, "extractVariableName(%q) returned unexpected result", tt.input)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsPrometheusVariable(t *testing.T) {
|
||||||
|
// Dashboard with Prometheus __inputs
|
||||||
|
dashboardWithPrometheus := map[string]interface{}{
|
||||||
|
"__inputs": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "DS_PROMETHEUS",
|
||||||
|
"type": "datasource",
|
||||||
|
"pluginId": "prometheus",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dashboard with MySQL __inputs
|
||||||
|
dashboardWithMySQL := map[string]interface{}{
|
||||||
|
"__inputs": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "DS_MYSQL",
|
||||||
|
"type": "datasource",
|
||||||
|
"pluginId": "mysql",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dashboard without __inputs
|
||||||
|
dashboardWithoutInputs := map[string]interface{}{
|
||||||
|
"title": "Test Dashboard",
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
varRef string
|
||||||
|
dashboard map[string]interface{}
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"prometheus variable with inputs", "${DS_PROMETHEUS}", dashboardWithPrometheus, true},
|
||||||
|
{"prometheus simple var", "$DS_PROMETHEUS", dashboardWithPrometheus, true},
|
||||||
|
{"mysql variable", "${DS_MYSQL}", dashboardWithMySQL, false},
|
||||||
|
{"not variable", "concrete-uid", dashboardWithPrometheus, false},
|
||||||
|
{"variable without inputs", "${prometheus}", dashboardWithoutInputs, true}, // Fallback to true for MVP
|
||||||
|
{"wrong variable name", "${OTHER}", dashboardWithPrometheus, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := isPrometheusVariable(tt.varRef, tt.dashboard)
|
||||||
|
require.Equal(t, tt.expected, result, "isPrometheusVariable(%q, dashboard) returned unexpected result", tt.varRef)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveDatasourceUID(t *testing.T) {
|
||||||
|
singleUID := "prom-uid-123"
|
||||||
|
|
||||||
|
dashboardWithPrometheus := map[string]interface{}{
|
||||||
|
"__inputs": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "DS_PROMETHEUS",
|
||||||
|
"type": "datasource",
|
||||||
|
"pluginId": "prometheus",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
dashboardWithMySQL := map[string]interface{}{
|
||||||
|
"__inputs": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "DS_MYSQL",
|
||||||
|
"type": "datasource",
|
||||||
|
"pluginId": "mysql",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
uid string
|
||||||
|
dashboard map[string]interface{}
|
||||||
|
expectedUID string
|
||||||
|
description string
|
||||||
|
}{
|
||||||
|
{"concrete uid", "concrete-123", dashboardWithPrometheus, "concrete-123", "should return concrete UID as-is"},
|
||||||
|
{"prometheus variable", "${DS_PROMETHEUS}", dashboardWithPrometheus, singleUID, "should resolve to single datasource UID"},
|
||||||
|
{"prometheus simple var", "$DS_PROMETHEUS", dashboardWithPrometheus, singleUID, "should resolve simple $ syntax"},
|
||||||
|
{"mysql variable", "${DS_MYSQL}", dashboardWithMySQL, "${DS_MYSQL}", "should return non-Prometheus variable as-is"},
|
||||||
|
{"empty uid", "", dashboardWithPrometheus, "", "should return empty string as-is"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := resolveDatasourceUID(tt.uid, singleUID, tt.dashboard)
|
||||||
|
require.Equal(t, tt.expectedUID, result, "resolveDatasourceUID(%q, %q, dashboard): %s", tt.uid, singleUID, tt.description)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -40,7 +40,7 @@ export interface ManagedFieldsEntry {
|
|||||||
subresource?: string;
|
subresource?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Investigation {
|
export interface DashboardCompatibilityScore {
|
||||||
kind: string;
|
kind: string;
|
||||||
apiVersion: string;
|
apiVersion: string;
|
||||||
metadata: Metadata;
|
metadata: Metadata;
|
||||||
Generated
+42
@@ -0,0 +1,42 @@
|
|||||||
|
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||||
|
|
||||||
|
// DataSourceMapping specifies a datasource to validate dashboard queries against.
|
||||||
|
// Maps logical datasource references in the dashboard to actual datasource instances.
|
||||||
|
export interface DataSourceMapping {
|
||||||
|
// Unique identifier of the datasource instance.
|
||||||
|
// Example: "prometheus-prod-us-west"
|
||||||
|
uid: string;
|
||||||
|
// Type of datasource plugin.
|
||||||
|
// MVP: Only "prometheus" supported.
|
||||||
|
// Future: "mysql", "postgres", "elasticsearch", etc.
|
||||||
|
type: string;
|
||||||
|
// Optional human-readable name for display in results.
|
||||||
|
// If not provided, UID will be used in error messages.
|
||||||
|
// Example: "Production Prometheus (US-West)"
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultDataSourceMapping = (): DataSourceMapping => ({
|
||||||
|
uid: "",
|
||||||
|
type: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface Spec {
|
||||||
|
// Complete dashboard JSON object to validate.
|
||||||
|
// Must be a v1 dashboard schema (contains "panels" array).
|
||||||
|
// v2 dashboards (with "elements" structure) are not yet supported.
|
||||||
|
dashboardJson: Record<string, any>;
|
||||||
|
// Array of datasources to validate against.
|
||||||
|
// The validator will check dashboard queries against each datasource
|
||||||
|
// and provide per-datasource compatibility results.
|
||||||
|
//
|
||||||
|
// MVP: Only single datasource supported (array length = 1), Prometheus type only.
|
||||||
|
// Future: Will support multiple datasources for dashboards with mixed queries.
|
||||||
|
datasourceMappings: DataSourceMapping[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultSpec = (): Spec => ({
|
||||||
|
dashboardJson: {},
|
||||||
|
datasourceMappings: [],
|
||||||
|
});
|
||||||
|
|
||||||
Generated
+142
@@ -0,0 +1,142 @@
|
|||||||
|
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||||
|
|
||||||
|
// DataSourceResult contains validation results for a single datasource.
|
||||||
|
// Provides aggregate statistics and per-query breakdown of compatibility.
|
||||||
|
export interface DataSourceResult {
|
||||||
|
// Datasource UID that was validated (matches DataSourceMapping.uid)
|
||||||
|
uid: string;
|
||||||
|
// Datasource type (matches DataSourceMapping.type)
|
||||||
|
type: string;
|
||||||
|
// Optional display name (matches DataSourceMapping.name if provided)
|
||||||
|
name?: string;
|
||||||
|
// Total number of queries in the dashboard targeting this datasource.
|
||||||
|
// Includes all panel targets/queries that reference this datasource.
|
||||||
|
totalQueries: number;
|
||||||
|
// Number of queries successfully validated.
|
||||||
|
// May be less than totalQueries if some queries couldn't be parsed.
|
||||||
|
checkedQueries: number;
|
||||||
|
// Total number of unique metrics/identifiers referenced across all queries.
|
||||||
|
// For Prometheus: metric names extracted from PromQL expressions.
|
||||||
|
// For SQL datasources: table and column names.
|
||||||
|
totalMetrics: number;
|
||||||
|
// Number of metrics that exist in the datasource schema.
|
||||||
|
// foundMetrics <= totalMetrics
|
||||||
|
foundMetrics: number;
|
||||||
|
// Array of metric names that were referenced but don't exist.
|
||||||
|
// Useful for debugging why a dashboard shows "no data".
|
||||||
|
// Example for Prometheus: ["http_requests_total", "api_latency_seconds"]
|
||||||
|
missingMetrics: string[];
|
||||||
|
// Per-query breakdown showing which specific queries have issues.
|
||||||
|
// One entry per query target (refId: "A", "B", "C", etc.) in each panel.
|
||||||
|
// Allows pinpointing exactly which panel/query needs fixing.
|
||||||
|
queryBreakdown: QueryBreakdown[];
|
||||||
|
// Overall compatibility score for this datasource (0-100).
|
||||||
|
// Calculated as: (foundMetrics / totalMetrics) * 100
|
||||||
|
// Used to calculate the global compatibilityScore in status.
|
||||||
|
compatibilityScore: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultDataSourceResult = (): DataSourceResult => ({
|
||||||
|
uid: "",
|
||||||
|
type: "",
|
||||||
|
totalQueries: 0,
|
||||||
|
checkedQueries: 0,
|
||||||
|
totalMetrics: 0,
|
||||||
|
foundMetrics: 0,
|
||||||
|
missingMetrics: [],
|
||||||
|
queryBreakdown: [],
|
||||||
|
compatibilityScore: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
// QueryBreakdown provides compatibility details for a single query within a panel.
|
||||||
|
// Granular per-query results allow users to identify exactly which queries need fixing.
|
||||||
|
//
|
||||||
|
// Note: A panel can have multiple queries (refId: "A", "B", "C", etc.),
|
||||||
|
// so there may be multiple QueryBreakdown entries for the same panelID.
|
||||||
|
export interface QueryBreakdown {
|
||||||
|
// Human-readable panel title for context.
|
||||||
|
// Example: "CPU Usage", "Request Rate"
|
||||||
|
panelTitle: string;
|
||||||
|
// Numeric panel ID from dashboard JSON.
|
||||||
|
// Used to correlate with dashboard structure.
|
||||||
|
panelID: number;
|
||||||
|
// Query identifier within the panel.
|
||||||
|
// Values: "A", "B", "C", etc. (from panel.targets[].refId)
|
||||||
|
// Uniquely identifies which query in a multi-query panel this refers to.
|
||||||
|
queryRefId: string;
|
||||||
|
// Number of unique metrics referenced in this specific query.
|
||||||
|
// For Prometheus: metrics extracted from the PromQL expr.
|
||||||
|
// Example: rate(http_requests_total[5m]) references 1 metric.
|
||||||
|
totalMetrics: number;
|
||||||
|
// Number of those metrics that exist in the datasource.
|
||||||
|
// foundMetrics <= totalMetrics
|
||||||
|
foundMetrics: number;
|
||||||
|
// Array of missing metric names specific to this query.
|
||||||
|
// Helps identify exactly which part of a query expression will fail.
|
||||||
|
// Empty array means query is fully compatible.
|
||||||
|
missingMetrics: string[];
|
||||||
|
// Compatibility percentage for this individual query (0-100).
|
||||||
|
// Calculated as: (foundMetrics / totalMetrics) * 100
|
||||||
|
// 100 = query will work perfectly, 0 = query will return no data.
|
||||||
|
compatibilityScore: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultQueryBreakdown = (): QueryBreakdown => ({
|
||||||
|
panelTitle: "",
|
||||||
|
panelID: 0,
|
||||||
|
queryRefId: "",
|
||||||
|
totalMetrics: 0,
|
||||||
|
foundMetrics: 0,
|
||||||
|
missingMetrics: [],
|
||||||
|
compatibilityScore: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface OperatorState {
|
||||||
|
// lastEvaluation is the ResourceVersion last evaluated
|
||||||
|
lastEvaluation: string;
|
||||||
|
// state describes the state of the lastEvaluation.
|
||||||
|
// It is limited to three possible states for machine evaluation.
|
||||||
|
state: "success" | "in_progress" | "failed";
|
||||||
|
// descriptiveState is an optional more descriptive state field which has no requirements on format
|
||||||
|
descriptiveState?: string;
|
||||||
|
// details contains any extra information that is operator-specific
|
||||||
|
details?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultOperatorState = (): OperatorState => ({
|
||||||
|
lastEvaluation: "",
|
||||||
|
state: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface Status {
|
||||||
|
// Overall compatibility score across all datasources (0-100).
|
||||||
|
// Calculated as: (total found metrics / total referenced metrics) * 100
|
||||||
|
//
|
||||||
|
// Score interpretation:
|
||||||
|
// - 100: Perfect compatibility, all queries will work
|
||||||
|
// - 80-99: Excellent, minor missing metrics
|
||||||
|
// - 50-79: Fair, significant missing metrics
|
||||||
|
// - 0-49: Poor, most queries will fail
|
||||||
|
compatibilityScore: number;
|
||||||
|
// Per-datasource validation results.
|
||||||
|
// Array length matches spec.datasourceMappings.
|
||||||
|
// Each element contains detailed metrics and query-level breakdown.
|
||||||
|
datasourceResults: DataSourceResult[];
|
||||||
|
// ISO 8601 timestamp of when validation was last performed.
|
||||||
|
// Example: "2024-01-15T10:30:00Z"
|
||||||
|
lastChecked?: string;
|
||||||
|
// operatorStates is a map of operator ID to operator state evaluations.
|
||||||
|
// Any operator which consumes this kind SHOULD add its state evaluation information to this field.
|
||||||
|
operatorStates?: Record<string, OperatorState>;
|
||||||
|
// Human-readable summary of validation result.
|
||||||
|
// Examples: "All queries compatible", "3 missing metrics found"
|
||||||
|
message?: string;
|
||||||
|
// additionalFields is reserved for future use
|
||||||
|
additionalFields?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultStatus = (): Status => ({
|
||||||
|
compatibilityScore: 0,
|
||||||
|
datasourceResults: [],
|
||||||
|
});
|
||||||
|
|
||||||
@@ -24,8 +24,6 @@ replace github.com/grafana/grafana/apps/alerting/historian => ../alerting/histor
|
|||||||
|
|
||||||
replace github.com/grafana/grafana/apps/correlations => ../correlations
|
replace github.com/grafana/grafana/apps/correlations => ../correlations
|
||||||
|
|
||||||
replace github.com/grafana/grafana/apps/investigations => ../investigations
|
|
||||||
|
|
||||||
replace github.com/grafana/grafana/apps/logsdrilldown => ../logsdrilldown
|
replace github.com/grafana/grafana/apps/logsdrilldown => ../logsdrilldown
|
||||||
|
|
||||||
replace github.com/grafana/grafana/apps/playlist => ../playlist
|
replace github.com/grafana/grafana/apps/playlist => ../playlist
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
package v0alpha1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (u User) AuthID() string {
|
|
||||||
meta, err := utils.MetaAccessor(&u)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
// TODO: Workaround until we move all definitions
|
|
||||||
// After having all resource definitions here in the app, we can remove this
|
|
||||||
// and we need to change the List authorization to use the MetaAccessor and the GetDeprecatedInternalID method
|
|
||||||
//nolint:staticcheck
|
|
||||||
return fmt.Sprintf("%d", meta.GetDeprecatedInternalID())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s ServiceAccount) AuthID() string {
|
|
||||||
meta, err := utils.MetaAccessor(&s)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
// TODO: Workaround until we move all definitions
|
|
||||||
// After having all resource definitions here in the app, we can remove this
|
|
||||||
// and we need to change the List authorization to use the MetaAccessor and the GetDeprecatedInternalID method
|
|
||||||
//nolint:staticcheck
|
|
||||||
return fmt.Sprintf("%d", meta.GetDeprecatedInternalID())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t Team) AuthID() string {
|
|
||||||
meta, err := utils.MetaAccessor(&t)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
// TODO: Workaround until we move all definitions
|
|
||||||
// After having all resource definitions here in the app, we can remove this
|
|
||||||
// and we need to change the List authorization to use the MetaAccessor and the GetDeprecatedInternalID method
|
|
||||||
//nolint:staticcheck
|
|
||||||
return fmt.Sprintf("%d", meta.GetDeprecatedInternalID())
|
|
||||||
}
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"id": "896312ce-65b0-4b50-ade1-e7f04fa22c66",
|
|
||||||
"title": "Thursday morning investigation",
|
|
||||||
"hasCustomName": false,
|
|
||||||
"isFavorite": false,
|
|
||||||
"collectables": [
|
|
||||||
{
|
|
||||||
"origin": "Explore Logs",
|
|
||||||
"type": "timeseries",
|
|
||||||
"queries": [
|
|
||||||
{
|
|
||||||
"refId": "LABEL_BREAKDOWN_VALUES",
|
|
||||||
"queryType": "range",
|
|
||||||
"editorMode": "code",
|
|
||||||
"supportingQueryType": "grafana-lokiexplore-app",
|
|
||||||
"legendFormat": "{{detected_level}}",
|
|
||||||
"expr": "sum(count_over_time({service_name=\"web_app_1\"} | detected_level != \"\"[$__auto])) by (detected_level)"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timeRange": {
|
|
||||||
"to": "2025-02-13T11:31:20.536Z",
|
|
||||||
"from": "2025-02-13T11:16:20.536Z",
|
|
||||||
"raw": {
|
|
||||||
"from": "now-15m",
|
|
||||||
"to": "now"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"datasource": {
|
|
||||||
"uid": "fe9k7u07b1a0wc"
|
|
||||||
},
|
|
||||||
"url": "http://localhost:3000/a/grafana-lokiexplore-app/explore/service/web_app_1/labels?patterns=%5B%5D&from=now-15m&to=now&var-ds=fe9k7u07b1a0wc&var-filters=service_name%7C%3D%7Cweb_app_1&var-fields=&var-levels=&var-metadata=&var-patterns=&var-lineFilterV2=&var-lineFilters=&urlColumns=%5B%5D&visualizationType=%22logs%22&displayedFields=%5B%5D&timezone=browser&var-all-fields=&var-labelBy=$__all",
|
|
||||||
"id": "LABEL_BREAKDOWN_VALUES_detected_level",
|
|
||||||
"title": "detected_level",
|
|
||||||
"logoPath": "public/plugins/grafana-lokiexplore-app/img/img/logo.svg",
|
|
||||||
"createdAt": "2025-02-13T11:31:23.637Z"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"createdAt": "2025-02-13T11:31:23.636Z",
|
|
||||||
"updatedAt": "2025-02-13T11:31:23.637Z",
|
|
||||||
"viewMode": {
|
|
||||||
"mode": "compact",
|
|
||||||
"showComments": true,
|
|
||||||
"showTooltips": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "e9cf1958-d0ed-46b7-b597-9052c7648656",
|
|
||||||
"title": "Thursday morning investigation",
|
|
||||||
"hasCustomName": false,
|
|
||||||
"isFavorite": false,
|
|
||||||
"collectables": [
|
|
||||||
{
|
|
||||||
"origin": "Explore Logs",
|
|
||||||
"type": "timeseries",
|
|
||||||
"queries": [
|
|
||||||
{
|
|
||||||
"refId": "LABEL_BREAKDOWN_VALUES",
|
|
||||||
"queryType": "range",
|
|
||||||
"editorMode": "code",
|
|
||||||
"supportingQueryType": "grafana-lokiexplore-app",
|
|
||||||
"legendFormat": "{{detected_level}}",
|
|
||||||
"expr": "sum(count_over_time({service_name=\"web_app_1\"} | detected_level != \"\"[$__auto])) by (detected_level)"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timeRange": {
|
|
||||||
"to": "2025-02-13T11:31:20.536Z",
|
|
||||||
"from": "2025-02-13T11:16:20.536Z",
|
|
||||||
"raw": {
|
|
||||||
"from": "now-15m",
|
|
||||||
"to": "now"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"datasource": {
|
|
||||||
"uid": "fe9k7u07b1a0wc"
|
|
||||||
},
|
|
||||||
"url": "http://localhost:3000/a/grafana-lokiexplore-app/explore/service/web_app_1/labels?patterns=%5B%5D&from=now-15m&to=now&var-ds=fe9k7u07b1a0wc&var-filters=service_name%7C%3D%7Cweb_app_1&var-fields=&var-levels=&var-metadata=&var-patterns=&var-lineFilterV2=&var-lineFilters=&urlColumns=%5B%5D&visualizationType=%22logs%22&displayedFields=%5B%5D&timezone=browser&var-all-fields=&var-labelBy=$__all",
|
|
||||||
"id": "LABEL_BREAKDOWN_VALUES_detected_level",
|
|
||||||
"title": "detected_level",
|
|
||||||
"logoPath": "public/plugins/grafana-lokiexplore-app/img/img/logo.svg",
|
|
||||||
"createdAt": "2025-02-13T11:31:23.638Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"origin": "Explore Logs",
|
|
||||||
"type": "timeseries",
|
|
||||||
"queries": [
|
|
||||||
{
|
|
||||||
"refId": "LABEL_BREAKDOWN_VALUES",
|
|
||||||
"queryType": "range",
|
|
||||||
"editorMode": "code",
|
|
||||||
"supportingQueryType": "grafana-lokiexplore-app",
|
|
||||||
"legendFormat": "{{service_name}}",
|
|
||||||
"expr": "sum(count_over_time({service_name=\"web_app_1\",service_name != \"\"} [$__auto])) by (service_name)"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timeRange": {
|
|
||||||
"to": "2025-02-13T11:31:20.536Z",
|
|
||||||
"from": "2025-02-13T11:16:20.536Z",
|
|
||||||
"raw": {
|
|
||||||
"from": "now-15m",
|
|
||||||
"to": "now"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"datasource": {
|
|
||||||
"uid": "fe9k7u07b1a0wc"
|
|
||||||
},
|
|
||||||
"url": "http://localhost:3000/a/grafana-lokiexplore-app/explore/service/web_app_1/labels?patterns=%5B%5D&from=now-15m&to=now&var-ds=fe9k7u07b1a0wc&var-filters=service_name%7C%3D%7Cweb_app_1&var-fields=&var-levels=&var-metadata=&var-patterns=&var-lineFilterV2=&var-lineFilters=&urlColumns=%5B%5D&visualizationType=%22logs%22&displayedFields=%5B%5D&timezone=browser&var-all-fields=&var-labelBy=$__all",
|
|
||||||
"id": "LABEL_BREAKDOWN_VALUES_service_name",
|
|
||||||
"title": "service_name",
|
|
||||||
"logoPath": "public/plugins/grafana-lokiexplore-app/img/img/logo.svg",
|
|
||||||
"createdAt": "2025-02-13T11:31:41.507Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"origin": "Explore Logs",
|
|
||||||
"type": "timeseries",
|
|
||||||
"queries": [
|
|
||||||
{
|
|
||||||
"refId": "LABEL_BREAKDOWN_VALUES",
|
|
||||||
"queryType": "range",
|
|
||||||
"editorMode": "code",
|
|
||||||
"supportingQueryType": "grafana-lokiexplore-app",
|
|
||||||
"legendFormat": "{{service}}",
|
|
||||||
"expr": "sum(count_over_time({service_name=\"web_app_1\",service != \"\"} [$__auto])) by (service)"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"timeRange": {
|
|
||||||
"to": "2025-02-13T11:31:20.536Z",
|
|
||||||
"from": "2025-02-13T11:16:20.536Z",
|
|
||||||
"raw": {
|
|
||||||
"from": "now-15m",
|
|
||||||
"to": "now"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"datasource": {
|
|
||||||
"uid": "fe9k7u07b1a0wc"
|
|
||||||
},
|
|
||||||
"url": "http://localhost:3000/a/grafana-lokiexplore-app/explore/service/web_app_1/labels?patterns=%5B%5D&from=now-15m&to=now&var-ds=fe9k7u07b1a0wc&var-filters=service_name%7C%3D%7Cweb_app_1&var-fields=&var-levels=&var-metadata=&var-patterns=&var-lineFilterV2=&var-lineFilters=&urlColumns=%5B%5D&visualizationType=%22logs%22&displayedFields=%5B%5D&timezone=browser&var-all-fields=&var-labelBy=$__all",
|
|
||||||
"id": "LABEL_BREAKDOWN_VALUES_service",
|
|
||||||
"title": "service",
|
|
||||||
"logoPath": "public/plugins/grafana-lokiexplore-app/img/img/logo.svg",
|
|
||||||
"createdAt": "2025-02-13T11:31:43.698Z"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"createdAt": "2025-02-13T11:31:23.637Z",
|
|
||||||
"updatedAt": "2025-02-13T11:31:43.698Z",
|
|
||||||
"viewMode": {
|
|
||||||
"mode": "compact",
|
|
||||||
"showComments": true,
|
|
||||||
"showTooltips": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
module github.com/grafana/grafana/apps/investigations
|
|
||||||
|
|
||||||
go 1.25.5
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/grafana/grafana-app-sdk v0.48.7
|
|
||||||
k8s.io/apimachinery v0.34.3
|
|
||||||
k8s.io/klog/v2 v2.130.1
|
|
||||||
k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
|
|
||||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
|
||||||
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
|
|
||||||
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
|
|
||||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
|
||||||
github.com/getkin/kin-openapi v0.133.0 // indirect
|
|
||||||
github.com/go-logr/logr v1.4.3 // indirect
|
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
|
||||||
github.com/go-openapi/jsonpointer v0.22.4 // indirect
|
|
||||||
github.com/go-openapi/jsonreference v0.21.4 // indirect
|
|
||||||
github.com/go-openapi/swag v0.25.4 // indirect
|
|
||||||
github.com/go-openapi/swag/cmdutils v0.25.4 // indirect
|
|
||||||
github.com/go-openapi/swag/conv v0.25.4 // indirect
|
|
||||||
github.com/go-openapi/swag/fileutils v0.25.4 // indirect
|
|
||||||
github.com/go-openapi/swag/jsonname v0.25.4 // indirect
|
|
||||||
github.com/go-openapi/swag/jsonutils v0.25.4 // indirect
|
|
||||||
github.com/go-openapi/swag/loading v0.25.4 // indirect
|
|
||||||
github.com/go-openapi/swag/mangling v0.25.4 // indirect
|
|
||||||
github.com/go-openapi/swag/netutils v0.25.4 // indirect
|
|
||||||
github.com/go-openapi/swag/stringutils v0.25.4 // indirect
|
|
||||||
github.com/go-openapi/swag/typeutils v0.25.4 // indirect
|
|
||||||
github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
|
|
||||||
github.com/go-test/deep v1.1.1 // indirect
|
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
|
||||||
github.com/google/gnostic-models v0.7.1 // indirect
|
|
||||||
github.com/google/go-cmp v0.7.0 // indirect
|
|
||||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/grafana/grafana-app-sdk/logging v0.48.7 // indirect
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
|
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
|
||||||
github.com/mailru/easyjson v0.9.1 // indirect
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
|
||||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
|
||||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
|
|
||||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
|
|
||||||
github.com/onsi/ginkgo/v2 v2.22.2 // indirect
|
|
||||||
github.com/onsi/gomega v1.36.2 // indirect
|
|
||||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
|
||||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
|
||||||
github.com/prometheus/client_model v0.6.2 // indirect
|
|
||||||
github.com/prometheus/common v0.67.4 // indirect
|
|
||||||
github.com/prometheus/procfs v0.19.2 // indirect
|
|
||||||
github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.10 // indirect
|
|
||||||
github.com/woodsbury/decimal128 v1.4.0 // indirect
|
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
|
||||||
go.opentelemetry.io/otel v1.39.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
|
||||||
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
|
||||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
|
||||||
golang.org/x/net v0.48.0 // indirect
|
|
||||||
golang.org/x/oauth2 v0.34.0 // indirect
|
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
|
||||||
golang.org/x/sys v0.39.0 // indirect
|
|
||||||
golang.org/x/term v0.38.0 // indirect
|
|
||||||
golang.org/x/text v0.32.0 // indirect
|
|
||||||
golang.org/x/time v0.14.0 // indirect
|
|
||||||
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect
|
|
||||||
google.golang.org/grpc v1.77.0 // indirect
|
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
k8s.io/api v0.34.3 // indirect
|
|
||||||
k8s.io/apiextensions-apiserver v0.34.3 // indirect
|
|
||||||
k8s.io/client-go v0.34.3 // indirect
|
|
||||||
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
|
|
||||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
|
||||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
|
||||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect
|
|
||||||
sigs.k8s.io/yaml v1.6.0 // indirect
|
|
||||||
)
|
|
||||||
@@ -1,264 +0,0 @@
|
|||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I=
|
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
|
|
||||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
|
||||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
|
|
||||||
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
|
||||||
github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8=
|
|
||||||
github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
|
||||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
|
||||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
|
||||||
github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ=
|
|
||||||
github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE=
|
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
|
||||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
|
||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
|
||||||
github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4=
|
|
||||||
github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80=
|
|
||||||
github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8=
|
|
||||||
github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4=
|
|
||||||
github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU=
|
|
||||||
github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ=
|
|
||||||
github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4=
|
|
||||||
github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0=
|
|
||||||
github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4=
|
|
||||||
github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU=
|
|
||||||
github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y=
|
|
||||||
github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk=
|
|
||||||
github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI=
|
|
||||||
github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=
|
|
||||||
github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA=
|
|
||||||
github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY=
|
|
||||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo=
|
|
||||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM=
|
|
||||||
github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s=
|
|
||||||
github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE=
|
|
||||||
github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48=
|
|
||||||
github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg=
|
|
||||||
github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0=
|
|
||||||
github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg=
|
|
||||||
github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8=
|
|
||||||
github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0=
|
|
||||||
github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw=
|
|
||||||
github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE=
|
|
||||||
github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw=
|
|
||||||
github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc=
|
|
||||||
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4=
|
|
||||||
github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg=
|
|
||||||
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
|
|
||||||
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
|
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
|
||||||
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
|
||||||
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
|
||||||
github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c=
|
|
||||||
github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
|
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
|
||||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
|
||||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo=
|
|
||||||
github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k=
|
|
||||||
github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw=
|
|
||||||
github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
|
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
|
||||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
|
||||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
|
||||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
|
||||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
|
||||||
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
|
|
||||||
github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
|
||||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
|
|
||||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
|
||||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
|
|
||||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
|
|
||||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
|
|
||||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
|
|
||||||
github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
|
|
||||||
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
|
|
||||||
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
|
|
||||||
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
|
|
||||||
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
|
||||||
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
|
||||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
|
||||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
|
||||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
|
||||||
github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
|
|
||||||
github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
|
|
||||||
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
|
|
||||||
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
|
|
||||||
github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU=
|
|
||||||
github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU=
|
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
|
||||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
|
||||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
|
||||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
|
||||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
|
||||||
github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc=
|
|
||||||
github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU=
|
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
|
||||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
|
||||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
|
||||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU=
|
|
||||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
|
||||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
|
||||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
|
||||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
|
||||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
|
||||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
|
||||||
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
|
|
||||||
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
|
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
|
||||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
|
||||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
|
||||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
|
||||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
|
||||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
|
||||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
|
||||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
|
||||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
|
||||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
|
||||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
|
||||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
|
||||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
|
||||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
|
||||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
|
|
||||||
gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
|
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU=
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
|
||||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
|
||||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
|
||||||
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
|
|
||||||
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
|
||||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
|
||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4=
|
|
||||||
k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk=
|
|
||||||
k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g=
|
|
||||||
k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0=
|
|
||||||
k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE=
|
|
||||||
k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
|
|
||||||
k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A=
|
|
||||||
k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM=
|
|
||||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
|
||||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
|
||||||
k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ=
|
|
||||||
k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
|
|
||||||
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=
|
|
||||||
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
|
||||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
|
|
||||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
|
||||||
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
|
|
||||||
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
|
||||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E=
|
|
||||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.1/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
|
||||||
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
|
||||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package investigations
|
|
||||||
|
|
||||||
// Collectable represents an item collected during investigation
|
|
||||||
#Collectable: {
|
|
||||||
id: string
|
|
||||||
createdAt: string
|
|
||||||
|
|
||||||
title: string
|
|
||||||
origin: string
|
|
||||||
type: string
|
|
||||||
queries: [...string] // +listType=atomic
|
|
||||||
timeRange: #TimeRange
|
|
||||||
datasource: #DatasourceRef
|
|
||||||
url: string
|
|
||||||
logoPath?: string
|
|
||||||
|
|
||||||
note: string
|
|
||||||
noteUpdatedAt: string
|
|
||||||
|
|
||||||
fieldConfig: string
|
|
||||||
}
|
|
||||||
|
|
||||||
#CollectableSummary: {
|
|
||||||
id: string
|
|
||||||
title: string
|
|
||||||
logoPath: string
|
|
||||||
origin: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimeRange represents a time range with both absolute and relative values
|
|
||||||
#TimeRange: {
|
|
||||||
from: string
|
|
||||||
to: string
|
|
||||||
raw: {
|
|
||||||
from: string
|
|
||||||
to: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DatasourceRef is a reference to a datasource
|
|
||||||
#DatasourceRef: {
|
|
||||||
uid: string
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
module: "github.com/grafana/grafana/apps/investigations"
|
|
||||||
language: {
|
|
||||||
version: "v0.11.0"
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package investigations
|
|
||||||
|
|
||||||
investigationV0alpha1: {
|
|
||||||
kind: "Investigation"
|
|
||||||
pluralName: "Investigations"
|
|
||||||
schema: {
|
|
||||||
spec: {
|
|
||||||
title: string
|
|
||||||
createdByProfile: #Person
|
|
||||||
hasCustomName: bool
|
|
||||||
isFavorite: bool
|
|
||||||
overviewNote: string
|
|
||||||
overviewNoteUpdatedAt: string
|
|
||||||
collectables: [...#Collectable] // +listType=atomic
|
|
||||||
viewMode: #ViewMode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type definition for investigation summaries
|
|
||||||
#InvestigationSummary: {
|
|
||||||
title: string
|
|
||||||
createdByProfile: #Person
|
|
||||||
hasCustomName: bool
|
|
||||||
isFavorite: bool
|
|
||||||
overviewNote: string
|
|
||||||
overviewNoteUpdatedAt: string
|
|
||||||
viewMode: #ViewMode
|
|
||||||
collectableSummaries: [...#CollectableSummary] // +listType=atomic
|
|
||||||
}
|
|
||||||
|
|
||||||
// Person represents a user profile with basic information
|
|
||||||
#Person: {
|
|
||||||
uid: string // Unique identifier for the user
|
|
||||||
name: string // Display name of the user
|
|
||||||
gravatarUrl: string // URL to user's Gravatar image
|
|
||||||
}
|
|
||||||
|
|
||||||
#ViewMode: {
|
|
||||||
mode: "compact" | "full"
|
|
||||||
showComments: bool
|
|
||||||
showTooltips: bool
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package investigations
|
|
||||||
|
|
||||||
investigationIndexV0alpha1:{
|
|
||||||
kind: "InvestigationIndex"
|
|
||||||
pluralName: "InvestigationIndexes"
|
|
||||||
schema: {
|
|
||||||
spec: {
|
|
||||||
// Title of the index, e.g. 'Favorites' or 'My Investigations'
|
|
||||||
title: string
|
|
||||||
|
|
||||||
// The Person who owns this investigation index
|
|
||||||
owner: #Person
|
|
||||||
|
|
||||||
// Array of investigation summaries
|
|
||||||
investigationSummaries: [...#InvestigationSummary] // +listType=atomic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package investigations
|
|
||||||
|
|
||||||
manifest: {
|
|
||||||
appName: "investigations"
|
|
||||||
groupOverride: "investigations.grafana.app"
|
|
||||||
versions: {
|
|
||||||
"v0alpha1": {
|
|
||||||
codegen: {
|
|
||||||
ts: {enabled: false}
|
|
||||||
go: {enabled: true}
|
|
||||||
}
|
|
||||||
kinds: [
|
|
||||||
investigationV0alpha1,
|
|
||||||
investigationIndexV0alpha1,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package v0alpha1
|
|
||||||
|
|
||||||
import "k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
|
|
||||||
const (
|
|
||||||
// APIGroup is the API group used by all kinds in this package
|
|
||||||
APIGroup = "investigations.grafana.app"
|
|
||||||
// APIVersion is the API version used by all kinds in this package
|
|
||||||
APIVersion = "v0alpha1"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// GroupVersion is a schema.GroupVersion consisting of the Group and Version constants for this package
|
|
||||||
GroupVersion = schema.GroupVersion{
|
|
||||||
Group: APIGroup,
|
|
||||||
Version: APIVersion,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
-80
@@ -1,80 +0,0 @@
|
|||||||
package v0alpha1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-app-sdk/resource"
|
|
||||||
)
|
|
||||||
|
|
||||||
type InvestigationClient struct {
|
|
||||||
client *resource.TypedClient[*Investigation, *InvestigationList]
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewInvestigationClient(client resource.Client) *InvestigationClient {
|
|
||||||
return &InvestigationClient{
|
|
||||||
client: resource.NewTypedClient[*Investigation, *InvestigationList](client, InvestigationKind()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewInvestigationClientFromGenerator(generator resource.ClientGenerator) (*InvestigationClient, error) {
|
|
||||||
c, err := generator.ClientFor(InvestigationKind())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return NewInvestigationClient(c), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationClient) Get(ctx context.Context, identifier resource.Identifier) (*Investigation, error) {
|
|
||||||
return c.client.Get(ctx, identifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*InvestigationList, error) {
|
|
||||||
return c.client.List(ctx, namespace, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*InvestigationList, error) {
|
|
||||||
resp, err := c.client.List(ctx, namespace, resource.ListOptions{
|
|
||||||
ResourceVersion: opts.ResourceVersion,
|
|
||||||
Limit: opts.Limit,
|
|
||||||
LabelFilters: opts.LabelFilters,
|
|
||||||
FieldSelectors: opts.FieldSelectors,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for resp.GetContinue() != "" {
|
|
||||||
page, err := c.client.List(ctx, namespace, resource.ListOptions{
|
|
||||||
Continue: resp.GetContinue(),
|
|
||||||
ResourceVersion: opts.ResourceVersion,
|
|
||||||
Limit: opts.Limit,
|
|
||||||
LabelFilters: opts.LabelFilters,
|
|
||||||
FieldSelectors: opts.FieldSelectors,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resp.SetContinue(page.GetContinue())
|
|
||||||
resp.SetResourceVersion(page.GetResourceVersion())
|
|
||||||
resp.SetItems(append(resp.GetItems(), page.GetItems()...))
|
|
||||||
}
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationClient) Create(ctx context.Context, obj *Investigation, opts resource.CreateOptions) (*Investigation, error) {
|
|
||||||
// Make sure apiVersion and kind are set
|
|
||||||
obj.APIVersion = GroupVersion.Identifier()
|
|
||||||
obj.Kind = InvestigationKind().Kind()
|
|
||||||
return c.client.Create(ctx, obj, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationClient) Update(ctx context.Context, obj *Investigation, opts resource.UpdateOptions) (*Investigation, error) {
|
|
||||||
return c.client.Update(ctx, obj, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*Investigation, error) {
|
|
||||||
return c.client.Patch(ctx, identifier, req, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
|
|
||||||
return c.client.Delete(ctx, identifier, opts)
|
|
||||||
}
|
|
||||||
-28
@@ -1,28 +0,0 @@
|
|||||||
//
|
|
||||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
|
||||||
//
|
|
||||||
|
|
||||||
package v0alpha1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-app-sdk/resource"
|
|
||||||
)
|
|
||||||
|
|
||||||
// InvestigationJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding
|
|
||||||
type InvestigationJSONCodec struct{}
|
|
||||||
|
|
||||||
// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into`
|
|
||||||
func (*InvestigationJSONCodec) Read(reader io.Reader, into resource.Object) error {
|
|
||||||
return json.NewDecoder(reader).Decode(into)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes JSON-encoded bytes into `writer` marshaled from `from`
|
|
||||||
func (*InvestigationJSONCodec) Write(writer io.Writer, from resource.Object) error {
|
|
||||||
return json.NewEncoder(writer).Encode(from)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface compliance checks
|
|
||||||
var _ resource.Codec = &InvestigationJSONCodec{}
|
|
||||||
-31
@@ -1,31 +0,0 @@
|
|||||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
|
||||||
|
|
||||||
package v0alpha1
|
|
||||||
|
|
||||||
import (
|
|
||||||
time "time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// metadata contains embedded CommonMetadata and can be extended with custom string fields
|
|
||||||
// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
|
|
||||||
// without external reference as using the CommonMetadata reference breaks thema codegen.
|
|
||||||
type InvestigationMetadata struct {
|
|
||||||
UpdateTimestamp time.Time `json:"updateTimestamp"`
|
|
||||||
CreatedBy string `json:"createdBy"`
|
|
||||||
Uid string `json:"uid"`
|
|
||||||
CreationTimestamp time.Time `json:"creationTimestamp"`
|
|
||||||
DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"`
|
|
||||||
Finalizers []string `json:"finalizers"`
|
|
||||||
ResourceVersion string `json:"resourceVersion"`
|
|
||||||
Generation int64 `json:"generation"`
|
|
||||||
UpdatedBy string `json:"updatedBy"`
|
|
||||||
Labels map[string]string `json:"labels"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationMetadata creates a new InvestigationMetadata object.
|
|
||||||
func NewInvestigationMetadata() *InvestigationMetadata {
|
|
||||||
return &InvestigationMetadata{
|
|
||||||
Finalizers: []string{},
|
|
||||||
Labels: map[string]string{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-293
@@ -1,293 +0,0 @@
|
|||||||
//
|
|
||||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
|
||||||
//
|
|
||||||
|
|
||||||
package v0alpha1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/grafana/grafana-app-sdk/resource"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type Investigation struct {
|
|
||||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
|
||||||
metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
|
|
||||||
|
|
||||||
// Spec is the spec of the Investigation
|
|
||||||
Spec InvestigationSpec `json:"spec" yaml:"spec"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) GetSpec() any {
|
|
||||||
return o.Spec
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) SetSpec(spec any) error {
|
|
||||||
cast, ok := spec.(InvestigationSpec)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec)
|
|
||||||
}
|
|
||||||
o.Spec = cast
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) GetSubresources() map[string]any {
|
|
||||||
return map[string]any{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) GetSubresource(name string) (any, bool) {
|
|
||||||
switch name {
|
|
||||||
default:
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) SetSubresource(name string, value any) error {
|
|
||||||
switch name {
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("subresource '%s' does not exist", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) GetStaticMetadata() resource.StaticMetadata {
|
|
||||||
gvk := o.GroupVersionKind()
|
|
||||||
return resource.StaticMetadata{
|
|
||||||
Name: o.ObjectMeta.Name,
|
|
||||||
Namespace: o.ObjectMeta.Namespace,
|
|
||||||
Group: gvk.Group,
|
|
||||||
Version: gvk.Version,
|
|
||||||
Kind: gvk.Kind,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) SetStaticMetadata(metadata resource.StaticMetadata) {
|
|
||||||
o.Name = metadata.Name
|
|
||||||
o.Namespace = metadata.Namespace
|
|
||||||
o.SetGroupVersionKind(schema.GroupVersionKind{
|
|
||||||
Group: metadata.Group,
|
|
||||||
Version: metadata.Version,
|
|
||||||
Kind: metadata.Kind,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) GetCommonMetadata() resource.CommonMetadata {
|
|
||||||
dt := o.DeletionTimestamp
|
|
||||||
var deletionTimestamp *time.Time
|
|
||||||
if dt != nil {
|
|
||||||
deletionTimestamp = &dt.Time
|
|
||||||
}
|
|
||||||
// Legacy ExtraFields support
|
|
||||||
extraFields := make(map[string]any)
|
|
||||||
if o.Annotations != nil {
|
|
||||||
extraFields["annotations"] = o.Annotations
|
|
||||||
}
|
|
||||||
if o.ManagedFields != nil {
|
|
||||||
extraFields["managedFields"] = o.ManagedFields
|
|
||||||
}
|
|
||||||
if o.OwnerReferences != nil {
|
|
||||||
extraFields["ownerReferences"] = o.OwnerReferences
|
|
||||||
}
|
|
||||||
return resource.CommonMetadata{
|
|
||||||
UID: string(o.UID),
|
|
||||||
ResourceVersion: o.ResourceVersion,
|
|
||||||
Generation: o.Generation,
|
|
||||||
Labels: o.Labels,
|
|
||||||
CreationTimestamp: o.CreationTimestamp.Time,
|
|
||||||
DeletionTimestamp: deletionTimestamp,
|
|
||||||
Finalizers: o.Finalizers,
|
|
||||||
UpdateTimestamp: o.GetUpdateTimestamp(),
|
|
||||||
CreatedBy: o.GetCreatedBy(),
|
|
||||||
UpdatedBy: o.GetUpdatedBy(),
|
|
||||||
ExtraFields: extraFields,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) SetCommonMetadata(metadata resource.CommonMetadata) {
|
|
||||||
o.UID = types.UID(metadata.UID)
|
|
||||||
o.ResourceVersion = metadata.ResourceVersion
|
|
||||||
o.Generation = metadata.Generation
|
|
||||||
o.Labels = metadata.Labels
|
|
||||||
o.CreationTimestamp = metav1.NewTime(metadata.CreationTimestamp)
|
|
||||||
if metadata.DeletionTimestamp != nil {
|
|
||||||
dt := metav1.NewTime(*metadata.DeletionTimestamp)
|
|
||||||
o.DeletionTimestamp = &dt
|
|
||||||
} else {
|
|
||||||
o.DeletionTimestamp = nil
|
|
||||||
}
|
|
||||||
o.Finalizers = metadata.Finalizers
|
|
||||||
if o.Annotations == nil {
|
|
||||||
o.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
if !metadata.UpdateTimestamp.IsZero() {
|
|
||||||
o.SetUpdateTimestamp(metadata.UpdateTimestamp)
|
|
||||||
}
|
|
||||||
if metadata.CreatedBy != "" {
|
|
||||||
o.SetCreatedBy(metadata.CreatedBy)
|
|
||||||
}
|
|
||||||
if metadata.UpdatedBy != "" {
|
|
||||||
o.SetUpdatedBy(metadata.UpdatedBy)
|
|
||||||
}
|
|
||||||
// Legacy support for setting Annotations, ManagedFields, and OwnerReferences via ExtraFields
|
|
||||||
if metadata.ExtraFields != nil {
|
|
||||||
if annotations, ok := metadata.ExtraFields["annotations"]; ok {
|
|
||||||
if cast, ok := annotations.(map[string]string); ok {
|
|
||||||
o.Annotations = cast
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if managedFields, ok := metadata.ExtraFields["managedFields"]; ok {
|
|
||||||
if cast, ok := managedFields.([]metav1.ManagedFieldsEntry); ok {
|
|
||||||
o.ManagedFields = cast
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ownerReferences, ok := metadata.ExtraFields["ownerReferences"]; ok {
|
|
||||||
if cast, ok := ownerReferences.([]metav1.OwnerReference); ok {
|
|
||||||
o.OwnerReferences = cast
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) GetCreatedBy() string {
|
|
||||||
if o.ObjectMeta.Annotations == nil {
|
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
return o.ObjectMeta.Annotations["grafana.com/createdBy"]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) SetCreatedBy(createdBy string) {
|
|
||||||
if o.ObjectMeta.Annotations == nil {
|
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) GetUpdateTimestamp() time.Time {
|
|
||||||
if o.ObjectMeta.Annotations == nil {
|
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"])
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) SetUpdateTimestamp(updateTimestamp time.Time) {
|
|
||||||
if o.ObjectMeta.Annotations == nil {
|
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) GetUpdatedBy() string {
|
|
||||||
if o.ObjectMeta.Annotations == nil {
|
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
return o.ObjectMeta.Annotations["grafana.com/updatedBy"]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) SetUpdatedBy(updatedBy string) {
|
|
||||||
if o.ObjectMeta.Annotations == nil {
|
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) Copy() resource.Object {
|
|
||||||
return resource.CopyObject(o)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) DeepCopyObject() runtime.Object {
|
|
||||||
return o.Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) DeepCopy() *Investigation {
|
|
||||||
cpy := &Investigation{}
|
|
||||||
o.DeepCopyInto(cpy)
|
|
||||||
return cpy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Investigation) DeepCopyInto(dst *Investigation) {
|
|
||||||
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
|
|
||||||
dst.TypeMeta.Kind = o.TypeMeta.Kind
|
|
||||||
o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta)
|
|
||||||
o.Spec.DeepCopyInto(&dst.Spec)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface compliance compile-time check
|
|
||||||
var _ resource.Object = &Investigation{}
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationList struct {
|
|
||||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
|
||||||
metav1.ListMeta `json:"metadata" yaml:"metadata"`
|
|
||||||
Items []Investigation `json:"items" yaml:"items"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationList) DeepCopyObject() runtime.Object {
|
|
||||||
return o.Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationList) Copy() resource.ListObject {
|
|
||||||
cpy := &InvestigationList{
|
|
||||||
TypeMeta: o.TypeMeta,
|
|
||||||
Items: make([]Investigation, len(o.Items)),
|
|
||||||
}
|
|
||||||
o.ListMeta.DeepCopyInto(&cpy.ListMeta)
|
|
||||||
for i := 0; i < len(o.Items); i++ {
|
|
||||||
if item, ok := o.Items[i].Copy().(*Investigation); ok {
|
|
||||||
cpy.Items[i] = *item
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cpy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationList) GetItems() []resource.Object {
|
|
||||||
items := make([]resource.Object, len(o.Items))
|
|
||||||
for i := 0; i < len(o.Items); i++ {
|
|
||||||
items[i] = &o.Items[i]
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationList) SetItems(items []resource.Object) {
|
|
||||||
o.Items = make([]Investigation, len(items))
|
|
||||||
for i := 0; i < len(items); i++ {
|
|
||||||
o.Items[i] = *items[i].(*Investigation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationList) DeepCopy() *InvestigationList {
|
|
||||||
cpy := &InvestigationList{}
|
|
||||||
o.DeepCopyInto(cpy)
|
|
||||||
return cpy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationList) DeepCopyInto(dst *InvestigationList) {
|
|
||||||
resource.CopyObjectInto(dst, o)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface compliance compile-time check
|
|
||||||
var _ resource.ListObject = &InvestigationList{}
|
|
||||||
|
|
||||||
// Copy methods for all subresource types
|
|
||||||
|
|
||||||
// DeepCopy creates a full deep copy of Spec
|
|
||||||
func (s *InvestigationSpec) DeepCopy() *InvestigationSpec {
|
|
||||||
cpy := &InvestigationSpec{}
|
|
||||||
s.DeepCopyInto(cpy)
|
|
||||||
return cpy
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyInto deep copies Spec into another Spec object
|
|
||||||
func (s *InvestigationSpec) DeepCopyInto(dst *InvestigationSpec) {
|
|
||||||
resource.CopyObjectInto(dst, s)
|
|
||||||
}
|
|
||||||
-34
@@ -1,34 +0,0 @@
|
|||||||
//
|
|
||||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
|
||||||
//
|
|
||||||
|
|
||||||
package v0alpha1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/grafana/grafana-app-sdk/resource"
|
|
||||||
)
|
|
||||||
|
|
||||||
// schema is unexported to prevent accidental overwrites
|
|
||||||
var (
|
|
||||||
schemaInvestigation = resource.NewSimpleSchema("investigations.grafana.app", "v0alpha1", &Investigation{}, &InvestigationList{}, resource.WithKind("Investigation"),
|
|
||||||
resource.WithPlural("investigations"), resource.WithScope(resource.NamespacedScope))
|
|
||||||
kindInvestigation = resource.Kind{
|
|
||||||
Schema: schemaInvestigation,
|
|
||||||
Codecs: map[resource.KindEncoding]resource.Codec{
|
|
||||||
resource.KindEncodingJSON: &InvestigationJSONCodec{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Kind returns a resource.Kind for this Schema with a JSON codec
|
|
||||||
func InvestigationKind() resource.Kind {
|
|
||||||
return kindInvestigation
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schema returns a resource.SimpleSchema representation of Investigation
|
|
||||||
func InvestigationSchema() *resource.SimpleSchema {
|
|
||||||
return schemaInvestigation
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface compliance checks
|
|
||||||
var _ resource.Schema = kindInvestigation
|
|
||||||
-126
@@ -1,126 +0,0 @@
|
|||||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
|
||||||
|
|
||||||
package v0alpha1
|
|
||||||
|
|
||||||
// Person represents a user profile with basic information
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationPerson struct {
|
|
||||||
// Unique identifier for the user
|
|
||||||
Uid string `json:"uid"`
|
|
||||||
// Display name of the user
|
|
||||||
Name string `json:"name"`
|
|
||||||
// URL to user's Gravatar image
|
|
||||||
GravatarUrl string `json:"gravatarUrl"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationPerson creates a new InvestigationPerson object.
|
|
||||||
func NewInvestigationPerson() *InvestigationPerson {
|
|
||||||
return &InvestigationPerson{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collectable represents an item collected during investigation
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationCollectable struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
CreatedAt string `json:"createdAt"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Origin string `json:"origin"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
// +listType=atomic
|
|
||||||
Queries []string `json:"queries"`
|
|
||||||
TimeRange InvestigationTimeRange `json:"timeRange"`
|
|
||||||
Datasource InvestigationDatasourceRef `json:"datasource"`
|
|
||||||
Url string `json:"url"`
|
|
||||||
LogoPath *string `json:"logoPath,omitempty"`
|
|
||||||
Note string `json:"note"`
|
|
||||||
NoteUpdatedAt string `json:"noteUpdatedAt"`
|
|
||||||
FieldConfig string `json:"fieldConfig"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationCollectable creates a new InvestigationCollectable object.
|
|
||||||
func NewInvestigationCollectable() *InvestigationCollectable {
|
|
||||||
return &InvestigationCollectable{
|
|
||||||
Queries: []string{},
|
|
||||||
TimeRange: *NewInvestigationTimeRange(),
|
|
||||||
Datasource: *NewInvestigationDatasourceRef(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimeRange represents a time range with both absolute and relative values
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationTimeRange struct {
|
|
||||||
From string `json:"from"`
|
|
||||||
To string `json:"to"`
|
|
||||||
Raw InvestigationV0alpha1TimeRangeRaw `json:"raw"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationTimeRange creates a new InvestigationTimeRange object.
|
|
||||||
func NewInvestigationTimeRange() *InvestigationTimeRange {
|
|
||||||
return &InvestigationTimeRange{
|
|
||||||
Raw: *NewInvestigationV0alpha1TimeRangeRaw(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DatasourceRef is a reference to a datasource
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationDatasourceRef struct {
|
|
||||||
Uid string `json:"uid"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationDatasourceRef creates a new InvestigationDatasourceRef object.
|
|
||||||
func NewInvestigationDatasourceRef() *InvestigationDatasourceRef {
|
|
||||||
return &InvestigationDatasourceRef{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationViewMode struct {
|
|
||||||
Mode InvestigationViewModeMode `json:"mode"`
|
|
||||||
ShowComments bool `json:"showComments"`
|
|
||||||
ShowTooltips bool `json:"showTooltips"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationViewMode creates a new InvestigationViewMode object.
|
|
||||||
func NewInvestigationViewMode() *InvestigationViewMode {
|
|
||||||
return &InvestigationViewMode{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationSpec struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
CreatedByProfile InvestigationPerson `json:"createdByProfile"`
|
|
||||||
HasCustomName bool `json:"hasCustomName"`
|
|
||||||
IsFavorite bool `json:"isFavorite"`
|
|
||||||
OverviewNote string `json:"overviewNote"`
|
|
||||||
OverviewNoteUpdatedAt string `json:"overviewNoteUpdatedAt"`
|
|
||||||
// +listType=atomic
|
|
||||||
Collectables []InvestigationCollectable `json:"collectables"`
|
|
||||||
ViewMode InvestigationViewMode `json:"viewMode"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationSpec creates a new InvestigationSpec object.
|
|
||||||
func NewInvestigationSpec() *InvestigationSpec {
|
|
||||||
return &InvestigationSpec{
|
|
||||||
CreatedByProfile: *NewInvestigationPerson(),
|
|
||||||
Collectables: []InvestigationCollectable{},
|
|
||||||
ViewMode: *NewInvestigationViewMode(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationV0alpha1TimeRangeRaw struct {
|
|
||||||
From string `json:"from"`
|
|
||||||
To string `json:"to"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationV0alpha1TimeRangeRaw creates a new InvestigationV0alpha1TimeRangeRaw object.
|
|
||||||
func NewInvestigationV0alpha1TimeRangeRaw() *InvestigationV0alpha1TimeRangeRaw {
|
|
||||||
return &InvestigationV0alpha1TimeRangeRaw{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationViewModeMode string
|
|
||||||
|
|
||||||
const (
|
|
||||||
InvestigationViewModeModeCompact InvestigationViewModeMode = "compact"
|
|
||||||
InvestigationViewModeModeFull InvestigationViewModeMode = "full"
|
|
||||||
)
|
|
||||||
-80
@@ -1,80 +0,0 @@
|
|||||||
package v0alpha1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-app-sdk/resource"
|
|
||||||
)
|
|
||||||
|
|
||||||
type InvestigationIndexClient struct {
|
|
||||||
client *resource.TypedClient[*InvestigationIndex, *InvestigationIndexList]
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewInvestigationIndexClient(client resource.Client) *InvestigationIndexClient {
|
|
||||||
return &InvestigationIndexClient{
|
|
||||||
client: resource.NewTypedClient[*InvestigationIndex, *InvestigationIndexList](client, InvestigationIndexKind()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewInvestigationIndexClientFromGenerator(generator resource.ClientGenerator) (*InvestigationIndexClient, error) {
|
|
||||||
c, err := generator.ClientFor(InvestigationIndexKind())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return NewInvestigationIndexClient(c), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationIndexClient) Get(ctx context.Context, identifier resource.Identifier) (*InvestigationIndex, error) {
|
|
||||||
return c.client.Get(ctx, identifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationIndexClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*InvestigationIndexList, error) {
|
|
||||||
return c.client.List(ctx, namespace, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationIndexClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*InvestigationIndexList, error) {
|
|
||||||
resp, err := c.client.List(ctx, namespace, resource.ListOptions{
|
|
||||||
ResourceVersion: opts.ResourceVersion,
|
|
||||||
Limit: opts.Limit,
|
|
||||||
LabelFilters: opts.LabelFilters,
|
|
||||||
FieldSelectors: opts.FieldSelectors,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for resp.GetContinue() != "" {
|
|
||||||
page, err := c.client.List(ctx, namespace, resource.ListOptions{
|
|
||||||
Continue: resp.GetContinue(),
|
|
||||||
ResourceVersion: opts.ResourceVersion,
|
|
||||||
Limit: opts.Limit,
|
|
||||||
LabelFilters: opts.LabelFilters,
|
|
||||||
FieldSelectors: opts.FieldSelectors,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resp.SetContinue(page.GetContinue())
|
|
||||||
resp.SetResourceVersion(page.GetResourceVersion())
|
|
||||||
resp.SetItems(append(resp.GetItems(), page.GetItems()...))
|
|
||||||
}
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationIndexClient) Create(ctx context.Context, obj *InvestigationIndex, opts resource.CreateOptions) (*InvestigationIndex, error) {
|
|
||||||
// Make sure apiVersion and kind are set
|
|
||||||
obj.APIVersion = GroupVersion.Identifier()
|
|
||||||
obj.Kind = InvestigationIndexKind().Kind()
|
|
||||||
return c.client.Create(ctx, obj, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationIndexClient) Update(ctx context.Context, obj *InvestigationIndex, opts resource.UpdateOptions) (*InvestigationIndex, error) {
|
|
||||||
return c.client.Update(ctx, obj, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationIndexClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*InvestigationIndex, error) {
|
|
||||||
return c.client.Patch(ctx, identifier, req, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InvestigationIndexClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
|
|
||||||
return c.client.Delete(ctx, identifier, opts)
|
|
||||||
}
|
|
||||||
-28
@@ -1,28 +0,0 @@
|
|||||||
//
|
|
||||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
|
||||||
//
|
|
||||||
|
|
||||||
package v0alpha1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-app-sdk/resource"
|
|
||||||
)
|
|
||||||
|
|
||||||
// InvestigationIndexJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding
|
|
||||||
type InvestigationIndexJSONCodec struct{}
|
|
||||||
|
|
||||||
// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into`
|
|
||||||
func (*InvestigationIndexJSONCodec) Read(reader io.Reader, into resource.Object) error {
|
|
||||||
return json.NewDecoder(reader).Decode(into)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes JSON-encoded bytes into `writer` marshaled from `from`
|
|
||||||
func (*InvestigationIndexJSONCodec) Write(writer io.Writer, from resource.Object) error {
|
|
||||||
return json.NewEncoder(writer).Encode(from)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface compliance checks
|
|
||||||
var _ resource.Codec = &InvestigationIndexJSONCodec{}
|
|
||||||
-31
@@ -1,31 +0,0 @@
|
|||||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
|
||||||
|
|
||||||
package v0alpha1
|
|
||||||
|
|
||||||
import (
|
|
||||||
time "time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// metadata contains embedded CommonMetadata and can be extended with custom string fields
|
|
||||||
// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
|
|
||||||
// without external reference as using the CommonMetadata reference breaks thema codegen.
|
|
||||||
type InvestigationIndexMetadata struct {
|
|
||||||
UpdateTimestamp time.Time `json:"updateTimestamp"`
|
|
||||||
CreatedBy string `json:"createdBy"`
|
|
||||||
Uid string `json:"uid"`
|
|
||||||
CreationTimestamp time.Time `json:"creationTimestamp"`
|
|
||||||
DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"`
|
|
||||||
Finalizers []string `json:"finalizers"`
|
|
||||||
ResourceVersion string `json:"resourceVersion"`
|
|
||||||
Generation int64 `json:"generation"`
|
|
||||||
UpdatedBy string `json:"updatedBy"`
|
|
||||||
Labels map[string]string `json:"labels"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationIndexMetadata creates a new InvestigationIndexMetadata object.
|
|
||||||
func NewInvestigationIndexMetadata() *InvestigationIndexMetadata {
|
|
||||||
return &InvestigationIndexMetadata{
|
|
||||||
Finalizers: []string{},
|
|
||||||
Labels: map[string]string{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-293
@@ -1,293 +0,0 @@
|
|||||||
//
|
|
||||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
|
||||||
//
|
|
||||||
|
|
||||||
package v0alpha1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/grafana/grafana-app-sdk/resource"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationIndex struct {
|
|
||||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
|
||||||
metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
|
|
||||||
|
|
||||||
// Spec is the spec of the InvestigationIndex
|
|
||||||
Spec InvestigationIndexSpec `json:"spec" yaml:"spec"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) GetSpec() any {
|
|
||||||
return o.Spec
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) SetSpec(spec any) error {
|
|
||||||
cast, ok := spec.(InvestigationIndexSpec)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec)
|
|
||||||
}
|
|
||||||
o.Spec = cast
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) GetSubresources() map[string]any {
|
|
||||||
return map[string]any{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) GetSubresource(name string) (any, bool) {
|
|
||||||
switch name {
|
|
||||||
default:
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) SetSubresource(name string, value any) error {
|
|
||||||
switch name {
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("subresource '%s' does not exist", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) GetStaticMetadata() resource.StaticMetadata {
|
|
||||||
gvk := o.GroupVersionKind()
|
|
||||||
return resource.StaticMetadata{
|
|
||||||
Name: o.ObjectMeta.Name,
|
|
||||||
Namespace: o.ObjectMeta.Namespace,
|
|
||||||
Group: gvk.Group,
|
|
||||||
Version: gvk.Version,
|
|
||||||
Kind: gvk.Kind,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) SetStaticMetadata(metadata resource.StaticMetadata) {
|
|
||||||
o.Name = metadata.Name
|
|
||||||
o.Namespace = metadata.Namespace
|
|
||||||
o.SetGroupVersionKind(schema.GroupVersionKind{
|
|
||||||
Group: metadata.Group,
|
|
||||||
Version: metadata.Version,
|
|
||||||
Kind: metadata.Kind,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) GetCommonMetadata() resource.CommonMetadata {
|
|
||||||
dt := o.DeletionTimestamp
|
|
||||||
var deletionTimestamp *time.Time
|
|
||||||
if dt != nil {
|
|
||||||
deletionTimestamp = &dt.Time
|
|
||||||
}
|
|
||||||
// Legacy ExtraFields support
|
|
||||||
extraFields := make(map[string]any)
|
|
||||||
if o.Annotations != nil {
|
|
||||||
extraFields["annotations"] = o.Annotations
|
|
||||||
}
|
|
||||||
if o.ManagedFields != nil {
|
|
||||||
extraFields["managedFields"] = o.ManagedFields
|
|
||||||
}
|
|
||||||
if o.OwnerReferences != nil {
|
|
||||||
extraFields["ownerReferences"] = o.OwnerReferences
|
|
||||||
}
|
|
||||||
return resource.CommonMetadata{
|
|
||||||
UID: string(o.UID),
|
|
||||||
ResourceVersion: o.ResourceVersion,
|
|
||||||
Generation: o.Generation,
|
|
||||||
Labels: o.Labels,
|
|
||||||
CreationTimestamp: o.CreationTimestamp.Time,
|
|
||||||
DeletionTimestamp: deletionTimestamp,
|
|
||||||
Finalizers: o.Finalizers,
|
|
||||||
UpdateTimestamp: o.GetUpdateTimestamp(),
|
|
||||||
CreatedBy: o.GetCreatedBy(),
|
|
||||||
UpdatedBy: o.GetUpdatedBy(),
|
|
||||||
ExtraFields: extraFields,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) SetCommonMetadata(metadata resource.CommonMetadata) {
|
|
||||||
o.UID = types.UID(metadata.UID)
|
|
||||||
o.ResourceVersion = metadata.ResourceVersion
|
|
||||||
o.Generation = metadata.Generation
|
|
||||||
o.Labels = metadata.Labels
|
|
||||||
o.CreationTimestamp = metav1.NewTime(metadata.CreationTimestamp)
|
|
||||||
if metadata.DeletionTimestamp != nil {
|
|
||||||
dt := metav1.NewTime(*metadata.DeletionTimestamp)
|
|
||||||
o.DeletionTimestamp = &dt
|
|
||||||
} else {
|
|
||||||
o.DeletionTimestamp = nil
|
|
||||||
}
|
|
||||||
o.Finalizers = metadata.Finalizers
|
|
||||||
if o.Annotations == nil {
|
|
||||||
o.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
if !metadata.UpdateTimestamp.IsZero() {
|
|
||||||
o.SetUpdateTimestamp(metadata.UpdateTimestamp)
|
|
||||||
}
|
|
||||||
if metadata.CreatedBy != "" {
|
|
||||||
o.SetCreatedBy(metadata.CreatedBy)
|
|
||||||
}
|
|
||||||
if metadata.UpdatedBy != "" {
|
|
||||||
o.SetUpdatedBy(metadata.UpdatedBy)
|
|
||||||
}
|
|
||||||
// Legacy support for setting Annotations, ManagedFields, and OwnerReferences via ExtraFields
|
|
||||||
if metadata.ExtraFields != nil {
|
|
||||||
if annotations, ok := metadata.ExtraFields["annotations"]; ok {
|
|
||||||
if cast, ok := annotations.(map[string]string); ok {
|
|
||||||
o.Annotations = cast
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if managedFields, ok := metadata.ExtraFields["managedFields"]; ok {
|
|
||||||
if cast, ok := managedFields.([]metav1.ManagedFieldsEntry); ok {
|
|
||||||
o.ManagedFields = cast
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ownerReferences, ok := metadata.ExtraFields["ownerReferences"]; ok {
|
|
||||||
if cast, ok := ownerReferences.([]metav1.OwnerReference); ok {
|
|
||||||
o.OwnerReferences = cast
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) GetCreatedBy() string {
|
|
||||||
if o.ObjectMeta.Annotations == nil {
|
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
return o.ObjectMeta.Annotations["grafana.com/createdBy"]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) SetCreatedBy(createdBy string) {
|
|
||||||
if o.ObjectMeta.Annotations == nil {
|
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) GetUpdateTimestamp() time.Time {
|
|
||||||
if o.ObjectMeta.Annotations == nil {
|
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"])
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) SetUpdateTimestamp(updateTimestamp time.Time) {
|
|
||||||
if o.ObjectMeta.Annotations == nil {
|
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) GetUpdatedBy() string {
|
|
||||||
if o.ObjectMeta.Annotations == nil {
|
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
return o.ObjectMeta.Annotations["grafana.com/updatedBy"]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) SetUpdatedBy(updatedBy string) {
|
|
||||||
if o.ObjectMeta.Annotations == nil {
|
|
||||||
o.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) Copy() resource.Object {
|
|
||||||
return resource.CopyObject(o)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) DeepCopyObject() runtime.Object {
|
|
||||||
return o.Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) DeepCopy() *InvestigationIndex {
|
|
||||||
cpy := &InvestigationIndex{}
|
|
||||||
o.DeepCopyInto(cpy)
|
|
||||||
return cpy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndex) DeepCopyInto(dst *InvestigationIndex) {
|
|
||||||
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
|
|
||||||
dst.TypeMeta.Kind = o.TypeMeta.Kind
|
|
||||||
o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta)
|
|
||||||
o.Spec.DeepCopyInto(&dst.Spec)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface compliance compile-time check
|
|
||||||
var _ resource.Object = &InvestigationIndex{}
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationIndexList struct {
|
|
||||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
|
||||||
metav1.ListMeta `json:"metadata" yaml:"metadata"`
|
|
||||||
Items []InvestigationIndex `json:"items" yaml:"items"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndexList) DeepCopyObject() runtime.Object {
|
|
||||||
return o.Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndexList) Copy() resource.ListObject {
|
|
||||||
cpy := &InvestigationIndexList{
|
|
||||||
TypeMeta: o.TypeMeta,
|
|
||||||
Items: make([]InvestigationIndex, len(o.Items)),
|
|
||||||
}
|
|
||||||
o.ListMeta.DeepCopyInto(&cpy.ListMeta)
|
|
||||||
for i := 0; i < len(o.Items); i++ {
|
|
||||||
if item, ok := o.Items[i].Copy().(*InvestigationIndex); ok {
|
|
||||||
cpy.Items[i] = *item
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cpy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndexList) GetItems() []resource.Object {
|
|
||||||
items := make([]resource.Object, len(o.Items))
|
|
||||||
for i := 0; i < len(o.Items); i++ {
|
|
||||||
items[i] = &o.Items[i]
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndexList) SetItems(items []resource.Object) {
|
|
||||||
o.Items = make([]InvestigationIndex, len(items))
|
|
||||||
for i := 0; i < len(items); i++ {
|
|
||||||
o.Items[i] = *items[i].(*InvestigationIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndexList) DeepCopy() *InvestigationIndexList {
|
|
||||||
cpy := &InvestigationIndexList{}
|
|
||||||
o.DeepCopyInto(cpy)
|
|
||||||
return cpy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *InvestigationIndexList) DeepCopyInto(dst *InvestigationIndexList) {
|
|
||||||
resource.CopyObjectInto(dst, o)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface compliance compile-time check
|
|
||||||
var _ resource.ListObject = &InvestigationIndexList{}
|
|
||||||
|
|
||||||
// Copy methods for all subresource types
|
|
||||||
|
|
||||||
// DeepCopy creates a full deep copy of Spec
|
|
||||||
func (s *InvestigationIndexSpec) DeepCopy() *InvestigationIndexSpec {
|
|
||||||
cpy := &InvestigationIndexSpec{}
|
|
||||||
s.DeepCopyInto(cpy)
|
|
||||||
return cpy
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyInto deep copies Spec into another Spec object
|
|
||||||
func (s *InvestigationIndexSpec) DeepCopyInto(dst *InvestigationIndexSpec) {
|
|
||||||
resource.CopyObjectInto(dst, s)
|
|
||||||
}
|
|
||||||
-34
@@ -1,34 +0,0 @@
|
|||||||
//
|
|
||||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
|
||||||
//
|
|
||||||
|
|
||||||
package v0alpha1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/grafana/grafana-app-sdk/resource"
|
|
||||||
)
|
|
||||||
|
|
||||||
// schema is unexported to prevent accidental overwrites
|
|
||||||
var (
|
|
||||||
schemaInvestigationIndex = resource.NewSimpleSchema("investigations.grafana.app", "v0alpha1", &InvestigationIndex{}, &InvestigationIndexList{}, resource.WithKind("InvestigationIndex"),
|
|
||||||
resource.WithPlural("investigationindexes"), resource.WithScope(resource.NamespacedScope))
|
|
||||||
kindInvestigationIndex = resource.Kind{
|
|
||||||
Schema: schemaInvestigationIndex,
|
|
||||||
Codecs: map[resource.KindEncoding]resource.Codec{
|
|
||||||
resource.KindEncodingJSON: &InvestigationIndexJSONCodec{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Kind returns a resource.Kind for this Schema with a JSON codec
|
|
||||||
func InvestigationIndexKind() resource.Kind {
|
|
||||||
return kindInvestigationIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schema returns a resource.SimpleSchema representation of InvestigationIndex
|
|
||||||
func InvestigationIndexSchema() *resource.SimpleSchema {
|
|
||||||
return schemaInvestigationIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface compliance checks
|
|
||||||
var _ resource.Schema = kindInvestigationIndex
|
|
||||||
-94
@@ -1,94 +0,0 @@
|
|||||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
|
||||||
|
|
||||||
package v0alpha1
|
|
||||||
|
|
||||||
// Person represents a user profile with basic information
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationIndexPerson struct {
|
|
||||||
// Unique identifier for the user
|
|
||||||
Uid string `json:"uid"`
|
|
||||||
// Display name of the user
|
|
||||||
Name string `json:"name"`
|
|
||||||
// URL to user's Gravatar image
|
|
||||||
GravatarUrl string `json:"gravatarUrl"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationIndexPerson creates a new InvestigationIndexPerson object.
|
|
||||||
func NewInvestigationIndexPerson() *InvestigationIndexPerson {
|
|
||||||
return &InvestigationIndexPerson{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type definition for investigation summaries
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationIndexInvestigationSummary struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
CreatedByProfile InvestigationIndexPerson `json:"createdByProfile"`
|
|
||||||
HasCustomName bool `json:"hasCustomName"`
|
|
||||||
IsFavorite bool `json:"isFavorite"`
|
|
||||||
OverviewNote string `json:"overviewNote"`
|
|
||||||
OverviewNoteUpdatedAt string `json:"overviewNoteUpdatedAt"`
|
|
||||||
ViewMode InvestigationIndexViewMode `json:"viewMode"`
|
|
||||||
// +listType=atomic
|
|
||||||
CollectableSummaries []InvestigationIndexCollectableSummary `json:"collectableSummaries"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationIndexInvestigationSummary creates a new InvestigationIndexInvestigationSummary object.
|
|
||||||
func NewInvestigationIndexInvestigationSummary() *InvestigationIndexInvestigationSummary {
|
|
||||||
return &InvestigationIndexInvestigationSummary{
|
|
||||||
CreatedByProfile: *NewInvestigationIndexPerson(),
|
|
||||||
ViewMode: *NewInvestigationIndexViewMode(),
|
|
||||||
CollectableSummaries: []InvestigationIndexCollectableSummary{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationIndexViewMode struct {
|
|
||||||
Mode InvestigationIndexViewModeMode `json:"mode"`
|
|
||||||
ShowComments bool `json:"showComments"`
|
|
||||||
ShowTooltips bool `json:"showTooltips"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationIndexViewMode creates a new InvestigationIndexViewMode object.
|
|
||||||
func NewInvestigationIndexViewMode() *InvestigationIndexViewMode {
|
|
||||||
return &InvestigationIndexViewMode{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationIndexCollectableSummary struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
LogoPath string `json:"logoPath"`
|
|
||||||
Origin string `json:"origin"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationIndexCollectableSummary creates a new InvestigationIndexCollectableSummary object.
|
|
||||||
func NewInvestigationIndexCollectableSummary() *InvestigationIndexCollectableSummary {
|
|
||||||
return &InvestigationIndexCollectableSummary{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationIndexSpec struct {
|
|
||||||
// Title of the index, e.g. 'Favorites' or 'My Investigations'
|
|
||||||
Title string `json:"title"`
|
|
||||||
// The Person who owns this investigation index
|
|
||||||
Owner InvestigationIndexPerson `json:"owner"`
|
|
||||||
// Array of investigation summaries
|
|
||||||
// +listType=atomic
|
|
||||||
InvestigationSummaries []InvestigationIndexInvestigationSummary `json:"investigationSummaries"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationIndexSpec creates a new InvestigationIndexSpec object.
|
|
||||||
func NewInvestigationIndexSpec() *InvestigationIndexSpec {
|
|
||||||
return &InvestigationIndexSpec{
|
|
||||||
Owner: *NewInvestigationIndexPerson(),
|
|
||||||
InvestigationSummaries: []InvestigationIndexInvestigationSummary{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationIndexViewModeMode string
|
|
||||||
|
|
||||||
const (
|
|
||||||
InvestigationIndexViewModeModeCompact InvestigationIndexViewModeMode = "compact"
|
|
||||||
InvestigationIndexViewModeModeFull InvestigationIndexViewModeMode = "full"
|
|
||||||
)
|
|
||||||
-44
@@ -1,44 +0,0 @@
|
|||||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
|
||||||
|
|
||||||
package v0alpha1
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationIndexstatusOperatorState struct {
|
|
||||||
// lastEvaluation is the ResourceVersion last evaluated
|
|
||||||
LastEvaluation string `json:"lastEvaluation"`
|
|
||||||
// state describes the state of the lastEvaluation.
|
|
||||||
// It is limited to three possible states for machine evaluation.
|
|
||||||
State InvestigationIndexStatusOperatorStateState `json:"state"`
|
|
||||||
// descriptiveState is an optional more descriptive state field which has no requirements on format
|
|
||||||
DescriptiveState *string `json:"descriptiveState,omitempty"`
|
|
||||||
// details contains any extra information that is operator-specific
|
|
||||||
Details map[string]interface{} `json:"details,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationIndexstatusOperatorState creates a new InvestigationIndexstatusOperatorState object.
|
|
||||||
func NewInvestigationIndexstatusOperatorState() *InvestigationIndexstatusOperatorState {
|
|
||||||
return &InvestigationIndexstatusOperatorState{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationIndexStatus struct {
|
|
||||||
// operatorStates is a map of operator ID to operator state evaluations.
|
|
||||||
// Any operator which consumes this kind SHOULD add its state evaluation information to this field.
|
|
||||||
OperatorStates map[string]InvestigationIndexstatusOperatorState `json:"operatorStates,omitempty"`
|
|
||||||
// additionalFields is reserved for future use
|
|
||||||
AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvestigationIndexStatus creates a new InvestigationIndexStatus object.
|
|
||||||
func NewInvestigationIndexStatus() *InvestigationIndexStatus {
|
|
||||||
return &InvestigationIndexStatus{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// +k8s:openapi-gen=true
|
|
||||||
type InvestigationIndexStatusOperatorStateState string
|
|
||||||
|
|
||||||
const (
|
|
||||||
InvestigationIndexStatusOperatorStateStateSuccess InvestigationIndexStatusOperatorStateState = "success"
|
|
||||||
InvestigationIndexStatusOperatorStateStateInProgress InvestigationIndexStatusOperatorStateState = "in_progress"
|
|
||||||
InvestigationIndexStatusOperatorStateStateFailed InvestigationIndexStatusOperatorStateState = "failed"
|
|
||||||
)
|
|
||||||
File diff suppressed because it is too large
Load Diff
-136
@@ -1,136 +0,0 @@
|
|||||||
//
|
|
||||||
// This file is generated by grafana-app-sdk
|
|
||||||
// DO NOT EDIT
|
|
||||||
//
|
|
||||||
|
|
||||||
package apis
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-app-sdk/app"
|
|
||||||
"github.com/grafana/grafana-app-sdk/resource"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/kube-openapi/pkg/spec3"
|
|
||||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
|
||||||
|
|
||||||
v0alpha1 "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
rawSchemaInvestigationv0alpha1 = []byte(`{"Collectable":{"additionalProperties":false,"description":"Collectable represents an item collected during investigation","properties":{"createdAt":{"type":"string"},"datasource":{"$ref":"#/components/schemas/DatasourceRef"},"fieldConfig":{"type":"string"},"id":{"type":"string"},"logoPath":{"type":"string"},"note":{"type":"string"},"noteUpdatedAt":{"type":"string"},"origin":{"type":"string"},"queries":{"description":"+listType=atomic","items":{"type":"string"},"type":"array"},"timeRange":{"$ref":"#/components/schemas/TimeRange"},"title":{"type":"string"},"type":{"type":"string"},"url":{"type":"string"}},"required":["id","createdAt","title","origin","type","queries","timeRange","datasource","url","note","noteUpdatedAt","fieldConfig"],"type":"object"},"DatasourceRef":{"additionalProperties":false,"description":"DatasourceRef is a reference to a datasource","properties":{"uid":{"type":"string"}},"required":["uid"],"type":"object"},"Investigation":{"properties":{"spec":{"$ref":"#/components/schemas/spec"}},"required":["spec"]},"Person":{"additionalProperties":false,"description":"Person represents a user profile with basic information","properties":{"gravatarUrl":{"description":"URL to user's Gravatar image","type":"string"},"name":{"description":"Display name of the user","type":"string"},"uid":{"description":"Unique identifier for the user","type":"string"}},"required":["uid","name","gravatarUrl"],"type":"object"},"TimeRange":{"additionalProperties":false,"description":"TimeRange represents a time range with both absolute and relative values","properties":{"from":{"type":"string"},"raw":{"additionalProperties":false,"properties":{"from":{"type":"string"},"to":{"type":"string"}},"required":["from","to"],"type":"object"},"to":{"type":"string"}},"required":["from","to","raw"],"type":"object"},"ViewMode":{"additionalProperties":false,"properties":{"mode":{"enum":["compact","full"],"type":"string"},"showComments":{"type":"boolean"},"showTooltips":{"type":"boolean"}},"required":["mode","showComments","showTooltips"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"collectables":{"description":"+listType=atomic","items":{"$ref":"#/components/schemas/Collectable"},"type":"array"},"createdByProfile":{"$ref":"#/components/schemas/Person"},"hasCustomName":{"type":"boolean"},"isFavorite":{"type":"boolean"},"overviewNote":{"type":"string"},"overviewNoteUpdatedAt":{"type":"string"},"title":{"type":"string"},"viewMode":{"$ref":"#/components/schemas/ViewMode"}},"required":["title","createdByProfile","hasCustomName","isFavorite","overviewNote","overviewNoteUpdatedAt","collectables","viewMode"],"type":"object"}}`)
|
|
||||||
versionSchemaInvestigationv0alpha1 app.VersionSchema
|
|
||||||
_ = json.Unmarshal(rawSchemaInvestigationv0alpha1, &versionSchemaInvestigationv0alpha1)
|
|
||||||
rawSchemaInvestigationIndexv0alpha1 = []byte(`{"CollectableSummary":{"additionalProperties":false,"properties":{"id":{"type":"string"},"logoPath":{"type":"string"},"origin":{"type":"string"},"title":{"type":"string"}},"required":["id","title","logoPath","origin"],"type":"object"},"InvestigationIndex":{"properties":{"spec":{"$ref":"#/components/schemas/spec"}},"required":["spec"]},"InvestigationSummary":{"additionalProperties":false,"description":"Type definition for investigation summaries","properties":{"collectableSummaries":{"description":"+listType=atomic","items":{"$ref":"#/components/schemas/CollectableSummary"},"type":"array"},"createdByProfile":{"$ref":"#/components/schemas/Person"},"hasCustomName":{"type":"boolean"},"isFavorite":{"type":"boolean"},"overviewNote":{"type":"string"},"overviewNoteUpdatedAt":{"type":"string"},"title":{"type":"string"},"viewMode":{"$ref":"#/components/schemas/ViewMode"}},"required":["title","createdByProfile","hasCustomName","isFavorite","overviewNote","overviewNoteUpdatedAt","viewMode","collectableSummaries"],"type":"object"},"Person":{"additionalProperties":false,"description":"Person represents a user profile with basic information","properties":{"gravatarUrl":{"description":"URL to user's Gravatar image","type":"string"},"name":{"description":"Display name of the user","type":"string"},"uid":{"description":"Unique identifier for the user","type":"string"}},"required":["uid","name","gravatarUrl"],"type":"object"},"ViewMode":{"additionalProperties":false,"properties":{"mode":{"enum":["compact","full"],"type":"string"},"showComments":{"type":"boolean"},"showTooltips":{"type":"boolean"}},"required":["mode","showComments","showTooltips"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"investigationSummaries":{"description":"Array of investigation summaries\n+listType=atomic","items":{"$ref":"#/components/schemas/InvestigationSummary"},"type":"array"},"owner":{"$ref":"#/components/schemas/Person","description":"The Person who owns this investigation index"},"title":{"description":"Title of the index, e.g. 'Favorites' or 'My Investigations'","type":"string"}},"required":["title","owner","investigationSummaries"],"type":"object"}}`)
|
|
||||||
versionSchemaInvestigationIndexv0alpha1 app.VersionSchema
|
|
||||||
_ = json.Unmarshal(rawSchemaInvestigationIndexv0alpha1, &versionSchemaInvestigationIndexv0alpha1)
|
|
||||||
)
|
|
||||||
|
|
||||||
var appManifestData = app.ManifestData{
|
|
||||||
AppName: "investigations",
|
|
||||||
Group: "investigations.grafana.app",
|
|
||||||
PreferredVersion: "v0alpha1",
|
|
||||||
Versions: []app.ManifestVersion{
|
|
||||||
{
|
|
||||||
Name: "v0alpha1",
|
|
||||||
Served: true,
|
|
||||||
Kinds: []app.ManifestVersionKind{
|
|
||||||
{
|
|
||||||
Kind: "Investigation",
|
|
||||||
Plural: "Investigations",
|
|
||||||
Scope: "Namespaced",
|
|
||||||
Conversion: false,
|
|
||||||
Schema: &versionSchemaInvestigationv0alpha1,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
Kind: "InvestigationIndex",
|
|
||||||
Plural: "InvestigationIndexes",
|
|
||||||
Scope: "Namespaced",
|
|
||||||
Conversion: false,
|
|
||||||
Schema: &versionSchemaInvestigationIndexv0alpha1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Routes: app.ManifestVersionRoutes{
|
|
||||||
Namespaced: map[string]spec3.PathProps{},
|
|
||||||
Cluster: map[string]spec3.PathProps{},
|
|
||||||
Schemas: map[string]spec.Schema{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func LocalManifest() app.Manifest {
|
|
||||||
return app.NewEmbeddedManifest(appManifestData)
|
|
||||||
}
|
|
||||||
|
|
||||||
func RemoteManifest() app.Manifest {
|
|
||||||
return app.NewAPIServerManifest("investigations")
|
|
||||||
}
|
|
||||||
|
|
||||||
var kindVersionToGoType = map[string]resource.Kind{
|
|
||||||
"Investigation/v0alpha1": v0alpha1.InvestigationKind(),
|
|
||||||
"InvestigationIndex/v0alpha1": v0alpha1.InvestigationIndexKind(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// ManifestGoTypeAssociator returns the associated resource.Kind instance for a given Kind and Version, if one exists.
|
|
||||||
// If there is no association for the provided Kind and Version, exists will return false.
|
|
||||||
func ManifestGoTypeAssociator(kind, version string) (goType resource.Kind, exists bool) {
|
|
||||||
goType, exists = kindVersionToGoType[fmt.Sprintf("%s/%s", kind, version)]
|
|
||||||
return goType, exists
|
|
||||||
}
|
|
||||||
|
|
||||||
var customRouteToGoResponseType = map[string]any{}
|
|
||||||
|
|
||||||
// ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists.
|
|
||||||
// kind may be empty for custom routes which are not kind subroutes. Leading slashes are removed from subroute paths.
|
|
||||||
// If there is no association for the provided kind, version, custom route path, and method, exists will return false.
|
|
||||||
// Resource routes (those without a kind) should prefix their route with "<namespace>/" if the route is namespaced (otherwise the route is assumed to be cluster-scope)
|
|
||||||
func ManifestCustomRouteResponsesAssociator(kind, version, path, verb string) (goType any, exists bool) {
|
|
||||||
if len(path) > 0 && path[0] == '/' {
|
|
||||||
path = path[1:]
|
|
||||||
}
|
|
||||||
goType, exists = customRouteToGoResponseType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))]
|
|
||||||
return goType, exists
|
|
||||||
}
|
|
||||||
|
|
||||||
var customRouteToGoParamsType = map[string]runtime.Object{}
|
|
||||||
|
|
||||||
func ManifestCustomRouteQueryAssociator(kind, version, path, verb string) (goType runtime.Object, exists bool) {
|
|
||||||
if len(path) > 0 && path[0] == '/' {
|
|
||||||
path = path[1:]
|
|
||||||
}
|
|
||||||
goType, exists = customRouteToGoParamsType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))]
|
|
||||||
return goType, exists
|
|
||||||
}
|
|
||||||
|
|
||||||
var customRouteToGoRequestBodyType = map[string]any{}
|
|
||||||
|
|
||||||
func ManifestCustomRouteRequestBodyAssociator(kind, version, path, verb string) (goType any, exists bool) {
|
|
||||||
if len(path) > 0 && path[0] == '/' {
|
|
||||||
path = path[1:]
|
|
||||||
}
|
|
||||||
goType, exists = customRouteToGoRequestBodyType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))]
|
|
||||||
return goType, exists
|
|
||||||
}
|
|
||||||
|
|
||||||
type GoTypeAssociator struct{}
|
|
||||||
|
|
||||||
func NewGoTypeAssociator() *GoTypeAssociator {
|
|
||||||
return &GoTypeAssociator{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GoTypeAssociator) KindToGoType(kind, version string) (goType resource.Kind, exists bool) {
|
|
||||||
return ManifestGoTypeAssociator(kind, version)
|
|
||||||
}
|
|
||||||
func (g *GoTypeAssociator) CustomRouteReturnGoType(kind, version, path, verb string) (goType any, exists bool) {
|
|
||||||
return ManifestCustomRouteResponsesAssociator(kind, version, path, verb)
|
|
||||||
}
|
|
||||||
func (g *GoTypeAssociator) CustomRouteQueryGoType(kind, version, path, verb string) (goType runtime.Object, exists bool) {
|
|
||||||
return ManifestCustomRouteQueryAssociator(kind, version, path, verb)
|
|
||||||
}
|
|
||||||
func (g *GoTypeAssociator) CustomRouteRequestBodyGoType(kind, version, path, verb string) (goType any, exists bool) {
|
|
||||||
return ManifestCustomRouteRequestBodyAssociator(kind, version, path, verb)
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana-app-sdk/app"
|
|
||||||
"github.com/grafana/grafana-app-sdk/operator"
|
|
||||||
"github.com/grafana/grafana-app-sdk/resource"
|
|
||||||
"github.com/grafana/grafana-app-sdk/simple"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
|
|
||||||
investigationsv0alpha1 "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func New(cfg app.Config) (app.App, error) {
|
|
||||||
var err error
|
|
||||||
simpleConfig := simple.AppConfig{
|
|
||||||
Name: "investigation",
|
|
||||||
KubeConfig: cfg.KubeConfig,
|
|
||||||
InformerConfig: simple.AppInformerConfig{
|
|
||||||
InformerOptions: operator.InformerOptions{
|
|
||||||
ErrorHandler: func(_ context.Context, err error) {
|
|
||||||
klog.ErrorS(err, "Informer processing error")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ManagedKinds: []simple.AppManagedKind{
|
|
||||||
{
|
|
||||||
Kind: investigationsv0alpha1.InvestigationKind(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Kind: investigationsv0alpha1.InvestigationIndexKind(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
a, err := simple.NewApp(simpleConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = a.ValidateManifest(cfg.ManifestData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return a, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetKinds() map[schema.GroupVersion][]resource.Kind {
|
|
||||||
gv := schema.GroupVersion{
|
|
||||||
Group: investigationsv0alpha1.InvestigationKind().Group(),
|
|
||||||
Version: investigationsv0alpha1.InvestigationKind().Version(),
|
|
||||||
}
|
|
||||||
return map[schema.GroupVersion][]resource.Kind{
|
|
||||||
gv: {
|
|
||||||
investigationsv0alpha1.InvestigationKind(),
|
|
||||||
investigationsv0alpha1.InvestigationIndexKind(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user