mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
@@ -2178,16 +2178,14 @@ exports[`better eslint`] = {
|
|||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||||
],
|
],
|
||||||
"public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx:5381": [
|
"public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx:5381": [
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
[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"]
|
|
||||||
],
|
],
|
||||||
"public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx:5381": [
|
"public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx:5381": [
|
||||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
[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, "Do not use any type assertions.", "1"],
|
||||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "2"],
|
[0, 0, 0, "Do not use any type assertions.", "2"],
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
|
[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": [
|
"public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx:5381": [
|
||||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ Experimental features might be changed or removed without prior notice.
|
|||||||
| `prometheusIncrementalQueryInstrumentation` | Adds RudderStack events to incremental queries |
|
| `prometheusIncrementalQueryInstrumentation` | Adds RudderStack events to incremental queries |
|
||||||
| `logsExploreTableVisualisation` | A table visualisation for logs in Explore |
|
| `logsExploreTableVisualisation` | A table visualisation for logs in Explore |
|
||||||
| `awsDatasourcesTempCredentials` | Support temporary security credentials in AWS plugins for Grafana Cloud customers |
|
| `awsDatasourcesTempCredentials` | Support temporary security credentials in AWS plugins for Grafana Cloud customers |
|
||||||
|
| `transformationsRedesign` | Enables the transformations redesign |
|
||||||
|
|
||||||
## Development feature toggles
|
## Development feature toggles
|
||||||
|
|
||||||
|
|||||||
@@ -111,4 +111,5 @@ export interface FeatureToggles {
|
|||||||
prometheusIncrementalQueryInstrumentation?: boolean;
|
prometheusIncrementalQueryInstrumentation?: boolean;
|
||||||
logsExploreTableVisualisation?: boolean;
|
logsExploreTableVisualisation?: boolean;
|
||||||
awsDatasourcesTempCredentials?: boolean;
|
awsDatasourcesTempCredentials?: boolean;
|
||||||
|
transformationsRedesign?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,13 +208,13 @@ export const Components = {
|
|||||||
alertV2: (severity: string) => `data-testid Alert ${severity}`,
|
alertV2: (severity: string) => `data-testid Alert ${severity}`,
|
||||||
},
|
},
|
||||||
TransformTab: {
|
TransformTab: {
|
||||||
content: 'Transform editor tab content',
|
content: 'data-testid Transform editor tab content',
|
||||||
newTransform: (name: string) => `New transform ${name}`,
|
newTransform: (name: string) => `data-testid New transform ${name}`,
|
||||||
transformationEditor: (name: string) => `Transformation editor ${name}`,
|
transformationEditor: (name: string) => `data-testid Transformation editor ${name}`,
|
||||||
transformationEditorDebugger: (name: string) => `Transformation editor debugger ${name}`,
|
transformationEditorDebugger: (name: string) => `data-testid Transformation editor debugger ${name}`,
|
||||||
},
|
},
|
||||||
Transforms: {
|
Transforms: {
|
||||||
card: (name: string) => `New transform ${name}`,
|
card: (name: string) => `data-testid New transform ${name}`,
|
||||||
Reduce: {
|
Reduce: {
|
||||||
modeLabel: 'Transform mode label',
|
modeLabel: 'Transform mode label',
|
||||||
calculationsLabel: 'Transform calculations label',
|
calculationsLabel: 'Transform calculations label',
|
||||||
@@ -241,6 +241,7 @@ export const Components = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
searchInput: 'search transformations',
|
searchInput: 'search transformations',
|
||||||
|
addTransformationButton: 'data-testid add transformation button',
|
||||||
},
|
},
|
||||||
NavBar: {
|
NavBar: {
|
||||||
Configuration: {
|
Configuration: {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
height: 32px;
|
height: 32px;
|
||||||
position: relative;
|
position: relative;
|
||||||
border: 1px solid ${theme.colors.background.secondary};
|
border: 1px solid ${theme.colors.background.secondary};
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: ${theme.colors.action.hover};
|
background: ${theme.colors.action.hover};
|
||||||
|
|||||||
@@ -634,5 +634,12 @@ var (
|
|||||||
Stage: FeatureStageExperimental,
|
Stage: FeatureStageExperimental,
|
||||||
Owner: awsDatasourcesSquad,
|
Owner: awsDatasourcesSquad,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "transformationsRedesign",
|
||||||
|
Description: "Enables the transformations redesign",
|
||||||
|
Stage: FeatureStageExperimental,
|
||||||
|
FrontendOnly: true,
|
||||||
|
Owner: grafanaObservabilityMetricsSquad,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -92,3 +92,4 @@ vizAndWidgetSplit,experimental,@grafana/dashboards-squad,false,false,false,true
|
|||||||
prometheusIncrementalQueryInstrumentation,experimental,@grafana/observability-metrics,false,false,false,true
|
prometheusIncrementalQueryInstrumentation,experimental,@grafana/observability-metrics,false,false,false,true
|
||||||
logsExploreTableVisualisation,experimental,@grafana/observability-logs,false,false,false,true
|
logsExploreTableVisualisation,experimental,@grafana/observability-logs,false,false,false,true
|
||||||
awsDatasourcesTempCredentials,experimental,@grafana/aws-datasources,false,false,false,false
|
awsDatasourcesTempCredentials,experimental,@grafana/aws-datasources,false,false,false,false
|
||||||
|
transformationsRedesign,experimental,@grafana/observability-metrics,false,false,false,true
|
||||||
|
|||||||
|
@@ -378,4 +378,8 @@ const (
|
|||||||
// FlagAwsDatasourcesTempCredentials
|
// FlagAwsDatasourcesTempCredentials
|
||||||
// Support temporary security credentials in AWS plugins for Grafana Cloud customers
|
// Support temporary security credentials in AWS plugins for Grafana Cloud customers
|
||||||
FlagAwsDatasourcesTempCredentials = "awsDatasourcesTempCredentials"
|
FlagAwsDatasourcesTempCredentials = "awsDatasourcesTempCredentials"
|
||||||
|
|
||||||
|
// FlagTransformationsRedesign
|
||||||
|
// Enables the transformations redesign
|
||||||
|
FlagTransformationsRedesign = "transformationsRedesign"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,8 +28,13 @@ export const PanelEditorTabs = React.memo(({ panel, dashboard, tabs, onChangeTab
|
|||||||
|
|
||||||
const instrumentedOnChangeTab = useCallback(
|
const instrumentedOnChangeTab = useCallback(
|
||||||
(tab: PanelEditorTab) => {
|
(tab: PanelEditorTab) => {
|
||||||
|
let eventName = 'panel_editor_tabs_changed';
|
||||||
|
if (config.featureToggles.transformationsRedesign) {
|
||||||
|
eventName = 'transformations_redesign_' + eventName;
|
||||||
|
}
|
||||||
|
|
||||||
if (!tab.active) {
|
if (!tab.active) {
|
||||||
reportInteraction('panel_editor_tabs_changed', { tab_id: tab.id });
|
reportInteraction(eventName, { tab_id: tab.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeTab(tab);
|
onChangeTab(tab);
|
||||||
|
|||||||
@@ -74,12 +74,12 @@ export const TransformationEditor = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.editor} aria-label={selectors.components.TransformTab.transformationEditor(uiConfig.name)}>
|
<div className={styles.editor} data-testid={selectors.components.TransformTab.transformationEditor(uiConfig.name)}>
|
||||||
{editor}
|
{editor}
|
||||||
{debugMode && (
|
{debugMode && (
|
||||||
<div
|
<div
|
||||||
className={styles.debugWrapper}
|
className={styles.debugWrapper}
|
||||||
aria-label={selectors.components.TransformTab.transformationEditorDebugger(uiConfig.name)}
|
data-testid={selectors.components.TransformTab.transformationEditorDebugger(uiConfig.name)}
|
||||||
>
|
>
|
||||||
<div className={styles.debug}>
|
<div className={styles.debug}>
|
||||||
<div className={styles.debugTitle}>Transformation input data</div>
|
<div className={styles.debugTitle}>Transformation input data</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useToggle } from 'react-use';
|
|||||||
|
|
||||||
import { DataFrame, DataTransformerConfig, TransformerRegistryItem, FrameMatcherID } from '@grafana/data';
|
import { DataFrame, DataTransformerConfig, TransformerRegistryItem, FrameMatcherID } from '@grafana/data';
|
||||||
import { reportInteraction } from '@grafana/runtime';
|
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 { OperationRowHelp } from 'app/core/components/QueryOperationRow/OperationRowHelp';
|
||||||
import {
|
import {
|
||||||
QueryOperationAction,
|
QueryOperationAction,
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
QueryOperationRow,
|
QueryOperationRow,
|
||||||
QueryOperationRowRenderProps,
|
QueryOperationRowRenderProps,
|
||||||
} from 'app/core/components/QueryOperationRow/QueryOperationRow';
|
} from 'app/core/components/QueryOperationRow/QueryOperationRow';
|
||||||
|
import config from 'app/core/config';
|
||||||
import { PluginStateInfo } from 'app/features/plugins/components/PluginStateInfo';
|
import { PluginStateInfo } from 'app/features/plugins/components/PluginStateInfo';
|
||||||
|
|
||||||
import { TransformationEditor } from './TransformationEditor';
|
import { TransformationEditor } from './TransformationEditor';
|
||||||
@@ -38,6 +39,7 @@ export const TransformationOperationRow = ({
|
|||||||
uiConfig,
|
uiConfig,
|
||||||
onChange,
|
onChange,
|
||||||
}: TransformationOperationRowProps) => {
|
}: TransformationOperationRowProps) => {
|
||||||
|
const [showDeleteModal, setShowDeleteModal] = useToggle(false);
|
||||||
const [showDebug, toggleDebug] = useToggle(false);
|
const [showDebug, toggleDebug] = useToggle(false);
|
||||||
const [showHelp, toggleHelp] = useToggle(false);
|
const [showHelp, toggleHelp] = useToggle(false);
|
||||||
const disabled = !!configs[index].transformation.disabled;
|
const disabled = !!configs[index].transformation.disabled;
|
||||||
@@ -73,7 +75,12 @@ export const TransformationOperationRow = ({
|
|||||||
const instrumentToggleCallback = useCallback(
|
const instrumentToggleCallback = useCallback(
|
||||||
(callback: (e: React.MouseEvent) => void, toggleId: string, active: boolean | undefined) =>
|
(callback: (e: React.MouseEvent) => void, toggleId: string, active: boolean | undefined) =>
|
||||||
(e: React.MouseEvent) => {
|
(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',
|
action: active ? 'off' : 'on',
|
||||||
toggleId,
|
toggleId,
|
||||||
transformationId: configs[index].transformation.id,
|
transformationId: configs[index].transformation.id,
|
||||||
@@ -115,7 +122,25 @@ export const TransformationOperationRow = ({
|
|||||||
onClick={instrumentToggleCallback(() => onDisableToggle(index), 'disabled', disabled)}
|
onClick={instrumentToggleCallback(() => onDisableToggle(index), 'disabled', disabled)}
|
||||||
active={disabled}
|
active={disabled}
|
||||||
/>
|
/>
|
||||||
<QueryOperationAction title="Remove" icon="trash-alt" onClick={() => onRemove(index)} />
|
<QueryOperationAction
|
||||||
|
title="Remove"
|
||||||
|
icon="trash-alt"
|
||||||
|
onClick={() => (config.featureToggles.transformationsRedesign ? setShowDeleteModal(true) : onRemove(index))}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{config.featureToggles.transformationsRedesign && (
|
||||||
|
<ConfirmModal
|
||||||
|
isOpen={showDeleteModal}
|
||||||
|
title={`Delete ${uiConfig.name}?`}
|
||||||
|
body="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="Delete"
|
||||||
|
onConfirm={() => {
|
||||||
|
setShowDeleteModal(false);
|
||||||
|
onRemove(index);
|
||||||
|
}}
|
||||||
|
onDismiss={() => setShowDeleteModal(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import React from 'react';
|
|||||||
|
|
||||||
import { DataTransformerConfig, standardTransformersRegistry } from '@grafana/data';
|
import { DataTransformerConfig, standardTransformersRegistry } from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
|
import config from 'app/core/config';
|
||||||
import { getStandardTransformers } from 'app/features/transformers/standardTransformers';
|
import { getStandardTransformers } from 'app/features/transformers/standardTransformers';
|
||||||
|
|
||||||
import { PanelModel } from '../../state';
|
import { PanelModel } from '../../state';
|
||||||
@@ -20,30 +21,43 @@ describe('TransformationsEditor', () => {
|
|||||||
standardTransformersRegistry.setInit(getStandardTransformers);
|
standardTransformersRegistry.setInit(getStandardTransformers);
|
||||||
|
|
||||||
describe('when no transformations configured', () => {
|
describe('when no transformations configured', () => {
|
||||||
it('renders transformations selection list', () => {
|
function renderList() {
|
||||||
setup();
|
setup();
|
||||||
|
|
||||||
const cards = screen.getAllByLabelText(/^New transform/i);
|
const cards = screen.getAllByTestId(/New transform/i);
|
||||||
expect(cards.length).toEqual(standardTransformersRegistry.list().length);
|
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', () => {
|
describe('when transformations configured', () => {
|
||||||
it('renders transformation editors', () => {
|
function renderEditors() {
|
||||||
setup([
|
setup([
|
||||||
{
|
{
|
||||||
id: 'reduce',
|
id: 'reduce',
|
||||||
options: {},
|
options: {},
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
const editors = screen.getAllByLabelText(/^Transformation editor/);
|
const editors = screen.getAllByTestId(/Transformation editor/);
|
||||||
expect(editors).toHaveLength(1);
|
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', () => {
|
describe('when Add transformation clicked', () => {
|
||||||
it('renders transformations picker', async () => {
|
async function renderPicker() {
|
||||||
const buttonLabel = 'Add transformation';
|
|
||||||
setup([
|
setup([
|
||||||
{
|
{
|
||||||
id: 'reduce',
|
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);
|
await userEvent.click(addTransformationButton);
|
||||||
|
|
||||||
const search = screen.getByLabelText(selectors.components.Transforms.searchInput);
|
const search = screen.getByTestId(selectors.components.Transforms.searchInput);
|
||||||
expect(search).toBeDefined();
|
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('actions', () => {
|
||||||
describe('debug', () => {
|
describe('debug', () => {
|
||||||
it('should show/hide debugger', async () => {
|
async function showHideDebugger() {
|
||||||
setup([
|
setup([
|
||||||
{
|
{
|
||||||
id: 'reduce',
|
id: 'reduce',
|
||||||
@@ -70,12 +91,19 @@ describe('TransformationsEditor', () => {
|
|||||||
]);
|
]);
|
||||||
const debuggerSelector = selectors.components.TransformTab.transformationEditorDebugger('Reduce');
|
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'));
|
const debugButton = screen.getByLabelText(selectors.components.QueryEditorRow.actionButton('Debug'));
|
||||||
await userEvent.click(debugButton);
|
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;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,25 +12,33 @@ import {
|
|||||||
SelectableValue,
|
SelectableValue,
|
||||||
standardTransformersRegistry,
|
standardTransformersRegistry,
|
||||||
TransformerRegistryItem,
|
TransformerRegistryItem,
|
||||||
|
TransformerCategory,
|
||||||
|
DataTransformerID,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { reportInteraction } from '@grafana/runtime';
|
import { reportInteraction } from '@grafana/runtime';
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
Button,
|
Button,
|
||||||
|
ConfirmModal,
|
||||||
Container,
|
Container,
|
||||||
CustomScrollbar,
|
CustomScrollbar,
|
||||||
|
FilterPill,
|
||||||
Themeable,
|
Themeable,
|
||||||
VerticalGroup,
|
VerticalGroup,
|
||||||
withTheme,
|
withTheme,
|
||||||
Input,
|
Input,
|
||||||
|
Icon,
|
||||||
IconButton,
|
IconButton,
|
||||||
useStyles2,
|
useStyles2,
|
||||||
Card,
|
Card,
|
||||||
|
Switch,
|
||||||
} from '@grafana/ui';
|
} from '@grafana/ui';
|
||||||
import { LocalStorageValueProvider } from 'app/core/components/LocalStorageValueProvider';
|
import { LocalStorageValueProvider } from 'app/core/components/LocalStorageValueProvider';
|
||||||
|
import config from 'app/core/config';
|
||||||
import { getDocsLink } from 'app/core/utils/docsLinks';
|
import { getDocsLink } from 'app/core/utils/docsLinks';
|
||||||
import { PluginStateInfo } from 'app/features/plugins/components/PluginStateInfo';
|
import { PluginStateInfo } from 'app/features/plugins/components/PluginStateInfo';
|
||||||
|
import { categoriesLabels } from 'app/features/transformers/utils';
|
||||||
|
|
||||||
import { AppNotificationSeverity } from '../../../../types';
|
import { AppNotificationSeverity } from '../../../../types';
|
||||||
import { PanelModel } from '../../state';
|
import { PanelModel } from '../../state';
|
||||||
@@ -45,11 +53,26 @@ interface TransformationsEditorProps extends Themeable {
|
|||||||
panel: PanelModel;
|
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 {
|
interface State {
|
||||||
data: DataFrame[];
|
data: DataFrame[];
|
||||||
transformations: TransformationsEditorTransformation[];
|
transformations: TransformationsEditorTransformation[];
|
||||||
search: string;
|
search: string;
|
||||||
showPicker?: boolean;
|
showPicker?: boolean;
|
||||||
|
scrollTop?: number;
|
||||||
|
showRemoveAllModal?: boolean;
|
||||||
|
selectedFilter?: FilterCategory;
|
||||||
|
showIllustrations?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class UnThemedTransformationsEditor extends React.PureComponent<TransformationsEditorProps, State> {
|
class UnThemedTransformationsEditor extends React.PureComponent<TransformationsEditorProps, State> {
|
||||||
@@ -67,6 +90,8 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
|
|||||||
})),
|
})),
|
||||||
data: [],
|
data: [],
|
||||||
search: '',
|
search: '',
|
||||||
|
selectedFilter: viewAllValue,
|
||||||
|
showIllustrations: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,6 +150,25 @@ 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 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[]) {
|
onChange(transformations: TransformationsEditorTransformation[]) {
|
||||||
this.setState({ transformations });
|
this.setState({ transformations });
|
||||||
this.props.panel.setTransformations(transformations.map((t) => t.transformation));
|
this.props.panel.setTransformations(transformations.map((t) => t.transformation));
|
||||||
@@ -145,7 +189,12 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
|
|||||||
};
|
};
|
||||||
|
|
||||||
onTransformationAdd = (selectable: SelectableValue<string>) => {
|
onTransformationAdd = (selectable: SelectableValue<string>) => {
|
||||||
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',
|
action: 'add',
|
||||||
transformationId: selectable.value,
|
transformationId: selectable.value,
|
||||||
});
|
});
|
||||||
@@ -165,21 +214,31 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
|
|||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
onTransformationChange = (idx: number, config: DataTransformerConfig) => {
|
onTransformationChange = (idx: number, dataConfig: DataTransformerConfig) => {
|
||||||
const { transformations } = this.state;
|
const { transformations } = this.state;
|
||||||
const next = Array.from(transformations);
|
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',
|
action: 'change',
|
||||||
transformationId: next[idx].transformation.id,
|
transformationId: next[idx].transformation.id,
|
||||||
});
|
});
|
||||||
next[idx].transformation = config;
|
next[idx].transformation = dataConfig;
|
||||||
this.onChange(next);
|
this.onChange(next);
|
||||||
};
|
};
|
||||||
|
|
||||||
onTransformationRemove = (idx: number) => {
|
onTransformationRemove = (idx: number) => {
|
||||||
const { transformations } = this.state;
|
const { transformations } = this.state;
|
||||||
const next = Array.from(transformations);
|
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',
|
action: 'remove',
|
||||||
transformationId: next[idx].transformation.id,
|
transformationId: next[idx].transformation.id,
|
||||||
});
|
});
|
||||||
@@ -187,6 +246,11 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
|
|||||||
this.onChange(next);
|
this.onChange(next);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onTransformationRemoveAll = () => {
|
||||||
|
this.onChange([]);
|
||||||
|
this.setState({ showRemoveAllModal: false });
|
||||||
|
};
|
||||||
|
|
||||||
onDragEnd = (result: DropResult) => {
|
onDragEnd = (result: DropResult) => {
|
||||||
const { transformations } = this.state;
|
const { transformations } = this.state;
|
||||||
|
|
||||||
@@ -230,10 +294,20 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
|
|||||||
};
|
};
|
||||||
|
|
||||||
renderTransformsPicker() {
|
renderTransformsPicker() {
|
||||||
|
const styles = getStyles(config.theme2);
|
||||||
const { transformations, search } = this.state;
|
const { transformations, search } = this.state;
|
||||||
let suffix: React.ReactNode = null;
|
let suffix: React.ReactNode = null;
|
||||||
let xforms = standardTransformersRegistry.list().sort((a, b) => (a.name > b.name ? 1 : b.name > a.name ? -1 : 0));
|
let xforms = standardTransformersRegistry.list().sort((a, b) => (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) {
|
if (search) {
|
||||||
const lower = search.toLowerCase();
|
const lower = search.toLowerCase();
|
||||||
const filtered = xforms.filter((t) => {
|
const filtered = xforms.filter((t) => {
|
||||||
@@ -272,6 +346,107 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Picker = () => (
|
||||||
|
<>
|
||||||
|
{config.featureToggles.transformationsRedesign && (
|
||||||
|
<>
|
||||||
|
{!noTransforms && (
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
fill="text"
|
||||||
|
icon="angle-left"
|
||||||
|
onClick={() => {
|
||||||
|
this.setState({ showPicker: false });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Go back to <i>Transformations in use</i>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<div className={styles.pickerInformationLine}>
|
||||||
|
<a href={getDocsLink(DocsId.Transformations)} className="external-link" target="_blank" rel="noreferrer">
|
||||||
|
<span className={styles.pickerInformationLineHighlight}>Transformations</span>{' '}
|
||||||
|
<Icon name="external-link-alt" />
|
||||||
|
</a>
|
||||||
|
allow you to manipulate your data before a visualization is applied.
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<VerticalGroup>
|
||||||
|
{!config.featureToggles.transformationsRedesign && (
|
||||||
|
<Input
|
||||||
|
data-testid={selectors.components.Transforms.searchInput}
|
||||||
|
value={search ?? ''}
|
||||||
|
autoFocus={!noTransforms}
|
||||||
|
placeholder="Search for transformation"
|
||||||
|
onChange={this.onSearchChange}
|
||||||
|
onKeyDown={this.onSearchKeyDown}
|
||||||
|
suffix={suffix}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!config.featureToggles.transformationsRedesign &&
|
||||||
|
xforms.map((t) => {
|
||||||
|
return (
|
||||||
|
<TransformationCard
|
||||||
|
key={t.name}
|
||||||
|
transform={t}
|
||||||
|
onClick={() => {
|
||||||
|
this.onTransformationAdd({ value: t.id });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{config.featureToggles.transformationsRedesign && (
|
||||||
|
<div className={styles.searchWrapper}>
|
||||||
|
<Input
|
||||||
|
data-testid={selectors.components.Transforms.searchInput}
|
||||||
|
className={styles.searchInput}
|
||||||
|
value={search ?? ''}
|
||||||
|
autoFocus={!noTransforms}
|
||||||
|
placeholder="Search for transformation"
|
||||||
|
onChange={this.onSearchChange}
|
||||||
|
onKeyDown={this.onSearchKeyDown}
|
||||||
|
suffix={suffix}
|
||||||
|
/>
|
||||||
|
<div className={styles.showImages}>
|
||||||
|
<span className={styles.illustationSwitchLabel}>Show images</span>{' '}
|
||||||
|
<Switch
|
||||||
|
value={this.state.showIllustrations}
|
||||||
|
onChange={() => this.setState({ showIllustrations: !this.state.showIllustrations })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{config.featureToggles.transformationsRedesign && (
|
||||||
|
<div className={styles.filterWrapper}>
|
||||||
|
{filterCategoriesLabels.map(([slug, label]) => {
|
||||||
|
return (
|
||||||
|
<FilterPill
|
||||||
|
key={slug}
|
||||||
|
onClick={() => this.setState({ selectedFilter: slug })}
|
||||||
|
label={label}
|
||||||
|
selected={this.state.selectedFilter === slug}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{config.featureToggles.transformationsRedesign && (
|
||||||
|
<TransformationsGrid
|
||||||
|
showIllustrations={this.state.showIllustrations}
|
||||||
|
transformations={xforms}
|
||||||
|
onClick={(id) => {
|
||||||
|
this.onTransformationAdd({ value: id });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</VerticalGroup>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{noTransforms && (
|
{noTransforms && (
|
||||||
@@ -312,29 +487,7 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
|
|||||||
</Container>
|
</Container>
|
||||||
)}
|
)}
|
||||||
{showPicker ? (
|
{showPicker ? (
|
||||||
<VerticalGroup>
|
<Picker />
|
||||||
<Input
|
|
||||||
aria-label={selectors.components.Transforms.searchInput}
|
|
||||||
value={search ?? ''}
|
|
||||||
autoFocus={!noTransforms}
|
|
||||||
placeholder="Add transformation"
|
|
||||||
onChange={this.onSearchChange}
|
|
||||||
onKeyDown={this.onSearchKeyDown}
|
|
||||||
suffix={suffix}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{xforms.map((t) => {
|
|
||||||
return (
|
|
||||||
<TransformationCard
|
|
||||||
key={t.name}
|
|
||||||
transform={t}
|
|
||||||
onClick={() => {
|
|
||||||
this.onTransformationAdd({ value: t.id });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</VerticalGroup>
|
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
icon="plus"
|
icon="plus"
|
||||||
@@ -342,8 +495,9 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
this.setState({ showPicker: true });
|
this.setState({ showPicker: true });
|
||||||
}}
|
}}
|
||||||
|
data-testid={selectors.components.Transforms.addTransformationButton}
|
||||||
>
|
>
|
||||||
Add transformation
|
Add{config.featureToggles.transformationsRedesign ? ' another ' : ' '}transformation
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -351,6 +505,7 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const styles = getStyles(config.theme2);
|
||||||
const {
|
const {
|
||||||
panel: { alert },
|
panel: { alert },
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@@ -363,16 +518,40 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomScrollbar autoHeightMin="100%">
|
<CustomScrollbar scrollTop={this.state.scrollTop} autoHeightMin="100%">
|
||||||
<Container padding="md">
|
<Container padding="lg">
|
||||||
<div aria-label={selectors.components.TransformTab.content}>
|
<div data-testid={selectors.components.TransformTab.content}>
|
||||||
{hasTransforms && alert ? (
|
{hasTransforms && alert ? (
|
||||||
<Alert
|
<Alert
|
||||||
severity={AppNotificationSeverity.Error}
|
severity={AppNotificationSeverity.Error}
|
||||||
title="Transformations can't be used on a panel with alerts"
|
title="Transformations can't be used on a panel with alerts"
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{hasTransforms && this.renderTransformationEditors()}
|
{hasTransforms && config.featureToggles.transformationsRedesign && !this.state.showPicker && (
|
||||||
|
<div className={styles.listInformationLineWrapper}>
|
||||||
|
<span className={styles.listInformationLineText}>Transformations in use</span>{' '}
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
this.setState({ showRemoveAllModal: true });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete all transformations
|
||||||
|
</Button>
|
||||||
|
<ConfirmModal
|
||||||
|
isOpen={Boolean(this.state.showRemoveAllModal)}
|
||||||
|
title="Delete all transformations?"
|
||||||
|
body="By deleting all transformations, you will go back to the main selection screen."
|
||||||
|
confirmText="Delete all"
|
||||||
|
onConfirm={() => this.onTransformationRemoveAll()}
|
||||||
|
onDismiss={() => this.setState({ showRemoveAllModal: false })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{hasTransforms &&
|
||||||
|
(!config.featureToggles.transformationsRedesign || !this.state.showPicker) &&
|
||||||
|
this.renderTransformationEditors()}
|
||||||
{this.renderTransformsPicker()}
|
{this.renderTransformsPicker()}
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
@@ -391,7 +570,7 @@ function TransformationCard({ transform, onClick }: TransformationCardProps) {
|
|||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className={styles.card}
|
className={styles.card}
|
||||||
aria-label={selectors.components.TransformTab.newTransform(transform.name)}
|
data-testid={selectors.components.TransformTab.newTransform(transform.name)}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<Card.Heading>{transform.name}</Card.Heading>
|
<Card.Heading>{transform.name}</Card.Heading>
|
||||||
@@ -411,7 +590,159 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: ${theme.spacing(1)};
|
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<TransformerRegistryItem<any>>;
|
||||||
|
showIllustrations?: boolean;
|
||||||
|
onClick: (id: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TransformationsGrid({ showIllustrations, transformations, onClick }: TransformationsGridProps) {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.grid}>
|
||||||
|
{transformations.map((transform) => (
|
||||||
|
<Card
|
||||||
|
key={transform.id}
|
||||||
|
className={styles.newCard}
|
||||||
|
data-testid={selectors.components.TransformTab.newTransform(transform.name)}
|
||||||
|
onClick={() => onClick(transform.id)}
|
||||||
|
>
|
||||||
|
<Card.Heading className={styles.heading}>
|
||||||
|
<>
|
||||||
|
<span>{transform.name}</span>
|
||||||
|
<span className={styles.pluginStateInfoWrapper}>
|
||||||
|
<PluginStateInfo className={styles.badge} state={transform.state} />
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
</Card.Heading>
|
||||||
|
<Card.Description className={styles.description}>
|
||||||
|
<>
|
||||||
|
<span>{getTransformationsRedesignDescriptions(transform.id)}</span>
|
||||||
|
{showIllustrations && (
|
||||||
|
<span>
|
||||||
|
<img className={styles.image} src={getImagePath(transform.id)} alt={transform.name} />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
</Card.Description>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
export const TransformationsEditor = withTheme(UnThemedTransformationsEditor);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Badge, BadgeProps } from '@grafana/ui';
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
state?: PluginState;
|
state?: PluginState;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PluginStateInfo = (props: Props) => {
|
export const PluginStateInfo = (props: Props) => {
|
||||||
@@ -14,7 +15,15 @@ export const PluginStateInfo = (props: Props) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Badge color={display.color} title={display.tooltip} text={display.text} icon={display.icon} />;
|
return (
|
||||||
|
<Badge
|
||||||
|
className={props.className}
|
||||||
|
color={display.color}
|
||||||
|
title={display.tooltip}
|
||||||
|
text={display.text}
|
||||||
|
icon={display.icon}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function getFeatureStateInfo(state?: PluginState): BadgeProps | null {
|
function getFeatureStateInfo(state?: PluginState): BadgeProps | null {
|
||||||
|
|||||||
Reference in New Issue
Block a user