[Code] Apply default search scope into fetching suggestions (#36494)

* [Code] adjust setup guide

* [Code] Apply default search scope into fetching suggestions

* [Code] Refactor the search bar in the source view page to use the same searchbar in other pages
This commit is contained in:
Mengwei Ding 2019-05-15 06:06:34 -07:00 committed by Fuyao Zhao
parent 5f56c30ffd
commit ef8d7ea905
14 changed files with 81 additions and 197 deletions

View file

@ -153,3 +153,9 @@ export enum SearchScope {
REPOSITORY = 'repository', // Only search repositories
FILE = 'file', // Only search files
}
export interface SearchOptions {
repoScope: Repository[];
defaultRepoScopeOn: boolean;
defaultRepoScope?: Repository;
}

View file

@ -5,7 +5,7 @@
*/
import { createAction } from 'redux-actions';
import { DocumentSearchResult, Repository, SearchScope } from '../../model';
import { DocumentSearchResult, Repository, SearchOptions, SearchScope } from '../../model';
export interface DocumentSearchPayload {
query: string;
@ -19,11 +19,6 @@ export interface RepositorySearchPayload {
query: string;
}
export interface SearchOptions {
repoScope: Repository[];
defaultRepoScopeOn: boolean;
}
// For document search page
export const documentSearch = createAction<DocumentSearchPayload>('DOCUMENT SEARCH');
export const documentSearchSuccess = createAction<DocumentSearchResult>('DOCUMENT SEARCH SUCCESS');
@ -45,7 +40,7 @@ export const repositoryTypeaheadSearchFailed = createAction<string>('REPOSITORY
export const saveSearchOptions = createAction<SearchOptions>('SAVE SEARCH OPTIONS');
export const turnOnDefaultRepoScope = createAction('TURN ON DEFAULT REPO SCOPE');
export const turnOnDefaultRepoScope = createAction<Repository>('TURN ON DEFAULT REPO SCOPE');
export const searchReposForScope = createAction<RepositorySearchPayload>('SEARCH REPOS FOR SCOPE');
export const searchReposForScopeSuccess = createAction<any>('SEARCH REPOS FOR SCOPE SUCCESS');

View file

@ -10,8 +10,8 @@ import { connect } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import url from 'url';
import { EuiTab, EuiTabs } from '@elastic/eui';
import { Repository, SearchScope } from '../../../model';
import { changeSearchScope, SearchOptions } from '../../actions';
import { Repository, SearchOptions, SearchScope } from '../../../model';
import { changeSearchScope } from '../../actions';
import { RootState } from '../../reducers';
import { SearchBar } from '../search_bar';
import { EmptyProject } from './empty_project';
@ -126,9 +126,10 @@ class AdminPage extends React.PureComponent<Props, State> {
<div className="codeContainer__rootInner">
<div className="codeContainer__adminWrapper">
<SearchBar
repoScope={this.props.searchOptions.repoScope.map(r => r.uri)}
searchOptions={this.props.searchOptions}
query={this.props.query}
onSearchScopeChanged={this.props.onSearchScopeChanged}
enableSubmitWhenOptionsChanged={false}
ref={element => (this.searchBar = element)}
/>
<div className="codeContainer__adminMain">

View file

@ -141,10 +141,11 @@ class SetupGuidePage extends React.PureComponent<{ setupOk?: boolean }, { hideTo
)}
{this.props.setupOk === true && (
<React.Fragment>
<EuiSpacer size="xs" />
<EuiSpacer size="s" />
<EuiButton iconType="sortLeft">
<Link to="/admin">Back To Project Dashboard</Link>
</EuiButton>
<EuiSpacer size="s" />
</React.Fragment>
)}
<EuiPanel>
@ -158,7 +159,7 @@ class SetupGuidePage extends React.PureComponent<{ setupOk?: boolean }, { hideTo
</div>
);
}
return <div className="condeContainer__setup">{setup}</div>;
return <div className="codeContainer__setup">{setup}</div>;
}
}

