Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/772 search #802

Merged
merged 37 commits into from
Apr 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
17a693a
Add search functionality for component ActiveFocusInvestigation
kelvin-muchiri Apr 14, 2020
a5e7357
Add search for component IRSPlansList
kelvin-muchiri Apr 14, 2020
a8f343a
Refactor search forms into a single reusable component
kelvin-muchiri Apr 14, 2020
93e6df7
Add search for manage plans list
kelvin-muchiri Apr 15, 2020
2d7986f
Debounce search
kelvin-muchiri Apr 15, 2020
93cc049
Merge branch 'master' into feature/772_search
kelvin-muchiri Apr 15, 2020
166ebd6
Merge master and resolve conflicts
kelvin-muchiri Apr 16, 2020
5e0329b
Add getPlansArrayByTitle selector
kelvin-muchiri Apr 16, 2020
0b37a92
Merge branch 'master' into feature/772_search
kelvin-muchiri Apr 17, 2020
efddc76
Make use of reselect for Focus investigation search
kelvin-muchiri Apr 17, 2020
c1ef0ec
Merge branch 'master' into feature/772_search
kelvin-muchiri Apr 17, 2020
6ed03c0
Refactor code
kelvin-muchiri Apr 17, 2020
c905009
Refactor code to enhance performance
kelvin-muchiri Apr 17, 2020
a94c5de
Refactor code
kelvin-muchiri Apr 17, 2020
c36d5c1
Search plan list by title instead of name
kelvin-muchiri Apr 17, 2020
e137047
Add selector getPlanDefinitionsArrayByTitle
kelvin-muchiri Apr 20, 2020
030fe13
Remove plan23 fixture from src/store/ducks/tests/fixtures.ts
kelvin-muchiri Apr 20, 2020
bb14c8b
Add selector getIRSPlansArrayByTitle
kelvin-muchiri Apr 20, 2020
fe127c2
Move selectors in ActiveFocusInvestigation to mapStateToProps
kelvin-muchiri Apr 20, 2020
e579318
Move search handle submit and handle change to component SearchForm
kelvin-muchiri Apr 21, 2020
a06db1f
Merge master and resolve conflicts
kelvin-muchiri Apr 21, 2020
28f8cf8
Add placeholder prop for SearchForm. Update failing test snapshot
kelvin-muchiri Apr 21, 2020
4628aed
Merge branch 'master' into feature/772_search
kelvin-muchiri Apr 21, 2020
b75e3ab
Update SearchForm input value on mount with the value from the query …
kelvin-muchiri Apr 21, 2020
396d8d8
Refactor to eliminate redundancy since selectors will handle empty title
kelvin-muchiri Apr 21, 2020
f3c2d99
Add default props for component SearchForm
kelvin-muchiri Apr 22, 2020
4c9b75f
Remove unused prop for component ActiveFocusInvestigation
kelvin-muchiri Apr 22, 2020
d91d68b
Change query param search to title and access the string from constants
kelvin-muchiri Apr 22, 2020
156c430
Add makeIRSPlansArraySelector and use state in mapStateToProps for co…
kelvin-muchiri Apr 22, 2020
055ff1c
Add and use selector makePlanDefinitionsArraySelector
kelvin-muchiri Apr 22, 2020
b001a14
Use state in place of store.getState() for makePlansArraySelector arg…
kelvin-muchiri Apr 22, 2020
b541aa4
Reduce huge PlanDefinitionList snapshot
kelvin-muchiri Apr 22, 2020
33a624b
Ehance PlanDefinitionList snapshot
kelvin-muchiri Apr 22, 2020
ce7f378
Add HelmetWrapper snapshot for PlanDefinitionList
kelvin-muchiri Apr 22, 2020
75f0b58
Make SearchForm prop placeholder not optional
kelvin-muchiri Apr 22, 2020
4de5360
Remove repetition
kelvin-muchiri Apr 22, 2020
5bc3d78
Merge branch 'master' into feature/772_search
kelvin-muchiri Apr 22, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions src/components/forms/Search/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { History, Location } from 'history';
import React, { useEffect, useState } from 'react';
import { Button, Form, FormGroup, Input } from 'reactstrap';
import { SEARCH } from '../../../configs/lang';
import { QUERY_PARAM_TITLE } from '../../../constants';
import { getQueryParams } from '../../../helpers/utils';

