mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
PluginExtensions: Add category to link extensions (#71074)
* adding a panel menu group item to UI extensions. * draft * Fixed so we support groups in the old panel menu. * added proper styling. * Made tests green and small refactor. * Added tests for panel menu category. * Will truncate the extensions category to 25 chars. * added test for truncating category.
This commit is contained in:
parent
689d9ed430
commit
57a54fc38a
@ -163,7 +163,7 @@ export interface PanelOptionsEditorConfig<TOptions, TSettings = any, TValue = an
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export interface PanelMenuItem {
|
export interface PanelMenuItem {
|
||||||
type?: 'submenu' | 'divider';
|
type?: 'submenu' | 'divider' | 'group';
|
||||||
text: string;
|
text: string;
|
||||||
iconClassName?: IconName;
|
iconClassName?: IconName;
|
||||||
onClick?: (event: React.MouseEvent<any>) => void;
|
onClick?: (event: React.MouseEvent<any>) => void;
|
||||||
|
@ -29,6 +29,7 @@ export type PluginExtensionLink = PluginExtensionBase & {
|
|||||||
path?: string;
|
path?: string;
|
||||||
onClick?: (event?: React.MouseEvent) => void;
|
onClick?: (event?: React.MouseEvent) => void;
|
||||||
icon?: IconName;
|
icon?: IconName;
|
||||||
|
category?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PluginExtensionComponent = PluginExtensionBase & {
|
export type PluginExtensionComponent = PluginExtensionBase & {
|
||||||
@ -65,11 +66,15 @@ export type PluginExtensionLinkConfig<Context extends object = object> = {
|
|||||||
path: string;
|
path: string;
|
||||||
onClick: (event: React.MouseEvent | undefined, helpers: PluginExtensionEventHelpers<Context>) => void;
|
onClick: (event: React.MouseEvent | undefined, helpers: PluginExtensionEventHelpers<Context>) => void;
|
||||||
icon: IconName;
|
icon: IconName;
|
||||||
|
category: string;
|
||||||
}>
|
}>
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
// (Optional) A icon that can be displayed in the ui for the extension option.
|
// (Optional) A icon that can be displayed in the ui for the extension option.
|
||||||
icon?: IconName;
|
icon?: IconName;
|
||||||
|
|
||||||
|
// (Optional) A category to be used when grouping the options in the ui
|
||||||
|
category?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PluginExtensionComponentConfig<Context extends object = object> = {
|
export type PluginExtensionComponentConfig<Context extends object = object> = {
|
||||||
|
@ -44,18 +44,48 @@ export class PanelHeaderMenu extends PureComponent<Props> {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className={classnames('panel-menu-container', 'dropdown', 'open', this.props.className)}>
|
<div className={classnames('panel-menu-container', 'dropdown', 'open', this.props.className)}>
|
||||||
{this.renderItems(this.props.items)}
|
{this.renderItems(flattenGroups(this.props.items))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function flattenGroups(items: PanelMenuItem[]): PanelMenuItem[] {
|
||||||
|
return items.reduce((all: PanelMenuItem[], item) => {
|
||||||
|
if (Array.isArray(item.subMenu) && item.type === 'submenu') {
|
||||||
|
all.push({
|
||||||
|
...item,
|
||||||
|
subMenu: flattenGroups(item.subMenu),
|
||||||
|
});
|
||||||
|
return all;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(item.subMenu) && item.type === 'group') {
|
||||||
|
const { subMenu, ...rest } = item;
|
||||||
|
all.push(rest);
|
||||||
|
all.push.apply(all, flattenGroups(subMenu));
|
||||||
|
return all;
|
||||||
|
}
|
||||||
|
|
||||||
|
all.push(item);
|
||||||
|
return all;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
export function PanelHeaderMenuNew({ items }: Props) {
|
export function PanelHeaderMenuNew({ items }: Props) {
|
||||||
const renderItems = (items: PanelMenuItem[]) => {
|
const renderItems = (items: PanelMenuItem[]) => {
|
||||||
return items.map((item) =>
|
return items.map((item) => {
|
||||||
item.type === 'divider' ? (
|
switch (item.type) {
|
||||||
<Menu.Divider key={item.text} />
|
case 'divider':
|
||||||
) : (
|
return <Menu.Divider key={item.text} />;
|
||||||
|
case 'group':
|
||||||
|
return (
|
||||||
|
<Menu.Group key={item.text} label={item.text}>
|
||||||
|
{item.subMenu ? renderItems(item.subMenu) : undefined}
|
||||||
|
</Menu.Group>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return (
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
key={item.text}
|
key={item.text}
|
||||||
label={item.text}
|
label={item.text}
|
||||||
@ -66,8 +96,9 @@ export function PanelHeaderMenuNew({ items }: Props) {
|
|||||||
shortcut={item.shortcut}
|
shortcut={item.shortcut}
|
||||||
testId={selectors.components.Panels.Panel.menuItems(item.text)}
|
testId={selectors.components.Panels.Panel.menuItems(item.text)}
|
||||||
/>
|
/>
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return <Menu>{renderItems(items)}</Menu>;
|
return <Menu>{renderItems(items)}</Menu>;
|
||||||
|
@ -12,14 +12,20 @@ interface Props {
|
|||||||
export const PanelHeaderMenuItem = (props: Props & PanelMenuItem) => {
|
export const PanelHeaderMenuItem = (props: Props & PanelMenuItem) => {
|
||||||
const [ref, setRef] = useState<HTMLLIElement | null>(null);
|
const [ref, setRef] = useState<HTMLLIElement | null>(null);
|
||||||
const isSubMenu = props.type === 'submenu';
|
const isSubMenu = props.type === 'submenu';
|
||||||
const isDivider = props.type === 'divider';
|
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
const icon = props.iconClassName ? toIconName(props.iconClassName) : undefined;
|
const icon = props.iconClassName ? toIconName(props.iconClassName) : undefined;
|
||||||
|
|
||||||
return isDivider ? (
|
switch (props.type) {
|
||||||
<li className="divider" />
|
case 'divider':
|
||||||
) : (
|
return <li className="divider" />;
|
||||||
|
case 'group':
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
<span className={styles.groupLabel}>{props.text}</span>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return (
|
||||||
<li
|
<li
|
||||||
className={isSubMenu ? `dropdown-submenu ${getDropdownLocationCssClass(ref)}` : undefined}
|
className={isSubMenu ? `dropdown-submenu ${getDropdownLocationCssClass(ref)}` : undefined}
|
||||||
ref={setRef}
|
ref={setRef}
|
||||||
@ -41,6 +47,7 @@ export const PanelHeaderMenuItem = (props: Props & PanelMenuItem) => {
|
|||||||
{props.children}
|
{props.children}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function getDropdownLocationCssClass(element: HTMLElement | null) {
|
function getDropdownLocationCssClass(element: HTMLElement | null) {
|
||||||
@ -76,5 +83,10 @@ function getStyles(theme: GrafanaTheme2) {
|
|||||||
right: theme.spacing(0.5),
|
right: theme.spacing(0.5),
|
||||||
color: theme.colors.text.secondary,
|
color: theme.colors.text.secondary,
|
||||||
}),
|
}),
|
||||||
|
groupLabel: css({
|
||||||
|
color: theme.colors.text.secondary,
|
||||||
|
fontSize: theme.typography.size.sm,
|
||||||
|
padding: theme.spacing(0.5, 1),
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
PluginExtensionTypes,
|
PluginExtensionTypes,
|
||||||
toDataFrame,
|
toDataFrame,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { AngularComponent, getPluginExtensions } from '@grafana/runtime';
|
import { AngularComponent, getPluginLinkExtensions } from '@grafana/runtime';
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import * as actions from 'app/features/explore/state/main';
|
import * as actions from 'app/features/explore/state/main';
|
||||||
import { setStore } from 'app/store/store';
|
import { setStore } from 'app/store/store';
|
||||||
@ -29,13 +29,15 @@ jest.mock('app/core/services/context_srv', () => ({
|
|||||||
jest.mock('@grafana/runtime', () => ({
|
jest.mock('@grafana/runtime', () => ({
|
||||||
...jest.requireActual('@grafana/runtime'),
|
...jest.requireActual('@grafana/runtime'),
|
||||||
setPluginExtensionGetter: jest.fn(),
|
setPluginExtensionGetter: jest.fn(),
|
||||||
getPluginExtensions: jest.fn(),
|
getPluginLinkExtensions: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const getPluginLinkExtensionsMock = jest.mocked(getPluginLinkExtensions);
|
||||||
|
|
||||||
describe('getPanelMenu()', () => {
|
describe('getPanelMenu()', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(getPluginExtensions as jest.Mock).mockRestore();
|
getPluginLinkExtensionsMock.mockRestore();
|
||||||
(getPluginExtensions as jest.Mock).mockReturnValue({ extensions: [] });
|
getPluginLinkExtensionsMock.mockReturnValue({ extensions: [] });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the correct panel menu items', () => {
|
it('should return the correct panel menu items', () => {
|
||||||
@ -119,9 +121,10 @@ describe('getPanelMenu()', () => {
|
|||||||
|
|
||||||
describe('when extending panel menu from plugins', () => {
|
describe('when extending panel menu from plugins', () => {
|
||||||
it('should contain menu item from link extension', () => {
|
it('should contain menu item from link extension', () => {
|
||||||
(getPluginExtensions as jest.Mock).mockReturnValue({
|
getPluginLinkExtensionsMock.mockReturnValue({
|
||||||
extensions: [
|
extensions: [
|
||||||
{
|
{
|
||||||
|
id: '1',
|
||||||
pluginId: '...',
|
pluginId: '...',
|
||||||
type: PluginExtensionTypes.link,
|
type: PluginExtensionTypes.link,
|
||||||
title: 'Declare incident',
|
title: 'Declare incident',
|
||||||
@ -147,9 +150,10 @@ describe('getPanelMenu()', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should truncate menu item title to 25 chars', () => {
|
it('should truncate menu item title to 25 chars', () => {
|
||||||
(getPluginExtensions as jest.Mock).mockReturnValue({
|
getPluginLinkExtensionsMock.mockReturnValue({
|
||||||
extensions: [
|
extensions: [
|
||||||
{
|
{
|
||||||
|
id: '1',
|
||||||
pluginId: '...',
|
pluginId: '...',
|
||||||
type: PluginExtensionTypes.link,
|
type: PluginExtensionTypes.link,
|
||||||
title: 'Declare incident when pressing this amazing menu item',
|
title: 'Declare incident when pressing this amazing menu item',
|
||||||
@ -177,9 +181,10 @@ describe('getPanelMenu()', () => {
|
|||||||
it('should pass onClick from plugin extension link to menu item', () => {
|
it('should pass onClick from plugin extension link to menu item', () => {
|
||||||
const expectedOnClick = jest.fn();
|
const expectedOnClick = jest.fn();
|
||||||
|
|
||||||
(getPluginExtensions as jest.Mock).mockReturnValue({
|
getPluginLinkExtensionsMock.mockReturnValue({
|
||||||
extensions: [
|
extensions: [
|
||||||
{
|
{
|
||||||
|
id: '1',
|
||||||
pluginId: '...',
|
pluginId: '...',
|
||||||
type: PluginExtensionTypes.link,
|
type: PluginExtensionTypes.link,
|
||||||
title: 'Declare incident when pressing this amazing menu item',
|
title: 'Declare incident when pressing this amazing menu item',
|
||||||
@ -287,7 +292,126 @@ describe('getPanelMenu()', () => {
|
|||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getPluginExtensions).toBeCalledWith(expect.objectContaining({ context }));
|
expect(getPluginLinkExtensionsMock).toBeCalledWith(expect.objectContaining({ context }));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain menu item with category', () => {
|
||||||
|
getPluginLinkExtensionsMock.mockReturnValue({
|
||||||
|
extensions: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
pluginId: '...',
|
||||||
|
type: PluginExtensionTypes.link,
|
||||||
|
title: 'Declare incident',
|
||||||
|
description: 'Declaring an incident in the app',
|
||||||
|
path: '/a/grafana-basic-app/declare-incident',
|
||||||
|
category: 'Incident',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const panel = new PanelModel({});
|
||||||
|
const dashboard = createDashboardModelFixture({});
|
||||||
|
const menuItems = getPanelMenu(dashboard, panel);
|
||||||
|
const extensionsSubMenu = menuItems.find((i) => i.text === 'Extensions')?.subMenu;
|
||||||
|
|
||||||
|
expect(extensionsSubMenu).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
text: 'Incident',
|
||||||
|
subMenu: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
text: 'Declare incident',
|
||||||
|
href: '/a/grafana-basic-app/declare-incident',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should truncate category to 25 chars', () => {
|
||||||
|
getPluginLinkExtensionsMock.mockReturnValue({
|
||||||
|
extensions: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
pluginId: '...',
|
||||||
|
type: PluginExtensionTypes.link,
|
||||||
|
title: 'Declare incident',
|
||||||
|
description: 'Declaring an incident in the app',
|
||||||
|
path: '/a/grafana-basic-app/declare-incident',
|
||||||
|
category: 'Declare incident when pressing this amazing menu item',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const panel = new PanelModel({});
|
||||||
|
const dashboard = createDashboardModelFixture({});
|
||||||
|
const menuItems = getPanelMenu(dashboard, panel);
|
||||||
|
const extensionsSubMenu = menuItems.find((i) => i.text === 'Extensions')?.subMenu;
|
||||||
|
|
||||||
|
expect(extensionsSubMenu).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
text: 'Declare incident when...',
|
||||||
|
subMenu: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
text: 'Declare incident',
|
||||||
|
href: '/a/grafana-basic-app/declare-incident',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain menu item with category and append items without category after divider', () => {
|
||||||
|
getPluginLinkExtensionsMock.mockReturnValue({
|
||||||
|
extensions: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
pluginId: '...',
|
||||||
|
type: PluginExtensionTypes.link,
|
||||||
|
title: 'Declare incident',
|
||||||
|
description: 'Declaring an incident in the app',
|
||||||
|
path: '/a/grafana-basic-app/declare-incident',
|
||||||
|
category: 'Incident',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
pluginId: '...',
|
||||||
|
type: PluginExtensionTypes.link,
|
||||||
|
title: 'Create forecast',
|
||||||
|
description: 'Declaring an incident in the app',
|
||||||
|
path: '/a/grafana-basic-app/declare-incident',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const panel = new PanelModel({});
|
||||||
|
const dashboard = createDashboardModelFixture({});
|
||||||
|
const menuItems = getPanelMenu(dashboard, panel);
|
||||||
|
const extensionsSubMenu = menuItems.find((i) => i.text === 'Extensions')?.subMenu;
|
||||||
|
|
||||||
|
expect(extensionsSubMenu).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
text: 'Incident',
|
||||||
|
subMenu: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
text: 'Declare incident',
|
||||||
|
href: '/a/grafana-basic-app/declare-incident',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
type: 'divider',
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
text: 'Create forecast',
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
import { PanelMenuItem, PluginExtensionPoints, type PluginExtensionPanelContext } from '@grafana/data';
|
|
||||||
import {
|
import {
|
||||||
isPluginExtensionLink,
|
PanelMenuItem,
|
||||||
|
PluginExtensionLink,
|
||||||
|
PluginExtensionPoints,
|
||||||
|
type PluginExtensionPanelContext,
|
||||||
|
} from '@grafana/data';
|
||||||
|
import {
|
||||||
AngularComponent,
|
AngularComponent,
|
||||||
getDataSourceSrv,
|
getDataSourceSrv,
|
||||||
getPluginExtensions,
|
|
||||||
locationService,
|
locationService,
|
||||||
reportInteraction,
|
reportInteraction,
|
||||||
|
getPluginLinkExtensions,
|
||||||
} from '@grafana/runtime';
|
} from '@grafana/runtime';
|
||||||
import { PanelCtrl } from 'app/angular/panel/panel_ctrl';
|
import { PanelCtrl } from 'app/angular/panel/panel_ctrl';
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
@ -275,31 +279,18 @@ export function getPanelMenu(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { extensions } = getPluginExtensions({
|
const { extensions } = getPluginLinkExtensions({
|
||||||
extensionPointId: PluginExtensionPoints.DashboardPanelMenu,
|
extensionPointId: PluginExtensionPoints.DashboardPanelMenu,
|
||||||
context: createExtensionContext(panel, dashboard),
|
context: createExtensionContext(panel, dashboard),
|
||||||
limitPerPlugin: 2,
|
limitPerPlugin: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (extensions.length > 0 && !panel.isEditing) {
|
if (extensions.length > 0 && !panel.isEditing) {
|
||||||
const extensionsMenu: PanelMenuItem[] = [];
|
|
||||||
|
|
||||||
for (const extension of extensions) {
|
|
||||||
if (isPluginExtensionLink(extension)) {
|
|
||||||
extensionsMenu.push({
|
|
||||||
text: truncateTitle(extension.title, 25),
|
|
||||||
href: extension.path,
|
|
||||||
onClick: extension.onClick,
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
menu.push({
|
menu.push({
|
||||||
text: 'Extensions',
|
text: 'Extensions',
|
||||||
iconClassName: 'plug',
|
iconClassName: 'plug',
|
||||||
type: 'submenu',
|
type: 'submenu',
|
||||||
subMenu: extensionsMenu,
|
subMenu: createExtensionSubMenu(extensions),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,3 +335,53 @@ function createExtensionContext(panel: PanelModel, dashboard: DashboardModel): P
|
|||||||
data: panel.getQueryRunner().getLastResult(),
|
data: panel.getQueryRunner().getLastResult(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createExtensionSubMenu(extensions: PluginExtensionLink[]): PanelMenuItem[] {
|
||||||
|
const categorized: Record<string, PanelMenuItem[]> = {};
|
||||||
|
const uncategorized: PanelMenuItem[] = [];
|
||||||
|
|
||||||
|
for (const extension of extensions) {
|
||||||
|
const category = extension.category;
|
||||||
|
|
||||||
|
if (!category) {
|
||||||
|
uncategorized.push({
|
||||||
|
text: truncateTitle(extension.title, 25),
|
||||||
|
href: extension.path,
|
||||||
|
onClick: extension.onClick,
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(categorized[category])) {
|
||||||
|
categorized[category] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
categorized[category].push({
|
||||||
|
text: truncateTitle(extension.title, 25),
|
||||||
|
href: extension.path,
|
||||||
|
onClick: extension.onClick,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const subMenu = Object.keys(categorized).reduce((subMenu: PanelMenuItem[], category) => {
|
||||||
|
subMenu.push({
|
||||||
|
text: truncateTitle(category, 25),
|
||||||
|
type: 'group',
|
||||||
|
subMenu: categorized[category],
|
||||||
|
});
|
||||||
|
return subMenu;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (uncategorized.length > 0) {
|
||||||
|
if (subMenu.length > 0) {
|
||||||
|
subMenu.push({
|
||||||
|
text: 'divider',
|
||||||
|
type: 'divider',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.prototype.push.apply(subMenu, uncategorized);
|
||||||
|
}
|
||||||
|
|
||||||
|
return subMenu;
|
||||||
|
}
|
||||||
|
@ -117,6 +117,7 @@ describe('getPluginExtensions()', () => {
|
|||||||
description: 'Updated description',
|
description: 'Updated description',
|
||||||
path: `/a/${pluginId}/updated-path`,
|
path: `/a/${pluginId}/updated-path`,
|
||||||
icon: 'search',
|
icon: 'search',
|
||||||
|
category: 'Machine Learning',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]);
|
const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]);
|
||||||
@ -130,6 +131,7 @@ describe('getPluginExtensions()', () => {
|
|||||||
expect(extension.description).toBe('Updated description');
|
expect(extension.description).toBe('Updated description');
|
||||||
expect(extension.path).toBe(`/a/${pluginId}/updated-path`);
|
expect(extension.path).toBe(`/a/${pluginId}/updated-path`);
|
||||||
expect(extension.icon).toBe('search');
|
expect(extension.icon).toBe('search');
|
||||||
|
expect(extension.category).toBe('Machine Learning');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should hide the extension if it tries to override not-allowed properties with the configure() function', () => {
|
test('should hide the extension if it tries to override not-allowed properties with the configure() function', () => {
|
||||||
|
@ -78,6 +78,7 @@ export const getPluginExtensions: GetExtensions = ({ context, extensionPointId,
|
|||||||
title: overrides?.title || extensionConfig.title,
|
title: overrides?.title || extensionConfig.title,
|
||||||
description: overrides?.description || extensionConfig.description,
|
description: overrides?.description || extensionConfig.description,
|
||||||
path: overrides?.path || extensionConfig.path,
|
path: overrides?.path || extensionConfig.path,
|
||||||
|
category: overrides?.category || extensionConfig.category,
|
||||||
};
|
};
|
||||||
|
|
||||||
extensions.push(extension);
|
extensions.push(extension);
|
||||||
@ -125,6 +126,7 @@ function getLinkExtensionOverrides(pluginId: string, config: PluginExtensionLink
|
|||||||
description = config.description,
|
description = config.description,
|
||||||
path = config.path,
|
path = config.path,
|
||||||
icon = config.icon,
|
icon = config.icon,
|
||||||
|
category = config.category,
|
||||||
...rest
|
...rest
|
||||||
} = overrides;
|
} = overrides;
|
||||||
|
|
||||||
@ -149,6 +151,7 @@ function getLinkExtensionOverrides(pluginId: string, config: PluginExtensionLink
|
|||||||
description,
|
description,
|
||||||
path,
|
path,
|
||||||
icon,
|
icon,
|
||||||
|
category,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
|
Loading…
Reference in New Issue
Block a user