SelectionState;
onFolderClick: (uid: string, newOpenState: boolean) => void;
onAllSelectionChange: (newState: boolean) => void;
@@ -48,7 +54,7 @@ export function DashboardsTree({
onItemSelectionChange,
isItemLoaded,
requestLoadMore,
- canSelect = false,
+ permissions,
}: DashboardsTreeProps) {
const treeID = useId();
@@ -94,10 +100,11 @@ export function DashboardsTree({
Header: t('browse-dashboards.dashboards-tree.tags-column', 'Tags'),
Cell: TagsCell,
};
+ const canSelect = canSelectItems(permissions);
const columns = [canSelect && checkboxColumn, nameColumn, tagsColumns].filter(isTruthy);
return columns;
- }, [onFolderClick, canSelect]);
+ }, [onFolderClick, permissions]);
const table = useTable({ columns: tableColumns, data: items }, useCustomFlexLayout);
const { getTableProps, getTableBodyProps, headerGroups } = table;
@@ -109,10 +116,11 @@ export function DashboardsTree({
onAllSelectionChange,
onItemSelectionChange,
treeID,
+ permissions,
}),
// we need this to rerender if items changes
// eslint-disable-next-line react-hooks/exhaustive-deps
- [table, isSelected, onAllSelectionChange, onItemSelectionChange, items, treeID]
+ [table, isSelected, onAllSelectionChange, onItemSelectionChange, items, treeID, permissions]
);
const handleIsItemLoaded = useCallback(
@@ -156,7 +164,7 @@ export function DashboardsTree({
return (
- {column.render('Header', { isSelected, onAllSelectionChange })}
+ {column.render('Header', { isSelected, onAllSelectionChange, permissions })}
);
})}
@@ -203,12 +211,13 @@ interface VirtualListRowProps {
onAllSelectionChange: DashboardsTreeCellProps['onAllSelectionChange'];
onItemSelectionChange: DashboardsTreeCellProps['onItemSelectionChange'];
treeID: string;
+ permissions: BrowseDashboardsPermissions;
};
}
function VirtualListRow({ index, style, data }: VirtualListRowProps) {
const styles = useStyles2(getStyles);
- const { table, isSelected, onItemSelectionChange, treeID } = data;
+ const { table, isSelected, onItemSelectionChange, treeID, permissions } = data;
const { rows, prepareRow } = table;
const row = rows[index];
@@ -240,7 +249,7 @@ function VirtualListRow({ index, style, data }: VirtualListRowProps) {
return (
- {cell.render('Cell', { isSelected, onItemSelectionChange, treeID })}
+ {cell.render('Cell', { isSelected, onItemSelectionChange, treeID, permissions })}
);
})}
diff --git a/public/app/features/browse-dashboards/components/FolderActionsButton.test.tsx b/public/app/features/browse-dashboards/components/FolderActionsButton.test.tsx
index 535be61f806..dd019520aeb 100644
--- a/public/app/features/browse-dashboards/components/FolderActionsButton.test.tsx
+++ b/public/app/features/browse-dashboards/components/FolderActionsButton.test.tsx
@@ -32,6 +32,7 @@ describe('browse-dashboards FolderActionsButton', () => {
canEditFolders: true,
canViewPermissions: true,
canSetPermissions: true,
+ canDeleteDashboards: true,
};
beforeEach(() => {
diff --git a/public/app/features/browse-dashboards/components/SearchView.tsx b/public/app/features/browse-dashboards/components/SearchView.tsx
index 281455f140e..7f7b1c9cbb3 100644
--- a/public/app/features/browse-dashboards/components/SearchView.tsx
+++ b/public/app/features/browse-dashboards/components/SearchView.tsx
@@ -11,11 +11,14 @@ import { useDispatch, useSelector } from 'app/types/store';
import { useHasSelection } from '../state/hooks';
import { setAllSelection, setItemSelectionState } from '../state/slice';
+import { BrowseDashboardsPermissions } from '../types';
+
+import { canEditItemType, canSelectItems } from './utils';
interface SearchViewProps {
height: number;
width: number;
- canSelect: boolean;
+ permissions: BrowseDashboardsPermissions;
searchState: SearchState;
searchStateManager: SearchStateManager;
emptyState?: ReactNode;
@@ -48,7 +51,7 @@ const initialLoadingView = {
export function SearchView({
width,
height,
- canSelect,
+ permissions,
searchState,
searchStateManager: stateManager,
emptyState: emptyStateProp,
@@ -67,7 +70,12 @@ export function SearchView({
return false;
}
- // Currently, this indicates _some_ items are selected, not nessicarily all are
+ // Check if user has permission to select this item type
+ if (!canEditItemType(kind, permissions)) {
+ return false;
+ }
+
+ // Currently, this indicates _some_ items are selected, not necessarily all are
// selected.
if (kind === '*' && uid === '*') {
return hasSelection;
@@ -78,7 +86,7 @@ export function SearchView({
return selectedItems[assertDashboardViewItemKind(kind)][uid] ?? false;
},
- [selectedItems, hasSelection]
+ [selectedItems, hasSelection, permissions]
);
const clearSelection = useCallback(() => {
@@ -87,13 +95,17 @@ export function SearchView({
const handleItemSelectionChange = useCallback(
(kind: string, uid: string) => {
+ if (!canEditItemType(kind, permissions)) {
+ return; // Cannot select this item
+ }
+
const newIsSelected = !selectionChecker(kind, uid);
dispatch(
setItemSelectionState({ item: { kind: assertDashboardViewItemKind(kind), uid }, isSelected: newIsSelected })
);
},
- [selectionChecker, dispatch]
+ [selectionChecker, dispatch, permissions]
);
if (value.totalRows === 0) {
@@ -113,6 +125,7 @@ export function SearchView({
return {emptyState}
;
}
+ const canSelect = canSelectItems(permissions);
const props: SearchResultsProps = {
response: value,
selection: canSelect ? selectionChecker : undefined,
diff --git a/public/app/features/browse-dashboards/components/utils.ts b/public/app/features/browse-dashboards/components/utils.ts
index 0427ba787a0..66a85a6d0ee 100644
--- a/public/app/features/browse-dashboards/components/utils.ts
+++ b/public/app/features/browse-dashboards/components/utils.ts
@@ -6,7 +6,7 @@ import { DashboardViewItem } from 'app/features/search/types';
import { useChildrenByParentUIDState } from '../state/hooks';
import { findItem } from '../state/utils';
-import { DashboardTreeSelection, DashboardViewItemWithUIItems } from '../types';
+import { DashboardTreeSelection, DashboardViewItemWithUIItems, BrowseDashboardsPermissions } from '../types';
export function makeRowID(baseId: string, item: DashboardViewItemWithUIItems) {
return baseId + item.uid;
@@ -105,3 +105,18 @@ export function collectSelectedItems(
return targets;
}
+
+export function canEditItemType(itemKind: string, permissions: BrowseDashboardsPermissions) {
+ const { canEditFolders, canDeleteFolders, canEditDashboards, canDeleteDashboards } = permissions;
+ return itemKind === 'folder'
+ ? Boolean(canEditFolders || canDeleteFolders)
+ : Boolean(canEditDashboards || canDeleteDashboards);
+}
+
+export function canSelectItems(permissions: BrowseDashboardsPermissions) {
+ const { canEditFolders, canDeleteFolders, canEditDashboards, canDeleteDashboards } = permissions;
+ // Users can select items only if they have both edit and delete permissions for at least one item type
+ const canSelectFolders = canEditFolders || canDeleteFolders;
+ const canSelectDashboards = canEditDashboards || canDeleteDashboards;
+ return Boolean(canSelectFolders || canSelectDashboards);
+}
diff --git a/public/app/features/browse-dashboards/permissions.ts b/public/app/features/browse-dashboards/permissions.ts
index d589af9480a..ff7839bf2de 100644
--- a/public/app/features/browse-dashboards/permissions.ts
+++ b/public/app/features/browse-dashboards/permissions.ts
@@ -20,6 +20,7 @@ export function getFolderPermissions(folderDTO?: FolderDTO) {
const canCreateDashboards = checkFolderPermission(AccessControlAction.DashboardsCreate, folderDTO);
const canCreateFolders = checkCanCreateFolders(folderDTO);
const canDeleteFolders = checkFolderPermission(AccessControlAction.FoldersDelete, folderDTO);
+ const canDeleteDashboards = checkFolderPermission(AccessControlAction.DashboardsDelete, folderDTO);
const canEditDashboards = checkFolderPermission(AccessControlAction.DashboardsWrite, folderDTO);
const canEditFolders = checkFolderPermission(AccessControlAction.FoldersWrite, folderDTO);
const canSetPermissions = checkFolderPermission(AccessControlAction.FoldersPermissionsWrite, folderDTO);
@@ -33,5 +34,6 @@ export function getFolderPermissions(folderDTO?: FolderDTO) {
canEditFolders,
canSetPermissions,
canViewPermissions,
+ canDeleteDashboards,
};
}
diff --git a/public/app/features/browse-dashboards/types.ts b/public/app/features/browse-dashboards/types.ts
index db797d0b0f6..795d3bb794d 100644
--- a/public/app/features/browse-dashboards/types.ts
+++ b/public/app/features/browse-dashboards/types.ts
@@ -49,6 +49,7 @@ interface RendererUserProps {
onAllSelectionChange?: (newState: boolean) => void;
onItemSelectionChange?: (item: DashboardViewItem, newState: boolean) => void;
treeID?: string;
+ permissions?: BrowseDashboardsPermissions;
}
export type DashboardsTreeColumn = Column;
@@ -60,3 +61,10 @@ export enum SelectionState {
Selected,
Mixed,
}
+
+export interface BrowseDashboardsPermissions {
+ canEditFolders: boolean;
+ canEditDashboards: boolean;
+ canDeleteFolders?: boolean;
+ canDeleteDashboards?: boolean;
+}