/**
* Interface for handleSearchChange event handler
*/
export type Change = (event: React.ChangeEvent<HTMLInputElement>) => void;

/**
* Interface for handleSubmit event handler
*/
export type Submit = (event: React.FormEvent<HTMLFormElement>) => void;

/**
* Interface for SearchForm props
*/
export interface SearchFormProps {
history: History;
location: Location;
placeholder: string;
}

/**
* default props for SerchForm component
*/
export const defaultSearchFormProps = {
placeholder: SEARCH,
};

/** SearchForm component */
export const SearchForm = (props: SearchFormProps) => {
peterMuriuki marked this conversation as resolved.
Show resolved Hide resolved
const [searchQuery, setSearchQuery] = useState('');

useEffect(() => {
setSearchQuery(getQueryParams(props.location)[QUERY_PARAM_TITLE] as string);
}, []);

const handleSearchChange: Change = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchQuery(event.target.value);
};

const handleSubmit: Submit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
props.history.push({
search: `?${QUERY_PARAM_TITLE}=${searchQuery}`,
});
};

return (
<Form inline={true} onSubmit={handleSubmit}>
<FormGroup className="mb-2 mr-sm-2 mb-sm-0">
<Input
type="text"
name="search"
placeholder={props.placeholder}
onChange={handleSearchChange}
value={searchQuery}
/>
</FormGroup>
<Button outline={true} color="success">
{SEARCH}
</Button>
</Form>
);
};

SearchForm.defaultProps = defaultSearchFormProps;
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`src/components/SearchForm renders correctly 1`] = `
<SearchForm
handleSearchChange={[MockFunction]}
history={
Object {
"action": "POP",
"block": [Function],
"createHref": [Function],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"length": 1,
"listen": [Function],
"location": Object {
"hash": "",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [Function],
"replace": [Function],
}
}
location={
Object {
"hash": "",
"pathname": "/",
"query": Object {},
"search": "",
"state": undefined,
}
}
placeholder="Search"
>
<Form
inline={true}
onSubmit={[Function]}
tag="form"
>
<form
className="form-inline"
onSubmit={[Function]}
>
<FormGroup
className="mb-2 mr-sm-2 mb-sm-0"
tag="div"
>
<div
className="mb-2 mr-sm-2 mb-sm-0 form-group"
>
<Input
name="search"
onChange={[Function]}
placeholder="Search"
type="text"
>
<input
className="form-control"
name="search"
onChange={[Function]}
placeholder="Search"
type="text"
/>
</Input>
</div>
</FormGroup>
<Button
color="success"
outline={true}
tag="button"
>
<button
aria-label={null}
className="btn btn-outline-success"
onClick={[Function]}
>
Search
</button>
</Button>
</form>
</Form>
</SearchForm>
`;
46 changes: 46 additions & 0 deletions src/components/forms/Search/tests/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { mount } from 'enzyme';
import toJson from 'enzyme-to-json';
import { createBrowserHistory } from 'history';
import React from 'react';
import { SearchForm } from '../../Search';

const history = createBrowserHistory();

