Commit 854d22fa by Marcus Efraimsson

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
parent 4f56d4ac
......@@ -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);
}
this.$q.all(promises).then(() => {
this.getDashboards();
});
}
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`;
}
}
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');
});
}
......
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('/');
});
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment