diff --git a/public/app/core/components/search/search.html b/public/app/core/components/search/search.html index 811e21bc64a..3b0d9c003dd 100644 --- a/public/app/core/components/search/search.html +++ b/public/app/core/components/search/search.html @@ -20,37 +20,12 @@
-
-
No dashboards matching your query were found.
- -
diff --git a/public/app/core/components/search/search.ts b/public/app/core/components/search/search.ts index 728b6a3ee18..f8dc871e192 100644 --- a/public/app/core/components/search/search.ts +++ b/public/app/core/components/search/search.ts @@ -94,13 +94,11 @@ export class SearchCtrl { return query.query === '' && query.starred === false && query.tag.length === 0; } - filterByTag(tag, evt) { - this.query.tag.push(tag); - this.search(); - this.giveSearchFocus = this.giveSearchFocus + 1; - if (evt) { - evt.stopPropagation(); - evt.preventDefault(); + filterByTag(tag) { + if (_.indexOf(this.query.tag, tag) === -1) { + this.query.tag.push(tag); + this.search(); + this.giveSearchFocus = this.giveSearchFocus + 1; } } diff --git a/public/app/core/components/search/search_results.html b/public/app/core/components/search/search_results.html new file mode 100644 index 00000000000..582fbe2b059 --- /dev/null +++ b/public/app/core/components/search/search_results.html @@ -0,0 +1,43 @@ + \ No newline at end of file diff --git a/public/app/core/components/search/search_results.jest.ts b/public/app/core/components/search/search_results.jest.ts new file mode 100644 index 00000000000..ab24242860e --- /dev/null +++ b/public/app/core/components/search/search_results.jest.ts @@ -0,0 +1,75 @@ +import { SearchResultsCtrl } from './search_results'; + +describe('SearchResultsCtrl', () => { + let ctrl; + + describe('when checking an item that is not checked', () => { + let item = {checked: false}; + let selectionChanged = false; + + beforeEach(() => { + ctrl = new SearchResultsCtrl(); + ctrl.onSelectionChanged = () => selectionChanged = true; + ctrl.toggleSelection(item); + }); + + it('should set checked to true', () => { + expect(item.checked).toBeTruthy(); + }); + + it('should trigger selection changed callback', () => { + expect(selectionChanged).toBeTruthy(); + }); + }); + + describe('when checking an item that is checked', () => { + let item = {checked: true}; + let selectionChanged = false; + + beforeEach(() => { + ctrl = new SearchResultsCtrl(); + ctrl.onSelectionChanged = () => selectionChanged = true; + ctrl.toggleSelection(item); + }); + + it('should set checked to false', () => { + expect(item.checked).toBeFalsy(); + }); + + it('should trigger selection changed callback', () => { + expect(selectionChanged).toBeTruthy(); + }); + }); + + describe('when selecting a tag', () => { + let selectedTag = null; + + beforeEach(() => { + ctrl = new SearchResultsCtrl(); + ctrl.onTagSelected = (tag) => selectedTag = tag; + ctrl.selectTag('tag-test'); + }); + + it('should trigger tag selected callback', () => { + expect(selectedTag["$tag"]).toBe('tag-test'); + }); + }); + + describe('when toggle a folder', () => { + let folderToggled = false; + let folder = { + toggle: () => { + folderToggled = true; + } + }; + + beforeEach(() => { + ctrl = new SearchResultsCtrl(); + ctrl.toggleFolderExpand(folder); + }); + + it('should trigger folder toggle callback', () => { + expect(folderToggled).toBeTruthy(); + }); + }); +}); diff --git a/public/app/core/components/search/search_results.ts b/public/app/core/components/search/search_results.ts new file mode 100644 index 00000000000..de34d5bd5d0 --- /dev/null +++ b/public/app/core/components/search/search_results.ts @@ -0,0 +1,56 @@ +// import _ from 'lodash'; +import coreModule from '../../core_module'; + +export class SearchResultsCtrl { + results: any; + onSelectionChanged: any; + onTagSelected: any; + + toggleFolderExpand(section) { + if (section.toggle) { + section.toggle(section); + } + } + + toggleSelection(item, evt) { + item.checked = !item.checked; + + if (this.onSelectionChanged) { + this.onSelectionChanged(); + } + + if (evt) { + evt.stopPropagation(); + evt.preventDefault(); + } + } + + selectTag(tag, evt) { + if (this.onTagSelected) { + this.onTagSelected({$tag: tag}); + } + + if (evt) { + evt.stopPropagation(); + evt.preventDefault(); + } + } +} + +export function searchResultsDirective() { + return { + restrict: 'E', + templateUrl: 'public/app/core/components/search/search_results.html', + controller: SearchResultsCtrl, + bindToController: true, + controllerAs: 'ctrl', + scope: { + editable: '@', + results: '=', + onSelectionChanged: '&', + onTagSelected: '&' + }, + }; +} + +coreModule.directive('dashboardSearchResults', searchResultsDirective); diff --git a/public/app/core/core.ts b/public/app/core/core.ts index 602cb49cb68..a1a93c3cdcc 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -52,6 +52,7 @@ import {gfPageDirective} from './components/gf_page'; import {orgSwitcher} from './components/org_switcher'; import {profiler} from './profiler'; import {registerAngularDirectives} from './angular_wrappers'; +import {searchResultsDirective} from './components/search/search_results'; export { profiler, @@ -83,5 +84,6 @@ export { userGroupPicker, geminiScrollbar, gfPageDirective, - orgSwitcher + orgSwitcher, + searchResultsDirective }; diff --git a/public/app/core/services/search_srv.ts b/public/app/core/services/search_srv.ts index d7c0e75de5b..fdd9b8ca77f 100644 --- a/public/app/core/services/search_srv.ts +++ b/public/app/core/services/search_srv.ts @@ -128,14 +128,20 @@ export class SearchSrv { }); } - private browse() { + private browse(options) { let sections: any = {}; - let promises = [ - this.getRecentDashboards(sections), - this.getStarred(sections), - this.getDashboardsAndFolders(sections), - ]; + let promises = []; + + if (!options.skipRecent) { + promises.push(this.getRecentDashboards(sections)); + } + + if (!options.skipStarred) { + promises.push(this.getStarred(sections)); + } + + promises.push(this.getDashboardsAndFolders(sections)); return this.$q.all(promises).then(() => { return _.sortBy(_.values(sections), 'score'); @@ -149,7 +155,7 @@ export class SearchSrv { search(options) { if (!options.query && (!options.tag || options.tag.length === 0) && !options.starred) { - return this.browse(); + return this.browse(options); } let query = _.clone(options); @@ -157,6 +163,10 @@ export class SearchSrv { query.type = 'dash-db'; return this.backendSrv.search(query).then(results => { + if (results.length === 0) { + return results; + } + let section = { hideHeader: true, items: [], diff --git a/public/app/core/specs/search_srv.jest.ts b/public/app/core/specs/search_srv.jest.ts index 0624618a224..649cfbf00dd 100644 --- a/public/app/core/specs/search_srv.jest.ts +++ b/public/app/core/specs/search_srv.jest.ts @@ -2,6 +2,7 @@ import { SearchSrv } from 'app/core/services/search_srv'; import { BackendSrvMock } from 'test/mocks/backend_srv'; import impressionSrv from 'app/core/services/impression_srv'; import { contextSrv } from 'app/core/services/context_srv'; +import { beforeEach } from 'test/lib/common'; jest.mock('app/core/store', () => { return { @@ -244,4 +245,43 @@ describe('SearchSrv', () => { expect(backendSrvMock.search.mock.calls[0][0].starred).toEqual(true); }); }); + + describe('when skipping recent dashboards', () => { + let getRecentDashboardsCalled = false; + + beforeEach(() => { + backendSrvMock.search = jest.fn(); + backendSrvMock.search.mockReturnValue(Promise.resolve([])); + + searchSrv.getRecentDashboards = () => { + getRecentDashboardsCalled = true; + }; + + return searchSrv.search({ skipRecent: true }).then(() => {}); + }); + + it('should not fetch recent dashboards', () => { + expect(getRecentDashboardsCalled).toBeFalsy(); + }); + }); + + describe('when skipping starred dashboards', () => { + let getStarredCalled = false; + + beforeEach(() => { + backendSrvMock.search = jest.fn(); + backendSrvMock.search.mockReturnValue(Promise.resolve([])); + impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([]); + + searchSrv.getStarred = () => { + getStarredCalled = true; + }; + + return searchSrv.search({ skipStarred: true }).then(() => {}); + }); + + it('should not fetch starred dashboards', () => { + expect(getStarredCalled).toBeFalsy(); + }); + }); }); diff --git a/public/app/features/dashboard/dashboard_list_ctrl.ts b/public/app/features/dashboard/dashboard_list_ctrl.ts index 93e9df84c15..59a90426be3 100644 --- a/public/app/features/dashboard/dashboard_list_ctrl.ts +++ b/public/app/features/dashboard/dashboard_list_ctrl.ts @@ -18,7 +18,7 @@ export class DashboardListCtrl { /** @ngInject */ constructor(private backendSrv, navModelSrv, private $q, private searchSrv: SearchSrv) { this.navModel = navModelSrv.getNav('dashboards', 'dashboards', 0); - this.query = {query: '', mode: 'tree', tag: [], starred: false}; + this.query = {query: '', mode: 'tree', tag: [], starred: false, skipRecent: true, skipStarred: true}; this.selectedStarredFilter = this.starredFilterOptions[0]; this.getDashboards().then(() => { @@ -148,11 +148,9 @@ export class DashboardListCtrl { }); } - filterByTag(tag, evt) { - this.query.tag.push(tag); - if (evt) { - evt.stopPropagation(); - evt.preventDefault(); + filterByTag(tag) { + if (_.indexOf(this.query.tag, tag) === -1) { + this.query.tag.push(tag); } return this.getDashboards(); @@ -163,9 +161,9 @@ export class DashboardListCtrl { } onTagFilterChange() { - this.query.tag.push(this.selectedTagFilter.term); + var res = this.filterByTag(this.selectedTagFilter.term); this.selectedTagFilter = this.tagFilterOptions[0]; - return this.getDashboards(); + return res; } removeTag(tag, evt) { diff --git a/public/app/features/dashboard/partials/dashboardList.html b/public/app/features/dashboard/partials/dashboardList.html index 8bb46ebd127..8a994f12c8c 100644 --- a/public/app/features/dashboard/partials/dashboardList.html +++ b/public/app/features/dashboard/partials/dashboardList.html @@ -78,54 +78,13 @@ />
-
- +
+
No dashboards matching your query were found.
+
- - - No Dashboards or Folders found. - diff --git a/public/sass/components/_dashboard_list.scss b/public/sass/components/_dashboard_list.scss index 6863dfa785b..9d0b3144ba6 100644 --- a/public/sass/components/_dashboard_list.scss +++ b/public/sass/components/_dashboard_list.scss @@ -3,6 +3,7 @@ .search-results-container { padding-left: 0; + padding-right: 0; } } diff --git a/public/sass/components/_search.scss b/public/sass/components/_search.scss index 240cfe80e5d..0278fd7afb2 100644 --- a/public/sass/components/_search.scss +++ b/public/sass/components/_search.scss @@ -129,12 +129,8 @@ } } -.search-section__header__with-checkbox { - display: flex; -} - .search-section__header__icon { - padding: 5px 10px; + padding: 2px 10px; } .search-section__header__toggle { @@ -145,14 +141,6 @@ flex-grow: 1; } -.search-item__with-checkbox { - display: flex; - - .search-item { - margin: 1px 3px; - } -} - .search-item { @include list-item(); @include left-brand-border(); diff --git a/public/sass/components/_switch.scss b/public/sass/components/_switch.scss index f09e83f8908..4888f5c9c4a 100644 --- a/public/sass/components/_switch.scss +++ b/public/sass/components/_switch.scss @@ -102,6 +102,73 @@ $switch-height: 1.5rem; } } +.gf-form-switch--search-result__section, .gf-form-switch--search-result__item { + min-width: 2.6rem; + + input + label { + background-color: inherit; + height: 1.7rem; + } +} + +.gf-form-switch--search-result__section { + min-width: 3.3rem; + margin-right: -0.3rem; + + &:hover { + input + label::before { + @include buttonBackground($panel-bg, $panel-bg); + } + + input + label::after { + @include buttonBackground($panel-bg, $panel-bg, lighten($orange, 10%)); + } + } + + input + label::before, input + label::after { + @include buttonBackground($panel-bg, $panel-bg); + } + + input + label::before { + color: $gray-2 + } + + input + label::after { + color: $orange + } +} + +.gf-form-switch--search-result__item { + input + label { + height: 2.7rem; + } + + + &:hover { + input + label::before { + @include buttonBackground($list-item-hover-bg, $list-item-hover-bg); + } + + input + label::after { + @include buttonBackground($list-item-hover-bg, $list-item-hover-bg); + color: lighten($orange, 10%); + } + + } + + input + label::before, input + label::after { + @include buttonBackground($list-item-hover-bg, $list-item-hover-bg); + } + + input + label::before { + color: $gray-2 + } + + input + label::after { + color: $orange + } +} + gf-form-switch[disabled] { .gf-form-label, .gf-form-switch input + label {