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

Fetch next page of facilities via infinite scroll component #750

Merged
merged 2 commits into from
Aug 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
94 changes: 13 additions & 81 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,8 +140,9 @@ 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';
expect(makeGetFacilitiesURLWithQueryString(qs)).toEqual(expectedMatch);
const expectedMatch = `/api/facilities/?hello=world&pageSize=${FACILITIES_REQUEST_PAGE_SIZE}`;
expect(makeGetFacilitiesURLWithQueryString(qs, FACILITIES_REQUEST_PAGE_SIZE))
.toEqual(expectedMatch);
});

it('gets the value from a React Select option object', () => {
Expand Down Expand Up @@ -877,83 +878,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 +1272,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');
});
40 changes: 38 additions & 2 deletions src/app/src/actions/facilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import apiRequest from '../util/apiRequest';

import { fetchCurrentTileCacheKey } from './vectorTileLayer';

import { FACILITIES_REQUEST_PAGE_SIZE } from '../util/constants';

import {
logErrorAndDispatchFailure,
makeGetFacilitiesURLWithQueryString,
Expand All @@ -24,7 +26,10 @@ export const failFetchSingleFacility = createAction('FAIL_FETCH_SINGLE_FACILITY'
export const completeFetchSingleFacility = createAction('COMPLETE_FETCH_SINGLE_FACILITY');
export const resetSingleFacility = createAction('RESET_SINGLE_FACILITY');

export function fetchFacilities(pushNewRoute = noop) {
export function fetchFacilities({
pageSize = FACILITIES_REQUEST_PAGE_SIZE,
pushNewRoute = noop,
}) {
return (dispatch, getState) => {
dispatch(fetchCurrentTileCacheKey());
dispatch(startFetchFacilities());
Expand All @@ -36,7 +41,7 @@ export function fetchFacilities(pushNewRoute = noop) {
const qs = createQueryStringFromSearchFilters(filters);

return apiRequest
.get(makeGetFacilitiesURLWithQueryString(qs))
.get(makeGetFacilitiesURLWithQueryString(qs, pageSize))
.then(({ data }) => {
const responseHasOnlyOneFacility = get(
data,
Expand Down Expand Up @@ -65,6 +70,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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a benefit to return noop(); instead of the equivalent return;?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe they're basically equivalent insofar as each will make the return value undefined, but I think the explicit return noop() is a little more readable when scanning through the code.

}

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
10 changes: 5 additions & 5 deletions src/app/src/actions/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ 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');

export const reportWindowResize =
createAction('REPORT_WINDOW_RESIZE');

export const updateSidebarFacilitiesTabTextFilter =
createAction('UPDATE_SIDEBAR_FACILITIES_TAB_TEXT_FILTER');
export const resetSidebarFacilitiesTabTextFilter =
createAction('RESET_SIDEBAR_FACILITIES_TAB_TEXT_FILTER');
12 changes: 11 additions & 1 deletion src/app/src/components/FilterSidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ import { Route } from 'react-router-dom';
import FilterSidebarGuideTab from './FilterSidebarGuideTab';
import FilterSidebarSearchTab from './FilterSidebarSearchTab';
import FilterSidebarFacilitiesTab from './FilterSidebarFacilitiesTab';
import NonVectorTileFilterSidebarFacilitiesTab from './NonVectorTileFilterSidebarFacilitiesTab';
import FeatureFlag from './FeatureFlag';

import {
filterSidebarTabsEnum,
filterSidebarTabs,
VECTOR_TILE,
} from '../util/constants';

import {
Expand Down Expand Up @@ -136,7 +139,14 @@ class FilterSidebar extends Component {
// in its `mapDispatchToProps` function.
return <Route component={FilterSidebarSearchTab} />;
case filterSidebarTabsEnum.facilities:
return <FilterSidebarFacilitiesTab />;
return (
<FeatureFlag
flag={VECTOR_TILE}
alternative={<NonVectorTileFilterSidebarFacilitiesTab />}
>
<FilterSidebarFacilitiesTab />
</FeatureFlag>
);
default:
window.console.warn('invalid tab selection', activeFilterSidebarTab);
return null;
Expand Down