From f0cad585df828b942affe78dac32e3dae90ae5b2 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 distance from a point, based on query params. --- src/backend/data/schema.js | 31 ++++++++++++++++++++--- src/frontend/app.js | 1 + src/frontend/components/CallAssignment.js | 16 +++++++++--- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/backend/data/schema.js b/src/backend/data/schema.js index 7003c46f..a1daf085 100644 --- a/src/backend/data/schema.js +++ b/src/backend/data/schema.js @@ -554,10 +554,18 @@ const GraphQLUser = new GraphQLObjectType({ intervieweeForCallAssignment: { type: GraphQLPerson, args: { - callAssignmentId: {type: GraphQLString} + callAssignmentId: {type: GraphQLString}, + centerLatLon: {type: GraphQLString}, + // TODO(zestyping): It would be a better API if the radius were a float + // in meters, but I couldn't figure out how to convert the "miles" + // query param to a float before it got passed to resolve(). + radiusMiles: {type: GraphQLString} }, - resolve: async(user, {callAssignmentId}, {rootValue}) => { - + resolve: async( + user, + {callAssignmentId, centerLatLon, radiusMiles}, + {rootValue} + ) => { let localCallAssignmentId = fromGlobalId(callAssignmentId) if (localCallAssignmentId.type !== 'CallAssignment') localCallAssignmentId = callAssignmentId @@ -647,6 +655,23 @@ const GraphQLUser = new GraphQLObjectType({ if (userAddress) query = query.whereNot('bsd_people.cons_id', userAddress.cons_id) + // Filter by distance from a geographical point. + let coords = (centerLatLon || '').split(',') + let radiusMeters = (radiusMiles || 0) * 1609.34 + if (coords.length == 2 && radiusMeters > 0) { + let lat = coords[0] - 0 + let lon = coords[1] - 0 + query = query.whereRaw(` + ST_DWithin(bsd_addresses.geom, + ST_Transform( + ST_SetSRID(ST_MakePoint(${lon}, ${lat}), 4326), + 900913 + ), + ${radiusMeters} + ) + `) + } + log.info(`Running query: ${query}`) let person = await query diff --git a/src/frontend/app.js b/src/frontend/app.js index 563d4735..c1e42c95 100644 --- a/src/frontend/app.js +++ b/src/frontend/app.js @@ -204,6 +204,7 @@ ReactDOM.render( ...CallAssignmentQueries, ...CurrentUserQueries }} + queryParams={['ll', 'miles']} renderLoading={() => } /> diff --git a/src/frontend/components/CallAssignment.js b/src/frontend/components/CallAssignment.js index 3ebc24a9..423833aa 100644 --- a/src/frontend/components/CallAssignment.js +++ b/src/frontend/components/CallAssignment.js @@ -409,7 +409,11 @@ ${userFirstName}` } export default Relay.createContainer(CallAssignment, { - initialVariables: { id: '' }, + initialVariables: { + id: '', + ll: null, // URL query parameter: point in lat,lon format + miles: null // URL query parameter: radius in miles + }, fragments: { callAssignment: () => Relay.QL` fragment on CallAssignment { @@ -428,9 +432,13 @@ 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, + centerLatLon: $ll, + radiusMiles: $miles + ) { id prefix firstName From 80ea640b4c3b5d34bd1285e7cd284427c316cea2 Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Tue, 17 May 2016 00:51:43 -0700 Subject: [PATCH 2/3] Represent center as a GeoPoint and radius as a GraphQLFloat. --- src/backend/data/schema.js | 29 ++++++++++++----------- src/frontend/components/CallAssignment.js | 26 ++++++++++++++++---- src/frontend/helpers/convertType.js | 26 ++++++++++++++++++++ src/frontend/helpers/getDefaultQuery.js | 18 ++++++++++++++ 4 files changed, 81 insertions(+), 18 deletions(-) create mode 100644 src/frontend/helpers/convertType.js create mode 100644 src/frontend/helpers/getDefaultQuery.js diff --git a/src/backend/data/schema.js b/src/backend/data/schema.js index a1daf085..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', @@ -555,16 +563,11 @@ const GraphQLUser = new GraphQLObjectType({ type: GraphQLPerson, args: { callAssignmentId: {type: GraphQLString}, - centerLatLon: {type: GraphQLString}, - // TODO(zestyping): It would be a better API if the radius were a float - // in meters, but I couldn't figure out how to convert the "miles" - // query param to a float before it got passed to resolve(). - radiusMiles: {type: GraphQLString} + center: {type: GeoPoint}, + radiusMeters: {type: GraphQLFloat} }, resolve: async( - user, - {callAssignmentId, centerLatLon, radiusMiles}, - {rootValue} + user, {callAssignmentId, center, radiusMeters}, {rootValue} ) => { let localCallAssignmentId = fromGlobalId(callAssignmentId) if (localCallAssignmentId.type !== 'CallAssignment') @@ -656,15 +659,13 @@ const GraphQLUser = new GraphQLObjectType({ query = query.whereNot('bsd_people.cons_id', userAddress.cons_id) // Filter by distance from a geographical point. - let coords = (centerLatLon || '').split(',') - let radiusMeters = (radiusMiles || 0) * 1609.34 - if (coords.length == 2 && radiusMeters > 0) { - let lat = coords[0] - 0 - let lon = coords[1] - 0 + // 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(${lon}, ${lat}), 4326), + ST_SetSRID(ST_MakePoint(${center.lon}, ${center.lat}), 4326), 900913 ), ${radiusMeters} diff --git a/src/frontend/components/CallAssignment.js b/src/frontend/components/CallAssignment.js index 423833aa..3369a6dc 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 getDefaultQuery from '../helpers/getDefaultQuery' const phoneUtil = PhoneNumberUtil.getInstance() const SurveyRenderers = { @@ -408,11 +409,28 @@ ${userFirstName}` } } +// Convert the query parameters 'll' and 'miles' into the API arguments +// for the center and radius of the filter region. +var center = null; +var radiusMeters = null; + +var queryParams = getDefaultQuery({ + ll: null, // URL query parameter: point in lat,lon format + miles: null // URL query parameter: radius in miles +}) +var llParts = (queryParams.ll || '').split(',') +if (llParts.length === 2) { + center = {lat: Number(llParts[0]), lon: Number(llParts[1])} +} +if (queryParams.miles) { + radiusMeters = Number(queryParams.miles) * 1609.34 +} + export default Relay.createContainer(CallAssignment, { initialVariables: { id: '', - ll: null, // URL query parameter: point in lat,lon format - miles: null // URL query parameter: radius in miles + center: center, + radiusMeters: radiusMeters }, fragments: { callAssignment: () => Relay.QL` @@ -436,8 +454,8 @@ export default Relay.createContainer(CallAssignment, { completedCallsMade: callsMade(forAssignmentId: $id, completed: true) intervieweeForCallAssignment( callAssignmentId: $id, - centerLatLon: $ll, - radiusMiles: $miles + center: $center, + radiusMeters: $radiusMeters ) { id prefix diff --git a/src/frontend/helpers/convertType.js b/src/frontend/helpers/convertType.js new file mode 100644 index 00000000..2b389e92 --- /dev/null +++ b/src/frontend/helpers/convertType.js @@ -0,0 +1,26 @@ +const convertType = (value) => { + if (typeof value === 'object'){ + let updatedValue = {} + Object.keys(value).forEach((key) => { + const currentValue = convertType(value[key]) + if (currentValue != undefined) + value[key] = currentValue + }) + return value + } + else if (value === 'none') + return null + else if (value === 'true') + return true + else if (value === 'false') + return false + else if (value != '' && !isNaN(value) && String(Number(value)) === value) + return Number(value) + else if (value) + return String(value) + else { + return undefined + } +} + +export default convertType diff --git a/src/frontend/helpers/getDefaultQuery.js b/src/frontend/helpers/getDefaultQuery.js new file mode 100644 index 00000000..1b178326 --- /dev/null +++ b/src/frontend/helpers/getDefaultQuery.js @@ -0,0 +1,18 @@ +import qs from 'qs' +import convertType from './convertType' + +export default function(defaultParams) { + const hashParams = convertType(qs.parse(location.hash.substr(1), { strictNullHandling: true })) + if (hashParams.query){ + try { + let newQueryParams = {...defaultParams, ...hashParams.query} + newQueryParams.filters = {...defaultParams.filters, ...hashParams.query.filters} + return newQueryParams + } + catch(ex) { + console.error('Invalid query parameters', ex) + } + } + + return defaultParams +} From 7b8c2622073cf2ac1437561e9092b2361143af64 Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Tue, 17 May 2016 01:03:20 -0700 Subject: [PATCH 3/3] Make getDefaultQuery return the parsed params instead of the defaults. --- src/frontend/helpers/getDefaultQuery.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/helpers/getDefaultQuery.js b/src/frontend/helpers/getDefaultQuery.js index 1b178326..aa69548f 100644 --- a/src/frontend/helpers/getDefaultQuery.js +++ b/src/frontend/helpers/getDefaultQuery.js @@ -14,5 +14,5 @@ export default function(defaultParams) { } } - return defaultParams + return {...defaultParams, ...hashParams} }