grafana/public/app/features/plugins/extensions/usePluginExtensions.tsx
Marcus Andersson 804c726413
PluginExtensions: Make the extensions registry reactive (#83085)
* feat: add a reactive extension registry

Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com>

* feat: add hooks to work with the reactive registry

Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com>

* feat: start using the reactive registry

Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com>

* feat: update the "command palette" extension point to use the hook

* feat: update the "alerting" extension point to use the hooks

Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com>

* feat: update the "explore" extension point to use the hooks

Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com>

* feat: update the "datasources config" extension point to use the hooks

Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com>

* feat: update the "panel menu" extension point to use the hooks

Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com>

* feat: update the "pyroscope datasource" extension point to use the hooks

Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com>

* feat: update the "user profile page" extension point to use the hooks

* chore: update betterer

* fix: update the hooks to not re-render unnecessarily

* chore: remove the old `createPluginExtensionRegistry` impementation

* chore: add "TODO" for `PanelMenuBehaviour` extension point

* feat: update the return value of the hooks to contain a `{ isLoading }` param

* tests: add more tests for the usePluginExtensions() hook

* fix: exclude the cloud-home-app from being non-awaited

* refactor: use uuidv4() for random ID generation (for the registry object)

* fix: linting issue

* feat: use the hooks for the new alerting extension point

* feat: use `useMemo()` for `AlertInstanceAction` extension point context

---------

Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com>
2024-04-24 09:33:16 +02:00

55 lines
1.9 KiB
TypeScript

import { useObservable } from 'react-use';
import { PluginExtension } from '@grafana/data';
import { GetPluginExtensionsOptions, UsePluginExtensionsResult } from '@grafana/runtime';
import { getPluginExtensions } from './getPluginExtensions';
import { ReactivePluginExtensionsRegistry } from './reactivePluginExtensionRegistry';
export function createPluginExtensionsHook(extensionsRegistry: ReactivePluginExtensionsRegistry) {
const observableRegistry = extensionsRegistry.asObservable();
const cache: {
id: string;
extensions: Record<string, { context: GetPluginExtensionsOptions['context']; extensions: PluginExtension[] }>;
} = {
id: '',
extensions: {},
};
return function usePluginExtensions(options: GetPluginExtensionsOptions): UsePluginExtensionsResult<PluginExtension> {
const registry = useObservable(observableRegistry);
if (!registry) {
return { extensions: [], isLoading: false };
}
if (registry.id !== cache.id) {
cache.id = registry.id;
cache.extensions = {};
}
// `getPluginExtensions` will return a new array of objects even if it is called with the same options, as it always constructing a frozen objects.
// Due to this we are caching the result of `getPluginExtensions` to avoid unnecessary re-renders for components that are using this hook.
// (NOTE: we are only checking referential equality of `context` object, so it is important to not mutate the object passed to this hook.)
const key = `${options.extensionPointId}-${options.limitPerPlugin}`;
if (cache.extensions[key] && cache.extensions[key].context === options.context) {
return {
extensions: cache.extensions[key].extensions,
isLoading: false,
};
}
const { extensions } = getPluginExtensions({ ...options, registry });
cache.extensions[key] = {
context: options.context,
extensions,
};
return {
extensions,
isLoading: false,
};
};
}