diff --git a/src/components/HOCs/WithChallengeTaskClusters/WithChallengeTaskClusters.jsx b/src/components/HOCs/WithChallengeTaskClusters/WithChallengeTaskClusters.jsx index e409cfdac..e9b338386 100644 --- a/src/components/HOCs/WithChallengeTaskClusters/WithChallengeTaskClusters.jsx +++ b/src/components/HOCs/WithChallengeTaskClusters/WithChallengeTaskClusters.jsx @@ -17,7 +17,7 @@ import { fromLatLngBounds, boundsWithinAllowedMaxDegrees } from '../../../services/MapBounds/MapBounds' import { fetchTaskClusters, clearTaskClusters } from '../../../services/Task/TaskClusters' -import { fetchBoundedTasks, clearBoundedTasks } +import { fetchBoundedTaskMarkers, fetchBoundedTasks, clearBoundedTasks } from '../../../services/Task/BoundedTask' import { MAX_ZOOM, UNCLUSTER_THRESHOLD } from '../../TaskClusterMap/TaskClusterMap' @@ -109,12 +109,13 @@ export const WithChallengeTaskClusters = function(WrappedComponent, storeTasks=f searchCriteria.page = 0 // Fetch up to threshold+1 individual tasks (eg. 1001 tasks) - this.props.fetchBoundedTasks(searchCriteria, UNCLUSTER_THRESHOLD + 1, !storeTasks, ignoreLocked, true).then(results => { + this.props.fetchBoundedTaskMarkers(searchCriteria, UNCLUSTER_THRESHOLD + 1, !storeTasks, ignoreLocked).then(results => { if (currentFetchId >= this.state.fetchId) { + const totalCount = results.length // If we retrieved 1001 tasks then there might be more tasks and // they should be clustered. So fetch as clusters // (unless we are zoomed all the way in already) - if (results.totalCount > UNCLUSTER_THRESHOLD && + if (totalCount > UNCLUSTER_THRESHOLD && _get(this.props, 'criteria.zoom', 0) < MAX_ZOOM) { this.props.fetchTaskClusters(challengeId, searchCriteria, 25, overrideDisable ).then(results => { @@ -127,8 +128,8 @@ export const WithChallengeTaskClusters = function(WrappedComponent, storeTasks=f }) } else { - this.setState({clusters: results.tasks, loading: false, - taskCount: results.totalCount}) + this.setState({clusters: results, loading: false, + taskCount: totalCount}) } } }).catch(error => { @@ -256,7 +257,7 @@ export const WithChallengeTaskClusters = function(WrappedComponent, storeTasks=f export const mapDispatchToProps = dispatch => Object.assign( {}, - bindActionCreators({ fetchTaskClusters, fetchBoundedTasks }, dispatch), + bindActionCreators({ fetchTaskClusters, fetchBoundedTaskMarkers, fetchBoundedTasks }, dispatch), { clearTasksAndClusters: () => { dispatch(clearBoundedTasks()) diff --git a/src/services/Server/APIRoutes.js b/src/services/Server/APIRoutes.js index c4b69b550..627d4eacb 100644 --- a/src/services/Server/APIRoutes.js +++ b/src/services/Server/APIRoutes.js @@ -105,6 +105,7 @@ const apiRoutes = (factory) => { tasks: { random: factory.get("/tasks/random", { noCache: true }), withinBounds: factory.put("/tasks/box/:left/:bottom/:right/:top"), + markersWithinBounds: factory.put("/markers/box/:left/:bottom/:right/:top"), bulkUpdate: factory.put("/tasks"), bulkStatusChange: factory.put("/tasks/changeStatus"), review: factory.get("/tasks/review"), diff --git a/src/services/Task/BoundedTask.js b/src/services/Task/BoundedTask.js index 9d0b28774..86e3faa24 100644 --- a/src/services/Task/BoundedTask.js +++ b/src/services/Task/BoundedTask.js @@ -9,7 +9,6 @@ import { addError } from '../Error/Error' import AppErrors from '../Error/AppErrors' import _get from 'lodash/get' import _values from 'lodash/values' -import _isArray from 'lodash/isArray' import _isUndefined from 'lodash/isUndefined' import _map from 'lodash/map' import { generateSearchParametersString } from '../Search/Search' @@ -43,14 +42,109 @@ export const receiveBoundedTasks = function(tasks, } } -// async action creators +/** + * Retrieve all task markers (up to the given limit) matching the given search + * criteria, which should at least include a boundingBox field, and may + * optionally include a filters field with additional constraints + */ +export function fetchBoundedTaskMarkers(criteria, limit = 50, skipDispatch = false, ignoreLocked = true) { + return function(dispatch) { + if (!skipDispatch) { + // The map is either showing task clusters or bounded tasks so we shouldn't + // have both in redux. + // (ChallengeLocation needs to know which challenge tasks pass the location) + dispatch(clearTaskClusters()) + } + + const normalizedBounds = toLatLngBounds(criteria.boundingBox) + if (!normalizedBounds) { + return null + } + + const filters = criteria.filters ?? {}; + const searchParameters = generateSearchParametersString( + filters, + null, + criteria.savedChallengesOnly, + null, + null, + criteria.invertFields + ) + + if (!filters.challengeId) { + const onlyEnabled = criteria.onlyEnabled ?? true; + const challengeStatus = criteria.challengeStatus + if (challengeStatus) { + searchParameters.cStatus = challengeStatus.join(',') + } + + // ce: limit to enabled challenges + // pe: limit to enabled projects + searchParameters.ce = onlyEnabled ? 'true' : 'false' + searchParameters.pe = onlyEnabled ? 'true' : 'false' + + // if we are restricting to onlyEnabled challenges then let's + // not show 'local' challenges either. + searchParameters.cLocal = onlyEnabled ? CHALLENGE_EXCLUDE_LOCAL : + CHALLENGE_INCLUDE_LOCAL + } + + // If we are searching within map bounds we need to ensure the parent + // challenge is also within those bounds + if (filters.location === CHALLENGE_LOCATION_WITHIN_MAPBOUNDS) { + if (Array.isArray(criteria.boundingBox)) { + searchParameters.bb = criteria.boundingBox.join(',') + } else { + searchParameters.bb = criteria.boundingBox + } + } + + const fetchId = uuidv1() + !skipDispatch && dispatch(receiveBoundedTasks(null, RequestStatus.inProgress, fetchId)) + + return new Endpoint( + api.tasks.markersWithinBounds, { + schema: { + tasks: [taskSchema()] + }, + variables: { + left: normalizedBounds.getWest(), + bottom: normalizedBounds.getSouth(), + right: normalizedBounds.getEast(), + top: normalizedBounds.getNorth(), + }, + params: { + limit, + excludeLocked: ignoreLocked, + ...searchParameters, + }, + json: filters.taskPropertySearch ? { + taskPropertySearch: filters.taskPropertySearch + } : null, + } + ).execute().then(({ result }) => { + let tasks = result ? Object.values(result) : []; + tasks = tasks.map(task => Object.assign(task, task.pointReview)) + + if (!skipDispatch) { + dispatch(receiveBoundedTasks(tasks, RequestStatus.success, fetchId, tasks.length)) + } + + return tasks + }).catch(error => { + dispatch(receiveBoundedTasks([], RequestStatus.error, fetchId)) + dispatch(addError(AppErrors.boundedTask.fetchFailure)) + console.log(error.response || error) + }) + } +} /** * Retrieve all tasks (up to the given limit) matching the given search * criteria, which should at least include a boundingBox field, and may * optionally include a filters field with additional constraints */ -export const fetchBoundedTasks = function(criteria, limit=50, skipDispatch=false, ignoreLocked=true, withGeometries) { +export function fetchBoundedTasks(criteria, limit=50, skipDispatch=false, ignoreLocked=true, withGeometries) { return function(dispatch) { if (!skipDispatch) { // The map is either showing task clusters or bounded tasks so we shouldn't @@ -101,7 +195,7 @@ export const fetchBoundedTasks = function(criteria, limit=50, skipDispatch=false // If we are searching within map bounds we need to ensure the parent // challenge is also within those bounds if (filters.location === CHALLENGE_LOCATION_WITHIN_MAPBOUNDS) { - if (_isArray(criteria.boundingBox)) { + if (Array.isArray(criteria.boundingBox)) { searchParameters.bb = criteria.boundingBox.join(',') } else { @@ -177,7 +271,7 @@ export const currentBoundedTasks = function(state={}, action) { updatedTasks.loading = true } else { - updatedTasks.tasks = _isArray(action.tasks) ? action.tasks : [] + updatedTasks.tasks = Array.isArray(action.tasks) ? action.tasks : [] updatedTasks.loading = false updatedTasks.totalCount = action.totalCount }