Sidecar: Remove extension helpers and use the sidecar service directly (#94979)

This commit is contained in:
Andrej Ocenas 2024-10-22 11:46:29 +02:00 committed by GitHub
parent 94f5e21493
commit 5b1d99b8c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 58 additions and 57 deletions

View File

@ -75,10 +75,7 @@ export type PluginExtensionAddedComponentConfig<Props = {}> = PluginExtensionCon
component: React.ComponentType<Props>; component: React.ComponentType<Props>;
}; };
export type PluginAddedLinksConfigureFunc<Context extends object> = ( export type PluginAddedLinksConfigureFunc<Context extends object> = (context: Readonly<Context> | undefined) =>
context: Readonly<Context> | undefined,
helpers: PluginExtensionHelpers
) =>
| Partial<{ | Partial<{
title: string; title: string;
description: string; description: string;
@ -145,27 +142,11 @@ export type PluginExtensionOpenModalOptions = {
height?: string | number; height?: string | number;
}; };
type PluginExtensionHelpers = {
/** Checks if the app plugin (that registers the extension) is currently visible (either in the main view or in the side view)
* @experimental
*/
isAppOpened: () => boolean;
};
export type PluginExtensionEventHelpers<Context extends object = object> = { export type PluginExtensionEventHelpers<Context extends object = object> = {
context?: Readonly<Context>; context?: Readonly<Context>;
// Opens a modal dialog and renders the provided React component inside it // Opens a modal dialog and renders the provided React component inside it
openModal: (options: PluginExtensionOpenModalOptions) => void; openModal: (options: PluginExtensionOpenModalOptions) => void;
};
/** Opens the app plugin (that registers the extensions) in a side view
* @experimental
*/
openAppInSideview: (context?: unknown) => void;
/** Closes the side view for the app plugin (that registers the extensions) in case it was open
* @experimental
*/
closeAppInSideview: () => void;
} & PluginExtensionHelpers;
// Extension Points & Contexts // Extension Points & Contexts
// -------------------------------------------------------- // --------------------------------------------------------

View File

@ -0,0 +1,47 @@
import { config } from '../config';
import { SidecarService_EXPERIMENTAL } from './SidecarService_EXPERIMENTAL';
describe('SidecarService_EXPERIMENTAL', () => {
beforeEach(() => {
config.featureToggles.appSidecar = true;
});
afterEach(() => {
config.featureToggles.appSidecar = undefined;
});
it('has the correct state after opening and closing an app', () => {
const sidecarService = new SidecarService_EXPERIMENTAL({});
sidecarService.openApp('pluginId', { filter: 'test' });
expect(sidecarService.activePluginId).toBe('pluginId');
expect(sidecarService.initialContext).toMatchObject({ filter: 'test' });
sidecarService.closeApp('pluginId');
expect(sidecarService.activePluginId).toBe(undefined);
expect(sidecarService.initialContext).toBe(undefined);
});
it('reports correct opened state', () => {
const sidecarService = new SidecarService_EXPERIMENTAL({});
expect(sidecarService.isAppOpened('pluginId')).toBe(false);
sidecarService.openApp('pluginId');
expect(sidecarService.isAppOpened('pluginId')).toBe(true);
sidecarService.closeApp('pluginId');
expect(sidecarService.isAppOpened('pluginId')).toBe(false);
});
it('does not close app that is not opened', () => {
const sidecarService = new SidecarService_EXPERIMENTAL({});
sidecarService.openApp('pluginId');
sidecarService.closeApp('foobar');
expect(sidecarService.isAppOpened('pluginId')).toBe(true);
expect(sidecarService.activePluginId).toBe('pluginId');
});
});

View File

@ -97,7 +97,7 @@ func createIndexMappings() *mapping.IndexMappingImpl {
indexMapping.TypeField = "Kind" indexMapping.TypeField = "Kind"
// for all kinds, create their index mappings // for all kinds, create their index mappings
for k, _ := range getSpecObjectMappings() { for k := range getSpecObjectMappings() {
objMapping := createIndexMappingForKind(k) objMapping := createIndexMappingForKind(k)
indexMapping.AddDocumentMapping(k, objMapping) indexMapping.AddDocumentMapping(k, objMapping)
} }

View File

@ -43,7 +43,7 @@ describe('getExploreExtensionConfigs', () => {
const extensions = getExploreExtensionConfigs(); const extensions = getExploreExtensionConfigs();
const [extension] = extensions; const [extension] = extensions;
expect(extension?.configure?.(undefined, { isAppOpened: () => false })).toBeUndefined(); expect(extension?.configure?.(undefined)).toBeUndefined();
}); });
it('should return empty object if sufficient permissions', () => { it('should return empty object if sufficient permissions', () => {
@ -52,7 +52,7 @@ describe('getExploreExtensionConfigs', () => {
const extensions = getExploreExtensionConfigs(); const extensions = getExploreExtensionConfigs();
const [extension] = extensions; const [extension] = extensions;
expect(extension?.configure?.(undefined, { isAppOpened: () => false })).toEqual({}); expect(extension?.configure?.(undefined)).toEqual({});
}); });
}); });
}); });

