mirror of
https://github.com/grafana/grafana.git
synced 2025-08-01 01:31:50 +08:00

* feat(extensions): don't allow core grafana extension point ids in plugins * feat(extensions): log more specific errors if extension point id validation fails * chore: move the ExtensionSidebar ext. point id to grafana-data * review: remove type assertion
127 lines
4.3 KiB
TypeScript
127 lines
4.3 KiB
TypeScript
import { isString } from 'lodash';
|
|
import { useMemo } from 'react';
|
|
import { useObservable } from 'react-use';
|
|
|
|
import { PluginExtensionLink, PluginExtensionTypes, usePluginContext } from '@grafana/data';
|
|
import { UsePluginLinksOptions, UsePluginLinksResult } from '@grafana/runtime';
|
|
|
|
import { useAddedLinksRegistry } from './ExtensionRegistriesContext';
|
|
import * as errors from './errors';
|
|
import { log } from './logs/log';
|
|
import { useLoadAppPlugins } from './useLoadAppPlugins';
|
|
import {
|
|
generateExtensionId,
|
|
getExtensionPointPluginDependencies,
|
|
getLinkExtensionOnClick,
|
|
getLinkExtensionOverrides,
|
|
getLinkExtensionPathWithTracking,
|
|
getReadOnlyProxy,
|
|
isGrafanaDevMode,
|
|
} from './utils';
|
|
import { isExtensionPointIdValid, isExtensionPointMetaInfoMissing } from './validators';
|
|
|
|
// Returns an array of component extensions for the given extension point
|
|
export function usePluginLinks({
|
|
limitPerPlugin,
|
|
extensionPointId,
|
|
context,
|
|
}: UsePluginLinksOptions): UsePluginLinksResult {
|
|
const registry = useAddedLinksRegistry();
|
|
const pluginContext = usePluginContext();
|
|
const registryState = useObservable(registry.asObservable());
|
|
const { isLoading: isLoadingAppPlugins } = useLoadAppPlugins(getExtensionPointPluginDependencies(extensionPointId));
|
|
|
|
return useMemo(() => {
|
|
const isInsidePlugin = Boolean(pluginContext);
|
|
const pluginId = pluginContext?.meta.id ?? '';
|
|
const pointLog = log.child({
|
|
pluginId,
|
|
extensionPointId,
|
|
});
|
|
|
|
if (isGrafanaDevMode() && !isExtensionPointIdValid({ extensionPointId, pluginId, isInsidePlugin, log: pointLog })) {
|
|
return {
|
|
isLoading: false,
|
|
links: [],
|
|
};
|
|
}
|
|
|
|
if (isGrafanaDevMode() && pluginContext && isExtensionPointMetaInfoMissing(extensionPointId, pluginContext)) {
|
|
pointLog.error(errors.EXTENSION_POINT_META_INFO_MISSING);
|
|
return {
|
|
isLoading: false,
|
|
links: [],
|
|
};
|
|
}
|
|
|
|
if (isLoadingAppPlugins) {
|
|
return {
|
|
isLoading: true,
|
|
links: [],
|
|
};
|
|
}
|
|
|
|
if (!registryState || !registryState[extensionPointId]) {
|
|
return {
|
|
isLoading: false,
|
|
links: [],
|
|
};
|
|
}
|
|
|
|
const frozenContext = context ? getReadOnlyProxy(context) : {};
|
|
const extensions: PluginExtensionLink[] = [];
|
|
const extensionsByPlugin: Record<string, number> = {};
|
|
|
|
for (const addedLink of registryState[extensionPointId] ?? []) {
|
|
const { pluginId } = addedLink;
|
|
const linkLog = pointLog.child({
|
|
path: addedLink.path ?? '',
|
|
title: addedLink.title,
|
|
description: addedLink.description ?? '',
|
|
onClick: typeof addedLink.onClick,
|
|
});
|
|
|
|
// Only limit if the `limitPerPlugin` is set
|
|
if (limitPerPlugin && extensionsByPlugin[pluginId] >= limitPerPlugin) {
|
|
linkLog.debug(`Skipping link extension from plugin "${pluginId}". Reason: Limit reached.`);
|
|
continue;
|
|
}
|
|
|
|
if (extensionsByPlugin[pluginId] === undefined) {
|
|
extensionsByPlugin[pluginId] = 0;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
return {
|
|
isLoading: false,
|
|
links: extensions,
|
|
};
|
|
}, [context, extensionPointId, limitPerPlugin, registryState, pluginContext, isLoadingAppPlugins]);
|
|
}
|