mirror of https://github.com/grafana/grafana.git
ExtensionSidebar: Remove feature flag and enable by default (#109906)
* ExtensionSidebar: Remove feature flag and enable by default * ExtensionSidebar: Remove `isEnabled` * ExtensionSidebar: Lint * ExtensionSidebar: Lint * ExtensionSidebar: Remove more FF * i dont know why, but okay
This commit is contained in:
parent
d31e682345
commit
b6d7374b25
|
@ -907,10 +907,6 @@ export interface FeatureToggles {
|
||||||
*/
|
*/
|
||||||
unifiedStorageGrpcConnectionPool?: boolean;
|
unifiedStorageGrpcConnectionPool?: boolean;
|
||||||
/**
|
/**
|
||||||
* Enables the extension sidebar
|
|
||||||
*/
|
|
||||||
extensionSidebar?: boolean;
|
|
||||||
/**
|
|
||||||
* Enables UI functionality to permanently delete alert rules
|
* Enables UI functionality to permanently delete alert rules
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1563,13 +1563,6 @@ var (
|
||||||
HideFromAdminPage: true,
|
HideFromAdminPage: true,
|
||||||
HideFromDocs: true,
|
HideFromDocs: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "extensionSidebar",
|
|
||||||
Description: "Enables the extension sidebar",
|
|
||||||
Stage: FeatureStageExperimental,
|
|
||||||
FrontendOnly: true,
|
|
||||||
Owner: grafanaObservabilityLogsSquad,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: "alertingRulePermanentlyDelete",
|
Name: "alertingRulePermanentlyDelete",
|
||||||
Description: "Enables UI functionality to permanently delete alert rules",
|
Description: "Enables UI functionality to permanently delete alert rules",
|
||||||
|
|
|
@ -203,7 +203,6 @@ unifiedStorageHistoryPruner,GA,@grafana/search-and-storage,false,false,false
|
||||||
azureMonitorLogsBuilderEditor,preview,@grafana/partner-datasources,false,false,false
|
azureMonitorLogsBuilderEditor,preview,@grafana/partner-datasources,false,false,false
|
||||||
localeFormatPreference,preview,@grafana/grafana-frontend-platform,false,false,false
|
localeFormatPreference,preview,@grafana/grafana-frontend-platform,false,false,false
|
||||||
unifiedStorageGrpcConnectionPool,experimental,@grafana/search-and-storage,false,false,false
|
unifiedStorageGrpcConnectionPool,experimental,@grafana/search-and-storage,false,false,false
|
||||||
extensionSidebar,experimental,@grafana/observability-logs,false,false,true
|
|
||||||
alertingRulePermanentlyDelete,GA,@grafana/alerting-squad,false,false,true
|
alertingRulePermanentlyDelete,GA,@grafana/alerting-squad,false,false,true
|
||||||
alertingRuleRecoverDeleted,GA,@grafana/alerting-squad,false,false,true
|
alertingRuleRecoverDeleted,GA,@grafana/alerting-squad,false,false,true
|
||||||
multiTenantTempCredentials,experimental,@grafana/aws-datasources,false,false,false
|
multiTenantTempCredentials,experimental,@grafana/aws-datasources,false,false,false
|
||||||
|
|
|
|
@ -823,10 +823,6 @@ const (
|
||||||
// Enables the unified storage grpc connection pool
|
// Enables the unified storage grpc connection pool
|
||||||
FlagUnifiedStorageGrpcConnectionPool = "unifiedStorageGrpcConnectionPool"
|
FlagUnifiedStorageGrpcConnectionPool = "unifiedStorageGrpcConnectionPool"
|
||||||
|
|
||||||
// FlagExtensionSidebar
|
|
||||||
// Enables the extension sidebar
|
|
||||||
FlagExtensionSidebar = "extensionSidebar"
|
|
||||||
|
|
||||||
// FlagAlertingRulePermanentlyDelete
|
// FlagAlertingRulePermanentlyDelete
|
||||||
// Enables UI functionality to permanently delete alert rules
|
// Enables UI functionality to permanently delete alert rules
|
||||||
FlagAlertingRulePermanentlyDelete = "alertingRulePermanentlyDelete"
|
FlagAlertingRulePermanentlyDelete = "alertingRulePermanentlyDelete"
|
||||||
|
|
|
@ -1322,7 +1322,8 @@
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"name": "extensionSidebar",
|
"name": "extensionSidebar",
|
||||||
"resourceVersion": "1753448760331",
|
"resourceVersion": "1753448760331",
|
||||||
"creationTimestamp": "2025-04-03T10:16:35Z"
|
"creationTimestamp": "2025-04-03T10:16:35Z",
|
||||||
|
"deletionTimestamp": "2025-08-20T11:59:44Z"
|
||||||
},
|
},
|
||||||
"spec": {
|
"spec": {
|
||||||
"description": "Enables the extension sidebar",
|
"description": "Enables the extension sidebar",
|
||||||
|
|
|
@ -105,9 +105,6 @@ export class AppWrapper extends Component<AppWrapperProps, AppWrapperState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
const MaybeTimeRangeProvider = config.featureToggles.timeRangeProvider ? TimeRangeProvider : Fragment;
|
const MaybeTimeRangeProvider = config.featureToggles.timeRangeProvider ? TimeRangeProvider : Fragment;
|
||||||
const MaybeExtensionSidebarProvider = config.featureToggles.extensionSidebar
|
|
||||||
? ExtensionSidebarContextProvider
|
|
||||||
: Fragment;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
|
@ -122,7 +119,7 @@ export class AppWrapper extends Component<AppWrapperProps, AppWrapperState> {
|
||||||
<MaybeTimeRangeProvider>
|
<MaybeTimeRangeProvider>
|
||||||
<ScopesContextProvider>
|
<ScopesContextProvider>
|
||||||
<ExtensionRegistriesProvider registries={pluginExtensionRegistries}>
|
<ExtensionRegistriesProvider registries={pluginExtensionRegistries}>
|
||||||
<MaybeExtensionSidebarProvider>
|
<ExtensionSidebarContextProvider>
|
||||||
<UNSAFE_PortalProvider getContainer={getPortalContainer}>
|
<UNSAFE_PortalProvider getContainer={getPortalContainer}>
|
||||||
<GlobalStylesWrapper />
|
<GlobalStylesWrapper />
|
||||||
<div className="grafana-app">
|
<div className="grafana-app">
|
||||||
|
@ -131,7 +128,7 @@ export class AppWrapper extends Component<AppWrapperProps, AppWrapperState> {
|
||||||
<PortalContainer />
|
<PortalContainer />
|
||||||
</div>
|
</div>
|
||||||
</UNSAFE_PortalProvider>
|
</UNSAFE_PortalProvider>
|
||||||
</MaybeExtensionSidebarProvider>
|
</ExtensionSidebarContextProvider>
|
||||||
</ExtensionRegistriesProvider>
|
</ExtensionRegistriesProvider>
|
||||||
</ScopesContextProvider>
|
</ScopesContextProvider>
|
||||||
</MaybeTimeRangeProvider>
|
</MaybeTimeRangeProvider>
|
||||||
|
|
|
@ -33,7 +33,6 @@ export function AppChrome({ children }: Props) {
|
||||||
const { chrome } = useGrafana();
|
const { chrome } = useGrafana();
|
||||||
const {
|
const {
|
||||||
isOpen: isExtensionSidebarOpen,
|
isOpen: isExtensionSidebarOpen,
|
||||||
isEnabled: isExtensionSidebarEnabled,
|
|
||||||
extensionSidebarWidth,
|
extensionSidebarWidth,
|
||||||
setExtensionSidebarWidth,
|
setExtensionSidebarWidth,
|
||||||
} = useExtensionSidebarContext();
|
} = useExtensionSidebarContext();
|
||||||
|
@ -138,7 +137,7 @@ export function AppChrome({ children }: Props) {
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
{!state.chromeless && isExtensionSidebarEnabled && isExtensionSidebarOpen && (
|
{!state.chromeless && isExtensionSidebarOpen && (
|
||||||
<Resizable
|
<Resizable
|
||||||
className={styles.sidebarContainer}
|
className={styles.sidebarContainer}
|
||||||
defaultSize={{ width: extensionSidebarWidth }}
|
defaultSize={{ width: extensionSidebarWidth }}
|
||||||
|
|
|
@ -44,7 +44,6 @@ const addedComponentConfigMock: ExtensionInfo = {
|
||||||
|
|
||||||
const extensionSidebarContextMock: ExtensionSidebarContextType = {
|
const extensionSidebarContextMock: ExtensionSidebarContextType = {
|
||||||
dockedComponentId: getComponentIdFromComponentMeta(pluginId, addedComponentConfigMock),
|
dockedComponentId: getComponentIdFromComponentMeta(pluginId, addedComponentConfigMock),
|
||||||
isEnabled: true,
|
|
||||||
props: {},
|
props: {},
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
setDockedComponentId: jest.fn(),
|
setDockedComponentId: jest.fn(),
|
||||||
|
@ -71,25 +70,9 @@ describe('ExtensionSidebar', () => {
|
||||||
config.buildInfo.env = originalEnv;
|
config.buildInfo.env = originalEnv;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render nothing when the extension sidebar is not enabled', () => {
|
|
||||||
mockUseExtensionSidebarContext.mockReturnValue({
|
|
||||||
...extensionSidebarContextMock,
|
|
||||||
isEnabled: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
mockUsePluginComponents.mockReturnValue({
|
|
||||||
components: [createComponentWithMeta(addedComponentRegistryItemMock, extensionPointId)],
|
|
||||||
isLoading: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { container } = render(<ExtensionSidebar />);
|
|
||||||
expect(container.firstChild).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render nothing when the extension sidebar is enabled but no component is docked', () => {
|
it('should render nothing when the extension sidebar is enabled but no component is docked', () => {
|
||||||
mockUseExtensionSidebarContext.mockReturnValue({
|
mockUseExtensionSidebarContext.mockReturnValue({
|
||||||
...extensionSidebarContextMock,
|
...extensionSidebarContextMock,
|
||||||
isEnabled: true,
|
|
||||||
dockedComponentId: undefined,
|
dockedComponentId: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -105,7 +88,6 @@ describe('ExtensionSidebar', () => {
|
||||||
it('should render nothing when the extension sidebar is enabled but the component docked is not found in the available components', () => {
|
it('should render nothing when the extension sidebar is enabled but the component docked is not found in the available components', () => {
|
||||||
mockUseExtensionSidebarContext.mockReturnValue({
|
mockUseExtensionSidebarContext.mockReturnValue({
|
||||||
...extensionSidebarContextMock,
|
...extensionSidebarContextMock,
|
||||||
isEnabled: true,
|
|
||||||
dockedComponentId: 'test-component-id-not-found',
|
dockedComponentId: 'test-component-id-not-found',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -121,7 +103,6 @@ describe('ExtensionSidebar', () => {
|
||||||
it('should render nothing when the extension sidebar is enabled but the component docked is not found in the available components', () => {
|
it('should render nothing when the extension sidebar is enabled but the component docked is not found in the available components', () => {
|
||||||
mockUseExtensionSidebarContext.mockReturnValue({
|
mockUseExtensionSidebarContext.mockReturnValue({
|
||||||
...extensionSidebarContextMock,
|
...extensionSidebarContextMock,
|
||||||
isEnabled: true,
|
|
||||||
dockedComponentId: 'test-component-id-not-found',
|
dockedComponentId: 'test-component-id-not-found',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -137,7 +118,6 @@ describe('ExtensionSidebar', () => {
|
||||||
it('should render nothing when components are loading', () => {
|
it('should render nothing when components are loading', () => {
|
||||||
mockUseExtensionSidebarContext.mockReturnValue({
|
mockUseExtensionSidebarContext.mockReturnValue({
|
||||||
...extensionSidebarContextMock,
|
...extensionSidebarContextMock,
|
||||||
isEnabled: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
mockUsePluginComponents.mockReturnValue({
|
mockUsePluginComponents.mockReturnValue({
|
||||||
|
@ -152,7 +132,6 @@ describe('ExtensionSidebar', () => {
|
||||||
it('should render the component when all conditions are met', () => {
|
it('should render the component when all conditions are met', () => {
|
||||||
mockUseExtensionSidebarContext.mockReturnValue({
|
mockUseExtensionSidebarContext.mockReturnValue({
|
||||||
...extensionSidebarContextMock,
|
...extensionSidebarContextMock,
|
||||||
isEnabled: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
mockUsePluginComponents.mockReturnValue({
|
mockUsePluginComponents.mockReturnValue({
|
||||||
|
|
|
@ -16,12 +16,12 @@ type ExtensionSidebarComponentProps = {
|
||||||
|
|
||||||
export function ExtensionSidebar() {
|
export function ExtensionSidebar() {
|
||||||
const styles = getStyles(useTheme2());
|
const styles = getStyles(useTheme2());
|
||||||
const { dockedComponentId, isEnabled, props = {} } = useExtensionSidebarContext();
|
const { dockedComponentId, props = {} } = useExtensionSidebarContext();
|
||||||
const { components, isLoading } = usePluginComponents<ExtensionSidebarComponentProps>({
|
const { components, isLoading } = usePluginComponents<ExtensionSidebarComponentProps>({
|
||||||
extensionPointId: PluginExtensionPoints.ExtensionSidebar,
|
extensionPointId: PluginExtensionPoints.ExtensionSidebar,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isLoading || !dockedComponentId || !isEnabled) {
|
if (isLoading || !dockedComponentId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { render, screen, act } from '@testing-library/react';
|
import { render, screen, act } from '@testing-library/react';
|
||||||
|
|
||||||
import { store, EventBusSrv, EventBus } from '@grafana/data';
|
import { store, EventBusSrv, EventBus } from '@grafana/data';
|
||||||
import { config, getAppEvents, setAppEvents, locationService } from '@grafana/runtime';
|
import { getAppEvents, setAppEvents, locationService } from '@grafana/runtime';
|
||||||
import { getExtensionPointPluginMeta } from 'app/features/plugins/extensions/utils';
|
import { getExtensionPointPluginMeta } from 'app/features/plugins/extensions/utils';
|
||||||
import { OpenExtensionSidebarEvent, CloseExtensionSidebarEvent } from 'app/types/events';
|
import { OpenExtensionSidebarEvent, CloseExtensionSidebarEvent } from 'app/types/events';
|
||||||
|
|
||||||
|
@ -43,13 +43,6 @@ jest.mock('app/features/plugins/extensions/utils', () => ({
|
||||||
|
|
||||||
jest.mock('@grafana/runtime', () => ({
|
jest.mock('@grafana/runtime', () => ({
|
||||||
...jest.requireActual('@grafana/runtime'),
|
...jest.requireActual('@grafana/runtime'),
|
||||||
config: {
|
|
||||||
...jest.requireActual('@grafana/runtime').config,
|
|
||||||
featureToggles: {
|
|
||||||
...jest.requireActual('@grafana/runtime').config.featureToggles,
|
|
||||||
extensionSidebar: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
locationService: {
|
locationService: {
|
||||||
getLocation: jest.fn().mockReturnValue({ pathname: '/test-path' }),
|
getLocation: jest.fn().mockReturnValue({ pathname: '/test-path' }),
|
||||||
getLocationObservable: jest.fn(),
|
getLocationObservable: jest.fn(),
|
||||||
|
@ -83,8 +76,6 @@ describe('ExtensionSidebarProvider', () => {
|
||||||
|
|
||||||
getExtensionPointPluginMetaMock.mockReturnValue(new Map([[mockPluginMeta.pluginId, mockPluginMeta]]));
|
getExtensionPointPluginMetaMock.mockReturnValue(new Map([[mockPluginMeta.pluginId, mockPluginMeta]]));
|
||||||
|
|
||||||
jest.replaceProperty(config.featureToggles, 'extensionSidebar', true);
|
|
||||||
|
|
||||||
locationObservableMock = {
|
locationObservableMock = {
|
||||||
subscribe: jest.fn((callback) => {
|
subscribe: jest.fn((callback) => {
|
||||||
locationObservableMock.callback = callback;
|
locationObservableMock.callback = callback;
|
||||||
|
@ -113,7 +104,6 @@ describe('ExtensionSidebarProvider', () => {
|
||||||
<div data-testid="docked-component-id">{context.dockedComponentId || 'undefined'}</div>
|
<div data-testid="docked-component-id">{context.dockedComponentId || 'undefined'}</div>
|
||||||
<div data-testid="available-components-size">{context.availableComponents.size}</div>
|
<div data-testid="available-components-size">{context.availableComponents.size}</div>
|
||||||
<div data-testid="plugin-ids">{Array.from(context.availableComponents.keys()).join(', ')}</div>
|
<div data-testid="plugin-ids">{Array.from(context.availableComponents.keys()).join(', ')}</div>
|
||||||
<div data-testid="is-enabled">{context.isEnabled.toString()}</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -128,20 +118,6 @@ describe('ExtensionSidebarProvider', () => {
|
||||||
expect(screen.getByTestId('is-open')).toHaveTextContent('false');
|
expect(screen.getByTestId('is-open')).toHaveTextContent('false');
|
||||||
expect(screen.getByTestId('docked-component-id')).toHaveTextContent('undefined');
|
expect(screen.getByTestId('docked-component-id')).toHaveTextContent('undefined');
|
||||||
expect(screen.getByTestId('available-components-size')).toHaveTextContent('1');
|
expect(screen.getByTestId('available-components-size')).toHaveTextContent('1');
|
||||||
expect(screen.getByTestId('is-enabled')).toHaveTextContent('true');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have empty available components when feature toggle is disabled', () => {
|
|
||||||
jest.replaceProperty(config.featureToggles, 'extensionSidebar', false);
|
|
||||||
|
|
||||||
render(
|
|
||||||
<ExtensionSidebarContextProvider>
|
|
||||||
<TestComponent />
|
|
||||||
</ExtensionSidebarContextProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.getByTestId('is-enabled')).toHaveTextContent('false');
|
|
||||||
expect(screen.getByTestId('available-components-size')).toHaveTextContent('0');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should load docked component from storage if available', () => {
|
it('should load docked component from storage if available', () => {
|
||||||
|
@ -158,22 +134,6 @@ describe('ExtensionSidebarProvider', () => {
|
||||||
expect(screen.getByTestId('docked-component-id')).toHaveTextContent(componentId);
|
expect(screen.getByTestId('docked-component-id')).toHaveTextContent(componentId);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not load docked component from storage if feature toggle is disabled', () => {
|
|
||||||
jest.replaceProperty(config.featureToggles, 'extensionSidebar', false);
|
|
||||||
|
|
||||||
const componentId = getComponentIdFromComponentMeta(mockPluginMeta.pluginId, mockComponent);
|
|
||||||
(store.get as jest.Mock).mockReturnValue(componentId);
|
|
||||||
|
|
||||||
render(
|
|
||||||
<ExtensionSidebarContextProvider>
|
|
||||||
<TestComponent />
|
|
||||||
</ExtensionSidebarContextProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.getByTestId('is-open')).toHaveTextContent('false');
|
|
||||||
expect(screen.getByTestId('docked-component-id')).toHaveTextContent('undefined');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update storage when docked component changes', () => {
|
it('should update storage when docked component changes', () => {
|
||||||
const componentId = getComponentIdFromComponentMeta(mockPluginMeta.pluginId, mockComponent);
|
const componentId = getComponentIdFromComponentMeta(mockPluginMeta.pluginId, mockComponent);
|
||||||
|
|
||||||
|
@ -260,18 +220,6 @@ describe('ExtensionSidebarProvider', () => {
|
||||||
expect(subscribeSpy).toHaveBeenCalledWith(CloseExtensionSidebarEvent, expect.any(Function));
|
expect(subscribeSpy).toHaveBeenCalledWith(CloseExtensionSidebarEvent, expect.any(Function));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not subscribe to OpenExtensionSidebarEvent or CloseExtensionSidebarEvent when feature is disabled', () => {
|
|
||||||
jest.replaceProperty(config.featureToggles, 'extensionSidebar', false);
|
|
||||||
|
|
||||||
render(
|
|
||||||
<ExtensionSidebarContextProvider>
|
|
||||||
<TestComponent />
|
|
||||||
</ExtensionSidebarContextProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(subscribeSpy).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set dockedComponentId and props when receiving a valid OpenExtensionSidebarEvent', () => {
|
it('should set dockedComponentId and props when receiving a valid OpenExtensionSidebarEvent', () => {
|
||||||
const TestComponentWithProps = () => {
|
const TestComponentWithProps = () => {
|
||||||
const context = useExtensionSidebarContext();
|
const context = useExtensionSidebarContext();
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { createContext, ReactNode, useCallback, useContext, useEffect, useState,
|
||||||
import { useLocalStorage } from 'react-use';
|
import { useLocalStorage } from 'react-use';
|
||||||
|
|
||||||
import { PluginExtensionPoints, store, type ExtensionInfo } from '@grafana/data';
|
import { PluginExtensionPoints, store, type ExtensionInfo } from '@grafana/data';
|
||||||
import { config, getAppEvents, reportInteraction, usePluginLinks, locationService } from '@grafana/runtime';
|
import { getAppEvents, reportInteraction, usePluginLinks, locationService } from '@grafana/runtime';
|
||||||
import { ExtensionPointPluginMeta, getExtensionPointPluginMeta } from 'app/features/plugins/extensions/utils';
|
import { ExtensionPointPluginMeta, getExtensionPointPluginMeta } from 'app/features/plugins/extensions/utils';
|
||||||
import { CloseExtensionSidebarEvent, OpenExtensionSidebarEvent } from 'app/types/events';
|
import { CloseExtensionSidebarEvent, OpenExtensionSidebarEvent } from 'app/types/events';
|
||||||
|
|
||||||
|
@ -18,10 +18,6 @@ const PERMITTED_EXTENSION_SIDEBAR_PLUGINS = [
|
||||||
];
|
];
|
||||||
|
|
||||||
export type ExtensionSidebarContextType = {
|
export type ExtensionSidebarContextType = {
|
||||||
/**
|
|
||||||
* Whether the extension sidebar is enabled.
|
|
||||||
*/
|
|
||||||
isEnabled: boolean;
|
|
||||||
/**
|
/**
|
||||||
* Whether the extension sidebar is open.
|
* Whether the extension sidebar is open.
|
||||||
*/
|
*/
|
||||||
|
@ -51,7 +47,6 @@ export type ExtensionSidebarContextType = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ExtensionSidebarContext = createContext<ExtensionSidebarContextType>({
|
export const ExtensionSidebarContext = createContext<ExtensionSidebarContextType>({
|
||||||
isEnabled: !!config.featureToggles.extensionSidebar,
|
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
dockedComponentId: undefined,
|
dockedComponentId: undefined,
|
||||||
setDockedComponentId: () => {},
|
setDockedComponentId: () => {},
|
||||||
|
@ -99,13 +94,11 @@ export const ExtensionSidebarContextProvider = ({ children }: ExtensionSidebarCo
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const isEnabled = !!config.featureToggles.extensionSidebar;
|
|
||||||
// get all components for this extension point, but only for the permitted plugins
|
// get all components for this extension point, but only for the permitted plugins
|
||||||
// if the extension sidebar is not enabled, we will return an empty map
|
// if the extension sidebar is not enabled, we will return an empty map
|
||||||
const availableComponents = useMemo(
|
const availableComponents = useMemo(
|
||||||
() =>
|
() =>
|
||||||
isEnabled
|
new Map(
|
||||||
? new Map(
|
|
||||||
Array.from(getExtensionPointPluginMeta(PluginExtensionPoints.ExtensionSidebar).entries()).filter(
|
Array.from(getExtensionPointPluginMeta(PluginExtensionPoints.ExtensionSidebar).entries()).filter(
|
||||||
([pluginId, pluginMeta]) =>
|
([pluginId, pluginMeta]) =>
|
||||||
PERMITTED_EXTENSION_SIDEBAR_PLUGINS.includes(pluginId) &&
|
PERMITTED_EXTENSION_SIDEBAR_PLUGINS.includes(pluginId) &&
|
||||||
|
@ -115,15 +108,8 @@ export const ExtensionSidebarContextProvider = ({ children }: ExtensionSidebarCo
|
||||||
pluginMeta.addedComponents.some((component) => component.title === link.title)
|
pluginMeta.addedComponents.some((component) => component.title === link.title)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
: new Map<
|
[links]
|
||||||
string,
|
|
||||||
{
|
|
||||||
readonly addedComponents: ExtensionInfo[];
|
|
||||||
readonly addedLinks: ExtensionInfo[];
|
|
||||||
}
|
|
||||||
>(),
|
|
||||||
[isEnabled, links]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// check if the stored docked component is still available
|
// check if the stored docked component is still available
|
||||||
|
@ -164,10 +150,6 @@ export const ExtensionSidebarContextProvider = ({ children }: ExtensionSidebarCo
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// handler to open the extension sidebar from plugins. this is done with the `helpers.openSidebar` function
|
// handler to open the extension sidebar from plugins. this is done with the `helpers.openSidebar` function
|
||||||
const openSidebarHandler = (event: OpenExtensionSidebarEvent) => {
|
const openSidebarHandler = (event: OpenExtensionSidebarEvent) => {
|
||||||
if (
|
if (
|
||||||
|
@ -195,7 +177,7 @@ export const ExtensionSidebarContextProvider = ({ children }: ExtensionSidebarCo
|
||||||
openSubscription.unsubscribe();
|
openSubscription.unsubscribe();
|
||||||
closeSubscription.unsubscribe();
|
closeSubscription.unsubscribe();
|
||||||
};
|
};
|
||||||
}, [isEnabled, setDockedComponentWithProps, availableComponents]);
|
}, [setDockedComponentWithProps, availableComponents]);
|
||||||
|
|
||||||
// update the stored docked component id when it changes
|
// update the stored docked component id when it changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -226,8 +208,7 @@ export const ExtensionSidebarContextProvider = ({ children }: ExtensionSidebarCo
|
||||||
return (
|
return (
|
||||||
<ExtensionSidebarContext.Provider
|
<ExtensionSidebarContext.Provider
|
||||||
value={{
|
value={{
|
||||||
isEnabled,
|
isOpen: dockedComponentId !== undefined,
|
||||||
isOpen: isEnabled && dockedComponentId !== undefined,
|
|
||||||
dockedComponentId,
|
dockedComponentId,
|
||||||
setDockedComponentId: (componentId) => setDockedComponentWithProps(componentId, undefined),
|
setDockedComponentId: (componentId) => setDockedComponentWithProps(componentId, undefined),
|
||||||
availableComponents,
|
availableComponents,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
import { EventBusSrv, store } from '@grafana/data';
|
import { EventBusSrv, store } from '@grafana/data';
|
||||||
import { config, setAppEvents, usePluginLinks } from '@grafana/runtime';
|
import { setAppEvents, usePluginLinks } from '@grafana/runtime';
|
||||||
import { getExtensionPointPluginMeta } from 'app/features/plugins/extensions/utils';
|
import { getExtensionPointPluginMeta } from 'app/features/plugins/extensions/utils';
|
||||||
|
|
||||||
import { ExtensionSidebarContextProvider, useExtensionSidebarContext } from './ExtensionSidebarProvider';
|
import { ExtensionSidebarContextProvider, useExtensionSidebarContext } from './ExtensionSidebarProvider';
|
||||||
|
@ -26,13 +26,6 @@ jest.mock('@grafana/data', () => ({
|
||||||
|
|
||||||
jest.mock('@grafana/runtime', () => ({
|
jest.mock('@grafana/runtime', () => ({
|
||||||
...jest.requireActual('@grafana/runtime'),
|
...jest.requireActual('@grafana/runtime'),
|
||||||
config: {
|
|
||||||
...jest.requireActual('@grafana/runtime').config,
|
|
||||||
featureToggles: {
|
|
||||||
...jest.requireActual('@grafana/runtime').config.featureToggles,
|
|
||||||
extensionSidebar: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
usePluginLinks: jest.fn().mockImplementation(() => ({
|
usePluginLinks: jest.fn().mockImplementation(() => ({
|
||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
|
@ -81,7 +74,6 @@ describe('ExtensionToolbarItem', () => {
|
||||||
(store.get as jest.Mock).mockClear();
|
(store.get as jest.Mock).mockClear();
|
||||||
(store.set as jest.Mock).mockClear();
|
(store.set as jest.Mock).mockClear();
|
||||||
(store.delete as jest.Mock).mockClear();
|
(store.delete as jest.Mock).mockClear();
|
||||||
jest.replaceProperty(config.featureToggles, 'extensionSidebar', true);
|
|
||||||
setAppEvents(new EventBusSrv());
|
setAppEvents(new EventBusSrv());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -89,12 +81,6 @@ describe('ExtensionToolbarItem', () => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not render when feature toggle is disabled', () => {
|
|
||||||
jest.replaceProperty(config.featureToggles, 'extensionSidebar', false);
|
|
||||||
setup();
|
|
||||||
expect(screen.queryByRole('button')).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not render when no components are available', () => {
|
it('should not render when no components are available', () => {
|
||||||
(getExtensionPointPluginMeta as jest.Mock).mockReturnValue(new Map());
|
(getExtensionPointPluginMeta as jest.Mock).mockReturnValue(new Map());
|
||||||
setup();
|
setup();
|
||||||
|
|
|
@ -13,9 +13,9 @@ import { ExtensionToolbarItemButton } from './ExtensionToolbarItemButton';
|
||||||
type ComponentWithPluginId = ExtensionInfo & { pluginId: string };
|
type ComponentWithPluginId = ExtensionInfo & { pluginId: string };
|
||||||
|
|
||||||
export function ExtensionToolbarItem() {
|
export function ExtensionToolbarItem() {
|
||||||
const { availableComponents, dockedComponentId, setDockedComponentId, isEnabled } = useExtensionSidebarContext();
|
const { availableComponents, dockedComponentId, setDockedComponentId } = useExtensionSidebarContext();
|
||||||
|
|
||||||
if (!isEnabled || availableComponents.size === 0) {
|
if (availableComponents.size === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -104,7 +104,7 @@ export const SingleTopBar = memo(function SingleTopBar({
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
)}
|
)}
|
||||||
<NavToolbarSeparator />
|
<NavToolbarSeparator />
|
||||||
{config.featureToggles.extensionSidebar && !isSmallScreen && <ExtensionToolbarItem />}
|
{!isSmallScreen && <ExtensionToolbarItem />}
|
||||||
{!showToolbarLevel && actions}
|
{!showToolbarLevel && actions}
|
||||||
{!contextSrv.user.isSignedIn && <SignInLink />}
|
{!contextSrv.user.isSignedIn && <SignInLink />}
|
||||||
<InviteUserButton />
|
<InviteUserButton />
|
||||||
|
|
Loading…
Reference in New Issue