diff --git a/public/app/core/components/manage_dashboards/manage_dashboards.ts b/public/app/core/components/manage_dashboards/manage_dashboards.ts index 66873067621..772edf82f2a 100644 --- a/public/app/core/components/manage_dashboards/manage_dashboards.ts +++ b/public/app/core/components/manage_dashboards/manage_dashboards.ts @@ -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(); }); } diff --git a/public/app/core/services/backend_srv.ts b/public/app/core/services/backend_srv.ts index 5ce0a3c89de..097e4de3106 100644 --- a/public/app/core/services/backend_srv.ts +++ b/public/app/core/services/backend_srv.ts @@ -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); + } } diff --git a/public/app/core/specs/manage_dashboards.jest.ts b/public/app/core/specs/manage_dashboards.jest.ts index c231af46eb0..00e1d0b11bd 100644 --- a/public/app/core/specs/manage_dashboards.jest.ts +++ b/public/app/core/specs/manage_dashboards.jest.ts @@ -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, searchSrvStub); + return new ManageDashboardsCtrl({}, { getNav: () => { } }, searchSrvStub); } diff --git a/public/app/features/dashboard/folder_settings_ctrl.ts b/public/app/features/dashboard/folder_settings_ctrl.ts index a187606567e..2c0d4383ff5 100644 --- a/public/app/features/dashboard/folder_settings_ctrl.ts +++ b/public/app/features/dashboard/folder_settings_ctrl.ts @@ -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'); }); } diff --git a/public/app/features/dashboard/move_to_folder_modal/move_to_folder.ts b/public/app/features/dashboard/move_to_folder_modal/move_to_folder.ts index 9300b754a9a..68cce11bb50 100644 --- a/public/app/features/dashboard/move_to_folder_modal/move_to_folder.ts +++ b/public/app/features/dashboard/move_to_folder_modal/move_to_folder.ts @@ -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(); diff --git a/public/app/features/dashboard/settings/settings.ts b/public/app/features/dashboard/settings/settings.ts index 87ac91d568b..93bb470d1df 100644 --- a/public/app/features/dashboard/settings/settings.ts +++ b/public/app/features/dashboard/settings/settings.ts @@ -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('/'); });