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

DATAP-1621 - adding suggest typeahead and sub-agg search for product and issue #569

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
96 changes: 96 additions & 0 deletions src/components/Filters/FilterSearch/FilterSearch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import '../../Typeahead/Typeahead.scss';
import { useEffect, useRef, useState } from 'react';
import { useDispatch } from 'react-redux';
import { Typeahead } from 'react-bootstrap-typeahead';
import { filterAdded } from '../../../actions';
import PropTypes from 'prop-types';
import { useGetAggregations } from '../../../api/hooks/useGetAggregations';
import { SLUG_SEPARATOR } from '../../../constants';
import { ClearButton } from '../../Typeahead/ClearButton/ClearButton';
import getIcon from '../../Common/Icon/iconMap';

export const FilterSearch = ({ fieldName }) => {
const ref = useRef();
const dispatch = useDispatch();
const [inputText, setInputText] = useState('');
const fieldNameNew = fieldName.replace(/_/g, ' ');
const { data } = useGetAggregations();

const aggResults = data[fieldName] || [];
const subaggName = `sub_${fieldName}.raw`.toLowerCase();
const buckets = [];
aggResults.forEach((option) => {
buckets.push(option);
if (option[subaggName] && option[subaggName].buckets) {
option[subaggName].buckets.forEach((bucket) => {
const item = {
key: option.key + SLUG_SEPARATOR + bucket.key,
value: bucket.key,
};
buckets.push(item);
});
}
});

const handleClear = () => {
ref.current.clear();
setInputText('');
};

const handleSelections = (selected) => {
dispatch(filterAdded(fieldName, selected[0].key));
handleClear();
};

// give the input focus when the component renders the first time
useEffect(() => {
ref.current.focus();
}, [ref]);

return (
<section className="typeahead">
<div className="o-search-input">
<div className="o-search-input__input">
<label
aria-label={'Search ' + fieldName}
className="o-search-input__input-label"
htmlFor={'filter-search' + fieldName}
>
{getIcon('search')}
</label>
<Typeahead
id={'filter-search' + fieldName}
minLength={2}
className="typeahead-selector"
filterBy={['key']}
onChange={(selected) => handleSelections(selected)}
onInputChange={(text) => setInputText(text)}
placeholder={'Enter name of ' + fieldNameNew}
labelKey="key"
options={buckets}
ref={ref}
inputProps={{
'aria-label': `${fieldNameNew} Filter Menu Input`,
className: 'a-text-input a-text-input--full',
}}
renderMenuItemChildren={(option) => (
<li className="typeahead-option body-copy">
{option.key.split(SLUG_SEPARATOR)[0]}
{option.value ? (
<div>
<small>{option.value}</small>
</div>
) : null}
</li>
)}
/>
{!!inputText && <ClearButton onClear={handleClear} />}
</div>
</div>
</section>
);
};

FilterSearch.propTypes = {
fieldName: PropTypes.string.isRequired,
};
47 changes: 4 additions & 43 deletions src/components/Filters/Issue/Issue.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useSelector } from 'react-redux';
import { sortSelThenCount } from '../../../utils';
import { CollapsibleFilter } from '../CollapsibleFilter/CollapsibleFilter';
import { filtersReplaced } from '../../../reducers/filters/filtersSlice';
import { SLUG_SEPARATOR } from '../../../constants';
import { Typeahead } from '../../Typeahead/Typeahead/Typeahead';
import { selectFiltersIssue } from '../../../reducers/filters/selectors';
import { MoreOrLess } from '../MoreOrLess/MoreOrLess';
import { AggregationBranch } from '../Aggregation/AggregationBranch/AggregationBranch';
import { useGetAggregations } from '../../../api/hooks/useGetAggregations';
import { FilterSearch } from '../FilterSearch/FilterSearch';
import { SLUG_SEPARATOR } from '../../../constants';

// eslint-disable-next-line react/prop-types
export const Issue = () => {
const dispatch = useDispatch();
const [dropdownOptions, setDropdownOptions] = useState([]);
const { data } = useGetAggregations();
const filters = useSelector(selectFiltersIssue);

Expand All @@ -40,33 +36,6 @@ export const Issue = () => {
});
// Make a cloned, sorted version of the aggs
const options = sortSelThenCount(aggsFilters, selections);
// create an array optimized for typeahead
const optionKeys = options.map((opt) => opt.key);

