mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Search/refactor dashboard search (#23274)
* Search: add search wrapper * Search: add DashboardSearch.tsx * Search: enable search * Search: update types * Search: useReducer for saving search results * Search: use default query * Search: add toggle custom action * Search: add onQueryChange * Search: debounce search * Search: pas dispatch as a prop * Search: add tag filter * Search: Fix types * Search: revert changes * Search: close overlay on esc * Search: enable tag filtering * Search: clear query * Search: add autofocus to search field * Search: Rename close to closeSearch * Search: Add no results message * Search: Add loading state * Search: Remove Select from Forms namespace * Remove Add selectedIndex * Remove Add getFlattenedSections * Remove Enable selecting items * Search: add hasId * Search: preselect first item * Search: Add utils tests * Search: Fix moving selection down * Search: Add findSelected * Search: Add type to section * Search: Handle Enter key press on item highlight * Search: Move reducer et al. to separate files * Search: Remove redundant render check * Search: Close overlay on Esc and ArrowLeft press * Search: Add close button * Search: Document utils * Search: use Icon for remove icon * Search: Add DashboardSearch.test.tsx * Search: Move test data to a separate file * Search: Finalise DashboardSearch.test.tsx * Add search reducer tests * Search: Add search results loading indicator * Search: Remove inline function * Search: Do not mutate item * Search: Tweak utils * Search: Do not clear query on tag clear * Search: Fix folder:current search * Search: Fix results scroll * Search: Update tests * Search: Close overlay on cog icon click * Add mobile styles for close button * Search: Use CustomScrollbar * Search: Memoize TagList.tsx * Search: Fix type errors * Search: More strictNullChecks fixes * Search: Consistent handler names * Search: Fix search items types in test * Search: Fix merge conflicts * Search: Fix strictNullChecks errors
This commit is contained in:
5
public/app/features/search/reducers/actionTypes.ts
Normal file
5
public/app/features/search/reducers/actionTypes.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export const FETCH_RESULTS = 'FETCH_RESULTS';
|
||||
export const TOGGLE_SECTION = 'TOGGLE_SECTION';
|
||||
export const FETCH_ITEMS = 'FETCH_ITEMS';
|
||||
export const MOVE_SELECTION_UP = 'MOVE_SELECTION_UP';
|
||||
export const MOVE_SELECTION_DOWN = 'MOVE_SELECTION_DOWN';
|
||||
100
public/app/features/search/reducers/dashboardSearch.test.ts
Normal file
100
public/app/features/search/reducers/dashboardSearch.test.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { FETCH_ITEMS, FETCH_RESULTS, TOGGLE_SECTION, MOVE_SELECTION_DOWN, MOVE_SELECTION_UP } from './actionTypes';
|
||||
import { searchReducer as reducer, initialState } from './dashboardSearch';
|
||||
import { searchResults, sections } from '../testData';
|
||||
|
||||
describe('Dashboard Search reducer', () => {
|
||||
it('should return the initial state', () => {
|
||||
expect(reducer(initialState, {} as any)).toEqual(initialState);
|
||||
});
|
||||
it('should set the results and mark first item as selected', () => {
|
||||
const newState = reducer(initialState, { type: FETCH_RESULTS, payload: searchResults });
|
||||
expect(newState).toEqual({ loading: false, selectedIndex: 0, results: searchResults });
|
||||
expect(newState.results[0].selected).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should toggle selected section', () => {
|
||||
const newState = reducer({ loading: false, results: sections }, { type: TOGGLE_SECTION, payload: sections[5] });
|
||||
expect(newState.results[5].expanded).toBeFalsy();
|
||||
const newState2 = reducer({ loading: false, results: sections }, { type: TOGGLE_SECTION, payload: sections[1] });
|
||||
expect(newState2.results[1].expanded).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should handle FETCH_ITEMS', () => {
|
||||
const items = [
|
||||
{
|
||||
id: 4072,
|
||||
uid: 'OzAIf_rWz',
|
||||
title: 'New dashboard Copy 3',
|
||||
type: 'dash-db',
|
||||
isStarred: false,
|
||||
},
|
||||
{
|
||||
id: 46,
|
||||
uid: '8DY63kQZk',
|
||||
title: 'Stocks',
|
||||
type: 'dash-db',
|
||||
isStarred: false,
|
||||
},
|
||||
];
|
||||
const newState = reducer(
|
||||
{ loading: false, results: sections },
|
||||
{
|
||||
type: FETCH_ITEMS,
|
||||
payload: {
|
||||
section: sections[2],
|
||||
items,
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(newState.results[2].items).toEqual(items);
|
||||
});
|
||||
|
||||
it('should handle MOVE_SELECTION_DOWN', () => {
|
||||
const newState = reducer(
|
||||
{ loading: false, selectedIndex: 0, results: sections },
|
||||
{
|
||||
type: MOVE_SELECTION_DOWN,
|
||||
}
|
||||
);
|
||||
|
||||
expect(newState.selectedIndex).toEqual(1);
|
||||
expect(newState.results[0].items[0].selected).toBeTruthy();
|
||||
|
||||
const newState2 = reducer(newState, {
|
||||
type: MOVE_SELECTION_DOWN,
|
||||
});
|
||||
|
||||
expect(newState2.selectedIndex).toEqual(2);
|
||||
expect(newState2.results[1].selected).toBeTruthy();
|
||||
|
||||
// Shouldn't go over the visible results length - 1 (9)
|
||||
const newState3 = reducer(
|
||||
{ loading: false, selectedIndex: 9, results: sections },
|
||||
{
|
||||
type: MOVE_SELECTION_DOWN,
|
||||
}
|
||||
);
|
||||
expect(newState3.selectedIndex).toEqual(9);
|
||||
});
|
||||
|
||||
it('should handle MOVE_SELECTION_UP', () => {
|
||||
// shouldn't move beyond 0
|
||||
const newState = reducer(
|
||||
{ loading: false, selectedIndex: 0, results: sections },
|
||||
{
|
||||
type: MOVE_SELECTION_UP,
|
||||
}
|
||||
);
|
||||
|
||||
expect(newState.selectedIndex).toEqual(0);
|
||||
|
||||
const newState2 = reducer(
|
||||
{ loading: false, selectedIndex: 3, results: sections },
|
||||
{
|
||||
type: MOVE_SELECTION_UP,
|
||||
}
|
||||
);
|
||||
expect(newState2.selectedIndex).toEqual(2);
|
||||
expect(newState2.results[1].selected).toBeTruthy();
|
||||
});
|
||||
});
|
||||
80
public/app/features/search/reducers/dashboardSearch.ts
Normal file
80
public/app/features/search/reducers/dashboardSearch.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { DashboardSection, SearchAction } from '../types';
|
||||
import { getFlattenedSections, getLookupField, markSelected } from '../utils';
|
||||
import { FETCH_ITEMS, FETCH_RESULTS, TOGGLE_SECTION, MOVE_SELECTION_DOWN, MOVE_SELECTION_UP } from './actionTypes';
|
||||
|
||||
interface State {
|
||||
results: DashboardSection[];
|
||||
loading: boolean;
|
||||
selectedIndex: number;
|
||||
}
|
||||
|
||||
export const initialState: State = {
|
||||
results: [],
|
||||
loading: true,
|
||||
selectedIndex: 0,
|
||||
};
|
||||
|
||||
export const searchReducer = (state: any, action: SearchAction) => {
|
||||
switch (action.type) {
|
||||
case FETCH_RESULTS: {
|
||||
const results = action.payload;
|
||||
// Highlight the first item ('Starred' folder)
|
||||
if (results.length) {
|
||||
results[0].selected = true;
|
||||
}
|
||||
return { ...state, results, loading: false };
|
||||
}
|
||||
case TOGGLE_SECTION: {
|
||||
const section = action.payload;
|
||||
const lookupField = getLookupField(section.title);
|
||||
return {
|
||||
...state,
|
||||
results: state.results.map((result: DashboardSection) => {
|
||||
if (section[lookupField] === result[lookupField]) {
|
||||
return { ...result, expanded: !result.expanded };
|
||||
}
|
||||
return result;
|
||||
}),
|
||||
};
|
||||
}
|
||||
case FETCH_ITEMS: {
|
||||
const { section, items } = action.payload;
|
||||
return {
|
||||
...state,
|
||||
results: state.results.map((result: DashboardSection) => {
|
||||
if (section.id === result.id) {
|
||||
return { ...result, items };
|
||||
}
|
||||
return result;
|
||||
}),
|
||||
};
|
||||
}
|
||||
case MOVE_SELECTION_DOWN: {
|
||||
const flatIds = getFlattenedSections(state.results);
|
||||
if (state.selectedIndex < flatIds.length - 1) {
|
||||
const newIndex = state.selectedIndex + 1;
|
||||
const selectedId = flatIds[newIndex];
|
||||
return {
|
||||
...state,
|
||||
selectedIndex: newIndex,
|
||||
results: markSelected(state.results, selectedId),
|
||||
};
|
||||
}
|
||||
return state;
|
||||
}
|
||||
case MOVE_SELECTION_UP:
|
||||
if (state.selectedIndex > 0) {
|
||||
const flatIds = getFlattenedSections(state.results);
|
||||
const newIndex = state.selectedIndex - 1;
|
||||
const selectedId = flatIds[newIndex];
|
||||
return {
|
||||
...state,
|
||||
selectedIndex: newIndex,
|
||||
results: markSelected(state.results, selectedId),
|
||||
};
|
||||
}
|
||||
return state;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user