diff --git a/.betterer.results b/.betterer.results
index d18fb807162..74fadb4922c 100644
--- a/.betterer.results
+++ b/.betterer.results
@@ -2178,16 +2178,14 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx:5381": [
- [0, 0, 0, "Unexpected any. Specify a different type.", "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, "Unexpected any. Specify a different type.", "0"]
],
"public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "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, "Do not use any type assertions.", "1"],
+ [0, 0, 0, "Do not use any type assertions.", "2"],
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
- [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "4"]
+ [0, 0, 0, "Unexpected any. Specify a different type.", "4"]
],
"public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md
index 3b35e348ec3..f7dc58851fc 100644
--- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md
+++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md
@@ -124,6 +124,7 @@ Experimental features might be changed or removed without prior notice.
| `prometheusIncrementalQueryInstrumentation` | Adds RudderStack events to incremental queries |
| `logsExploreTableVisualisation` | A table visualisation for logs in Explore |
| `awsDatasourcesTempCredentials` | Support temporary security credentials in AWS plugins for Grafana Cloud customers |
+| `transformationsRedesign` | Enables the transformations redesign |
## Development feature toggles
diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts
index fb31e139fc7..e470a82ca52 100644
--- a/packages/grafana-data/src/types/featureToggles.gen.ts
+++ b/packages/grafana-data/src/types/featureToggles.gen.ts
@@ -111,4 +111,5 @@ export interface FeatureToggles {
prometheusIncrementalQueryInstrumentation?: boolean;
logsExploreTableVisualisation?: boolean;
awsDatasourcesTempCredentials?: boolean;
+ transformationsRedesign?: boolean;
}
diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts
index 05d789ae69e..572988f638a 100644
--- a/packages/grafana-e2e-selectors/src/selectors/components.ts
+++ b/packages/grafana-e2e-selectors/src/selectors/components.ts
@@ -208,13 +208,13 @@ export const Components = {
alertV2: (severity: string) => `data-testid Alert ${severity}`,
},
TransformTab: {
- content: 'Transform editor tab content',
- newTransform: (name: string) => `New transform ${name}`,
- transformationEditor: (name: string) => `Transformation editor ${name}`,
- transformationEditorDebugger: (name: string) => `Transformation editor debugger ${name}`,
+ content: 'data-testid Transform editor tab content',
+ newTransform: (name: string) => `data-testid New transform ${name}`,
+ transformationEditor: (name: string) => `data-testid Transformation editor ${name}`,
+ transformationEditorDebugger: (name: string) => `data-testid Transformation editor debugger ${name}`,
},
Transforms: {
- card: (name: string) => `New transform ${name}`,
+ card: (name: string) => `data-testid New transform ${name}`,
Reduce: {
modeLabel: 'Transform mode label',
calculationsLabel: 'Transform calculations label',
@@ -241,6 +241,7 @@ export const Components = {
},
},
searchInput: 'search transformations',
+ addTransformationButton: 'data-testid add transformation button',
},
NavBar: {
Configuration: {
diff --git a/packages/grafana-ui/src/components/FilterPill/FilterPill.tsx b/packages/grafana-ui/src/components/FilterPill/FilterPill.tsx
index 52f37c7d869..c047112c322 100644
--- a/packages/grafana-ui/src/components/FilterPill/FilterPill.tsx
+++ b/packages/grafana-ui/src/components/FilterPill/FilterPill.tsx
@@ -41,6 +41,7 @@ const getStyles = (theme: GrafanaTheme2) => {
height: 32px;
position: relative;
border: 1px solid ${theme.colors.background.secondary};
+ white-space: nowrap;
&:hover {
background: ${theme.colors.action.hover};
diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go
index 535790a5be4..6c2c2995a77 100644
--- a/pkg/services/featuremgmt/registry.go
+++ b/pkg/services/featuremgmt/registry.go
@@ -634,5 +634,12 @@ var (
Stage: FeatureStageExperimental,
Owner: awsDatasourcesSquad,
},
+ {
+ Name: "transformationsRedesign",
+ Description: "Enables the transformations redesign",
+ Stage: FeatureStageExperimental,
+ FrontendOnly: true,
+ Owner: grafanaObservabilityMetricsSquad,
+ },
}
)
diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv
index 8334b67b32c..05fcc46eea3 100644
--- a/pkg/services/featuremgmt/toggles_gen.csv
+++ b/pkg/services/featuremgmt/toggles_gen.csv
@@ -92,3 +92,4 @@ vizAndWidgetSplit,experimental,@grafana/dashboards-squad,false,false,false,true
prometheusIncrementalQueryInstrumentation,experimental,@grafana/observability-metrics,false,false,false,true
logsExploreTableVisualisation,experimental,@grafana/observability-logs,false,false,false,true
awsDatasourcesTempCredentials,experimental,@grafana/aws-datasources,false,false,false,false
+transformationsRedesign,experimental,@grafana/observability-metrics,false,false,false,true
diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go
index 93a768fd187..53e962cc790 100644
--- a/pkg/services/featuremgmt/toggles_gen.go
+++ b/pkg/services/featuremgmt/toggles_gen.go
@@ -378,4 +378,8 @@ const (
// FlagAwsDatasourcesTempCredentials
// Support temporary security credentials in AWS plugins for Grafana Cloud customers
FlagAwsDatasourcesTempCredentials = "awsDatasourcesTempCredentials"
+
+ // FlagTransformationsRedesign
+ // Enables the transformations redesign
+ FlagTransformationsRedesign = "transformationsRedesign"
)
diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx
index 85d0274fb69..01d124c602a 100644
--- a/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx
+++ b/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx
@@ -28,8 +28,13 @@ export const PanelEditorTabs = React.memo(({ panel, dashboard, tabs, onChangeTab
const instrumentedOnChangeTab = useCallback(
(tab: PanelEditorTab) => {
+ let eventName = 'panel_editor_tabs_changed';
+ if (config.featureToggles.transformationsRedesign) {
+ eventName = 'transformations_redesign_' + eventName;
+ }
+
if (!tab.active) {
- reportInteraction('panel_editor_tabs_changed', { tab_id: tab.id });
+ reportInteraction(eventName, { tab_id: tab.id });
}
onChangeTab(tab);
diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx
index 26fe38488a2..833550b632a 100644
--- a/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx
+++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx
@@ -74,12 +74,12 @@ export const TransformationEditor = ({
);
return (
-
+
{editor}
{debugMode && (
Transformation input data
diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx
index dd4cde96514..b423c811502 100644
--- a/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx
+++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx
@@ -3,7 +3,7 @@ import { useToggle } from 'react-use';
import { DataFrame, DataTransformerConfig, TransformerRegistryItem, FrameMatcherID } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime';
-import { HorizontalGroup } from '@grafana/ui';
+import { ConfirmModal, HorizontalGroup } from '@grafana/ui';
import { OperationRowHelp } from 'app/core/components/QueryOperationRow/OperationRowHelp';
import {
QueryOperationAction,
@@ -13,6 +13,7 @@ import {
QueryOperationRow,
QueryOperationRowRenderProps,
} from 'app/core/components/QueryOperationRow/QueryOperationRow';
+import config from 'app/core/config';
import { PluginStateInfo } from 'app/features/plugins/components/PluginStateInfo';
import { TransformationEditor } from './TransformationEditor';
@@ -38,6 +39,7 @@ export const TransformationOperationRow = ({
uiConfig,
onChange,
}: TransformationOperationRowProps) => {
+ const [showDeleteModal, setShowDeleteModal] = useToggle(false);
const [showDebug, toggleDebug] = useToggle(false);
const [showHelp, toggleHelp] = useToggle(false);
const disabled = !!configs[index].transformation.disabled;
@@ -73,7 +75,12 @@ export const TransformationOperationRow = ({
const instrumentToggleCallback = useCallback(
(callback: (e: React.MouseEvent) => void, toggleId: string, active: boolean | undefined) =>
(e: React.MouseEvent) => {
- reportInteraction('panel_editor_tabs_transformations_toggle', {
+ let eventName = 'panel_editor_tabs_transformations_toggle';
+ if (config.featureToggles.transformationsRedesign) {
+ eventName = 'transformations_redesign_' + eventName;
+ }
+
+ reportInteraction(eventName, {
action: active ? 'off' : 'on',
toggleId,
transformationId: configs[index].transformation.id,
@@ -115,7 +122,25 @@ export const TransformationOperationRow = ({
onClick={instrumentToggleCallback(() => onDisableToggle(index), 'disabled', disabled)}
active={disabled}
/>
-
onRemove(index)} />
+ (config.featureToggles.transformationsRedesign ? setShowDeleteModal(true) : onRemove(index))}
+ />
+
+ {config.featureToggles.transformationsRedesign && (
+ {
+ setShowDeleteModal(false);
+ onRemove(index);
+ }}
+ onDismiss={() => setShowDeleteModal(false)}
+ />
+ )}
);
};
diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.test.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.test.tsx
index 83586953eb1..16aa54cf8ce 100644
--- a/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.test.tsx
+++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.test.tsx
@@ -4,6 +4,7 @@ import React from 'react';
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';
@@ -20,30 +21,43 @@ describe('TransformationsEditor', () => {
standardTransformersRegistry.setInit(getStandardTransformers);
describe('when no transformations configured', () => {
- it('renders transformations selection list', () => {
+ function renderList() {
setup();
- const cards = screen.getAllByLabelText(/^New transform/i);
+ const cards = screen.getAllByTestId(/New transform/i);
expect(cards.length).toEqual(standardTransformersRegistry.list().length);
+ }
+
+ it('renders transformations selection list', renderList);
+ it('renders transformations selection list with transformationsRedesign feature toggled on', () => {
+ config.featureToggles.transformationsRedesign = true;
+ renderList();
+ config.featureToggles.transformationsRedesign = false;
});
});
describe('when transformations configured', () => {
- it('renders transformation editors', () => {
+ function renderEditors() {
setup([
{
id: 'reduce',
options: {},
},
]);
- const editors = screen.getAllByLabelText(/^Transformation editor/);
+ 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', () => {
- it('renders transformations picker', async () => {
- const buttonLabel = 'Add transformation';
+ async function renderPicker() {
setup([
{
id: 'reduce',
@@ -51,17 +65,24 @@ describe('TransformationsEditor', () => {
},
]);
- const addTransformationButton = screen.getByText(buttonLabel);
+ const addTransformationButton = screen.getByTestId(selectors.components.Transforms.addTransformationButton);
await userEvent.click(addTransformationButton);
- const search = screen.getByLabelText(selectors.components.Transforms.searchInput);
+ 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', () => {
- it('should show/hide debugger', async () => {
+ async function showHideDebugger() {
setup([
{
id: 'reduce',
@@ -70,12 +91,19 @@ describe('TransformationsEditor', () => {
]);
const debuggerSelector = selectors.components.TransformTab.transformationEditorDebugger('Reduce');
- expect(screen.queryByLabelText(debuggerSelector)).toBeNull();
+ expect(screen.queryByTestId(debuggerSelector)).toBeNull();
const debugButton = screen.getByLabelText(selectors.components.QueryEditorRow.actionButton('Debug'));
await userEvent.click(debugButton);
- expect(screen.getByLabelText(debuggerSelector)).toBeInTheDocument();
+ 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;
});
});
});
diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx
index 4b9cdcb9bfd..f3b7f49da1a 100644
--- a/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx
+++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx
@@ -12,25 +12,33 @@ import {
SelectableValue,
standardTransformersRegistry,
TransformerRegistryItem,
+ TransformerCategory,
+ DataTransformerID,
} from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { reportInteraction } from '@grafana/runtime';
import {
Alert,
Button,
+ ConfirmModal,
Container,
CustomScrollbar,
+ FilterPill,
Themeable,
VerticalGroup,
withTheme,
Input,
+ Icon,
IconButton,
useStyles2,
Card,
+ Switch,
} from '@grafana/ui';
import { LocalStorageValueProvider } from 'app/core/components/LocalStorageValueProvider';
+import config from 'app/core/config';
import { getDocsLink } from 'app/core/utils/docsLinks';
import { PluginStateInfo } from 'app/features/plugins/components/PluginStateInfo';
+import { categoriesLabels } from 'app/features/transformers/utils';
import { AppNotificationSeverity } from '../../../../types';
import { PanelModel } from '../../state';
@@ -45,11 +53,26 @@ interface TransformationsEditorProps extends Themeable {
panel: PanelModel;
}
+type viewAllType = 'viewAll';
+const viewAllValue = 'viewAll';
+const viewAllLabel = 'View all';
+
+type FilterCategory = TransformerCategory | viewAllType;
+
+const filterCategoriesLabels: Array<[FilterCategory, string]> = [
+ [viewAllValue, viewAllLabel],
+ ...(Object.entries(categoriesLabels) as Array<[FilterCategory, string]>),
+];
+
interface State {
data: DataFrame[];
transformations: TransformationsEditorTransformation[];
search: string;
showPicker?: boolean;
+ scrollTop?: number;
+ showRemoveAllModal?: boolean;
+ selectedFilter?: FilterCategory;
+ showIllustrations?: boolean;
}
class UnThemedTransformationsEditor extends React.PureComponent {
@@ -67,6 +90,8 @@ class UnThemedTransformationsEditor extends React.PureComponent, prevState: Readonly): void {
+ if (config.featureToggles.transformationsRedesign) {
+ const prevHasTransforms = prevState.transformations.length > 0;
+ const prevShowPicker = !prevHasTransforms || prevState.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;
+
+ this.setState({ scrollTop: currentShowPicker ? kindOfZero : Number.MAX_SAFE_INTEGER });
+ }
+ }
+ }
+
onChange(transformations: TransformationsEditorTransformation[]) {
this.setState({ transformations });
this.props.panel.setTransformations(transformations.map((t) => t.transformation));
@@ -145,7 +189,12 @@ class UnThemedTransformationsEditor extends React.PureComponent) => {
- reportInteraction('panel_editor_tabs_transformations_management', {
+ let eventName = 'panel_editor_tabs_transformations_management';
+ if (config.featureToggles.transformationsRedesign) {
+ eventName = 'transformations_redesign_' + eventName;
+ }
+
+ reportInteraction(eventName, {
action: 'add',
transformationId: selectable.value,
});
@@ -165,21 +214,31 @@ class UnThemedTransformationsEditor extends React.PureComponent {
+ onTransformationChange = (idx: number, dataConfig: DataTransformerConfig) => {
const { transformations } = this.state;
const next = Array.from(transformations);
- reportInteraction('panel_editor_tabs_transformations_management', {
+ let eventName = 'panel_editor_tabs_transformations_management';
+ if (config.featureToggles.transformationsRedesign) {
+ eventName = 'transformations_redesign_' + eventName;
+ }
+
+ reportInteraction(eventName, {
action: 'change',
transformationId: next[idx].transformation.id,
});
- next[idx].transformation = config;
+ next[idx].transformation = dataConfig;
this.onChange(next);
};
onTransformationRemove = (idx: number) => {
const { transformations } = this.state;
const next = Array.from(transformations);
- reportInteraction('panel_editor_tabs_transformations_management', {
+ let eventName = 'panel_editor_tabs_transformations_management';
+ if (config.featureToggles.transformationsRedesign) {
+ eventName = 'transformations_redesign_' + eventName;
+ }
+
+ reportInteraction(eventName, {
action: 'remove',
transformationId: next[idx].transformation.id,
});
@@ -187,6 +246,11 @@ class UnThemedTransformationsEditor extends React.PureComponent {
+ this.onChange([]);
+ this.setState({ showRemoveAllModal: false });
+ };
+
onDragEnd = (result: DropResult) => {
const { transformations } = this.state;
@@ -230,10 +294,20 @@ class UnThemedTransformationsEditor extends React.PureComponent (a.name > b.name ? 1 : b.name > a.name ? -1 : 0));
+ if (this.state.selectedFilter !== viewAllValue) {
+ xforms = xforms.filter(
+ (t) =>
+ t.categories &&
+ this.state.selectedFilter &&
+ t.categories.has(this.state.selectedFilter as TransformerCategory)
+ );
+ }
+
if (search) {
const lower = search.toLowerCase();
const filtered = xforms.filter((t) => {
@@ -272,6 +346,107 @@ class UnThemedTransformationsEditor extends React.PureComponent (
+ <>
+ {config.featureToggles.transformationsRedesign && (
+ <>
+ {!noTransforms && (
+ {
+ this.setState({ showPicker: false });
+ }}
+ >
+ Go back to Transformations in use
+
+ )}
+
+ >
+ )}
+
+ {!config.featureToggles.transformationsRedesign && (
+
+ )}
+
+ {!config.featureToggles.transformationsRedesign &&
+ xforms.map((t) => {
+ return (
+ {
+ this.onTransformationAdd({ value: t.id });
+ }}
+ />
+ );
+ })}
+
+ {config.featureToggles.transformationsRedesign && (
+
+
+
+ Show images {' '}
+ this.setState({ showIllustrations: !this.state.showIllustrations })}
+ />
+
+
+ )}
+
+ {config.featureToggles.transformationsRedesign && (
+
+ {filterCategoriesLabels.map(([slug, label]) => {
+ return (
+ this.setState({ selectedFilter: slug })}
+ label={label}
+ selected={this.state.selectedFilter === slug}
+ />
+ );
+ })}
+
+ )}
+
+ {config.featureToggles.transformationsRedesign && (
+ {
+ this.onTransformationAdd({ value: id });
+ }}
+ />
+ )}
+
+ >
+ );
+
return (
<>
{noTransforms && (
@@ -312,29 +487,7 @@ class UnThemedTransformationsEditor extends React.PureComponent
)}
{showPicker ? (
-
-
-
- {xforms.map((t) => {
- return (
- {
- this.onTransformationAdd({ value: t.id });
- }}
- />
- );
- })}
-
+
) : (
{
this.setState({ showPicker: true });
}}
+ data-testid={selectors.components.Transforms.addTransformationButton}
>
- Add transformation
+ Add{config.featureToggles.transformationsRedesign ? ' another ' : ' '}transformation
)}
>
@@ -351,6 +505,7 @@ class UnThemedTransformationsEditor extends React.PureComponent
-
-
+
+
+
{hasTransforms && alert ? (
) : null}
- {hasTransforms && this.renderTransformationEditors()}
+ {hasTransforms && config.featureToggles.transformationsRedesign && !this.state.showPicker && (
+
+ Transformations in use {' '}
+ {
+ this.setState({ showRemoveAllModal: true });
+ }}
+ >
+ Delete all transformations
+
+ this.onTransformationRemoveAll()}
+ onDismiss={() => this.setState({ showRemoveAllModal: false })}
+ />
+
+ )}
+ {hasTransforms &&
+ (!config.featureToggles.transformationsRedesign || !this.state.showPicker) &&
+ this.renderTransformationEditors()}
{this.renderTransformsPicker()}
@@ -391,7 +570,7 @@ function TransformationCard({ transform, onClick }: TransformationCardProps) {
return (
{transform.name}
@@ -411,7 +590,159 @@ const getStyles = (theme: GrafanaTheme2) => {
margin: 0;
padding: ${theme.spacing(1)};
`,
+ grid: css`
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
+ grid-auto-rows: 1fr;
+ gap: ${theme.spacing(2)} ${theme.spacing(1)};
+ width: 100%;
+ `,
+ newCard: css`
+ grid-template-rows: min-content 0 1fr 0;
+ `,
+ badge: css`
+ padding: 4px 3px;
+ `,
+ heading: css`
+ font-weight: 400;
+
+ > button {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: no-wrap;
+ }
+ `,
+ description: css`
+ font-size: 12px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ `,
+ image: css`
+ display: block;
+ max-width: 100%;
+ margin-top: ${theme.spacing(2)};
+ `,
+ searchWrapper: css`
+ display: flex;
+ flex-wrap: wrap;
+ column-gap: 27px;
+ row-gap: 16px;
+ width: 100%;
+ `,
+ searchInput: css`
+ flex-grow: 1;
+ width: initial;
+ `,
+ showImages: css`
+ flex-basis: 0;
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ `,
+ pickerInformationLine: css`
+ font-size: 16px;
+ margin-bottom: ${theme.spacing(2)};
+ `,
+ pickerInformationLineHighlight: css`
+ vertical-align: middle;
+ `,
+ illustationSwitchLabel: css`
+ white-space: nowrap;
+ `,
+ filterWrapper: css`
+ padding: ${theme.spacing(1)} 0;
+ display: flex;
+ flex-wrap: wrap;
+ row-gap: ${theme.spacing(1)};
+ column-gap: ${theme.spacing(0.5)};
+ `,
+ listInformationLineWrapper: css`
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 24px;
+ `,
+ listInformationLineText: css`
+ font-size: 16px;
+ `,
+ pluginStateInfoWrapper: css`
+ margin-left: 5px;
+ `,
};
};
+interface TransformationsGridProps {
+ transformations: Array>;
+ showIllustrations?: boolean;
+ onClick: (id: string) => void;
+}
+
+function TransformationsGrid({ showIllustrations, transformations, onClick }: TransformationsGridProps) {
+ const styles = useStyles2(getStyles);
+
+ return (
+
+ {transformations.map((transform) => (
+
onClick(transform.id)}
+ >
+
+ <>
+ {transform.name}
+
+
+
+ >
+
+
+ <>
+ {getTransformationsRedesignDescriptions(transform.id)}
+ {showIllustrations && (
+
+
+
+ )}
+ >
+
+
+ ))}
+
+ );
+}
+
+const getImagePath = (id: string) => {
+ const folder = config.theme2.isDark ? 'dark' : 'light';
+
+ return `public/img/transformations/${folder}/${id}.svg`;
+};
+
+const getTransformationsRedesignDescriptions = (id: string): string => {
+ const overrides: { [key: string]: string } = {
+ [DataTransformerID.concatenate]: 'Combine all fields into a single frame.',
+ [DataTransformerID.configFromData]: 'Set unit, min, max and more.',
+ [DataTransformerID.fieldLookup]: 'Use a field value to lookup countries, states, or airports.',
+ [DataTransformerID.filterFieldsByName]: 'Removes part of the query results using a regex pattern.',
+ [DataTransformerID.filterByRefId]: 'Filter out queries in panels that have multiple queries.',
+ [DataTransformerID.filterByValue]: 'Removes rows of the query results using user-defined filters.',
+ [DataTransformerID.groupBy]: 'Group the data by a field value then process calculations.',
+ [DataTransformerID.groupingToMatrix]: 'Summarizes and reorganizes data based on three fields.',
+ [DataTransformerID.joinByField]: 'Combine rows from 2+ tables, based on a related field.',
+ [DataTransformerID.labelsToFields]: 'Groups series by time and return labels or tags as fields.',
+ [DataTransformerID.merge]: 'Merge multiple series. Values will be combined into one row.',
+ [DataTransformerID.organize]: 'Allows the user to re-order, hide, or rename fields / columns.',
+ [DataTransformerID.partitionByValues]: 'Splits a one-frame dataset into multiple series.',
+ [DataTransformerID.prepareTimeSeries]: 'Will stretch data frames from the wide format into the long format.',
+ [DataTransformerID.reduce]: 'Reduce all rows or data points to a single value (ex. max, mean).',
+ [DataTransformerID.renameByRegex]: 'Reduce all rows or data points to a single value (ex. max, mean).',
+ [DataTransformerID.seriesToRows]: 'Merge multiple series. Return time, metric and values as a row.',
+ };
+
+ return overrides[id] || standardTransformersRegistry.getIfExists(id)?.description || '';
+};
+
export const TransformationsEditor = withTheme(UnThemedTransformationsEditor);
diff --git a/public/app/features/plugins/components/PluginStateInfo.tsx b/public/app/features/plugins/components/PluginStateInfo.tsx
index 29a63a3b47b..e27e1f0243f 100644
--- a/public/app/features/plugins/components/PluginStateInfo.tsx
+++ b/public/app/features/plugins/components/PluginStateInfo.tsx
@@ -5,6 +5,7 @@ import { Badge, BadgeProps } from '@grafana/ui';
interface Props {
state?: PluginState;
+ className?: string;
}
export const PluginStateInfo = (props: Props) => {
@@ -14,7 +15,15 @@ export const PluginStateInfo = (props: Props) => {
return null;
}
- return ;
+ return (
+
+ );
};
function getFeatureStateInfo(state?: PluginState): BadgeProps | null {