View File

@ -186,10 +186,7 @@ describe('getPluginExtensions()', () => {
getPluginExtensions({ ...registries, context, extensionPointId: extensionPoint2 }); getPluginExtensions({ ...registries, context, extensionPointId: extensionPoint2 });
expect(link2.configure).toHaveBeenCalledTimes(1); expect(link2.configure).toHaveBeenCalledTimes(1);
expect(link2.configure).toHaveBeenCalledWith( expect(link2.configure).toHaveBeenCalledWith(context);
context,
expect.objectContaining({ isAppOpened: expect.any(Function) })
);
}); });
test('should be possible to update the basic properties with the configure() function', async () => { test('should be possible to update the basic properties with the configure() function', async () => {

View File

@ -2,7 +2,7 @@ import { useMemo } from 'react';
import { useObservable } from 'react-use'; import { useObservable } from 'react-use';
import { PluginExtension, usePluginContext } from '@grafana/data'; import { PluginExtension, usePluginContext } from '@grafana/data';
import { GetPluginExtensionsOptions, UsePluginExtensionsResult, useSidecar_EXPERIMENTAL } from '@grafana/runtime'; import { GetPluginExtensionsOptions, UsePluginExtensionsResult } from '@grafana/runtime';
import { getPluginExtensions } from './getPluginExtensions'; import { getPluginExtensions } from './getPluginExtensions';
import { log } from './logs/log'; import { log } from './logs/log';
@ -18,7 +18,6 @@ export function createUsePluginExtensions(registries: PluginExtensionRegistries)
const pluginContext = usePluginContext(); const pluginContext = usePluginContext();
const addedComponentsRegistry = useObservable(observableAddedComponentsRegistry); const addedComponentsRegistry = useObservable(observableAddedComponentsRegistry);
const addedLinksRegistry = useObservable(observableAddedLinksRegistry); const addedLinksRegistry = useObservable(observableAddedLinksRegistry);
const { activePluginId } = useSidecar_EXPERIMENTAL();
const { extensionPointId, context, limitPerPlugin } = options; const { extensionPointId, context, limitPerPlugin } = options;
const { extensions } = useMemo(() => { const { extensions } = useMemo(() => {
@ -65,15 +64,7 @@ export function createUsePluginExtensions(registries: PluginExtensionRegistries)
// options object so we are checking it's simple value attributes. // options object so we are checking it's simple value attributes.
// The context though still has to be memoized though and not mutated. // The context though still has to be memoized though and not mutated.
// eslint-disable-next-line react-hooks/exhaustive-deps -- TODO: refactor `getPluginExtensions` to accept service dependencies as arguments instead of relying on the sidecar singleton under the hood // eslint-disable-next-line react-hooks/exhaustive-deps -- TODO: refactor `getPluginExtensions` to accept service dependencies as arguments instead of relying on the sidecar singleton under the hood
}, [ }, [addedLinksRegistry, addedComponentsRegistry, extensionPointId, context, limitPerPlugin, pluginContext]);
addedLinksRegistry,
addedComponentsRegistry,
extensionPointId,
context,
limitPerPlugin,
activePluginId,
pluginContext,
]);
return { extensions, isLoading: false }; return { extensions, isLoading: false };
}; };

View File

@ -20,12 +20,7 @@ import {
PluginExtensionExposedComponentConfig, PluginExtensionExposedComponentConfig,
PluginExtensionAddedComponentConfig, PluginExtensionAddedComponentConfig,
} from '@grafana/data'; } from '@grafana/data';
import { import { reportInteraction, config } from '@grafana/runtime';
reportInteraction,
config,
// TODO: instead of depending on the service as a singleton, inject it as an argument from the React context
sidecarServiceSingleton_EXPERIMENTAL,
} from '@grafana/runtime';
import { Modal } from '@grafana/ui'; import { Modal } from '@grafana/ui';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import { getPluginSettings } from 'app/features/plugins/pluginSettings'; import { getPluginSettings } from 'app/features/plugins/pluginSettings';
@ -312,7 +307,7 @@ export function getLinkExtensionOverrides(
context?: object context?: object
) { ) {
try { try {
const overrides = config.configure?.(context, { isAppOpened: () => isAppOpened(pluginId) }); const overrides = config.configure?.(context);
// Hiding the extension // Hiding the extension
if (overrides === undefined) { if (overrides === undefined) {
@ -390,9 +385,6 @@ export function getLinkExtensionOnClick(
const helpers: PluginExtensionEventHelpers = { const helpers: PluginExtensionEventHelpers = {
context, context,
openModal: createOpenModalFunction(pluginId), openModal: createOpenModalFunction(pluginId),
isAppOpened: () => isAppOpened(pluginId),
openAppInSideview: (context?: unknown) => openAppInSideview(pluginId, context),
closeAppInSideview: () => closeAppInSideview(pluginId),
}; };
log.debug(`onClick '${config.title}' at '${extensionPointId}'`); log.debug(`onClick '${config.title}' at '${extensionPointId}'`);
@ -429,13 +421,6 @@ export function getLinkExtensionPathWithTracking(pluginId: string, path: string,
); );
} }
export const openAppInSideview = (pluginId: string, context?: unknown) =>
sidecarServiceSingleton_EXPERIMENTAL.openApp(pluginId, context);
export const closeAppInSideview = (pluginId: string) => sidecarServiceSingleton_EXPERIMENTAL.closeApp(pluginId);
export const isAppOpened = (pluginId: string) => sidecarServiceSingleton_EXPERIMENTAL.isAppOpened(pluginId);
// Comes from the `app_mode` setting in the Grafana config (defaults to "development") // Comes from the `app_mode` setting in the Grafana config (defaults to "development")
// Can be set with the `GF_DEFAULT_APP_MODE` environment variable // Can be set with the `GF_DEFAULT_APP_MODE` environment variable
export const isGrafanaDevMode = () => config.buildInfo.env === 'development'; export const isGrafanaDevMode = () => config.buildInfo.env === 'development';

View File

@ -71,7 +71,7 @@ describe('Plugin Extension Validators', () => {
title: 'Title', title: 'Title',
description: 'Description', description: 'Description',
targets: 'grafana/some-page/extension-point-a', targets: 'grafana/some-page/extension-point-a',
configure: (_, {}) => {}, configure: () => {},
} as PluginExtensionAddedLinkConfig); } as PluginExtensionAddedLinkConfig);
}).not.toThrowError(); }).not.toThrowError();
}); });