mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Code] minor fixes to search page (#27798)
This commit is contained in:
parent
56ad6c61e9
commit
6a168a3a0c
9 changed files with 170 additions and 194 deletions
|
@ -4,10 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiTab } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiTab, EuiTabs } from '@elastic/eui';
|
||||
import querystring from 'querystring';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Url from 'url';
|
||||
|
||||
import { SearchScope } from '../../common/types';
|
||||
|
@ -19,33 +18,40 @@ interface Props {
|
|||
}
|
||||
|
||||
export class ScopeTab extends React.PureComponent<Props> {
|
||||
public onPageClicked = (page: number) => {
|
||||
const { query } = this.props;
|
||||
const queries = querystring.parse(history.location.search.replace('?', ''));
|
||||
history.push(
|
||||
Url.format({
|
||||
pathname: '/search',
|
||||
query: {
|
||||
...queries,
|
||||
q: query,
|
||||
p: page + 1,
|
||||
},
|
||||
})
|
||||
);
|
||||
public onTabClicked = (scope: SearchScope) => {
|
||||
return () => {
|
||||
const { query } = this.props;
|
||||
const queries = querystring.parse(history.location.search.replace('?', ''));
|
||||
history.push(
|
||||
Url.format({
|
||||
pathname: '/search',
|
||||
query: {
|
||||
...queries,
|
||||
q: query,
|
||||
scope,
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
public render() {
|
||||
const emptyFunction = () => null;
|
||||
return (
|
||||
<EuiFlexGroup>
|
||||
<EuiTab isSelected={this.props.scope !== SearchScope.repository} onClick={emptyFunction}>
|
||||
<Link to={`/search?q=${this.props.query}&scope=${SearchScope.symbol}`}>Code</Link>
|
||||
</EuiTab>
|
||||
<EuiTab isSelected={this.props.scope === SearchScope.repository} onClick={emptyFunction}>
|
||||
<Link to={`/search?q=${this.props.query}&scope=${SearchScope.repository}`}>
|
||||
<EuiTabs>
|
||||
<EuiTab
|
||||
isSelected={this.props.scope !== SearchScope.repository}
|
||||
onClick={this.onTabClicked(SearchScope.symbol)}
|
||||
>
|
||||
Code
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
isSelected={this.props.scope === SearchScope.repository}
|
||||
onClick={this.onTabClicked(SearchScope.repository)}
|
||||
>
|
||||
Repository
|
||||
</Link>
|
||||
</EuiTab>
|
||||
</EuiTab>
|
||||
</EuiTabs>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ import styled from 'styled-components';
|
|||
import Url from 'url';
|
||||
|
||||
import { DocumentSearchResult } from '../../../model';
|
||||
import { documentSearch } from '../../actions';
|
||||
import { SearchScope } from '../../common/types';
|
||||
import { RootState } from '../../reducers';
|
||||
import { history } from '../../utils/url';
|
||||
|
@ -39,16 +38,18 @@ const CodeResultContainer = styled.div`
|
|||
margin-top: 80px;
|
||||
`;
|
||||
|
||||
const RepositoryResultContainer = CodeResultContainer;
|
||||
|
||||
interface Props {
|
||||
query: string;
|
||||
scope: SearchScope;
|
||||
page?: number;
|
||||
languages?: Set<string>;
|
||||
repositories?: Set<string>;
|
||||
isLoading: boolean;
|
||||
error?: Error;
|
||||
searchResult?: DocumentSearchResult;
|
||||
documentSearch: (q: string, p: number) => void;
|
||||
repositorySearch: any;
|
||||
documentSearchResults?: DocumentSearchResult;
|
||||
repositorySearchResults?: any;
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -119,12 +120,28 @@ class SearchPage extends React.PureComponent<Props, State> {
|
|||
};
|
||||
|
||||
public render() {
|
||||
const { query, searchResult, languages, repositories, repositorySearch } = this.props;
|
||||
const {
|
||||
query,
|
||||
scope,
|
||||
documentSearchResults,
|
||||
languages,
|
||||
repositories,
|
||||
repositorySearchResults,
|
||||
} = this.props;
|
||||
|
||||
if (repositorySearch.scope === SearchScope.repository) {
|
||||
const { repositories: repos } = repositorySearch.repositories;
|
||||
if (
|
||||
scope === SearchScope.repository &&
|
||||
repositorySearchResults &&
|
||||
repositorySearchResults.total > 0
|
||||
) {
|
||||
const { repositories: repos } = repositorySearchResults;
|
||||
const resultComps =
|
||||
repos && repos.map((repo: any) => <RepoItem key={repo.uri} uri={repo.uri} />);
|
||||
repos &&
|
||||
repos.map((repo: any) => (
|
||||
<EuiFlexItem key={repo.uri}>
|
||||
<RepoItem uri={repo.uri} />
|
||||
</EuiFlexItem>
|
||||
));
|
||||
const mainComp = (
|
||||
<EuiFlexGroup style={{ padding: '0 1rem' }}>
|
||||
<EuiFlexItem grow={2}>
|
||||
|
@ -152,20 +169,25 @@ class SearchPage extends React.PureComponent<Props, State> {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<MainContentContainer grow={8}>{resultComps}</MainContentContainer>
|
||||
<MainContentContainer grow={8}>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem style={{ height: '32px' }}>
|
||||
<ScopeTab query={query} scope={scope} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
<RepositoryResultContainer>{resultComps}</RepositoryResultContainer>
|
||||
</MainContentContainer>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
return (
|
||||
<SearchContainer>
|
||||
<SearchBar query={query} />
|
||||
<ScopeTab query={query} scope={repositorySearch.scope} />
|
||||
{mainComp}
|
||||
</SearchContainer>
|
||||
);
|
||||
}
|
||||
|
||||
if (searchResult) {
|
||||
const { stats, results } = searchResult!;
|
||||
} else if (scope === SearchScope.default && documentSearchResults) {
|
||||
const { stats, results } = documentSearchResults!;
|
||||
const { total, from, to, page, totalPage, repoStats, languageStats } = stats!;
|
||||
|
||||
const statsComp = (
|
||||
|
@ -189,8 +211,8 @@ class SearchPage extends React.PureComponent<Props, State> {
|
|||
<MainContentContainer grow={8}>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>{statsComp}</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<ScopeTab query={query} scope={repositorySearch.scope} />
|
||||
<EuiFlexItem style={{ height: '32px' }}>
|
||||
<ScopeTab query={query} scope={scope} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
|
@ -220,16 +242,7 @@ class SearchPage extends React.PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
const mapStateToProps = (state: RootState) => ({
|
||||
...state.documentSearch,
|
||||
repositorySearch: state.repositorySearch,
|
||||
...state.search,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
documentSearch,
|
||||
};
|
||||
|
||||
export const Search = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
// @ts-ignore
|
||||
)(SearchPage);
|
||||
export const Search = connect(mapStateToProps)(SearchPage);
|
||||
|
|
|
@ -8,21 +8,19 @@ import { combineReducers } from 'redux';
|
|||
|
||||
import { blame, BlameState } from './blame';
|
||||
import { commit, CommitState } from './commit';
|
||||
import { documentSearch, DocumentSearchState } from './document_search';
|
||||
import { editor, EditorState } from './editor';
|
||||
import { file, FileState } from './file';
|
||||
import { languageServer, LanguageServerState } from './language_server';
|
||||
import { recentProjects, RecentProjectsState } from './recent_projects';
|
||||
import { repository, RepositoryState } from './repository';
|
||||
import { repositorySearch, RepositorySearchState } from './repository_search';
|
||||
import { route, RouteState } from './route';
|
||||
import { search, SearchState } from './search';
|
||||
import { status, StatusState } from './status';
|
||||
import { symbol, SymbolState } from './symbol';
|
||||
import { userConfig, UserConfigState } from './user';
|
||||
export interface RootState {
|
||||
repository: RepositoryState;
|
||||
documentSearch: DocumentSearchState;
|
||||
repositorySearch: RepositorySearchState;
|
||||
search: SearchState;
|
||||
file: FileState;
|
||||
symbol: SymbolState;
|
||||
editor: EditorState;
|
||||
|
@ -40,8 +38,7 @@ const reducers = {
|
|||
file,
|
||||
symbol,
|
||||
editor,
|
||||
documentSearch,
|
||||
repositorySearch,
|
||||
search,
|
||||
route,
|
||||
status,
|
||||
userConfig,
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import produce from 'immer';
|
||||
|
||||
import { Action, handleActions } from 'redux-actions';
|
||||
|
||||
import {
|
||||
changeSearchScope,
|
||||
repositorySearch as repositorySearchAction,
|
||||
repositorySearchFailed,
|
||||
RepositorySearchPayload,
|
||||
repositorySearchSuccess,
|
||||
} from '../actions/search';
|
||||
|
||||
export interface RepositorySearchState {
|
||||
scope: string;
|
||||
query: string;
|
||||
isLoading: boolean;
|
||||
repositories: any;
|
||||
error?: Error;
|
||||
}
|
||||
|
||||
const initialState: RepositorySearchState = {
|
||||
scope: '',
|
||||
query: '',
|
||||
isLoading: false,
|
||||
repositories: {},
|
||||
};
|
||||
|
||||
export const repositorySearch = handleActions(
|
||||
{
|
||||
[String(repositorySearchAction)]: (
|
||||
state: RepositorySearchState,
|
||||
action: Action<RepositorySearchPayload>
|
||||
) =>
|
||||
produce<RepositorySearchState>(state, draft => {
|
||||
if (action.payload) {
|
||||
draft.query = action.payload.query;
|
||||
draft.isLoading = true;
|
||||
}
|
||||
}),
|
||||
[String(repositorySearchSuccess)]: (state: RepositorySearchState, action: Action<any>) =>
|
||||
produce<RepositorySearchState>(state, draft => {
|
||||
draft.repositories = action.payload;
|
||||
draft.isLoading = false;
|
||||
}),
|
||||
[String(changeSearchScope)]: (state: RepositorySearchState, action: Action<any>) =>
|
||||
produce<RepositorySearchState>(state, draft => {
|
||||
draft.scope = action.payload;
|
||||
draft.isLoading = false;
|
||||
}),
|
||||
[String(repositorySearchFailed)]: (state: RepositorySearchState, action: Action<any>) => {
|
||||
if (action.payload) {
|
||||
return produce<RepositorySearchState>(state, draft => {
|
||||
draft.isLoading = false;
|
||||
draft.error = action.payload.error;
|
||||
});
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
},
|
||||
},
|
||||
initialState
|
||||
);
|
|
@ -10,34 +10,49 @@ import { Action, handleActions } from 'redux-actions';
|
|||
|
||||
import { DocumentSearchResult, RepositoryUri } from '../../model';
|
||||
import {
|
||||
changeSearchScope,
|
||||
documentSearch as documentSearchQuery,
|
||||
documentSearchFailed,
|
||||
DocumentSearchPayload,
|
||||
documentSearchSuccess,
|
||||
repositorySearch as repositorySearchAction,
|
||||
repositorySearchFailed,
|
||||
RepositorySearchPayload,
|
||||
repositorySearchSuccess,
|
||||
} from '../actions';
|
||||
import { SearchScope } from '../common/types';
|
||||
|
||||
export interface DocumentSearchState {
|
||||
export interface SearchState {
|
||||
scope: SearchScope;
|
||||
query: string;
|
||||
page?: number;
|
||||
languages?: Set<string>;
|
||||
repositories?: Set<RepositoryUri>;
|
||||
isLoading: boolean;
|
||||
error?: Error;
|
||||
searchResult?: DocumentSearchResult;
|
||||
documentSearchResults?: DocumentSearchResult;
|
||||
repositorySearchResults?: any;
|
||||
}
|
||||
|
||||
const initialState: DocumentSearchState = {
|
||||
query: 'queryBuilder',
|
||||
const initialState: SearchState = {
|
||||
query: '',
|
||||
isLoading: false,
|
||||
scope: SearchScope.default,
|
||||
};
|
||||
|
||||
export const documentSearch = handleActions<DocumentSearchState, any>(
|
||||
export const search = handleActions<SearchState, any>(
|
||||
{
|
||||
[String(documentSearchQuery)]: (
|
||||
state: DocumentSearchState,
|
||||
action: Action<DocumentSearchPayload>
|
||||
) =>
|
||||
produce<DocumentSearchState>(state, draft => {
|
||||
[String(changeSearchScope)]: (state: SearchState, action: Action<any>) =>
|
||||
produce<SearchState>(state, draft => {
|
||||
if (Object.values(SearchScope).includes(action.payload)) {
|
||||
draft.scope = action.payload;
|
||||
} else {
|
||||
draft.scope = SearchScope.default;
|
||||
}
|
||||
draft.isLoading = false;
|
||||
}),
|
||||
[String(documentSearchQuery)]: (state: SearchState, action: Action<DocumentSearchPayload>) =>
|
||||
produce<SearchState>(state, draft => {
|
||||
if (action.payload) {
|
||||
draft.query = action.payload.query;
|
||||
draft.page = parseInt(action.payload.page as string, 10);
|
||||
|
@ -57,11 +72,8 @@ export const documentSearch = handleActions<DocumentSearchState, any>(
|
|||
draft.error = undefined;
|
||||
}
|
||||
}),
|
||||
[String(documentSearchSuccess)]: (
|
||||
state: DocumentSearchState,
|
||||
action: Action<DocumentSearchResult>
|
||||
) =>
|
||||
produce<DocumentSearchState>(state, draft => {
|
||||
[String(documentSearchSuccess)]: (state: SearchState, action: Action<DocumentSearchResult>) =>
|
||||
produce<SearchState>(state, draft => {
|
||||
const {
|
||||
from,
|
||||
page,
|
||||
|
@ -88,8 +100,8 @@ export const documentSearch = handleActions<DocumentSearchState, any>(
|
|||
};
|
||||
});
|
||||
|
||||
draft.searchResult = {
|
||||
...draft.searchResult,
|
||||
draft.documentSearchResults = {
|
||||
...draft.documentSearchResults,
|
||||
query: state.query,
|
||||
total,
|
||||
took,
|
||||
|
@ -105,9 +117,9 @@ export const documentSearch = handleActions<DocumentSearchState, any>(
|
|||
results,
|
||||
};
|
||||
}),
|
||||
[String(documentSearchFailed)]: (state: DocumentSearchState, action: Action<Error>) => {
|
||||
[String(documentSearchFailed)]: (state: SearchState, action: Action<Error>) => {
|
||||
if (action.payload) {
|
||||
return produce<DocumentSearchState>(state, draft => {
|
||||
return produce<SearchState>(state, draft => {
|
||||
draft.isLoading = false;
|
||||
draft.error = action.payload!;
|
||||
});
|
||||
|
@ -115,6 +127,31 @@ export const documentSearch = handleActions<DocumentSearchState, any>(
|
|||
return state;
|
||||
}
|
||||
},
|
||||
[String(repositorySearchAction)]: (
|
||||
state: SearchState,
|
||||
action: Action<RepositorySearchPayload>
|
||||
) =>
|
||||
produce<SearchState>(state, draft => {
|
||||
if (action.payload) {
|
||||
draft.query = action.payload.query;
|
||||
draft.isLoading = true;
|
||||
}
|
||||
}),
|
||||
[String(repositorySearchSuccess)]: (state: SearchState, action: Action<any>) =>
|
||||
produce<SearchState>(state, draft => {
|
||||
draft.repositorySearchResults = action.payload;
|
||||
draft.isLoading = false;
|
||||
}),
|
||||
[String(repositorySearchFailed)]: (state: SearchState, action: Action<any>) => {
|
||||
if (action.payload) {
|
||||
return produce<SearchState>(state, draft => {
|
||||
draft.isLoading = false;
|
||||
draft.error = action.payload.error;
|
||||
});
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
},
|
||||
},
|
||||
initialState
|
||||
);
|
|
@ -8,7 +8,6 @@ import { fork } from 'redux-saga/effects';
|
|||
|
||||
import { watchBlame, watchLoadBlame } from './blame';
|
||||
import { watchLoadCommit } from './commit';
|
||||
import { watchDocumentSearch, watchSearchRouteChange } from './document_search';
|
||||
import {
|
||||
watchCloseReference,
|
||||
watchLoadRepo,
|
||||
|
@ -27,7 +26,7 @@ import {
|
|||
watchIndexRepo,
|
||||
watchInitRepoCmd,
|
||||
} from './repository';
|
||||
import { watchRepositorySearch } from './repository_search';
|
||||
import { watchDocumentSearch, watchRepositorySearch, watchSearchRouteChange } from './search';
|
||||
import { watchRepoCloneSuccess } from './status';
|
||||
import { watchLoadStructure } from './structure';
|
||||
import { watchLoadUserConfig } from './user';
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { call, put, takeLatest } from 'redux-saga/effects';
|
||||
import { kfetch } from 'ui/kfetch';
|
||||
|
||||
import { Action } from 'redux-actions';
|
||||
import {
|
||||
repositorySearch,
|
||||
repositorySearchFailed,
|
||||
RepositorySearchPayload,
|
||||
repositorySearchQueryChanged,
|
||||
repositorySearchSuccess,
|
||||
} from '../actions/search';
|
||||
|
||||
function requestRepositorySearch(q: string) {
|
||||
return kfetch({
|
||||
pathname: `../api/code/search/repo`,
|
||||
method: 'get',
|
||||
query: { q },
|
||||
});
|
||||
}
|
||||
|
||||
function* handleRepositorySearch(action: Action<RepositorySearchPayload>) {
|
||||
try {
|
||||
const data = yield call(requestRepositorySearch, action.payload!.query);
|
||||
yield put(repositorySearchSuccess(data));
|
||||
} catch (err) {
|
||||
yield put(repositorySearchFailed(err));
|
||||
}
|
||||
}
|
||||
|
||||
export function* watchRepositorySearch() {
|
||||
yield takeLatest(
|
||||
[String(repositorySearch), String(repositorySearchQueryChanged)],
|
||||
handleRepositorySearch
|
||||
);
|
||||
}
|
|
@ -16,6 +16,10 @@ import {
|
|||
documentSearchSuccess,
|
||||
Match,
|
||||
repositorySearch,
|
||||
repositorySearchFailed,
|
||||
RepositorySearchPayload,
|
||||
repositorySearchQueryChanged,
|
||||
repositorySearchSuccess,
|
||||
} from '../actions';
|
||||
import { SearchScope } from '../common/types';
|
||||
import { searchRoutePattern } from './patterns';
|
||||
|
@ -62,26 +66,52 @@ function* handleDocumentSearch(action: Action<DocumentSearchPayload>) {
|
|||
}
|
||||
}
|
||||
|
||||
function requestRepositorySearch(q: string) {
|
||||
return kfetch({
|
||||
pathname: `../api/code/search/repo`,
|
||||
method: 'get',
|
||||
query: { q },
|
||||
});
|
||||
}
|
||||
|
||||
export function* watchDocumentSearch() {
|
||||
yield takeLatest(String(documentSearch), handleDocumentSearch);
|
||||
}
|
||||
|
||||
function* handleRepositorySearch(action: Action<RepositorySearchPayload>) {
|
||||
try {
|
||||
const data = yield call(requestRepositorySearch, action.payload!.query);
|
||||
yield put(repositorySearchSuccess(data));
|
||||
} catch (err) {
|
||||
yield put(repositorySearchFailed(err));
|
||||
}
|
||||
}
|
||||
|
||||
export function* watchRepositorySearch() {
|
||||
yield takeLatest(
|
||||
[String(repositorySearch), String(repositorySearchQueryChanged)],
|
||||
handleRepositorySearch
|
||||
);
|
||||
}
|
||||
|
||||
function* handleSearchRouteChange(action: Action<Match>) {
|
||||
const { location } = action.payload!;
|
||||
const queryParams = queryString.parse(location.search);
|
||||
const rawSearchStr = location.search.length > 0 ? location.search.substring(1) : '';
|
||||
const queryParams = queryString.parse(rawSearchStr);
|
||||
const { q, p, langs, repos, scope } = queryParams;
|
||||
yield put(changeSearchScope(scope as string));
|
||||
if (scope === SearchScope.repository) {
|
||||
yield put(repositorySearch({ query: q as string }));
|
||||
} else {
|
||||
yield put(
|
||||
documentSearch({
|
||||
query: q as string,
|
||||
page: p as string,
|
||||
languages: langs as string,
|
||||
repositories: repos as string,
|
||||
})
|
||||
);
|
||||
}
|
||||
yield put(
|
||||
documentSearch({
|
||||
query: q as string,
|
||||
page: p as string,
|
||||
languages: langs as string,
|
||||
repositories: repos as string,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function* watchSearchRouteChange() {
|
|
@ -27,6 +27,8 @@ export const refUrlSelector = (state: RootState) => {
|
|||
|
||||
export const fileSelector = (state: RootState) => state.file.file;
|
||||
|
||||
export const searchScopeSelector = (state: RootState) => state.search.scope;
|
||||
|
||||
export const repoUriSelector = (state: RootState) => {
|
||||
const { resource, org, repo } = state.route.match.params;
|
||||
return `${resource}/${org}/${repo}`;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue