2025-04-01 10:38:02 +09:00

156 lines
5.5 KiB
TypeScript

import { isString } from 'lodash';
import {
PluginExtensionTypes,
type PluginExtension,
type PluginExtensionLink,
type PluginExtensionComponent,
} from '@grafana/data';
import { GetPluginExtensions } from '@grafana/runtime';
import { log } from './logs/log';
import { AddedComponentRegistryItem } from './registry/AddedComponentsRegistry';
import { AddedLinkRegistryItem } from './registry/AddedLinksRegistry';
import { RegistryType } from './registry/Registry';
import type { PluginExtensionRegistries } from './registry/types';
import {
getReadOnlyProxy,
generateExtensionId,
wrapWithPluginContext,
getLinkExtensionOnClick,
getLinkExtensionOverrides,
getLinkExtensionPathWithTracking,
} from './utils';
type GetExtensions = ({
context,
extensionPointId,
limitPerPlugin,
addedLinksRegistry,
addedComponentsRegistry,
}: {
context?: object | Record<string | symbol, unknown>;
extensionPointId: string;
limitPerPlugin?: number;
addedComponentsRegistry: RegistryType<AddedComponentRegistryItem[]> | undefined;
addedLinksRegistry: RegistryType<AddedLinkRegistryItem[]> | undefined;
}) => { extensions: PluginExtension[] };
export function createPluginExtensionsGetter(registries: PluginExtensionRegistries): GetPluginExtensions {
let addedComponentsRegistry: RegistryType<AddedComponentRegistryItem[]>;
let addedLinksRegistry: RegistryType<Array<AddedLinkRegistryItem<object>>>;
// Create registry subscriptions to keep an copy of the registry state for use in the non-async
// plugin extensions getter.
registries.addedComponentsRegistry.asObservable().subscribe((componentsRegistry) => {
addedComponentsRegistry = componentsRegistry;
});
registries.addedLinksRegistry.asObservable().subscribe((linksRegistry) => {
addedLinksRegistry = linksRegistry;
});
return (options) => getPluginExtensions({ ...options, addedComponentsRegistry, addedLinksRegistry });
}
// Returns with a list of plugin extensions for the given extension point
export const getPluginExtensions: GetExtensions = ({
context,
extensionPointId,
limitPerPlugin,
addedLinksRegistry,
addedComponentsRegistry,
}) => {
const frozenContext = context ? getReadOnlyProxy(context) : {};
// 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 addedLink of addedLinksRegistry?.[extensionPointId] ?? []) {
try {
const { pluginId } = addedLink;
// Only limit if the `limitPerPlugin` is set
if (limitPerPlugin && extensionsByPlugin[pluginId] >= limitPerPlugin) {
continue;
}
if (extensionsByPlugin[pluginId] === undefined) {
extensionsByPlugin[pluginId] = 0;
}
const linkLog = log.child({
pluginId,
extensionPointId,
path: addedLink.path ?? '',
title: addedLink.title,
description: addedLink.description ?? '',
onClick: typeof addedLink.onClick,
});
// Run the configure() function with the current context, and apply the ovverides
const overrides = getLinkExtensionOverrides(pluginId, addedLink, linkLog, frozenContext);
// configure() returned an `undefined` -> hide the extension
if (addedLink.configure && overrides === undefined) {
continue;
}
const path = overrides?.path || addedLink.path;
const extension: PluginExtensionLink = {
id: generateExtensionId(pluginId, extensionPointId, addedLink.title),
type: PluginExtensionTypes.link,
pluginId: pluginId,
onClick: getLinkExtensionOnClick(pluginId, extensionPointId, addedLink, linkLog, frozenContext),
// Configurable properties
icon: overrides?.icon || addedLink.icon,
title: overrides?.title || addedLink.title,
description: overrides?.description || addedLink.description || '',
path: isString(path) ? getLinkExtensionPathWithTracking(pluginId, path, extensionPointId) : undefined,
category: overrides?.category || addedLink.category,
};
extensions.push(extension);
extensionsByPlugin[pluginId] += 1;
} catch (error) {
if (error instanceof Error) {
log.error(error.message, {
stack: error.stack ?? '',
message: error.message,
});
}
}
}
const addedComponents = addedComponentsRegistry?.[extensionPointId] ?? [];
for (const addedComponent of addedComponents) {
// Only limit if the `limitPerPlugin` is set
if (limitPerPlugin && extensionsByPlugin[addedComponent.pluginId] >= limitPerPlugin) {
continue;
}
if (extensionsByPlugin[addedComponent.pluginId] === undefined) {
extensionsByPlugin[addedComponent.pluginId] = 0;
}
const componentLog = log.child({
title: addedComponent.title,
description: addedComponent.description ?? '',
pluginId: addedComponent.pluginId,
});
const extension: PluginExtensionComponent = {
id: generateExtensionId(addedComponent.pluginId, extensionPointId, addedComponent.title),
type: PluginExtensionTypes.component,
pluginId: addedComponent.pluginId,
title: addedComponent.title,
description: addedComponent.description ?? '',
component: wrapWithPluginContext(addedComponent.pluginId, addedComponent.component, componentLog),
};
extensions.push(extension);
extensionsByPlugin[addedComponent.pluginId] += 1;
}
return { extensions };
};