From af9a0d3598852ba7a9e615e7c029c98e80544b9f Mon Sep 17 00:00:00 2001 From: Develer Date: Tue, 16 Dec 2025 20:01:16 +0100 Subject: [PATCH] add tests --- .../EmptyTransformationsMessage.test.tsx | 98 ++++++++++ .../PanelDataTransformationsTab.test.tsx | 175 +++++++++++++++++- 2 files changed, 271 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/EmptyTransformationsMessage.test.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/EmptyTransformationsMessage.test.tsx index 7f7af75146b..b23726c0288 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/EmptyTransformationsMessage.test.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/EmptyTransformationsMessage.test.tsx @@ -117,4 +117,102 @@ describe('EmptyTransformationsMessage', () => { expect(screen.getByTestId(selectors.components.Transforms.addTransformationButton)).toBeInTheDocument(); }); }); + + describe('SQL card disabled state', () => { + beforeEach(() => { + config.featureToggles.transformationsEmptyPlaceholder = true; + config.featureToggles.sqlExpressions = true; + }); + + // Helper to check if the info icon button is present (rendered when disabled) + const getInfoIconButton = (container: HTMLElement) => { + // The IconButton for info renders as a button with an SVG icon + // When disabled, there are 2 buttons: the card button and the info icon button + const buttons = container.querySelectorAll('button'); + return buttons.length > 1 ? buttons[1] : null; + }; + + it('should show disabled SQL card with info icon when isSqlApplicable is false', () => { + render( + + ); + + const sqlCard = screen.getByTestId('go-to-queries-button'); + // Should show info icon button when disabled (2 buttons total) + expect(getInfoIconButton(sqlCard)).toBeInTheDocument(); + }); + + it('should not call onGoToQueries when SQL card is disabled and clicked', async () => { + const user = userEvent.setup(); + + render( + + ); + + const sqlCard = screen.getByTestId('go-to-queries-button'); + const button = sqlCard.querySelector('button'); + await user.click(button!); + + // onGoToQueries should NOT be called when disabled + expect(onGoToQueries).not.toHaveBeenCalled(); + }); + + it('should call onGoToQueries when SQL card is enabled and clicked', async () => { + const user = userEvent.setup(); + + render( + + ); + + const sqlCard = screen.getByTestId('go-to-queries-button'); + const button = sqlCard.querySelector('button'); + await user.click(button!); + + expect(onGoToQueries).toHaveBeenCalledTimes(1); + }); + + it('should not show info icon when SQL card is enabled', () => { + render( + + ); + + const sqlCard = screen.getByTestId('go-to-queries-button'); + // Should NOT show info icon button when enabled (only 1 button) + expect(getInfoIconButton(sqlCard)).toBeNull(); + }); + + it('should default to enabled when isSqlApplicable is not provided', () => { + render( + + ); + + const sqlCard = screen.getByTestId('go-to-queries-button'); + // Should NOT show info icon button when default (enabled) + expect(getInfoIconButton(sqlCard)).toBeNull(); + }); + }); }); diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.test.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.test.tsx index d6aa23f6073..307889d5439 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.test.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.test.tsx @@ -11,9 +11,11 @@ import { toDataFrame, } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import { DataSourceSrv, getDataSourceSrv } from '@grafana/runtime'; import { SceneDataTransformer, SceneQueryRunner } from '@grafana/scenes'; import config from 'app/core/config'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; +import { ExpressionDatasourceUID } from 'app/features/expressions/types'; import { getStandardTransformers } from 'app/features/transformers/standardTransformers'; import { DashboardDataDTO } from 'app/types/dashboard'; @@ -24,14 +26,40 @@ import { testDashboard } from '../testfiles/testDashboard'; import { PanelDataTransformationsTab, PanelDataTransformationsTabRendered } from './PanelDataTransformationsTab'; +// Mock getDataSourceSrv +jest.mock('@grafana/runtime', () => { + const actual = jest.requireActual('@grafana/runtime'); + return { + ...actual, + getDataSourceSrv: jest.fn(() => ({ + getInstanceSettings: jest.fn(), + })), + }; +}); + +const getDataSourceSrvMock = getDataSourceSrv as jest.MockedFunction; + +// Helper to create DataSourceSrv mock with custom getInstanceSettings +const createMockDataSourceSrv = ( + getInstanceSettingsFn: (ref: { uid?: string; type?: string } | undefined) => unknown +): DataSourceSrv => + ({ + get: jest.fn(), + getList: jest.fn(), + getInstanceSettings: getInstanceSettingsFn, + reload: jest.fn(), + registerRuntimeDataSource: jest.fn(), + }) as unknown as DataSourceSrv; + function createModelMock( panelData: PanelData, transformations?: DataTransformerConfig[], - onChangeTransformationsMock?: Function + onChangeTransformationsMock?: Function, + queries: Array<{ refId: string; datasource?: { uid?: string; type?: string } }> = [] ) { return { getDataTransformer: () => new SceneDataTransformer({ data: panelData, transformations: transformations || [] }), - getQueryRunner: () => new SceneQueryRunner({ queries: [], data: panelData }), + getQueryRunner: () => new SceneQueryRunner({ queries, data: panelData }), onChangeTransformations: onChangeTransformationsMock, } as unknown as PanelDataTransformationsTab; } @@ -208,6 +236,149 @@ describe('PanelDataTransformationsTab', () => { }); }); +describe('SQL Expression applicability', () => { + standardTransformersRegistry.setInit(getStandardTransformers); + + let originalTransformationsToggle: boolean | undefined; + let originalSqlToggle: boolean | undefined; + + beforeEach(() => { + originalTransformationsToggle = config.featureToggles.transformationsEmptyPlaceholder; + originalSqlToggle = config.featureToggles.sqlExpressions; + config.featureToggles.transformationsEmptyPlaceholder = true; + config.featureToggles.sqlExpressions = true; + }); + + afterEach(() => { + config.featureToggles.transformationsEmptyPlaceholder = originalTransformationsToggle; + config.featureToggles.sqlExpressions = originalSqlToggle; + jest.clearAllMocks(); + }); + + // Helper to check if the info icon button is present (rendered when disabled) + // The IconButton for info renders as a button with an SVG icon + // When disabled, there are 2 buttons: the card button and the info icon button + const getInfoIconButton = (container: HTMLElement) => { + const buttons = container.querySelectorAll('button'); + return buttons.length > 1 ? buttons[1] : null; + }; + + it('should enable SQL card when datasource is a backend datasource', async () => { + getDataSourceSrvMock.mockReturnValue( + createMockDataSourceSrv(() => ({ + uid: 'prometheus', + name: 'Prometheus', + meta: { backend: true }, + })) + ); + + const modelMock = createModelMock(mockData, [], undefined, [ + { refId: 'A', datasource: { uid: 'prometheus', type: 'prometheus' } }, + ]); + + render(); + + const sqlCard = await screen.findByTestId('go-to-queries-button'); + // Card should NOT have disabled background styling - check it's clickable + expect(sqlCard).toBeInTheDocument(); + // The card should not show the disabled info icon button + expect(getInfoIconButton(sqlCard)).toBeNull(); + }); + + it('should disable SQL card when datasource is frontend-only', async () => { + getDataSourceSrvMock.mockReturnValue( + createMockDataSourceSrv(() => ({ + uid: 'googlesheets', + name: 'Google Sheets', + meta: { backend: false, isBackend: false }, + })) + ); + + const modelMock = createModelMock(mockData, [], undefined, [ + { refId: 'A', datasource: { uid: 'googlesheets', type: 'grafana-googlesheets-datasource' } }, + ]); + + render(); + + const sqlCard = await screen.findByTestId('go-to-queries-button'); + // Card should show the disabled info icon button + expect(getInfoIconButton(sqlCard)).toBeInTheDocument(); + }); + + it('should enable SQL card when datasource settings cannot be found', async () => { + // Return undefined for getInstanceSettings - simulating unknown datasource + getDataSourceSrvMock.mockReturnValue(createMockDataSourceSrv(() => undefined)); + + const modelMock = createModelMock(mockData, [], undefined, [ + { refId: 'A', datasource: { uid: 'unknown-ds', type: 'unknown' } }, + ]); + + render(); + + const sqlCard = await screen.findByTestId('go-to-queries-button'); + // Card should NOT be disabled when we can't determine datasource type + expect(getInfoIconButton(sqlCard)).toBeNull(); + }); + + it('should skip expression queries when checking SQL applicability', async () => { + getDataSourceSrvMock.mockReturnValue( + createMockDataSourceSrv((ref) => { + if (ref?.uid === ExpressionDatasourceUID) { + // Expression datasource - this should be skipped + return { uid: ExpressionDatasourceUID, name: 'Expression', meta: { backend: false } }; + } + // Backend datasource + return { uid: 'prometheus', name: 'Prometheus', meta: { backend: true } }; + }) + ); + + const modelMock = createModelMock(mockData, [], undefined, [ + { refId: 'A', datasource: { uid: 'prometheus', type: 'prometheus' } }, + { refId: 'B', datasource: { uid: ExpressionDatasourceUID, type: '__expr__' } }, // Expression query + ]); + + render(); + + const sqlCard = await screen.findByTestId('go-to-queries-button'); + // Should still be enabled because expression queries are skipped + expect(getInfoIconButton(sqlCard)).toBeNull(); + }); + + it('should disable SQL card if any non-expression query uses frontend-only datasource', async () => { + getDataSourceSrvMock.mockReturnValue( + createMockDataSourceSrv((ref) => { + if (ref?.uid === 'googlesheets') { + return { uid: 'googlesheets', name: 'Google Sheets', meta: { backend: false, isBackend: false } }; + } + return { uid: 'prometheus', name: 'Prometheus', meta: { backend: true } }; + }) + ); + + const modelMock = createModelMock(mockData, [], undefined, [ + { refId: 'A', datasource: { uid: 'prometheus', type: 'prometheus' } }, + { refId: 'B', datasource: { uid: 'googlesheets', type: 'grafana-googlesheets-datasource' } }, + ]); + + render(); + + const sqlCard = await screen.findByTestId('go-to-queries-button'); + // Card should be disabled because one datasource is frontend-only + expect(getInfoIconButton(sqlCard)).toBeInTheDocument(); + }); + + it('should enable SQL card when there are no queries', async () => { + getDataSourceSrvMock.mockReturnValue(createMockDataSourceSrv(() => undefined)); + + const modelMock = createModelMock(mockData, [], undefined, []); + + render(); + + const sqlCard = await screen.findByTestId('go-to-queries-button'); + // Card should be enabled when there are no queries + expect(getInfoIconButton(sqlCard)).toBeNull(); + }); +}); + function setupTabScene(panelId: string) { const scene = transformSaveModelToScene({ dashboard: testDashboard as unknown as DashboardDataDTO, meta: {} }); const panel = findVizPanelByKey(scene, panelId)!;