Browse Source

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
Marcus Efraimsson 8 years ago
parent
commit
854d22fa8e

+ 64 - 13
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();
     });
   }
 

+ 91 - 0
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);
+  }
 }
 
 

+ 12 - 8
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, <SearchSrv>searchSrvStub);
+  return new ManageDashboardsCtrl({}, { getNav: () => { } }, <SearchSrv>searchSrvStub);
 }

+ 2 - 2
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');
         });
       }

+ 8 - 62
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();

+ 1 - 1
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('/');
     });