Files
grafana/public/app/features/dashboard/components/PanelEditor/getVisualizationOptions.tsx
Hugo Häggmark 2b8c74de2e i18n: removes useTranslate hook (#106556)
* i18n: removes useTranslate hook

* chore: fix duplicate imports

* chore: fix import sorting and hook dependencies
2025-06-12 11:03:52 +02:00

355 lines
11 KiB
TypeScript

import { get as lodashGet } from 'lodash';
import {
EventBus,
InterpolateFunction,
PanelData,
PanelPlugin,
StandardEditorContext,
VariableSuggestionsScope,
PanelOptionsEditorBuilder,
} from '@grafana/data';
import { NestedValueAccess, isNestedPanelOptions, PanelOptionsSupplier } from '@grafana/data/internal';
import { t } from '@grafana/i18n';
import { VizPanel } from '@grafana/scenes';
import { Input } from '@grafana/ui';
import { LibraryVizPanelInfo } from 'app/features/dashboard-scene/panel-edit/LibraryVizPanelInfo';
import { LibraryPanelBehavior } from 'app/features/dashboard-scene/scene/LibraryPanelBehavior';
import { getDataLinksVariableSuggestions } from 'app/features/panel/panellinks/link_srv';
import { OptionsPaneCategoryDescriptor } from './OptionsPaneCategoryDescriptor';
import { OptionsPaneItemDescriptor } from './OptionsPaneItemDescriptor';
import { getOptionOverrides } from './state/getOptionOverrides';
import { OptionPaneRenderProps } from './types';
import { setOptionImmutably, updateDefaultFieldConfigValue } from './utils';
type categoryGetter = (categoryNames?: string[]) => OptionsPaneCategoryDescriptor;
interface GetStandardEditorContextProps {
data: PanelData | undefined;
replaceVariables: InterpolateFunction;
options: Record<string, unknown>;
eventBus: EventBus;
instanceState: OptionPaneRenderProps['instanceState'];
}
export function getStandardEditorContext({
data,
replaceVariables,
options,
eventBus,
instanceState,
}: GetStandardEditorContextProps): StandardEditorContext<unknown, unknown> {
const dataSeries = data?.series ?? [];
const context: StandardEditorContext<unknown, unknown> = {
data: dataSeries,
replaceVariables,
options,
eventBus,
getSuggestions: (scope?: VariableSuggestionsScope) => getDataLinksVariableSuggestions(dataSeries, scope),
instanceState,
annotations: data?.annotations,
};
return context;
}
export function getVisualizationOptions(props: OptionPaneRenderProps): OptionsPaneCategoryDescriptor[] {
const { plugin, panel, onPanelOptionsChanged, onFieldConfigsChange, data, dashboard, instanceState } = props;
const currentOptions = panel.getOptions();
const currentFieldConfig = panel.fieldConfig;
const categoryIndex: Record<string, OptionsPaneCategoryDescriptor> = {};
const context = getStandardEditorContext({
data,
replaceVariables: panel.replaceVariables,
options: currentOptions,
eventBus: dashboard.events,
instanceState,
});
const getOptionsPaneCategory = (categoryNames?: string[]): OptionsPaneCategoryDescriptor => {
const categoryName = (categoryNames && categoryNames[0]) ?? `${plugin.meta.name}`;
const category = categoryIndex[categoryName];
if (category) {
return category;
}
return (categoryIndex[categoryName] = new OptionsPaneCategoryDescriptor({
title: categoryName,
id: categoryName,
sandboxId: plugin.meta.id,
}));
};
const access: NestedValueAccess = {
getValue: (path) => lodashGet(currentOptions, path),
onChange: (path, value) => {
const newOptions = setOptionImmutably(currentOptions, path, value);
onPanelOptionsChanged(newOptions);
},
};
// Load the options into categories
fillOptionsPaneItems(plugin.getPanelOptionsSupplier(), access, getOptionsPaneCategory, context);
/**
* Field options
*/
for (const fieldOption of plugin.fieldConfigRegistry.list()) {
if (fieldOption.isCustom) {
if (
fieldOption.showIf &&
!fieldOption.showIf(currentFieldConfig.defaults.custom, data?.series, data?.annotations)
) {
continue;
}
} else {
if (fieldOption.showIf && !fieldOption.showIf(currentFieldConfig.defaults, data?.series, data?.annotations)) {
continue;
}
}
if (fieldOption.hideFromDefaults) {
continue;
}
const category = getOptionsPaneCategory(fieldOption.category);
const Editor = fieldOption.editor;
const defaults = currentFieldConfig.defaults;
const value = fieldOption.isCustom
? defaults.custom
? lodashGet(defaults.custom, fieldOption.path)
: undefined
: lodashGet(defaults, fieldOption.path);
if (fieldOption.getItemsCount) {
category.props.itemsCount = fieldOption.getItemsCount(value);
}
category.addItem(
new OptionsPaneItemDescriptor({
title: fieldOption.name,
description: fieldOption.description,
overrides: getOptionOverrides(fieldOption, currentFieldConfig, data?.series),
render: function renderEditor() {
const onChange = (v: unknown) => {
onFieldConfigsChange(
updateDefaultFieldConfigValue(currentFieldConfig, fieldOption.path, v, fieldOption.isCustom)
);
};
return <Editor value={value} onChange={onChange} item={fieldOption} context={context} id={fieldOption.id} />;
},
})
);
}
return Object.values(categoryIndex);
}
export function getLibraryVizPanelOptionsCategory(libraryPanel: LibraryPanelBehavior): OptionsPaneCategoryDescriptor {
const descriptor = new OptionsPaneCategoryDescriptor({
title: t(
'dashboard.get-library-viz-panel-options-category.descriptor.title.library-panel-options',
'Library panel options'
),
id: 'Library panel options',
isOpenDefault: true,
});
descriptor
.addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.get-library-viz-panel-options-category.title.name', 'Name'),
value: libraryPanel,
popularRank: 1,
render: function renderName() {
return (
<Input
id="LibraryPanelFrameName"
data-testid="library panel name input"
defaultValue={libraryPanel.state.name}
onBlur={(e) => libraryPanel.setState({ name: e.currentTarget.value })}
/>
);
},
})
)
.addItem(
new OptionsPaneItemDescriptor({
title: t('dashboard.get-library-viz-panel-options-category.title.information', 'Information'),
render: function renderLibraryPanelInformation() {
return <LibraryVizPanelInfo libraryPanel={libraryPanel} />;
},
})
);
return descriptor;
}
export interface OptionPaneRenderProps2 {
panel: VizPanel;
eventBus: EventBus;
plugin: PanelPlugin;
data?: PanelData;
instanceState: unknown;
}
export function getVisualizationOptions2(props: OptionPaneRenderProps2): OptionsPaneCategoryDescriptor[] {
const { plugin, panel, data, eventBus, instanceState } = props;
const categoryIndex: Record<string, OptionsPaneCategoryDescriptor> = {};
const getOptionsPaneCategory = (categoryNames?: string[]): OptionsPaneCategoryDescriptor => {
const categoryName = categoryNames?.[0] ?? plugin.meta.name;
const category = categoryIndex[categoryName];
if (category) {
return category;
}
return (categoryIndex[categoryName] = new OptionsPaneCategoryDescriptor({
title: categoryName,
id: categoryName,
sandboxId: plugin.meta.id,
}));
};
const currentOptions = panel.state.options;
const access: NestedValueAccess = {
getValue: (path) => lodashGet(currentOptions, path),
onChange: (path, value) => {
const newOptions = setOptionImmutably(currentOptions, path, value);
panel.onOptionsChange(newOptions);
},
};
const context = getStandardEditorContext({
data,
replaceVariables: panel.interpolate,
options: currentOptions,
eventBus: eventBus,
instanceState,
});
// Load the options into categories
fillOptionsPaneItems(plugin.getPanelOptionsSupplier(), access, getOptionsPaneCategory, context);
// Field options
const currentFieldConfig = panel.state.fieldConfig;
for (const fieldOption of plugin.fieldConfigRegistry.list()) {
const hideOption =
fieldOption.showIf &&
(fieldOption.isCustom
? !fieldOption.showIf(currentFieldConfig.defaults.custom, data?.series, data?.annotations)
: !fieldOption.showIf(currentFieldConfig.defaults, data?.series, data?.annotations));
if (fieldOption.hideFromDefaults || hideOption) {
continue;
}
const category = getOptionsPaneCategory(fieldOption.category);
const Editor = fieldOption.editor;
const defaults = currentFieldConfig.defaults;
const value = fieldOption.isCustom
? defaults.custom
? lodashGet(defaults.custom, fieldOption.path)
: undefined
: lodashGet(defaults, fieldOption.path);
if (fieldOption.getItemsCount) {
category.props.itemsCount = fieldOption.getItemsCount(value);
}
category.addItem(
new OptionsPaneItemDescriptor({
title: fieldOption.name,
description: fieldOption.description,
overrides: getOptionOverrides(fieldOption, currentFieldConfig, data?.series),
render: function renderEditor() {
const onChange = (v: unknown) => {
panel.onFieldConfigChange(
updateDefaultFieldConfigValue(currentFieldConfig, fieldOption.path, v, fieldOption.isCustom),
true
);
};
return <Editor value={value} onChange={onChange} item={fieldOption} context={context} id={fieldOption.id} />;
},
})
);
}
return Object.values(categoryIndex);
}
/**
* This will iterate all options panes and add register them with the configured categories
*
* @internal
*/
export function fillOptionsPaneItems(
supplier: PanelOptionsSupplier<any>,
access: NestedValueAccess,
getOptionsPaneCategory: categoryGetter,
context: StandardEditorContext<any>,
parentCategory?: OptionsPaneCategoryDescriptor
) {
const builder = new PanelOptionsEditorBuilder();
supplier(builder, context);
for (const pluginOption of builder.getItems()) {
if (pluginOption.showIf && !pluginOption.showIf(context.options, context.data, context.annotations)) {
continue;
}
let category = parentCategory;
if (!category) {
category = getOptionsPaneCategory(pluginOption.category);
} else if (pluginOption.category?.[0]?.length) {
category = category.getCategory(pluginOption.category[0]);
}
// Nested options get passed up one level
if (isNestedPanelOptions(pluginOption)) {
const subAccess = pluginOption.getNestedValueAccess(access);
const subContext = subAccess.getContext
? subAccess.getContext(context)
: { ...context, options: access.getValue(pluginOption.path) };
fillOptionsPaneItems(
pluginOption.getBuilder(),
subAccess,
getOptionsPaneCategory,
subContext,
category // parent category
);
continue;
}
const Editor = pluginOption.editor;
category.addItem(
new OptionsPaneItemDescriptor({
title: pluginOption.name,
description: pluginOption.description,
render: function renderEditor() {
return (
<Editor
value={access.getValue(pluginOption.path)}
onChange={(value) => {
access.onChange(pluginOption.path, value);
}}
item={pluginOption}
context={context}
id={pluginOption.id}
/>
);
},
})
);
}
}