mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Plugin Extensions: Custom limits for extensions-per-plugin (#69430)
* feat(plugins): remove global limit on extensions per placement * feat: add a way to limit extension per plugin at the getter * test(plugins): fix failing getPluginExtensions test * refactor(panelmenu): put back limit of 2 extensions per plugin * tests: add a test for checking all extensions are returned by default --------- Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com>
This commit is contained in:
parent
d25cea90b0
commit
7b2bd48677
@ -5,9 +5,11 @@ import { isPluginExtensionComponent, isPluginExtensionLink } from './utils';
|
||||
export type GetPluginExtensions<T = PluginExtension> = ({
|
||||
extensionPointId,
|
||||
context,
|
||||
limitPerPlugin,
|
||||
}: {
|
||||
extensionPointId: string;
|
||||
context?: object | Record<string | symbol, unknown>;
|
||||
limitPerPlugin?: number;
|
||||
}) => {
|
||||
extensions: T[];
|
||||
};
|
||||
|
@ -277,6 +277,7 @@ export function getPanelMenu(
|
||||
const { extensions } = getPluginExtensions({
|
||||
extensionPointId: PluginExtensionPoints.DashboardPanelMenu,
|
||||
context: createExtensionContext(panel, dashboard),
|
||||
limitPerPlugin: 2,
|
||||
});
|
||||
|
||||
if (extensions.length > 0 && !panel.isEditing) {
|
||||
|
@ -1 +0,0 @@
|
||||
export const MAX_EXTENSIONS_PER_POINT = 2;
|
@ -63,33 +63,6 @@ describe('createRegistry()', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should register maximum 2 extensions / plugin / placement', () => {
|
||||
const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link1, link1, link1] }]);
|
||||
|
||||
expect(Object.getOwnPropertyNames(registry)).toEqual([placement1]);
|
||||
|
||||
// Placement 1
|
||||
expect(registry[placement1]).toHaveLength(2);
|
||||
expect(registry[placement1]).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
pluginId,
|
||||
config: {
|
||||
...link1,
|
||||
configure: expect.any(Function),
|
||||
},
|
||||
}),
|
||||
expect.objectContaining({
|
||||
pluginId,
|
||||
config: {
|
||||
...link1,
|
||||
configure: expect.any(Function),
|
||||
},
|
||||
}),
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should not register link extensions with invalid path configured', () => {
|
||||
const registry = createPluginExtensionRegistry([
|
||||
{ pluginId, extensionConfigs: [{ ...link1, path: 'invalid-path' }, link2] },
|
||||
|
@ -1,14 +1,11 @@
|
||||
import type { PluginPreloadResult } from '../pluginPreloader';
|
||||
|
||||
import { MAX_EXTENSIONS_PER_POINT } from './constants';
|
||||
import { ExtensionsPerPlugin } from './extensionsPerPlugin';
|
||||
import type { PluginExtensionRegistryItem, PluginExtensionRegistry } from './types';
|
||||
import { deepFreeze, logWarning } from './utils';
|
||||
import { isPluginExtensionConfigValid } from './validators';
|
||||
|
||||
export function createPluginExtensionRegistry(pluginPreloadResults: PluginPreloadResult[]): PluginExtensionRegistry {
|
||||
const registry: PluginExtensionRegistry = {};
|
||||
const extensionsPerPlugin = new ExtensionsPerPlugin();
|
||||
|
||||
for (const { pluginId, extensionConfigs, error } of pluginPreloadResults) {
|
||||
if (error) {
|
||||
@ -19,13 +16,6 @@ export function createPluginExtensionRegistry(pluginPreloadResults: PluginPreloa
|
||||
for (const extensionConfig of extensionConfigs) {
|
||||
const { extensionPointId } = extensionConfig;
|
||||
|
||||
if (!extensionsPerPlugin.allowedToAdd(extensionConfig)) {
|
||||
logWarning(
|
||||
`"${pluginId}" plugin has reached the limit of ${MAX_EXTENSIONS_PER_POINT} for "${extensionPointId}", skip registering extension "${extensionConfig.title}".`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!extensionConfig || !isPluginExtensionConfigValid(pluginId, extensionConfig)) {
|
||||
continue;
|
||||
}
|
||||
|
@ -1,33 +0,0 @@
|
||||
import type { PluginExtensionConfig } from '@grafana/data';
|
||||
|
||||
import { MAX_EXTENSIONS_PER_POINT } from './constants';
|
||||
|
||||
export class ExtensionsPerPlugin {
|
||||
private extensionsByExtensionPoint: Record<string, string[]> = {};
|
||||
|
||||
allowedToAdd({ extensionPointId, title }: PluginExtensionConfig): boolean {
|
||||
if (this.countByExtensionPoint(extensionPointId) >= MAX_EXTENSIONS_PER_POINT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.addExtensionToExtensionPoint(extensionPointId, title);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
addExtensionToExtensionPoint(extensionPointId: string, extensionTitle: string) {
|
||||
if (!this.extensionsByExtensionPoint[extensionPointId]) {
|
||||
this.extensionsByExtensionPoint[extensionPointId] = [];
|
||||
}
|
||||
|
||||
this.extensionsByExtensionPoint[extensionPointId].push(extensionTitle);
|
||||
}
|
||||
|
||||
countByExtensionPoint(extensionPointId: string) {
|
||||
return this.extensionsByExtensionPoint[extensionPointId]?.length ?? 0;
|
||||
}
|
||||
|
||||
getExtensionTitlesByExtensionPoint(extensionPointId: string) {
|
||||
return this.extensionsByExtensionPoint[extensionPointId];
|
||||
}
|
||||
}
|
@ -48,6 +48,52 @@ describe('getPluginExtensions()', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('should not limit the number of extensions per plugin by default', () => {
|
||||
// Registering 3 extensions for the same plugin for the same placement
|
||||
const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link1, link1, link1, link2] }]);
|
||||
const { extensions } = getPluginExtensions({ registry, extensionPointId: extensionPoint1 });
|
||||
|
||||
expect(extensions).toHaveLength(3);
|
||||
expect(extensions[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
pluginId,
|
||||
type: PluginExtensionTypes.link,
|
||||
title: link1.title,
|
||||
description: link1.description,
|
||||
path: link1.path,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('should be possible to limit the number of extensions per plugin for a given placement', () => {
|
||||
const registry = createPluginExtensionRegistry([
|
||||
{ pluginId, extensionConfigs: [link1, link1, link1, link2] },
|
||||
{
|
||||
pluginId: 'my-plugin',
|
||||
extensionConfigs: [
|
||||
{ ...link1, path: '/a/my-plugin/declare-incident' },
|
||||
{ ...link1, path: '/a/my-plugin/declare-incident' },
|
||||
{ ...link1, path: '/a/my-plugin/declare-incident' },
|
||||
{ ...link2, path: '/a/my-plugin/declare-incident' },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
// Limit to 1 extension per plugin
|
||||
const { extensions } = getPluginExtensions({ registry, extensionPointId: extensionPoint1, limitPerPlugin: 1 });
|
||||
|
||||
expect(extensions).toHaveLength(2);
|
||||
expect(extensions[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
pluginId,
|
||||
type: PluginExtensionTypes.link,
|
||||
title: link1.title,
|
||||
description: link1.description,
|
||||
path: link1.path,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('should return with an empty list if there are no extensions registered for a placement yet', () => {
|
||||
const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link1, link2] }]);
|
||||
const { extensions } = getPluginExtensions({ registry, extensionPointId: 'placement-with-no-extensions' });
|
||||
|
@ -26,23 +26,36 @@ import {
|
||||
type GetExtensions = ({
|
||||
context,
|
||||
extensionPointId,
|
||||
limitPerPlugin,
|
||||
registry,
|
||||
}: {
|
||||
context?: object | Record<string | symbol, unknown>;
|
||||
extensionPointId: string;
|
||||
limitPerPlugin?: number;
|
||||
registry: PluginExtensionRegistry;
|
||||
}) => { extensions: PluginExtension[] };
|
||||
|
||||
// Returns with a list of plugin extensions for the given extension point
|
||||
export const getPluginExtensions: GetExtensions = ({ context, extensionPointId, registry }) => {
|
||||
export const getPluginExtensions: GetExtensions = ({ context, extensionPointId, limitPerPlugin, registry }) => {
|
||||
const frozenContext = context ? getReadOnlyProxy(context) : {};
|
||||
const registryItems = registry[extensionPointId] ?? [];
|
||||
// We don't return the extensions separated by type, because in that case it would be much harder to define a sort-order for them.
|
||||
const extensions: PluginExtension[] = [];
|
||||
const extensionsByPlugin: Record<string, number> = {};
|
||||
|
||||
for (const registryItem of registryItems) {
|
||||
try {
|
||||
const extensionConfig = registryItem.config;
|
||||
const { pluginId } = registryItem;
|
||||
|
||||
// Only limit if the `limitPerPlugin` is set
|
||||
if (limitPerPlugin && extensionsByPlugin[pluginId] >= limitPerPlugin) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (extensionsByPlugin[pluginId] === undefined) {
|
||||
extensionsByPlugin[pluginId] = 0;
|
||||
}
|
||||
|
||||
// LINK
|
||||
if (isPluginExtensionLinkConfig(extensionConfig)) {
|
||||
@ -67,6 +80,7 @@ export const getPluginExtensions: GetExtensions = ({ context, extensionPointId,
|
||||
};
|
||||
|
||||
extensions.push(extension);
|
||||
extensionsByPlugin[pluginId] += 1;
|
||||
}
|
||||
|
||||
// COMPONENT
|
||||
@ -84,6 +98,7 @@ export const getPluginExtensions: GetExtensions = ({ context, extensionPointId,
|
||||
};
|
||||
|
||||
extensions.push(extension);
|
||||
extensionsByPlugin[pluginId] += 1;
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
|
Loading…
Reference in New Issue
Block a user