Plugin Extensions: Clean up the deprecated APIs (#102102)
* PanelMenuBehaviour: stop using the deprecated `getPluginLinkExtensions()` API * Wip * grafana-runtime: remove deprecated APIs `usePluginExtensions()`, `usePluginLinkExtensions()` and `usePluginComponentExtensions()` * Wip * Wip * wip * wip * Chore: removed PluginExtensionLinkConfig * Chore: removed PluginExtensionComponentConfig * Chore: fixed grafana-pyroscope-datasource QueryEditor test * Chore: fixed PublicDashboardScenePage.test.tsx * Chore: fix PanelDataQueriesTab test * Chore: fix PanelMenuBehavior test * Chore: fix transformSceneToSaveModel test * Chore: fix last type errors * Chore: fix alerting/unified/testSetup/plugins.ts * Chore: break out types to separate file * feat(Extensions): expose an observable API for added links and components * chore: prettier fixes * Revert "chore: prettier fixes" This reverts commit53aa767664. * Revert "feat(Extensions): expose an observable API for added links and components" This reverts commitbdc588250e. --------- Co-authored-by: Hugo Häggmark <hugo.haggmark@gmail.com>
This commit is contained in:
co-authored by
Hugo Häggmark
parent
c1cadc7d6f
commit
39dcff23f9
@@ -9,24 +9,6 @@ export * from './appEvents';
|
||||
export * from './SidecarService_EXPERIMENTAL';
|
||||
export * from './SidecarContext_EXPERIMENTAL';
|
||||
|
||||
export {
|
||||
setPluginExtensionGetter,
|
||||
getPluginExtensions,
|
||||
getPluginLinkExtensions,
|
||||
getPluginComponentExtensions,
|
||||
type GetPluginExtensions,
|
||||
type GetPluginExtensionsOptions,
|
||||
type GetPluginExtensionsResult,
|
||||
type UsePluginExtensions,
|
||||
type UsePluginExtensionsResult,
|
||||
} from './pluginExtensions/getPluginExtensions';
|
||||
export {
|
||||
setPluginExtensionsHook,
|
||||
usePluginExtensions,
|
||||
usePluginLinkExtensions,
|
||||
usePluginComponentExtensions,
|
||||
} from './pluginExtensions/usePluginExtensions';
|
||||
|
||||
export {
|
||||
setPluginComponentHook,
|
||||
usePluginComponent,
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { setPluginExtensionGetter, type GetPluginExtensions, getPluginExtensions } from './getPluginExtensions';
|
||||
|
||||
describe('Plugin Extensions / Get Plugin Extensions', () => {
|
||||
afterEach(() => {
|
||||
process.env.NODE_ENV = 'test';
|
||||
});
|
||||
|
||||
test('should always return the same extension-getter function that was previously set', () => {
|
||||
const getter: GetPluginExtensions = jest.fn().mockReturnValue({ extensions: [] });
|
||||
|
||||
setPluginExtensionGetter(getter);
|
||||
getPluginExtensions({ extensionPointId: 'panel-menu' });
|
||||
|
||||
expect(getter).toHaveBeenCalledTimes(1);
|
||||
expect(getter).toHaveBeenCalledWith({ extensionPointId: 'panel-menu' });
|
||||
});
|
||||
|
||||
test('should throw an error when trying to redefine the app-wide extension-getter function', () => {
|
||||
// By default, NODE_ENV is set to 'test' in jest.config.js, which allows to override the registry in tests.
|
||||
process.env.NODE_ENV = 'production';
|
||||
|
||||
const getter: GetPluginExtensions = () => ({ extensions: [] });
|
||||
|
||||
expect(() => {
|
||||
setPluginExtensionGetter(getter);
|
||||
setPluginExtensionGetter(getter);
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
test('should throw an error when trying to access the extension-getter function before it was set', () => {
|
||||
// "Unsetting" the registry
|
||||
// @ts-ignore
|
||||
setPluginExtensionGetter(undefined);
|
||||
|
||||
expect(() => {
|
||||
getPluginExtensions({ extensionPointId: 'panel-menu' });
|
||||
}).toThrowError();
|
||||
});
|
||||
});
|
||||
@@ -1,67 +0,0 @@
|
||||
import type { PluginExtension, PluginExtensionLink, PluginExtensionComponent } from '@grafana/data';
|
||||
|
||||
import { isPluginExtensionComponent, isPluginExtensionLink } from './utils';
|
||||
|
||||
export type GetPluginExtensions<T = PluginExtension> = (
|
||||
options: GetPluginExtensionsOptions
|
||||
) => GetPluginExtensionsResult<T>;
|
||||
|
||||
export type UsePluginExtensions<T = PluginExtension> = (
|
||||
options: GetPluginExtensionsOptions
|
||||
) => UsePluginExtensionsResult<T>;
|
||||
|
||||
export type GetPluginExtensionsOptions = {
|
||||
extensionPointId: string;
|
||||
// Make sure this object is properly memoized and not mutated.
|
||||
context?: object | Record<string | symbol, unknown>;
|
||||
limitPerPlugin?: number;
|
||||
};
|
||||
|
||||
export type GetPluginExtensionsResult<T = PluginExtension> = {
|
||||
extensions: T[];
|
||||
};
|
||||
|
||||
export type UsePluginExtensionsResult<T = PluginExtension> = {
|
||||
extensions: T[];
|
||||
isLoading: boolean;
|
||||
};
|
||||
|
||||
let singleton: GetPluginExtensions | undefined;
|
||||
|
||||
export function setPluginExtensionGetter(instance: GetPluginExtensions): void {
|
||||
// We allow overriding the registry in tests
|
||||
if (singleton && process.env.NODE_ENV !== 'test') {
|
||||
throw new Error('setPluginExtensionGetter() function should only be called once, when Grafana is starting.');
|
||||
}
|
||||
singleton = instance;
|
||||
}
|
||||
|
||||
function getPluginExtensionGetter(): GetPluginExtensions {
|
||||
if (!singleton) {
|
||||
throw new Error('getPluginExtensionGetter() can only be used after the Grafana instance has started.');
|
||||
}
|
||||
return singleton;
|
||||
}
|
||||
|
||||
export const getPluginExtensions: GetPluginExtensions = (options) => getPluginExtensionGetter()(options);
|
||||
|
||||
export const getPluginLinkExtensions: GetPluginExtensions<PluginExtensionLink> = (options) => {
|
||||
const { extensions } = getPluginExtensions(options);
|
||||
|
||||
return {
|
||||
extensions: extensions.filter(isPluginExtensionLink),
|
||||
};
|
||||
};
|
||||
|
||||
// This getter doesn't support the `context` option (contextual information can be passed in as component props)
|
||||
export const getPluginComponentExtensions = <Props = {}>(options: {
|
||||
extensionPointId: string;
|
||||
limitPerPlugin?: number;
|
||||
}): { extensions: Array<PluginExtensionComponent<Props>> } => {
|
||||
const { extensions } = getPluginExtensions(options);
|
||||
const componentExtensions = extensions.filter(isPluginExtensionComponent) as Array<PluginExtensionComponent<Props>>;
|
||||
|
||||
return {
|
||||
extensions: componentExtensions,
|
||||
};
|
||||
};
|
||||
@@ -1,262 +0,0 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
|
||||
import { PluginExtension, PluginExtensionTypes } from '@grafana/data';
|
||||
|
||||
import { UsePluginExtensions } from './getPluginExtensions';
|
||||
import {
|
||||
setPluginExtensionsHook,
|
||||
usePluginComponentExtensions,
|
||||
usePluginExtensions,
|
||||
usePluginLinkExtensions,
|
||||
} from './usePluginExtensions';
|
||||
|
||||
describe('Plugin Extensions / usePluginExtensions', () => {
|
||||
afterEach(() => {
|
||||
process.env.NODE_ENV = 'test';
|
||||
});
|
||||
|
||||
test('should always return the same extension-hook function that was previously set', () => {
|
||||
const hook: UsePluginExtensions = jest.fn().mockReturnValue({ extensions: [], isLoading: false });
|
||||
|
||||
setPluginExtensionsHook(hook);
|
||||
usePluginExtensions({ extensionPointId: 'panel-menu' });
|
||||
|
||||
expect(hook).toHaveBeenCalledTimes(1);
|
||||
expect(hook).toHaveBeenCalledWith({ extensionPointId: 'panel-menu' });
|
||||
});
|
||||
|
||||
test('should throw an error when trying to redefine the app-wide extension-hook function', () => {
|
||||
// By default, NODE_ENV is set to 'test' in jest.config.js, which allows to override the registry in tests.
|
||||
process.env.NODE_ENV = 'production';
|
||||
|
||||
const hook: UsePluginExtensions = () => ({ extensions: [], isLoading: false });
|
||||
|
||||
expect(() => {
|
||||
setPluginExtensionsHook(hook);
|
||||
setPluginExtensionsHook(hook);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test('should throw an error when trying to access the extension-hook function before it was set', () => {
|
||||
// "Unsetting" the registry
|
||||
// @ts-ignore
|
||||
setPluginExtensionsHook(undefined);
|
||||
|
||||
expect(() => {
|
||||
usePluginExtensions({ extensionPointId: 'panel-menu' });
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
describe('usePluginExtensionLinks()', () => {
|
||||
test('should return only links extensions', () => {
|
||||
const usePluginExtensionsMock: UsePluginExtensions = () => ({
|
||||
extensions: [
|
||||
{
|
||||
id: '1',
|
||||
pluginId: '',
|
||||
title: '',
|
||||
description: '',
|
||||
type: PluginExtensionTypes.component,
|
||||
component: () => undefined,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
pluginId: '',
|
||||
title: '',
|
||||
description: '',
|
||||
path: '',
|
||||
type: PluginExtensionTypes.link,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
pluginId: '',
|
||||
title: '',
|
||||
description: '',
|
||||
path: '',
|
||||
type: PluginExtensionTypes.link,
|
||||
},
|
||||
],
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
setPluginExtensionsHook(usePluginExtensionsMock);
|
||||
|
||||
const { result } = renderHook(() => usePluginLinkExtensions({ extensionPointId: 'panel-menu' }));
|
||||
const { extensions } = result.current;
|
||||
|
||||
expect(extensions).toHaveLength(2);
|
||||
expect(extensions[0].type).toBe('link');
|
||||
expect(extensions[1].type).toBe('link');
|
||||
expect(extensions.find(({ id }) => id === '2')).toBeDefined();
|
||||
expect(extensions.find(({ id }) => id === '3')).toBeDefined();
|
||||
});
|
||||
|
||||
test('should return the same object if the extensions do not change', () => {
|
||||
const extensionPointId = 'foo';
|
||||
const extensions: PluginExtension[] = [
|
||||
{
|
||||
id: '1',
|
||||
pluginId: '',
|
||||
title: '',
|
||||
description: '',
|
||||
path: '',
|
||||
type: PluginExtensionTypes.link,
|
||||
},
|
||||
];
|
||||
|
||||
// Mimicing that the extensions do not change between renders
|
||||
const usePluginExtensionsMock: UsePluginExtensions = () => ({
|
||||
extensions,
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
setPluginExtensionsHook(usePluginExtensionsMock);
|
||||
|
||||
const { result, rerender } = renderHook(() => usePluginLinkExtensions({ extensionPointId }));
|
||||
const firstExtensions = result.current.extensions;
|
||||
|
||||
rerender();
|
||||
|
||||
const secondExtensions = result.current.extensions;
|
||||
|
||||
expect(firstExtensions === secondExtensions).toBe(true);
|
||||
});
|
||||
|
||||
test('should return a different object if the extensions do change', () => {
|
||||
const extensionPointId = 'foo';
|
||||
|
||||
// Mimicing that the extensions is a new array object every time
|
||||
const usePluginExtensionsMock: UsePluginExtensions = () => ({
|
||||
extensions: [
|
||||
{
|
||||
id: '1',
|
||||
pluginId: '',
|
||||
title: '',
|
||||
description: '',
|
||||
path: '',
|
||||
type: PluginExtensionTypes.link,
|
||||
},
|
||||
],
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
setPluginExtensionsHook(usePluginExtensionsMock);
|
||||
|
||||
const { result, rerender } = renderHook(() => usePluginLinkExtensions({ extensionPointId }));
|
||||
const firstExtensions = result.current.extensions;
|
||||
|
||||
rerender();
|
||||
|
||||
const secondExtensions = result.current.extensions;
|
||||
|
||||
// The results differ
|
||||
expect(firstExtensions === secondExtensions).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('usePluginExtensionComponents()', () => {
|
||||
test('should return only component extensions', () => {
|
||||
const hook: UsePluginExtensions = () => ({
|
||||
extensions: [
|
||||
{
|
||||
id: '1',
|
||||
pluginId: '',
|
||||
title: '',
|
||||
description: '',
|
||||
type: PluginExtensionTypes.component,
|
||||
component: () => undefined,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
pluginId: '',
|
||||
title: '',
|
||||
description: '',
|
||||
path: '',
|
||||
type: PluginExtensionTypes.link,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
pluginId: '',
|
||||
title: '',
|
||||
description: '',
|
||||
path: '',
|
||||
type: PluginExtensionTypes.link,
|
||||
},
|
||||
],
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
setPluginExtensionsHook(hook);
|
||||
|
||||
const hookRender = renderHook(() => usePluginComponentExtensions({ extensionPointId: 'panel-menu' }));
|
||||
const { extensions } = hookRender.result.current;
|
||||
|
||||
expect(extensions).toHaveLength(1);
|
||||
expect(extensions[0].type).toBe('component');
|
||||
expect(extensions.find(({ id }) => id === '1')).toBeDefined();
|
||||
});
|
||||
|
||||
test('should return the same object if the extensions do not change', () => {
|
||||
const extensionPointId = 'foo';
|
||||
const extensions: PluginExtension[] = [
|
||||
{
|
||||
id: '1',
|
||||
pluginId: '',
|
||||
title: '',
|
||||
description: '',
|
||||
type: PluginExtensionTypes.component,
|
||||
component: () => undefined,
|
||||
},
|
||||
];
|
||||
|
||||
// Mimicing that the extensions do not change between renders
|
||||
const usePluginExtensionsMock: UsePluginExtensions = () => ({
|
||||
extensions,
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
setPluginExtensionsHook(usePluginExtensionsMock);
|
||||
|
||||
const { result, rerender } = renderHook(() => usePluginComponentExtensions({ extensionPointId }));
|
||||
const firstExtensions = result.current.extensions;
|
||||
|
||||
rerender();
|
||||
|
||||
const secondExtensions = result.current.extensions;
|
||||
|
||||
// The results are the same
|
||||
expect(firstExtensions === secondExtensions).toBe(true);
|
||||
});
|
||||
|
||||
test('should return a different object if the extensions do change', () => {
|
||||
const extensionPointId = 'foo';
|
||||
|
||||
// Mimicing that the extensions is a new array object every time
|
||||
const usePluginExtensionsMock: UsePluginExtensions = () => ({
|
||||
extensions: [
|
||||
{
|
||||
id: '1',
|
||||
pluginId: '',
|
||||
title: '',
|
||||
description: '',
|
||||
type: PluginExtensionTypes.component,
|
||||
component: () => undefined,
|
||||
},
|
||||
],
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
setPluginExtensionsHook(usePluginExtensionsMock);
|
||||
|
||||
const { result, rerender } = renderHook(() => usePluginComponentExtensions({ extensionPointId }));
|
||||
const firstExtensions = result.current.extensions;
|
||||
|
||||
rerender();
|
||||
|
||||
const secondExtensions = result.current.extensions;
|
||||
|
||||
// The results differ
|
||||
expect(firstExtensions === secondExtensions).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,59 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { PluginExtensionComponent, PluginExtensionLink } from '@grafana/data';
|
||||
|
||||
import { GetPluginExtensionsOptions, UsePluginExtensions, UsePluginExtensionsResult } from './getPluginExtensions';
|
||||
import { isPluginExtensionComponent, isPluginExtensionLink } from './utils';
|
||||
|
||||
let singleton: UsePluginExtensions | undefined;
|
||||
|
||||
export function setPluginExtensionsHook(hook: UsePluginExtensions): void {
|
||||
// We allow overriding the registry in tests
|
||||
if (singleton && process.env.NODE_ENV !== 'test') {
|
||||
throw new Error('setPluginExtensionsHook() function should only be called once, when Grafana is starting.');
|
||||
}
|
||||
singleton = hook;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use either usePluginLinks() or usePluginComponents() instead.
|
||||
*/
|
||||
export function usePluginExtensions(options: GetPluginExtensionsOptions): UsePluginExtensionsResult {
|
||||
if (!singleton) {
|
||||
throw new Error('usePluginExtensions(options) can only be used after the Grafana instance has started.');
|
||||
}
|
||||
return singleton(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use usePluginLinks() instead.
|
||||
*/
|
||||
export function usePluginLinkExtensions(
|
||||
options: GetPluginExtensionsOptions
|
||||
): UsePluginExtensionsResult<PluginExtensionLink> {
|
||||
const { extensions, isLoading } = usePluginExtensions(options);
|
||||
|
||||
return useMemo(() => {
|
||||
return {
|
||||
extensions: extensions.filter(isPluginExtensionLink),
|
||||
isLoading,
|
||||
};
|
||||
}, [extensions, isLoading]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use usePluginComponents() instead.
|
||||
*/
|
||||
export function usePluginComponentExtensions<Props = {}>(
|
||||
options: GetPluginExtensionsOptions
|
||||
): { extensions: Array<PluginExtensionComponent<Props>>; isLoading: boolean } {
|
||||
const { extensions, isLoading } = usePluginExtensions(options);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
extensions: extensions.filter(isPluginExtensionComponent) as Array<PluginExtensionComponent<Props>>,
|
||||
isLoading,
|
||||
}),
|
||||
[extensions, isLoading]
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user