From c59aff3bb9b592fedbec47a93be539cee57e2bd5 Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Wed, 24 Sep 2025 19:22:43 +0300 Subject: [PATCH] Provisioning: Disable editing options for read only repositories (#111547) --- .../dashgrid/DashboardEmpty.test.tsx | 33 +++++++++++++++++++ .../dashboard/dashgrid/DashboardEmpty.tsx | 12 +++++-- .../GettingStarted/GettingStartedPage.tsx | 20 ++++------- .../features/provisioning/utils/repository.ts | 4 +-- public/locales/en-US/grafana.json | 4 +-- 5 files changed, 53 insertions(+), 20 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/DashboardEmpty.test.tsx b/public/app/features/dashboard/dashgrid/DashboardEmpty.test.tsx index 7dd3f6c4165..06f1438646d 100644 --- a/public/app/features/dashboard/dashgrid/DashboardEmpty.test.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardEmpty.test.tsx @@ -28,6 +28,18 @@ jest.mock('app/features/dashboard/utils/dashboard', () => ({ onAddLibraryPanel: jest.fn(), })); +jest.mock('app/features/provisioning/hooks/useGetResourceRepositoryView', () => ({ + useGetResourceRepositoryView: jest.fn(() => ({ + isReadOnlyRepo: false, + isInstanceManaged: false, + isLoading: false, + })), +})); + +const mockUseGetResourceRepositoryView = jest.mocked( + require('app/features/provisioning/hooks/useGetResourceRepositoryView').useGetResourceRepositoryView +); + function setup(options?: Partial) { const props = { dashboard: createDashboardModelFixture(defaultDashboard), @@ -40,6 +52,12 @@ function setup(options?: Partial) { beforeEach(() => { jest.clearAllMocks(); + // Reset the mock to default state + mockUseGetResourceRepositoryView.mockReturnValue({ + isReadOnlyRepo: false, + isInstanceManaged: false, + isLoading: false, + }); }); it('renders page with correct title for an empty dashboard', () => { @@ -117,3 +135,18 @@ it('renders page without Add Widget button when feature flag is disabled', () => expect(screen.getByRole('button', { name: 'Add library panel' })).toBeInTheDocument(); expect(screen.queryByRole('button', { name: 'Add widget' })).not.toBeInTheDocument(); }); + +it('renders with buttons disabled when repository is read-only', () => { + // Mock the hook to return read-only repository + mockUseGetResourceRepositoryView.mockReturnValue({ + isReadOnlyRepo: true, + isInstanceManaged: false, + isLoading: false, + }); + + setup({ canCreate: true }); + + expect(screen.getByRole('button', { name: 'Add visualization' })).toBeDisabled(); + expect(screen.getByRole('button', { name: 'Import dashboard' })).toBeDisabled(); + expect(screen.getByRole('button', { name: 'Add library panel' })).toBeDisabled(); +}); diff --git a/public/app/features/dashboard/dashgrid/DashboardEmpty.tsx b/public/app/features/dashboard/dashgrid/DashboardEmpty.tsx index 9555bea733e..f5f8a6856d1 100644 --- a/public/app/features/dashboard/dashgrid/DashboardEmpty.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardEmpty.tsx @@ -14,6 +14,7 @@ import { import { buildPanelEditScene } from 'app/features/dashboard-scene/panel-edit/PanelEditor'; import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene'; import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions'; +import { useGetResourceRepositoryView } from 'app/features/provisioning/hooks/useGetResourceRepositoryView'; import { useDispatch, useSelector } from 'app/types/store'; import { setInitialDatasource } from '../state/reducers'; @@ -28,6 +29,11 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => { const dispatch = useDispatch(); const initialDatasource = useSelector((state) => state.dashboard.initialDatasource); + // Get repository information to check if it's read-only + const { isReadOnlyRepo } = useGetResourceRepositoryView({ + folderName: dashboard instanceof DashboardScene ? dashboard.state.meta.folderUid : dashboard.meta.folderUid, + }); + const onAddVisualization = () => { let id; if (dashboard instanceof DashboardScene) { @@ -77,7 +83,7 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => { icon="plus" data-testid={selectors.pages.AddDashboard.itemButton('Create new panel button')} onClick={onAddVisualization} - disabled={!canCreate} + disabled={!canCreate || isReadOnlyRepo} > Add visualization @@ -101,7 +107,7 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => { fill="outline" data-testid={selectors.pages.AddDashboard.itemButton('Add a panel from the panel library button')} onClick={onAddLibraryPanel} - disabled={!canCreate || isProvisioned} + disabled={!canCreate || isProvisioned || isReadOnlyRepo} > Add library panel @@ -131,7 +137,7 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => { DashboardInteractions.emptyDashboardButtonClicked({ item: 'import_dashboard' }); onImportDashboard(); }} - disabled={!canCreate} + disabled={!canCreate || isReadOnlyRepo} > Import dashboard diff --git a/public/app/features/provisioning/GettingStarted/GettingStartedPage.tsx b/public/app/features/provisioning/GettingStarted/GettingStartedPage.tsx index ff9999698ba..0a9d6822780 100644 --- a/public/app/features/provisioning/GettingStarted/GettingStartedPage.tsx +++ b/public/app/features/provisioning/GettingStarted/GettingStartedPage.tsx @@ -1,7 +1,7 @@ import { GrafanaEdition } from '@grafana/data/internal'; import { Trans, t } from '@grafana/i18n'; import { config } from '@grafana/runtime'; -import { Box, Text, TextLink } from '@grafana/ui'; +import { Alert, Stack, Text, TextLink } from '@grafana/ui'; import { Repository } from 'app/api/clients/provisioning/v0alpha1'; import { Page } from 'app/core/components/Page/Page'; @@ -24,8 +24,10 @@ export default function GettingStartedPage({ items }: Props) { }} > - - + + + + ); @@ -39,15 +41,7 @@ function Banner() { } return ( - + This feature is currently under active development. For the best experience and latest improvements, we @@ -58,6 +52,6 @@ function Banner() { of Grafana. - + ); } diff --git a/public/app/features/provisioning/utils/repository.ts b/public/app/features/provisioning/utils/repository.ts index 0f3d39728f6..f8990c14897 100644 --- a/public/app/features/provisioning/utils/repository.ts +++ b/public/app/features/provisioning/utils/repository.ts @@ -25,11 +25,11 @@ export const getReadOnlyTooltipText = ({ isLocal = false }) => { return isLocal ? t( 'provisioning.read-only-local-tooltip', - 'This folder is read-only and provisioned through file provisioning. To make any changes in the folder, update the connected file repository. To modify the folder settings go to Administration > Provisioning > Repositories.' + 'This resource is read-only and provisioned through file provisioning. To make any changes, update the connected repository. To modify the settings go to Administration > Provisioning > Repositories.' ) : t( 'provisioning.read-only-remote-tooltip', - 'This folder is read-only and provisioned through Git. To make any changes in the folder, update the connected repository. To modify the folder settings go to Administration > Provisioning > Repositories.' + 'This resource is read-only and provisioned through Git. To make any changes, update the connected repository. To modify the settings go to Administration > Provisioning > Repositories.' ); }; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index c9dfbd95d55..298ac1a1e84 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -11526,8 +11526,8 @@ "subtitle": "Use this option if you want to sync and manage your entire Grafana instance through external storage." } }, - "read-only-local-tooltip": "This folder is read-only and provisioned through file provisioning. To make any changes in the folder, update the connected file repository. To modify the folder settings go to Administration > Provisioning > Repositories.", - "read-only-remote-tooltip": "This folder is read-only and provisioned through Git. To make any changes in the folder, update the connected repository. To modify the folder settings go to Administration > Provisioning > Repositories.", + "read-only-local-tooltip": "This resource is read-only and provisioned through file provisioning. To make any changes, update the connected repository. To modify the settings go to Administration > Provisioning > Repositories.", + "read-only-remote-tooltip": "This resource is read-only and provisioned through Git. To make any changes, update the connected repository. To modify the settings go to Administration > Provisioning > Repositories.", "recent-jobs": { "active-jobs": "active jobs", "column-action": "Action",