const onInputChange = (value) => {
const num = value.toLowerCase();
if (num === '') {
setDropdownOptions([]);
return;
}
const options = optionKeys.map((opt) => ({
key: opt,
label: opt,
position: opt.toLowerCase().indexOf(num),
value,
}));
setDropdownOptions(options);
};

const onSelection = (items) => {
const replacementFilters = filters
// remove child items
.filter((filter) => filter.indexOf(items[0].key + SLUG_SEPARATOR) === -1)
// add parent item
.concat(items[0].key);
dispatch(filtersReplaced('issue', replacementFilters));
};

const onBucket = (bucket, props) => {
props.subitems = bucket['sub_issue.raw'].buckets;
return props;
Expand All @@ -78,15 +47,7 @@ export const Issue = () => {
desc={desc}
className="aggregation issue"
>
<Typeahead
ariaLabel="Start typing to begin listing issues"
htmlId="issue-typeahead"
placeholder="Enter name of issue"
handleChange={onSelection}
handleInputChange={onInputChange}
hasClearButton={true}
options={dropdownOptions}
/>
<FilterSearch fieldName="issue" />
<MoreOrLess
listComponent={AggregationBranch}
listComponentProps={listComponentProps}
Expand Down
21 changes: 10 additions & 11 deletions src/components/Filters/Issue/Issue.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import fetchMock from 'jest-fetch-mock';
import userEvent from '@testing-library/user-event';
import { merge } from '../../../testUtils/functionHelpers';
import { Issue } from './Issue';
import { listOfIssues } from '../../../testUtils/aggsConstants';
import * as filterActions from '../../../reducers/filters/filtersSlice';
import { filtersState } from '../../../reducers/filters/filtersSlice';
import { aggResponse } from './fixture';
Expand All @@ -32,8 +31,8 @@ describe('Issue', () => {
});

test('Options appear when user types and dispatches filtersReplaced on selection', async () => {
const filtersReplacedSpy = jest
.spyOn(filterActions, 'filtersReplaced')
const filterAddedSpy = jest
.spyOn(filterActions, 'filterAdded')
.mockImplementation(() => jest.fn());
fetchMock.mockResponseOnce(JSON.stringify(aggResponse));
renderComponent();
Expand All @@ -43,15 +42,15 @@ describe('Issue', () => {
const input = screen.getByPlaceholderText('Enter name of issue');
await user.type(input, 'Improper');
const option = await screen.findByRole('option', {
name: /Improper use of your report/,
name: 'Improper use of your report•Reporting company used your report improperly',
});
await user.click(option);

await waitFor(() =>
expect(filtersReplacedSpy).toBeCalledWith('issue', [
'Incorrect information on your report',
listOfIssues[0].key,
]),
expect(filterAddedSpy).toBeCalledWith(
'issue',
'Improper use of your report•Reporting company used your report improperly',
),
);
});

Expand All @@ -78,10 +77,10 @@ describe('Issue', () => {
const input = screen.getByPlaceholderText('Enter name of issue');
await user.type(input, 'Improper');
const option = await screen.findByRole('option', {
name: /Improper use of your report/,
name: 'Improper use of your report•Reporting company used your report improperly',
});
await user.clear(input);

await user.clear(screen.getByPlaceholderText('Enter name of issue'));
expect(input).toHaveValue('');
expect(option).not.toBeInTheDocument();
});
});
2 changes: 2 additions & 0 deletions src/components/Filters/Product/Product.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { selectFiltersProduct } from '../../../reducers/filters/selectors';
import { selectViewTab } from '../../../reducers/view/selectors';
import { useGetAggregations } from '../../../api/hooks/useGetAggregations';
import { FilterSearch } from '../FilterSearch/FilterSearch';

/**
* Helper function generate and sort options
Expand Down Expand Up @@ -98,6 +99,7 @@ export const Product = () => {
desc={desc}
className="aggregation product"
>
<FilterSearch fieldName="product" />
<MoreOrLess
listComponent={AggregationBranch}
listComponentProps={listComponentProps}
Expand Down
Loading