describe('src/components/SearchForm', () => {
it('renders correctly', () => {
const props = {
handleSearchChange: jest.fn(),
history,
location: { hash: '', pathname: '/', search: '', state: undefined, query: {} },
};
const wrapper = mount(<SearchForm {...props} />);
expect(toJson(wrapper)).toMatchSnapshot();
wrapper.unmount();
});

it('handles submit correctly', () => {
jest.spyOn(history, 'push');
const props = {
handleSearchChange: jest.fn(),
history,
location: { hash: '', pathname: '/', search: '', state: undefined, query: {} },
};
const wrapper = mount(<SearchForm {...props} />);
wrapper.find('Input').simulate('change', { target: { value: 'test' } });
wrapper.find('Form').simulate('submit');
expect(history.push).toBeCalledWith({ search: '?title=test' });
wrapper.unmount();
});

it('displays placeholder correctly', () => {
const props = {
handleSearchChange: jest.fn(),
history,
location: { hash: '', pathname: '/', search: '', state: undefined, query: {} },
placeholder: 'Search me',
};
const wrapper = mount(<SearchForm {...props} />);
expect(wrapper.find('Input').prop('placeholder')).toEqual('Search me');
wrapper.unmount();
});
});
3 changes: 3 additions & 0 deletions src/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,6 @@ export const PRACTITIONER_CODE = {
};
/** Field to sort plans by */
export const SORT_BY_EFFECTIVE_PERIOD_START_FIELD = 'plan_effective_period_start';

