mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Nested folders: very naive implementations of move/delete (#67309)
* implement naive delete * implement naive move * use params object instead of appending to url
This commit is contained in:
parent
1d99500b3e
commit
4b241311b3
@ -1,5 +1,5 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { memo, useEffect, useMemo } from 'react';
|
||||
import React, { memo, useEffect, useMemo, useState } from 'react';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
@ -29,6 +29,9 @@ export interface Props extends GrafanaRouteComponentProps<BrowseDashboardsPageRo
|
||||
// New Browse/Manage/Search Dashboards views for nested folders
|
||||
|
||||
const BrowseDashboardsPage = memo(({ match }: Props) => {
|
||||
// this is a complete hack to force a full rerender.
|
||||
// TODO remove once we move everything to RTK query
|
||||
const [rerender, setRerender] = useState(0);
|
||||
const { uid: folderUID } = match.params;
|
||||
|
||||
const styles = useStyles2(getStyles);
|
||||
@ -59,15 +62,15 @@ const BrowseDashboardsPage = memo(({ match }: Props) => {
|
||||
onChange={(e) => stateManager.onQueryChange(e)}
|
||||
/>
|
||||
|
||||
{hasSelection ? <BrowseActions /> : <BrowseFilters />}
|
||||
{hasSelection ? <BrowseActions onActionComplete={() => setRerender(rerender + 1)} /> : <BrowseFilters />}
|
||||
|
||||
<div className={styles.subView}>
|
||||
<AutoSizer>
|
||||
{({ width, height }) =>
|
||||
isSearching ? (
|
||||
<SearchView width={width} height={height} />
|
||||
<SearchView key={rerender} width={width} height={height} />
|
||||
) : (
|
||||
<BrowseView width={width} height={height} folderUID={folderUID} />
|
||||
<BrowseView key={rerender} width={width} height={height} folderUID={folderUID} />
|
||||
)
|
||||
}
|
||||
</AutoSizer>
|
||||
|
@ -3,7 +3,8 @@ import { lastValueFrom } from 'rxjs';
|
||||
|
||||
import { isTruthy } from '@grafana/data';
|
||||
import { BackendSrvRequest, getBackendSrv } from '@grafana/runtime';
|
||||
import { FolderDTO } from 'app/types';
|
||||
import { DeleteDashboardResponse } from 'app/features/manage-dashboards/types';
|
||||
import { DashboardDTO, FolderDTO } from 'app/types';
|
||||
|
||||
import { DashboardTreeSelection } from '../types';
|
||||
|
||||
@ -35,6 +36,62 @@ export const browseDashboardsAPI = createApi({
|
||||
reducerPath: 'browseDashboardsAPI',
|
||||
baseQuery: createBackendSrvBaseQuery({ baseURL: '/api' }),
|
||||
endpoints: (builder) => ({
|
||||
deleteDashboard: builder.mutation<DeleteDashboardResponse, string>({
|
||||
query: (dashboardUID) => ({
|
||||
url: `/dashboards/uid/${dashboardUID}`,
|
||||
method: 'DELETE',
|
||||
}),
|
||||
}),
|
||||
deleteFolder: builder.mutation<void, string>({
|
||||
query: (folderUID) => ({
|
||||
url: `/folders/${folderUID}`,
|
||||
method: 'DELETE',
|
||||
params: {
|
||||
forceDeleteRules: true,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
// TODO we can define this return type properly
|
||||
moveDashboard: builder.mutation<
|
||||
unknown,
|
||||
{
|
||||
dashboardUID: string;
|
||||
destinationUID: string;
|
||||
}
|
||||
>({
|
||||
queryFn: async ({ dashboardUID, destinationUID }, _api, _extraOptions, baseQuery) => {
|
||||
const fullDash: DashboardDTO = await getBackendSrv().get(`/api/dashboards/uid/${dashboardUID}`);
|
||||
|
||||
const options = {
|
||||
dashboard: fullDash.dashboard,
|
||||
folderUid: destinationUID,
|
||||
overwrite: false,
|
||||
};
|
||||
|
||||
return baseQuery({
|
||||
url: '/dashboards/db',
|
||||
method: 'POST',
|
||||
data: {
|
||||
message: '',
|
||||
...options,
|
||||
},
|
||||
});
|
||||
},
|
||||
}),
|
||||
// TODO this doesn't return void, find where the correct type is
|
||||
moveFolder: builder.mutation<
|
||||
void,
|
||||
{
|
||||
folderUID: string;
|
||||
destinationUID: string;
|
||||
}
|
||||
>({
|
||||
query: ({ folderUID, destinationUID }) => ({
|
||||
url: `/folders/${folderUID}/move`,
|
||||
method: 'POST',
|
||||
data: { parentUid: destinationUID },
|
||||
}),
|
||||
}),
|
||||
getFolder: builder.query<FolderDTO, string>({
|
||||
query: (folderUID) => ({ url: `/folders/${folderUID}` }),
|
||||
}),
|
||||
@ -93,5 +150,13 @@ export const browseDashboardsAPI = createApi({
|
||||
}),
|
||||
});
|
||||
|
||||
export const { useGetFolderQuery, useLazyGetFolderQuery, useGetAffectedItemsQuery } = browseDashboardsAPI;
|
||||
export const {
|
||||
useDeleteDashboardMutation,
|
||||
useDeleteFolderMutation,
|
||||
useGetAffectedItemsQuery,
|
||||
useGetFolderQuery,
|
||||
useLazyGetFolderQuery,
|
||||
useMoveDashboardMutation,
|
||||
useMoveFolderMutation,
|
||||
} = browseDashboardsAPI;
|
||||
export { skipToken } from '@reduxjs/toolkit/query/react';
|
||||
|
@ -6,40 +6,88 @@ import { Button, useStyles2 } from '@grafana/ui';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { ShowModalReactEvent } from 'app/types/events';
|
||||
|
||||
import {
|
||||
useDeleteDashboardMutation,
|
||||
useDeleteFolderMutation,
|
||||
useMoveDashboardMutation,
|
||||
useMoveFolderMutation,
|
||||
} from '../../api/browseDashboardsAPI';
|
||||
import { useActionSelectionState } from '../../state';
|
||||
|
||||
import { DeleteModal } from './DeleteModal';
|
||||
import { MoveModal } from './MoveModal';
|
||||
|
||||
export interface Props {}
|
||||
export interface Props {
|
||||
// this is a complete hack to force a full rerender.
|
||||
// TODO remove once we move everything to RTK query
|
||||
onActionComplete?: () => void;
|
||||
}
|
||||
|
||||
export function BrowseActions() {
|
||||
export function BrowseActions({ onActionComplete }: Props) {
|
||||
const styles = useStyles2(getStyles);
|
||||
const selectedItems = useActionSelectionState();
|
||||
const [deleteDashboard] = useDeleteDashboardMutation();
|
||||
const [deleteFolder] = useDeleteFolderMutation();
|
||||
const [moveFolder] = useMoveFolderMutation();
|
||||
const [moveDashboard] = useMoveDashboardMutation();
|
||||
const selectedDashboards = Object.keys(selectedItems.dashboard).filter((uid) => selectedItems.dashboard[uid]);
|
||||
const selectedFolders = Object.keys(selectedItems.folder).filter((uid) => selectedItems.folder[uid]);
|
||||
|
||||
const onMove = () => {
|
||||
const onDelete = async () => {
|
||||
// Delete all the folders sequentially
|
||||
// TODO error handling here
|
||||
for (const folderUID of selectedFolders) {
|
||||
await deleteFolder(folderUID).unwrap();
|
||||
}
|
||||
|
||||
// Delete all the dashboards sequenetially
|
||||
// TODO error handling here
|
||||
for (const dashboardUID of selectedDashboards) {
|
||||
await deleteDashboard(dashboardUID).unwrap();
|
||||
}
|
||||
onActionComplete?.();
|
||||
};
|
||||
|
||||
const onMove = async (destinationUID: string) => {
|
||||
// Move all the folders sequentially
|
||||
// TODO error handling here
|
||||
for (const folderUID of selectedFolders) {
|
||||
await moveFolder({
|
||||
folderUID,
|
||||
destinationUID,
|
||||
}).unwrap();
|
||||
}
|
||||
|
||||
// Move all the dashboards sequentially
|
||||
// TODO error handling here
|
||||
for (const dashboardUID of selectedDashboards) {
|
||||
await moveDashboard({
|
||||
dashboardUID,
|
||||
destinationUID,
|
||||
}).unwrap();
|
||||
}
|
||||
onActionComplete?.();
|
||||
};
|
||||
|
||||
const showMoveModal = () => {
|
||||
appEvents.publish(
|
||||
new ShowModalReactEvent({
|
||||
component: MoveModal,
|
||||
props: {
|
||||
selectedItems,
|
||||
onConfirm: (moveTarget: string) => {
|
||||
console.log(`MoveModal onConfirm clicked with target ${moveTarget}!`);
|
||||
},
|
||||
onConfirm: onMove,
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const onDelete = () => {
|
||||
const showDeleteModal = () => {
|
||||
appEvents.publish(
|
||||
new ShowModalReactEvent({
|
||||
component: DeleteModal,
|
||||
props: {
|
||||
selectedItems,
|
||||
onConfirm: () => {
|
||||
console.log('DeleteModal onConfirm clicked!');
|
||||
},
|
||||
onConfirm: onDelete,
|
||||
},
|
||||
})
|
||||
);
|
||||
@ -47,10 +95,10 @@ export function BrowseActions() {
|
||||
|
||||
return (
|
||||
<div className={styles.row} data-testid="manage-actions">
|
||||
<Button onClick={onMove} variant="secondary">
|
||||
<Button onClick={showMoveModal} variant="secondary">
|
||||
Move
|
||||
</Button>
|
||||
<Button onClick={onDelete} variant="destructive">
|
||||
<Button onClick={showDeleteModal} variant="destructive">
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user