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
This commit is contained in:
Ihor Yeromin
2025-11-18 10:01:45 +01:00
committed by GitHub
parent a553256b46
commit 4c75ea918a
3 changed files with 78 additions and 21 deletions
@@ -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(
<NewEmptyTransformationsMessage
<EmptyTransformationsMessage
onShowPicker={onShowPicker}
onGoToQueries={onGoToQueries}
onAddTransformation={onAddTransformation}
@@ -59,30 +67,49 @@ describe('EmptyTransformationsMessage', () => {
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(
<NewEmptyTransformationsMessage
<EmptyTransformationsMessage
onShowPicker={onShowPicker}
onGoToQueries={onGoToQueries}
onAddTransformation={onAddTransformation}
/>
);
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(
<EmptyTransformationsMessage
onShowPicker={onShowPicker}
onGoToQueries={onGoToQueries}
onAddTransformation={onAddTransformation}
/>
);
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(<NewEmptyTransformationsMessage onShowPicker={onShowPicker} onAddTransformation={onAddTransformation} />);
render(<EmptyTransformationsMessage onShowPicker={onShowPicker} onAddTransformation={onAddTransformation} />);
expect(screen.queryByText('SQL Expressions')).not.toBeInTheDocument();
});
it('should not show transformation cards grid when neither onGoToQueries nor onAddTransformation are provided', () => {
render(<NewEmptyTransformationsMessage onShowPicker={onShowPicker} />);
render(<EmptyTransformationsMessage onShowPicker={onShowPicker} />);
expect(screen.queryByText('SQL Expressions')).not.toBeInTheDocument();
@@ -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 (
<Box alignItems="center" padding={4}>
@@ -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 (
<Box alignItems="center" padding={4}>
<Stack direction="column" alignItems="center" gap={4}>
{(hasAddTransformation || hasGoToQueries) && (
<Grid columns={5} gap={1}>
{hasGoToQueries && (
<Grid columns={gridColumns} gap={1}>
{showSqlCard && (
<SqlExpressionCard
name={t('dashboard-scene.empty-transformations-message.sql-name', 'SQL Expressions')}
description={t(
@@ -167,9 +167,12 @@ describe('PanelDataTransformationsTab', () => {
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(<PanelDataTransformationsTabRendered model={modelMock}></PanelDataTransformationsTabRendered>);
// 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;
}
});
});