mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Scenes: Add panel frame options and visualization options to panel editor (#80884)
This commit is contained in:
parent
7218e11e23
commit
b2f2864628
@ -2554,16 +2554,7 @@ exports[`better eslint`] = {
|
||||
],
|
||||
"public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx:5381": [
|
||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"],
|
||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "2"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "3"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "4"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "5"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "6"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "7"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "8"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "9"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "10"]
|
||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"]
|
||||
],
|
||||
"public/app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
|
@ -138,6 +138,12 @@ export function buildPanelEditScene(panel: VizPanel): PanelEditor {
|
||||
body: new PanelOptionsPane(vizPanelMgr),
|
||||
width: '100%',
|
||||
}),
|
||||
primaryPaneStyles: {
|
||||
minWidth: '0',
|
||||
},
|
||||
secondaryPaneStyles: {
|
||||
minWidth: '0',
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { SceneComponentProps, SceneObjectBase, SceneObjectState } from '@grafana/scenes';
|
||||
import { ButtonGroup, FilterInput, RadioButtonGroup, ToolbarButton, useStyles2 } from '@grafana/ui';
|
||||
import { getPanelFrameCategory2 } from 'app/features/dashboard/components/PanelEditor/getPanelFrameOptions';
|
||||
import { getVisualizationOptions2 } from 'app/features/dashboard/components/PanelEditor/getVisualizationOptions';
|
||||
import { getAllPanelPluginMeta } from 'app/features/panel/state/util';
|
||||
|
||||
import { PanelVizTypePicker } from './PanelVizTypePicker';
|
||||
@ -24,9 +26,27 @@ export class PanelOptionsPane extends SceneObjectBase<PanelOptionsPaneState> {
|
||||
static Component = ({ model }: SceneComponentProps<PanelOptionsPane>) => {
|
||||
const { panelManager } = model;
|
||||
const { panel } = panelManager.state;
|
||||
const { pluginId } = panel.useState();
|
||||
const { pluginId, options } = panel.useState();
|
||||
const styles = useStyles2(getStyles);
|
||||
const [isVizPickerOpen, setVizPickerOpen] = useState(true);
|
||||
const panelFrameOptions = useMemo(() => getPanelFrameCategory2(panel), [panel]);
|
||||
|
||||
const visualizationOptions = useMemo(() => {
|
||||
const plugin = panel.getPlugin();
|
||||
if (!plugin) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return getVisualizationOptions2({
|
||||
panel,
|
||||
plugin: plugin,
|
||||
eventBus: panel.getPanelContext().eventBus,
|
||||
instanceState: panel.getPanelContext().instanceState!,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [panel, options]);
|
||||
|
||||
const mainBoxElements = [panelFrameOptions.render(), ...(visualizationOptions?.map((v) => v.render()) ?? [])];
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
@ -44,18 +64,23 @@ export class PanelOptionsPane extends SceneObjectBase<PanelOptionsPaneState> {
|
||||
)}
|
||||
{!isVizPickerOpen && (
|
||||
<>
|
||||
<FilterInput value={''} placeholder="Search options" onChange={() => {}} />
|
||||
<RadioButtonGroup
|
||||
options={[
|
||||
{ label: 'All', value: 'All' },
|
||||
{ label: 'Overrides', value: 'Overrides' },
|
||||
]}
|
||||
value={'All'}
|
||||
fullWidth
|
||||
></RadioButtonGroup>
|
||||
{/* <OptionsPaneCategory id="test" title="Panel options">
|
||||
Placeholder
|
||||
</OptionsPaneCategory> */}
|
||||
<div className={styles.top}>
|
||||
<FilterInput
|
||||
className={styles.searchOptions}
|
||||
value={''}
|
||||
placeholder="Search options"
|
||||
onChange={() => {}}
|
||||
/>
|
||||
<RadioButtonGroup
|
||||
options={[
|
||||
{ label: 'All', value: 'All' },
|
||||
{ label: 'Overrides', value: 'Overrides' },
|
||||
]}
|
||||
value={'All'}
|
||||
fullWidth
|
||||
></RadioButtonGroup>
|
||||
</div>
|
||||
<div className={styles.mainBox}>{mainBoxElements}</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@ -66,20 +91,38 @@ export class PanelOptionsPane extends SceneObjectBase<PanelOptionsPaneState> {
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
top: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: theme.spacing(1),
|
||||
gap: theme.spacing(1),
|
||||
border: `1px solid ${theme.colors.border.weak}`,
|
||||
borderBottom: 'none',
|
||||
borderTopLeftRadius: theme.shape.radius.default,
|
||||
background: theme.colors.background.primary,
|
||||
}),
|
||||
box: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: '1',
|
||||
padding: theme.spacing(1),
|
||||
background: theme.colors.background.primary,
|
||||
border: `1px solid ${theme.colors.border.weak}`,
|
||||
gap: theme.spacing(1),
|
||||
overflow: 'hidden',
|
||||
}),
|
||||
wrapper: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(2),
|
||||
flexGrow: '1',
|
||||
gap: theme.spacing(2),
|
||||
}),
|
||||
mainBox: css({
|
||||
flexGrow: 1,
|
||||
background: theme.colors.background.primary,
|
||||
border: `1px solid ${theme.components.panel.borderColor}`,
|
||||
borderTop: 'none',
|
||||
overflow: 'auto',
|
||||
}),
|
||||
searchOptions: css({
|
||||
minHeight: theme.spacing(4),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
@ -49,8 +49,13 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
padding: theme.spacing(1),
|
||||
height: '100%',
|
||||
gap: theme.spacing(1),
|
||||
border: `1px solid ${theme.colors.border.weak}`,
|
||||
borderRight: 'none',
|
||||
borderBottom: 'none',
|
||||
borderTopLeftRadius: theme.shape.radius.default,
|
||||
}),
|
||||
filter: css({
|
||||
minHeight: theme.spacing(4),
|
||||
|
@ -95,7 +95,7 @@ const instance2SettingsMock = {
|
||||
jest.mock('app/core/store', () => ({
|
||||
exists: jest.fn(),
|
||||
get: jest.fn(),
|
||||
getObject: jest.fn(),
|
||||
getObject: jest.fn((_a, b) => b),
|
||||
setObject: jest.fn(),
|
||||
}));
|
||||
|
||||
|
@ -6,7 +6,11 @@ import { restore, versions } from './__mocks__/dashboardHistoryMocks';
|
||||
const getMock = jest.fn().mockResolvedValue({});
|
||||
const postMock = jest.fn().mockResolvedValue({});
|
||||
|
||||
jest.mock('app/core/store');
|
||||
jest.mock('app/core/store', () => ({
|
||||
get: jest.fn(),
|
||||
getObject: jest.fn((_a, b) => b),
|
||||
}));
|
||||
|
||||
jest.mock('@grafana/runtime', () => {
|
||||
const original = jest.requireActual('@grafana/runtime');
|
||||
|
||||
|
@ -16,7 +16,7 @@ import { DashboardExporter, LibraryElementExport } from './DashboardExporter';
|
||||
jest.mock('app/core/store', () => {
|
||||
return {
|
||||
getBool: jest.fn(),
|
||||
getObject: jest.fn(),
|
||||
getObject: jest.fn((_a, b) => b),
|
||||
get: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
@ -22,7 +22,7 @@ export interface OptionsPaneCategoryProps {
|
||||
sandboxId?: string;
|
||||
}
|
||||
|
||||
const CATEGORY_PARAM_NAME = 'showCategory';
|
||||
const CATEGORY_PARAM_NAME = 'showCategory' as const;
|
||||
|
||||
export const OptionsPaneCategory = React.memo(
|
||||
({
|
||||
@ -30,23 +30,21 @@ export const OptionsPaneCategory = React.memo(
|
||||
title,
|
||||
children,
|
||||
forceOpen,
|
||||
isOpenDefault,
|
||||
isOpenDefault = true,
|
||||
renderTitle,
|
||||
className,
|
||||
itemsCount,
|
||||
isNested = false,
|
||||
sandboxId,
|
||||
}: OptionsPaneCategoryProps) => {
|
||||
const initialIsExpanded = isOpenDefault !== false;
|
||||
const [savedState, setSavedState] = useLocalStorage(getOptionGroupStorageKey(id), {
|
||||
isExpanded: initialIsExpanded,
|
||||
isExpanded: isOpenDefault,
|
||||
});
|
||||
|
||||
const styles = useStyles2(getStyles);
|
||||
const [queryParams, updateQueryParams] = useQueryParams();
|
||||
const [isExpanded, setIsExpanded] = useState(savedState?.isExpanded ?? initialIsExpanded);
|
||||
const [isExpanded, setIsExpanded] = useState(savedState?.isExpanded ?? isOpenDefault);
|
||||
const manualClickTime = useRef(0);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [queryParams, updateQueryParams] = useQueryParams();
|
||||
const isOpenFromUrl = queryParams[CATEGORY_PARAM_NAME] === id;
|
||||
|
||||
useEffect(() => {
|
||||
@ -92,13 +90,13 @@ export const OptionsPaneCategory = React.memo(
|
||||
};
|
||||
}
|
||||
|
||||
const styles = useStyles2(getStyles);
|
||||
const boxStyles = cx(
|
||||
{
|
||||
[styles.box]: true,
|
||||
[styles.boxNestedExpanded]: isNested && isExpanded,
|
||||
},
|
||||
className,
|
||||
'options-group'
|
||||
className
|
||||
);
|
||||
|
||||
const headerStyles = cx(styles.header, {
|
||||
@ -149,61 +147,60 @@ export const OptionsPaneCategory = React.memo(
|
||||
|
||||
OptionsPaneCategory.displayName = 'OptionsPaneCategory';
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
box: css`
|
||||
border-top: 1px solid ${theme.colors.border.weak};
|
||||
`,
|
||||
boxNestedExpanded: css`
|
||||
margin-bottom: ${theme.spacing(2)};
|
||||
`,
|
||||
title: css`
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
line-height: 1.5;
|
||||
font-size: 1rem;
|
||||
padding-left: 6px;
|
||||
font-weight: ${theme.typography.fontWeightMedium};
|
||||
margin: 0;
|
||||
`,
|
||||
header: css`
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
padding: ${theme.spacing(0.5)};
|
||||
color: ${theme.colors.text.primary};
|
||||
font-weight: ${theme.typography.fontWeightMedium};
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
box: css({
|
||||
borderTop: `1px solid ${theme.colors.border.weak}`,
|
||||
}),
|
||||
boxNestedExpanded: css({
|
||||
marginBottom: theme.spacing(2),
|
||||
}),
|
||||
title: css({
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
lineHeight: 1.5,
|
||||
fontSize: '1rem',
|
||||
paddingLeft: '6px',
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
margin: 0,
|
||||
}),
|
||||
header: css({
|
||||
display: 'flex',
|
||||
cursor: 'pointer',
|
||||
alignItems: 'center',
|
||||
padding: theme.spacing(0.5),
|
||||
color: theme.colors.text.primary,
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
|
||||
&:hover {
|
||||
background: ${theme.colors.emphasize(theme.colors.background.primary, 0.03)};
|
||||
}
|
||||
`,
|
||||
toggleButton: css`
|
||||
align-self: baseline;
|
||||
`,
|
||||
headerExpanded: css`
|
||||
color: ${theme.colors.text.primary};
|
||||
`,
|
||||
headerNested: css`
|
||||
padding: ${theme.spacing(0.5, 0, 0.5, 0)};
|
||||
`,
|
||||
body: css`
|
||||
padding: ${theme.spacing(1, 2, 1, 4)};
|
||||
`,
|
||||
bodyNested: css`
|
||||
position: relative;
|
||||
padding-right: 0;
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 8px;
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
background: ${theme.colors.border.weak};
|
||||
}
|
||||
`,
|
||||
};
|
||||
};
|
||||
'&:hover': {
|
||||
background: theme.colors.emphasize(theme.colors.background.primary, 0.03),
|
||||
},
|
||||
}),
|
||||
toggleButton: css({
|
||||
alignSelf: 'baseline',
|
||||
}),
|
||||
headerExpanded: css({
|
||||
color: theme.colors.text.primary,
|
||||
}),
|
||||
headerNested: css({
|
||||
padding: theme.spacing(0.5, 0, 0.5, 0),
|
||||
}),
|
||||
body: css({
|
||||
padding: theme.spacing(1, 2, 1, 4),
|
||||
}),
|
||||
bodyNested: css({
|
||||
position: 'relative',
|
||||
paddingRight: 0,
|
||||
|
||||
const getOptionGroupStorageKey = (id: string): string => `${PANEL_EDITOR_UI_STATE_STORAGE_KEY}.optionGroup[${id}]`;
|
||||
'&:before': {
|
||||
content: "''",
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: '8px',
|
||||
width: '1px',
|
||||
height: '100%',
|
||||
background: theme.colors.border.weak,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const getOptionGroupStorageKey = (id: string) => `${PANEL_EDITOR_UI_STATE_STORAGE_KEY}.optionGroup[${id}]`;
|
||||
|
@ -15,10 +15,10 @@ export interface OptionsPaneCategoryDescriptorProps {
|
||||
customRender?: () => React.ReactNode;
|
||||
sandboxId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is not a real React component but an intermediary to enable deep option search without traversing a React node tree.
|
||||
*/
|
||||
|
||||
export class OptionsPaneCategoryDescriptor {
|
||||
items: OptionsPaneItemDescriptor[] = [];
|
||||
categories: OptionsPaneCategoryDescriptor[] = [];
|
||||
@ -41,14 +41,14 @@ export class OptionsPaneCategoryDescriptor {
|
||||
|
||||
getCategory(name: string): OptionsPaneCategoryDescriptor {
|
||||
let sub = this.categories.find((c) => c.props.id === name);
|
||||
if (sub) {
|
||||
return sub;
|
||||
if (!sub) {
|
||||
sub = new OptionsPaneCategoryDescriptor({
|
||||
title: name,
|
||||
id: name,
|
||||
});
|
||||
this.addCategory(sub);
|
||||
}
|
||||
sub = new OptionsPaneCategoryDescriptor({
|
||||
title: name,
|
||||
id: name,
|
||||
});
|
||||
this.addCategory(sub);
|
||||
|
||||
return sub;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
import { VizPanel } from '@grafana/scenes';
|
||||
import { DataLinksInlineEditor, Input, RadioButtonGroup, Select, Switch, TextArea } from '@grafana/ui';
|
||||
import { getPanelLinksVariableSuggestions } from 'app/features/panel/panellinks/link_srv';
|
||||
|
||||
@ -171,3 +172,150 @@ export function getPanelFrameCategory(props: OptionPaneRenderProps): OptionsPane
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function getPanelFrameCategory2(panel: VizPanel): OptionsPaneCategoryDescriptor {
|
||||
const descriptor = new OptionsPaneCategoryDescriptor({
|
||||
title: 'Panel options',
|
||||
id: 'Panel options',
|
||||
isOpenDefault: true,
|
||||
});
|
||||
|
||||
return descriptor
|
||||
.addItem(
|
||||
new OptionsPaneItemDescriptor({
|
||||
title: 'Title',
|
||||
value: panel.state.title,
|
||||
popularRank: 1,
|
||||
render: function renderTitle() {
|
||||
return (
|
||||
<Input
|
||||
id="PanelFrameTitle"
|
||||
defaultValue={panel.state.title}
|
||||
onBlur={(e) => panel.setState({ title: e.currentTarget.value })}
|
||||
/>
|
||||
);
|
||||
},
|
||||
// addon: config.featureToggles.dashgpt && <GenAIPanelTitleButton onGenerate={setPanelTitle} panel={panel} />,
|
||||
})
|
||||
)
|
||||
.addItem(
|
||||
new OptionsPaneItemDescriptor({
|
||||
title: 'Description',
|
||||
description: panel.state.description,
|
||||
value: panel.state.description,
|
||||
render: function renderDescription() {
|
||||
return (
|
||||
<TextArea
|
||||
id="description-text-area"
|
||||
defaultValue={panel.state.description}
|
||||
onBlur={(e) => panel.setState({ description: e.currentTarget.value })}
|
||||
/>
|
||||
);
|
||||
},
|
||||
// addon: config.featureToggles.dashgpt && (
|
||||
// <GenAIPanelDescriptionButton onGenerate={setPanelDescription} panel={panel} />
|
||||
// ),
|
||||
})
|
||||
)
|
||||
.addItem(
|
||||
new OptionsPaneItemDescriptor({
|
||||
title: 'Transparent background',
|
||||
render: function renderTransparent() {
|
||||
return (
|
||||
<Switch
|
||||
value={panel.state.displayMode === 'transparent'}
|
||||
id="transparent-background"
|
||||
onChange={() => {
|
||||
panel.setState({
|
||||
displayMode: panel.state.displayMode === 'transparent' ? 'default' : 'transparent',
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
})
|
||||
);
|
||||
// .addCategory(
|
||||
// new OptionsPaneCategoryDescriptor({
|
||||
// title: 'Panel links',
|
||||
// id: 'Panel links',
|
||||
// isOpenDefault: false,
|
||||
// itemsCount: panel.state.links?.length,
|
||||
// }).addItem(
|
||||
// new OptionsPaneItemDescriptor({
|
||||
// title: 'Panel links',
|
||||
// render: function renderLinks() {
|
||||
// return (
|
||||
// <DataLinksInlineEditor
|
||||
// links={panel.links}
|
||||
// onChange={(links) => onPanelConfigChange('links', links)}
|
||||
// getSuggestions={getPanelLinksVariableSuggestions}
|
||||
// data={[]}
|
||||
// />
|
||||
// );
|
||||
// },
|
||||
// })
|
||||
// )
|
||||
// )
|
||||
// .addCategory(
|
||||
// new OptionsPaneCategoryDescriptor({
|
||||
// title: 'Repeat options',
|
||||
// id: 'Repeat options',
|
||||
// isOpenDefault: false,
|
||||
// })
|
||||
// .addItem(
|
||||
// new OptionsPaneItemDescriptor({
|
||||
// title: 'Repeat by variable',
|
||||
// description:
|
||||
// 'Repeat this panel for each value in the selected variable. This is not visible while in edit mode. You need to go back to dashboard and then update the variable or reload the dashboard.',
|
||||
// render: function renderRepeatOptions() {
|
||||
// return (
|
||||
// <RepeatRowSelect
|
||||
// id="repeat-by-variable-select"
|
||||
// repeat={panel.repeat}
|
||||
// onChange={(value?: string) => {
|
||||
// onPanelConfigChange('repeat', value);
|
||||
// }}
|
||||
// />
|
||||
// );
|
||||
// },
|
||||
// })
|
||||
// )
|
||||
// .addItem(
|
||||
// new OptionsPaneItemDescriptor({
|
||||
// title: 'Repeat direction',
|
||||
// showIf: () => !!panel.repeat,
|
||||
// render: function renderRepeatOptions() {
|
||||
// const directionOptions = [
|
||||
// { label: 'Horizontal', value: 'h' },
|
||||
// { label: 'Vertical', value: 'v' },
|
||||
// ];
|
||||
|
||||
// return (
|
||||
// <RadioButtonGroup
|
||||
// options={directionOptions}
|
||||
// value={panel.repeatDirection || 'h'}
|
||||
// onChange={(value) => onPanelConfigChange('repeatDirection', value)}
|
||||
// />
|
||||
// );
|
||||
// },
|
||||
// })
|
||||
// )
|
||||
// .addItem(
|
||||
// new OptionsPaneItemDescriptor({
|
||||
// title: 'Max per row',
|
||||
// showIf: () => Boolean(panel.repeat && panel.repeatDirection === 'h'),
|
||||
// render: function renderOption() {
|
||||
// const maxPerRowOptions = [2, 3, 4, 6, 8, 12].map((value) => ({ label: value.toString(), value }));
|
||||
// return (
|
||||
// <Select
|
||||
// options={maxPerRowOptions}
|
||||
// value={panel.maxPerRow}
|
||||
// onChange={(value) => onPanelConfigChange('maxPerRow', value.value)}
|
||||
// />
|
||||
// );
|
||||
// },
|
||||
// })
|
||||
// )
|
||||
// );
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
EventBus,
|
||||
InterpolateFunction,
|
||||
PanelData,
|
||||
PanelPlugin,
|
||||
StandardEditorContext,
|
||||
VariableSuggestionsScope,
|
||||
} from '@grafana/data';
|
||||
@ -14,6 +15,7 @@ import {
|
||||
NestedValueAccess,
|
||||
PanelOptionsEditorBuilder,
|
||||
} from '@grafana/data/src/utils/OptionsUIBuilders';
|
||||
import { VizPanel } from '@grafana/scenes';
|
||||
import { getDataLinksVariableSuggestions } from 'app/features/panel/panellinks/link_srv';
|
||||
|
||||
import { OptionsPaneCategoryDescriptor } from './OptionsPaneCategoryDescriptor';
|
||||
@ -146,6 +148,100 @@ export function getVisualizationOptions(props: OptionPaneRenderProps): OptionsPa
|
||||
return Object.values(categoryIndex);
|
||||
}
|
||||
|
||||
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)
|
||||
: !fieldOption.showIf(currentFieldConfig.defaults, data?.series));
|
||||
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)
|
||||
);
|
||||
};
|
||||
|
||||
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
|
||||
*
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
// Mock the store module
|
||||
jest.mock('app/core/store', () => ({
|
||||
exists: jest.fn(),
|
||||
getObject: jest.fn(),
|
||||
getObject: jest.fn((_a, b) => b),
|
||||
setObject: jest.fn(),
|
||||
get: jest.fn(),
|
||||
}));
|
||||
|
Loading…
Reference in New Issue
Block a user