mirror of https://github.com/grafana/grafana.git
				
				
				
			Dashboards: Disable saving in the UI for provisioned k8s dashboards (#105429)
* disable editing in UI for k8s dashboards * lint * use AnnoKeyManagerAllowsEdits; clean up * fix * add tests; update the logic * clean up
This commit is contained in:
		
							parent
							
								
									0166b6bcc6
								
							
						
					
					
						commit
						c6ada816c2
					
				|  | @ -3,9 +3,12 @@ import userEvent from '@testing-library/user-event'; | |||
| import { TestProvider } from 'test/helpers/TestProvider'; | ||||
| 
 | ||||
| import { selectors } from '@grafana/e2e-selectors'; | ||||
| import { config } from '@grafana/runtime'; | ||||
| import { sceneGraph, SceneRefreshPicker } from '@grafana/scenes'; | ||||
| import { AnnoKeyManagerKind, ManagerKind } from 'app/features/apiserver/types'; | ||||
| import { SaveDashboardResponseDTO } from 'app/types'; | ||||
| 
 | ||||
| import { DashboardSceneState } from '../scene/DashboardScene'; | ||||
| import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene'; | ||||
| import { transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel'; | ||||
| 
 | ||||
|  | @ -152,6 +155,70 @@ describe('SaveDashboardDrawer', () => { | |||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   describe('When a dashboard is managed by an external system', () => { | ||||
|     beforeEach(() => { | ||||
|       config.featureToggles.provisioning = true; | ||||
|     }); | ||||
| 
 | ||||
|     afterEach(() => { | ||||
|       config.featureToggles.provisioning = false; | ||||
|     }); | ||||
| 
 | ||||
|     it('It should show the changes tab if the resource can be edited', async () => { | ||||
|       const { dashboard, openAndRender } = setup({ | ||||
|         meta: { | ||||
|           k8s: { | ||||
|             annotations: { | ||||
|               [AnnoKeyManagerKind]: ManagerKind.Repo, | ||||
|             }, | ||||
|           }, | ||||
|         }, | ||||
|       }); | ||||
| 
 | ||||
|       // just changing the title here, in real case scenario changes are reflected through migrations
 | ||||
|       // eg. panel version - same for other manager tests below
 | ||||
|       dashboard.setState({ title: 'updated title' }); | ||||
|       openAndRender(); | ||||
| 
 | ||||
|       expect(screen.queryByRole('tab', { name: /Changes/ })).toBeInTheDocument(); | ||||
|     }); | ||||
| 
 | ||||
|     it('It should not show the changes tab if the resource cannot be edited; kubectl', async () => { | ||||
|       const { dashboard, openAndRender } = setup({ | ||||
|         meta: { k8s: { annotations: { [AnnoKeyManagerKind]: ManagerKind.Kubectl } } }, | ||||
|       }); | ||||
| 
 | ||||
|       dashboard.setState({ title: 'updated title' }); | ||||
|       openAndRender(); | ||||
| 
 | ||||
|       expect(screen.queryByRole('tab', { name: /Changes/ })).not.toBeInTheDocument(); | ||||
|     }); | ||||
| 
 | ||||
|     it('It should not show the changes tab if the resource cannot be edited; terraform', async () => { | ||||
|       const { dashboard, openAndRender } = setup({ | ||||
|         meta: { k8s: { annotations: { [AnnoKeyManagerKind]: ManagerKind.Terraform } } }, | ||||
|       }); | ||||
| 
 | ||||
|       dashboard.setState({ title: 'updated title' }); | ||||
|       openAndRender(); | ||||
| 
 | ||||
|       expect(screen.queryByRole('tab', { name: /Changes/ })).not.toBeInTheDocument(); | ||||
|     }); | ||||
| 
 | ||||
|     it('It should not show the changes tab if the resource cannot be edited; plugin', async () => { | ||||
|       const { dashboard, openAndRender } = setup({ | ||||
|         meta: { | ||||
|           k8s: { annotations: { [AnnoKeyManagerKind]: ManagerKind.Plugin } }, | ||||
|         }, | ||||
|       }); | ||||
| 
 | ||||
|       dashboard.setState({ title: 'updated title' }); | ||||
|       openAndRender(); | ||||
| 
 | ||||
|       expect(screen.queryByRole('tab', { name: /Changes/ })).not.toBeInTheDocument(); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   describe('Save as copy', () => { | ||||
|     it('Should show save as form', async () => { | ||||
|       const { openAndRender } = setup(); | ||||
|  | @ -199,7 +266,7 @@ function mockSaveDashboard(options: Partial<MockBackendApiOptions> = {}) { | |||
| 
 | ||||
| let cleanUp = () => {}; | ||||
| 
 | ||||
| function setup() { | ||||
| function setup(overrides?: Partial<DashboardSceneState>) { | ||||
|   const dashboard = transformSaveModelToScene({ | ||||
|     dashboard: { | ||||
|       title: 'hello', | ||||
|  | @ -209,6 +276,7 @@ function setup() { | |||
|       version: 10, | ||||
|     }, | ||||
|     meta: {}, | ||||
|     ...overrides, | ||||
|   }); | ||||
| 
 | ||||
|   // Clear any data layers
 | ||||
|  |  | |||
|  | @ -56,6 +56,7 @@ export class SaveDashboardDrawer extends SceneObjectBase<SaveDashboardDrawerStat | |||
|     const dashboard = model.state.dashboardRef.resolve(); | ||||
|     const { meta } = dashboard.useState(); | ||||
|     const { provisioned: isProvisioned, folderTitle } = meta; | ||||
|     const managedResourceCannotBeEdited = dashboard.managedResourceCannotBeEdited(); | ||||
|     const isProvisionedNG = useIsProvisionedNG(dashboard); | ||||
| 
 | ||||
|     const tabs = ( | ||||
|  | @ -65,7 +66,7 @@ export class SaveDashboardDrawer extends SceneObjectBase<SaveDashboardDrawerStat | |||
|           active={!showDiff} | ||||
|           onChangeTab={() => model.setState({ showDiff: false })} | ||||
|         /> | ||||
|         {changesCount > 0 && ( | ||||
|         {changesCount > 0 && !managedResourceCannotBeEdited && ( | ||||
|           <Tab | ||||
|             label={t('dashboard-scene.save-dashboard-drawer.tabs.label-changes', 'Changes')} | ||||
|             active={showDiff} | ||||
|  | @ -106,7 +107,7 @@ export class SaveDashboardDrawer extends SceneObjectBase<SaveDashboardDrawerStat | |||
|         return <SaveDashboardAsForm dashboard={dashboard} changeInfo={changeInfo} />; | ||||
|       } | ||||
| 
 | ||||
|       if (isProvisioned) { | ||||
|       if (isProvisioned || managedResourceCannotBeEdited) { | ||||
|         return <SaveProvisionedDashboardForm dashboard={dashboard} changeInfo={changeInfo} drawer={model} />; | ||||
|       } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| import { CoreApp, GrafanaConfig, LoadingState, getDefaultTimeRange, locationUtil, store } from '@grafana/data'; | ||||
| import { locationService, RefreshEvent } from '@grafana/runtime'; | ||||
| import { config, locationService, RefreshEvent } from '@grafana/runtime'; | ||||
| import { | ||||
|   sceneGraph, | ||||
|   SceneGridLayout, | ||||
|  | @ -15,6 +15,7 @@ import { | |||
| import { Dashboard, DashboardCursorSync, LibraryPanel } from '@grafana/schema'; | ||||
| import appEvents from 'app/core/app_events'; | ||||
| import { LS_PANEL_COPY_KEY } from 'app/core/constants'; | ||||
| import { AnnoKeyManagerKind, ManagerKind } from 'app/features/apiserver/types'; | ||||
| import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; | ||||
| import { VariablesChanged } from 'app/features/variables/types'; | ||||
| 
 | ||||
|  | @ -797,6 +798,82 @@ describe('DashboardScene', () => { | |||
|       } | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   describe('When checking dashboard managed by an external system', () => { | ||||
|     beforeEach(() => { | ||||
|       config.featureToggles.provisioning = true; | ||||
|     }); | ||||
| 
 | ||||
|     afterEach(() => { | ||||
|       config.featureToggles.provisioning = false; | ||||
|     }); | ||||
| 
 | ||||
|     it('should return true if the dashboard is managed', () => { | ||||
|       const scene = buildTestScene({ | ||||
|         meta: { | ||||
|           k8s: { | ||||
|             annotations: { | ||||
|               [AnnoKeyManagerKind]: ManagerKind.Repo, | ||||
|             }, | ||||
|           }, | ||||
|         }, | ||||
|       }); | ||||
|       expect(scene.isManaged()).toBe(true); | ||||
|     }); | ||||
| 
 | ||||
|     it('dashboard should be editable if managed by repo', () => { | ||||
|       const scene = buildTestScene({ | ||||
|         meta: { | ||||
|           k8s: { | ||||
|             annotations: { | ||||
|               [AnnoKeyManagerKind]: ManagerKind.Repo, | ||||
|             }, | ||||
|           }, | ||||
|         }, | ||||
|       }); | ||||
|       expect(scene.managedResourceCannotBeEdited()).toBe(false); | ||||
|     }); | ||||
| 
 | ||||
|     it('dashboard should not be editable if managed by systems that do not allow edits: kubectl', () => { | ||||
|       const scene = buildTestScene({ | ||||
|         meta: { | ||||
|           k8s: { | ||||
|             annotations: { | ||||
|               [AnnoKeyManagerKind]: ManagerKind.Kubectl, | ||||
|             }, | ||||
|           }, | ||||
|         }, | ||||
|       }); | ||||
|       expect(scene.managedResourceCannotBeEdited()).toBe(true); | ||||
|     }); | ||||
| 
 | ||||
|     it('dashboard should not be editable if managed by systems that do not allow edits: terraform', () => { | ||||
|       const scene = buildTestScene({ | ||||
|         meta: { | ||||
|           k8s: { | ||||
|             annotations: { | ||||
|               [AnnoKeyManagerKind]: ManagerKind.Terraform, | ||||
|             }, | ||||
|           }, | ||||
|         }, | ||||
|       }); | ||||
|       expect(scene.managedResourceCannotBeEdited()).toBe(true); | ||||
|     }); | ||||
| 
 | ||||
|     it('dashboard should not be editable if managed by systems that do not allow edits: plugin', () => { | ||||
|       const scene = buildTestScene({ | ||||
|         meta: { | ||||
|           k8s: { annotations: { [AnnoKeyManagerKind]: ManagerKind.Plugin } }, | ||||
|         }, | ||||
|       }); | ||||
|       expect(scene.managedResourceCannotBeEdited()).toBe(true); | ||||
|     }); | ||||
| 
 | ||||
|     it('dashboard should be editable if not managed', () => { | ||||
|       const scene = buildTestScene(); | ||||
|       expect(scene.managedResourceCannotBeEdited()).toBe(false); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
| function buildTestScene(overrides?: Partial<DashboardSceneState>) { | ||||
|  |  | |||
|  | @ -33,7 +33,13 @@ import { VariablesChanged } from 'app/features/variables/types'; | |||
| import { DashboardDTO, DashboardMeta, KioskMode, SaveDashboardResponseDTO } from 'app/types'; | ||||
| import { ShowConfirmModalEvent } from 'app/types/events'; | ||||
| 
 | ||||
| import { AnnoKeyManagerKind, AnnoKeySourcePath, ManagerKind, ResourceForCreate } from '../../apiserver/types'; | ||||
| import { | ||||
|   AnnoKeyManagerAllowsEdits, | ||||
|   AnnoKeyManagerKind, | ||||
|   AnnoKeySourcePath, | ||||
|   ManagerKind, | ||||
|   ResourceForCreate, | ||||
| } from '../../apiserver/types'; | ||||
| import { DashboardEditPane } from '../edit-pane/DashboardEditPane'; | ||||
| import { PanelEditor } from '../panel-edit/PanelEditor'; | ||||
| import { DashboardSceneChangeTracker } from '../saving/DashboardSceneChangeTracker'; | ||||
|  | @ -311,7 +317,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> impleme | |||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (!this.state.isDirty || skipConfirm) { | ||||
|     if (!this.state.isDirty || skipConfirm || this.managedResourceCannotBeEdited()) { | ||||
|       this.exitEditModeConfirmed(restoreInitialState || this.state.isDirty); | ||||
|       return; | ||||
|     } | ||||
|  | @ -804,6 +810,12 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> impleme | |||
|     return Boolean(this.getManagerKind() === ManagerKind.Repo); | ||||
|   } | ||||
| 
 | ||||
|   managedResourceCannotBeEdited() { | ||||
|     return ( | ||||
|       this.isManaged() && !this.isManagedRepository() && !this.state.meta.k8s?.annotations?.[AnnoKeyManagerAllowsEdits] | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   getPath() { | ||||
|     return this.state.meta.k8s?.annotations?.[AnnoKeySourcePath]; | ||||
|   } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue