merge master
authorPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Tue, 9 Oct 2018 13:32:13 +0000 (15:32 +0200)
committerPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Tue, 9 Oct 2018 13:32:13 +0000 (15:32 +0200)
Feature #14313

Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>

1  2 
src/store/search-bar/search-bar-actions.ts
src/views-components/search-bar/search-bar-advanced-view.tsx
src/views-components/search-bar/search-bar-autocomplete-view.tsx
src/views-components/search-bar/search-bar-basic-view.tsx
src/views-components/search-bar/search-bar-view.tsx
src/views-components/search-bar/search-bar.tsx

index 3d6d3fdc7348d849cc2db158f2713103079e693b,2d171e02d59cb33d0c390a362d0298e123bd1f38..2b8ca83e694c62ff18cf457214f6852b363054c8
@@@ -3,27 -3,48 +3,58 @@@
  // SPDX-License-Identifier: AGPL-3.0
  
  import { unionize, ofType, UnionOf } from "~/common/unionize";
+ import { GroupContentsResource, GroupContentsResourcePrefix } from '~/services/groups-service/groups-service';
  import { Dispatch } from 'redux';
  import { RootState } from '~/store/store';
  import { ServiceRepository } from '~/services/services';
+ import { FilterBuilder } from "~/services/api/filter-builder";
+ import { ResourceKind } from '~/models/resource';
+ import { GroupClass } from '~/models/group';
  
  export const searchBarActions = unionize({
      SET_CURRENT_VIEW: ofType<string>(),
      OPEN_SEARCH_VIEW: ofType<{}>(),
-     CLOSE_SEARCH_VIEW: ofType<{}>()
+     CLOSE_SEARCH_VIEW: ofType<{}>(),
+     SET_SEARCH_RESULTS: ofType<GroupContentsResource[]>(),
+     SET_SEARCH_VALUE: ofType<string>()
  });
  
  export type SearchBarActions = UnionOf<typeof searchBarActions>;
  
  export const goToView = (currentView: string) => searchBarActions.SET_CURRENT_VIEW(currentView);
  
 -export const searchData = (searchValue: string) => 
 +export const saveRecentQuery = (query: string) =>
 +    (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
 +        services.searchQueriesService.saveRecentQuery(query);
 +    };
 +
 +export const loadRecentQueries = () =>
 +    (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
 +        const recentSearchQueries = services.searchQueriesService.getRecentQueries();
 +        return recentSearchQueries || [];
-     };
++    };
++
++export const searchData = (searchValue: string) =>
+     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+         dispatch(searchBarActions.SET_SEARCH_VALUE(searchValue));
+         dispatch(searchBarActions.SET_SEARCH_RESULTS([]));
+         if (searchValue) {
+             const filters = getFilters('name', searchValue);
+             const { items } = await services.groupsService.contents('', {
 -                filters, 
++                filters,
+                 limit: 5,
+                 recursive: true
+             });
+             dispatch(searchBarActions.SET_SEARCH_RESULTS(items));
+         }
+     };
 -
+ const getFilters = (filterName: string, searchValue: string): string => {
+     return new FilterBuilder()
+         .addIsA("uuid", [ResourceKind.PROJECT, ResourceKind.COLLECTION, ResourceKind.PROCESS])
+         .addILike(filterName, searchValue, GroupContentsResourcePrefix.COLLECTION)
+         .addILike(filterName, searchValue, GroupContentsResourcePrefix.PROCESS)
+         .addILike(filterName, searchValue, GroupContentsResourcePrefix.PROJECT)
+         .addEqual('groupClass', GroupClass.PROJECT, GroupContentsResourcePrefix.PROJECT)
+         .getFilters();
 -};
++};
index fe167ba965fa859beb1660394f654708699f9db9,356eb33fcbaf1898b737411198a9266bd7ebd330..dde23685f57b51f9b6802d661360507780c7f926
@@@ -5,17 -5,14 +5,17 @@@
  import * as React from 'react';
  import { Paper, StyleRulesCallback, withStyles, WithStyles, List, Button } from '@material-ui/core';
  import { SearchView } from '~/store/search-bar/search-bar-reducer';
