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

This commit is contained in:
Mariell Hoversholm committed 2025-02-19 13:25:58 +01:00
commit 43df78669d
119 files changed
+1135 -714

No files matched your search

+7 -36
View File
@@ -1548,7 +1548,7 @@ exports[`better eslint`] = {
"public/app/features/alerting/unified/GrafanaRuleQueryViewer.tsx:5381": [
[0, 0, 0, "\'@grafana/data/src/datetime/rangeutil\' import is restricted from being used by a pattern. Import from the public export instead.", "0"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
[0, 0, 0, "No untranslated strings in text props in text props. Wrap text with <Trans /> or use t() or use t()", "2"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"],
@@ -2470,19 +2470,6 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"]
],
"public/app/features/alerting/unified/components/rule-viewer/tabs/Details.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "6"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "7"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "8"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "9"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "10"]
],
"public/app/features/alerting/unified/components/rule-viewer/tabs/Routing.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
@@ -5493,12 +5480,6 @@ exports[`better eslint`] = {
"public/app/features/plugins/admin/components/GetStartedWithPlugin/index.ts:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./GetStartedWithPlugin\`)", "0"]
],
"public/app/features/plugins/admin/components/InstallControls/ExternallyManagedButton.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"]
],
"public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"]
],
@@ -6332,8 +6313,7 @@ exports[`better eslint`] = {
],
"public/app/features/transformers/FilterByValueTransformer/ValueMatchers/BasicMatcherEditor.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"]
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
],
"public/app/features/transformers/FilterByValueTransformer/ValueMatchers/NoopMatcherEditor.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
@@ -6341,15 +6321,11 @@ exports[`better eslint`] = {
"public/app/features/transformers/FilterByValueTransformer/ValueMatchers/RangeMatcherEditor.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "2"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"],
[0, 0, 0, "Unexpected any. Specify a different type.", "6"]
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "Unexpected any. Specify a different type.", "3"]
],
"public/app/features/transformers/FilterByValueTransformer/ValueMatchers/RegexMatcherEditor.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"]
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"]
],
"public/app/features/transformers/FilterByValueTransformer/ValueMatchers/types.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
@@ -6532,10 +6508,7 @@ exports[`better eslint`] = {
[0, 0, 0, "\'@grafana/data/src/transformations/transformers/histogram\' import is restricted from being used by a pattern. Import from the public export instead.", "0"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "2"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "3"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "4"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "5"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "6"]
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "3"]
],
"public/app/features/transformers/editors/JoinByFieldTransformerEditor.tsx:5381": [
[0, 0, 0, "\'@grafana/data/src/transformations/transformers/joinByField\' import is restricted from being used by a pattern. Import from the public export instead.", "0"],
@@ -6554,9 +6527,7 @@ exports[`better eslint`] = {
],
"public/app/features/transformers/editors/LimitTransformerEditor.tsx:5381": [
[0, 0, 0, "\'@grafana/data/src/transformations/transformers/limit\' import is restricted from being used by a pattern. Import from the public export instead.", "0"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "2"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "3"]
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"]
],
"public/app/features/transformers/editors/MergeTransformerEditor.tsx:5381": [
[0, 0, 0, "\'@grafana/data/src/transformations/transformers/merge\' import is restricted from being used by a pattern. Import from the public export instead.", "0"],
+5 -5
View File
@@ -179,7 +179,7 @@ steps:
name: yarn-install
- commands:
- apk add --update git bash
- yarn betterer ci
- yarn betterer:ci
depends_on:
- yarn-install
image: node:22.11.0-alpine
@@ -1717,7 +1717,7 @@ steps:
name: yarn-install
- commands:
- apk add --update git bash
- yarn betterer ci
- yarn betterer:ci
depends_on:
- yarn-install
image: node:22.11.0-alpine
@@ -2865,7 +2865,7 @@ steps:
name: yarn-install
- commands:
- apk add --update git bash
- yarn betterer ci
- yarn betterer:ci
depends_on:
- yarn-install
image: node:22.11.0-alpine
@@ -4342,7 +4342,7 @@ steps:
name: yarn-install
- commands:
- apk add --update git bash
- yarn betterer ci
- yarn betterer:ci
depends_on:
- yarn-install
image: node:22.11.0-alpine
@@ -5605,6 +5605,6 @@ kind: secret
name: gcr_credentials
---
kind: signature
hmac: a1d8f3defdf52b76049d8a8df1c21dd13cac9a2a9f80566abceffb4acd1b49d2
hmac: 7a16cbedacf8a7381304abfde88319ce712a133e24c1ab304543c4c7ac318816
...
@@ -11,7 +11,9 @@ on:
type: string
required: true
secrets:
token:
GRAFANA_DELIVERY_BOT_APP_ID:
required: true
GRAFANA_DELIVERY_BOT_APP_PEM:
required: true
outputs:
branch:
@@ -26,7 +28,9 @@ on:
type: string
required: true
secrets:
token:
GRAFANA_DELIVERY_BOT_APP_ID:
required: true
GRAFANA_DELIVERY_BOT_APP_PEM:
required: true
jobs:
main:
@@ -34,10 +38,16 @@ jobs:
outputs:
branch: ${{ steps.branch.outputs.branch }}
steps:
- name: "Generate token"
id: generate_token
uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92
with:
app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }}
private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }}
- name: Create release branch
id: branch
uses: grafana/grafana-github-actions-go/bump-release@main
with:
ownerRepo: ${{ inputs.ownerRepo }}
source: ${{ inputs.source }}
token: ${{ secrets.token }}
token: ${{ steps.generate_token.outputs.token }}
+13 -3
View File
@@ -16,7 +16,9 @@ on:
required: true
type: string
secrets:
token:
GRAFANA_DELIVERY_BOT_APP_ID:
required: true
GRAFANA_DELIVERY_BOT_APP_PEM:
required: true
workflow_dispatch:
inputs:
@@ -33,17 +35,25 @@ on:
required: true
type: string
secrets:
token:
GRAFANA_DELIVERY_BOT_APP_ID:
required: true
GRAFANA_DELIVERY_BOT_APP_PEM:
required: true
jobs:
main:
runs-on: ubuntu-latest
steps:
- name: "Generate token"
id: generate_token
uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92
with:
app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }}
private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }}
- name: Migrate PRs
uses: grafana/grafana-github-actions-go/migrate-open-prs@main
with:
token: ${{ secrets.token }}
token: ${{ steps.generate_token.outputs.token }}
ownerRepo: ${{ inputs.ownerRepo }}
from: ${{ inputs.from }}
to: ${{ inputs.to }}
+15 -19
View File
@@ -19,7 +19,7 @@ on:
- closed
branches:
- 'main'
- 'v*.*.*'
- 'release-*.*.*'
jobs:
setup:
@@ -30,15 +30,8 @@ jobs:
release_branch: ${{ steps.output.outputs.release_branch }}
dry_run: ${{ steps.output.outputs.dry_run }}
latest: ${{ steps.output.outputs.latest }}
token: ${{ steps.output.outputs.token }}
runs-on: ubuntu-latest
steps:
- name: "Generate token"
id: generate_token
uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92
with:
app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }}
private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }}
# The github-release action expects a `LATEST` value of a string of either '1' or '0'
- if: ${{ github.event_name == 'workflow_dispatch' }}
run: |
@@ -58,7 +51,6 @@ jobs:
echo "version: $VERSION"
echo "release_branch=$(echo $VERSION | sed -s 's/^v/release-/g')" >> "$GITHUB_OUTPUT"
echo "token=${{ steps.generate_token.outputs.token }}" >> "$GITHUB_OUTPUT"
echo "dry_run=$DRY_RUN" >> "$GITHUB_OUTPUT"
echo "latest=$LATEST" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
@@ -66,42 +58,46 @@ jobs:
name: Create next release branch (Grafana)
needs: setup
uses: ./.github/workflows/create-next-release-branch.yml
secrets:
GRAFANA_DELIVERY_BOT_APP_ID: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }}
GRAFANA_DELIVERY_BOT_APP_PEM: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }}
with:
ownerRepo: 'grafana/grafana'
source: ${{ needs.setup.outputs.release_branch }}
secrets:
token: ${{ needs.setup.outputs.token }}
create_next_release_branch_enterprise:
name: Create next release branch (Grafana Enterprise)
needs: setup
uses: ./.github/workflows/create-next-release-branch.yml
with:
ownerRepo: 'grafana/grafana'
source: ${{ needs.setup.outputs.release_branch }}
secrets:
token: ${{ needs.setup.outputs.token }}
GRAFANA_DELIVERY_BOT_APP_ID: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }}
GRAFANA_DELIVERY_BOT_APP_PEM: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }}
with:
ownerRepo: 'grafana/grafana-enterprise'
source: ${{ needs.setup.outputs.release_branch }}
migrate_prs_grafana:
needs:
- setup
- create_next_release_branch_grafana
uses: ./.github/workflows/migrate-prs.yml
secrets:
GRAFANA_DELIVERY_BOT_APP_ID: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }}
GRAFANA_DELIVERY_BOT_APP_PEM: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }}
with:
ownerRepo: 'grafana/grafana'
from: ${{ needs.setup.outputs.release_branch }}
to: ${{ needs.create_next_release_branch_grafana.outputs.branch }}
secrets:
token: ${{ needs.setup.outputs.token }}
migrate_prs_enterprise:
needs:
- setup
- create_next_release_branch_enterprise
uses: ./.github/workflows/migrate-prs.yml
secrets:
GRAFANA_DELIVERY_BOT_APP_ID: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }}
GRAFANA_DELIVERY_BOT_APP_PEM: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }}
with:
ownerRepo: 'grafana/grafana-enterprise'
from: ${{ needs.setup.outputs.release_branch }}
to: ${{ needs.create_next_release_branch_enterprise.outputs.branch }}
secrets:
token: ${{ needs.setup.outputs.token }}
post_changelog_on_forum:
needs: setup
uses: ./.github/workflows/community-release.yml
+2
View File
@@ -74,6 +74,7 @@ jobs:
ref: ${{ steps.branch.outputs.branch }}
fetch-depth: 0
fetch-tags: true
token: ${{ steps.generate_token.outputs.token }}
- name: Checkout Grafana (main)
uses: actions/checkout@v4
with:
@@ -81,6 +82,7 @@ jobs:
fetch-depth: '0'
fetch-tags: 'false'
path: .grafana-main
token: ${{ steps.generate_token.outputs.token }}
- name: Setup nodejs environment
uses: actions/setup-node@v4
with:
+1 -1
View File
@@ -1,5 +1,5 @@
run:
timeout: 10m
timeout: 15m
concurrency: 10
allow-parallel-runners: true
linters-settings:
+111
View File
@@ -1,3 +1,114 @@
<!-- 11.5.2 START -->
# 11.5.2 (2025-02-18)
### Features and enhancements
- **Docker:** Use our own glibc 2.40 binaries [#99918](https://github.com/grafana/grafana/pull/99918), [@DanCech](https://github.com/DanCech)
- **TransformationFilter:** Include transformation outputs in transformation filtering options [#99878](https://github.com/grafana/grafana/pull/99878), [@Sergej-Vlasov](https://github.com/Sergej-Vlasov)
- **grafana-ui:** Update InlineField error prop type to React.ReactNode [#100373](https://github.com/grafana/grafana/pull/100373), [@Clarity-89](https://github.com/Clarity-89)
### Bug fixes
- **Alerting:** Allow specifying uid for new rules added to groups [#100450](https://github.com/grafana/grafana/pull/100450), [@yuri-tceretian](https://github.com/yuri-tceretian)
- **Alerting:** Allow specifying uid for new rules added to groups [#100450](https://github.com/grafana/grafana/pull/100450), [@yuri-tceretian](https://github.com/yuri-tceretian)
- **Alerting:** Call RLock() before reading sendAlertsTo map [#99880](https://github.com/grafana/grafana/pull/99880), [@santihernandezc](https://github.com/santihernandezc)
- **Auth:** Fix redirect with JWT auth URL login [#100355](https://github.com/grafana/grafana/pull/100355), [@mgyongyosi](https://github.com/mgyongyosi)
- **AuthN:** Refetch user on "ErrUserAlreadyExists" [#100582](https://github.com/grafana/grafana/pull/100582), [@kalleep](https://github.com/kalleep)
- **Azure:** Correctly set application insights resource values [#99599](https://github.com/grafana/grafana/pull/99599), [@aangelisc](https://github.com/aangelisc)
- **CodeEditor:** Fix cursor alignment [#99863](https://github.com/grafana/grafana/pull/99863), [@ashharrison90](https://github.com/ashharrison90)
- **DashboardList:** Throttle the re-renders [#100046](https://github.com/grafana/grafana/pull/100046), [@bfmatei](https://github.com/bfmatei)
- **Dashboards:** Bring back scripted dashboards [#100633](https://github.com/grafana/grafana/pull/100633), [@dprokop](https://github.com/dprokop)
- **Plugin Metrics:** Eliminate data race in plugin metrics middleware [#100078](https://github.com/grafana/grafana/pull/100078), [@clord](https://github.com/clord)
- **RBAC:** Don't check folder access if `annotationPermissionUpdate` FT is enabled [#100117](https://github.com/grafana/grafana/pull/100117), [@IevaVasiljeva](https://github.com/IevaVasiljeva)
<!-- 11.5.2 END -->
<!-- 11.4.2 START -->
# 11.4.2 (2025-02-18)
### Features and enhancements
- **Docker:** Use our own glibc 2.40 binaries [#99924](https://github.com/grafana/grafana/pull/99924), [@DanCech](https://github.com/DanCech)
### Bug fixes
- **Auth:** Fix redirect with JWT auth URL login [#100494](https://github.com/grafana/grafana/pull/100494), [@mgyongyosi](https://github.com/mgyongyosi)
- **AuthN:** Refetch user on "ErrUserAlreadyExists" [#100585](https://github.com/grafana/grafana/pull/100585), [@kalleep](https://github.com/kalleep)
- **Azure:** Correctly set application insights resource values [#99598](https://github.com/grafana/grafana/pull/99598), [@aangelisc](https://github.com/aangelisc)
- **Dashboards:** Bring back scripted dashboards [#100629](https://github.com/grafana/grafana/pull/100629), [@dprokop](https://github.com/dprokop)
- **Plugin Metrics:** Eliminate data race in plugin metrics middleware [#100077](https://github.com/grafana/grafana/pull/100077), [@clord](https://github.com/clord)
- **RBAC:** Don't check folder access if `annotationPermissionUpdate` FT is enabled [#100116](https://github.com/grafana/grafana/pull/100116), [@IevaVasiljeva](https://github.com/IevaVasiljeva)
<!-- 11.4.2 END -->
<!-- 11.3.4 START -->
# 11.3.4 (2025-02-18)
### Features and enhancements
- **Docker:** Use our own glibc 2.40 binaries [#99923](https://github.com/grafana/grafana/pull/99923), [@DanCech](https://github.com/DanCech)
### Bug fixes
- **Auth:** Fix redirect with JWT auth URL login [#100495](https://github.com/grafana/grafana/pull/100495), [@mgyongyosi](https://github.com/mgyongyosi)
- **Azure:** Correctly set application insights resource values [#99597](https://github.com/grafana/grafana/pull/99597), [@aangelisc](https://github.com/aangelisc)
- **Dashboards:** Bring back scripted dashboards [#100627](https://github.com/grafana/grafana/pull/100627), [@dprokop](https://github.com/dprokop)
- **Plugin Metrics:** Eliminate data race in plugin metrics middleware [#100076](https://github.com/grafana/grafana/pull/100076), [@clord](https://github.com/clord)
<!-- 11.3.4 END -->
<!-- 11.2.7 START -->
# 11.2.7 (2025-02-18)
### Features and enhancements
- **Docker:** Use our own glibc 2.40 binaries [#99922](https://github.com/grafana/grafana/pull/99922), [@DanCech](https://github.com/DanCech)
### Bug fixes
- **Azure:** Correctly set application insights resource values [#99596](https://github.com/grafana/grafana/pull/99596), [@aangelisc](https://github.com/aangelisc)
<!-- 11.2.7 END -->
<!-- 11.1.12 START -->
# 11.1.12 (2025-02-18)
### Features and enhancements
- **Docker:** Use our own glibc 2.40 binaries [#99917](https://github.com/grafana/grafana/pull/99917), [@DanCech](https://github.com/DanCech)
### Bug fixes
- **Azure:** Correctly set application insights resource values [#99595](https://github.com/grafana/grafana/pull/99595), [@aangelisc](https://github.com/aangelisc)
<!-- 11.1.12 END -->
<!-- 11.0.11 START -->
# 11.0.11 (2025-02-18)
### Features and enhancements
- **Docker:** Use our own glibc 2.40 binaries [#100730](https://github.com/grafana/grafana/pull/100730), [@DanCech](https://github.com/DanCech)
### Bug fixes
- **Azure:** Correctly set application insights resource values [#99594](https://github.com/grafana/grafana/pull/99594), [@aangelisc](https://github.com/aangelisc)
<!-- 11.0.11 END -->
<!-- 10.4.16 START -->
# 10.4.16 (2025-02-18)
### Features and enhancements
- **Docker:** Use our own glibc 2.40 binaries [#99920](https://github.com/grafana/grafana/pull/99920), [@DanCech](https://github.com/DanCech)
### Bug fixes
- **Dashboard:** Fix for overwriting an edited dashboard in the old architecture [#100288](https://github.com/grafana/grafana/pull/100288), [@bfmatei](https://github.com/bfmatei)
<!-- 10.4.16 END -->
<!-- 11.5.1 START -->
# 11.5.1 (2025-02-03)
+3 -1
View File
@@ -121,7 +121,9 @@ update: ## Fetch the latest version of this Makefile and the `make-docs` script
curl -s -LO https://raw.githubusercontent.com/grafana/writers-toolkit/main/docs/make-docs
chmod +x make-docs
.PHONY: topic/%
# ls static/templates/ | sed 's/-template\.md//' | xargs
TOPIC_TYPES := concept multiple-tasks reference section task tutorial visualization
.PHONY: $(patsubst %,topic/%,$(TOPIC_TYPES))
topic/%: ## Create a topic from the Writers' Toolkit template. Specify the topic type as the target, for example, `make topic/task TOPIC_PATH=sources/my-new-topic.md`.
topic/%:
$(if $(TOPIC_PATH),,$(error "You must set the TOPIC_PATH variable to the path where the $(@F) topic will be created. For example: make $(@) TOPIC_PATH=sources/my-new-topic.md"))
+6
View File
@@ -8,6 +8,12 @@
# [Semantic versioning](https://semver.org/) is used to help the reader identify the significance of changes.
# Changes are relevant to this script and the support docs.mk GNU Make interface.
#
# ## 8.5.1 (2025-02-18)
#
# ### Fixed
#
# - PHONY declaration for topic/<KIND> targets.
#
# ## 8.5.0 (2025-02-13)
#
# ### Added
@@ -14,11 +14,32 @@ labels:
menuTitle: Amazon SNS
title: Configure Amazon SNS for Alerting
weight: 100
refs:
notification-templates:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/configure-notifications/template-notifications/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/alerting-and-irm/alerting/configure-notifications/template-notifications/
configure-contact-points:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/configure-notifications/manage-contact-points/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/alerting-and-irm/alerting/configure-notifications/manage-contact-points/
test-contact-point:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/configure-notifications/manage-contact-points/#test-a-contact-point
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/alerting-and-irm/alerting/configure-notifications/manage-contact-points/#test-a-contact-point
enable-contact-point-notifications:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/configure-notifications/manage-contact-points/#enable-notifications-for-a-contact-point
- pattern: /docs/grafana-cloud/
destination: /docs/grafana-cloud/alerting-and-irm/alerting/configure-notifications/manage-contact-points/#enable-notifications-for-a-contact-point
---
# Configure Amazon SNS for Alerting
# Configure Amazon SNS notifications
Use the Grafana Alerting - Amazon SNS integration to send notifications to Amazon SNS when your alerts are firing. You can receive notifications via the various subscriber channels supported by SNS.
Use the Amazon SNS integration in a contact point to send alert notifications to a SNS topic. Then, configure the SNS topic to forward notifications to distinct subscriber channels used in your SNS account.
## Before you begin
@@ -27,71 +48,51 @@ Before you begin, ensure you have the following:
- **AWS SNS Topic**: An SNS topic to send notifications to.
- **AWS IAM Identity with necessary access**: An IAM identity (e.g. user, role) with the necessary permissions to publish messages to the SNS topic.
For an example setup, see [Example Minimal Setup Using Assumed IAM Role]({{< relref "#example-minimal-setup-using-assumed-iam-role" >}}).
For a minimal setup, refer to [Example using an Access Key](#example-using-an-access-key).
## Adding the SNS Contact Point in Grafana
## Configure Amazon SNS for a contact point
With AWS resources configured, proceed to add SNS as a contact point in Grafana.
To create a contact point with a SNS integration, complete the following steps.
- Navigate to **Alerts & IRM** -> **Alerting** -> **Contact points**.
- Click on **"Add contact point"**.
- **Name**: Enter a descriptive name (e.g., `AWS SNS`).
- Choose **"AWS SNS"** from the list of contact point types.
1. Navigate to **Alerts & IRM** -> **Alerting** -> **Contact points**.
1. Click **+ Add contact point**.
1. Enter a name for the contact point.
1. From the **Integration** list, select **AWS SNS**.
1. Set up the required [settings](#sns-settings) for your SNS configuration.
1. Click **Save contact point**.
### 2. Configure SNS Settings
For more details on contact points, including how to test them and enable notifications, refer to [Configure contact points](ref:configure-contact-points).
#### SNS Settings
## SNS Settings
- **The Amazon SNS API URL**: (Optional) The SNS API URL, e.g., `https://sns.us-east-2.amazonaws.com`. If not specified, the SNS API URL from the SNS SDK will be used.
- **Signature Version (sigv4)**: Configures AWS's Signature Verification 4 signing process to sign requests.
- **Region**: (Optional) The AWS region. If blank, the region from the default credentials chain is used.
- **Access Key**: The AWS API access key.
- **Secret Key**: The AWS API secret key.
- **Access Key** : (Optional) The AWS API access key.
- **Secret Key**: (Optional) The AWS API secret key.
> Both `Access Key` and `Secret Key` must be provided together or left blank together.
>
> If left blank, Grafana searches for credentials using the default credentials chain, including environment variables (`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`), the shared credential file, and EC2 instance roles.
- **Profile**: (Optional) Named AWS profile used to authenticate.
- **Role ARN**: (Optional) The ARN of an AWS IAM role to assume for authentication, serving as an alternative to using AWS API keys.
- **SNS topic ARN**: (Optional) If you don't specify this value, you must specify a value for the `Phone number` or `Target ARN`. If you are using a FIFO SNS topic you should set a message group interval longer than 5 minutes to prevent messages with the same group key being deduplicated by the SNS default deduplication window.
- **Phone number**: (Optional) Phone number if message is delivered via SMS in E.164 format. If you don't specify this value, you must specify a value for the `SNS topic ARN` or `Target ARN`.
- **Target ARN**: (Optional) The mobile platform endpoint ARN if message is delivered via mobile notifications. If you don't specify this value, you must specify a value for the `SNS topic ARN` or `Phone number`.
- **Subject**: (Optional) Customize the subject line or use the default template. This field is templateable.
- **Message**: (Optional) Customize the message content or use the default template. This field is templateable.
- **Subject**: (Optional) Customize the subject. This field supports [notification templates](ref:notification-templates) and, by default, uses the default title template (`default.title`). It **cannot be an empty string**.
- **Message**: (Optional) Customize the message. This field supports [notification templates](ref:notification-templates) and, by default, uses the default message template (`default.message`).
- **Attributes**: (Optional) Add any SNS message attributes.
{{< admonition type="note" >}}
Both `Access Key` and `Secret Key` must be provided together or left blank together. If blank it defaults to a chain of credential
providers to search for credentials in environment variables, shared credential file, and EC2 Instance Roles.
## Example using an Access Key
Environment variables: `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`.
{{< /admonition >}}
This section outlines a minimal setup to configure Amazon SNS with Alerting.
### 3. Test & Save the Contact Point
- Click **"Test"** to verify that the SNS configuration is working correctly.
- After the test is successful, click **"Save contact point"** to add the SNS contact point.
### 4. Next steps
The SNS contact point is ready to receive alert notifications.
To add this contact point to your alert, complete the following steps.
1. In Grafana, navigate to **Alerting** > **Alert rules**.
1. Edit or create a new alert rule.
1. Scroll down to the **Configure labels and notifications** section.
1. Under Notifications click **Select contact point**.
1. From the drop-down menu, select the previously created contact point.
1. **Click Save rule and exit**.
## Example Minimal Setup Using Assumed IAM Role
This section outlines a minimal setup to configure SNS with Grafana using an assumed IAM Role.
### 1. Create an SNS Topic
### 1. Create an SNS Topic and Email Subscriber
1. **Navigate to SNS in AWS Console**:
- Go to the [Amazon SNS Console](https://console.aws.amazon.com/sns/v3/home).
2. **Create a new topic** [[AWS Documentation](https://docs.aws.amazon.com/sns/latest/dg/sns-create-topic.html)]:
2. **Create a new topic**:
- On the **Topics** page, choose **"Create topic"**.
- Select **"Standard"** as the type.
@@ -99,105 +100,70 @@ This section outlines a minimal setup to configure SNS with Grafana using an ass
- **Encryption**: Leave disabled for this minimal setup.
- Click **"Create topic"**.
3. (Optional) **Add an email subscriber to help test** [[AWS Documentation](https://docs.aws.amazon.com/sns/latest/dg/sns-email-notifications.html)]:
3. (Optional) **Add an email subscriber to help test**:
- Within your newly created topic, click on **"Create subscription"**.
- **Protocol**: Choose `Email`.
- **Endpoint**: Enter your email address to receive test notifications.
- Click **"Create subscription"**.
- **Confirm Subscription**: Check your email and confirm the subscription by clicking the provided link.
### 2. Create an IAM Role
### 2. Create an IAM Policy, User, and Access Key
1. **Navigate to IAM in AWS Console**:
- Go to the [IAM Console](https://console.aws.amazon.com/iam/home).
2. **Create a new role** [[AWS Documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user.html)]:
2. **Create a new policy**:
- On the **Roles** page, choose **"Create role"**.
- **Trusted Entity**: Select **"This account"**.
- Click **"Next"** until the end, name it (e.g., `GrafanaSNSRole`), and click **"Create role"**.
3. **Attach Inline Policy**:
- After creating the role, select it and navigate to the **"Permissions"** tab.
- Click on **"Add permission"** > **"Create inline policy"**.
- On the **Policies** page, choose **"Create policy"**.
- Switch to the **"JSON"** tab and paste the following policy, replacing `Resource` with your SNS topic ARN:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["sns:Publish", "sns:GetTopicAttributes"],
"Resource": "arn:aws:sns:<region>:<account_id>:<topic_name>"
}
]
}
```
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["sns:Publish", "sns:GetTopicAttributes"],
"Resource": "arn:aws:sns:<region>:<account_id>:<topic_name>"
}
]
}
```
- Click **"Next"**, name it (e.g., `SNSPublishPolicy`), and click **"Create policy"**.
### 3. Create an IAM Policy
1. **Create a new policy to allow assuming the above IAM role** [[AWS Documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_create-console.html)]:
- In the IAM Console, on the **Policies** page, choose **"Create policy"**.
- Switch to the **"JSON"** tab and paste the following policy, replacing `Resource` with the ARN of the role you created earlier:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::<account_id>:role/GrafanaSNSRole"
}
]
}
```
2. **Review and Create**:
- Click **"Next"**, name it (e.g., `AssumeSNSRolePolicy`), and click **"Create policy"**.
### 4. Create an IAM User
1. **Create a new IAM user to assume the above role** [[AWS Documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html)]:
3. **Create a new IAM user and assign the policy**
- In the IAM Console, on the **Users** page, choose **"Create user"**.
- Enter a **User name**, e.g., `grafana-sns-user`.
- Enter a **User name**, e.g., `alerting-sns-user`.
- Click **"Next"**.
- Select **"Attach policies directly"**.
- Search for the policy you created earlier (`AssumeSNSRolePolicy`) and select it.
- In **Set permissions**, select **"Attach policies directly"**.
- Search for the policy you created earlier (`SNSPublishPolicy`) and select it.
- Click **"Next"** , and click **"Create user"**.
2. **Generate credentials**:
4. **Create an Access Key**:
- Within your newly created user, click on **"Create access key"**.
- Select an appropriate use-case, e.g., `Application running outside AWS`.
- Click **"Next"** , and click **"Create access key"**.
- **Save Credentials**: Note the **Access key ID** and **Secret access key**. You'll need these for Grafana's configuration.
- **Save Credentials**: Note the **Access key ID** and **Secret access key** that are required in the next step.
### 5. Add the SNS Contact Point in Grafana
### 3. Configure the SNS Contact Point in Grafana
After creating the IAM user and obtaining the necessary credentials, proceed to [configure the SNS contact point in Grafana]({{< relref "#adding-the-sns-contact-point-in-grafana" >}}) using the following details:
Follow the steps in [configure Amazon SNS for a contact point](#configure-amazon-sns-for-a-contact-point), using the settings below and replacing the placeholders with the SNS and IAM values created in the previous steps.
- **The Amazon SNS API URL**: `https://sns.us-east-1.amazonaws.com`
- **The Amazon SNS API URL**: `https://sns.<region>.amazonaws.com`
- **Signature Version (sigv4)**:
- **Region**: `us-east-1`
- **Region**: `<region>`
- **Access Key**: `<YOUR_ACCESS_KEY>`.
- **Secret Key**: `<YOUR_SECRET_ACCESS_KEY>`
- **Role ARN**: `arn:aws:iam::<account_id>:role/GrafanaSNSRole`
- **SNS topic ARN**: `arn:aws:sns:<region>:<account_id>:My-Topic`
- **SNS topic ARN**: `arn:aws:sns:<region>:<account_id>:<topic_name>`
{{< admonition type="note" >}}
Replace the placeholder values (`https://sns.us-east-1.amazonaws.com`, `us-east-1`, `<YOUR_ACCESS_KEY>`, `<YOUR_SECRET_ACCESS_KEY>`, `arn:aws:iam::<account_id>:role/GrafanaSNSRole`, `arn:aws:sns:<region>:<account_id>:My-Topic`) with your actual AWS credentials and ARNs.
{{< /admonition >}}
[Test the contact point](ref:test-contact-point) to ensure it's working, or [enable notifications](ref:enable-contact-point-notifications) for it.
## Additional Resources
- [Configure contact points](ref:configure-contact-points)
- [Amazon SNS Documentation](https://docs.aws.amazon.com/sns/index.html)
- [AWS IAM Documentation](https://docs.aws.amazon.com/iam/index.html)
- [Prometheus Alertmanager SNS Integration](https://prometheus.io/docs/alerting/configuration/#sns_config)
- [Cloudwatch AWS Authentication]({{< relref "../../../../datasources/aws-cloudwatch/aws-authentication" >}})
- [Amazon IAM Documentation](https://docs.aws.amazon.com/iam/index.html)
- [Prometheus Alertmanager SNS Configuration](https://prometheus.io/docs/alerting/configuration/#sns_config)
@@ -50,7 +50,7 @@ To create a contact point with webhook integration, complete the following steps
1. Enter a name for the contact point.
1. From the **Integration** list, select **Webhook**.
1. In the **URL** field, copy in your Webhook URL.
1. (Optional) Configure [additional settings](#settings).
1. (Optional) Configure [additional settings](#webhook-settings).
1. Click **Save contact point**.
For more details on contact points, including how to test them and enable notifications, refer to [Configure contact points](ref:configure-contact-points).
@@ -76,7 +76,7 @@ Each policy consists of a set of label matchers (0 or more) that specify which a
{{< figure src="/media/docs/alerting/notification-routing.png" max-width="750px" caption="Matching alert instances with notification policies" alt="Example of a notification policy tree" >}}
{{< admonition type="tip" >}}
For a practical example of routing with notification policies, refer to our [Getting Started with Alert Instances and Notification Routing tutorial](https://grafana.com/tutorials/alerting-get-started-pt4/).
For a practical example of routing with notification policies, refer to our [Getting Started with Alert Instances and Notification Routing tutorial](https://grafana.com/tutorials/alerting-get-started-pt2/).
{{< /admonition >}}
## Routing
@@ -168,9 +168,8 @@ Experimental features might be changed or removed without prior notice.
| `disableClassicHTTPHistogram` | Disables classic HTTP Histogram (use with enableNativeHTTPHistogram) |
| `kubernetesSnapshots` | Routes snapshot requests from /api to the /apis endpoint |
| `kubernetesDashboards` | Use the kubernetes API in the frontend for dashboards |
| `kubernetesCliDashboards` | Use the k8s client to retrieve dashboards internally |
| `kubernetesRestore` | Allow restoring objects in k8s |
| `kubernetesFoldersServiceV2` | Use the Folders Service V2, and route Folder Service requests to k8s |
| `kubernetesClientDashboardsFolders` | Route the folder and dashboard service requests to k8s |
| `datasourceQueryTypes` | Show query type endpoints in datasource API servers (currently hardcoded for testdata, expressions, and prometheus) |
| `queryService` | Register /apis/query.grafana.app/ -- will eventually replace /api/ds/query |
| `queryServiceRewrite` | Rewrite requests targeting /ds/query to the query service |
@@ -221,10 +221,14 @@ HTTPS protocol is supported in the image renderer v3.11.0 and later.
Change the protocol of the server, it can be `http` or `https`. Default is `http`.
```bash
HTTP_PROTOCOL=https
```
```json
{
"service": {
"protocol": "http"
"protocol": "https"
}
}
```
@@ -233,6 +237,11 @@ Change the protocol of the server, it can be `http` or `https`. Default is `http
Path to the image renderer certificate and key file used to start an HTTPS server.
```bash
HTTP_CERT_FILE=./path/to/cert
HTTP_CERT_KEY=./path/to/key
```
```json
{
"service": {
@@ -246,6 +255,10 @@ Path to the image renderer certificate and key file used to start an HTTPS serve
Minimum TLS version allowed. Accepted values are: `TLSv1.2`, `TLSv1.3`. Default is `TLSv1.2`.
```bash
HTTP_MIN_TLS_VERSION=TLSv1.2
```
```json
{
"service": {
+3 -3
View File
@@ -39,7 +39,7 @@ require (
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // @grafana/grafana-backend-group
github.com/bwmarrin/snowflake v0.3.0 // @grafan/grafana-app-platform-squad
github.com/centrifugal/centrifuge v0.33.3 // @grafana/grafana-app-platform-squad
github.com/crewjam/saml v0.4.13 // @grafana/identity-access-team
github.com/crewjam/saml v0.4.14 // @grafana/identity-access-team
github.com/dlmiddlecote/sqlstats v1.0.2 // @grafana/grafana-backend-group
github.com/dolthub/go-mysql-server v0.19.1-0.20250206012855-c216e59c21a7 // @grafana/grafana-datasources-core-services
github.com/dolthub/vitess v0.0.0-20250123002143-3b45b8cacbfa // @grafana/grafana-datasources-core-services
@@ -75,8 +75,8 @@ require (
github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group
github.com/gorilla/websocket v1.5.3 // @grafana/grafana-app-platform-squad
github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038 // @grafana/alerting-backend
github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc // @grafana/identity-access-team
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c // @grafana/identity-access-team
github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 // @grafana/identity-access-team
github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 // @grafana/identity-access-team
github.com/grafana/dashboard-linter v0.0.0-20241106223805-1e7999311752 // @grafana/grafana-app-platform-squad
github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics
github.com/grafana/dataplane/sdata v0.0.9 // @grafana/observability-metrics
+4 -4
View File
@@ -1530,10 +1530,10 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038 h1:dG/UKAjY/KlKp9fY8aEm+gSQHHRmPm5q+9cea3hRSu8=
github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU=
github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc h1:qx8avmRi3wx+SuAn6UV0pKma1fJ0/4POUYNc+2rxZkg=
github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc/go.mod h1:HamgITzYqErFp8qLwYM/JwwE3z6fXft0Z+5n0Dx1rUM=
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo=
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM=
github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 h1:NTMmow+74I3Jb033xhbRgWQS7A//5TDhiM4tl7bsVP4=
github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7/go.mod h1:T3X4z0ejGfJOiOmZLFeKCRT/yxWJq/RtclAc/PHj/w4=
github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 h1:EokLC5grHwLPs4tXW8T6E8187H1e5G9AP0QQ5B60HbA=
github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM=
github.com/grafana/dashboard-linter v0.0.0-20241106223805-1e7999311752 h1:pIEPQPua6kCkMib/nOn//WLgtiA4urf2RPBViSF6CcE=
github.com/grafana/dashboard-linter v0.0.0-20241106223805-1e7999311752/go.mod h1:QRxC8NNAbpGx2vz9/XckJcWXtHU3My57byeudPrf0WU=
github.com/grafana/dataplane/examples v0.0.1 h1:K9M5glueWyLoL4//H+EtTQq16lXuHLmOhb6DjSCahzA=
+7
View File
@@ -606,8 +606,11 @@ github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grafana/authlib/types v0.0.0-20250120144156-d6737a7dc8f5/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM=
github.com/grafana/cloudflare-go v0.0.0-20230110200409-c627cf6792f2 h1:qhugDMdQ4Vp68H0tp/0iN17DM2ehRo1rLEdOFe/gB8I=
github.com/grafana/cloudflare-go v0.0.0-20230110200409-c627cf6792f2/go.mod h1:w/aiO1POVIeXUQyl0VQSZjl5OAGDTL5aX+4v0RA1tcw=
github.com/grafana/dashboard-linter v0.0.0-20241106223805-1e7999311752 h1:pIEPQPua6kCkMib/nOn//WLgtiA4urf2RPBViSF6CcE=
github.com/grafana/dashboard-linter v0.0.0-20241106223805-1e7999311752/go.mod h1:QRxC8NNAbpGx2vz9/XckJcWXtHU3My57byeudPrf0WU=
github.com/grafana/go-gelf/v2 v2.0.1 h1:BOChP0h/jLeD+7F9mL7tq10xVkDG15he3T1zHuQaWak=
github.com/grafana/go-gelf/v2 v2.0.1/go.mod h1:lexHie0xzYGwCgiRGcvZ723bSNyNI8ZRD4s0CLobh90=
github.com/grafana/tail v0.0.0-20230510142333-77b18831edf0 h1:bjh0PVYSVVFxzINqPFYJmAmJNrWPgnVjuSdYJGHmtFU=
@@ -1063,6 +1066,7 @@ go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc=
golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
@@ -1086,6 +1090,7 @@ golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
@@ -1110,12 +1115,14 @@ gonum.org/v1/plot v0.14.0/go.mod h1:MLdR9424SJed+5VqC6MsouEpig9pZX2VZ57H9ko2bXU=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro=
google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo=
google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo=
google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4=
google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY=
google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:E5//3O5ZIG2l71Xnt+P/CYUY8Bxs8E7WMoZ9tlcMbAY=
google.golang.org/genproto/googleapis/bytestream v0.0.0-20250102185135-69823020774d h1:NZBSeFsuFS5YrgHMW/8xfTbzNXMshQPNgq2Yb7xipEs=
google.golang.org/genproto/googleapis/bytestream v0.0.0-20250102185135-69823020774d/go.mod h1:s4mHJ3FfG8P6A3O+gZ8TVqB3ufjOl9UG3ANCMMwCHmo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
+1
View File
@@ -58,6 +58,7 @@
"betterer": "betterer --tsconfig ./scripts/cli/tsconfig.json",
"betterer:stats": "ts-node --transpile-only --project ./scripts/cli/tsconfig.json ./scripts/cli/reportBettererStats.ts",
"betterer:issues": "ts-node --transpile-only --project ./scripts/cli/tsconfig.json ./scripts/cli/generateBettererIssues.ts",
"betterer:ci": "betterer ci --tsconfig ./scripts/cli/tsconfig.json",
"plugin:build": "nx run-many -t build --projects='tag:scope:plugin'",
"plugin:build:commit": "nx run-many -t build:commit --projects='tag:scope:plugin'",
"plugin:build:dev": "nx run-many -t dev --projects='tag:scope:plugin' --maxParallel=100",
@@ -108,9 +108,8 @@ export interface FeatureToggles {
kubernetesPlaylists?: boolean;
kubernetesSnapshots?: boolean;
kubernetesDashboards?: boolean;
kubernetesCliDashboards?: boolean;
kubernetesRestore?: boolean;
kubernetesFoldersServiceV2?: boolean;
kubernetesClientDashboardsFolders?: boolean;
datasourceQueryTypes?: boolean;
queryService?: boolean;
queryServiceRewrite?: boolean;
+1
View File
@@ -44,6 +44,7 @@
"@grafana/ui": "11.6.0-pre",
"history": "4.10.1",
"lodash": "4.17.21",
"react-loading-skeleton": "3.5.0",
"react-use": "17.6.0",
"rxjs": "7.8.1",
"tslib": "2.8.1"
+1 -1
View File
@@ -192,7 +192,7 @@ func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response
}
metrics.MFolderIDsAPICount.WithLabelValues(metrics.GetDashboard).Inc()
// lookup folder title & url
if dash.FolderUID != "" && hs.Features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
if dash.FolderUID != "" && hs.Features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
queryResult, err := hs.folderService.Get(ctx, &folder.GetFolderQuery{
OrgID: c.SignedInUser.GetOrgID(),
UID: &dash.FolderUID,
+6 -3
View File
@@ -55,6 +55,7 @@ import (
"github.com/grafana/grafana/pkg/services/publicdashboards/api"
publicdashboardModels "github.com/grafana/grafana/pkg/services/publicdashboards/models"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/star/startest"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
@@ -833,11 +834,11 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr
quotaService := quotatest.New(false, nil)
folderSvc := folderimpl.ProvideService(
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore,
nil, db, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
nil, db, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
if dashboardService == nil {
dashboardService, err = service.ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore, features, folderPermissions,
ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(),
ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(),
)
require.NoError(t, err)
dashboardService.(dashboards.PermissionsRegistrationService).RegisterDashboardPermissions(dashboardPermissions)
@@ -845,7 +846,9 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr
dashboardProvisioningService, err := service.ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore, features, folderPermissions,
ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(),
ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil,
dualwrite.ProvideTestService(),
sort.ProvideService(),
)
require.NoError(t, err)
+4 -3
View File
@@ -40,6 +40,7 @@ import (
"github.com/grafana/grafana/pkg/services/org/orgimpl"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/star"
"github.com/grafana/grafana/pkg/services/star/startest"
@@ -463,7 +464,7 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog
fStore := folderimpl.ProvideStore(sc.db)
folderServiceWithFlagOn := folderimpl.ProvideService(
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore,
nil, sc.db, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
nil, sc.db, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
acSvc := acimpl.ProvideOSSService(
sc.cfg, acdb.ProvideService(sc.db), actionSets, localcache.ProvideService(),
features, tracing.InitializeTracerForTest(), sc.db, permreg.ProvidePermissionRegistry(), nil,
@@ -474,7 +475,7 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog
dashboardSvc, err := dashboardservice.ProvideDashboardServiceImpl(
sc.cfg, dashStore, folderStore,
features, folderPermissions, ac,
folderServiceWithFlagOn, fStore, nil, client.MockTestRestConfig{}, nil, quotaSrv, nil, nil, nil, dualwrite.ProvideTestService(),
folderServiceWithFlagOn, fStore, nil, client.MockTestRestConfig{}, nil, quotaSrv, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(),
)
require.NoError(b, err)
@@ -491,7 +492,7 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog
SQLStore: sc.db,
Features: features,
QuotaService: quotaSrv,
SearchService: search.ProvideService(sc.cfg, sc.db, starSvc, dashboardSvc, folderServiceWithFlagOn, features),
SearchService: search.ProvideService(sc.cfg, sc.db, starSvc, dashboardSvc, folderServiceWithFlagOn, features, sort.ProvideService()),
folderService: folderServiceWithFlagOn,
DashboardService: dashboardSvc,
}
+1 -1
View File
@@ -625,7 +625,7 @@ func TestGetFolderLegacyAndUnifiedStorage(t *testing.T) {
featuresArr := []any{featuremgmt.FlagNestedFolders}
if tc.unifiedStorageEnabled {
featuresArr = append(featuresArr, featuremgmt.FlagKubernetesFoldersServiceV2)
featuresArr = append(featuresArr, featuremgmt.FlagKubernetesClientDashboardsFolders)
}
server := SetupAPITestServer(t, func(hs *HTTPServer) {
+2 -2
View File
@@ -3,8 +3,8 @@ module github.com/grafana/grafana/pkg/apimachinery
go 1.23.1
require (
github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc // @grafana/identity-access-team
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c // @grafana/identity-access-team
github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 // @grafana/identity-access-team
github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 // @grafana/identity-access-team
github.com/stretchr/testify v1.10.0
k8s.io/apimachinery v0.32.1
k8s.io/apiserver v0.32.1
+4 -4
View File
@@ -32,10 +32,10 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc h1:qx8avmRi3wx+SuAn6UV0pKma1fJ0/4POUYNc+2rxZkg=
github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc/go.mod h1:HamgITzYqErFp8qLwYM/JwwE3z6fXft0Z+5n0Dx1rUM=
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo=
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM=
github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 h1:NTMmow+74I3Jb033xhbRgWQS7A//5TDhiM4tl7bsVP4=
github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7/go.mod h1:T3X4z0ejGfJOiOmZLFeKCRT/yxWJq/RtclAc/PHj/w4=
github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 h1:EokLC5grHwLPs4tXW8T6E8187H1e5G9AP0QQ5B60HbA=
github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+1 -1
View File
@@ -6,7 +6,7 @@ toolchain go1.23.6
require (
github.com/google/go-cmp v0.6.0
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c
github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31
github.com/grafana/grafana-app-sdk/logging v0.30.0
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1
github.com/prometheus/client_golang v1.20.5
+2 -2
View File
@@ -79,8 +79,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo=
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM=
github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 h1:EokLC5grHwLPs4tXW8T6E8187H1e5G9AP0QQ5B60HbA=
github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM=
github.com/grafana/grafana-app-sdk/logging v0.30.0 h1:K/P/bm7Cp7Di4tqIJ3EQz2+842JozQGRaz62r95ApME=
github.com/grafana/grafana-app-sdk/logging v0.30.0/go.mod h1:xy6ZyVXl50Z3DBDLybvBPphbykPhuVNed/VNmen9DQM=
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1 h1:ItDcDxUjVLPKja+hogpqgW/kj8LxUL2qscelXIsN1Bs=
@@ -21,6 +21,7 @@ import (
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/unified"
@@ -63,7 +64,7 @@ func ToUnifiedStorage(c utils.CommandLine, cfg *setting.Cfg, sqlStore db.DB) err
migrator := legacy.NewDashboardAccess(
legacysql.NewDatabaseProvider(sqlStore),
authlib.OrgNamespaceFormatter,
nil, provisioning, false,
nil, provisioning, false, sort.ProvideService(),
)
yes, err := promptYesNo(fmt.Sprintf("Count legacy resources for namespace: %s?", opts.Namespace))
+1 -1
View File
@@ -20,7 +20,7 @@ type Installer interface {
type PluginSource interface {
PluginClass(ctx context.Context) Class
PluginURIs(ctx context.Context) []string
DefaultSignature(ctx context.Context) (Signature, bool)
DefaultSignature(ctx context.Context, pluginID string) (Signature, bool)
}
type FileStore interface {
+1 -1
View File
@@ -494,7 +494,7 @@ func (s *FakePluginSource) PluginURIs(ctx context.Context) []string {
return []string{}
}
func (s *FakePluginSource) DefaultSignature(ctx context.Context) (plugins.Signature, bool) {
func (s *FakePluginSource) DefaultSignature(ctx context.Context, _ string) (plugins.Signature, bool) {
if s.DefaultSignatureFunc != nil {
return s.DefaultSignatureFunc(ctx)
}
+1 -1
View File
@@ -141,7 +141,7 @@ func (s *Signature) ReadPluginManifestFromFS(ctx context.Context, pfs plugins.FS
}
func (s *Signature) Calculate(ctx context.Context, src plugins.PluginSource, plugin plugins.FoundPlugin) (plugins.Signature, error) {
if defaultSignature, exists := src.DefaultSignature(ctx); exists {
if defaultSignature, exists := src.DefaultSignature(ctx, plugin.JSONData.ID); exists {
return defaultSignature, nil
}
@@ -30,7 +30,7 @@ func (s *LocalSource) PluginURIs(_ context.Context) []string {
return s.paths
}
func (s *LocalSource) DefaultSignature(_ context.Context) (plugins.Signature, bool) {
func (s *LocalSource) DefaultSignature(_ context.Context, _ string) (plugins.Signature, bool) {
switch s.class {
case plugins.ClassCore:
return plugins.Signature{
+4 -4
View File
@@ -41,7 +41,7 @@ func TestSources_List(t *testing.T) {
filepath.Join(testdata, "app", "plugins", "datasource"),
filepath.Join(testdata, "app", "plugins", "panel"),
})
sig, exists := srcs[0].DefaultSignature(ctx)
sig, exists := srcs[0].DefaultSignature(ctx, "")
require.True(t, exists)
require.Equal(t, plugins.SignatureStatusInternal, sig.Status)
require.Equal(t, plugins.SignatureType(""), sig.Type)
@@ -51,7 +51,7 @@ func TestSources_List(t *testing.T) {
require.Equal(t, srcs[1].PluginURIs(ctx), []string{
filepath.Join(testdata, "pluginRootWithDist", "datasource"),
})
sig, exists = srcs[1].DefaultSignature(ctx)
sig, exists = srcs[1].DefaultSignature(ctx, "")
require.False(t, exists)
require.Equal(t, plugins.Signature{}, sig)
@@ -59,7 +59,7 @@ func TestSources_List(t *testing.T) {
require.Equal(t, srcs[2].PluginURIs(ctx), []string{
filepath.Join(testdata, "pluginRootWithDist", "dist"),
})
sig, exists = srcs[2].DefaultSignature(ctx)
sig, exists = srcs[2].DefaultSignature(ctx, "")
require.False(t, exists)
require.Equal(t, plugins.Signature{}, sig)
@@ -67,7 +67,7 @@ func TestSources_List(t *testing.T) {
require.Equal(t, srcs[3].PluginURIs(ctx), []string{
filepath.Join(testdata, "pluginRootWithDist", "panel"),
})
sig, exists = srcs[3].DefaultSignature(ctx)
sig, exists = srcs[3].DefaultSignature(ctx, "")
require.False(t, exists)
require.Equal(t, plugins.Signature{}, sig)
})
@@ -16,6 +16,7 @@ import (
folders "github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/provisioning"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/unified/apistore"
@@ -42,9 +43,10 @@ type LegacyMigrator interface {
func ProvideLegacyMigrator(
sql db.DB, // direct access to tables
provisioning provisioning.ProvisioningService, // only needed for dashboard settings
sort sort.Service,
) LegacyMigrator {
dbp := legacysql.NewDatabaseProvider(sql)
return NewDashboardAccess(dbp, authlib.OrgNamespaceFormatter, nil, provisioning, false)
return NewDashboardAccess(dbp, authlib.OrgNamespaceFormatter, nil, provisioning, false, sort)
}
type BlobStoreInfo struct {
@@ -24,6 +24,7 @@ import (
gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/provisioning"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
@@ -68,8 +69,9 @@ func NewDashboardAccess(sql legacysql.LegacyDatabaseProvider,
dashStore dashboards.Store,
provisioning provisioning.ProvisioningService,
softDelete bool,
sorter sort.Service,
) DashboardAccess {
dashboardSearchClient := legacysearcher.NewDashboardSearchClient(dashStore)
dashboardSearchClient := legacysearcher.NewDashboardSearchClient(dashStore, sorter)
return &dashboardSqlAccess{
sql: sql,
namespacer: namespacer,
@@ -14,9 +14,10 @@ import (
folderv0alpha1 "github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
"github.com/grafana/grafana/pkg/storage/unified/resource"
unisearch "github.com/grafana/grafana/pkg/storage/unified/search"
"google.golang.org/grpc"
"k8s.io/apimachinery/pkg/selection"
)
@@ -24,10 +25,19 @@ import (
type DashboardSearchClient struct {
resource.ResourceIndexClient
dashboardStore dashboards.Store
sorter sort.Service
}
func NewDashboardSearchClient(dashboardStore dashboards.Store) *DashboardSearchClient {
return &DashboardSearchClient{dashboardStore: dashboardStore}
func NewDashboardSearchClient(dashboardStore dashboards.Store, sorter sort.Service) *DashboardSearchClient {
return &DashboardSearchClient{dashboardStore: dashboardStore, sorter: sorter}
}
var sortByMapping = map[string]string{
unisearch.DASHBOARD_VIEWS_LAST_30_DAYS: "viewed-recently-",
unisearch.DASHBOARD_VIEWS_TOTAL: "viewed-",
unisearch.DASHBOARD_ERRORS_LAST_30_DAYS: "errors-recently-",
unisearch.DASHBOARD_ERRORS_TOTAL: "errors-",
"title": "alpha-",
}
// nolint:gocyclo
@@ -78,16 +88,30 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour
query.Type = queryType
}
// technically, there exists the ability to register multiple ways of sorting using the legacy database
// see RegisterSortOption in pkg/services/search/sorting.go
// however, it doesn't look like we are taking advantage of that. And since by default the legacy
// sql will sort by title ascending, we only really need to handle the "alpha-desc" case
if req.SortBy != nil {
for _, sort := range req.SortBy {
if sort.Field == "title" && sort.Desc {
query.Sort = search.SortAlphaDesc
}
sortByField := ""
if len(req.SortBy) != 0 {
if len(req.SortBy) > 1 {
return nil, fmt.Errorf("only one sort field is supported")
}
sort := req.SortBy[0]
sortByField = strings.TrimPrefix(sort.Field, resource.SEARCH_FIELD_PREFIX)
sorterName := sortByMapping[sortByField]
if sort.Desc {
sorterName += "desc"
} else {
sorterName += "asc"
}
if sorter, ok := c.sorter.GetSortOption(sorterName); ok {
query.Sort = sorter
}
}
// the title search will not return any sortMeta (an int64), like
// most sorting will. Without this, the title will be set to sortMeta (0)
if sortByField == resource.SEARCH_FIELD_TITLE {
sortByField = ""
}
// if searching for tags, get those instead of the dashboards or folders
@@ -178,7 +202,6 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour
query.ProvisionedRepo, _ = dashboard.GetProvisionedFileNameFromMeta(vals[0])
}
}
searchFields := resource.StandardSearchFields()
list := &resource.ResourceSearchResponse{
Results: &resource.ResourceTable{
@@ -186,6 +209,10 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour
searchFields.Field(resource.SEARCH_FIELD_TITLE),
searchFields.Field(resource.SEARCH_FIELD_FOLDER),
searchFields.Field(resource.SEARCH_FIELD_TAGS),
&resource.ResourceTableColumnDefinition{
Name: sortByField,
Type: resource.ResourceTableColumnDefinition_INT64,
},
},
},
}
@@ -213,7 +240,7 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour
Key: getResourceKey(&dashboards.DashboardSearchProjection{
UID: dashboard.UID,
}, req.Options.Key.Namespace),
Cells: [][]byte{[]byte(dashboard.Title), []byte(dashboard.FolderUID), {}},
Cells: [][]byte{[]byte(dashboard.Title), []byte(dashboard.FolderUID), {}, {}},
})
}
@@ -235,7 +262,7 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour
list.Results.Rows = append(list.Results.Rows, &resource.ResourceTableRow{
Key: getResourceKey(dashboard, req.Options.Key.Namespace),
Cells: [][]byte{[]byte(dashboard.Title), []byte(dashboard.FolderUID), tags},
Cells: [][]byte{[]byte(dashboard.Title), []byte(dashboard.FolderUID), tags, []byte(strconv.FormatInt(dashboard.SortMeta, 10))},
})
}
@@ -276,6 +303,7 @@ func formatQueryResult(res []dashboards.DashboardSearchProjection) []*dashboards
FolderUID: item.FolderUID,
Tags: []string{},
IsFolder: item.IsFolder,
SortMeta: item.SortMeta,
}
hitList = append(hitList, hit)
hits[key] = hit
@@ -3,14 +3,18 @@ package legacysearcher
import (
"context"
"encoding/json"
"strconv"
"testing"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/apis/dashboard"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/search/model"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/storage/unified/resource"
unisearch "github.com/grafana/grafana/pkg/storage/unified/search"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/selection"
@@ -18,10 +22,13 @@ import (
func TestDashboardSearchClient_Search(t *testing.T) {
mockStore := dashboards.NewFakeDashboardStore(t)
client := NewDashboardSearchClient(mockStore)
sortSvc := sort.ProvideService()
client := NewDashboardSearchClient(mockStore, sortSvc)
ctx := context.Background()
user := &user.SignedInUser{OrgID: 2}
ctx = identity.WithRequester(ctx, user)
emptyTags, err := json.Marshal([]string{})
require.NoError(t, err)
dashboardKey := &resource.ResourceKey{
Name: "uid",
@@ -29,9 +36,11 @@ func TestDashboardSearchClient_Search(t *testing.T) {
}
t.Run("Should parse results into GRPC", func(t *testing.T) {
sorter, _ := sortSvc.GetSortOption("alpha-asc")
mockStore.On("FindDashboards", mock.Anything, &dashboards.FindPersistedDashboardsQuery{
SignedInUser: user, // user from context should be used
Type: "dash-db", // should set type based off of key
Sort: sorter,
}).Return([]dashboards.DashboardSearchProjection{
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder1", Term: "term"},
{UID: "uid2", Title: "Test Dashboard2", FolderUID: "folder2"},
@@ -41,17 +50,19 @@ func TestDashboardSearchClient_Search(t *testing.T) {
Options: &resource.ListOptions{
Key: dashboardKey,
},
SortBy: []*resource.ResourceSearchRequest_Sort{
{
Field: resource.SEARCH_FIELD_TITLE,
},
},
}
resp, err := client.Search(ctx, req)
require.NoError(t, err)
tags, err := json.Marshal([]string{"term"})
require.NoError(t, err)
emptyTags, err := json.Marshal([]string{})
require.NoError(t, err)
require.NotNil(t, resp)
searchFields := resource.StandardSearchFields()
require.Equal(t, &resource.ResourceSearchResponse{
TotalHits: 2,
@@ -60,6 +71,10 @@ func TestDashboardSearchClient_Search(t *testing.T) {
searchFields.Field(resource.SEARCH_FIELD_TITLE),
searchFields.Field(resource.SEARCH_FIELD_FOLDER),
searchFields.Field(resource.SEARCH_FIELD_TAGS),
&resource.ResourceTableColumnDefinition{
Name: "", // sort by should be empty if title is what we sorted by
Type: resource.ResourceTableColumnDefinition_INT64,
},
},
Rows: []*resource.ResourceTableRow{
{
@@ -72,6 +87,7 @@ func TestDashboardSearchClient_Search(t *testing.T) {
[]byte("Test Dashboard"),
[]byte("folder1"),
tags,
[]byte(strconv.FormatInt(0, 10)),
},
},
{
@@ -84,6 +100,127 @@ func TestDashboardSearchClient_Search(t *testing.T) {
[]byte("Test Dashboard2"),
[]byte("folder2"),
emptyTags,
[]byte(strconv.FormatInt(0, 10)),
},
},
},
},
}, resp)
mockStore.AssertExpectations(t)
})
t.Run("Sorting should be properly parsed into legacy sorting options (asc), and results added", func(t *testing.T) {
sortOptionAsc := model.SortOption{
Name: "viewed-asc", // should add -asc to the sort field and match on that
}
sortSvc.RegisterSortOption(sortOptionAsc)
mockStore.On("FindDashboards", mock.Anything, &dashboards.FindPersistedDashboardsQuery{
SignedInUser: user,
Type: "dash-db",
Sort: sortOptionAsc,
}).Return([]dashboards.DashboardSearchProjection{
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder", SortMeta: int64(50)},
}, nil).Once()
req := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Key: dashboardKey,
},
SortBy: []*resource.ResourceSearchRequest_Sort{
{
Field: resource.SEARCH_FIELD_PREFIX + unisearch.DASHBOARD_VIEWS_TOTAL, // "fields." prefix should be removed
Desc: false,
},
},
}
resp, err := client.Search(ctx, req)
require.NoError(t, err)
require.NotNil(t, resp)
searchFields := resource.StandardSearchFields()
require.Equal(t, &resource.ResourceSearchResponse{
TotalHits: 1,
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
searchFields.Field(resource.SEARCH_FIELD_TITLE),
searchFields.Field(resource.SEARCH_FIELD_FOLDER),
searchFields.Field(resource.SEARCH_FIELD_TAGS),
&resource.ResourceTableColumnDefinition{
Name: "views_total",
Type: resource.ResourceTableColumnDefinition_INT64,
},
},
Rows: []*resource.ResourceTableRow{
{
Key: &resource.ResourceKey{
Name: "uid",
Group: dashboard.GROUP,
Resource: dashboard.DASHBOARD_RESOURCE,
},
Cells: [][]byte{
[]byte("Test Dashboard"),
[]byte("folder"),
emptyTags,
[]byte(strconv.FormatInt(50, 10)),
},
},
},
},
}, resp)
mockStore.AssertExpectations(t)
})
t.Run("Sorting should be properly parsed into legacy sorting options (desc)", func(t *testing.T) {
sortOptionAsc := model.SortOption{
Name: "errors-recently-desc", // should add -asc to the sort field and match on that
}
sortSvc.RegisterSortOption(sortOptionAsc)
mockStore.On("FindDashboards", mock.Anything, &dashboards.FindPersistedDashboardsQuery{
SignedInUser: user,
Type: "dash-db",
Sort: sortOptionAsc,
}).Return([]dashboards.DashboardSearchProjection{
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder", SortMeta: int64(2)},
}, nil).Once()
req := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Key: dashboardKey,
},
SortBy: []*resource.ResourceSearchRequest_Sort{
{
Field: unisearch.DASHBOARD_ERRORS_LAST_30_DAYS,
Desc: true,
},
},
}
resp, err := client.Search(ctx, req)
require.NoError(t, err)
require.NotNil(t, resp)
searchFields := resource.StandardSearchFields()
require.Equal(t, &resource.ResourceSearchResponse{
TotalHits: 1,
Results: &resource.ResourceTable{
Columns: []*resource.ResourceTableColumnDefinition{
searchFields.Field(resource.SEARCH_FIELD_TITLE),
searchFields.Field(resource.SEARCH_FIELD_FOLDER),
searchFields.Field(resource.SEARCH_FIELD_TAGS),
&resource.ResourceTableColumnDefinition{
Name: "errors_last_30_days",
Type: resource.ResourceTableColumnDefinition_INT64,
},
},
Rows: []*resource.ResourceTableRow{
{
Key: &resource.ResourceKey{
Name: "uid",
Group: dashboard.GROUP,
Resource: dashboard.DASHBOARD_RESOURCE,
},
Cells: [][]byte{
[]byte("Test Dashboard"),
[]byte("folder"),
emptyTags,
[]byte(strconv.FormatInt(2, 10)),
},
},
},
+1 -1
View File
@@ -290,7 +290,7 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) {
if queryParams.Has("sort") {
for _, sort := range queryParams["sort"] {
if slices.Contains(search.DashboardFields(), sort) {
sort = "fields." + sort
sort = resource.SEARCH_FIELD_PREFIX + sort
}
s := &resource.ResourceSearchRequest_Sort{Field: sort}
if strings.HasPrefix(sort, "-") {
@@ -31,6 +31,7 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/provisioning"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
@@ -70,11 +71,12 @@ func RegisterAPIService(cfg *setting.Cfg, features featuremgmt.FeatureToggles,
tracing *tracing.TracingService,
unified resource.ResourceClient,
dual dualwrite.Service,
sorter sort.Service,
) *DashboardsAPIBuilder {
softDelete := features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore)
dbp := legacysql.NewDatabaseProvider(sql)
namespacer := request.GetNamespaceMapper(cfg)
legacyDashboardSearcher := legacysearcher.NewDashboardSearchClient(dashStore)
legacyDashboardSearcher := legacysearcher.NewDashboardSearchClient(dashStore, sorter)
builder := &DashboardsAPIBuilder{
log: log.New("grafana-apiserver.dashboards.v0alpha1"),
DashboardsAPIBuilder: dashboard.DashboardsAPIBuilder{
@@ -88,7 +90,7 @@ func RegisterAPIService(cfg *setting.Cfg, features featuremgmt.FeatureToggles,
legacy: &dashboard.DashboardStorage{
Resource: dashboardv0alpha1.DashboardResourceInfo,
Access: legacy.NewDashboardAccess(dbp, namespacer, dashStore, provisioning, softDelete),
Access: legacy.NewDashboardAccess(dbp, namespacer, dashStore, provisioning, softDelete, sorter),
TableConverter: dashboardv0alpha1.DashboardResourceInfo.TableConverter(),
Features: features,
},
@@ -29,6 +29,7 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/provisioning"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/unified/apistore"
@@ -65,6 +66,7 @@ func RegisterAPIService(cfg *setting.Cfg, features featuremgmt.FeatureToggles,
sql db.DB,
tracing *tracing.TracingService,
unified resource.ResourceClient,
sorter sort.Service,
) *DashboardsAPIBuilder {
softDelete := features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore)
dbp := legacysql.NewDatabaseProvider(sql)
@@ -81,7 +83,7 @@ func RegisterAPIService(cfg *setting.Cfg, features featuremgmt.FeatureToggles,
legacy: &dashboard.DashboardStorage{
Resource: dashboardv1alpha1.DashboardResourceInfo,
Access: legacy.NewDashboardAccess(dbp, namespacer, dashStore, provisioning, softDelete),
Access: legacy.NewDashboardAccess(dbp, namespacer, dashStore, provisioning, softDelete, sorter),
TableConverter: dashboardv1alpha1.DashboardResourceInfo.TableConverter(),
Features: features,
},
@@ -29,6 +29,7 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/provisioning"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/unified/apistore"
@@ -65,6 +66,7 @@ func RegisterAPIService(cfg *setting.Cfg, features featuremgmt.FeatureToggles,
sql db.DB,
tracing *tracing.TracingService,
unified resource.ResourceClient,
sorter sort.Service,
) *DashboardsAPIBuilder {
softDelete := features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore)
dbp := legacysql.NewDatabaseProvider(sql)
@@ -82,7 +84,7 @@ func RegisterAPIService(cfg *setting.Cfg, features featuremgmt.FeatureToggles,
legacy: &dashboard.DashboardStorage{
Resource: dashboardv2alpha1.DashboardResourceInfo,
Access: legacy.NewDashboardAccess(dbp, namespacer, dashStore, provisioning, softDelete),
Access: legacy.NewDashboardAccess(dbp, namespacer, dashStore, provisioning, softDelete, sorter),
TableConverter: dashboardv2alpha1.DashboardResourceInfo.TableConverter(),
Features: features,
},
+1 -1
View File
@@ -66,7 +66,7 @@ func RegisterAPIService(cfg *setting.Cfg,
unified resource.ResourceClient,
) *FolderAPIBuilder {
if !featuremgmt.AnyEnabled(features,
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs,
featuremgmt.FlagProvisioning) {
return nil // skip registration unless opting into Kubernetes folders or unless we want to customize registration when testing
+2
View File
@@ -120,6 +120,7 @@ import (
"github.com/grafana/grafana/pkg/services/quota/quotaimpl"
"github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/searchV2"
"github.com/grafana/grafana/pkg/services/secrets"
secretsDatabase "github.com/grafana/grafana/pkg/services/secrets/database"
@@ -236,6 +237,7 @@ var wireBasicSet = wire.NewSet(
wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)),
authinfoimpl.ProvideStore,
datasourceproxy.ProvideService,
sort.ProvideService,
search.ProvideService,
searchV2.ProvideService,
searchV2.ProvideSearchHTTPService,
@@ -110,7 +110,7 @@ func ProvideFolderPermissions(
// if the folder is not found, this may be on the create path,
// where the write path to legacy will then go through the read
// path and try to read from both legacy & unified before it exists on both
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) && errors.Is(err, dashboards.ErrFolderNotFound) {
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) && errors.Is(err, dashboards.ErrFolderNotFound) {
_, err = folderService.GetLegacy(ctx, &folder.GetFolderQuery{
UID: &resourceID,
OrgID: orgID,
@@ -16,6 +16,7 @@ import (
"github.com/grafana/grafana/pkg/services/licensing/licensingtest"
"github.com/grafana/grafana/pkg/services/org/orgimpl"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/supportbundles/bundleregistry"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
@@ -48,7 +49,7 @@ func ProvideFolderPermissions(
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
fService := folderimpl.ProvideService(
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore,
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
acSvc := acimpl.ProvideOSSService(
cfg, acdb.ProvideService(sqlStore), actionSets, localcache.ProvideService(),
@@ -24,6 +24,7 @@ import (
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
@@ -51,9 +52,9 @@ func TestIntegrationAuthorize(t *testing.T) {
ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures())
folderSvc := folderimpl.ProvideService(
fStore, accesscontrolmock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore,
nil, sql, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
nil, sql, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
dashSvc, err := dashboardsservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), accesscontrolmock.NewMockedPermissionsService(),
ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService())
ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService())
require.NoError(t, err)
dashSvc.RegisterDashboardPermissions(accesscontrolmock.NewMockedPermissionsService())
@@ -29,6 +29,7 @@ import (
"github.com/grafana/grafana/pkg/services/guardian"
alertingStore "github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
@@ -63,9 +64,9 @@ func TestIntegrationAnnotationListingWithRBAC(t *testing.T) {
ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures())
folderSvc := folderimpl.ProvideService(
fStore, accesscontrolmock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore,
nil, sql, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
nil, sql, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
dashSvc, err := dashboardsservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), accesscontrolmock.NewMockedPermissionsService(),
ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService())
ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService())
require.NoError(t, err)
dashSvc.RegisterDashboardPermissions(accesscontrolmock.NewMockedPermissionsService())
repo := ProvideService(sql, cfg, features, tagService, tracing.InitializeTracerForTest(), ruleStore, dashSvc)
@@ -246,9 +247,9 @@ func TestIntegrationAnnotationListingWithInheritedRBAC(t *testing.T) {
folderStore := folderimpl.ProvideDashboardFolderStore(sql)
folderSvc := folderimpl.ProvideService(
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore,
nil, sql, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
nil, sql, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
dashSvc, err := dashboardsservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, features, accesscontrolmock.NewMockedPermissionsService(),
ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService())
ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService())
require.NoError(t, err)
dashSvc.RegisterDashboardPermissions(accesscontrolmock.NewMockedPermissionsService())
cfg.AnnotationMaximumTagsLength = 60
@@ -57,7 +57,8 @@ import (
// making sure we only register metrics once into legacy registry
var registerIntoLegacyRegistryOnce sync.Once
func readCABundlePEM(path string, devMode bool) ([]byte, error) {
//nolint:unused
func _readCABundlePEM(path string, devMode bool) ([]byte, error) {
if devMode {
return nil, nil
}
@@ -128,8 +129,6 @@ func CreateAggregatorConfig(commandOptions *options.Options, sharedConfig generi
},
ExtraConfig: aggregatorapiserver.ExtraConfig{
DisableRemoteAvailableConditionController: true,
ProxyClientCertFile: commandOptions.KubeAggregatorOptions.ProxyClientCertFile,
ProxyClientKeyFile: commandOptions.KubeAggregatorOptions.ProxyClientKeyFile,
// NOTE: while ProxyTransport can be skipped in the configuration, it allows honoring
// DISABLE_HTTP2, HTTPS_PROXY and NO_PROXY env vars as needed
ProxyTransport: createProxyTransport(),
@@ -137,6 +136,13 @@ func CreateAggregatorConfig(commandOptions *options.Options, sharedConfig generi
},
}
if commandOptions.KubeAggregatorOptions.LegacyClientCertAuth {
// NOTE: the availability controller below is a bit different and uses the cert/key pair regardless
// of the legacy bool, this is because we are still using that for discovery requests
aggregatorConfig.ExtraConfig.ProxyClientCertFile = commandOptions.KubeAggregatorOptions.ProxyClientCertFile
aggregatorConfig.ExtraConfig.ProxyClientKeyFile = commandOptions.KubeAggregatorOptions.ProxyClientKeyFile
}
if err := commandOptions.KubeAggregatorOptions.ApplyTo(aggregatorConfig, commandOptions.RecommendedOptions.Etcd); err != nil {
return nil, err
}
@@ -152,10 +158,6 @@ func CreateAggregatorConfig(commandOptions *options.Options, sharedConfig generi
return NewConfig(aggregatorConfig, sharedInformerFactory, []builder.APIGroupBuilder{serviceAPIBuilder}, nil), nil
}
_, err = readCABundlePEM(commandOptions.KubeAggregatorOptions.APIServiceCABundleFile, commandOptions.ExtraOptions.DevMode)
if err != nil {
return nil, err
}
remoteServices, err := ReadRemoteServices(commandOptions.KubeAggregatorOptions.RemoteServicesFile)
if err != nil {
return nil, err
+3 -2
View File
@@ -22,6 +22,7 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/dashboard/legacysearcher"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/storage/unified/resource"
@@ -51,8 +52,8 @@ type k8sHandler struct {
}
func NewK8sHandler(dual dualwrite.Service, namespacer request.NamespaceMapper, gvr schema.GroupVersionResource,
restConfig func(context.Context) *rest.Config, dashStore dashboards.Store, userSvc user.Service, resourceClient resource.ResourceClient) K8sHandler {
legacySearcher := legacysearcher.NewDashboardSearchClient(dashStore)
restConfig func(context.Context) *rest.Config, dashStore dashboards.Store, userSvc user.Service, resourceClient resource.ResourceClient, sorter sort.Service) K8sHandler {
legacySearcher := legacysearcher.NewDashboardSearchClient(dashStore, sorter)
searchClient := resource.NewSearchClient(dual, gvr.GroupResource(), resourceClient, legacySearcher)
return &k8sHandler{
@@ -23,6 +23,7 @@ type KubeAggregatorOptions struct {
AlternateDNS []string
ProxyClientCertFile string
ProxyClientKeyFile string
LegacyClientCertAuth bool
RemoteServicesFile string
APIServiceCABundleFile string
}
@@ -46,6 +47,9 @@ func (o *KubeAggregatorOptions) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&o.ProxyClientKeyFile, "proxy-client-key-file", o.ProxyClientKeyFile,
"path to proxy client key file")
fs.BoolVar(&o.LegacyClientCertAuth, "legacy_client_cert_auth", true,
"whether to use legacy client cert auth")
}
func (o *KubeAggregatorOptions) Validate() []error {
@@ -12,7 +12,9 @@ import (
"github.com/grafana/authlib/types"
"github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel/attribute"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/grpcserver/interceptors"
@@ -75,7 +77,11 @@ func newAuthenticator(auth authn.Authenticator, tracer tracing.Tracer) intercept
info, err := auth.Authenticate(ctx, authn.NewGRPCTokenProvider(md))
if err != nil {
span.RecordError(err)
return ctx, err
if authn.IsUnauthenticatedErr(err) {
return nil, status.Error(codes.Unauthenticated, err.Error())
}
return ctx, status.Error(codes.Internal, err.Error())
}
// FIXME: Add attribute with service subject once https://github.com/grafana/authlib/issues/139 is closed.
@@ -25,6 +25,7 @@ import (
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/org/orgimpl"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
@@ -303,7 +304,7 @@ func TestIntegrationDashboardInheritedFolderRBAC(t *testing.T) {
folderStore := folderimpl.ProvideStore(sqlStore)
folderSvc := folderimpl.ProvideService(
folderStore, mock.New(), bus.ProvideBus(tracer), dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(sqlStore),
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
parentUID := ""
for i := 0; ; i++ {
@@ -23,6 +23,7 @@ import (
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/search/model"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
@@ -929,7 +930,7 @@ func TestIntegrationFindDashboardsByTitle(t *testing.T) {
fStore := folderimpl.ProvideStore(sqlStore)
folderServiceWithFlagOn := folderimpl.ProvideService(
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore,
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
user := &user.SignedInUser{
OrgID: 1,
@@ -1049,7 +1050,7 @@ func TestIntegrationFindDashboardsByFolder(t *testing.T) {
folderServiceWithFlagOn := folderimpl.ProvideService(
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore,
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
user := &user.SignedInUser{
OrgID: 1,
@@ -49,6 +49,7 @@ import (
"github.com/grafana/grafana/pkg/services/publicdashboards"
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/search/model"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
"github.com/grafana/grafana/pkg/services/store/entity"
"github.com/grafana/grafana/pkg/services/user"
@@ -96,9 +97,9 @@ func ProvideDashboardServiceImpl(
ac accesscontrol.AccessControl, folderSvc folder.Service, fStore folder.Store, r prometheus.Registerer,
restConfigProvider apiserver.RestConfigProvider, userService user.Service,
quotaService quota.Service, orgService org.Service, publicDashboardService publicdashboards.ServiceWrapper,
resourceClient resource.ResourceClient, dual dualwrite.Service,
resourceClient resource.ResourceClient, dual dualwrite.Service, sorter sort.Service,
) (*DashboardServiceImpl, error) {
k8sHandler := client.NewK8sHandler(dual, request.GetNamespaceMapper(cfg), dashboardv0alpha1.DashboardResourceInfo.GroupVersionResource(), restConfigProvider.GetRestConfig, dashboardStore, userService, resourceClient)
k8sHandler := client.NewK8sHandler(dual, request.GetNamespaceMapper(cfg), dashboardv0alpha1.DashboardResourceInfo.GroupVersionResource(), restConfigProvider.GetRestConfig, dashboardStore, userService, resourceClient, sorter)
dashSvc := &DashboardServiceImpl{
cfg: cfg,
@@ -152,7 +153,7 @@ func (dr *DashboardServiceImpl) getPermissionsService(isFolder bool) accesscontr
}
func (dr *DashboardServiceImpl) Count(ctx context.Context, scopeParams *quota.ScopeParameters) (*quota.Map, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
u := &quota.Map{}
orgs, err := dr.orgService.Search(ctx, &org.SearchOrgsQuery{})
if err != nil {
@@ -188,7 +189,7 @@ func (dr *DashboardServiceImpl) Count(ctx context.Context, scopeParams *quota.Sc
}
func (dr *DashboardServiceImpl) CountDashboardsInOrg(ctx context.Context, orgID int64) (int64, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
resp, err := dr.k8sclient.GetStats(ctx, orgID)
if err != nil {
return 0, err
@@ -226,7 +227,7 @@ func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) {
}
func (dr *DashboardServiceImpl) GetProvisionedDashboardData(ctx context.Context, name string) ([]*dashboards.DashboardProvisioning, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
orgs, err := dr.orgService.Search(ctx, &org.SearchOrgsQuery{})
if err != nil {
return nil, err
@@ -267,7 +268,7 @@ func (dr *DashboardServiceImpl) GetProvisionedDashboardData(ctx context.Context,
}
func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardID(ctx context.Context, dashboardID int64) (*dashboards.DashboardProvisioning, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
// if dashboard id is 0, it is a new dashboard
if dashboardID == 0 {
return nil, nil
@@ -301,7 +302,7 @@ func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardID(ctx con
}
func (dr *DashboardServiceImpl) GetProvisionedDashboardDataByDashboardUID(ctx context.Context, orgID int64, dashboardUID string) (*dashboards.DashboardProvisioning, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
if dashboardUID == "" {
return nil, nil
}
@@ -368,7 +369,7 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d
}
// Validate folder
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
folder, err := dr.folderService.Get(ctx, &folder.GetFolderQuery{
OrgID: dash.OrgID,
UID: &dash.FolderUID,
@@ -555,7 +556,7 @@ func (dr *DashboardServiceImpl) ValidateDashboardBeforeSave(ctx context.Context,
}
func (dr *DashboardServiceImpl) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *dashboards.DeleteOrphanedProvisionedDashboardsCommand) error {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
// check each org for orphaned provisioned dashboards
orgs, err := dr.orgService.Search(ctx, &org.SearchOrgsQuery{})
if err != nil {
@@ -673,7 +674,7 @@ func (dr *DashboardServiceImpl) SaveProvisionedDashboard(ctx context.Context, dt
}
var dash *dashboards.Dashboard
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
// save the dashboard but then do NOT return
// we want to save the provisioning data to the dashboard_provisioning table still
// to ensure we can safely rollback to mode2 if needed
@@ -748,7 +749,7 @@ func (dr *DashboardServiceImpl) SaveDashboard(ctx context.Context, dto *dashboar
}
func (dr *DashboardServiceImpl) saveDashboard(ctx context.Context, cmd *dashboards.SaveDashboardCommand) (*dashboards.Dashboard, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return dr.saveDashboardThroughK8s(ctx, cmd, cmd.OrgID)
}
@@ -756,7 +757,7 @@ func (dr *DashboardServiceImpl) saveDashboard(ctx context.Context, cmd *dashboar
}
func (dr *DashboardServiceImpl) GetSoftDeletedDashboard(ctx context.Context, orgID int64, uid string) (*dashboards.Dashboard, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return dr.getDashboardThroughK8s(ctx, &dashboards.GetDashboardQuery{OrgID: orgID, UID: uid, IncludeDeleted: true})
}
@@ -815,7 +816,7 @@ func (dr *DashboardServiceImpl) SoftDeleteDashboard(ctx context.Context, orgID i
return fmt.Errorf("feature flag %s is not enabled", featuremgmt.FlagDashboardRestore)
}
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
// deletes in unistore are soft deletes, so we can just delete in the same way
return dr.deleteDashboardThroughK8s(ctx, &dashboards.DeleteDashboardCommand{OrgID: orgID, UID: dashboardUID}, true)
}
@@ -836,7 +837,7 @@ func (dr *DashboardServiceImpl) DeleteDashboard(ctx context.Context, dashboardId
// DeleteAllDashboards will delete all dashboards within a given org.
func (dr *DashboardServiceImpl) DeleteAllDashboards(ctx context.Context, orgId int64) error {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return dr.deleteAllDashboardThroughK8s(ctx, orgId)
}
@@ -859,7 +860,7 @@ func (dr *DashboardServiceImpl) deleteDashboard(ctx context.Context, dashboardId
cmd := &dashboards.DeleteDashboardCommand{OrgID: orgId, ID: dashboardId, UID: dashboardUID}
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
err := dr.deleteDashboardThroughK8s(ctx, cmd, validateProvisionedDashboard)
if err != nil {
return err
@@ -924,7 +925,7 @@ func (dr *DashboardServiceImpl) ImportDashboard(ctx context.Context, dto *dashbo
// UnprovisionDashboard removes info about dashboard being provisioned. Used after provisioning configs are changed
// and provisioned dashboards are left behind but not deleted.
func (dr *DashboardServiceImpl) UnprovisionDashboard(ctx context.Context, dashboardId int64) error {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
orgs, err := dr.orgService.Search(ctx, &org.SearchOrgsQuery{})
if err != nil {
return err
@@ -957,7 +958,7 @@ func (dr *DashboardServiceImpl) UnprovisionDashboard(ctx context.Context, dashbo
}
func (dr *DashboardServiceImpl) GetDashboardsByPluginID(ctx context.Context, query *dashboards.GetDashboardsByPluginIDQuery) ([]*dashboards.Dashboard, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
dashs, err := dr.searchDashboardsThroughK8s(ctx, &dashboards.FindPersistedDashboardsQuery{
OrgId: query.OrgID,
ProvisionedRepo: dashboard.PluginIDRepoName,
@@ -1055,7 +1056,7 @@ func (dr *DashboardServiceImpl) setDefaultFolderPermissions(ctx context.Context,
}
func (dr *DashboardServiceImpl) GetDashboard(ctx context.Context, query *dashboards.GetDashboardQuery) (*dashboards.Dashboard, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return dr.getDashboardThroughK8s(ctx, query)
}
@@ -1063,7 +1064,7 @@ func (dr *DashboardServiceImpl) GetDashboard(ctx context.Context, query *dashboa
}
func (dr *DashboardServiceImpl) GetDashboardUIDByID(ctx context.Context, query *dashboards.GetDashboardRefByIDQuery) (*dashboards.DashboardRef, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
requester, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
@@ -1089,7 +1090,7 @@ func (dr *DashboardServiceImpl) GetDashboardUIDByID(ctx context.Context, query *
}
func (dr *DashboardServiceImpl) GetDashboards(ctx context.Context, query *dashboards.GetDashboardsQuery) ([]*dashboards.Dashboard, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
if query.OrgID == 0 {
requester, err := identity.GetRequester(ctx)
if err != nil {
@@ -1237,7 +1238,7 @@ func (dr *DashboardServiceImpl) FindDashboards(ctx context.Context, query *dashb
}(time.Now())
}
if dr.features.IsEnabled(ctx, featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabled(ctx, featuremgmt.FlagKubernetesClientDashboardsFolders) {
if query.OrgId == 0 {
requester, err := identity.GetRequester(ctx)
if err != nil {
@@ -1319,7 +1320,7 @@ func (dr *DashboardServiceImpl) SearchDashboards(ctx context.Context, query *das
}
func (dr *DashboardServiceImpl) GetAllDashboards(ctx context.Context) ([]*dashboards.Dashboard, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
requester, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
@@ -1331,7 +1332,7 @@ func (dr *DashboardServiceImpl) GetAllDashboards(ctx context.Context) ([]*dashbo
}
func (dr *DashboardServiceImpl) GetAllDashboardsByOrgId(ctx context.Context, orgID int64) ([]*dashboards.Dashboard, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return dr.listDashboardsThroughK8s(ctx, orgID)
}
@@ -1405,7 +1406,7 @@ func makeQueryResult(query *dashboards.FindPersistedDashboardsQuery, res []dashb
}
func (dr *DashboardServiceImpl) GetDashboardTags(ctx context.Context, query *dashboards.GetDashboardTagsQuery) ([]*dashboards.DashboardTagCloudItem, error) {
if dr.features.IsEnabled(ctx, featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabled(ctx, featuremgmt.FlagKubernetesClientDashboardsFolders) {
res, err := dr.k8sclient.Search(ctx, query.OrgID, &resource.ResourceSearchRequest{
Facet: map[string]*resource.ResourceSearchRequest_Facet{
"tags": {
@@ -1437,7 +1438,7 @@ func (dr *DashboardServiceImpl) GetDashboardTags(ctx context.Context, query *das
}
func (dr DashboardServiceImpl) CountInFolders(ctx context.Context, orgID int64, folderUIDs []string, u identity.Requester) (int64, error) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
dashs, err := dr.searchDashboardsThroughK8s(ctx, &dashboards.FindPersistedDashboardsQuery{
OrgId: orgID,
FolderUIDs: folderUIDs,
@@ -27,6 +27,7 @@ import (
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/publicdashboards"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
@@ -898,7 +899,9 @@ func permissionScenario(t *testing.T, desc string, canSave bool, fn permissionSc
nil,
tracer,
nil,
dualwrite.ProvideTestService())
dualwrite.ProvideTestService(),
sort.ProvideService(),
)
dashboardPermissions := accesscontrolmock.NewMockedPermissionsService()
dashboardService, err := ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore,
@@ -915,6 +918,7 @@ func permissionScenario(t *testing.T, desc string, canSave bool, fn permissionSc
nil,
nil,
dualwrite.ProvideTestService(),
sort.ProvideService(),
)
dashboardService.RegisterDashboardPermissions(dashboardPermissions)
require.NoError(t, err)
@@ -988,7 +992,9 @@ func callSaveWithResult(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSt
nil,
tracer,
nil,
dualwrite.ProvideTestService())
dualwrite.ProvideTestService(),
sort.ProvideService(),
)
dashboardPermissions := accesscontrolmock.NewMockedPermissionsService()
dashboardPermissions.On("SetPermissions",
mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
@@ -1007,6 +1013,7 @@ func callSaveWithResult(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSt
nil,
nil,
dualwrite.ProvideTestService(),
sort.ProvideService(),
)
require.NoError(t, err)
service.RegisterDashboardPermissions(dashboardPermissions)
@@ -1041,7 +1048,9 @@ func callSaveWithError(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSto
nil,
tracer,
nil,
dualwrite.ProvideTestService())
dualwrite.ProvideTestService(),
sort.ProvideService(),
)
service, err := ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore,
featuremgmt.WithFeatures(),
@@ -1057,6 +1066,7 @@ func callSaveWithError(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSto
nil,
nil,
dualwrite.ProvideTestService(),
sort.ProvideService(),
)
require.NoError(t, err)
service.RegisterDashboardPermissions(accesscontrolmock.NewMockedPermissionsService())
@@ -1110,7 +1120,9 @@ func saveTestDashboard(t *testing.T, title string, orgID int64, folderUID string
nil,
tracer,
nil,
dualwrite.ProvideTestService())
dualwrite.ProvideTestService(),
sort.ProvideService(),
)
service, err := ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore,
features,
@@ -1126,6 +1138,7 @@ func saveTestDashboard(t *testing.T, title string, orgID int64, folderUID string
nil,
nil,
dualwrite.ProvideTestService(),
sort.ProvideService(),
)
require.NoError(t, err)
service.RegisterDashboardPermissions(dashboardPermissions)
@@ -1185,7 +1198,9 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore db.DB) *da
nil,
tracer,
nil,
dualwrite.ProvideTestService())
dualwrite.ProvideTestService(),
sort.ProvideService(),
)
folderPermissions.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
service, err := ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore,
@@ -1202,6 +1217,7 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore db.DB) *da
nil,
nil,
dualwrite.ProvideTestService(),
sort.ProvideService(),
)
require.NoError(t, err)
service.RegisterDashboardPermissions(accesscontrolmock.NewMockedPermissionsService())
@@ -265,7 +265,7 @@ func TestDashboardService(t *testing.T) {
func setupK8sDashboardTests(service *DashboardServiceImpl) (context.Context, *client.MockK8sHandler) {
mockCli := new(client.MockK8sHandler)
service.k8sclient = mockCli
service.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesCliDashboards)
service.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders)
ctx := context.Background()
userCtx := &user.SignedInUser{UserID: 1, OrgID: 1}
@@ -1096,7 +1096,13 @@ func TestSaveProvisionedDashboard(t *testing.T) {
service := &DashboardServiceImpl{
cfg: setting.NewCfg(),
dashboardStore: &fakeStore,
log: log.NewNopLogger(),
folderService: &foldertest.FakeService{
ExpectedFolder: &folder.Folder{
ID: 0,
UID: "general",
},
},
log: log.NewNopLogger(),
}
origNewDashboardGuardian := guardian.New
@@ -1160,6 +1166,9 @@ func TestSaveDashboard(t *testing.T) {
service := &DashboardServiceImpl{
cfg: setting.NewCfg(),
dashboardStore: &fakeStore,
folderService: &foldertest.FakeService{
ExpectedFolder: &folder.Folder{},
},
}
origNewDashboardGuardian := guardian.New
@@ -23,6 +23,7 @@ import (
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
"github.com/grafana/grafana/pkg/services/folder/foldertest"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/secrets/database"
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
@@ -119,6 +120,7 @@ func TestValidateDashboardExists(t *testing.T) {
nil,
nil,
dualwrite.ProvideTestService(),
sort.ProvideService(),
)
require.NoError(t, err)
s := ProvideService(dsStore, secretsService, dashSvc)
@@ -21,6 +21,7 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards"
dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
@@ -42,7 +43,7 @@ type Service struct {
}
func ProvideService(cfg *setting.Cfg, db db.DB, dashboardService dashboards.DashboardService, dashboardStore dashboards.Store, features featuremgmt.FeatureToggles,
restConfigProvider apiserver.RestConfigProvider, userService user.Service, unified resource.ResourceClient, dual dualwrite.Service) dashver.Service {
restConfigProvider apiserver.RestConfigProvider, userService user.Service, unified resource.ResourceClient, dual dualwrite.Service, sorter sort.Service) dashver.Service {
return &Service{
cfg: cfg,
store: &sqlStore{
@@ -58,6 +59,7 @@ func ProvideService(cfg *setting.Cfg, db db.DB, dashboardService dashboards.Dash
dashboardStore,
userService,
unified,
sorter,
),
dashSvc: dashboardService,
log: log.New("dashboard-version"),
@@ -85,7 +87,7 @@ func (s *Service) Get(ctx context.Context, query *dashver.GetDashboardVersionQue
query.DashboardID = id
}
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
version, err := s.getHistoryThroughK8s(ctx, query.OrgID, query.DashboardUID, query.Version)
if err != nil {
return nil, err
@@ -156,7 +158,7 @@ func (s *Service) List(ctx context.Context, query *dashver.ListDashboardVersions
query.Limit = 1000
}
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
versions, err := s.listHistoryThroughK8s(
ctx,
query.OrgID,
@@ -44,7 +44,7 @@ func TestDashboardVersionService(t *testing.T) {
dashboardVersionService := Service{dashSvc: dashboardService, features: featuremgmt.WithFeatures()}
mockCli := new(client.MockK8sHandler)
dashboardVersionService.k8sclient = mockCli
dashboardVersionService.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesCliDashboards)
dashboardVersionService.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders)
dashboardService.On("GetDashboardUIDByID", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardRefByIDQuery")).Return(&dashboards.DashboardRef{UID: "uid"}, nil)
mockCli.On("GetUserFromMeta", mock.Anything, "user:1").Return(&user.User{ID: 1}, nil)
@@ -232,7 +232,7 @@ func TestListDashboardVersions(t *testing.T) {
dashboardVersionService := Service{dashSvc: dashboardService, features: featuremgmt.WithFeatures()}
mockCli := new(client.MockK8sHandler)
dashboardVersionService.k8sclient = mockCli
dashboardVersionService.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesCliDashboards)
dashboardVersionService.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders)
dashboardService.On("GetDashboardUIDByID", mock.Anything,
mock.AnythingOfType("*dashboards.GetDashboardRefByIDQuery")).
+3 -9
View File
@@ -680,12 +680,6 @@ var (
Owner: grafanaAppPlatformSquad,
FrontendOnly: true,
},
{
Name: "kubernetesCliDashboards",
Description: "Use the k8s client to retrieve dashboards internally",
Stage: FeatureStageExperimental,
Owner: grafanaAppPlatformSquad,
},
{
Name: "kubernetesRestore",
Description: "Allow restoring objects in k8s",
@@ -693,10 +687,10 @@ var (
Owner: grafanaAppPlatformSquad,
},
{
Name: "kubernetesFoldersServiceV2",
Description: "Use the Folders Service V2, and route Folder Service requests to k8s",
Name: "kubernetesClientDashboardsFolders",
Description: "Route the folder and dashboard service requests to k8s",
Stage: FeatureStageExperimental,
Owner: grafanaSearchAndStorageSquad,
Owner: grafanaAppPlatformSquad,
},
{
Name: "datasourceQueryTypes",
+1 -3
View File
@@ -360,7 +360,6 @@ appPlatformAccessTokens,2024-09-05T16:18:44Z,2024-10-14T10:47:18Z,d5ebaa0ef92ede
appSidecar,2024-09-09T12:45:05Z,,5e2ac24890906e5070323d87730dd78a4f885963,Andrej Ocenas
vizActions,2024-09-09T14:11:55Z,,af48d3db1eb2d8681843f5997e50fea5e5ea3096,Adela Almasan
groupAttributeSync,2024-09-09T15:29:43Z,,6ded6a8872204a818b3795dc733cc5fe5db066a0,Aaron Godin
kubernetesFolders,2024-09-10T09:22:08Z,,b12a29a1dac8b9aec4a99be08e1665939cb27dc5,Arati R.
alertingFilterV2,2024-09-11T11:29:26Z,,90ee52e8d9c14237f8a57b622c0def7512e657cd,Gilles De Mey
improvedExternalSessionHandling,2024-09-17T10:54:39Z,,41cd0f51800d4849345fc0980ca4173967fc8e9e,Misi
datasourceAPIServers,2024-09-19T08:28:27Z,,f21a5987a22bcdb596d6a258d2960e4151348b63,Ryan McKinley
@@ -407,7 +406,6 @@ feedbackButton,2024-12-02T17:08:15Z,,8a1b89a5ebb847f6b29e92dcea796f00c30431d6,Mi
elasticsearchCrossClusterSearch,2024-12-12T22:20:04Z,,b3a12f486eba69e20dd7ff3a3d4dd065ede7a99f,Isabella Siu
unifiedHistory,2024-12-13T10:41:18Z,,aac62c89dae1092836a91d1b6ae6bd7127fe676a,Laura Fernández
lokiLabelNamesQueryApi,2024-12-13T14:31:41Z,,5ac7443fcec0db412d3333044a82c2c26b5aece7,Sven Grossmann
kubernetesCliDashboards,2024-12-13T22:55:43Z,,8f6e9f8ed0a5024a510cc337c9f1e6972bfb23d4,Stephanie Hingtgen
useV2DashboardsAPI,2024-12-17T21:17:09Z,,070f0e4457c5967102ef157197073dc2662f6fb8,Dominik Prokop
investigationsBackend,2024-12-18T08:31:03Z,,f46c07aba7b6faccd2ecafc83051d1410cacc867,Jackson Coelho
unifiedStorageSearchSprinkles,2024-12-18T17:00:54Z,,4837585cab0fd84184a8c6f5d6891f442a2b95f1,owensmallwood
@@ -421,9 +419,9 @@ improvedExternalSessionHandlingSAML,2025-01-09T17:02:49Z,,c52ec21c75ab72c2f7d282
teamHttpHeadersMimir,2025-01-13T10:42:47Z,,04acbcdef23f673bd6bbfdbbece29c9769ce155a,Eric Leijonmarck
ABTestFeatureToggleA,2025-01-13T21:13:13Z,,009d7f42b3d09b3a6be1f00f07314e2b25af7ebc,Nathan Marrs
ABTestFeatureToggleB,2025-01-13T21:13:13Z,,009d7f42b3d09b3a6be1f00f07314e2b25af7ebc,Nathan Marrs
kubernetesFoldersServiceV2,2025-01-13T21:15:35Z,,766d645d827f5e6e0872ae30e5fe23226ae85785,maicon
queryLibraryDashboards,2025-01-14T11:01:15Z,,740cd22fe51a3543c182857f31fc97fd42263306,Ashley Harrison
elasticsearchImprovedParsing,2025-01-15T17:05:54Z,,bab55a4cb84f2ba57838f96a492ab9aa7f307957,Adam Yeats
grafanaAdvisor,2025-01-20T10:08:00Z,,c1364d6be6f552203ba786f17a89664304b89247,Andres Martinez Gotor
datasourceConnectionsTab,2025-01-21T17:39:48Z,,97d8f68b705f9949493079d1833abfe80e7b48f3,Syerikjan Kh
unifiedStorageSearchPermissionFiltering,2025-01-22T11:38:37Z,,dd483fc17fa4a2931848e3574cfc31ea6f6530d9,owensmallwood
kubernetesClientDashboardsFolders,2025-02-18T21:15:35Z,,766d645d827f5e6e0872ae30e5fe23226ae85785,Stephanie Hingtgen
1 #name created deleted hash author
360 appSidecar 2024-09-09T12:45:05Z 5e2ac24890906e5070323d87730dd78a4f885963 Andrej Ocenas
361 vizActions 2024-09-09T14:11:55Z af48d3db1eb2d8681843f5997e50fea5e5ea3096 Adela Almasan
362 groupAttributeSync 2024-09-09T15:29:43Z 6ded6a8872204a818b3795dc733cc5fe5db066a0 Aaron Godin
kubernetesFolders 2024-09-10T09:22:08Z b12a29a1dac8b9aec4a99be08e1665939cb27dc5 Arati R.
363 alertingFilterV2 2024-09-11T11:29:26Z 90ee52e8d9c14237f8a57b622c0def7512e657cd Gilles De Mey
364 improvedExternalSessionHandling 2024-09-17T10:54:39Z 41cd0f51800d4849345fc0980ca4173967fc8e9e Misi
365 datasourceAPIServers 2024-09-19T08:28:27Z f21a5987a22bcdb596d6a258d2960e4151348b63 Ryan McKinley
406 elasticsearchCrossClusterSearch 2024-12-12T22:20:04Z b3a12f486eba69e20dd7ff3a3d4dd065ede7a99f Isabella Siu
407 unifiedHistory 2024-12-13T10:41:18Z aac62c89dae1092836a91d1b6ae6bd7127fe676a Laura Fernández
408 lokiLabelNamesQueryApi 2024-12-13T14:31:41Z 5ac7443fcec0db412d3333044a82c2c26b5aece7 Sven Grossmann
kubernetesCliDashboards 2024-12-13T22:55:43Z 8f6e9f8ed0a5024a510cc337c9f1e6972bfb23d4 Stephanie Hingtgen
409 useV2DashboardsAPI 2024-12-17T21:17:09Z 070f0e4457c5967102ef157197073dc2662f6fb8 Dominik Prokop
410 investigationsBackend 2024-12-18T08:31:03Z f46c07aba7b6faccd2ecafc83051d1410cacc867 Jackson Coelho
411 unifiedStorageSearchSprinkles 2024-12-18T17:00:54Z 4837585cab0fd84184a8c6f5d6891f442a2b95f1 owensmallwood
419 teamHttpHeadersMimir 2025-01-13T10:42:47Z 04acbcdef23f673bd6bbfdbbece29c9769ce155a Eric Leijonmarck
420 ABTestFeatureToggleA 2025-01-13T21:13:13Z 009d7f42b3d09b3a6be1f00f07314e2b25af7ebc Nathan Marrs
421 ABTestFeatureToggleB 2025-01-13T21:13:13Z 009d7f42b3d09b3a6be1f00f07314e2b25af7ebc Nathan Marrs
kubernetesFoldersServiceV2 2025-01-13T21:15:35Z 766d645d827f5e6e0872ae30e5fe23226ae85785 maicon
422 queryLibraryDashboards 2025-01-14T11:01:15Z 740cd22fe51a3543c182857f31fc97fd42263306 Ashley Harrison
423 elasticsearchImprovedParsing 2025-01-15T17:05:54Z bab55a4cb84f2ba57838f96a492ab9aa7f307957 Adam Yeats
424 grafanaAdvisor 2025-01-20T10:08:00Z c1364d6be6f552203ba786f17a89664304b89247 Andres Martinez Gotor
425 datasourceConnectionsTab 2025-01-21T17:39:48Z 97d8f68b705f9949493079d1833abfe80e7b48f3 Syerikjan Kh
426 unifiedStorageSearchPermissionFiltering 2025-01-22T11:38:37Z dd483fc17fa4a2931848e3574cfc31ea6f6530d9 owensmallwood
427 kubernetesClientDashboardsFolders 2025-02-18T21:15:35Z 766d645d827f5e6e0872ae30e5fe23226ae85785 Stephanie Hingtgen
+1 -2
View File
@@ -89,9 +89,8 @@ formatString,GA,@grafana/dataviz-squad,false,false,true
kubernetesPlaylists,GA,@grafana/grafana-app-platform-squad,false,true,false
kubernetesSnapshots,experimental,@grafana/grafana-app-platform-squad,false,true,false
kubernetesDashboards,experimental,@grafana/grafana-app-platform-squad,false,false,true
kubernetesCliDashboards,experimental,@grafana/grafana-app-platform-squad,false,false,false
kubernetesRestore,experimental,@grafana/grafana-app-platform-squad,false,false,false
kubernetesFoldersServiceV2,experimental,@grafana/search-and-storage,false,false,false
kubernetesClientDashboardsFolders,experimental,@grafana/grafana-app-platform-squad,false,false,false
datasourceQueryTypes,experimental,@grafana/grafana-app-platform-squad,false,true,false
queryService,experimental,@grafana/grafana-app-platform-squad,false,true,false
queryServiceRewrite,experimental,@grafana/grafana-app-platform-squad,false,true,false
1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
89 kubernetesPlaylists GA @grafana/grafana-app-platform-squad false true false
90 kubernetesSnapshots experimental @grafana/grafana-app-platform-squad false true false
91 kubernetesDashboards experimental @grafana/grafana-app-platform-squad false false true
kubernetesCliDashboards experimental @grafana/grafana-app-platform-squad false false false
92 kubernetesRestore experimental @grafana/grafana-app-platform-squad false false false
93 kubernetesFoldersServiceV2 kubernetesClientDashboardsFolders experimental @grafana/search-and-storage @grafana/grafana-app-platform-squad false false false
94 datasourceQueryTypes experimental @grafana/grafana-app-platform-squad false true false
95 queryService experimental @grafana/grafana-app-platform-squad false true false
96 queryServiceRewrite experimental @grafana/grafana-app-platform-squad false true false
+3 -7
View File
@@ -367,17 +367,13 @@ const (
// Use the kubernetes API in the frontend for dashboards
FlagKubernetesDashboards = "kubernetesDashboards"
// FlagKubernetesCliDashboards
// Use the k8s client to retrieve dashboards internally
FlagKubernetesCliDashboards = "kubernetesCliDashboards"
// FlagKubernetesRestore
// Allow restoring objects in k8s
FlagKubernetesRestore = "kubernetesRestore"
// FlagKubernetesFoldersServiceV2
// Use the Folders Service V2, and route Folder Service requests to k8s
FlagKubernetesFoldersServiceV2 = "kubernetesFoldersServiceV2"
// FlagKubernetesClientDashboardsFolders
// Route the folder and dashboard service requests to k8s
FlagKubernetesClientDashboardsFolders = "kubernetesClientDashboardsFolders"
// FlagDatasourceQueryTypes
// Show query type endpoints in datasource API servers (currently hardcoded for testdata, expressions, and prometheus)
+7 -32
View File
@@ -2227,12 +2227,15 @@
},
{
"metadata": {
"name": "kubernetesCliDashboards",
"resourceVersion": "1733520389522",
"creationTimestamp": "2024-12-13T22:55:43Z"
"name": "kubernetesClientDashboardsFolders",
"resourceVersion": "1739913583714",
"creationTimestamp": "2025-02-18T21:15:35Z",
"annotations": {
"grafana.app/updatedTimestamp": "2025-02-18 21:19:43.714638 +0000 UTC"
}
},
"spec": {
"description": "Use the k8s client to retrieve dashboards internally",
"description": "Route the folder and dashboard service requests to k8s",
"stage": "experimental",
"codeowner": "@grafana/grafana-app-platform-squad"
}
@@ -2264,34 +2267,6 @@
"hideFromAdminPage": true
}
},
{
"metadata": {
"name": "kubernetesFolders",
"resourceVersion": "1725863636605",
"creationTimestamp": "2024-09-10T09:22:08Z",
"deletionTimestamp": "2025-01-22T20:49:15Z",
"annotations": {
"grafana.app/updatedTimestamp": "2024-09-09 06:33:56.605329 +0000 UTC"
}
},
"spec": {
"description": "Use the kubernetes API in the frontend for folders, and route /api/folders requests to k8s",
"stage": "experimental",
"codeowner": "@grafana/search-and-storage"
}
},
{
"metadata": {
"name": "kubernetesFoldersServiceV2",
"resourceVersion": "1735336477446",
"creationTimestamp": "2025-01-13T21:15:35Z"
},
"spec": {
"description": "Use the Folders Service V2, and route Folder Service requests to k8s",
"stage": "experimental",
"codeowner": "@grafana/search-and-storage"
}
},
{
"metadata": {
"name": "kubernetesPlaylists",
+16 -12
View File
@@ -37,6 +37,7 @@ import (
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/publicdashboards"
"github.com/grafana/grafana/pkg/services/search/model"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
@@ -89,6 +90,7 @@ func ProvideService(
tracer tracing.Tracer,
resourceClient resource.ResourceClient,
dual dualwrite.Service,
sorter sort.Service,
) *Service {
srv := &Service{
log: slog.Default().With("logger", "folder-service"),
@@ -111,7 +113,7 @@ func ProvideService(
ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(folderStore, srv))
ac.RegisterScopeAttributeResolver(dashboards.NewFolderUIDScopeResolver(srv))
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
k8sHandler := client.NewK8sHandler(
dual,
request.GetNamespaceMapper(cfg),
@@ -120,6 +122,7 @@ func ProvideService(
dashboardStore,
userService,
resourceClient,
sorter,
)
unifiedStore := ProvideUnifiedStore(k8sHandler, userService)
@@ -128,7 +131,7 @@ func ProvideService(
srv.k8sclient = k8sHandler
}
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
dashHandler := client.NewK8sHandler(
dual,
request.GetNamespaceMapper(cfg),
@@ -137,6 +140,7 @@ func ProvideService(
dashboardStore,
userService,
resourceClient,
sorter,
)
srv.dashboardK8sClient = dashHandler
}
@@ -191,7 +195,7 @@ func (s *Service) DBMigration(db db.DB) {
}
func (s *Service) SearchFolders(ctx context.Context, q folder.SearchFoldersQuery) (model.HitList, error) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
// TODO:
// - implement filtering by alerting folders and k6 folders (see the dashboards store `FindDashboards` method for reference)
// - implement fallback on search client in unistore to go to legacy store (will need to read from dashboard store)
@@ -202,7 +206,7 @@ func (s *Service) SearchFolders(ctx context.Context, q folder.SearchFoldersQuery
}
func (s *Service) GetFolders(ctx context.Context, q folder.GetFoldersQuery) ([]*folder.Folder, error) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.getFoldersFromApiServer(ctx, q)
}
return s.GetFoldersLegacy(ctx, q)
@@ -261,7 +265,7 @@ func (s *Service) GetFoldersLegacy(ctx context.Context, q folder.GetFoldersQuery
}
func (s *Service) Get(ctx context.Context, q *folder.GetFolderQuery) (*folder.Folder, error) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.getFromApiServer(ctx, q)
}
return s.GetLegacy(ctx, q)
@@ -402,7 +406,7 @@ func (s *Service) setFullpath(ctx context.Context, f *folder.Folder, user identi
}
func (s *Service) GetChildren(ctx context.Context, q *folder.GetChildrenQuery) ([]*folder.Folder, error) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.getChildrenFromApiServer(ctx, q)
}
return s.GetChildrenLegacy(ctx, q)
@@ -672,7 +676,7 @@ func (s *Service) deduplicateAvailableFolders(ctx context.Context, folders []*fo
}
func (s *Service) GetParents(ctx context.Context, q folder.GetParentsQuery) ([]*folder.Folder, error) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.getParentsFromApiServer(ctx, q)
}
return s.GetParentsLegacy(ctx, q)
@@ -705,7 +709,7 @@ func (s *Service) getFolderByTitle(ctx context.Context, orgID int64, title strin
}
func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (*folder.Folder, error) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.createOnApiServer(ctx, cmd)
}
return s.CreateLegacy(ctx, cmd)
@@ -824,7 +828,7 @@ func (s *Service) CreateLegacy(ctx context.Context, cmd *folder.CreateFolderComm
}
func (s *Service) Update(ctx context.Context, cmd *folder.UpdateFolderCommand) (*folder.Folder, error) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.updateOnApiServer(ctx, cmd)
}
return s.UpdateLegacy(ctx, cmd)
@@ -963,7 +967,7 @@ func prepareForUpdate(dashFolder *dashboards.Dashboard, orgId int64, userId int6
}
func (s *Service) Delete(ctx context.Context, cmd *folder.DeleteFolderCommand) error {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.deleteFromApiServer(ctx, cmd)
}
return s.DeleteLegacy(ctx, cmd)
@@ -1086,7 +1090,7 @@ func (s *Service) legacyDelete(ctx context.Context, cmd *folder.DeleteFolderComm
}
func (s *Service) Move(ctx context.Context, cmd *folder.MoveFolderCommand) (*folder.Folder, error) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.moveOnApiServer(ctx, cmd)
}
return s.MoveLegacy(ctx, cmd)
@@ -1313,7 +1317,7 @@ func (s *Service) nestedFolderDelete(ctx context.Context, cmd *folder.DeleteFold
}
func (s *Service) GetDescendantCounts(ctx context.Context, q *folder.GetDescendantCountsQuery) (folder.DescendantCounts, error) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
return s.getDescendantCountsFromApiServer(ctx, q)
}
@@ -44,6 +44,7 @@ import (
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/publicdashboards"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/store/entity"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
@@ -68,7 +69,7 @@ func TestIntegrationProvideFolderService(t *testing.T) {
store := ProvideStore(db)
ProvideService(
store, ac, bus.ProvideBus(tracing.InitializeTracerForTest()),
nil, nil, nil, db, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
nil, nil, nil, db, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
require.Len(t, ac.Calls.RegisterAttributeScopeResolver, 2)
})
@@ -496,8 +497,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
})
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil)
dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuresFlagOn, folderPermissions, ac, serviceWithFlagOn, nestedFolderStore, nil,
client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil, dualwrite.ProvideTestService())
dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuresFlagOn, folderPermissions, ac, serviceWithFlagOn, nestedFolderStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil, dualwrite.ProvideTestService(), sort.ProvideService())
require.NoError(t, err)
dashSrv.RegisterDashboardPermissions(dashboardPermissions)
@@ -583,7 +583,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil)
dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuresFlagOff,
folderPermissions, ac, serviceWithFlagOff, nestedFolderStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil, dualwrite.ProvideTestService())
folderPermissions, ac, serviceWithFlagOff, nestedFolderStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil, dualwrite.ProvideTestService(), sort.ProvideService())
require.NoError(t, err)
dashSrv.RegisterDashboardPermissions(dashboardPermissions)
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOff, db, serviceWithFlagOff, dashSrv, ac, b)
@@ -726,8 +726,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
tc.service.store = nestedFolderStore
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil)
dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, tc.featuresFlag, folderPermissions, ac, tc.service, tc.service.store, nil,
client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil, dualwrite.ProvideTestService())
dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, tc.featuresFlag, folderPermissions, ac, tc.service, tc.service.store, nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil, dualwrite.ProvideTestService(), sort.ProvideService())
require.NoError(t, err)
dashSrv.RegisterDashboardPermissions(dashboardPermissions)
@@ -1521,6 +1520,7 @@ func TestIntegrationNestedFolderSharedWithMe(t *testing.T) {
nil,
nil,
dualwrite.ProvideTestService(),
sort.ProvideService(),
)
require.NoError(t, err)
dashboardService.RegisterDashboardPermissions(dashboardPermissions)
@@ -26,7 +26,6 @@ import (
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/search/model"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
"github.com/grafana/grafana/pkg/services/store/entity"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/search"
@@ -659,71 +658,43 @@ func (s *Service) deleteFromApiServer(ctx context.Context, cmd *folder.DeleteFol
return folder.ErrFolderNotEmpty.Errorf("folder contains %d alert rules", alertRulesInFolder)
}
// if dashboard restore is on we don't delete public dashboards, the hard delete will take care of it later
if !s.features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore) {
// We need a list of dashboard uids inside the folder to delete related dashboards & public dashboards
var dashboardUIDs []string
// we cannot use the dashboard service directly due to circular dependencies,
// so either use the search client if the feature is enabled or use the dashboard store
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
request := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Labels: []*resource.Requirement{},
Fields: []*resource.Requirement{
{
Key: resource.SEARCH_FIELD_FOLDER,
Operator: string(selection.In),
Values: folders,
},
},
// We need a list of dashboard uids inside the folder to delete related dashboards & public dashboards -
// we cannot use the dashboard service directly due to circular dependencies, so use the search client to get the dashboards
request := &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Labels: []*resource.Requirement{},
Fields: []*resource.Requirement{
{
Key: resource.SEARCH_FIELD_FOLDER,
Operator: string(selection.In),
Values: folders,
},
Limit: 100000}
},
},
Limit: 100000}
res, err := s.dashboardK8sClient.Search(ctx, cmd.OrgID, request)
if err != nil {
return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err)
}
res, err := s.dashboardK8sClient.Search(ctx, cmd.OrgID, request)
if err != nil {
return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err)
}
hits, err := dashboardsearch.ParseResults(res, 0)
if err != nil {
return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err)
}
dashboardUIDs = make([]string, len(hits.Hits))
for i, dashboard := range hits.Hits {
dashboardUIDs[i] = dashboard.Name
err = s.dashboardK8sClient.Delete(ctx, dashboard.Name, cmd.OrgID, metav1.DeleteOptions{})
if err != nil {
return folder.ErrInternal.Errorf("failed to delete child dashboard: %w", err)
}
}
} else {
dashes, err := s.dashboardStore.FindDashboards(ctx, &dashboards.FindPersistedDashboardsQuery{
SignedInUser: cmd.SignedInUser,
FolderUIDs: folders,
OrgId: cmd.OrgID,
Type: searchstore.TypeDashboard,
})
if err != nil {
return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err)
}
dashboardUIDs = make([]string, len(dashes))
for i, dashboard := range dashes {
dashboardUIDs[i] = dashboard.UID
err = s.dashboardStore.DeleteDashboard(ctx, &dashboards.DeleteDashboardCommand{
UID: dashboard.UID,
OrgID: cmd.OrgID,
})
if err != nil {
return folder.ErrInternal.Errorf("failed to delete child dashboard: %w", err)
}
}
}
// Delete all public dashboards in the folders
err = s.publicDashboardService.DeleteByDashboardUIDs(ctx, cmd.OrgID, dashboardUIDs)
hits, err := dashboardsearch.ParseResults(res, 0)
if err != nil {
return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err)
}
dashboardUIDs := make([]string, len(hits.Hits))
for i, dashboard := range hits.Hits {
dashboardUIDs[i] = dashboard.Name
err = s.dashboardK8sClient.Delete(ctx, dashboard.Name, cmd.OrgID, metav1.DeleteOptions{})
if err != nil {
return folder.ErrInternal.Errorf("failed to delete public dashboards: %w", err)
return folder.ErrInternal.Errorf("failed to delete child dashboard: %w", err)
}
}
// Delete all public dashboards in the folders
err = s.publicDashboardService.DeleteByDashboardUIDs(ctx, cmd.OrgID, dashboardUIDs)
if err != nil {
return folder.ErrInternal.Errorf("failed to delete public dashboards: %w", err)
}
}
err = s.unifiedStore.Delete(ctx, folders, cmd.OrgID)
@@ -36,6 +36,7 @@ import (
ngstore "github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/publicdashboards"
"github.com/grafana/grafana/pkg/services/search/model"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/services/user/usertest"
@@ -179,11 +180,11 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
}
featuresArr := []any{
featuremgmt.FlagKubernetesFoldersServiceV2}
featuremgmt.FlagKubernetesClientDashboardsFolders}
features := featuremgmt.WithFeatures(featuresArr...)
dashboardStore := dashboards.NewFakeDashboardStore(t)
k8sCli := client.NewK8sHandler(dualwrite.ProvideTestService(), request.GetNamespaceMapper(cfg), v0alpha1.FolderResourceInfo.GroupVersionResource(), restCfgProvider.GetRestConfig, dashboardStore, userService, nil)
k8sCli := client.NewK8sHandler(dualwrite.ProvideTestService(), request.GetNamespaceMapper(cfg), v0alpha1.FolderResourceInfo.GroupVersionResource(), restCfgProvider.GetRestConfig, dashboardStore, userService, nil, sort.ProvideService())
unifiedStore := ProvideUnifiedStore(k8sCli, userService)
ctx := context.Background()
@@ -206,6 +207,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
publicDashboardService := publicdashboards.NewFakePublicDashboardServiceWrapper(t)
fakeK8sClient := new(client.MockK8sHandler)
folderService := &Service{
log: slog.New(logtest.NewTestHandler(t)).With("logger", "test-folder-service"),
unifiedStore: unifiedStore,
@@ -216,7 +218,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
metrics: newFoldersMetrics(nil),
tracer: tracing.InitializeTracerForTest(),
k8sclient: k8sCli,
dashboardStore: dashboardStore,
dashboardK8sClient: fakeK8sClient,
publicDashboardService: publicDashboardService,
}
@@ -348,7 +350,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
})
t.Run("When deleting folder by uid should not return access denied error - ForceDeleteRules false", func(t *testing.T) {
dashboardStore.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{}, nil).Once()
fakeK8sClient.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{Results: &resource.ResourceTable{}}, nil).Once()
publicDashboardService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil)
err := folderService.Delete(ctx, &folder.DeleteFolderCommand{
@@ -362,6 +364,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
t.Run("When deleting folder by uid, expectedForceDeleteRules as false, and dashboard Restore turned on should not return access denied error", func(t *testing.T) {
folderService.features = featuremgmt.WithFeatures(append(featuresArr, featuremgmt.FlagDashboardRestore)...)
fakeK8sClient.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{Results: &resource.ResourceTable{}}, nil).Once()
expectedForceDeleteRules := false
err := folderService.Delete(ctx, &folder.DeleteFolderCommand{
@@ -375,6 +378,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
t.Run("When deleting folder by uid, expectedForceDeleteRules as true, and dashboard Restore turned on should not return access denied error", func(t *testing.T) {
folderService.features = featuremgmt.WithFeatures(append(featuresArr, featuremgmt.FlagDashboardRestore)...)
fakeK8sClient.On("Search", mock.Anything, mock.Anything, mock.Anything).Return(&resource.ResourceSearchResponse{Results: &resource.ResourceTable{}}, nil).Once()
expectedForceDeleteRules := true
err := folderService.Delete(ctx, &folder.DeleteFolderCommand{
@@ -526,7 +530,7 @@ func TestSearchFoldersFromApiServer(t *testing.T) {
}
service := Service{
k8sclient: fakeK8sClient,
features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesFoldersServiceV2),
features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders),
unifiedStore: folderStore,
}
user := &user.SignedInUser{OrgID: 1}
@@ -779,7 +783,7 @@ func TestDeleteFoldersFromApiServer(t *testing.T) {
dashboardStore: dashboardStore,
publicDashboardService: publicDashboardFakeService,
registry: make(map[string]folder.RegistryService),
features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesFoldersServiceV2),
features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesClientDashboardsFolders),
}
user := &user.SignedInUser{OrgID: 1}
ctx := identity.WithRequester(context.Background(), user)
@@ -798,52 +802,22 @@ func TestDeleteFoldersFromApiServer(t *testing.T) {
require.NoError(t, service.RegisterService(alertingStore))
t.Run("Should delete folder", func(t *testing.T) {
dashboardStore.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{}, nil).Once()
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{}).Return(nil).Once()
dashboardK8sclient.On("Search", mock.Anything, int64(1), mock.Anything).Return(&resource.ResourceSearchResponse{Results: &resource.ResourceTable{}}, nil).Once()
err := service.deleteFromApiServer(ctx, &folder.DeleteFolderCommand{
UID: "uid",
UID: "uid1",
OrgID: 1,
SignedInUser: user,
})
require.NoError(t, err)
})
t.Run("Should delete dashboards and public dashboards within the folder", func(t *testing.T) {
dashboardStore.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{
{
UID: "test",
OrgID: 1,
},
{
UID: "test2",
OrgID: 1,
},
}, nil).Once()
dashboardStore.On("DeleteDashboard", mock.Anything, &dashboards.DeleteDashboardCommand{
UID: "test",
OrgID: 1,
}).Return(nil).Once()
dashboardStore.On("DeleteDashboard", mock.Anything, &dashboards.DeleteDashboardCommand{
UID: "test2",
OrgID: 1,
}).Return(nil).Once()
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{"test", "test2"}).Return(nil).Once()
err := service.deleteFromApiServer(ctx, &folder.DeleteFolderCommand{
UID: "uid",
OrgID: 1,
SignedInUser: user,
})
require.NoError(t, err)
dashboardStore.AssertExpectations(t)
dashboardK8sclient.AssertExpectations(t)
publicDashboardFakeService.AssertExpectations(t)
})
// enable k8s ff for dashboards, retest
service.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesFoldersServiceV2, featuremgmt.FlagKubernetesCliDashboards)
t.Run("Should delete dashboards and public dashboards within the folder through k8s if the ff is enabled", func(t *testing.T) {
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{"uid1"}).Return(nil).Once()
dashboardK8sclient.On("Delete", mock.Anything, "uid1", int64(1), mock.Anything).Return(nil).Once()
t.Run("Should delete folders, dashboards, and public dashboards within the folder", func(t *testing.T) {
fakeFolderStore.ExpectedFolders = []*folder.Folder{{UID: "uid2", ID: 2}}
dashboardK8sclient.On("Delete", mock.Anything, "test", int64(1), mock.Anything).Return(nil).Once()
dashboardK8sclient.On("Delete", mock.Anything, "test2", int64(1), mock.Anything).Return(nil).Once()
dashboardK8sclient.On("Search", mock.Anything, int64(1), &resource.ResourceSearchRequest{
Options: &resource.ListOptions{
Labels: []*resource.Requirement{},
@@ -851,7 +825,7 @@ func TestDeleteFoldersFromApiServer(t *testing.T) {
{
Key: resource.SEARCH_FIELD_FOLDER,
Operator: string(selection.In),
Values: []string{"uid1"},
Values: []string{"uid", "uid2"},
},
},
},
@@ -870,11 +844,21 @@ func TestDeleteFoldersFromApiServer(t *testing.T) {
Rows: []*resource.ResourceTableRow{
{
Key: &resource.ResourceKey{
Name: "uid1",
Resource: "folder",
Name: "test",
Resource: "dashboard",
},
Cells: [][]byte{
[]byte("folder1"),
[]byte("uid"),
[]byte(""),
},
},
{
Key: &resource.ResourceKey{
Name: "test2",
Resource: "dashboard",
},
Cells: [][]byte{
[]byte("uid2"),
[]byte(""),
},
},
@@ -882,13 +866,14 @@ func TestDeleteFoldersFromApiServer(t *testing.T) {
},
TotalHits: 1,
}, nil).Once()
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{"test", "test2"}).Return(nil).Once()
err := service.deleteFromApiServer(ctx, &folder.DeleteFolderCommand{
UID: "uid1",
UID: "uid",
OrgID: 1,
SignedInUser: user,
})
require.NoError(t, err)
dashboardStore.AssertExpectations(t)
publicDashboardFakeService.AssertExpectations(t)
dashboardK8sclient.AssertExpectations(t)
})
}
+2 -2
View File
@@ -19,7 +19,7 @@ import (
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/libraryelements/model"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
"github.com/grafana/grafana/pkg/setting"
@@ -468,7 +468,7 @@ func (l *LibraryElementService) getAllLibraryElements(c context.Context, signedI
if err := folderFilter.writeFolderFilterSQL(false, &builder); err != nil {
return err
}
if query.SortDirection == search.SortAlphaDesc.Name {
if query.SortDirection == sort.SortAlphaDesc.Name {
builder.Write(" ORDER BY 1 DESC")
} else {
builder.Write(" ORDER BY 1 ASC")
@@ -11,7 +11,7 @@ import (
"github.com/grafana/grafana/pkg/kinds/librarypanel"
"github.com/grafana/grafana/pkg/services/libraryelements/model"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/services/search/sort"
)
func TestGetAllLibraryElements(t *testing.T) {
@@ -281,7 +281,7 @@ func TestGetAllLibraryElements(t *testing.T) {
err := sc.reqContext.Req.ParseForm()
require.NoError(t, err)
sc.reqContext.Req.Form.Add("sortDirection", search.SortAlphaDesc.Name)
sc.reqContext.Req.Form.Add("sortDirection", sort.SortAlphaDesc.Name)
resp = sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
@@ -41,6 +41,7 @@ import (
"github.com/grafana/grafana/pkg/services/org/orgimpl"
"github.com/grafana/grafana/pkg/services/publicdashboards"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
@@ -348,7 +349,7 @@ func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash
fStore := folderimpl.ProvideStore(sqlStore)
folderSvc := folderimpl.ProvideService(
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore,
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
_, err = folderSvc.Create(context.Background(), &folder.CreateFolderCommand{UID: folderUID, SignedInUser: &user, Title: folderUID + "-title"})
require.NoError(t, err)
service, err := dashboardservice.ProvideDashboardServiceImpl(
@@ -364,6 +365,7 @@ func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash
nil,
nil,
dualwrite.ProvideTestService(),
sort.ProvideService(),
)
require.NoError(t, err)
service.RegisterDashboardPermissions(dashboardPermissions)
@@ -387,7 +389,7 @@ func createFolder(t *testing.T, sc scenarioContext, title string, folderSvc fold
store := folderimpl.ProvideStore(sc.sqlStore)
folderSvc = folderimpl.ProvideService(
store, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore,
nil, sc.sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
nil, sc.sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
t.Logf("Creating folder with title %q and UID uid_for_%s", title, title)
}
ctx := identity.WithRequester(context.Background(), &sc.user)
@@ -451,12 +453,12 @@ func scenarioWithPanel(t *testing.T, desc string, fn func(t *testing.T, sc scena
fStore := folderimpl.ProvideStore(sqlStore)
folderSvc := folderimpl.ProvideService(
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore,
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
dashboardService, svcErr := dashboardservice.ProvideDashboardServiceImpl(
cfg, dashboardStore, folderStore,
features, folderPermissions, ac,
folderSvc, fStore,
nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(),
nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(),
)
require.NoError(t, svcErr)
dashboardService.RegisterDashboardPermissions(dashboardPermissions)
@@ -520,7 +522,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
publicDash.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil)
folderSvc := folderimpl.ProvideService(
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore,
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), publicDash, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), publicDash, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
alertStore, err := ngstore.ProvideDBStore(cfg, features, sqlStore, &foldertest.FakeService{}, &dashboards.FakeDashboardService{}, ac, bus.ProvideBus(tracing.InitializeTracerForTest()))
require.NoError(t, err)
err = folderSvc.RegisterService(alertStore)
@@ -529,7 +531,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
cfg, dashboardStore, folderStore,
features, folderPermissions, ac,
folderSvc, fStore,
nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(),
nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(),
)
require.NoError(t, dashSvcErr)
dashService.RegisterDashboardPermissions(dashboardPermissions)
@@ -36,6 +36,7 @@ import (
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/org/orgimpl"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
@@ -737,7 +738,7 @@ func createDashboard(t *testing.T, sqlStore db.DB, user *user.SignedInUser, dash
cfg, dashboardStore, folderStore,
features, acmock.NewMockedPermissionsService(), ac,
foldertest.NewFakeService(), folder.NewFakeStore(),
nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(),
nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(),
)
require.NoError(t, err)
service.RegisterDashboardPermissions(dashPermissionService)
@@ -759,7 +760,7 @@ func createFolder(t *testing.T, sc scenarioContext, title string) *folder.Folder
fStore := folderimpl.ProvideStore(sc.sqlStore)
s := folderimpl.ProvideService(
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore,
nil, sc.sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
nil, sc.sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
t.Logf("Creating folder with title and UID %q", title)
ctx := identity.WithRequester(context.Background(), sc.user)
@@ -835,7 +836,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
cfg, dashStore, folderStore,
features, acmock.NewMockedPermissionsService(), ac,
folderSvc, folder.NewFakeStore(),
nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(),
nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(),
)
require.NoError(t, err)
dashService.RegisterDashboardPermissions(dashPermissionService)
@@ -847,7 +848,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
folderService := folderimpl.ProvideService(
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore,
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), folderService, features, ac, dashService)
service := LibraryPanelService{
@@ -44,6 +44,7 @@ import (
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
"github.com/grafana/grafana/pkg/services/ngalert/store"
ngalertfakes "github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/secrets"
secrets_fakes "github.com/grafana/grafana/pkg/services/secrets/fakes"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
@@ -1922,7 +1923,7 @@ func createTestEnv(t *testing.T, testConfig string) testEnvironment {
fStore := folderimpl.ProvideStore(sqlStore)
folderService := folderimpl.ProvideService(
fStore, actest.FakeAccessControl{ExpectedEvaluate: true}, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore,
nil, sqlStore, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
nil, sqlStore, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
store := store.DBstore{
Logger: log,
SQLStore: sqlStore,
@@ -18,6 +18,7 @@ import (
"github.com/grafana/grafana/pkg/expr"
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
@@ -1607,7 +1608,7 @@ func TestProvisiongWithFullpath(t *testing.T) {
fStore := folderimpl.ProvideStore(sqlStore)
folderService := folderimpl.ProvideService(
fStore, ac, inProcBus, dashboardStore, folderStore,
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
ruleService := createAlertRuleService(t, folderService)
var orgID int64 = 1
+5 -2
View File
@@ -23,6 +23,7 @@ import (
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/org/orgimpl"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/setting"
@@ -33,7 +34,7 @@ func SetupFolderService(tb testing.TB, cfg *setting.Cfg, db db.DB, dashboardStor
tb.Helper()
fStore := folderimpl.ProvideStore(db)
return folderimpl.ProvideService(fStore, ac, bus, dashboardStore, folderStore, nil, db,
features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
}
func SetupDashboardService(tb testing.TB, sqlStore db.DB, fs *folderimpl.DashboardFolderStoreImpl, cfg *setting.Cfg) (*dashboardservice.DashboardServiceImpl, dashboards.Store) {
@@ -64,7 +65,9 @@ func SetupDashboardService(tb testing.TB, sqlStore db.DB, fs *folderimpl.Dashboa
cfg, dashboardStore, fs,
features, folderPermissions, ac,
foldertest.NewFakeService(), folder.NewFakeStore(),
nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(),
nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil,
dualwrite.ProvideTestService(),
sort.ProvideService(),
)
require.NoError(tb, err)
dashboardService.RegisterDashboardPermissions(dashboardPermissions)
@@ -23,6 +23,7 @@ import (
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
@@ -132,7 +133,7 @@ func TestDashboardFileReader(t *testing.T) {
folderStore := folderimpl.ProvideDashboardFolderStore(sql)
folderSvc := folderimpl.ProvideService(fStore, actest.FakeAccessControl{}, bus.ProvideBus(tracing.InitializeTracerForTest()),
dashStore, folderStore, nil, sql, featuremgmt.WithFeatures(),
supportbundlestest.NewFakeBundleService(), nil, cfgT, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
supportbundlestest.NewFakeBundleService(), nil, cfgT, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
t.Run("Reading dashboards from disk", func(t *testing.T) {
t.Run("Can read default dashboard", func(t *testing.T) {
@@ -19,6 +19,7 @@ import (
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
grafanasort "github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
@@ -51,7 +52,7 @@ func TestDuplicatesValidator(t *testing.T) {
folderStore := folderimpl.ProvideDashboardFolderStore(sql)
folderSvc := folderimpl.ProvideService(fStore, actest.FakeAccessControl{}, bus.ProvideBus(tracing.InitializeTracerForTest()),
dashStore, folderStore, nil, sql, featuremgmt.WithFeatures(),
supportbundlestest.NewFakeBundleService(), nil, cfgT, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
supportbundlestest.NewFakeBundleService(), nil, cfgT, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), grafanasort.ProvideService())
t.Run("Duplicates validator should collect info about duplicate UIDs and titles within folders", func(t *testing.T) {
const folderName = "duplicates-validator-folder"
@@ -42,6 +42,7 @@ import (
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
publicdashboardsService "github.com/grafana/grafana/pkg/services/publicdashboards/service"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
@@ -328,7 +329,7 @@ func TestIntegrationUnauthenticatedUserCanGetPubdashPanelQueryData(t *testing.T)
cfg, dashboardStoreService, folderStore,
featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), ac,
foldertest.NewFakeService(), folder.NewFakeStore(), nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil,
nil, dualwrite.ProvideTestService(),
nil, dualwrite.ProvideTestService(), sort.ProvideService(),
)
require.NoError(t, err)
dashService.RegisterDashboardPermissions(dashPermissionService)
@@ -36,6 +36,7 @@ import (
"github.com/grafana/grafana/pkg/services/publicdashboards/service/intervalv2"
"github.com/grafana/grafana/pkg/services/publicdashboards/validation"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
@@ -1398,10 +1399,9 @@ func TestPublicDashboardServiceImpl_ListPublicDashboards(t *testing.T) {
folderStore := folderimpl.ProvideDashboardFolderStore(testDB)
folderSvc := folderimpl.ProvideService(
fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore,
nil, testDB, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
nil, testDB, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
dashboardService, err := dashsvc.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), folderPermissions, ac, folderSvc, fStore, nil,
client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService())
dashboardService, err := dashsvc.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), folderPermissions, ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService())
require.NoError(t, err)
dashboardService.RegisterDashboardPermissions(&actest.FakePermissionsService{})
fakeGuardian := &guardian.FakeDashboardGuardian{
+3 -2
View File
@@ -43,6 +43,7 @@ import (
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/secrets/fakes"
secretskvs "github.com/grafana/grafana/pkg/services/secrets/kvstore"
secretsmng "github.com/grafana/grafana/pkg/services/secrets/manager"
@@ -496,9 +497,9 @@ func setupEnv(t *testing.T, sqlStore db.DB, cfg *setting.Cfg, b bus.Bus, quotaSe
ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures())
folderSvc := folderimpl.ProvideService(
fStore, acmock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore,
nil, sqlStore, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
nil, sqlStore, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
dashService, err := dashService.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(),
ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService())
ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService())
require.NoError(t, err)
dashService.RegisterDashboardPermissions(acmock.NewMockedPermissionsService())
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
+14 -8
View File
@@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/search/model"
grafanasort "github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/star"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
@@ -19,18 +20,15 @@ import (
var tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/search")
func ProvideService(cfg *setting.Cfg, sqlstore db.DB, starService star.Service, dashboardService dashboards.DashboardService, folderService folder.Service, features featuremgmt.FeatureToggles) *SearchService {
func ProvideService(cfg *setting.Cfg, sqlstore db.DB, starService star.Service, dashboardService dashboards.DashboardService, folderService folder.Service, features featuremgmt.FeatureToggles, sortService grafanasort.Service) *SearchService {
s := &SearchService{
Cfg: cfg,
sortOptions: map[string]model.SortOption{
SortAlphaAsc.Name: SortAlphaAsc,
SortAlphaDesc.Name: SortAlphaDesc,
},
Cfg: cfg,
sqlstore: sqlstore,
starService: starService,
folderService: folderService,
features: features,
dashboardService: dashboardService,
sortService: sortService,
}
return s
}
@@ -61,7 +59,7 @@ type Service interface {
type SearchService struct {
Cfg *setting.Cfg
sortOptions map[string]model.SortOption
sortService grafanasort.Service
sqlstore db.DB
starService star.Service
dashboardService dashboards.DashboardService
@@ -109,7 +107,7 @@ func (s *SearchService) SearchHandler(ctx context.Context, query *Query) (model.
IsDeleted: query.IsDeleted,
}
if sortOpt, exists := s.sortOptions[query.Sort]; exists {
if sortOpt, exists := s.sortService.GetSortOption(query.Sort); exists {
dashboardQuery.Sort = sortOpt
}
@@ -154,3 +152,11 @@ func sortedHits(unsorted model.HitList) model.HitList {
return hits
}
func (s *SearchService) RegisterSortOption(option model.SortOption) {
s.sortService.RegisterSortOption(option)
}
func (s *SearchService) SortOptions() []model.SortOption {
return s.sortService.SortOptions()
}
@@ -1,4 +1,4 @@
package search
package sort
import (
"sort"
@@ -7,6 +7,10 @@ import (
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
)
// sort is separated into its own service to allow the dashboard service to use it in the k8s
// fallback (see pkg/registry/apis/dashboard/legacysearcher/search_client.go), since search
// has a direct dependency on the dashboard service (and thus would create a circular dependency in wire)
var (
SortAlphaAsc = model.SortOption{
Name: "alpha-asc",
@@ -28,13 +32,26 @@ var (
}
)
type Service struct {
sortOptions map[string]model.SortOption
}
func ProvideService() Service {
return Service{
sortOptions: map[string]model.SortOption{
SortAlphaAsc.Name: SortAlphaAsc,
SortAlphaDesc.Name: SortAlphaDesc,
},
}
}
// RegisterSortOption allows for hooking in more search options from
// other services.
func (s *SearchService) RegisterSortOption(option model.SortOption) {
func (s *Service) RegisterSortOption(option model.SortOption) {
s.sortOptions[option.Name] = option
}
func (s *SearchService) SortOptions() []model.SortOption {
func (s *Service) SortOptions() []model.SortOption {
opts := make([]model.SortOption, 0, len(s.sortOptions))
for _, o := range s.sortOptions {
opts = append(opts, o)
@@ -44,3 +61,8 @@ func (s *SearchService) SortOptions() []model.SortOption {
})
return opts
}
func (s *Service) GetSortOption(sort string) (model.SortOption, bool) {
option, ok := s.sortOptions[sort]
return option, ok
}
@@ -25,6 +25,7 @@ import (
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/permissions"
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
@@ -825,7 +826,7 @@ func setupNestedTest(t *testing.T, usr *user.SignedInUser, perms []accesscontrol
fStore := folderimpl.ProvideStore(db)
folderSvc := folderimpl.ProvideService(
fStore, actest.FakeAccessControl{ExpectedEvaluate: true}, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderimpl.ProvideDashboardFolderStore(db),
nil, db, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
nil, db, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
// create parent folder
parent, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
@@ -24,6 +24,7 @@ import (
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/permissions"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
@@ -82,7 +83,7 @@ func setupBenchMark(b *testing.B, usr user.SignedInUser, features featuremgmt.Fe
fStore := folderimpl.ProvideStore(store)
folderSvc := folderimpl.ProvideService(
fStore, mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(store),
nil, store, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
nil, store, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanViewValue: true, CanSaveValue: true})
+1 -1
View File
@@ -165,7 +165,7 @@ func (ss *sqlStatsService) GetSystemStats(ctx context.Context, query *stats.GetS
sb.Write(`(SELECT COUNT(DISTINCT (` + dialect.Quote("rule_group") + `)) FROM ` + dialect.Quote("alert_rule") + `) AS rule_groups,`)
}
// currently not supported when dashboards are in unified storage
if !ss.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
if !ss.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) {
sb.Write(`(SELECT SUM(LENGTH(data)) FROM `+dialect.Quote("dashboard")+` WHERE is_folder = ?) AS dashboard_bytes_total,`, dialect.BooleanStr(false))
sb.Write(`(SELECT MAX(LENGTH(data)) FROM `+dialect.Quote("dashboard")+` WHERE is_folder = ?) AS dashboard_bytes_max,`, dialect.BooleanStr(false))
}
+7 -2
View File
@@ -61,9 +61,14 @@ target = all
protocol = https
[feature_toggles]
; store folders in k8s
kubernetesFolders = true
grafanaAPIServerWithExperimentalAPIs = true
kubernetesClientDashboardsFolders = true
[unified_storage.folders.folder.grafana.app]
dualWriterMode = 4
[unified_storage.dashboards.dashboard.grafana.app]
dualWriterMode = 4
[grafana-apiserver]
; use unified storage for k8s apiserver
+2 -2
View File
@@ -14,7 +14,7 @@ exclude k8s.io/client-go v12.0.0+incompatible
require (
github.com/bwmarrin/snowflake v0.3.0
github.com/google/uuid v1.6.0
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c
github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31
github.com/grafana/grafana v11.4.0-00010101000000-000000000000+incompatible
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250121113133-e747350fee2d
github.com/grafana/grafana/pkg/apiserver v0.0.0-20250121113133-e747350fee2d
@@ -192,7 +192,7 @@ require (
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038 // indirect
github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc // indirect
github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 // indirect
github.com/grafana/dataplane/sdata v0.0.9 // indirect
github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 // indirect
github.com/grafana/grafana-app-sdk/logging v0.30.0 // indirect
+4 -4
View File
@@ -566,10 +566,10 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038 h1:dG/UKAjY/KlKp9fY8aEm+gSQHHRmPm5q+9cea3hRSu8=
github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU=
github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc h1:qx8avmRi3wx+SuAn6UV0pKma1fJ0/4POUYNc+2rxZkg=
github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc/go.mod h1:HamgITzYqErFp8qLwYM/JwwE3z6fXft0Z+5n0Dx1rUM=
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo=
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM=
github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 h1:NTMmow+74I3Jb033xhbRgWQS7A//5TDhiM4tl7bsVP4=
github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7/go.mod h1:T3X4z0ejGfJOiOmZLFeKCRT/yxWJq/RtclAc/PHj/w4=
github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 h1:EokLC5grHwLPs4tXW8T6E8187H1e5G9AP0QQ5B60HbA=
github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM=
github.com/grafana/dataplane/examples v0.0.1 h1:K9M5glueWyLoL4//H+EtTQq16lXuHLmOhb6DjSCahzA=
github.com/grafana/dataplane/examples v0.0.1/go.mod h1:h5YwY8s407/17XF5/dS8XrUtsTVV2RnuW8+m1Mp46mg=
github.com/grafana/dataplane/sdata v0.0.9 h1:AGL1LZnCUG4MnQtnWpBPbQ8ZpptaZs14w6kE/MWfg7s=
+10 -9
View File
@@ -3,17 +3,18 @@ package apistore
import (
"bytes"
"context"
"errors"
"fmt"
"math"
"time"
"github.com/google/uuid"
authtypes "github.com/grafana/authlib/types"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apiserver/pkg/storage"
"k8s.io/klog/v2"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resource"
)
@@ -37,9 +38,9 @@ func formatBytes(numBytes int) string {
// Called on create
func (s *Storage) prepareObjectForStorage(ctx context.Context, newObject runtime.Object) ([]byte, error) {
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
info, ok := authtypes.AuthInfoFrom(ctx)
if !ok {
return nil, errors.New("missing auth info")
}
obj, err := utils.MetaAccessor(newObject)
@@ -80,7 +81,7 @@ func (s *Storage) prepareObjectForStorage(ctx context.Context, newObject runtime
obj.SetRepositoryInfo(repo)
obj.SetUpdatedBy("")
obj.SetUpdatedTimestamp(nil)
obj.SetCreatedBy(user.GetUID())
obj.SetCreatedBy(info.GetUID())
var buf bytes.Buffer
if err = s.codec.Encode(newObject, &buf); err != nil {
@@ -91,9 +92,9 @@ func (s *Storage) prepareObjectForStorage(ctx context.Context, newObject runtime
// Called on update
func (s *Storage) prepareObjectForUpdate(ctx context.Context, updateObject runtime.Object, previousObject runtime.Object) ([]byte, error) {
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
info, ok := authtypes.AuthInfoFrom(ctx)
if !ok {
return nil, errors.New("missing auth info")
}
obj, err := utils.MetaAccessor(updateObject)
@@ -141,7 +142,7 @@ func (s *Storage) prepareObjectForUpdate(ctx context.Context, updateObject runti
return nil, err
}
obj.SetRepositoryInfo(repo)
obj.SetUpdatedBy(user.GetUID())
obj.SetUpdatedBy(info.GetUID())
obj.SetUpdatedTimestampMillis(time.Now().UnixMilli())
var buf bytes.Buffer
+5 -4
View File
@@ -6,10 +6,10 @@ import (
"time"
"github.com/bwmarrin/snowflake"
authtypes "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
"github.com/grafana/grafana/pkg/services/user"
"github.com/stretchr/testify/require"
"golang.org/x/exp/rand"
"k8s.io/apimachinery/pkg/api/apitesting"
@@ -32,12 +32,13 @@ func TestPrepareObjectForStorage(t *testing.T) {
LargeObjectSupport: nil,
},
}
ctx := identity.WithRequester(context.Background(), &user.SignedInUser{UserID: 1, UserUID: "user-uid"})
t.Run("Error getting requester from context", func(t *testing.T) {
ctx := authtypes.WithAuthInfo(context.Background(), &identity.StaticRequester{UserID: 1, UserUID: "user-uid", Type: authtypes.TypeUser})
t.Run("Error getting auth info from context", func(t *testing.T) {
_, err := s.prepareObjectForStorage(context.Background(), nil)
require.Error(t, err)
require.Contains(t, err.Error(), "a Requester was not found in the context")
require.Contains(t, err.Error(), "missing auth info")
})
t.Run("Error on missing name", func(t *testing.T) {
@@ -23,6 +23,7 @@ import (
"github.com/grafana/grafana/pkg/services/guardian"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
ngalertstore "github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
"github.com/grafana/grafana/pkg/services/user"
@@ -53,7 +54,7 @@ func TestDirectSQLStats(t *testing.T) {
fStore := folderimpl.ProvideStore(db)
folderSvc := folderimpl.ProvideService(
fStore, actest.FakeAccessControl{ExpectedEvaluate: true}, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderimpl.ProvideDashboardFolderStore(db),
nil, db, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService())
nil, db, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService())
// create parent folder
+2 -1
View File
@@ -237,7 +237,7 @@ func (x *searchableDocumentFields) Fields() []string {
}
func (x *searchableDocumentFields) Field(name string) *ResourceTableColumnDefinition {
name = strings.TrimPrefix(name, "fields.")
name = strings.TrimPrefix(name, SEARCH_FIELD_PREFIX)
f, ok := x.fields[name]
if ok {
@@ -246,6 +246,7 @@ func (x *searchableDocumentFields) Field(name string) *ResourceTableColumnDefini
return nil
}
const SEARCH_FIELD_PREFIX = "fields."
const SEARCH_FIELD_ID = "_id" // {namespace}/{group}/{resource}/{name}
const SEARCH_FIELD_KIND = "kind" // resource ( for federated index filtering )
const SEARCH_FIELD_GROUP_RESOURCE = "gr" // group/resource
+2 -2
View File
@@ -11,8 +11,8 @@ replace (
require (
github.com/fullstorydev/grpchan v1.1.1
github.com/google/uuid v1.6.0
github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c
github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7
github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31
github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040
github.com/grafana/grafana v11.4.0-00010101000000-000000000000+incompatible
github.com/grafana/grafana-plugin-sdk-go v0.266.0
+4 -4
View File
@@ -407,10 +407,10 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038 h1:dG/UKAjY/KlKp9fY8aEm+gSQHHRmPm5q+9cea3hRSu8=
github.com/grafana/alerting v0.0.0-20250207161551-04c87cf39038/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU=
github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc h1:qx8avmRi3wx+SuAn6UV0pKma1fJ0/4POUYNc+2rxZkg=
github.com/grafana/authlib v0.0.0-20250217135712-106ec5baf6bc/go.mod h1:HamgITzYqErFp8qLwYM/JwwE3z6fXft0Z+5n0Dx1rUM=
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo=
github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM=
github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7 h1:NTMmow+74I3Jb033xhbRgWQS7A//5TDhiM4tl7bsVP4=
github.com/grafana/authlib v0.0.0-20250219100139-6a3b1bbb50e7/go.mod h1:T3X4z0ejGfJOiOmZLFeKCRT/yxWJq/RtclAc/PHj/w4=
github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31 h1:EokLC5grHwLPs4tXW8T6E8187H1e5G9AP0QQ5B60HbA=
github.com/grafana/authlib/types v0.0.0-20250219092154-21ce22b49f31/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM=
github.com/grafana/dataplane/sdata v0.0.9 h1:AGL1LZnCUG4MnQtnWpBPbQ8ZpptaZs14w6kE/MWfg7s=
github.com/grafana/dataplane/sdata v0.0.9/go.mod h1:Jvs5ddpGmn6vcxT7tCTWAZ1mgi4sbcdFt9utQx5uMAU=
github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 h1:IR+UNYHqaU31t8/TArJk8K/GlDwOyxMpGNkWCXeZ28g=
+3 -3
View File
@@ -552,7 +552,7 @@ func (b *bleveIndex) toBleveSearchRequest(ctx context.Context, req *resource.Res
fields := make([]string, 0, len(req.Fields))
for _, f := range req.Fields {
if slices.Contains(DashboardFields(), f) {
f = "fields." + f
f = resource.SEARCH_FIELD_PREFIX + f
}
fields = append(fields, f)
}
@@ -677,7 +677,7 @@ func getSortFields(req *resource.ResourceSearchRequest) []string {
}
if slices.Contains(DashboardFields(), input) {
input = "fields." + input
input = resource.SEARCH_FIELD_PREFIX + input
}
if sort.Desc {
@@ -847,7 +847,7 @@ func (b *bleveIndex) hitsToTable(ctx context.Context, selectFields []string, hit
v := match.Fields[fieldName]
// fields that are specific to the resource get stored as fields.<fieldName>, so we need to check for that
if v == nil {
v = match.Fields["fields."+fieldName]
v = match.Fields[resource.SEARCH_FIELD_PREFIX+fieldName]
}
if v != nil {
// Encode the value to protobuf
+11 -11
View File
@@ -49,7 +49,7 @@ func TestIntegrationFoldersApp(t *testing.T) {
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
AppModeProduction: true,
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
},
})
@@ -123,7 +123,7 @@ func TestIntegrationFoldersApp(t *testing.T) {
},
},
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
},
}))
})
@@ -139,7 +139,7 @@ func TestIntegrationFoldersApp(t *testing.T) {
},
},
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
},
}))
})
@@ -155,7 +155,7 @@ func TestIntegrationFoldersApp(t *testing.T) {
},
},
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
featuremgmt.FlagNestedFolders,
},
}))
@@ -172,7 +172,7 @@ func TestIntegrationFoldersApp(t *testing.T) {
},
},
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
featuremgmt.FlagNestedFolders,
},
}))
@@ -189,7 +189,7 @@ func TestIntegrationFoldersApp(t *testing.T) {
},
},
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
featuremgmt.FlagNestedFolders,
},
}))
@@ -206,7 +206,7 @@ func TestIntegrationFoldersApp(t *testing.T) {
},
},
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
featuremgmt.FlagNestedFolders,
},
}))
@@ -577,7 +577,7 @@ func TestIntegrationFolderCreatePermissions(t *testing.T) {
},
EnableFeatureToggles: []string{
featuremgmt.FlagNestedFolders,
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
},
})
@@ -679,7 +679,7 @@ func TestIntegrationFolderGetPermissions(t *testing.T) {
},
EnableFeatureToggles: []string{
featuremgmt.FlagNestedFolders,
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
},
})
@@ -856,7 +856,7 @@ func TestFoldersCreateAPIEndpointK8S(t *testing.T) {
},
EnableFeatureToggles: []string{
featuremgmt.FlagNestedFolders,
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
},
})
@@ -1026,7 +1026,7 @@ func TestFoldersGetAPIEndpointK8S(t *testing.T) {
},
EnableFeatureToggles: []string{
featuremgmt.FlagNestedFolders,
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagKubernetesClientDashboardsFolders,
},
})
+2 -2
View File
@@ -28,8 +28,8 @@ func TestIntegrationOpenAPIs(t *testing.T) {
h := NewK8sTestHelper(t, testinfra.GrafanaOpts{
AppModeProduction: true,
EnableFeatureToggles: []string{
featuremgmt.FlagKubernetesFoldersServiceV2, // Will be default on by G12
featuremgmt.FlagQueryService, // Query Library
featuremgmt.FlagKubernetesClientDashboardsFolders, // Will be default on by G12
featuremgmt.FlagQueryService, // Query Library
featuremgmt.FlagProvisioning,
featuremgmt.FlagInvestigationsBackend,
},
@@ -48,9 +48,8 @@ func TestIntegrationProvisioning(t *testing.T) {
EnableFeatureToggles: []string{
featuremgmt.FlagProvisioning,
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs,
featuremgmt.FlagKubernetesCliDashboards,
featuremgmt.FlagKubernetesFoldersServiceV2,
featuremgmt.FlagUnifiedStorageSearch,
featuremgmt.FlagKubernetesClientDashboardsFolders,
},
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
"dashboards.dashboard.grafana.app": {
Loaded 100 of 119 files, more files were not shown because too many files have changed in this diff. Show more