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:
Alex Khomenko
2020-04-08 18:14:03 +03:00
committed by GitHub
parent dbda5aece9
commit d04dce6a37
26 changed files with 1037 additions and 129 deletions

View 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';

View 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();
});
});

View 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;
}
};