- import { RenderRecentQueries } from '~/views-components/search-bar/search-bar-view';
+ import { RecentQueriesItem } from '~/views-components/search-bar/search-bar-view';
  
 -type CssRules = 'list';
 +type CssRules = 'list' | 'searchView';
  
  const styles: StyleRulesCallback<CssRules> = theme => {
      return {
          list: {
              padding: '0px'
 +        },
 +        searchView: {
 +            borderRadius: `0 0 ${theme.spacing.unit / 4}px ${theme.spacing.unit / 4}px`
          }
      };
  };
@@@ -26,9 -23,9 +26,9 @@@ interface SearchBarAdvancedViewProps 
  
  export const SearchBarAdvancedView = withStyles(styles)(
      ({ classes, setView }: SearchBarAdvancedViewProps & WithStyles<CssRules>) =>
 -        <Paper>
 +        <Paper className={classes.searchView}>
              <List component="nav" className={classes.list}>
-                 <RenderRecentQueries text='ADVANCED VIEW' />
+                 <RecentQueriesItem text='ADVANCED VIEW' />
              </List>
              <Button onClick={() => setView(SearchView.BASIC)}>Back</Button>
          </Paper>
index faeabb173aed2fa4d8c93891077bbb34c68cf4aa,c4b2457d45bc9db37b7f0cfdc7e9762e8ce9958c..affaf5310cc561e0e60d9b5497dd0d47bce60067
@@@ -3,30 -3,38 +3,42 @@@
  // SPDX-License-Identifier: AGPL-3.0
  
  import * as React from 'react';
 -import { Paper, StyleRulesCallback, withStyles, WithStyles, List, ListItem, ListItemText } from '@material-ui/core';
 -import { ArvadosTheme } from '~/common/custom-theme';
 +import { Paper, StyleRulesCallback, withStyles, WithStyles, List } from '@material-ui/core';
- import { RenderRecentQueries } from '~/views-components/search-bar/search-bar-view';
+ import { RecentQueriesItem } from '~/views-components/search-bar/search-bar-view';
+ import { GroupContentsResource } from '~/services/groups-service/groups-service';
+ import Highlighter from "react-highlight-words";
  
 -type CssRules = 'list';
 +type CssRules = 'list' | 'searchView';
  
 -const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
 -    list: {
 -        padding: 0
 -    }
 -});
 +const styles: StyleRulesCallback<CssRules> = theme => {
 +    return {
 +        list: {
-             padding: '0px'
++            padding: 0
 +        },
 +        searchView: {
 +            borderRadius: `0 0 ${theme.spacing.unit / 4}px ${theme.spacing.unit / 4}px`
 +        }
 +    };
 +};
  
