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