Files
grafana/public/app/features/plugins/extensions/usePluginFunctions.tsx
Dominik Süß 8a8e47fcea PluginExtensions: Added support for sharing functions (#98888)
* feat: add generic plugin extension functions

* updated betterer.

* Fixed type issues after sync with main.

* Remved extensions from datasource and panel.

* Added validation for extension function registry.

* Added tests and validation logic for function extensions registry.

* removed prop already existing on base.

* fixed lint error.

---------

Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com>
2025-02-13 10:18:55 +01:00

83 lines
3.0 KiB
TypeScript

import { useMemo } from 'react';
import { useObservable } from 'react-use';
import { usePluginContext, PluginExtensionFunction, PluginExtensionTypes } from '@grafana/data';
import { UsePluginFunctionsOptions, UsePluginFunctionsResult } from '@grafana/runtime';
import { useAddedFunctionsRegistry } from './ExtensionRegistriesContext';
import * as errors from './errors';
import { log } from './logs/log';
import { useLoadAppPlugins } from './useLoadAppPlugins';
import { generateExtensionId, getExtensionPointPluginDependencies, isGrafanaDevMode } from './utils';
import { isExtensionPointIdValid, isExtensionPointMetaInfoMissing } from './validators';
// Returns an array of component extensions for the given extension point
export function usePluginFunctions<Signature>({
limitPerPlugin,
extensionPointId,
}: UsePluginFunctionsOptions): UsePluginFunctionsResult<Signature> {
const registry = useAddedFunctionsRegistry();
const registryState = useObservable(registry.asObservable());
const pluginContext = usePluginContext();
const deps = getExtensionPointPluginDependencies(extensionPointId);
const { isLoading: isLoadingAppPlugins } = useLoadAppPlugins(deps);
return useMemo(() => {
// For backwards compatibility we don't enable restrictions in production or when the hook is used in core Grafana.
const enableRestrictions = isGrafanaDevMode() && pluginContext;
const results: Array<PluginExtensionFunction<Signature>> = [];
const extensionsByPlugin: Record<string, number> = {};
const pluginId = pluginContext?.meta.id ?? '';
const pointLog = log.child({
pluginId,
extensionPointId,
});
if (enableRestrictions && !isExtensionPointIdValid({ extensionPointId, pluginId })) {
pointLog.error(errors.INVALID_EXTENSION_POINT_ID);
}
if (enableRestrictions && isExtensionPointMetaInfoMissing(extensionPointId, pluginContext)) {
pointLog.error(errors.EXTENSION_POINT_META_INFO_MISSING);
return {
isLoading: false,
functions: [],
};
}
if (isLoadingAppPlugins) {
return {
isLoading: true,
functions: [],
};
}
for (const registryItem of registryState?.[extensionPointId] ?? []) {
const { pluginId } = registryItem;
// Only limit if the `limitPerPlugin` is set
if (limitPerPlugin && extensionsByPlugin[pluginId] >= limitPerPlugin) {
continue;
}
if (extensionsByPlugin[pluginId] === undefined) {
extensionsByPlugin[pluginId] = 0;
}
results.push({
id: generateExtensionId(pluginId, extensionPointId, registryItem.title),
type: PluginExtensionTypes.function,
title: registryItem.title,
description: registryItem.description ?? '',
pluginId: pluginId,
fn: registryItem.fn as Signature,
});
extensionsByPlugin[pluginId] += 1;
}
return {
isLoading: false,
functions: results,
};
}, [extensionPointId, limitPerPlugin, pluginContext, registryState, isLoadingAppPlugins]);
}