/** Query Params */
export const QUERY_PARAM_TITLE = 'title';
72 changes: 33 additions & 39 deletions src/containers/pages/FocusInvestigation/active/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import { RouteComponentProps } from 'react-router';
import { Link } from 'react-router-dom';
import { CellInfo, Column } from 'react-table';
import 'react-table/react-table.css';
import { Button, Col, Form, FormGroup, Input, Row, Table } from 'reactstrap';
import { Col, Row, Table } from 'reactstrap';
import { Store } from 'redux';
import { format } from 'util';
import DrillDownTableLinkedCell from '../../../../components/DrillDownTableLinkedCell';
import { SearchForm } from '../../../../components/forms/Search';
import LinkAsButton from '../../../../components/LinkAsButton';
import NewRecordBadge from '../../../../components/NewRecordBadge';
import HeaderBreadCrumb, {
Expand All @@ -40,8 +41,6 @@ import {
PREVIOUS,
REACTIVE,
ROUTINE_TITLE,
SEARCH,
SEARCH_ACTIVE_FOCUS_INVESTIGATIONS,
START_DATE,
STATUS_HEADER,
} from '../../../../configs/lang';
Expand All @@ -56,6 +55,7 @@ import {
FI_SINGLE_URL,
FI_URL,
HOME_URL,
QUERY_PARAM_TITLE,
ROUTINE,
} from '../../../../constants';
import { displayError } from '../../../../helpers/errors';
Expand All @@ -64,15 +64,16 @@ import '../../../../helpers/tables.css';
import {
defaultTableProps,
getFilteredFIPlansURL,
getQueryParams,
removeNullJurisdictionPlans,
} from '../../../../helpers/utils';
import { extractPlan, getLocationColumns } from '../../../../helpers/utils';
import supersetFetch from '../../../../services/superset';
import plansReducer, {
fetchPlans,
getPlanById,
getPlansArray,
InterventionType,
makePlansArraySelector,
Plan,
PlanStatus,
reducerName as plansReducerName,
Expand All @@ -95,6 +96,7 @@ export interface ActiveFIProps {
routinePlans: Plan[] | null;
supersetService: typeof supersetFetch;
plan: Plan | null;
searchedTitle: string | null;
}

/** default props for ActiveFI component */
Expand All @@ -103,6 +105,7 @@ export const defaultActiveFIProps: ActiveFIProps = {
fetchPlansActionCreator: fetchPlans,
plan: null,
routinePlans: null,
searchedTitle: null,
supersetService: supersetFetch,
};

Expand All @@ -112,6 +115,7 @@ class ActiveFocusInvestigation extends React.Component<
{}
> {
public static defaultProps: ActiveFIProps = defaultActiveFIProps;

constructor(props: ActiveFIProps & RouteComponentProps<RouteParams>) {
super(props);
}
Expand All @@ -125,9 +129,7 @@ class ActiveFocusInvestigation extends React.Component<
.then((result: Plan[]) => fetchPlansActionCreator(result))
.catch(err => displayError(err));
}
public handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
}

public render() {
const breadcrumbProps: BreadCrumbProps = {
currentPage: {
Expand All @@ -146,7 +148,7 @@ class ActiveFocusInvestigation extends React.Component<
url: HOME_URL,
};

const { caseTriggeredPlans, routinePlans, plan } = this.props;
const { caseTriggeredPlans, routinePlans, plan, searchedTitle } = this.props;
// We need to initialize jurisdictionName to a falsy value
let jurisdictionName = null;

Expand Down Expand Up @@ -176,7 +178,8 @@ class ActiveFocusInvestigation extends React.Component<
caseTriggeredPlans &&
caseTriggeredPlans.length === 0 &&
routinePlans &&
routinePlans.length === 0
routinePlans.length === 0 &&
searchedTitle === null
) {
return <Loading />;
}
Expand All @@ -192,19 +195,7 @@ class ActiveFocusInvestigation extends React.Component<
<HeaderBreadCrumb {...breadcrumbProps} />
<h2 className="mb-3 mt-5 page-title">{pageTitle}</h2>
<hr />
<Form inline={true} onSubmit={this.handleSubmit}>
<FormGroup className="mb-2 mr-sm-2 mb-sm-0">
<Input
type="text"
name="search"
id="exampleEmail"
placeholder={SEARCH_ACTIVE_FOCUS_INVESTIGATIONS}
/>
</FormGroup>
<Button outline={true} color="success">
{SEARCH}
</Button>
</Form>
<SearchForm history={this.props.history} location={this.props.location} />
{[caseTriggeredPlans, routinePlans].forEach((plansArray: Plan[] | null, i) => {
const locationColumns: Column[] = getLocationColumns(locationHierarchy, true);
if (plansArray && plansArray.length) {
Expand Down Expand Up @@ -402,7 +393,7 @@ class ActiveFocusInvestigation extends React.Component<
columns: emptyPlansColumns,
};
routineReactivePlans.push(
<NullDataTable tableProps={tableProps} reasonType={header} key={`${'current'}`} />
<NullDataTable tableProps={tableProps} reasonType={header} key={i} />
moshthepitt marked this conversation as resolved.
Show resolved Hide resolved
);
}
})}
Expand All @@ -425,6 +416,7 @@ interface DispatchedStateProps {
plan: Plan | null;
caseTriggeredPlans: Plan[] | null;
routinePlans: Plan[] | null;
searchedTitle: string;
}

/** map state to props */
Expand All @@ -436,26 +428,28 @@ const mapStateToProps = (state: Partial<Store>, ownProps: any): DispatchedStateP
ownProps.match.params && ownProps.match.params.jurisdiction_parent_id
? ownProps.match.params.jurisdiction_parent_id
: null;
const caseTriggeredPlans = getPlansArray(
state,
InterventionType.FI,
[PlanStatus.ACTIVE, PlanStatus.COMPLETE],
CASE_TRIGGERED,
[],
jurisdictionParentId
);
const routinePlans = getPlansArray(
state,
InterventionType.FI,
[PlanStatus.ACTIVE, PlanStatus.COMPLETE],
ROUTINE,
[],
jurisdictionParentId
);

const searchedTitle = getQueryParams(ownProps.location)[QUERY_PARAM_TITLE] as string;
const caseTriggeredPlans = makePlansArraySelector()(state, {
interventionType: InterventionType.FI,
parentJurisdictionId: jurisdictionParentId,
reason: CASE_TRIGGERED,
statusList: [PlanStatus.ACTIVE, PlanStatus.COMPLETE],
title: searchedTitle,
});
const routinePlans = makePlansArraySelector()(state, {
interventionType: InterventionType.FI,
parentJurisdictionId: jurisdictionParentId,
reason: ROUTINE,
statusList: [PlanStatus.ACTIVE, PlanStatus.COMPLETE],
title: searchedTitle,
});

return {
caseTriggeredPlans,
plan,
routinePlans,
searchedTitle,
};
};

Expand Down
Loading