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:
@@ -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];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user