Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Event logging update #212

Merged
merged 13 commits into from
Mar 25, 2019
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;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment with the actual keys that these codes correspond to

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(){
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JS Doc says this function should have a callback param

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