Skip to content

Commit

Permalink
OCLOMRS-98: Add Functionality To The Search (#71)
Browse files Browse the repository at this point in the history
  • Loading branch information
EleisonC authored and dkayiwa committed Jul 17, 2018
1 parent b3cbdcc commit 6c69f41
Show file tree
Hide file tree
Showing 17 changed files with 428 additions and 58 deletions.
2 changes: 2 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import CreateConcept from './components/dictionaryConcepts/containers/CreateConc
import NotFound from './components/NotFound';
import DictionaryOverview from './components/dashboard/components/dictionary/DictionaryContainer';
import OwnerDictionary from './components/dashboard/container/OwnerDictionary';
import GeneralSearchContainer from './components/GeneralSearch/GeneralSearchContainer';

const App = () => (
<Provider store={store}>
Expand All @@ -33,6 +34,7 @@ const App = () => (
<Route exact path="/concepts/:type/:typeName/:collectionName/:dictionaryName" component={Authenticate(DictionaryConcepts)} />
<Route exact path="/dictionaryOverview/:ownerType/:owner/:type/:name" component={Authenticate(DictionaryOverview)} />
<Route exact path="/concepts/:type/:typeName/:collectionName/:dictionaryName/new/:conceptType?" component={Authenticate(CreateConcept)} />
<Route exact path="/search/:query?" component={Authenticate(GeneralSearchContainer)} />
<Route component={NotFound} />
</Switch>
</div>
Expand Down
53 changes: 53 additions & 0 deletions src/components/GeneralSearch/GeneralSearchContainer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React, { Component } from 'react';
import propTypes from 'prop-types';
import { connect } from 'react-redux';
import '../dashboard/styles/index.css';
import ListSearchResults from './ListSearchResults';
import generalSearch from '../../redux/actions/GeneralSearchActions/generalSearchActionCreators';

export class GeneralSearchContainer extends Component {
static propTypes = {
match: propTypes.shape({
params: propTypes.shape({
typeName: propTypes.string,
}),
}).isRequired,
dictionaries: propTypes.arrayOf(propTypes.shape({
dictionaryName: propTypes.string,
})).isRequired,
generalSearch: propTypes.func.isRequired,
loading: propTypes.bool.isRequired,
}
componentDidMount() {
const {
match: {
params: {
query,
},
},
} = this.props;
this.props.generalSearch(query);
}
render() {
const { dictionaries, loading } = this.props;
return (
<div className="dashboard-wrapper">
<div className="dashboard-head">
<div className="row justify-content-center">
<div className="offset-sm-1 col-10">
<ListSearchResults
dictionaries={dictionaries}
fetching={loading}
/>
</div>
</div>
</div>
</div>
);
}
}
export const mapStateToProps = state => ({
dictionaries: state.generalSearch.dictionaries,
loading: state.generalSearch.loading,
});
export default connect(mapStateToProps, { generalSearch })(GeneralSearchContainer);
42 changes: 42 additions & 0 deletions src/components/GeneralSearch/ListSearchResults.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import PropTypes from 'prop-types';
import Card from '../dashboard/components/dictionary/DictionaryCard';
import Loader from '../Loader';

const ListDictionaries = ({
dictionaries, fetching,
}) => {
if (fetching) {
return (
<div className="text-center mt-3">
<Loader />
</div>
);
}
if (dictionaries.length >= 1) {
return (
<div>
<div className="row justify-content-center">
{dictionaries.map(dictionary =>
(<Card
dictionary={dictionary}
key={dictionary.uuid}
/>))}
</div>
</div>
);
}
return (
<div className="text-center mt-3" id="Noresults">
<h5>No Results Found <span aria-label="sad-emoji" role="img"> 😞 </span> </h5>
</div>
);
};

ListDictionaries.propTypes = {
fetching: PropTypes.bool.isRequired,
dictionaries: PropTypes.arrayOf(PropTypes.shape({
dictionaryName: PropTypes.string,
})).isRequired,
};
export default ListDictionaries;
25 changes: 25 additions & 0 deletions src/components/GeneralSearch/NavbarGeneralSearch.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import PropTypes from 'prop-types';

const GeneralSearch = ({ onSearch, onSubmit, searchValue }) => (
<form className="form-inline" id="generalSearchForm" onSubmit={onSubmit}>
<input
className="form-control mr-sm-2"
name="searchInput"
id="generalSearch"
type="search"
value={searchValue}
onChange={onSearch}
placeholder="Search"
aria-label="Search"
required
/>
</form>
);
GeneralSearch.propTypes = {
onSearch: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
searchValue: PropTypes.string.isRequired,
};

export default GeneralSearch;
17 changes: 0 additions & 17 deletions src/components/NavBar/GeneralSearch.jsx

This file was deleted.

44 changes: 41 additions & 3 deletions src/components/Navbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,39 @@ import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { logoutAction } from '../redux/actions/auth/authActions';
import GeneralSearch from './NavBar/GeneralSearch';
import GeneralSearch from './GeneralSearch/NavbarGeneralSearch';
import { searchDictionaries, fetchDictionaries } from '../redux/actions/dictionaries/dictionaryActionCreators';
import generalSearch from '../redux/actions/GeneralSearchActions/generalSearchActionCreators';

export class Navbar extends Component {
constructor(props) {
super(props);
this.state = {
searchInput: '',
};
}

onSubmit = (event) => {
event.preventDefault();
const { searchInput } = this.state;
this.props.generalSearch(searchInput);
this.props.history.push(`/search/${searchInput}`);
}

onSearch = (event) => {
const { value, name } = event.target;
this.setState({
[name]: value,
});
}
logoutUser = (event) => {
event.preventDefault();
this.props.logoutAction();
this.props.history.push('/');
notify.show('You Loggedout successfully', 'success', 3000);
};
render() {
const { searchInput } = this.state;
return (
<div>
<Notification options={{ zIndex: 10000, top: '200px' }} />
Expand All @@ -23,7 +46,13 @@ export class Navbar extends Component {
OCL for OpenMRS
</a>
</strong>
<GeneralSearch />
{this.props.loggedIn && (
<GeneralSearch
onSearch={this.onSearch}
onSubmit={this.onSubmit}
searchValue={searchInput}
/>
)}
<div className="collapse navbar-collapse " id="navbarNav">
{this.props.loggedIn && (
<ul className="navbar-nav ml-auto" id="navList">
Expand Down Expand Up @@ -105,6 +134,7 @@ Navbar.propTypes = {
loggedIn: PropTypes.bool.isRequired,
user: PropTypes.shape({ username: PropTypes.string }),
logoutAction: PropTypes.func.isRequired,
generalSearch: PropTypes.func.isRequired,
history: PropTypes.shape({ url: PropTypes.string, push: PropTypes.func }).isRequired,
};

Expand All @@ -118,4 +148,12 @@ const mapStateToProps = state => ({
payload: state.users.payload,
});

export default connect(mapStateToProps, { logoutAction })(withRouter(Navbar));
export default connect(
mapStateToProps,
{
logoutAction,
searchDictionaries,
fetchDictionaries,
generalSearch,
},
)(withRouter(Navbar));
13 changes: 12 additions & 1 deletion src/components/dashboard/styles/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -501,4 +501,15 @@ p{
float:left;
margin-top: 1px;
align-content: center;
}
}
#Noresults {
margin-top: 17rem!important;
margin-right: 13%;
}
#Noresults h5{
font-size: 2.25rem;
}

#generalSearch::placeholder{
color: #f5f5f594;
}
12 changes: 12 additions & 0 deletions src/redux/actions/GeneralSearchActions/GeneralSearchActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { SEARCH_RESULTS, IS_FETCHING } from '../types';

export const searchResults = payload => ({
type: SEARCH_RESULTS,
payload,
});

export const isFetching = payload => ({
type: IS_FETCHING,
payload,
});

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import api from '../../api';
import { searchResults, isFetching } from './GeneralSearchActions';
import { filterPayload } from '../../reducers/util';

const generalsearch = searchItem => (dispatch) => {
if (searchItem) {
dispatch(isFetching(true));
return api.dictionaries
.searchDictionaries(searchItem)
.then((payload) => {
const result = filterPayload(payload);
dispatch(searchResults(result));
dispatch(isFetching(false));
});
}
dispatch(isFetching(true));
return api.dictionaries
.fetchingDictionaries()
.then((payload) => {
const result = filterPayload(payload);
dispatch(searchResults(result));
dispatch(isFetching(false));
});
};

export default generalsearch;
1 change: 1 addition & 0 deletions src/redux/actions/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ export const FETCH_NEXT_CONCEPTS = '[concepts] fetch_next_concepts';
export const TOTAL_CONCEPT_COUNT = '[concepts] total_concept_count';

export const ADD_EXISTING_CONCEPTS = '[concepts] add_existing_concepts';
export const SEARCH_RESULTS = '[searchResults] search';
20 changes: 20 additions & 0 deletions src/redux/reducers/generalSearchReducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { SEARCH_RESULTS, IS_FETCHING } from '../actions/types';

const initialState = { dictionaries: [], loading: false };

export default (state = initialState, action) => {
switch (action.type) {
case SEARCH_RESULTS:
return {
...state,
dictionaries: action.payload,
};
case IS_FETCHING:
return {
...state,
loading: action.payload,
};
default:
return state;
}
};
2 changes: 2 additions & 0 deletions src/redux/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import sources from './sourcesReducer';
import organizations from './dictionaryReducer';
import concepts from './ConceptReducers';
import dictionaries from './dictionaryReducers';
import generalSearch from './generalSearchReducer';

// combined reducer to give a global state
const rootReducer = combineReducers({
Expand All @@ -12,6 +13,7 @@ const rootReducer = combineReducers({
organizations,
concepts,
dictionaries,
generalSearch,
});

export default rootReducer;
62 changes: 62 additions & 0 deletions src/tests/GeneralSearch/Actions/generalSearchActions.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import moxios from 'moxios';
import configure from 'redux-mock-store';
import thunk from 'redux-thunk';

import instance from '../../../config/axiosConfig';
import { IS_FETCHING, SEARCH_RESULTS } from '../../../redux/actions/types';
import generalsearch from '../../../redux/actions/GeneralSearchActions/generalSearchActionCreators';
import dictionaries from '../../__mocks__/dictionaries';

const mockstore = configure([thunk]);

describe('Test suite for Search Results actions', () => {
beforeEach(() => {
moxios.install(instance);
});

afterEach(() => {
moxios.uninstall(instance);
});

it('should return a dictionary', () => {
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: [dictionaries],
});
});
const expectedActions = [
{ type: IS_FETCHING, payload: true },
{ type: SEARCH_RESULTS, payload: [dictionaries] },
{ type: IS_FETCHING, payload: false },
];
const store = mockstore({
payload: {},
});

return store.dispatch(generalsearch('ChrisMain4567')).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
it('should return a dictionary', () => {
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: [dictionaries],
});
});
const expectedActions = [
{ type: IS_FETCHING, payload: true },
{ type: SEARCH_RESULTS, payload: [dictionaries] },
{ type: IS_FETCHING, payload: false },
];
const store = mockstore({
payload: {},
});
return store.dispatch(generalsearch()).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
});
Loading

0 comments on commit 6c69f41

Please sign in to comment.