Skip to content
This repository has been archived by the owner on Feb 1, 2024. It is now read-only.

Commit

Permalink
Fetch next page of facilities via infinite scroll
Browse files Browse the repository at this point in the history
Adjust facilities tab so that it will fetch the next page of facilities
on reaching the bottom of the infinite scroll list.

Also:

- remove some now unused utility code & tests
- update facilities list endpoint to return results in alphabetical
order
- update url to retrieve facilities to request pages of 25 results at a
time
- add a loading indicator contextually if there's a next page of facilities to fetch
  • Loading branch information
Kelly Innes committed Aug 28, 2019
1 parent af0667a commit bee55de
Show file tree
Hide file tree
Showing 10 changed files with 116 additions and 180 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]
### Added
- Adjust marker icon on selecting a new facility on the vector tiles layer [#749](https://github.com/open-apparel-registry/open-apparel-registry/pull/749)
- Fetch next page of facilities while scrolling through sidebar list [#750](https://github.com/open-apparel-registry/open-apparel-registry/pull/750)

### Changed

Expand Down
91 changes: 11 additions & 80 deletions src/app/src/__tests__/utils.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,6 @@ const {
makeUserProfileURL,
makeProfileRouteLink,
joinDataIntoCSVString,
caseInsensitiveIncludes,
sortFacilitiesAlphabeticallyByName,
updateListWithLabels,
makeSubmitFormOnEnterKeyPressFunction,
makeFacilityListItemsRetrieveCSVItemsURL,
Expand All @@ -75,6 +73,7 @@ const {
claimFacilityContactInfoStepIsValid,
claimFacilityFacilityInfoStepIsValid,
anyListItemMatchesAreInactive,
pluralizeResultsCount,
} = require('../util/util');

const {
Expand All @@ -88,6 +87,7 @@ const {
ENTER_KEY,
facilityListItemStatusChoicesEnum,
facilityListSummaryStatusMessages,
FACILITIES_REQUEST_PAGE_SIZE,
} = require('../util/constants');

it('creates a route for checking facility list items', () => {
Expand Down Expand Up @@ -140,7 +140,7 @@ it('creates an API URL for getting a single facility by OAR ID', () => {

it('creates an API URL for getting facilities with a query string', () => {
const qs = 'hello=world';
const expectedMatch = '/api/facilities/?hello=world';
const expectedMatch = `/api/facilities/?hello=world&pageSize=${FACILITIES_REQUEST_PAGE_SIZE}`;
expect(makeGetFacilitiesURLWithQueryString(qs)).toEqual(expectedMatch);
});

Expand Down Expand Up @@ -877,83 +877,6 @@ it('joins a 2-d array into a correctly escaped CSV string', () => {
expect(joinDataIntoCSVString(escapedArray)).toBe(expectedEscapedArrayMatch);
});

it('checks whether one string includes another regardless of char case', () => {
const uppercaseTarget = 'HELLOWORLD';
const lowercaseTest = 'world';
const lowercaseTarget = 'helloworld';
const uppercaseTest = 'WORLD';
const uppercaseNonMatchTest = 'FOO';
const lowercaseNonMatchTest = 'foo';

expect(caseInsensitiveIncludes(uppercaseTarget, lowercaseTest)).toBe(true);
expect(caseInsensitiveIncludes(lowercaseTarget, uppercaseTest)).toBe(true);
expect(caseInsensitiveIncludes(lowercaseTarget, lowercaseTest)).toBe(true);
expect(caseInsensitiveIncludes(uppercaseTarget, uppercaseTest)).toBe(true);

expect(caseInsensitiveIncludes(uppercaseTarget, lowercaseNonMatchTest)).toBe(false);
expect(caseInsensitiveIncludes(lowercaseTarget, uppercaseNonMatchTest)).toBe(false);
expect(caseInsensitiveIncludes(lowercaseTarget, lowercaseNonMatchTest)).toBe(false);
expect(caseInsensitiveIncludes(uppercaseTarget, uppercaseNonMatchTest)).toBe(false);
});

it('sorts an array of facilities alphabetically by name without mutating the input', () => {
const inputData = [
{
properties: {
name: 'hello World',
},
},
{
properties: {
name: 'FOO',
},
},
{
properties: {
name: 'Bar',
},
},
{
properties: {
name: 'baz',
},
},
];

const expectedSortedData = [
{
properties: {
name: 'Bar',
},
},
{
properties: {
name: 'baz',
},
},
{
properties: {
name: 'FOO',
},
},
{
properties: {
name: 'hello World',
},
},
];

expect(isEqual(
sortFacilitiesAlphabeticallyByName(inputData),
expectedSortedData,
)).toBe(true);

expect(isEqual(
inputData,
expectedSortedData,
)).toBe(false);
});

it('updates a list of unlabeled values with the correct labels from a given source', () => {
const source = [
{
Expand Down Expand Up @@ -1348,3 +1271,11 @@ it('checks a facility list item to see whether any matches have been set to inac

expect(anyListItemMatchesAreInactive(listItemWithInactiveMatches)).toBe(true);
});

it('pluralizes a results count correclty, returning null if count is undefined or null', () => {
expect(pluralizeResultsCount(undefined)).toBeNull();
expect(pluralizeResultsCount(null)).toBeNull();
expect(pluralizeResultsCount(1)).toBe('1 result');
expect(pluralizeResultsCount(0)).toBe('0 results');
expect(pluralizeResultsCount(200)).toBe('200 results');
});
31 changes: 31 additions & 0 deletions src/app/src/actions/facilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,37 @@ export function fetchFacilities(pushNewRoute = noop) {
};
}

export const startFetchNextPageOfFacilities = createAction('START_FETCH_NEXT_PAGE_OF_FACILITIES');
export const failFetchNextPageOfFacilities = createAction('FAIL_FETCH_NEXT_PAGE_OF_FACILITIES');
export const completeFetchNextPageOfFacilities = createAction('COMPLETE_FETCH_NEXT_PAGE_OF_FACILITIES');

export function fetchNextPageOfFacilities() {
return (dispatch, getState) => {
const {
facilities: {
facilities: {
nextPageURL,
},
},
} = getState();

if (!nextPageURL) {
return noop();
}

dispatch(startFetchNextPageOfFacilities());

return apiRequest
.get(nextPageURL)
.then(({ data }) => dispatch(completeFetchNextPageOfFacilities(data)))
.catch(err => dispatch(logErrorAndDispatchFailure(
err,
'An error prevented fetching the next page of facilities',
failFetchNextPageOfFacilities,
)));
};
}

export function fetchSingleFacility(oarID = null) {
return (dispatch) => {
dispatch(startFetchSingleFacility());
Expand Down
5 changes: 0 additions & 5 deletions src/app/src/actions/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@ export const makeSidebarGuideTabActive = createAction('MAKE_SIDEBAR_GUIDE_TAB_AC
export const makeSidebarSearchTabActive = createAction('MAKE_SIDEBAR_SEARCH_TAB_ACTIVE');
export const makeSidebarFacilitiesTabActive = createAction('MAKE_SIDEBAR_FACILITIES_TAB_ACTIVE');

export const updateSidebarFacilitiesTabTextFilter =
createAction('UPDATE_SIDEBAR_FACILITIES_TAB_TEXT_FILTER');
export const resetSidebarFacilitiesTabTextFilter =
createAction('RESET_SIDEBAR_FACILITIES_TAB_TEXT_FILTER');

export const recordSearchTabResetButtonClick =
createAction('RECORD_SEARCH_TAB_RESET_BUTTON_CLICK');

Expand Down
72 changes: 25 additions & 47 deletions src/app/src/components/FilterSidebarFacilitiesTab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,12 @@ import get from 'lodash/get';
import { toast } from 'react-toastify';
import InfiniteAnyHeight from 'react-infinite-any-height';

import ControlledTextInput from './ControlledTextInput';

import {
makeSidebarSearchTabActive,
updateSidebarFacilitiesTabTextFilter,
} from '../actions/ui';

import { fetchNextPageOfFacilities } from '../actions/facilities';

import { logDownload } from '../actions/logDownload';

import { facilityCollectionPropType } from '../util/propTypes';
Expand All @@ -35,17 +34,13 @@ import {

import {
makeFacilityDetailLink,
getValueFromEvent,
caseInsensitiveIncludes,
sortFacilitiesAlphabeticallyByName,
pluralizeResultsCount,
} from '../util/util';

import COLOURS from '../util/COLOURS';

import { filterSidebarStyles } from '../util/styles';

const SEARCH_TERM_INPUT = 'SEARCH_TERM_INPUT';

const facilitiesTabStyles = Object.freeze({
noResultsTextStyles: Object.freeze({
margin: '30px',
Expand Down Expand Up @@ -95,9 +90,9 @@ function FilterSidebarFacilitiesTab({
logDownloadError,
user,
returnToSearchTab,
filterText,
updateFilterText,
handleDownload,
fetchNextPage,
isInfiniteLoading,
}) {
const [loginRequiredDialogIsOpen, setLoginRequiredDialogIsOpen] = useState(false);
const [requestedDownload, setRequestedDownload] = useState(false);
Expand Down Expand Up @@ -188,27 +183,9 @@ function FilterSidebarFacilitiesTab({
);
}

const filteredFacilities = filterText
? facilities
.filter(({
properties: {
address,
name,
country_name: countryName,
},
}) => caseInsensitiveIncludes(address, filterText)
|| caseInsensitiveIncludes(name, filterText)
|| caseInsensitiveIncludes(countryName, filterText))
: facilities;

const orderedFacilities =
sortFacilitiesAlphabeticallyByName(filteredFacilities);

const facilitiesCount = get(data, 'count', null);

const headerDisplayString = facilitiesCount && (facilitiesCount !== filteredFacilities.length)
? `Displaying ${filteredFacilities.length} facilities of ${facilitiesCount} results`
: `Displaying ${filteredFacilities.length} facilities`;
const headerDisplayString = pluralizeResultsCount(facilitiesCount);

const LoginLink = props => <Link to={authLoginFormRoute} {...props} />;
const RegisterLink = props => <Link to={authRegisterFormRoute} {...props} />;
Expand Down Expand Up @@ -242,19 +219,6 @@ function FilterSidebarFacilitiesTab({
</Button>
</div>
</Typography>
<div style={facilitiesTabStyles.listHeaderTextSearchStyles}>
<label
htmlFor={SEARCH_TERM_INPUT}
>
Filter results
</label>
<ControlledTextInput
id={SEARCH_TERM_INPUT}
value={filterText}
onChange={updateFilterText}
placeholder="Filter by name, address, or country"
/>
</div>
</div>
);

Expand All @@ -265,15 +229,28 @@ function FilterSidebarFacilitiesTab({

const resultListHeight = windowHeight - nonResultListComponentHeight;

const loadingElement = (facilities.length !== facilitiesCount) && (
<Fragment>
<Divider />
<ListItem style={facilitiesTabStyles.listItemStyles}>
<ListItemText primary="Loading more facilities..." />
</ListItem>
</Fragment>
);

return (
<Fragment>
{listHeaderInsetComponent}
<div style={filterSidebarStyles.controlPanelContentStyles}>
<List style={facilitiesTabStyles.listStyles}>
<InfiniteAnyHeight
containerHeight={resultListHeight}
infiniteLoadBeginEdgeOffset={100}
isInfiniteLoading={fetching || isInfiniteLoading}
onInfiniteLoad={fetchNextPage}
loadingSpinnerDelegate={loadingElement}
list={
orderedFacilities
facilities
.map(({
properties: {
address,
Expand Down Expand Up @@ -363,9 +340,9 @@ FilterSidebarFacilitiesTab.propTypes = {
windowHeight: number.isRequired,
logDownloadError: arrayOf(string),
returnToSearchTab: func.isRequired,
filterText: string.isRequired,
updateFilterText: func.isRequired,
handleDownload: func.isRequired,
fetchNextPage: func.isRequired,
isInfiniteLoading: bool.isRequired,
};

function mapStateToProps({
Expand All @@ -379,6 +356,7 @@ function mapStateToProps({
data,
error,
fetching,
isInfiniteLoading,
},
},
ui: {
Expand All @@ -401,15 +379,15 @@ function mapStateToProps({
user,
logDownloadError,
windowHeight,
isInfiniteLoading,
};
}

function mapDispatchToProps(dispatch) {
return {
returnToSearchTab: () => dispatch(makeSidebarSearchTabActive()),
updateFilterText: e =>
dispatch((updateSidebarFacilitiesTabTextFilter(getValueFromEvent(e)))),
handleDownload: () => dispatch(logDownload()),
fetchNextPage: () => dispatch(fetchNextPageOfFacilities()),
};
}

Expand Down

0 comments on commit bee55de

Please sign in to comment.