diff --git a/public/app/core/components/Select/DashboardPicker.tsx b/public/app/core/components/Select/DashboardPicker.tsx index dd2b266c314..529f7f2cb4a 100644 --- a/public/app/core/components/Select/DashboardPicker.tsx +++ b/public/app/core/components/Select/DashboardPicker.tsx @@ -4,7 +4,8 @@ import { useAsyncFn } from 'react-use'; import { SelectableValue } from '@grafana/data'; import { AsyncSelect } from '@grafana/ui'; import { backendSrv } from 'app/core/services/backend_srv'; -import { DashboardSearchHit, DashboardDTO } from 'app/types'; +import { DashboardSearchHit } from 'app/features/search/types'; +import { DashboardDTO } from 'app/types'; export interface Props { onSelected: (dashboard: DashboardDTO) => void; diff --git a/public/app/core/components/Select/FolderPicker.tsx b/public/app/core/components/Select/FolderPicker.tsx index a6063dcfe74..6d19a8c3996 100644 --- a/public/app/core/components/Select/FolderPicker.tsx +++ b/public/app/core/components/Select/FolderPicker.tsx @@ -5,7 +5,7 @@ import { debounce } from 'lodash'; import appEvents from '../../app_events'; import { getBackendSrv } from '@grafana/runtime'; import { contextSrv } from 'app/core/services/context_srv'; -import { DashboardSearchHit } from '../../../types'; +import { DashboardSearchHit } from 'app/features/search/types'; export interface Props { onChange: ($folder: { title: string; id: number }) => void; diff --git a/public/app/core/components/SharedPreferences/SharedPreferences.tsx b/public/app/core/components/SharedPreferences/SharedPreferences.tsx index 088a4cf64c8..71ba2a9ccc8 100644 --- a/public/app/core/components/SharedPreferences/SharedPreferences.tsx +++ b/public/app/core/components/SharedPreferences/SharedPreferences.tsx @@ -3,7 +3,7 @@ import React, { PureComponent } from 'react'; import { InlineFormLabel, LegacyForms } from '@grafana/ui'; const { Select } = LegacyForms; -import { DashboardSearchHit, DashboardSearchHitType } from 'app/types'; +import { DashboardSearchHit, DashboardSearchItemType } from 'app/features/search/types'; import { backendSrv } from 'app/core/services/backend_srv'; export interface Props { @@ -50,7 +50,7 @@ export class SharedPreferences extends PureComponent { id: 0, title: 'Default', tags: [], - type: '' as DashboardSearchHitType, + type: '' as DashboardSearchItemType, uid: '', uri: '', url: '', @@ -60,6 +60,7 @@ export class SharedPreferences extends PureComponent { folderUrl: '', isStarred: false, slug: '', + items: [], }; if (prefs.homeDashboardId > 0 && !dashboards.find(d => d.id === prefs.homeDashboardId)) { diff --git a/public/app/core/components/manage_dashboards/manage_dashboards.html b/public/app/core/components/manage_dashboards/manage_dashboards.html deleted file mode 100644 index 923bb7228e8..00000000000 --- a/public/app/core/components/manage_dashboards/manage_dashboards.html +++ /dev/null @@ -1,118 +0,0 @@ -
- - -
-
-
- - -
-
- -
- -
-
- -
- - No dashboards matching your search were found. - -
- -
- - No dashboards found. - -
- -
- -
- -
-
-
- -
- -
diff --git a/public/app/core/components/manage_dashboards/manage_dashboards.ts b/public/app/core/components/manage_dashboards/manage_dashboards.ts deleted file mode 100644 index deec960960a..00000000000 --- a/public/app/core/components/manage_dashboards/manage_dashboards.ts +++ /dev/null @@ -1,357 +0,0 @@ -import { IScope } from 'angular'; -import _ from 'lodash'; -import { SelectableValue } from '@grafana/data'; -//@ts-ignore -import coreModule from 'app/core/core_module'; -import appEvents from 'app/core/app_events'; -import { SearchSrv } from 'app/core/services/search_srv'; -import { backendSrv } from 'app/core/services/backend_srv'; -import { ContextSrv } from 'app/core/services/context_srv'; -import { CoreEvents } from 'app/types'; -import { promiseToDigest } from '../../utils/promiseToDigest'; - -export interface Section { - id: number; - uid: string; - title: string; - expanded: boolean; - removable: boolean; - items: any[]; - url: string; - icon: string; - score: number; - checked: boolean; - hideHeader: boolean; - toggle: Function; - type?: string; -} - -export interface FoldersAndDashboardUids { - folderUids: string[]; - dashboardUids: string[]; -} - -class Query { - query: string; - mode: string; - tag: any[]; - starred: boolean; - skipRecent: boolean; - skipStarred: boolean; - folderIds: number[]; -} - -export class ManageDashboardsCtrl { - sections: Section[]; - - query: Query; - navModel: any; - - selectAllChecked = false; - - // enable/disable actions depending on the folders or dashboards selected - canDelete = false; - canMove = false; - - // filter variables - hasFilters = false; - tagFilterOptions: any[]; - selectedTagFilter: any; - selectedStarredFilter: any; - - // used when managing dashboards for a specific folder - folderId?: number; - folderUid?: string; - - // if user can add new folders and/or add new dashboards - canSave = false; - - // if user has editor role or higher - isEditor: boolean; - - hasEditPermissionInFolders: boolean; - - /** @ngInject */ - constructor(private $scope: IScope, private searchSrv: SearchSrv, private contextSrv: ContextSrv) { - this.isEditor = this.contextSrv.isEditor; - this.hasEditPermissionInFolders = this.contextSrv.hasEditPermissionInFolders; - - this.query = { - query: '', - mode: 'tree', - tag: [], - starred: false, - skipRecent: true, - skipStarred: true, - folderIds: [], - }; - - if (this.folderId) { - this.query.folderIds = [this.folderId]; - } - - this.refreshList().then(() => { - this.initTagFilter(); - }); - } - - refreshList() { - return this.searchSrv - .search(this.query) - .then((result: Section[]) => { - return this.initDashboardList(result); - }) - .then(() => { - if (!this.folderUid) { - this.$scope.$digest(); - return undefined; - } - - return backendSrv.getFolderByUid(this.folderUid).then((folder: any) => { - this.canSave = folder.canSave; - if (!this.canSave) { - this.hasEditPermissionInFolders = false; - } - this.$scope.$digest(); - }); - }); - } - - initDashboardList(result: Section[]) { - this.canMove = false; - this.canDelete = false; - this.selectAllChecked = false; - this.hasFilters = this.query.query.length > 0 || this.query.tag.length > 0 || this.query.starred; - - if (!result) { - this.sections = []; - return; - } - - this.sections = result; - - for (const section of this.sections) { - section.checked = false; - - for (const dashboard of section.items) { - dashboard.checked = false; - } - } - - if (this.folderId && this.sections.length > 0) { - this.sections[0].hideHeader = true; - } - } - - selectionChanged = () => { - let selectedDashboards = 0; - - if (this.sections) { - for (const section of this.sections) { - selectedDashboards += _.filter(section.items, { checked: true } as any).length; - } - - const selectedFolders = _.filter(this.sections, { checked: true }).length; - this.canMove = selectedDashboards > 0; - this.canDelete = selectedDashboards > 0 || selectedFolders > 0; - } - }; - - getFoldersAndDashboardsToDelete(): FoldersAndDashboardUids { - const selectedDashboards: FoldersAndDashboardUids = { - folderUids: [], - dashboardUids: [], - }; - - for (const section of this.sections) { - if (section.checked && section.id !== 0) { - selectedDashboards.folderUids.push(section.uid); - } else { - const selected = _.filter(section.items, { checked: true } as any); - selectedDashboards.dashboardUids.push(..._.map(selected, 'uid')); - } - } - - return selectedDashboards; - } - - getFolderIds(sections: Section[]) { - const ids = []; - for (const s of sections) { - if (s.checked) { - ids.push(s.id); - } - } - return ids; - } - - delete = () => { - const data = this.getFoldersAndDashboardsToDelete(); - const folderCount = data.folderUids.length; - const dashCount = data.dashboardUids.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(CoreEvents.showConfirmModal, { - title: 'Delete', - text: text, - text2: text2, - icon: 'trash-alt', - yesText: 'Delete', - onConfirm: () => { - this.deleteFoldersAndDashboards(data.folderUids, data.dashboardUids); - }, - }); - }; - - private deleteFoldersAndDashboards(folderUids: string[], dashboardUids: string[]) { - promiseToDigest(this.$scope)( - backendSrv.deleteFoldersAndDashboards(folderUids, dashboardUids).then(() => { - this.refreshList(); - }) - ); - } - - getDashboardsToMove() { - const selectedDashboards = []; - - for (const section of this.sections) { - const selected = _.filter(section.items, { checked: true } as any); - selectedDashboards.push(..._.map(selected, 'uid')); - } - - return selectedDashboards; - } - - moveTo = () => { - const selectedDashboards = this.getDashboardsToMove(); - - const template = - '' + - ''; - appEvents.emit(CoreEvents.showModal, { - templateHtml: template, - modalClass: 'modal--narrow', - model: { - dashboards: selectedDashboards, - afterSave: this.refreshList.bind(this), - }, - }); - }; - - initTagFilter() { - return this.searchSrv.getDashboardTags().then((results: any) => { - this.tagFilterOptions = results.map((result: any) => ({ value: result.term, label: result.term })); - }); - } - - filterByTag = (tag: any) => { - if (tag) { - if (_.indexOf(this.query.tag, tag) === -1) { - this.query.tag.push(tag); - } - } - return this.refreshList(); - }; - - onQueryChange() { - return this.refreshList(); - } - - onTagFilterChange = (filter: SelectableValue) => { - const res = this.filterByTag(filter.value); - this.selectedTagFilter = filter.value; - return res; - }; - - removeTag(tag: any, evt: Event) { - this.query.tag = _.without(this.query.tag, tag); - this.refreshList(); - if (evt) { - evt.stopPropagation(); - evt.preventDefault(); - } - } - - removeStarred() { - this.query.starred = false; - return this.refreshList(); - } - - onStarredFilterChange = (filter: SelectableValue) => { - this.query.starred = filter.value; - this.selectedStarredFilter = filter.value; - return this.refreshList(); - }; - - onSelectAllChanged = () => { - this.selectAllChecked = !this.selectAllChecked; - - for (const section of this.sections) { - if (!section.hideHeader) { - section.checked = this.selectAllChecked; - } - - section.items = _.map(section.items, (item: any) => { - item.checked = this.selectAllChecked; - return item; - }); - } - this.selectionChanged(); - }; - - clearFilters() { - this.query.query = ''; - this.query.tag = []; - this.query.starred = false; - this.selectedStarredFilter = 'starred'; - this.selectedTagFilter = 'tag'; - this.refreshList(); - } - - createDashboardUrl() { - let url = 'dashboard/new'; - - if (this.folderId) { - url += `?folderId=${this.folderId}`; - } - - return url; - } - - importDashboardUrl() { - let url = 'dashboard/import'; - - if (this.folderId) { - url += `?folderId=${this.folderId}`; - } - - return url; - } -} - -export function manageDashboardsDirective() { - return { - restrict: 'E', - templateUrl: 'public/app/core/components/manage_dashboards/manage_dashboards.html', - controller: ManageDashboardsCtrl, - bindToController: true, - controllerAs: 'ctrl', - scope: { - folderId: '=', - folderUid: '=', - }, - }; -} - -//coreModule.directive('manageDashboards', manageDashboardsDirective); diff --git a/public/app/core/components/search/search.html b/public/app/core/components/search/search.html deleted file mode 100644 index 66451b10647..00000000000 --- a/public/app/core/components/search/search.html +++ /dev/null @@ -1,62 +0,0 @@ -
- - diff --git a/public/app/core/components/search/search.ts b/public/app/core/components/search/search.ts deleted file mode 100644 index 6a45f5dc076..00000000000 --- a/public/app/core/components/search/search.ts +++ /dev/null @@ -1,355 +0,0 @@ -import _, { debounce } from 'lodash'; -import coreModule from '../../core_module'; -import { SearchSrv } from 'app/core/services/search_srv'; -import { contextSrv } from 'app/core/services/context_srv'; - -import appEvents from 'app/core/app_events'; -import { parse, SearchParserOptions, SearchParserResult } from 'search-query-parser'; -import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; -import { CoreEvents } from 'app/types'; - -export interface SearchQuery { - query: string; - parsedQuery: SearchParserResult; - tags: string[]; - starred: boolean; -} - -class SearchQueryParser { - config: SearchParserOptions; - constructor(config: SearchParserOptions) { - this.config = config; - } - - parse(query: string) { - const parsedQuery = parse(query, this.config); - - if (typeof parsedQuery === 'string') { - return { - text: parsedQuery, - } as SearchParserResult; - } - - return parsedQuery; - } -} - -interface SelectedIndicies { - dashboardIndex?: number; - folderIndex?: number; -} - -interface OpenSearchParams { - query?: string; -} - -export class SearchCtrl { - isOpen: boolean; - query: SearchQuery; - giveSearchFocus: boolean; - selectedIndex: number; - results: any; - currentSearchId: number; - showImport: boolean; - dismiss: any; - ignoreClose: any; - isLoading: boolean; - initialFolderFilterTitle: string; - isEditor: string; - hasEditPermissionInFolders: boolean; - queryParser: SearchQueryParser; - - /** @ngInject */ - constructor(private $scope: any, private $location: any, private $timeout: any, private searchSrv: SearchSrv) { - appEvents.on(CoreEvents.showDashSearch, this.openSearch.bind(this), $scope); - appEvents.on(CoreEvents.hideDashSearch, this.closeSearch.bind(this), $scope); - appEvents.on(CoreEvents.searchQuery, debounce(this.search.bind(this), 500), $scope); - - this.initialFolderFilterTitle = 'All'; - this.isEditor = contextSrv.isEditor; - this.hasEditPermissionInFolders = contextSrv.hasEditPermissionInFolders; - this.onQueryChange = this.onQueryChange.bind(this); - this.onKeyDown = this.onKeyDown.bind(this); - this.query = { - query: '', - parsedQuery: { text: '' }, - tags: [], - starred: false, - }; - - this.queryParser = new SearchQueryParser({ - keywords: ['folder'], - }); - } - - closeSearch() { - this.isOpen = this.ignoreClose; - } - - onQueryChange(query: SearchQuery | string) { - if (typeof query === 'string') { - this.query = { - ...this.query, - parsedQuery: this.queryParser.parse(query), - query: query, - }; - } else { - this.query = query; - } - appEvents.emit(CoreEvents.searchQuery); - } - - openSearch(payload: OpenSearchParams = {}) { - if (this.isOpen) { - this.closeSearch(); - return; - } - - this.isOpen = true; - this.giveSearchFocus = true; - this.selectedIndex = -1; - this.results = []; - this.query = { - query: payload.query ? `${payload.query} ` : '', - parsedQuery: this.queryParser.parse(payload.query), - tags: [], - starred: false, - }; - - this.currentSearchId = 0; - this.ignoreClose = true; - this.isLoading = true; - - this.$timeout(() => { - this.ignoreClose = false; - this.giveSearchFocus = true; - this.search(); - }, 100); - } - - onKeyDown(evt: KeyboardEvent) { - if (evt.keyCode === 27) { - this.closeSearch(); - } - if (evt.keyCode === 40) { - this.moveSelection(1); - } - if (evt.keyCode === 38) { - this.moveSelection(-1); - } - if (evt.keyCode === 13) { - const flattenedResult = this.getFlattenedResultForNavigation(); - const currentItem = flattenedResult[this.selectedIndex]; - - if (currentItem) { - if (currentItem.dashboardIndex !== undefined) { - const selectedDash = this.results[currentItem.folderIndex].items[currentItem.dashboardIndex]; - - if (selectedDash) { - this.$location.search({}); - this.$location.path(selectedDash.url); - this.closeSearch(); - } - } else { - const selectedFolder = this.results[currentItem.folderIndex]; - - if (selectedFolder) { - selectedFolder.toggle(selectedFolder); - } - } - } - } - } - - onFilterboxClick() { - this.giveSearchFocus = false; - this.preventClose(); - } - - preventClose() { - this.ignoreClose = true; - - this.$timeout(() => { - this.ignoreClose = false; - }, 100); - } - - moveSelection(direction: number) { - if (this.results.length === 0) { - return; - } - - const flattenedResult = this.getFlattenedResultForNavigation(); - const currentItem = flattenedResult[this.selectedIndex]; - - if (currentItem) { - if (currentItem.dashboardIndex !== undefined) { - this.results[currentItem.folderIndex].items[currentItem.dashboardIndex].selected = false; - } else { - this.results[currentItem.folderIndex].selected = false; - } - } - - if (direction === 0) { - this.selectedIndex = -1; - return; - } - - const max = flattenedResult.length; - const newIndex = (this.selectedIndex + direction) % max; - this.selectedIndex = newIndex < 0 ? newIndex + max : newIndex; - const selectedItem = flattenedResult[this.selectedIndex]; - - if (selectedItem.dashboardIndex === undefined && this.results[selectedItem.folderIndex].id === 0) { - this.moveSelection(direction); - return; - } - - if (selectedItem.dashboardIndex !== undefined) { - if (!this.results[selectedItem.folderIndex].expanded) { - this.moveSelection(direction); - return; - } - - this.results[selectedItem.folderIndex].items[selectedItem.dashboardIndex].selected = true; - return; - } - - if (this.results[selectedItem.folderIndex].hideHeader) { - this.moveSelection(direction); - return; - } - - this.results[selectedItem.folderIndex].selected = true; - } - - searchDashboards(folderContext?: string) { - this.currentSearchId = this.currentSearchId + 1; - const localSearchId = this.currentSearchId; - const folderIds = []; - - const { parsedQuery } = this.query; - - if (folderContext === 'current') { - folderIds.push(getDashboardSrv().getCurrent().meta.folderId); - } - - const query = { - ...this.query, - query: parsedQuery.text, - tag: this.query.tags, - folderIds, - }; - - return this.searchSrv - .search({ - ...query, - }) - .then(results => { - if (localSearchId < this.currentSearchId) { - return; - } - this.results = results || []; - this.isLoading = false; - this.moveSelection(1); - this.$scope.$digest(); - }); - } - - queryHasNoFilters() { - const query = this.query; - return query.query === '' && query.starred === false && query.tags.length === 0; - } - - filterByTag = (tag: string) => { - if (tag) { - if (_.indexOf(this.query.tags, tag) === -1) { - this.query.tags.push(tag); - this.search(); - } - } - }; - - removeTag(tag: string, evt: any) { - this.query.tags = _.without(this.query.tags, tag); - this.search(); - this.giveSearchFocus = true; - evt.stopPropagation(); - evt.preventDefault(); - } - - getTags = () => { - return this.searchSrv.getDashboardTags(); - }; - - onTagFiltersChanged = (tags: string[]) => { - this.query.tags = tags; - this.search(); - }; - - clearSearchFilter() { - this.query.query = ''; - this.query.tags = []; - this.search(); - } - - showStarred() { - this.query.starred = !this.query.starred; - this.giveSearchFocus = true; - this.search(); - } - - selectionChanged = () => { - // TODO remove after React-side state management is implemented - // This method is only used as a callback after toggling section, to trigger results rerender - }; - - search() { - this.showImport = false; - this.selectedIndex = -1; - this.searchDashboards(this.query.parsedQuery['folder']); - } - - folderExpanding = () => { - this.moveSelection(0); - }; - - private getFlattenedResultForNavigation(): SelectedIndicies[] { - let folderIndex = 0; - - return _.flatMap(this.results, (s: any) => { - let result: SelectedIndicies[] = []; - - result.push({ - folderIndex: folderIndex, - }); - - let dashboardIndex = 0; - - result = result.concat( - _.map(s.items || [], i => { - return { - folderIndex: folderIndex, - dashboardIndex: dashboardIndex++, - }; - }) - ); - - folderIndex++; - return result; - }); - } -} - -export function searchDirective() { - return { - restrict: 'E', - templateUrl: 'public/app/core/components/search/search.html', - controller: SearchCtrl, - bindToController: true, - controllerAs: 'ctrl', - scope: {}, - }; -} - -coreModule.directive('dashboardSearch', searchDirective); diff --git a/public/app/core/components/search/search_results.html b/public/app/core/components/search/search_results.html deleted file mode 100644 index 2ce9d60bf46..00000000000 --- a/public/app/core/components/search/search_results.html +++ /dev/null @@ -1,64 +0,0 @@ - diff --git a/public/app/core/components/search/search_results.ts b/public/app/core/components/search/search_results.ts deleted file mode 100644 index cf897af36a7..00000000000 --- a/public/app/core/components/search/search_results.ts +++ /dev/null @@ -1,102 +0,0 @@ -import _ from 'lodash'; -import { ILocationService, IScope } from 'angular'; -import { e2e } from '@grafana/e2e'; - -import coreModule from '../../core_module'; -import appEvents from 'app/core/app_events'; -import { CoreEvents } from 'app/types'; -import { promiseToDigest } from '../../utils/promiseToDigest'; - -export class SearchResultsCtrl { - results: any; - onSelectionChanged: any; - onTagSelected: any; - onFolderExpanding: any; - editable: boolean; - selectors: typeof e2e.pages.Dashboards.selectors; - - /** @ngInject */ - constructor(private $location: ILocationService, private $scope: IScope) { - this.selectors = e2e.pages.Dashboards.selectors; - } - - toggleFolderExpand(section: any) { - if (section.toggle) { - if (!section.expanded && this.onFolderExpanding) { - this.onFolderExpanding(); - } - - promiseToDigest(this.$scope)( - section.toggle(section).then((f: any) => { - if (this.editable && f.expanded) { - if (f.items) { - _.each(f.items, i => { - i.checked = f.checked; - }); - - if (this.onSelectionChanged) { - this.onSelectionChanged(); - } - } - } - }) - ); - } - } - - toggleSelection(item: any, evt: any) { - item.checked = !item.checked; - - if (item.items) { - _.each(item.items, i => { - i.checked = item.checked; - }); - } - - if (this.onSelectionChanged) { - this.onSelectionChanged(); - } - - if (evt) { - evt.stopPropagation(); - evt.preventDefault(); - } - } - - onItemClick(item: any) { - //Check if one string can be found in the other - if (this.$location.path().indexOf(item.url) > -1 || item.url.indexOf(this.$location.path()) > -1) { - appEvents.emit(CoreEvents.hideDashSearch); - } - } - - selectTag(tag: any, evt: any) { - 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: '&', - onFolderExpanding: '&', - }, - }; -} - -coreModule.directive('dashboardSearchResults', searchResultsDirective); diff --git a/public/app/core/core.ts b/public/app/core/core.ts index 07e61f46667..a9c6ce57421 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -18,7 +18,6 @@ import './services/search_srv'; import './services/ng_react'; import { colors, JsonExplorer } from '@grafana/ui/'; -import { searchDirective } from './components/search/search'; import { infoPopover } from './components/info_popover'; import { arrayJoin } from './directives/array_join'; import { liveSrv } from './live/live_srv'; @@ -42,8 +41,6 @@ import { profiler } from './profiler'; import { registerAngularDirectives } from './angular_wrappers'; import { updateLegendValues } from './time_series2'; import TimeSeries from './time_series2'; -import { searchResultsDirective } from './components/search/search_results'; -import { manageDashboardsDirective } from './components/manage_dashboards/manage_dashboards'; import { NavModel } from '@grafana/data'; export { @@ -51,7 +48,6 @@ export { registerAngularDirectives, arrayJoin, coreModule, - searchDirective, liveSrv, switchDirective, infoPopover, @@ -69,8 +65,6 @@ export { NavModelSrv, NavModel, geminiScrollbar, - manageDashboardsDirective, TimeSeries, updateLegendValues, - searchResultsDirective, }; diff --git a/public/app/core/services/backend_srv.ts b/public/app/core/services/backend_srv.ts index 1852a87ae03..e99cd1ee8bf 100644 --- a/public/app/core/services/backend_srv.ts +++ b/public/app/core/services/backend_srv.ts @@ -7,12 +7,12 @@ import { AppEvents } from '@grafana/data'; import appEvents from 'app/core/app_events'; import config from 'app/core/config'; import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; -import { DashboardSearchHit } from 'app/types/search'; +import { DataSourceResponse } from 'app/types/events'; +import { DashboardSearchHit } from 'app/features/search/types'; import { CoreEvents, DashboardDTO, FolderInfo } from 'app/types'; -import { ContextSrv, contextSrv } from './context_srv'; import { coreModule } from 'app/core/core_module'; +import { ContextSrv, contextSrv } from './context_srv'; import { Emitter } from '../utils/emitter'; -import { DataSourceResponse } from '../../types/events'; import { parseInitFromOptions, parseUrlFromOptions } from '../utils/fetch'; export interface DatasourceRequestOptions { diff --git a/public/app/core/services/search_srv.ts b/public/app/core/services/search_srv.ts index 848d8f47836..267ab88a1af 100644 --- a/public/app/core/services/search_srv.ts +++ b/public/app/core/services/search_srv.ts @@ -1,16 +1,14 @@ import _ from 'lodash'; -import coreModule from 'app/core/core_module'; import impressionSrv from 'app/core/services/impression_srv'; import store from 'app/core/store'; import { contextSrv } from 'app/core/services/context_srv'; +import { hasFilters } from 'app/features/search/utils'; +import { DashboardSection, DashboardSearchItemType, DashboardSearchHit } from 'app/features/search/types'; import { backendSrv } from './backend_srv'; -import { Section } from '../components/manage_dashboards/manage_dashboards'; -import { DashboardSearchHit, DashboardSearchHitType } from 'app/types/search'; -import { hasFilters } from '../../features/search/utils'; interface Sections { - [key: string]: Partial
; + [key: string]: Partial; } export class SearchSrv { @@ -22,18 +20,16 @@ export class SearchSrv { this.starredIsOpen = store.getBool('search.sections.starred', true); } - private getRecentDashboards(sections: Sections) { + private getRecentDashboards(sections: DashboardSection[] | any) { return this.queryForRecentDashboards().then((result: any[]) => { if (result.length > 0) { sections['recent'] = { title: 'Recent', icon: 'clock-nine', score: -1, - removable: true, expanded: this.recentIsOpen, - toggle: this.toggleRecent.bind(this), items: result, - type: DashboardSearchHitType.DashHitFolder, + type: DashboardSearchItemType.DashFolder, }; } }); @@ -50,45 +46,24 @@ export class SearchSrv { .map(orderId => { return _.find(result, { id: orderId }); }) - .filter(hit => hit && !hit.isStarred); + .filter(hit => hit && !hit.isStarred) as DashboardSearchHit[]; }); } - private toggleRecent(section: Section) { - this.recentIsOpen = section.expanded = !section.expanded; - store.set('search.sections.recent', this.recentIsOpen); - - if (!section.expanded || section.items.length) { - return Promise.resolve(section); - } - - return this.queryForRecentDashboards().then(result => { - section.items = result; - return Promise.resolve(section); - }); - } - - private toggleStarred(section: Section) { - this.starredIsOpen = section.expanded = !section.expanded; - store.set('search.sections.starred', this.starredIsOpen); - return Promise.resolve(section); - } - - private getStarred(sections: Sections) { + private getStarred(sections: DashboardSection) { if (!contextSrv.isSignedIn) { return Promise.resolve(); } return backendSrv.search({ starred: true, limit: 30 }).then(result => { if (result.length > 0) { - sections['starred'] = { + (sections as any)['starred'] = { title: 'Starred', icon: 'star', score: -2, expanded: this.starredIsOpen, - toggle: this.toggleStarred.bind(this), items: result, - type: DashboardSearchHitType.DashHitFolder, + type: DashboardSearchItemType.DashFolder, }; } }); @@ -138,7 +113,6 @@ export class SearchSrv { title: hit.title, expanded: false, items: [], - toggle: this.toggleFolder.bind(this), url: hit.url, icon: 'folder', score: _.keys(sections).length, @@ -162,9 +136,8 @@ export class SearchSrv { url: hit.folderUrl, items: [], icon: 'folder-open', - toggle: this.toggleFolder.bind(this), score: _.keys(sections).length, - type: DashboardSearchHitType.DashHitFolder, + type: DashboardSearchItemType.DashFolder, }; } else { section = { @@ -172,9 +145,8 @@ export class SearchSrv { title: 'General', items: [], icon: 'folder-open', - toggle: this.toggleFolder.bind(this), score: _.keys(sections).length, - type: DashboardSearchHitType.DashHitFolder, + type: DashboardSearchItemType.DashFolder, }; } // add section @@ -182,28 +154,10 @@ export class SearchSrv { } section.expanded = true; - section.items.push(hit); + section.items && section.items.push(hit); } } - private toggleFolder(section: Section) { - section.expanded = !section.expanded; - section.icon = section.expanded ? 'folder-open' : 'folder'; - - if (section.items.length) { - return Promise.resolve(section); - } - - const query = { - folderIds: [section.id], - }; - - return backendSrv.search(query).then(results => { - section.items = results; - return Promise.resolve(section); - }); - } - getDashboardTags() { return backendSrv.get('/api/dashboards/tags'); } @@ -212,5 +166,3 @@ export class SearchSrv { return backendSrv.get('/api/search/sorting'); } } - -coreModule.service('searchSrv', SearchSrv); diff --git a/public/app/core/specs/manage_dashboards.test.ts b/public/app/core/specs/manage_dashboards.test.ts deleted file mode 100644 index 9efab0c8655..00000000000 --- a/public/app/core/specs/manage_dashboards.test.ts +++ /dev/null @@ -1,598 +0,0 @@ -// @ts-ignore -import q from 'q'; -import { - ManageDashboardsCtrl, - Section, - FoldersAndDashboardUids, -} from 'app/core/components/manage_dashboards/manage_dashboards'; -import { SearchSrv } from 'app/core/services/search_srv'; -import { ContextSrv } from '../services/context_srv'; - -const mockSection = (overides?: object): Section => { - const defaultSection: Section = { - id: 0, - items: [], - checked: false, - expanded: false, - removable: false, - hideHeader: false, - icon: '', - score: 0, - title: 'Some Section', - toggle: jest.fn(), - uid: 'someuid', - url: '/some/url/', - }; - - return { ...defaultSection, ...overides }; -}; - -describe('ManageDashboards', () => { - let ctrl: ManageDashboardsCtrl; - - describe('when browsing dashboards', () => { - beforeEach(() => { - const tags: any[] = []; - const response = [ - { - id: 410, - title: 'afolder', - type: 'dash-folder', - items: [ - { - id: 399, - title: 'Dashboard Test', - url: 'dashboard/db/dashboard-test', - icon: 'folder', - tags, - isStarred: false, - }, - ], - tags, - isStarred: false, - }, - { - id: 0, - title: 'General', - icon: 'folder-open', - uri: 'db/something-else', - type: 'dash-db', - items: [ - { - id: 500, - title: 'Dashboard Test', - url: 'dashboard/db/dashboard-test', - icon: 'folder', - tags, - isStarred: false, - }, - ], - tags, - isStarred: false, - }, - ]; - ctrl = createCtrlWithStubs(response); - return ctrl.refreshList(); - }); - - it('should set checked to false on all sections and children', () => { - expect(ctrl.sections.length).toEqual(2); - expect(ctrl.sections[0].checked).toEqual(false); - 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 tags: any[] = []; - const response = [ - { - id: 410, - title: 'afolder', - type: 'dash-folder', - items: [ - { - id: 399, - title: 'Dashboard Test', - url: 'dashboard/db/dashboard-test', - icon: 'folder', - tags, - isStarred: false, - }, - ], - tags, - isStarred: false, - }, - ]; - ctrl = createCtrlWithStubs(response); - ctrl.folderId = 410; - return ctrl.refreshList(); - }); - - it('should set hide header to true on section', () => { - expect(ctrl.sections[0].hideHeader).toBeTruthy(); - }); - }); - - describe('when searching dashboards', () => { - beforeEach(() => { - const tags: any[] = []; - const response = [ - { - checked: false, - expanded: true, - hideHeader: true, - items: [ - { - id: 399, - title: 'Dashboard Test', - url: 'dashboard/db/dashboard-test', - icon: 'folder', - tags, - isStarred: false, - folderId: 410, - folderUid: 'uid', - folderTitle: 'Folder', - folderUrl: '/dashboards/f/uid/folder', - }, - { - id: 500, - title: 'Dashboard Test', - url: 'dashboard/db/dashboard-test', - icon: 'folder', - tags, - folderId: 499, - isStarred: false, - }, - ], - }, - ]; - - ctrl = createCtrlWithStubs(response); - }); - - describe('with query filter', () => { - beforeEach(() => { - ctrl.query.query = 'd'; - ctrl.canMove = true; - ctrl.canDelete = true; - ctrl.selectAllChecked = true; - return ctrl.refreshList(); - }); - - it('should set checked to false on all sections and children', () => { - expect(ctrl.sections.length).toEqual(1); - expect(ctrl.sections[0].checked).toEqual(false); - expect(ctrl.sections[0].items[0].checked).toEqual(false); - expect(ctrl.sections[0].items[1].checked).toEqual(false); - }); - - it('should uncheck select all', () => { - expect(ctrl.selectAllChecked).toBeFalsy(); - }); - - it('should disable Move To button', () => { - expect(ctrl.canMove).toBeFalsy(); - }); - - it('should disable delete button', () => { - expect(ctrl.canDelete).toBeFalsy(); - }); - - it('should have active filters', () => { - expect(ctrl.hasFilters).toBeTruthy(); - }); - - describe('when select all is checked', () => { - beforeEach(() => { - ctrl.selectAllChecked = false; - ctrl.onSelectAllChanged(); - }); - - it('should select all dashboards', () => { - expect(ctrl.sections[0].checked).toBeFalsy(); - expect(ctrl.sections[0].items[0].checked).toBeTruthy(); - expect(ctrl.sections[0].items[1].checked).toBeTruthy(); - }); - - it('should enable Move To button', () => { - expect(ctrl.canMove).toBeTruthy(); - }); - - it('should enable delete button', () => { - expect(ctrl.canDelete).toBeTruthy(); - }); - - describe('when clearing filters', () => { - beforeEach(() => { - return ctrl.clearFilters(); - }); - - it('should reset query filter', () => { - expect(ctrl.query.query).toEqual(''); - }); - }); - }); - }); - - describe('with tag filter', () => { - beforeEach(() => { - return ctrl.filterByTag('test'); - }); - - it('should set tag filter', () => { - expect(ctrl.sections.length).toEqual(1); - expect(ctrl.query.tag[0]).toEqual('test'); - }); - - it('should have active filters', () => { - expect(ctrl.hasFilters).toBeTruthy(); - }); - - describe('when clearing filters', () => { - beforeEach(() => { - return ctrl.clearFilters(); - }); - - it('should reset tag filter', () => { - expect(ctrl.query.tag.length).toEqual(0); - }); - }); - }); - - describe('with starred filter', () => { - beforeEach(() => { - const yesOption: any = { label: 'Yes', value: true }; - - ctrl.selectedStarredFilter = yesOption; - return ctrl.onStarredFilterChange(yesOption); - }); - - it('should set starred filter', () => { - expect(ctrl.sections.length).toEqual(1); - expect(ctrl.query.starred).toEqual(true); - }); - - it('should have active filters', () => { - expect(ctrl.hasFilters).toBeTruthy(); - }); - - describe('when clearing filters', () => { - beforeEach(() => { - return ctrl.clearFilters(); - }); - - it('should reset starred filter', () => { - expect(ctrl.query.starred).toEqual(false); - }); - }); - }); - }); - - describe('when selecting dashboards', () => { - let ctrl: ManageDashboardsCtrl; - - beforeEach(() => { - ctrl = createCtrlWithStubs([]); - }); - - describe('and no dashboards are selected', () => { - beforeEach(() => { - ctrl.sections = [ - mockSection({ - id: 1, - items: [{ id: 2, checked: false }], - checked: false, - }), - mockSection({ - id: 0, - items: [{ id: 3, checked: false }], - checked: false, - }), - ]; - ctrl.selectionChanged(); - }); - - it('should disable Move To button', () => { - expect(ctrl.canMove).toBeFalsy(); - }); - - it('should disable delete button', () => { - expect(ctrl.canDelete).toBeFalsy(); - }); - - describe('when select all is checked', () => { - beforeEach(() => { - ctrl.selectAllChecked = false; - ctrl.onSelectAllChanged(); - }); - - it('should select all folders and dashboards', () => { - expect(ctrl.sections[0].checked).toBeTruthy(); - expect(ctrl.sections[0].items[0].checked).toBeTruthy(); - expect(ctrl.sections[1].checked).toBeTruthy(); - expect(ctrl.sections[1].items[0].checked).toBeTruthy(); - }); - - it('should enable Move To button', () => { - expect(ctrl.canMove).toBeTruthy(); - }); - - it('should enable delete button', () => { - expect(ctrl.canDelete).toBeTruthy(); - }); - }); - }); - - describe('and all folders and dashboards are selected', () => { - beforeEach(() => { - ctrl.sections = [ - mockSection({ - id: 1, - items: [{ id: 2, checked: true }], - checked: true, - }), - mockSection({ - id: 0, - items: [{ id: 3, checked: true }], - checked: true, - }), - ]; - ctrl.selectionChanged(); - }); - - it('should enable Move To button', () => { - expect(ctrl.canMove).toBeTruthy(); - }); - - it('should enable delete button', () => { - expect(ctrl.canDelete).toBeTruthy(); - }); - - describe('when select all is unchecked', () => { - beforeEach(() => { - ctrl.selectAllChecked = true; - ctrl.onSelectAllChanged(); - }); - - it('should uncheck all checked folders and dashboards', () => { - expect(ctrl.sections[0].checked).toBeFalsy(); - expect(ctrl.sections[0].items[0].checked).toBeFalsy(); - expect(ctrl.sections[1].checked).toBeFalsy(); - expect(ctrl.sections[1].items[0].checked).toBeFalsy(); - }); - - it('should disable Move To button', () => { - expect(ctrl.canMove).toBeFalsy(); - }); - - it('should disable delete button', () => { - expect(ctrl.canDelete).toBeFalsy(); - }); - }); - }); - - describe('and one dashboard in root is selected', () => { - beforeEach(() => { - ctrl.sections = [ - mockSection({ - id: 1, - title: 'folder', - items: [{ id: 2, checked: false }], - checked: false, - }), - mockSection({ - id: 0, - title: 'General', - items: [{ id: 3, checked: true }], - checked: false, - }), - ]; - ctrl.selectionChanged(); - }); - - it('should enable Move To button', () => { - expect(ctrl.canMove).toBeTruthy(); - }); - - it('should enable delete button', () => { - expect(ctrl.canDelete).toBeTruthy(); - }); - }); - - describe('and one child dashboard is selected', () => { - beforeEach(() => { - ctrl.sections = [ - mockSection({ - id: 1, - title: 'folder', - items: [{ id: 2, checked: true }], - checked: false, - }), - mockSection({ - id: 0, - title: 'General', - items: [{ id: 3, checked: false }], - checked: false, - }), - ]; - - ctrl.selectionChanged(); - }); - - it('should enable Move To button', () => { - expect(ctrl.canMove).toBeTruthy(); - }); - - it('should enable delete button', () => { - expect(ctrl.canDelete).toBeTruthy(); - }); - }); - - describe('and one child dashboard and one dashboard is selected', () => { - beforeEach(() => { - ctrl.sections = [ - mockSection({ - id: 1, - title: 'folder', - items: [{ id: 2, checked: true }], - checked: false, - }), - mockSection({ - id: 0, - title: 'General', - items: [{ id: 3, checked: true }], - checked: false, - }), - ]; - - ctrl.selectionChanged(); - }); - - it('should enable Move To button', () => { - expect(ctrl.canMove).toBeTruthy(); - }); - - it('should enable delete button', () => { - expect(ctrl.canDelete).toBeTruthy(); - }); - }); - - describe('and one child dashboard and one folder is selected', () => { - beforeEach(() => { - ctrl.sections = [ - mockSection({ - id: 1, - title: 'folder', - items: [{ id: 2, checked: false }], - checked: true, - }), - mockSection({ - id: 3, - title: 'folder', - items: [{ id: 4, checked: true }], - checked: false, - }), - mockSection({ - id: 0, - title: 'General', - items: [{ id: 3, checked: false }], - checked: false, - }), - ]; - - ctrl.selectionChanged(); - }); - - it('should enable Move To button', () => { - expect(ctrl.canMove).toBeTruthy(); - }); - - it('should enable delete button', () => { - expect(ctrl.canDelete).toBeTruthy(); - }); - }); - }); - - describe('when deleting dashboards', () => { - let toBeDeleted: FoldersAndDashboardUids; - - beforeEach(() => { - ctrl = createCtrlWithStubs([]); - - ctrl.sections = [ - mockSection({ - id: 1, - uid: 'folder', - title: 'folder', - items: [{ id: 2, checked: true, uid: 'folder-dash' }], - checked: true, - }), - mockSection({ - id: 3, - title: 'folder-2', - items: [{ id: 3, checked: true, uid: 'folder-2-dash' }], - checked: false, - uid: 'folder-2', - }), - mockSection({ - id: 0, - title: 'General', - items: [{ id: 3, checked: true, uid: 'root-dash' }], - checked: true, - }), - ]; - - toBeDeleted = ctrl.getFoldersAndDashboardsToDelete(); - }); - - it('should return 1 folder', () => { - expect(toBeDeleted.folderUids.length).toEqual(1); - }); - - it('should return 2 dashboards', () => { - expect(toBeDeleted.dashboardUids.length).toEqual(2); - }); - - it('should filter out children if parent is checked', () => { - expect(toBeDeleted.folderUids[0]).toEqual('folder'); - }); - - it('should not filter out children if parent not is checked', () => { - expect(toBeDeleted.dashboardUids[0]).toEqual('folder-2-dash'); - }); - - it('should not filter out children if parent is checked and root', () => { - expect(toBeDeleted.dashboardUids[1]).toEqual('root-dash'); - }); - }); - - describe('when moving dashboards', () => { - beforeEach(() => { - ctrl = createCtrlWithStubs([]); - - ctrl.sections = [ - mockSection({ - id: 1, - title: 'folder', - items: [{ id: 2, checked: true, uid: 'dash' }], - checked: false, - uid: 'folder', - }), - mockSection({ - id: 0, - title: 'General', - items: [{ id: 3, checked: true, uid: 'dash-2' }], - checked: false, - }), - ]; - }); - - it('should get selected dashboards', () => { - const toBeMove = ctrl.getDashboardsToMove(); - expect(toBeMove.length).toEqual(2); - expect(toBeMove[0]).toEqual('dash'); - expect(toBeMove[1]).toEqual('dash-2'); - }); - }); -}); - -function createCtrlWithStubs(searchResponse: any, tags?: any) { - const searchSrvStub = { - search: (options: any) => { - return q.resolve(searchResponse); - }, - getDashboardTags: () => { - return q.resolve(tags || []); - }, - }; - - return new ManageDashboardsCtrl( - { $digest: jest.fn() } as any, - searchSrvStub as SearchSrv, - { isEditor: true } as ContextSrv - ); -} diff --git a/public/app/core/specs/search.test.ts b/public/app/core/specs/search.test.ts deleted file mode 100644 index 76ad1a5bc54..00000000000 --- a/public/app/core/specs/search.test.ts +++ /dev/null @@ -1,341 +0,0 @@ -import { SearchCtrl } from '../components/search/search'; -import { SearchSrv } from '../services/search_srv'; - -jest.mock('app/core/services/context_srv', () => ({ - contextSrv: { - user: { orgId: 1 }, - }, -})); - -describe('SearchCtrl', () => { - const searchSrvStub = { - search: (options: any) => {}, - getDashboardTags: () => {}, - }; - const ctrl = new SearchCtrl({ $on: () => {} }, {}, {}, searchSrvStub as SearchSrv); - - describe('Given an empty result', () => { - beforeEach(() => { - ctrl.results = []; - }); - - describe('When navigating down one step', () => { - beforeEach(() => { - ctrl.selectedIndex = 0; - ctrl.moveSelection(1); - }); - - it('should not navigate', () => { - expect(ctrl.selectedIndex).toBe(0); - }); - }); - - describe('When navigating up one step', () => { - beforeEach(() => { - ctrl.selectedIndex = 0; - ctrl.moveSelection(-1); - }); - - it('should not navigate', () => { - expect(ctrl.selectedIndex).toBe(0); - }); - }); - }); - - describe('Given a result of one selected collapsed folder with no dashboards and a root folder with 2 dashboards', () => { - beforeEach(() => { - ctrl.results = [ - { - id: 1, - title: 'folder', - items: [], - selected: true, - expanded: false, - toggle: (i: any) => (i.expanded = !i.expanded), - }, - { - id: 0, - title: 'General', - items: [ - { id: 3, selected: false }, - { id: 5, selected: false }, - ], - selected: false, - expanded: true, - toggle: (i: any) => (i.expanded = !i.expanded), - }, - ]; - }); - - describe('When navigating down one step', () => { - beforeEach(() => { - ctrl.selectedIndex = 0; - ctrl.moveSelection(1); - }); - - it('should select first dashboard in root folder', () => { - expect(ctrl.results[0].selected).toBeFalsy(); - expect(ctrl.results[1].selected).toBeFalsy(); - expect(ctrl.results[1].items[0].selected).toBeTruthy(); - expect(ctrl.results[1].items[1].selected).toBeFalsy(); - }); - }); - - describe('When navigating down two steps', () => { - beforeEach(() => { - ctrl.selectedIndex = 0; - ctrl.moveSelection(1); - ctrl.moveSelection(1); - }); - - it('should select last dashboard in root folder', () => { - expect(ctrl.results[0].selected).toBeFalsy(); - expect(ctrl.results[1].selected).toBeFalsy(); - expect(ctrl.results[1].items[0].selected).toBeFalsy(); - expect(ctrl.results[1].items[1].selected).toBeTruthy(); - }); - }); - - describe('When navigating down three steps', () => { - beforeEach(() => { - ctrl.selectedIndex = 0; - ctrl.moveSelection(1); - ctrl.moveSelection(1); - ctrl.moveSelection(1); - }); - - it('should select first folder', () => { - expect(ctrl.results[0].selected).toBeTruthy(); - expect(ctrl.results[1].selected).toBeFalsy(); - expect(ctrl.results[1].items[0].selected).toBeFalsy(); - expect(ctrl.results[1].items[1].selected).toBeFalsy(); - }); - }); - - describe('When navigating up one step', () => { - beforeEach(() => { - ctrl.selectedIndex = 0; - ctrl.moveSelection(-1); - }); - - it('should select last dashboard in root folder', () => { - expect(ctrl.results[0].selected).toBeFalsy(); - expect(ctrl.results[1].selected).toBeFalsy(); - expect(ctrl.results[1].items[0].selected).toBeFalsy(); - expect(ctrl.results[1].items[1].selected).toBeTruthy(); - }); - }); - - describe('When navigating up two steps', () => { - beforeEach(() => { - ctrl.selectedIndex = 0; - ctrl.moveSelection(-1); - ctrl.moveSelection(-1); - }); - - it('should select first dashboard in root folder', () => { - expect(ctrl.results[0].selected).toBeFalsy(); - expect(ctrl.results[1].selected).toBeFalsy(); - expect(ctrl.results[1].items[0].selected).toBeTruthy(); - expect(ctrl.results[1].items[1].selected).toBeFalsy(); - }); - }); - }); - - describe('Given a result of one selected collapsed folder with 2 dashboards and a root folder with 2 dashboards', () => { - beforeEach(() => { - ctrl.results = [ - { - id: 1, - title: 'folder', - items: [ - { id: 2, selected: false }, - { id: 4, selected: false }, - ], - selected: true, - expanded: false, - toggle: (i: any) => (i.expanded = !i.expanded), - }, - { - id: 0, - title: 'General', - items: [ - { id: 3, selected: false }, - { id: 5, selected: false }, - ], - selected: false, - expanded: true, - toggle: (i: any) => (i.expanded = !i.expanded), - }, - ]; - }); - - describe('When navigating down one step', () => { - beforeEach(() => { - ctrl.selectedIndex = 0; - ctrl.moveSelection(1); - }); - - it('should select first dashboard in root folder', () => { - expect(ctrl.results[0].selected).toBeFalsy(); - expect(ctrl.results[1].selected).toBeFalsy(); - expect(ctrl.results[0].items[0].selected).toBeFalsy(); - expect(ctrl.results[0].items[1].selected).toBeFalsy(); - expect(ctrl.results[1].items[0].selected).toBeTruthy(); - expect(ctrl.results[1].items[1].selected).toBeFalsy(); - }); - }); - - describe('When navigating down two steps', () => { - beforeEach(() => { - ctrl.selectedIndex = 0; - ctrl.moveSelection(1); - ctrl.moveSelection(1); - }); - - it('should select last dashboard in root folder', () => { - expect(ctrl.results[0].selected).toBeFalsy(); - expect(ctrl.results[1].selected).toBeFalsy(); - expect(ctrl.results[0].items[0].selected).toBeFalsy(); - expect(ctrl.results[0].items[1].selected).toBeFalsy(); - expect(ctrl.results[1].items[0].selected).toBeFalsy(); - expect(ctrl.results[1].items[1].selected).toBeTruthy(); - }); - }); - - describe('When navigating down three steps', () => { - beforeEach(() => { - ctrl.selectedIndex = 0; - ctrl.moveSelection(1); - ctrl.moveSelection(1); - ctrl.moveSelection(1); - }); - - it('should select first folder', () => { - expect(ctrl.results[0].selected).toBeTruthy(); - expect(ctrl.results[1].selected).toBeFalsy(); - expect(ctrl.results[0].items[0].selected).toBeFalsy(); - expect(ctrl.results[0].items[1].selected).toBeFalsy(); - expect(ctrl.results[1].items[0].selected).toBeFalsy(); - expect(ctrl.results[1].items[1].selected).toBeFalsy(); - }); - }); - - describe('When navigating up one step', () => { - beforeEach(() => { - ctrl.selectedIndex = 0; - ctrl.moveSelection(-1); - }); - - it('should select last dashboard in root folder', () => { - expect(ctrl.results[0].selected).toBeFalsy(); - expect(ctrl.results[1].selected).toBeFalsy(); - expect(ctrl.results[0].items[0].selected).toBeFalsy(); - expect(ctrl.results[0].items[1].selected).toBeFalsy(); - expect(ctrl.results[1].items[0].selected).toBeFalsy(); - expect(ctrl.results[1].items[1].selected).toBeTruthy(); - }); - }); - - describe('When navigating up two steps', () => { - beforeEach(() => { - ctrl.selectedIndex = 0; - ctrl.moveSelection(-1); - ctrl.moveSelection(-1); - }); - - it('should select first dashboard in root folder', () => { - expect(ctrl.results[0].selected).toBeFalsy(); - expect(ctrl.results[1].selected).toBeFalsy(); - expect(ctrl.results[1].items[0].selected).toBeTruthy(); - expect(ctrl.results[1].items[1].selected).toBeFalsy(); - }); - }); - }); - - describe('Given a result of a search with 2 dashboards where the first is selected', () => { - beforeEach(() => { - ctrl.results = [ - { - hideHeader: true, - items: [ - { id: 3, selected: true }, - { id: 5, selected: false }, - ], - selected: false, - expanded: true, - toggle: (i: any) => (i.expanded = !i.expanded), - }, - ]; - }); - - describe('When navigating down one step', () => { - beforeEach(() => { - ctrl.selectedIndex = 1; - ctrl.moveSelection(1); - }); - - it('should select last dashboard', () => { - expect(ctrl.results[0].selected).toBeFalsy(); - expect(ctrl.results[0].items[0].selected).toBeFalsy(); - expect(ctrl.results[0].items[1].selected).toBeTruthy(); - }); - }); - - describe('When navigating down two steps', () => { - beforeEach(() => { - ctrl.selectedIndex = 1; - ctrl.moveSelection(1); - ctrl.moveSelection(1); - }); - - it('should select first dashboard', () => { - expect(ctrl.results[0].selected).toBeFalsy(); - expect(ctrl.results[0].items[0].selected).toBeTruthy(); - expect(ctrl.results[0].items[1].selected).toBeFalsy(); - }); - }); - - describe('When navigating down three steps', () => { - beforeEach(() => { - ctrl.selectedIndex = 1; - ctrl.moveSelection(1); - ctrl.moveSelection(1); - ctrl.moveSelection(1); - }); - - it('should select last dashboard', () => { - expect(ctrl.results[0].selected).toBeFalsy(); - expect(ctrl.results[0].items[0].selected).toBeFalsy(); - expect(ctrl.results[0].items[1].selected).toBeTruthy(); - }); - }); - - describe('When navigating up one step', () => { - beforeEach(() => { - ctrl.selectedIndex = 1; - ctrl.moveSelection(-1); - }); - - it('should select last dashboard', () => { - expect(ctrl.results[0].selected).toBeFalsy(); - expect(ctrl.results[0].items[0].selected).toBeFalsy(); - expect(ctrl.results[0].items[1].selected).toBeTruthy(); - }); - }); - - describe('When navigating up two steps', () => { - beforeEach(() => { - ctrl.selectedIndex = 1; - ctrl.moveSelection(-1); - ctrl.moveSelection(-1); - }); - - it('should select first dashboard', () => { - expect(ctrl.results[0].selected).toBeFalsy(); - expect(ctrl.results[0].items[0].selected).toBeTruthy(); - expect(ctrl.results[0].items[1].selected).toBeFalsy(); - }); - }); - }); -}); diff --git a/public/app/core/specs/search_results.test.ts b/public/app/core/specs/search_results.test.ts deleted file mode 100644 index 9b6277ae7e9..00000000000 --- a/public/app/core/specs/search_results.test.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { ILocationService, IScope } from 'angular'; - -import { SearchResultsCtrl } from '../components/search/search_results'; -import { afterEach, beforeEach } from 'test/lib/common'; -import appEvents from 'app/core/app_events'; -import { CoreEvents } from 'app/types'; - -jest.mock('app/core/app_events', () => { - return { - emit: jest.fn(), - }; -}); - -describe('SearchResultsCtrl', () => { - let ctrl: any; - const $location = {} as ILocationService; - const $scope = ({ $evalAsync: jest.fn() } as any) as IScope; - - describe('when checking an item that is not checked', () => { - const item = { checked: false }; - let selectionChanged = false; - - beforeEach(() => { - ctrl = new SearchResultsCtrl($location, $scope); - 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', () => { - const item = { checked: true }; - let selectionChanged = false; - - beforeEach(() => { - ctrl = new SearchResultsCtrl($location, $scope); - 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: any = null; - - beforeEach(() => { - ctrl = new SearchResultsCtrl($location, $scope); - ctrl.onTagSelected = (tag: any) => (selectedTag = tag); - ctrl.selectTag('tag-test'); - }); - - it('should trigger tag selected callback', () => { - expect(selectedTag['$tag']).toBe('tag-test'); - }); - }); - - describe('when toggle a collapsed folder', () => { - let folderExpanded = false; - - beforeEach(() => { - ctrl = new SearchResultsCtrl($location, $scope); - ctrl.onFolderExpanding = () => { - folderExpanded = true; - }; - - const folder = { - expanded: false, - toggle: () => Promise.resolve(folder), - }; - - ctrl.toggleFolderExpand(folder); - }); - - it('should trigger folder expanding callback', () => { - expect(folderExpanded).toBeTruthy(); - }); - }); - - describe('when toggle an expanded folder', () => { - let folderExpanded = false; - - beforeEach(() => { - ctrl = new SearchResultsCtrl($location, $scope); - ctrl.onFolderExpanding = () => { - folderExpanded = true; - }; - - const folder = { - expanded: true, - toggle: () => Promise.resolve(folder), - }; - - ctrl.toggleFolderExpand(folder); - }); - - it('should not trigger folder expanding callback', () => { - expect(folderExpanded).toBeFalsy(); - }); - }); - - describe('when clicking on a link in search result', () => { - const dashPath = 'dashboard/path'; - const $location = ({ path: () => dashPath } as any) as ILocationService; - const appEventsMock = appEvents as any; - - describe('with the same url as current path', () => { - beforeEach(() => { - ctrl = new SearchResultsCtrl($location, $scope); - const item = { url: dashPath }; - ctrl.onItemClick(item); - }); - - it('should close the search', () => { - expect(appEventsMock.emit.mock.calls.length).toBe(1); - expect(appEventsMock.emit.mock.calls[0][0]).toBe(CoreEvents.hideDashSearch); - }); - }); - - describe('with a different url than current path', () => { - beforeEach(() => { - ctrl = new SearchResultsCtrl($location, $scope); - const item = { url: 'another/path' }; - ctrl.onItemClick(item); - }); - - it('should do nothing', () => { - expect(appEventsMock.emit.mock.calls.length).toBe(0); - }); - }); - - afterEach(() => { - appEventsMock.emit.mockClear(); - }); - }); -}); diff --git a/public/app/features/manage-dashboards/DashboardListCtrl.ts b/public/app/features/manage-dashboards/DashboardListCtrl.ts deleted file mode 100644 index 7e8142c8390..00000000000 --- a/public/app/features/manage-dashboards/DashboardListCtrl.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { NavModelSrv } from 'app/core/core'; - -export class DashboardListCtrl { - navModel: any; - - /** @ngInject */ - constructor(navModelSrv: NavModelSrv) { - this.navModel = navModelSrv.getNav('dashboards', 'manage-dashboards', 0); - } -} diff --git a/public/app/features/manage-dashboards/components/MoveToFolderModal/MoveToFolderCtrl.ts b/public/app/features/manage-dashboards/components/MoveToFolderModal/MoveToFolderCtrl.ts deleted file mode 100644 index 52d5b8b2949..00000000000 --- a/public/app/features/manage-dashboards/components/MoveToFolderModal/MoveToFolderCtrl.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { IScope } from 'angular'; -import { AppEvents } from '@grafana/data'; -import coreModule from 'app/core/core_module'; -import appEvents from 'app/core/app_events'; -import { backendSrv } from 'app/core/services/backend_srv'; -import { promiseToDigest } from 'app/core/utils/promiseToDigest'; - -export class MoveToFolderCtrl { - dashboards: any; - folder: any; - dismiss: any; - afterSave: any; - isValidFolderSelection = true; - - constructor(private $scope: IScope) {} - - onFolderChange = (folder: any) => { - this.folder = folder; - }; - - save = () => { - return promiseToDigest(this.$scope)( - backendSrv.moveDashboards(this.dashboards, this.folder).then((result: any) => { - 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(AppEvents.alertSuccess, [header, msg]); - } - - if (result.totalCount === result.alreadyInFolderCount) { - appEvents.emit(AppEvents.alertError, ['Error', `Dashboards already belongs to folder ${this.folder.title}`]); - } - - this.dismiss(); - return this.afterSave(); - }) - ); - }; - - onEnterFolderCreation = () => { - this.isValidFolderSelection = false; - }; - - onExitFolderCreation = () => { - this.isValidFolderSelection = true; - }; -} - -export function moveToFolderModal() { - return { - restrict: 'E', - templateUrl: 'public/app/features/manage-dashboards/components/MoveToFolderModal/template.html', - controller: MoveToFolderCtrl, - bindToController: true, - controllerAs: 'ctrl', - scope: { - dismiss: '&', - dashboards: '=', - afterSave: '&', - }, - }; -} - -coreModule.directive('moveToFolderModal', moveToFolderModal); diff --git a/public/app/features/manage-dashboards/components/MoveToFolderModal/index.ts b/public/app/features/manage-dashboards/components/MoveToFolderModal/index.ts deleted file mode 100644 index df0553aedb9..00000000000 --- a/public/app/features/manage-dashboards/components/MoveToFolderModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { MoveToFolderCtrl } from './MoveToFolderCtrl'; diff --git a/public/app/features/manage-dashboards/components/MoveToFolderModal/template.html b/public/app/features/manage-dashboards/components/MoveToFolderModal/template.html deleted file mode 100644 index 5776da9ccae..00000000000 --- a/public/app/features/manage-dashboards/components/MoveToFolderModal/template.html +++ /dev/null @@ -1,38 +0,0 @@ - diff --git a/public/app/features/manage-dashboards/index.ts b/public/app/features/manage-dashboards/index.ts index 9d7c2bbc811..79b9b7148e3 100644 --- a/public/app/features/manage-dashboards/index.ts +++ b/public/app/features/manage-dashboards/index.ts @@ -2,14 +2,11 @@ export { ValidationSrv } from './services/ValidationSrv'; // Components -export * from './components/MoveToFolderModal'; export * from './components/UploadDashboard'; // Controllers -import { DashboardListCtrl } from './DashboardListCtrl'; import { SnapshotListCtrl } from './SnapshotListCtrl'; import coreModule from 'app/core/core_module'; -coreModule.controller('DashboardListCtrl', DashboardListCtrl); coreModule.controller('SnapshotListCtrl', SnapshotListCtrl); diff --git a/public/app/features/search/components/MoveToFolderModal.tsx b/public/app/features/search/components/MoveToFolderModal.tsx index f894b562c20..64d6d353df3 100644 --- a/public/app/features/search/components/MoveToFolderModal.tsx +++ b/public/app/features/search/components/MoveToFolderModal.tsx @@ -23,29 +23,24 @@ export const MoveToFolderModal: FC = ({ results, onMoveItems, isOpen, onD const selectedDashboards = getCheckedDashboards(results); const moveTo = () => { - if (folder) { + if (folder && selectedDashboards.length) { const folderTitle = folder.title ?? 'General'; - backendSrv - .moveDashboards( - selectedDashboards.map(d => d.uid), - folder - ) - .then((result: any) => { - if (result.successCount > 0) { - const ending = result.successCount === 1 ? '' : 's'; - const header = `Dashboard${ending} Moved`; - const msg = `${result.successCount} dashboard${ending} moved to ${folderTitle}`; - appEvents.emit(AppEvents.alertSuccess, [header, msg]); - } + backendSrv.moveDashboards(selectedDashboards.map(d => d.uid) as string[], folder).then((result: any) => { + if (result.successCount > 0) { + const ending = result.successCount === 1 ? '' : 's'; + const header = `Dashboard${ending} Moved`; + const msg = `${result.successCount} dashboard${ending} moved to ${folderTitle}`; + appEvents.emit(AppEvents.alertSuccess, [header, msg]); + } - if (result.totalCount === result.alreadyInFolderCount) { - appEvents.emit(AppEvents.alertError, ['Error', `Dashboard already belongs to folder ${folderTitle}`]); - } + if (result.totalCount === result.alreadyInFolderCount) { + appEvents.emit(AppEvents.alertError, ['Error', `Dashboard already belongs to folder ${folderTitle}`]); + } - onMoveItems(selectedDashboards, folder); - onDismiss(); - }); + onMoveItems(selectedDashboards, folder); + onDismiss(); + }); } }; diff --git a/public/app/features/search/components/SearchResults.tsx b/public/app/features/search/components/SearchResults.tsx index b4829ed64c0..755f28fbcd3 100644 --- a/public/app/features/search/components/SearchResults.tsx +++ b/public/app/features/search/components/SearchResults.tsx @@ -1,4 +1,4 @@ -import React, { FC, MutableRefObject } from 'react'; +import React, { FC } from 'react'; import { css, cx } from 'emotion'; import { FixedSizeList } from 'react-window'; import AutoSizer from 'react-virtualized-auto-sizer'; @@ -18,7 +18,6 @@ export interface Props { onToggleSection: (section: DashboardSection) => void; results: DashboardSection[]; layout?: string; - wrapperRef?: MutableRefObject; } export const SearchResults: FC = ({ @@ -28,7 +27,6 @@ export const SearchResults: FC = ({ onToggleChecked, onToggleSection, results, - wrapperRef, layout, }) => { const theme = useTheme(); diff --git a/public/app/features/search/components/SectionHeader.tsx b/public/app/features/search/components/SectionHeader.tsx index c5013160fe8..f47d6c6d755 100644 --- a/public/app/features/search/components/SectionHeader.tsx +++ b/public/app/features/search/components/SectionHeader.tsx @@ -36,7 +36,7 @@ export const SectionHeader: FC = ({ [section] ); - return !section.hideHeader ? ( + return (
@@ -49,8 +49,6 @@ export const SectionHeader: FC = ({ )}
- ) : ( -
); }; diff --git a/public/app/features/search/hooks/useSearch.ts b/public/app/features/search/hooks/useSearch.ts index 3f78dbb396f..c4c7ee1e1ec 100644 --- a/public/app/features/search/hooks/useSearch.ts +++ b/public/app/features/search/hooks/useSearch.ts @@ -24,10 +24,6 @@ export const useSearch: UseSearch = (query, reducer, params) => { dispatch({ type: SEARCH_START }); const parsedQuery = getParsedQuery(query, queryParsing); searchSrv.search(parsedQuery).then(results => { - // Remove header for folder search - if (query.folderIds.length === 1 && results.length) { - results[0].hideHeader = true; - } dispatch({ type: FETCH_RESULTS, payload: results }); if (searchCallback) { diff --git a/public/app/features/search/index.ts b/public/app/features/search/index.ts index ec97ee4de48..3ccf45b7880 100644 --- a/public/app/features/search/index.ts +++ b/public/app/features/search/index.ts @@ -7,4 +7,3 @@ export { SearchResultsFilter } from './components/SearchResultsFilter'; export { ManageDashboards } from './components/ManageDashboards'; export { ConfirmDeleteModal } from './components/ConfirmDeleteModal'; export { MoveToFolderModal } from './components/MoveToFolderModal'; -export * from './types'; diff --git a/public/app/features/search/types.ts b/public/app/features/search/types.ts index f0fbfe6459f..45fc24b29e2 100644 --- a/public/app/features/search/types.ts +++ b/public/app/features/search/types.ts @@ -15,14 +15,14 @@ export interface DashboardSection { title: string; expanded?: boolean; url: string; - icon: string; - score: number; - hideHeader?: boolean; + icon?: string; + score?: number; checked?: boolean; items: DashboardSectionItem[]; toggle?: (section: DashboardSection) => Promise; selected?: boolean; type: DashboardSearchItemType; + slug?: string; } export interface DashboardSectionItem { @@ -37,11 +37,13 @@ export interface DashboardSectionItem { tags: string[]; title: string; type: DashboardSearchItemType; - uid: string; + uid?: string; uri: string; url: string; } +export interface DashboardSearchHit extends DashboardSectionItem, DashboardSection {} + export interface DashboardTag { term: string; count: number; diff --git a/public/app/types/index.ts b/public/app/types/index.ts index da4a563a1b9..ade379da7d9 100644 --- a/public/app/types/index.ts +++ b/public/app/types/index.ts @@ -10,7 +10,6 @@ export * from './datasources'; export * from './plugins'; export * from './organization'; export * from './appNotifications'; -export * from './search'; export * from './explore'; export * from './store'; export * from './ldap'; diff --git a/public/app/types/search.ts b/public/app/types/search.ts deleted file mode 100644 index 4dc8449a858..00000000000 --- a/public/app/types/search.ts +++ /dev/null @@ -1,20 +0,0 @@ -export enum DashboardSearchHitType { - DashHitDB = 'dash-db', - DashHitHome = 'dash-home', - DashHitFolder = 'dash-folder', -} -export interface DashboardSearchHit { - folderId?: number; - folderTitle?: string; - folderUid?: string; - folderUrl?: string; - id: number; - isStarred: boolean; - slug: string; - tags: string[]; - title: string; - type: DashboardSearchHitType; - uid: string; - uri: string; - url: string; -}