From 658218c3896b79a096d41d4e5666356301740f49 Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Sun, 15 May 2016 03:36:35 -0700 Subject: [PATCH 1/3] Filter interviewees by geo distance, using ?ll=...&miles=... --- src/backend/data/schema.js | 32 ++++++++++++++++++++--- src/frontend/components/CallAssignment.js | 23 +++++++++++++--- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/backend/data/schema.js b/src/backend/data/schema.js index 7003c46f..6efc7ff1 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 diff --git a/src/frontend/components/CallAssignment.js b/src/frontend/components/CallAssignment.js index 3ebc24a9..a2c81d2d 100644 --- a/src/frontend/components/CallAssignment.js +++ b/src/frontend/components/CallAssignment.js @@ -12,6 +12,7 @@ import SubmitCallSurvey from '../mutations/SubmitCallSurvey' import CallStatsBar from './CallStatsBar' import MutationHandler from './MutationHandler' import {PhoneNumberFormat, PhoneNumberUtil} from 'google-libphonenumber' +import qs from 'qs' const phoneUtil = PhoneNumberUtil.getInstance() const SurveyRenderers = { @@ -408,8 +409,20 @@ ${userFirstName}` } } +// Convert the query parameters 'll' and 'miles' into the API arguments +// for the center and radius of the filter region. +var {ll, miles} = qs.parse(location.search.substr(1)); +var llParts = (ll || '').split(',') +var center = llParts.length === 2 ? { + lat: Number(llParts[0]), lon: Number(llParts[1])} : null +var radiusMeters = miles ? Number(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 +441,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 From a0b90a0681698fa07f82e85b414bcc7832efb32f Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Tue, 17 May 2016 13:05:30 -0700 Subject: [PATCH 2/3] Split ?ll= into lat= and lon= query params. --- src/frontend/components/CallAssignment.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/frontend/components/CallAssignment.js b/src/frontend/components/CallAssignment.js index a2c81d2d..05a3eca1 100644 --- a/src/frontend/components/CallAssignment.js +++ b/src/frontend/components/CallAssignment.js @@ -13,6 +13,7 @@ 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 = { @@ -409,13 +410,11 @@ ${userFirstName}` } } -// Convert the query parameters 'll' and 'miles' into the API arguments +// Convert the query parameters 'lat', 'lon', and 'miles' into API arguments // for the center and radius of the filter region. -var {ll, miles} = qs.parse(location.search.substr(1)); -var llParts = (ll || '').split(',') -var center = llParts.length === 2 ? { - lat: Number(llParts[0]), lon: Number(llParts[1])} : null -var radiusMeters = miles ? Number(miles) * 1609.34 : null +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: { From f5ce9f5673d90ddb43ec5eabb66b364fd5896013 Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Sat, 21 May 2016 06:00:08 -0700 Subject: [PATCH 3/3] Filter just official events with ?official=true is given. --- src/backend/data/schema.js | 9 +++++++-- src/frontend/components/CallAssignment.js | 2 +- .../survey-renderers/PhonebankRSVPSurvey.js | 11 +++++++++-- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/backend/data/schema.js b/src/backend/data/schema.js index 6efc7ff1..e156650d 100644 --- a/src/backend/data/schema.js +++ b/src/backend/data/schema.js @@ -809,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 @@ -827,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 05a3eca1..45524011 100644 --- a/src/frontend/components/CallAssignment.js +++ b/src/frontend/components/CallAssignment.js @@ -412,7 +412,7 @@ ${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 {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 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