Skip to content

Commit

Permalink
Merge pull request #212 from mapbox/[email protected]
Browse files Browse the repository at this point in the history
Event logging update
  • Loading branch information
Scott Farley authored Mar 25, 2019
2 parents 41dc4f9 + f6ad56f commit 4431b04
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 71 deletions.
1 change: 1 addition & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ A geocoder component using Mapbox Geocoding API
- `options.localGeocoder` **[Function][55]?** A function accepting the query string which performs local geocoding to supplement results from the Mapbox Geocoding API. Expected to return an Array of GeoJSON Features in the [Carmen GeoJSON][56] format.
- `options.reverseMode` **(`"distance"` \| `"score"`)** Set the factors that are used to sort nearby results. (optional, default `'distance'`)
- `options.reverseGeocode` **[boolean][52]?** Enable reverse geocoding. Defaults to false. Expects coordinates to be lat, lon.
- `options.enableEventLogging` **[Boolean][52]** Allow Mapbox to collect anonymous usage statistics from the plugin (optional, default `true`)
- `options.marker` **([Boolean][52] \| [Object][48])** If `true`, a [Marker][57] will be added to the map at the location of the user-selected result using a default set of Marker options. If the value is an object, the marker will be constructed using these options. If `false`, no marker will be added to the map. (optional, default `true`)
- `options.mapboxgl` **[Object][48]?** A [mapbox-gl][58] instance to use when creating [Markers][57]. Required if `options.marker` is true.
- `options.render` **[Function][55]?** A function that specifies how the results should be rendered in the dropdown menu
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
## Master

