From b6d7374b25a1e78a91c908126b5a03f9d197aa18 Mon Sep 17 00:00:00 2001 From: Sven Grossmann Date: Mon, 1 Sep 2025 12:14:17 +0200 Subject: [PATCH] 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 --- .../src/types/featureToggles.gen.ts | 4 -- pkg/services/featuremgmt/registry.go | 7 --- pkg/services/featuremgmt/toggles_gen.csv | 1 - pkg/services/featuremgmt/toggles_gen.go | 4 -- pkg/services/featuremgmt/toggles_gen.json | 3 +- public/app/AppWrapper.tsx | 7 +-- .../core/components/AppChrome/AppChrome.tsx | 3 +- .../ExtensionSidebar.test.tsx | 21 -------- .../ExtensionSidebar/ExtensionSidebar.tsx | 4 +- .../ExtensionSidebarProvider.test.tsx | 54 +------------------ .../ExtensionSidebarProvider.tsx | 47 +++++----------- .../ExtensionToolbarItem.test.tsx | 16 +----- .../ExtensionSidebar/ExtensionToolbarItem.tsx | 4 +- .../AppChrome/TopBar/SingleTopBar.tsx | 2 +- 14 files changed, 26 insertions(+), 151 deletions(-) diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index cc50e705d4b..3b506b414f8 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -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 */ diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 4f2911a3069..57048abcdfd 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -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", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 8895ebcc190..6fb7e2d645a 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -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 diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 13041b4714c..73db0aa6fe5 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -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" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index a7dc6c26854..55ed373d996 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -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", diff --git a/public/app/AppWrapper.tsx b/public/app/AppWrapper.tsx index 1d5a84f954b..5c173604fb5 100644 --- a/public/app/AppWrapper.tsx +++ b/public/app/AppWrapper.tsx @@ -105,9 +105,6 @@ export class AppWrapper extends Component { }; const MaybeTimeRangeProvider = config.featureToggles.timeRangeProvider ? TimeRangeProvider : Fragment; - const MaybeExtensionSidebarProvider = config.featureToggles.extensionSidebar - ? ExtensionSidebarContextProvider - : Fragment; return ( @@ -122,7 +119,7 @@ export class AppWrapper extends Component { - +
@@ -131,7 +128,7 @@ export class AppWrapper extends Component {
-
+
diff --git a/public/app/core/components/AppChrome/AppChrome.tsx b/public/app/core/components/AppChrome/AppChrome.tsx index bc017c618b8..1c11f0add3d 100644 --- a/public/app/core/components/AppChrome/AppChrome.tsx +++ b/public/app/core/components/AppChrome/AppChrome.tsx @@ -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} - {!state.chromeless && isExtensionSidebarEnabled && isExtensionSidebarOpen && ( + {!state.chromeless && isExtensionSidebarOpen && ( { 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(); - 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({ diff --git a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebar.tsx b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebar.tsx index 328ff2aa469..11ab8b354f0 100644 --- a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebar.tsx +++ b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebar.tsx @@ -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({ extensionPointId: PluginExtensionPoints.ExtensionSidebar, }); - if (isLoading || !dockedComponentId || !isEnabled) { + if (isLoading || !dockedComponentId) { return null; } diff --git a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.test.tsx b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.test.tsx index 61651cabb84..7839207632b 100644 --- a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.test.tsx +++ b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.test.tsx @@ -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', () => {
{context.dockedComponentId || 'undefined'}
{context.availableComponents.size}
{Array.from(context.availableComponents.keys()).join(', ')}
-
{context.isEnabled.toString()}
); }; @@ -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( - - - - ); - - 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( - - - - ); - - 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( - - - - ); - - expect(subscribeSpy).not.toHaveBeenCalled(); - }); - it('should set dockedComponentId and props when receiving a valid OpenExtensionSidebarEvent', () => { const TestComponentWithProps = () => { const context = useExtensionSidebarContext(); diff --git a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx index 571b264cdd3..8b90bd24914 100644 --- a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx +++ b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx @@ -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({ - isEnabled: !!config.featureToggles.extensionSidebar, isOpen: false, dockedComponentId: undefined, setDockedComponentId: () => {}, @@ -99,31 +94,22 @@ 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( - Array.from(getExtensionPointPluginMeta(PluginExtensionPoints.ExtensionSidebar).entries()).filter( - ([pluginId, pluginMeta]) => - PERMITTED_EXTENSION_SIDEBAR_PLUGINS.includes(pluginId) && - links.some( - (link) => - link.pluginId === pluginId && - pluginMeta.addedComponents.some((component) => component.title === link.title) - ) + new Map( + Array.from(getExtensionPointPluginMeta(PluginExtensionPoints.ExtensionSidebar).entries()).filter( + ([pluginId, pluginMeta]) => + PERMITTED_EXTENSION_SIDEBAR_PLUGINS.includes(pluginId) && + links.some( + (link) => + link.pluginId === pluginId && + 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 ( setDockedComponentWithProps(componentId, undefined), availableComponents, diff --git a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionToolbarItem.test.tsx b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionToolbarItem.test.tsx index 45b2c662cc7..8fdb8b5eccb 100644 --- a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionToolbarItem.test.tsx +++ b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionToolbarItem.test.tsx @@ -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(); diff --git a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionToolbarItem.tsx b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionToolbarItem.tsx index 50fe56630af..1f5639191b2 100644 --- a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionToolbarItem.tsx +++ b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionToolbarItem.tsx @@ -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; } diff --git a/public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx b/public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx index f2b02f35bd9..644487f6f03 100644 --- a/public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx +++ b/public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx @@ -104,7 +104,7 @@ export const SingleTopBar = memo(function SingleTopBar({ )} - {config.featureToggles.extensionSidebar && !isSmallScreen && } + {!isSmallScreen && } {!showToolbarLevel && actions} {!contextSrv.user.isSignedIn && }