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
pull/105845/head
Haris Rozajac 2 months ago committed by GitHub
parent 0166b6bcc6
commit c6ada816c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 70
      public/app/features/dashboard-scene/saving/SaveDashboardDrawer.test.tsx
  2. 5
      public/app/features/dashboard-scene/saving/SaveDashboardDrawer.tsx
  3. 79
      public/app/features/dashboard-scene/scene/DashboardScene.test.tsx
  4. 16
      public/app/features/dashboard-scene/scene/DashboardScene.tsx

@ -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…
Cancel
Save