Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 92f1fba9b4 | |||
| 563ca4aa39 | |||
| f1d4c6433b | |||
| ccf0c5b40e | |||
| 6569f64267 | |||
| 1cc2a4cbe7 | |||
| 93c9887bc4 | |||
| 66d8379061 | |||
| 7e708e5976 | |||
| a9f59cf340 | |||
| 02fadf48fc | |||
| 496911e716 | |||
| 6d8ad119bf | |||
| 49f78c15e8 | |||
| 76340a9741 | |||
| b15acdf1f2 | |||
| ca8402fbda | |||
| abb44794fe | |||
| c228eaa99d | |||
| f41cc1c0d6 | |||
| b557d71c9a | |||
| e404352a38 | |||
| 454380431d | |||
| 2f2950eb29 | |||
| a95f556e24 | |||
| 7c95d3c8a9 | |||
| 854a8f7e70 | |||
| ed69a1f16d | |||
| 67c26c493e | |||
| 22ed5499a2 | |||
| 7fd9ab9481 | |||
| 544872d117 | |||
| 2e9432b9d5 | |||
| badea8bc37 | |||
| 8a28381278 | |||
| ace05e999d | |||
| 958f5a7c52 | |||
| 3c5c6b8185 | |||
| d09708fe55 | |||
| 08230cbc09 | |||
| bc843913e4 | |||
| 7020bdd7e1 | |||
| d9f0d642cc | |||
| 7bccabfb1f | |||
| 4497af20b2 | |||
| 2f558e8cb7 | |||
| 1fd4611487 | |||
| 23fa9a1484 | |||
| 76f7836419 | |||
| 6f3d2106c0 | |||
| c072ace9cd | |||
| 0df8e42f4e | |||
| d21178e348 | |||
| 04c3d9bff1 | |||
| 62cc0f9c0e | |||
| e3e0a1b8ca | |||
| 005da25698 | |||
| 2750a3516a | |||
| d58b8aff7b | |||
| 9445328a59 | |||
| 7cbc55d615 | |||
| f9e82aba9c | |||
| ed2273b2d2 | |||
| 8e8c36203f | |||
| e2913815d3 | |||
| 46258ac2c1 | |||
| dc1c5a610c | |||
| 0f54622db7 | |||
| 24107abea3 | |||
| d715bda8af | |||
| f0095d84e3 | |||
| 02227855e8 | |||
| d692303e76 | |||
| ba202ebab1 | |||
| a459d43746 | |||
| ce55d70fa5 | |||
| 439fefeda8 | |||
| c4bc83019d | |||
| 3e14a48ebb | |||
| 9a641c651f | |||
| b4e63c36c3 | |||
| 801fde02a7 | |||
| 726c7ba71b | |||
| bd529226a3 | |||
| feb4368de5 | |||
| 647c1424d4 | |||
| 9f7101e2ad | |||
| 9920f4b437 | |||
| 5eb42ece91 | |||
| 1b9e479b68 | |||
| 6a54340501 | |||
| a8378af6ed |
@@ -416,6 +416,7 @@
|
||||
/e2e-playwright/dashboards/cujs/ @grafana/dashboards-squad
|
||||
/e2e-playwright/dashboards/DashboardForConditionalRendering.json @grafana/dashboards-squad
|
||||
/e2e-playwright/dashboards/DashboardWithAllConditionalRendering.json @grafana/dashboards-squad
|
||||
/e2e-playwright/dashboards/README.md @grafana/dashboards-squad
|
||||
/e2e-playwright/dashboards/AdHocFilterTest.json @grafana/datapro
|
||||
/e2e-playwright/dashboards/DashboardLiveTest.json @grafana/dashboards-squad
|
||||
/e2e-playwright/dashboards/DataLinkWithoutSlugTest.json @grafana/dashboards-squad
|
||||
@@ -460,6 +461,7 @@
|
||||
/e2e-playwright/fixtures/long-trace-response.json @grafana/observability-traces-and-profiling
|
||||
/e2e-playwright/fixtures/tempo-response.json @grafana/oss-big-tent
|
||||
/e2e-playwright/fixtures/prometheus-response.json @grafana/datapro
|
||||
/e2e-playwright/panels-suite/canvas-scene.spec.ts @grafana/dataviz-squad
|
||||
/e2e-playwright/panels-suite/dashlist.spec.ts @grafana/grafana-search-navigate-organise
|
||||
/e2e-playwright/panels-suite/datagrid-data-change.spec.ts @grafana/dataviz-squad
|
||||
/e2e-playwright/panels-suite/datagrid-editing-features.spec.ts @grafana/dataviz-squad
|
||||
@@ -1137,6 +1139,8 @@ eslint-suppressions.json @grafanabot
|
||||
# Feature toggles
|
||||
/pkg/services/featuremgmt/ @grafana/grafana-backend-services-squad
|
||||
|
||||
# Data source migrations
|
||||
/pkg/services/promtypemigration/ @grafana/partner-datasources @grafana/aws-datasources
|
||||
|
||||
# Kind definitions
|
||||
/kinds/dashboard @grafana/dashboards-squad
|
||||
|
||||
@@ -13,17 +13,29 @@ on:
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
bump-version:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Grafana
|
||||
uses: actions/checkout@v4
|
||||
- uses: grafana/shared-workflows/actions/get-vault-secrets@main
|
||||
with:
|
||||
persist-credentials: false
|
||||
repo_secrets: |
|
||||
GRAFANA_DELIVERY_BOT_APP_PEM=delivery-bot-app:PRIVATE_KEY
|
||||
- name: Generate token
|
||||
id: generate_token
|
||||
uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a
|
||||
with:
|
||||
app_id: ${{ vars.DELIVERY_BOT_APP_ID }}
|
||||
private_key: ${{ env.GRAFANA_DELIVERY_BOT_APP_PEM }}
|
||||
repositories: '["grafana"]'
|
||||
permissions: '{"contents": "write", "pull_requests": "write", "workflows": "write"}'
|
||||
- name: Checkout Grafana
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
- name: Update package.json versions
|
||||
uses: ./pkg/build/actions/bump-version
|
||||
with:
|
||||
@@ -35,10 +47,10 @@ jobs:
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
RUN_ID: ${{ github.run_id }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
run: |
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "grafana-delivery-bot[bot]"
|
||||
git config --local user.email "grafana-delivery-bot[bot]@users.noreply.github.com"
|
||||
git config --local --add --bool push.autoSetupRemote true
|
||||
git checkout -b "bump-version/${RUN_ID}/${VERSION}"
|
||||
git add .
|
||||
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up version (Release Branches)
|
||||
@@ -140,7 +140,7 @@ jobs:
|
||||
# The downside to this is that the frontend will be built for each one when it could be reused for all of them.
|
||||
# This could be a future improvement.
|
||||
include:
|
||||
- name: linux-amd64
|
||||
- name: linux-amd64 # publish-npm relies on this step building npm packages
|
||||
artifacts: targz:grafana:linux/amd64,deb:grafana:linux/amd64,rpm:grafana:linux/amd64,docker:grafana:linux/amd64,docker:grafana:linux/amd64:ubuntu,npm:grafana,storybook
|
||||
verify: true
|
||||
- name: linux-arm64
|
||||
@@ -169,7 +169,7 @@ jobs:
|
||||
verify: true
|
||||
steps:
|
||||
- uses: grafana/shared-workflows/actions/dockerhub-login@dockerhub-login/v1.0.2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up QEMU
|
||||
@@ -197,6 +197,7 @@ jobs:
|
||||
name: artifacts-${{ matrix.name }}
|
||||
path: ${{ steps.build.outputs.dist-dir }}
|
||||
retention-days: 1
|
||||
|
||||
publish-artifacts:
|
||||
name: Upload artifacts
|
||||
uses: grafana/grafana/.github/workflows/publish-artifact.yml@main
|
||||
@@ -211,6 +212,7 @@ jobs:
|
||||
run-id: ${{ github.run_id }}
|
||||
bucket-path: ${{ needs.setup.outputs.version }}_${{ github.run_id }}
|
||||
environment: prod
|
||||
|
||||
publish-dockerhub:
|
||||
if: github.ref_name == 'main'
|
||||
permissions:
|
||||
@@ -268,3 +270,68 @@ jobs:
|
||||
docker manifest push grafana/grafana:main-ubuntu
|
||||
docker manifest push "grafana/grafana-dev:${VERSION}"
|
||||
docker manifest push "grafana/grafana-dev:${VERSION}-ubuntu"
|
||||
|
||||
publish-npm-canaries:
|
||||
if: github.ref_name == 'main'
|
||||
name: Publish NPM canaries
|
||||
uses: ./.github/workflows/release-npm.yml
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
needs:
|
||||
- setup
|
||||
- build
|
||||
with:
|
||||
grafana_commit: ${{ needs.setup.outputs.grafana-commit }}
|
||||
version: ${{ needs.setup.outputs.version }}
|
||||
build_id: ${{ github.run_id }}
|
||||
version_type: "canary"
|
||||
|
||||
# notify-pr creates (or updates) a comment in a pull request to link to this workflow where the release artifacts are
|
||||
# being built.
|
||||
notify-pr:
|
||||
runs-on: ubuntu-x64-small
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
needs:
|
||||
- setup
|
||||
steps:
|
||||
- id: vault-secrets
|
||||
uses: grafana/shared-workflows/actions/get-vault-secrets@main
|
||||
with:
|
||||
repo_secrets: |
|
||||
GRAFANA_DELIVERY_BOT_APP_PEM=delivery-bot-app:PRIVATE_KEY
|
||||
- name: Generate token
|
||||
id: generate_token
|
||||
uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a
|
||||
with:
|
||||
app_id: ${{ vars.DELIVERY_BOT_APP_ID }}
|
||||
private_key: ${{ env.GRAFANA_DELIVERY_BOT_APP_PEM }}
|
||||
repositories: '["grafana"]'
|
||||
permissions: '{"issues": "write", "pull_requests": "write", "contents": "read"}'
|
||||
- name: Find PR
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
GRAFANA_COMMIT: ${{ needs.setup.outputs.grafana-commit }}
|
||||
run: echo "ISSUE_NUMBER=$(gh api "/repos/grafana/grafana/commits/${GRAFANA_COMMIT}/pulls" | jq -r '.[0].number')" >> "$GITHUB_ENV"
|
||||
- name: Find Comment
|
||||
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ env.ISSUE_NUMBER }}
|
||||
comment-author: 'grafana-delivery-bot[bot]'
|
||||
body-includes: GitHub Actions Build
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
- name: Create or update comment
|
||||
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
|
||||
with:
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
issue-number: ${{ env.ISSUE_NUMBER }}
|
||||
body: |
|
||||
:rocket: Your submission is now being built and packaged.
|
||||
|
||||
- [GitHub Actions Build](https://github.com/grafana/grafana/actions/runs/${{ github.run_id }})
|
||||
- Version: ${{ needs.setup.outputs.version }}
|
||||
edit-mode: replace
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
name: Release NPM packages
|
||||
run-name: Publish NPM ${{ inputs.version_type }} ${{ inputs.version }}
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
grafana_commit:
|
||||
description: 'Grafana commit SHA to build against'
|
||||
required: true
|
||||
type: string
|
||||
version:
|
||||
description: 'Version to publish as'
|
||||
required: true
|
||||
type: string
|
||||
build_id:
|
||||
description: 'Run ID from the original release-build workflow'
|
||||
required: true
|
||||
type: string
|
||||
version_type:
|
||||
description: 'Version type (canary, nightly, stable)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
grafana_commit:
|
||||
description: 'Grafana commit SHA to build against'
|
||||
required: true
|
||||
version:
|
||||
description: 'Version to publish as'
|
||||
required: true
|
||||
build_id:
|
||||
description: 'Run ID from the original release-build workflow'
|
||||
required: true
|
||||
version_type:
|
||||
description: 'Version type (canary, nightly, stable)'
|
||||
required: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
# If called with version_type 'canary' or 'stable', build + publish to NPM
|
||||
# If called with version_type 'nightly', just tag the given version with nightly tag. It was already published by the canary build.
|
||||
|
||||
publish:
|
||||
name: Publish NPM packages
|
||||
runs-on: github-hosted-ubuntu-x64-small
|
||||
if: inputs.version_type == 'canary' || inputs.version_type == 'stable'
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Info
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
GRAFANA_COMMIT: ${{ inputs.grafana_commit }}
|
||||
run: |
|
||||
echo "GRAFANA_COMMIT: $GRAFANA_COMMIT"
|
||||
echo "github.ref: $GITHUB_REF"
|
||||
|
||||
- name: Checkout workflow ref
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 100
|
||||
fetch-tags: false
|
||||
|
||||
# this will fail with "{commit} is not a valid commit" if the commit is valid but
|
||||
# not in the last 100 commits.
|
||||
- name: Verify commit is in workflow HEAD
|
||||
env:
|
||||
GIT_COMMIT: ${{ inputs.grafana_commit }}
|
||||
run: ./.github/workflows/scripts/validate-commit-in-head.sh
|
||||
shell: bash
|
||||
|
||||
- name: Map version type to NPM tag
|
||||
id: npm-tag
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
VERSION_TYPE: ${{ inputs.version_type }}
|
||||
REFERENCE_PKG: "@grafana/runtime"
|
||||
run: |
|
||||
TAG=$(./.github/workflows/scripts/determine-npm-tag.sh)
|
||||
echo "NPM_TAG=$TAG" >> "$GITHUB_OUTPUT"
|
||||
shell: bash
|
||||
|
||||
- name: Checkout build commit
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ inputs.grafana_commit }}
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
# Trusted Publishing is only available in npm v11.5.1 and later
|
||||
- name: Update npm
|
||||
run: npm install -g npm@^11.5.1
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable
|
||||
|
||||
- name: Typecheck packages
|
||||
run: yarn run packages:typecheck
|
||||
|
||||
- name: Version, build, and pack packages
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
yarn run packages:build
|
||||
yarn lerna version "$VERSION" \
|
||||
--exact \
|
||||
--no-git-tag-version \
|
||||
--no-push \
|
||||
--force-publish \
|
||||
--yes
|
||||
yarn run packages:pack
|
||||
|
||||
- name: Debug packed files
|
||||
run: tree -a ./npm-artifacts
|
||||
|
||||
- name: Validate packages
|
||||
run: ./scripts/validate-npm-packages.sh
|
||||
|
||||
- name: Debug OIDC Claims
|
||||
uses: github/actions-oidc-debugger@2e9ba5d3f4bebaad1f91a2cede055115738b7ae8
|
||||
with:
|
||||
audience: '${{ github.server_url }}/${{ github.repository_owner }}'
|
||||
|
||||
- name: Publish packages
|
||||
env:
|
||||
NPM_TAG: ${{ steps.npm-tag.outputs.NPM_TAG }}
|
||||
run: ./scripts/publish-npm-packages.sh --dist-tag "$NPM_TAG" --registry 'https://registry.npmjs.org/'
|
||||
|
||||
# TODO: finish this step
|
||||
tag-nightly:
|
||||
name: Tag nightly release
|
||||
runs-on: github-hosted-ubuntu-x64-small
|
||||
needs: publish
|
||||
if: inputs.version_type == 'nightly'
|
||||
|
||||
steps:
|
||||
- name: Checkout workflow ref
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# TODO: tag the given release with nightly
|
||||
|
||||
|
||||
@@ -245,3 +245,7 @@ public/mockServiceWorker.js
|
||||
|
||||
/e2e-playwright/test-plugins/*/dist
|
||||
/apps/provisioning/cmd/job-controller/bin/
|
||||
|
||||
|
||||
# Ignore unified storage kv store files
|
||||
/grafana-kv-data
|
||||
|
||||
@@ -684,8 +684,8 @@ VariableSort: "disabled" | "alphabeticalAsc" | "alphabeticalDesc" | "numericalAs
|
||||
VariableRefresh: *"never" | "onDashboardLoad" | "onTimeRangeChanged"
|
||||
|
||||
// Determine if the variable shows on dashboard
|
||||
// Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing).
|
||||
VariableHide: *"dontHide" | "hideLabel" | "hideVariable"
|
||||
// Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing), `inControlsMenu` (show in a drop-down menu).
|
||||
VariableHide: *"dontHide" | "hideLabel" | "hideVariable" | "inControlsMenu"
|
||||
|
||||
// Determine the origin of the adhoc variable filter
|
||||
FilterOrigin: "dashboard"
|
||||
|
||||
@@ -78,7 +78,7 @@ lineage: schemas: [{
|
||||
|
||||
// Version of the JSON schema, incremented each time a Grafana update brings
|
||||
// changes to said schema.
|
||||
schemaVersion: uint16 | *41
|
||||
schemaVersion: uint16 | *42
|
||||
|
||||
// Version of the dashboard, incremented each time the dashboard is updated.
|
||||
version?: uint32
|
||||
@@ -243,8 +243,8 @@ lineage: schemas: [{
|
||||
#VariableRefresh: 0 | 1 | 2 @cuetsy(kind="enum",memberNames="never|onDashboardLoad|onTimeRangeChanged")
|
||||
|
||||
// Determine if the variable shows on dashboard
|
||||
// Accepted values are 0 (show label and value), 1 (show value only), 2 (show nothing).
|
||||
#VariableHide: 0 | 1 | 2 @cuetsy(kind="enum",memberNames="dontHide|hideLabel|hideVariable") @grafana(TSVeneer="type")
|
||||
// Accepted values are 0 (show label and value), 1 (show value only), 2 (show nothing), 3 (show under the controls dropdown menu).
|
||||
#VariableHide: 0 | 1 | 2 | 3 @cuetsy(kind="enum",memberNames="dontHide|hideLabel|hideVariable|inControlsMenu") @grafana(TSVeneer="type")
|
||||
|
||||
// Sort variable options
|
||||
// Accepted values are:
|
||||
|
||||
@@ -78,7 +78,7 @@ lineage: schemas: [{
|
||||
|
||||
// Version of the JSON schema, incremented each time a Grafana update brings
|
||||
// changes to said schema.
|
||||
schemaVersion: uint16 | *41
|
||||
schemaVersion: uint16 | *42
|
||||
|
||||
// Version of the dashboard, incremented each time the dashboard is updated.
|
||||
version?: uint32
|
||||
@@ -243,8 +243,8 @@ lineage: schemas: [{
|
||||
#VariableRefresh: 0 | 1 | 2 @cuetsy(kind="enum",memberNames="never|onDashboardLoad|onTimeRangeChanged")
|
||||
|
||||
// Determine if the variable shows on dashboard
|
||||
// Accepted values are 0 (show label and value), 1 (show value only), 2 (show nothing).
|
||||
#VariableHide: 0 | 1 | 2 @cuetsy(kind="enum",memberNames="dontHide|hideLabel|hideVariable") @grafana(TSVeneer="type")
|
||||
// Accepted values are 0 (show label and value), 1 (show value only), 2 (show nothing), 3 (show under the controls dropdown menu).
|
||||
#VariableHide: 0 | 1 | 2 | 3 @cuetsy(kind="enum",memberNames="dontHide|hideLabel|hideVariable|inControlsMenu") @grafana(TSVeneer="type")
|
||||
|
||||
// Sort variable options
|
||||
// Accepted values are:
|
||||
|
||||
@@ -688,8 +688,8 @@ VariableSort: "disabled" | "alphabeticalAsc" | "alphabeticalDesc" | "numericalAs
|
||||
VariableRefresh: *"never" | "onDashboardLoad" | "onTimeRangeChanged"
|
||||
|
||||
// Determine if the variable shows on dashboard
|
||||
// Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing).
|
||||
VariableHide: *"dontHide" | "hideLabel" | "hideVariable"
|
||||
// Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing), `inControlsMenu` (show in a drop-down menu).
|
||||
VariableHide: *"dontHide" | "hideLabel" | "hideVariable" | "inControlsMenu"
|
||||
|
||||
// Determine the origin of the adhoc variable filter
|
||||
FilterOrigin: "dashboard"
|
||||
|
||||
@@ -1282,14 +1282,15 @@ func NewDashboardVariableOption() *DashboardVariableOption {
|
||||
}
|
||||
|
||||
// Determine if the variable shows on dashboard
|
||||
// Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing).
|
||||
// Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing), `inControlsMenu` (show in a drop-down menu).
|
||||
// +k8s:openapi-gen=true
|
||||
type DashboardVariableHide string
|
||||
|
||||
const (
|
||||
DashboardVariableHideDontHide DashboardVariableHide = "dontHide"
|
||||
DashboardVariableHideHideLabel DashboardVariableHide = "hideLabel"
|
||||
DashboardVariableHideHideVariable DashboardVariableHide = "hideVariable"
|
||||
DashboardVariableHideDontHide DashboardVariableHide = "dontHide"
|
||||
DashboardVariableHideHideLabel DashboardVariableHide = "hideLabel"
|
||||
DashboardVariableHideHideVariable DashboardVariableHide = "hideVariable"
|
||||
DashboardVariableHideInControlsMenu DashboardVariableHide = "inControlsMenu"
|
||||
)
|
||||
|
||||
// Options to config when to refresh a variable
|
||||
|
||||
@@ -278,15 +278,15 @@ func TestConversionMetrics(t *testing.T) {
|
||||
ObjectMeta: metav1.ObjectMeta{UID: "test-uid-2"},
|
||||
Spec: common.Unstructured{Object: map[string]any{
|
||||
"title": "test dashboard",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
}},
|
||||
},
|
||||
target: &dashv0.Dashboard{},
|
||||
expectSuccess: true,
|
||||
expectedSourceAPI: dashv1.APIVERSION,
|
||||
expectedTargetAPI: dashv0.APIVERSION,
|
||||
expectedSourceSchema: "41",
|
||||
expectedTargetSchema: "41", // V1→V0 keeps same schema version
|
||||
expectedSourceSchema: "42",
|
||||
expectedTargetSchema: "42", // V1→V0 keeps same schema version
|
||||
},
|
||||
{
|
||||
name: "successful v2alpha1 to v2beta1 conversion",
|
||||
@@ -600,7 +600,7 @@ func TestConversionLogging(t *testing.T) {
|
||||
"targetVersionAPI": dashv1.APIVERSION,
|
||||
"dashboardUID": "test-uid-log-1",
|
||||
"sourceSchemaVersion": "20",
|
||||
"targetSchemaVersion": fmt.Sprintf("%d", 41), // LATEST_VERSION
|
||||
"targetSchemaVersion": fmt.Sprintf("%d", 42), // LATEST_VERSION
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -620,7 +620,7 @@ func TestConversionLogging(t *testing.T) {
|
||||
"targetVersionAPI": dashv1.APIVERSION,
|
||||
"dashboardUID": "test-uid-log-2",
|
||||
"sourceSchemaVersion": "5",
|
||||
"targetSchemaVersion": fmt.Sprintf("%d", 41), // LATEST_VERSION
|
||||
"targetSchemaVersion": fmt.Sprintf("%d", 42), // LATEST_VERSION
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
const (
|
||||
MIN_VERSION = 13
|
||||
LATEST_VERSION = 41
|
||||
LATEST_VERSION = 42
|
||||
|
||||
// The pluginVersion to set after simulating auto-migrate for angular panels
|
||||
pluginVersionForAutoMigrate = "12.1.0"
|
||||
@@ -66,6 +66,7 @@ func GetMigrations(dsInfoProvider DataSourceInfoProvider) map[int]SchemaVersionM
|
||||
39: V39,
|
||||
40: V40,
|
||||
41: V41,
|
||||
42: V42,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package schemaversion
|
||||
|
||||
import "context"
|
||||
|
||||
// V42 ensures that when a field is hidden from visualization, it is also hidden from tooltips.
|
||||
//
|
||||
// This migration addresses the inconsistency where fields could be hidden from visualizations
|
||||
// (hideFrom.viz = true) but would still appear in tooltips. To prevent user confusion and ensure
|
||||
// consistent behavior, this migration automatically sets hideFrom.tooltip = true for any field
|
||||
// configuration override that has hideFrom.viz = true.
|
||||
//
|
||||
// The migration specifically targets field configuration overrides, including the special
|
||||
// __systemRef override, and updates the hideFrom object to include tooltip: true whenever
|
||||
// viz: true is found.
|
||||
//
|
||||
// Example transformation:
|
||||
//
|
||||
// Before migration:
|
||||
//
|
||||
// fieldConfig: {
|
||||
// overrides: [{
|
||||
// properties: [{
|
||||
// id: "custom.hideFrom",
|
||||
// value: { viz: true }
|
||||
// }]
|
||||
// }]
|
||||
// }
|
||||
//
|
||||
// After migration:
|
||||
//
|
||||
// fieldConfig: {
|
||||
// overrides: [{
|
||||
// properties: [{
|
||||
// id: "custom.hideFrom",
|
||||
// value: { viz: true, tooltip: true }
|
||||
// }]
|
||||
// }]
|
||||
// }
|
||||
func V42(_ context.Context, dash map[string]interface{}) error {
|
||||
dash["schemaVersion"] = int(42)
|
||||
|
||||
// Get panels from dashboard
|
||||
panels, ok := dash["panels"].([]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Process each panel
|
||||
for _, panelInterface := range panels {
|
||||
panel, ok := panelInterface.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
migrateHideFromForPanel(panel)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// migrateHideFromForPanel processes a single panel and its nested panels
|
||||
func migrateHideFromForPanel(panel map[string]interface{}) {
|
||||
// Process the panel's field config
|
||||
if fieldConfig, ok := panel["fieldConfig"].(map[string]interface{}); ok {
|
||||
if overrides, ok := fieldConfig["overrides"].([]interface{}); ok {
|
||||
for _, overrideInterface := range overrides {
|
||||
override, ok := overrideInterface.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if properties, ok := override["properties"].([]interface{}); ok {
|
||||
for _, propertyInterface := range properties {
|
||||
property, ok := propertyInterface.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if this is a custom.hideFrom property
|
||||
if id, ok := property["id"].(string); ok && id == "custom.hideFrom" {
|
||||
if value, ok := property["value"].(map[string]interface{}); ok {
|
||||
// If viz is true, also set tooltip to true
|
||||
if GetBoolValue(value, "viz") {
|
||||
value["tooltip"] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process nested panels (for rows)
|
||||
if nestedPanels, ok := panel["panels"].([]interface{}); ok {
|
||||
for _, nestedPanelInterface := range nestedPanels {
|
||||
if nestedPanel, ok := nestedPanelInterface.(map[string]interface{}); ok {
|
||||
migrateHideFromForPanel(nestedPanel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
package schemaversion_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion"
|
||||
)
|
||||
|
||||
func TestV42(t *testing.T) {
|
||||
tests := []migrationTestCase{
|
||||
{
|
||||
name: "hideFrom.viz = true should also set hideFrom.tooltip = true",
|
||||
input: map[string]interface{}{
|
||||
"title": "Test Dashboard",
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"title": "Panel 1",
|
||||
"fieldConfig": map[string]interface{}{
|
||||
"overrides": []interface{}{
|
||||
map[string]interface{}{
|
||||
"matcher": map[string]interface{}{
|
||||
"id": "byName",
|
||||
"options": "Field 1",
|
||||
},
|
||||
"properties": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": "custom.hideFrom",
|
||||
"value": map[string]interface{}{
|
||||
"viz": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"title": "Test Dashboard",
|
||||
"schemaVersion": 42,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"title": "Panel 1",
|
||||
"fieldConfig": map[string]interface{}{
|
||||
"overrides": []interface{}{
|
||||
map[string]interface{}{
|
||||
"matcher": map[string]interface{}{
|
||||
"id": "byName",
|
||||
"options": "Field 1",
|
||||
},
|
||||
"properties": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": "custom.hideFrom",
|
||||
"value": map[string]interface{}{
|
||||
"viz": true,
|
||||
"tooltip": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "hideFrom.viz = false should not change",
|
||||
input: map[string]interface{}{
|
||||
"title": "Test Dashboard",
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"title": "Panel 1",
|
||||
"fieldConfig": map[string]interface{}{
|
||||
"overrides": []interface{}{
|
||||
map[string]interface{}{
|
||||
"properties": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": "custom.hideFrom",
|
||||
"value": map[string]interface{}{
|
||||
"viz": false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"title": "Test Dashboard",
|
||||
"schemaVersion": 42,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"title": "Panel 1",
|
||||
"fieldConfig": map[string]interface{}{
|
||||
"overrides": []interface{}{
|
||||
map[string]interface{}{
|
||||
"properties": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": "custom.hideFrom",
|
||||
"value": map[string]interface{}{
|
||||
"viz": false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple panels with hideFrom.viz = true",
|
||||
input: map[string]interface{}{
|
||||
"title": "Test Dashboard",
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"fieldConfig": map[string]interface{}{
|
||||
"overrides": []interface{}{
|
||||
map[string]interface{}{
|
||||
"properties": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": "custom.hideFrom",
|
||||
"value": map[string]interface{}{
|
||||
"viz": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"id": 2,
|
||||
"fieldConfig": map[string]interface{}{
|
||||
"overrides": []interface{}{
|
||||
map[string]interface{}{
|
||||
"properties": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": "custom.hideFrom",
|
||||
"value": map[string]interface{}{
|
||||
"viz": true,
|
||||
"legend": false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"title": "Test Dashboard",
|
||||
"schemaVersion": 42,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"fieldConfig": map[string]interface{}{
|
||||
"overrides": []interface{}{
|
||||
map[string]interface{}{
|
||||
"properties": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": "custom.hideFrom",
|
||||
"value": map[string]interface{}{
|
||||
"viz": true,
|
||||
"tooltip": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"id": 2,
|
||||
"fieldConfig": map[string]interface{}{
|
||||
"overrides": []interface{}{
|
||||
map[string]interface{}{
|
||||
"properties": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": "custom.hideFrom",
|
||||
"value": map[string]interface{}{
|
||||
"viz": true,
|
||||
"legend": false,
|
||||
"tooltip": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "panel without hideFrom property",
|
||||
input: map[string]interface{}{
|
||||
"title": "Test Dashboard",
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"title": "Panel 1",
|
||||
"fieldConfig": map[string]interface{}{
|
||||
"overrides": []interface{}{
|
||||
map[string]interface{}{
|
||||
"properties": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": "unit",
|
||||
"value": "short",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"title": "Test Dashboard",
|
||||
"schemaVersion": 42,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"title": "Panel 1",
|
||||
"fieldConfig": map[string]interface{}{
|
||||
"overrides": []interface{}{
|
||||
map[string]interface{}{
|
||||
"properties": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": "unit",
|
||||
"value": "short",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nested panels in rows should also be migrated",
|
||||
input: map[string]interface{}{
|
||||
"title": "Test Dashboard",
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"type": "row",
|
||||
"title": "Row 1",
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 2,
|
||||
"fieldConfig": map[string]interface{}{
|
||||
"overrides": []interface{}{
|
||||
map[string]interface{}{
|
||||
"properties": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": "custom.hideFrom",
|
||||
"value": map[string]interface{}{
|
||||
"viz": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"title": "Test Dashboard",
|
||||
"schemaVersion": 42,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"type": "row",
|
||||
"title": "Row 1",
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 2,
|
||||
"fieldConfig": map[string]interface{}{
|
||||
"overrides": []interface{}{
|
||||
map[string]interface{}{
|
||||
"properties": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": "custom.hideFrom",
|
||||
"value": map[string]interface{}{
|
||||
"viz": true,
|
||||
"tooltip": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "__systemRef override should also be migrated",
|
||||
input: map[string]interface{}{
|
||||
"title": "Test Dashboard",
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"fieldConfig": map[string]interface{}{
|
||||
"overrides": []interface{}{
|
||||
map[string]interface{}{
|
||||
"__systemRef": "hideSeriesFrom",
|
||||
"matcher": map[string]interface{}{
|
||||
"id": "byNames",
|
||||
"options": map[string]interface{}{
|
||||
"mode": "exclude",
|
||||
"names": []interface{}{"foo"},
|
||||
"prefix": "All except:",
|
||||
"readOnly": true,
|
||||
},
|
||||
},
|
||||
"properties": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": "custom.hideFrom",
|
||||
"value": map[string]interface{}{
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"title": "Test Dashboard",
|
||||
"schemaVersion": 42,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"fieldConfig": map[string]interface{}{
|
||||
"overrides": []interface{}{
|
||||
map[string]interface{}{
|
||||
"__systemRef": "hideSeriesFrom",
|
||||
"matcher": map[string]interface{}{
|
||||
"id": "byNames",
|
||||
"options": map[string]interface{}{
|
||||
"mode": "exclude",
|
||||
"names": []interface{}{"foo"},
|
||||
"prefix": "All except:",
|
||||
"readOnly": true,
|
||||
},
|
||||
},
|
||||
"properties": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": "custom.hideFrom",
|
||||
"value": map[string]interface{}{
|
||||
"legend": false,
|
||||
"tooltip": true,
|
||||
"viz": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "dashboard without panels",
|
||||
input: map[string]interface{}{
|
||||
"title": "Test Dashboard",
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"title": "Test Dashboard",
|
||||
"schemaVersion": 42,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "panel without fieldConfig",
|
||||
input: map[string]interface{}{
|
||||
"title": "Test Dashboard",
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"title": "Panel 1",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"title": "Test Dashboard",
|
||||
"schemaVersion": 42,
|
||||
"panels": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"title": "Panel 1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runMigrationTests(t, tests, schemaversion.V42)
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
{
|
||||
"title": "v42 Migration Test - HideFrom Tooltip",
|
||||
"schemaVersion": 41,
|
||||
"panels": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Panel with hideFrom.viz = true",
|
||||
"type": "timeseries",
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Field1"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "custom.hideFrom",
|
||||
"value": {
|
||||
"viz": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Panel with multiple overrides",
|
||||
"type": "graph",
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Field2"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "custom.hideFrom",
|
||||
"value": {
|
||||
"viz": true,
|
||||
"legend": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "__systemRef",
|
||||
"options": "hiddenSeries"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "custom.hideFrom",
|
||||
"value": {
|
||||
"viz": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "row",
|
||||
"title": "Row with nested panels",
|
||||
"collapsed": true,
|
||||
"panels": [
|
||||
{
|
||||
"id": 4,
|
||||
"title": "Nested panel with hideFrom",
|
||||
"type": "stat",
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byRegexp",
|
||||
"options": "/.*/"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "custom.hideFrom",
|
||||
"value": {
|
||||
"viz": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"title": "Panel without hideFrom",
|
||||
"type": "table",
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": [
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "short"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"title": "Panel with viz false (should not be modified)",
|
||||
"type": "gauge",
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": [
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"id": "custom.hideFrom",
|
||||
"value": {
|
||||
"viz": false,
|
||||
"tooltip": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"title": "Panel with already set tooltip (should not be modified)",
|
||||
"type": "barchart",
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": [
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"id": "custom.hideFrom",
|
||||
"value": {
|
||||
"viz": true,
|
||||
"tooltip": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -125,7 +125,7 @@
|
||||
}
|
||||
],
|
||||
"refresh": "1m",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"style": "dark",
|
||||
"tags": [
|
||||
"example-service",
|
||||
|
||||
@@ -3886,7 +3886,7 @@
|
||||
}
|
||||
],
|
||||
"refresh": "1m",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"style": "dark",
|
||||
"tags": [
|
||||
"example-service",
|
||||
|
||||
@@ -3888,7 +3888,7 @@
|
||||
}
|
||||
],
|
||||
"refresh": "1m",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"style": "dark",
|
||||
"tags": [
|
||||
"example-service",
|
||||
|
||||
@@ -2262,7 +2262,7 @@
|
||||
}
|
||||
],
|
||||
"refresh": "5m",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"style": "dark",
|
||||
"tags": [
|
||||
"metrics",
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
|
||||
+1
-1
@@ -938,7 +938,7 @@
|
||||
}
|
||||
],
|
||||
"refresh": "30s",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"style": "dark",
|
||||
"tags": [
|
||||
"sample-monitoring"
|
||||
|
||||
@@ -482,6 +482,6 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"title": "V16 Grid Layout Migration Test Dashboard"
|
||||
}
|
||||
@@ -312,6 +312,6 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"title": "V17 MinSpan to MaxPerRow Migration Test Dashboard"
|
||||
}
|
||||
@@ -157,6 +157,6 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"title": "V18 Gauge Options Migration Test Dashboard"
|
||||
}
|
||||
@@ -201,6 +201,6 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"title": "V19 Panel Links Migration Test Dashboard"
|
||||
}
|
||||
+1
-1
@@ -215,7 +215,7 @@
|
||||
}
|
||||
],
|
||||
"refresh": "5s",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"style": "dark",
|
||||
"tags": [
|
||||
"migration-test"
|
||||
|
||||
+1
-1
@@ -161,6 +161,6 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"title": "V21 Data Links Series to Field Migration Test Dashboard"
|
||||
}
|
||||
@@ -88,6 +88,6 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"title": "V22 Table Panel Styles Test"
|
||||
}
|
||||
+1
-1
@@ -30,7 +30,7 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
|
||||
@@ -1369,5 +1369,5 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41
|
||||
}
|
||||
"schemaVersion": 42
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
|
||||
@@ -68,5 +68,5 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41
|
||||
"schemaVersion": 42
|
||||
}
|
||||
Vendored
+1
-1
@@ -64,7 +64,7 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"panels": [],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
|
||||
+1
-1
@@ -262,7 +262,7 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
|
||||
@@ -535,7 +535,7 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
|
||||
+1
-1
@@ -368,6 +368,6 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"title": "V30 Value Mappings and Tooltip Options Migration Test Dashboard"
|
||||
}
|
||||
+1
-1
@@ -285,6 +285,6 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"title": "V31 LabelsToFields Merge Migration Test Dashboard"
|
||||
}
|
||||
@@ -124,7 +124,7 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
|
||||
@@ -265,6 +265,6 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"title": "V33 Panel Datasource Name to Ref Test"
|
||||
}
|
||||
+1
-1
@@ -634,6 +634,6 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"title": "CloudWatch Multiple Statistics Test Dashboard"
|
||||
}
|
||||
+1
-1
@@ -255,6 +255,6 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"title": "X-Axis Visibility Test Dashboard"
|
||||
}
|
||||
@@ -322,7 +322,7 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
|
||||
@@ -125,6 +125,6 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"title": "V37 Legend Normalization Test Dashboard"
|
||||
}
|
||||
+1
-1
@@ -218,6 +218,6 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"title": "V38 Table Migration Comprehensive Test Dashboard"
|
||||
}
|
||||
+1
-1
@@ -218,6 +218,6 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"title": "V38 Table Migration Test Dashboard"
|
||||
}
|
||||
+1
-1
@@ -154,6 +154,6 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"title": "V39 TimeSeriesTable Transformation Migration Test Dashboard"
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"panels": [],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"panels": [],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"panels": [],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"panels": [],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"panels": [],
|
||||
"refresh": "1m",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"panels": [],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"panels": [],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"timepicker": {
|
||||
"refresh_intervals": [
|
||||
"5s",
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"panels": [],
|
||||
"refresh": "",
|
||||
"schemaVersion": 41,
|
||||
"schemaVersion": 42,
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
{
|
||||
"panels": [
|
||||
{
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Field1"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "custom.hideFrom",
|
||||
"value": {
|
||||
"tooltip": true,
|
||||
"viz": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": 1,
|
||||
"title": "Panel with hideFrom.viz = true",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Field2"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "custom.hideFrom",
|
||||
"value": {
|
||||
"legend": false,
|
||||
"tooltip": true,
|
||||
"viz": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "__systemRef",
|
||||
"options": "hiddenSeries"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "custom.hideFrom",
|
||||
"value": {
|
||||
"tooltip": true,
|
||||
"viz": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": 2,
|
||||
"title": "Panel with multiple overrides",
|
||||
"type": "graph"
|
||||
},
|
||||
{
|
||||
"collapsed": true,
|
||||
"id": 3,
|
||||
"panels": [
|
||||
{
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byRegexp",
|
||||
"options": "/.*/"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "custom.hideFrom",
|
||||
"value": {
|
||||
"tooltip": true,
|
||||
"viz": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": 4,
|
||||
"title": "Nested panel with hideFrom",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": [
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "short"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": 5,
|
||||
"title": "Panel without hideFrom",
|
||||
"type": "table"
|
||||
}
|
||||
],
|
||||
"title": "Row with nested panels",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": [
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"id": "custom.hideFrom",
|
||||
"value": {
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": 6,
|
||||
"title": "Panel with viz false (should not be modified)",
|
||||
"type": "gauge"
|
||||
},
|
||||
{
|
||||
"fieldConfig": {
|
||||
"defaults": {},
|
||||
"overrides": [
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"id": "custom.hideFrom",
|
||||
"value": {
|
||||
"tooltip": true,
|
||||
"viz": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": 7,
|
||||
"title": "Panel with already set tooltip (should not be modified)",
|
||||
"type": "barchart"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 42,
|
||||
"title": "v42 Migration Test - HideFrom Tooltip"
|
||||
}
|
||||
@@ -109,7 +109,7 @@ func LoadConfigFromEnv() (*Config, error) {
|
||||
cfg.KubeConfig = kubeConfig
|
||||
}
|
||||
|
||||
cfg.ZanzanaClient.Address = os.Getenv("ZANZANA_ADDR")
|
||||
cfg.ZanzanaClient.URL = os.Getenv("ZANZANA_ADDR")
|
||||
cfg.ZanzanaClient.Token = os.Getenv("ZANZANA_TOKEN")
|
||||
cfg.ZanzanaClient.TokenExchangeURL = os.Getenv("TOKEN_EXCHANGE_URL")
|
||||
cfg.ZanzanaClient.ServerCertFile = os.Getenv("ZANZANA_SERVER_CERT_FILE")
|
||||
|
||||
@@ -744,8 +744,6 @@ github.com/grafana/grafana-aws-sdk v1.1.0 h1:G0fvwbQmHw14c5RXPd7Gnw9ZQcgzl139LtM
|
||||
github.com/grafana/grafana-aws-sdk v1.1.0/go.mod h1:7e+47EdHynteYWGoT5Ere9KeOXQObsk8F0vkOLQ1tz8=
|
||||
github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0 h1:0TYrkzAc3u0HX+9GK86cGrLTUAcmQfl3/LEB3tL+SOA=
|
||||
github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0/go.mod h1:H9sVh9A4yg5egMGZeh0mifxT1Q/uqwKe1LBjBJU6pN8=
|
||||
github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 h1:r+mU5bGMzcXCRVAuOrTn54S80qbfVkvTdUJZfSfTNbs=
|
||||
github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79/go.mod h1:wc6Hbh3K2TgCUSfBC/BOzabItujtHMESZeFk5ZhdxhQ=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.278.0 h1:5/rIYparLi02pofdaag8wnjspMMVNCi8cZhC4cdC3Ho=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.278.0/go.mod h1:+8NXT/XUJ/89GV6FxGQ366NZ3nU+cAXDMd0OUESF9H4=
|
||||
github.com/grafana/grafana/pkg/promlib v0.0.8 h1:VUWsqttdf0wMI4j9OX9oNrykguQpZcruudDAFpJJVw0=
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package v0alpha1
|
||||
|
||||
ServiceAccountSpec: {
|
||||
disabled: bool |* false
|
||||
plugin: string
|
||||
role: OrgRole
|
||||
title: string
|
||||
disabled: bool
|
||||
}
|
||||
|
||||
OrgRole: "None" | "Viewer" | "Editor" | "Admin" @cuetsy(kind="enum")
|
||||
|
||||
@@ -2,13 +2,27 @@
|
||||
|
||||
package v0alpha1
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type ServiceAccountOrgRole string
|
||||
|
||||
const (
|
||||
ServiceAccountOrgRoleNone ServiceAccountOrgRole = "None"
|
||||
ServiceAccountOrgRoleViewer ServiceAccountOrgRole = "Viewer"
|
||||
ServiceAccountOrgRoleEditor ServiceAccountOrgRole = "Editor"
|
||||
ServiceAccountOrgRoleAdmin ServiceAccountOrgRole = "Admin"
|
||||
)
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type ServiceAccountSpec struct {
|
||||
Title string `json:"title"`
|
||||
Disabled bool `json:"disabled"`
|
||||
Disabled bool `json:"disabled"`
|
||||
Plugin string `json:"plugin"`
|
||||
Role ServiceAccountOrgRole `json:"role"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
// NewServiceAccountSpec creates a new ServiceAccountSpec object.
|
||||
func NewServiceAccountSpec() *ServiceAccountSpec {
|
||||
return &ServiceAccountSpec{}
|
||||
return &ServiceAccountSpec{
|
||||
Disabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1877,13 +1877,6 @@ func schema_pkg_apis_iam_v0alpha1_ServiceAccountSpec(ref common.ReferenceCallbac
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"title": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"disabled": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: false,
|
||||
@@ -1891,8 +1884,29 @@ func schema_pkg_apis_iam_v0alpha1_ServiceAccountSpec(ref common.ReferenceCallbac
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"plugin": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"role": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"title": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"title", "disabled"},
|
||||
Required: []string{"disabled", "plugin", "role", "title"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -25,6 +25,11 @@ func (c *ZanzanaPermissionStore) SetFolderParent(ctx context.Context, namespace,
|
||||
return err
|
||||
}
|
||||
|
||||
if parentUID == "" {
|
||||
// Setting the parent to empty means the folder is at root which Zanzana doesn't care about.
|
||||
return nil
|
||||
}
|
||||
|
||||
user, err := toFolderTuple(parentUID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/authlib/authn"
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
)
|
||||
|
||||
@@ -19,20 +18,22 @@ type tokenExchanger interface {
|
||||
type RoundTripper struct {
|
||||
client tokenExchanger
|
||||
transport http.RoundTripper
|
||||
audience string
|
||||
}
|
||||
|
||||
// NewRoundTripper constructs a RoundTripper that exchanges the provided token per request
|
||||
// and forwards the request to the provided base transport.
|
||||
func NewRoundTripper(tokenExchangeClient tokenExchanger, base http.RoundTripper) *RoundTripper {
|
||||
func NewRoundTripper(tokenExchangeClient tokenExchanger, base http.RoundTripper, audience string) *RoundTripper {
|
||||
return &RoundTripper{
|
||||
client: tokenExchangeClient,
|
||||
transport: base,
|
||||
audience: audience,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
tokenResponse, err := t.client.Exchange(req.Context(), authn.TokenExchangeRequest{
|
||||
Audiences: []string{provisioning.GROUP},
|
||||
Audiences: []string{t.audience},
|
||||
Namespace: "*",
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -34,7 +34,7 @@ func TestRoundTripper_SetsAccessTokenHeader(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
rr.WriteHeader(http.StatusOK)
|
||||
return rr.Result(), nil
|
||||
}))
|
||||
}), "example-audience")
|
||||
|
||||
req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://example", nil)
|
||||
resp, err := tr.RoundTrip(req)
|
||||
@@ -50,7 +50,7 @@ func TestRoundTripper_PropagatesExchangeError(t *testing.T) {
|
||||
tr := NewRoundTripper(&fakeExchanger{err: io.EOF}, roundTripperFunc(func(_ *http.Request) (*http.Response, error) {
|
||||
t.Fatal("transport should not be called on exchange error")
|
||||
return nil, nil
|
||||
}))
|
||||
}), "example-audience")
|
||||
|
||||
req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://example", nil)
|
||||
resp, err := tr.RoundTrip(req)
|
||||
|
||||
@@ -1903,8 +1903,6 @@ public_key_retrieval_disabled = false
|
||||
public_key_retrieval_on_startup = false
|
||||
# Enter a comma-separated list of plugin identifiers to avoid loading (including core plugins). These plugins will be hidden in the catalog.
|
||||
disable_plugins =
|
||||
# Comma separated list of plugin ids for which angular deprecation UI should be disabled
|
||||
hide_angular_deprecation =
|
||||
# Comma separated list of plugin ids for which environment variables should be forwarded. Used only when feature flag pluginsSkipHostEnvVars is enabled.
|
||||
forward_host_env_vars =
|
||||
# Comma separated list of plugin ids to install as part of the startup process.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -94,6 +94,7 @@
|
||||
"rows-to-fields": (import '../dev-dashboards/transforms/rows-to-fields.json'),
|
||||
"shared_queries": (import '../dev-dashboards/panel-common/shared_queries.json'),
|
||||
"slow_queries_and_annotations": (import '../dev-dashboards/scenarios/slow_queries_and_annotations.json'),
|
||||
"table_footer": (import '../dev-dashboards/panel-table/table_footer.json'),
|
||||
"table_kitchen_sink": (import '../dev-dashboards/panel-table/table_kitchen_sink.json'),
|
||||
"table_markdown": (import '../dev-dashboards/panel-table/table_markdown.json'),
|
||||
"table_pagination": (import '../dev-dashboards/panel-table/table_pagination.json'),
|
||||
|
||||
@@ -75,6 +75,16 @@ refs:
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-overrides/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-overrides/
|
||||
saved-queries:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/#saved-queries
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/#saved-queries
|
||||
save-query:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/#save-a-query
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/#save-a-query
|
||||
---
|
||||
|
||||
## Create a dashboard
|
||||
@@ -108,11 +118,21 @@ Dashboards and panels allow you to show your data in visual form. Each panel nee
|
||||
{{< figure class="float-right" src="/media/docs/grafana/dashboards/screenshot-data-source-selector-10.0.png" max-width="800px" alt="Select data source modal" >}}
|
||||
|
||||
The **Edit panel** view opens with your data source selected.
|
||||
You can change the panel data source later using the drop-down in the **Query** tab of the panel editor if needed.
|
||||
You can change the panel data source later using the drop-down in the **Queries** tab of the panel editor if needed.
|
||||
|
||||
For more information about data sources, refer to [Data sources](ref:data-sources) for specific guidelines.
|
||||
|
||||
1. Write or construct a query in the query language of your data source.
|
||||
1. To add a query, do one of the following:
|
||||
- Write or construct a query in the query language of your data source.
|
||||
- Click **+ Add from saved queries** to add a previously saved query.
|
||||
- If you've already written a query, you can click the **Replace with saved query** icon to use a previously saved query instead.
|
||||
|
||||
1. (Optional) To [save the query](ref:save-query) for reuse, click the **Save query** icon.
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
[Saved queries](ref:saved-queries) is in [public preview](https://grafana.com/docs/release-life-cycle/) in Grafana Enterprise and Cloud only.
|
||||
{{< /admonition >}}
|
||||
|
||||
1. Click **Refresh** to query the data source.
|
||||
1. In the visualization list, select a visualization type.
|
||||
|
||||
|
||||
@@ -298,16 +298,21 @@ groupByNode(summarize(movingAverage(apps.$app.$server.counters.requests.count, 5
|
||||
## Add ad hoc filters
|
||||
|
||||
_Ad hoc filters_ are one of the most complex and flexible variable options available.
|
||||
Instead of a regular list of variable options, this variable allows you to build a dashboard-wide ad hoc query.
|
||||
Instead of creating a variable for each dimension by which you want to filter, ad hoc filters automatically create variables (key/value pairs) for all the dimensions returned by your data source query.
|
||||
This allows you to apply filters dashboard-wide.
|
||||
|
||||
Ad hoc filters let you add label/value filters that are automatically added to all metric queries that use the specified data source.
|
||||
Unlike other variables, you don't use ad hoc filters in queries.
|
||||
Instead, you use ad hoc filters to write filters for existing queries.
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
Not all data sources support ad hoc filters.
|
||||
Examples of those that do include Prometheus, Loki, InfluxDB, and Elasticsearch.
|
||||
{{< /admonition >}}
|
||||
The following data sources support ad hoc filters:
|
||||
|
||||
- Dashboard - Use this special data source to [apply ad hoc filters to data from unsupported data sources](#filter-any-data-using-the-dashboard-data-source).
|
||||
- Prometheus
|
||||
- Loki
|
||||
- InfluxDB
|
||||
- Elasticsearch
|
||||
- OpenSearch
|
||||
|
||||
To create an ad hoc filter, follow these steps:
|
||||
|
||||
@@ -324,6 +329,60 @@ To create an ad hoc filter, follow these steps:
|
||||
|
||||
Now you can [filter data on the dashboard](ref:filter-dashboard).
|
||||
|
||||
### Filter any data using the Dashboard data source
|
||||
|
||||
In cases where a data source doesn't support the use of ad hoc filters, you can use the Dashboard data source to reference that data, and then filter it in a new panel.
|
||||
This allows you to bypass the limitations of the data source in the source panel.
|
||||
|
||||
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-adhoc-filter-dashboard-ds-v12.2.png" max-width="750px" alt="The query section of a panel with the Dashboard data source configured" >}}
|
||||
|
||||
To use ad hoc filters on data from an unsupported data source, follow these steps:
|
||||
|
||||
1. Navigate to the dashboard with the panel with the data you want to filter.
|
||||
1. Click **Edit** in top-right corner of the dashboard.
|
||||
1. At the top of the dashboard, click **Add** and select **Visualization** in the drop-down list.
|
||||
1. In the **Queries** tab of the edit panel view, enter `Dashboard` in the **Data source** field and select **-- Dashboard --**.
|
||||
1. In the query configuration section, make the following selections:
|
||||
- **Source panel** - Choose the panel with the source data.
|
||||
- **Data** - Select **All Data** to use the data of the panel, and not just the annotations. This is the default selection.
|
||||
- **AdHoc Filters** - Toggle on the switch to make the data from the referenced panel filterable.
|
||||
|
||||
{{< admonition type="note">}}
|
||||
If you're referencing multiple panels in a dashboard with the Dashboard data source, you can only use one of those source panels at a time for ad hoc filtering.
|
||||
{{< /admonition >}}
|
||||
|
||||
1. Configure any other needed options for the panel.
|
||||
1. Click **Save dashboard**.
|
||||
|
||||
Now you can filter the data from the source panel by way of the Dashboard data source.
|
||||
Add as many panels as you need.
|
||||
|
||||
### Dashboard drilldown with ad hoc filters
|
||||
|
||||
In table and bar chart visualizations, you can apply ad hoc filters directly from the visualization.
|
||||
To quickly apply ad hoc filter variables, follow these steps:
|
||||
|
||||
1. To display the filter icons, hover your cursor over the table cell with the value for which you want to filter. In this example, the cell value is `ConfigMap Updated`, which is in the `alertname` column:
|
||||
|
||||
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-adhoc-filter-icon-v12.2.png" max-width="550px" alt="Table and bar chart with ad hoc filter icon displayed on a table cell" >}}
|
||||
|
||||
In bar chart visualizations, hover and click the bar to display the filter button:
|
||||
|
||||
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-adhoc-filter-icon-bar-v12.2.png" max-width="300px" alt="The ad hoc filter button in a bar chart tooltip">}}
|
||||
|
||||
1. Click the add filter icon.
|
||||
|
||||
The variable pair `alertname = ConfigMap Updated` is added to the ad hoc filter and all panels using the same data source that include that variable value are filtered by that value:
|
||||
|
||||
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-adhoc-filter-applied-v12.2.png" max-width="550px" alt="Table and bar chart, filtered" >}}
|
||||
|
||||
If one of the panels in the dashboard using that data source doesn't include that variable value, the panel won't return any data. In this example, the variable pair `_name_ = ALERTS` has been added to the ad hoc filter so the bar chart doesn't return any results:
|
||||
|
||||
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-adhoc-filter-no-data-v12.2.png" max-width="650px" alt="Table, filtered and bar chart returning no results" >}}
|
||||
|
||||
In cases where the data source you're using doesn't support ad hoc filtering, consider using the special Dashboard data source.
|
||||
For more information, refer to [Filter any data using the Dashboard data source](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/dashboards/variables/add-template-variables/#filter-any-data-using-the-dashboard-data-source).
|
||||
|
||||
<!-- vale Grafana.Spelling = YES -->
|
||||
<!-- vale Grafana.WordList = YES -->
|
||||
|
||||
|
||||
@@ -255,34 +255,34 @@ After you have provisioned a data source you cannot edit it.
|
||||
|
||||
**Example of a Prometheus data source configuration:**
|
||||
|
||||
```yaml
|
||||
apiVersion: 1
|
||||
```yaml
|
||||
apiVersion: 1
|
||||
|
||||
datasources:
|
||||
- name: Prometheus
|
||||
type: prometheus
|
||||
access: proxy
|
||||
url: http://localhost:9090
|
||||
jsonData:
|
||||
httpMethod: POST
|
||||
manageAlerts: true
|
||||
allowAsRecordingRulesTarget: true
|
||||
prometheusType: Prometheus
|
||||
prometheusVersion: 3.3.0
|
||||
cacheLevel: 'High'
|
||||
disableRecordingRules: false
|
||||
timeInterval: 10s # Prometheus scrape interval
|
||||
incrementalQueryOverlapWindow: 10m
|
||||
exemplarTraceIdDestinations:
|
||||
# Field with internal link pointing to data source in Grafana.
|
||||
# datasourceUid value can be anything, but it should be unique across all defined data source uids.
|
||||
- datasourceUid: my_jaeger_uid
|
||||
name: traceID
|
||||
datasources:
|
||||
- name: Prometheus
|
||||
type: prometheus
|
||||
access: proxy
|
||||
url: http://localhost:9090
|
||||
jsonData:
|
||||
httpMethod: POST
|
||||
manageAlerts: true
|
||||
allowAsRecordingRulesTarget: true
|
||||
prometheusType: Prometheus
|
||||
prometheusVersion: 3.3.0
|
||||
cacheLevel: 'High'
|
||||
disableRecordingRules: false
|
||||
timeInterval: 10s # Prometheus scrape interval
|
||||
incrementalQueryOverlapWindow: 10m
|
||||
exemplarTraceIdDestinations:
|
||||
# Field with internal link pointing to data source in Grafana.
|
||||
# datasourceUid value can be anything, but it should be unique across all defined data source uids.
|
||||
- datasourceUid: my_jaeger_uid
|
||||
name: traceID
|
||||
|
||||
# Field with external link.
|
||||
- name: traceID
|
||||
url: 'http://localhost:3000/explore?orgId=1&left=%5B%22now-1h%22,%22now%22,%22Jaeger%22,%7B%22query%22:%22$${__value.raw}%22%7D%5D'
|
||||
```
|
||||
# Field with external link.
|
||||
- name: traceID
|
||||
url: 'http://localhost:3000/explore?orgId=1&left=%5B%22now-1h%22,%22now%22,%22Jaeger%22,%7B%22query%22:%22$${__value.raw}%22%7D%5D'
|
||||
```
|
||||
|
||||
## Azure authentication settings
|
||||
|
||||
|
||||
@@ -11,6 +11,17 @@ labels:
|
||||
- enterprise
|
||||
- oss
|
||||
title: Get started with Explore
|
||||
refs:
|
||||
saved-queries:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/#saved-queries
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/#saved-queries
|
||||
save-query:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/#save-a-query
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/#save-a-query
|
||||
weight: 5
|
||||
---
|
||||
|
||||
@@ -61,8 +72,14 @@ Explore consists of a toolbar, outline, query editor, the ability to add multipl
|
||||
|
||||
- **Query editor** - Interface where you construct the query for a specific data source. Query editor elements differ based on data source. In order to run queries across multiple data sources you need to select **Mixed** from the data source picker.
|
||||
|
||||
- **+Add query** - Add additional queries.
|
||||
- **Query history** - Query history contains the list of queries that you created in Explore. Refer to [Query history](/docs/grafana/<GRAFANA_VERSION>/explore/query-management/#query-history) for detailed information on working with your query history.
|
||||
- **+ Add query** - Add additional queries.
|
||||
- **+ Add from saved queries** - Add a saved query. If you've already written a query, you can click the **Replace with saved query** icon to use a previously saved query instead. To [save the query](ref:save-query) for reuse, click the **Save query** icon.
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
[Saved queries](ref:saved-queries) is in [public preview](https://grafana.com/docs/release-life-cycle/) in Grafana Enterprise and Cloud only.
|
||||
{{< /admonition >}}
|
||||
|
||||
- **Query history** - Query history contains the list of queries that you created in Explore. You can also add queries from the history to your saved queries. Refer to [Query history](/docs/grafana/<GRAFANA_VERSION>/explore/query-management/#query-history) for detailed information on working with your query history.
|
||||
- **Query inspector** - Provides detailed statistics regarding your query. Inspector functions as a kind of debugging tool that "inspects" your query. It provides query statistics under **Stats**, request response time under **Query**, data frame details under **{} JSON**, and the shape of your data under **Data**. Refer to [Query inspector in Explore](/docs/grafana/latest/explore/explore-inspector/) for additional information.
|
||||
|
||||
## Access Explore
|
||||
|
||||
@@ -10,6 +10,12 @@ labels:
|
||||
- oss
|
||||
title: Query management in Explore
|
||||
weight: 10
|
||||
refs:
|
||||
saved-queries:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/#saved-queries
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/#saved-queries
|
||||
---
|
||||
|
||||
# Query management in Explore
|
||||
@@ -49,6 +55,7 @@ The Query history depicts a history of your queries for the past two weeks, unle
|
||||
- Copy a shortened link with the query to the clipboard.
|
||||
- Delete a query.
|
||||
- Star a query.
|
||||
- Add a query from your history to your [saved queries](ref:saved-queries).
|
||||
|
||||
By default, query history shows you newest queries first. Click the sort box in the upper right to change to **Oldest first** to older queries first. You can search your queries using keywords.
|
||||
|
||||
|
||||
@@ -278,6 +278,17 @@ When linking to another dashboard that uses template variables, select variable
|
||||
|
||||
If you want to add all of the current dashboard's variables to the URL, then use `${__all_variables}`.
|
||||
|
||||
When you link to another dashboard, ensure that:
|
||||
|
||||
- The target dashboard has the same variable name. If it doesn't (for example, `server` in the source dashboard and `host` in the target), you must align them or explicitly map values (for example, `&var-host=${server}`).
|
||||
- You use the variable _name_, and not the label. Labels are only used as display text and aren't recognized in URLs.
|
||||
|
||||
For example, if you have a variable with the name `var-server` and the label `ChooseYourServer`, you must use `var-server` in the URL, as shown in the following table:
|
||||
|
||||
| Correct link | Incorrect link |
|
||||
| ---------------------------------------------- | -------------------------------------------------------- |
|
||||
| `/d/xxxx/dashboard-b?orgId=1&var-server=web02` | `/d/xxxx/dashboard-b?orgId=1&var-ChooseYourServer=web02` |
|
||||
|
||||
## Add data links or actions {#add-a-data-link}
|
||||
|
||||
The following tasks describe how to configure data links and actions.
|
||||
@@ -296,9 +307,7 @@ To add a data link, follow these steps:
|
||||
This is a human-readable label for the link displayed in the UI. This is a required field.
|
||||
|
||||
1. Enter the **URL** to which you want to link.
|
||||
|
||||
To add a data link variable, click in the **URL** field and enter `$` or press Ctrl+Space or Cmd+Space to see a list of available variables. This is a required field.
|
||||
|
||||
1. (Optional) To add a data link variable, click in the **URL** field and enter `$` or press Ctrl+Space or Cmd+Space to see a list of available variables.
|
||||
1. If you want the link to open in a new tab, toggle the **Open in a new tab** switch.
|
||||
1. If you want the data link to open with a single click on the visualization, toggle the **One click** switch.
|
||||
|
||||
|
||||
@@ -29,29 +29,39 @@ refs:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/transform-data/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/transform-data/
|
||||
destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/transform-data/
|
||||
the-overview-of-grafana-alerting:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/
|
||||
destination: /docs/grafana-cloud/alerting-and-irm/alerting/
|
||||
table:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/visualizations/table/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/visualizations/table/
|
||||
destination: /docs/grafana-cloud/visualizations/panels-visualizations/visualizations/table/
|
||||
add-a-query:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/#add-a-query
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/#add-a-query
|
||||
destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/#add-a-query
|
||||
saved-queries:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/#saved-queries
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/#saved-queries
|
||||
save-query:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/#save-a-query
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/#save-a-query
|
||||
---
|
||||
|
||||
# Panel editor
|
||||
|
||||
In the panel editor, you can update all the elements of a visualization including the data source, queries, time range, and visualization display options.
|
||||
|
||||

|
||||

|
||||
|
||||
This following sections describe the areas of the Grafana panel editor.
|
||||
|
||||
@@ -75,7 +85,14 @@ The visualization preview section contains the following options:
|
||||
|
||||
The data section contains tabs where you enter queries, transform your data, and create alert rules (if applicable).
|
||||
|
||||
- **Queries** - Select your data source and enter queries here. For more information, refer to [Add a query](ref:add-a-query). When you create a new dashboard, you'll be prompted to select a data source before you get to the panel editor. You set or update the data source in existing dashboards using the drop-down in the **Queries** tab.
|
||||
- **Queries**
|
||||
- Select your data source. You can also set or update the data source in existing dashboards using the drop-down menu in the **Queries** tab.
|
||||
- [Add queries](ref:add-a-query). Write or construct a query in the query language of your data source or click **+ Add from saved queries** to add a previously saved query. If you've already written a query, you can click the **Replace with saved query** icon to use a previously saved query instead. To [save the query](ref:save-query) for reuse, click the **Save query** icon.
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
[Saved queries](ref:saved-queries) is in [public preview](https://grafana.com/docs/release-life-cycle/) in Grafana Enterprise and Cloud only.
|
||||
{{< /admonition >}}
|
||||
|
||||
- **Transformations** - Apply data transformations. For more information, refer to [Transform data](ref:transform-data).
|
||||
- **Alert** - Write alert rules. For more information, refer to [the overview of Grafana Alerting](ref:the-overview-of-grafana-alerting).
|
||||
|
||||
|
||||
@@ -26,55 +26,63 @@ refs:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/
|
||||
destination: /docs/grafana-cloud/connect-externally-hosted/data-sources/
|
||||
built-in-core-data-sources:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/#data-source-plugins
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/#built-in-core-data-sources
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/#data-source-plugins
|
||||
destination: /docs/grafana-cloud/connect-externally-hosted/data-sources/#built-in-core-data-sources
|
||||
use-expressions-to-manipulate-data:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/expression-queries/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/expression-queries/
|
||||
destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/expression-queries/
|
||||
global-variables:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/dashboards/variables/add-template-variables/#global-variables
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/dashboards/variables/add-template-variables/#global-variables
|
||||
destination: /docs/grafana-cloud/visualizations/dashboards/variables/add-template-variables/#global-variables
|
||||
plugin-management:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/administration/plugin-management/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/administration/plugin-management/
|
||||
recorded-queries:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/administration/recorded-queries/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/administration/recorded-queries/
|
||||
special-data-sources:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/#special-data-sources
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/connect-externally-hosted/data-sources/#special-data-sources
|
||||
---
|
||||
|
||||
# Query and transform data
|
||||
|
||||
Grafana supports many types of [data sources](ref:data-sources).
|
||||
Data source **queries** return data that Grafana can **transform** and visualize.
|
||||
Data source _queries_ return data that Grafana can _transform_ and visualize.
|
||||
Each data source uses its own query language, and data source plugins each implement a query-building user interface called a query editor.
|
||||
|
||||
## About queries
|
||||
|
||||
Grafana panels communicate with data sources via queries, which retrieve data for the visualization.
|
||||
Grafana panels communicate with data sources using queries, which retrieve data for the visualization.
|
||||
A query is a question written in the query language used by the data source.
|
||||
|
||||
You can configure query frequency and data collection limits in the panel's data source options.
|
||||
Grafana supports up to 26 queries per panel.
|
||||
|
||||
> **Important:** You **must** be familiar with a data source's query language.
|
||||
> For more information, refer to [Data sources](ref:data-sources).
|
||||
{{< admonition type="note" >}}
|
||||
You **must** be familiar with a data source's query language.
|
||||
For more information, refer to [Data sources](ref:data-sources).
|
||||
{{< /admonition >}}
|
||||
|
||||
### Query editors
|
||||
|
||||
{{< figure src="/static/img/docs/queries/influxdb-query-editor-7-2.png" class="docs-image--no-shadow" max-width="1000px" alt="The InfluxDB query editor" >}}
|
||||
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-queries-tab-v11.6.png" max-width="750px" alt="The InfluxDB query editor" >}}
|
||||
|
||||
Each data source's **query editor** provides a customized user interface that helps you write queries that take advantage of its unique capabilities.
|
||||
Each data source's query editor provides a customized user interface that helps you write queries that take advantage of its unique capabilities.
|
||||
|
||||
Because of the differences between query languages, each data source query editor looks and functions differently.
|
||||
Depending on your data source, the query editor might provide auto-completion features, metric names, variable suggestions, or a visual query-building interface.
|
||||
@@ -86,7 +94,7 @@ For example, this video demonstrates the visual Prometheus query builder:
|
||||
For details on a specific data source's unique query editor features, refer to its documentation:
|
||||
|
||||
- For data sources included with Grafana, refer to [Built-in core data sources](ref:built-in-core-data-sources), which links to each core data source's documentation.
|
||||
- For data sources installed as plugins, refer to its own documentation.
|
||||
- For data sources installed as plugins, refer to the documentation for the plugin.
|
||||
- Data source plugins in Grafana's [plugin catalog](/grafana/plugins/) link to or include their documentation in their catalog listings.
|
||||
For details about the plugin catalog, refer to [Plugin management](ref:plugin-management).
|
||||
- For links to Grafana Enterprise data source plugin documentation, refer to the [Enterprise plugins index](/docs/plugins/).
|
||||
@@ -108,42 +116,121 @@ SELECT hostname FROM host WHERE region IN($region)
|
||||
query_result(max_over_time(<metric>[${__range_s}s]) != <state>)
|
||||
```
|
||||
|
||||
### Saved queries
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
Saved queries is currently in [public preview](https://grafana.com/docs/release-life-cycle/). Grafana Labs offers limited support, and breaking changes might occur prior to the feature being made generally available.
|
||||
|
||||
This feature is only available on Grafana Enterprise and Grafana Cloud.
|
||||
{{< /admonition >}}
|
||||
|
||||
You can save queries that you've created so they can be reused by you and others in your organization.
|
||||
This helps users across your organization create dashboards or find insights in Explore without having to create their own queries or know a query language.
|
||||
It also helps you avoid having several users build the same queries for the same data sources multiple times.
|
||||
|
||||
You can see a list of these queries in the **Saved queries** drawer:
|
||||
|
||||
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-saved-queries-v12.2png.png" max-width="550px" alt="List of saved queries and the edit query form" caption="The Saved queries drawer accessed from Dashboards" >}}
|
||||
|
||||
When you first open the drawer, the list of queries in the **All** tab is filtered by the data source of the panel.
|
||||
However, you can clear that filter to display all saved queries.
|
||||
|
||||
The list in the **Favorites** tab is also filtered by data source, by default.
|
||||
The **Recent** tab displays the last 20 queries across all data sources from your **Query history** in Explore.
|
||||
From this tab, you can save queries for reuse as well.
|
||||
|
||||
In the **Saved queries** drawer, you can:
|
||||
|
||||
- Search for queries by data source name, query content, title, or description.
|
||||
- Sort queries alphabetically or by creation date.
|
||||
- Filter by data source name, author name, and tags (the tag filter uses the `OR` operator, while the others use the `AND` operator).
|
||||
- Set queries as favorites.
|
||||
- Duplicate, lock and unlock a query for editing, or delete a saved query.
|
||||
- Edit a query title, description, tags, or the availability of the query to other users in your organization. By default, saved queries are locked for editing.
|
||||
- When you access the **Saved queries** drawer from Explore, you can use the **Edit in Explore** option to edit the body of a query.
|
||||
|
||||
Access the duplicate, lock, unlock, and delete query options through the menu in the top-right corner of the query form next to the **Edit** button.
|
||||
|
||||
To access your saved queries, click **+ Add from saved queries** in the query editor:
|
||||
|
||||
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-add-from-saved-2-v12.2.png" max-width="750px" alt="Add a saved query" >}}
|
||||
|
||||
If you've already entered a query, you also have the option to replace it with a saved one:
|
||||
|
||||
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-replace-w-saved-v12.2.png" max-width="750px" alt="Replace a query with a saved one" >}}
|
||||
|
||||
#### Save a query
|
||||
|
||||
To save a query you've created:
|
||||
|
||||
1. From the query editor, click the **Save query** icon:
|
||||
|
||||
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-save-query-v12.2.png" max-width="750px" alt="Save a query" >}}
|
||||
|
||||
1. In the **Saved queries** drawer, enter a title for the query that will make it easy to find later.
|
||||
1. (Optional) Enter a description and relevant tags.
|
||||
1. Clear the **Share query with all users** checkbox if you only want the saved query to be available to you.
|
||||
1. Click **Save**.
|
||||
|
||||
#### Known limitations
|
||||
|
||||
- No validation is performed when you save a query, so it's possible to save an invalid query. You should confirm the query is working properly before you save it.
|
||||
- Saved queries are currently accessible from the query editors in Dashboards and Explore.
|
||||
- You can save a maximum of 1000 queries.
|
||||
- Users with the Viewer role who have access to Explore can use saved queries, but can't write them.
|
||||
- If you have multiple queries open in Explore and you edit one of them by way of the **Edit in Explore** function in the **Saved queries** drawer, the edited query replaces your open queries in Explore.
|
||||
|
||||
### Special data sources
|
||||
|
||||
Grafana also includes three special data sources: **Grafana**, **Mixed**, and **Dashboard**.
|
||||
For details, refer to [Data sources](ref:data-sources)
|
||||
|
||||
## Navigate the Query tab
|
||||
## Navigate the Queries tab {#navigate-the-query-tab}
|
||||
|
||||
A panel's Query tab consists of the following elements:
|
||||
A panel's **Queries** tab consists of the following elements:
|
||||
|
||||
- **Data source selector:** Selects the data source to query.
|
||||
- **Data source selector** - Selects the data source to query.
|
||||
For more information about data sources, refer to [Data sources](ref:data-sources).
|
||||
- **Query options:** Sets maximum data retrieval parameters and query execution time intervals.
|
||||
- **Query inspector button:** Opens the query inspector panel, where you can view and optimize your query.
|
||||
- **Query editor list:** Lists the queries you've written.
|
||||
- **Expressions:** Uses the expression builder to create alert expressions.
|
||||
- **Query options** - Sets maximum data retrieval parameters and query execution time intervals.
|
||||
- **Query inspector button** - Opens the query inspector panel, where you can view and optimize your query.
|
||||
- **Query editor list** - The list of queries you've written. Each query can be expanded or collapsed.
|
||||
- **Expressions** - Uses the expression builder to create alert expressions.
|
||||
For more information about expressions, refer to [Use expressions to manipulate data](ref:use-expressions-to-manipulate-data).
|
||||
|
||||
{{< figure src="/static/img/docs/queries/query-editor-7-2.png" class="docs-image--no-shadow" max-width="1000px" alt="The Query tab of the panel editor" >}}
|
||||
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-queries-tab2-v11.6.png" max-width="750px" alt="The Query tab of the panel editor" >}}
|
||||
|
||||
## Add a query
|
||||
|
||||
A query returns data that Grafana visualizes in dashboard panels.
|
||||
When you create a panel, Grafana automatically selects the default data source.
|
||||
|
||||
**To add a query:**
|
||||
To add a query, follow these steps:
|
||||
|
||||
1. Edit the panel to which you're adding a query.
|
||||
1. Click the **Query** tab.
|
||||
1. Hover the cursor over any part of the panel to which you're adding a query to display the menu icon in the top-right corner.
|
||||
1. Click the menu and select **Edit**.
|
||||
1. In the panel editor, click the **Queries** tab.
|
||||
1. Click the **Data source** drop-down menu and select a data source.
|
||||
|
||||
If you're creating a new dashboard, you'll be prompted to select a data source when you add the first panel.
|
||||
|
||||
1. Click **Query options** to configure the maximum number of data points you need.
|
||||
|
||||
For more information about query options, refer to [Query options](#query-options).
|
||||
1. Write the query using the query editor.
|
||||
1. Click **Apply**.
|
||||
|
||||
1. To add a query, do one of the following:
|
||||
- Write or construct a query in the query language of your data source.
|
||||
- Click **+ Add from saved queries** to add a previously saved query.
|
||||
- If you've already written a query, you can click the **Replace with saved query** icon to use a previously saved query instead.
|
||||
|
||||
1. (Optional) To save the query for reuse, click the **Save query** icon.
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
[Saved queries](#saved-queries) is currently in [public preview](https://grafana.com/docs/release-life-cycle/). Grafana Labs offers limited support, and breaking changes might occur prior to the feature being made generally available.
|
||||
|
||||
This feature is only available on Grafana Enterprise and Grafana Cloud.
|
||||
{{< /admonition >}}
|
||||
|
||||
1. Click **Run queries**.
|
||||
|
||||
Grafana queries the data source and visualizes the data.
|
||||
|
||||
@@ -154,20 +241,24 @@ Each query row contains a query editor and is identified with a letter (A, B, C,
|
||||
|
||||
You can:
|
||||
|
||||
| Icon | Description |
|
||||
| :--------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| {{< figure src="/static/img/docs/queries/query-editor-help-7-4.png" class="docs-image--no-shadow" max-width="30px" max-height="30px" alt="Help icon" >}} | Toggles query editor help. If supported by the data source, click this icon to display information on how to use the query editor or provide quick access to common queries. |
|
||||
| {{< figure src="/static/img/docs/queries/duplicate-query-icon-7-0.png" class="docs-image--no-shadow" max-width="30px" max-height="30px" alt="Duplicate icon" >}} | Copies a query. Duplicating queries is useful when working with multiple complex queries that are similar and you want to either experiment with different variants or do minor alterations. |
|
||||
| {{< figure src="/static/img/docs/queries/hide-query-icon-7-0.png" class="docs-image--no-shadow" max-width="30px" max-height="30px" alt="Hide icon" >}} | Hides a query. Grafana does not send hidden queries to the data source. |
|
||||
| {{< figure src="/static/img/docs/queries/remove-query-icon-7-0.png" class="docs-image--no-shadow" max-width="30px" max-height="30px" alt="Remove icon">}} | Removes a query. Removing a query permanently deletes it, but sometimes you can recover deleted queries by reverting to previously saved versions of the panel. |
|
||||
| {{< figure src="/static/img/docs/queries/query-drag-icon-7-2.png" class="docs-image--no-shadow" max-width="30px" max-height="30px" alt="Drag icon" >}} | Reorders queries. Change the order of queries by clicking and holding the drag icon, then drag queries where desired. The order of results reflects the order of the queries, so you can often adjust your visual results based on query order. |
|
||||
<!-- prettier-ignore-start -->
|
||||
| Icon | Description |
|
||||
| ------- | -------------------------------------------- |
|
||||
| {{< figure src="/static/img/docs/queries/query-editor-help-7-4.png" max-width="30px" max-height="30px" alt="Help icon" >}} | Toggles query editor help. If supported by the data source, click this icon to display information on how to use the query editor or provide quick access to common queries. |
|
||||
| {{< figure src="/media/docs/grafana/panels-visualizations/create-recorded-query-icon.png" max-width="30px" max-height="30px" alt="Create recorded query icon" >}} | Create [recorded queries](ref:recorded-queries) so you can see trends over time by taking a snapshot of a data point on a set interval (Enterprise and Cloud only). |
|
||||
| {{< figure src="/media/docs/grafana/panels-visualizations/save-to-query-icon.png" max-width="30px" max-height="30px" alt="Save query icon" >}} | Save query. Saves the query so it can be reused. Access saved queries by clicking **+ Add saved query**. For more information, refer to [Saved queries](#saved-queries) (Enterprise and Cloud only). |
|
||||
| {{< figure src="/static/img/docs/queries/duplicate-query-icon-7-0.png" max-width="30px" max-height="30px" alt="Duplicate icon" >}} | Copies a query. Duplicating queries is useful when working with multiple complex queries that are similar and you want to either experiment with different variants or do minor alterations. |
|
||||
| {{< figure src="/static/img/docs/queries/hide-query-icon-7-0.png" max-width="30px" max-height="30px" alt="Hide icon" >}} | Hides a query. Grafana does not send hidden queries to the data source. |
|
||||
| {{< figure src="/static/img/docs/queries/remove-query-icon-7-0.png" max-width="30px" max-height="30px" alt="Remove icon">}} | Removes a query. Removing a query permanently deletes it, but sometimes you can recover deleted queries by reverting to previously saved versions of the panel. |
|
||||
| {{< figure src="/static/img/docs/queries/query-drag-icon-7-2.png" max-width="30px" max-height="30px" alt="Drag icon" >}} | Reorders queries. Change the order of queries by clicking and holding the drag icon, then drag queries where desired. The order of results reflects the order of the queries, so you can often adjust your visual results based on query order. |
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## Query options
|
||||
|
||||
Click **Query options** next to the data source selector to see settings for the selected data source.
|
||||
Changes you make here affect only queries made in this panel.
|
||||
|
||||
{{< figure src="/static/img/docs/queries/data-source-options-7-0.png" class="docs-image--no-shadow" max-width="1000px" alt="Data source query options" >}}
|
||||
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-query-options-v11.6.png" max-width="750px" alt="Data source query options" >}}
|
||||
|
||||
Grafana sets defaults that are shown in dark gray text.
|
||||
Changes are displayed in white text.
|
||||
@@ -175,7 +266,7 @@ To return a field to the default setting, delete the white text from the field.
|
||||
|
||||
Panel data source query options include:
|
||||
|
||||
- **Max data points:** If the data source supports it, this sets the maximum number of data points for each series returned.
|
||||
- **Max data points** - If the data source supports it, this sets the maximum number of data points for each series returned.
|
||||
If the query returns more data points than the max data points setting, then the data source reduces the number of points returned by aggregating them together by average, max, or another function.
|
||||
|
||||
You can limit the number of points to improve query performance or smooth the visualized line.
|
||||
@@ -185,7 +276,7 @@ Panel data source query options include:
|
||||
Streaming is a continuous flow of data, and buffering divides the stream into chunks.
|
||||
For example, Loki streams data in its live tailing mode.
|
||||
|
||||
- **Min interval:** Sets a minimum limit for the automatically calculated interval, which is typically the minimum scrape interval.
|
||||
- **Min interval** - Sets a minimum limit for the automatically calculated interval, which is typically the minimum scrape interval.
|
||||
If a data point is saved every 15 seconds, you don't benefit from having an interval lower than that.
|
||||
You can also set this to a higher minimum than the scrape interval to retrieve queries that are more coarse-grained and well-functioning.
|
||||
|
||||
@@ -193,7 +284,7 @@ Panel data source query options include:
|
||||
The **Min interval** corresponds to the min step in Prometheus. Changing the Prometheus interval can change the start and end of the query range because Prometheus aligns the range to the interval. Refer to [Min step](https://grafana.com/docs/grafana/latest/datasources/prometheus/query-editor/#min-step) for more details.
|
||||
{{< /admonition >}}
|
||||
|
||||
- **Interval:** Sets a time span that you can use when aggregating or grouping data points by time.
|
||||
- **Interval** - Sets a time span that you can use when aggregating or grouping data points by time.
|
||||
|
||||
Grafana automatically calculates an appropriate interval that you can use as a variable in templated queries.
|
||||
The variable is measured in either seconds (`$__interval`) or milliseconds (`$__interval_ms`).
|
||||
@@ -207,10 +298,12 @@ Panel data source query options include:
|
||||
|
||||
For more information, refer to [Global variables](ref:global-variables).
|
||||
|
||||
- **Relative time:** Overrides the relative time range for individual panels, which causes them to be different than what is selected in the dashboard time picker in the top-right corner of the dashboard.
|
||||
- **Relative time** - Overrides the relative time range for individual panels, which causes them to be different than what is selected in the dashboard time picker in the top-right corner of the dashboard.
|
||||
You can use this to show metrics from different time periods or days on the same dashboard.
|
||||
|
||||
> **Note:** Panel time overrides have no effect when the dashboard's time range is absolute.
|
||||
{{< admonition type="note">}}
|
||||
Panel time overrides have no effect when the dashboard's time range is absolute.
|
||||
{{< /admonition >}}
|
||||
|
||||
| Example | Relative time field |
|
||||
| ---------------- | ------------------- |
|
||||
@@ -222,10 +315,12 @@ Panel data source query options include:
|
||||
|
||||
{{< docs/play title="Time range override" url="https://play.grafana.org/d/000000041/" >}}
|
||||
|
||||
- **Time shift:** Overrides the time range for individual panels by shifting its start and end relative to the time picker.
|
||||
- **Time shift** - Overrides the time range for individual panels by shifting its start and end relative to the time picker.
|
||||
For example, you can shift the time range for the panel to be two hours earlier than the dashboard time picker.
|
||||
|
||||
> **Note:** Panel time overrides have no effect when the dashboard's time range is absolute.
|
||||
{{< admonition type="note">}}
|
||||
Panel time overrides have no effect when the dashboard's time range is absolute.
|
||||
{{< /admonition >}}
|
||||
|
||||
| Example | Time shift field |
|
||||
| -------------------- | ---------------- |
|
||||
@@ -235,5 +330,5 @@ Panel data source query options include:
|
||||
| This entire year | `1d/y` |
|
||||
| Last entire year | `1y/y` |
|
||||
|
||||
- **Cache timeout:** _(Visible only if available in the data source)_ Overrides the default cache timeout if your time series store has a query cache.
|
||||
- **Cache timeout** - _(Visible only if available in the data source)_ Overrides the default cache timeout if your time series store has a query cache.
|
||||
Specify this value as a numeric value in seconds.
|
||||
|
||||
@@ -19,7 +19,7 @@ refs:
|
||||
|
||||
# SQL expressions
|
||||
|
||||
{{< docs/private-preview product="SQL expressions" >}}
|
||||
{{< docs/public-preview product="SQL expressions" >}}
|
||||
|
||||
SQL Expressions are server-side expressions that manipulate and transform the results of data source queries using MySQL-like syntax. They allow you to easily query and transform your data after it has been queried, using SQL, which provides a familiar and powerful syntax that can handle everything from simple filters to highly complex, multi-step transformations.
|
||||
|
||||
@@ -60,11 +60,17 @@ A key capability of SQL expressions is the ability to JOIN data from multiple ta
|
||||
|
||||
To work with SQL expressions, you must use data from a backend data source. In Grafana, a backend data source refers to a data source plugin or integration that communicates with a database, service, or API through the Grafana server, rather than directly from the browser (frontend).
|
||||
|
||||
## Known limitations
|
||||
|
||||
- Currently, only one SQL expression is supported per panel or alert.
|
||||
- Grafana supports certain data sources. Refer to [compatible data sources](#compatible-data-sources) for a current list.
|
||||
- Autocomplete is available, but column/field autocomplete is only available after enabling the `sqlExpressionsColumnAutoComplete` feature toggle, which is provided on an experimental basis.
|
||||
|
||||
## Compatible data sources
|
||||
|
||||
The following are compatible data sources:
|
||||
|
||||
**Full support:** All query types for each data source are supported.
|
||||
**Full support:** Grafana supports all query types for each of these data sources.
|
||||
|
||||
- Elasticsearch
|
||||
- MySQL
|
||||
@@ -73,7 +79,7 @@ The following are compatible data sources:
|
||||
- Google Sheets
|
||||
- Amazon Athena
|
||||
|
||||
**Partial support:** The following data sources offer limited or conditional support. Some allow different types of queries, depending on the service being accessed. For example, Azure Monitor can query multiple services, each with its own query format. In some cases, you can also change the query type within a panel.
|
||||
**Partial support:** The following data sources have limited or conditional support. Some support multiple query types depending on the service. For example, Azure Monitor can query multiple services, each with its own query format. In some cases, you can also switch the query type within a panel.
|
||||
|
||||
- InfluxDB
|
||||
- Infinity
|
||||
@@ -97,6 +103,10 @@ To create a SQL expression, complete the following steps:
|
||||
|
||||
After you have added a SQL expression, you can select from other data source queries by referencing the RefIDs of the queries in your SQL expression as if they were tables in a SQL database.
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
The **RefID** is a unique identifier assigned to each query within a Grafana panel that serves as a reference name for that query's data.
|
||||
{{< /admonition >}}
|
||||
|
||||

|
||||
|
||||
## Workflow to build SQL expressions
|
||||
@@ -134,22 +144,65 @@ The SQL expression workflow in Grafana is designed with the following behaviors:
|
||||
|
||||
- **Non-tabular or incorrectly shaped data will not render in certain panels.** Visualizations such as graphs or gauges require properly structured data. Mismatched formats will result in rendering issues or missing data.
|
||||
|
||||
For data to be used in SQL expressions, it must be in a **tabular format**, specifically the **FullLong format**. This means all relevant data is contained within a single table, with values such as metric labels stored as columns and individual cells. Because not all data sources return results in this format by default, Grafana will automatically convert compatible query results to FullLong format when they are referenced in a SQL expression.
|
||||
|
||||
## SQL conversion rules
|
||||
|
||||
When a RefID is referenced within a SQL statement (e.g., `SELECT * FROM A`), the system invokes a distinct SQL conversion process.
|
||||
When you reference a RefID within a SQL statement (e.g., `SELECT * FROM A`), the system invokes a distinct SQL conversion process.
|
||||
|
||||
The SQL conversion path:
|
||||
|
||||
- The query result is treated as a single data frame, without labels, and is mapped directly to a tabular format.
|
||||
- If the frame type is present and is either numeric, wide time series, or multi-frame time series (for example, labeled formats), Grafana automatically converts the data into a table structure.
|
||||
- The query result appears as a single data frame, without labels, and is mapped directly to a tabular format.
|
||||
- If the frame type is present and is either numeric, wide time series, or multi-frame time series (for example: labeled formats), Grafana automatically converts the data into a table structure.
|
||||
|
||||
## Known limitations
|
||||
## Supported functions
|
||||
|
||||
- Currently, only one SQL expression is supported per panel or alert.
|
||||
- Grafana supports certain data sources. Refer to [compatible data sources](#compatible-data-sources) for a current list.
|
||||
- Autocomplete is available, but column/field autocomplete is only available after enabling the `sqlExpressionsColumnAutoComplete` feature toggle, which is provided on an experimental basis.
|
||||
Grafana maintains a complete list of supported SQL keywords, operators, and functions in the SQL expressions query validator implementation.
|
||||
|
||||
For the most up-to-date reference of all supported SQL functionality, refer to the `allowedNode` and `allowedFunction` definitions in the Grafana [codebase](https://github.com/grafana/grafana/blob/main/pkg/expr/sql/parser_allow.go).
|
||||
|
||||
## Alerting and recording rules
|
||||
|
||||
SQL expressions integrates alerting and recording rules, allowing you to define complex conditions and metrics using standard SQL queries. The system processes your query results and automatically creates alert instances or recorded metrics based on the returned data structure.
|
||||
|
||||
For SQL Expressions to work properly with alerting and recording rules, your query must return:
|
||||
|
||||
- One numeric column - **_required_**. This contains the value that triggers alerts or gets recorded.
|
||||
- Unique string column combinations - **_required_**. Each row must have a unique combination of string column values.
|
||||
- One or more string columns - _optional_. These become **labels** for the alert instances or metrics. Examples: `service`, `region`.
|
||||
|
||||
Consider the following query results:
|
||||
|
||||
```sql
|
||||
error_count,service,region
|
||||
25,auth-service,us-east
|
||||
0,payment-service,us-west
|
||||
15,user-service,eu-west
|
||||
```
|
||||
|
||||
This query returns:
|
||||
|
||||
- the numeric column `error_count` (values: 25, 0, 15)
|
||||
- the string columns `service` and `region`
|
||||
|
||||
For alert rules, this creates three alert instances:
|
||||
|
||||
- First instance with labels {service=auth-service, region=us-east} and value 25 (triggers alert - high error count)
|
||||
- Second instance with labels {service=payment-service, region=us-west} and value 0 (no alert - zero errors)
|
||||
- Third instance with labels {service=user-service, region=eu-west} and value 15 (triggers alert - elevated error count)
|
||||
|
||||
For recording rules, creates one metric with three series:
|
||||
|
||||
- First series: error_count_total{service=auth-service, region=us-east} 25
|
||||
- Second series: error_count_total{service=payment-service, region=us-west} 0
|
||||
- Third series: error_count_total{service=user-service, region=eu-west} 15
|
||||
|
||||
Following are some best practices for alerting and recording rules:
|
||||
|
||||
- Keep numeric values meaningful (for example: error counts, request duration).
|
||||
- Use clear, descriptive column names - these become your labels.
|
||||
- Keep string values short and consistent.
|
||||
- Avoid too many unique label combinations, as this can result in high cardinality.
|
||||
- Always use `GROUP BY` to avoid duplicate label errors.
|
||||
- Aggregate numeric values logically (for example: `SUM(error_count)`).
|
||||
|
||||
## Supported data source formats
|
||||
|
||||
@@ -202,3 +255,19 @@ During conversion:
|
||||
2. Add the SQL expression `SELECT * from A`. After you add a SQL expression that selects from RefID A, Grafana converts it to a table response:
|
||||
|
||||

|
||||
|
||||
## LLM integration
|
||||
|
||||
The Grafana LLM plugin seamlessly integrates AI-powered assistance into your SQL expressions workflow.
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
The Grafana LLM plugin is currently in public preview, meaning Grafana offers limited support, and breaking changes might occur prior to the feature being made generally available.
|
||||
{{< /admonition >}}
|
||||
|
||||
To use this integration, first [install and configure the LLM plugin](https://grafana.com/grafana/plugins/grafana-llm-app/). After installation, open your dashboard and select **Edit** to open the panel editor. Navigate to the **Queries** tab and scroll to the bottom where you'll find two new buttons positioned to the right of the **Run query** button in your SQL Expressions query.
|
||||
|
||||
{{< figure src="/media/docs/sql-expressions/sqlexpressions-LLM-integration-v12.2.png" caption="LLM integration" >}}
|
||||
|
||||
Click **Explain query** to open a drawer that displays a detailed explanation of your query, including its interpreted business meaning and performance statistics. Once the explanation is generated, the button changes to **View explanation**.
|
||||
|
||||
Click **Improve query** to open a suggestions drawer that contains performance and reliability enhancements, column naming best practices, and guidance on panel optimization. Click **Apply** to implement a suggestion. After you’ve interacted with the interface, you'll see a **Suggestions** button for quick access. Newer suggestions appear at the top, with older ones listed below, creating a history of improvements. If your SQL query has a parsing error, such as a syntax issue, the LLM will attempt to provide a corrected version. The LLM automatically identifies errors and helps you rewrite the query correctly.
|
||||
|
||||
@@ -88,6 +88,22 @@ While the first field can be time-based and you can use a bar chart to plot time
|
||||
|
||||
We recommend that you only use one dataset in a bar chart because using multiple datasets can result in unexpected behavior.
|
||||
|
||||
<!-- vale Grafana.WordList = NO -->
|
||||
<!-- vale Grafana.Spelling = NO -->
|
||||
|
||||
## Apply ad hoc filters from the bar chart
|
||||
|
||||
In bar charts, you can apply ad hoc filters directly from the visualization.
|
||||
|
||||
To display the filter button, hover your cursor over the bar that has the value for which you want to filter and click the bar:
|
||||
|
||||
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-adhoc-filter-icon-bar-v12.2.png" max-width="300px" alt="The ad hoc filter button in a bar chart tooltip">}}
|
||||
|
||||
For more information about applying ad hoc filters this way, refer to [Dashboard drilldown with ad hoc filters](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/dashboards/variables/add-template-variables/#dashboard-drilldown-with-ad-hoc-filters).
|
||||
|
||||
<!-- vale Grafana.Spelling = YES -->
|
||||
<!-- vale Grafana.WordList = YES -->
|
||||
|
||||
## Configuration options
|
||||
|
||||
{{< docs/shared lookup="visualizations/config-options-intro.md" source="grafana" version="<GRAFANA_VERSION>" >}}
|
||||
|
||||
@@ -174,6 +174,22 @@ Columns with filters applied have a blue filter displayed next to the title.
|
||||
|
||||
To remove the filter, click the blue filter icon and then click **Clear filter**.
|
||||
|
||||
<!-- vale Grafana.WordList = NO -->
|
||||
<!-- vale Grafana.Spelling = NO -->
|
||||
|
||||
### Apply ad hoc filters from the table
|
||||
|
||||
In tables, you can apply ad hoc filters directly from the visualization with one click.
|
||||
|
||||
To display the filter icons, hover your cursor over the cell that has the value for which you want to filter:
|
||||
|
||||
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-table-adhoc-filter-v12.2.png" max-width="500px" alt="Table with ad hoc filter icon displayed on a cell" >}}
|
||||
|
||||
For more information about applying ad hoc filters this way, refer to [Dashboard drilldown with ad hoc filters](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/dashboards/variables/add-template-variables/#dashboard-drilldown-with-ad-hoc-filters).
|
||||
|
||||
<!-- vale Grafana.Spelling = YES -->
|
||||
<!-- vale Grafana.WordList = YES -->
|
||||
|
||||
## Sort columns
|
||||
|
||||
Click a column title to change the sort order from default to descending to ascending.
|
||||
@@ -219,7 +235,7 @@ This option is only available when you're editing the panel.
|
||||
The table footer displays the results of calculations (and reducer functions) on fields.
|
||||
The footer is only displayed after you select an option in the **Calculation** drop-down list:
|
||||
|
||||
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-table-footer-selector-v12.2.png" max-width="300px" alt="" >}}
|
||||
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-table-footer-selector-v12.2.png" max-width="300px" alt="The footer calculation selector, open" >}}
|
||||
|
||||
There are several calculations you can choose from including minimum, maximum, first, last, and total.
|
||||
For the full list of options, refer to [Calculations](ref:calculations).
|
||||
@@ -227,21 +243,16 @@ For the full list of options, refer to [Calculations](ref:calculations).
|
||||
In the table footer:
|
||||
|
||||
- You can apply multiple calculations at once.
|
||||
- All calculations and reducer functions are labeled except **Total** when it's the only function applied.
|
||||
- The calculations and reducer functions apply to all fields in the table, by default. To control which fields have a calculation or function applied, add the table footer in an override instead.
|
||||
- If you enable a mathematical function for a non-numeric field, nothing for that function is displayed for that field.
|
||||
|
||||
In the following image, multiple calculations—**Mean**, **Max**, and **Last**—have been applied:
|
||||
|
||||
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-table-footer-1-v12.2.png" max-width="750px" alt="" >}}
|
||||
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-tablefooter-v12.2.png" max-width="750px" alt="Table with footer displaying mean, max, and last" >}}
|
||||
|
||||
You can also see in the previous image that the mathematical functions, **Mean** and **Max**, haven't been applied to the text field in the table.
|
||||
Only the **Last** function has been applied to that field.
|
||||
|
||||
In the following image, the **Total** calculation has been applied, and no label is displayed because it's the only function:
|
||||
|
||||
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-table-footer-2-v12.2.png" max-width="750px" alt="" >}}
|
||||
|
||||
{{< admonition type="note">}}
|
||||
Calculations applied to cell types like **Markdown + HTML** might have unexpected results.
|
||||
{{< /admonition>}}
|
||||
@@ -413,7 +424,7 @@ However, you can switch back and forth between tabs.
|
||||
|
||||
The **Pill** cell type displays each item in a comma-separated string in a colored block.
|
||||
|
||||
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-table-pills-v12.1.png" max-width="750px" alt="Table using the pill cell type" >}}
|
||||
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-table-pill-cells-v12.2.png" max-width="750px" alt="Table using the pill cell type" >}}
|
||||
|
||||
The colors applied to each piece of text are maintained throughout the table.
|
||||
For example, if the word "test" is first displayed in a red pill, it will always be displayed in a red pill.
|
||||
@@ -444,6 +455,8 @@ in these cells if the [`disable_sanitize_html`](https://grafana.com/docs/grafana
|
||||
Toggle on the **Tooltip from field** switch to use the values from another field (or column) in a tooltip.
|
||||
For more information, refer to [Tooltip from field](#tooltip-from-field).
|
||||
|
||||
{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-table-markdown-v12.2.png" max-width="600px" alt="Table using the pill cell type" >}}
|
||||
|
||||
#### Image
|
||||
|
||||
If you have a field value that is an image URL or a base64 encoded image, this cell type displays it as an image.
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
description: Guide for upgrading to Grafana v12.2
|
||||
keywords:
|
||||
- grafana
|
||||
- configuration
|
||||
- documentation
|
||||
- upgrade
|
||||
- '12.2'
|
||||
title: Upgrade to Grafana v12.2
|
||||
menuTitle: Upgrade to v12.2
|
||||
weight: 498
|
||||
---
|
||||
|
||||
# Upgrade to Grafana v12.2
|
||||
|
||||
{{< docs/shared lookup="upgrade/intro_2.md" source="grafana" version="<GRAFANA_VERSION>" >}}
|
||||
|
||||
{{< docs/shared lookup="back-up/back-up-grafana.md" source="grafana" version="<GRAFANA_VERSION>" leveloffset="+1" >}}
|
||||
|
||||
{{< docs/shared lookup="upgrade/upgrade-common-tasks.md" source="grafana" version="<GRAFANA_VERSION>" >}}
|
||||
@@ -192,6 +192,7 @@ For a complete list of every change, with links to pull requests and related iss
|
||||
|
||||
## Grafana 12
|
||||
|
||||
- [What's new in 12.2](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/whatsnew/whats-new-in-v12-2)
|
||||
- [What's new in 12.1](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/whatsnew/whats-new-in-v12-1)
|
||||
- [What's new in 12.0](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/whatsnew/whats-new-in-v12-0)
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
description: Feature and improvement highlights for Grafana v12.2
|
||||
keywords:
|
||||
- grafana
|
||||
- new
|
||||
- documentation
|
||||
- '12.2'
|
||||
- release notes
|
||||
labels:
|
||||
products:
|
||||
- cloud
|
||||
- enterprise
|
||||
- oss
|
||||
title: What's new in Grafana v12.2
|
||||
posts:
|
||||
- title: SQL expressions
|
||||
items:
|
||||
- whats-new/2025-09-05-sql-expressions.md
|
||||
- title: Dashboards and visualizations
|
||||
items:
|
||||
- whats-new/2025-08-22-new-table-visualization-is-generally-available.md
|
||||
- whats-new/2025-08-27-generate-tooltips-from-table-fields.md
|
||||
- whats-new/2025-08-27-improved-footer-for-table-visualization.md
|
||||
- whats-new/2025-07-17-disable-tooltips-in-canvas-visualizations.md
|
||||
- whats-new/2025-07-14-static-options-for-query-variable.md
|
||||
- whats-new/2025-07-24-dynamic-connection-direction-in-canvas.md
|
||||
- whats-new/2025-08-04-canvas-pan-zoom-improvements.md
|
||||
- whats-new/2025-09-01-actions-authentication-via-infinity-datasource.md
|
||||
- whats-new/2025-09-02-enhanced-ad-hoc-filter-support.md
|
||||
- whats-new/2025-09-02-new-dashboard-apis-now-enabled-by-default.md
|
||||
- title: Reporting
|
||||
items:
|
||||
- whats-new/2025-05-27-new-and-improved-reporting.md
|
||||
- title: Data sources
|
||||
items:
|
||||
- whats-new/2025-08-12-jenkins-enterprise-data-source-for-grafana.md
|
||||
- whats-new/2025-07-16-google-sheets-data-source-now-supports-template-variables.md
|
||||
- whats-new/2025-09-04-azure-monitor-resource-picker-filtering-and-recent-resources.md
|
||||
- title: Explore
|
||||
items:
|
||||
- whats-new/2025-07-08-saved-queries-in-dashboards-and-explore.md
|
||||
- title: Logs Drilldown
|
||||
items:
|
||||
- whats-new/2025-08-29-json-log-line-viewer-in-logs-drilldown-is-now-generally-available.md
|
||||
- title: Metrics Drilldown
|
||||
items:
|
||||
- whats-new/2025-08-07-grafana-metrics-drilldown-entry-point-from-alerting-rule.md
|
||||
- title: Plugins
|
||||
items:
|
||||
- whats-new/2025-09-11-translate-your-plugin.md
|
||||
- title: Authentication and authorization
|
||||
items:
|
||||
- whats-new/2025-09-10-scim-configuration-ui.md
|
||||
whats_new_grafana_version: 12.2
|
||||
weight: -51
|
||||
---
|
||||
|
||||
# What’s new in Grafana v12.2
|
||||
|
||||
Welcome to Grafana 12.2! This release focuses on making it easier to gain insights from your data.
|
||||
|
||||
We're excited to announce several features are now GA. Enhanced ad hoc filtering transforms your dashboards into true command centers, allowing you to slice and dice datasets on the fly. The redesigned table visualization offers improved performance and visual aids for quick pattern and anomaly identification, helping you make faster decisions. The Logs Drilldown JSON viewer makes intimidating log structures organized and explorable. Metrics Drilldown now integrates with alert creation in Grafana, so you can explore Prometheus data with intuitive point-and-click interactions, find the right visualization, and easily use its query in your alert rule.
|
||||
|
||||
We're also collecting feedback on some new public preview features. AI-powered SQL expressions eliminate the barrier between questions and answers by generating SQL queries from natural language and providing instant explanations for existing queries. Our enhanced Canvas Pan and Zoom experience lets you design complex dashboards exactly as you envision them.
|
||||
|
||||
Keep reading to learn more about everything 12.2 has in store.
|
||||
|
||||
{{< youtube id=-7A_tePidEM >}}
|
||||
|
||||
For even more detail about all the changes in this release, refer to the [changelog](https://github.com/grafana/grafana/blob/main/CHANGELOG.md). For the specific steps we recommend when you upgrade to v12.2, check out our [Upgrade Guide](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/upgrade-guide/upgrade-v12.2/).
|
||||
|
||||
{{< docs/whats-new >}}
|
||||
@@ -0,0 +1,9 @@
|
||||
# Dashboards
|
||||
|
||||
## Adding Dashboard JSONs
|
||||
|
||||
When adding dashboard JSON files to this folder that were exported from Grafana:
|
||||
|
||||
⚠️ **Important:** Don't forget to remove the `"id"` field from the exported JSON before adding it to this folder.
|
||||
|
||||
Exported dashboard JSONs contain an `id` field that should be removed to avoid conflicts when importing the dashboard in tests.
|
||||
+8
-8
@@ -43,10 +43,10 @@ test.describe('Canvas Panel - Scene Tests', () => {
|
||||
await expect(viewerBounds).toBeDefined();
|
||||
|
||||
// Test pan functionality
|
||||
const startX = viewerBounds.x + 50;
|
||||
const startY = viewerBounds.y + 50;
|
||||
const endX = viewerBounds.x + 250;
|
||||
const endY = viewerBounds.y + 250;
|
||||
const startX = viewerBounds!.x + 50;
|
||||
const startY = viewerBounds!.y + 50;
|
||||
const endX = viewerBounds!.x + 250;
|
||||
const endY = viewerBounds!.y + 250;
|
||||
await page.getByTestId('canvas-scene-pan-zoom');
|
||||
await page.mouse.move(startX, startY);
|
||||
await page.mouse.down({ button: 'middle' });
|
||||
@@ -79,9 +79,9 @@ async function isOutsideViewport(element: Locator, viewPort: Locator): Promise<b
|
||||
const elementBounds = await element.boundingBox();
|
||||
const viewportBounds = await viewPort.boundingBox();
|
||||
return (
|
||||
elementBounds.x + elementBounds.width < viewportBounds.x ||
|
||||
elementBounds.x > viewportBounds.x + viewportBounds.width ||
|
||||
elementBounds.y + elementBounds.height < viewportBounds.y ||
|
||||
elementBounds.y > viewportBounds.y + viewportBounds.height
|
||||
elementBounds!.x + elementBounds!.width < viewportBounds!.x ||
|
||||
elementBounds!.x > viewportBounds!.x + viewportBounds!.width ||
|
||||
elementBounds!.y + elementBounds!.height < viewportBounds!.y ||
|
||||
elementBounds!.y > viewportBounds!.y + viewportBounds!.height
|
||||
);
|
||||
}
|
||||
@@ -4,14 +4,14 @@ import { test, expect } from '@grafana/plugin-e2e';
|
||||
|
||||
import { getColumnIdx } from './table-utils';
|
||||
|
||||
const DASHBOARD_UID = '1ea31838-e4e8-4aa0-9333-1d4c3fa95641';
|
||||
const DASHBOARD_UID = '8100236d-603c-421e-a21b-2a0b0ea4eaa3';
|
||||
|
||||
const waitForTableLoad = async (loc: Page | Locator) => {
|
||||
await expect(loc.locator('.rdg')).toBeVisible();
|
||||
};
|
||||
|
||||
test.describe('Panels test: Table - Footer', { tag: ['@panels', '@table'] }, () => {
|
||||
test('Footer unaffected by filtering', async ({ gotoDashboardPage, selectors, page }) => {
|
||||
test('Footer affected by filtering', async ({ gotoDashboardPage, selectors, page }) => {
|
||||
const dashboardPage = await gotoDashboardPage({
|
||||
uid: DASHBOARD_UID,
|
||||
queryParams: new URLSearchParams({ editPanel: '4' }),
|
||||
@@ -26,12 +26,6 @@ test.describe('Panels test: Table - Footer', { tag: ['@panels', '@table'] }, ()
|
||||
const minColumnIdx = await getColumnIdx(page, 'Min');
|
||||
|
||||
// this is the footer cell for the "Min" column.
|
||||
await expect(
|
||||
dashboardPage
|
||||
.getByGrafanaSelector(selectors.components.Panels.Visualization.TableNG.Footer.ReducerLabel)
|
||||
.nth(minColumnIdx)
|
||||
).toHaveText('Last *');
|
||||
|
||||
const minReducerValue = await dashboardPage
|
||||
.getByGrafanaSelector(selectors.components.Panels.Visualization.TableNG.Footer.Value)
|
||||
.nth(minColumnIdx)
|
||||
@@ -57,7 +51,7 @@ test.describe('Panels test: Table - Footer', { tag: ['@panels', '@table'] }, ()
|
||||
dashboardPage
|
||||
.getByGrafanaSelector(selectors.components.Panels.Visualization.TableNG.Footer.Value)
|
||||
.nth(minColumnIdx)
|
||||
).toHaveText(minReducerValue);
|
||||
).not.toHaveText(minReducerValue);
|
||||
});
|
||||
|
||||
test('Footer unaffected by sorting', async ({ gotoDashboardPage, selectors, page }) => {
|
||||
@@ -75,12 +69,6 @@ test.describe('Panels test: Table - Footer', { tag: ['@panels', '@table'] }, ()
|
||||
const minColumnIdx = await getColumnIdx(page, 'Min');
|
||||
|
||||
// this is the footer cell for the "Min" column.
|
||||
await expect(
|
||||
dashboardPage
|
||||
.getByGrafanaSelector(selectors.components.Panels.Visualization.TableNG.Footer.ReducerLabel)
|
||||
.nth(minColumnIdx)
|
||||
).toHaveText('Last *');
|
||||
|
||||
const minReducerValue = await dashboardPage
|
||||
.getByGrafanaSelector(selectors.components.Panels.Visualization.TableNG.Footer.Value)
|
||||
.nth(minColumnIdx)
|
||||
@@ -114,26 +102,6 @@ test.describe('Panels test: Table - Footer', { tag: ['@panels', '@table'] }, ()
|
||||
).toHaveText(minReducerValue);
|
||||
});
|
||||
|
||||
test('Single-sum reducer label is hidden', async ({ gotoDashboardPage, selectors, page }) => {
|
||||
const dashboardPage = await gotoDashboardPage({
|
||||
uid: DASHBOARD_UID,
|
||||
queryParams: new URLSearchParams({ editPanel: '6' }),
|
||||
});
|
||||
|
||||
await expect(
|
||||
dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Single sum reducer'))
|
||||
).toBeVisible();
|
||||
|
||||
await waitForTableLoad(page);
|
||||
|
||||
await expect(
|
||||
dashboardPage.getByGrafanaSelector(selectors.components.Panels.Visualization.TableNG.Footer.ReducerLabel)
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
dashboardPage.getByGrafanaSelector(selectors.components.Panels.Visualization.TableNG.Footer.Value)
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('Count rows for normal case', async ({ gotoDashboardPage, selectors, page }) => {
|
||||
const dashboardPage = await gotoDashboardPage({
|
||||
uid: DASHBOARD_UID,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@test-plugins/extensions-test-app",
|
||||
"version": "12.2.0-pre",
|
||||
"version": "12.2.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "NODE_OPTIONS='--experimental-strip-types --no-warnings=ExperimentalWarning' webpack -c ./webpack.config.ts --env production",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@test-plugins/grafana-e2etest-datasource",
|
||||
"version": "12.2.0-pre",
|
||||
"version": "12.2.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "NODE_OPTIONS='--experimental-strip-types --no-warnings=ExperimentalWarning' webpack -c ./webpack.config.ts --env production",
|
||||
|
||||
+12
-67
@@ -2040,21 +2040,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroupCondition.tsx": {
|
||||
"no-restricted-syntax": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroupVisibility.tsx": {
|
||||
"no-restricted-syntax": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingTimeRangeSize.tsx": {
|
||||
"no-restricted-syntax": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/dashboard-scene/edit-pane/DashboardEditPaneRenderer.tsx": {
|
||||
"@typescript-eslint/consistent-type-assertions": {
|
||||
"count": 1
|
||||
@@ -2389,9 +2374,6 @@
|
||||
"public/app/features/dashboard-scene/v2schema/ImportDashboardOverviewV2.tsx": {
|
||||
"@typescript-eslint/consistent-type-assertions": {
|
||||
"count": 10
|
||||
},
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/dashboard-scene/v2schema/test-helpers.ts": {
|
||||
@@ -3487,11 +3469,6 @@
|
||||
"count": 9
|
||||
}
|
||||
},
|
||||
"public/app/features/transformers/FilterByValueTransformer/ValueMatchers/BasicMatcherEditor.tsx": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/transformers/FilterByValueTransformer/ValueMatchers/types.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 3
|
||||
@@ -3590,7 +3567,7 @@
|
||||
},
|
||||
"public/app/features/transformers/spatial/optionsHelper.tsx": {
|
||||
"@typescript-eslint/consistent-type-assertions": {
|
||||
"count": 3
|
||||
"count": 2
|
||||
},
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 3
|
||||
@@ -4008,7 +3985,7 @@
|
||||
},
|
||||
"public/app/plugins/datasource/cloudwatch/language/cloudwatch-logs/CloudWatchLogsLanguageProvider.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 3
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/plugins/datasource/cloudwatch/types.ts": {
|
||||
@@ -4041,7 +4018,7 @@
|
||||
},
|
||||
"public/app/plugins/datasource/elasticsearch/LanguageProvider.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 4
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"public/app/plugins/datasource/elasticsearch/QueryBuilder.ts": {
|
||||
@@ -4154,9 +4131,6 @@
|
||||
"public/app/plugins/datasource/grafana-testdata-datasource/components/SimulationQueryEditor.tsx": {
|
||||
"@typescript-eslint/consistent-type-assertions": {
|
||||
"count": 1
|
||||
},
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"public/app/plugins/datasource/grafana-testdata-datasource/components/SimulationSchemaForm.tsx": {
|
||||
@@ -4167,9 +4141,6 @@
|
||||
"public/app/plugins/datasource/grafana-testdata-datasource/datasource.ts": {
|
||||
"@typescript-eslint/consistent-type-assertions": {
|
||||
"count": 1
|
||||
},
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/plugins/datasource/grafana/components/AnnotationQueryEditor.tsx": {
|
||||
@@ -4183,9 +4154,6 @@
|
||||
"public/app/plugins/datasource/grafana/components/QueryEditor.tsx": {
|
||||
"@typescript-eslint/consistent-type-assertions": {
|
||||
"count": 2
|
||||
},
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/plugins/datasource/grafana/components/TimeRegionEditor.tsx": {
|
||||
@@ -4216,7 +4184,7 @@
|
||||
"count": 4
|
||||
},
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 15
|
||||
"count": 10
|
||||
}
|
||||
},
|
||||
"public/app/plugins/datasource/graphite/gfunc.ts": {
|
||||
@@ -4232,7 +4200,7 @@
|
||||
"count": 1
|
||||
},
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 10
|
||||
"count": 9
|
||||
}
|
||||
},
|
||||
"public/app/plugins/datasource/graphite/lexer.ts": {
|
||||
@@ -4333,12 +4301,12 @@
|
||||
},
|
||||
"public/app/plugins/datasource/influxdb/influx_series.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 9
|
||||
"count": 8
|
||||
}
|
||||
},
|
||||
"public/app/plugins/datasource/influxdb/query_part.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 14
|
||||
"count": 12
|
||||
}
|
||||
},
|
||||
"public/app/plugins/datasource/influxdb/response_parser.ts": {
|
||||
@@ -4351,11 +4319,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/plugins/datasource/loki/LanguageProvider.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/plugins/datasource/loki/LogContextProvider.ts": {
|
||||
"no-restricted-syntax": {
|
||||
"count": 2
|
||||
@@ -4376,11 +4339,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"public/app/plugins/datasource/loki/configuration/ConfigEditor.tsx": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/plugins/datasource/loki/configuration/DerivedField.tsx": {
|
||||
"no-restricted-syntax": {
|
||||
"count": 13
|
||||
@@ -4463,7 +4421,7 @@
|
||||
},
|
||||
"public/app/plugins/datasource/opentsdb/datasource.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 18
|
||||
"count": 16
|
||||
}
|
||||
},
|
||||
"public/app/plugins/datasource/parca/QueryEditor/QueryOptions.tsx": {
|
||||
@@ -4502,9 +4460,6 @@
|
||||
}
|
||||
},
|
||||
"public/app/plugins/datasource/tempo/_importedDependencies/components/AdHocFilter/AdHocFilterRenderer.tsx": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 1
|
||||
},
|
||||
"no-restricted-syntax": {
|
||||
"count": 1
|
||||
}
|
||||
@@ -4542,11 +4497,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"public/app/plugins/datasource/tempo/language_provider.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/plugins/datasource/tempo/resultTransformer.ts": {
|
||||
"@typescript-eslint/consistent-type-assertions": {
|
||||
"count": 2
|
||||
@@ -4574,11 +4524,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/plugins/datasource/zipkin/utils/transforms.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/plugins/panel/annolist/AnnoListPanel.tsx": {
|
||||
"@typescript-eslint/consistent-type-assertions": {
|
||||
"count": 1
|
||||
@@ -4928,12 +4873,12 @@
|
||||
},
|
||||
"public/app/types/events.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 9
|
||||
"count": 5
|
||||
}
|
||||
},
|
||||
"public/app/types/jquery/jquery.d.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 8
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"public/app/types/store.ts": {
|
||||
@@ -4971,7 +4916,7 @@
|
||||
},
|
||||
"public/test/core/thunk/thunkTester.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 6
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"public/test/global-jquery-shim.ts": {
|
||||
@@ -4996,7 +4941,7 @@
|
||||
},
|
||||
"public/test/specs/helpers.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 5
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,6 @@ require (
|
||||
github.com/grafana/grafana-api-golang-client v0.27.0 // @grafana/alerting-backend
|
||||
github.com/grafana/grafana-app-sdk v0.40.3 // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/grafana-app-sdk/logging v0.40.3 // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/grafana-app-sdk/plugin v0.40.3 // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/grafana-aws-sdk v1.1.0 // @grafana/aws-datasources
|
||||
github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0 // @grafana/partner-datasources
|
||||
github.com/grafana/grafana-cloud-migration-snapshot v1.9.0 // @grafana/grafana-operator-experience-squad
|
||||
@@ -233,7 +232,6 @@ require (
|
||||
|
||||
require (
|
||||
github.com/grafana/grafana/apps/advisor v0.0.0 // @grafana/plugins-platform-backend
|
||||
github.com/grafana/grafana/apps/alerting/alertenrichment v0.0.0 // @grafana/alerting-backend
|
||||
github.com/grafana/grafana/apps/alerting/notifications v0.0.0 // @grafana/alerting-backend
|
||||
github.com/grafana/grafana/apps/dashboard v0.0.0 // @grafana/grafana-app-platform-squad @grafana/dashboards-squad
|
||||
github.com/grafana/grafana/apps/folder v0.0.0 // @grafana/grafana-search-and-storage
|
||||
|
||||
@@ -1605,8 +1605,6 @@ github.com/grafana/grafana-app-sdk v0.40.3 h1:JFo7uAfbAJUfZ9neD7/4sODKm1xgu9zhck
|
||||
github.com/grafana/grafana-app-sdk v0.40.3/go.mod h1:j0KzHo3Sa6kd+lnwSScBNoV9Vobkg/YY9HtEjxpyPrk=
|
||||
github.com/grafana/grafana-app-sdk/logging v0.40.3 h1:2VXsXXEQiqAavRP8wusRDB6rDqf5lufP7A6NfjELqPE=
|
||||
github.com/grafana/grafana-app-sdk/logging v0.40.3/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU=
|
||||
github.com/grafana/grafana-app-sdk/plugin v0.40.3 h1:uH0oFZnYOUL+OXcyhd5NVYwoM+Wa0WUXvZ2Om1M91r0=
|
||||
github.com/grafana/grafana-app-sdk/plugin v0.40.3/go.mod h1:+ylwE0P8WgPu5zURK5aDnVJpwRpuK3573rwrVV28qzQ=
|
||||
github.com/grafana/grafana-aws-sdk v1.1.0 h1:G0fvwbQmHw14c5RXPd7Gnw9ZQcgzl139LtMDoe0KhmE=
|
||||
github.com/grafana/grafana-aws-sdk v1.1.0/go.mod h1:7e+47EdHynteYWGoT5Ere9KeOXQObsk8F0vkOLQ1tz8=
|
||||
github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0 h1:0TYrkzAc3u0HX+9GK86cGrLTUAcmQfl3/LEB3tL+SOA=
|
||||
|
||||
@@ -74,7 +74,7 @@ lineage: schemas: [{
|
||||
|
||||
// Version of the JSON schema, incremented each time a Grafana update brings
|
||||
// changes to said schema.
|
||||
schemaVersion: uint16 | *41
|
||||
schemaVersion: uint16 | *42
|
||||
|
||||
// Version of the dashboard, incremented each time the dashboard is updated.
|
||||
version?: uint32
|
||||
@@ -239,8 +239,8 @@ lineage: schemas: [{
|
||||
#VariableRefresh: 0 | 1 | 2 @cuetsy(kind="enum",memberNames="never|onDashboardLoad|onTimeRangeChanged")
|
||||
|
||||
// Determine if the variable shows on dashboard
|
||||
// Accepted values are 0 (show label and value), 1 (show value only), 2 (show nothing).
|
||||
#VariableHide: 0 | 1 | 2 @cuetsy(kind="enum",memberNames="dontHide|hideLabel|hideVariable") @grafana(TSVeneer="type")
|
||||
// Accepted values are 0 (show label and value), 1 (show value only), 2 (show nothing), 3 (show under the controls dropdown menu).
|
||||
#VariableHide: 0 | 1 | 2 | 3 @cuetsy(kind="enum",memberNames="dontHide|hideLabel|hideVariable|inControlsMenu") @grafana(TSVeneer="type")
|
||||
|
||||
// Sort variable options
|
||||
// Accepted values are:
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||
"npmClient": "yarn",
|
||||
"version": "12.2.0-pre"
|
||||
"version": "12.2.0"
|
||||
}
|
||||
|
||||
+5
-5
@@ -3,7 +3,7 @@
|
||||
"license": "AGPL-3.0-only",
|
||||
"private": true,
|
||||
"name": "grafana",
|
||||
"version": "12.2.0-pre",
|
||||
"version": "12.2.0",
|
||||
"repository": "github:grafana/grafana",
|
||||
"scripts": {
|
||||
"predev": "./scripts/check-frontend-dev.sh",
|
||||
@@ -264,7 +264,7 @@
|
||||
"@emotion/css": "11.13.5",
|
||||
"@emotion/react": "11.14.0",
|
||||
"@fingerprintjs/fingerprintjs": "^3.4.2",
|
||||
"@floating-ui/react": "0.27.15",
|
||||
"@floating-ui/react": "0.27.16",
|
||||
"@formatjs/intl-durationformat": "^0.7.0",
|
||||
"@glideapps/glide-data-grid": "^6.0.0",
|
||||
"@grafana/alerting": "workspace:*",
|
||||
@@ -286,8 +286,8 @@
|
||||
"@grafana/plugin-ui": "^0.10.10",
|
||||
"@grafana/prometheus": "workspace:*",
|
||||
"@grafana/runtime": "workspace:*",
|
||||
"@grafana/scenes": "6.33.0",
|
||||
"@grafana/scenes-react": "6.33.0",
|
||||
"@grafana/scenes": "6.34.0",
|
||||
"@grafana/scenes-react": "6.34.0",
|
||||
"@grafana/schema": "workspace:*",
|
||||
"@grafana/sql": "workspace:*",
|
||||
"@grafana/ui": "workspace:*",
|
||||
@@ -414,7 +414,7 @@
|
||||
"slate": "0.47.9",
|
||||
"slate-plain-serializer": "0.7.13",
|
||||
"slate-react": "0.22.10",
|
||||
"swagger-ui-react": "5.27.0",
|
||||
"swagger-ui-react": "5.28.1",
|
||||
"symbol-observable": "4.0.0",
|
||||
"systemjs": "6.15.1",
|
||||
"tinycolor2": "1.6.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/alerting",
|
||||
"version": "12.2.0-pre",
|
||||
"version": "12.2.0",
|
||||
"description": "Grafana Alerting Library – Build vertical integrations on top of the industry-leading alerting solution",
|
||||
"keywords": [
|
||||
"typescript",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/data",
|
||||
"version": "12.2.0-pre",
|
||||
"version": "12.2.0",
|
||||
"description": "Grafana Data Library",
|
||||
"keywords": [
|
||||
"typescript"
|
||||
@@ -56,8 +56,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "7.0.1",
|
||||
"@grafana/i18n": "12.2.0-pre",
|
||||
"@grafana/schema": "12.2.0-pre",
|
||||
"@grafana/i18n": "12.2.0",
|
||||
"@grafana/schema": "12.2.0",
|
||||
"@leeoniya/ufuzzy": "1.0.18",
|
||||
"@types/d3-interpolate": "^3.0.0",
|
||||
"@types/string-hash": "1.1.3",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { FieldType } from '../types/dataFrame';
|
||||
import { TimeRange } from '../types/time';
|
||||
|
||||
import { createDataFrame, toDataFrame } from './processDataFrame';
|
||||
import { anySeriesWithTimeField, addRow } from './utils';
|
||||
import { anySeriesWithTimeField, addRow, alignTimeRangeCompareData, shouldAlignTimeCompare } from './utils';
|
||||
|
||||
describe('anySeriesWithTimeField', () => {
|
||||
describe('single frame', () => {
|
||||
@@ -104,3 +105,287 @@ describe('addRow', () => {
|
||||
expect(frame.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('alignTimeRangeCompareData', () => {
|
||||
const ONE_DAY_MS = 24 * 60 * 60 * 1000; // 86400000ms
|
||||
const ONE_WEEK_MS = 7 * ONE_DAY_MS; // 604800000ms
|
||||
|
||||
it('should align time field values with positive diff (1 day)', () => {
|
||||
const frame = toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000] },
|
||||
{ name: 'value', type: FieldType.number, values: [10, 20, 30] },
|
||||
],
|
||||
});
|
||||
|
||||
alignTimeRangeCompareData(frame, ONE_DAY_MS);
|
||||
|
||||
expect(frame.fields[0].values).toEqual([ONE_DAY_MS + 1000, ONE_DAY_MS + 2000, ONE_DAY_MS + 3000]);
|
||||
expect(frame.fields[1].values).toEqual([10, 20, 30]); // non-time fields unchanged
|
||||
});
|
||||
|
||||
it('should align time field values with negative diff (1 week)', () => {
|
||||
const frame = toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000] },
|
||||
{ name: 'value', type: FieldType.number, values: [10, 20, 30] },
|
||||
],
|
||||
});
|
||||
|
||||
alignTimeRangeCompareData(frame, -ONE_WEEK_MS);
|
||||
|
||||
// When diff is negative, function does v - diff, so v - (-ONE_WEEK_MS) = v + ONE_WEEK_MS
|
||||
expect(frame.fields[0].values).toEqual([ONE_WEEK_MS + 1000, ONE_WEEK_MS + 2000, ONE_WEEK_MS + 3000]);
|
||||
});
|
||||
|
||||
it('should apply default gray color and timeCompare config', () => {
|
||||
const frame = toDataFrame({
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000] },
|
||||
{ name: 'value', type: FieldType.number, values: [10, 20] },
|
||||
],
|
||||
});
|
||||
|
||||
alignTimeRangeCompareData(frame, ONE_DAY_MS);
|
||||
|
||||
frame.fields.forEach((field) => {
|
||||
expect(field.config.color?.fixedColor).toBe('gray');
|
||||
expect(field.config.custom?.timeCompare).toEqual({
|
||||
diffMs: ONE_DAY_MS,
|
||||
isTimeShiftQuery: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should apply custom color when provided', () => {
|
||||
const frame = toDataFrame({
|
||||
fields: [{ name: 'value', type: FieldType.number, values: [10, 20] }],
|
||||
});
|
||||
|
||||
alignTimeRangeCompareData(frame, ONE_DAY_MS, 'red');
|
||||
|
||||
expect(frame.fields[0].config.color?.fixedColor).toBe('red');
|
||||
});
|
||||
|
||||
it('should preserve existing config when merging', () => {
|
||||
const frame = toDataFrame({
|
||||
fields: [
|
||||
{
|
||||
name: 'value',
|
||||
type: FieldType.number,
|
||||
values: [10, 20],
|
||||
config: {
|
||||
displayName: 'My Display Name',
|
||||
custom: { existingProperty: 'existingValue' },
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
alignTimeRangeCompareData(frame, ONE_WEEK_MS);
|
||||
|
||||
expect(frame.fields[0].config.displayName).toBe('My Display Name');
|
||||
expect(frame.fields[0].config.custom?.existingProperty).toBe('existingValue');
|
||||
expect(frame.fields[0].config.custom?.timeCompare?.diffMs).toBe(ONE_WEEK_MS);
|
||||
});
|
||||
});
|
||||
|
||||
describe('shouldAlignTimeCompare', () => {
|
||||
const TIME_VALUES_A = [1000, 2000, 3000];
|
||||
const TIME_VALUES_B = [5000, 6000, 7000];
|
||||
const ORIGINAL_VALUES = [10, 20, 30];
|
||||
const COMPARE_VALUES = [15, 25, 35];
|
||||
|
||||
const mockTimeRange: TimeRange = {
|
||||
from: { valueOf: () => 4000 },
|
||||
to: { valueOf: () => 8000 },
|
||||
raw: { from: 'now-1h', to: 'now' },
|
||||
} as TimeRange;
|
||||
|
||||
it('should return true when compare first time is before time range', () => {
|
||||
const originalFrame = toDataFrame({
|
||||
refId: 'A',
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: TIME_VALUES_A },
|
||||
{ name: 'value', type: FieldType.number, values: ORIGINAL_VALUES },
|
||||
],
|
||||
});
|
||||
|
||||
const compareFrame = toDataFrame({
|
||||
refId: 'A-compare',
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: TIME_VALUES_A },
|
||||
{ name: 'value', type: FieldType.number, values: COMPARE_VALUES },
|
||||
],
|
||||
meta: {
|
||||
timeCompare: {
|
||||
isTimeShiftQuery: true,
|
||||
diffMs: 86400000,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const allFrames = [originalFrame, compareFrame];
|
||||
expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when compare first time is after time range', () => {
|
||||
const originalFrame = toDataFrame({
|
||||
refId: 'A',
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: TIME_VALUES_A },
|
||||
{ name: 'value', type: FieldType.number, values: ORIGINAL_VALUES },
|
||||
],
|
||||
});
|
||||
|
||||
const compareFrame = toDataFrame({
|
||||
refId: 'A-compare',
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: TIME_VALUES_B },
|
||||
{ name: 'value', type: FieldType.number, values: COMPARE_VALUES },
|
||||
],
|
||||
meta: {
|
||||
timeCompare: {
|
||||
isTimeShiftQuery: true,
|
||||
diffMs: 86400000,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const allFrames = [originalFrame, compareFrame];
|
||||
expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when compare frame refId does not end with -compare', () => {
|
||||
const compareFrame = toDataFrame({
|
||||
refId: 'A',
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: TIME_VALUES_A },
|
||||
{ name: 'value', type: FieldType.number, values: ORIGINAL_VALUES },
|
||||
],
|
||||
});
|
||||
|
||||
const allFrames = [compareFrame];
|
||||
expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when original frame is not found', () => {
|
||||
const compareFrame = toDataFrame({
|
||||
refId: 'A-compare',
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: TIME_VALUES_A },
|
||||
{ name: 'value', type: FieldType.number, values: ORIGINAL_VALUES },
|
||||
],
|
||||
});
|
||||
|
||||
const allFrames = [compareFrame]; // No original frame with refId 'A'
|
||||
expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when compare frame has no time field', () => {
|
||||
const originalFrame = toDataFrame({
|
||||
refId: 'A',
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: TIME_VALUES_A },
|
||||
{ name: 'value', type: FieldType.number, values: ORIGINAL_VALUES },
|
||||
],
|
||||
});
|
||||
|
||||
const compareFrame = toDataFrame({
|
||||
refId: 'A-compare',
|
||||
fields: [{ name: 'value', type: FieldType.number, values: COMPARE_VALUES }],
|
||||
});
|
||||
|
||||
const allFrames = [originalFrame, compareFrame];
|
||||
expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when original frame has no time field', () => {
|
||||
const originalFrame = toDataFrame({
|
||||
refId: 'A',
|
||||
fields: [{ name: 'value', type: FieldType.number, values: ORIGINAL_VALUES }],
|
||||
});
|
||||
|
||||
const compareFrame = toDataFrame({
|
||||
refId: 'A-compare',
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: TIME_VALUES_A },
|
||||
{ name: 'value', type: FieldType.number, values: COMPARE_VALUES },
|
||||
],
|
||||
});
|
||||
|
||||
const allFrames = [originalFrame, compareFrame];
|
||||
expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when time fields have empty values', () => {
|
||||
const EMPTY_VALUES: number[] = [];
|
||||
|
||||
const originalFrame = toDataFrame({
|
||||
refId: 'A',
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: EMPTY_VALUES },
|
||||
{ name: 'value', type: FieldType.number, values: EMPTY_VALUES },
|
||||
],
|
||||
});
|
||||
|
||||
const compareFrame = toDataFrame({
|
||||
refId: 'A-compare',
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: EMPTY_VALUES },
|
||||
{ name: 'value', type: FieldType.number, values: EMPTY_VALUES },
|
||||
],
|
||||
});
|
||||
|
||||
const allFrames = [originalFrame, compareFrame];
|
||||
expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle null values and return true when first non-null time is before range', () => {
|
||||
const TIME_WITH_NULLS = [null, ...TIME_VALUES_A];
|
||||
const ORIGINAL_WITH_NULLS = [null, ...ORIGINAL_VALUES];
|
||||
const COMPARE_WITH_NULLS = [null, ...COMPARE_VALUES];
|
||||
|
||||
const originalFrame = toDataFrame({
|
||||
refId: 'A',
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: TIME_WITH_NULLS },
|
||||
{ name: 'value', type: FieldType.number, values: ORIGINAL_WITH_NULLS },
|
||||
],
|
||||
});
|
||||
|
||||
const compareFrame = toDataFrame({
|
||||
refId: 'A-compare',
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: TIME_WITH_NULLS },
|
||||
{ name: 'value', type: FieldType.number, values: COMPARE_WITH_NULLS },
|
||||
],
|
||||
});
|
||||
|
||||
const allFrames = [originalFrame, compareFrame];
|
||||
expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when all time values are null', () => {
|
||||
const ALL_NULL_TIMES = [null, null, null];
|
||||
|
||||
const originalFrame = toDataFrame({
|
||||
refId: 'A',
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: ALL_NULL_TIMES },
|
||||
{ name: 'value', type: FieldType.number, values: ORIGINAL_VALUES },
|
||||
],
|
||||
});
|
||||
|
||||
const compareFrame = toDataFrame({
|
||||
refId: 'A-compare',
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: ALL_NULL_TIMES },
|
||||
{ name: 'value', type: FieldType.number, values: COMPARE_VALUES },
|
||||
],
|
||||
});
|
||||
|
||||
const allFrames = [originalFrame, compareFrame];
|
||||
expect(shouldAlignTimeCompare(compareFrame, allFrames, mockTimeRange)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { DataFrame, Field, FieldType } from '../types/dataFrame';
|
||||
import { TimeRange } from '../types/time';
|
||||
|
||||
import { getTimeField } from './processDataFrame';
|
||||
|
||||
@@ -123,3 +124,79 @@ export function addRow(dataFrame: DataFrame, row: Record<string, unknown> | unkn
|
||||
// does not need any external updating.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aligns time range comparison data by adjusting timestamps and applying compare-specific styling
|
||||
* @param series - The DataFrame containing the comparison data
|
||||
* @param diff - The time difference in milliseconds to align the timestamps
|
||||
* @param compareColor - Optional color to use for the comparison series (defaults to 'gray')
|
||||
*/
|
||||
export function alignTimeRangeCompareData(series: DataFrame, diff: number, compareColor = 'gray') {
|
||||
series.fields.forEach((field: Field) => {
|
||||
// Align compare series time stamps with reference series
|
||||
if (field.type === FieldType.time) {
|
||||
field.values = field.values.map((v: number) => {
|
||||
return diff < 0 ? v - diff : v + diff;
|
||||
});
|
||||
}
|
||||
|
||||
field.config = {
|
||||
...(field.config ?? {}),
|
||||
color: {
|
||||
mode: 'fixed',
|
||||
fixedColor: compareColor,
|
||||
},
|
||||
custom: {
|
||||
...(field.config?.custom ?? {}),
|
||||
timeCompare: {
|
||||
diffMs: diff,
|
||||
isTimeShiftQuery: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a time comparison frame needs alignment based on whether its first time is before the current time range.
|
||||
* Returns true if the first time in compare is before timeRange.from, indicating it needs shifting.
|
||||
* @param compareFrame - The frame with time comparison data
|
||||
* @param allFrames - Array of all frames to find the matching original frame
|
||||
* @param timeRange - The current panel time range
|
||||
* @returns true if alignment is needed
|
||||
*/
|
||||
export function shouldAlignTimeCompare(compareFrame: DataFrame, allFrames: DataFrame[], timeRange: TimeRange): boolean {
|
||||
// Find the matching original frame by removing '-compare' from refId
|
||||
const compareRefId = compareFrame.refId;
|
||||
if (!compareRefId || !compareRefId.endsWith('-compare')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const originalRefId = compareRefId.replace('-compare', '');
|
||||
const originalFrame = allFrames.find(
|
||||
(frame) => frame.refId === originalRefId && !frame.meta?.timeCompare?.isTimeShiftQuery
|
||||
);
|
||||
|
||||
if (!originalFrame) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find time fields
|
||||
const compareTimeField = compareFrame.fields.find((field) => field.type === FieldType.time);
|
||||
const originalTimeField = originalFrame.fields.find((field) => field.type === FieldType.time);
|
||||
|
||||
if (!compareTimeField?.values.length || !originalTimeField?.values.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find first non-null time value from each frame
|
||||
const compareFirstTime = compareTimeField.values.find((value) => value != null);
|
||||
const originalFirstTime = originalTimeField.values.find((value) => value != null);
|
||||
|
||||
if (compareFirstTime == null || originalFirstTime == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if first non-null time value is before timeRange.from
|
||||
return compareFirstTime < timeRange.from.valueOf();
|
||||
}
|
||||
|
||||
@@ -50,6 +50,8 @@ export {
|
||||
isTimeSeriesField,
|
||||
getRowUniqueId,
|
||||
addRow,
|
||||
alignTimeRangeCompareData,
|
||||
shouldAlignTimeCompare,
|
||||
} from './dataframe/utils';
|
||||
export {
|
||||
StreamingDataFrame,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user