- `trackProximity` turned on by default [#195](https://github.com/mapbox/mapbox-gl-geocoder/issues/195)
- Mapbox events upgraded to v0.2.0 for better handling [#212](https://github.com/mapbox/mapbox-gl-geocoder/pull/212)
- Support for the Mapbox GL JS 0.47.0 API. This is compatible with 0.47.0 and later, and may not be compatible with earlier versions.
- Pass `flyTo` options to the map on result selection on both map#flyTo and map#fitBounds operations [#214](https://github.com/mapbox/mapbox-gl-geocoder/pull/214) and [#227](https://github.com/mapbox/mapbox-gl-geocoder/pull/227)
- Obtain language from user's browser settings [#195](https://github.com/mapbox/mapbox-gl-geocoder/issues/195)
Expand Down
165 changes: 118 additions & 47 deletions lib/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,22 @@ var nanoid = require('nanoid')
* Construct a new mapbox event client to send interaction events to the mapbox event service
* @param {Object} options options with which to create the service
* @param {String} options.accessToken the mapbox access token to make requests
* @param {Number} [options.flushInterval=1000] the number of ms after which to flush the event queue
* @param {Number} [options.maxQueueSize=100] the number of events to queue before flushing
* @private
*/
function MapboxEventManager(options) {
this.origin = options.origin || 'https://api.mapbox.com';
this.endpoint = '/events/v2';
this.endpoint = 'events/v2';
this.access_token = options.accessToken;
this.version = '0.0.1'
this.version = '0.2.0'
this.sessionID = this.generateSessionID();
this.userAgent = this.getUserAgent();

this.options = options;
this.send = this.send.bind(this);


// parse global options to be sent with each request
this.countries = (options.countries) ? options.countries.split(",") : null;
this.types = (options.types) ? options.types.split(",") : null;
Expand All @@ -22,47 +29,71 @@ function MapboxEventManager(options) {
this.limit = (options.limit) ? +options.limit : null;
this.locale = navigator.language || null;
this.enableEventLogging = this.shouldEnableLogging(options);
this.eventQueue = new Array();
this.flushInterval = options.flushInterval || 1000;
this.maxQueueSize = options.maxQueueSize || 100;
this.timer = (this.flushInterval) ? setTimeout(this.flush.bind(this), this.flushInterval) : null;
// keep some state to deduplicate requests if necessary
this.lastSentInput = "";
this.lastSentIndex = 0;
}

MapboxEventManager.prototype = {

/**
* Send a search.select event to the mapbox events service
* This event marks the array index of the item selected by the user out of the array of possible options
* @private
* @param {Object} selected the geojson feature selected by the user
* @param {Object} geocoder a mapbox-gl-geocoder instance
* @param {Function} callback a callback function to invoke once the event has been sent (optional)
* @returns {Promise}
*/
select: function (selected, geocoder, callback) {
* Send a search.select event to the mapbox events service
* This event marks the array index of the item selected by the user out of the array of possible options
* @private
* @param {Object} selected the geojson feature selected by the user
* @param {Object} geocoder a mapbox-gl-geocoder instance
* @returns {Promise}
*/
select: function(selected, geocoder){
var resultIndex = this.getSelectedIndex(selected, geocoder);
var payload = this.getEventPayload('search.select', geocoder);
payload.resultIndex = resultIndex;
payload.resultPlaceName = selected.place_name;
payload.resultId = selected.id;
if ((resultIndex === this.lastSentIndex && payload.queryString === this.lastSentInput) || resultIndex == -1) {
// don't log duplicate events if the user re-selected the same feature on the same search
if (callback) return callback();
else return;
return;
}
this.lastSentIndex = resultIndex;
this.lastSentInput = payload.queryString;
return this.send(payload, callback)
if (!payload.queryString) return; // will be rejected
return this.push(payload)
},

/**
* Send a search-start event to the mapbox events service
* This turnstile event marks when a user starts a new search
* Send a search-start event to the mapbox events service
* This turnstile event marks when a user starts a new search
* @private
* @param {Object} geocoder a mapbox-gl-geocoder instance
* @returns {Promise}
*/
start: function(geocoder){
var payload = this.getEventPayload('search.start', geocoder);
if (!payload.queryString) return; // will be rejected
return this.push(payload);
},

/**
* Send a search-keyevent event to the mapbox events service
* This event records each keypress in sequence
* @private
* @param {Object} geocoder a mapbox-gl-geocoder instance
* @param {Function} callback
* @returns {Promise}
* @param {Object} keyEvent the keydown event to log
* @param {Obeject} geocoder a mapbox-gl-geocoder instance
*
*/
start: function (geocoder, callback) {
var payload = this.getEventPayload('search.start', geocoder);
return this.send(payload, callback);
keyevent: function(keyEvent, geocoder){
//pass invalid event
if (!keyEvent.key) return;
// don't send events for keys that don't change the input
// TAB, ESC, LEFT, RIGHT, ENTER, UP, DOWN
if (keyEvent.metaKey || [9, 27, 37, 39, 13, 38, 40].indexOf(keyEvent.keyCode) !== -1) return;
var payload = this.getEventPayload('search.keystroke', geocoder);
payload.lastAction = keyEvent.key;
if (!payload.queryString) return; // will be rejected
return this.push(payload);
},

/**
Expand All @@ -72,27 +103,29 @@ MapboxEventManager.prototype = {
*
* @private
* @param {Object} payload the http POST body of the event
* @param {Function} [callback] a callback function to invoke when the send has completed
* @returns {Promise}
*/
send: function (payload, callback) {
if (!callback) callback = function () {
return
};
if (!this.enableEventLogging) {
return callback();
if (callback) return callback();
return;
}
var options = this.getRequestOptions(payload);
this.request(options, function (err) {
this.request(options, function(err){
if (err) return this.handleError(err, callback);
if (callback) return callback();
})
if (callback) {
return callback();
}
}.bind(this))
},
/**
* Get http request options
* @private
* @param {*} payload
*/
getRequestOptions: function (payload) {
getRequestOptions: function(payload){
if (!Array.isArray(payload)) payload = [payload];
var options = {
// events must be sent with POST
method: "POST",
Expand All @@ -101,7 +134,7 @@ MapboxEventManager.prototype = {
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify([payload]) //events are arrays
body:JSON.stringify(payload) //events are arrays
}
return options
},
Expand Down Expand Up @@ -134,7 +167,7 @@ MapboxEventManager.prototype = {
proximity: proximity,
limit: geocoder.options.limit,
// routing: search.routing, //todo --> add to plugin
queryString: geocoder.inputString,
queryString: (geocoder._inputEl) ? geocoder._inputEl.value : geocoder.inputString,
mapZoom: zoom,
keyboardLocale: this.locale
}
Expand All @@ -159,7 +192,8 @@ MapboxEventManager.prototype = {
}
}
};
xhttp.open(opts.method, opts.host + opts.path, true);

xhttp.open(opts.method, opts.host + '/' + opts.path, true);
for (var header in opts.headers){
var headerValue = opts.headers[header];
xhttp.setRequestHeader(header, headerValue)
Expand Down Expand Up @@ -194,13 +228,14 @@ MapboxEventManager.prototype = {
},

/**
* Get the 0-based numeric index of the item that the user selected out of the list of options
* @private
* @param {Object} selected the geojson feature selected by the user
* @param {Object} geocoder a Mapbox-GL-Geocoder instance
* @returns {Number} the index of the selected result
*/
getSelectedIndex: function (selected, geocoder) {
* Get the 0-based numeric index of the item that the user selected out of the list of options
* @private
* @param {Object} selected the geojson feature selected by the user
* @param {Object} geocoder a Mapbox-GL-Geocoder instance
* @returns {Number} the index of the selected result
*/
getSelectedIndex: function(selected, geocoder){
if (!geocoder._typeahead) return;
var results = geocoder._typeahead.data;
var selectedID = selected.id;
var resultIDs = results.map(function (feature) {
Expand All @@ -211,17 +246,53 @@ MapboxEventManager.prototype = {
},

/**
* Check whether events should be logged
* Clients using a localGeocoder or an origin other than mapbox should not have events logged
* @private
*/
shouldEnableLogging: function (options) {
if (this.origin.indexOf('api.mapbox.com') == -1) return false;
* Check whether events should be logged
* Clients using a localGeocoder or an origin other than mapbox should not have events logged
* @private
*/
shouldEnableLogging: function(options){
if (options.enableEventLogging === false) return false;
if (options.origin && options.origin.indexOf('api.mapbox.com') == -1) return false;
// hard to make sense of events when a local instance is suplementing results from origin
if (options.localGeocoder) return false;
// hard to make sense of events when a custom filter is in use
if (options.filter) return false;
return true;
},

/**
* Flush out the event queue by sending events to the events service
* @private
*/
flush: function(){
if (this.eventQueue.length > 0){
this.send(this.eventQueue);
this.eventQueue = new Array();
}
// //reset the timer
if (this.timer) clearTimeout(this.timer);
if (this.flushInterval) this.timer = setTimeout(this.flush.bind(this), this.flushInterval)
},

/**
* Push event into the pending queue
* @param {Object} evt the event to send to the events service
* @param {Boolean} forceFlush indicates that the event queue should be flushed after adding this event regardless of size of the queue
* @private
*/
push: function(evt, forceFlush){
this.eventQueue.push(evt);
if (this.eventQueue.length >= this.maxQueueSize || forceFlush){
this.flush();
}
},

/**
* Flush any remaining events from the queue before it is removed
* @private
*/
remove: function(){
this.flush();
}
}

Expand Down
6 changes: 6 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ var geocoderService;
* @param {Function} [options.localGeocoder] A function accepting the query string which performs local geocoding to supplement results from the Mapbox Geocoding API. Expected to return an Array of GeoJSON Features in the [Carmen GeoJSON](https://github.com/mapbox/carmen/blob/master/carmen-geojson.md) format.
* @param {'distance'|'score'} [options.reverseMode='distance'] - Set the factors that are used to sort nearby results.
* @param {boolean} [options.reverseGeocode] Enable reverse geocoding. Defaults to false. Expects coordinates to be lat, lon.
* @param {Boolean} [options.enableEventLogging=true] Allow Mapbox to collect anonymous usage statistics from the plugin
* @param {Boolean|Object} [options.marker=true] If `true`, a [Marker](https://docs.mapbox.com/mapbox-gl-js/api/#marker) will be added to the map at the location of the user-selected result using a default set of Marker options. If the value is an object, the marker will be constructed using these options. If `false`, no marker will be added to the map.
* @param {Object} [options.mapboxgl] A [mapbox-gl](https://github.com/mapbox/mapbox-gl-js) instance to use when creating [Markers](https://docs.mapbox.com/mapbox-gl-js/api/#marker). Required if `options.marker` is true.
* @param {Function} [options.render] A function that specifies how the results should be rendered in the dropdown menu
Expand Down Expand Up @@ -71,6 +72,7 @@ MapboxGeocoder.prototype = {
reverseGeocode: false,
limit: 5,
origin: 'https://api.mapbox.com',
enableEventLogging: true,
marker: true,
mapboxgl: null,
collapsed: false,
Expand Down Expand Up @@ -129,6 +131,9 @@ MapboxGeocoder.prototype = {
this._inputEl.addEventListener('change', this._onChange);
this.container.addEventListener('mouseenter', this._showButton);
this.container.addEventListener('mouseleave', this._hideButton);
this._inputEl.addEventListener('keyup', function(e){
this.eventManager.keyevent(e, this);
}.bind(this));

var actions = document.createElement('div');
actions.classList.add('geocoder-pin-right');
Expand Down Expand Up @@ -757,6 +762,7 @@ MapboxGeocoder.prototype = {
*/
off: function(type, fn) {
this._eventEmitter.removeListener(type, fn);
this.eventManager.remove();
return this;
}
};
Expand Down
Loading

0 comments on commit 4431b04

Please sign in to comment.