POC/Provisioning: Do not allow deleting provisioned folders/dashboards (#100119)
* Enable deleting dashboard from settings page * Custom provisioned modal * Show modal for folders * Add move check * Betterer
This commit is contained in:
1 parent
83dab41af2
commit
7a903258b0
6 files changed
+93
-4
No files matched your search
@@ -3388,6 +3388,13 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"]
|
||||
],
|
||||
"public/app/features/dashboard-scene/saving/provisioned/ProvisionedResourceDeleteModal.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"]
|
||||
],
|
||||
"public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboard.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
|
||||
|
||||
@@ -442,7 +442,7 @@ export const {
|
||||
|
||||
export { skipToken } from '@reduxjs/toolkit/query/react';
|
||||
|
||||
type FolderDataType = FolderListItemDTO | NestedFolderDTO | FolderDTO;
|
||||
export type FolderDataType = FolderListItemDTO | NestedFolderDTO | FolderDTO;
|
||||
|
||||
// Overloaded function signatures to handle different input types
|
||||
export async function addRepositoryData(data: FolderListItemDTO[]): Promise<FolderListItemDTO[]>;
|
||||
@@ -465,7 +465,12 @@ export async function addRepositoryData(
|
||||
}
|
||||
|
||||
const addRepositoryToItem = (item: FolderDataType) => {
|
||||
const repository = settings.items.find((repo) => repo.name === item.uid);
|
||||
const repository = settings.items.find((repo) => {
|
||||
if (typeof item.repository === 'string') {
|
||||
return repo.name === item.repository;
|
||||
}
|
||||
return repo.name === item.uid;
|
||||
});
|
||||
return repository ? { ...item, repository } : item;
|
||||
};
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { t, Trans } from 'app/core/internationalization';
|
||||
import { FolderDTO } from 'app/types';
|
||||
import { ShowModalReactEvent } from 'app/types/events';
|
||||
|
||||
import { ProvisionedResourceDeleteModal } from '../../dashboard-scene/saving/provisioned/ProvisionedResourceDeleteModal';
|
||||
import { useDeleteFolderMutation, useMoveFolderMutation } from '../api/browseDashboardsAPI';
|
||||
import { getFolderPermissions } from '../permissions';
|
||||
|
||||
@@ -25,7 +26,7 @@ export function FolderActionsButton({ folder }: Props) {
|
||||
const [deleteFolder] = useDeleteFolderMutation();
|
||||
const { canEditFolders, canDeleteFolders, canViewPermissions, canSetPermissions } = getFolderPermissions(folder);
|
||||
// Can only move folders when nestedFolders is enabled
|
||||
const canMoveFolder = config.featureToggles.nestedFolders && canEditFolders;
|
||||
const canMoveFolder = config.featureToggles.nestedFolders && canEditFolders && !folder.repository;
|
||||
|
||||
const onMove = async (destinationUID: string) => {
|
||||
await moveFolder({ folder, destinationUID });
|
||||
@@ -86,6 +87,17 @@ export function FolderActionsButton({ folder }: Props) {
|
||||
);
|
||||
};
|
||||
|
||||
const showDeleteProvisionedModal = () => {
|
||||
appEvents.publish(
|
||||
new ShowModalReactEvent({
|
||||
component: ProvisionedResourceDeleteModal,
|
||||
props: {
|
||||
resource: folder,
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const managePermissionsLabel = t('browse-dashboards.folder-actions-button.manage-permissions', 'Manage permissions');
|
||||
const moveLabel = t('browse-dashboards.folder-actions-button.move', 'Move');
|
||||
const deleteLabel = t('browse-dashboards.folder-actions-button.delete', 'Delete');
|
||||
@@ -94,7 +106,13 @@ export function FolderActionsButton({ folder }: Props) {
|
||||
<Menu>
|
||||
{canViewPermissions && <MenuItem onClick={() => setShowPermissionsDrawer(true)} label={managePermissionsLabel} />}
|
||||
{canMoveFolder && <MenuItem onClick={showMoveModal} label={moveLabel} />}
|
||||
{canDeleteFolders && <MenuItem destructive onClick={showDeleteModal} label={deleteLabel} />}
|
||||
{canDeleteFolders && (
|
||||
<MenuItem
|
||||
destructive
|
||||
onClick={folder.repository ? showDeleteProvisionedModal : showDeleteModal}
|
||||
label={deleteLabel}
|
||||
/>
|
||||
)}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { Button, Modal } from '@grafana/ui';
|
||||
|
||||
import { FolderDataType } from '../../../browse-dashboards/api/browseDashboardsAPI';
|
||||
import { DashboardScene } from '../../scene/DashboardScene';
|
||||
|
||||
export interface Props {
|
||||
onDismiss: () => void;
|
||||
resource: DashboardScene | FolderDataType;
|
||||
}
|
||||
|
||||
export function ProvisionedResourceDeleteModal({ onDismiss, resource }: Props) {
|
||||
const type = isDashboard(resource) ? 'dashboard' : 'folder';
|
||||
return (
|
||||
<Modal isOpen={true} title="Cannot delete provisioned resource" onDismiss={onDismiss}>
|
||||
<>
|
||||
<p>
|
||||
This {type} is managed by version control and cannot be deleted. To remove it, delete it from the repository
|
||||
and synchronise to apply the changes.
|
||||
</p>
|
||||
{isDashboard(resource) && <p>File path: {resource.getPath()}</p>}
|
||||
</>
|
||||
|
||||
<Modal.ButtonRow>
|
||||
<Button variant="primary" onClick={onDismiss}>
|
||||
OK
|
||||
</Button>
|
||||
</Modal.ButtonRow>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function isDashboard(resource: DashboardScene | FolderDataType): resource is DashboardScene {
|
||||
return resource instanceof DashboardScene;
|
||||
}
|
||||
|
||||
export function isFolder(resource: DashboardScene | FolderDataType): resource is FolderDataType {
|
||||
return !isDashboard(resource);
|
||||
}
|
||||
@@ -41,6 +41,7 @@ import { VariablesChanged } from 'app/features/variables/types';
|
||||
import { DashboardDTO, DashboardMeta, KioskMode, SaveDashboardResponseDTO } from 'app/types';
|
||||
import { ShowConfirmModalEvent } from 'app/types/events';
|
||||
|
||||
import { AnnoKeyRepoName, AnnoKeyRepoPath } from '../../apiserver/types';
|
||||
import { DashboardEditPane } from '../edit-pane/DashboardEditPane';
|
||||
import { PanelEditor } from '../panel-edit/PanelEditor';
|
||||
import { DashboardSceneChangeTracker } from '../saving/DashboardSceneChangeTracker';
|
||||
@@ -739,6 +740,21 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
getDashboardChanges(saveTimeRange?: boolean, saveVariables?: boolean, saveRefresh?: boolean): DashboardChangeInfo {
|
||||
return this._serializer.getDashboardChangesFromScene(this, { saveTimeRange, saveVariables, saveRefresh });
|
||||
}
|
||||
|
||||
/**
|
||||
* Provisioned dashboards helpers
|
||||
*/
|
||||
getRepoName() {
|
||||
return this.state.meta.k8s?.annotations?.[AnnoKeyRepoName];
|
||||
}
|
||||
|
||||
isProvisioned() {
|
||||
return Boolean(this.getRepoName());
|
||||
}
|
||||
|
||||
getPath() {
|
||||
return this.state.meta.k8s?.annotations?.[AnnoKeyRepoPath];
|
||||
}
|
||||
}
|
||||
|
||||
export class DashboardVariableDependency implements SceneVariableDependencyConfigLike {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Button, ConfirmModal, Modal, Space, Text } from '@grafana/ui';
|
||||
import { t, Trans } from 'app/core/internationalization';
|
||||
|
||||
import { useDeleteItemsMutation } from '../../browse-dashboards/api/browseDashboardsAPI';
|
||||
import { ProvisionedResourceDeleteModal } from '../saving/provisioned/ProvisionedResourceDeleteModal';
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
|
||||
interface ButtonProps {
|
||||
@@ -53,6 +54,10 @@ export function DeleteDashboardButton({ dashboard }: ButtonProps) {
|
||||
return <ProvisionedDeleteModal dashboardId={dashboard.state.meta.provisionedExternalId} onClose={toggleModal} />;
|
||||
}
|
||||
|
||||
if (dashboard.isProvisioned() && showModal) {
|
||||
return <ProvisionedResourceDeleteModal resource={dashboard} onDismiss={toggleModal} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
|
||||
Reference in new issue
Block a user