From 196a05d4875d55768a6ed92ef2388580b3b89984 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 14 Dec 2017 11:25:16 +0100 Subject: [PATCH 1/2] dashfolders: bulk move/delete improvements Use slug and url instead of uri Check/uncheck a folder will check/uncheck all its children dashboards Allow to move dashboards when there are both folders and dashboards selected Hide the header of section when only managing one folder #10081 #10181 --- .../manage_dashboards/manage_dashboards.ts | 17 ++-- .../core/components/search/search_results.ts | 28 +++++- public/app/core/services/search_srv.ts | 10 +- .../app/core/specs/manage_dashboards.jest.ts | 91 +++++++++++++++---- public/app/core/specs/search_results.jest.ts | 4 +- .../move_to_folder_modal/move_to_folder.ts | 2 +- 6 files changed, 117 insertions(+), 35 deletions(-) diff --git a/public/app/core/components/manage_dashboards/manage_dashboards.ts b/public/app/core/components/manage_dashboards/manage_dashboards.ts index 5b74a0e50fc..66873067621 100644 --- a/public/app/core/components/manage_dashboards/manage_dashboards.ts +++ b/public/app/core/components/manage_dashboards/manage_dashboards.ts @@ -58,10 +58,13 @@ export class ManageDashboardsCtrl { dashboard.checked = false; } } + + if (this.folderId && this.sections.length > 0) { + this.sections[0].hideHeader = true; + } } selectionChanged() { - let selectedDashboards = 0; for (let section of this.sections) { @@ -69,7 +72,7 @@ export class ManageDashboardsCtrl { } const selectedFolders = _.filter(this.sections, { checked: true }).length; - this.canMove = selectedDashboards > 0 && selectedFolders === 0; + this.canMove = selectedDashboards > 0; this.canDelete = selectedDashboards > 0 || selectedFolders > 0; } @@ -77,11 +80,11 @@ export class ManageDashboardsCtrl { let selectedDashboards = []; for (const section of this.sections) { - if (section.checked) { - selectedDashboards.push(section.uri); + if (section.checked && section.id !== 0) { + selectedDashboards.push(section.slug); } else { const selected = _.filter(section.items, { checked: true }); - selectedDashboards.push(..._.map(selected, 'uri')); + selectedDashboards.push(..._.map(selected, 'slug')); } } @@ -109,7 +112,7 @@ export class ManageDashboardsCtrl { onConfirm: () => { const promises = []; for (let dash of selectedDashboards) { - promises.push(this.backendSrv.delete(`/api/dashboards/${dash}`)); + promises.push(this.backendSrv.delete(`/api/dashboards/db/${dash}`)); } this.$q.all(promises).then(() => { @@ -124,7 +127,7 @@ export class ManageDashboardsCtrl { for (const section of this.sections) { const selected = _.filter(section.items, { checked: true }); - selectedDashboards.push(..._.map(selected, 'uri')); + selectedDashboards.push(..._.map(selected, 'slug')); } return selectedDashboards; diff --git a/public/app/core/components/search/search_results.ts b/public/app/core/components/search/search_results.ts index 0c74600d1c3..3e7e8e6147b 100644 --- a/public/app/core/components/search/search_results.ts +++ b/public/app/core/components/search/search_results.ts @@ -1,4 +1,4 @@ -// import _ from 'lodash'; +import _ from 'lodash'; import coreModule from '../../core_module'; export class SearchResultsCtrl { @@ -6,10 +6,10 @@ export class SearchResultsCtrl { onSelectionChanged: any; onTagSelected: any; onFolderExpanding: any; + editable: boolean; /** @ngInject */ - constructor(private $location) { - } + constructor(private $location) {} toggleFolderExpand(section) { if (section.toggle) { @@ -17,12 +17,24 @@ export class SearchResultsCtrl { this.onFolderExpanding(); } - section.toggle(section); + section.toggle(section).then(f => { + if (this.editable && f.expanded) { + if (f.items) { + _.each(f.items, i => { + i.checked = f.checked; + }); + + if (this.onSelectionChanged) { + this.onSelectionChanged(); + } + } + } + }); } } navigateToFolder(section, evt) { - this.$location.path('/dashboards/folder/' + section.id + '/' + section.uri); + this.$location.path(section.url); if (evt) { evt.stopPropagation(); @@ -33,6 +45,12 @@ export class SearchResultsCtrl { toggleSelection(item, evt) { item.checked = !item.checked; + if (item.items) { + _.each(item.items, i => { + i.checked = item.checked; + }); + } + if (this.onSelectionChanged) { this.onSelectionChanged(); } diff --git a/public/app/core/services/search_srv.ts b/public/app/core/services/search_srv.ts index 69341757a45..a930868852f 100644 --- a/public/app/core/services/search_srv.ts +++ b/public/app/core/services/search_srv.ts @@ -51,18 +51,19 @@ export class SearchSrv { store.set('search.sections.recent', this.recentIsOpen); if (!section.expanded || section.items.length) { - return Promise.resolve(); + return Promise.resolve(section); } return this.queryForRecentDashboards().then(result => { section.items = result; + return Promise.resolve(section); }); } private toggleStarred(section) { this.starredIsOpen = section.expanded = !section.expanded; store.set('search.sections.starred', this.starredIsOpen); - return Promise.resolve(); + return Promise.resolve(section); } private getStarred(sections) { @@ -134,6 +135,7 @@ export class SearchSrv { items: [], toggle: this.toggleFolder.bind(this), url: `dashboards/folder/${hit.id}/${hit.slug}`, + slug: hit.slug, icon: 'fa fa-folder', score: _.keys(sections).length, }; @@ -152,6 +154,7 @@ export class SearchSrv { id: hit.folderId, title: hit.folderTitle, url: `dashboards/folder/${hit.folderId}/${hit.folderSlug}`, + slug: hit.slug, items: [], icon: 'fa fa-folder-open', toggle: this.toggleFolder.bind(this), @@ -181,7 +184,7 @@ export class SearchSrv { section.icon = section.expanded ? 'fa fa-folder-open' : 'fa fa-folder'; if (section.items.length) { - return Promise.resolve(); + return Promise.resolve(section); } let query = { @@ -190,6 +193,7 @@ export class SearchSrv { return this.backendSrv.search(query).then(results => { section.items = _.map(results, this.transformToViewModel); + return Promise.resolve(section); }); } diff --git a/public/app/core/specs/manage_dashboards.jest.ts b/public/app/core/specs/manage_dashboards.jest.ts index 770679f09bf..c231af46eb0 100644 --- a/public/app/core/specs/manage_dashboards.jest.ts +++ b/public/app/core/specs/manage_dashboards.jest.ts @@ -58,6 +58,41 @@ describe('ManageDashboards', () => { expect(ctrl.sections[0].items[0].checked).toEqual(false); expect(ctrl.sections[1].checked).toEqual(false); expect(ctrl.sections[1].items[0].checked).toEqual(false); + expect(ctrl.sections[0].hideHeader).toBeFalsy(); + }); + }); + + describe('when browsing dashboards for a folder', () => { + beforeEach(() => { + const response = [ + { + id: 410, + title: "afolder", + type: "dash-folder", + items: [ + { + id: 399, + title: "Dashboard Test", + url: "dashboard/db/dashboard-test", + icon: 'fa fa-folder', + tags: [], + isStarred: false, + folderId: 410, + folderTitle: "afolder", + folderSlug: "afolder" + } + ], + tags: [], + isStarred: false + } + ]; + ctrl = createCtrlWithStubs(response); + ctrl.folderId = 410; + return ctrl.getDashboards(); + }); + + it('should set hide header to true on section', () => { + expect(ctrl.sections[0].hideHeader).toBeTruthy(); }); }); @@ -263,8 +298,8 @@ describe('ManageDashboards', () => { expect(ctrl.sections[1].items[0].checked).toBeTruthy(); }); - it('should disable Move To button', () => { - expect(ctrl.canMove).toBeFalsy(); + it('should enable Move To button', () => { + expect(ctrl.canMove).toBeTruthy(); }); it('should enable delete button', () => { @@ -294,8 +329,8 @@ describe('ManageDashboards', () => { ctrl.selectionChanged(); }); - it('should disable Move To button', () => { - expect(ctrl.canMove).toBeFalsy(); + it('should enable Move To button', () => { + expect(ctrl.canMove).toBeTruthy(); }); it('should enable delete button', () => { @@ -455,8 +490,8 @@ describe('ManageDashboards', () => { ctrl.selectionChanged(); }); - it('should disable Move To button', () => { - expect(ctrl.canMove).toBeFalsy(); + it('should enable Move To button', () => { + expect(ctrl.canMove).toBeTruthy(); }); it('should enable delete button', () => { @@ -466,6 +501,8 @@ describe('ManageDashboards', () => { }); describe('when deleting dashboards', () => { + let toBeDeleted = []; + beforeEach(() => { ctrl = createCtrlWithStubs([]); @@ -474,27 +511,47 @@ describe('ManageDashboards', () => { id: 1, title: 'folder', items: [ - { id: 2, checked: true, uri: 'dash' } + { id: 2, checked: true, slug: 'folder-dash' } ], checked: true, - uri: 'folder' + slug: 'folder' + }, + { + id: 3, + title: 'folder-2', + items: [ + { id: 3, checked: true, slug: 'folder-2-dash' } + ], + checked: false, + slug: 'folder-2' }, { id: 0, title: 'Root', items: [ - { id: 3, checked: true, uri: 'dash-2' } + { id: 3, checked: true, slug: 'root-dash' } ], - checked: false + checked: true } ]; + + toBeDeleted = ctrl.getDashboardsToDelete(); }); - it('should filter out children if parent is selected', () => { - const toBeDeleted = ctrl.getDashboardsToDelete(); - expect(toBeDeleted.length).toEqual(2); + it('should return 3 items', () => { + expect(toBeDeleted.length).toEqual(3); + }); + + it('should filter out children if parent is checked', () => { expect(toBeDeleted[0]).toEqual('folder'); - expect(toBeDeleted[1]).toEqual('dash-2'); + }); + + it('should not filter out children if parent not is checked', () => { + expect(toBeDeleted[1]).toEqual('folder-2-dash'); + }); + + it('should not filter out children if parent is checked and root', () => { + expect(toBeDeleted[2]).toEqual('root-dash'); }); }); @@ -507,16 +564,16 @@ describe('ManageDashboards', () => { id: 1, title: 'folder', items: [ - { id: 2, checked: true, uri: 'dash' } + { id: 2, checked: true, slug: 'dash' } ], checked: false, - uri: 'folder' + slug: 'folder' }, { id: 0, title: 'Root', items: [ - { id: 3, checked: true, uri: 'dash-2' } + { id: 3, checked: true, slug: 'dash-2' } ], checked: false } diff --git a/public/app/core/specs/search_results.jest.ts b/public/app/core/specs/search_results.jest.ts index b8eed3cef9b..7084f5e7d8f 100644 --- a/public/app/core/specs/search_results.jest.ts +++ b/public/app/core/specs/search_results.jest.ts @@ -64,7 +64,7 @@ describe('SearchResultsCtrl', () => { let folder = { expanded: false, - toggle: () => {} + toggle: () => Promise.resolve(folder) }; ctrl.toggleFolderExpand(folder); @@ -84,7 +84,7 @@ describe('SearchResultsCtrl', () => { let folder = { expanded: true, - toggle: () => {} + toggle: () => Promise.resolve(folder) }; ctrl.toggleFolderExpand(folder); 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 0a41029fe68..9300b754a9a 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 @@ -32,7 +32,7 @@ export class MoveToFolderCtrl { private moveDashboard(dash) { let deferred = this.$q.defer(); - this.backendSrv.get('/api/dashboards/' + dash) + this.backendSrv.get('/api/dashboards/db/' + dash) .then(fullDash => { const model = new DashboardModel(fullDash.dashboard, fullDash.meta); From e136b7c2865c002cd86b8e3b76d24041de6b74ff Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 14 Dec 2017 11:30:57 +0100 Subject: [PATCH 2/2] dashfolders: /dashboards should render index page with a 200 OK --- pkg/api/api.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/api/api.go b/pkg/api/api.go index 4b570ce5b89..3f6d8d4d954 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -64,6 +64,7 @@ func (hs *HttpServer) registerRoutes() { r.Get("/dashboard-solo/snapshot/*", Index) r.Get("/dashboard-solo/*", reqSignedIn, Index) r.Get("/import/dashboard", reqSignedIn, Index) + r.Get("/dashboards/", reqSignedIn, Index) r.Get("/dashboards/*", reqSignedIn, Index) r.Get("/playlists/", reqSignedIn, Index)