- interface SearchBarAutocompleteViewProps {
+ export interface SearchBarAutocompleteViewDataProps {
+     searchResults?: GroupContentsResource[];
+     searchValue?: string;
  }
  
+ type SearchBarAutocompleteViewProps = SearchBarAutocompleteViewDataProps & WithStyles<CssRules>;
  export const SearchBarAutocompleteView = withStyles(styles)(
-     ({ classes }: SearchBarAutocompleteViewProps & WithStyles<CssRules>) =>
 -    ({ classes, searchResults, searchValue }: SearchBarAutocompleteViewProps ) =>
 -        <Paper>
 -            {searchResults &&  <List component="nav" className={classes.list}>
++    ({ classes, searchResults, searchValue }: SearchBarAutocompleteViewProps) =>
 +        <Paper className={classes.searchView}>
-             <List component="nav" className={classes.list}>
-                 <RenderRecentQueries text='AUTOCOMPLETE VIEW' />
-             </List>
++            {searchResults && <List component="nav" className={classes.list}>
+                 {searchResults.map((item: GroupContentsResource) => {
+                     return <RecentQueriesItem key={item.uuid} text={getFormattedText(item.name, searchValue)} />;
+                 })}
+             </List>}
          </Paper>
- );
+ );
+ const getFormattedText = (textToHighlight: string, searchString = '') => {
+     return <Highlighter searchWords={[searchString]} autoEscape={true} textToHighlight={textToHighlight} />;
+ };
index b8746316e0414d9b6d6fa8dc1e9de665053a9ea9,c2bca73e13a1df9faba331d0c2b2456c8a6aa3e2..7f90ecdee5ccb0f98a100ef23cdaf92324529a6d
@@@ -5,7 -5,7 +5,7 @@@
  import * as React from 'react';
  import { Paper, StyleRulesCallback, withStyles, WithStyles, List } from '@material-ui/core';
  import { SearchView } from '~/store/search-bar/search-bar-reducer';
- import { RenderRecentQueries, RenderSavedQueries } from '~/views-components/search-bar/search-bar-view';
+ import { RecentQueriesItem, RenderSavedQueries } from '~/views-components/search-bar/search-bar-view';
  
  type CssRules = 'advanced' | 'searchQueryList' | 'list' | 'searchView';
  
@@@ -28,19 -28,17 +28,19 @@@ const styles: StyleRulesCallback<CssRul
              padding: '0px'
          },
          searchView: {
 -            color: theme.palette.common.black
 +            color: theme.palette.common.black,
 +            borderRadius: `0 0 ${theme.spacing.unit / 4}px ${theme.spacing.unit / 4}px`
          }
      };
  };
  
  interface SearchBarBasicViewProps {
      setView: (currentView: string) => void;
 +    recentQueries: () => string[];
  }
  
  export const SearchBarBasicView = withStyles(styles)(
 -    ({ classes, setView }: SearchBarBasicViewProps & WithStyles<CssRules>) =>
 +    ({ classes, setView, recentQueries }: SearchBarBasicViewProps & WithStyles<CssRules>) =>
          <Paper className={classes.searchView}>
              <div className={classes.searchQueryList}>Saved search queries</div>
              <List component="nav" className={classes.list}>
@@@ -49,7 -47,8 +49,7 @@@
              </List>
              <div className={classes.searchQueryList}>Recent search queries</div>
              <List component="nav" className={classes.list}>
-                 {recentQueries().map((query, index) => <RenderRecentQueries key={query + index} text={query} />)}
 -                <RecentQueriesItem text="cos" />
 -                <RecentQueriesItem text="testtest" />
++                {recentQueries().map((query, index) => <RecentQueriesItem key={query + index} text={query} />)}
              </List>
              <div className={classes.advanced} onClick={() => setView(SearchView.ADVANCED)}>Advanced search</div>
          </Paper>
index b4d98f884cb3963c082d9ce97b6b49725c45a12f,f26cb7e6909139f613923e3858b97ff9c9c4fbff..b2575a8f5f3ec171c24f192ab235262aa9295334
@@@ -19,21 -19,17 +19,22 @@@ import { RemoveIcon } from '~/component
  import { SearchView } from '~/store/search-bar/search-bar-reducer';
  import { SearchBarBasicView } from '~/views-components/search-bar/search-bar-basic-view';
  import { SearchBarAdvancedView } from '~/views-components/search-bar/search-bar-advanced-view';
- import { SearchBarAutocompleteView } from '~/views-components/search-bar/search-bar-autocomplete-view';
+ import { SearchBarAutocompleteView, SearchBarAutocompleteViewDataProps } from '~/views-components/search-bar/search-bar-autocomplete-view';
+ import { ArvadosTheme } from '~/common/custom-theme';
  
 -type CssRules = 'container' | 'input' | 'searchBar';
 +type CssRules = 'container' | 'containerSearchViewOpened' | 'input' | 'searchBar';
  
- const styles: StyleRulesCallback<CssRules> = theme => {
+ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => {
      return {
          container: {
              position: 'relative',
              width: '100%',
 -            borderRadius: '0px'
 +            borderRadius: theme.spacing.unit / 4
 +        },
 +        containerSearchViewOpened: {
 +            position: 'relative',
 +            width: '100%',
 +            borderRadius: `${theme.spacing.unit / 4}px ${theme.spacing.unit / 4}px 0 0`
          },
          input: {
              border: 'none',
      };
  };
  
interface SearchBarDataProps {
-     value: string;
type SearchBarDataProps = {
+     searchValue: string;
      currentView: string;
      open: boolean;
- }
+ } & SearchBarAutocompleteViewDataProps;
  
  interface SearchBarActionProps {
      onSearch: (value: string) => any;
@@@ -57,8 -53,6 +58,8 @@@
      onSetView: (currentView: string) => void;
      openView: () => void;
      closeView: () => void;
 +    saveQuery: (query: string) => void;
 +    loadQueries: () => string[];
  }
  
  type SearchBarProps = SearchBarDataProps & SearchBarActionProps & WithStyles<CssRules>;
@@@ -68,10 -62,10 +69,10 @@@ interface SearchBarState 
  }
  
  interface RenderQueriesProps {
-     text: string;
+     text: string | JSX.Element;
  }
  
- export const RenderRecentQueries = (props: RenderQueriesProps) => {
+ export const RecentQueriesItem = (props: RenderQueriesProps) => {
      return <ListItem button>
          <ListItemText secondary={props.text} />
      </ListItem>;
@@@ -104,7 -98,7 +105,7 @@@ export const SearchBarView = withStyles
          render() {
              const { classes, currentView, openView, closeView, open } = this.props;
              return <ClickAwayListener onClickAway={() => closeView()}>
 -                <Paper className={classes.container} >
 +                <Paper className={open ? classes.containerSearchViewOpened : classes.container} >
                      <form onSubmit={this.handleSubmit} className={classes.searchBar}>
                          <Input
                              className={classes.input}
          }
  
          componentDidMount() {
-             this.setState({ value: this.props.value });
+             this.setState({ value: this.props.searchValue });
          }
  
          componentWillReceiveProps(nextProps: SearchBarProps) {
-             if (nextProps.value !== this.props.value) {
-                 this.setState({ value: nextProps.value });
+             if (nextProps.searchValue !== this.props.searchValue) {
+                 this.setState({ value: nextProps.searchValue });
              }
          }
  
          getView = (currentView: string) => {
              switch (currentView) {
                  case SearchView.BASIC:
 -                    return <SearchBarBasicView setView={this.props.onSetView} />;
 +                    return <SearchBarBasicView setView={this.props.onSetView} recentQueries={this.props.loadQueries} />;
                  case SearchView.ADVANCED:
                      return <SearchBarAdvancedView setView={this.props.onSetView} />;
                  case SearchView.AUTOCOMPLETE:
-                     return <SearchBarAutocompleteView />;
+                     return <SearchBarAutocompleteView 
+                                 searchResults={this.props.searchResults} 
+                                 searchValue={this.props.searchValue} />;
                  default:
 -                    return <SearchBarBasicView setView={this.props.onSetView} />;
 +                    return <SearchBarBasicView setView={this.props.onSetView} recentQueries={this.props.loadQueries} />;
              }
          }
  
          handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
              event.preventDefault();
              clearTimeout(this.timeout);
 +            this.props.saveQuery(this.state.value);
              this.props.onSearch(this.state.value);
 +            this.props.loadQueries();
          }
  
          handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
index e9ebc7c73503d6aad46ecd21e32d1aac442da65a,df8808c115a3fbe5ec2fe484289aadeeb41d465c..98440fda29517e7a62d2337fd04fe9b1572a2b74
@@@ -5,23 -5,23 +5,26 @@@
  import { connect } from 'react-redux';
  import { RootState } from '~/store/store';
  import { Dispatch } from 'redux';
- import { goToView, searchBarActions } from '~/store/search-bar/search-bar-actions';
+ import { goToView, searchData, searchBarActions } from '~/store/search-bar/search-bar-actions';
  import { SearchBarView } from '~/views-components/search-bar/search-bar-view';
 +import { saveRecentQuery, loadRecentQueries } from '~/store/search-bar/search-bar-actions';
  
  const mapStateToProps = ({ searchBar }: RootState) => {
      return {
+         searchValue: searchBar.searchValue,
          currentView: searchBar.currentView,
-         open: searchBar.open
+         open: searchBar.open,
+         searchResults: searchBar.searchResults
      };
  };
  
  const mapDispatchToProps = (dispatch: Dispatch) => ({
+     onSearch: (valueSearch: string) => dispatch<any>(searchData(valueSearch)),
      onSetView: (currentView: string) => dispatch(goToView(currentView)),
      openView: () => dispatch<any>(searchBarActions.OPEN_SEARCH_VIEW()),
 -    closeView: () => dispatch<any>(searchBarActions.CLOSE_SEARCH_VIEW())
 +    closeView: () => dispatch<any>(searchBarActions.CLOSE_SEARCH_VIEW()),
 +    saveQuery: (query: string) => dispatch<any>(saveRecentQuery(query)),
 +    loadQueries: () => dispatch<any>(loadRecentQueries())
  });
  
  export const SearchBar = connect(mapStateToProps, mapDispatchToProps)(SearchBarView);