ExtensionSidebar: Pass closeSidebar to plugin helpers (#108171)
* ExtensionSidebar: Add `CloseExtensionSidebarEvent` * ExtensionSidebar: Use `CloseExtensionSidebarEvent` in provider * ExtensionSidebar: Pass `closeSidebar` to plugin helpers
This commit is contained in:
@@ -174,6 +174,11 @@ export type PluginExtensionEventHelpers<Context extends object = object> = {
|
||||
* @param props The props to be passed to the component.
|
||||
*/
|
||||
openSidebar: (componentTitle: string, props?: Record<string, unknown>) => void;
|
||||
/**
|
||||
* @internal
|
||||
* Closes the extension sidebar.
|
||||
*/
|
||||
closeSidebar: () => void;
|
||||
};
|
||||
|
||||
// Extension Points & Contexts
|
||||
|
||||
+58
-8
@@ -3,7 +3,7 @@ import { render, screen, act } from '@testing-library/react';
|
||||
import { store, EventBusSrv, EventBus } from '@grafana/data';
|
||||
import { config, getAppEvents, setAppEvents, locationService } from '@grafana/runtime';
|
||||
import { getExtensionPointPluginMeta } from 'app/features/plugins/extensions/utils';
|
||||
import { OpenExtensionSidebarEvent } from 'app/types/events';
|
||||
import { OpenExtensionSidebarEvent, CloseExtensionSidebarEvent } from 'app/types/events';
|
||||
|
||||
import {
|
||||
ExtensionSidebarContextProvider,
|
||||
@@ -249,7 +249,7 @@ describe('ExtensionSidebarProvider', () => {
|
||||
expect(screen.getByTestId('plugin-ids')).toHaveTextContent(permittedPluginMeta.pluginId);
|
||||
});
|
||||
|
||||
it('should subscribe to OpenExtensionSidebarEvent when feature is enabled', async () => {
|
||||
it('should subscribe to OpenExtensionSidebarEvent and CloseExtensionSidebarEvent when feature is enabled', async () => {
|
||||
render(
|
||||
<ExtensionSidebarContextProvider>
|
||||
<TestComponent />
|
||||
@@ -257,9 +257,10 @@ describe('ExtensionSidebarProvider', () => {
|
||||
);
|
||||
|
||||
expect(subscribeSpy).toHaveBeenCalledWith(OpenExtensionSidebarEvent, expect.any(Function));
|
||||
expect(subscribeSpy).toHaveBeenCalledWith(CloseExtensionSidebarEvent, expect.any(Function));
|
||||
});
|
||||
|
||||
it('should not subscribe to OpenExtensionSidebarEvent when feature is disabled', () => {
|
||||
it('should not subscribe to OpenExtensionSidebarEvent or CloseExtensionSidebarEvent when feature is disabled', () => {
|
||||
jest.replaceProperty(config.featureToggles, 'extensionSidebar', false);
|
||||
|
||||
render(
|
||||
@@ -341,12 +342,58 @@ describe('ExtensionSidebarProvider', () => {
|
||||
expect(screen.getByTestId('is-open')).toHaveTextContent('false');
|
||||
});
|
||||
|
||||
it('should unsubscribe from OpenExtensionSidebarEvent on unmount', () => {
|
||||
const unsubscribeMock = jest.fn();
|
||||
subscribeSpy.mockReturnValue({
|
||||
unsubscribe: unsubscribeMock,
|
||||
it('should close sidebar when receiving a CloseExtensionSidebarEvent', () => {
|
||||
const componentId = getComponentIdFromComponentMeta(mockPluginMeta.pluginId, mockComponent);
|
||||
|
||||
const TestComponentWithProps = () => {
|
||||
const context = useExtensionSidebarContext();
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="is-open">{context.isOpen.toString()}</div>
|
||||
<div data-testid="docked-component-id">{context.dockedComponentId || 'undefined'}</div>
|
||||
<button onClick={() => context.setDockedComponentId(componentId)}>Open Sidebar</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render(
|
||||
<ExtensionSidebarContextProvider>
|
||||
<TestComponentWithProps />
|
||||
</ExtensionSidebarContextProvider>
|
||||
);
|
||||
|
||||
// First open the sidebar manually
|
||||
act(() => {
|
||||
screen.getByText('Open Sidebar').click();
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('is-open')).toHaveTextContent('true');
|
||||
expect(screen.getByTestId('docked-component-id')).toHaveTextContent(componentId);
|
||||
|
||||
// Now test the close event
|
||||
act(() => {
|
||||
// Find the CloseExtensionSidebarEvent subscriber
|
||||
const closeEventSubscriberCall = subscribeSpy.mock.calls.find((call) => call[0] === CloseExtensionSidebarEvent);
|
||||
|
||||
expect(closeEventSubscriberCall).toBeDefined();
|
||||
const [, subscriberFn] = closeEventSubscriberCall!;
|
||||
|
||||
// Call the close event handler
|
||||
subscriberFn(new CloseExtensionSidebarEvent());
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('is-open')).toHaveTextContent('false');
|
||||
expect(screen.getByTestId('docked-component-id')).toHaveTextContent('undefined');
|
||||
});
|
||||
|
||||
it('should unsubscribe from both OpenExtensionSidebarEvent and CloseExtensionSidebarEvent on unmount', () => {
|
||||
const unsubscribeMocks = [jest.fn(), jest.fn()];
|
||||
let callIndex = 0;
|
||||
|
||||
subscribeSpy.mockImplementation(() => ({
|
||||
unsubscribe: unsubscribeMocks[callIndex++],
|
||||
}));
|
||||
|
||||
const { unmount } = render(
|
||||
<ExtensionSidebarContextProvider>
|
||||
<TestComponent />
|
||||
@@ -354,7 +401,10 @@ describe('ExtensionSidebarProvider', () => {
|
||||
);
|
||||
|
||||
unmount();
|
||||
expect(unsubscribeMock).toHaveBeenCalled();
|
||||
|
||||
// Both event subscriptions should be unsubscribed
|
||||
expect(unsubscribeMocks[0]).toHaveBeenCalled();
|
||||
expect(unsubscribeMocks[1]).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should subscribe to location service observable', () => {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useLocalStorage } from 'react-use';
|
||||
import { PluginExtensionPoints, store, type ExtensionInfo } from '@grafana/data';
|
||||
import { config, getAppEvents, reportInteraction, usePluginLinks, locationService } from '@grafana/runtime';
|
||||
import { ExtensionPointPluginMeta, getExtensionPointPluginMeta } from 'app/features/plugins/extensions/utils';
|
||||
import { OpenExtensionSidebarEvent } from 'app/types/events';
|
||||
import { CloseExtensionSidebarEvent, OpenExtensionSidebarEvent } from 'app/types/events';
|
||||
|
||||
import { DEFAULT_EXTENSION_SIDEBAR_WIDTH } from './ExtensionSidebar';
|
||||
|
||||
@@ -173,7 +173,10 @@ export const ExtensionSidebarContextProvider = ({ children }: ExtensionSidebarCo
|
||||
if (
|
||||
event.payload.pluginId &&
|
||||
event.payload.componentTitle &&
|
||||
PERMITTED_EXTENSION_SIDEBAR_PLUGINS.includes(event.payload.pluginId)
|
||||
PERMITTED_EXTENSION_SIDEBAR_PLUGINS.includes(event.payload.pluginId) &&
|
||||
availableComponents
|
||||
.get(event.payload.pluginId)
|
||||
?.addedComponents.some((component) => component.title === event.payload.componentTitle)
|
||||
) {
|
||||
setDockedComponentWithProps(
|
||||
JSON.stringify({ pluginId: event.payload.pluginId, componentTitle: event.payload.componentTitle }),
|
||||
@@ -182,11 +185,17 @@ export const ExtensionSidebarContextProvider = ({ children }: ExtensionSidebarCo
|
||||
}
|
||||
};
|
||||
|
||||
const subscription = getAppEvents().subscribe(OpenExtensionSidebarEvent, openSidebarHandler);
|
||||
return () => {
|
||||
subscription.unsubscribe();
|
||||
const closeSidebarHandler = () => {
|
||||
setDockedComponentId(undefined);
|
||||
};
|
||||
}, [isEnabled, setDockedComponentWithProps]);
|
||||
|
||||
const openSubscription = getAppEvents().subscribe(OpenExtensionSidebarEvent, openSidebarHandler);
|
||||
const closeSubscription = getAppEvents().subscribe(CloseExtensionSidebarEvent, closeSidebarHandler);
|
||||
return () => {
|
||||
openSubscription.unsubscribe();
|
||||
closeSubscription.unsubscribe();
|
||||
};
|
||||
}, [isEnabled, setDockedComponentWithProps, availableComponents]);
|
||||
|
||||
// update the stored docked component id when it changes
|
||||
useEffect(() => {
|
||||
|
||||
@@ -20,7 +20,7 @@ import { reportInteraction, config, AppPluginConfig } from '@grafana/runtime';
|
||||
import { Modal } from '@grafana/ui';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { getPluginSettings } from 'app/features/plugins/pluginSettings';
|
||||
import { OpenExtensionSidebarEvent, ShowModalReactEvent } from 'app/types/events';
|
||||
import { CloseExtensionSidebarEvent, OpenExtensionSidebarEvent, ShowModalReactEvent } from 'app/types/events';
|
||||
|
||||
import { ExtensionErrorBoundary } from './ExtensionErrorBoundary';
|
||||
import { ExtensionsLog, log as baseLog } from './logs/log';
|
||||
@@ -541,6 +541,9 @@ export function getLinkExtensionOnClick(
|
||||
})
|
||||
);
|
||||
},
|
||||
closeSidebar: () => {
|
||||
appEvents.publish(new CloseExtensionSidebarEvent());
|
||||
},
|
||||
};
|
||||
|
||||
log.debug(`onClick '${config.title}' at '${extensionPointId}'`);
|
||||
|
||||
@@ -194,6 +194,10 @@ export class OpenExtensionSidebarEvent extends BusEventWithPayload<OpenExtension
|
||||
static type = 'open-extension-sidebar';
|
||||
}
|
||||
|
||||
export class CloseExtensionSidebarEvent extends BusEventBase {
|
||||
static type = 'close-extension-sidebar';
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use ShowModalReactEvent instead that has this capability built in
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user