Merge branch 'main' into ismail/refactor-language-provider-methods

This commit is contained in:
ismail simsek
2025-05-25 00:15:52 +02:00
298 changed files with 5907 additions and 2425 deletions
-9
View File
@@ -1640,15 +1640,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "5"],
[0, 0, 0, "Unexpected any. Specify a different type.", "6"]
],
"public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboardForm.tsx:5381": [
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"],
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"],
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "2"],
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "3"],
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "4"],
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "5"],
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "6"]
],
"public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
+3 -2
View File
@@ -4343,7 +4343,8 @@ steps:
- apk add docker
- export GITHUB_TOKEN=$(cat /github-app/token)
- dagger run --silent go run ./pkg/build/cmd artifacts -a $${ARTIFACTS} --grafana-ref=$${GRAFANA_REF}
--enterprise-ref=$${ENTERPRISE_REF} --grafana-repo=$${GRAFANA_REPO} --version=$${VERSION}
--enterprise-ref=$${ENTERPRISE_REF} --grafana-repo=$${GRAFANA_REPO} --build-id=$${DRONE_BUILD_NUMBER}
--version=$${VERSION}
depends_on:
- github-app-generate-token
environment:
@@ -5205,6 +5206,6 @@ kind: secret
name: gcr_credentials
---
kind: signature
hmac: 1da5c9da34dc5dbffef8bef4ef84282997cb2363f929fdf4b897d2ee87a33703
hmac: e430c51190ba46dbd426d13f47c13c275f4d05b6b1a26d1d9f67b5a8f09ecb41
...
+2 -1
View File
@@ -177,6 +177,7 @@
/pkg/tests/apis/query @grafana/grafana-datasources-core-services
/pkg/tests/apis/alerting @grafana/grafana-app-platform-squad @grafana/alerting-backend
/pkg/tests/api/correlations/ @grafana/datapro
/pkg/tests/apis/secret/ @grafana/grafana-operator-experience-squad
/pkg/tsdb/grafanads/ @grafana/grafana-backend-group
/pkg/tsdb/opentsdb/ @grafana/partner-datasources
/pkg/util/ @grafana/grafana-backend-group
@@ -485,7 +486,7 @@ playwright.config.ts @grafana/plugins-platform-frontend
/public/app/core/components/TimelineChart/ @grafana/dataviz-squad
/public/app/core/components/Form/ @grafana/grafana-frontend-platform
/public/app/core/components/OptionsUI/ @grafana/dashboards-squad @grafana/dataviz-squad
/public/app/mock-api-utils.ts @grafana/grafana-frontend-platform
/public/app/dev-utils.ts @grafana/grafana-frontend-platform
/public/app/core/history/ @grafana/observability-traces-and-profiling
/public/app/features/admin/ @grafana/identity-access-team
+1 -1
View File
@@ -160,7 +160,7 @@
"name": "datasource/Elasticsearch",
"action": "addToProject",
"addToProject": {
"url": "https://github.com/orgs/grafana/projects/97"
"url": "https://github.com/orgs/grafana/projects/190"
}
},
{
@@ -42,10 +42,12 @@ jobs:
GRAFANA_DELIVERY_BOT_APP_PEM=delivery-bot-app:PRIVATE_KEY
- name: "Generate token"
id: generate_token
uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92
uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a
with:
app_id: ${{ vars.DELIVERY_BOT_APP_ID }}
private_key: ${{ env.GRAFANA_DELIVERY_BOT_APP_PEM }}
repositories: "[\"grafana\", \"grafana-enterprise\"]"
permissions: "{\"contents\": \"write\", \"pull_requests\": \"write\", \"workflows\":\"write\"}"
- name: Create release branch
id: branch
uses: grafana/grafana-github-actions-go/bump-release@main # zizmor: ignore[unpinned-uses]
@@ -3,8 +3,9 @@ name: Crowdin automatic task management
on:
workflow_dispatch:
# once a week on Sunday at midnight
schedule:
- cron: "0 0 * * 0"
# TODO enable once we're ready to create tasks automatically
# schedule:
# - cron: "0 0 * * 0"
jobs:
create-tasks-in-crowdin:
+6 -13
View File
@@ -44,13 +44,6 @@ jobs:
VERSION: ${{ inputs.version }}
runs-on: ubuntu-latest
steps:
- name: "Get vault secrets"
id: vault-secrets
uses: grafana/shared-workflows/actions/get-vault-secrets@main
with:
# Secrets placed in the ci/data/repo/grafana/grafana/delivery-bot-app path in Vault
repo_secrets: |
GRAFANA_DELIVERY_BOT_APP_PEM=delivery-bot-app:PRIVATE_KEY
- if: ${{ github.event.pull_request.merged == true && startsWith(github.head_ref, 'release/') }}
run: |
{
@@ -73,14 +66,14 @@ jobs:
create_next_release_branch_grafana:
name: Create next release branch (Grafana)
needs: setup
uses: ./.github/workflows/create-next-release-branch.yml
uses: grafana/grafana/.github/workflows/create-next-release-branch.yml@main
with:
ownerRepo: 'grafana/grafana'
source: ${{ needs.setup.outputs.release_branch }}
create_next_release_branch_enterprise:
name: Create next release branch (Grafana Enterprise)
needs: setup
uses: ./.github/workflows/create-next-release-branch.yml
uses: grafana/grafana/.github/workflows/create-next-release-branch.yml@main
with:
ownerRepo: 'grafana/grafana-enterprise'
source: ${{ needs.setup.outputs.release_branch }}
@@ -88,7 +81,7 @@ jobs:
needs:
- setup
- create_next_release_branch_grafana
uses: ./.github/workflows/migrate-prs.yml
uses: grafana/grafana/.github/workflows/migrate-prs.yml@main
with:
ownerRepo: 'grafana/grafana'
from: ${{ needs.setup.outputs.release_branch }}
@@ -97,14 +90,14 @@ jobs:
needs:
- setup
- create_next_release_branch_enterprise
uses: ./.github/workflows/migrate-prs.yml
uses: grafana/grafana/.github/workflows/migrate-prs.yml@main
with:
ownerRepo: 'grafana/grafana-enterprise'
from: ${{ needs.setup.outputs.release_branch }}
to: ${{ needs.create_next_release_branch_enterprise.outputs.branch }}
post_changelog_on_forum:
needs: setup
uses: ./.github/workflows/community-release.yml
uses: grafana/grafana/.github/workflows/community-release.yml@main
with:
version: ${{ needs.setup.outputs.version }}
dry_run: ${{ needs.setup.outputs.dry_run == 'true' }}
@@ -113,7 +106,7 @@ jobs:
# The github-release action retrieves the changelog using the /repos/grafana/grafana/contents/CHANGELOG.md API
# endpoint.
needs: setup
uses: ./.github/workflows/github-release.yml
uses: grafana/grafana/.github/workflows/github-release.yml@main
with:
version: ${{ needs.setup.outputs.version }}
dry_run: ${{ needs.setup.outputs.dry_run == 'true' }}
+25 -22
View File
@@ -16,10 +16,6 @@ on:
required: true
type: string
description: The version of Grafana that is being released (without the `v` prefix)`
target:
required: true
type: string
description: The release branch pattern (eg v9.5.x) that these changes are being merged into
changelog:
required: false
type: boolean
@@ -73,19 +69,28 @@ jobs:
id: vault-secrets
uses: grafana/shared-workflows/actions/get-vault-secrets@main
with:
# Secrets placed in the ci/data/repo/grafana/grafana/delivery-bot-app path in Vault
repo_secrets: |
GRAFANA_DELIVERY_BOT_APP_PEM=delivery-bot-app:PRIVATE_KEY
- name: Generate token
id: generate_changelog_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\", \"grafana-enterprise\"]"
permissions: "{\"contents\": \"write\", \"pull_requests\": \"write\", \"workflows\":\"write\"}"
- run: echo "RELEASE_BRANCH=release-${VERSION}" >> "$GITHUB_ENV"
- name: Checkout Grafana
uses: actions/checkout@v4
with:
token: ${{ steps.generate_changelog_token.outputs.token }}
ref: ${{ env.RELEASE_BRANCH }}
fetch-tags: true
fetch-depth: 0
- name: Checkout Grafana (main)
uses: actions/checkout@v4
with:
token: ${{ steps.generate_changelog_token.outputs.token }}
ref: main
fetch-depth: '0'
path: .grafana-main
@@ -94,21 +99,17 @@ jobs:
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Configure git user
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
- name: Create branch
run: git checkout -b "release/${{ github.run_number }}/$VERSION"
- name: Generate changelog token
if: ${{ inputs.changelog == true || inputs.changelog == 'true' }}
id: generate_changelog_token
uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92
with:
app_id: ${{ vars.DELIVERY_BOT_APP_ID }}
private_key: ${{ env.GRAFANA_DELIVERY_BOT_APP_PEM }}
- name: Generate changelog
id: changelog
if: ${{ inputs.changelog == true || inputs.changelog == 'true' }}
@@ -156,13 +157,16 @@ jobs:
- name: Commit CHANGELOG.md changes
if: ${{ inputs.changelog == true || inputs.changelog == 'true' }}
run: git add CHANGELOG.md && git commit --allow-empty -m "Update changelog" CHANGELOG.md
- name: Update package.json versions
- name: Bump versions
if: ${{ inputs.bump == true || inputs.bump == 'true' }}
uses: ./.grafana-main/pkg/build/actions/bump-version
uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e
with:
version: 'patch'
verb: run
args: go run -C .grafana-main ./pkg/build/actions/bump-version -version="patch"
- name: make gen-cue
shell: bash
run: make gen-cue
- name: Add package.json changes
if: ${{ inputs.bump == true || inputs.bump == 'true' }}
run: |
@@ -171,14 +175,13 @@ jobs:
git commit -m "Update version to $VERSION"
- name: Git push
if: inputs.dry_run != true
run: git push
- name: Create PR
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: ${{ steps.branch.outputs.branch }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
LATEST_FLAG=""
LATEST_FLAG=()
if [ "$LATEST" = "true" ]; then
LATEST_FLAG=(-l "release/latest")
fi
@@ -186,6 +189,6 @@ jobs:
"${LATEST_FLAG[@]}" \
-l "no-changelog" \
--dry-run="$DRY_RUN" \
-B "$BRANCH" \
-B "${RELEASE_BRANCH}" \
--title "Release: $VERSION" \
--body "These code changes must be merged after a release is complete"
-2
View File
@@ -41,8 +41,6 @@ __debug_bin*
# This is the new place of the block, but I leave the previous here for a while
/devenv/docker/blocks/auth/saml-enterprise
/devenv/docker/blocks/auth/signer
/devenv/docker/blocks/spanner_tests
/devenv/docker/blocks/spanner_tests_multi
/devenv/docker/blocks/mt-db
/tmp
-1
View File
@@ -19,4 +19,3 @@
# This is the new place of the block, but I leave the previous here for a while
!/devenv/docker/blocks/auth/saml-enterprise
!/devenv/docker/blocks/auth/signer
!/devenv/docker/blocks/spanner_tests
+179
View File
@@ -1,3 +1,182 @@
<!-- 12.0.1 START -->
# 12.0.1 (2025-05-22)
### Features and enhancements
- **Chore:** Bump Go version to 1.24.3 [#105101](https://github.com/grafana/grafana/pull/105101), [@macabu](https://github.com/macabu)
- **Dependencies:** Bump github.com/openfga/openfga from v1.8.6 to v1.8.12 [#105368](https://github.com/grafana/grafana/pull/105368), [@macabu](https://github.com/macabu)
- **Dependencies:** Unpin and bump github.com/getkin/kin-openapi from v0.126.0 to v0.132.0 [#105249](https://github.com/grafana/grafana/pull/105249), [@macabu](https://github.com/macabu)
- **K8s:** Dashboards: Add fine grained access control checks to /apis [#104419](https://github.com/grafana/grafana/pull/104419), [@stephaniehingtgen](https://github.com/stephaniehingtgen)
### Bug fixes
- **Dashboard:** Fix Panel Explore link subpath duplication (#104952) [#105056](https://github.com/grafana/grafana/pull/105056), [@axelavargas](https://github.com/axelavargas)
- **Dashboard:** Fixes issue with row repeats and first row [#104469](https://github.com/grafana/grafana/pull/104469), [@torkelo](https://github.com/torkelo)
- **Graphite:** Ensure template variables are interpolated correctly [#105389](https://github.com/grafana/grafana/pull/105389), [@aangelisc](https://github.com/aangelisc)
- **Graphite:** Fix Graphite series interpolation [#104516](https://github.com/grafana/grafana/pull/104516), [@aangelisc](https://github.com/aangelisc)
- **InfluxDB:** Fix nested variable interpolation [#104176](https://github.com/grafana/grafana/pull/104176), [@aangelisc](https://github.com/aangelisc)
- **MetricsDrilldown:** Restore link to Metrics Drilldown from Explore [#104073](https://github.com/grafana/grafana/pull/104073), [@NWRichmond](https://github.com/NWRichmond)
- **NestedFolderPicker:** Fix scroll jumps back to top [#105769](https://github.com/grafana/grafana/pull/105769), [@samsch](https://github.com/samsch)
- **Preferences:** Disable the save button whilst saving preferences [#105612](https://github.com/grafana/grafana/pull/105612), [@ashharrison90](https://github.com/ashharrison90)
- **Prometheus:** Fix semver import path [#104945](https://github.com/grafana/grafana/pull/104945), [@jackw](https://github.com/jackw)
- **Themes:** Prevent duplicated API call in drawer [#105611](https://github.com/grafana/grafana/pull/105611), [@ashharrison90](https://github.com/ashharrison90)
- **XYChart:** Coerce threshold steps to numbers [#104492](https://github.com/grafana/grafana/pull/104492), [@leeoniya](https://github.com/leeoniya)
- **Security:** Fix CVE-2025-4123
- **Security:** Fix CVE-2025-3580
<!-- 12.0.1 END -->
<!-- 11.6.2 START -->
# 11.6.2 (2025-05-22)
### Features and enhancements
- **Chore:** Bump Go version to 1.24.3 [#105103](https://github.com/grafana/grafana/pull/105103), [@macabu](https://github.com/macabu)
- **Dependencies:** Bump github.com/blevesearch/bleve/v2 from v2.4.4-git to v2.5.0 [#105443](https://github.com/grafana/grafana/pull/105443), [@macabu](https://github.com/macabu)
- **Dependencies:** Bump github.com/openfga/openfga from v1.8.6 to v1.8.12 [#105369](https://github.com/grafana/grafana/pull/105369), [@macabu](https://github.com/macabu)
- **Dependencies:** Unpin and bump github.com/getkin/kin-openapi from v0.126.0 to v0.132.0 [#105251](https://github.com/grafana/grafana/pull/105251), [@macabu](https://github.com/macabu)
### Bug fixes
- **Dashboard:** Fixes issue with row repeats and first row [#104467](https://github.com/grafana/grafana/pull/104467), [@torkelo](https://github.com/torkelo)
- **Graphite:** Ensure template variables are interpolated correctly [#105388](https://github.com/grafana/grafana/pull/105388), [@aangelisc](https://github.com/aangelisc)
- **Graphite:** Fix Graphite series interpolation [#104568](https://github.com/grafana/grafana/pull/104568), [@aangelisc](https://github.com/aangelisc)
- **Prometheus:** Fix semver import path [#104943](https://github.com/grafana/grafana/pull/104943), [@jackw](https://github.com/jackw)
- **Security:** Fix CVE-2025-4123
- **Security:** Fix CVE-2025-3580
<!-- 11.6.2 END -->
<!-- 11.5.5 START -->
# 11.5.5 (2025-05-22)
### Features and enhancements
- **Chore:** Bump Go version to 1.24.3 [#105109](https://github.com/grafana/grafana/pull/105109), [@macabu](https://github.com/macabu)
- **Dependencies:** Bump github.com/blevesearch/bleve/v2 from v2.4.3 to v2.5.0 [#105441](https://github.com/grafana/grafana/pull/105441), [@macabu](https://github.com/macabu)
- **Dependencies:** Bump github.com/openfga/openfga from v1.8.5 to v1.8.12 [#105373](https://github.com/grafana/grafana/pull/105373), [@macabu](https://github.com/macabu)
- **Dependencies:** Unpin and bump github.com/getkin/kin-openapi from v0.126.0 to v0.132.0 [#105252](https://github.com/grafana/grafana/pull/105252), [@macabu](https://github.com/macabu)
- **Security:** Fix CVE-2025-4123
- **Security:** Fix CVE-2025-3580
<!-- 11.5.5 END -->
<!-- 11.4.5 START -->
# 11.4.5 (2025-05-22)
### Features and enhancements
- **Chore:** Bump Go version to 1.24.3 [#105110](https://github.com/grafana/grafana/pull/105110), [@macabu](https://github.com/macabu)
- **Dependencies:** Bump github.com/blevesearch/bleve/v2 from v2.4.2 to v2.5.0 [#105445](https://github.com/grafana/grafana/pull/105445), [@macabu](https://github.com/macabu)
- **Dependencies:** Bump github.com/openfga/openfga from v1.8.5 to v1.8.12 [#105375](https://github.com/grafana/grafana/pull/105375), [@macabu](https://github.com/macabu)
- **Dependencies:** Unpin and bump github.com/getkin/kin-openapi from v0.125.0 to v0.132.0 [#105253](https://github.com/grafana/grafana/pull/105253), [@macabu](https://github.com/macabu)
- **Security:** Fix CVE-2025-4123
- **Security:** Fix CVE-2025-3580
<!-- 11.4.5 END -->
<!-- 11.3.7 START -->
# 11.3.7 (2025-05-22)
### Features and enhancements
- **Chore:** Bump Go version to 1.24.3 [#105112](https://github.com/grafana/grafana/pull/105112), [@macabu](https://github.com/macabu)
- **Dependencies:** Bump github.com/blevesearch/bleve/v2 from v2.4.2 to v2.5.0 [#105447](https://github.com/grafana/grafana/pull/105447), [@macabu](https://github.com/macabu)
- **Dependencies:** Bump github.com/openfga/openfga from v1.8.5 to v1.8.12 [#105376](https://github.com/grafana/grafana/pull/105376), [@macabu](https://github.com/macabu)
- **Dependencies:** Unpin and bump github.com/getkin/kin-openapi from v0.125.0 to v0.132.0 [#105254](https://github.com/grafana/grafana/pull/105254), [@macabu](https://github.com/macabu)
- **Security:** Fix CVE-2025-4123
- **Security:** Fix CVE-2025-3580
<!-- 11.3.7 END -->
<!-- 11.2.10 START -->
# 11.2.10 (2025-05-22)
### Features and enhancements
- **Chore:** Bump Go version to 1.24.3 [#105113](https://github.com/grafana/grafana/pull/105113), [@macabu](https://github.com/macabu)
- **Dependencies:** Bump github.com/openfga/openfga from v1.8.5 to v1.8.12 [#105378](https://github.com/grafana/grafana/pull/105378), [@macabu](https://github.com/macabu)
- **Dependencies:** Unpin and bump github.com/getkin/kin-openapi from v0.125.0 to v0.132.0 [#105255](https://github.com/grafana/grafana/pull/105255), [@macabu](https://github.com/macabu)
- **Security:** Fix CVE-2025-4123
- **Security:** Fix CVE-2025-3580
<!-- 11.2.10 END -->
<!-- 10.4.19 START -->
# 10.4.19 (2025-05-22)
### Features and enhancements
- **Chore:** Bump Go version to 1.24.3 [#105115](https://github.com/grafana/grafana/pull/105115), [@macabu](https://github.com/macabu)
- **Dependencies:** Bump golang.org/x/net from v0.37.0 to v0.40.0 [#105449](https://github.com/grafana/grafana/pull/105449), [@macabu](https://github.com/macabu)
- **Security:** Fix CVE-2025-4123
- **Security:** Fix CVE-2025-3580
<!-- 10.4.19 END -->
<!-- 12.0.0+security-01 START -->
# 12.0.0+security-01 (2025-05-21)
### Bug fixes
- **Security:** Fix CVE-2025-4123
<!-- 12.0.0+security-01 END -->
<!-- 11.6.1+security-01 START -->
# 11.6.1+security-01 (2025-05-21)
### Bug fixes
- **Security:** Fix CVE-2025-4123
<!-- 11.6.1+security-01 END -->
<!-- 11.5.4+security-01 START -->
# 11.5.4+security-01 (2025-05-21)
### Bug fixes
- **Security:** Fix CVE-2025-4123
<!-- 11.5.4+security-01 END -->
<!-- 11.4.4+security-01 START -->
# 11.4.4+security-01 (2025-05-21)
### Bug fixes
- **Security:** Fix CVE-2025-4123
<!-- 11.4.4+security-01 END -->
<!-- 11.3.6+security-01 START -->
# 11.3.6+security-01 (2025-05-21)
### Bug fixes
- **Security:** Fix CVE-2025-4123
<!-- 11.3.6+security-01 END -->
<!-- 11.2.9+security-01 START -->
# 11.2.9+security-01 (2025-05-21)
### Bug fixes
- **Security:** Fix CVE-2025-4123
<!-- 11.2.9+security-01 END -->
<!-- 10.4.18+security-01 START -->
# 10.4.18+security-01 (2025-05-21)
### Bug fixes
- **Security:** Fix CVE-2025-4123
<!-- 10.4.18+security-01 END -->
<!-- 12.0.0 START -->
# 12.0.0 (2025-05-05)
@@ -30,7 +30,7 @@ func (c *check) ID() string {
}
func (c *check) Name() string {
return "SSO Setting"
return "SSO setting"
}
func (c *check) Init(ctx context.Context) error {
@@ -88,7 +88,7 @@ func (c *check) ID() string {
}
func (c *check) Name() string {
return "Data Source"
return "data source"
}
func (c *check) Init(ctx context.Context) error {
@@ -46,7 +46,7 @@ func (c *check) ID() string {
}
func (c *check) Name() string {
return "Plugin"
return "plugin"
}
func (c *check) Items(ctx context.Context) ([]any, error) {
@@ -149,7 +149,7 @@ func (s *deprecationStep) Run(ctx context.Context, log logging.Logger, _ *adviso
p.ID,
[]advisor.CheckErrorLink{
{
Message: "Admin",
Message: "View plugin",
Url: fmt.Sprintf("/plugins/%s", p.ID),
},
},
@@ -47,7 +47,7 @@ func TestRun(t *testing.T) {
Links: []advisor.CheckErrorLink{
{
Url: "/plugins/plugin1",
Message: "Admin",
Message: "View plugin",
},
},
},
@@ -72,9 +72,12 @@ func (r *Runner) createOrUpdate(ctx context.Context, log logging.Logger, obj res
return err
}
currentAnnotations := current.GetAnnotations()
if currentAnnotations == nil {
currentAnnotations = make(map[string]string)
}
annotations := obj.GetAnnotations()
maps.Copy(annotations, currentAnnotations)
obj.SetAnnotations(annotations)
maps.Copy(currentAnnotations, annotations)
obj.SetAnnotations(currentAnnotations) // This will update the annotations in the object
_, err = r.client.Update(ctx, id, obj, resource.UpdateOptions{})
if err != nil {
// Ignore the error, it's probably due to a race condition
@@ -874,6 +874,7 @@ CustomVariableKind: {
GroupByVariableSpec: {
name: string | *""
datasource?: DataSourceRef
defaultValue?: VariableOption
current: VariableOption | *{
text: ""
value: ""
@@ -878,6 +878,7 @@ CustomVariableKind: {
GroupByVariableSpec: {
name: string | *""
datasource?: DataSourceRef
defaultValue?: VariableOption
current: VariableOption | *{
text: ""
value: ""
@@ -1601,15 +1601,16 @@ func NewDashboardGroupByVariableKind() *DashboardGroupByVariableKind {
// GroupBy variable specification
// +k8s:openapi-gen=true
type DashboardGroupByVariableSpec struct {
Name string `json:"name"`
Datasource *DashboardDataSourceRef `json:"datasource,omitempty"`
Current DashboardVariableOption `json:"current"`
Options []DashboardVariableOption `json:"options"`
Multi bool `json:"multi"`
Label *string `json:"label,omitempty"`
Hide DashboardVariableHide `json:"hide"`
SkipUrlSync bool `json:"skipUrlSync"`
Description *string `json:"description,omitempty"`
Name string `json:"name"`
Datasource *DashboardDataSourceRef `json:"datasource,omitempty"`
DefaultValue *DashboardVariableOption `json:"defaultValue,omitempty"`
Current DashboardVariableOption `json:"current"`
Options []DashboardVariableOption `json:"options"`
Multi bool `json:"multi"`
Label *string `json:"label,omitempty"`
Hide DashboardVariableHide `json:"hide"`
SkipUrlSync bool `json:"skipUrlSync"`
Description *string `json:"description,omitempty"`
}
// NewDashboardGroupByVariableSpec creates a new DashboardGroupByVariableSpec object.
+26 -12
View File
@@ -112,22 +112,36 @@ Content-Type: application/json
Content-Length: 357
{
"jti":"2",
"iss":"https://grafana.com",
"sub":"https://play.grafana.org/"
"lid":"1",
"included_users":15,
"lic_exp_warn_days":30,
"tok_exp_warn_days":2,
"update_days":1,
"prod":["grafana-enterprise"],
"company":"Grafana Labs"
"status":0,
"jti":"",
"iss":"",
"sub":"",
"iat":0,
"exp":0,
"nbf":0,
"lexp":0,
"lid":"",
"limit_by":"",
"included_users":0,
"lic_exp_warn_days":0,
"tok_exp_warn_days":0,
"update_days":0,
"prod":null,
"company":"",
"account":"",
"slug":"",
"usage_billing":false,
"max_concurrent_user_sessions":0,
"details_url":"",
"trial":false,
"trial_exp":0,
"anonymousRatio":0
}
```
The response is a JSON blob available for debugging purposes. The
available fields may change at any time without any prior notice.
The response is a JSON blob with specific values intentionally not shown. The
available fields may change at any time without any prior notice. Refer to [Check license availability](#check-license-availability) for information on using the API to check the status of your license.
Status Codes:
@@ -147,3 +147,54 @@ Status Codes:
- **401** - Unauthorized
- **403** - Permission denied
- **404** - Team not found/Group not found
## Search Team Groups
`GET /api/teams/:teamId/groups/search`
Search for team groups with pagination support.
**Required permissions**
| Action | Scope |
| ---------------------- | -------- |
| teams.permissions:read | teams:\* |
**Example Request**:
```http
GET /api/teams/1/groups/search?name=editors&query=group&page=1&perpage=10 HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt
```
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
[
{
"totalCount": 1,
"teamGroups": [
{
"orgId": 1,
"teamId": 1,
"groupId": "cn=editors,ou=groups,dc=grafana,dc=org"
}
],
"page": 1,
"perPage": 10
}
]
```
Status Codes:
- **200** - Ok
- **400** - Bad Request (invalid team ID format or missing query parameter)
- **401** - Unauthorized
- **403** - Permission denied
- **500** - Internal Server Error
@@ -172,7 +172,7 @@
"grafanaDependency": {
"type": "string",
"description": "Required Grafana version for this plugin. Validated using https://github.com/npm/node-semver.",
"pattern": "^(<=|>=|<|>|=|~|\\^)?([0-9]+)(\\.[0-9x\\*]+)?(\\.[0-9x\\*]+)?(\\s(<=|>=|<|=>)?([0-9]+)(\\.[0-9x\\*]+)?(\\.[0-9x\\*]+)?)?$"
"pattern": "^(<=|>=|<|>|=|~|\\^)?([0-9]+)(\\.[0-9x\\*]+)?(\\.[0-9x\\*]+)?(-[0-9A-Za-z-.]+)?(\\s(<=|>=|<|=>)?([0-9]+)(\\.[0-9x\\*]+)?(\\.[0-9x\\*]+)?(-[0-9A-Za-z-.]+)?)?$"
},
"plugins": {
"type": "array",
@@ -18,7 +18,7 @@ weight: 300
# Provision resources and sync dashboards
{{< admonition type="caution" >}}
Provisioning is an [experimental feature](https://grafana.com/docs/release-life-cycle/) introduced in Grafana v12 for open source and Enterprise editions. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. This feature is not publicly available in Grafana Cloud yet.
Provisioning is an [experimental feature](https://grafana.com/docs/release-life-cycle/) introduced in Grafana v12 for open source and Enterprise editions. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. This feature is not publicly available in Grafana Cloud yet. Only the cloud-hosted version of GitHub (GitHub.com) is supported at this time. GitHub Enterprise is not yet compatible.
Sign up for Grafana Cloud Git Sync early access using [this form](https://forms.gle/WKkR3EVMcbqsNnkD9).
{{< /admonition >}}
@@ -16,7 +16,7 @@ weight: 200
# Set up file provisioning
{{< admonition type="caution" >}}
Local file provisioning is an [experimental feature](https://grafana.com/docs/release-life-cycle/) introduced in Grafana v12 for open source and Enterprise editions. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. Enable the `provisioning` and `kubernetesDashboards` feature toggles in Grafana to use this feature. This feature is not publicly available in Grafana Cloud yet.
Local file provisioning is an [experimental feature](https://grafana.com/docs/release-life-cycle/) introduced in Grafana v12 for open source and Enterprise editions. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. Enable the `provisioning` and `kubernetesDashboards` feature toggles in Grafana to use this feature. This feature is not publicly available in Grafana Cloud yet. Only the cloud-hosted version of GitHub (GitHub.com) is supported at this time. GitHub Enterprise is not yet compatible.
Sign up for Grafana Cloud Git Sync early access using [this form](https://forms.gle/WKkR3EVMcbqsNnkD9).
@@ -16,7 +16,7 @@ weight: 100
# Set up Git Sync
{{< admonition type="caution" >}}
Git Sync is an [experimental feature](https://grafana.com/docs/release-life-cycle/) introduced in Grafana v12 for open source and Enterprise editions. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. Enable the `provisioning` and `kubernetesDashboards` feature toggles in Grafana to use this feature. This feature is not publicly available in Grafana Cloud yet.
Git Sync is an [experimental feature](https://grafana.com/docs/release-life-cycle/) introduced in Grafana v12 for open source and Enterprise editions. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. Enable the `provisioning` and `kubernetesDashboards` feature toggles in Grafana to use this feature. This feature is not publicly available in Grafana Cloud yet. Only the cloud-hosted version of GitHub (GitHub.com) is supported at this time. GitHub Enterprise is not yet compatible.
Sign up for Grafana Cloud Git Sync early access using [this form](https://forms.gle/WKkR3EVMcbqsNnkD9).
@@ -16,7 +16,7 @@ weight: 100
# Git Sync
{{< admonition type="caution" >}}
Git Sync is an [experimental feature](https://grafana.com/docs/release-life-cycle/) introduced in Grafana v12 for open source and Enterprise editions. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. Enable the `provisioning` and `kubernetesDashboards` feature toggles in Grafana to use this feature. This feature is not publicly available in Grafana Cloud yet.
Git Sync is an [experimental feature](https://grafana.com/docs/release-life-cycle/) introduced in Grafana v12 for open source and Enterprise editions. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. Enable the `provisioning` and `kubernetesDashboards` feature toggles in Grafana to use this feature. This feature is not publicly available in Grafana Cloud yet. Only the cloud-hosted version of GitHub (GitHub.com) is supported at this time. GitHub Enterprise is not yet compatible.
Sign up for Grafana Cloud Git Sync early access using [this form](https://forms.gle/WKkR3EVMcbqsNnkD9).
@@ -16,7 +16,7 @@ weight: 300
# Work with provisioned dashboards
{{< admonition type="caution" >}}
Git Sync and File path provisioning an [experimental feature](https://grafana.com/docs/release-life-cycle/) introduced in Grafana v12 for open source and Enterprise editions. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. Enable the `provisioning` and `kubernetesDashboards` feature toggles in Grafana. These features aren't available publicly in Grafana Cloud yet.
Git Sync and File path provisioning an [experimental feature](https://grafana.com/docs/release-life-cycle/) introduced in Grafana v12 for open source and Enterprise editions. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. Enable the `provisioning` and `kubernetesDashboards` feature toggles in Grafana. These features aren't available publicly in Grafana Cloud yet. Only the cloud-hosted version of GitHub (GitHub.com) is supported at this time. GitHub Enterprise is not yet compatible.
Sign up for Grafana Cloud Git Sync early access using [this form](https://forms.gle/WKkR3EVMcbqsNnkD9).
@@ -19,7 +19,7 @@ weight: 400
# Manage provisioned repositories with Git Sync
{{< admonition type="caution" >}}
Git Sync is an [experimental feature](https://grafana.com/docs/release-life-cycle/) introduced in Grafana v12 for open source and Enterprise editions. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. Enable the `provisioning` and `kubernetesDashboards` feature toggles in Grafana to use this feature. This feature is not publicly available in Grafana Cloud yet.
Git Sync is an [experimental feature](https://grafana.com/docs/release-life-cycle/) introduced in Grafana v12 for open source and Enterprise editions. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. Enable the `provisioning` and `kubernetesDashboards` feature toggles in Grafana to use this feature. This feature is not publicly available in Grafana Cloud yet. Only the cloud-hosted version of GitHub (GitHub.com) is supported at this time. GitHub Enterprise is not yet compatible.
Sign up for Grafana Cloud Git Sync early access using [this form](https://forms.gle/WKkR3EVMcbqsNnkD9).
@@ -148,31 +148,31 @@ The table includes default and other fields:
### `annotations`
The configuration for the list of annotations that are associated with the dashboard.
For the JSON and field usage notes, refer to the [annotations schema documentation](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/json-models/schema-v2/annotations-schema/).
For the JSON and field usage notes, refer to the [annotations schema documentation](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/schema-v2/annotations-schema/).
### `elements`
Dashboards can contain the following elements:
- [PanelKind](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/json-models/schema-v2/panel-schema/)
- [LibraryPanelKind](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/json-models/schema-v2/librarypanel-schema/)
- [PanelKind](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/schema-v2/panel-schema/)
- [LibraryPanelKind](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/schema-v2/librarypanel-schema/)
### `layout`
Dashboards can have four layout options:
- [GridLayoutKind](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/json-models/schema-v2/layout-schema/#gridlayoutkind)
- [AutoGridLayoutKind](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/json-models/schema-v2/layout-schema/#autogridlayoutkind)
- [RowsLayoutKind](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/json-models/schema-v2/layout-schema/#rowslayoutkind)
- [TabsLayoutKind](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/json-models/schema-v2/layout-schema/#tabslayoutkind)
- [GridLayoutKind](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/schema-v2/layout-schema/#gridlayoutkind)
- [AutoGridLayoutKind](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/schema-v2/layout-schema/#autogridlayoutkind)
- [RowsLayoutKind](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/schema-v2/layout-schema/#rowslayoutkind)
- [TabsLayoutKind](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/schema-v2/layout-schema/#tabslayoutkind)
For the JSON and field usage notes about each of these, refer to the [layout schema documentation](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/json-models/schema-v2/layout-schema/).
For the JSON and field usage notes about each of these, refer to the [layout schema documentation](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/schema-v2/layout-schema/).
### `links`
The configuration for links with references to other dashboards or external websites.
For the JSON and field usage notes, refer to the [links schema documentation](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/json-models/schema-v2/links-schema/).
For the JSON and field usage notes, refer to the [links schema documentation](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/schema-v2/links-schema/).
### `tags`
@@ -183,7 +183,7 @@ The tags associated with the dashboard:
### `timesettings`
The `TimeSettingsSpec` defines the default time configuration for the time picker and the refresh picker for the specific dashboard.
For the JSON and field usage notes about the `TimeSettingsSpec`, refer to the [timesettings schema documentation](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/json-models/schema-v2/timesettings-schema/).
For the JSON and field usage notes about the `TimeSettingsSpec`, refer to the [timesettings schema documentation](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/schema-v2/timesettings-schema/).
### `variables`
@@ -200,7 +200,7 @@ There are eight variables types:
- GroupByVariableKind
- AdhocVariableKind
For the JSON and field usage notes about the `variables` spec, refer to the [variables schema documentation](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/json-models/schema-v2/variables-schema/).
For the JSON and field usage notes about the `variables` spec, refer to the [variables schema documentation](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/schema-v2/variables-schema/).
## Notes and limitations
@@ -87,14 +87,14 @@ Following is the JSON for a default grid layout, a grid layout item, and a grid
The following table explains the usage of the grid layout item JSON fields:
| Name | Usage |
| ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| x | integer. Position of the item x-axis. |
| y | integer. Position of the item y-axis. |
| width | Width of the item in pixels. |
| height | Height of the item in pixels. |
| element | `ElementReference`. Reference to a [`PanelKind`](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/json-models/schema-v2/panel-schema/) from `dashboard.spec.elements` expressed as JSON Schema reference. |
| repeat? | [RepeatOptions](#repeatoptions). Configured repeat options, if any |
| Name | Usage |
| ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| x | integer. Position of the item x-axis. |
| y | integer. Position of the item y-axis. |
| width | Width of the item in pixels. |
| height | Height of the item in pixels. |
| element | `ElementReference`. Reference to a [`PanelKind`](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/schema-v2/panel-schema/) from `dashboard.spec.elements` expressed as JSON Schema reference. |
| repeat? | [RepeatOptions](#repeatoptions). Configured repeat options, if any |
#### `RepeatOptions`
@@ -188,7 +188,7 @@ The following table explains the usage of the auto grid layout item JSON fields:
| Name | Usage |
| ---- | ----- |
| element | `ElementReference`. Reference to a [`PanelKind`](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/json-models/schema-v2/panel-schema/) from `dashboard.spec.elements` expressed as JSON Schema reference. |
| element | `ElementReference`. Reference to a [`PanelKind`](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/observability-as-code/schema-v2/panel-schema/) from `dashboard.spec.elements` expressed as JSON Schema reference. |
| repeat? | [AutoGridRepeatOptions](#autogridrepeatoptions). Configured repeat options, if any. |
| conditionalRendering? | `ConditionalRenderingGroupKind`. Rules for hiding or showing panels, if any. Consists of:<ul><li>kind: "ConditionalRenderingGroup"</li><li>spec: [ConditionalRenderingGroupSpec](#conditionalrenderinggroupspec)</li></ul> |
@@ -201,6 +201,7 @@ Use this transformation to add a new field calculated from two other fields. Eac
- **All number fields** - Set the left side of a **Binary operation** to apply the calculation to all number fields.
- **As percentile** - If you select **Row index** mode, then the **As percentile** switch appears. This switch allows you to transform the row index as a percentage of the total number of rows.
- **Alias** - (Optional) Enter the name of your new field. If you leave this blank, then the field will be named to match the calculation.
> **Note:** If a variable will be used in this transformation, the default alias will be interpolated with the value of the variable. Please explicitly define an alias if you would like the alias to not be affected by variable changes.
- **Replace all fields** - (Optional) Select this option if you want to hide all other fields and display only your calculated field in the visualization.
In the example below, we added two fields together and named them Sum.
@@ -209,6 +209,10 @@ You can use the `org_attribute_path` and `org_mapping` configuration options to
To ease configuration of a proper JMESPath expression, go to [JMESPath](http://jmespath.org/) to test and evaluate expressions with custom payloads.
{{< admonition type="note" >}}
When using `org_attribute_path`, the value returned by the JMESPath expression must be an array, not a string.
{{< /admonition >}}
#### Role mapping examples
This section includes examples of JMESPath expressions used for role mapping.
@@ -85,7 +85,7 @@ The table below describes all SCIM configuration options. Like any other Grafana
- SCIM group sync (`group_sync_enabled = true`) and Team Sync cannot be enabled simultaneously
- You can use SCIM user sync (`user_sync_enabled = true`) alongside Team Sync
- For more details about migration and compatibility, see [SCIM vs Team Sync](./manage-users-teams/_index.md#scim-vs-team-sync)
- For more details about migration and compatibility, see [SCIM vs Team Sync](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-security/configure-scim-provisioning/manage-users-teams/#scim-vs-team-sync)
{{< /admonition >}}
### Example SCIM configuration
@@ -134,5 +134,5 @@ The following table compares SCIM with other synchronization methods to help you
## Next steps
- [Manage users and teams with SCIM provisioning](manage-users-teams/)
- [Configure SCIM with Azure AD](azuread/)
- [Configure SCIM with Okta](okta/)
- [Configure SCIM with Azure AD](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-security/configure-scim-provisioning/configure-scim-with-azuread/)
- [Configure SCIM with Okta](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-security/configure-scim-provisioning/configure-scim-with-okta/)
@@ -128,7 +128,7 @@ services:
restart: unless-stopped
environment:
- TERM=linux
- GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-polystat-panel
- GF_PLUGINS_PREINSTALL=grafana-clock-panel,grafana-polystat-panel
ports:
- '3000:3000'
volumes:
@@ -0,0 +1,30 @@
import { e2e } from '../utils';
import { flows } from './dashboard-edit-flows';
describe('Dashboard panels', () => {
beforeEach(() => {
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
});
it('can duplicate a panel', () => {
e2e.flows.scenes.importV2Dashboard({ title: 'Paste tab' });
e2e.flows.scenes.toggleEditMode();
const panelTitle = 'Unique';
flows.changePanelTitle('New panel', panelTitle);
e2e.components.Panels.Panel.title(panelTitle).should('have.length', 1);
e2e.components.Panels.Panel.menu(panelTitle).click({ force: true });
e2e.components.Panels.Panel.menuItems('More...').trigger('mouseover');
e2e.components.Panels.Panel.menuItems('Duplicate').click();
e2e.components.Panels.Panel.title(panelTitle).should('have.length', 2);
// Save, reload, and ensure duplicate has persisted
e2e.flows.scenes.saveDashboard();
cy.reload();
e2e.components.Panels.Panel.title(panelTitle).should('have.length', 2);
});
});
@@ -34,6 +34,10 @@ describe('Dashboard edit - variables', () => {
e2e.pages.Dashboard.SubMenu.submenuItemLabels(variable.label).should('be.visible').contains(variable.label);
const values = variable.value.split(',');
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts(values[0]).should('be.visible');
// check that variable deletion works
e2e.components.EditPaneHeader.deleteButton().click();
e2e.pages.Dashboard.SubMenu.submenuItemLabels(variable.label).should('not.exist');
});
it('can add a new constant variable', () => {
+2 -5
View File
@@ -6,7 +6,6 @@ require (
buf.build/gen/go/parca-dev/parca/connectrpc/go v1.17.0-20240902100956-02fd72488966.1 // @grafana/observability-traces-and-profiling
buf.build/gen/go/parca-dev/parca/protocolbuffers/go v1.34.2-20240902100956-02fd72488966.2 // @grafana/observability-traces-and-profiling
cloud.google.com/go/kms v1.20.5 // @grafana/grafana-backend-group
cloud.google.com/go/spanner v1.75.0 // @grafana/grafana-search-and-storage
cloud.google.com/go/storage v1.50.0 // @grafana/grafana-backend-group
connectrpc.com/connect v1.17.0 // @grafana/observability-traces-and-profiling
cuelang.org/go v0.11.1 // @grafana/grafana-as-code
@@ -75,7 +74,6 @@ require (
github.com/google/uuid v1.6.0 // @grafana/grafana-backend-group
github.com/google/wire v0.6.0 // @grafana/grafana-backend-group
github.com/googleapis/gax-go/v2 v2.14.1 // @grafana/grafana-backend-group
github.com/googleapis/go-sql-spanner v1.11.1 // @grafana/grafana-search-and-storage
github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // @grafana/grafana-app-platform-squad
github.com/grafana/alerting v0.0.0-20250521131632-6e476b0b04c3 // @grafana/alerting-backend
@@ -193,7 +191,7 @@ require (
gopkg.in/mail.v2 v2.3.1 // @grafana/grafana-backend-group
gopkg.in/yaml.v2 v2.4.0 // @grafana/alerting-backend
gopkg.in/yaml.v3 v3.0.1 // @grafana/alerting-backend
k8s.io/api v0.32.3 // indirect; @grafana/grafana-app-platform-squad
k8s.io/api v0.32.3 // @grafana/grafana-app-platform-squad
k8s.io/apimachinery v0.32.3 // @grafana/grafana-app-platform-squad
k8s.io/apiserver v0.32.3 // @grafana/grafana-app-platform-squad
k8s.io/client-go v0.32.3 // @grafana/grafana-app-platform-squad
@@ -202,6 +200,7 @@ require (
k8s.io/kube-aggregator v0.32.0 // @grafana/grafana-app-platform-squad
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // @grafana/grafana-app-platform-squad
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // @grafana/partner-datasources
sigs.k8s.io/randfill v1.0.0 // @grafana/grafana-app-platform-squad
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // @grafana-app-platform-squad
xorm.io/builder v0.3.6 // @grafana/grafana-backend-group
)
@@ -252,7 +251,6 @@ require (
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect
github.com/FZambia/eagle v0.2.0 // indirect
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 // indirect
@@ -569,7 +567,6 @@ require (
modernc.org/sqlite v1.37.0 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
-13
View File
@@ -187,7 +187,6 @@ cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvj
cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA=
cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs=
cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU=
cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE=
cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI=
cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU=
@@ -541,8 +540,6 @@ cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+
cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos=
cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk=
cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M=
cloud.google.com/go/spanner v1.75.0 h1:2zrltTJv/4P3pCgpYgde4Eb1vN8Cgy1fNy7pbTnOovg=
cloud.google.com/go/spanner v1.75.0/go.mod h1:TLFZBvPQmx3We7sGh12eTk9lLsRLczzZaiweqfMpR80=
cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM=
cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ=
cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0=
@@ -718,8 +715,6 @@ github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/FZambia/eagle v0.2.0 h1:1kQaZpJvbkvAXFRE/9K2ucBMuVqo+E29EMLYB74hIis=
github.com/FZambia/eagle v0.2.0/go.mod h1:LKMYBwGYhao5sJI0TppvQ4SvvldFj9gITxrl8NvGwG0=
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2 h1:DBjmt6/otSdULyJdVg2BlG0qGZO5tKL4VzOs0jpvw5Q=
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 h1:f2Qw/Ehhimh5uO1fayV0QIW7DShEQqhtUfhYc+cBPlw=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0/go.mod h1:2bIszWvQRlJVmJLiuLhukLImRjKPcYdzzsx6darK02A=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 h1:o90wcURuxekmXrtxmYWTyNla0+ZEHhud6DI1ZTxd1vI=
@@ -1019,7 +1014,6 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk=
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
@@ -1133,7 +1127,6 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34=
github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI=
github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q=
github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
@@ -1144,7 +1137,6 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo=
github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w=
github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss=
github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss=
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
@@ -1549,8 +1541,6 @@ github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEP
github.com/googleapis/gnostic v0.0.0-20170426233943-68f4ded48ba9/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.3.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/go-sql-spanner v1.11.1 h1:z3ThtKV5HFvaNv9UGc26+ggS+lS0dsCAkaFduKL7vws=
github.com/googleapis/go-sql-spanner v1.11.1/go.mod h1:fuA5q4yMS3SZiVfRr5bvksPNk7zUn/irbQW62H/ffZw=
github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gophercloud/gophercloud v0.3.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
@@ -2392,7 +2382,6 @@ github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
@@ -3398,11 +3387,9 @@ google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCD
google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww=
google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY=
google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY=
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
+1 -2
View File
@@ -442,6 +442,7 @@ cloud.google.com/go/shell v1.8.1/go.mod h1:jaU7OHeldDhTwgs3+clM0KYEDYnBAPevUI6wN
cloud.google.com/go/shell v1.8.3 h1:mjYgUsOtV3jl9xvDmcvlRRmA64deEPf52zOfuc68b/g=
cloud.google.com/go/shell v1.8.3/go.mod h1:OYcrgWF6JSp/uk76sNTtYFlMD0ho2+Cdzc7U3P/bF54=
cloud.google.com/go/spanner v1.70.0/go.mod h1:X5T0XftydYp0K1adeJQDJtdWpbrOeJ7wHecM4tK6FiE=
cloud.google.com/go/spanner v1.73.0 h1:0bab8QDn6MNj9lNK6XyGAVFhMlhMU2waePPa6GZNoi8=
cloud.google.com/go/spanner v1.73.0/go.mod h1:mw98ua5ggQXVWwp83yjwggqEmW9t8rjs9Po1ohcUGW4=
cloud.google.com/go/speech v1.25.1 h1:iGZJS3wrdkje/Vqiacx1+r+zVwUZoXVMdklYIVsvfNw=
cloud.google.com/go/speech v1.25.1/go.mod h1:WgQghvghkZ1htG6BhYn98mP7Tg0mti8dBFDLMVXH/vM=
@@ -1047,8 +1048,6 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg78
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c=
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
+5 -5
View File
@@ -259,7 +259,7 @@
"yargs": "^17.5.1"
},
"dependencies": {
"@bsull/augurs": "^0.9.0",
"@bsull/augurs": "^0.10.0",
"@emotion/css": "11.13.5",
"@emotion/react": "11.14.0",
"@fingerprintjs/fingerprintjs": "^3.4.2",
@@ -267,7 +267,7 @@
"@formatjs/intl-durationformat": "^0.7.0",
"@glideapps/glide-data-grid": "^6.0.0",
"@grafana/alerting": "workspace:*",
"@grafana/aws-sdk": "0.6.0",
"@grafana/aws-sdk": "0.7.0",
"@grafana/azure-sdk": "0.0.7",
"@grafana/data": "workspace:*",
"@grafana/e2e-selectors": "workspace:*",
@@ -278,7 +278,7 @@
"@grafana/google-sdk": "0.1.2",
"@grafana/i18n": "workspace:*",
"@grafana/lezer-logql": "0.2.7",
"@grafana/llm": "0.13.2",
"@grafana/llm": "0.19.2",
"@grafana/monaco-logql": "^0.0.8",
"@grafana/o11y-ds-frontend": "workspace:*",
"@grafana/plugin-ui": "0.10.6",
@@ -302,7 +302,7 @@
"@msagl/parser": "^1.1.19",
"@opentelemetry/api": "1.9.0",
"@opentelemetry/exporter-collector": "0.25.0",
"@opentelemetry/semantic-conventions": "1.28.0",
"@opentelemetry/semantic-conventions": "1.34.0",
"@popperjs/core": "2.11.8",
"@react-aria/dialog": "3.5.25",
"@react-aria/focus": "3.20.3",
@@ -390,7 +390,7 @@
"react-router": "5.3.4",
"react-router-dom": "5.3.4",
"react-router-dom-v5-compat": "^6.26.1",
"react-select": "5.10.0",
"react-select": "5.10.1",
"react-split-pane": "0.1.92",
"react-table": "7.8.0",
"react-transition-group": "4.4.5",
@@ -1,6 +1,6 @@
import { isNumber, set, unset, get, cloneDeep } from 'lodash';
import { useMemo, useRef } from 'react';
import usePrevious from 'react-use/lib/usePrevious';
import { usePrevious } from 'react-use';
import { ThresholdsMode, VariableFormatID } from '@grafana/schema';
+4
View File
@@ -814,7 +814,11 @@ export {
export {
type Action,
type ActionModel,
type ActionVariable,
type ActionVariableInput,
ActionType,
HttpRequestMethod,
ActionVariableType,
defaultActionConfig,
contentTypeOptions,
httpMethodOptions,
@@ -695,9 +695,9 @@ export function getNameFromOptions(options: CalculateFieldTransformerOptions) {
const { binary } = options;
const alias = `${binary?.left?.matcher?.options ?? binary?.left?.fixed ?? ''} ${binary?.operator ?? ''} ${binary?.right?.matcher?.options ?? binary?.right?.fixed ?? ''}`;
//Remove $ signs as they will be interpolated and cause issues. Variables can still be used
//in alias but shouldn't in the autogenerated name
return alias.replace(/\$/g, '');
// binary calculations with variables will be interpolated on the visualization but we don't want to do that here, so just give a blank placeholder
const variableFound = /\$/g.test(alias);
return variableFound ? '' : alias;
}
case CalculateFieldMode.ReduceRow:
{
@@ -86,9 +86,17 @@ describe('Transpose transformer', () => {
{ name: 'env', type: FieldType.string, values: ['dev', 'prod', 'staging', 'release', 'beta'] },
{ name: 'january', type: FieldType.number, values: [11, 12, 13, 14, 15] },
{ name: 'february', type: FieldType.number, values: [6, 7, 8, 9, 10] },
{ name: 'type', type: FieldType.string, values: ['metricA', 'metricB', 'metricC', 'metricD', 'metricE'] },
{
name: 'metricName',
type: FieldType.string,
values: ['metricA', 'metricB', 'metricC', 'metricD', 'metricE'],
config: {
displayName: 'type',
},
},
],
});
await expect(transformDataFrame([cfgB], [seriesB])).toEmitValuesWith((received) => {
const result = received[0];
expect(result[0].fields).toEqual([
@@ -1,5 +1,6 @@
import { map } from 'rxjs/operators';
import { cacheFieldDisplayNames } from '../../field/fieldState';
import { DataFrame, Field, FieldType } from '../../types/dataFrame';
import { DataTransformerInfo } from '../../types/transformations';
@@ -28,6 +29,8 @@ export const transposeTransformer: DataTransformerInfo<TransposeTransformerOptio
};
function transposeDataFrame(options: TransposeTransformerOptions, data: DataFrame[]): DataFrame[] {
cacheFieldDisplayNames(data);
return data.map((frame) => {
const firstField = frame.fields[0];
const firstName = !options.firstFieldName ? 'Field' : options.firstFieldName;
@@ -38,8 +41,12 @@ function transposeDataFrame(options: TransposeTransformerOptions, data: DataFram
? [firstName, ...fieldValuesAsStrings(firstField, firstField.values)]
: [firstName, ...firstField.values.map((_, i) => restName)];
const rows = useFirstFieldAsHeaders
? frame.fields.map((field) => field.name).slice(1)
: frame.fields.map((field) => field.name);
? frame.fields
.map((field) => {
return field.state?.displayName ?? field.name;
})
.slice(1)
: frame.fields.map((field) => field.state?.displayName ?? field.name);
const fieldType = determineFieldType(
useFirstFieldAsHeaders
? frame.fields.map((field) => field.type).slice(1)
+17 -3
View File
@@ -1,4 +1,4 @@
import { CSSProperties } from 'react';
import { CSSProperties, ReactNode } from 'react';
import { SelectableValue } from './select';
@@ -18,6 +18,7 @@ export interface Action {
[ActionType.Fetch]: FetchOptions;
confirmation?: string;
oneClick?: boolean;
variables?: ActionVariable[];
style?: ActionButtonCssProperties;
}
@@ -26,10 +27,21 @@ export interface Action {
*/
export interface ActionModel<T = any> {
title: string;
onClick: (event: any, origin?: any) => void;
confirmation?: string;
onClick: (event: any, origin?: any, actionVars?: ActionVariableInput) => void;
confirmation: (actionVars?: ActionVariableInput) => ReactNode;
oneClick?: boolean;
style: ActionButtonCssProperties;
variables?: ActionVariable[];
}
export type ActionVariable = {
key: string;
name: string;
type: ActionVariableType;
};
export enum ActionVariableType {
String = 'string',
}
interface FetchOptions {
@@ -70,3 +82,5 @@ export const defaultActionConfig: Action = {
headers: [['Content-Type', 'application/json']],
},
};
export type ActionVariableInput = { [key: string]: string };
@@ -679,10 +679,6 @@ export interface FeatureToggles {
*/
exploreLogsLimitedTimeRange?: boolean;
/**
* Used in Home for users who want to return to the onboarding flow or quickly find popular config pages
*/
homeSetupGuide?: boolean;
/**
* Enables the gRPC client to authenticate with the App Platform by using ID & access tokens
*/
appPlatformGrpcClientAuth?: boolean;
@@ -845,6 +841,10 @@ export interface FeatureToggles {
*/
teamHttpHeadersMimir?: boolean;
/**
* Enables LBAC for datasources for Tempo to apply LBAC filtering of traces to the client requests for users in teams
*/
teamHttpHeadersTempo?: boolean;
/**
* Test feature toggle to see how cohorts could be set up AB testing
* @default false
*/
@@ -1028,4 +1028,9 @@ export interface FeatureToggles {
* Use proxy-based read-only objects for plugin extensions instead of deep cloning
*/
extensionsReadOnlyProxy?: boolean;
/**
* Enables restore deleted dashboards feature
* @default false
*/
restoreDashboards?: boolean;
}
@@ -78,6 +78,7 @@ export interface GroupByVariableModel extends VariableWithOptions {
datasource: DataSourceRef | null;
multi: true;
allowCustomValue?: boolean;
defaultValue?: VariableOption;
}
export interface VariableOption {
+5 -5
View File
@@ -1,4 +1,4 @@
import i18n, { InitOptions, ReactOptions, TFunction } from 'i18next';
import i18n, { InitOptions, ReactOptions, TFunction as I18NextTFunction } from 'i18next';
import LanguageDetector, { DetectorOptions } from 'i18next-browser-languagedetector';
// eslint-disable-next-line no-restricted-imports
import { initReactI18next, setDefaults, setI18n, Trans as I18NextTrans, getI18n } from 'react-i18next';
@@ -6,9 +6,9 @@ import { initReactI18next, setDefaults, setI18n, Trans as I18NextTrans, getI18n
import { DEFAULT_LANGUAGE, PSEUDO_LOCALE } from './constants';
import { initRegionalFormat } from './dates';
import { LANGUAGES } from './languages';
import { TransProps, TransType } from './types';
import { TFunction, TransProps, TransType } from './types';
let tFunc: TFunction<string[], undefined> | undefined;
let tFunc: I18NextTFunction<string[], undefined> | undefined;
let transComponent: TransType;
export async function initPluginTranslations(id: string) {
@@ -140,7 +140,7 @@ export function addResourceBundle(language: string, namespace: string, resource:
getI18nInstance().addResourceBundle(language, namespace, resource, undefined, true);
}
export function t(id: string, defaultMessage: string, values?: Record<string, unknown>) {
export const t: TFunction = (id: string, defaultMessage: string, values?: Record<string, unknown>) => {
if (!tFunc) {
if (process.env.NODE_ENV !== 'test') {
console.warn(
@@ -156,7 +156,7 @@ export function t(id: string, defaultMessage: string, values?: Record<string, un
}
return tFunc(id, defaultMessage, values);
}
};
export function useTranslate() {
return { t };
+1 -1
View File
@@ -23,5 +23,5 @@ export {
DEFAULT_LANGUAGE,
} from './constants';
export { initPluginTranslations, Trans, useTranslate } from './i18n';
export type { TransProps } from './types';
export type { TFunction, TransProps } from './types';
export { formatDate, formatDuration, formatDateRange } from './dates';
+1 -6
View File
@@ -9,12 +9,6 @@
* preventing the code from being importable by plugins or other npm packages making it truly "internal".
*
*/
import { t } from '../i18n';
type TFunction = typeof t;
export type { TFunction };
export { t };
export {
addResourceBundle,
@@ -23,4 +17,5 @@ export {
getLanguage,
getResolvedLanguage,
initializeI18n,
t,
} from '../i18n';
+6 -1
View File
@@ -60,4 +60,9 @@ declare function Trans(props: TransProps): React.ReactElement;
*/
type TransType = typeof Trans;
export type { UseTranslateHook, TransProps, TransType };
/**
* Type for the translation function
*/
type TFunction = (id: string, defaultMessage: string, values?: Record<string, unknown>) => string;
export type { UseTranslateHook, TransProps, TransType, TFunction };
@@ -24,7 +24,7 @@
"@grafana/runtime": "12.1.0-pre",
"@grafana/schema": "12.1.0-pre",
"@grafana/ui": "12.1.0-pre",
"react-select": "5.10.0",
"react-select": "5.10.1",
"react-use": "17.6.0",
"rxjs": "7.8.2",
"tslib": "2.8.1"
+1 -1
View File
@@ -51,7 +51,7 @@
"@lezer/common": "1.2.3",
"@lezer/highlight": "1.2.1",
"@lezer/lr": "1.4.2",
"@prometheus-io/lezer-promql": "0.301.0",
"@prometheus-io/lezer-promql": "0.304.0",
"@reduxjs/toolkit": "2.5.1",
"@types/debounce-promise": "3.1.9",
"@types/lodash": "4.17.15",
@@ -1,3 +1,17 @@
/**
* Ensures a string is compatible with Prometheus' UTF-8 handling rules.
*
* Prometheus has specific rules for handling UTF-8 strings in metric names and label values:
* - Legacy names (matching pattern [a-zA-Z_:][a-zA-Z0-9_:]*) are used as-is
* - Non-legacy names containing UTF-8 characters must be wrapped in double quotes
*
* @param value - The string to make UTF-8 compatible
* @returns The original string if it's empty or a valid legacy name, otherwise the string wrapped in double quotes
*
* @example
* utf8Support('metric_name') // returns 'metric_name'
* utf8Support('metric-📈') // returns '"metric-📈"'
*/
export const utf8Support = (value: string) => {
if (value === '') {
return value;
@@ -9,6 +23,22 @@ export const utf8Support = (value: string) => {
return `"${value}"`;
};
/**
* Escapes a string to make it compatible with Prometheus UTF-8 support.
*
* This function converts non-legacy name characters to an escaped format:
* - Underscores are doubled as '__'
* - Valid legacy runes are preserved as-is
* - Invalid code points are replaced with '_FFFD_'
* - Other characters are converted to '_HEX_' format where HEX is the hexadecimal code point
*
* @param value - The string to escape
* @returns An escaped string prefixed with 'U__' that is compatible with Prometheus
*
* @example
* escapeForUtf8Support("my lovely_http.status:sum") // returns U__my_20_lovely__http_2e_status:sum
* escapeForUtf8Support("label with 😱") // returns U__label_20_with_20__1f631_
*/
export const escapeForUtf8Support = (value: string) => {
const isLegacyLabel = isValidLegacyName(value);
if (isLegacyLabel) {
@@ -42,6 +72,16 @@ export const escapeForUtf8Support = (value: string) => {
return escaped;
};
/**
* Checks if a string is a valid legacy (the standard) Prometheus metric or label name.
*
* Valid legacy (the standard) names match the pattern [a-zA-Z_:][a-zA-Z0-9_:]* which means:
* - First character must be a letter, underscore, or colon
* - Remaining characters can only be letters, numbers, underscores, or colons
*
* @param name - The string to check
* @returns true if the string is a valid legacy (the standard) name, false otherwise
*/
export const isValidLegacyName = (name: string): boolean => {
if (name.length === 0) {
return false;
@@ -57,9 +97,17 @@ export const isValidLegacyName = (name: string): boolean => {
return true;
};
// const labelNamePriorToUtf8Support = /^[a-zA-Z_:][a-zA-Z0-9_:]*$/;
// instead of regex we use rune check (converted from prometheus code)
// https://github.com/prometheus/common/blob/main/model/metric.go#L426-L428
/**
* Checks if a character is valid for a legacy (the standard) Prometheus metric or label name.
*
* This is an implementation of the Prometheus model rune validation logic, which
* determines if a character is allowed in a legacy metric or label name.
* https://github.com/prometheus/common/blob/v0.64.0/model/metric.go#L430-L432
*
* @param char - The character to check
* @param index - The position of the character in the string
* @returns true if the character is valid at the given position, false otherwise
*/
const isValidLegacyRune = (char: string, index: number): boolean => {
const codePoint = char.codePointAt(0);
if (codePoint === undefined) {
@@ -75,11 +123,26 @@ const isValidLegacyRune = (char: string, index: number): boolean => {
);
};
/**
* Validates if a Unicode code point is valid for UTF-8 encoding.
*
* @param codePoint - The Unicode code point to validate
* @returns true if the code point is valid (between 0 and 0x10FFFF), false otherwise
*/
const isValidCodePoint = (codePoint: number): boolean => {
// Validate the code point for UTF-8 compliance if needed.
return codePoint >= 0 && codePoint <= 0x10ffff;
};
/**
* Wraps each key in a Prometheus filter string with UTF-8 support.
*
* This function processes a filter string (e.g. 'metric="value",name=~"pattern"')
* and applies UTF-8 support to each key while preserving the operators and values.
* It handles quoted values and comma separators correctly.
*
* @param filterStr - The filter string to process
* @returns A new filter string with UTF-8 support applied to the keys
*/
export const wrapUtf8Filters = (filterStr: string): string => {
const resultArray: string[] = [];
const operatorRegex = /(=~|!=|!~|=)/; // NOTE: the order of the operators is important here
@@ -1290,6 +1290,7 @@ export const defaultGroupByVariableKind = (): GroupByVariableKind => ({
export interface GroupByVariableSpec {
name: string;
datasource?: DataSourceRef;
defaultValue?: VariableOption;
current: VariableOption;
options: VariableOption[];
multi: boolean;
+1 -1
View File
@@ -25,7 +25,7 @@
"lodash": "4.17.21",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-select": "5.10.0",
"react-select": "5.10.1",
"react-use": "17.6.0",
"react-virtualized-auto-sizer": "1.0.25",
"rxjs": "7.8.2",
@@ -1,7 +1,6 @@
import { css } from '@emotion/css';
import { useState, useMemo, useEffect } from 'react';
import { useAsyncFn } from 'react-use';
import useDebounce from 'react-use/lib/useDebounce';
import { useAsyncFn, useDebounce } from 'react-use';
import { formattedValueToString, getValueFormat, TimeRange } from '@grafana/data';
import { Icon, Spinner, useTheme2 } from '@grafana/ui';
@@ -1,4 +1,4 @@
import useAsync from 'react-use/lib/useAsync';
import { useAsync } from 'react-use';
import { SelectableValue, TypedVariableModel } from '@grafana/data';
import { getTemplateSrv } from '@grafana/runtime';
+2 -2
View File
@@ -73,7 +73,7 @@
"@grafana/schema": "12.1.0-pre",
"@hello-pangea/dnd": "17.0.0",
"@leeoniya/ufuzzy": "1.0.18",
"@monaco-editor/react": "4.6.0",
"@monaco-editor/react": "4.7.0",
"@popperjs/core": "2.11.8",
"@react-aria/dialog": "3.5.25",
"@react-aria/focus": "3.20.3",
@@ -117,7 +117,7 @@
"react-loading-skeleton": "3.5.0",
"react-router-dom": "5.3.4",
"react-router-dom-v5-compat": "^6.26.1",
"react-select": "5.10.0",
"react-select": "5.10.1",
"react-table": "7.8.0",
"react-transition-group": "4.4.5",
"react-use": "17.6.0",
@@ -1,12 +1,14 @@
import { useState } from 'react';
import { ActionModel, Field } from '@grafana/data';
import { ActionModel, Field, ActionVariableInput } from '@grafana/data';
import { useTheme2 } from '../../themes';
import { t } from '../../utils/i18n';
import { Button, ButtonProps } from '../Button';
import { ConfirmModal } from '../ConfirmModal/ConfirmModal';
import { VariablesInputModal } from './VariablesInputModal';
type ActionButtonProps = ButtonProps & {
action: ActionModel<Field>;
};
@@ -21,27 +23,52 @@ export function ActionButton({ action, ...buttonProps }: ActionButtonProps) {
const [showConfirm, setShowConfirm] = useState(false);
// Action variables
const [showVarsModal, setShowVarsModal] = useState(false);
const [actionVars, setActionVars] = useState<ActionVariableInput>({});
const actionHasVariables = action.variables && action.variables.length > 0;
const onClick = () => {
if (actionHasVariables) {
setShowVarsModal(true);
} else {
setShowConfirm(true);
}
};
return (
<>
<Button
variant="primary"
size="sm"
onClick={() => setShowConfirm(true)}
onClick={onClick}
{...buttonProps}
style={{ width: 'fit-content', backgroundColor, color: textColor }}
>
{action.title}
</Button>
{actionHasVariables && showVarsModal && (
<VariablesInputModal
onDismiss={() => setShowVarsModal(false)}
action={action}
onShowConfirm={() => setShowConfirm(true)}
variables={actionVars}
setVariables={setActionVars}
/>
)}
{showConfirm && (
<ConfirmModal
isOpen={true}
title={t('grafana-ui.action-editor.button.confirm-action', 'Confirm action')}
body={action.confirmation}
body={action.confirmation(actionVars)}
confirmText={t('grafana-ui.action-editor.button.confirm', 'Confirm')}
confirmButtonVariant="primary"
onConfirm={() => {
setShowConfirm(false);
action.onClick(new MouseEvent('click'));
action.onClick(new MouseEvent('click'), null, actionVars);
}}
onDismiss={() => {
setShowConfirm(false);
@@ -0,0 +1,72 @@
import { css } from '@emotion/css';
import { ActionModel, ActionVariableInput } from '@grafana/data';
import { useStyles2 } from '../../themes';
import { t } from '../../utils/i18n';
import { Button } from '../Button/Button';
import { Field } from '../Forms/Field';
import { FieldSet } from '../Forms/FieldSet';
import { Input } from '../Input/Input';
import { Modal } from '../Modal/Modal';
interface Props {
action: ActionModel;
onDismiss: () => void;
onShowConfirm: () => void;
variables: ActionVariableInput;
setVariables: (vars: ActionVariableInput) => void;
}
/**
* @internal
*/
export function VariablesInputModal({ action, onDismiss, onShowConfirm, variables, setVariables }: Props) {
const styles = useStyles2(getStyles);
const onModalContinue = () => {
onDismiss();
onShowConfirm();
};
return (
<Modal
isOpen={true}
title={t('grafana-ui.action-editor.button.action-variables-title', 'Action variables')}
onDismiss={onDismiss}
className={styles.variablesModal}
>
<FieldSet>
{action.variables!.map((variable) => (
<Field key={variable.name} label={variable.name}>
<Input
type="text"
value={variables[variable.key] ?? ''}
onChange={(e) => {
setVariables({ ...variables, [variable.key]: e.currentTarget.value });
}}
placeholder={t('grafana-ui.action-editor.button.variable-value-placeholder', 'Value')}
width={20}
/>
</Field>
))}
</FieldSet>
<Modal.ButtonRow>
<Button variant="secondary" onClick={onDismiss}>
{t('grafana-ui.action-editor.close', 'Close')}
</Button>
<Button variant="primary" onClick={onModalContinue}>
{t('grafana-ui.action-editor.continue', 'Continue')}
</Button>
</Modal.ButtonRow>
</Modal>
);
}
const getStyles = () => {
return {
variablesModal: css({
zIndex: 10000,
}),
};
};
@@ -100,7 +100,7 @@ export const Examples: StoryFn<typeof Button> = () => {
<Button variant="secondary" icon="angle-down" />
</ButtonGroup>
</Stack>
<Card>
<Card noMargin>
<Card.Heading>Button inside card</Card.Heading>
<Card.Actions>
{allButtonVariants.map((variant) => (
@@ -15,7 +15,7 @@ const meta: Meta<typeof Card> = {
tags: ['autodocs', 'nosort'],
parameters: {
controls: {
exclude: ['onClick', 'href', 'heading', 'description', 'className'],
exclude: ['onClick', 'href', 'heading', 'description', 'className', 'noMargin'],
},
},
};
@@ -25,7 +25,7 @@ const meta: Meta<typeof Card> = {
*/
export const Basic: StoryFn<typeof Card> = (args) => {
return (
<Card {...args}>
<Card noMargin {...args}>
<Card.Heading>Filter by name</Card.Heading>
<Card.Description>
Filter data by query. This is useful if you are sharing the results from a different panel that has many queries
@@ -41,7 +41,7 @@ export const Basic: StoryFn<typeof Card> = (args) => {
*/
export const MultipleMetadataElements: StoryFn<typeof Card> = (args) => {
return (
<Card>
<Card noMargin>
<Card.Heading>Test dashboard</Card.Heading>
<Card.Meta>{['Folder: Test', 'Views: 100']}</Card.Meta>
</Card>
@@ -54,7 +54,7 @@ export const MultipleMetadataElements: StoryFn<typeof Card> = (args) => {
*/
export const ComplexMetadataElements: StoryFn<typeof Card> = (args) => {
return (
<Card>
<Card noMargin>
<Card.Heading>Test dashboard</Card.Heading>
<Card.Meta>
<>Grafana</>
@@ -71,7 +71,7 @@ export const ComplexMetadataElements: StoryFn<typeof Card> = (args) => {
*/
export const MultipleMetadataWithCustomSeparator: StoryFn<typeof Card> = (args) => {
return (
<Card>
<Card noMargin>
<Card.Heading>Test dashboard</Card.Heading>
<Card.Meta separator={'-'}>
Grafana
@@ -90,7 +90,7 @@ export const MultipleMetadataWithCustomSeparator: StoryFn<typeof Card> = (args)
*/
export const Tags: StoryFn<typeof Card> = (args) => {
return (
<Card>
<Card noMargin>
<Card.Heading>Test dashboard</Card.Heading>
<Card.Description>Card with a list of tags</Card.Description>
<Card.Tags>
@@ -105,7 +105,7 @@ export const Tags: StoryFn<typeof Card> = (args) => {
*/
export const AsALink: StoryFn<typeof Card> = (args) => {
return (
<Card href="https://grafana.com">
<Card noMargin href="https://grafana.com">
<Card.Heading>Redirect to Grafana</Card.Heading>
<Card.Description>Clicking this card will redirect to grafana website</Card.Description>
</Card>
@@ -119,7 +119,7 @@ export const AsALink: StoryFn<typeof Card> = (args) => {
*/
export const AsAButton: StoryFn<typeof Card> = (args) => {
return (
<Card onClick={() => alert('Hello, Grafana!')}>
<Card noMargin onClick={() => alert('Hello, Grafana!')}>
<Card.Heading>Hello, Grafana</Card.Heading>
<Card.Description>Clicking this card will create an alert</Card.Description>
</Card>
@@ -131,27 +131,27 @@ export const AsAButton: StoryFn<typeof Card> = (args) => {
*/
export const InsideAListItem: StoryFn<typeof Card> = (args) => {
return (
<ul style={{ padding: '20px', listStyle: 'none', display: 'grid' }}>
<ul style={{ padding: '20px', listStyle: 'none', display: 'grid', gap: '8px' }}>
<li>
<Card>
<Card noMargin>
<Card.Heading>List card item</Card.Heading>
<Card.Description>Card that is rendered inside li element.</Card.Description>
</Card>
</li>
<li>
<Card>
<Card noMargin>
<Card.Heading>List card item</Card.Heading>
<Card.Description>Card that is rendered inside li element.</Card.Description>
</Card>
</li>
<li>
<Card>
<Card noMargin>
<Card.Heading>List card item</Card.Heading>
<Card.Description>Card that is rendered inside li element.</Card.Description>
</Card>
</li>
<li>
<Card>
<Card noMargin>
<Card.Heading>List card item</Card.Heading>
<Card.Description>Card that is rendered inside li element.</Card.Description>
</Card>
@@ -161,11 +161,11 @@ export const InsideAListItem: StoryFn<typeof Card> = (args) => {
};
/**
* Cards can also be rendered with media content such icons or images. Such elements need to be wrapped in `Card.Figure` component.
* Cards can also be rendered with media content such as icons or images. Such elements need to be wrapped in `Card.Figure` component.
*/
export const WithMediaElements: StoryFn<typeof Card> = (args) => {
return (
<Card>
<Card noMargin>
<Card.Heading>1-ops-tools1-fallback</Card.Heading>
<Card.Figure>
<img src={logo} alt="Grafana Logo" width="40" height="40" />
@@ -187,7 +187,7 @@ export const WithMediaElements: StoryFn<typeof Card> = (args) => {
*/
export const ActionCards: StoryFn<typeof Card> = (args) => {
return (
<Card {...args}>
<Card noMargin {...args}>
<Card.Heading>1-ops-tools1-fallback</Card.Heading>
<Card.Meta>
Prometheus
@@ -220,7 +220,7 @@ export const ActionCards: StoryFn<typeof Card> = (args) => {
*/
export const DisabledState: StoryFn<typeof Card> = (args) => {
return (
<Card disabled>
<Card noMargin disabled>
<Card.Heading>1-ops-tools1-fallback</Card.Heading>
<Card.Meta>
Grafana
@@ -249,7 +249,7 @@ export const DisabledState: StoryFn<typeof Card> = (args) => {
export const Selectable: StoryFn<typeof Card> = () => {
return (
<Card isSelected disabled>
<Card noMargin isSelected disabled>
<Card.Heading>Option #1</Card.Heading>
<Card.Description>This is a really great option, you will not regret it.</Card.Description>
<Card.Figure>
@@ -261,7 +261,7 @@ export const Selectable: StoryFn<typeof Card> = () => {
export const Full: StoryFn<typeof Card> = (args) => {
return (
<Card {...args}>
<Card noMargin {...args}>
<Card.Heading>Card title</Card.Heading>
<Card.Description>
Description, body text. Greetings! Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
@@ -11,7 +11,7 @@ describe('Card', () => {
const user = userEvent.setup();
const callback = jest.fn();
render(
<Card onClick={callback}>
<Card noMargin onClick={callback}>
<Card.Heading>Test Heading</Card.Heading>
</Card>
);
@@ -22,7 +22,7 @@ describe('Card', () => {
describe('Card Actions', () => {
it('Children should be disabled or enabled according to Card disabled prop', () => {
const { rerender } = render(
<Card>
<Card noMargin>
<Card.Heading>Test Heading</Card.Heading>
<Card.Actions>
<Button>Click Me</Button>
@@ -37,7 +37,7 @@ describe('Card', () => {
expect(screen.getByRole('button', { name: 'Delete' })).toBeEnabled();
rerender(
<Card disabled>
<Card noMargin disabled>
<Card.Heading>Test Heading</Card.Heading>
<Card.Actions>
<Button>Click Me</Button>
@@ -54,7 +54,7 @@ describe('Card', () => {
it('Children should be independently enabled or disabled if explicitly set', () => {
const { rerender } = render(
<Card>
<Card noMargin>
<Card.Heading>Test Heading</Card.Heading>
<Card.Actions>
<Button disabled>Click Me</Button>
@@ -69,7 +69,7 @@ describe('Card', () => {
expect(screen.getByRole('button', { name: 'Delete' })).toBeDisabled();
rerender(
<Card disabled>
<Card noMargin disabled>
<Card.Heading>Test Heading</Card.Heading>
<Card.Actions>
<Button disabled={false}>Click Me</Button>
@@ -87,7 +87,7 @@ describe('Card', () => {
it('Children should be conditional', () => {
const shouldNotRender = false;
render(
<Card>
<Card noMargin>
<Card.Heading>Test Heading</Card.Heading>
<Card.Actions>
<Button>Click Me</Button>
@@ -105,7 +105,7 @@ describe('Card', () => {
it('Should allow selectable cards', () => {
const { rerender } = render(
<Card isSelected={true}>
<Card noMargin isSelected={true}>
<Card.Heading>My Option</Card.Heading>
</Card>
);
@@ -114,7 +114,7 @@ describe('Card', () => {
expect(screen.getByRole('radio')).toBeChecked();
rerender(
<Card isSelected={false}>
<Card noMargin isSelected={false}>
<Card.Heading>My Option</Card.Heading>
</Card>
);
@@ -123,7 +123,7 @@ describe('Card', () => {
expect(screen.getByRole('radio')).not.toBeChecked();
rerender(
<Card>
<Card noMargin>
<Card.Heading>My Option</Card.Heading>
</Card>
);
@@ -11,6 +11,7 @@ import { Input, Props as InputProps } from '../Input/Input';
import { Portal } from '../Portal/Portal';
import { ComboboxList } from './ComboboxList';
import { SuffixIcon } from './SuffixIcon';
import { itemToString } from './filter';
import { getComboboxStyles, MENU_OPTION_HEIGHT, MENU_OPTION_HEIGHT_DESCRIPTION } from './getComboboxStyles';
import { ComboboxOption } from './types';
@@ -129,7 +130,6 @@ export const Combobox = <T extends string | number>(props: ComboboxProps<T>) =>
autoFocus,
onBlur,
disabled,
loading,
invalid,
} = props;
@@ -330,12 +330,7 @@ export const Combobox = <T extends string | number>(props: ComboboxProps<T>) =>
const InputComponent = isAutoSize ? AutoSizeInput : Input;
const placeholder = (isOpen ? itemToString(selectedItem) : null) || placeholderProp;
const suffixIcon = asyncLoading
? 'spinner'
: // If it's loading, show loading icon. Otherwise, icon indicating menu state
isOpen
? 'search'
: 'angle-down';
const loading = props.loading || asyncLoading;
const inputSuffix = (
<>
@@ -357,7 +352,7 @@ export const Combobox = <T extends string | number>(props: ComboboxProps<T>) =>
/>
)}
<Icon name={suffixIcon} />
<SuffixIcon isLoading={loading || false} isOpen={isOpen} />
</>
);
@@ -369,7 +364,6 @@ export const Combobox = <T extends string | number>(props: ComboboxProps<T>) =>
autoFocus={autoFocus}
onBlur={onBlur}
disabled={disabled}
loading={loading}
invalid={invalid}
className={styles.input}
suffix={inputSuffix}
@@ -3,7 +3,7 @@ import { autoUpdate, flip, offset, shift, useFloating } from '@floating-ui/react
import Prism, { Grammar, LanguageMap } from 'prismjs';
import { memo, useEffect, useRef, useState } from 'react';
import * as React from 'react';
import usePrevious from 'react-use/lib/usePrevious';
import { usePrevious } from 'react-use';
import { Value } from 'slate';
import Plain from 'slate-plain-serializer';
import { Editor } from 'slate-react';
@@ -2,7 +2,7 @@ import { css, cx } from '@emotion/css';
import { groupBy, capitalize } from 'lodash';
import { useRef, useMemo } from 'react';
import * as React from 'react';
import useClickAway from 'react-use/lib/useClickAway';
import { useClickAway } from 'react-use';
import { VariableSuggestion, GrafanaTheme2 } from '@grafana/data';
@@ -1,6 +1,6 @@
import { css, cx } from '@emotion/css';
import { forwardRef, HTMLProps, ReactNode, useContext } from 'react';
import useMeasure from 'react-use/lib/useMeasure';
import { useMeasure } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data';
@@ -2,7 +2,7 @@ import { action } from '@storybook/addon-actions';
import { Meta, StoryFn } from '@storybook/react';
import { useState } from 'react';
import * as React from 'react';
import { AsyncState } from 'react-use/lib/useAsync';
import { type AsyncState } from 'react-use/lib/useAsync';
import { SelectableValue } from '@grafana/data';
@@ -3,7 +3,7 @@ import { isObject } from 'lodash';
import { HTMLProps } from 'react';
import * as React from 'react';
import { useAsyncFn } from 'react-use';
import { AsyncState } from 'react-use/lib/useAsync';
import { type AsyncState } from 'react-use/lib/useAsync';
import { SelectableValue } from '@grafana/data';
@@ -1,7 +1,7 @@
import { cx, css } from '@emotion/css';
import { HTMLProps, useRef, useState } from 'react';
import * as React from 'react';
import useClickAway from 'react-use/lib/useClickAway';
import { useClickAway } from 'react-use';
import { useStyles2 } from '../../themes';
import { measureText } from '../../utils/measureText';
@@ -19,6 +19,7 @@ describe('DataLinksActionsTooltip', () => {
const mockAction: ActionModel = {
title: 'Action1',
onClick: jest.fn(),
confirmation: jest.fn(),
style: { backgroundColor: '#ff0000' },
};
@@ -226,7 +226,7 @@ export const ThemeDemo = () => {
Disabled
</Button>
</Stack>
<Card>
<Card noMargin>
<Card.Heading>Button inside card</Card.Heading>
<Card.Actions>
{allButtonVariants.map((variant) => (
@@ -259,8 +259,8 @@ export function VizHuesDemo({ theme, color }: VizHuesDemoProps) {
return (
<tr>
<td>{color.name}</td>
{color.shades.map((shade) => (
<td>
{color.shades.map((shade, index) => (
<td key={index}>
<div
className={css({
background: shade.color,
@@ -228,8 +228,11 @@ export const TooltipPlugin2 = ({
// in some ways this is similar to ClickOutsideWrapper.tsx
const downEventOutside = (e: Event) => {
// this tooltip is Portaled, but actions inside it create forms in Modals
const isModalOrPortaled = '[role="dialog"], #grafana-portal-container';
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
if (!domRef.current!.contains(e.target as Node)) {
if ((e.target as HTMLElement).closest(isModalOrPortaled) == null) {
dismiss();
}
};
+15 -12
View File
@@ -63,18 +63,21 @@ done
export HOME="$GF_PATHS_HOME"
if [ ! -z "${GF_INSTALL_PLUGINS}" ]; then
OLDIFS=$IFS
IFS=','
for plugin in ${GF_INSTALL_PLUGINS}; do
IFS=$OLDIFS
if [[ $plugin =~ .*\;.* ]]; then
pluginUrl=$(echo "$plugin" | cut -d';' -f 1)
pluginInstallFolder=$(echo "$plugin" | cut -d';' -f 2)
grafana cli --pluginUrl ${pluginUrl} --pluginsDir "${GF_PATHS_PLUGINS}" plugins install "${pluginInstallFolder}"
else
grafana cli --pluginsDir "${GF_PATHS_PLUGINS}" plugins install ${plugin}
fi
done
>&2 echo "\033[0;33mWARN\033[0m: GF_INSTALL_PLUGINS is deprecated. Use GF_PLUGINS_PREINSTALL or GF_PLUGINS_PREINSTALL_SYNC instead. Checkout the documentation for more info."
if [ "${GF_INSTALL_PLUGINS_FORCE}" = "true" ]; then
OLDIFS=$IFS
IFS=','
for plugin in ${GF_INSTALL_PLUGINS}; do
IFS=$OLDIFS
if [[ $plugin =~ .*\;.* ]]; then
pluginUrl=$(echo "$plugin" | cut -d';' -f 1)
pluginInstallFolder=$(echo "$plugin" | cut -d';' -f 2)
grafana cli --pluginUrl ${pluginUrl} --pluginsDir "${GF_PATHS_PLUGINS}" plugins install "${pluginInstallFolder}"
else
grafana cli --pluginsDir "${GF_PATHS_PLUGINS}" plugins install ${plugin}
fi
done
fi
fi
exec grafana server \
+4 -1
View File
@@ -12,9 +12,12 @@ runs:
go-version-file: go.mod
- name: Bump versions
uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e
env:
GO_MOD_DIR: ${{ inputs.go-mod-dir }}
VERSION: ${{ inputs.version }}
with:
verb: run
args: go run ./pkg/build/actions/bump-version -version=${{ inputs.version }}
args: go run ./pkg/build/actions/bump-version -version=${VERSION}
- name: make gen-cue
shell: bash
run: make gen-cue
@@ -24,7 +24,6 @@ dagger run --silent go run ./pkg/build/cmd \
-a docker:grafana:linux/arm/v7 \
--yarn-cache=${YARN_CACHE_FOLDER} \
--checksum \
--verify \
--build-id=${DRONE_BUILD_NUMBER} \
--grafana-dir=${GRAFANA_DIR} \
--github-token=${GITHUB_TOKEN} \
@@ -36,4 +35,4 @@ echo "Final list of artifacts:"
cat assets.txt
# Move the tar.gz packages to their expected locations
cat assets.txt | IS_MAIN=true go run ./scripts/move_packages.go ./dist/main
cat assets.txt | IS_MAIN=true go run ./pkg/build/daggerbuild/scripts/move_packages.go ./dist/main
@@ -19,13 +19,11 @@ dagger run --silent go run ./pkg/build/cmd \
-a docker:enterprise:linux/arm64 \
--yarn-cache=${YARN_CACHE_FOLDER} \
--checksum \
--verify \
--build-id=${DRONE_BUILD_NUMBER} \
--grafana-ref=${SOURCE_COMMIT} \
--grafana-repo="https://github.com/grafana/grafana.git" \
--enterprise-ref=${DRONE_COMMIT} \
--github-token=${GITHUB_TOKEN} \
--go-version=${GO_VERSION} \
--ubuntu-base=${UBUNTU_BASE} \
--alpine-base=${ALPINE_BASE} \
--patches-repo=${PATCHES_REPO} \
@@ -35,4 +33,4 @@ dagger run --silent go run ./pkg/build/cmd \
cat assets.txt
# Move the tar.gz packages to their expected locations
cat assets.txt | DESTINATION=gs://grafana-downloads IS_MAIN=true go run ./scripts/move_packages.go ./dist/main
cat assets.txt | DESTINATION=gs://grafana-downloads IS_MAIN=true go run ./pkg/build/daggerbuild/scripts/move_packages.go ./dist/main
@@ -15,13 +15,11 @@ dagger run --silent go run ./pkg/build/cmd \
-a frontend:enterprise \
--yarn-cache=${YARN_CACHE_FOLDER} \
--checksum \
--verify \
--build-id=${DRONE_BUILD_NUMBER} \
--grafana-ref=${SOURCE_COMMIT} \
--grafana-repo="https://github.com/grafana/grafana.git" \
--enterprise-ref=${DRONE_COMMIT} \
--github-token=${GITHUB_TOKEN} \
--go-version=${GO_VERSION} \
--ubuntu-base=${UBUNTU_BASE} \
--alpine-base=${ALPINE_BASE} \
--patches-repo=${PATCHES_REPO} \
@@ -30,5 +28,5 @@ dagger run --silent go run ./pkg/build/cmd \
echo "Final list of artifacts:"
# Move the tar.gz packages to their expected locations
cat assets.txt | grep -v "public" | DESTINATION=gs://grafana-downloads-enterprise2 IS_MAIN=true go run ./scripts/move_packages.go ./dist/main
cat assets.txt | grep "public" | DESTINATION=gs://grafana-static-assets IS_MAIN=true go run ./scripts/move_packages.go ./dist/cdn
cat assets.txt | grep -v "public" | DESTINATION=gs://grafana-downloads-enterprise2 IS_MAIN=true go run ./pkg/build/daggerbuild/scripts/move_packages.go ./dist/main
cat assets.txt | grep "public" | DESTINATION=gs://grafana-static-assets IS_MAIN=true go run ./pkg/build/daggerbuild/scripts/move_packages.go ./dist/cdn
@@ -12,7 +12,7 @@ docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --install all
# -a deb:enterprise:linux/arm/v7:nightly \
# -a docker:enterprise:linux/arm/v7 \
# -a docker:enterprise:linux/arm/v7:ubuntu \
dagger run --silent go run ./pkg/build/cmd \
artifacts \
-a targz:enterprise:linux/amd64 \
@@ -46,7 +46,6 @@ dagger run --silent go run ./pkg/build/cmd \
--github-token=${GITHUB_TOKEN} \
--destination=${local_dst} \
--yarn-cache=${YARN_CACHE_FOLDER} \
--go-version=${GO_VERSION} \
--ubuntu-base="${UBUNTU_BASE}" \
--alpine-base="${ALPINE_BASE}" > assets.txt
@@ -37,7 +37,6 @@ dagger run --silent go run ./pkg/build/cmd \
--github-token=${GITHUB_TOKEN} \
--destination=${local_dst} \
--yarn-cache=${YARN_CACHE_FOLDER} \
--go-version=${GO_VERSION} \
--ubuntu-base="${UBUNTU_BASE}" \
--alpine-base="${ALPINE_BASE}" > assets.txt
@@ -42,11 +42,10 @@ dagger run --silent go run ./pkg/build/cmd \
--grafana-ref=${DRONE_TAG} \
--grafana-repo=https://github.com/grafana/grafana-security-mirror.git \
--github-token=${GITHUB_TOKEN} \
--go-version=${GO_VERSION} \
--ubuntu-base="${UBUNTU_BASE}" \
--alpine-base="${ALPINE_BASE}" \
--version=${DRONE_TAG} \
--destination=${local_dst} > assets.txt
# Move the tar.gz packages to their expected locations
cat assets.txt | go run ./scripts/move_packages.go ./dist/prerelease
cat assets.txt | go run ./pkg/build/daggerbuild/scripts/move_packages.go ./dist/prerelease
@@ -39,10 +39,9 @@ dagger run --silent go run ./pkg/build/cmd \
--build-id=${DRONE_BUILD_NUMBER} \
--grafana-dir=${GRAFANA_DIR} \
--github-token=${GITHUB_TOKEN} \
--go-version=${GO_VERSION} \
--ubuntu-base="${UBUNTU_BASE}" \
--alpine-base="${ALPINE_BASE}" \
--version=${DRONE_TAG} \
--destination=${local_dst} > assets.txt
cat assets.txt | go run ./scripts/move_packages.go ./dist/prerelease
cat assets.txt | go run ./pkg/build/daggerbuild/scripts/move_packages.go ./dist/prerelease
@@ -33,10 +33,9 @@ dagger run --silent go run ./pkg/build/cmd \
--grafana-repo=https://github.com/grafana/grafana-security-mirror.git \
--github-token=${GITHUB_TOKEN} \
--version=${DRONE_TAG} \
--go-version=${GO_VERSION} \
--ubuntu-base="${UBUNTU_BASE}" \
--alpine-base="${ALPINE_BASE}" \
--destination=${local_dst} > assets.txt
# Move the tar.gz packages to their expected locations
cat assets.txt | go run ./scripts/move_packages.go ./dist/prerelease
cat assets.txt | go run ./pkg/build/daggerbuild/scripts/move_packages.go ./dist/prerelease
+5 -5
View File
@@ -6,7 +6,6 @@ package extensions
import (
_ "cloud.google.com/go/kms/apiv1"
_ "cloud.google.com/go/kms/apiv1/kmspb"
_ "cloud.google.com/go/spanner"
_ "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
_ "github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys"
_ "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault"
@@ -30,6 +29,11 @@ import (
_ "github.com/spf13/cobra" // used by the standalone apiserver cli
_ "github.com/stretchr/testify/require"
_ "golang.org/x/time/rate"
_ "k8s.io/api"
_ "k8s.io/kube-aggregator/pkg/apiserver/scheme"
_ "k8s.io/kube-aggregator/pkg/generated/openapi"
_ "k8s.io/kube-aggregator/pkg/registry/apiservice/rest"
_ "sigs.k8s.io/randfill"
_ "xorm.io/builder"
_ "github.com/grafana/authlib/authn"
@@ -42,8 +46,4 @@ import (
_ "github.com/grafana/e2e"
_ "github.com/grafana/gofpdf"
_ "github.com/grafana/gomemcache/memcache"
_ "k8s.io/kube-aggregator/pkg/apiserver/scheme"
_ "k8s.io/kube-aggregator/pkg/generated/openapi"
_ "k8s.io/kube-aggregator/pkg/registry/apiservice/rest"
)
-9
View File
@@ -54,7 +54,6 @@ type InitTestDBOpt = sqlstore.InitTestDBOpt
var SetupTestDB = sqlstore.SetupTestDB
var CleanupTestDB = sqlstore.CleanupTestDB
var ProvideService = sqlstore.ProvideService
var SkipTestsOnSpanner = sqlstore.SkipTestsOnSpanner
func InitTestDB(t sqlutil.ITestDB, opts ...InitTestDBOpt) *sqlstore.SQLStore {
db, _ := InitTestDBWithCfg(t, opts...)
@@ -96,11 +95,3 @@ func IsTestDBMSSQL() bool {
return false
}
func IsTestDBSpanner() bool {
if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present {
return db == migrator.Spanner
}
return false
}
-4
View File
@@ -374,10 +374,6 @@ func (sl *ServerLockService) createLock(ctx context.Context,
}
lockRow.Id = id
} else {
if sl.SQLStore.GetDBType() == migrator.Spanner {
rawSQL += " THEN RETURN id" // Required for successful LastInsertId call.
}
res, err := dbSession.Exec(
rawSQL,
lockRow.OperationUID, lockRow.LastExecution, 0)
@@ -186,11 +186,6 @@ func (r *rowsWrapper) ContinueToken() string {
return r.row.token.String()
}
// ContinueTokenWithCurrentRV implements resource.ListIterator.
func (r *rowsWrapper) ContinueTokenWithCurrentRV() string {
return r.row.token.String()
}
// Error implements resource.ListIterator.
func (r *rowsWrapper) Error() error {
return r.err
@@ -229,7 +229,12 @@ func (a *dashboardSqlAccess) ReadResource(ctx context.Context, req *resourcepb.R
return rsp
}
// List implements AppendingStore.
// ListHistory implements StorageBackend.
func (a *dashboardSqlAccess) ListHistory(ctx context.Context, req *resourcepb.ListRequest, cb func(resource.ListIterator) error) (int64, error) {
return a.ListIterator(ctx, req, cb)
}
// List implements StorageBackend.
func (a *dashboardSqlAccess) ListIterator(ctx context.Context, req *resourcepb.ListRequest, cb func(resource.ListIterator) error) (int64, error) {
if req.ResourceVersion != 0 {
return 0, apierrors.NewBadRequest("List with explicit resourceVersion is not supported with this storage backend")
@@ -304,9 +304,7 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resourcepb.Reso
return nil, err
}
hits := formatQueryResult(res)
for _, dashboard := range hits {
for _, dashboard := range res {
tags, err := json.Marshal(dashboard.Tags)
if err != nil {
return nil, err
@@ -324,7 +322,7 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resourcepb.Reso
}
list.Results.Rows = append(list.Results.Rows, &resourcepb.ResourceTableRow{
Key: getResourceKey(dashboard, req.Options.Key.Namespace),
Key: getResourceKey(&dashboard, req.Options.Key.Namespace),
Cells: cells,
})
}
@@ -352,35 +350,6 @@ func getResourceKey(item *dashboards.DashboardSearchProjection, namespace string
}
}
func formatQueryResult(res []dashboards.DashboardSearchProjection) []*dashboards.DashboardSearchProjection {
hitList := make([]*dashboards.DashboardSearchProjection, 0)
hits := make(map[string]*dashboards.DashboardSearchProjection)
for _, item := range res {
key := fmt.Sprintf("%s-%d", item.UID, item.OrgID)
hit, exists := hits[key]
if !exists {
hit = &dashboards.DashboardSearchProjection{
ID: item.ID,
UID: item.UID,
Title: item.Title,
FolderUID: item.FolderUID,
Tags: []string{},
IsFolder: item.IsFolder,
SortMeta: item.SortMeta,
}
hitList = append(hitList, hit)
hits[key] = hit
}
if len(item.Term) > 0 {
hit.Tags = append(hit.Tags, item.Term)
}
}
return hitList
}
func (c *DashboardSearchClient) GetStats(ctx context.Context, req *resourcepb.ResourceStatsRequest, _ ...grpc.CallOption) (*resourcepb.ResourceStatsResponse, error) {
info, err := claims.ParseNamespace(req.Namespace)
if err != nil {
@@ -44,8 +44,8 @@ func TestDashboardSearchClient_Search(t *testing.T) {
Type: "dash-db", // should set type based off of key
Sort: sorter,
}).Return([]dashboards.DashboardSearchProjection{
{ID: 1, UID: "uid", Title: "Test Dashboard", FolderUID: "folder1", Term: "term"},
{ID: 2, UID: "uid2", Title: "Test Dashboard2", FolderUID: "folder2"},
{ID: 1, UID: "uid", Title: "Test Dashboard", FolderUID: "folder1", Tags: []string{"term"}},
{ID: 2, UID: "uid2", Title: "Test Dashboard2", FolderUID: "folder2", Tags: []string{}},
}, nil).Once()
req := &resourcepb.ResourceSearchRequest{
@@ -121,7 +121,7 @@ func TestDashboardSearchClient_Search(t *testing.T) {
Type: "dash-db",
Sort: sortOptionAsc,
}).Return([]dashboards.DashboardSearchProjection{
{ID: 1, UID: "uid", Title: "Test Dashboard", FolderUID: "folder", SortMeta: int64(50)},
{ID: 1, UID: "uid", Title: "Test Dashboard", FolderUID: "folder", SortMeta: int64(50), Tags: []string{}},
}, nil).Once()
req := &resourcepb.ResourceSearchRequest{
@@ -186,7 +186,7 @@ func TestDashboardSearchClient_Search(t *testing.T) {
Type: "dash-db",
Sort: sortOptionAsc,
}).Return([]dashboards.DashboardSearchProjection{
{ID: 1, UID: "uid", Title: "Test Dashboard", FolderUID: "folder", SortMeta: int64(2)},
{ID: 1, UID: "uid", Title: "Test Dashboard", FolderUID: "folder", SortMeta: int64(2), Tags: []string{}},
}, nil).Once()
req := &resourcepb.ResourceSearchRequest{
+1 -1
View File
@@ -254,7 +254,7 @@ func (b *QueryAPIBuilder) handleQuerySingleDatasource(ctx context.Context, req d
return &backend.QueryDataResponse{}, nil
}
client, err := b.client.GetDataSourceClient(
client, err := b.clientSupplier.GetDataSourceClient(
ctx,
v0alpha1.DataSourceRef{
Type: req.PluginId,
+2 -2
View File
@@ -24,7 +24,7 @@ import (
func TestQueryRestConnectHandler(t *testing.T) {
b := &QueryAPIBuilder{
client: mockClient{
clientSupplier: mockClient{
lastCalledWithHeaders: &map[string]string{},
},
tracer: tracing.InitializeTracerForTest(),
@@ -80,7 +80,7 @@ func TestQueryRestConnectHandler(t *testing.T) {
"X-Rule-Type": "type-1",
"X-Rule-Version": "version-1",
"X-Grafana-Org-Id": "1",
}, *b.client.(mockClient).lastCalledWithHeaders)
}, *b.clientSupplier.(mockClient).lastCalledWithHeaders)
}
func TestInstantQueryFromAlerting(t *testing.T) {
+9 -9
View File
@@ -43,17 +43,17 @@ type QueryAPIBuilder struct {
authorizer authorizer.Authorizer
tracer tracing.Tracer
metrics *metrics.ExprMetrics
parser *queryParser
client clientapi.DataSourceClientSupplier
registry query.DataSourceApiServerRegistry
converter *expr.ResultConverter
queryTypes *query.QueryTypeDefinitionList
tracer tracing.Tracer
metrics *metrics.ExprMetrics
parser *queryParser
clientSupplier clientapi.DataSourceClientSupplier
registry query.DataSourceApiServerRegistry
converter *expr.ResultConverter
queryTypes *query.QueryTypeDefinitionList
}
func NewQueryAPIBuilder(features featuremgmt.FeatureToggles,
client clientapi.DataSourceClientSupplier,
clientSupplier clientapi.DataSourceClientSupplier,
ar authorizer.Authorizer,
registry query.DataSourceApiServerRegistry,
legacy service.LegacyDataSourceLookup,
@@ -80,7 +80,7 @@ func NewQueryAPIBuilder(features featuremgmt.FeatureToggles,
return &QueryAPIBuilder{
concurrentQueryLimit: 4,
log: log.New("query_apiserver"),
client: client,
clientSupplier: clientSupplier,
authorizer: ar,
registry: registry,
parser: newQueryParser(reader, legacy, tracer, log.New("query_parser")),
@@ -203,10 +203,6 @@ func TestIntegrationAnnotationListingWithInheritedRBAC(t *testing.T) {
t.Skip("skipping integration test")
}
if db.IsTestDBSpanner() {
t.Skip("skipping integration test")
}
orgID := int64(1)
permissions := []accesscontrol.Permission{
{
@@ -334,7 +334,6 @@ func (r *xormRepositoryImpl) Get(ctx context.Context, query annotations.ItemQuer
}
if len(tags) > 0 {
// "at" is a keyword in Spanner and needs to be quoted.
tagsSubQuery := fmt.Sprintf(`
SELECT SUM(1) FROM annotation_tag `+r.db.Quote("at")+`
INNER JOIN tag on tag.id = `+r.db.Quote("at")+`.tag_id
@@ -146,14 +146,11 @@ func (s *AnonDBStore) CreateOrUpdateDevice(ctx context.Context, device *Device)
}
// If CreatedAt time is not set (i.e. it's zero), and we end up creating the device, use current time as creation time.
// Spanner converts zero time to NULL, but CreatedAt is not nullable, so this helps to fix that problem too.
// If database converts zero time to NULL, but CreatedAt is not nullable, this helps to fix that problem too.
created := device.CreatedAt
if created.IsZero() {
created = time.Now()
}
if s.sqlStore.GetDBType() == migrator.Spanner {
return s.insertIntoSpanner(ctx, device, created)
}
args := []any{device.DeviceID, device.ClientIP, device.UserAgent, created.UTC(), device.UpdatedAt.UTC()}
switch s.sqlStore.GetDBType() {
@@ -192,30 +189,6 @@ func (s *AnonDBStore) CreateOrUpdateDevice(ctx context.Context, device *Device)
return err
}
// In Spanner INSERT OR UPDATE only works when conflict is on primary key. However here we expect conflict on non-PK
// column "device_id", so we need to use a transaction instead.
func (s *AnonDBStore) insertIntoSpanner(ctx context.Context, dev *Device, created time.Time) error {
return s.sqlStore.WithTransactionalDbSession(ctx, func(dbSession *sqlstore.DBSession) error {
prev := &Device{DeviceID: dev.DeviceID}
ok, err := dbSession.Table(tableName).Get(prev)
if err != nil {
return err
}
if !ok {
// Don't call Insert(dev) directly, because successful Insert modifies dev.DeviceID,
// but we don't do the same for other databases. That's why we create a copy.
devCopy := *dev
devCopy.CreatedAt = created
_, err = dbSession.Table(tableName).Insert(devCopy)
return err
}
// Include all columns in the update, even when empty (it's what inserts for other databases do too).
_, err = dbSession.Table(tableName).Where("device_id=?", dev.DeviceID).MustCols("client_ip", "user_agent", "created_at", "updated_at").Update(dev)
return err
})
}
func (s *AnonDBStore) CountDevices(ctx context.Context, from time.Time, to time.Time) (int64, error) {
var count int64
err := s.sqlStore.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error {
+33 -1
View File
@@ -922,7 +922,39 @@ func (d *dashboardStore) FindDashboards(ctx context.Context, query *dashboards.F
return nil, err
}
return res, nil
if len(res) <= 1 {
return res, nil
}
// the search query above will return one row per dashboard tag, and dashboards
// can have multiple tags. we only want to return one row per dashboard, so dedup
// the results by id.
// note: we must preserve the order of the results as we dedup, as the query can be sorted
seen := make(map[int64]int)
uniqueRes := make([]dashboards.DashboardSearchProjection, 0, len(res))
for _, item := range res {
if idx, exists := seen[item.ID]; exists {
if item.Term != "" {
if uniqueRes[idx].Tags == nil {
uniqueRes[idx].Tags = make([]string, 0)
}
uniqueRes[idx].Tags = append(uniqueRes[idx].Tags, item.Term)
}
continue
}
if item.Tags == nil {
item.Tags = make([]string, 0)
}
if item.Term != "" {
item.Tags = append(item.Tags, item.Term)
}
seen[item.ID] = len(uniqueRes)
uniqueRes = append(uniqueRes, item)
}
return uniqueRes, nil
}
func (d *dashboardStore) GetDashboardTags(ctx context.Context, query *dashboards.GetDashboardTagsQuery) ([]*dashboards.DashboardTagCloudItem, error) {
@@ -234,10 +234,6 @@ func TestIntegrationDashboardInheritedFolderRBAC(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
if db.IsTestDBSpanner() {
t.Skip("skipping integration test")
}
// the maximux nested folder hierarchy starting from parent down to subfolders
nestedFolders := make([]*folder.Folder, 0, folder.MaxNestedFolderDepth+1)

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