From 0687017595efdedeead06b0bb88ac25d6f3b500c Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Tue, 19 Aug 2025 09:25:16 +0100 Subject: [PATCH 01/26] E2E: Run playwright cloud plugins tests as part of github actions (#109055) * add github workflow scaffolding * update comments * Add image and resource commands * Add secrets paths * Block workflow run for forks * ignore via package.json, update CODEOWNERS * fix workflow path * remove old azure monitor test * pull docker image first * add permissions for docker pull step * checkout first * keep creds file * try all in one job * with creds... * add cloud: 'azure' * pass CLOUD to docker * add -playwright * actually use the env vars * don't need to pass CLOUD env var * remove commented out code and tidy up * kick CI * Update container names and set PLAYWRIGHT_CI * Update path * fix zizmor violation * use bigger runner, add double quoting * add separate command and increase timeout * remove timeout * parameterise the e2e command in CI * move cloud-plugins-e2e-tests into normal e2e test workflow * fix detect changes * pass creds into dagger * try remove quotes * add a debug log * exec playwright command after mounting file * reassign e2eContainer, add change to check the tests fail correctly * fix test --------- Co-authored-by: Andreas Christou --- .github/CODEOWNERS | 2 +- .github/actions/change-detection/action.yml | 10 + .github/workflows/pr-e2e-tests.yml | 68 ++++ .../cloud-plugins-suite/azure-monitor.spec.ts | 14 +- e2e/cloud-plugins-suite/azure-monitor.spec.ts | 360 ------------------ package.json | 3 +- pkg/build/e2e-playwright/e2e.go | 24 +- pkg/build/e2e-playwright/main.go | 20 + 8 files changed, 125 insertions(+), 376 deletions(-) delete mode 100644 e2e/cloud-plugins-suite/azure-monitor.spec.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 67ef71a4cf9..1d0c622d57d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -412,8 +412,8 @@ /public/locales/i18next-parser-enterprise.config.cjs @grafana/grafana-frontend-platform /public/app/core/internationalization/ @grafana/grafana-frontend-platform /e2e/ @grafana/grafana-frontend-platform -/e2e/cloud-plugins-suite/ @grafana/partner-datasources /e2e-playwright/ @grafana/grafana-frontend-platform +/e2e-playwright/cloud-plugins-suite/ @grafana/partner-datasources /e2e-playwright/dashboard-new-layouts @grafana/dashboards-squad /e2e-playwright/dashboards-suite/dashboard-browse-nested.spec.ts @grafana/grafana-search-navigate-organise /e2e-playwright/dashboards-suite/dashboard-browse.spec.ts @grafana/grafana-search-navigate-organise diff --git a/.github/actions/change-detection/action.yml b/.github/actions/change-detection/action.yml index e2c3a6c3b89..e7606f453e1 100644 --- a/.github/actions/change-detection/action.yml +++ b/.github/actions/change-detection/action.yml @@ -19,6 +19,9 @@ outputs: value: ${{ steps.changed-files.outputs.e2e_any_changed == 'true' || steps.changed-files.outputs.backend_any_changed == 'true' || steps.changed-files.outputs.frontend_any_changed == 'true' || 'true' }} + e2e-cloud-plugins: + description: Whether the cloud plugins code or tests have changed in any way + value: ${{ steps.changed-files.outputs.e2e_cloud_plugins_any_changed || 'true' }} dev-tooling: description: Whether the dev tooling or self have changed in any way value: ${{ steps.changed-files.outputs.dev_tooling_any_changed || 'true' }} @@ -102,6 +105,11 @@ runs: - 'conf/**' - 'cypress.config.js' - '${{ inputs.self }}' + e2e_cloud_plugins: + - 'pkg/tsdb/azuremonitor/**' + - 'public/app/plugins/datasource/azuremonitor/**' + - 'e2e-playwright/cloud-plugins-suite/azure-monitor.spec.ts' + - '${{ inputs.self }}' dev_tooling: - '.github/actions/setup-enterprise/**' - '.github/actions/checkout/**' @@ -139,6 +147,8 @@ runs: echo " --> ${{ steps.changed-files.outputs.e2e_all_changed_files }}" echo " --> ${{ steps.changed-files.outputs.backend_all_changed_files }}" echo " --> ${{ steps.changed-files.outputs.frontend_all_changed_files }}" + echo "E2E cloud plugins: ${{ steps.changed-files.outputs.e2e_cloud_plugins_any_changed || 'true' }}" + echo " --> ${{ steps.changed-files.outputs.e2e_cloud_plugins_all_changed_files }}" echo "Dev Tooling: ${{ steps.changed-files.outputs.dev_tooling_any_changed || 'true' }}" echo " --> ${{ steps.changed-files.outputs.dev_tooling_all_changed_files }}" echo "Docs: ${{ steps.changed-files.outputs.docs_any_changed || 'true' }}" diff --git a/.github/workflows/pr-e2e-tests.yml b/.github/workflows/pr-e2e-tests.yml index 03ba03b8031..b4a9f6baf6c 100644 --- a/.github/workflows/pr-e2e-tests.yml +++ b/.github/workflows/pr-e2e-tests.yml @@ -26,6 +26,7 @@ jobs: contents: read outputs: changed: ${{ steps.detect-changes.outputs.e2e }} + cloud_plugins_changed: ${{ steps.detect-changes.outputs.e2e-cloud-plugins }} steps: - uses: actions/checkout@v4 with: @@ -318,9 +319,76 @@ jobs: path: ./blob-report retention-days: 1 + run-azure-monitor-e2e: + if: needs.detect-changes.outputs.cloud_plugins_changed == 'true' && github.event.pull_request.head.repo.fork == false + runs-on: ubuntu-x64-large + needs: + - build-grafana + - detect-changes + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - uses: grafana/shared-workflows/actions/login-to-gar@login-to-gar-v0.4.0 + id: login-to-gar + with: + registry: "us-docker.pkg.dev" + environment: "dev" + + - id: pull-docker-image + run: | + docker pull us-docker.pkg.dev/grafanalabs-dev/docker-oss-plugin-partnerships-dev/e2e-playwright:latest + + - id: vault-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@main + with: + repo_secrets: | + AZURE_SP_APP_ID=cpp-azure-resourcemanager-credentials:application_id + AZURE_SP_PASSWORD=cpp-azure-resourcemanager-credentials:application_secret + AZURE_TENANT=cpp-azure-resourcemanager-credentials:tenant_id + + - id: deploy-resources + env: + AZURE_SP_APP_ID: ${{ env.AZURE_SP_APP_ID}} + AZURE_SP_PASSWORD: ${{ env.AZURE_SP_PASSWORD}} + AZURE_TENANT: ${{ env.AZURE_TENANT }} + NAME: ${{ github.ref_name }} + run: | + docker container run --name cpp-e2e-deploy -e AZURE_SP_APP_ID -e AZURE_SP_PASSWORD -e AZURE_TENANT -e PLAYWRIGHT_CI=true us-docker.pkg.dev/grafanalabs-dev/docker-oss-plugin-partnerships-dev/e2e-playwright:latest ./cpp-e2e/scripts/ci-run-playwright.sh azure "${NAME}" deploy + + - id: extract-creds + # see https://github.com/grafana/oss-plugin-partnerships/blob/a77040d0456003cd258668b61d542dc7c75db5b5/e2e/scripts/deploy.sh#L25 for path + run: | + docker cp cpp-e2e-deploy:/outputs.json /tmp/outputs.json + + - uses: actions/download-artifact@v4 + with: + name: grafana-tar-gz + + - name: Run E2E tests + uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e + with: + verb: run + args: go run ./pkg/build/e2e-playwright --package=grafana.tar.gz --playwright-command="yarn e2e:playwright:cloud-plugins" --cloud-plugin-creds=/tmp/outputs.json + + - name: Destroy resources + if: always() && steps.deploy-resources.outcome == 'success' + env: + AZURE_SP_APP_ID: ${{ env.AZURE_SP_APP_ID }} + AZURE_SP_PASSWORD: ${{ env.AZURE_SP_PASSWORD }} + AZURE_TENANT: ${{ env.AZURE_TENANT }} + NAME: ${{ github.ref_name }} + run: | + docker container run --name cpp-e2e-destroy -e AZURE_SP_APP_ID -e AZURE_SP_PASSWORD -e AZURE_TENANT us-docker.pkg.dev/grafanalabs-dev/docker-oss-plugin-partnerships-dev/e2e-playwright:latest ./cpp-e2e/scripts/ci-run-playwright.sh azure "${NAME}" destroy + required-playwright-tests: needs: - run-playwright-tests + - run-azure-monitor-e2e - run-storybook-test - build-grafana if: ${{ !cancelled() }} diff --git a/e2e-playwright/cloud-plugins-suite/azure-monitor.spec.ts b/e2e-playwright/cloud-plugins-suite/azure-monitor.spec.ts index 29cc753df75..4e6ec84b169 100644 --- a/e2e-playwright/cloud-plugins-suite/azure-monitor.spec.ts +++ b/e2e-playwright/cloud-plugins-suite/azure-monitor.spec.ts @@ -4,12 +4,12 @@ import { Page } from 'playwright-core'; import { v4 as uuidv4 } from 'uuid'; import { - test, - expect, CreateDataSourcePageArgs, + DashboardPage, DataSourceConfigPage, E2ESelectorGroups, - DashboardPage, + expect, + test, } from '@grafana/plugin-e2e'; import { AzureQueryType } from '../../public/app/plugins/datasource/azuremonitor/dataquery.gen'; @@ -71,8 +71,7 @@ async function provisionAzureMonitorDatasources( await configPage.saveAndTest(); } -// TODO unskip when we've figured out how to populate the credentials in CI -test.describe.skip( +test.describe( 'Azure Monitor datasource', { tag: ['@cloud-plugins'], @@ -84,7 +83,7 @@ test.describe.skip( // Check if we're running in CI const CI = process.env.CI; if (CI) { - const outputs = JSON.parse(readFileSync('outputs.json', 'utf8')); + const outputs = JSON.parse(readFileSync('/tmp/outputs.json', 'utf8')); datasourceConfig = { jsonData: { cloudName: 'Azure', @@ -134,6 +133,7 @@ test.describe.skip( await expect(page.getByText(rootSubscription)).toBeVisible({ timeout: 30000 }); const resourceSearchInput = page.getByTestId(azMonSelectors.components.queryEditor.resourcePicker.search.input); await resourceSearchInput.fill(storageAcctName); + await resourceSearchInput.press('Enter'); await expect(page.getByText(storageAcctName)).toBeVisible({ timeout: 30000 }); await page.getByText(storageAcctName).click(); const applyButton = page.getByTestId(azMonSelectors.components.queryEditor.resourcePicker.apply.button); @@ -164,6 +164,7 @@ test.describe.skip( await resourcePickerButton.click(); await expect(page.getByText(rootSubscription)).toBeVisible({ timeout: 30000 }); await resourceSearchInput.fill(logAnalyticsName); + await resourceSearchInput.press('Enter'); await expect(page.getByText(logAnalyticsName)).toBeVisible({ timeout: 30000 }); await page.getByText(logAnalyticsName).click(); await applyButton.click(); @@ -220,6 +221,7 @@ test.describe.skip( await resourcePickerButton.click(); await expect(page.getByText(rootSubscription)).toBeVisible({ timeout: 30000 }); await resourceSearchInput.fill(applicationInsightsName); + await resourceSearchInput.press('Enter'); await expect(page.getByText(applicationInsightsName)).toBeVisible({ timeout: 30000 }); await page.getByText(applicationInsightsName).click(); await applyButton.click(); diff --git a/e2e/cloud-plugins-suite/azure-monitor.spec.ts b/e2e/cloud-plugins-suite/azure-monitor.spec.ts deleted file mode 100644 index 469bdbfc9ed..00000000000 --- a/e2e/cloud-plugins-suite/azure-monitor.spec.ts +++ /dev/null @@ -1,360 +0,0 @@ -import { Interception } from 'cypress/types/net-stubbing'; -import { load } from 'js-yaml'; -import { v4 as uuidv4 } from 'uuid'; - -import { selectors as rawSelectors } from '@grafana/e2e-selectors'; - -import { selectors } from '../../public/app/plugins/datasource/azuremonitor/e2e/selectors'; -import { AzureQueryType } from '../../public/app/plugins/datasource/azuremonitor/types/query'; -import { - AzureMonitorDataSourceJsonData, - AzureMonitorDataSourceSecureJsonData, -} from '../../public/app/plugins/datasource/azuremonitor/types/types'; -import { e2e } from '../utils'; - -const provisioningPath = `provisioning/datasources/azmonitor-ds.yaml`; -const e2eSelectors = e2e.getSelectors(selectors.components); - -type AzureMonitorConfig = { - secureJsonData: AzureMonitorDataSourceSecureJsonData; - jsonData: AzureMonitorDataSourceJsonData; -}; - -type AzureMonitorProvision = { datasources: AzureMonitorConfig[] }; - -const dataSourceName = `Azure Monitor E2E Tests - ${uuidv4()}`; - -const maxRetryCount = 3; - -Cypress.Commands.add('checkHealthRetryable', function (fn: Function, retryCount: number) { - cy.then(() => { - const result = fn(++retryCount); - result.then((res: Interception) => { - if (retryCount < maxRetryCount && res.response.statusCode !== 200) { - cy.wait(20000); - cy.checkHealthRetryable(fn, retryCount); - } - }); - }); -}); - -function provisionAzureMonitorDatasources(datasources: AzureMonitorProvision[]) { - const datasource = datasources[0].datasources[0]; - - cy.intercept(/subscriptions/).as('subscriptions'); - - e2e.flows.addDataSource({ - type: 'Azure Monitor', - name: dataSourceName, - form: () => { - e2eSelectors.configEditor.azureCloud.input().find('input').type('Azure').type('{enter}'); - // We set the log value to false here to ensure that secrets aren't printed to logs - e2eSelectors.configEditor.tenantID.input().find('input').type(datasource.jsonData.tenantId, { log: false }); - e2eSelectors.configEditor.clientID.input().find('input').type(datasource.jsonData.clientId, { log: false }); - e2eSelectors.configEditor.clientSecret - .input() - .find('input') - .type(datasource.secureJsonData.clientSecret, { log: false }); - e2eSelectors.configEditor.loadSubscriptions.button().click().wait('@subscriptions').wait(500); - e2eSelectors.configEditor.defaultSubscription.input().find('input').type('datasources{enter}'); - - // We can do this because awaitHealth is set to true so @health is defined - cy.checkHealthRetryable(() => { - return e2e.pages.DataSource.saveAndTest().click().wait('@health'); - }, 0); - }, - expectedAlertMessage: 'Successfully connected to all Azure Monitor endpoints', - // Reduce the timeout from 30s to error faster when an invalid alert message is presented - timeout: 10000, - awaitHealth: true, - }); -} - -// Helper function to add template variables -const addAzureMonitorVariable = ( - name: string, - type: AzureQueryType, - isFirst: boolean, - options?: { subscription?: string; resourceGroup?: string; namespace?: string; resource?: string; region?: string } -) => { - e2e.components.NavToolbar.editDashboard.editButton().should('be.visible').click(); - e2e.components.NavToolbar.editDashboard.settingsButton().should('be.visible').click(); - e2e.components.Tab.title('Variables').click(); - if (isFirst) { - e2e.pages.Dashboard.Settings.Variables.List.addVariableCTAV2().click(); - } else { - cy.get(`[data-testid="${rawSelectors.pages.Dashboard.Settings.Variables.List.newButton}"]`).click(); - } - e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInputV2().clear().type(name); - e2e.components.DataSourcePicker.inputV2().type(`${dataSourceName}{enter}`); - e2eSelectors.variableEditor.queryType - .input() - .find('input') - .type(`${type.replace('Azure', '').trim()}{enter}`); - switch (type) { - case AzureQueryType.ResourceGroupsQuery: - e2eSelectors.variableEditor.subscription.input().find('input').type(`${options?.subscription}{enter}`); - break; - case AzureQueryType.LocationsQuery: - e2eSelectors.variableEditor.subscription.input().find('input').type(`${options?.subscription}{enter}`); - break; - case AzureQueryType.NamespacesQuery: - e2eSelectors.variableEditor.subscription.input().find('input').type(`${options?.subscription}{enter}`); - e2eSelectors.variableEditor.resourceGroup.input().find('input').type(`${options?.resourceGroup}{enter}`); - break; - case AzureQueryType.ResourceNamesQuery: - e2eSelectors.variableEditor.subscription.input().find('input').type(`${options?.subscription}{enter}`); - e2eSelectors.variableEditor.resourceGroup.input().find('input').type(`${options?.resourceGroup}{enter}`); - e2eSelectors.variableEditor.namespace.input().find('input').type(`${options?.namespace}{enter}`); - e2eSelectors.variableEditor.region.input().find('input').type(`${options?.region}{enter}`); - break; - case AzureQueryType.MetricNamesQuery: - e2eSelectors.variableEditor.subscription.input().find('input').type(`${options?.subscription}{enter}`); - e2eSelectors.variableEditor.resourceGroup.input().find('input').type(`${options?.resourceGroup}{enter}`); - e2eSelectors.variableEditor.namespace.input().find('input').type(`${options?.namespace}{enter}`); - e2eSelectors.variableEditor.resource.input().find('input').type(`${options?.resource}{enter}`); - break; - } - e2e.pages.Dashboard.Settings.Variables.Edit.General.submitButton().click(); - e2e.components.NavToolbar.editDashboard.backToDashboardButton().click(); - e2e.components.NavToolbar.editDashboard.exitButton().click(); -}; - -const storageAcctName = 'azmonteststorage'; -const logAnalyticsName = 'az-mon-test-logs'; -const applicationInsightsName = 'az-mon-test-ai-a'; - -describe('Azure monitor datasource', () => { - before(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - - // Add datasource - // This variable will be set in CI - const CI = Cypress.env('CI'); - if (CI) { - cy.readFile('outputs.json').then((outputs) => { - provisionAzureMonitorDatasources([ - { - datasources: [ - { - jsonData: { - cloudName: 'Azure', - tenantId: outputs.tenantId, - clientId: outputs.clientId, - }, - secureJsonData: { clientSecret: outputs.clientSecret }, - }, - ], - }, - ]); - }); - } else { - cy.readFile(provisioningPath).then((azMonitorProvision: string) => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const yaml = load(azMonitorProvision) as AzureMonitorProvision; - provisionAzureMonitorDatasources([yaml]); - }); - } - e2e.setScenarioContext({ addedDataSources: [] }); - }); - - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - }); - - after(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - e2e.flows.revertAllChanges(); - }); - - it('create dashboard, add panel for metrics, log analytics, ARG, and traces queries', () => { - e2e.flows.addDashboard({ - timeRange: { - from: 'now-6h', - to: 'now', - zone: 'Coordinated Universal Time', - }, - }); - e2e.flows.addPanel({ - dataSourceName, - visitDashboardAtStart: false, - queriesForm: () => { - e2eSelectors.queryEditor.resourcePicker.select.button().click(); - e2eSelectors.queryEditor.resourcePicker.search - .input() - .wait(100) - .type(storageAcctName) - .wait(500) - .type('{enter}'); - cy.contains(storageAcctName).click(); - e2eSelectors.queryEditor.resourcePicker.apply.button().click(); - cy.contains('microsoft.storage/storageaccounts'); - e2eSelectors.queryEditor.metricsQueryEditor.metricName.input().find('input').type('Used capacity{enter}'); - }, - timeout: 10000, - }); - e2e.components.NavToolbar.editDashboard.backToDashboardButton().click(); - e2e.components.NavToolbar.editDashboard.exitButton().click(); - e2e.flows.addPanel({ - dataSourceName, - visitDashboardAtStart: false, - queriesForm: () => { - e2eSelectors.queryEditor.header.select().find('input').type('Logs{enter}'); - e2eSelectors.queryEditor.resourcePicker.select.button().click(); - e2eSelectors.queryEditor.resourcePicker.search - .input() - .wait(100) - .type(logAnalyticsName) - .wait(500) - .type('{enter}'); - cy.contains(logAnalyticsName).click(); - e2eSelectors.queryEditor.resourcePicker.apply.button().click(); - e2e.components.CodeEditor.container().type('AzureDiagnostics'); - e2eSelectors.queryEditor.logsQueryEditor.formatSelection.input().type('Time series{enter}'); - }, - timeout: 10000, - }); - e2e.components.NavToolbar.editDashboard.backToDashboardButton().click(); - e2e.components.NavToolbar.editDashboard.exitButton().click(); - e2e.flows.addPanel({ - dataSourceName, - visitDashboardAtStart: false, - queriesForm: () => { - e2eSelectors.queryEditor.header.select().find('input').type('Azure Resource Graph{enter}'); - cy.wait(2000); // Need to wait for code editor to completely load - e2eSelectors.queryEditor.argsQueryEditor.subscriptions.input().find('[aria-label="Clear value"]').click(); - e2eSelectors.queryEditor.argsQueryEditor.subscriptions.input().find('input').type('datasources{enter}'); - e2e.components.CodeEditor.container().type( - "Resources | where resourceGroup == 'cloud-plugins-e2e-test-azmon' | project name, resourceGroup" - ); - e2e.components.PanelEditor.toggleTableView().click({ force: true }); - }, - timeout: 10000, - }); - e2e.components.NavToolbar.editDashboard.backToDashboardButton().click(); - e2e.components.NavToolbar.editDashboard.exitButton().click(); - e2e.flows.addPanel({ - dataSourceName, - visitDashboardAtStart: false, - queriesForm: () => { - e2eSelectors.queryEditor.header.select().find('input').type('Traces{enter}'); - e2eSelectors.queryEditor.resourcePicker.select.button().click(); - e2eSelectors.queryEditor.resourcePicker.search - .input() - .wait(100) - .type(applicationInsightsName) - .wait(500) - .type('{enter}'); - cy.contains(applicationInsightsName).click(); - e2eSelectors.queryEditor.resourcePicker.apply.button().click(); - cy.wait(10000); - e2eSelectors.queryEditor.logsQueryEditor.formatSelection.input().type('Trace{enter}'); - }, - timeout: 10000, - }); - }); - - it('creates a dashboard that includes a template variable', () => { - e2e.flows.addDashboard({ - timeRange: { - from: 'now-6h', - to: 'now', - zone: 'Coordinated Universal Time', - }, - }); - addAzureMonitorVariable('subscription', AzureQueryType.SubscriptionsQuery, true); - addAzureMonitorVariable('resourceGroups', AzureQueryType.ResourceGroupsQuery, false, { - subscription: '$subscription', - }); - addAzureMonitorVariable('namespaces', AzureQueryType.NamespacesQuery, false, { - subscription: '$subscription', - resourceGroup: '$resourceGroups', - }); - addAzureMonitorVariable('region', AzureQueryType.LocationsQuery, false, { - subscription: '$subscription', - }); - addAzureMonitorVariable('resource', AzureQueryType.ResourceNamesQuery, false, { - subscription: '$subscription', - resourceGroup: '$resourceGroups', - namespace: '$namespace', - region: '$region', - }); - e2e.pages.Dashboard.SubMenu.submenuItemLabels('subscription') - .parent() - .within(() => { - cy.get('input').click(); - }); - e2e.components.Select.option().contains('grafanalabs-datasources-dev').click(); - e2e.pages.Dashboard.SubMenu.submenuItemLabels('resourceGroups') - .parent() - .within(() => { - cy.get('input').type('cloud-plugins-e2e-test-azmon{downArrow}{enter}'); - }); - e2e.pages.Dashboard.SubMenu.submenuItemLabels('namespaces') - .parent() - .within(() => { - cy.get('input').type('microsoft.storage/storageaccounts{downArrow}{enter}'); - }); - e2e.pages.Dashboard.SubMenu.submenuItemLabels('region') - .parent() - .within(() => { - cy.get('input').type('uk south{downArrow}{enter}'); - }); - e2e.pages.Dashboard.SubMenu.submenuItemLabels('resource') - .parent() - .within(() => { - cy.get('input').type(`${storageAcctName}{downArrow}{enter}`); - }); - e2e.flows.addPanel({ - dataSourceName, - visitDashboardAtStart: false, - queriesForm: () => { - e2eSelectors.queryEditor.resourcePicker.select.button().click(); - e2eSelectors.queryEditor.resourcePicker.advanced.collapse().click(); - e2eSelectors.queryEditor.resourcePicker.advanced.subscription.input().find('input').type('$subscription'); - e2eSelectors.queryEditor.resourcePicker.advanced.resourceGroup.input().find('input').type('$resourceGroups'); - e2eSelectors.queryEditor.resourcePicker.advanced.namespace.input().find('input').type('$namespaces'); - e2eSelectors.queryEditor.resourcePicker.advanced.region.input().find('input').type('$region'); - e2eSelectors.queryEditor.resourcePicker.advanced.resource.input().find('input').type('$resource'); - e2eSelectors.queryEditor.resourcePicker.apply.button().click(); - e2eSelectors.queryEditor.metricsQueryEditor.metricName.input().find('input').type('Transactions{enter}'); - }, - timeout: 10000, - }); - }); - - it.skip('creates a dashboard that includes an annotation', () => { - e2e.flows.addDashboard({ - timeRange: { - from: '2022-10-03 00:00:00', - to: '2022-10-03 23:59:59', - zone: 'Coordinated Universal Time', - }, - }); - e2e.components.PageToolbar.item('Dashboard settings').click(); - e2e.components.Tab.title('Annotations').click(); - e2e.pages.Dashboard.Settings.Annotations.List.addAnnotationCTAV2().click(); - e2e.pages.Dashboard.Settings.Annotations.Settings.name().type('TestAnnotation'); - e2e.components.DataSourcePicker.inputV2().click().type(`${dataSourceName}{enter}`); - e2eSelectors.queryEditor.resourcePicker.select.button().click(); - e2eSelectors.queryEditor.resourcePicker.search.input().type(storageAcctName); - cy.contains(storageAcctName).click(); - e2eSelectors.queryEditor.resourcePicker.apply.button().click(); - cy.contains('microsoft.storage/storageaccounts'); - e2eSelectors.queryEditor.metricsQueryEditor.metricName.input().find('input').type('Transactions{enter}'); - cy.get('table').contains('text').parent().find('input').click().type('Transactions (number){enter}'); - e2e.components.PageToolbar.item('Go Back').click(); - e2e.flows.addPanel({ - dataSourceName, - visitDashboardAtStart: false, - queriesForm: () => { - e2eSelectors.queryEditor.resourcePicker.select.button().click(); - e2eSelectors.queryEditor.resourcePicker.search.input().type(storageAcctName); - cy.contains(storageAcctName).click(); - e2eSelectors.queryEditor.resourcePicker.apply.button().click(); - cy.contains('microsoft.storage/storageaccounts'); - e2eSelectors.queryEditor.metricsQueryEditor.metricName.input().find('input').type('Used capacity{enter}'); - }, - }); - }); -}); diff --git a/package.json b/package.json index 1c326a3352d..9babb485971 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "e2e:enterprise": "./e2e/start-and-run-suite enterprise", "e2e:enterprise:dev": "./e2e/start-and-run-suite enterprise dev", "e2e:enterprise:debug": "./e2e/start-and-run-suite enterprise debug", - "e2e:playwright": "yarn playwright test", + "e2e:playwright": "yarn playwright test --grep-invert @cloud-plugins", + "e2e:playwright:cloud-plugins": "yarn playwright test --grep @cloud-plugins", "e2e:playwright:storybook": "yarn playwright test -c playwright.storybook.config.ts", "e2e:acceptance": "yarn playwright test --grep @acceptance", "e2e:storybook": "PORT=9001 ./e2e/run-suite storybook true", diff --git a/pkg/build/e2e-playwright/e2e.go b/pkg/build/e2e-playwright/e2e.go index 81044598d3f..f23cbdeafe8 100644 --- a/pkg/build/e2e-playwright/e2e.go +++ b/pkg/build/e2e-playwright/e2e.go @@ -23,6 +23,8 @@ type RunTestOpts struct { HTMLReportExportDir string BlobReportExportDir string TestResultsExportDir string + PlaywrightCommand string + CloudPluginCreds *dagger.File } func RunTest( @@ -47,10 +49,16 @@ func RunTest( WithEnvVariable("bustcache", "1"). WithEnvVariable("PLAYWRIGHT_HTML_OPEN", "never"). WithEnvVariable("PLAYWRIGHT_HTML_OUTPUT_DIR", htmlResultsDir). - WithEnvVariable("PLAYWRIGHT_BLOB_OUTPUT_DIR", blobResultsDir). - WithExec(playwrightCommand, dagger.ContainerWithExecOpts{ - Expect: dagger.ReturnTypeAny, - }) + WithEnvVariable("PLAYWRIGHT_BLOB_OUTPUT_DIR", blobResultsDir) + + if opts.CloudPluginCreds != nil { + fmt.Println("DEBUG: CloudPluginCreds file is provided, mounting to /tmp/outputs.json") + e2eContainer = e2eContainer.WithMountedFile("/tmp/outputs.json", opts.CloudPluginCreds) + } + + e2eContainer = e2eContainer.WithExec(playwrightCommand, dagger.ContainerWithExecOpts{ + Expect: dagger.ReturnTypeAny, + }) if opts.TestResultsExportDir != "" { _, err := e2eContainer.Directory(testResultsDir).Export(ctx, opts.TestResultsExportDir) @@ -89,14 +97,14 @@ func buildPlaywrightCommand(opts RunTestOpts) []string { playwrightReporters = append(playwrightReporters, "blob") } - playwrightCommand := []string{ - "yarn", - "e2e:playwright", + playwrightExec := strings.Split(opts.PlaywrightCommand, " ") + + playwrightCommand := append(playwrightExec, "--reporter", strings.Join(playwrightReporters, ","), "--output", testResultsDir, - } + ) if opts.Shard != "" { playwrightCommand = append(playwrightCommand, "--shard", opts.Shard) diff --git a/pkg/build/e2e-playwright/main.go b/pkg/build/e2e-playwright/main.go index c7ec56c8869..a7cff28cff0 100644 --- a/pkg/build/e2e-playwright/main.go +++ b/pkg/build/e2e-playwright/main.go @@ -71,6 +71,17 @@ func NewApp() *cli.Command { Usage: "Enables the blob reporter, exported to this directory. Useful with --shard (optional)", Validator: mustBeDir("blob-dir", true, true), }, + &cli.StringFlag{ + Name: "playwright-command", + Usage: "The playwright command to run.", + Value: "yarn e2e:playwright", + }, + &cli.StringFlag{ + Name: "cloud-plugin-creds", + Usage: "Path to the cloud plugin credentials file (only required for running @cloud-plugins e2e tests)", + Validator: mustBeFile("cloud-plugin-creds", true), + TakesFile: true, + }, }, Action: run, } @@ -80,10 +91,12 @@ func run(ctx context.Context, cmd *cli.Command) error { grafanaDir := cmd.String("grafana-dir") targzPath := cmd.String("package") licensePath := cmd.String("license") + cloudPluginCredsPath := cmd.String("cloud-plugin-creds") pwShard := cmd.String("shard") resultsDir := cmd.String("results-dir") htmlDir := cmd.String("html-dir") blobDir := cmd.String("blob-dir") + playwrightCommand := cmd.String("playwright-command") // pa11yConfigPath := cmd.String("config") // pa11yResultsPath := cmd.String("results") // noThresholdFail := cmd.Bool("no-threshold-fail") @@ -156,6 +169,11 @@ func run(ctx context.Context, cmd *cli.Command) error { license = d.Host().File(licensePath) } + var cloudPluginCreds *dagger.File + if cloudPluginCredsPath != "" { + cloudPluginCreds = d.Host().File(cloudPluginCredsPath) + } + svc, err := GrafanaService(ctx, d, GrafanaServiceOpts{ HostSrc: grafanaHostSrc, FrontendContainer: frontendContainer, @@ -174,6 +192,8 @@ func run(ctx context.Context, cmd *cli.Command) error { TestResultsExportDir: resultsDir, HTMLReportExportDir: htmlDir, BlobReportExportDir: blobDir, + PlaywrightCommand: playwrightCommand, + CloudPluginCreds: cloudPluginCreds, } c, runErr := RunTest(ctx, d, runOpts) From 1a87679dc72ce37a8fd96999530fabdf2cda025c Mon Sep 17 00:00:00 2001 From: Yulia Shanyrova Date: Tue, 19 Aug 2025 11:13:01 +0200 Subject: [PATCH 02/26] Plugins: Add apps to connections page to be consistent with cloud (#109600) * Add apps to connections page * fix the tests * fix connections tests --- .../features/connections/Connections.test.tsx | 2 - .../tabs/ConnectData/ConnectData.test.tsx | 24 +++++++- .../tabs/ConnectData/ConnectData.tsx | 58 ++++++++++++++++--- public/locales/en-US/grafana.json | 3 +- 4 files changed, 74 insertions(+), 13 deletions(-) diff --git a/public/app/features/connections/Connections.test.tsx b/public/app/features/connections/Connections.test.tsx index 1d3519426f7..3778042af10 100644 --- a/public/app/features/connections/Connections.test.tsx +++ b/public/app/features/connections/Connections.test.tsx @@ -95,8 +95,6 @@ describe('Connections', () => { test('renders the core "Add new connection" page in case there is no standalone plugin page override for it', async () => { renderPage(ROUTES.AddNewConnection); - // We expect to see no results and "Data sources" as a header (we only have data sources in OSS Grafana at this point) - expect(await screen.findByText('Data sources')).toBeVisible(); expect(await screen.findByText('No results matching your query were found')).toBeVisible(); }); diff --git a/public/app/features/connections/tabs/ConnectData/ConnectData.test.tsx b/public/app/features/connections/tabs/ConnectData/ConnectData.test.tsx index a4dabd2bbf9..3588aa64305 100644 --- a/public/app/features/connections/tabs/ConnectData/ConnectData.test.tsx +++ b/public/app/features/connections/tabs/ConnectData/ConnectData.test.tsx @@ -33,6 +33,12 @@ const mockCatalogDataSourcePlugin = getCatalogPluginMock({ id: 'sample-data-source', }); +const mockCatalogAppPlugin = getCatalogPluginMock({ + type: PluginType.app, + name: 'Sample app', + id: 'sample-app', +}); + describe('Badges', () => { test('shows enterprise and deprecated badges for plugins', async () => { renderPage([ @@ -64,8 +70,8 @@ describe('Add new connection', () => { expect(screen.queryByText('No results matching your query were found')).toBeInTheDocument(); }); - test('renders no results if there is no data source plugin in the list', async () => { - renderPage([getCatalogPluginMock()]); + test('renders no results if there are no datasource or app plugins in the list', async () => { + renderPage([getCatalogPluginMock({ type: PluginType.panel })]); expect(screen.queryByText('No results matching your query were found')).toBeInTheDocument(); }); @@ -75,6 +81,20 @@ describe('Add new connection', () => { expect(await screen.findByText('Sample data source')).toBeVisible(); }); + + test('renders app plugins when list is populated', async () => { + renderPage([getCatalogPluginMock(), mockCatalogAppPlugin]); + + expect(await screen.findByText('Sample app')).toBeVisible(); + }); + + test('renders app plugin and datasource plugin when list is populated', async () => { + renderPage([getCatalogPluginMock(), mockCatalogAppPlugin, mockCatalogDataSourcePlugin]); + + expect(await screen.findByText('Sample app')).toBeVisible(); + expect(await screen.findByText('Sample data source')).toBeVisible(); + }); + test('should list plugins with update when filtering by update', async () => { const { queryByText } = renderPage( [ diff --git a/public/app/features/connections/tabs/ConnectData/ConnectData.tsx b/public/app/features/connections/tabs/ConnectData/ConnectData.tsx index 3668047af9f..c2957147241 100644 --- a/public/app/features/connections/tabs/ConnectData/ConnectData.tsx +++ b/public/app/features/connections/tabs/ConnectData/ConnectData.tsx @@ -61,7 +61,6 @@ export function AddNewConnection() { const { error, plugins, isLoading } = useGetAll( { keyword: searchTerm, - type: PluginType.datasource, isInstalled: filterBy === 'installed' ? true : undefined, hasUpdate: filterBy === 'has-update' ? true : undefined, }, @@ -100,14 +99,34 @@ export function AddNewConnection() { setFocusedItem(null); }; - const cardGridItems = useMemo( + const getPluginsByType = useMemo(() => { + return { + [PluginType.datasource]: plugins.filter((plugin) => plugin.type === PluginType.datasource), + [PluginType.app]: plugins.filter((plugin) => plugin.type === PluginType.app), + }; + }, [plugins]); + + const dataSourcesPlugins = getPluginsByType[PluginType.datasource]; + const appsPlugins = getPluginsByType[PluginType.app]; + + const datasourceCardGridItems = useMemo( () => - plugins.map((plugin) => ({ + dataSourcesPlugins.map((plugin) => ({ ...plugin, logo: plugin.info.logos.small, url: ROUTES.DataSourcesDetails.replace(':id', plugin.id), })), - [plugins] + [dataSourcesPlugins] + ); + + const appsCardGridItems = useMemo( + () => + appsPlugins.map((plugin) => ({ + ...plugin, + logo: plugin.info.logos.small, + url: `/plugins/${plugin.id}`, + })), + [appsPlugins] ); const onSortByChange = (value: SelectableValue) => { @@ -118,8 +137,10 @@ export function AddNewConnection() { history.push({ query: { filterBy: value } }); }; - const showNoResults = useMemo(() => !isLoading && !error && plugins.length < 1, [isLoading, error, plugins]); - const categoryHeaderLabel = t('connections.connect-data.category-header-label', 'Data sources'); + const showNoResults = useMemo( + () => !isLoading && !error && dataSourcesPlugins.length < 1 && appsPlugins.length < 1, + [isLoading, error, dataSourcesPlugins, appsPlugins] + ); return ( <> @@ -179,7 +200,7 @@ export function AddNewConnection() { - + {isLoading ? ( ) : !!error ? ( @@ -187,8 +208,29 @@ export function AddNewConnection() { Error message: "{{ error: error.message }}" ) : ( - + <> + {/* Data Sources Section */} + {dataSourcesPlugins.length > 0 && ( + <> + + + + )} + + {/* Apps Section */} + {appsPlugins.length > 0 && ( + <> +
+ + + + )} + )} + {showNoResults && ( Date: Tue, 19 Aug 2025 11:34:12 +0200 Subject: [PATCH 03/26] Rendering: Remove SVG sanitization (#109797) --- go.mod | 2 +- pkg/extensions/enterprise_imports.go | 2 +- .../webhooks/pullrequest/render_test.go | 2 +- .../backgroundsvcs/background_services.go | 2 - pkg/server/wire.go | 2 - pkg/server/wire_gen.go | 9 +- pkg/services/authn/clients/render_test.go | 2 +- pkg/services/rendering/capabilities.go | 1 - pkg/services/rendering/interface.go | 15 +- pkg/services/rendering/mock.go | 69 +++---- pkg/services/rendering/rendering.go | 45 +---- pkg/services/rendering/svgSanitizer.go | 182 ------------------ pkg/services/screenshot/screenshot_test.go | 2 +- pkg/services/store/sanitize.go | 35 +--- pkg/services/store/sanitizer/Provider.go | 23 --- 15 files changed, 49 insertions(+), 344 deletions(-) delete mode 100644 pkg/services/rendering/svgSanitizer.go delete mode 100644 pkg/services/store/sanitizer/Provider.go diff --git a/go.mod b/go.mod index e5f070f04d1..821527e863d 100644 --- a/go.mod +++ b/go.mod @@ -193,6 +193,7 @@ require ( go.opentelemetry.io/otel/trace v1.37.0 // @grafana/grafana-backend-group go.uber.org/atomic v1.11.0 // @grafana/alerting-backend go.uber.org/goleak v1.3.0 // @grafana/grafana-search-and-storage + go.uber.org/mock v0.5.2 // @grafana/grafana-operator-experience-squad go.uber.org/zap v1.27.0 // @grafana/identity-access-team gocloud.dev v0.42.0 // @grafana/grafana-app-platform-squad gocloud.dev/secrets/hashivault v0.42.0 // @grafana/grafana-operator-experience-squad @@ -599,7 +600,6 @@ require ( go.opentelemetry.io/otel/sdk/log v0.12.2 // indirect go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect go.opentelemetry.io/proto/otlp v1.7.0 // indirect - go.uber.org/mock v0.5.2 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go4.org/netipx v0.0.0-20230125063823-8449b0a6169f // indirect diff --git a/pkg/extensions/enterprise_imports.go b/pkg/extensions/enterprise_imports.go index d33ccf561aa..3585c68ebf3 100644 --- a/pkg/extensions/enterprise_imports.go +++ b/pkg/extensions/enterprise_imports.go @@ -27,7 +27,6 @@ import ( _ "github.com/robfig/cron/v3" _ "github.com/russellhaering/goxmldsig" _ "github.com/spf13/cobra" // used by the standalone apiserver cli - _ "github.com/spyzhov/ajson" _ "github.com/stretchr/testify/require" _ "gocloud.dev/secrets/awskms" _ "gocloud.dev/secrets/azurekeyvault" @@ -53,4 +52,5 @@ import ( _ "github.com/grafana/e2e" _ "github.com/grafana/gofpdf" _ "github.com/grafana/gomemcache/memcache" + _ "github.com/spyzhov/ajson" ) diff --git a/pkg/registry/apis/provisioning/webhooks/pullrequest/render_test.go b/pkg/registry/apis/provisioning/webhooks/pullrequest/render_test.go index a54d12af1b6..32e9e2bab36 100644 --- a/pkg/registry/apis/provisioning/webhooks/pullrequest/render_test.go +++ b/pkg/registry/apis/provisioning/webhooks/pullrequest/render_test.go @@ -8,9 +8,9 @@ import ( "path/filepath" "testing" - "github.com/golang/mock/gomock" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" "github.com/grafana/grafana/pkg/models" diff --git a/pkg/registry/backgroundsvcs/background_services.go b/pkg/registry/backgroundsvcs/background_services.go index f6ae88f9dd1..04d3575e75a 100644 --- a/pkg/registry/backgroundsvcs/background_services.go +++ b/pkg/registry/backgroundsvcs/background_services.go @@ -43,7 +43,6 @@ import ( "github.com/grafana/grafana/pkg/services/ssosettings" "github.com/grafana/grafana/pkg/services/ssosettings/ssosettingsimpl" "github.com/grafana/grafana/pkg/services/store" - "github.com/grafana/grafana/pkg/services/store/sanitizer" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlesimpl" "github.com/grafana/grafana/pkg/services/team/teamapi" "github.com/grafana/grafana/pkg/services/updatemanager" @@ -73,7 +72,6 @@ func ProvideBackgroundServiceRegistry( // Need to make sure these are initialized, is there a better place to put them? _ dashboardsnapshots.Service, _ serviceaccounts.Service, - _ *sanitizer.Provider, _ *grpcserver.HealthService, _ *grpcserver.ReflectionService, _ *ldapapi.Service, _ *apiregistry.Service, _ auth.IDService, _ *teamapi.TeamAPI, _ ssosettings.Service, _ cloudmigration.Service, _ authnimpl.Registration, diff --git a/pkg/server/wire.go b/pkg/server/wire.go index 392a4fec45e..a6c18625815 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -157,7 +157,6 @@ import ( "github.com/grafana/grafana/pkg/services/stats/statsimpl" "github.com/grafana/grafana/pkg/services/store" "github.com/grafana/grafana/pkg/services/store/resolver" - "github.com/grafana/grafana/pkg/services/store/sanitizer" "github.com/grafana/grafana/pkg/services/supportbundles" "github.com/grafana/grafana/pkg/services/supportbundles/bundleregistry" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlesimpl" @@ -349,7 +348,6 @@ var wireBasicSet = wire.NewSet( plugindashboardsservice.ProvideService, wire.Bind(new(plugindashboards.Service), new(*plugindashboardsservice.Service)), plugindashboardsservice.ProvideDashboardUpdater, - sanitizer.ProvideService, secretsStore.ProvideService, avatar.ProvideAvatarCacheServer, statscollector.ProvideService, diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index 11a06b02dbb..656a18efc52 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -230,7 +230,6 @@ import ( "github.com/grafana/grafana/pkg/services/stats/statsimpl" "github.com/grafana/grafana/pkg/services/store" "github.com/grafana/grafana/pkg/services/store/resolver" - "github.com/grafana/grafana/pkg/services/store/sanitizer" "github.com/grafana/grafana/pkg/services/supportbundles" "github.com/grafana/grafana/pkg/services/supportbundles/bundleregistry" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlesimpl" @@ -772,7 +771,6 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api } importDashboardService := service11.ProvideService(routeRegisterImpl, quotaService, service14, pluginstoreService, libraryPanelService, dashboardService, accessControl, folderimplService, featureToggles) dashboardUpdater := service8.ProvideDashboardUpdater(inProcBus, pluginstoreService, service14, importDashboardService, service13, pluginService, dashboardService) - sanitizerProvider := sanitizer.ProvideService(renderingService) healthService, err := grpcserver.ProvideHealthService(cfg, grpcserverProvider) if err != nil { return nil, err @@ -848,7 +846,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api } ossUserProtectionImpl := authinfoimpl.ProvideOSSUserProtectionService() registration := authnimpl.ProvideRegistration(cfg, authnService, orgService, userAuthTokenService, acimplService, permissionRegistry, apikeyService, userService, authService, ossUserProtectionImpl, loginattemptimplService, quotaService, authinfoimplService, renderingService, featureToggles, oauthtokenService, socialService, remoteCache, ldapImpl, ossImpl, tracingService, tempuserService, notificationService) - backgroundServiceRegistry := backgroundsvcs.ProvideBackgroundServiceRegistry(httpServer, alertNG, cleanUpService, grafanaLive, gateway, notificationService, pluginstoreService, renderingService, userAuthTokenService, tracingService, provisioningServiceImpl, usageStats, statscollectorService, grafanaService, pluginsService, internalMetricsService, secretsService, remoteCache, storageService, searchService, entityEventsService, serviceAccountsService, grpcserverProvider, secretMigrationProviderImpl, loginattemptimplService, supportbundlesimplService, metricService, keyRetriever, angulardetectorsproviderDynamic, apiserverService, anonDeviceService, ssosettingsimplService, pluginexternalService, plugininstallerService, zanzanaReconciler, appregistryService, dashboardUpdater, dashboardServiceImpl, serviceImpl, serviceAccountsProxy, sanitizerProvider, healthService, reflectionService, apiService, apiregistryService, idimplService, teamAPI, ssosettingsimplService, cloudmigrationService, registration) + backgroundServiceRegistry := backgroundsvcs.ProvideBackgroundServiceRegistry(httpServer, alertNG, cleanUpService, grafanaLive, gateway, notificationService, pluginstoreService, renderingService, userAuthTokenService, tracingService, provisioningServiceImpl, usageStats, statscollectorService, grafanaService, pluginsService, internalMetricsService, secretsService, remoteCache, storageService, searchService, entityEventsService, serviceAccountsService, grpcserverProvider, secretMigrationProviderImpl, loginattemptimplService, supportbundlesimplService, metricService, keyRetriever, angulardetectorsproviderDynamic, apiserverService, anonDeviceService, ssosettingsimplService, pluginexternalService, plugininstallerService, zanzanaReconciler, appregistryService, dashboardUpdater, dashboardServiceImpl, serviceImpl, serviceAccountsProxy, healthService, reflectionService, apiService, apiregistryService, idimplService, teamAPI, ssosettingsimplService, cloudmigrationService, registration) usageStatsProvidersRegistry := usagestatssvcs.ProvideUsageStatsProvidersRegistry(acimplService, userService) server, err := New(opts, cfg, httpServer, acimplService, provisioningServiceImpl, backgroundServiceRegistry, usageStatsProvidersRegistry, statscollectorService, registerer) if err != nil { @@ -1351,7 +1349,6 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac } importDashboardService := service11.ProvideService(routeRegisterImpl, quotaService, service14, pluginstoreService, libraryPanelService, dashboardService, accessControl, folderimplService, featureToggles) dashboardUpdater := service8.ProvideDashboardUpdater(inProcBus, pluginstoreService, service14, importDashboardService, service13, pluginService, dashboardService) - sanitizerProvider := sanitizer.ProvideService(renderingService) healthService, err := grpcserver.ProvideHealthService(cfg, grpcserverProvider) if err != nil { return nil, err @@ -1427,7 +1424,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac } ossUserProtectionImpl := authinfoimpl.ProvideOSSUserProtectionService() registration := authnimpl.ProvideRegistration(cfg, authnService, orgService, userAuthTokenService, acimplService, permissionRegistry, apikeyService, userService, authService, ossUserProtectionImpl, loginattemptimplService, quotaService, authinfoimplService, renderingService, featureToggles, oauthtokentestService, socialService, remoteCache, ldapImpl, ossImpl, tracingService, tempuserService, notificationServiceMock) - backgroundServiceRegistry := backgroundsvcs.ProvideBackgroundServiceRegistry(httpServer, alertNG, cleanUpService, grafanaLive, gateway, notificationService, pluginstoreService, renderingService, userAuthTokenService, tracingService, provisioningServiceImpl, usageStats, statscollectorService, grafanaService, pluginsService, internalMetricsService, secretsService, remoteCache, storageService, searchService, entityEventsService, serviceAccountsService, grpcserverProvider, secretMigrationProviderImpl, loginattemptimplService, supportbundlesimplService, metricService, keyRetriever, angulardetectorsproviderDynamic, apiserverService, anonDeviceService, ssosettingsimplService, pluginexternalService, plugininstallerService, zanzanaReconciler, appregistryService, dashboardUpdater, dashboardServiceImpl, serviceImpl, serviceAccountsProxy, sanitizerProvider, healthService, reflectionService, apiService, apiregistryService, idimplService, teamAPI, ssosettingsimplService, cloudmigrationService, registration) + backgroundServiceRegistry := backgroundsvcs.ProvideBackgroundServiceRegistry(httpServer, alertNG, cleanUpService, grafanaLive, gateway, notificationService, pluginstoreService, renderingService, userAuthTokenService, tracingService, provisioningServiceImpl, usageStats, statscollectorService, grafanaService, pluginsService, internalMetricsService, secretsService, remoteCache, storageService, searchService, entityEventsService, serviceAccountsService, grpcserverProvider, secretMigrationProviderImpl, loginattemptimplService, supportbundlesimplService, metricService, keyRetriever, angulardetectorsproviderDynamic, apiserverService, anonDeviceService, ssosettingsimplService, pluginexternalService, plugininstallerService, zanzanaReconciler, appregistryService, dashboardUpdater, dashboardServiceImpl, serviceImpl, serviceAccountsProxy, healthService, reflectionService, apiService, apiregistryService, idimplService, teamAPI, ssosettingsimplService, cloudmigrationService, registration) usageStatsProvidersRegistry := usagestatssvcs.ProvideUsageStatsProvidersRegistry(acimplService, userService) server, err := New(opts, cfg, httpServer, acimplService, provisioningServiceImpl, backgroundServiceRegistry, usageStatsProvidersRegistry, statscollectorService, registerer) if err != nil { @@ -1618,7 +1615,7 @@ var withOTelSet = wire.NewSet( otelTracer, grpcserver.ProvideService, interceptors.ProvideAuthenticator, ) -var wireBasicSet = wire.NewSet(annotationsimpl.ProvideService, wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)), New, api.ProvideHTTPServer, query.ProvideService, wire.Bind(new(query.Service), new(*query.ServiceImpl)), bus.ProvideBus, wire.Bind(new(bus.Bus), new(*bus.InProcBus)), rendering.ProvideService, wire.Bind(new(rendering.Service), new(*rendering.RenderingService)), routing.ProvideRegister, wire.Bind(new(routing.RouteRegister), new(*routing.RouteRegisterImpl)), hooks.ProvideService, kvstore.ProvideService, localcache.ProvideService, bundleregistry.ProvideService, wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)), updatemanager.ProvideGrafanaService, updatemanager.ProvidePluginsService, service.ProvideService, wire.Bind(new(usagestats.Service), new(*service.UsageStats)), validator3.ProvideService, legacy.ProvideLegacyMigrator, pluginsintegration.WireSet, dashboards.ProvideFileStoreManager, wire.Bind(new(dashboards.FileStore), new(*dashboards.FileStoreManager)), cloudwatch.ProvideService, cloudmonitoring.ProvideService, azuremonitor.ProvideService, postgres.ProvideService, mysql.ProvideService, mssql.ProvideService, store.ProvideEntityEventsService, dualwrite.ProvideService, httpclientprovider.New, wire.Bind(new(httpclient.Provider), new(*httpclient2.Provider)), serverlock.ProvideService, annotationsimpl.ProvideCleanupService, wire.Bind(new(annotations.Cleaner), new(*annotationsimpl.CleanupServiceImpl)), cleanup.ProvideService, shorturlimpl.ProvideService, wire.Bind(new(shorturls.Service), new(*shorturlimpl.ShortURLService)), queryhistory.ProvideService, wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)), correlations.ProvideService, wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)), quotaimpl.ProvideService, remotecache.ProvideService, wire.Bind(new(remotecache.CacheStorage), new(*remotecache.RemoteCache)), authinfoimpl.ProvideService, wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)), authinfoimpl.ProvideStore, datasourceproxy.ProvideService, sort.ProvideService, search2.ProvideService, searchV2.ProvideService, searchV2.ProvideSearchHTTPService, store.ProvideService, store.ProvideSystemUsersService, live.ProvideService, pushhttp.ProvideService, contexthandler.ProvideService, service12.ProvideService, wire.Bind(new(service12.LDAP), new(*service12.LDAPImpl)), jwt.ProvideService, wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)), store2.ProvideDBStore, image.ProvideDeleteExpiredService, ngalert.ProvideService, librarypanels.ProvideService, wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)), libraryelements.ProvideService, wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)), notifications.ProvideService, notifications.ProvideSmtpService, github.ProvideFactory, tracing.ProvideService, tracing.ProvideTracingConfig, wire.Bind(new(tracing.Tracer), new(*tracing.TracingService)), withOTelSet, testdatasource.ProvideService, api4.ProvideService, opentsdb.ProvideService, socialimpl.ProvideService, influxdb.ProvideService, wire.Bind(new(social.Service), new(*socialimpl.SocialService)), tempo.ProvideService, loki.ProvideService, graphite.ProvideService, prometheus.ProvideService, elasticsearch.ProvideService, pyroscope.ProvideService, parca.ProvideService, zipkin.ProvideService, jaeger.ProvideService, service9.ProvideCacheService, wire.Bind(new(datasources.CacheService), new(*service9.CacheServiceImpl)), service2.ProvideEncryptionService, wire.Bind(new(encryption2.Internal), new(*service2.Service)), manager.ProvideSecretsService, wire.Bind(new(secrets2.Service), new(*manager.SecretsService)), database.ProvideSecretsStore, wire.Bind(new(secrets2.Store), new(*database.SecretsStoreImpl)), grafanads.ProvideService, wire.Bind(new(dashboardsnapshots.Store), new(*database5.DashboardSnapshotStore)), database5.ProvideStore, wire.Bind(new(dashboardsnapshots.Service), new(*service10.ServiceImpl)), service10.ProvideService, service9.ProvideService, wire.Bind(new(datasources.DataSourceService), new(*service9.Service)), service9.ProvideLegacyDataSourceLookup, retriever.ProvideService, wire.Bind(new(serviceaccounts.ServiceAccountRetriever), new(*retriever.Service)), ossaccesscontrol.ProvideServiceAccountPermissions, wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), manager3.ProvideServiceAccountsService, proxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*proxy.ServiceAccountsProxy)), mtdsclient.NewNullMTDatasourceClientBuilder, expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, service7.ProvideDashboardServiceImpl, wire.Bind(new(dashboards2.PermissionsRegistrationService), new(*service7.DashboardServiceImpl)), service7.ProvideDashboardService, service7.ProvideDashboardProvisioningService, service7.ProvideDashboardPluginService, database2.ProvideDashboardStore, folderimpl.ProvideService, wire.Bind(new(folder.Service), new(*folderimpl.Service)), folderimpl.ProvideStore, wire.Bind(new(folder.Store), new(*folderimpl.FolderStoreImpl)), folderimpl.ProvideDashboardFolderStore, wire.Bind(new(folder.FolderStore), new(*folderimpl.DashboardFolderStoreImpl)), service11.ProvideService, wire.Bind(new(dashboardimport.Service), new(*service11.ImportDashboardService)), service8.ProvideService, wire.Bind(new(plugindashboards.Service), new(*service8.Service)), service8.ProvideDashboardUpdater, sanitizer.ProvideService, kvstore2.ProvideService, avatar.ProvideAvatarCacheServer, statscollector.ProvideService, csrf.ProvideCSRFFilter, wire.Bind(new(csrf.Service), new(*csrf.CSRF)), ossaccesscontrol.ProvideTeamPermissions, wire.Bind(new(accesscontrol.TeamPermissionsService), new(*ossaccesscontrol.TeamPermissionsService)), ossaccesscontrol.ProvideFolderPermissions, wire.Bind(new(accesscontrol.FolderPermissionsService), new(*ossaccesscontrol.FolderPermissionsService)), ossaccesscontrol.ProvideDashboardPermissions, wire.Bind(new(accesscontrol.DashboardPermissionsService), new(*ossaccesscontrol.DashboardPermissionsService)), ossaccesscontrol.ProvideReceiverPermissionsService, wire.Bind(new(accesscontrol.ReceiverPermissionsService), new(*ossaccesscontrol.ReceiverPermissionsService)), starimpl.ProvideService, playlistimpl.ProvideService, apikeyimpl.ProvideService, dashverimpl.ProvideService, service3.ProvideService, wire.Bind(new(publicdashboards.Service), new(*service3.PublicDashboardServiceImpl)), database3.ProvideStore, wire.Bind(new(publicdashboards.Store), new(*database3.PublicDashboardStoreImpl)), metric.ProvideService, api2.ProvideApi, api3.ProvideApi, userimpl.ProvideService, orgimpl.ProvideService, orgimpl.ProvideDeletionService, statsimpl.ProvideService, grpccontext.ProvideContextHandler, grpcserver.ProvideHealthService, grpcserver.ProvideReflectionService, resolver.ProvideEntityReferenceResolver, teamimpl.ProvideService, teamapi.ProvideTeamAPI, tempuserimpl.ProvideService, loginattemptimpl.ProvideService, wire.Bind(new(loginattempt.Service), new(*loginattemptimpl.Service)), migrations2.ProvideDataSourceMigrationService, migrations2.ProvideSecretMigrationProvider, wire.Bind(new(migrations2.SecretMigrationProvider), new(*migrations2.SecretMigrationProviderImpl)), resourcepermissions.NewActionSetService, wire.Bind(new(accesscontrol.ActionResolver), new(resourcepermissions.ActionSetService)), wire.Bind(new(pluginaccesscontrol.ActionSetRegistry), new(resourcepermissions.ActionSetService)), permreg.ProvidePermissionRegistry, acimpl.ProvideAccessControl, dualwrite2.ProvideZanzanaReconciler, navtreeimpl.ProvideService, wire.Bind(new(accesscontrol.AccessControl), new(*acimpl.AccessControl)), wire.Bind(new(notifications.TempUserStore), new(tempuser.Service)), tagimpl.ProvideService, wire.Bind(new(tag.Service), new(*tagimpl.Service)), authnimpl.ProvideService, authnimpl.ProvideIdentitySynchronizer, authnimpl.ProvideAuthnService, authnimpl.ProvideAuthnServiceAuthenticateOnly, authnimpl.ProvideRegistration, supportbundlesimpl.ProvideService, extsvcaccounts.ProvideExtSvcAccountsService, wire.Bind(new(serviceaccounts.ExtSvcAccountsService), new(*extsvcaccounts.ExtSvcAccountsService)), registry2.ProvideExtSvcRegistry, wire.Bind(new(extsvcauth.ExternalServiceRegistry), new(*registry2.Registry)), anonstore.ProvideAnonDBStore, wire.Bind(new(anonstore.AnonStore), new(*anonstore.AnonDBStore)), loggermw.Provide, slogadapter.Provide, signingkeysimpl.ProvideEmbeddedSigningKeysService, wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)), ssosettingsimpl.ProvideService, wire.Bind(new(ssosettings.Service), new(*ssosettingsimpl.Service)), idimpl.ProvideService, wire.Bind(new(auth.IDService), new(*idimpl.Service)), cloudmigrationimpl.ProvideService, userimpl.ProvideVerifier, connectors.ProvideOrgRoleMapper, wire.Bind(new(user.Verifier), new(*userimpl.Verifier)), authz.WireSet, metadata.ProvideSecureValueMetadataStorage, metadata.ProvideKeeperMetadataStorage, metadata.ProvideDecryptStorage, decrypt.ProvideDecryptAuthorizer, decrypt.ProvideDecryptService, inline.ProvideInlineSecureValueService, encryption.ProvideDataKeyStorage, encryption.ProvideGlobalDataKeyStorage, encryption.ProvideEncryptedValueStorage, encryption.ProvideGlobalEncryptedValueStorage, service5.ProvideSecureValueService, validator.ProvideKeeperValidator, validator.ProvideSecureValueValidator, mutator.ProvideKeeperMutator, mutator.ProvideSecureValueMutator, migrator2.NewWithEngine, database4.ProvideDatabase, wire.Bind(new(contracts.Database), new(*database4.Database)), manager2.ProvideEncryptionManager, service4.ProvideAESGCMCipherService, resource.ProvideStorageMetrics, resource.ProvideIndexMetrics, apiserver.WireSet, apiregistry.WireSet, appregistry.WireSet) +var wireBasicSet = wire.NewSet(annotationsimpl.ProvideService, wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)), New, api.ProvideHTTPServer, query.ProvideService, wire.Bind(new(query.Service), new(*query.ServiceImpl)), bus.ProvideBus, wire.Bind(new(bus.Bus), new(*bus.InProcBus)), rendering.ProvideService, wire.Bind(new(rendering.Service), new(*rendering.RenderingService)), routing.ProvideRegister, wire.Bind(new(routing.RouteRegister), new(*routing.RouteRegisterImpl)), hooks.ProvideService, kvstore.ProvideService, localcache.ProvideService, bundleregistry.ProvideService, wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)), updatemanager.ProvideGrafanaService, updatemanager.ProvidePluginsService, service.ProvideService, wire.Bind(new(usagestats.Service), new(*service.UsageStats)), validator3.ProvideService, legacy.ProvideLegacyMigrator, pluginsintegration.WireSet, dashboards.ProvideFileStoreManager, wire.Bind(new(dashboards.FileStore), new(*dashboards.FileStoreManager)), cloudwatch.ProvideService, cloudmonitoring.ProvideService, azuremonitor.ProvideService, postgres.ProvideService, mysql.ProvideService, mssql.ProvideService, store.ProvideEntityEventsService, dualwrite.ProvideService, httpclientprovider.New, wire.Bind(new(httpclient.Provider), new(*httpclient2.Provider)), serverlock.ProvideService, annotationsimpl.ProvideCleanupService, wire.Bind(new(annotations.Cleaner), new(*annotationsimpl.CleanupServiceImpl)), cleanup.ProvideService, shorturlimpl.ProvideService, wire.Bind(new(shorturls.Service), new(*shorturlimpl.ShortURLService)), queryhistory.ProvideService, wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)), correlations.ProvideService, wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)), quotaimpl.ProvideService, remotecache.ProvideService, wire.Bind(new(remotecache.CacheStorage), new(*remotecache.RemoteCache)), authinfoimpl.ProvideService, wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)), authinfoimpl.ProvideStore, datasourceproxy.ProvideService, sort.ProvideService, search2.ProvideService, searchV2.ProvideService, searchV2.ProvideSearchHTTPService, store.ProvideService, store.ProvideSystemUsersService, live.ProvideService, pushhttp.ProvideService, contexthandler.ProvideService, service12.ProvideService, wire.Bind(new(service12.LDAP), new(*service12.LDAPImpl)), jwt.ProvideService, wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)), store2.ProvideDBStore, image.ProvideDeleteExpiredService, ngalert.ProvideService, librarypanels.ProvideService, wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)), libraryelements.ProvideService, wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)), notifications.ProvideService, notifications.ProvideSmtpService, github.ProvideFactory, tracing.ProvideService, tracing.ProvideTracingConfig, wire.Bind(new(tracing.Tracer), new(*tracing.TracingService)), withOTelSet, testdatasource.ProvideService, api4.ProvideService, opentsdb.ProvideService, socialimpl.ProvideService, influxdb.ProvideService, wire.Bind(new(social.Service), new(*socialimpl.SocialService)), tempo.ProvideService, loki.ProvideService, graphite.ProvideService, prometheus.ProvideService, elasticsearch.ProvideService, pyroscope.ProvideService, parca.ProvideService, zipkin.ProvideService, jaeger.ProvideService, service9.ProvideCacheService, wire.Bind(new(datasources.CacheService), new(*service9.CacheServiceImpl)), service2.ProvideEncryptionService, wire.Bind(new(encryption2.Internal), new(*service2.Service)), manager.ProvideSecretsService, wire.Bind(new(secrets2.Service), new(*manager.SecretsService)), database.ProvideSecretsStore, wire.Bind(new(secrets2.Store), new(*database.SecretsStoreImpl)), grafanads.ProvideService, wire.Bind(new(dashboardsnapshots.Store), new(*database5.DashboardSnapshotStore)), database5.ProvideStore, wire.Bind(new(dashboardsnapshots.Service), new(*service10.ServiceImpl)), service10.ProvideService, service9.ProvideService, wire.Bind(new(datasources.DataSourceService), new(*service9.Service)), service9.ProvideLegacyDataSourceLookup, retriever.ProvideService, wire.Bind(new(serviceaccounts.ServiceAccountRetriever), new(*retriever.Service)), ossaccesscontrol.ProvideServiceAccountPermissions, wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), manager3.ProvideServiceAccountsService, proxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*proxy.ServiceAccountsProxy)), mtdsclient.NewNullMTDatasourceClientBuilder, expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, service7.ProvideDashboardServiceImpl, wire.Bind(new(dashboards2.PermissionsRegistrationService), new(*service7.DashboardServiceImpl)), service7.ProvideDashboardService, service7.ProvideDashboardProvisioningService, service7.ProvideDashboardPluginService, database2.ProvideDashboardStore, folderimpl.ProvideService, wire.Bind(new(folder.Service), new(*folderimpl.Service)), folderimpl.ProvideStore, wire.Bind(new(folder.Store), new(*folderimpl.FolderStoreImpl)), folderimpl.ProvideDashboardFolderStore, wire.Bind(new(folder.FolderStore), new(*folderimpl.DashboardFolderStoreImpl)), service11.ProvideService, wire.Bind(new(dashboardimport.Service), new(*service11.ImportDashboardService)), service8.ProvideService, wire.Bind(new(plugindashboards.Service), new(*service8.Service)), service8.ProvideDashboardUpdater, kvstore2.ProvideService, avatar.ProvideAvatarCacheServer, statscollector.ProvideService, csrf.ProvideCSRFFilter, wire.Bind(new(csrf.Service), new(*csrf.CSRF)), ossaccesscontrol.ProvideTeamPermissions, wire.Bind(new(accesscontrol.TeamPermissionsService), new(*ossaccesscontrol.TeamPermissionsService)), ossaccesscontrol.ProvideFolderPermissions, wire.Bind(new(accesscontrol.FolderPermissionsService), new(*ossaccesscontrol.FolderPermissionsService)), ossaccesscontrol.ProvideDashboardPermissions, wire.Bind(new(accesscontrol.DashboardPermissionsService), new(*ossaccesscontrol.DashboardPermissionsService)), ossaccesscontrol.ProvideReceiverPermissionsService, wire.Bind(new(accesscontrol.ReceiverPermissionsService), new(*ossaccesscontrol.ReceiverPermissionsService)), starimpl.ProvideService, playlistimpl.ProvideService, apikeyimpl.ProvideService, dashverimpl.ProvideService, service3.ProvideService, wire.Bind(new(publicdashboards.Service), new(*service3.PublicDashboardServiceImpl)), database3.ProvideStore, wire.Bind(new(publicdashboards.Store), new(*database3.PublicDashboardStoreImpl)), metric.ProvideService, api2.ProvideApi, api3.ProvideApi, userimpl.ProvideService, orgimpl.ProvideService, orgimpl.ProvideDeletionService, statsimpl.ProvideService, grpccontext.ProvideContextHandler, grpcserver.ProvideHealthService, grpcserver.ProvideReflectionService, resolver.ProvideEntityReferenceResolver, teamimpl.ProvideService, teamapi.ProvideTeamAPI, tempuserimpl.ProvideService, loginattemptimpl.ProvideService, wire.Bind(new(loginattempt.Service), new(*loginattemptimpl.Service)), migrations2.ProvideDataSourceMigrationService, migrations2.ProvideSecretMigrationProvider, wire.Bind(new(migrations2.SecretMigrationProvider), new(*migrations2.SecretMigrationProviderImpl)), resourcepermissions.NewActionSetService, wire.Bind(new(accesscontrol.ActionResolver), new(resourcepermissions.ActionSetService)), wire.Bind(new(pluginaccesscontrol.ActionSetRegistry), new(resourcepermissions.ActionSetService)), permreg.ProvidePermissionRegistry, acimpl.ProvideAccessControl, dualwrite2.ProvideZanzanaReconciler, navtreeimpl.ProvideService, wire.Bind(new(accesscontrol.AccessControl), new(*acimpl.AccessControl)), wire.Bind(new(notifications.TempUserStore), new(tempuser.Service)), tagimpl.ProvideService, wire.Bind(new(tag.Service), new(*tagimpl.Service)), authnimpl.ProvideService, authnimpl.ProvideIdentitySynchronizer, authnimpl.ProvideAuthnService, authnimpl.ProvideAuthnServiceAuthenticateOnly, authnimpl.ProvideRegistration, supportbundlesimpl.ProvideService, extsvcaccounts.ProvideExtSvcAccountsService, wire.Bind(new(serviceaccounts.ExtSvcAccountsService), new(*extsvcaccounts.ExtSvcAccountsService)), registry2.ProvideExtSvcRegistry, wire.Bind(new(extsvcauth.ExternalServiceRegistry), new(*registry2.Registry)), anonstore.ProvideAnonDBStore, wire.Bind(new(anonstore.AnonStore), new(*anonstore.AnonDBStore)), loggermw.Provide, slogadapter.Provide, signingkeysimpl.ProvideEmbeddedSigningKeysService, wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)), ssosettingsimpl.ProvideService, wire.Bind(new(ssosettings.Service), new(*ssosettingsimpl.Service)), idimpl.ProvideService, wire.Bind(new(auth.IDService), new(*idimpl.Service)), cloudmigrationimpl.ProvideService, userimpl.ProvideVerifier, connectors.ProvideOrgRoleMapper, wire.Bind(new(user.Verifier), new(*userimpl.Verifier)), authz.WireSet, metadata.ProvideSecureValueMetadataStorage, metadata.ProvideKeeperMetadataStorage, metadata.ProvideDecryptStorage, decrypt.ProvideDecryptAuthorizer, decrypt.ProvideDecryptService, inline.ProvideInlineSecureValueService, encryption.ProvideDataKeyStorage, encryption.ProvideGlobalDataKeyStorage, encryption.ProvideEncryptedValueStorage, encryption.ProvideGlobalEncryptedValueStorage, service5.ProvideSecureValueService, validator.ProvideKeeperValidator, validator.ProvideSecureValueValidator, mutator.ProvideKeeperMutator, mutator.ProvideSecureValueMutator, migrator2.NewWithEngine, database4.ProvideDatabase, wire.Bind(new(contracts.Database), new(*database4.Database)), manager2.ProvideEncryptionManager, service4.ProvideAESGCMCipherService, resource.ProvideStorageMetrics, resource.ProvideIndexMetrics, apiserver.WireSet, apiregistry.WireSet, appregistry.WireSet) var wireSet = wire.NewSet( wireBasicSet, metrics.WireSet, sqlstore.ProvideService, metrics2.ProvideService, wire.Bind(new(notifications.Service), new(*notifications.NotificationService)), wire.Bind(new(notifications.WebhookSender), new(*notifications.NotificationService)), wire.Bind(new(notifications.EmailSender), new(*notifications.NotificationService)), wire.Bind(new(db.DB), new(*sqlstore.SQLStore)), prefimpl.ProvideService, oauthtoken.ProvideService, wire.Bind(new(oauthtoken.OAuthTokenService), new(*oauthtoken.Service)), wire.Bind(new(cleanup.AlertRuleService), new(*store2.DBstore)), diff --git a/pkg/services/authn/clients/render_test.go b/pkg/services/authn/clients/render_test.go index fb269fd6575..c5e21dd9f79 100644 --- a/pkg/services/authn/clients/render_test.go +++ b/pkg/services/authn/clients/render_test.go @@ -6,8 +6,8 @@ import ( "testing" "time" - "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" claims "github.com/grafana/authlib/types" diff --git a/pkg/services/rendering/capabilities.go b/pkg/services/rendering/capabilities.go index 855422aece4..391040ff6f0 100644 --- a/pkg/services/rendering/capabilities.go +++ b/pkg/services/rendering/capabilities.go @@ -18,7 +18,6 @@ type CapabilityName string const ( ScalingDownImages CapabilityName = "ScalingDownImages" FullHeightImages CapabilityName = "FullHeightImages" - SVGSanitization CapabilityName = "SvgSanitization" PDFRendering CapabilityName = "PdfRendering" ) diff --git a/pkg/services/rendering/interface.go b/pkg/services/rendering/interface.go index a54d2b92560..9649c22ee16 100644 --- a/pkg/services/rendering/interface.go +++ b/pkg/services/rendering/interface.go @@ -74,15 +74,6 @@ type ErrorOpts struct { ErrorRenderUnavailable bool } -type SanitizeSVGRequest struct { - Filename string - Content []byte -} - -type SanitizeSVGResponse struct { - Sanitized []byte -} - type Result struct { FilePath string FileName string @@ -99,7 +90,6 @@ type RenderCSVResult struct { type renderFunc func(ctx context.Context, renderType RenderType, renderKey string, options Opts) (*RenderResult, error) type renderCSVFunc func(ctx context.Context, renderKey string, options CSVOpts) (*RenderCSVResult, error) -type sanitizeFunc func(ctx context.Context, req *SanitizeSVGRequest) (*SanitizeSVGResponse, error) type renderKeyProvider interface { get(ctx context.Context, opts AuthOpts) (string, error) @@ -121,16 +111,15 @@ type CapabilitySupportRequestResult struct { SemverConstraint string } -//go:generate mockgen -destination=mock.go -package=rendering github.com/grafana/grafana/pkg/services/rendering Service +//go:generate go run go.uber.org/mock/mockgen@v0.5.2 -destination=mock.go -package=rendering github.com/grafana/grafana/pkg/services/rendering Service type Service interface { IsAvailable(ctx context.Context) bool Version() string Render(ctx context.Context, renderType RenderType, opts Opts, session Session) (*RenderResult, error) RenderCSV(ctx context.Context, opts CSVOpts, session Session) (*RenderCSVResult, error) - RenderErrorImage(theme models.Theme, error error) (*RenderResult, error) + RenderErrorImage(theme models.Theme, err error) (*RenderResult, error) GetRenderUser(ctx context.Context, key string) (*RenderUser, bool) HasCapability(ctx context.Context, capability CapabilityName) (CapabilitySupportRequestResult, error) IsCapabilitySupported(ctx context.Context, capability CapabilityName) error CreateRenderingSession(ctx context.Context, authOpts AuthOpts, sessionOpts SessionOpts) (Session, error) - SanitizeSVG(ctx context.Context, req *SanitizeSVGRequest) (*SanitizeSVGResponse, error) } diff --git a/pkg/services/rendering/mock.go b/pkg/services/rendering/mock.go index 4dfe3d71bea..ab83e78be58 100644 --- a/pkg/services/rendering/mock.go +++ b/pkg/services/rendering/mock.go @@ -1,21 +1,27 @@ // Code generated by MockGen. DO NOT EDIT. // Source: github.com/grafana/grafana/pkg/services/rendering (interfaces: Service) +// +// Generated by this command: +// +// mockgen -destination=mock.go -package=rendering github.com/grafana/grafana/pkg/services/rendering Service +// +// Package rendering is a generated GoMock package. package rendering import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - models "github.com/grafana/grafana/pkg/models" + gomock "go.uber.org/mock/gomock" ) // MockService is a mock of Service interface. type MockService struct { ctrl *gomock.Controller recorder *MockServiceMockRecorder + isgomock struct{} } // MockServiceMockRecorder is the mock recorder for MockService. @@ -45,7 +51,7 @@ func (m *MockService) CreateRenderingSession(ctx context.Context, authOpts AuthO } // CreateRenderingSession indicates an expected call of CreateRenderingSession. -func (mr *MockServiceMockRecorder) CreateRenderingSession(ctx, authOpts, sessionOpts interface{}) *gomock.Call { +func (mr *MockServiceMockRecorder) CreateRenderingSession(ctx, authOpts, sessionOpts any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateRenderingSession", reflect.TypeOf((*MockService)(nil).CreateRenderingSession), ctx, authOpts, sessionOpts) } @@ -60,7 +66,7 @@ func (m *MockService) GetRenderUser(ctx context.Context, key string) (*RenderUse } // GetRenderUser indicates an expected call of GetRenderUser. -func (mr *MockServiceMockRecorder) GetRenderUser(ctx, key interface{}) *gomock.Call { +func (mr *MockServiceMockRecorder) GetRenderUser(ctx, key any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRenderUser", reflect.TypeOf((*MockService)(nil).GetRenderUser), ctx, key) } @@ -75,25 +81,11 @@ func (m *MockService) HasCapability(ctx context.Context, capability CapabilityNa } // HasCapability indicates an expected call of HasCapability. -func (mr *MockServiceMockRecorder) HasCapability(ctx, capability interface{}) *gomock.Call { +func (mr *MockServiceMockRecorder) HasCapability(ctx, capability any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasCapability", reflect.TypeOf((*MockService)(nil).HasCapability), ctx, capability) } -// IsCapabilitySupported mocks base method. -func (m *MockService) IsCapabilitySupported(ctx context.Context, capability CapabilityName) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IsCapabilitySupported", ctx, capability) - ret0, _ := ret[0].(error) - return ret0 -} - -// IsCapabilitySupported indicates an expected call of IsCapabilitySupported. -func (mr *MockServiceMockRecorder) IsCapabilitySupported(ctx, capability interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsCapabilitySupported", reflect.TypeOf((*MockService)(nil).IsCapabilitySupported), ctx, capability) -} - // IsAvailable mocks base method. func (m *MockService) IsAvailable(ctx context.Context) bool { m.ctrl.T.Helper() @@ -103,11 +95,25 @@ func (m *MockService) IsAvailable(ctx context.Context) bool { } // IsAvailable indicates an expected call of IsAvailable. -func (mr *MockServiceMockRecorder) IsAvailable(ctx interface{}) *gomock.Call { +func (mr *MockServiceMockRecorder) IsAvailable(ctx any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAvailable", reflect.TypeOf((*MockService)(nil).IsAvailable), ctx) } +// IsCapabilitySupported mocks base method. +func (m *MockService) IsCapabilitySupported(ctx context.Context, capability CapabilityName) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsCapabilitySupported", ctx, capability) + ret0, _ := ret[0].(error) + return ret0 +} + +// IsCapabilitySupported indicates an expected call of IsCapabilitySupported. +func (mr *MockServiceMockRecorder) IsCapabilitySupported(ctx, capability any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsCapabilitySupported", reflect.TypeOf((*MockService)(nil).IsCapabilitySupported), ctx, capability) +} + // Render mocks base method. func (m *MockService) Render(ctx context.Context, renderType RenderType, opts Opts, session Session) (*RenderResult, error) { m.ctrl.T.Helper() @@ -118,7 +124,7 @@ func (m *MockService) Render(ctx context.Context, renderType RenderType, opts Op } // Render indicates an expected call of Render. -func (mr *MockServiceMockRecorder) Render(ctx, renderType, opts, session interface{}) *gomock.Call { +func (mr *MockServiceMockRecorder) Render(ctx, renderType, opts, session any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Render", reflect.TypeOf((*MockService)(nil).Render), ctx, renderType, opts, session) } @@ -133,7 +139,7 @@ func (m *MockService) RenderCSV(ctx context.Context, opts CSVOpts, session Sessi } // RenderCSV indicates an expected call of RenderCSV. -func (mr *MockServiceMockRecorder) RenderCSV(ctx, opts, session interface{}) *gomock.Call { +func (mr *MockServiceMockRecorder) RenderCSV(ctx, opts, session any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RenderCSV", reflect.TypeOf((*MockService)(nil).RenderCSV), ctx, opts, session) } @@ -148,24 +154,9 @@ func (m *MockService) RenderErrorImage(theme models.Theme, err error) (*RenderRe } // RenderErrorImage indicates an expected call of RenderErrorImage. -func (mr *MockServiceMockRecorder) RenderErrorImage(theme, error interface{}) *gomock.Call { +func (mr *MockServiceMockRecorder) RenderErrorImage(theme, err any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RenderErrorImage", reflect.TypeOf((*MockService)(nil).RenderErrorImage), theme, error) -} - -// SanitizeSVG mocks base method. -func (m *MockService) SanitizeSVG(ctx context.Context, req *SanitizeSVGRequest) (*SanitizeSVGResponse, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SanitizeSVG", ctx, req) - ret0, _ := ret[0].(*SanitizeSVGResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SanitizeSVG indicates an expected call of SanitizeSVG. -func (mr *MockServiceMockRecorder) SanitizeSVG(ctx, req interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SanitizeSVG", reflect.TypeOf((*MockService)(nil).SanitizeSVG), ctx, req) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RenderErrorImage", reflect.TypeOf((*MockService)(nil).RenderErrorImage), theme, err) } // Version mocks base method. diff --git a/pkg/services/rendering/rendering.go b/pkg/services/rendering/rendering.go index 2585f9b783e..8e0820fa3e4 100644 --- a/pkg/services/rendering/rendering.go +++ b/pkg/services/rendering/rendering.go @@ -31,8 +31,6 @@ type RenderingService struct { plugin Plugin renderAction renderFunc renderCSVAction renderCSVFunc - sanitizeSVGAction sanitizeFunc - sanitizeURL string domain string inProgressCount int32 version string @@ -75,21 +73,14 @@ func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, remot logger := log.New("rendering") - // URL for HTTP sanitize API - var sanitizeURL string - // value used for domain attribute of renderKey cookie var domain string // value used by the image renderer to make requests to Grafana rendererCallbackURL := cfg.RendererCallbackUrl - if cfg.RendererServerUrl != "" { - sanitizeURL = getSanitizerURL(cfg.RendererServerUrl) - - // Default value for callback URL using a remote renderer should be AppURL - if rendererCallbackURL == "" { - rendererCallbackURL = cfg.AppURL - } + // Default value for callback URL using a remote renderer should be AppURL + if cfg.RendererServerUrl != "" && rendererCallbackURL == "" { + rendererCallbackURL = cfg.AppURL } switch { @@ -140,10 +131,6 @@ func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, remot name: ScalingDownImages, semverConstraint: ">= 3.4.0", }, - { - name: SVGSanitization, - semverConstraint: ">= 3.5.0", - }, { name: PDFRendering, semverConstraint: ">= 3.10.0", @@ -155,7 +142,6 @@ func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, remot RendererPluginManager: rm, log: logger, domain: domain, - sanitizeURL: sanitizeURL, pluginAvailable: exists, rendererCallbackURL: rendererCallbackURL, } @@ -165,11 +151,6 @@ func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, remot return s, nil } -func getSanitizerURL(rendererURL string) string { - rendererBaseURL := strings.TrimSuffix(rendererURL, "/render") - return rendererBaseURL + "/sanitize" -} - func (rs *RenderingService) Run(ctx context.Context) error { if rs.remoteAvailable() { rs.log = rs.log.New("renderer", "http") @@ -188,7 +169,6 @@ func (rs *RenderingService) Run(ctx context.Context) error { }) rs.renderAction = rs.renderViaHTTP rs.renderCSVAction = rs.renderCSVViaHTTP - rs.sanitizeSVGAction = rs.sanitizeViaHTTP refreshTicker := time.NewTicker(remoteVersionRefreshInterval) @@ -213,7 +193,6 @@ func (rs *RenderingService) Run(ctx context.Context) error { rs.version = rp.Version() rs.renderAction = rs.renderViaPlugin rs.renderCSVAction = rs.renderCSVViaPlugin - rs.sanitizeSVGAction = rs.sanitizeSVGViaPlugin <-ctx.Done() return nil @@ -367,24 +346,6 @@ func (rs *RenderingService) RenderCSV(ctx context.Context, opts CSVOpts, session return result, err } -func (rs *RenderingService) SanitizeSVG(ctx context.Context, req *SanitizeSVGRequest) (*SanitizeSVGResponse, error) { - capability, err := rs.HasCapability(ctx, SVGSanitization) - if err != nil { - return nil, err - } - - if !capability.IsSupported { - return nil, fmt.Errorf("svg sanitization unsupported, requires image renderer version: %s", capability.SemverConstraint) - } - - start := time.Now() - - action, err := rs.sanitizeSVGAction(ctx, req) - rs.log.Info("svg sanitization finished", "duration", time.Since(start), "filename", req.Filename, "isError", err != nil) - - return action, err -} - func (rs *RenderingService) renderCSV(ctx context.Context, opts CSVOpts, renderKeyProvider renderKeyProvider) (*RenderCSVResult, error) { logger := rs.log.FromContext(ctx) diff --git a/pkg/services/rendering/svgSanitizer.go b/pkg/services/rendering/svgSanitizer.go deleted file mode 100644 index 9a87992e104..00000000000 --- a/pkg/services/rendering/svgSanitizer.go +++ /dev/null @@ -1,182 +0,0 @@ -package rendering - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "mime/multipart" - "net/http" - "net/textproto" - "net/url" - "time" - - "github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2" -) - -var ( - domPurifySvgConfig = map[string]any{ - // domPurifyConfig is passed directly to DOMPurify https://github.com/cure53/DOMPurify#can-i-configure-dompurify - "domPurifyConfig": map[string]any{ - "USE_PROFILES": map[string]bool{"svg": true, "svgFilters": true}, - "ADD_TAGS": []string{"use"}, - }, - // allowAllLinksInSvgUseTags will preserve all `use` tags. - // By default, we remove all non-self-referential `use` tags, i.e. those which `href` attribute does not start with `#` - "allowAllLinksInSvgUseTags": false, - } - domPurifyConfigType = "DOMPurify" -) - -type formFile struct { - fileName string - key string - contentType string - content io.Reader -} - -func createMultipartRequestBody(values []formFile) (bytes.Buffer, string, error) { - var b bytes.Buffer - w := multipart.NewWriter(&b) - for _, f := range values { - h := make(textproto.MIMEHeader) - h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, f.key, f.fileName)) - h.Set("Content-Type", f.contentType) - formWriter, err := w.CreatePart(h) - - if err != nil { - return bytes.Buffer{}, "", err - } - - if _, err := io.Copy(formWriter, f.content); err != nil { - return bytes.Buffer{}, "", err - } - - if x, ok := f.content.(io.Closer); ok { - _ = x.Close() - } - } - - if err := w.Close(); err != nil { - return bytes.Buffer{}, "", err - } - - return b, w.FormDataContentType(), nil -} - -func (rs *RenderingService) sanitizeViaHTTP(ctx context.Context, req *SanitizeSVGRequest) (*SanitizeSVGResponse, error) { - sanitizerUrl, err := url.Parse(rs.sanitizeURL) - if err != nil { - return nil, err - } - - configJson, err := json.Marshal(map[string]any{ - "config": domPurifySvgConfig, - "configType": domPurifyConfigType, - }) - if err != nil { - rs.log.Error("Sanitizer - HTTP: failed to create the request config", "error", err, "filename", req.Filename) - return nil, fmt.Errorf("config creation fail: %s", err) - } - - body, contentType, err := createMultipartRequestBody([]formFile{ - { - fileName: "config", - key: "config", - contentType: "application/json", - content: bytes.NewReader(configJson), - }, - { - fileName: req.Filename, - key: "file", - contentType: "image/svg+xml", - content: bytes.NewReader(req.Content), - }, - }) - if err != nil { - rs.log.Error("Sanitizer - HTTP: failed to create the request body", "error", err, "filename", req.Filename) - return nil, fmt.Errorf("body creation fail: %s", err) - } - - reqContext, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - httpReq, err := http.NewRequestWithContext(reqContext, "POST", sanitizerUrl.String(), &body) - if err != nil { - rs.log.Error("Sanitizer - HTTP: failed to create the HTTP request", "error", err, "filename", req.Filename) - return nil, err - } - - httpReq.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", rs.Cfg.BuildVersion)) - httpReq.Header.Set("Content-Type", contentType) - - rs.log.Debug("Sanitizer - HTTP: calling", "filename", req.Filename, "contentLength", len(req.Content), "url", sanitizerUrl) - // make request to renderer server - resp, err := netClient.Do(httpReq) - if err != nil { - rs.log.Error("Sanitizer - HTTP: failed to send request", "error", err) - return nil, fmt.Errorf("sanitizer - HTTP: failed to send request: %w", err) - } - - defer func() { - if err := resp.Body.Close(); err != nil { - rs.log.Error("Sanitizer - HTTP: failed to close response body", "statusCode", resp.StatusCode, "error", err) - } - }() - - if resp.StatusCode != http.StatusOK { - if body, err := io.ReadAll(resp.Body); body != nil { - rs.log.Error("Sanitizer - HTTP: failed to sanitize", "statusCode", resp.StatusCode, "error", err, "resp", string(body)) - } else { - rs.log.Error("Sanitizer - HTTP: failed to sanitize", "statusCode", resp.StatusCode, "error", err) - } - return nil, fmt.Errorf("sanitizer - HTTP: failed to sanitize %s", req.Filename) - } - - sanitized, err := io.ReadAll(resp.Body) - if err != nil { - rs.log.Error("Sanitizer - HTTP: failed to read response body", "error", err, "filename", req.Filename) - return nil, fmt.Errorf("sanitizer - HTTP: failed to read response body: %s", err) - } - - return &SanitizeSVGResponse{Sanitized: sanitized}, nil -} - -func (rs *RenderingService) sanitizeSVGViaPlugin(ctx context.Context, req *SanitizeSVGRequest) (*SanitizeSVGResponse, error) { - ctx, cancel := context.WithTimeout(ctx, time.Second*20) - defer cancel() - - domPurifyConfig, err := json.Marshal(domPurifySvgConfig) - if err != nil { - rs.log.Error("Sanitizer - plugin: failed to parse domPurifyConfig") - return nil, fmt.Errorf("sanitizer - plugin: failed to parse domPurifyConfig %s", err) - } - grpcReq := &pluginextensionv2.SanitizeRequest{ - Filename: req.Filename, - Content: req.Content, - ConfigType: domPurifyConfigType, - Config: domPurifyConfig, - } - rs.log.Debug("Sanitizer - plugin: calling", "filename", req.Filename, "contentLength", len(req.Content)) - - rc, err := rs.plugin.Client() - if err != nil { - return nil, err - } - rsp, err := rc.Sanitize(ctx, grpcReq) - if err != nil { - if errors.Is(ctx.Err(), context.DeadlineExceeded) { - rs.log.Info("Sanitizer - plugin: time out") - return nil, ErrTimeout - } - - return nil, err - } - - if rsp.Error != "" { - return nil, fmt.Errorf("sanitizer - plugin: failed to sanitize: %s", rsp.Error) - } - - return &SanitizeSVGResponse{Sanitized: rsp.Sanitized}, nil -} diff --git a/pkg/services/screenshot/screenshot_test.go b/pkg/services/screenshot/screenshot_test.go index 77583649883..b553bcc25b0 100644 --- a/pkg/services/screenshot/screenshot_test.go +++ b/pkg/services/screenshot/screenshot_test.go @@ -5,11 +5,11 @@ import ( "fmt" "testing" - "github.com/golang/mock/gomock" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/org" diff --git a/pkg/services/store/sanitize.go b/pkg/services/store/sanitize.go index 7c2aa356917..7cdd8b4bb77 100644 --- a/pkg/services/store/sanitize.go +++ b/pkg/services/store/sanitize.go @@ -2,46 +2,23 @@ package store import ( "context" + "errors" "mime" "path/filepath" "github.com/grafana/grafana/pkg/infra/filestorage" - "github.com/grafana/grafana/pkg/services/rendering" - "github.com/grafana/grafana/pkg/services/store/sanitizer" "github.com/grafana/grafana/pkg/services/user" ) -func (s *standardStorageService) sanitizeContents(ctx context.Context, user *user.SignedInUser, req *UploadRequest, storagePath string) ([]byte, error) { +func (s *standardStorageService) sanitizeUploadRequest(ctx context.Context, user *user.SignedInUser, req *UploadRequest, storagePath string) (*filestorage.UpsertFileCommand, error) { if req.EntityType == EntityTypeImage { ext := filepath.Ext(req.Path) - if ext == ".svg" { - resp, err := sanitizer.SanitizeSVG(ctx, &rendering.SanitizeSVGRequest{ - Filename: storagePath, - Content: req.Contents, - }) - if err != nil { - if s.cfg != nil && s.cfg.AllowUnsanitizedSvgUpload { - grafanaStorageLogger.Debug("Allowing unsanitized svg upload", "filename", req.Path, "sanitizationError", err) - return req.Contents, nil - } else { - grafanaStorageLogger.Debug("Disallowing unsanitized svg upload", "filename", req.Path, "sanitizationError", err) - return nil, err - } - } - - return resp.Sanitized, nil + if ext == ".svg" && !s.cfg.AllowUnsanitizedSvgUpload { + grafanaStorageLogger.Debug("Disallowing svg upload", "filename", req.Path) + return nil, errors.New("SVG uploads are not allowed") } } - return req.Contents, nil -} - -func (s *standardStorageService) sanitizeUploadRequest(ctx context.Context, user *user.SignedInUser, req *UploadRequest, storagePath string) (*filestorage.UpsertFileCommand, error) { - contents, err := s.sanitizeContents(ctx, user, req, storagePath) - if err != nil { - return nil, err - } - // we have already validated that the file contents match the extension in `./validate.go` mimeType := mime.TypeByExtension(filepath.Ext(req.Path)) if mimeType == "" { @@ -51,7 +28,7 @@ func (s *standardStorageService) sanitizeUploadRequest(ctx context.Context, user return &filestorage.UpsertFileCommand{ Path: storagePath, - Contents: contents, + Contents: req.Contents, MimeType: mimeType, CacheControl: req.CacheControl, ContentDisposition: req.ContentDisposition, diff --git a/pkg/services/store/sanitizer/Provider.go b/pkg/services/store/sanitizer/Provider.go deleted file mode 100644 index 9830b05c200..00000000000 --- a/pkg/services/store/sanitizer/Provider.go +++ /dev/null @@ -1,23 +0,0 @@ -package sanitizer - -import ( - "context" - "errors" - - "github.com/grafana/grafana/pkg/services/rendering" -) - -// workaround for cyclic dep between the store and the renderer - -type Provider struct{} - -var SanitizeSVG = func(ctx context.Context, req *rendering.SanitizeSVGRequest) (*rendering.SanitizeSVGResponse, error) { - return nil, errors.New("not implemented") -} - -func ProvideService( - renderer rendering.Service, -) *Provider { - SanitizeSVG = renderer.SanitizeSVG - return &Provider{} -} From 2e5b55a8557e664ed7a3332bffa9981e78df891e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Farkas?= Date: Tue, 19 Aug 2025 12:37:56 +0200 Subject: [PATCH 04/26] datasources: querier: renamed the "mt" builder to "qs" builder (#109779) --- .github/CODEOWNERS | 2 +- pkg/api/ds_query_test.go | 6 +++--- pkg/expr/dataplane_test.go | 4 ++-- pkg/expr/nodes.go | 6 +++--- pkg/expr/service.go | 8 ++++---- pkg/expr/service_test.go | 4 ++-- pkg/registry/apis/query/query.go | 8 ++++---- pkg/server/wire.go | 4 ++-- pkg/server/wire_gen.go | 16 +++++++-------- .../qs_datasource_client_builder.go} | 20 +++++++++---------- pkg/services/ngalert/eval/eval_test.go | 6 +++--- .../ngalert/schedule/schedule_unit_test.go | 6 +++--- pkg/services/query/query.go | 18 ++++++++--------- pkg/services/query/query_test.go | 14 ++++++------- 14 files changed, 61 insertions(+), 61 deletions(-) rename pkg/services/{mtdsclient/mt_datasource_client_builder.go => dsquerierclient/qs_datasource_client_builder.go} (72%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1d0c622d57d..a6dee32a4fd 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -158,7 +158,7 @@ /pkg/services/hooks/ @grafana/grafana-backend-group /pkg/services/kmsproviders/ @grafana/grafana-operator-experience-squad /pkg/services/licensing/ @grafana/grafana-operator-experience-squad -/pkg/services/mtdsclient/ @grafana/grafana-datasources-core-services +/pkg/services/dsquerierclient/ @grafana/grafana-datasources-core-services /pkg/services/navtree/ @grafana/grafana-backend-group /pkg/services/notifications/ @grafana/grafana-backend-group /pkg/services/org/ @grafana/grafana-backend-group diff --git a/pkg/api/ds_query_test.go b/pkg/api/ds_query_test.go index 3f057367e4f..e330f8f4438 100644 --- a/pkg/api/ds_query_test.go +++ b/pkg/api/ds_query_test.go @@ -22,7 +22,7 @@ import ( "github.com/grafana/grafana/pkg/plugins/manager/registry" "github.com/grafana/grafana/pkg/services/datasources" fakeDatasources "github.com/grafana/grafana/pkg/services/datasources/fakes" - "github.com/grafana/grafana/pkg/services/mtdsclient" + "github.com/grafana/grafana/pkg/services/dsquerierclient" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginconfig" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" pluginSettings "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings/service" @@ -81,7 +81,7 @@ func TestAPIEndpoint_Metrics_QueryMetricsV2(t *testing.T) { ), pluginconfig.NewFakePluginRequestConfigProvider(), ), - mtdsclient.NewNullMTDatasourceClientBuilder(), + dsquerierclient.NewNullQSDatasourceClientBuilder(), ) server := SetupAPITestServer(t, func(hs *HTTPServer) { hs.queryDataService = qds @@ -264,7 +264,7 @@ func TestDataSourceQueryError(t *testing.T) { &fakeDatasources.FakeCacheService{}, ds, pluginSettings.ProvideService(dbtest.NewFakeDB(), secretstest.NewFakeSecretsService()), pluginconfig.NewFakePluginRequestConfigProvider()), - mtdsclient.NewNullMTDatasourceClientBuilder(), + dsquerierclient.NewNullQSDatasourceClientBuilder(), ) hs.QuotaService = quotatest.New(false, nil) }) diff --git a/pkg/expr/dataplane_test.go b/pkg/expr/dataplane_test.go index a8247a2bb9b..0f04fdd0c1a 100644 --- a/pkg/expr/dataplane_test.go +++ b/pkg/expr/dataplane_test.go @@ -17,8 +17,8 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/datasources" datafakes "github.com/grafana/grafana/pkg/services/datasources/fakes" + "github.com/grafana/grafana/pkg/services/dsquerierclient" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/mtdsclient" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginconfig" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" @@ -71,7 +71,7 @@ func framesPassThroughService(t *testing.T, frames data.Frames) (data.Frames, er Features: features, Tracer: tracing.InitializeTracerForTest(), }, - mtDatasourceClientBuilder: mtdsclient.NewNullMTDatasourceClientBuilder(), + qsDatasourceClientBuilder: dsquerierclient.NewNullQSDatasourceClientBuilder(), } queries := []Query{{ RefID: "A", diff --git a/pkg/expr/nodes.go b/pkg/expr/nodes.go index eaaff0f1388..85ce5d533ad 100644 --- a/pkg/expr/nodes.go +++ b/pkg/expr/nodes.go @@ -395,7 +395,7 @@ func (dn *DSNode) Execute(ctx context.Context, now time.Time, _ mathexp.Vars, s }() var resp *backend.QueryDataResponse - mtDSClient, ok, err := s.mtDatasourceClientBuilder.BuildClient(dn.datasource.Type, dn.datasource.UID) + qsDSClient, ok, err := s.qsDatasourceClientBuilder.BuildClient(dn.datasource.Type, dn.datasource.UID) if err != nil { return mathexp.Results{}, MakeQueryError(dn.refID, dn.datasource.UID, err) } @@ -410,14 +410,14 @@ func (dn *DSNode) Execute(ctx context.Context, now time.Time, _ mathexp.Vars, s if err != nil { return mathexp.Results{}, MakeQueryError(dn.refID, dn.datasource.UID, err) } - } else { + } else { // use query-service client (single or multi tenant) k8sReq, err := ConvertBackendRequestToDataRequest(req) if err != nil { return mathexp.Results{}, MakeQueryError(dn.refID, dn.datasource.UID, err) } // make the query with a mt client - resp, err = mtDSClient.QueryData(ctx, *k8sReq) + resp, err = qsDSClient.QueryData(ctx, *k8sReq) // handle error if err != nil { diff --git a/pkg/expr/service.go b/pkg/expr/service.go index b624685c38e..9dd06f27983 100644 --- a/pkg/expr/service.go +++ b/pkg/expr/service.go @@ -15,8 +15,8 @@ import ( "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/datasources" + "github.com/grafana/grafana/pkg/services/dsquerierclient" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/mtdsclient" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" "github.com/grafana/grafana/pkg/setting" ) @@ -68,7 +68,7 @@ type Service struct { tracer tracing.Tracer metrics *metrics.ExprMetrics - mtDatasourceClientBuilder mtdsclient.MTDatasourceClientBuilder + qsDatasourceClientBuilder dsquerierclient.QSDatasourceClientBuilder } type pluginContextProvider interface { @@ -77,7 +77,7 @@ type pluginContextProvider interface { } func ProvideService(cfg *setting.Cfg, pluginClient plugins.Client, pCtxProvider *plugincontext.Provider, - features featuremgmt.FeatureToggles, registerer prometheus.Registerer, tracer tracing.Tracer, builder mtdsclient.MTDatasourceClientBuilder) *Service { + features featuremgmt.FeatureToggles, registerer prometheus.Registerer, tracer tracing.Tracer, builder dsquerierclient.QSDatasourceClientBuilder) *Service { return &Service{ cfg: cfg, dataService: pluginClient, @@ -90,7 +90,7 @@ func ProvideService(cfg *setting.Cfg, pluginClient plugins.Client, pCtxProvider Features: features, Tracer: tracer, }, - mtDatasourceClientBuilder: builder, + qsDatasourceClientBuilder: builder, } } diff --git a/pkg/expr/service_test.go b/pkg/expr/service_test.go index 42f495d67ac..af159f1cfb5 100644 --- a/pkg/expr/service_test.go +++ b/pkg/expr/service_test.go @@ -19,8 +19,8 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/datasources" datafakes "github.com/grafana/grafana/pkg/services/datasources/fakes" + "github.com/grafana/grafana/pkg/services/dsquerierclient" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/mtdsclient" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginconfig" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" @@ -256,6 +256,6 @@ func newMockQueryService(responses map[string]backend.DataResponse, queries []Qu Features: features, Tracer: tracing.InitializeTracerForTest(), }, - mtDatasourceClientBuilder: mtdsclient.NewNullMTDatasourceClientBuilder(), + qsDatasourceClientBuilder: dsquerierclient.NewNullQSDatasourceClientBuilder(), }, &Request{Queries: queries, User: &user.SignedInUser{}} } diff --git a/pkg/registry/apis/query/query.go b/pkg/registry/apis/query/query.go index f2b574de933..feb6975bc80 100644 --- a/pkg/registry/apis/query/query.go +++ b/pkg/registry/apis/query/query.go @@ -15,7 +15,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/expr" "github.com/grafana/grafana/pkg/services/datasources" - "github.com/grafana/grafana/pkg/services/mtdsclient" + "github.com/grafana/grafana/pkg/services/dsquerierclient" "github.com/grafana/grafana/pkg/setting" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" @@ -257,7 +257,7 @@ func handleQuery(ctx context.Context, raw query.QueryDataRequest, b QueryAPIBuil dsQuerierLoggerWithSlug := instance.GetLogger(connectLogger).New("ruleuid", headers["X-Rule-Uid"]) - mtDsClientBuilder := mtdsclient.NewMtDatasourceClientBuilderWithInstance( + qsDsClientBuilder := dsquerierclient.NewQsDatasourceClientBuilderWithInstance( instance, ctx, dsQuerierLoggerWithSlug, @@ -275,10 +275,10 @@ func handleQuery(ctx context.Context, raw query.QueryDataRequest, b QueryAPIBuil instanceConfig.FeatureToggles, nil, b.tracer, - mtDsClientBuilder, + qsDsClientBuilder, ) - qdr, err := service.QueryData(ctx, dsQuerierLoggerWithSlug, cache, exprService, mReq, mtDsClientBuilder, headers) + qdr, err := service.QueryData(ctx, dsQuerierLoggerWithSlug, cache, exprService, mReq, qsDsClientBuilder, headers) // tell the `instance` structure that it can now report // metrics that are only reported once during a request diff --git a/pkg/server/wire.go b/pkg/server/wire.go index a6c18625815..e50fcac941d 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -84,6 +84,7 @@ import ( "github.com/grafana/grafana/pkg/services/datasourceproxy" "github.com/grafana/grafana/pkg/services/datasources" datasourceservice "github.com/grafana/grafana/pkg/services/datasources/service" + "github.com/grafana/grafana/pkg/services/dsquerierclient" "github.com/grafana/grafana/pkg/services/encryption" encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service" "github.com/grafana/grafana/pkg/services/extsvcauth" @@ -105,7 +106,6 @@ import ( "github.com/grafana/grafana/pkg/services/login/authinfoimpl" "github.com/grafana/grafana/pkg/services/loginattempt" "github.com/grafana/grafana/pkg/services/loginattempt/loginattemptimpl" - "github.com/grafana/grafana/pkg/services/mtdsclient" "github.com/grafana/grafana/pkg/services/navtree/navtreeimpl" "github.com/grafana/grafana/pkg/services/ngalert" ngimage "github.com/grafana/grafana/pkg/services/ngalert/image" @@ -327,7 +327,7 @@ var wireBasicSet = wire.NewSet( serviceaccountsmanager.ProvideServiceAccountsService, serviceaccountsproxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*serviceaccountsproxy.ServiceAccountsProxy)), - mtdsclient.NewNullMTDatasourceClientBuilder, + dsquerierclient.NewNullQSDatasourceClientBuilder, expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index 656a18efc52..45a5f618e92 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -124,6 +124,7 @@ import ( "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources/guardian" service9 "github.com/grafana/grafana/pkg/services/datasources/service" + "github.com/grafana/grafana/pkg/services/dsquerierclient" encryption2 "github.com/grafana/grafana/pkg/services/encryption" "github.com/grafana/grafana/pkg/services/encryption/provider" service2 "github.com/grafana/grafana/pkg/services/encryption/service" @@ -149,7 +150,6 @@ import ( "github.com/grafana/grafana/pkg/services/login/authinfoimpl" "github.com/grafana/grafana/pkg/services/loginattempt" "github.com/grafana/grafana/pkg/services/loginattempt/loginattemptimpl" - "github.com/grafana/grafana/pkg/services/mtdsclient" "github.com/grafana/grafana/pkg/services/navtree/navtreeimpl" "github.com/grafana/grafana/pkg/services/ngalert" "github.com/grafana/grafana/pkg/services/ngalert/image" @@ -626,9 +626,9 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api starService := starimpl.ProvideService(sqlStore) searchSearchService := search2.ProvideService(cfg, sqlStore, starService, dashboardService, folderimplService, featureToggles, sortService) plugincontextProvider := plugincontext.ProvideService(cfg, cacheService, pluginstoreService, cacheServiceImpl, service15, service13, requestConfigProvider) - mtDatasourceClientBuilder := mtdsclient.NewNullMTDatasourceClientBuilder() - exprService := expr.ProvideService(cfg, middlewareHandler, plugincontextProvider, featureToggles, registerer, tracingService, mtDatasourceClientBuilder) - queryServiceImpl := query.ProvideService(cfg, cacheServiceImpl, exprService, ossDataSourceRequestValidator, middlewareHandler, plugincontextProvider, mtDatasourceClientBuilder) + qsDatasourceClientBuilder := dsquerierclient.NewNullQSDatasourceClientBuilder() + exprService := expr.ProvideService(cfg, middlewareHandler, plugincontextProvider, featureToggles, registerer, tracingService, qsDatasourceClientBuilder) + queryServiceImpl := query.ProvideService(cfg, cacheServiceImpl, exprService, ossDataSourceRequestValidator, middlewareHandler, plugincontextProvider, qsDatasourceClientBuilder) repositoryImpl := annotationsimpl.ProvideService(sqlStore, cfg, featureToggles, tagimplService, tracingService, dBstore, dashboardService, registerer) grafanaLive, err := live.ProvideService(plugincontextProvider, cfg, routeRegisterImpl, pluginstoreService, middlewareHandler, cacheService, cacheServiceImpl, sqlStore, secretsService, usageStats, queryServiceImpl, featureToggles, accessControl, dashboardService, repositoryImpl, orgService, eventualRestConfigProvider) if err != nil { @@ -1203,9 +1203,9 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac starService := starimpl.ProvideService(sqlStore) searchSearchService := search2.ProvideService(cfg, sqlStore, starService, dashboardService, folderimplService, featureToggles, sortService) plugincontextProvider := plugincontext.ProvideService(cfg, cacheService, pluginstoreService, cacheServiceImpl, service15, service13, requestConfigProvider) - mtDatasourceClientBuilder := mtdsclient.NewNullMTDatasourceClientBuilder() - exprService := expr.ProvideService(cfg, middlewareHandler, plugincontextProvider, featureToggles, registerer, tracingService, mtDatasourceClientBuilder) - queryServiceImpl := query.ProvideService(cfg, cacheServiceImpl, exprService, ossDataSourceRequestValidator, middlewareHandler, plugincontextProvider, mtDatasourceClientBuilder) + qsDatasourceClientBuilder := dsquerierclient.NewNullQSDatasourceClientBuilder() + exprService := expr.ProvideService(cfg, middlewareHandler, plugincontextProvider, featureToggles, registerer, tracingService, qsDatasourceClientBuilder) + queryServiceImpl := query.ProvideService(cfg, cacheServiceImpl, exprService, ossDataSourceRequestValidator, middlewareHandler, plugincontextProvider, qsDatasourceClientBuilder) repositoryImpl := annotationsimpl.ProvideService(sqlStore, cfg, featureToggles, tagimplService, tracingService, dBstore, dashboardService, registerer) grafanaLive, err := live.ProvideService(plugincontextProvider, cfg, routeRegisterImpl, pluginstoreService, middlewareHandler, cacheService, cacheServiceImpl, sqlStore, secretsService, usageStats, queryServiceImpl, featureToggles, accessControl, dashboardService, repositoryImpl, orgService, eventualRestConfigProvider) if err != nil { @@ -1615,7 +1615,7 @@ var withOTelSet = wire.NewSet( otelTracer, grpcserver.ProvideService, interceptors.ProvideAuthenticator, ) -var wireBasicSet = wire.NewSet(annotationsimpl.ProvideService, wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)), New, api.ProvideHTTPServer, query.ProvideService, wire.Bind(new(query.Service), new(*query.ServiceImpl)), bus.ProvideBus, wire.Bind(new(bus.Bus), new(*bus.InProcBus)), rendering.ProvideService, wire.Bind(new(rendering.Service), new(*rendering.RenderingService)), routing.ProvideRegister, wire.Bind(new(routing.RouteRegister), new(*routing.RouteRegisterImpl)), hooks.ProvideService, kvstore.ProvideService, localcache.ProvideService, bundleregistry.ProvideService, wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)), updatemanager.ProvideGrafanaService, updatemanager.ProvidePluginsService, service.ProvideService, wire.Bind(new(usagestats.Service), new(*service.UsageStats)), validator3.ProvideService, legacy.ProvideLegacyMigrator, pluginsintegration.WireSet, dashboards.ProvideFileStoreManager, wire.Bind(new(dashboards.FileStore), new(*dashboards.FileStoreManager)), cloudwatch.ProvideService, cloudmonitoring.ProvideService, azuremonitor.ProvideService, postgres.ProvideService, mysql.ProvideService, mssql.ProvideService, store.ProvideEntityEventsService, dualwrite.ProvideService, httpclientprovider.New, wire.Bind(new(httpclient.Provider), new(*httpclient2.Provider)), serverlock.ProvideService, annotationsimpl.ProvideCleanupService, wire.Bind(new(annotations.Cleaner), new(*annotationsimpl.CleanupServiceImpl)), cleanup.ProvideService, shorturlimpl.ProvideService, wire.Bind(new(shorturls.Service), new(*shorturlimpl.ShortURLService)), queryhistory.ProvideService, wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)), correlations.ProvideService, wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)), quotaimpl.ProvideService, remotecache.ProvideService, wire.Bind(new(remotecache.CacheStorage), new(*remotecache.RemoteCache)), authinfoimpl.ProvideService, wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)), authinfoimpl.ProvideStore, datasourceproxy.ProvideService, sort.ProvideService, search2.ProvideService, searchV2.ProvideService, searchV2.ProvideSearchHTTPService, store.ProvideService, store.ProvideSystemUsersService, live.ProvideService, pushhttp.ProvideService, contexthandler.ProvideService, service12.ProvideService, wire.Bind(new(service12.LDAP), new(*service12.LDAPImpl)), jwt.ProvideService, wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)), store2.ProvideDBStore, image.ProvideDeleteExpiredService, ngalert.ProvideService, librarypanels.ProvideService, wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)), libraryelements.ProvideService, wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)), notifications.ProvideService, notifications.ProvideSmtpService, github.ProvideFactory, tracing.ProvideService, tracing.ProvideTracingConfig, wire.Bind(new(tracing.Tracer), new(*tracing.TracingService)), withOTelSet, testdatasource.ProvideService, api4.ProvideService, opentsdb.ProvideService, socialimpl.ProvideService, influxdb.ProvideService, wire.Bind(new(social.Service), new(*socialimpl.SocialService)), tempo.ProvideService, loki.ProvideService, graphite.ProvideService, prometheus.ProvideService, elasticsearch.ProvideService, pyroscope.ProvideService, parca.ProvideService, zipkin.ProvideService, jaeger.ProvideService, service9.ProvideCacheService, wire.Bind(new(datasources.CacheService), new(*service9.CacheServiceImpl)), service2.ProvideEncryptionService, wire.Bind(new(encryption2.Internal), new(*service2.Service)), manager.ProvideSecretsService, wire.Bind(new(secrets2.Service), new(*manager.SecretsService)), database.ProvideSecretsStore, wire.Bind(new(secrets2.Store), new(*database.SecretsStoreImpl)), grafanads.ProvideService, wire.Bind(new(dashboardsnapshots.Store), new(*database5.DashboardSnapshotStore)), database5.ProvideStore, wire.Bind(new(dashboardsnapshots.Service), new(*service10.ServiceImpl)), service10.ProvideService, service9.ProvideService, wire.Bind(new(datasources.DataSourceService), new(*service9.Service)), service9.ProvideLegacyDataSourceLookup, retriever.ProvideService, wire.Bind(new(serviceaccounts.ServiceAccountRetriever), new(*retriever.Service)), ossaccesscontrol.ProvideServiceAccountPermissions, wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), manager3.ProvideServiceAccountsService, proxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*proxy.ServiceAccountsProxy)), mtdsclient.NewNullMTDatasourceClientBuilder, expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, service7.ProvideDashboardServiceImpl, wire.Bind(new(dashboards2.PermissionsRegistrationService), new(*service7.DashboardServiceImpl)), service7.ProvideDashboardService, service7.ProvideDashboardProvisioningService, service7.ProvideDashboardPluginService, database2.ProvideDashboardStore, folderimpl.ProvideService, wire.Bind(new(folder.Service), new(*folderimpl.Service)), folderimpl.ProvideStore, wire.Bind(new(folder.Store), new(*folderimpl.FolderStoreImpl)), folderimpl.ProvideDashboardFolderStore, wire.Bind(new(folder.FolderStore), new(*folderimpl.DashboardFolderStoreImpl)), service11.ProvideService, wire.Bind(new(dashboardimport.Service), new(*service11.ImportDashboardService)), service8.ProvideService, wire.Bind(new(plugindashboards.Service), new(*service8.Service)), service8.ProvideDashboardUpdater, kvstore2.ProvideService, avatar.ProvideAvatarCacheServer, statscollector.ProvideService, csrf.ProvideCSRFFilter, wire.Bind(new(csrf.Service), new(*csrf.CSRF)), ossaccesscontrol.ProvideTeamPermissions, wire.Bind(new(accesscontrol.TeamPermissionsService), new(*ossaccesscontrol.TeamPermissionsService)), ossaccesscontrol.ProvideFolderPermissions, wire.Bind(new(accesscontrol.FolderPermissionsService), new(*ossaccesscontrol.FolderPermissionsService)), ossaccesscontrol.ProvideDashboardPermissions, wire.Bind(new(accesscontrol.DashboardPermissionsService), new(*ossaccesscontrol.DashboardPermissionsService)), ossaccesscontrol.ProvideReceiverPermissionsService, wire.Bind(new(accesscontrol.ReceiverPermissionsService), new(*ossaccesscontrol.ReceiverPermissionsService)), starimpl.ProvideService, playlistimpl.ProvideService, apikeyimpl.ProvideService, dashverimpl.ProvideService, service3.ProvideService, wire.Bind(new(publicdashboards.Service), new(*service3.PublicDashboardServiceImpl)), database3.ProvideStore, wire.Bind(new(publicdashboards.Store), new(*database3.PublicDashboardStoreImpl)), metric.ProvideService, api2.ProvideApi, api3.ProvideApi, userimpl.ProvideService, orgimpl.ProvideService, orgimpl.ProvideDeletionService, statsimpl.ProvideService, grpccontext.ProvideContextHandler, grpcserver.ProvideHealthService, grpcserver.ProvideReflectionService, resolver.ProvideEntityReferenceResolver, teamimpl.ProvideService, teamapi.ProvideTeamAPI, tempuserimpl.ProvideService, loginattemptimpl.ProvideService, wire.Bind(new(loginattempt.Service), new(*loginattemptimpl.Service)), migrations2.ProvideDataSourceMigrationService, migrations2.ProvideSecretMigrationProvider, wire.Bind(new(migrations2.SecretMigrationProvider), new(*migrations2.SecretMigrationProviderImpl)), resourcepermissions.NewActionSetService, wire.Bind(new(accesscontrol.ActionResolver), new(resourcepermissions.ActionSetService)), wire.Bind(new(pluginaccesscontrol.ActionSetRegistry), new(resourcepermissions.ActionSetService)), permreg.ProvidePermissionRegistry, acimpl.ProvideAccessControl, dualwrite2.ProvideZanzanaReconciler, navtreeimpl.ProvideService, wire.Bind(new(accesscontrol.AccessControl), new(*acimpl.AccessControl)), wire.Bind(new(notifications.TempUserStore), new(tempuser.Service)), tagimpl.ProvideService, wire.Bind(new(tag.Service), new(*tagimpl.Service)), authnimpl.ProvideService, authnimpl.ProvideIdentitySynchronizer, authnimpl.ProvideAuthnService, authnimpl.ProvideAuthnServiceAuthenticateOnly, authnimpl.ProvideRegistration, supportbundlesimpl.ProvideService, extsvcaccounts.ProvideExtSvcAccountsService, wire.Bind(new(serviceaccounts.ExtSvcAccountsService), new(*extsvcaccounts.ExtSvcAccountsService)), registry2.ProvideExtSvcRegistry, wire.Bind(new(extsvcauth.ExternalServiceRegistry), new(*registry2.Registry)), anonstore.ProvideAnonDBStore, wire.Bind(new(anonstore.AnonStore), new(*anonstore.AnonDBStore)), loggermw.Provide, slogadapter.Provide, signingkeysimpl.ProvideEmbeddedSigningKeysService, wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)), ssosettingsimpl.ProvideService, wire.Bind(new(ssosettings.Service), new(*ssosettingsimpl.Service)), idimpl.ProvideService, wire.Bind(new(auth.IDService), new(*idimpl.Service)), cloudmigrationimpl.ProvideService, userimpl.ProvideVerifier, connectors.ProvideOrgRoleMapper, wire.Bind(new(user.Verifier), new(*userimpl.Verifier)), authz.WireSet, metadata.ProvideSecureValueMetadataStorage, metadata.ProvideKeeperMetadataStorage, metadata.ProvideDecryptStorage, decrypt.ProvideDecryptAuthorizer, decrypt.ProvideDecryptService, inline.ProvideInlineSecureValueService, encryption.ProvideDataKeyStorage, encryption.ProvideGlobalDataKeyStorage, encryption.ProvideEncryptedValueStorage, encryption.ProvideGlobalEncryptedValueStorage, service5.ProvideSecureValueService, validator.ProvideKeeperValidator, validator.ProvideSecureValueValidator, mutator.ProvideKeeperMutator, mutator.ProvideSecureValueMutator, migrator2.NewWithEngine, database4.ProvideDatabase, wire.Bind(new(contracts.Database), new(*database4.Database)), manager2.ProvideEncryptionManager, service4.ProvideAESGCMCipherService, resource.ProvideStorageMetrics, resource.ProvideIndexMetrics, apiserver.WireSet, apiregistry.WireSet, appregistry.WireSet) +var wireBasicSet = wire.NewSet(annotationsimpl.ProvideService, wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)), New, api.ProvideHTTPServer, query.ProvideService, wire.Bind(new(query.Service), new(*query.ServiceImpl)), bus.ProvideBus, wire.Bind(new(bus.Bus), new(*bus.InProcBus)), rendering.ProvideService, wire.Bind(new(rendering.Service), new(*rendering.RenderingService)), routing.ProvideRegister, wire.Bind(new(routing.RouteRegister), new(*routing.RouteRegisterImpl)), hooks.ProvideService, kvstore.ProvideService, localcache.ProvideService, bundleregistry.ProvideService, wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)), updatemanager.ProvideGrafanaService, updatemanager.ProvidePluginsService, service.ProvideService, wire.Bind(new(usagestats.Service), new(*service.UsageStats)), validator3.ProvideService, legacy.ProvideLegacyMigrator, pluginsintegration.WireSet, dashboards.ProvideFileStoreManager, wire.Bind(new(dashboards.FileStore), new(*dashboards.FileStoreManager)), cloudwatch.ProvideService, cloudmonitoring.ProvideService, azuremonitor.ProvideService, postgres.ProvideService, mysql.ProvideService, mssql.ProvideService, store.ProvideEntityEventsService, dualwrite.ProvideService, httpclientprovider.New, wire.Bind(new(httpclient.Provider), new(*httpclient2.Provider)), serverlock.ProvideService, annotationsimpl.ProvideCleanupService, wire.Bind(new(annotations.Cleaner), new(*annotationsimpl.CleanupServiceImpl)), cleanup.ProvideService, shorturlimpl.ProvideService, wire.Bind(new(shorturls.Service), new(*shorturlimpl.ShortURLService)), queryhistory.ProvideService, wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)), correlations.ProvideService, wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)), quotaimpl.ProvideService, remotecache.ProvideService, wire.Bind(new(remotecache.CacheStorage), new(*remotecache.RemoteCache)), authinfoimpl.ProvideService, wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)), authinfoimpl.ProvideStore, datasourceproxy.ProvideService, sort.ProvideService, search2.ProvideService, searchV2.ProvideService, searchV2.ProvideSearchHTTPService, store.ProvideService, store.ProvideSystemUsersService, live.ProvideService, pushhttp.ProvideService, contexthandler.ProvideService, service12.ProvideService, wire.Bind(new(service12.LDAP), new(*service12.LDAPImpl)), jwt.ProvideService, wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)), store2.ProvideDBStore, image.ProvideDeleteExpiredService, ngalert.ProvideService, librarypanels.ProvideService, wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)), libraryelements.ProvideService, wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)), notifications.ProvideService, notifications.ProvideSmtpService, github.ProvideFactory, tracing.ProvideService, tracing.ProvideTracingConfig, wire.Bind(new(tracing.Tracer), new(*tracing.TracingService)), withOTelSet, testdatasource.ProvideService, api4.ProvideService, opentsdb.ProvideService, socialimpl.ProvideService, influxdb.ProvideService, wire.Bind(new(social.Service), new(*socialimpl.SocialService)), tempo.ProvideService, loki.ProvideService, graphite.ProvideService, prometheus.ProvideService, elasticsearch.ProvideService, pyroscope.ProvideService, parca.ProvideService, zipkin.ProvideService, jaeger.ProvideService, service9.ProvideCacheService, wire.Bind(new(datasources.CacheService), new(*service9.CacheServiceImpl)), service2.ProvideEncryptionService, wire.Bind(new(encryption2.Internal), new(*service2.Service)), manager.ProvideSecretsService, wire.Bind(new(secrets2.Service), new(*manager.SecretsService)), database.ProvideSecretsStore, wire.Bind(new(secrets2.Store), new(*database.SecretsStoreImpl)), grafanads.ProvideService, wire.Bind(new(dashboardsnapshots.Store), new(*database5.DashboardSnapshotStore)), database5.ProvideStore, wire.Bind(new(dashboardsnapshots.Service), new(*service10.ServiceImpl)), service10.ProvideService, service9.ProvideService, wire.Bind(new(datasources.DataSourceService), new(*service9.Service)), service9.ProvideLegacyDataSourceLookup, retriever.ProvideService, wire.Bind(new(serviceaccounts.ServiceAccountRetriever), new(*retriever.Service)), ossaccesscontrol.ProvideServiceAccountPermissions, wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), manager3.ProvideServiceAccountsService, proxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*proxy.ServiceAccountsProxy)), dsquerierclient.NewNullQSDatasourceClientBuilder, expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, service7.ProvideDashboardServiceImpl, wire.Bind(new(dashboards2.PermissionsRegistrationService), new(*service7.DashboardServiceImpl)), service7.ProvideDashboardService, service7.ProvideDashboardProvisioningService, service7.ProvideDashboardPluginService, database2.ProvideDashboardStore, folderimpl.ProvideService, wire.Bind(new(folder.Service), new(*folderimpl.Service)), folderimpl.ProvideStore, wire.Bind(new(folder.Store), new(*folderimpl.FolderStoreImpl)), folderimpl.ProvideDashboardFolderStore, wire.Bind(new(folder.FolderStore), new(*folderimpl.DashboardFolderStoreImpl)), service11.ProvideService, wire.Bind(new(dashboardimport.Service), new(*service11.ImportDashboardService)), service8.ProvideService, wire.Bind(new(plugindashboards.Service), new(*service8.Service)), service8.ProvideDashboardUpdater, kvstore2.ProvideService, avatar.ProvideAvatarCacheServer, statscollector.ProvideService, csrf.ProvideCSRFFilter, wire.Bind(new(csrf.Service), new(*csrf.CSRF)), ossaccesscontrol.ProvideTeamPermissions, wire.Bind(new(accesscontrol.TeamPermissionsService), new(*ossaccesscontrol.TeamPermissionsService)), ossaccesscontrol.ProvideFolderPermissions, wire.Bind(new(accesscontrol.FolderPermissionsService), new(*ossaccesscontrol.FolderPermissionsService)), ossaccesscontrol.ProvideDashboardPermissions, wire.Bind(new(accesscontrol.DashboardPermissionsService), new(*ossaccesscontrol.DashboardPermissionsService)), ossaccesscontrol.ProvideReceiverPermissionsService, wire.Bind(new(accesscontrol.ReceiverPermissionsService), new(*ossaccesscontrol.ReceiverPermissionsService)), starimpl.ProvideService, playlistimpl.ProvideService, apikeyimpl.ProvideService, dashverimpl.ProvideService, service3.ProvideService, wire.Bind(new(publicdashboards.Service), new(*service3.PublicDashboardServiceImpl)), database3.ProvideStore, wire.Bind(new(publicdashboards.Store), new(*database3.PublicDashboardStoreImpl)), metric.ProvideService, api2.ProvideApi, api3.ProvideApi, userimpl.ProvideService, orgimpl.ProvideService, orgimpl.ProvideDeletionService, statsimpl.ProvideService, grpccontext.ProvideContextHandler, grpcserver.ProvideHealthService, grpcserver.ProvideReflectionService, resolver.ProvideEntityReferenceResolver, teamimpl.ProvideService, teamapi.ProvideTeamAPI, tempuserimpl.ProvideService, loginattemptimpl.ProvideService, wire.Bind(new(loginattempt.Service), new(*loginattemptimpl.Service)), migrations2.ProvideDataSourceMigrationService, migrations2.ProvideSecretMigrationProvider, wire.Bind(new(migrations2.SecretMigrationProvider), new(*migrations2.SecretMigrationProviderImpl)), resourcepermissions.NewActionSetService, wire.Bind(new(accesscontrol.ActionResolver), new(resourcepermissions.ActionSetService)), wire.Bind(new(pluginaccesscontrol.ActionSetRegistry), new(resourcepermissions.ActionSetService)), permreg.ProvidePermissionRegistry, acimpl.ProvideAccessControl, dualwrite2.ProvideZanzanaReconciler, navtreeimpl.ProvideService, wire.Bind(new(accesscontrol.AccessControl), new(*acimpl.AccessControl)), wire.Bind(new(notifications.TempUserStore), new(tempuser.Service)), tagimpl.ProvideService, wire.Bind(new(tag.Service), new(*tagimpl.Service)), authnimpl.ProvideService, authnimpl.ProvideIdentitySynchronizer, authnimpl.ProvideAuthnService, authnimpl.ProvideAuthnServiceAuthenticateOnly, authnimpl.ProvideRegistration, supportbundlesimpl.ProvideService, extsvcaccounts.ProvideExtSvcAccountsService, wire.Bind(new(serviceaccounts.ExtSvcAccountsService), new(*extsvcaccounts.ExtSvcAccountsService)), registry2.ProvideExtSvcRegistry, wire.Bind(new(extsvcauth.ExternalServiceRegistry), new(*registry2.Registry)), anonstore.ProvideAnonDBStore, wire.Bind(new(anonstore.AnonStore), new(*anonstore.AnonDBStore)), loggermw.Provide, slogadapter.Provide, signingkeysimpl.ProvideEmbeddedSigningKeysService, wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)), ssosettingsimpl.ProvideService, wire.Bind(new(ssosettings.Service), new(*ssosettingsimpl.Service)), idimpl.ProvideService, wire.Bind(new(auth.IDService), new(*idimpl.Service)), cloudmigrationimpl.ProvideService, userimpl.ProvideVerifier, connectors.ProvideOrgRoleMapper, wire.Bind(new(user.Verifier), new(*userimpl.Verifier)), authz.WireSet, metadata.ProvideSecureValueMetadataStorage, metadata.ProvideKeeperMetadataStorage, metadata.ProvideDecryptStorage, decrypt.ProvideDecryptAuthorizer, decrypt.ProvideDecryptService, inline.ProvideInlineSecureValueService, encryption.ProvideDataKeyStorage, encryption.ProvideGlobalDataKeyStorage, encryption.ProvideEncryptedValueStorage, encryption.ProvideGlobalEncryptedValueStorage, service5.ProvideSecureValueService, validator.ProvideKeeperValidator, validator.ProvideSecureValueValidator, mutator.ProvideKeeperMutator, mutator.ProvideSecureValueMutator, migrator2.NewWithEngine, database4.ProvideDatabase, wire.Bind(new(contracts.Database), new(*database4.Database)), manager2.ProvideEncryptionManager, service4.ProvideAESGCMCipherService, resource.ProvideStorageMetrics, resource.ProvideIndexMetrics, apiserver.WireSet, apiregistry.WireSet, appregistry.WireSet) var wireSet = wire.NewSet( wireBasicSet, metrics.WireSet, sqlstore.ProvideService, metrics2.ProvideService, wire.Bind(new(notifications.Service), new(*notifications.NotificationService)), wire.Bind(new(notifications.WebhookSender), new(*notifications.NotificationService)), wire.Bind(new(notifications.EmailSender), new(*notifications.NotificationService)), wire.Bind(new(db.DB), new(*sqlstore.SQLStore)), prefimpl.ProvideService, oauthtoken.ProvideService, wire.Bind(new(oauthtoken.OAuthTokenService), new(*oauthtoken.Service)), wire.Bind(new(cleanup.AlertRuleService), new(*store2.DBstore)), diff --git a/pkg/services/mtdsclient/mt_datasource_client_builder.go b/pkg/services/dsquerierclient/qs_datasource_client_builder.go similarity index 72% rename from pkg/services/mtdsclient/mt_datasource_client_builder.go rename to pkg/services/dsquerierclient/qs_datasource_client_builder.go index b530a277142..8c99599cb2c 100644 --- a/pkg/services/mtdsclient/mt_datasource_client_builder.go +++ b/pkg/services/dsquerierclient/qs_datasource_client_builder.go @@ -1,4 +1,4 @@ -package mtdsclient +package dsquerierclient import ( "context" @@ -8,7 +8,7 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/query/clientapi" ) -type MTDatasourceClientBuilder interface { +type QSDatasourceClientBuilder interface { BuildClient(pluginId string, uid string) (clientapi.QueryDataClient, bool, error) } @@ -18,18 +18,18 @@ func (m *nullBuilder) BuildClient(pluginId string, uid string) (clientapi.QueryD return nil, false, nil } -// we use this noop for st flows -func NewNullMTDatasourceClientBuilder() MTDatasourceClientBuilder { +// we use this noop for non-query-service flows +func NewNullQSDatasourceClientBuilder() QSDatasourceClientBuilder { return &nullBuilder{} } -type MtDatasourceClientBuilderWithInstance struct { +type QsDatasourceClientBuilderWithInstance struct { instance clientapi.Instance ctx context.Context logger log.Logger } -func (b *MtDatasourceClientBuilderWithInstance) BuildClient(pluginId string, uid string) (clientapi.QueryDataClient, bool, error) { +func (b *QsDatasourceClientBuilderWithInstance) BuildClient(pluginId string, uid string) (clientapi.QueryDataClient, bool, error) { dsClient, err := b.instance.GetDataSourceClient( b.ctx, v0alpha1.DataSourceRef{ @@ -44,19 +44,19 @@ func (b *MtDatasourceClientBuilderWithInstance) BuildClient(pluginId string, uid } // TODO: I think we might be able to refactor this to just use the instance -func NewMtDatasourceClientBuilderWithInstance( +func NewQsDatasourceClientBuilderWithInstance( instance clientapi.Instance, ctx context.Context, logger log.Logger, -) MTDatasourceClientBuilder { - return &MtDatasourceClientBuilderWithInstance{ +) QSDatasourceClientBuilder { + return &QsDatasourceClientBuilderWithInstance{ instance: instance, ctx: ctx, logger: logger, } } -func NewTestMTDSClientBuilder(isMultiTenant bool, mockClient clientapi.QueryDataClient) MTDatasourceClientBuilder { +func NewTestQSDSClientBuilder(isMultiTenant bool, mockClient clientapi.QueryDataClient) QSDatasourceClientBuilder { return &testBuilder{ mockClient: mockClient, isMultitenant: isMultiTenant, diff --git a/pkg/services/ngalert/eval/eval_test.go b/pkg/services/ngalert/eval/eval_test.go index a93ecacd64d..abc66238702 100644 --- a/pkg/services/ngalert/eval/eval_test.go +++ b/pkg/services/ngalert/eval/eval_test.go @@ -19,8 +19,8 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/datasources" fakes "github.com/grafana/grafana/pkg/services/datasources/fakes" + "github.com/grafana/grafana/pkg/services/dsquerierclient" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/mtdsclient" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" "github.com/grafana/grafana/pkg/services/user" @@ -599,7 +599,7 @@ func TestValidate(t *testing.T) { featuremgmt.WithFeatures(), nil, tracing.InitializeTracerForTest(), - mtdsclient.NewNullMTDatasourceClientBuilder(), + dsquerierclient.NewNullQSDatasourceClientBuilder(), ) validator := NewConditionValidator(cacheService, expressions, store) evalCtx := NewContext(context.Background(), u) @@ -729,7 +729,7 @@ func TestCreate_HysteresisCommand(t *testing.T) { featuremgmt.WithFeatures(), nil, tracing.InitializeTracerForTest(), - mtdsclient.NewNullMTDatasourceClientBuilder(), + dsquerierclient.NewNullQSDatasourceClientBuilder(), ), ) evalCtx := NewContextWithPreviousResults(context.Background(), u, testCase.reader) diff --git a/pkg/services/ngalert/schedule/schedule_unit_test.go b/pkg/services/ngalert/schedule/schedule_unit_test.go index 981a643c005..83dd137ba42 100644 --- a/pkg/services/ngalert/schedule/schedule_unit_test.go +++ b/pkg/services/ngalert/schedule/schedule_unit_test.go @@ -25,8 +25,8 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" datasources "github.com/grafana/grafana/pkg/services/datasources/fakes" + "github.com/grafana/grafana/pkg/services/dsquerierclient" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/mtdsclient" "github.com/grafana/grafana/pkg/services/ngalert/eval" "github.com/grafana/grafana/pkg/services/ngalert/metrics" "github.com/grafana/grafana/pkg/services/ngalert/models" @@ -77,7 +77,7 @@ func TestProcessTicks(t *testing.T) { featuremgmt.WithFeatures(), nil, tracing.InitializeTracerForTest(), - mtdsclient.NewNullMTDatasourceClientBuilder(), + dsquerierclient.NewNullQSDatasourceClientBuilder(), ), ) rrSet := setting.RecordingRuleSettings{ @@ -1215,7 +1215,7 @@ func setupScheduler(t *testing.T, rs *fakeRulesStore, is *state.FakeInstanceStor featuremgmt.WithFeatures(), nil, tracing.InitializeTracerForTest(), - mtdsclient.NewNullMTDatasourceClientBuilder(), + dsquerierclient.NewNullQSDatasourceClientBuilder(), ), ) } diff --git a/pkg/services/query/query.go b/pkg/services/query/query.go index 0c4dda928b5..db4ab88781c 100644 --- a/pkg/services/query/query.go +++ b/pkg/services/query/query.go @@ -22,7 +22,7 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/services/datasources" - "github.com/grafana/grafana/pkg/services/mtdsclient" + "github.com/grafana/grafana/pkg/services/dsquerierclient" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" "github.com/grafana/grafana/pkg/services/validations" @@ -49,7 +49,7 @@ func ProvideService( dataSourceRequestValidator validations.DataSourceRequestValidator, pluginClient plugins.Client, pCtxProvider *plugincontext.Provider, - mtDatasourceClientBuilder mtdsclient.MTDatasourceClientBuilder, + qsDatasourceClientBuilder dsquerierclient.QSDatasourceClientBuilder, ) *ServiceImpl { g := &ServiceImpl{ cfg: cfg, @@ -60,7 +60,7 @@ func ProvideService( pCtxProvider: pCtxProvider, log: log.New("query_data"), concurrentQueryLimit: cfg.SectionWithEnvOverrides("query").Key("concurrent_query_limit").MustInt(runtime.NumCPU()), - mtDatasourceClientBuilder: mtDatasourceClientBuilder, + qsDatasourceClientBuilder: qsDatasourceClientBuilder, } g.log.Info("Query Service initialization") return g @@ -87,7 +87,7 @@ type ServiceImpl struct { pCtxProvider *plugincontext.Provider log log.Logger concurrentQueryLimit int - mtDatasourceClientBuilder mtdsclient.MTDatasourceClientBuilder + qsDatasourceClientBuilder dsquerierclient.QSDatasourceClientBuilder headers map[string]string } @@ -224,13 +224,13 @@ func buildErrorResponses(err error, queries []*simplejson.Json) splitResponse { return splitResponse{er, http.Header{}} } -func QueryData(ctx context.Context, log log.Logger, dscache datasources.CacheService, exprService *expr.Service, reqDTO dtos.MetricRequest, mtDatasourceClientBuilder mtdsclient.MTDatasourceClientBuilder, headers map[string]string) (*backend.QueryDataResponse, error) { +func QueryData(ctx context.Context, log log.Logger, dscache datasources.CacheService, exprService *expr.Service, reqDTO dtos.MetricRequest, qsDatasourceClientBuilder dsquerierclient.QSDatasourceClientBuilder, headers map[string]string) (*backend.QueryDataResponse, error) { s := &ServiceImpl{ log: log, dataSourceCache: dscache, expressionService: exprService, dataSourceRequestValidator: validations.ProvideValidator(), - mtDatasourceClientBuilder: mtDatasourceClientBuilder, + qsDatasourceClientBuilder: qsDatasourceClientBuilder, headers: headers, concurrentQueryLimit: 16, // TODO: make it configurable } @@ -302,7 +302,7 @@ func (s *ServiceImpl) handleQuerySingleDatasource(ctx context.Context, user iden req.Queries = append(req.Queries, q.query) } - mtDsClient, ok, err := s.mtDatasourceClientBuilder.BuildClient(ds.Type, ds.UID) + qsDsClient, ok, err := s.qsDatasourceClientBuilder.BuildClient(ds.Type, ds.UID) if err != nil { return nil, err } @@ -314,13 +314,13 @@ func (s *ServiceImpl) handleQuerySingleDatasource(ctx context.Context, user iden } req.PluginContext = pCtx return s.pluginClient.QueryData(ctx, req) - } else { // multi tenant flow + } else { // query-service flow (single or multi tenant) // transform request from backend.QueryDataRequest to k8s request k8sReq, err := expr.ConvertBackendRequestToDataRequest(req) if err != nil { return nil, err } - return mtDsClient.QueryData(ctx, *k8sReq) + return qsDsClient.QueryData(ctx, *k8sReq) } } diff --git a/pkg/services/query/query_test.go b/pkg/services/query/query_test.go index bf2737eab39..ce001d65302 100644 --- a/pkg/services/query/query_test.go +++ b/pkg/services/query/query_test.go @@ -31,8 +31,8 @@ import ( contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/datasources" fakeDatasources "github.com/grafana/grafana/pkg/services/datasources/fakes" + "github.com/grafana/grafana/pkg/services/dsquerierclient" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/mtdsclient" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginconfig" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" pluginSettings "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings/service" @@ -653,7 +653,7 @@ func TestIntegrationQueryDataMultipleSources(t *testing.T) { }) } -func TestIntegrationQueryDataWithMTDSClient(t *testing.T) { +func TestIntegrationQueryDataWithQSDSClient(t *testing.T) { if testing.Short() { t.Skip("skipping integration test in short mode") } @@ -758,11 +758,11 @@ func setup(t *testing.T, isMultiTenant bool, mockClient clientapi.QueryDataClien pluginconfig.NewFakePluginRequestConfigProvider(), ) - var mtdsClientBuilder mtdsclient.MTDatasourceClientBuilder + var qsdsClientBuilder dsquerierclient.QSDatasourceClientBuilder if isMultiTenant { - mtdsClientBuilder = mtdsclient.NewTestMTDSClientBuilder(isMultiTenant, mockClient) + qsdsClientBuilder = dsquerierclient.NewTestQSDSClientBuilder(isMultiTenant, mockClient) } else { - mtdsClientBuilder = mtdsclient.NewTestMTDSClientBuilder(false, nil) + qsdsClientBuilder = dsquerierclient.NewTestQSDSClientBuilder(false, nil) } exprService := expr.ProvideService( @@ -772,7 +772,7 @@ func setup(t *testing.T, isMultiTenant bool, mockClient clientapi.QueryDataClien featuremgmt.WithFeatures(), nil, tracing.InitializeTracerForTest(), - mtdsClientBuilder, + qsdsClientBuilder, ) queryService := ProvideService( @@ -782,7 +782,7 @@ func setup(t *testing.T, isMultiTenant bool, mockClient clientapi.QueryDataClien rv, pc, pCtxProvider, - mtdsClientBuilder, + qsdsClientBuilder, ) return &testContext{ From 06115478f9647dade240b54a6c5958c6dc6b2626 Mon Sep 17 00:00:00 2001 From: Will Assis <35489495+gassiss@users.noreply.github.com> Date: Tue, 19 Aug 2025 07:03:24 -0400 Subject: [PATCH 05/26] fix: dashboard version history duplicating entries sometimes (#109490) * fix: version history duplicating entries when navigation fails after restoring dashboard version * fix navigation issue --------- Co-authored-by: Haris Rozajac --- .../scene/DashboardScene.test.tsx | 48 ++++++++++++++++++- .../settings/VersionsEditView.tsx | 2 +- .../live/dashboard/dashboardWatcher.ts | 10 +++- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx index 214a47abc27..84963900760 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx @@ -1,4 +1,12 @@ -import { CoreApp, GrafanaConfig, LoadingState, getDefaultTimeRange, locationUtil, store } from '@grafana/data'; +import { + CoreApp, + GrafanaConfig, + LiveChannelEventType, + LoadingState, + getDefaultTimeRange, + locationUtil, + store, +} from '@grafana/data'; import { config, locationService, RefreshEvent } from '@grafana/runtime'; import { sceneGraph, @@ -17,6 +25,8 @@ import appEvents from 'app/core/app_events'; import { LS_PANEL_COPY_KEY } from 'app/core/constants'; import { AnnoKeyManagerKind, ManagerKind } from 'app/features/apiserver/types'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; +import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; +import { DashboardEventAction } from 'app/features/live/dashboard/types'; import { VariablesChanged } from 'app/features/variables/types'; import { buildPanelEditScene } from '../panel-edit/PanelEditor'; @@ -764,12 +774,46 @@ describe('DashboardScene', () => { return scene.onRestore(getVersionMock()).then((res) => { expect(res).toBe(true); - expect(scene.state.version).toBe(newVersion); expect(scene.state.isEditing).toBe(false); }); }); + it('should call dashboardWatcher.reloadPage even if dashboard is in editing mode', async () => { + // sometimes a dashboard can be in editing mode after user has already restored to a previous version + + const newVersion = 3; + const mockScene = new DashboardScene({ + title: 'new name', + uid: 'dash-1', + version: 4, + }); + jest.mocked(historySrv.restoreDashboard).mockResolvedValue({ version: newVersion }); + jest.mocked(transformSaveModelToScene).mockReturnValue(mockScene); + + const reloadSpy = jest.spyOn(dashboardWatcher, 'reloadPage').mockImplementation(() => {}); + + dashboardWatcher.editing = false; + const dash = { uid: 'dash-1', hasUnsavedChanges: () => true }; + jest + .spyOn(require('app/features/dashboard/services/DashboardSrv'), 'getDashboardSrv') + .mockReturnValue({ getCurrent: () => dash }); + + dashboardWatcher.observer.next({ + type: LiveChannelEventType.Message, + message: { + sessionId: 'other', + message: 'Restored from version 3', + uid: 'dash-1', + action: DashboardEventAction.Saved, + timestamp: Date.now(), + }, + }); + + expect(reloadSpy).toHaveBeenCalled(); + reloadSpy.mockRestore(); + }); + it('should return early if historySrv does not return a valid version number', () => { jest .mocked(historySrv.restoreDashboard) diff --git a/public/app/features/dashboard-scene/settings/VersionsEditView.tsx b/public/app/features/dashboard-scene/settings/VersionsEditView.tsx index e21c7c00cf1..2f194784bc8 100644 --- a/public/app/features/dashboard-scene/settings/VersionsEditView.tsx +++ b/public/app/features/dashboard-scene/settings/VersionsEditView.tsx @@ -113,7 +113,7 @@ export class VersionsEditView extends SceneObjectBase imp .then((result) => { this.setState({ isLoading: false, - versions: [...(this.state.versions ?? []), ...this.decorateVersions(result.versions)], + versions: [...(append ? (this.state.versions ?? []) : []), ...this.decorateVersions(result.versions)], }); this._start += this._limit; // Update the continueToken for the next request, if available diff --git a/public/app/features/live/dashboard/dashboardWatcher.ts b/public/app/features/live/dashboard/dashboardWatcher.ts index 3572a77011f..5c831085af5 100644 --- a/public/app/features/live/dashboard/dashboardWatcher.ts +++ b/public/app/features/live/dashboard/dashboardWatcher.ts @@ -109,7 +109,7 @@ class DashboardWatcher { return; // skip internal messages } - const { action } = event.message; + const { action, message } = event.message; switch (action) { case DashboardEventAction.EditingStarted: case DashboardEventAction.Saved: { @@ -124,7 +124,13 @@ class DashboardWatcher { return; } - const showPopup = this.editing || dash.hasUnsavedChanges(); + let showPopup = this.editing || dash.hasUnsavedChanges(); + + // Dashboard could have unsaved changes but if user has already restored from a version + // the reloadPage should be called below + if (message?.includes('Restored from version')) { + showPopup = false; + } if (action === DashboardEventAction.Saved) { if (showPopup) { From f5b9d9361067d6c115df30d27cc301e3aa29954f Mon Sep 17 00:00:00 2001 From: Andres Martinez Gotor Date: Tue, 19 Aug 2025 13:40:19 +0200 Subject: [PATCH 06/26] Add favorite button to EditDatasource page (#109609) --- .../src/utils/useFavoriteDatasources.ts | 8 + .../components/DataSourcesList.test.tsx | 1 + .../components/EditDataSourceActions.test.tsx | 180 +++++++++++++++++- .../components/EditDataSourceActions.tsx | 35 +++- .../components/picker/DataSourceList.tsx | 48 ++--- public/app/features/datasources/hooks.ts | 5 +- .../features/datasources/state/selectors.ts | 8 +- public/locales/en-US/grafana.json | 4 +- 8 files changed, 251 insertions(+), 38 deletions(-) diff --git a/packages/grafana-runtime/src/utils/useFavoriteDatasources.ts b/packages/grafana-runtime/src/utils/useFavoriteDatasources.ts index 8f891da8915..bbb47da67e7 100644 --- a/packages/grafana-runtime/src/utils/useFavoriteDatasources.ts +++ b/packages/grafana-runtime/src/utils/useFavoriteDatasources.ts @@ -10,6 +10,7 @@ const FAVORITE_DATASOURCES_KEY = 'favoriteDatasources'; export type FavoriteDatasources = { enabled: boolean; + isLoading: boolean; favoriteDatasources: string[]; initialFavoriteDataSources: string[]; addFavoriteDatasource: (ds: DataSourceInstanceSettings) => void; @@ -35,6 +36,7 @@ export function useFavoriteDatasources(): FavoriteDatasources { if (!config.featureToggles.favoriteDatasources) { return { enabled: false, + isLoading: false, favoriteDatasources: [], initialFavoriteDataSources: [], addFavoriteDatasource: () => {}, @@ -46,16 +48,19 @@ export function useFavoriteDatasources(): FavoriteDatasources { const [userStorage] = useState(() => new UserStorage('grafana-runtime')); const [favoriteDatasources, setFavoriteDatasources] = useState([]); const [initialFavoriteDataSources, setInitialFavoriteDataSources] = useState([]); + const [isLoading, setIsLoading] = useState(false); // Load favorites from storage on mount useEffect(() => { const loadFavorites = async () => { + setIsLoading(true); const stored = await userStorage.getItem(FAVORITE_DATASOURCES_KEY); if (stored) { const parsed = JSON.parse(stored); setFavoriteDatasources(parsed); setInitialFavoriteDataSources(parsed); } + setIsLoading(false); }; loadFavorites(); @@ -64,8 +69,10 @@ export function useFavoriteDatasources(): FavoriteDatasources { // Helper function to save favorites to storage const saveFavorites = useCallback( async (newFavorites: string[]) => { + setIsLoading(true); await userStorage.setItem(FAVORITE_DATASOURCES_KEY, JSON.stringify(newFavorites)); setFavoriteDatasources(newFavorites); + setIsLoading(false); }, [userStorage] ); @@ -104,6 +111,7 @@ export function useFavoriteDatasources(): FavoriteDatasources { return { enabled: true, + isLoading, favoriteDatasources, addFavoriteDatasource, removeFavoriteDatasource, diff --git a/public/app/features/datasources/components/DataSourcesList.test.tsx b/public/app/features/datasources/components/DataSourcesList.test.tsx index 33fc05ace84..fd7cdc37f9a 100644 --- a/public/app/features/datasources/components/DataSourcesList.test.tsx +++ b/public/app/features/datasources/components/DataSourcesList.test.tsx @@ -11,6 +11,7 @@ import { DataSourcesListView, ViewProps } from './DataSourcesList'; const mockIsFavoriteDatasource = jest.fn(); const mockUseFavoriteDatasources = jest.fn(() => ({ enabled: true, + isLoading: false, isFavoriteDatasource: mockIsFavoriteDatasource, favoriteDatasources: [], initialFavoriteDataSources: [], diff --git a/public/app/features/datasources/components/EditDataSourceActions.test.tsx b/public/app/features/datasources/components/EditDataSourceActions.test.tsx index 0bd748c900e..7f39ade59ab 100644 --- a/public/app/features/datasources/components/EditDataSourceActions.test.tsx +++ b/public/app/features/datasources/components/EditDataSourceActions.test.tsx @@ -1,7 +1,7 @@ import { render, screen, fireEvent } from '@testing-library/react'; import { PluginExtensionTypes, IconName } from '@grafana/data'; -import { setPluginLinksHook } from '@grafana/runtime'; +import { setPluginLinksHook, config, getDataSourceSrv } from '@grafana/runtime'; import { contextSrv } from 'app/core/services/context_srv'; import { getMockDataSource } from '../mocks/dataSourcesMocks'; @@ -16,11 +16,48 @@ jest.mock('../utils', () => ({ ), })); +// Mock @grafana/runtime +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + config: { + featureToggles: { + favoriteDatasources: false, + }, + }, + getDataSourceSrv: jest.fn(), + useFavoriteDatasources: jest.fn(), +})); + // Set default plugin links hook setPluginLinksHook(() => ({ links: [], isLoading: false })); // Mock contextSrv -const mockContextSrv = contextSrv as jest.Mocked; +const mockContextSrv = jest.mocked(contextSrv); + +// Mock getDataSourceSrv and favorite hooks +const mockGetDataSourceSrv = jest.mocked(getDataSourceSrv); +const mockUseFavoriteDatasources = jest.mocked(require('@grafana/runtime').useFavoriteDatasources); + +// Create mock datasource instance +const mockDataSourceInstance = { + uid: 'test-uid', + name: 'Test Prometheus', + type: 'prometheus', + meta: { + name: 'Prometheus', + builtIn: false, + }, +}; + +// Mock favorite datasources hook return value +const mockFavoriteHook = { + enabled: true, + favoriteDatasources: [], + initialFavoriteDataSources: [], + isFavoriteDatasource: jest.fn(), + addFavoriteDatasource: jest.fn(), + removeFavoriteDatasource: jest.fn(), +}; // Helper function to create mock plugin link extensions with all required properties const createMockPluginLink = ( @@ -63,6 +100,24 @@ describe('EditDataSourceActions', () => { setPluginLinksHook(() => ({ links: [], isLoading: false })); // Default contextSrv mock - user has explore rights mockContextSrv.hasAccessToExplore.mockReturnValue(true); + + // Setup default mocks for favorite functionality + mockGetDataSourceSrv.mockReturnValue({ + getInstanceSettings: jest.fn().mockReturnValue(mockDataSourceInstance), + get: jest.fn(), + getList: jest.fn(), + reload: jest.fn(), + registerRuntimeDataSource: jest.fn(), + }); + + // Reset favorite hook mocks + mockFavoriteHook.isFavoriteDatasource.mockReturnValue(false); + mockFavoriteHook.addFavoriteDatasource.mockClear(); + mockFavoriteHook.removeFavoriteDatasource.mockClear(); + + // Default: feature toggle disabled, so no favorite hook + mockUseFavoriteDatasources.mockReturnValue({ ...mockFavoriteHook, enabled: false }); + config.featureToggles.favoriteDatasources = false; }); describe('Core Actions', () => { @@ -236,7 +291,7 @@ describe('EditDataSourceActions', () => { title: 'Test Action', description: 'Test description', path: '/test-path', - icon: 'external-link-alt' as IconName, + icon: 'external-link-alt', pluginId: 'grafana-lokiexplore-app', }), ]; @@ -318,4 +373,123 @@ describe('EditDataSourceActions', () => { expect(screen.getByText('Explore data')).toBeInTheDocument(); }); }); + + describe('Favorite Actions', () => { + it('should not render favorite button when feature toggle is disabled', () => { + config.featureToggles.favoriteDatasources = false; + mockUseFavoriteDatasources.mockReturnValue({ ...mockFavoriteHook, enabled: false }); + + render(); + + // Should not find any favorite button + expect(screen.queryByTestId('favorite-button')).not.toBeInTheDocument(); + // Core actions should still be rendered + expect(screen.getByText('Explore data')).toBeInTheDocument(); + expect(screen.getByText('Build a dashboard')).toBeInTheDocument(); + }); + + it('should not render favorite button for built-in datasources', () => { + config.featureToggles.favoriteDatasources = true; + mockUseFavoriteDatasources.mockReturnValue(mockFavoriteHook); + + // Mock built-in datasource + const builtInDataSource = { ...mockDataSourceInstance, meta: { ...mockDataSourceInstance.meta, builtIn: true } }; + mockGetDataSourceSrv.mockReturnValue({ + getInstanceSettings: jest.fn().mockReturnValue(builtInDataSource), + get: jest.fn(), + getList: jest.fn(), + reload: jest.fn(), + registerRuntimeDataSource: jest.fn(), + }); + + render(); + + // Should not find any favorite button for built-in datasources + expect(screen.queryByTestId('favorite-button')).not.toBeInTheDocument(); + }); + + it('should render favorite button when feature toggle is enabled and datasource is not built-in', () => { + config.featureToggles.favoriteDatasources = true; + mockUseFavoriteDatasources.mockReturnValue(mockFavoriteHook); + mockFavoriteHook.isFavoriteDatasource.mockReturnValue(false); + + render(); + + // Should find star icon for non-favorite datasource + const favoriteButton = screen.getByTestId('favorite-button'); + expect(favoriteButton).toBeInTheDocument(); + + // Should have correct aria-label for non-favorite datasource + expect(favoriteButton).toHaveAttribute('aria-label', 'Add to favorites'); + }); + + it('should show favorite icon when datasource is favorited', () => { + config.featureToggles.favoriteDatasources = true; + mockUseFavoriteDatasources.mockReturnValue(mockFavoriteHook); + mockFavoriteHook.isFavoriteDatasource.mockReturnValue(true); + + render(); + + // Should find favorite button for favorited datasource + const favoriteButton = screen.getByTestId('favorite-button'); + expect(favoriteButton).toBeInTheDocument(); + + // Should have correct aria-label for favorited datasource + expect(favoriteButton).toHaveAttribute('aria-label', 'Remove from favorites'); + }); + + it('should add datasource to favorites when star button is clicked', () => { + config.featureToggles.favoriteDatasources = true; + mockUseFavoriteDatasources.mockReturnValue(mockFavoriteHook); + mockFavoriteHook.isFavoriteDatasource.mockReturnValue(false); + + render(); + + const favoriteButton = screen.getByTestId('favorite-button'); + fireEvent.click(favoriteButton); + + expect(mockFavoriteHook.addFavoriteDatasource).toHaveBeenCalledTimes(1); + expect(mockFavoriteHook.addFavoriteDatasource).toHaveBeenCalledWith(mockDataSourceInstance); + expect(mockFavoriteHook.removeFavoriteDatasource).not.toHaveBeenCalled(); + }); + + it('should remove datasource from favorites when favorite button is clicked', () => { + config.featureToggles.favoriteDatasources = true; + mockUseFavoriteDatasources.mockReturnValue(mockFavoriteHook); + mockFavoriteHook.isFavoriteDatasource.mockReturnValue(true); + + render(); + + const favoriteButton = screen.getByTestId('favorite-button'); + fireEvent.click(favoriteButton); + + expect(mockFavoriteHook.removeFavoriteDatasource).toHaveBeenCalledTimes(1); + expect(mockFavoriteHook.removeFavoriteDatasource).toHaveBeenCalledWith(mockDataSourceInstance); + expect(mockFavoriteHook.addFavoriteDatasource).not.toHaveBeenCalled(); + }); + + it('should call isFavoriteDatasource with correct uid', () => { + config.featureToggles.favoriteDatasources = true; + mockUseFavoriteDatasources.mockReturnValue(mockFavoriteHook); + mockFavoriteHook.isFavoriteDatasource.mockReturnValue(false); + + render(); + + expect(mockFavoriteHook.isFavoriteDatasource).toHaveBeenCalledWith('test-uid'); + }); + + it('should disable favorite button when isLoading is true', () => { + config.featureToggles.favoriteDatasources = true; + mockUseFavoriteDatasources.mockReturnValue({ + ...mockFavoriteHook, + isLoading: true, + }); + mockFavoriteHook.isFavoriteDatasource.mockReturnValue(false); + + render(); + + const favoriteButton = screen.getByTestId('favorite-button'); + expect(favoriteButton).toBeDisabled(); + }); + }); }); diff --git a/public/app/features/datasources/components/EditDataSourceActions.tsx b/public/app/features/datasources/components/EditDataSourceActions.tsx index bc231a4c951..fe24dae23f6 100644 --- a/public/app/features/datasources/components/EditDataSourceActions.tsx +++ b/public/app/features/datasources/components/EditDataSourceActions.tsx @@ -1,7 +1,7 @@ import { PluginExtensionPoints } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; -import { config, usePluginLinks } from '@grafana/runtime'; -import { Button, Dropdown, LinkButton, Menu, Icon } from '@grafana/ui'; +import { config, usePluginLinks, useFavoriteDatasources, getDataSourceSrv } from '@grafana/runtime'; +import { Button, Dropdown, LinkButton, Menu, Icon, IconButton } from '@grafana/ui'; import { contextSrv } from 'app/core/core'; import { ALLOWED_DATASOURCE_EXTENSION_PLUGINS } from '../constants'; @@ -13,6 +13,36 @@ interface Props { uid: string; } +const FavoriteButton = ({ uid }: { uid: string }) => { + const favoriteDataSources = useFavoriteDatasources(); + const dataSourceInstance = getDataSourceSrv().getInstanceSettings(uid); + const isFavorite = dataSourceInstance ? favoriteDataSources.isFavoriteDatasource(dataSourceInstance.uid) : false; + + return ( + favoriteDataSources.enabled && + dataSourceInstance && + !dataSourceInstance.meta.builtIn && ( + + isFavorite + ? favoriteDataSources.removeFavoriteDatasource(dataSourceInstance) + : favoriteDataSources.addFavoriteDatasource(dataSourceInstance) + } + disabled={favoriteDataSources.isLoading} + tooltip={ + isFavorite + ? t('datasources.edit-data-source-actions.remove-favorite', 'Remove from favorites') + : t('datasources.edit-data-source-actions.add-favorite', 'Add to favorites') + } + data-testid="favorite-button" + /> + ) + ); +}; + export function EditDataSourceActions({ uid }: Props) { const dataSource = useDataSource(uid); const hasExploreRights = contextSrv.hasAccessToExplore(); @@ -62,6 +92,7 @@ export function EditDataSourceActions({ uid }: Props) { return ( <> + {hasExploreRights && ( <> {!hasActions ? ( diff --git a/public/app/features/datasources/components/picker/DataSourceList.tsx b/public/app/features/datasources/components/picker/DataSourceList.tsx index a4c60c64055..139cff29df4 100644 --- a/public/app/features/datasources/components/picker/DataSourceList.tsx +++ b/public/app/features/datasources/components/picker/DataSourceList.tsx @@ -1,12 +1,12 @@ import { css, cx } from '@emotion/css'; -import { useCallback, useRef } from 'react'; +import { useRef } from 'react'; import * as React from 'react'; import { Observable } from 'rxjs'; import { DataSourceInstanceSettings, DataSourceJsonData, DataSourceRef, GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { Trans } from '@grafana/i18n'; -import { config, getTemplateSrv, useFavoriteDatasources } from '@grafana/runtime'; +import { getTemplateSrv, useFavoriteDatasources } from '@grafana/runtime'; import { useStyles2, useTheme2 } from '@grafana/ui'; import { useDatasources, useKeyboardNavigatableList, useRecentlyUsedDataSources } from '../../hooks'; @@ -58,9 +58,8 @@ export function DataSourceList(props: DataSourceListProps) { const styles = getStyles(theme, selectedItemCssSelector); const { className, current, onChange, enableKeyboardNavigation, onClickEmptyStateCTA } = props; - const dataSources = - props.dataSources || - useDatasources({ + const dataSources = useDatasources( + { alerting: props.alerting, annotations: props.annotations, dashboard: props.dashboard, @@ -71,29 +70,12 @@ export function DataSourceList(props: DataSourceListProps) { tracing: props.tracing, type: props.type, variables: props.variables, - }); + }, + props.dataSources + ); const [recentlyUsedDataSources, pushRecentlyUsedDataSource] = useRecentlyUsedDataSources(); - - const favoriteDataSourcesHook = config.featureToggles.favoriteDatasources ? useFavoriteDatasources() : null; - const storedFavoriteDataSources = favoriteDataSourcesHook?.initialFavoriteDataSources; - const isFavoriteDatasource = favoriteDataSourcesHook?.isFavoriteDatasource; - - const toggleFavoriteDatasource = useCallback( - (ds: DataSourceInstanceSettings) => { - if (!favoriteDataSourcesHook) { - return; - } - const { isFavoriteDatasource, addFavoriteDatasource, removeFavoriteDatasource } = favoriteDataSourcesHook; - - if (isFavoriteDatasource(ds.uid)) { - removeFavoriteDatasource(ds); - } else { - addFavoriteDatasource(ds); - } - }, - [favoriteDataSourcesHook] - ); + const favoriteDataSources = useFavoriteDatasources(); const filteredDataSources = props.filter ? dataSources.filter(props.filter) : dataSources; @@ -112,7 +94,7 @@ export function DataSourceList(props: DataSourceListProps) { current, recentlyUsedDataSources, getDataSourceVariableIDs(), - storedFavoriteDataSources + favoriteDataSources.enabled ? favoriteDataSources.initialFavoriteDataSources : undefined ) ) .map((ds) => ( @@ -125,8 +107,16 @@ export function DataSourceList(props: DataSourceListProps) { onChange(ds); }} selected={isDataSourceMatch(ds, current)} - isFavorite={isFavoriteDatasource ? isFavoriteDatasource(ds.uid) : undefined} - onToggleFavorite={toggleFavoriteDatasource} + isFavorite={favoriteDataSources.isFavoriteDatasource(ds.uid)} + onToggleFavorite={ + favoriteDataSources.enabled + ? () => { + favoriteDataSources.isFavoriteDatasource(ds.uid) + ? favoriteDataSources.removeFavoriteDatasource(ds) + : favoriteDataSources.addFavoriteDatasource(ds); + } + : undefined + } {...(enableKeyboardNavigation ? navigatableProps : {})} /> ))} diff --git a/public/app/features/datasources/hooks.ts b/public/app/features/datasources/hooks.ts index 73c356c4ff8..886a606f90f 100644 --- a/public/app/features/datasources/hooks.ts +++ b/public/app/features/datasources/hooks.ts @@ -42,7 +42,10 @@ export function useRecentlyUsedDataSources(): [string[], (ds: DataSourceInstance return [value, pushRecentlyUsedDataSource]; } -export function useDatasources(filters: GetDataSourceListFilters) { +export function useDatasources(filters: GetDataSourceListFilters, datasources?: DataSourceInstanceSettings[]) { + if (datasources) { + return datasources; + } const dataSourceSrv = getDataSourceSrv(); const dataSources = dataSourceSrv.getList(filters); diff --git a/public/app/features/datasources/state/selectors.ts b/public/app/features/datasources/state/selectors.ts index 53be4a748dd..74ad48924cd 100644 --- a/public/app/features/datasources/state/selectors.ts +++ b/public/app/features/datasources/state/selectors.ts @@ -3,6 +3,10 @@ import memoizeOne from 'memoize-one'; import { DataSourcePluginMeta, DataSourceSettings, UrlQueryValue } from '@grafana/data'; import { DataSourcesState } from 'app/types/datasources'; +// Use consistent references for empty objects to prevent infinite re-renders +const EMPTY_DATASOURCE = {} as DataSourceSettings; +const EMPTY_DATASOURCE_META = {} as DataSourcePluginMeta; + export const getDataSources = memoizeOne((state: DataSourcesState) => { const regex = new RegExp(state.searchQuery, 'i'); @@ -27,7 +31,7 @@ export const getDataSource = (state: DataSourcesState, dataSourceId: UrlQueryVal if (state.dataSource.uid === dataSourceId) { return state.dataSource; } - return {} as DataSourceSettings; + return EMPTY_DATASOURCE; }; export const getDataSourceMeta = (state: DataSourcesState, type: string): DataSourcePluginMeta => { @@ -35,7 +39,7 @@ export const getDataSourceMeta = (state: DataSourcesState, type: string): DataSo return state.dataSourceMeta; } - return {} as DataSourcePluginMeta; + return EMPTY_DATASOURCE_META; }; export const getDataSourcesSearchQuery = (state: DataSourcesState) => state.searchQuery; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 53d3ce78608..7423a3f2437 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -6552,9 +6552,11 @@ "explore": "Explore" }, "edit-data-source-actions": { + "add-favorite": "Add to favorites", "build-a-dashboard": "Build a dashboard", "explore-data": "Explore data", - "open-in-explore": "Open in Explore View" + "open-in-explore": "Open in Explore View", + "remove-favorite": "Remove from favorites" }, "error-details-link": { "aria-label-more-details-about-the-error": "More details about the error" From ee6a61490aafed8bae6cc7317d0c5248d8c48496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Fern=C3=A1ndez?= Date: Tue, 19 Aug 2025 13:52:57 +0200 Subject: [PATCH 07/26] Chore: Remove deprecated `HorizontalGroup` in Dashboards area (#109790) --- .betterer.results | 19 ++++--------------- .../AnnotationSettingsEdit.tsx | 18 +++--------------- .../PanelEditor/OverrideCategoryTitle.tsx | 6 +++--- .../forms/SaveProvisionedDashboardForm.tsx | 6 +++--- .../features/query/components/QueryGroup.tsx | 6 +++--- .../variables/editor/VariableEditorEditor.tsx | 6 +++--- 6 files changed, 19 insertions(+), 42 deletions(-) diff --git a/.betterer.results b/.betterer.results index 6aec09ff9c7..bc2288aa5a5 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1881,14 +1881,13 @@ exports[`better eslint`] = { [0, 0, 0, "Do not re-export imported variable (\`./AddLibraryPanelWidget\`)", "0"] ], "public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], + [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "2"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "3"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "4"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "5"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "6"], - [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "7"] + [0, 0, 0, "Using localeCompare() can cause performance issues when sorting large datasets. Consider using Intl.Collator for better performance when sorting arrays, or add an eslint-disable comment if sorting a small, known dataset.", "6"] ], "public/app/features/dashboard/components/DashExportModal/DashboardExporter.test.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], @@ -1981,9 +1980,6 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "1"], [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "2"] ], - "public/app/features/dashboard/components/PanelEditor/OverrideCategoryTitle.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"] @@ -2022,9 +2018,6 @@ exports[`better eslint`] = { [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"], [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "2"] ], - "public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "public/app/features/dashboard/components/SaveDashboard/useDashboardSave.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], @@ -2655,9 +2648,8 @@ exports[`better eslint`] = { [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"] ], "public/app/features/query/components/QueryGroup.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"], - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "2"] + [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"], + [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"] ], "public/app/features/query/state/DashboardQueryRunner/AnnotationsQueryRunner.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] @@ -2882,9 +2874,6 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"] ], - "public/app/features/variables/editor/VariableEditorEditor.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "public/app/features/variables/editor/VariableEditorList.tsx:5381": [ [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"], [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"] diff --git a/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx b/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx index 22c27294edb..d95d479c0c5 100644 --- a/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx +++ b/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx @@ -14,19 +14,7 @@ import { selectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; import { getDataSourceSrv, locationService } from '@grafana/runtime'; import { AnnotationPanelFilter } from '@grafana/schema/src/raw/dashboard/x/dashboard_types.gen'; -import { - Button, - Checkbox, - Field, - FieldSet, - HorizontalGroup, - Input, - MultiSelect, - Select, - useStyles2, - Stack, - Alert, -} from '@grafana/ui'; +import { Button, Checkbox, Field, FieldSet, Input, MultiSelect, Select, useStyles2, Stack, Alert } from '@grafana/ui'; import { ColorValueEditor } from 'app/core/components/OptionsUI/color'; import config from 'app/core/config'; import StandardAnnotationQueryEditor from 'app/features/annotations/components/StandardAnnotationQueryEditor'; @@ -231,9 +219,9 @@ export const AnnotationSettingsEdit = ({ editIdx, dashboard }: Props) => { 'Color to use for the annotation event markers' )} > - + - + - +
{overrideName}
@@ -67,7 +67,7 @@ export const SaveProvisionedDashboardForm = ({ dashboard, onCancel }: Omit Save JSON to file -
+ ); diff --git a/public/app/features/query/components/QueryGroup.tsx b/public/app/features/query/components/QueryGroup.tsx index a640be347a7..9a3b7dc4908 100644 --- a/public/app/features/query/components/QueryGroup.tsx +++ b/public/app/features/query/components/QueryGroup.tsx @@ -16,7 +16,7 @@ import { selectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; import { getDataSourceSrv, locationService } from '@grafana/runtime'; import { DataQuery } from '@grafana/schema'; -import { Button, HorizontalGroup, InlineFormLabel, Modal, ScrollContainer, stylesFactory } from '@grafana/ui'; +import { Button, InlineFormLabel, Modal, ScrollContainer, Stack, stylesFactory } from '@grafana/ui'; import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp'; import config from 'app/core/config'; import { backendSrv } from 'app/core/services/backend_srv'; @@ -293,7 +293,7 @@ export class QueryGroup extends PureComponent { const showAddButton = !isSharedDashboardQuery(dsSettings.name); return ( - + {showAddButton && ( )} {this.renderExtraActions()} - + ); } diff --git a/public/app/features/variables/editor/VariableEditorEditor.tsx b/public/app/features/variables/editor/VariableEditorEditor.tsx index 3a8e4b88c62..398a4ecef06 100644 --- a/public/app/features/variables/editor/VariableEditorEditor.tsx +++ b/public/app/features/variables/editor/VariableEditorEditor.tsx @@ -7,7 +7,7 @@ import { GrafanaTheme2, LoadingState, SelectableValue, VariableHide, VariableTyp import { selectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; import { locationService } from '@grafana/runtime'; -import { Button, HorizontalGroup, Icon, Themeable2, withTheme2 } from '@grafana/ui'; +import { Button, Stack, Icon, Themeable2, withTheme2 } from '@grafana/ui'; import { StoreState, ThunkDispatch } from 'app/types/store'; import { VariableHideSelect } from '../../dashboard-scene/settings/variables/components/VariableHideSelect'; @@ -219,7 +219,7 @@ export class VariableEditorEditorUnConnected extends PureComponent {hasOptions(this.props.variable) ? : null}
- + @@ -246,7 +246,7 @@ export class VariableEditorEditorUnConnected extends PureComponent > Apply - +
Date: Tue, 19 Aug 2025 14:07:21 +0200 Subject: [PATCH 08/26] Chore: Remove deprecated `VerticalGroup` in Dashboards area (#109675) --- .betterer.results | 25 +++---------------- .../DashboardLoading/DashboardLoading.tsx | 14 +++++------ .../DashboardSettings/VersionsSettings.tsx | 6 ++--- .../features/inspector/InspectDataOptions.tsx | 10 ++++---- .../LibraryPanelsSearch.tsx | 8 +++--- .../PanelLibraryOptionsGroup.tsx | 10 ++++---- .../inspect/VariablesUnknownTable.tsx | 10 ++++---- 7 files changed, 32 insertions(+), 51 deletions(-) diff --git a/.betterer.results b/.betterer.results index bc2288aa5a5..292ea5f3c71 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1915,10 +1915,6 @@ exports[`better eslint`] = { "public/app/features/dashboard/components/DashNav/index.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`DashNav\`)", "0"] ], - "public/app/features/dashboard/components/DashboardLoading/DashboardLoading.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "1"] - ], "public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.test.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], @@ -1957,9 +1953,6 @@ exports[`better eslint`] = { [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "3"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "4"] ], - "public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "public/app/features/dashboard/components/DashboardSettings/index.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./DashboardSettings\`)", "0"] ], @@ -2400,13 +2393,11 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"] ], "public/app/features/inspector/InspectDataOptions.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "1"], + [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], + [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "2"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "3"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "4"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "5"], - [0, 0, 0, "Do not use any type assertions.", "6"] + [0, 0, 0, "Do not use any type assertions.", "4"] ], "public/app/features/inspector/InspectDataTab.tsx:5381": [ [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"] @@ -2432,12 +2423,6 @@ exports[`better eslint`] = { [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"] ], - "public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.tsx:5381": [ - [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], - "public/app/features/library-panels/components/PanelLibraryOptionsGroup/PanelLibraryOptionsGroup.tsx:5381": [ - [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "public/app/features/live/centrifuge/LiveDataStream.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], @@ -2892,10 +2877,6 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], - "public/app/features/variables/inspect/VariablesUnknownTable.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "1"] - ], "public/app/features/variables/inspect/utils.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], diff --git a/public/app/features/dashboard/components/DashboardLoading/DashboardLoading.tsx b/public/app/features/dashboard/components/DashboardLoading/DashboardLoading.tsx index f5e279e5af1..9b116514802 100644 --- a/public/app/features/dashboard/components/DashboardLoading/DashboardLoading.tsx +++ b/public/app/features/dashboard/components/DashboardLoading/DashboardLoading.tsx @@ -3,7 +3,7 @@ import { css, keyframes } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; import { Trans } from '@grafana/i18n'; import { locationService } from '@grafana/runtime'; -import { Button, HorizontalGroup, Spinner, useStyles2, VerticalGroup } from '@grafana/ui'; +import { Button, Spinner, Stack, useStyles2 } from '@grafana/ui'; import { DashboardInitPhase } from 'app/types/dashboard'; export interface Props { @@ -19,16 +19,16 @@ export const DashboardLoading = ({ initPhase }: Props) => { return (
- - + + {initPhase} - {' '} - + {' '} + - - + +
); diff --git a/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx index 9c20ba24802..d5360e36663 100644 --- a/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx @@ -1,7 +1,7 @@ import { PureComponent } from 'react'; import * as React from 'react'; -import { Spinner, HorizontalGroup } from '@grafana/ui'; +import { Spinner, Stack } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; import { historySrv, RevisionsModel } from 'app/features/dashboard-scene/settings/version-history/HistorySrv'; import { VersionsHistoryButtons } from 'app/features/dashboard-scene/settings/version-history/VersionHistoryButtons'; @@ -198,8 +198,8 @@ export class VersionsSettings extends PureComponent { } export const VersionsHistorySpinner = ({ msg }: { msg: string }) => ( - + {msg} - + ); diff --git a/public/app/features/inspector/InspectDataOptions.tsx b/public/app/features/inspector/InspectDataOptions.tsx index 8e00c9e66b1..cc37517a178 100644 --- a/public/app/features/inspector/InspectDataOptions.tsx +++ b/public/app/features/inspector/InspectDataOptions.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { DataFrame, DataTransformerID, getFrameDisplayName, SelectableValue } from '@grafana/data'; import { t } from '@grafana/i18n'; -import { Field, HorizontalGroup, Select, Switch, VerticalGroup, useStyles2 } from '@grafana/ui'; +import { Field, Select, Stack, Switch, useStyles2 } from '@grafana/ui'; import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow'; import { DetailText } from 'app/features/inspector/DetailText'; import { GetDataOptions } from 'app/features/query/state/PanelQueryRunner'; @@ -100,7 +100,7 @@ export const InspectDataOptions = ({ actions={actions} >
- + {data!.length > 1 && ( ` menus will now portal to the document body by default. This is to give more consistent behaviour when positioning and overlaying. If you were setting `menuShouldPortal={true}` before you can safely remove that prop and behaviour will be the same. If you weren't explicitly setting that prop, there should be no visible changes in behaviour but your tests may need updating. Please see the original PR (https://github.com/grafana/grafana/pull/36398) for migration guides. If you were setting `menuShouldPortal={false}` this will continue to prevent the menu from portalling. - -Issue [#48176](https://github.com/grafana/grafana/issues/48176) - -Grafana alerting endpoint prefixed with `api/v1/rule/test` that tests a rule against a Corte/Loki data source now expects the data source UID as a path parameter instead of the data source numeric identifier. Issue [#48070](https://github.com/grafana/grafana/issues/48070) - -Grafana alerting endpoints prefixed with `api/prometheus/` that proxy requests to a Cortex/Loki data source now expect the data source UID as a path parameter instead of the data source numeric identifier. Issue [#48052](https://github.com/grafana/grafana/issues/48052) - -Grafana alerting endpoints prefixed with `api/ruler/` that proxy requests to a Cortex/Loki data source now expect the data source UID as a path parameter instead of the data source numeric identifier. Issue [#48046](https://github.com/grafana/grafana/issues/48046) - -Grafana alerting endpoints prefixed with `api/alertmanager/` that proxy requests to an Alertmanager now expect the data source UID as a path parameter instead of the data source numeric identifier. Issue [#47978](https://github.com/grafana/grafana/issues/47978) - -The format of log messages have been updated, `lvl` is now `level` and `eror`and `dbug` has been replaced with `error` and `debug`. The precision of timestamps has been increased. To smooth the transition, it is possible to opt-out of the new log format by enabling the feature toggle `oldlog`. This option will be removed in a future minor release. Issue [#47584](https://github.com/grafana/grafana/issues/47584) - -In the Loki data source, the dataframe format used to represent Loki logs-data has been changed to a more efficient format. The query-result is represented by a single dataframe with a "labels" column, instead of the separate dataframes for every labels-value. When displaying such data in explore, or in a logs-panel in the dashboard will continue to work without changes, but if the data was loaded into a different dashboard-panel, or Transforms were used, adjustments may be necessary. For example, if you used the "labels to fields" transformation with the logs data, please switch to the "extract fields" transformation. Issue [#47153](https://github.com/grafana/grafana/issues/47153) - -### Deprecations - -`setExploreQueryField`, `setExploreMetricsQueryField` and `setExploreLogsQueryField` are now deprecated and will be removed in a future release. If you need to set a different query editor for Explore, conditionally render based on `props.app` in your regular query editor. Please refer to [our documentation](https://grafana.com/developers/plugin-tools/how-to-guides/data-source-plugins/add-features-for-explore-queries) for more information. -Issue [#48701](https://github.com/grafana/grafana/issues/48701) - -### Plugin development fixes & changes - -- **Chore:** Remove react-testing-lib from bundles. [#50442](https://github.com/grafana/grafana/pull/50442), [@jackw](https://github.com/jackw) -- **Select:** Portal menu by default. [#48176](https://github.com/grafana/grafana/pull/48176), [@ashharrison90](https://github.com/ashharrison90) diff --git a/docs/sources/release-notes/release-notes-9-0-1.md b/docs/sources/release-notes/release-notes-9-0-1.md deleted file mode 100644 index 93c18bffa8c..00000000000 --- a/docs/sources/release-notes/release-notes-9-0-1.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -aliases: [] -hide_menu: true -labels: - products: - - cloud - - enterprise - - oss -title: Release notes for Grafana 9.0.1 ---- - - - -# Release notes for Grafana 9.0.1 - -### Features and enhancements - -- **Alerting:** Add support for image annotation in Alertmanager alerts. [#50686](https://github.com/grafana/grafana/pull/50686), [@grobinson-grafana](https://github.com/grobinson-grafana) -- **Alerting:** Add support for images in SensuGo alerts. [#50718](https://github.com/grafana/grafana/pull/50718), [@grobinson-grafana](https://github.com/grobinson-grafana) -- **Alerting:** Add support for images in Threema alerts. [#50734](https://github.com/grafana/grafana/pull/50734), [@grobinson-grafana](https://github.com/grobinson-grafana) -- **Alerting:** Adds Mimir to Alertmanager data source implementation. [#50943](https://github.com/grafana/grafana/pull/50943), [@gillesdemey](https://github.com/gillesdemey) -- **Alerting:** Invalid setting of enabled for unified alerting should return error. [#49876](https://github.com/grafana/grafana/pull/49876), [@grobinson-grafana](https://github.com/grobinson-grafana) -- **AzureMonitor:** Clean namespace when changing the resource. [#50311](https://github.com/grafana/grafana/pull/50311), [@andresmgot](https://github.com/andresmgot) -- **AzureMonitor:** Update supported namespaces and filter resources by the right type. [#50788](https://github.com/grafana/grafana/pull/50788), [@andresmgot](https://github.com/andresmgot) -- **CLI:** Allow relative symlinks in zip archives when installing plugins. [#50537](https://github.com/grafana/grafana/pull/50537), [@marefr](https://github.com/marefr) -- **Dashboard:** Don't show unsaved changes modal for automatic schema changes. [#50822](https://github.com/grafana/grafana/pull/50822), [@torkelo](https://github.com/torkelo) -- **Dashboard:** Unsaved changes warning should not trigger when only pluginVersion has changed. [#50677](https://github.com/grafana/grafana/pull/50677), [@torkelo](https://github.com/torkelo) -- **Expression:** Execute hidden expressions. [#50636](https://github.com/grafana/grafana/pull/50636), [@yesoreyeram](https://github.com/yesoreyeram) -- **Geomap:** Support showing tooltip content on click (not just hover). [#50985](https://github.com/grafana/grafana/pull/50985), [@ryantxu](https://github.com/ryantxu) -- **Heatmap:** Remove alpha flag from new heatmap panel. [#50733](https://github.com/grafana/grafana/pull/50733), [@ryantxu](https://github.com/ryantxu) -- **Instrumentation:** Define handlers for requests that are not handled with named handlers. [#50613](https://github.com/grafana/grafana/pull/50613), [@bergquist](https://github.com/bergquist) -- **Log Panel:** Improve log row hover contrast and visibility. [#50908](https://github.com/grafana/grafana/pull/50908), [@Seyaji](https://github.com/Seyaji) -- **Logs:** Handle backend-mode errors in histogram. [#50535](https://github.com/grafana/grafana/pull/50535), [@gabor](https://github.com/gabor) -- **Loki:** Do not show histogram for instant queries. [#50711](https://github.com/grafana/grafana/pull/50711), [@gabor](https://github.com/gabor) -- **Loki:** Handle data source configs with path in the url. [#50971](https://github.com/grafana/grafana/pull/50971), [@gabor](https://github.com/gabor) -- **Loki:** Handle invalid query type values. [#50755](https://github.com/grafana/grafana/pull/50755), [@gabor](https://github.com/gabor) -- **OAuth:** Redirect to login if no oauth module is found or if module is not configured. [#50661](https://github.com/grafana/grafana/pull/50661), [@kalleep](https://github.com/kalleep) -- **OptionsUI:** Move internal options editors out of @grafana/ui. [#50739](https://github.com/grafana/grafana/pull/50739), [@ryantxu](https://github.com/ryantxu) -- **Prometheus:** Don't show undefined for step in collapsed options in query editor when value is "auto". [#50511](https://github.com/grafana/grafana/pull/50511), [@aocenas](https://github.com/aocenas) -- **Prometheus:** Show query patterns in all editor modes for Prometheus and Loki. [#50263](https://github.com/grafana/grafana/pull/50263), [@ivanahuckova](https://github.com/ivanahuckova) -- **Tempo:** Add link to Tempo Search with node service selected. [#49776](https://github.com/grafana/grafana/pull/49776), [@joey-grafana](https://github.com/joey-grafana) -- **Time Series Panel:** Add Null Filling and "No Value" Support. [#50907](https://github.com/grafana/grafana/pull/50907), [@codeincarnate](https://github.com/codeincarnate) -- **TimeSeries:** Add an option to set legend width. [#49126](https://github.com/grafana/grafana/pull/49126), [@bobrik](https://github.com/bobrik) -- **Timeseries:** Improve cursor Y sync behavior. [#50740](https://github.com/grafana/grafana/pull/50740), [@ryantxu](https://github.com/ryantxu) -- **Traces:** Do not use red in span colors as this looks like an error. [#50074](https://github.com/grafana/grafana/pull/50074), [@joey-grafana](https://github.com/joey-grafana) - -### Bug fixes - -- **Alerting:** Fix AM config overwrite when SQLite db is locked during sync. [#50951](https://github.com/grafana/grafana/pull/50951), [@JacobsonMT](https://github.com/JacobsonMT) -- **Alerting:** Fix alert instances filtering for prom rules. [#50850](https://github.com/grafana/grafana/pull/50850), [@konrad147](https://github.com/konrad147) -- **Alerting:** Fix alert rule page crashing when datasource contained URL unsafe characters. [#51105](https://github.com/grafana/grafana/pull/51105), [@gillesdemey](https://github.com/gillesdemey) -- **Alerting:** Fix automatically select newly created folder option. [#50949](https://github.com/grafana/grafana/pull/50949), [@gillesdemey](https://github.com/gillesdemey) -- **Alerting:** Fix removal of notification policy without labels matchers. [#50678](https://github.com/grafana/grafana/pull/50678), [@konrad147](https://github.com/konrad147) -- **CloudWatch:** Allow hidden queries to be executed in case an ID is provided. [#50987](https://github.com/grafana/grafana/pull/50987), [@sunker](https://github.com/sunker) -- **Dashboard:** Prevent non-repeating panels being dropped from repeated rows when collapsed/expanded. [#50764](https://github.com/grafana/grafana/pull/50764), [@ashharrison90](https://github.com/ashharrison90) -- **Dashboards:** Fix folder picker not showing correct results when typing too fast. [#50303](https://github.com/grafana/grafana/pull/50303), [@joshhunt](https://github.com/joshhunt) -- **Datasource:** Prevent panic when proxying for non-existing data source. [#50667](https://github.com/grafana/grafana/pull/50667), [@wbrowne](https://github.com/wbrowne) -- **Explore:** Fix log context scroll to bottom. [#50600](https://github.com/grafana/grafana/pull/50600), [@ivanahuckova](https://github.com/ivanahuckova) -- **Explore:** Revert "Remove support for compact format URLs (#49350)". [#50873](https://github.com/grafana/grafana/pull/50873), [@gelicia](https://github.com/gelicia) -- **Expressions:** Fixes dashboard schema migration issue that caused Expression datasource to be set on panel level. [#50945](https://github.com/grafana/grafana/pull/50945), [@torkelo](https://github.com/torkelo) -- **Formatting:** Fixes valueFormats for a value of 0. [#50719](https://github.com/grafana/grafana/pull/50719), [@JoaoSilvaGrafana](https://github.com/JoaoSilvaGrafana) -- **GrafanaData:** Fix week start for non-English browsers. [#50582](https://github.com/grafana/grafana/pull/50582), [@AgnesToulet](https://github.com/AgnesToulet) -- **LibraryPanel:** Resizing a library panel to 6x3 no longer crashes the dashboard on startup. [#50400](https://github.com/grafana/grafana/pull/50400), [@ashharrison90](https://github.com/ashharrison90) -- **LogRow:** Fix placement of icon. [#51010](https://github.com/grafana/grafana/pull/51010), [@ivanahuckova](https://github.com/ivanahuckova) -- **Loki:** Fix bug in labels framing. [#51015](https://github.com/grafana/grafana/pull/51015), [@gabor](https://github.com/gabor) -- **Loki:** Fix issues with using query patterns. [#50414](https://github.com/grafana/grafana/pull/50414), [@ivanahuckova](https://github.com/ivanahuckova) -- **Loki:** Fix showing of duplicated label values in dropdown in query builder. [#50680](https://github.com/grafana/grafana/pull/50680), [@ivanahuckova](https://github.com/ivanahuckova) -- **MSSQL:** Fix ParseFloat error. [#50815](https://github.com/grafana/grafana/pull/50815), [@zoltanbedi](https://github.com/zoltanbedi) -- **Panels:** Fixes issue with showing 'Cannot visualize data' when query returned 0 rows. [#50485](https://github.com/grafana/grafana/pull/50485), [@torkelo](https://github.com/torkelo) -- **Playlists:** Disable Create Playlist buttons for users with viewer role. [#50840](https://github.com/grafana/grafana/pull/50840), [@asymness](https://github.com/asymness) -- **Plugins:** Fix typo in plugin data frames documentation. [#50554](https://github.com/grafana/grafana/pull/50554), [@osisoft-mbishop](https://github.com/osisoft-mbishop) -- **Prometheus:** Fix body not being included in resource calls if they are POST. [#50833](https://github.com/grafana/grafana/pull/50833), [@aocenas](https://github.com/aocenas) -- **RolePicker:** Fix submenu position on horizontal space overflow. [#50769](https://github.com/grafana/grafana/pull/50769), [@Clarity-89](https://github.com/Clarity-89) -- **Tracing:** Fix trace links in traces panel. [#50028](https://github.com/grafana/grafana/pull/50028), [@connorlindsey](https://github.com/connorlindsey) - -### Deprecations - -Support for compact Explore URLs is deprecated and will be removed in a future release. Until then, when navigating to Explore using the deprecated format the URLs are automatically converted. If you have existing links pointing to Explore update them using the format generated by Explore upon navigation. - -You can identify a compact URL by its format. Compact URLs have the left (and optionally right) url parameter as an array of strings, for example `&left=["now-1h","now"...]`. The standard explore URLs follow a key/value pattern, for example `&left={"datasource":"test"...}`. Please be sure to check your dashboards for any hardcoded links to Explore and update them to the standard URL pattern. Issue [#50873](https://github.com/grafana/grafana/issues/50873) diff --git a/docs/sources/release-notes/release-notes-9-0-2.md b/docs/sources/release-notes/release-notes-9-0-2.md deleted file mode 100644 index 08d648270c4..00000000000 --- a/docs/sources/release-notes/release-notes-9-0-2.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -aliases: [] -hide_menu: true -labels: - products: - - cloud - - enterprise - - oss -title: Release notes for Grafana 9.0.2 ---- - - - -# Release notes for Grafana 9.0.2 - -### Features and enhancements - -- **Alerting:** Add support for images in Pushover alerts. [#51372](https://github.com/grafana/grafana/pull/51372), [@grobinson-grafana](https://github.com/grobinson-grafana) -- **Alerting:** Don't stop the migration when alert rule tags are invalid. [#51253](https://github.com/grafana/grafana/pull/51253), [@gotjosh](https://github.com/gotjosh) -- **Alerting:** Don't stop the migration when alert rule tags are invalid (…. [#51341](https://github.com/grafana/grafana/pull/51341), [@gotjosh](https://github.com/gotjosh) -- **Alerting:** Skip the default data source if incompatible. [#51452](https://github.com/grafana/grafana/pull/51452), [@gillesdemey](https://github.com/gillesdemey) -- **AzureMonitor:** Parse non-fatal errors for Logs. [#51320](https://github.com/grafana/grafana/pull/51320), [@andresmgot](https://github.com/andresmgot) -- **OAuth:** Restore debug log behavior. [#51244](https://github.com/grafana/grafana/pull/51244), [@Jguer](https://github.com/Jguer) -- **Plugins:** Improved handling of symlinks. [#51324](https://github.com/grafana/grafana/pull/51324), [@marefr](https://github.com/marefr) - -### Bug fixes - -- **Alerting:** Code-gen parsing of URL parameters and fix related bugs. [#51353](https://github.com/grafana/grafana/pull/51353), [@alexweav](https://github.com/alexweav) -- **Alerting:** Code-gen parsing of URL parameters and fix related bugs. [#50731](https://github.com/grafana/grafana/pull/50731), [@alexweav](https://github.com/alexweav) -- **Annotations:** Fix annotation autocomplete causing panels to crash. [#51164](https://github.com/grafana/grafana/pull/51164), [@ashharrison90](https://github.com/ashharrison90) -- **Barchart:** Fix warning not showing. [#51190](https://github.com/grafana/grafana/pull/51190), [@joshhunt](https://github.com/joshhunt) -- **CloudWatch:** Enable custom session duration in AWS plugin auth. [#51322](https://github.com/grafana/grafana/pull/51322), [@sunker](https://github.com/sunker) -- **Dashboards:** Fixes issue with the initial panel layout counting as an unsaved change. [#51315](https://github.com/grafana/grafana/pull/51315), [@JoaoSilvaGrafana](https://github.com/JoaoSilvaGrafana) -- **Plugins:** Use a Grafana specific SDK logger implementation for core plugins. [#51229](https://github.com/grafana/grafana/pull/51229), [@marefr](https://github.com/marefr) -- **Search:** Fix pagination in the new search page. [#51366](https://github.com/grafana/grafana/pull/51366), [@ArturWierzbicki](https://github.com/ArturWierzbicki) diff --git a/docs/sources/release-notes/release-notes-9-0-3.md b/docs/sources/release-notes/release-notes-9-0-3.md deleted file mode 100644 index 0bd6ceb5ae9..00000000000 --- a/docs/sources/release-notes/release-notes-9-0-3.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -aliases: [] -hide_menu: true -labels: - products: - - cloud - - enterprise - - oss -title: Release notes for Grafana 9.0.3 ---- - - - -# Release notes for Grafana 9.0.3 - -### Features and enhancements - -- **Access Control:** Allow dashboard admins to query org users. [#51652](https://github.com/grafana/grafana/pull/51652), [@IevaVasiljeva](https://github.com/IevaVasiljeva) -- **Access control:** Allow organisation admins to add existing users to org. [#51668](https://github.com/grafana/grafana/pull/51668), [@IevaVasiljeva](https://github.com/IevaVasiljeva) -- **Alerting:** Add method to provisioning API for obtaining a group and its rules. [#51761](https://github.com/grafana/grafana/pull/51761), [@alexweav](https://github.com/alexweav) -- **Alerting:** Add method to provisioning API for obtaining a group and its rules. [#51398](https://github.com/grafana/grafana/pull/51398), [@alexweav](https://github.com/alexweav) -- **Alerting:** Allow filtering of contact points by name. [#51933](https://github.com/grafana/grafana/pull/51933), [@alexweav](https://github.com/alexweav) -- **Alerting:** Disable /api/admin/pause-all-alerts with Unified Alerting. [#51895](https://github.com/grafana/grafana/pull/51895), [@joeblubaugh](https://github.com/joeblubaugh) -- **Analytics:** Add total queries and cached queries in usage insights logs. (Enterprise) -- **Annotations:** Use point marker for short time range annotations. [#51520](https://github.com/grafana/grafana/pull/51520), [@codeincarnate](https://github.com/codeincarnate) -- **AzureMonitor:** Update UI to experimental package. [#52123](https://github.com/grafana/grafana/pull/52123), [@asimpson](https://github.com/asimpson) -- **AzureMonitor:** Update resource and namespace metadata. [#52030](https://github.com/grafana/grafana/pull/52030), [@despian](https://github.com/despian) -- **CloudWatch:** Remove simplejson in favor of 'encoding/json'. [#51062](https://github.com/grafana/grafana/pull/51062), [@asimpson](https://github.com/asimpson) -- **DashboardRow:** Collapse shortcut prevent to move the collapsed rows. [#51589](https://github.com/grafana/grafana/pull/51589), [@ivanortegaalba](https://github.com/ivanortegaalba) -- **Insights:** Add dashboard UID to exported logs. (Enterprise) -- **Navigation:** Highlight active nav item when Grafana is served from subpath. [#51767](https://github.com/grafana/grafana/pull/51767), [@kianelbo](https://github.com/kianelbo) -- **Plugins:** InfluxDB datasource - set epoch query param value as "ms". [#51651](https://github.com/grafana/grafana/pull/51651), [@itsmylife](https://github.com/itsmylife) -- **Plugins:** InfluxDB update time range query. [#51833](https://github.com/grafana/grafana/pull/51833), [@itsmylife](https://github.com/itsmylife) -- **StateTimeline:** Try to sort time field. [#51569](https://github.com/grafana/grafana/pull/51569), [@zoltanbedi](https://github.com/zoltanbedi) - -### Bug fixes - -- **API:** Do not validate/save legacy alerts when saving a dashboard if legacy alerting is disabled. [#51883](https://github.com/grafana/grafana/pull/51883), [@papagian](https://github.com/papagian) -- **Access Control:** Fix missing folder permissions. [#52153](https://github.com/grafana/grafana/pull/52153), [@IevaVasiljeva](https://github.com/IevaVasiljeva) -- **Alerting:** Add method to reset notification policy tree back to the default. [#51934](https://github.com/grafana/grafana/pull/51934), [@alexweav](https://github.com/alexweav) -- **Alerting:** Fix Teams notifier not failing on 200 response with error. [#52254](https://github.com/grafana/grafana/pull/52254), [@JacobsonMT](https://github.com/JacobsonMT) -- **Alerting:** Fix bug where state did not change between Alerting and Error. [#52204](https://github.com/grafana/grafana/pull/52204), [@grobinson-grafana](https://github.com/grobinson-grafana) -- **Alerting:** Fix consistency errors in OpenAPI documentation. [#51935](https://github.com/grafana/grafana/pull/51935), [@alexweav](https://github.com/alexweav) -- **Alerting:** Fix normalization of alert states for panel annotations. [#51637](https://github.com/grafana/grafana/pull/51637), [@gillesdemey](https://github.com/gillesdemey) -- **Alerting:** Provisioning API respects global rule quota. [#52180](https://github.com/grafana/grafana/pull/52180), [@alexweav](https://github.com/alexweav) -- **CSRF:** Fix additional headers option. [#50629](https://github.com/grafana/grafana/pull/50629), [@sakjur](https://github.com/sakjur) -- **Chore:** Bump parse-url to 6.0.2 to fix security vulnerabilities. [#51796](https://github.com/grafana/grafana/pull/51796), [@jackw](https://github.com/jackw) -- **Chore:** Fix CVE-2020-7753. [#51752](https://github.com/grafana/grafana/pull/51752), [@jackw](https://github.com/jackw) -- **Chore:** Fix CVE-2021-3807. [#51753](https://github.com/grafana/grafana/pull/51753), [@jackw](https://github.com/jackw) -- **Chore:** Fix CVE-2021-3918. [#51745](https://github.com/grafana/grafana/pull/51745), [@jackw](https://github.com/jackw) -- **Chore:** Fix CVE-2021-43138. [#51751](https://github.com/grafana/grafana/pull/51751), [@jackw](https://github.com/jackw) -- **Chore:** Fix CVE-2022-0155. [#51755](https://github.com/grafana/grafana/pull/51755), [@jackw](https://github.com/jackw) -- **Custom Branding:** Fix login logo size. (Enterprise) -- **Dashboard:** Fixes tooltip issue with TimePicker and Setting buttons. [#51836](https://github.com/grafana/grafana/pull/51836), [@torkelo](https://github.com/torkelo) -- **Dashboard:** Prevent unnecessary scrollbar when viewing single panel. [#52122](https://github.com/grafana/grafana/pull/52122), [@lpskdl](https://github.com/lpskdl) -- **Logs:** Fixed wrapping log lines from detected fields. [#52108](https://github.com/grafana/grafana/pull/52108), [@svennergr](https://github.com/svennergr) -- **Loki:** Add missing operators in label filter expression. [#51880](https://github.com/grafana/grafana/pull/51880), [@ivanahuckova](https://github.com/ivanahuckova) -- **Loki:** Fix error when changing operations with different parameters. [#51779](https://github.com/grafana/grafana/pull/51779), [@svennergr](https://github.com/svennergr) -- **Loki:** Fix suggesting of correct operations in query builder. [#52034](https://github.com/grafana/grafana/pull/52034), [@ivanahuckova](https://github.com/ivanahuckova) -- **Plugins:** InfluxDB variable interpolation fix. [#51917](https://github.com/grafana/grafana/pull/51917), [@itsmylife](https://github.com/itsmylife) -- **Plugins:** InfluxDB variable interpolation fix for influxdbBackendMigration feature flag. [#51624](https://github.com/grafana/grafana/pull/51624), [@itsmylife](https://github.com/itsmylife) -- **Reports:** Fix line breaks in message. (Enterprise) -- **Reports:** Fix saving report formats. (Enterprise) -- **SQLstore:** Fix fetching an inexistent playlist. [#51962](https://github.com/grafana/grafana/pull/51962), [@papagian](https://github.com/papagian) -- **Security:** Fixes for CVE-2022-31107 and CVE-2022-31097. [#52279](https://github.com/grafana/grafana/pull/52279), [@kminehart](https://github.com/kminehart) -- **Snapshots:** Fix deleting external snapshots when using RBAC. [#51897](https://github.com/grafana/grafana/pull/51897), [@idafurjes](https://github.com/idafurjes) -- **Table:** Fix scrollbar being hidden by pagination. [#51501](https://github.com/grafana/grafana/pull/51501), [@zoltanbedi](https://github.com/zoltanbedi) -- **Templating:** Changing between variables with the same name now correctly triggers a dashboard refresh. [#51490](https://github.com/grafana/grafana/pull/51490), [@ashharrison90](https://github.com/ashharrison90) -- **Time series panel:** Fix an issue with stacks being not complete due to the incorrect data frame length. [#51910](https://github.com/grafana/grafana/pull/51910), [@dprokop](https://github.com/dprokop) -- **[v9.0.x] Snapshots:** Fix deleting external snapshots when using RBAC (#51897). [#51904](https://github.com/grafana/grafana/pull/51904), [@idafurjes](https://github.com/idafurjes) diff --git a/docs/sources/release-notes/release-notes-9-0-4.md b/docs/sources/release-notes/release-notes-9-0-4.md deleted file mode 100644 index 2fa37b35bbe..00000000000 --- a/docs/sources/release-notes/release-notes-9-0-4.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -aliases: [] -hide_menu: true -labels: - products: - - cloud - - enterprise - - oss -title: Release notes for Grafana 9.0.4 ---- - - - -# Release notes for Grafana 9.0.4 - -### Features and enhancements - -- **Browse/Search:** Make browser back work properly when visiting Browse or search. [#52271](https://github.com/grafana/grafana/pull/52271), [@torkelo](https://github.com/torkelo) -- **Logs:** Improve getLogRowContext API. [#52130](https://github.com/grafana/grafana/pull/52130), [@gabor](https://github.com/gabor) -- **Loki:** Improve handling of empty responses. [#52397](https://github.com/grafana/grafana/pull/52397), [@gabor](https://github.com/gabor) -- **Plugins:** Always validate root URL if specified in signature manifest. [#52332](https://github.com/grafana/grafana/pull/52332), [@wbrowne](https://github.com/wbrowne) -- **Preferences:** Get home dashboard from teams. [#52225](https://github.com/grafana/grafana/pull/52225), [@sakjur](https://github.com/sakjur) -- **SQLStore:** Support Upserting multiple rows. [#52228](https://github.com/grafana/grafana/pull/52228), [@joeblubaugh](https://github.com/joeblubaugh) -- **Traces:** Add more template variables in Tempo & Zipkin. [#52306](https://github.com/grafana/grafana/pull/52306), [@joey-grafana](https://github.com/joey-grafana) -- **Traces:** Remove serviceMap feature flag. [#52375](https://github.com/grafana/grafana/pull/52375), [@joey-grafana](https://github.com/joey-grafana) - -### Bug fixes - -- **Access Control:** Fix missing folder permissions. [#52410](https://github.com/grafana/grafana/pull/52410), [@IevaVasiljeva](https://github.com/IevaVasiljeva) -- **Access control:** Fix org user removal for OSS users. [#52473](https://github.com/grafana/grafana/pull/52473), [@IevaVasiljeva](https://github.com/IevaVasiljeva) -- **Alerting:** Fix Slack notification preview. [#50230](https://github.com/grafana/grafana/pull/50230), [@ekrucio](https://github.com/ekrucio) -- **Alerting:** Fix Slack push notifications. [#52391](https://github.com/grafana/grafana/pull/52391), [@grobinson-grafana](https://github.com/grobinson-grafana) -- **Alerting:** Fixes slack push notifications. [#50267](https://github.com/grafana/grafana/pull/50267), [@jgillick](https://github.com/jgillick) -- **Alerting:** Preserve new-lines from custom email templates in rendered email. [#52253](https://github.com/grafana/grafana/pull/52253), [@alexweav](https://github.com/alexweav) -- **Insights:** Fix dashboard and data source insights pages. (Enterprise) -- **Log:** Fix text logging for unsupported types. [#51306](https://github.com/grafana/grafana/pull/51306), [@papagian](https://github.com/papagian) -- **Loki:** Fix `show context` not working in some occasions. [#52458](https://github.com/grafana/grafana/pull/52458), [@svennergr](https://github.com/svennergr) -- **Loki:** Fix incorrect TopK value type in query builder. [#52226](https://github.com/grafana/grafana/pull/52226), [@ivanahuckova](https://github.com/ivanahuckova) diff --git a/docs/sources/release-notes/release-notes-9-0-5.md b/docs/sources/release-notes/release-notes-9-0-5.md deleted file mode 100644 index 21c0afb2edb..00000000000 --- a/docs/sources/release-notes/release-notes-9-0-5.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -aliases: [] -hide_menu: true -labels: - products: - - cloud - - enterprise - - oss -title: Release notes for Grafana 9.0.5 ---- - - - -# Release notes for Grafana 9.0.5 - -### Features and enhancements - -- **Access control:** Show dashboard settings to users who can edit dashboard. [#52535](https://github.com/grafana/grafana/pull/52535), [@grafanabot](https://github.com/grafanabot) -- **Alerting:** Allow the webhook notifier to support a custom Authorization header. [#52515](https://github.com/grafana/grafana/pull/52515), [@gotjosh](https://github.com/gotjosh) -- **Chore:** Upgrade to Go version 1.17.12. [#52523](https://github.com/grafana/grafana/pull/52523), [@sakjur](https://github.com/sakjur) -- **Plugins:** Add signature wildcard globbing for dedicated private plugin type. [#52163](https://github.com/grafana/grafana/pull/52163), [@wbrowne](https://github.com/wbrowne) -- **Prometheus:** Don't show errors from unsuccessful API checks like rules or exemplar checks. [#52193](https://github.com/grafana/grafana/pull/52193), [@darrenjaneczek](https://github.com/darrenjaneczek) - -### Bug fixes - -- **Access control:** Allow organisation admins to add existing users to org (#51668). [#52553](https://github.com/grafana/grafana/pull/52553), [@vtorosyan](https://github.com/vtorosyan) -- **Alerting:** Fix alert panel instance-based rules filtering. [#52583](https://github.com/grafana/grafana/pull/52583), [@konrad147](https://github.com/konrad147) -- **Apps:** Fixes navigation between different app plugin pages. [#52571](https://github.com/grafana/grafana/pull/52571), [@torkelo](https://github.com/torkelo) -- **Cloudwatch:** Upgrade grafana-aws-sdk to fix auth issue with secret keys. [#52420](https://github.com/grafana/grafana/pull/52420), [@sarahzinger](https://github.com/sarahzinger) -- **Grafana/toolkit:** Fix incorrect image and font generation for plugin builds. [#52661](https://github.com/grafana/grafana/pull/52661), [@academo](https://github.com/academo) -- **Loki:** Fix `show context` not working in some occasions. [#52458](https://github.com/grafana/grafana/pull/52458), [@svennergr](https://github.com/svennergr) -- **RBAC:** Fix permissions on dashboards and folders created by anonymous users. [#52615](https://github.com/grafana/grafana/pull/52615), [@gamab](https://github.com/gamab) diff --git a/docs/sources/release-notes/release-notes-9-0-6.md b/docs/sources/release-notes/release-notes-9-0-6.md deleted file mode 100644 index 2667aa909b7..00000000000 --- a/docs/sources/release-notes/release-notes-9-0-6.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -aliases: [] -hide_menu: true -labels: - products: - - cloud - - enterprise - - oss -title: Release notes for Grafana 9.0.6 ---- - - - -# Release notes for Grafana 9.0.6 - -### Features and enhancements - -- **Access Control:** Allow org admins to invite new users to their organization. [#52904](https://github.com/grafana/grafana/pull/52904), [@IevaVasiljeva](https://github.com/IevaVasiljeva) - -### Bug fixes - -- **Grafana/toolkit:** Fix incorrect image and font generation for plugin builds. [#52927](https://github.com/grafana/grafana/pull/52927), [@academo](https://github.com/academo) -- **Prometheus:** Fix adding of multiple values for regex operator. [#52978](https://github.com/grafana/grafana/pull/52978), [@ivanahuckova](https://github.com/ivanahuckova) -- **UI/Card:** Fix card items always having pointer cursor. [#52809](https://github.com/grafana/grafana/pull/52809), [@gillesdemey](https://github.com/gillesdemey) diff --git a/docs/sources/release-notes/release-notes-9-0-7.md b/docs/sources/release-notes/release-notes-9-0-7.md deleted file mode 100644 index 547ac15f405..00000000000 --- a/docs/sources/release-notes/release-notes-9-0-7.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -aliases: [] -hide_menu: true -labels: - products: - - cloud - - enterprise - - oss -title: Release notes for Grafana 9.0.7 ---- - - - -# Release notes for Grafana 9.0.7 - -### Features and enhancements - -- **CloudMonitoring:** Remove link setting for SLO queries. [#53031](https://github.com/grafana/grafana/pull/53031), [@andresmgot](https://github.com/andresmgot) - -### Bug fixes - -- **GrafanaUI:** Render PageToolbar's leftItems regardless of title's presence. [#53285](https://github.com/grafana/grafana/pull/53285), [@Elfo404](https://github.com/Elfo404) -- **Reports:** Fix inconsistency reports. (Enterprise) -- **Reports:** Set UID when it's not received in the query. (Enterprise) -- **Reports:** Save and update in reports should be transactional. (Enterprise) diff --git a/docs/sources/release-notes/release-notes-9-0-8.md b/docs/sources/release-notes/release-notes-9-0-8.md deleted file mode 100644 index 821e2a3fd77..00000000000 --- a/docs/sources/release-notes/release-notes-9-0-8.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -aliases: [] -hide_menu: true -labels: - products: - - cloud - - enterprise - - oss -title: Release notes for Grafana 9.0.8 ---- - - - -# Release notes for Grafana 9.0.8 - -### Features and enhancements - -- **Alerting:** Hide "no rules" message when we are fetching from data sources. [#53778](https://github.com/grafana/grafana/pull/53778), [@gillesdemey](https://github.com/gillesdemey) -- **Rendering:** Add support for renderer token (#54425). [#54439](https://github.com/grafana/grafana/pull/54439), [@joanlopez](https://github.com/joanlopez) -- **Reports:** Title is showing under panels. (Enterprise) -- **Alerting:** AlertingProxy to elevate permissions for request forwarded to data proxy when RBAC enabled. [#53680](https://github.com/grafana/grafana/pull/53680), [@yuri-tceretian](https://github.com/yuri-tceretian) diff --git a/docs/sources/release-notes/release-notes-9-0-9.md b/docs/sources/release-notes/release-notes-9-0-9.md deleted file mode 100644 index f300f8cdce6..00000000000 --- a/docs/sources/release-notes/release-notes-9-0-9.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -aliases: [] -hide_menu: true -labels: - products: - - cloud - - enterprise - - oss -title: Release notes for Grafana 9.0.9 ---- - - - -# Release notes for Grafana 9.0.9 - -### Bug fixes - -- **AngularPanels:** Fixing changing angular panel options not taking having affect when coming back from panel edit. [#54834](https://github.com/grafana/grafana/pull/54834), [@grafanabot](https://github.com/grafanabot) -- **AuthNZ:** Security fixes for CVE-2022-35957 and CVE-2022-36062. [#55498](https://github.com/grafana/grafana/pull/55498), [@IevaVasiljeva](https://github.com/IevaVasiljeva) -- **FIX:** RBAC prevents deleting empty snapshots (#54385). [#54509](https://github.com/grafana/grafana/pull/54509), [@gamab](https://github.com/gamab) diff --git a/docs/sources/release-notes/release-notes-9-1-0-beta1.md b/docs/sources/release-notes/release-notes-9-1-0-beta1.md deleted file mode 100644 index 8f24c8bfb36..00000000000 --- a/docs/sources/release-notes/release-notes-9-1-0-beta1.md +++ /dev/null @@ -1,223 +0,0 @@ ---- -aliases: [] -hide_menu: true -labels: - products: - - cloud - - enterprise - - oss -title: Release notes for Grafana 9.1.0-beta1 ---- - - - -# Release notes for Grafana 9.1.0-beta1 - -### Features and enhancements - -- **API:** Migrate CSRF to service and support additional options. [#48120](https://github.com/grafana/grafana/pull/48120), [@sakjur](https://github.com/sakjur) -- **API:** Move swagger definitions to the handlers and rename operations after them. [#52643](https://github.com/grafana/grafana/pull/52643), [@papagian](https://github.com/papagian) -- **Access Control:** Allow org admins to invite new users. [#52894](https://github.com/grafana/grafana/pull/52894), [@IevaVasiljeva](https://github.com/IevaVasiljeva) -- **AccessControl:** Check dashboards permission for reports. (Enterprise) -- **Alerting:** Add config disabled_labels to disable reserved labels. [#51832](https://github.com/grafana/grafana/pull/51832), [@JacobsonMT](https://github.com/JacobsonMT) -- **Alerting:** Add custom templated title to Wecom notifier. [#51529](https://github.com/grafana/grafana/pull/51529), [@dingweiqings](https://github.com/dingweiqings) -- **Alerting:** Add file provisioning for alert rules. [#51635](https://github.com/grafana/grafana/pull/51635), [@JohnnyQQQQ](https://github.com/JohnnyQQQQ) -- **Alerting:** Add file provisioning for contact points. [#51924](https://github.com/grafana/grafana/pull/51924), [@JohnnyQQQQ](https://github.com/JohnnyQQQQ) -- **Alerting:** Add file provisioning for mute timings. [#52936](https://github.com/grafana/grafana/pull/52936), [@JohnnyQQQQ](https://github.com/JohnnyQQQQ) -- **Alerting:** Add file provisioning for notification policies. [#52877](https://github.com/grafana/grafana/pull/52877), [@JohnnyQQQQ](https://github.com/JohnnyQQQQ) -- **Alerting:** Add file provisioning for text templates. [#52952](https://github.com/grafana/grafana/pull/52952), [@JohnnyQQQQ](https://github.com/JohnnyQQQQ) -- **Alerting:** Add first Grafana reserved label grafana_folder. [#50262](https://github.com/grafana/grafana/pull/50262), [@JacobsonMT](https://github.com/JacobsonMT) -- **Alerting:** Add support for images in Kafka alerts. [#50758](https://github.com/grafana/grafana/pull/50758), [@grobinson-grafana](https://github.com/grobinson-grafana) -- **Alerting:** Add support for images in VictorOps alerts. [#50759](https://github.com/grafana/grafana/pull/50759), [@grobinson-grafana](https://github.com/grobinson-grafana) -- **Alerting:** Adds contact point template syntax highlighting. [#51559](https://github.com/grafana/grafana/pull/51559), [@gillesdemey](https://github.com/gillesdemey) -- **Alerting:** Adds visual tokens for templates. [#51376](https://github.com/grafana/grafana/pull/51376), [@gillesdemey](https://github.com/gillesdemey) -- **Alerting:** Alert rules pagination. [#50612](https://github.com/grafana/grafana/pull/50612), [@konrad147](https://github.com/konrad147) -- **Alerting:** Change **alertScreenshotToken** to **alertImageToken**. [#50771](https://github.com/grafana/grafana/pull/50771), [@grobinson-grafana](https://github.com/grobinson-grafana) -- **Alerting:** Configure alert manager data source as an external AM. [#52081](https://github.com/grafana/grafana/pull/52081), [@konrad147](https://github.com/konrad147) -- **Alerting:** Do not include button in googlechat notification if URL invalid. [#47317](https://github.com/grafana/grafana/pull/47317), [@j6s](https://github.com/j6s) -- **Alerting:** Group alert state history by labels and allow filtering. [#52784](https://github.com/grafana/grafana/pull/52784), [@gillesdemey](https://github.com/gillesdemey) -- **Alerting:** Make ticker to tick at predictable time. [#50197](https://github.com/grafana/grafana/pull/50197), [@yuri-tceretian](https://github.com/yuri-tceretian) -- **Alerting:** Persist rule position in the group. [#50051](https://github.com/grafana/grafana/pull/50051), [@yuri-tceretian](https://github.com/yuri-tceretian) -- **Alerting:** Prevent evaluation if "for" shorter than "evaluate". [#51797](https://github.com/grafana/grafana/pull/51797), [@peterholmberg](https://github.com/peterholmberg) -- **Alerting:** Provisioning UI. [#50776](https://github.com/grafana/grafana/pull/50776), [@gillesdemey](https://github.com/gillesdemey) -- **Alerting:** Rule api to fail update if provisioned rules are affected. [#50835](https://github.com/grafana/grafana/pull/50835), [@yuri-tceretian](https://github.com/yuri-tceretian) -- **Alerting:** Scheduler to drop ticks if a rule's evaluation is too slow. [#48885](https://github.com/grafana/grafana/pull/48885), [@yuri-tceretian](https://github.com/yuri-tceretian) -- **Alerting:** Show evaluation interval global limit warning. [#52942](https://github.com/grafana/grafana/pull/52942), [@konrad147](https://github.com/konrad147) -- **Alerting:** State manager to use tick time to determine stale states. [#50991](https://github.com/grafana/grafana/pull/50991), [@yuri-tceretian](https://github.com/yuri-tceretian) -- **Alerting:** Support for optimistic locking for alert rules. [#50274](https://github.com/grafana/grafana/pull/50274), [@yuri-tceretian](https://github.com/yuri-tceretian) -- **Alerting:** Update RBAC for alert rules to consider access to rule as access to group it belongs. [#49033](https://github.com/grafana/grafana/pull/49033), [@yuri-tceretian](https://github.com/yuri-tceretian) -- **Alerting:** Update default route groupBy to [grafana_folder, alertname]. [#50052](https://github.com/grafana/grafana/pull/50052), [@JacobsonMT](https://github.com/JacobsonMT) -- **Alertmanager:** Adding SigV4 Authentication to Alertmanager Datasource. [#49718](https://github.com/grafana/grafana/pull/49718), [@lewinkedrs](https://github.com/lewinkedrs) -- **Analytics:** Save all view time dates as UTC. (Enterprise) -- **Annotations:** Migrate dashboardId to dashboardUID. [#52588](https://github.com/grafana/grafana/pull/52588), [@lpskdl](https://github.com/lpskdl) -- **Auditing:** Allow users to have more verbose logs. (Enterprise) -- **Auth:** Add lookup params for saml and LDAP sync. (Enterprise) -- **Auth:** Add option for case insensitive login. [#49262](https://github.com/grafana/grafana/pull/49262), [@Jguer](https://github.com/Jguer) -- **Auth:** Case insensitive ids duplicate usagestats. [#50724](https://github.com/grafana/grafana/pull/50724), [@eleijonmarck](https://github.com/eleijonmarck) -- **Auth:** Implement Token URL Auth. [#52578](https://github.com/grafana/grafana/pull/52578), [@Jguer](https://github.com/Jguer) -- **Auth:** Implement Token URL JWT Auth. [#52662](https://github.com/grafana/grafana/pull/52662), [@Jguer](https://github.com/Jguer) -- **Auth:** Lockdown non-editables in frontend when external auth is configured. [#52160](https://github.com/grafana/grafana/pull/52160), [@Jguer](https://github.com/Jguer) -- **Azure Monitor:** Add new dashboard with geo map for app insights test availability. [#52494](https://github.com/grafana/grafana/pull/52494), [@jcolladokuri](https://github.com/jcolladokuri) -- **Azure Monitor:** New template variable editor. [#52594](https://github.com/grafana/grafana/pull/52594), [@andresmgot](https://github.com/andresmgot) -- **Azure Monitor:** Restore Metrics query parameters: subscription, resourceGroup, metricNamespace and resourceName. [#52897](https://github.com/grafana/grafana/pull/52897), [@andresmgot](https://github.com/andresmgot) -- **Chore:** Add dashboard UID as query parameter of Get annotation endpoint. [#52764](https://github.com/grafana/grafana/pull/52764), [@ying-jeanne](https://github.com/ying-jeanne) -- **Chore:** Remove jest-coverage-badges dep from toolkit. [#49883](https://github.com/grafana/grafana/pull/49883), [@zoltanbedi](https://github.com/zoltanbedi) -- **Chore:** Rename dashboardUID to dashboardUIDs in search endpoint and up…. [#52766](https://github.com/grafana/grafana/pull/52766), [@ying-jeanne](https://github.com/ying-jeanne) -- **CloudWatch:** Add default log groups to config page. [#49286](https://github.com/grafana/grafana/pull/49286), [@iwysiu](https://github.com/iwysiu) -- **CommandPalette:** Populate dashboard search when the palette is opened. [#51293](https://github.com/grafana/grafana/pull/51293), [@ryantxu](https://github.com/ryantxu) -- **Core Plugins:** Add support for HTTP logger. [#46578](https://github.com/grafana/grafana/pull/46578), [@toddtreece](https://github.com/toddtreece) -- **Correlations:** Add CreateCorrelation HTTP API. [#51630](https://github.com/grafana/grafana/pull/51630), [@Elfo404](https://github.com/Elfo404) -- **Correlations:** Add DeleteCorrelation HTTP API. [#51801](https://github.com/grafana/grafana/pull/51801), [@Elfo404](https://github.com/Elfo404) -- **Custom branding:** Add UI for setting configuration. (Enterprise) -- **Custom branding:** Add custom branding service (early access). (Enterprise) -- **Data Connections:** Create a new top-level page. [#50018](https://github.com/grafana/grafana/pull/50018), [@leventebalogh](https://github.com/leventebalogh) -- **DataSource:** Allow data source plugins to set query default values. [#49581](https://github.com/grafana/grafana/pull/49581), [@sunker](https://github.com/sunker) -- **Docs:** CSRF add configuration options and documentation for additional headers and origins. [#50473](https://github.com/grafana/grafana/pull/50473), [@eleijonmarck](https://github.com/eleijonmarck) -- **Elasticsearch:** Added `modifyQuery` method to add filters in Explore. [#52313](https://github.com/grafana/grafana/pull/52313), [@svennergr](https://github.com/svennergr) -- **Explore:** Add ability to include tags in trace to metrics queries. [#49433](https://github.com/grafana/grafana/pull/49433), [@connorlindsey](https://github.com/connorlindsey) -- **Explore:** Download and upload service graphs for Tempo. [#50260](https://github.com/grafana/grafana/pull/50260), [@connorlindsey](https://github.com/connorlindsey) -- **Explore:** Make service graph visualization use available vertical space. [#50518](https://github.com/grafana/grafana/pull/50518), [@connorlindsey](https://github.com/connorlindsey) -- **Explore:** Reset Graph overrides if underlying series changes. [#49680](https://github.com/grafana/grafana/pull/49680), [@Elfo404](https://github.com/Elfo404) -- **Explore:** Sort trace process attributes alphabetically. [#51261](https://github.com/grafana/grafana/pull/51261), [@connorlindsey](https://github.com/connorlindsey) -- **Frontend Logging:** Integrate grafana javascript agent. [#50801](https://github.com/grafana/grafana/pull/50801), [@tolzhabayev](https://github.com/tolzhabayev) -- **Geomap:** Add ability to select a data query filter for each layer. [#49966](https://github.com/grafana/grafana/pull/49966), [@mmandrus](https://github.com/mmandrus) -- **Geomap:** Route/path visualization. [#43554](https://github.com/grafana/grafana/pull/43554), [@alexanderzobnin](https://github.com/alexanderzobnin) -- **GeomapPanel:** Add base types to data layer options. [#50053](https://github.com/grafana/grafana/pull/50053), [@drew08t](https://github.com/drew08t) -- **Graph Panel:** Add feature toggle that will allow automatic migration to timeseries panel. [#50631](https://github.com/grafana/grafana/pull/50631), [@ryantxu](https://github.com/ryantxu) -- **Graphite:** Introduce new query types in annotation editor. [#52341](https://github.com/grafana/grafana/pull/52341), [@itsmylife](https://github.com/itsmylife) -- **Infra:** Pass custom headers in resource request. [#51291](https://github.com/grafana/grafana/pull/51291), [@aocenas](https://github.com/aocenas) -- **Insights:** Add RBAC for insights features. (Enterprise) -- **Instrumentation:** Add more buckets to the HTTP request histogram. [#51492](https://github.com/grafana/grafana/pull/51492), [@bergquist](https://github.com/bergquist) -- **Instrumentation:** Collect database connection stats. [#52797](https://github.com/grafana/grafana/pull/52797), [@bergquist](https://github.com/bergquist) -- **Instrumentation:** Convert some metrics to histograms. [#50420](https://github.com/grafana/grafana/pull/50420), [@SuperQ](https://github.com/SuperQ) -- **Jaeger:** Add support for variables. [#50500](https://github.com/grafana/grafana/pull/50500), [@joey-grafana](https://github.com/joey-grafana) -- **LDAP:** Allow specifying LDAP timeout. [#48870](https://github.com/grafana/grafana/pull/48870), [@hannes-256](https://github.com/hannes-256) -- **LibraryPanels:** Require only viewer permissions to use a Library Panel. [#50241](https://github.com/grafana/grafana/pull/50241), [@joshhunt](https://github.com/joshhunt) -- **Licensing:** Usage-based billing reporting enhancements. (Enterprise) -- **Logs:** Handle clicks on legend labels in histogram. [#49931](https://github.com/grafana/grafana/pull/49931), [@gabor](https://github.com/gabor) -- **Logs:** Improve the color for unknown log level. [#52711](https://github.com/grafana/grafana/pull/52711), [@gabor](https://github.com/gabor) -- **Loki/Logs:** Make it possible to copy log values to clipboard. [#50914](https://github.com/grafana/grafana/pull/50914), [@Seyaji](https://github.com/Seyaji) -- **Loki:** Add hint for pipeline error to query builder. [#52134](https://github.com/grafana/grafana/pull/52134), [@ivanahuckova](https://github.com/ivanahuckova) -- **Loki:** Add hints for level-like labels. [#52414](https://github.com/grafana/grafana/pull/52414), [@ivanahuckova](https://github.com/ivanahuckova) -- **Loki:** Add support for IP label and line filter in query builder. [#52658](https://github.com/grafana/grafana/pull/52658), [@ivanahuckova](https://github.com/ivanahuckova) -- **Loki:** Add unwrap with conversion function to builder. [#52639](https://github.com/grafana/grafana/pull/52639), [@ivanahuckova](https://github.com/ivanahuckova) -- **Loki:** Implement hints for query builder. [#51795](https://github.com/grafana/grafana/pull/51795), [@ivanahuckova](https://github.com/ivanahuckova) -- **Loki:** Move explain section to builder mode. [#52879](https://github.com/grafana/grafana/pull/52879), [@ivanahuckova](https://github.com/ivanahuckova) -- **Loki:** Show label options for unwrap operation. [#52810](https://github.com/grafana/grafana/pull/52810), [@ivanahuckova](https://github.com/ivanahuckova) -- **Loki:** Support json parser with expressions in query builder. [#51965](https://github.com/grafana/grafana/pull/51965), [@ivanahuckova](https://github.com/ivanahuckova) -- **Navigation:** Display `Starred` dashboards in the `Navbar`. [#51038](https://github.com/grafana/grafana/pull/51038), [@ashharrison90](https://github.com/ashharrison90) -- **Node Graph Panel:** Add options to configure units and arc colors. [#51057](https://github.com/grafana/grafana/pull/51057), [@connorlindsey](https://github.com/connorlindsey) -- **OAuth:** Allow role mapping from GitHub and GitLab groups. [#52407](https://github.com/grafana/grafana/pull/52407), [@Jguer](https://github.com/Jguer) -- **Opentsdb:** Add tag values into the opentsdb response. [#48672](https://github.com/grafana/grafana/pull/48672), [@xy-man](https://github.com/xy-man) -- **OptionsUI:** UnitPicker now supports isClearable setting. [#51064](https://github.com/grafana/grafana/pull/51064), [@ryantxu](https://github.com/ryantxu) -- **PanelEdit:** Hide multi-/all-select datasource variables in datasource picker. [#52142](https://github.com/grafana/grafana/pull/52142), [@eledobleefe](https://github.com/eledobleefe) -- **Piechart:** Implements series override -> hide in area for the legend or tooltip. [#51297](https://github.com/grafana/grafana/pull/51297), [@daniellee](https://github.com/daniellee) -- **Plugin admin:** Add a page to show where panel plugins are used in dashboards. [#50909](https://github.com/grafana/grafana/pull/50909), [@ryantxu](https://github.com/ryantxu) -- **Plugins:** Add validation for plugin manifest. [#52787](https://github.com/grafana/grafana/pull/52787), [@wbrowne](https://github.com/wbrowne) -- **Prometheus:** Move explain section to builder mode. [#52935](https://github.com/grafana/grafana/pull/52935), [@itsmylife](https://github.com/itsmylife) -- **Prometheus:** Support 1ms resolution intervals. [#44707](https://github.com/grafana/grafana/pull/44707), [@dankeder](https://github.com/dankeder) -- **Prometheus:** Throw error on direct access. [#50162](https://github.com/grafana/grafana/pull/50162), [@aocenas](https://github.com/aocenas) -- **RBAC:** Add RBAC for query caching. (Enterprise) -- **RBAC:** Add access control metadata to folder dtos. [#51158](https://github.com/grafana/grafana/pull/51158), [@kalleep](https://github.com/kalleep) -- **RBAC:** Allow app plugins access restriction. [#51524](https://github.com/grafana/grafana/pull/51524), [@gamab](https://github.com/gamab) -- **RBAC:** Rename alerting roles to match naming convention. [#50504](https://github.com/grafana/grafana/pull/50504), [@gamab](https://github.com/gamab) -- **Report:** Calculate grid height unit dynamically instead use hardcode values. (Enterprise) -- **Reports:** Add created column in report_dashboards. (Enterprise) -- **Reports:** Add dashboard title in all pdf pages. (Enterprise) -- **Reports:** Allow saving draft reports. (Enterprise) -- **Reports:** Multiple dashboards improvements. (Enterprise) -- **SAML :** Support Azure Single Sign Out. (Enterprise) -- **SAML:** Add NameIDFormat in SP metadata. (Enterprise) -- **SAML:** Improve debug logs for saml logout. (Enterprise) -- **SSE:** Add noData type. [#51973](https://github.com/grafana/grafana/pull/51973), [@kylebrandt](https://github.com/kylebrandt) -- **Search:** Filter punctuation and tokenize camel case. [#51165](https://github.com/grafana/grafana/pull/51165), [@FZambia](https://github.com/FZambia) -- **Search:** Sync state on read for HA consistency. [#50152](https://github.com/grafana/grafana/pull/50152), [@FZambia](https://github.com/FZambia) -- **Security:** Choose Lookup params per auth module (CVE-2022-31107). [#52312](https://github.com/grafana/grafana/pull/52312), [@Jguer](https://github.com/Jguer) -- **Service Accounts:** Managed permissions for service accounts. [#51818](https://github.com/grafana/grafana/pull/51818), [@IevaVasiljeva](https://github.com/IevaVasiljeva) -- **Service accounts:** Grafana service accounts are enabled by default. [#51402](https://github.com/grafana/grafana/pull/51402), [@vtorosyan](https://github.com/vtorosyan) -- **ServiceAccounts:** Add Prometheus metrics service. [#51831](https://github.com/grafana/grafana/pull/51831), [@Jguer](https://github.com/Jguer) -- **ServiceAccounts:** Add Service Account Token last used at date. [#51446](https://github.com/grafana/grafana/pull/51446), [@Jguer](https://github.com/Jguer) -- **SharePDF:** Use currently selected variables and time range when generating PDF. (Enterprise) -- **Slider:** Enforce numeric constraints and styling within the text input. [#50905](https://github.com/grafana/grafana/pull/50905), [@drew08t](https://github.com/drew08t) -- **State Timeline:** Enable support for annotations. [#47887](https://github.com/grafana/grafana/pull/47887), [@dprokop](https://github.com/dprokop) -- **Table panel:** Add multiple data links support to Default, Image and JSONView cells. [#51162](https://github.com/grafana/grafana/pull/51162), [@dprokop](https://github.com/dprokop) -- **TeamSync:** Remove LDAP specific example from team sync. [#51368](https://github.com/grafana/grafana/pull/51368), [@Jguer](https://github.com/Jguer) -- **TeamSync:** Support case insensitive matches and wildcard groups. (Enterprise) -- **Tempo:** Add context menu to edges. [#52396](https://github.com/grafana/grafana/pull/52396), [@joey-grafana](https://github.com/joey-grafana) -- **Tempo:** Consider tempo search out of beta and remove beta badge and feature flags. [#50030](https://github.com/grafana/grafana/pull/50030), [@connorlindsey](https://github.com/connorlindsey) -- **Tempo:** Tempo/Prometheus links select ds in new tab (cmd + click). [#52319](https://github.com/grafana/grafana/pull/52319), [@joey-grafana](https://github.com/joey-grafana) -- **Time series panel:** Hide axis when series is hidden from the visualization. [#51432](https://github.com/grafana/grafana/pull/51432), [@dprokop](https://github.com/dprokop) -- **TimeSeries:** Add option for symmetrical y axes (align 0). [#52555](https://github.com/grafana/grafana/pull/52555), [@leeoniya](https://github.com/leeoniya) -- **TimeSeries:** Add option to match axis color to series color. [#51437](https://github.com/grafana/grafana/pull/51437), [@leeoniya](https://github.com/leeoniya) -- **TimeSeries:** Improved constantY rendering parity with Graph (old). [#51401](https://github.com/grafana/grafana/pull/51401), [@leeoniya](https://github.com/leeoniya) -- **Timeseries:** Support multiple timezones in x axis. [#52424](https://github.com/grafana/grafana/pull/52424), [@ryantxu](https://github.com/ryantxu) -- **TopNav:** Adds new feature toggle for upcoming nav. [#51115](https://github.com/grafana/grafana/pull/51115), [@torkelo](https://github.com/torkelo) -- **Traces:** APM table. [#48654](https://github.com/grafana/grafana/pull/48654), [@joey-grafana](https://github.com/joey-grafana) -- **Traces:** Add absolute time to span details. [#50685](https://github.com/grafana/grafana/pull/50685), [@joey-grafana](https://github.com/joey-grafana) -- **Traces:** Add horizontal scroll. [#50278](https://github.com/grafana/grafana/pull/50278), [@joey-grafana](https://github.com/joey-grafana) -- **Traces:** Consistent span colors for service names. [#50782](https://github.com/grafana/grafana/pull/50782), [@joey-grafana](https://github.com/joey-grafana) -- **Traces:** Move towards using OTEL naming conventions. [#51379](https://github.com/grafana/grafana/pull/51379), [@joey-grafana](https://github.com/joey-grafana) -- **Traces:** Span bar label. [#50931](https://github.com/grafana/grafana/pull/50931), [@joey-grafana](https://github.com/joey-grafana) -- **Transformations:** Add standard deviation and variance reducers. [#52769](https://github.com/grafana/grafana/pull/52769), [@ryantxu](https://github.com/ryantxu) -- **Transforms:** Add Join by label transformation. [#52670](https://github.com/grafana/grafana/pull/52670), [@ryantxu](https://github.com/ryantxu) -- **URL:** Encode certain special characters. [#51806](https://github.com/grafana/grafana/pull/51806), [@L-M-K-B](https://github.com/L-M-K-B) -- **ValueMappings:** Make value mapping row focusable. [#52337](https://github.com/grafana/grafana/pull/52337), [@lpskdl](https://github.com/lpskdl) -- **Variables:** Add 'jsonwithoutquote' formatting options for variables, and format of variable supports pipeline. [#51859](https://github.com/grafana/grafana/pull/51859), [@MicroOps-cn](https://github.com/MicroOps-cn) -- **Variables:** Selectively reload panels on URL update. [#51003](https://github.com/grafana/grafana/pull/51003), [@toddtreece](https://github.com/toddtreece) -- **Various Panels:** Add ability to toggle legend with keyboard shortcut. [#52241](https://github.com/grafana/grafana/pull/52241), [@alyssabull](https://github.com/alyssabull) - -### Bug fixes - -- **API:** Fix failing test by initialising legacy guardian when creating folder scenario. [#50800](https://github.com/grafana/grafana/pull/50800), [@vicmarbev](https://github.com/vicmarbev) -- **Access control:** Show dashboard settings to users who can edit dashboard. [#52532](https://github.com/grafana/grafana/pull/52532), [@IevaVasiljeva](https://github.com/IevaVasiljeva) -- **Alerting:** Fix RegExp matchers in frontend for Silences and other previews. [#51726](https://github.com/grafana/grafana/pull/51726), [@joeblubaugh](https://github.com/joeblubaugh) -- **Alerting:** Fix rule API to accept 0 duration of field `For`. [#50992](https://github.com/grafana/grafana/pull/50992), [@yuri-tceretian](https://github.com/yuri-tceretian) -- **Alerting:** Increase alert rule operation perf by replacing subquery with threshold calculation. [#53069](https://github.com/grafana/grafana/pull/53069), [@alexweav](https://github.com/alexweav) -- **Barchart Panel:** Fix threshold colors changing when data is refreshed. [#52038](https://github.com/grafana/grafana/pull/52038), [@mingozh](https://github.com/mingozh) -- **Dashboard:** Fix iteration property change triggering unsaved changes warning. [#51272](https://github.com/grafana/grafana/pull/51272), [@torkelo](https://github.com/torkelo) -- **Dashboards:** Disable variable pickers for snapshots. [#52827](https://github.com/grafana/grafana/pull/52827), [@joshhunt](https://github.com/joshhunt) -- **Elasticsearch:** Always use fixed_interval. [#50297](https://github.com/grafana/grafana/pull/50297), [@gabor](https://github.com/gabor) -- **Geomap:** Fix tooltip offset bug. [#52627](https://github.com/grafana/grafana/pull/52627), [@drew08t](https://github.com/drew08t) -- **Geomap:** Update with template variable change. [#52007](https://github.com/grafana/grafana/pull/52007), [@drew08t](https://github.com/drew08t) -- **Loki:** Fix adding of multiple label filters when parser. [#52335](https://github.com/grafana/grafana/pull/52335), [@ivanahuckova](https://github.com/ivanahuckova) -- **Loki:** Fix support of ad-hoc filters for specific queries. [#51232](https://github.com/grafana/grafana/pull/51232), [@ivanahuckova](https://github.com/ivanahuckova) -- **Navigation:** Hide `Dashboards`/`Starred items` from navbar when unauthenticated. [#53051](https://github.com/grafana/grafana/pull/53051), [@ashharrison90](https://github.com/ashharrison90) -- **PasswordReset:** Enforce password length check on password reset request. [#51005](https://github.com/grafana/grafana/pull/51005), [@asymness](https://github.com/asymness) -- **Prometheus:** Fix integer overflow in rate interval calculation on 32-bit architectures. [#51508](https://github.com/grafana/grafana/pull/51508), [@andreasgerstmayr](https://github.com/andreasgerstmayr) -- **Search:** Fix indexing - re-index after initial provisioning. [#50959](https://github.com/grafana/grafana/pull/50959), [@FZambia](https://github.com/FZambia) -- **Slider:** Fixes styling of marker dots. [#52678](https://github.com/grafana/grafana/pull/52678), [@torkelo](https://github.com/torkelo) -- **Tracing:** Fix links to traces in Explore. [#50113](https://github.com/grafana/grafana/pull/50113), [@connorlindsey](https://github.com/connorlindsey) - -### Breaking changes - -Some swagger operations and responses have been renamed to match the respective handler names in order to better highlight their relation. -If you use the Swagger specification for generating code, you have to re-generate it and make the necessary adjustments. Issue [#52643](https://github.com/grafana/grafana/issues/52643) - -The following metrics have been converted to histograms: - -- grafana_datasource_request_total -- grafana_datasource_request_duration_seconds -- grafana_datasource_response_size_bytes -- grafana_datasource_request_in_flight -- grafana_plugin_request_duration_milliseconds -- grafana_alerting_rule_evaluation_duration_seconds Issue [#50420](https://github.com/grafana/grafana/issues/50420) - -In Elasticsearch versions 7.x, to specify the interval-value we used the `interval` property. In Grafana 9.1.0 we switched to use the `fixed_interval` property. This makes it to be the same as in Elasticsearch versions 8.x, also this provides a more consistent experience, `fixed_interval` is a better match to Grafana's time intervals. For most situations this will not cause any visible change to query results. Issue [#50297](https://github.com/grafana/grafana/issues/50297) - -### Grafana now reserves alert labels prefixed with `grafana_` - -Labels prefixed with `grafana_` are reserved by Grafana for special use. If a manually configured label is added beginning with `grafana_` it may be overwritten in case of collision. - -The current list of labels created by Grafana and available for use anywhere manually configured labels are: - -| Label | Description | -| -------------- | ----------------------------------------- | --------------------------------------------------------------- | -| grafana_folder | Title of the folder containing the alert. | Issue [#50262](https://github.com/grafana/grafana/issues/50262) | - -In Prometheus, browser access mode was deprecated in Grafana 7.4.0 and removed in 9.0.0. If you used this mode, please switch to server access mode on the datasource configuration page. Issue [#50162](https://github.com/grafana/grafana/issues/50162) - -### Plugin development fixes & changes - -- **Dropdown:** New dropdown component. [#52684](https://github.com/grafana/grafana/pull/52684), [@torkelo](https://github.com/torkelo) -- **Grafana/UI:** Add ColorPickerInput component. [#52222](https://github.com/grafana/grafana/pull/52222), [@Clarity-89](https://github.com/Clarity-89) -- **Plugins:** Validate root URLs when signing private plugins via grafana-toolkit. [#51968](https://github.com/grafana/grafana/pull/51968), [@wbrowne](https://github.com/wbrowne) diff --git a/docs/sources/release-notes/release-notes-9-1-0.md b/docs/sources/release-notes/release-notes-9-1-0.md deleted file mode 100644 index fd304535ae0..00000000000 --- a/docs/sources/release-notes/release-notes-9-1-0.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -aliases: [] -hide_menu: true -labels: - products: - - cloud - - enterprise - - oss -title: Release notes for Grafana 9.1.0 ---- - - - -# Release notes for Grafana 9.1.0 - -### Features and enhancements - -- **API:** Allow creating teams with a user defined identifier. [#48710](https://github.com/grafana/grafana/pull/48710), [@papagian](https://github.com/papagian) -- **Alerting:** Adds interval and For to alert rule details. [#53211](https://github.com/grafana/grafana/pull/53211), [@gillesdemey](https://github.com/gillesdemey) -- **Alerting:** Extend PUT rule-group route to write the entire rule group rather than top-level fields only. [#53078](https://github.com/grafana/grafana/pull/53078), [@alexweav](https://github.com/alexweav) -- **Alerting:** Use Adaptive Cards in Teams notifications. [#53532](https://github.com/grafana/grafana/pull/53532), [@grobinson-grafana](https://github.com/grobinson-grafana) -- **Azure Monitor:** Add Network Insights Dashboard. [#50362](https://github.com/grafana/grafana/pull/50362), [@Teddy-Lin](https://github.com/Teddy-Lin) -- **Chore:** Improve logging of unrecoverable errors. [#53664](https://github.com/grafana/grafana/pull/53664), [@sakjur](https://github.com/sakjur) -- **Correlations:** Add UpdateCorrelation HTTP API. [#52444](https://github.com/grafana/grafana/pull/52444), [@Elfo404](https://github.com/Elfo404) -- **Dashboard:** Reverted the changes of hiding multi-select and all variable in the datasource picker. [#53521](https://github.com/grafana/grafana/pull/53521), [@lpskdl](https://github.com/lpskdl) -- **Geomap:** Add alpha day/night layer. [#50201](https://github.com/grafana/grafana/pull/50201), [@ryantxu](https://github.com/ryantxu) -- **Geomap:** Add measuring tools. [#51608](https://github.com/grafana/grafana/pull/51608), [@drew08t](https://github.com/drew08t) -- **GrafanaUI:** Add success state to ClipboardButton. [#52069](https://github.com/grafana/grafana/pull/52069), [@evictorero](https://github.com/evictorero) -- **Heatmap:** Replace the heatmap panel with new implementation. [#50229](https://github.com/grafana/grafana/pull/50229), [@ryantxu](https://github.com/ryantxu) -- **KVStore:** Allow empty value in kv_store. [#53416](https://github.com/grafana/grafana/pull/53416), [@spinillos](https://github.com/spinillos) -- **Prometheus:** Promote Azure auth flag to configuration. [#53447](https://github.com/grafana/grafana/pull/53447), [@andresmgot](https://github.com/andresmgot) -- **Reports:** Save and update in reports should be transactional. (Enterprise) -- **Reports:** Set uid when we don't receive it in the query. (Enterprise) -- **Search:** Display only dashboards in General folder of Search Folder View. [#53607](https://github.com/grafana/grafana/pull/53607), [@lpskdl](https://github.com/lpskdl) -- **Status history/State timeline:** Support datalinks. [#50226](https://github.com/grafana/grafana/pull/50226), [@jloupdef](https://github.com/jloupdef) -- **Transform:** Add a limit transform. [#49291](https://github.com/grafana/grafana/pull/49291), [@josiahg](https://github.com/josiahg) -- **Transformations:** Add standard deviation and variance reducers. [#49753](https://github.com/grafana/grafana/pull/49753), [@selvavm](https://github.com/selvavm) - -### Bug fixes - -- **API:** Fix snapshot responses. [#52998](https://github.com/grafana/grafana/pull/52998), [@papagian](https://github.com/papagian) -- **Access Control:** Fix permission error during dashboard creation flow. [#53214](https://github.com/grafana/grafana/pull/53214), [@IevaVasiljeva](https://github.com/IevaVasiljeva) -- **Access Control:** Set permissions for Grafana's test data source. [#53247](https://github.com/grafana/grafana/pull/53247), [@IevaVasiljeva](https://github.com/IevaVasiljeva) -- **Alerting:** Fix migration failure. [#53253](https://github.com/grafana/grafana/pull/53253), [@papagian](https://github.com/papagian) -- **BarGauge:** Show empty bar when value, minValue and maxValue are all equal. [#53314](https://github.com/grafana/grafana/pull/53314), [@ashharrison90](https://github.com/ashharrison90) -- **Dashboard:** Fix color of bold and italics text in panel description tooltip. [#53380](https://github.com/grafana/grafana/pull/53380), [@joshhunt](https://github.com/joshhunt) -- **Loki:** Fix passing of query with defaults to code mode. [#53646](https://github.com/grafana/grafana/pull/53646), [@ivanahuckova](https://github.com/ivanahuckova) -- **Loki:** Fix producing correct log volume query for query with comments. [#53254](https://github.com/grafana/grafana/pull/53254), [@ivanahuckova](https://github.com/ivanahuckova) -- **Loki:** Fix showing of unusable labels field in detected fields. [#53319](https://github.com/grafana/grafana/pull/53319), [@ivanahuckova](https://github.com/ivanahuckova) -- **Reports:** Fix inconsistency reports. (Enterprise) -- **Tracing:** Fix OpenTelemetry Jaeger context propagation. [#53269](https://github.com/grafana/grafana/pull/53269), [@zhichli](https://github.com/zhichli) -- **Tracing:** Fix OpenTelemetry Jaeger context propagation (#53269). [#53724](https://github.com/grafana/grafana/pull/53724), [@idafurjes](https://github.com/idafurjes) -- **[9.1.x] Alerting:** AlertingProxy to elevate permissions for request forwarded to data proxy when RBAC enabled. [#53679](https://github.com/grafana/grafana/pull/53679), [@yuri-tceretian](https://github.com/yuri-tceretian) - -### Breaking changes - -Alert notifications to Microsoft Teams now use Adaptive Cards instead of Office 365 Connector Cards. Issue [#53532](https://github.com/grafana/grafana/issues/53532) - -Starting at 9.1.0, existing heatmap panels will start using a new implementation. This can be disabled by setting the `useLegacyHeatmapPanel` feature flag to true. It can be tested on a single dashbobard by adding `?__feature.useLegacyHeatmapPanel=true` to any dashboard URL. Please report any [heatmap migration issues.](https://github.com/grafana/grafana/issues/new/choose). The most notable changes are: - -- Significantly improved rendering performance -- When calculating heatmaps, the buckets are now placed on reasonable borders (1m, 5m, 30s etc) -- Round cells are no longer supported - Issue [#50229](https://github.com/grafana/grafana/issues/50229) - -### Plugin development fixes & changes - -- **Plugins:** Only pass `rootUrls` field in request when not empty. [#53135](https://github.com/grafana/grafana/pull/53135), [@wbrowne](https://github.com/wbrowne) diff --git a/docs/sources/release-notes/release-notes-9-1-1.md b/docs/sources/release-notes/release-notes-9-1-1.md deleted file mode 100644 index 6e489dd8b6f..00000000000 --- a/docs/sources/release-notes/release-notes-9-1-1.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -aliases: [] -hide_menu: true -labels: - products: - - cloud - - enterprise - - oss -title: Release notes for Grafana 9.1.1 ---- - - - -# Release notes for Grafana 9.1.1 - -### Features and enhancements - -- **Cloud Monitoring:** Support SLO burn rate. [#53710](https://github.com/grafana/grafana/pull/53710), [@itkq](https://github.com/itkq) -- **Schema:** Restore "hidden" in LegendDisplayMode. [#53925](https://github.com/grafana/grafana/pull/53925), [@academo](https://github.com/academo) -- **Timeseries:** Revert the timezone(s) property name change back to singular. [#53926](https://github.com/grafana/grafana/pull/53926), [@academo](https://github.com/academo) - -### Bug fixes - -- **Alerting:** Fix links in Microsoft Teams notifications. [#54003](https://github.com/grafana/grafana/pull/54003), [@grobinson-grafana](https://github.com/grobinson-grafana) -- **Alerting:** Fix notifications for Microsoft Teams. [#53810](https://github.com/grafana/grafana/pull/53810), [@grobinson-grafana](https://github.com/grobinson-grafana) -- **Alerting:** Fix width of Adaptive Cards in Teams notifications. [#53996](https://github.com/grafana/grafana/pull/53996), [@grobinson-grafana](https://github.com/grobinson-grafana) -- **ColorPickerInput:** Fix popover in disabled state. [#54000](https://github.com/grafana/grafana/pull/54000), [@Clarity-89](https://github.com/Clarity-89) -- **Decimals:** Fixes auto decimals to behave the same for positive and negative values. [#53960](https://github.com/grafana/grafana/pull/53960), [@JoaoSilvaGrafana](https://github.com/JoaoSilvaGrafana) -- **Loki:** Fix unique log row id generation. [#53932](https://github.com/grafana/grafana/pull/53932), [@gabor](https://github.com/gabor) -- **Plugins:** Fix file extension in development authentication guide. [#53838](https://github.com/grafana/grafana/pull/53838), [@pbzona](https://github.com/pbzona) -- **TimeSeries:** Fix jumping legend issue. [#53671](https://github.com/grafana/grafana/pull/53671), [@zoltanbedi](https://github.com/zoltanbedi) -- **TimeSeries:** Fix memory leak on viz re-init caused by KeyboardPlugin. [#53872](https://github.com/grafana/grafana/pull/53872), [@leeoniya](https://github.com/leeoniya) - -### Plugin development fixes & changes - -- **TimePicker:** Fixes relative timerange of less than a day not displaying. [#53975](https://github.com/grafana/grafana/pull/53975), [@JoaoSilvaGrafana](https://github.com/JoaoSilvaGrafana) -- **GrafanaUI:** Fixes ClipboardButton to always keep multi line content. [#53903](https://github.com/grafana/grafana/pull/53903), [@JoaoSilvaGrafana](https://github.com/JoaoSilvaGrafana) diff --git a/docs/sources/release-notes/release-notes-9-1-2.md b/docs/sources/release-notes/release-notes-9-1-2.md deleted file mode 100644 index 3ed1f863ab6..00000000000 --- a/docs/sources/release-notes/release-notes-9-1-2.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -aliases: [] -hide_menu: true -labels: - products: - - cloud - - enterprise - - oss -title: Release notes for Grafana 9.1.2 ---- - - - -# Release notes for Grafana 9.1.2 - -### Features and enhancements - -- **AdHoc variable:** Correctly preselect datasource when provisioning. [#54088](https://github.com/grafana/grafana/pull/54088), [@dprokop](https://github.com/dprokop) -- **AzureMonitor:** Added ARG query function for template variables. [#53059](https://github.com/grafana/grafana/pull/53059), [@yaelleC](https://github.com/yaelleC) -- **Dashboard save:** Persist details message when navigating through dashboard save drawer's tabs. [#54084](https://github.com/grafana/grafana/pull/54084), [@vbeskrovnov](https://github.com/vbeskrovnov) -- **Dashboards:** Correctly migrate mixed data source targets. [#54152](https://github.com/grafana/grafana/pull/54152), [@dprokop](https://github.com/dprokop) -- **Elasticsearch:** Use millisecond intervals for alerting. [#54157](https://github.com/grafana/grafana/pull/54157), [@gabor](https://github.com/gabor) -- **Elasticsearch:** Use millisecond intervals in frontend. [#54202](https://github.com/grafana/grafana/pull/54202), [@gabor](https://github.com/gabor) -- **Geomap:** Local color range. [#54348](https://github.com/grafana/grafana/pull/54348), [@adela-almasan](https://github.com/adela-almasan) -- **Plugins Catalog:** Use appSubUrl to generate plugins catalog urls. [#54426](https://github.com/grafana/grafana/pull/54426), [@academo](https://github.com/academo) -- **Rendering:** Add support for renderer token. [#54425](https://github.com/grafana/grafana/pull/54425), [@joanlopez](https://github.com/joanlopez) - -### Bug fixes - -- **Alerting:** Fix saving of screenshots uploaded with a signed url. [#53933](https://github.com/grafana/grafana/pull/53933), [@VDVsx](https://github.com/VDVsx) -- **AngularPanels:** Fixing changing angular panel options not taking having affect when coming back from panel edit. [#54087](https://github.com/grafana/grafana/pull/54087), [@torkelo](https://github.com/torkelo) -- **Explore:** Improve a11y of query row collapse button. [#53827](https://github.com/grafana/grafana/pull/53827), [@L-M-K-B](https://github.com/L-M-K-B) -- **Geomap:** Fix tooltip display. [#54245](https://github.com/grafana/grafana/pull/54245), [@adela-almasan](https://github.com/adela-almasan) -- **QueryEditorRow:** Filter data on mount. [#54260](https://github.com/grafana/grafana/pull/54260), [@asimpson](https://github.com/asimpson) -- **Search:** Show all dashboards in the folder view. [#54163](https://github.com/grafana/grafana/pull/54163), [@ryantxu](https://github.com/ryantxu) -- **Tracing:** Fix the event attributes in opentelemetry tracing. [#54117](https://github.com/grafana/grafana/pull/54117), [@ying-jeanne](https://github.com/ying-jeanne) - -### Plugin development fixes & changes - -- **GrafanaUI:** Fix styles for invalid selects & DataSourcePicker. [#53476](https://github.com/grafana/grafana/pull/53476), [@Elfo404](https://github.com/Elfo404) diff --git a/docs/sources/release-notes/release-notes-9-1-3.md b/docs/sources/release-notes/release-notes-9-1-3.md deleted file mode 100644 index d0cf502c8d7..00000000000 --- a/docs/sources/release-notes/release-notes-9-1-3.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -aliases: [] -hide_menu: true -labels: - products: - - cloud - - enterprise - - oss -title: Release notes for Grafana 9.1.3 ---- - - - -# Release notes for Grafana 9.1.3 - -### Features and enhancements - -- **API:** Do not expose user input in datasource error responses. [#53483](https://github.com/grafana/grafana/pull/53483), [@papagian](https://github.com/papagian) -- **Alerting:** Write and Delete multiple alert instances. [#54072](https://github.com/grafana/grafana/pull/54072), [@joeblubaugh](https://github.com/joeblubaugh) -- **Library Panel:** Allow to delete them when deprecated. [#54662](https://github.com/grafana/grafana/pull/54662), [@ivanortegaalba](https://github.com/ivanortegaalba) -- **Plugins Catalog:** Allow to filter plugins using special characters. [#54474](https://github.com/grafana/grafana/pull/54474), [@murtazaqa](https://github.com/murtazaqa) - -### Bug fixes - -- **Alerting:** Fix UI bug when setting custom notification policy group by. [#54607](https://github.com/grafana/grafana/pull/54607), [@JacobsonMT](https://github.com/JacobsonMT) -- **AppRootPage:** Fix issue navigating between two app plugin pages. [#54519](https://github.com/grafana/grafana/pull/54519), [@torkelo](https://github.com/torkelo) -- **Correlations:** Use correct fallback handlers. [#54511](https://github.com/grafana/grafana/pull/54511), [@kalleep](https://github.com/kalleep) -- **FIX:** RBAC prevents deleting empty snapshots (#54385). [#54510](https://github.com/grafana/grafana/pull/54510), [@gamab](https://github.com/gamab) -- **LibraryElements:** Fix inability to delete library panels under MySQL. [#54600](https://github.com/grafana/grafana/pull/54600), [@kaydelaney](https://github.com/kaydelaney) -- **Metrics:** fix `grafana_database_conn_*` metrics, and add new `go_sql_stats_*` metrics as eventual replacement. [#54405](https://github.com/grafana/grafana/pull/54405), [@hairyhenderson](https://github.com/hairyhenderson) -- **TestData DB:** Fix node graph not showing when the `Data type` field is set to `random`. [#54298](https://github.com/grafana/grafana/pull/54298), [@CrypticSignal](https://github.com/CrypticSignal) - -### Deprecations - -The `grafana_database_conn_*` metrics are deprecated, and will be removed in a future version of Grafana. Use the `go_sql_stats_*` metrics instead. Issue [#54405](https://github.com/grafana/grafana/issues/54405) diff --git a/docs/sources/release-notes/release-notes-9-1-4.md b/docs/sources/release-notes/release-notes-9-1-4.md deleted file mode 100644 index c0b03d18768..00000000000 --- a/docs/sources/release-notes/release-notes-9-1-4.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -aliases: [] -hide_menu: true -labels: - products: - - cloud - - enterprise - - oss -title: Release notes for Grafana 9.1.4 ---- - - - -# Release notes for Grafana 9.1.4 - -### Bug fixes - -- **GrafanaUI:** Fixes Chrome issue for various query fields. [#54566](https://github.com/grafana/grafana/pull/54566), [@kaydelaney](https://github.com/kaydelaney) diff --git a/docs/sources/release-notes/release-notes-9-1-5.md b/docs/sources/release-notes/release-notes-9-1-5.md deleted file mode 100644 index 3024d787402..00000000000 --- a/docs/sources/release-notes/release-notes-9-1-5.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -aliases: [] -hide_menu: true -labels: - products: - - cloud - - enterprise - - oss -title: Release notes for Grafana 9.1.5 ---- - - - -# Release notes for Grafana 9.1.5 - -### Features and enhancements - -- **Alerting:** Sanitize invalid label/annotation names for external alertmanagers. [#54537](https://github.com/grafana/grafana/pull/54537), [@JacobsonMT](https://github.com/JacobsonMT) -- **Alerting:** Telegram: Truncate long messages to avoid send error. [#54339](https://github.com/grafana/grafana/pull/54339), [@ZloyDyadka](https://github.com/ZloyDyadka) -- **DisplayProcessor:** Handle reverse-ordered data when auto-showing millis. [#54923](https://github.com/grafana/grafana/pull/54923), [@leeoniya](https://github.com/leeoniya) -- **Heatmap:** Add option to reverse color scheme. [#54365](https://github.com/grafana/grafana/pull/54365), [@leeoniya](https://github.com/leeoniya) -- **PluginLoader:** Alias slate-react as @grafana/slate-react. [#55027](https://github.com/grafana/grafana/pull/55027), [@kaydelaney](https://github.com/kaydelaney) -- **Search:** Add substring matcher, to bring back the old dashboard search behavior. [#54813](https://github.com/grafana/grafana/pull/54813), [@ArturWierzbicki](https://github.com/ArturWierzbicki) -- **Traces:** More visible span colors. [#54513](https://github.com/grafana/grafana/pull/54513), [@joey-grafana](https://github.com/joey-grafana) - -### Bug fixes - -- **Alerting:** Fix incorrect propagation of org ID and other fields in rule provisioning endpoints. [#54603](https://github.com/grafana/grafana/pull/54603), [@alexweav](https://github.com/alexweav) -- **Alerting:** Resetting the notification policy tree to the default policy will also restore default contact points. [#54608](https://github.com/grafana/grafana/pull/54608), [@alexweav](https://github.com/alexweav) -- **AzureMonitor:** Fix custom namespaces. [#54937](https://github.com/grafana/grafana/pull/54937), [@asimpson](https://github.com/asimpson) -- **AzureMonitor:** Fix issue where custom metric namespaces are not included in the metric namespace list. [#54826](https://github.com/grafana/grafana/pull/54826), [@andresmgot](https://github.com/andresmgot) -- **CloudWatch:** Fix display name of metric and namespace. [#54860](https://github.com/grafana/grafana/pull/54860), [@sunker](https://github.com/sunker) -- **Cloudwatch:** Fix annotation query serialization issue. [#54884](https://github.com/grafana/grafana/pull/54884), [@sunker](https://github.com/sunker) -- **Dashboard:** Fix issue where unsaved changes warning would appear even after save, and not being able to change library panels. [#54706](https://github.com/grafana/grafana/pull/54706), [@torkelo](https://github.com/torkelo) -- **Dashboard:** Hide overflow content for single left pane. [#54882](https://github.com/grafana/grafana/pull/54882), [@lpskdl](https://github.com/lpskdl) -- **Loki:** Fix a bug where adding adhoc filters was not possible. [#54920](https://github.com/grafana/grafana/pull/54920), [@svennergr](https://github.com/svennergr) -- **Reports:** Fix handling expired state. (Enterprise) diff --git a/docs/sources/release-notes/release-notes-9-1-6.md b/docs/sources/release-notes/release-notes-9-1-6.md deleted file mode 100644 index 0a37175b5eb..00000000000 --- a/docs/sources/release-notes/release-notes-9-1-6.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -aliases: [] -hide_menu: true -labels: - products: - - cloud - - enterprise - - oss -title: Release notes for Grafana 9.1.6 ---- - - - -# Release notes for Grafana 9.1.6 - -### Features and enhancements - -- **Auth:** Trigger auth token cleanup job. (Enterprise) -- **DataSource:** Adding possibility to hide queries from the inspector. [#54892](https://github.com/grafana/grafana/pull/54892), [@mckn](https://github.com/mckn) -- **Inspect:** Hide Actions tab when it is empty. [#55272](https://github.com/grafana/grafana/pull/55272), [@ryantxu](https://github.com/ryantxu) -- **PanelMenu:** Remove hide legend action as it was showing on all panel types. [#54876](https://github.com/grafana/grafana/pull/54876), [@torkelo](https://github.com/torkelo) -- **Provisioning Contact points:** Support disableResolveMessage via YAML. [#54122](https://github.com/grafana/grafana/pull/54122), [@mmusenbr](https://github.com/mmusenbr) -- **PublicDashboards:** Support subpaths when generating pubdash url. [#55204](https://github.com/grafana/grafana/pull/55204), [@owensmallwood](https://github.com/owensmallwood) - -### Bug fixes - -- **Alerting:** Fix legacy migration crash when rule name is too long. [#55053](https://github.com/grafana/grafana/pull/55053), [@alexweav](https://github.com/alexweav) -- **Alerting:** Fix send resolved notifications. [#54793](https://github.com/grafana/grafana/pull/54793), [@grobinson-grafana](https://github.com/grobinson-grafana) -- **Azure Monitor:** Fix migration issue with MetricDefinitionsQuery template variable query types. [#55262](https://github.com/grafana/grafana/pull/55262), [@yaelleC](https://github.com/yaelleC) -- **Browse:** Hide dashboard actions if user does not have enough permission. [#55218](https://github.com/grafana/grafana/pull/55218), [@lpskdl](https://github.com/lpskdl) -- **ElasticSearch:** Fix dispatching queries at a wrong time. [#55225](https://github.com/grafana/grafana/pull/55225), [@svennergr](https://github.com/svennergr) -- **Panel:** Disable legends when showLegend is false prior to schema v37. [#55126](https://github.com/grafana/grafana/pull/55126), [@ivanortegaalba](https://github.com/ivanortegaalba) -- **Prometheus:** Fix metadata requests for browser access mode. [#55403](https://github.com/grafana/grafana/pull/55403), [@itsmylife](https://github.com/itsmylife) -- **Search:** Avoid requesting all dashboards when in Folder View. [#55169](https://github.com/grafana/grafana/pull/55169), [@JoaoSilvaGrafana](https://github.com/JoaoSilvaGrafana) -- **TablePanel/StatPanel:** Fix values not being visible when background transparent. [#55092](https://github.com/grafana/grafana/pull/55092), [@mdvictor](https://github.com/mdvictor) diff --git a/docs/sources/release-notes/release-notes-9-1-7.md b/docs/sources/release-notes/release-notes-9-1-7.md deleted file mode 100644 index 917936045dc..00000000000 --- a/docs/sources/release-notes/release-notes-9-1-7.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -aliases: [] -hide_menu: true -labels: - products: - - cloud - - enterprise - - oss -title: Release notes for Grafana 9.1.7 ---- - - - -# Release notes for Grafana 9.1.7 - -### Features and enhancements - -- **Chore:** Upgrade Go version to 1.19.1 (backport). [#55733](https://github.com/grafana/grafana/pull/55733), [@sakjur](https://github.com/sakjur) -- **CloudWatch:** Add missing AWS/Prometheus metrics. [#54990](https://github.com/grafana/grafana/pull/54990), [@jangaraj](https://github.com/jangaraj) -- **Explore:** Add feature tracking events. [#54514](https://github.com/grafana/grafana/pull/54514), [@L-M-K-B](https://github.com/L-M-K-B) -- **Graphite:** Add error information to graphite queries tracing. [#55249](https://github.com/grafana/grafana/pull/55249), [@jesusvazquez](https://github.com/jesusvazquez) -- **Prometheus:** Restore FromAlert header. [#55255](https://github.com/grafana/grafana/pull/55255), [@kylebrandt](https://github.com/kylebrandt) -- **SAML:** Account for all orgs in org_mapping (#3855). (Enterprise) -- **Search:** Add search index configuration options. [#55525](https://github.com/grafana/grafana/pull/55525), [@ArturWierzbicki](https://github.com/ArturWierzbicki) -- **Thresholds:** Add option for dashed line style. [#55875](https://github.com/grafana/grafana/pull/55875), [@leeoniya](https://github.com/leeoniya) - -### Bug fixes - -- **Alerting:** Fix default query's data source when no default datasource specified. [#55435](https://github.com/grafana/grafana/pull/55435), [@konrad147](https://github.com/konrad147) -- **Alerting:** Fix mathexp.NoData cannot be reduced. [#55347](https://github.com/grafana/grafana/pull/55347), [@grobinson-grafana](https://github.com/grobinson-grafana) -- **Alerting:** Skip unsupported file types on provisioning. [#55573](https://github.com/grafana/grafana/pull/55573), [@JohnnyQQQQ](https://github.com/JohnnyQQQQ) -- **AzureMonitor:** Ensure resourceURI template variable is migrated. [#56095](https://github.com/grafana/grafana/pull/56095), [@aangelisc](https://github.com/aangelisc) -- **Dashboard:** Fix plugin dashboard save as button. [#55197](https://github.com/grafana/grafana/pull/55197), [@lpskdl](https://github.com/lpskdl) -- **Docs:** Fix decimals: auto docs for panel edit. [#55477](https://github.com/grafana/grafana/pull/55477), [@joshhunt](https://github.com/joshhunt) -- **Fix:** RBAC handle `error no resolver` found. [#55676](https://github.com/grafana/grafana/pull/55676), [@gamab](https://github.com/gamab) -- **Fix:** RBAC handle `error no resolver` found. (Enterprise) -- **LibraryPanelSearch:** Refactor and fix hyphen issue. [#55314](https://github.com/grafana/grafana/pull/55314), [@kaydelaney](https://github.com/kaydelaney) -- **Live:** Fix live streaming with `live-service-web-worker` feature flag enabled. [#55528](https://github.com/grafana/grafana/pull/55528), [@ArturWierzbicki](https://github.com/ArturWierzbicki) -- **QueryField:** Fix wrong cursor position on autocomplete. [#55576](https://github.com/grafana/grafana/pull/55576), [@svennergr](https://github.com/svennergr) diff --git a/docs/sources/release-notes/release-notes-v7-4-2.md b/docs/sources/release-notes/release-notes-v7-4-2.md deleted file mode 100644 index ba19e3b2b2e..00000000000 --- a/docs/sources/release-notes/release-notes-v7-4-2.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -_build: - list: false -labels: - products: - - cloud - - enterprise - - oss -title: undefined ---- - - diff --git a/docs/sources/whatsnew/_index.md b/docs/sources/whatsnew/_index.md index bcc9f3b4b4d..fb478253a93 100644 --- a/docs/sources/whatsnew/_index.md +++ b/docs/sources/whatsnew/_index.md @@ -53,6 +53,120 @@ aliases: - whats-new-in-v6-5/ - whats-new-in-v6-6/ - whats-new-in-v6-7/ + - release-notes/ + - release-notes/release-notes-9-1-7/ + - release-notes/release-notes-9-1-6/ + - release-notes/release-notes-9-1-5/ + - release-notes/release-notes-9-1-4/ + - release-notes/release-notes-9-1-3/ + - release-notes/release-notes-9-1-2/ + - release-notes/release-notes-9-1-1/ + - release-notes/release-notes-9-1-0/ + - release-notes/release-notes-9-1-0-beta1/ + - release-notes/release-notes-9-0-8/ + - release-notes/release-notes-9-0-5/ + - release-notes/release-notes-9-0-4/ + - release-notes/release-notes-9-0-3/ + - release-notes/release-notes-9-0-1/ + - release-notes/release-notes-9-0-0/ + - release-notes/release-notes-9-0-0-beta3/ + - release-notes/release-notes-9-0-0-beta2/ + - release-notes/release-notes-9-0-0-beta1/ + - release-notes/release-notes-8-5-13/ + - release-notes/release-notes-8-5-11/ + - release-notes/release-notes-8-5-10/ + - release-notes/release-notes-8-5-9/ + - release-notes/release-notes-8-5-6/ + - release-notes/release-notes-8-5-5/ + - release-notes/release-notes-8-5-4/ + - release-notes/release-notes-8-5-3/ + - release-notes/release-notes-8-5-2/ + - release-notes/release-notes-8-5-1/ + - release-notes/release-notes-8-5-0/ + - release-notes/release-notes-8-5-0-beta1/ + - release-notes/release-notes-8-4-11/ + - release-notes/release-notes-8-4-10/ + - release-notes/release-notes-8-4-7/ + - release-notes/release-notes-8-4-6/ + - release-notes/release-notes-8-4-5/ + - release-notes/release-notes-8-4-4/ + - release-notes/release-notes-8-4-3/ + - release-notes/release-notes-8-4-2/ + - release-notes/release-notes-8-4-1/ + - release-notes/release-notes-8-4-0/ + - release-notes/release-notes-8-4-0-beta1/ + - release-notes/release-notes-8-3-11/ + - release-notes/release-notes-8-3-7/ + - release-notes/release-notes-8-3-6/ + - release-notes/release-notes-8-3-5/ + - release-notes/release-notes-8-3-4/ + - release-notes/release-notes-8-3-2/ + - release-notes/release-notes-8-3-1/ + - release-notes/release-notes-8-3-0/ + - release-notes/release-notes-8-3-0-beta2/ + - release-notes/release-notes-8-3-0-beta1/ + - release-notes/release-notes-8-2-7/ + - release-notes/release-notes-8-2-6/ + - release-notes/release-notes-8-2-5/ + - release-notes/release-notes-8-2-4/ + - release-notes/release-notes-8-2-3/ + - release-notes/release-notes-8-2-1/ + - release-notes/release-notes-8-2-0/ + - release-notes/release-notes-8-2-0-beta2/ + - release-notes/release-notes-8-2-0-beta1/ + - release-notes/release-notes-8-1-8/ + - release-notes/release-notes-8-1-7/ + - release-notes/release-notes-8-1-6/ + - release-notes/release-notes-8-1-5/ + - release-notes/release-notes-8-1-4/ + - release-notes/release-notes-8-1-3/ + - release-notes/release-notes-8-1-2/ + - release-notes/release-notes-8-1-1/ + - release-notes/release-notes-8-1-0/ + - release-notes/release-notes-8-1-0-beta3/ + - release-notes/release-notes-8-1-0-beta2/ + - release-notes/release-notes-8-1-0-beta1/ + - release-notes/release-notes-8-0-7/ + - release-notes/release-notes-8-0-6/ + - release-notes/release-notes-8-0-5/ + - release-notes/release-notes-8-0-4/ + - release-notes/release-notes-8-0-3/ + - release-notes/release-notes-8-0-2/ + - release-notes/release-notes-8-0-1/ + - release-notes/release-notes-8-0-0/ + - release-notes/release-notes-8-0-0-beta3/ + - release-notes/release-notes-8-0-0-beta2/ + - release-notes/release-notes-8-0-0-beta1/ + - release-notes/release-notes-7-5-15/ + - release-notes/release-notes-7-5-13/ + - release-notes/release-notes-7-5-12/ + - release-notes/release-notes-7-5-11/ + - release-notes/release-notes-7-5-10/ + - release-notes/release-notes-7-5-9/ + - release-notes/release-notes-7-5-8/ + - release-notes/release-notes-7-5-7/ + - release-notes/release-notes-7-5-6/ + - release-notes/release-notes-7-5-5/ + - release-notes/release-notes-7-5-4/ + - release-notes/release-notes-7-5-3/ + - release-notes/release-notes-7-5-2/ + - release-notes/release-notes-7-5-1/ + - release-notes/release-notes-7-5-0/ + - release-notes/release-notes-7-5-0-beta2/ + - release-notes/release-notes-7-5-0-beta1/ + - release-notes/release-notes-7-4-5/ + - release-notes/release-notes-7-4-3/ + - release-notes/release-notes-7-4-2/ + - release-notes/release-notes-7-4-1/ + - release-notes/release-notes-7-4-0/ + - release-notes/release-notes-7-3-10/ + - release-notes/release-notes-7-3-7/ + - release-notes/release-notes-7-3-6/ + - release-notes/release-notes-7-3-4/ + - release-notes/release-notes-7-3-3/ + - release-notes/release-notes-7-3-2/ + - release-notes/release-notes-7-3-1/ + - release-notes/release-notes-7-3-0/ description: Learn about new and updated features in Grafana. labels: products: @@ -69,7 +183,9 @@ weight: 1 For release highlights, deprecations, and breaking changes in Grafana releases, refer to these "What's new" pages for each version. {{< admonition type="note" >}} -For Grafana versions prior to v9.2, additional information might also be available in the archive of [release notes](../release-notes/). +For Grafana versions prior to v9.2, additional information might also be available in the archived release notes. To access archived release notes, use the documentation for the minor version you want to see. + +For example, to view the release notes for Grafana v8.5.13, go to https://grafana.com/docs/grafana/v8.5/release-notes/. {{< /admonition >}} For a complete list of every change, with links to pull requests and related issues when available, see the [Changelog](https://github.com/grafana/grafana/blob/main/CHANGELOG.md). From 247373ac41bcca720e46919b457726a4743914b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Fern=C3=A1ndez?= Date: Tue, 19 Aug 2025 18:43:01 +0200 Subject: [PATCH 25/26] Chore: Remove `Deprecated layout components` (#109872) --- .betterer.results | 15 +++------------ .../PanelEditor/DynamicConfigValueEditor.tsx | 6 +++--- .../components/PanelEditor/PanelEditor.tsx | 5 ++--- .../ValueMappingsEditor/ValueMappingEditRow.tsx | 14 +++++++------- .../transformers/editors/EnumMappingRow.tsx | 6 +++--- .../cells/SparklineCellOptionsEditor.tsx | 6 +++--- 6 files changed, 21 insertions(+), 31 deletions(-) diff --git a/.betterer.results b/.betterer.results index 292ea5f3c71..7712f1a83a9 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1965,8 +1965,7 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"] ], "public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"] + [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] ], "public/app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], @@ -1974,8 +1973,7 @@ exports[`better eslint`] = { [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "2"] ], "public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"] + [0, 0, 0, "Do not use any type assertions.", "0"] ], "public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], @@ -2280,9 +2278,6 @@ exports[`better eslint`] = { [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"] ], - "public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingEditRow.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "public/app/features/dimensions/scale.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], @@ -2792,9 +2787,6 @@ exports[`better eslint`] = { "public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], - "public/app/features/transformers/editors/EnumMappingRow.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "public/app/features/transformers/editors/GroupByTransformerEditor.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], @@ -3872,8 +3864,7 @@ exports[`better eslint`] = { [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] ], "public/app/plugins/panel/table/table-new/cells/SparklineCellOptionsEditor.tsx:5381": [ - [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"] + [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] ], "public/app/plugins/panel/table/table-new/cells/TextWrapOptionsEditor.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] diff --git a/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx b/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx index 1a3bc57a3e6..2ee3c1f7fe7 100644 --- a/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx @@ -10,7 +10,7 @@ import { GrafanaTheme2, } from '@grafana/data'; import { t } from '@grafana/i18n'; -import { Counter, Field, HorizontalGroup, IconButton, Label, useStyles2 } from '@grafana/ui'; +import { Counter, Field, Stack, IconButton, Label, useStyles2 } from '@grafana/ui'; import { OptionsPaneCategory } from './OptionsPaneCategory'; @@ -56,7 +56,7 @@ export const DynamicConfigValueEditor = ({ const renderLabel = (includeDescription = true, includeCounter = false) => (isExpanded = false) => ( - +
)} - + ); /* eslint-enable react/display-name */ diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx index 76c38e695d9..927246b0228 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx @@ -10,7 +10,6 @@ import { Trans, t } from '@grafana/i18n'; import { locationService } from '@grafana/runtime'; import { Button, - HorizontalGroup, InlineSwitch, ModalsController, RadioButtonGroup, @@ -302,7 +301,7 @@ export class PanelEditorUnconnected extends PureComponent { return (
- 0 ? 'space-between' : 'flex-end'} align="flex-start"> + 0 ? 'space-between' : 'flex-end'} alignItems="flex-start"> {this.renderTemplateVariables(styles)} { {!uiState.isPanelOptionsVisible && } - +
); } diff --git a/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingEditRow.tsx b/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingEditRow.tsx index 304de7d1960..14063d16f14 100644 --- a/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingEditRow.tsx +++ b/public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingEditRow.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import { GrafanaTheme2, MappingType, SpecialValueMatch, SelectableValue, ValueMappingResult } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; -import { useStyles2, Icon, Select, HorizontalGroup, ColorPicker, IconButton, Input, Button } from '@grafana/ui'; +import { useStyles2, Icon, Select, ColorPicker, IconButton, Input, Button, Stack } from '@grafana/ui'; import { ResourcePickerSize, ResourceFolderName, MediaType } from '../../types'; import { ResourcePicker } from '../ResourcePicker'; @@ -246,7 +246,7 @@ export function ValueMappingEditRow({ mapping, index, onChange, onRemove, onDupl {result.color && ( - + - + )} {!result.color && ( @@ -268,7 +268,7 @@ export function ValueMappingEditRow({ mapping, index, onChange, onRemove, onDupl {showIconPicker && ( - + )} - + )} - + onDuplicate(index)} @@ -311,7 +311,7 @@ export function ValueMappingEditRow({ mapping, index, onChange, onRemove, onDupl )} tooltip={t('dimensions.value-mapping-edit-row.remove-value-mapping-tooltip-delete', 'Delete')} /> - + )} diff --git a/public/app/features/transformers/editors/EnumMappingRow.tsx b/public/app/features/transformers/editors/EnumMappingRow.tsx index 2b6cb99614d..2e9041a830c 100644 --- a/public/app/features/transformers/editors/EnumMappingRow.tsx +++ b/public/app/features/transformers/editors/EnumMappingRow.tsx @@ -4,7 +4,7 @@ import { FormEvent, useState, KeyboardEvent, useRef, useEffect } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { t } from '@grafana/i18n'; -import { Icon, Input, IconButton, HorizontalGroup, FieldValidationMessage, useStyles2 } from '@grafana/ui'; +import { Icon, Input, IconButton, FieldValidationMessage, useStyles2, Stack } from '@grafana/ui'; type EnumMappingRowProps = { transformIndex: number; @@ -112,7 +112,7 @@ const EnumMappingRow = ({ )} - + - + )} diff --git a/public/app/plugins/panel/table/table-new/cells/SparklineCellOptionsEditor.tsx b/public/app/plugins/panel/table/table-new/cells/SparklineCellOptionsEditor.tsx index b85f2edf098..05683aeaf8b 100644 --- a/public/app/plugins/panel/table/table-new/cells/SparklineCellOptionsEditor.tsx +++ b/public/app/plugins/panel/table/table-new/cells/SparklineCellOptionsEditor.tsx @@ -3,7 +3,7 @@ import { useId, useMemo } from 'react'; import { createFieldConfigRegistry, SetFieldConfigOptionsArgs } from '@grafana/data'; import { GraphFieldConfig, TableSparklineCellOptions } from '@grafana/schema'; -import { VerticalGroup, Field, useStyles2 } from '@grafana/ui'; +import { Field, useStyles2, Stack } from '@grafana/ui'; import { defaultSparklineCellConfig } from '@grafana/ui/internal'; import { getGraphFieldConfig } from '../../../timeseries/config'; @@ -54,7 +54,7 @@ export const SparklineCellOptionsEditor = (props: TableCellEditorProps + {registry.list(optionIds.map((id) => `custom.${id}`)).map((item) => { if (item.showIf && !item.showIf(values)) { return null; @@ -74,7 +74,7 @@ export const SparklineCellOptionsEditor = (props: TableCellEditorProps ); })} - + ); }; From 0113f12c7d58f7bb8d628a905cf6bbffe894403d Mon Sep 17 00:00:00 2001 From: Matias Chomicki Date: Tue, 19 Aug 2025 19:23:18 +0200 Subject: [PATCH 26/26] New Logs Panel: Details and JSON adjustments (#109867) * processing: detect json logs to apply custom grammar * LogLineDetails: improve label column width * wip * Remove log * grammar: remove unused grammar * grammar: fix log grammar * LogLineDetails: improve margins * LogLineDetails: further fine tune width * Update tests * processing: more tests --- .../logs/components/panel/LogLineDetails.tsx | 2 +- .../components/panel/LogLineDetailsFields.tsx | 2 +- .../logs/components/panel/grammar.test.ts | 14 ++--- .../features/logs/components/panel/grammar.ts | 34 +++++++++++-- .../logs/components/panel/processing.test.ts | 51 ++++++++++++++++++- .../logs/components/panel/processing.ts | 25 +++++---- 6 files changed, 106 insertions(+), 22 deletions(-) diff --git a/public/app/features/logs/components/panel/LogLineDetails.tsx b/public/app/features/logs/components/panel/LogLineDetails.tsx index b2121c64d55..72d827b1ebe 100644 --- a/public/app/features/logs/components/panel/LogLineDetails.tsx +++ b/public/app/features/logs/components/panel/LogLineDetails.tsx @@ -176,7 +176,7 @@ const getStyles = (theme: GrafanaTheme2, mode: LogLineDetailsMode) => ({ inlineWrapper: css({ gridColumn: '1 / -1', height: `${LOG_LINE_DETAILS_HEIGHT}vh`, - paddingBottom: theme.spacing(0.5), + padding: theme.spacing(1, 2, 1.5, 2), marginRight: 1, }), container: css({ diff --git a/public/app/features/logs/components/panel/LogLineDetailsFields.tsx b/public/app/features/logs/components/panel/LogLineDetailsFields.tsx index f1f37695a16..e7a503b41c2 100644 --- a/public/app/features/logs/components/panel/LogLineDetailsFields.tsx +++ b/public/app/features/logs/components/panel/LogLineDetailsFields.tsx @@ -105,7 +105,7 @@ const getFieldsStyles = (theme: GrafanaTheme2) => ({ fieldsTable: css({ display: 'grid', gap: theme.spacing(1), - gridTemplateColumns: `${theme.spacing(11.5)} auto 1fr`, + gridTemplateColumns: `${theme.spacing(11.5)} minmax(auto, 40%) 1fr`, }), fieldsTableNoActions: css({ display: 'grid', diff --git a/public/app/features/logs/components/panel/grammar.test.ts b/public/app/features/logs/components/panel/grammar.test.ts index db218d73b5f..56c34220377 100644 --- a/public/app/features/logs/components/panel/grammar.test.ts +++ b/public/app/features/logs/components/panel/grammar.test.ts @@ -7,6 +7,8 @@ import { generateLogGrammar } from './grammar'; describe('generateLogGrammar', () => { function generateScenario(entry: string) { const log = createLogLine({ labels: { place: 'luna', source: 'logs' }, entry }); + // Access body getter to trigger LogLineModel internals + expect(log.body).toBeDefined(); const grammar = generateLogGrammar(log); const tokens = Prism.tokenize(log.entry, grammar); return { log, grammar, tokens }; @@ -29,7 +31,7 @@ describe('generateLogGrammar', () => { expect(tokens[1].type).toBe('log-token-json-key'); } if (tokens[3] instanceof Token) { - expect(tokens[3].content).toBe('"value"'); + expect(tokens[3].content).toEqual(['"value"']); expect(tokens[3].type).toBe('log-token-string'); } if (tokens[5] instanceof Token) { @@ -37,10 +39,10 @@ describe('generateLogGrammar', () => { expect(tokens[5].type).toBe('log-token-json-key'); } if (tokens[7] instanceof Token) { - expect(tokens[7].content).toBe('"value2"'); + expect(tokens[7].content).toEqual(['"value2"']); expect(tokens[7].type).toBe('log-token-string'); } - expect.assertions(8); + expect.assertions(9); }); test('Identifies sizes', () => { @@ -53,7 +55,7 @@ describe('generateLogGrammar', () => { expect(tokens[2].content).toBe('2 KB'); expect(tokens[2].type).toBe('log-token-size'); } - expect.assertions(4); + expect.assertions(5); }); test('Identifies durations', () => { @@ -70,7 +72,7 @@ describe('generateLogGrammar', () => { expect(tokens[4].content).toBe('1h'); expect(tokens[4].type).toBe('log-token-duration'); } - expect.assertions(6); + expect.assertions(7); }); test.each(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS', 'TRACE', 'CONNECT'])( @@ -81,7 +83,7 @@ describe('generateLogGrammar', () => { expect(tokens[1].content).toBe(method); expect(tokens[1].type).toBe('log-token-method'); } - expect.assertions(2); + expect.assertions(3); } ); }); diff --git a/public/app/features/logs/components/panel/grammar.ts b/public/app/features/logs/components/panel/grammar.ts index 3ce047bf33e..d1f42f33197 100644 --- a/public/app/features/logs/components/panel/grammar.ts +++ b/public/app/features/logs/components/panel/grammar.ts @@ -5,14 +5,33 @@ import { escapeRegex, parseFlags } from '@grafana/data'; import { LogListModel } from './processing'; // The Logs grammar is used for highlight in the logs panel -export const logsGrammar: Grammar = { - 'log-token-uuid': /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}/g, - 'log-token-json-key': /"(\b|\B)[\w-]+"(?=\s*:)/gi, +const logsGrammar: Grammar = { 'log-token-key': /(\b|\B)[\w_]+(?=\s*=)/gi, + 'log-token-string': /"(?!:)([^'"])*?"(?!:)/g, +}; + +const tokensGrammar: Grammar = { + 'log-token-uuid': /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}/g, 'log-token-size': /(?:\b|")\d+\.{0,1}\d*\s*[kKmMGgtTPp]*[bB]{1}(?:"|\b)/g, 'log-token-duration': /(?:\b)\d+(\.\d+)?(ns|µs|ms|s|m|h|d)(?:\b)/g, 'log-token-method': /\b(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|TRACE|CONNECT)\b/g, - 'log-token-string': /"(?!:)([^'"])*?"(?!:)/g, +}; + +const jsonGrammar: Grammar = { + 'log-token-json-key': { + pattern: /(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?=\s*:)/, + lookbehind: true, + greedy: true, + }, + 'log-token-string': { + pattern: /(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?!\s*:)/, + lookbehind: true, + greedy: true, + inside: { + ...tokensGrammar, + }, + }, + 'log-token-size': /-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i, }; export const generateLogGrammar = (log: LogListModel) => { @@ -20,8 +39,15 @@ export const generateLogGrammar = (log: LogListModel) => { const logGrammar: Grammar = { 'log-token-label': new RegExp(`\\b(${labels.join('|')})(?:[=:]{1})\\b`, 'g'), }; + if (log.isJSON) { + return { + ...logGrammar, + ...jsonGrammar, + }; + } return { ...logGrammar, + ...tokensGrammar, ...logsGrammar, }; }; diff --git a/public/app/features/logs/components/panel/processing.test.ts b/public/app/features/logs/components/panel/processing.test.ts index 32b2d40b0f0..4b8c42dcf15 100644 --- a/public/app/features/logs/components/panel/processing.test.ts +++ b/public/app/features/logs/components/panel/processing.test.ts @@ -142,7 +142,7 @@ describe('preProcessLogs', () => { expect(logListModel.getDisplayedFieldValue(LOG_LINE_BODY_FIELD_NAME, true)).toBe('log message 1'); }); - test('Prettifies JSON', () => { + test('Does not modify unwrapped JSON', () => { const entry = '{"key": "value", "otherKey": "otherValue"}'; const logListModel = createLogLine( { entry }, @@ -154,6 +154,21 @@ describe('preProcessLogs', () => { } ); expect(logListModel.entry).toBe(entry); + expect(logListModel.body).toBe(entry); + }); + + test('Prettifies wrapped JSON', () => { + const entry = '{"key": "value", "otherKey": "otherValue"}'; + const logListModel = createLogLine( + { entry }, + { + escape: false, + order: LogsSortOrder.Descending, + timeZone: 'browser', + wrapLogMessage: true, // wrapped + } + ); + expect(logListModel.entry).toBe(entry); expect(logListModel.body).not.toBe(entry); }); @@ -171,6 +186,40 @@ describe('preProcessLogs', () => { expect(logListModel.entry).toBe(entry); expect(logListModel.body).toContain('90071992547409911'); }); + + test.each([ + '{"timestamp":"2025-08-19T12:34:56Z","level":"INFO","message":"User logged in","user_id":1234}', + '{"time":"2025-08-19T12:35:10Z","level":"ERROR","service":"payment","error":"Insufficient funds","transaction_id":"tx-98765"}', + '{"ts":1692444912,"lvl":"WARN","component":"auth","msg":"Token expired","session_id":"abcd1234"}', + '{"@timestamp":"2025-08-19T12:36:00Z","severity":"DEBUG","event":"cache_hit","key":"user_profile:1234","duration_ms":3}', + '{}', + ])('Detects JSON logs', (entry: string) => { + const logListModel = createLogLine( + { entry }, + { + escape: false, + order: LogsSortOrder.Descending, + timeZone: 'browser', + wrapLogMessage: false, + } + ); + expect(logListModel.body).toBeDefined(); // Triggers parsing + expect(logListModel.isJSON).toBe(true); + }); + + test.each(['1', '"1"', 'true', 'null', 'false', 'not json', '"nope"'])('Detects non-JSON logs', (entry: string) => { + const logListModel = createLogLine( + { entry }, + { + escape: false, + order: LogsSortOrder.Descending, + timeZone: 'browser', + wrapLogMessage: false, + } + ); + expect(logListModel.body).toBeDefined(); // Triggers parsing + expect(logListModel.isJSON).toBe(false); + }); }); test('Orders logs', () => { diff --git a/public/app/features/logs/components/panel/processing.ts b/public/app/features/logs/components/panel/processing.ts index 582cfa07d90..3d9a6853701 100644 --- a/public/app/features/logs/components/panel/processing.ts +++ b/public/app/features/logs/components/panel/processing.ts @@ -1,5 +1,5 @@ import ansicolor from 'ansicolor'; -import { parse, stringify } from 'lossless-json'; +import { LosslessNumber, parse, stringify } from 'lossless-json'; import Prism, { Grammar } from 'prismjs'; import { @@ -63,6 +63,7 @@ export class LogListModel implements LogRowModel { private _getFieldLinks: GetFieldLinksFn | undefined = undefined; private _virtualization?: LogLineVirtualization; private _wrapLogMessage: boolean; + private _json = false; constructor( log: LogRowModel, @@ -124,9 +125,13 @@ export class LogListModel implements LogRowModel { get body(): string { if (this._body === undefined) { try { - const parsed = stringify(parse(this.raw), undefined, this._wrapLogMessage ? 2 : 1); - if (parsed) { - this.raw = parsed; + const parsed = parse(this.raw); + if (typeof parsed === 'object' && parsed !== null && !(parsed instanceof LosslessNumber)) { + this._json = true; + } + const reStringified = this._wrapLogMessage ? stringify(parsed, undefined, 2) : this.raw; + if (reStringified) { + this.raw = reStringified; } } catch (error) {} const raw = config.featureToggles.otelLogsFormatting && this.otelLanguage ? getOtelFormattedBody(this) : this.raw; @@ -153,17 +158,19 @@ export class LogListModel implements LogRowModel { get highlightedBody() { if (this._highlightedBody === undefined) { + // Body is accessed first to trigger the getter code before generateLogGrammar() + const sanitizedBody = textUtil.sanitize(this.body); this._grammar = this._grammar ?? generateLogGrammar(this); const extraGrammar = generateTextMatchGrammar(this.searchWords, this._currentSearch); - this._highlightedBody = Prism.highlight( - textUtil.sanitize(this.body), - { ...extraGrammar, ...this._grammar }, - 'lokiql' - ); + this._highlightedBody = Prism.highlight(sanitizedBody, { ...extraGrammar, ...this._grammar }, 'lokiql'); } return this._highlightedBody; } + get isJSON() { + return this._json; + } + get sampledMessage(): string | undefined { return checkLogsSampled(this); }