View file

@ -14,7 +14,7 @@ import { CommitDiff, FileDiff } from '../../../common/git_diff';
import { SearchScope } from '../../../model';
import { changeSearchScope } from '../../actions';
import { RootState } from '../../reducers';
import { SearchBar } from '../search_bar';
// import { SearchBar } from '../search_bar';
import { DiffEditor } from './diff_editor';
const COMMIT_ID_LENGTH = 16;
@ -191,11 +191,11 @@ export class DiffPage extends React.Component<Props> {
));
return (
<div className="diff">
<SearchBar
{/* <SearchBar
repoScope={this.props.repoScope}
query={this.props.query}
onSearchScopeChanged={this.props.onSearchScopeChanged}
/>
/> */}
{topBar}
<Container>
<EuiText>{commit.commit.message}</EuiText>

View file

@ -17,12 +17,12 @@ import { RepositoryUtils } from '../../../common/repository_utils';
import {
FileTree,
FileTreeItemType,
SearchOptions,
SearchScope,
WorkerReservedProgress,
Repository,
} from '../../../model';
import { CommitInfo, ReferenceInfo } from '../../../model/commit';
import { changeSearchScope, FetchFileResponse, SearchOptions } from '../../actions';
import { changeSearchScope, FetchFileResponse } from '../../actions';
import { MainRouteParams, PathTypes } from '../../common/types';
import { RepoState, RepoStatus, RootState } from '../../reducers';
import {
@ -53,8 +53,8 @@ interface Props extends RouteComponentProps<MainRouteParams> {
onSearchScopeChanged: (s: SearchScope) => void;
repoScope: string[];
searchOptions: SearchOptions;
currentRepository?: Repository;
fileTreeLoading: boolean;
query: string;
}
const LANG_MD = 'markdown';
@ -224,12 +224,12 @@ class CodeContent extends React.PureComponent<Props> {
return (
<div className="codeContainer__main">
<TopBar
defaultSearchScope={this.props.currentRepository}
routeParams={this.props.match.params}
onSearchScopeChanged={this.props.onSearchScopeChanged}
buttons={this.renderButtons()}
searchOptions={this.props.searchOptions}
branches={this.props.branches}
query={this.props.query}
/>
{this.renderContent()}
</div>
@ -395,7 +395,7 @@ const mapStateToProps = (state: RootState) => ({
loadingCommits: state.file.loadingCommits,
repoStatus: statusSelector(state, repoUriSelector(state)),
searchOptions: state.search.searchOptions,
currentRepository: state.repository.currentRepository,
query: state.search.query,
});
const mapDispatchToProps = {

View file

@ -1,128 +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 { ParsedUrlQuery } from 'querystring';
import React from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import url from 'url';
import { unique } from 'lodash';
import { SearchScope, Repository } from '../../../model';
import { MainRouteParams, SearchScopeText } from '../../common/types';
import {
AutocompleteSuggestion,
FileSuggestionsProvider,
QueryBar,
RepositorySuggestionsProvider,
SymbolSuggestionsProvider,
} from '../query_bar';
import { Shortcut } from '../shortcuts';
import { SearchOptions } from '../../actions';
interface Props extends RouteComponentProps<MainRouteParams> {
onSearchScopeChanged: (s: SearchScope) => void;
searchOptions: SearchOptions;
defaultSearchScope?: Repository;
}
// TODO(mengwei): refactor this with the SearchBar in ../search_bar/
export class CodeSearchBar extends React.Component<Props> {
public state = {
searchScope: SearchScope.DEFAULT,
};
public queryBar: any | null = null;
public suggestionProviders = [
new SymbolSuggestionsProvider(),
new FileSuggestionsProvider(),
new RepositorySuggestionsProvider(),
];
public onSubmit = (queryString: string) => {
const { history } = this.props;
if (queryString.trim().length === 0) {
return;
}
const query: ParsedUrlQuery = {
q: queryString,
};
if (this.props.searchOptions.repoScope) {
// search from a repo page may have a default scope of this repo
if (this.props.searchOptions.defaultRepoScopeOn && this.props.defaultSearchScope) {
query.repoScope = unique([
...this.props.searchOptions.repoScope.map(r => r.uri),
this.props.defaultSearchScope.uri,
]).join(',');
} else {
query.repoScope = this.props.searchOptions.repoScope.map(r => r.uri).join(',');
}
}
if (this.state.searchScope === SearchScope.REPOSITORY) {
query.scope = SearchScope.REPOSITORY;
}
history.push(url.format({ pathname: '/search', query }));
};
public onSelect = (item: AutocompleteSuggestion) => {
this.props.history.push(item.selectUrl);
};
public render() {
return (
<div className="codeContainer__searchBar">
<Shortcut
keyCode="p"
help={SearchScopeText[SearchScope.REPOSITORY]}
onPress={() => {
this.props.onSearchScopeChanged(SearchScope.REPOSITORY);
if (this.queryBar) {
this.queryBar.focusInput();
}
}}
/>
<Shortcut
keyCode="y"
help={SearchScopeText[SearchScope.SYMBOL]}
onPress={() => {
this.props.onSearchScopeChanged(SearchScope.SYMBOL);
if (this.queryBar) {
this.queryBar.focusInput();
}
}}
/>
<Shortcut
keyCode="s"
help={SearchScopeText[SearchScope.DEFAULT]}
onPress={() => {
this.props.onSearchScopeChanged(SearchScope.DEFAULT);
if (this.queryBar) {
this.queryBar.focusInput();
}
}}
/>
<QueryBar
query=""
onSubmit={this.onSubmit}
onSelect={this.onSelect}
appName="code"
disableAutoFocus={true}
suggestionProviders={this.suggestionProviders}
enableSubmitWhenOptionsChanged={false}
onSearchScopeChanged={this.props.onSearchScopeChanged}
ref={instance => {
if (instance) {
// @ts-ignore
this.queryBar = instance.getWrappedInstance();
}
}}
/>
</div>
);
}
}
export const SearchBar = withRouter(CodeSearchBar);

View file

@ -6,14 +6,13 @@
import { EuiFlexGroup, EuiFlexItem, EuiSelect } from '@elastic/eui';
import React, { ChangeEvent } from 'react';
import { SearchScope, Repository } from '../../../model';
import { SearchOptions, SearchScope } from '../../../model';
import { ReferenceInfo } from '../../../model/commit';
import { MainRouteParams } from '../../common/types';
import { encodeRevisionString } from '../../utils/url';
import { history } from '../../utils/url';
import { Breadcrumb } from './breadcrumb';
import { SearchBar } from './search_bar';
import { SearchOptions } from '../../actions';
import { SearchBar } from '../search_bar';
interface Props {
routeParams: MainRouteParams;
@ -21,7 +20,7 @@ interface Props {
buttons: React.ReactNode;
searchOptions: SearchOptions;
branches: ReferenceInfo[];
defaultSearchScope?: Repository;
query: string;
}
export class TopBar extends React.Component<Props, { value: string }> {
@ -44,8 +43,9 @@ export class TopBar extends React.Component<Props, { value: string }> {
return (
<div className="code-top-bar__container">
<SearchBar
defaultSearchScope={this.props.defaultSearchScope}
query={this.props.query}
onSearchScopeChanged={this.props.onSearchScopeChanged}
enableSubmitWhenOptionsChanged={false}
searchOptions={this.props.searchOptions}
/>
<EuiFlexGroup gutterSize="none" justifyContent="spaceBetween">

View file

@ -22,8 +22,7 @@ import {
import { EuiIcon } from '@elastic/eui';
import { unique } from 'lodash';
import React, { Component } from 'react';
import { Repository } from '../../../../model';
import { SearchOptions as ISearchOptions } from '../../../actions';
import { SearchOptions as ISearchOptions, Repository } from '../../../../model';
interface State {
isFlyoutOpen: boolean;

View file

@ -9,15 +9,11 @@ import React, { Component } from 'react';
import { EuiFieldText, EuiFlexGroup, EuiFlexItem, EuiOutsideClickDetector } from '@elastic/eui';
import { connect } from 'react-redux';
import {
saveSearchOptions,
SearchOptions as ISearchOptions,
searchReposForScope,
} from '../../../actions';
import { saveSearchOptions, searchReposForScope } from '../../../actions';
import { matchPairs } from '../lib/match_pairs';
import { SuggestionsComponent } from './typeahead/suggestions_component';
import { SearchScope, Repository } from '../../../../model';
import { SearchOptions as ISearchOptions, SearchScope, Repository } from '../../../../model';
import { SearchScopePlaceholderText } from '../../../common/types';
import { RootState } from '../../../reducers';
import {
@ -194,11 +190,15 @@ export class CodeQueryBar extends Component<Props, State> {
const res = await Promise.all(
this.props.suggestionProviders.map((provider: SuggestionsProvider) => {
return provider.getSuggestions(
query,
this.props.searchScope,
this.props.searchOptions.repoScope.map(repo => repo.uri)
);
// Merge the default repository scope if necessary.
const repoScopes = this.props.searchOptions.repoScope.map(repo => repo.uri);
if (
this.props.searchOptions.defaultRepoScopeOn &&
this.props.searchOptions.defaultRepoScope
) {
repoScopes.push(this.props.searchOptions.defaultRepoScope.uri);
}
return provider.getSuggestions(query, this.props.searchScope, repoScopes);
})
);

View file

@ -8,7 +8,7 @@ import querystring from 'querystring';
import React from 'react';
import url from 'url';
import { SearchScope } from '../../../model';
import { SearchOptions, SearchScope } from '../../../model';
import { SearchScopeText } from '../../common/types';
import { history } from '../../utils/url';
import { ShortcutsProvider, Shortcut } from '../shortcuts';
@ -24,13 +24,20 @@ import {
interface Props {
query: string;
onSearchScopeChanged: (s: SearchScope) => void;
repoScope: string[];
searchOptions: SearchOptions;
enableSubmitWhenOptionsChanged: boolean;
}
export class SearchBar extends React.PureComponent<Props> {
public queryBar: any = null;
public onSearchChanged = (query: string) => {
// Merge the default repository scope if necessary.
const repoScopes = this.props.searchOptions.repoScope.map(repo => repo.uri);
if (this.props.searchOptions.defaultRepoScopeOn && this.props.searchOptions.defaultRepoScope) {
repoScopes.push(this.props.searchOptions.defaultRepoScope.uri);
}
// Update the url and push to history as well.
const queries = querystring.parse(history.location.search.replace('?', ''));
history.push(
@ -39,7 +46,7 @@ export class SearchBar extends React.PureComponent<Props> {
query: {
...queries,
q: query,
repoScope: this.props.repoScope,
repoScope: repoScopes,
},
})
);
@ -51,21 +58,21 @@ export class SearchBar extends React.PureComponent<Props> {
}
}
public onSubmit = (q: string) => {
this.onSearchChanged(q);
};
public onSelect = (item: AutocompleteSuggestion) => {
history.push(item.selectUrl);
};
public suggestionProviders = [
new SymbolSuggestionsProvider(),
new FileSuggestionsProvider(),
new RepositorySuggestionsProvider(),
];
public render() {
const onSubmit = (q: string) => {
this.onSearchChanged(q);
};
const onSelect = (item: AutocompleteSuggestion) => {
history.push(item.selectUrl);
};
const suggestionProviders = [
new SymbolSuggestionsProvider(),
new FileSuggestionsProvider(),
new RepositorySuggestionsProvider(),
];
return (
<div className="codeSearchbar__container">
<ShortcutsProvider />
@ -101,12 +108,12 @@ export class SearchBar extends React.PureComponent<Props> {
/>
<QueryBar
query={this.props.query}
onSubmit={onSubmit}
onSelect={onSelect}
onSubmit={this.onSubmit}
onSelect={this.onSelect}
appName="code"
suggestionProviders={suggestionProviders}
suggestionProviders={this.suggestionProviders}
onSearchScopeChanged={this.props.onSearchScopeChanged}
enableSubmitWhenOptionsChanged={true}
enableSubmitWhenOptionsChanged={this.props.enableSubmitWhenOptionsChanged}
ref={instance => {
if (instance) {
// @ts-ignore

View file

@ -11,8 +11,8 @@ import { connect } from 'react-redux';
import chrome from 'ui/chrome';
import url from 'url';
import { DocumentSearchResult, SearchScope } from '../../../model';
import { changeSearchScope, SearchOptions } from '../../actions';
import { DocumentSearchResult, SearchOptions, SearchScope } from '../../../model';
import { changeSearchScope } from '../../actions';
import { RootState } from '../../reducers';
import { history } from '../../utils/url';
import { ProjectItem } from '../admin_page/project_item';
@ -209,9 +209,10 @@ class SearchPage extends React.PureComponent<Props, State> {
/>
<div className="codeContainer__search--main">
<SearchBar
repoScope={this.props.searchOptions.repoScope.map(r => r.uri)}
searchOptions={this.props.searchOptions}
query={this.props.query}
onSearchScopeChanged={this.props.onSearchScopeChanged}
enableSubmitWhenOptionsChanged={true}
ref={(element: any) => (this.searchBar = element)}
/>
{mainComp}

View file

@ -8,7 +8,7 @@ import produce from 'immer';
import { Action, handleActions } from 'redux-actions';
import { DocumentSearchResult, RepositoryUri, SearchScope } from '../../model';
import { DocumentSearchResult, RepositoryUri, SearchOptions, SearchScope } from '../../model';
import {
changeSearchScope,
documentSearch as documentSearchQuery,
@ -20,7 +20,6 @@ import {
RepositorySearchPayload,
repositorySearchSuccess,
saveSearchOptions,
SearchOptions,
searchReposForScope,
searchReposForScopeSuccess,
turnOnDefaultRepoScope,
@ -177,6 +176,7 @@ export const search = handleActions<SearchState, any>(
}),
[String(turnOnDefaultRepoScope)]: (state: SearchState, action: Action<any>) =>
produce<SearchState>(state, draft => {
draft.searchOptions.defaultRepoScope = action.payload;
draft.searchOptions.defaultRepoScopeOn = true;
}),
},

View file

@ -136,6 +136,12 @@ function* loadRepoSaga(action: any) {
try {
const repo = yield call(fetchRepo, action.payload);
yield put(loadRepoSuccess(repo));
// turn on defaultRepoScope if there's no repo scope specified when enter a source view page
const repoScope = yield select(repoScopeSelector);
if (repoScope.length === 0) {
yield put(turnOnDefaultRepoScope(repo));
}
} catch (e) {
yield put(loadRepoFailed(e));
}
@ -148,11 +154,7 @@ export function* watchLoadRepo() {
function* handleMainRouteChange(action: Action<Match>) {
// in source view page, we need repos as default repo scope options when no query input
yield put(fetchRepos());
// turn on defaultRepoScope if there's no repo scope specified when enter a source view page
const repoScope = yield select(repoScopeSelector);
if (repoScope.length === 0) {
yield put(turnOnDefaultRepoScope());
}
const { location } = action.payload!;
const search = location.search.startsWith('?') ? location.search.substring(1) : location.search;
const queryParams = queryString.parse(search);