Transformations: feature toggle cleanup transformations redesign (#113561)

* remove feature toggle transformationsRedesign

* remove for event tracking

* remove uneccessary code
This commit is contained in:
Brendan O'Handley
2025-11-19 10:54:05 -06:00
committed by GitHub
parent 20a8987ff1
commit 77d98fc73d
10 changed files with 85 additions and 196 deletions
@@ -34,7 +34,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
| `unifiedRequestLog` | Writes error logs to the request logger | Yes |
| `logsExploreTableVisualisation` | A table visualisation for logs in Explore | Yes |
| `awsDatasourcesTempCredentials` | Support temporary security credentials in AWS plugins for Grafana Cloud customers | Yes |
| `transformationsRedesign` | Enables the transformations redesign | Yes |
| `awsAsyncQueryCaching` | Enable caching for async queries for Redshift and Athena. Requires that the datasource has caching and async query support enabled | Yes |
| `dashgpt` | Enable AI powered features in dashboards | Yes |
| `kubernetesDashboards` | Use the kubernetes API in the frontend for dashboards | Yes |
-5
View File
@@ -161,11 +161,6 @@ export interface FeatureToggles {
*/
awsDatasourcesTempCredentials?: boolean;
/**
* Enables the transformations redesign
* @default true
*/
transformationsRedesign?: boolean;
/**
* Enable support for Machine Learning in server-side expressions
*/
mlExpressions?: boolean;
-9
View File
@@ -255,15 +255,6 @@ var (
Expression: "true", // enabled by default
Owner: awsDatasourcesSquad,
},
{
Name: "transformationsRedesign",
Description: "Enables the transformations redesign",
Stage: FeatureStageGeneralAvailability,
FrontendOnly: true,
Expression: "true", // enabled by default
Owner: grafanaObservabilityMetricsSquad,
AllowSelfServe: true,
},
{
Name: "mlExpressions",
Description: "Enable support for Machine Learning in server-side expressions",
-1
View File
@@ -32,7 +32,6 @@ enableDatagridEditing,preview,@grafana/dataviz-squad,false,false,true
extraThemes,experimental,@grafana/grafana-frontend-platform,false,false,true
logsExploreTableVisualisation,GA,@grafana/observability-logs,false,false,true
awsDatasourcesTempCredentials,GA,@grafana/aws-datasources,false,false,false
transformationsRedesign,GA,@grafana/observability-metrics,false,false,true
mlExpressions,experimental,@grafana/alerting-squad,false,false,false
datasourceAPIServers,experimental,@grafana/grafana-app-platform-squad,false,true,false
grafanaAPIServerWithExperimentalAPIs,experimental,@grafana/grafana-app-platform-squad,true,true,false
1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
32 extraThemes experimental @grafana/grafana-frontend-platform false false true
33 logsExploreTableVisualisation GA @grafana/observability-logs false false true
34 awsDatasourcesTempCredentials GA @grafana/aws-datasources false false false
transformationsRedesign GA @grafana/observability-metrics false false true
35 mlExpressions experimental @grafana/alerting-squad false false false
36 datasourceAPIServers experimental @grafana/grafana-app-platform-squad false true false
37 grafanaAPIServerWithExperimentalAPIs experimental @grafana/grafana-app-platform-squad true true false
-4
View File
@@ -139,10 +139,6 @@ const (
// Support temporary security credentials in AWS plugins for Grafana Cloud customers
FlagAwsDatasourcesTempCredentials = "awsDatasourcesTempCredentials"
// FlagTransformationsRedesign
// Enables the transformations redesign
FlagTransformationsRedesign = "transformationsRedesign"
// FlagMlExpressions
// Enable support for Machine Learning in server-side expressions
FlagMlExpressions = "mlExpressions"
+2 -1
View File
@@ -4147,7 +4147,8 @@
"metadata": {
"name": "transformationsRedesign",
"resourceVersion": "1753448760331",
"creationTimestamp": "2023-07-12T16:35:49Z"
"creationTimestamp": "2023-07-12T16:35:49Z",
"deletionTimestamp": "2025-11-06T20:37:15Z"
},
"spec": {
"description": "Enables the transformations redesign",
@@ -29,10 +29,7 @@ export const PanelEditorTabs = memo(({ panel, dashboard, tabs, onChangeTab }: Pa
const instrumentedOnChangeTab = useCallback(
(tab: PanelEditorTab) => {
let eventName = 'panel_editor_tabs_changed';
if (config.featureToggles.transformationsRedesign) {
eventName = 'transformations_redesign_' + eventName;
}
let eventName = 'transformations_redesign_panel_editor_tabs_changed';
if (!tab.active) {
reportInteraction(eventName, { tab_id: tab.id });
@@ -20,7 +20,6 @@ import {
QueryOperationToggleAction,
} from 'app/core/components/QueryOperationRow/QueryOperationAction';
import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow';
import config from 'app/core/config';
import { PluginStateInfo } from 'app/features/plugins/components/PluginStateInfo';
import { TransformationEditor } from './TransformationEditor';
@@ -89,10 +88,7 @@ export const TransformationOperationRow = ({
const instrumentToggleCallback = useCallback(
(callback: (e: React.MouseEvent) => void, toggleId: string, active: boolean | undefined) =>
(e: React.MouseEvent) => {
let eventName = 'panel_editor_tabs_transformations_toggle';
if (config.featureToggles.transformationsRedesign) {
eventName = 'transformations_redesign_' + eventName;
}
let eventName = 'transformations_redesign_panel_editor_tabs_transformations_toggle';
reportInteraction(eventName, {
action: active ? 'off' : 'on',
@@ -198,27 +194,25 @@ export const TransformationOperationRow = ({
<QueryOperationAction
title={t('dashboard.transformation-operation-row.render-actions.title-remove', 'Remove')}
icon="trash-alt"
onClick={() => (config.featureToggles.transformationsRedesign ? setShowDeleteModal(true) : onRemove(index))}
onClick={() => setShowDeleteModal(true)}
/>
{config.featureToggles.transformationsRedesign && (
<ConfirmModal
isOpen={showDeleteModal}
title={t('dashboard.transformation-operation-row.title-delete', 'Delete {{name}}?', {
name: uiConfig.name,
})}
body={t(
'dashboard.transformation-operation-row.body-delete',
'Note that removing one transformation may break others. If there is only a single transformation, you will go back to the main selection screen.'
)}
confirmText={t('dashboard.transformation-operation-row.render-actions.confirmText-delete', 'Delete')}
onConfirm={() => {
setShowDeleteModal(false);
onRemove(index);
}}
onDismiss={() => setShowDeleteModal(false)}
/>
)}
<ConfirmModal
isOpen={showDeleteModal}
title={t('dashboard.transformation-operation-row.title-delete', 'Delete {{name}}?', {
name: uiConfig.name,
})}
body={t(
'dashboard.transformation-operation-row.body-delete',
'Note that removing one transformation may break others. If there is only a single transformation, you will go back to the main selection screen.'
)}
confirmText={t('dashboard.transformation-operation-row.render-actions.confirmText-delete', 'Delete')}
onConfirm={() => {
setShowDeleteModal(false);
onRemove(index);
}}
onDismiss={() => setShowDeleteModal(false)}
/>
</>
);
};
@@ -3,7 +3,6 @@ import userEvent from '@testing-library/user-event';
import { DataTransformerConfig, standardTransformersRegistry } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import config from 'app/core/config';
import { getStandardTransformers } from 'app/features/transformers/standardTransformers';
import { PanelModel } from '../../state/PanelModel';
@@ -20,23 +19,15 @@ describe('TransformationsEditor', () => {
standardTransformersRegistry.setInit(getStandardTransformers);
describe('when no transformations configured', () => {
it('renders transformation list by default and without transformationsRedesign on', () => {
setup();
const cards = screen.getAllByTestId(/New transform/i);
expect(cards.length).toEqual(standardTransformersRegistry.list().length);
});
it('renders transformation empty message with transformationsRedesign feature toggled on', () => {
config.featureToggles.transformationsRedesign = true;
it('renders transformation empty message', () => {
setup();
const message = screen.getAllByTestId('data-testid no transformations message');
expect(message.length).toEqual(1);
config.featureToggles.transformationsRedesign = false;
});
});
describe('when transformations configured', () => {
function renderEditors() {
it('renders transformation editors', () => {
setup([
{
id: 'reduce',
@@ -45,18 +36,11 @@ describe('TransformationsEditor', () => {
]);
const editors = screen.getAllByTestId(/Transformation editor/);
expect(editors).toHaveLength(1);
}
it('renders transformation editors', renderEditors);
it('renders transformation editors with transformationsRedesign feature toggled on', () => {
config.featureToggles.transformationsRedesign = true;
renderEditors();
config.featureToggles.transformationsRedesign = false;
});
});
describe('when Add transformation clicked', () => {
async function renderPicker() {
it('renders transformations picker', async () => {
setup([
{
id: 'reduce',
@@ -69,19 +53,12 @@ describe('TransformationsEditor', () => {
const search = screen.getByTestId(selectors.components.Transforms.searchInput);
expect(search).toBeDefined();
}
it('renders transformations picker', renderPicker);
it('renders transformation picker with transformationsRedesign feature toggled on', async () => {
config.featureToggles.transformationsRedesign = true;
await renderPicker();
config.featureToggles.transformationsRedesign = false;
});
});
describe('actions', () => {
describe('debug', () => {
async function showHideDebugger() {
it('should show/hide debugger', async () => {
setup([
{
id: 'reduce',
@@ -96,13 +73,6 @@ describe('TransformationsEditor', () => {
await userEvent.click(debugButton);
expect(screen.getByTestId(debuggerSelector)).toBeInTheDocument();
}
it('should show/hide debugger', showHideDebugger);
it('renders transformation editors with transformationsRedesign feature toggled on', async () => {
config.featureToggles.transformationsRedesign = true;
await showHideDebugger();
config.featureToggles.transformationsRedesign = false;
});
});
});
@@ -25,14 +25,12 @@ import {
ButtonGroup,
ScrollContainer,
} from '@grafana/ui';
import config from 'app/core/config';
import { EmptyTransformationsMessage } from 'app/features/dashboard-scene/panel-edit/PanelDataPane/EmptyTransformationsMessage';
import { PanelModel } from '../../state/PanelModel';
import { PanelNotSupported } from '../PanelEditor/PanelNotSupported';
import { TransformationOperationRows } from './TransformationOperationRows';
import { TransformationPicker } from './TransformationPicker';
import { TransformationPickerNg } from './TransformationPickerNg';
import { TransformationsEditorTransformation } from './types';
@@ -141,21 +139,19 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
}
componentDidUpdate(prevProps: Readonly<TransformationsEditorProps>, prevState: Readonly<State>): void {
if (config.featureToggles.transformationsRedesign) {
const prevHasTransforms = prevState.transformations.length > 0;
const prevShowPicker = !prevHasTransforms || prevState.showPicker;
const prevHasTransforms = prevState.transformations.length > 0;
const prevShowPicker = !prevHasTransforms || prevState.showPicker;
const currentHasTransforms = this.state.transformations.length > 0;
const currentShowPicker = !currentHasTransforms || this.state.showPicker;
const currentHasTransforms = this.state.transformations.length > 0;
const currentShowPicker = !currentHasTransforms || this.state.showPicker;
if (prevShowPicker !== currentShowPicker) {
// kindOfZero will be a random number between 0 and 0.5. It will be rounded to 0 by the scrollable component.
// We cannot always use 0 as it will not trigger a rerender of the scrollable component consistently
// due to React changes detection algo.
const kindOfZero = Math.random() / 2;
if (prevShowPicker !== currentShowPicker) {
// kindOfZero will be a random number between 0 and 0.5. It will be rounded to 0 by the scrollable component.
// We cannot always use 0 as it will not trigger a rerender of the scrollable component consistently
// due to React changes detection algo.
const kindOfZero = Math.random() / 2;
this.setState({ scrollTop: currentShowPicker ? kindOfZero : Number.MAX_SAFE_INTEGER });
}
this.setState({ scrollTop: currentShowPicker ? kindOfZero : Number.MAX_SAFE_INTEGER });
}
if (prevState.scrollTop !== this.state.scrollTop) {
@@ -183,10 +179,7 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
};
onTransformationAdd = (selectable: SelectableValue<string>) => {
let eventName = 'panel_editor_tabs_transformations_management';
if (config.featureToggles.transformationsRedesign) {
eventName = 'transformations_redesign_' + eventName;
}
const eventName = 'transformations_redesign_panel_editor_tabs_transformations_management';
reportInteraction(eventName, {
action: 'add',
@@ -211,10 +204,7 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
onTransformationChange = (idx: number, dataConfig: DataTransformerConfig) => {
const { transformations } = this.state;
const next = Array.from(transformations);
let eventName = 'panel_editor_tabs_transformations_management';
if (config.featureToggles.transformationsRedesign) {
eventName = 'transformations_redesign_' + eventName;
}
const eventName = 'transformations_redesign_panel_editor_tabs_transformations_management';
reportInteraction(eventName, {
action: 'change',
@@ -227,10 +217,7 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
onTransformationRemove = (idx: number) => {
const { transformations } = this.state;
const next = Array.from(transformations);
let eventName = 'panel_editor_tabs_transformations_management';
if (config.featureToggles.transformationsRedesign) {
eventName = 'transformations_redesign_' + eventName;
}
const eventName = 'transformations_redesign_panel_editor_tabs_transformations_management';
reportInteraction(eventName, {
action: 'remove',
@@ -298,21 +285,12 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
};
renderTransformsPicker() {
let { showPicker } = this.state;
const { transformations, search } = this.state;
const { transformationsRedesign } = config.featureToggles;
const { showPicker, transformations, search } = this.state;
const noTransforms = !transformations?.length;
const hasTransforms = transformations.length > 0;
let suffix: React.ReactNode = null;
let xforms = standardTransformersRegistry.list().sort((a, b) => (a.name > b.name ? 1 : b.name > a.name ? -1 : 0));
// In the case we're not on the transformation
// redesign and there are no transformations
// then we show the picker in that case
if (!transformationsRedesign && noTransforms) {
showPicker = true;
}
if (this.state.selectedFilter !== VIEW_ALL_VALUE) {
xforms = xforms.filter(
(t) =>
@@ -357,77 +335,53 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
);
}
// If we're in the transformation redesign
// we have the add transformation add the
// delete all control
let picker = null;
let deleteAll = null;
if (transformationsRedesign) {
picker = (
<TransformationPickerNg
noTransforms={noTransforms}
search={search}
suffix={suffix}
xforms={xforms}
onClose={() => this.setState({ showPicker: false })}
onSelectedFilterChange={(filter) => this.setState({ selectedFilter: filter })}
onShowIllustrationsChange={(showIllustrations) => this.setState({ showIllustrations })}
onSearchChange={this.onSearchChange}
onSearchKeyDown={this.onSearchKeyDown}
onTransformationAdd={this.onTransformationAdd}
data={this.state.data.series}
selectedFilter={this.state.selectedFilter}
showIllustrations={this.state.showIllustrations}
/>
);
const picker = (
<TransformationPickerNg
noTransforms={noTransforms}
search={search}
suffix={suffix}
xforms={xforms}
onClose={() => this.setState({ showPicker: false })}
onSelectedFilterChange={(filter) => this.setState({ selectedFilter: filter })}
onShowIllustrationsChange={(showIllustrations) => this.setState({ showIllustrations })}
onSearchChange={this.onSearchChange}
onSearchKeyDown={this.onSearchKeyDown}
onTransformationAdd={this.onTransformationAdd}
data={this.state.data.series}
selectedFilter={this.state.selectedFilter}
showIllustrations={this.state.showIllustrations}
/>
);
deleteAll = (
<>
<Button
icon="times"
variant="secondary"
onClick={() => this.setState({ showRemoveAllModal: true })}
style={{ marginLeft: this.props.theme.spacing.md }}
>
<Trans i18nKey="dashboard.un-themed-transformations-editor.delete-all-transformations">
Delete all transformations
</Trans>
</Button>
<ConfirmModal
isOpen={Boolean(this.state.showRemoveAllModal)}
title={t(
'dashboard.un-themed-transformations-editor.title-delete-all-transformations',
'Delete all transformations?'
)}
body={t(
'dashboard.un-theme-transformations-editor.body-delete-all-transformations',
'By deleting all transformations, you will go back to the main selection screen.'
)}
confirmText={t('dashboard.un-themed-transformations-editor.confirmText-delete-all', 'Delete all')}
onConfirm={() => this.onTransformationRemoveAll()}
onDismiss={() => this.setState({ showRemoveAllModal: false })}
/>
</>
);
}
// Otherwise we use the old picker
else {
picker = (
<TransformationPicker
noTransforms={noTransforms}
search={search}
suffix={suffix}
xforms={xforms}
onSearchChange={this.onSearchChange}
onSearchKeyDown={this.onSearchKeyDown}
onTransformationAdd={this.onTransformationAdd}
const deleteAll = (
<>
<Button
icon="times"
variant="secondary"
onClick={() => this.setState({ showRemoveAllModal: true })}
style={{ marginLeft: this.props.theme.spacing.md }}
>
<Trans i18nKey="dashboard.un-themed-transformations-editor.delete-all-transformations">
Delete all transformations
</Trans>
</Button>
<ConfirmModal
isOpen={Boolean(this.state.showRemoveAllModal)}
title={t(
'dashboard.un-themed-transformations-editor.title-delete-all-transformations',
'Delete all transformations?'
)}
body={t(
'dashboard.un-theme-transformations-editor.body-delete-all-transformations',
'By deleting all transformations, you will go back to the main selection screen.'
)}
confirmText={t('dashboard.un-themed-transformations-editor.confirmText-delete-all', 'Delete all')}
onConfirm={() => this.onTransformationRemoveAll()}
onDismiss={() => this.setState({ showRemoveAllModal: false })}
/>
);
}
</>
);
// Compose actions, if we're in the
// redesign a "Delete All Transformations"
// button (with confirm modal) is added
const actions = (
<ButtonGroup>
<Button
@@ -449,14 +403,7 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
return (
<>
{showPicker && picker}
{
// If the transformation redesign is enabled
// and there are transforms then show actions
(transformationsRedesign && hasTransforms && actions) ||
// If it's not enabled only show actions when there are
// transformations and the (old) picker isn't being shown
(!transformationsRedesign && !showPicker && hasTransforms && actions)
}
{hasTransforms && actions}
</>
);
}
@@ -481,7 +428,7 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
<ScrollContainer ref={this.ref} minHeight="100%">
<Container padding="lg">
<div data-testid={selectors.components.TransformTab.content}>
{!hasTransforms && config.featureToggles.transformationsRedesign && this.renderEmptyMessage()}
{!hasTransforms && this.renderEmptyMessage()}
{hasTransforms && this.renderTransformationEditors()}
{this.renderTransformsPicker()}
</div>