grafana/public/app/features/plugins/extensions/getPluginExtensions.ts
Marcus Andersson 9719ee9bd3
PluginExtensions: Fixed issue in typings for the onClick link callback (#65978)
* Fixed bug with event being forced.

* Made onClick undefinable in the config as well.

* Fixed some more type issues.
2023-04-05 07:55:44 +02:00

136 lines
4.0 KiB
TypeScript

import {
type PluginExtension,
PluginExtensionTypes,
PluginExtensionLink,
PluginExtensionLinkConfig,
} from '@grafana/data';
import type { PluginExtensionRegistry } from './types';
import { isPluginExtensionLinkConfig, deepFreeze, logWarning, generateExtensionId, getEventHelpers } from './utils';
import { assertIsNotPromise, assertLinkPathIsValid, assertStringProps, isPromise } from './validators';
type GetExtensions = ({
context,
extensionPointId,
registry,
}: {
context?: object | Record<string | symbol, unknown>;
extensionPointId: string;
registry: PluginExtensionRegistry;
}) => { extensions: PluginExtension[] };
// Returns with a list of plugin extensions for the given extension point
export const getPluginExtensions: GetExtensions = ({ context, extensionPointId, registry }) => {
const frozenContext = context ? deepFreeze(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[] = [];
for (const registryItem of registryItems) {
try {
const extensionConfig = registryItem.config;
if (isPluginExtensionLinkConfig(extensionConfig)) {
const overrides = getLinkExtensionOverrides(registryItem.pluginId, extensionConfig, frozenContext);
// Hide (configure() has returned `undefined`)
if (extensionConfig.configure && overrides === undefined) {
continue;
}
const extension: PluginExtensionLink = {
id: generateExtensionId(registryItem.pluginId, extensionConfig),
type: PluginExtensionTypes.link,
pluginId: registryItem.pluginId,
onClick: getLinkExtensionOnClick(extensionConfig, frozenContext),
// Configurable properties
title: overrides?.title || extensionConfig.title,
description: overrides?.description || extensionConfig.description,
path: overrides?.path || extensionConfig.path,
};
extensions.push(extension);
}
} catch (error) {
if (error instanceof Error) {
logWarning(error.message);
}
}
}
return { extensions };
};
function getLinkExtensionOverrides(pluginId: string, config: PluginExtensionLinkConfig, context?: object) {
try {
const overrides = config.configure?.(context);
// Hiding the extension
if (overrides === undefined) {
return undefined;
}
let { title = config.title, description = config.description, path = config.path, ...rest } = overrides;
assertIsNotPromise(
overrides,
`The configure() function for "${config.title}" returned a promise, skipping updates.`
);
path && assertLinkPathIsValid(pluginId, path);
assertStringProps({ title, description }, ['title', 'description']);
if (Object.keys(rest).length > 0) {
throw new Error(
`Invalid extension "${config.title}". Trying to override not-allowed properties: ${Object.keys(rest).join(
', '
)}`
);
}
return {
title,
description,
path,
};
} catch (error) {
if (error instanceof Error) {
logWarning(error.message);
}
// If there is an error, we hide the extension
// (This seems to be safest option in case the extension is doing something wrong.)
return undefined;
}
}
function getLinkExtensionOnClick(
config: PluginExtensionLinkConfig,
context?: object
): ((event?: React.MouseEvent) => void) | undefined {
const { onClick } = config;
if (!onClick) {
return;
}
return function onClickExtensionLink(event?: React.MouseEvent) {
try {
const result = onClick(event, getEventHelpers(context));
if (isPromise(result)) {
result.catch((e) => {
if (e instanceof Error) {
logWarning(e.message);
}
});
}
} catch (error) {
if (error instanceof Error) {
logWarning(error.message);
}
}
};
}