mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
dashfolders: bulk move/delete improvements
bulk delete dashboards synchronously moved bulk delete and move dashboards to backend_srv better error handling/messages when moving and deleting folders/dashboards fixes #10181
This commit is contained in:
parent
4f56d4ac07
commit
854d22fa8e
@ -18,7 +18,7 @@ export class ManageDashboardsCtrl {
|
||||
folderId?: number;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv, navModelSrv, private $q, private searchSrv: SearchSrv) {
|
||||
constructor(private backendSrv, navModelSrv, private searchSrv: SearchSrv) {
|
||||
this.query = { query: '', mode: 'tree', tag: [], starred: false, skipRecent: true, skipStarred: true };
|
||||
|
||||
if (this.folderId) {
|
||||
@ -76,15 +76,18 @@ export class ManageDashboardsCtrl {
|
||||
this.canDelete = selectedDashboards > 0 || selectedFolders > 0;
|
||||
}
|
||||
|
||||
getDashboardsToDelete() {
|
||||
let selectedDashboards = [];
|
||||
getFoldersAndDashboardsToDelete() {
|
||||
let selectedDashboards = {
|
||||
folders: [],
|
||||
dashboards: []
|
||||
};
|
||||
|
||||
for (const section of this.sections) {
|
||||
if (section.checked && section.id !== 0) {
|
||||
selectedDashboards.push(section.slug);
|
||||
selectedDashboards.folders.push(section.slug);
|
||||
} else {
|
||||
const selected = _.filter(section.items, { checked: true });
|
||||
selectedDashboards.push(..._.map(selected, 'slug'));
|
||||
selectedDashboards.dashboards.push(..._.map(selected, 'slug'));
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,23 +105,71 @@ export class ManageDashboardsCtrl {
|
||||
}
|
||||
|
||||
delete() {
|
||||
const selectedDashboards = this.getDashboardsToDelete();
|
||||
const data = this.getFoldersAndDashboardsToDelete();
|
||||
const folderCount = data.folders.length;
|
||||
const dashCount = data.dashboards.length;
|
||||
let text = 'Do you want to delete the ';
|
||||
let text2;
|
||||
|
||||
if (folderCount > 0 && dashCount > 0) {
|
||||
text += `selected folder${folderCount === 1 ? '' : 's'} and dashboard${dashCount === 1 ? '' : 's'}?`;
|
||||
text2 = `All dashboards of the selected folder${folderCount === 1 ? '' : 's'} will also be deleted`;
|
||||
} else if (folderCount > 0) {
|
||||
text += `selected folder${folderCount === 1 ? '' : 's'} and all its dashboards?`;
|
||||
} else {
|
||||
text += `selected dashboard${dashCount === 1 ? '' : 's'}?`;
|
||||
}
|
||||
|
||||
appEvents.emit('confirm-modal', {
|
||||
title: 'Delete',
|
||||
text: `Do you want to delete the ${selectedDashboards.length} selected dashboards?`,
|
||||
text: text,
|
||||
text2: text2,
|
||||
icon: 'fa-trash',
|
||||
yesText: 'Delete',
|
||||
onConfirm: () => {
|
||||
const promises = [];
|
||||
for (let dash of selectedDashboards) {
|
||||
promises.push(this.backendSrv.delete(`/api/dashboards/db/${dash}`));
|
||||
const foldersAndDashboards = data.folders.concat(data.dashboards);
|
||||
this.deleteFoldersAndDashboards(foldersAndDashboards);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private deleteFoldersAndDashboards(slugs) {
|
||||
this.backendSrv.deleteDashboards(slugs).then(result => {
|
||||
const folders = _.filter(result, dash => dash.meta.isFolder);
|
||||
const folderCount = folders.length;
|
||||
const dashboards = _.filter(result, dash => !dash.meta.isFolder);
|
||||
const dashCount = dashboards.length;
|
||||
|
||||
if (result.length > 0) {
|
||||
let header;
|
||||
let msg;
|
||||
|
||||
if (folderCount > 0 && dashCount > 0) {
|
||||
header = `Folder${folderCount === 1 ? '' : 's'} And Dashboard${dashCount === 1 ? '' : 's'} Deleted`;
|
||||
msg = `${folderCount} folder${folderCount === 1 ? '' : 's'} `;
|
||||
msg += `and ${dashCount} dashboard${dashCount === 1 ? '' : 's'} has been deleted`;
|
||||
} else if (folderCount > 0) {
|
||||
header = `Folder${folderCount === 1 ? '' : 's'} Deleted`;
|
||||
|
||||
if (folderCount === 1) {
|
||||
msg = `${folders[0].dashboard.title} has been deleted`;
|
||||
} else {
|
||||
msg = `${folderCount} folder${folderCount === 1 ? '' : 's'} has been deleted`;
|
||||
}
|
||||
} else if (dashCount > 0) {
|
||||
header = `Dashboard${dashCount === 1 ? '' : 's'} Deleted`;
|
||||
|
||||
if (dashCount === 1) {
|
||||
msg = `${dashboards[0].dashboard.title} has been deleted`;
|
||||
} else {
|
||||
msg = `${dashCount} dashboard${dashCount === 1 ? '' : 's'} has been deleted`;
|
||||
}
|
||||
}
|
||||
|
||||
this.$q.all(promises).then(() => {
|
||||
this.getDashboards();
|
||||
});
|
||||
appEvents.emit('alert-success', [header, msg]);
|
||||
}
|
||||
|
||||
this.getDashboards();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
import _ from 'lodash';
|
||||
import coreModule from 'app/core/core_module';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { DashboardModel } from 'app/features/dashboard/dashboard_model';
|
||||
|
||||
export class BackendSrv {
|
||||
private inFlightRequests = {};
|
||||
@ -246,6 +247,96 @@ export class BackendSrv {
|
||||
return this.getDashboard('db', res.slug);
|
||||
});
|
||||
}
|
||||
|
||||
deleteDashboard(slug) {
|
||||
let deferred = this.$q.defer();
|
||||
|
||||
this.getDashboard('db', slug)
|
||||
.then(fullDash => {
|
||||
this.delete(`/api/dashboards/db/${slug}`)
|
||||
.then(() => {
|
||||
deferred.resolve(fullDash);
|
||||
}).catch(err => {
|
||||
deferred.reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
deleteDashboards(dashboardSlugs) {
|
||||
const tasks = [];
|
||||
|
||||
for (let slug of dashboardSlugs) {
|
||||
tasks.push(this.createTask(this.deleteDashboard.bind(this), true, slug));
|
||||
}
|
||||
|
||||
return this.executeInOrder(tasks, []);
|
||||
}
|
||||
|
||||
moveDashboards(dashboardSlugs, toFolder) {
|
||||
const tasks = [];
|
||||
|
||||
for (let slug of dashboardSlugs) {
|
||||
tasks.push(this.createTask(this.moveDashboard.bind(this), true, slug, toFolder));
|
||||
}
|
||||
|
||||
return this.executeInOrder(tasks, [])
|
||||
.then(result => {
|
||||
return {
|
||||
totalCount: result.length,
|
||||
successCount: _.filter(result, { succeeded: true }).length,
|
||||
alreadyInFolderCount: _.filter(result, { alreadyInFolder: true }).length
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private moveDashboard(slug, toFolder) {
|
||||
let deferred = this.$q.defer();
|
||||
|
||||
this.getDashboard('db', slug).then(fullDash => {
|
||||
const model = new DashboardModel(fullDash.dashboard, fullDash.meta);
|
||||
|
||||
if ((!model.folderId && toFolder.id === 0) ||
|
||||
model.folderId === toFolder.id) {
|
||||
deferred.resolve({alreadyInFolder: true});
|
||||
return;
|
||||
}
|
||||
|
||||
model.folderId = toFolder.id;
|
||||
model.meta.folderId = toFolder.id;
|
||||
model.meta.folderTitle = toFolder.title;
|
||||
const clone = model.getSaveModelClone();
|
||||
|
||||
this.saveDashboard(clone, {})
|
||||
.then(() => {
|
||||
deferred.resolve({succeeded: true});
|
||||
}).catch(err => {
|
||||
deferred.resolve({succeeded: false});
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
private createTask(fn, ignoreRejections, ...args: any[]) {
|
||||
return (result) => {
|
||||
return fn.apply(null, args)
|
||||
.then(res => {
|
||||
return Array.prototype.concat(result, [res]);
|
||||
}).catch(err => {
|
||||
if (ignoreRejections) {
|
||||
return result;
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private executeInOrder(tasks, initialValue) {
|
||||
return tasks.reduce(this.$q.when, initialValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -501,7 +501,7 @@ describe('ManageDashboards', () => {
|
||||
});
|
||||
|
||||
describe('when deleting dashboards', () => {
|
||||
let toBeDeleted = [];
|
||||
let toBeDeleted: any;
|
||||
|
||||
beforeEach(() => {
|
||||
ctrl = createCtrlWithStubs([]);
|
||||
@ -535,23 +535,27 @@ describe('ManageDashboards', () => {
|
||||
}
|
||||
];
|
||||
|
||||
toBeDeleted = ctrl.getDashboardsToDelete();
|
||||
toBeDeleted = ctrl.getFoldersAndDashboardsToDelete();
|
||||
});
|
||||
|
||||
it('should return 3 items', () => {
|
||||
expect(toBeDeleted.length).toEqual(3);
|
||||
it('should return 1 folder', () => {
|
||||
expect(toBeDeleted.folders.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should return 2 dashboards', () => {
|
||||
expect(toBeDeleted.dashboards.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('should filter out children if parent is checked', () => {
|
||||
expect(toBeDeleted[0]).toEqual('folder');
|
||||
expect(toBeDeleted.folders[0]).toEqual('folder');
|
||||
});
|
||||
|
||||
it('should not filter out children if parent not is checked', () => {
|
||||
expect(toBeDeleted[1]).toEqual('folder-2-dash');
|
||||
expect(toBeDeleted.dashboards[0]).toEqual('folder-2-dash');
|
||||
});
|
||||
|
||||
it('should not filter out children if parent is checked and root', () => {
|
||||
expect(toBeDeleted[2]).toEqual('root-dash');
|
||||
expect(toBeDeleted.dashboards[1]).toEqual('root-dash');
|
||||
});
|
||||
});
|
||||
|
||||
@ -599,5 +603,5 @@ function createCtrlWithStubs(searchResponse: any, tags?: any) {
|
||||
}
|
||||
};
|
||||
|
||||
return new ManageDashboardsCtrl({}, { getNav: () => { } }, q, <SearchSrv>searchSrvStub);
|
||||
return new ManageDashboardsCtrl({}, { getNav: () => { } }, <SearchSrv>searchSrvStub);
|
||||
}
|
||||
|
@ -50,8 +50,8 @@ export class FolderSettingsCtrl {
|
||||
icon: 'fa-trash',
|
||||
yesText: 'Delete',
|
||||
onConfirm: () => {
|
||||
return this.backendSrv.delete(`/api/dashboards/${this.meta.type}/${this.meta.slug}`).then(() => {
|
||||
appEvents.emit('alert-success', ['Folder deleted']);
|
||||
return this.backendSrv.deleteDashboard(this.meta.slug).then(() => {
|
||||
appEvents.emit('alert-success', ['Folder Deleted', `${this.dashboard.title} has been deleted`]);
|
||||
this.$location.url('/dashboards');
|
||||
});
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
import _ from 'lodash';
|
||||
import coreModule from 'app/core/core_module';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { DashboardModel } from '../dashboard_model';
|
||||
|
||||
export class MoveToFolderCtrl {
|
||||
dashboards: any;
|
||||
@ -10,75 +8,23 @@ export class MoveToFolderCtrl {
|
||||
afterSave: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv, private $q) { }
|
||||
constructor(private backendSrv) { }
|
||||
|
||||
onFolderChange(folder) {
|
||||
this.folder = folder;
|
||||
}
|
||||
|
||||
private doNext(fn, ...args: any[]) {
|
||||
return function (result) {
|
||||
return fn.apply(null, args)
|
||||
.then(res => {
|
||||
return Array.prototype.concat(result, [res]);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private doInOrder(tasks, init) {
|
||||
return tasks.reduce(this.$q.when, init);
|
||||
}
|
||||
|
||||
private moveDashboard(dash) {
|
||||
let deferred = this.$q.defer();
|
||||
|
||||
this.backendSrv.get('/api/dashboards/db/' + dash)
|
||||
.then(fullDash => {
|
||||
const model = new DashboardModel(fullDash.dashboard, fullDash.meta);
|
||||
|
||||
if ((!model.folderId && this.folder.id === 0) ||
|
||||
model.folderId === this.folder.id) {
|
||||
deferred.resolve({alreadyInFolder: true});
|
||||
return;
|
||||
}
|
||||
|
||||
model.folderId = this.folder.id;
|
||||
model.meta.folderId = this.folder.id;
|
||||
model.meta.folderTitle = this.folder.title;
|
||||
const clone = model.getSaveModelClone();
|
||||
|
||||
this.backendSrv.saveDashboard(clone)
|
||||
.then(() => {
|
||||
deferred.resolve({succeeded: true});
|
||||
})
|
||||
.catch(err => {
|
||||
deferred.resolve({succeeded: false});
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
save() {
|
||||
const tasks = [];
|
||||
|
||||
for (let dash of this.dashboards) {
|
||||
tasks.push(this.doNext(this.moveDashboard.bind(this), dash));
|
||||
}
|
||||
|
||||
return this.doInOrder(tasks, [])
|
||||
return this.backendSrv.moveDashboards(this.dashboards, this.folder)
|
||||
.then(result => {
|
||||
const totalCount = result.length;
|
||||
const successCount = _.filter(result, { succeeded: true }).length;
|
||||
const alreadyInFolderCount = _.filter(result, { alreadyInFolder: true }).length;
|
||||
|
||||
if (successCount > 0) {
|
||||
const msg = successCount + ' dashboard' + (successCount === 1 ? '' : 's') + ' moved to ' + this.folder.title;
|
||||
appEvents.emit('alert-success', [ 'Dashboard' + (successCount === 1 ? '' : 's') + ' Moved', msg]);
|
||||
if (result.successCount > 0) {
|
||||
const header = `Dashboard${result.successCount === 1 ? '' : 's'} Moved`;
|
||||
const msg = `${result.successCount} dashboard${result.successCount === 1 ? '' : 's'} moved to ${this.folder.title}`;
|
||||
appEvents.emit('alert-success', [header, msg]);
|
||||
}
|
||||
|
||||
if (totalCount === alreadyInFolderCount) {
|
||||
appEvents.emit('alert-error', ['Error', 'Dashboards already belongs to folder ' + this.folder.title]);
|
||||
if (result.totalCount === result.alreadyInFolderCount) {
|
||||
appEvents.emit('alert-error', ['Error', `Dashboards already belongs to folder ${this.folder.title}`]);
|
||||
}
|
||||
|
||||
this.dismiss();
|
||||
|
@ -128,7 +128,7 @@ export class SettingsCtrl {
|
||||
}
|
||||
|
||||
deleteDashboardConfirmed() {
|
||||
this.backendSrv.delete('/api/dashboards/db/' + this.dashboard.meta.slug).then(() => {
|
||||
this.backendSrv.deleteDashboard(this.dashboard.meta.slug).then(() => {
|
||||
appEvents.emit('alert-success', ['Dashboard Deleted', this.dashboard.title + ' has been deleted']);
|
||||
this.$location.url('/');
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user