diff --git a/.betterer.results b/.betterer.results index f0cf511d4c4..5ceb2e76e47 100644 --- a/.betterer.results +++ b/.betterer.results @@ -2347,9 +2347,6 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "1"], [0, 0, 0, "Unexpected any. Specify a different type.", "2"] ], - "public/app/core/components/Select/SortPicker.tsx:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] - ], "public/app/core/components/TagFilter/TagFilter.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -2478,17 +2475,6 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "2"], [0, 0, 0, "Unexpected any. Specify a different type.", "3"] ], - "public/app/core/services/search_srv.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Do not use any type assertions.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"], - [0, 0, 0, "Do not use any type assertions.", "4"], - [0, 0, 0, "Unexpected any. Specify a different type.", "5"], - [0, 0, 0, "Unexpected any. Specify a different type.", "6"], - [0, 0, 0, "Unexpected any. Specify a different type.", "7"], - [0, 0, 0, "Unexpected any. Specify a different type.", "8"] - ], "public/app/core/specs/backend_srv.test.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -2503,14 +2489,6 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "1"], [0, 0, 0, "Unexpected any. Specify a different type.", "2"] ], - "public/app/core/specs/search_srv.test.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"], - [0, 0, 0, "Unexpected any. Specify a different type.", "4"], - [0, 0, 0, "Unexpected any. Specify a different type.", "5"] - ], "public/app/core/specs/ticks.test.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], diff --git a/public/app/core/components/Select/SortPicker.tsx b/public/app/core/components/Select/SortPicker.tsx index d5f9f824750..ec612c583a9 100644 --- a/public/app/core/components/Select/SortPicker.tsx +++ b/public/app/core/components/Select/SortPicker.tsx @@ -4,8 +4,7 @@ import { useAsync } from 'react-use'; import { SelectableValue } from '@grafana/data'; import { Icon, Select } from '@grafana/ui'; import { DEFAULT_SORT } from 'app/features/search/constants'; - -import { SearchSrv } from '../../services/search_srv'; +import { getGrafanaSearcher } from 'app/features/search/service'; export interface Props { onChange: (sortValue: SelectableValue) => void; @@ -17,9 +16,7 @@ export interface Props { } const defaultSortOptionsGetter = (): Promise => { - return new SearchSrv().getSortOptions().then(({ sortOptions }) => { - return sortOptions.map((opt: any) => ({ label: opt.displayName, value: opt.name })); - }); + return getGrafanaSearcher().getSortOptions(); }; export function SortPicker({ onChange, value, placeholder, filter, getSortOptions, isClearable }: Props) { diff --git a/public/app/core/services/search_srv.ts b/public/app/core/services/search_srv.ts deleted file mode 100644 index d9608c498e0..00000000000 --- a/public/app/core/services/search_srv.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { clone, keys, sortBy, take, values } from 'lodash'; - -import { contextSrv } from 'app/core/services/context_srv'; -import impressionSrv from 'app/core/services/impression_srv'; -import store from 'app/core/store'; -import { SECTION_STORAGE_KEY } from 'app/features/search/constants'; -import { - DashboardSection, - DashboardSearchItemType, - DashboardSearchItem, - SearchLayout, -} from 'app/features/search/types'; -import { hasFilters } from 'app/features/search/utils'; - -import { backendSrv } from './backend_srv'; - -interface Sections { - [key: string]: Partial; -} - -/** @deprecated */ -export class SearchSrv { - private getRecentDashboards(sections: DashboardSection[] | any) { - return this.queryForRecentDashboards().then((result: any[]) => { - if (result.length > 0) { - sections['recent'] = { - title: 'Recent', - icon: 'clock-nine', - score: -1, - expanded: store.getBool(`${SECTION_STORAGE_KEY}.recent`, true), - items: result, - type: DashboardSearchItemType.DashFolder, - }; - } - }); - } - - private queryForRecentDashboards(): Promise { - return new Promise((resolve) => { - impressionSrv.getDashboardOpened().then((uids) => { - const dashUIDs: string[] = take(uids, 30); - if (dashUIDs.length === 0) { - return resolve([]); - } - - backendSrv.search({ dashboardUIDs: dashUIDs }).then((result) => { - return resolve( - dashUIDs - .map((orderId) => result.find((result) => result.uid === orderId)) - .filter((hit) => hit && !hit.isStarred) as DashboardSearchItem[] - ); - }); - }); - }); - } - - private getStarred(sections: DashboardSection): Promise { - if (!contextSrv.isSignedIn) { - return Promise.resolve(); - } - - return backendSrv.search({ starred: true, limit: 30 }).then((result) => { - if (result.length > 0) { - (sections as any)['starred'] = { - title: 'Starred', - icon: 'star', - score: -2, - expanded: store.getBool(`${SECTION_STORAGE_KEY}.starred`, true), - items: result, - type: DashboardSearchItemType.DashFolder, - }; - } - }); - } - - search(options: any) { - const sections: any = {}; - const promises = []; - const query = clone(options); - const filters = hasFilters(options) || query.folderIds?.length > 0; - - query.folderIds = query.folderIds || []; - - if (query.layout === SearchLayout.List) { - return backendSrv - .search({ ...query, type: DashboardSearchItemType.DashDB }) - .then((results) => (results.length ? [{ title: '', items: results }] : [])); - } - - if (!filters) { - query.folderIds = [0]; - } - - if (!options.skipRecent && !filters) { - promises.push(this.getRecentDashboards(sections)); - } - - if (!options.skipStarred && !filters) { - promises.push(this.getStarred(sections)); - } - - promises.push( - backendSrv.search(query).then((results) => { - return this.handleSearchResult(sections, results); - }) - ); - - return Promise.all(promises).then(() => { - return sortBy(values(sections), 'score'); - }); - } - - private handleSearchResult(sections: Sections, results: DashboardSearchItem[]): any { - if (results.length === 0) { - return sections; - } - - // create folder index - for (const hit of results) { - if (hit.type === 'dash-folder') { - sections[hit.uid!] = { - uid: hit.uid, - title: hit.title, - expanded: false, - items: [], - url: hit.url, - icon: 'folder', - score: keys(sections).length, - type: hit.type, - }; - } - } - - for (const hit of results) { - if (hit.type === 'dash-folder') { - continue; - } - - let section = sections[hit.folderUid || 0]; - if (!section) { - if (hit.folderUid) { - section = { - uid: hit.folderUid, - title: hit.folderTitle, - url: hit.folderUrl, - items: [], - icon: 'folder-open', - score: keys(sections).length, - type: DashboardSearchItemType.DashFolder, - }; - } else { - section = { - uid: '', - title: 'General', - items: [], - icon: 'folder-open', - score: keys(sections).length, - type: DashboardSearchItemType.DashFolder, - }; - } - // add section - sections[hit.folderUid || 0] = section; - } - - section.expanded = true; - section.items && section.items.push(hit); - } - } - - getDashboardTags() { - return backendSrv.get('/api/dashboards/tags'); - } - - getSortOptions() { - return backendSrv.get('/api/search/sorting'); - } -} diff --git a/public/app/core/specs/search_srv.test.ts b/public/app/core/specs/search_srv.test.ts deleted file mode 100644 index 631481bb3b5..00000000000 --- a/public/app/core/specs/search_srv.test.ts +++ /dev/null @@ -1,299 +0,0 @@ -import { contextSrv } from 'app/core/services/context_srv'; -import impressionSrv from 'app/core/services/impression_srv'; -import { SearchSrv } from 'app/core/services/search_srv'; -import { DashboardSearchItem } from 'app/features/search/types'; - -import { backendSrv } from '../services/backend_srv'; - -jest.mock('app/core/store', () => { - return { - getBool: jest.fn(), - set: jest.fn(), - getObject: jest.fn(), - }; -}); - -jest.mock('app/core/services/impression_srv', () => { - return { - getDashboardOpened: jest.fn, - }; -}); - -describe('SearchSrv', () => { - let searchSrv: SearchSrv; - const searchMock = jest.spyOn(backendSrv, 'search'); // will use the mock in __mocks__ - - beforeEach(() => { - searchSrv = new SearchSrv(); - - contextSrv.isSignedIn = true; - impressionSrv.getDashboardOpened = jest.fn().mockResolvedValue([]); - jest.clearAllMocks(); - }); - - describe('With recent dashboards', () => { - let results: any; - - beforeEach(() => { - searchMock.mockImplementation((options) => { - if (options.dashboardUIDs) { - return Promise.resolve([ - { uid: 'DSNdW0gVk', title: 'second but first' }, - { uid: 'srx16xR4z', title: 'first but second' }, - ] as DashboardSearchItem[]); - } - return Promise.resolve([]); - }); - - impressionSrv.getDashboardOpened = jest.fn().mockResolvedValue(['srx16xR4z', 'DSNdW0gVk']); - - return searchSrv.search({ query: '' }).then((res) => { - results = res; - }); - }); - - it('should include recent dashboards section', () => { - expect(results[0].title).toBe('Recent'); - }); - - it('should return order decided by impressions store not api', () => { - expect(results[0].items[0].title).toBe('first but second'); - expect(results[0].items[1].title).toBe('second but first'); - }); - - describe('and 3 recent dashboards removed in backend', () => { - let results: any; - - beforeEach(() => { - searchMock.mockImplementation((options) => { - if (options.dashboardUIDs) { - return Promise.resolve([ - { uid: 'DSNdW0gVk', title: 'two' }, - { uid: 'srx16xR4z', title: 'one' }, - ] as DashboardSearchItem[]); - } - return Promise.resolve([]); - }); - - impressionSrv.getDashboardOpened = jest - .fn() - .mockResolvedValue(['Xrx16x4z', 'CSxdW0gYA', 'srx16xR4z', 'DSNdW0gVk', 'xSxdW0gYA']); - - return searchSrv.search({ query: '' }).then((res) => { - results = res; - }); - }); - - it('should return 2 dashboards', () => { - expect(results[0].items.length).toBe(2); - expect(results[0].items[0].uid).toBe('srx16xR4z'); - expect(results[0].items[1].uid).toBe('DSNdW0gVk'); - }); - }); - }); - - describe('With starred dashboards', () => { - let results: any; - - beforeEach(() => { - searchMock.mockImplementation((options) => { - if (options.starred) { - return Promise.resolve([{ uid: '1', title: 'starred' }] as DashboardSearchItem[]); - } - return Promise.resolve([]); - }); - - return searchSrv.search({ query: '' }).then((res) => { - results = res; - }); - }); - - it('should include starred dashboards section', () => { - expect(results[0].title).toBe('Starred'); - expect(results[0].items.length).toBe(1); - }); - }); - - describe('With starred dashboards and recent', () => { - let results: any; - - beforeEach(() => { - searchMock.mockImplementation((options) => { - if (options.dashboardUIDs) { - return Promise.resolve([ - { uid: 'srx16xR4z', title: 'starred and recent', isStarred: true }, - { uid: 'DSNdW0gVk', title: 'recent' }, - ] as DashboardSearchItem[]); - } - return Promise.resolve([{ uid: 'srx16xR4z', title: 'starred and recent' }] as DashboardSearchItem[]); - }); - - impressionSrv.getDashboardOpened = jest.fn().mockResolvedValue(['srx16xR4z', 'DSNdW0gVk']); - return searchSrv.search({ query: '' }).then((res) => { - results = res; - }); - }); - - it('should not show starred in recent', () => { - expect(results[1].title).toBe('Recent'); - expect(results[1].items[0].title).toBe('recent'); - }); - - it('should show starred', () => { - expect(results[0].title).toBe('Starred'); - expect(results[0].items[0].title).toBe('starred and recent'); - }); - }); - - describe('with no query string and dashboards with folders returned', () => { - let results: any; - - beforeEach(() => { - searchMock.mockImplementation( - jest - .fn() - .mockResolvedValueOnce(Promise.resolve([])) - .mockResolvedValue( - Promise.resolve([ - { - title: 'folder1', - type: 'dash-folder', - uid: 'folder-1', - }, - { - title: 'dash with no folder', - type: 'dash-db', - uid: '2', - }, - { - title: 'dash in folder1 1', - type: 'dash-db', - uid: '3', - folderUid: 'folder-1', - }, - { - title: 'dash in folder1 2', - type: 'dash-db', - uid: '4', - folderUid: 'folder-1', - }, - ]) - ) - ); - - return searchSrv.search({ query: '' }).then((res) => { - results = res; - }); - }); - - it('should create sections for each folder and root', () => { - expect(results).toHaveLength(2); - }); - - it('should place folders first', () => { - expect(results[0].title).toBe('folder1'); - }); - }); - - describe('with query string and dashboards with folders returned', () => { - let results: any; - - beforeEach(() => { - searchMock.mockImplementation( - jest.fn().mockResolvedValue([ - { - folderUid: 'dash-with-no-folder-uid', - title: 'dash with no folder', - type: 'dash-db', - }, - { - title: 'dash in folder1 1', - type: 'dash-db', - folderUid: 'uid', - folderTitle: 'folder1', - folderUrl: '/dashboards/f/uid/folder1', - }, - ]) - ); - - return searchSrv.search({ query: 'search' }).then((res) => { - results = res; - }); - }); - - it('should not specify folder ids', () => { - expect(searchMock.mock.calls[0][0].folderIds).toHaveLength(0); - }); - - it('should group results by folder', () => { - expect(results).toHaveLength(2); - expect(results[0].uid).toEqual('dash-with-no-folder-uid'); - expect(results[1].uid).toEqual('uid'); - expect(results[1].title).toEqual('folder1'); - expect(results[1].url).toEqual('/dashboards/f/uid/folder1'); - }); - }); - - describe('with tags', () => { - beforeEach(() => { - searchMock.mockImplementation(jest.fn().mockResolvedValue(Promise.resolve([]))); - - return searchSrv.search({ tag: ['atag'] }).then(() => {}); - }); - - it('should send tags query to backend search', () => { - expect(searchMock.mock.calls[0][0].tag).toHaveLength(1); - }); - }); - - describe('with starred', () => { - beforeEach(() => { - searchMock.mockImplementation(jest.fn().mockResolvedValue(Promise.resolve([]))); - - return searchSrv.search({ starred: true }).then(() => {}); - }); - - it('should send starred query to backend search', () => { - expect(searchMock.mock.calls[0][0].starred).toEqual(true); - }); - }); - - describe('when skipping recent dashboards', () => { - let getRecentDashboardsCalled = false; - - beforeEach(() => { - searchMock.mockImplementation(jest.fn().mockResolvedValue(Promise.resolve([]))); - - searchSrv['getRecentDashboards'] = () => { - getRecentDashboardsCalled = true; - return Promise.resolve(); - }; - - return searchSrv.search({ skipRecent: true }).then(() => {}); - }); - - it('should not fetch recent dashboards', () => { - expect(getRecentDashboardsCalled).toBeFalsy(); - }); - }); - - describe('when skipping starred dashboards', () => { - let getStarredCalled = false; - - beforeEach(() => { - searchMock.mockImplementation(jest.fn().mockResolvedValue(Promise.resolve([]))); - impressionSrv.getDashboardOpened = jest.fn().mockResolvedValue([]); - - searchSrv['getStarred'] = () => { - getStarredCalled = true; - return Promise.resolve({}); - }; - - return searchSrv.search({ skipStarred: true }).then(() => {}); - }); - - it('should not fetch starred dashboards', () => { - expect(getStarredCalled).toBeFalsy(); - }); - }); -}); diff --git a/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx b/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx index 3b9df62c30c..399156c2bdc 100644 --- a/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx +++ b/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx @@ -4,7 +4,9 @@ import userEvent from '@testing-library/user-event'; import React from 'react'; import { PanelPluginMeta, PluginType } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { Panel } from '@grafana/schema'; +import { getGrafanaSearcher } from 'app/features/search/service'; import { backendSrv } from '../../../../core/services/backend_srv'; import * as panelUtils from '../../../panel/state/util'; @@ -63,9 +65,21 @@ async function getTestContext( module: '', sort: 1, }; - const getSpy = jest - .spyOn(backendSrv, 'get') - .mockResolvedValue({ sortOptions: [{ displaName: 'Desc', name: 'alpha-desc' }] }); + + config.featureToggles = { panelTitleSearch: false }; + const getSpy = jest.spyOn(backendSrv, 'get'); + + jest.spyOn(getGrafanaSearcher(), 'getSortOptions').mockResolvedValue([ + { + label: 'Alphabetically (A–Z)', + value: 'alpha-asc', + }, + { + label: 'Alphabetically (Z–A)', + value: 'alpha-desc', + }, + ]); + const getLibraryPanelsSpy = jest.spyOn(api, 'getLibraryPanels').mockResolvedValue(searchResult); const getAllPanelPluginMetaSpy = jest.spyOn(panelUtils, 'getAllPanelPluginMeta').mockReturnValue([graph, timeseries]); diff --git a/public/app/features/search/page/components/FolderView.tsx b/public/app/features/search/page/components/FolderView.tsx index 5a7c69d17e1..0cce5f0ac62 100644 --- a/public/app/features/search/page/components/FolderView.tsx +++ b/public/app/features/search/page/components/FolderView.tsx @@ -19,6 +19,7 @@ type Props = Pick { const folders: DashboardSection[] = []; + if (!hidePseudoFolders) { if (contextSrv.isSignedIn) { const stars = await getBackendSrv().get('api/user/stars'); @@ -44,6 +46,7 @@ export const FolderView = ({ folders.push({ title: 'Recent', icon: 'clock-nine', kind: 'query-recent', uid: '__recent', itemsUIDs }); } } + folders.push({ title: 'General', url: '/dashboards', kind: 'folder', uid: GENERAL_FOLDER_UID }); const searcher = getGrafanaSearcher();