From 4c75ea918a5e7fdbf775beb583cd01c134768a41 Mon Sep 17 00:00:00 2001 From: Ihor Yeromin Date: Tue, 18 Nov 2025 10:01:45 +0100 Subject: [PATCH] Transformations: Wrap sql expression tile under FF (#114030) * Transformations: Add empty state placeholder with SQL expression and transformation cards - Extract TransformationCard and SqlExpressionCard into separate reusable components - Add new empty transformations placeholder UI behind transformationsEmptyPlaceholder feature toggle - Add auto-scroll to SQL expression when navigating from transformations tab - Add analytics tracking for transformation interactions - Split EmptyTransformationsMessage into Legacy and New variants for easier toggle removal * add tests --- .../EmptyTransformationsMessage.test.tsx | 59 ++++++++++++++----- .../EmptyTransformationsMessage.tsx | 10 +++- .../PanelDataTransformationsTab.test.tsx | 30 +++++++++- 3 files changed, 78 insertions(+), 21 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 4dab09c2d75..a506f923730 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 @@ -3,13 +3,10 @@ import userEvent from '@testing-library/user-event'; import { standardTransformersRegistry } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; +import config from 'app/core/config'; import { getStandardTransformers } from 'app/features/transformers/standardTransformers'; -import { LegacyEmptyTransformationsMessage, NewEmptyTransformationsMessage } from './EmptyTransformationsMessage'; - -jest.mock('@grafana/runtime', () => ({ - ...jest.requireActual('@grafana/runtime'), -})); +import { EmptyTransformationsMessage, LegacyEmptyTransformationsMessage } from './EmptyTransformationsMessage'; describe('EmptyTransformationsMessage', () => { standardTransformersRegistry.setInit(getStandardTransformers); @@ -20,6 +17,11 @@ describe('EmptyTransformationsMessage', () => { beforeEach(() => { jest.clearAllMocks(); + + // Set up feature toggles + config.featureToggles = config.featureToggles || {}; + config.featureToggles.transformationsEmptyPlaceholder = false; + config.featureToggles.sqlExpressions = true; }); describe('LegacyEmptyTransformationsMessage', () => { @@ -41,10 +43,16 @@ describe('EmptyTransformationsMessage', () => { }); }); - describe('NewEmptyTransformationsMessage', () => { - it('should render transformation cards when both onGoToQueries and onAddTransformation are provided', () => { + describe('EmptyTransformationsMessage (new UI)', () => { + beforeEach(() => { + config.featureToggles.transformationsEmptyPlaceholder = true; + }); + + it('should render SQL expression card and transformation cards when sqlExpressions toggle is enabled', () => { + config.featureToggles.sqlExpressions = true; + render( - { expect(screen.getByText('Filter data by values')).toBeInTheDocument(); }); - it('should call onShowPicker when "Show more" button is clicked', async () => { - const user = userEvent.setup(); + it('should not show SQL expression card when sqlExpressions toggle is disabled', () => { + config.featureToggles.sqlExpressions = false; + render( - ); - const button = screen.getByTestId(selectors.components.Transforms.addTransformationButton); - await user.click(button); + expect(screen.queryByText('SQL Expressions')).not.toBeInTheDocument(); + // But should still show transformation cards + expect(screen.getByText('Organize fields by name')).toBeInTheDocument(); + }); - expect(onShowPicker).toHaveBeenCalledTimes(1); + it('should call onGoToQueries when SQL expression card is clicked', async () => { + config.featureToggles.sqlExpressions = true; + 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 SQL transformation card when onGoToQueries is not provided', () => { - render(); + render(); expect(screen.queryByText('SQL Expressions')).not.toBeInTheDocument(); }); it('should not show transformation cards grid when neither onGoToQueries nor onAddTransformation are provided', () => { - render(); + render(); expect(screen.queryByText('SQL Expressions')).not.toBeInTheDocument(); diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/EmptyTransformationsMessage.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/EmptyTransformationsMessage.tsx index c5b069e577a..841076ef910 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/EmptyTransformationsMessage.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/EmptyTransformationsMessage.tsx @@ -25,6 +25,9 @@ const TRANSFORMATION_IDS = [ DataTransformerID.filterByValue, ]; +const GRID_COLUMNS_WITH_SQL = 5; +const GRID_COLUMNS_WITHOUT_SQL = 4; + export function LegacyEmptyTransformationsMessage({ onShowPicker }: { onShowPicker: () => void }) { return ( @@ -89,12 +92,15 @@ export function NewEmptyTransformationsMessage(props: EmptyTransformationsProps) props.onShowPicker(); }; + const showSqlCard = hasGoToQueries && config.featureToggles.sqlExpressions; + const gridColumns = showSqlCard ? GRID_COLUMNS_WITH_SQL : GRID_COLUMNS_WITHOUT_SQL; + return ( {(hasAddTransformation || hasGoToQueries) && ( - - {hasGoToQueries && ( + + {showSqlCard && ( { expect(reduce).toBeNull(); }); - it('renders SQL transformation card in empty state when feature toggle is enabled', async () => { - const originalFeatureToggle = config.featureToggles.transformationsEmptyPlaceholder; + it('renders SQL transformation card in empty state when feature toggles are enabled', async () => { + const originalTransformationsToggle = config.featureToggles.transformationsEmptyPlaceholder; + const originalSqlToggle = config.featureToggles.sqlExpressions; + config.featureToggles.transformationsEmptyPlaceholder = true; + config.featureToggles.sqlExpressions = true; try { const modelMock = createModelMock(mockData); @@ -179,7 +182,28 @@ describe('PanelDataTransformationsTab', () => { expect(screen.getByText('SQL Expressions')).toBeInTheDocument(); expect(screen.getByTestId('go-to-queries-button')).toBeInTheDocument(); } finally { - config.featureToggles.transformationsEmptyPlaceholder = originalFeatureToggle; + config.featureToggles.transformationsEmptyPlaceholder = originalTransformationsToggle; + config.featureToggles.sqlExpressions = originalSqlToggle; + } + }); + + it('does not render SQL transformation card when sqlExpressions toggle is disabled', async () => { + const originalTransformationsToggle = config.featureToggles.transformationsEmptyPlaceholder; + const originalSqlToggle = config.featureToggles.sqlExpressions; + + config.featureToggles.transformationsEmptyPlaceholder = true; + config.featureToggles.sqlExpressions = false; + + try { + const modelMock = createModelMock(mockData); + render(); + + // Should not show SQL transformation card + expect(screen.queryByText('SQL Expressions')).not.toBeInTheDocument(); + expect(screen.queryByTestId('go-to-queries-button')).not.toBeInTheDocument(); + } finally { + config.featureToggles.transformationsEmptyPlaceholder = originalTransformationsToggle; + config.featureToggles.sqlExpressions = originalSqlToggle; } }); });