diff --git a/src/backend/data/schema.js b/src/backend/data/schema.js index 7003c46f..e156650d 100644 --- a/src/backend/data/schema.js +++ b/src/backend/data/schema.js @@ -456,6 +456,14 @@ const GraphQLListContainer = new GraphQLObjectType({ interfaces: [nodeInterface] }) +const GeoPoint = new GraphQLInputObjectType({ + name: 'GeoPoint', + fields: { + lat: {type: GraphQLFloat}, + lon: {type: GraphQLFloat}, + } +}) + const GraphQLUser = new GraphQLObjectType({ name: 'User', description: 'User of ground control', @@ -554,10 +562,13 @@ const GraphQLUser = new GraphQLObjectType({ intervieweeForCallAssignment: { type: GraphQLPerson, args: { - callAssignmentId: {type: GraphQLString} + callAssignmentId: {type: GraphQLString}, + center: {type: GeoPoint}, + radiusMeters: {type: GraphQLFloat} }, - resolve: async(user, {callAssignmentId}, {rootValue}) => { - + resolve: async( + user, {callAssignmentId, center, radiusMeters}, {rootValue} + ) => { let localCallAssignmentId = fromGlobalId(callAssignmentId) if (localCallAssignmentId.type !== 'CallAssignment') localCallAssignmentId = callAssignmentId @@ -647,6 +658,21 @@ const GraphQLUser = new GraphQLObjectType({ if (userAddress) query = query.whereNot('bsd_people.cons_id', userAddress.cons_id) + // Filter by distance from a geographical point. + // Spatial ref 4326 is WGS 84, in degrees + // Spatial ref 900913 is Google Web Mercator, in meters + if (center && radiusMeters > 0) { + query = query.whereRaw(` + ST_DWithin(bsd_addresses.geom, + ST_Transform( + ST_SetSRID(ST_MakePoint(${center.lon}, ${center.lat}), 4326), + 900913 + ), + ${radiusMeters} + ) + `) + } + log.info(`Running query: ${query}`) let person = await query @@ -783,9 +809,10 @@ const GraphQLPerson = new GraphQLObjectType({ type: new GraphQLList(GraphQLEvent), args: { within: {type: GraphQLInt}, - type: {type: GraphQLString} + type: {type: GraphQLString}, + officialOnly: {type: GraphQLBoolean} }, - resolve: async(person, {within, type}, {rootValue}) => { + resolve: async(person, {within, type, officialOnly}, {rootValue}) => { let address = await getPrimaryAddress(person); let boundingDistance = within / 69 let eventTypes = null @@ -801,6 +828,10 @@ const GraphQLPerson = new GraphQLObjectType({ .where('flag_approval', false) .whereNot('is_searchable', 0) + if (officialOnly) { + query = query.where('is_official', true) + } + if (eventTypes) query = query.whereIn('event_type_id', eventTypes.map((type) => type.event_type_id)) diff --git a/src/frontend/components/CallAssignment.js b/src/frontend/components/CallAssignment.js index 3ebc24a9..45524011 100644 --- a/src/frontend/components/CallAssignment.js +++ b/src/frontend/components/CallAssignment.js @@ -12,6 +12,8 @@ import SubmitCallSurvey from '../mutations/SubmitCallSurvey' import CallStatsBar from './CallStatsBar' import MutationHandler from './MutationHandler' import {PhoneNumberFormat, PhoneNumberUtil} from 'google-libphonenumber' +import qs from 'qs' +import convertType from '../helpers/convertType' const phoneUtil = PhoneNumberUtil.getInstance() const SurveyRenderers = { @@ -408,8 +410,18 @@ ${userFirstName}` } } +// Convert the query parameters 'lat', 'lon', and 'miles' into API arguments +// for the center and radius of the filter region. +const {lat, lon, miles} = convertType(qs.parse(location.search.substr(1))) +const center = isFinite(lat) && isFinite(lon) ? {lat: lat, lon: lon} : null +const radiusMeters = isFinite(miles) ? miles * 1609.34 : null + export default Relay.createContainer(CallAssignment, { - initialVariables: { id: '' }, + initialVariables: { + id: '', + center: center, + radiusMeters: radiusMeters + }, fragments: { callAssignment: () => Relay.QL` fragment on CallAssignment { @@ -428,9 +440,11 @@ export default Relay.createContainer(CallAssignment, { fragment on User { id firstName - allCallsMade:callsMade(forAssignmentId:$id) - completedCallsMade:callsMade(forAssignmentId:$id,completed:true) - intervieweeForCallAssignment(callAssignmentId:$id) { + allCallsMade: callsMade(forAssignmentId: $id) + completedCallsMade: callsMade(forAssignmentId: $id, completed: true) + intervieweeForCallAssignment( + callAssignmentId: $id, center: $center, radiusMeters: $radiusMeters + ) { id prefix firstName diff --git a/src/frontend/components/survey-renderers/PhonebankRSVPSurvey.js b/src/frontend/components/survey-renderers/PhonebankRSVPSurvey.js index 71f314c9..da8ce413 100644 --- a/src/frontend/components/survey-renderers/PhonebankRSVPSurvey.js +++ b/src/frontend/components/survey-renderers/PhonebankRSVPSurvey.js @@ -9,6 +9,8 @@ import FontIcon from 'material-ui/lib/font-icon' import SideBarLayout from '../SideBarLayout' import GCSelectField from '../forms/GCSelectField' import GCBooleanField from '../forms/GCBooleanField' +import qs from 'qs' +import convertType from '../../helpers/convertType' const WEEKDAY_DATE_FORMAT = 'dddd, MMMM Do' @@ -544,9 +546,14 @@ class PhonebankRSVPSurvey extends React.Component { } } +// Convert the query parameter 'official' into a boolean flag. +const {official} = convertType(qs.parse(location.search.substr(1))) +const officialOnly = official ? true : false + export default Relay.createContainer(PhonebankRSVPSurvey, { initialVariables: { - type: 'phonebank' + type: 'phonebank', + officialOnly: officialOnly }, fragments: { currentUser: () => Relay.QL` @@ -562,7 +569,7 @@ export default Relay.createContainer(PhonebankRSVPSurvey, { latitude longitude } - nearbyEvents(within:20, type:$type) { + nearbyEvents(within:20, type:$type, officialOnly:$officialOnly) { id eventIdObfuscated name