From 0693a6fb88239c67ac2a441c35f30b329c1cc883 Mon Sep 17 00:00:00 2001 From: Krishna Rajendran Date: Mon, 20 Apr 2020 14:58:20 -0700 Subject: [PATCH] Invoke logEvent callbacks for each event when events are actually sent --- src/amplitude-client.js | 156 ++++++++++++++++++++------------------- test/amplitude-client.js | 101 +++++++++++++++---------- test/amplitude.js | 78 ++++++++------------ 3 files changed, 174 insertions(+), 161 deletions(-) diff --git a/src/amplitude-client.js b/src/amplitude-client.js index d400aab8..34fdc09b 100644 --- a/src/amplitude-client.js +++ b/src/amplitude-client.js @@ -167,21 +167,8 @@ AmplitudeClient.prototype.init = function init(apiKey, opt_userId, opt_config, o // load unsent events and identifies before any attempt to log new ones if (this.options.saveEvents) { - // validate event properties for unsent events - for (let i = 0; i < this._unsentEvents.length; i++) { - var eventProperties = this._unsentEvents[i].event_properties; - var groups = this._unsentEvents[i].groups; - this._unsentEvents[i].event_properties = utils.validateProperties(eventProperties); - this._unsentEvents[i].groups = utils.validateGroups(groups); - } - - // validate user properties for unsent identifys - for (let j = 0; j < this._unsentIdentifys.length; j++) { - var userProperties = this._unsentIdentifys[j].user_properties; - var identifyGroups = this._unsentIdentifys[j].groups; - this._unsentIdentifys[j].user_properties = utils.validateProperties(userProperties); - this._unsentIdentifys[j].groups = utils.validateGroups(identifyGroups); - } + _validateUnsentEventQueue(this._unsentEvents); + _validateUnsentEventQueue(this._unsentIdentifys); } this._lastEventTime = now; @@ -212,8 +199,8 @@ AmplitudeClient.prototype.init = function init(apiKey, opt_userId, opt_config, o } } if (this.options.saveEvents) { - this._unsentEvents = this._parseSavedUnsentEventsString(values[1]).concat(this._unsentEvents); - this._unsentIdentifys = this._parseSavedUnsentEventsString(values[2]).concat(this._unsentIdentifys); + this._unsentEvents = this._parseSavedUnsentEventsString(values[1]).map(event => ({event})).concat(this._unsentEvents); + this._unsentIdentifys = this._parseSavedUnsentEventsString(values[2]).map(event => ({event})).concat(this._unsentIdentifys); } if (DeviceInfo) { Promise.all([ @@ -247,8 +234,8 @@ AmplitudeClient.prototype.init = function init(apiKey, opt_userId, opt_config, o }); } else { if (this.options.saveEvents) { - this._unsentEvents = this._loadSavedUnsentEvents(this.options.unsentKey).concat(this._unsentEvents); - this._unsentIdentifys = this._loadSavedUnsentEvents(this.options.unsentIdentifyKey).concat(this._unsentIdentifys); + this._unsentEvents = this._loadSavedUnsentEvents(this.options.unsentKey).map(event => ({event})).concat(this._unsentEvents); + this._unsentIdentifys = this._loadSavedUnsentEvents(this.options.unsentIdentifyKey).map(event => ({event})).concat(this._unsentIdentifys); } initFromStorage(); this.runQueuedFunctions(); @@ -262,6 +249,19 @@ AmplitudeClient.prototype.init = function init(apiKey, opt_userId, opt_config, o } }; +// validate properties for unsent events +const _validateUnsentEventQueue = (queue) => { + for (let i = 0; i < queue.length; i++) { + const userProperties = queue[i].event.user_properties; + const eventProperties = queue[i].event.event_properties; + const groups = queue[i].event.groups; + + queue[i].event.user_properties = utils.validateProperties(userProperties); + queue[i].event.event_properties = utils.validateProperties(eventProperties); + queue[i].event.groups = utils.validateGroups(groups); + } +}; + /** * @private */ @@ -486,20 +486,20 @@ AmplitudeClient.prototype._unsentCount = function _unsentCount() { * Send events if ready. Returns true if events are sent. * @private */ -AmplitudeClient.prototype._sendEventsIfReady = function _sendEventsIfReady(callback) { +AmplitudeClient.prototype._sendEventsIfReady = function _sendEventsIfReady() { if (this._unsentCount() === 0) { return false; } // if batching disabled, send any unsent events immediately if (!this.options.batchEvents) { - this.sendEvents(callback); + this.sendEvents(); return true; } // if batching enabled, check if min threshold met for batch size if (this._unsentCount() >= this.options.eventUploadThreshold) { - this.sendEvents(callback); + this.sendEvents(); return true; } @@ -739,18 +739,22 @@ AmplitudeClient.prototype._saveReferrer = function _saveReferrer(referrer) { */ AmplitudeClient.prototype.saveEvents = function saveEvents() { try { + const serializedUnsentEvents = JSON.stringify(this._unsentEvents.map(({event}) => event)); + if (AsyncStorage) { - AsyncStorage.setItem(this.options.unsentKey + this._storageSuffix, JSON.stringify(this._unsentEvents)); + AsyncStorage.setItem(this.options.unsentKey + this._storageSuffix, serializedUnsentEvents); } else { - this._setInStorage(localStorage, this.options.unsentKey, JSON.stringify(this._unsentEvents)); + this._setInStorage(localStorage, this.options.unsentKey, serializedUnsentEvents); } } catch (e) {} try { + const serializedIdentifys = JSON.stringify(this._unsentIdentifys.map(unsentIdentify => unsentIdentify.event)); + if (AsyncStorage) { - AsyncStorage.setItem(this.options.unsentIdentifyKey + this._storageSuffix, JSON.stringify(this._unsentIdentifys)); + AsyncStorage.setItem(this.options.unsentIdentifyKey + this._storageSuffix, serializedIdentifys); } else { - this._setInStorage(localStorage, this.options.unsentIdentifyKey, JSON.stringify(this._unsentIdentifys)); + this._setInStorage(localStorage, this.options.unsentIdentifyKey, serializedIdentifys); } } catch (e) {} }; @@ -1183,10 +1187,10 @@ AmplitudeClient.prototype._logEvent = function _logEvent(eventType, eventPropert }; if (eventType === Constants.IDENTIFY_EVENT || eventType === Constants.GROUP_IDENTIFY_EVENT) { - this._unsentIdentifys.push(event); + this._unsentIdentifys.push({event, callback}); this._limitEventsQueued(this._unsentIdentifys); } else { - this._unsentEvents.push(event); + this._unsentEvents.push({event, callback}); this._limitEventsQueued(this._unsentEvents); } @@ -1194,9 +1198,7 @@ AmplitudeClient.prototype._logEvent = function _logEvent(eventType, eventPropert this.saveEvents(); } - if (!this._sendEventsIfReady(callback) && type(callback) === 'function') { - callback(0, 'No request sent', {reason: 'No events to send or upload queued'}); - } + this._sendEventsIfReady(callback); return eventId; } catch (e) { @@ -1400,9 +1402,9 @@ if (BUILD_COMPAT_2_0) { * Remove events in storage with event ids up to and including maxEventId. * @private */ -AmplitudeClient.prototype.removeEvents = function removeEvents(maxEventId, maxIdentifyId) { - _removeEvents(this, '_unsentEvents', maxEventId); - _removeEvents(this, '_unsentIdentifys', maxIdentifyId); +AmplitudeClient.prototype.removeEvents = function removeEvents(maxEventId, maxIdentifyId, status, response) { + _removeEvents(this, '_unsentEvents', maxEventId, status, response); + _removeEvents(this, '_unsentIdentifys', maxIdentifyId, status, response); }; /** @@ -1410,15 +1412,21 @@ AmplitudeClient.prototype.removeEvents = function removeEvents(maxEventId, maxId * Does a true filter in case events get out of order or old events are removed. * @private */ -var _removeEvents = function _removeEvents(scope, eventQueue, maxId) { +var _removeEvents = function _removeEvents(scope, eventQueue, maxId, status, response) { if (maxId < 0) { return; } var filteredEvents = []; for (var i = 0; i < scope[eventQueue].length || 0; i++) { - if (scope[eventQueue][i].event_id > maxId) { - filteredEvents.push(scope[eventQueue][i]); + const unsentEvent = scope[eventQueue][i]; + + if (unsentEvent.event.event_id > maxId) { + filteredEvents.push(unsentEvent); + } else { + if (unsentEvent.callback) { + unsentEvent.callback(status, response); + } } } scope[eventQueue] = filteredEvents; @@ -1428,32 +1436,26 @@ var _removeEvents = function _removeEvents(scope, eventQueue, maxId) { * Send unsent events. Note: this is called automatically after events are logged if option batchEvents is false. * If batchEvents is true, then events are only sent when batch criterias are met. * @private - * @param {Amplitude~eventCallback} callback - (optional) callback to run after events are sent. - * Note the server response code and response body are passed to the callback as input arguments. */ -AmplitudeClient.prototype.sendEvents = function sendEvents(callback) { +AmplitudeClient.prototype.sendEvents = function sendEvents() { if (!this._apiKeySet('sendEvents()')) { - if (type(callback) === 'function') { - callback(0, 'No request sent', {reason: 'API key not set'}); - } + this.removeEvents(Infinity, Infinity, 0, 'No request sent', {reason: 'API key not set'}); return; } + if (this.options.optOut) { - if (type(callback) === 'function') { - callback(0, 'No request sent', {reason: 'optOut is set to true'}); - } + this.removeEvents(Infinity, Infinity, 0, 'No request sent', {reason: 'Opt out is set to true'}); return; } + + // How is it possible to get into this state? if (this._unsentCount() === 0) { - if (type(callback) === 'function') { - callback(0, 'No request sent', {reason: 'No events to send'}); - } return; } + + // We only make one request at a time. sendEvents will be invoked again once + // the last request completes. if (this._sending) { - if (type(callback) === 'function') { - callback(0, 'No request sent', {reason: 'Request already in progress. Events will be sent once this request is complete'}); - } return; } @@ -1466,7 +1468,7 @@ AmplitudeClient.prototype.sendEvents = function sendEvents(callback) { var mergedEvents = this._mergeEventsAndIdentifys(numEvents); var maxEventId = mergedEvents.maxEventId; var maxIdentifyId = mergedEvents.maxIdentifyId; - var events = JSON.stringify(mergedEvents.eventsToSend); + var events = JSON.stringify(mergedEvents.eventsToSend.map(({event}) => event)); var uploadTime = new Date().getTime(); var data = { @@ -1482,7 +1484,7 @@ AmplitudeClient.prototype.sendEvents = function sendEvents(callback) { scope._sending = false; try { if (status === 200 && response === 'success') { - scope.removeEvents(maxEventId, maxIdentifyId); + scope.removeEvents(maxEventId, maxIdentifyId, status, response); // Update the event cache after the removal of sent events. if (scope.options.saveEvents) { @@ -1490,25 +1492,27 @@ AmplitudeClient.prototype.sendEvents = function sendEvents(callback) { } // Send more events if any queued during previous send. - if (!scope._sendEventsIfReady(callback) && type(callback) === 'function') { - callback(status, response); - } + scope._sendEventsIfReady(); // handle payload too large } else if (status === 413) { // utils.log('request too large'); // Can't even get this one massive event through. Drop it, even if it is an identify. if (scope.options.uploadBatchSize === 1) { - scope.removeEvents(maxEventId, maxIdentifyId); + scope.removeEvents(maxEventId, maxIdentifyId, status, response); } // The server complained about the length of the request. Backoff and try again. scope.options.uploadBatchSize = Math.ceil(numEvents / 2); - scope.sendEvents(callback); + scope.sendEvents(); - } else if (type(callback) === 'function') { // If server turns something like a 400 - callback(status, response); } + // else { + // all the events are still queued, and will be retried when the next + // event is sent In the interest of debugging, it would be nice to have + // something like an event emitter for a better debugging experince + // here. + // } } catch (e) { // utils.log('failed upload'); } @@ -1528,9 +1532,9 @@ AmplitudeClient.prototype._mergeEventsAndIdentifys = function _mergeEventsAndIde var maxIdentifyId = -1; while (eventsToSend.length < numEvents) { - var event; - var noIdentifys = identifyIndex >= this._unsentIdentifys.length; - var noEvents = eventIndex >= this._unsentEvents.length; + let unsentEvent; + let noIdentifys = identifyIndex >= this._unsentIdentifys.length; + let noEvents = eventIndex >= this._unsentEvents.length; // case 0: no events or identifys left // note this should not happen, this means we have less events and identifys than expected @@ -1541,29 +1545,29 @@ AmplitudeClient.prototype._mergeEventsAndIdentifys = function _mergeEventsAndIde // case 1: no identifys - grab from events else if (noIdentifys) { - event = this._unsentEvents[eventIndex++]; - maxEventId = event.event_id; + unsentEvent = this._unsentEvents[eventIndex++]; + maxEventId = unsentEvent.event.event_id; // case 2: no events - grab from identifys } else if (noEvents) { - event = this._unsentIdentifys[identifyIndex++]; - maxIdentifyId = event.event_id; + unsentEvent = this._unsentIdentifys[identifyIndex++]; + maxIdentifyId = unsentEvent.event.event_id; // case 3: need to compare sequence numbers } else { // events logged before v2.5.0 won't have a sequence number, put those first - if (!('sequence_number' in this._unsentEvents[eventIndex]) || - this._unsentEvents[eventIndex].sequence_number < - this._unsentIdentifys[identifyIndex].sequence_number) { - event = this._unsentEvents[eventIndex++]; - maxEventId = event.event_id; + if (!('sequence_number' in this._unsentEvents[eventIndex].event) || + this._unsentEvents[eventIndex].event.sequence_number < + this._unsentIdentifys[identifyIndex].event.sequence_number) { + unsentEvent = this._unsentEvents[eventIndex++]; + maxEventId = unsentEvent.event.event_id; } else { - event = this._unsentIdentifys[identifyIndex++]; - maxIdentifyId = event.event_id; + unsentEvent = this._unsentIdentifys[identifyIndex++]; + maxIdentifyId = unsentEvent.event.event_id; } } - eventsToSend.push(event); + eventsToSend.push(unsentEvent); } return { diff --git a/test/amplitude-client.js b/test/amplitude-client.js index 4497b2da..6e2712cc 100644 --- a/test/amplitude-client.js +++ b/test/amplitude-client.js @@ -450,8 +450,8 @@ describe('AmplitudeClient', function() { amplitude2.init(apiKey, null, {batchEvents: true}); // check event loaded into memory - assert.deepEqual(amplitude2._unsentEvents, JSON.parse(existingEvent)); - assert.deepEqual(amplitude2._unsentIdentifys, JSON.parse(existingIdentify)); + assert.deepEqual(amplitude2._unsentEvents.map(({event}) => event), JSON.parse(existingEvent)); + assert.deepEqual(amplitude2._unsentIdentifys.map(({event}) => event), JSON.parse(existingIdentify)); // check local storage keys are still same for default instance assert.equal(localStorage.getItem('amplitude_unsent_' + apiKey), existingEvent); @@ -479,8 +479,8 @@ describe('AmplitudeClient', function() { amplitude2.init(apiKey, null, {batchEvents: true}); // check event loaded into memory - assert.deepEqual(amplitude2._unsentEvents, JSON.parse(existingEvent)); - assert.deepEqual(amplitude2._unsentIdentifys, JSON.parse(existingIdentify)); + assert.deepEqual(amplitude2._unsentEvents.map(({event}) => event), JSON.parse(existingEvent)); + assert.deepEqual(amplitude2._unsentIdentifys.map(({event}) => event), JSON.parse(existingIdentify)); // check local storage keys are still same assert.equal(localStorage.getItem('amplitude_unsent_' + apiKey +'_new_app'), existingEvent); @@ -516,8 +516,8 @@ describe('AmplitudeClient', function() { } // check that event loaded into memory - assert.deepEqual(amplitude2._unsentEvents[0].event_properties, {}); - assert.deepEqual(amplitude2._unsentEvents[1].event_properties, expected); + assert.deepEqual(amplitude2._unsentEvents[0].event.event_properties, {}); + assert.deepEqual(amplitude2._unsentEvents[1].event.event_properties, expected); }); it('should validate user properties when loading saved identifys from localStorage', function() { @@ -545,7 +545,7 @@ describe('AmplitudeClient', function() { } // check that event loaded into memory - assert.deepEqual(amplitude2._unsentIdentifys[0].user_properties, {'$set': expected}); + assert.deepEqual(amplitude2._unsentIdentifys[0].event.user_properties, {'$set': expected}); }); it ('should load saved events from localStorage and send events for default instance', function() { @@ -660,8 +660,8 @@ it ('should load saved events from localStorage new keys and send events', funct } // check that event loaded into memory - assert.deepEqual(amplitude2._unsentEvents[0].event_properties, {}); - assert.deepEqual(amplitude2._unsentEvents[1].event_properties, expected); + assert.deepEqual(amplitude2._unsentEvents[0].event.event_properties, {}); + assert.deepEqual(amplitude2._unsentEvents[1].event.event_properties, expected); }); it('should not load saved events from another instances\'s localStorage', function() { @@ -895,12 +895,12 @@ describe('setVersionName', function() { amplitude.init(apiKey, null, {batchEvents: true}); amplitude.setVersionName('testVersionName1'); amplitude.logEvent('testEvent1'); - assert.equal(amplitude._unsentEvents[0].version_name, 'testVersionName1'); + assert.equal(amplitude._unsentEvents[0].event.version_name, 'testVersionName1'); // should ignore non-string values amplitude.setVersionName(15000); amplitude.logEvent('testEvent2'); - assert.equal(amplitude._unsentEvents[1].version_name, 'testVersionName1'); + assert.equal(amplitude._unsentEvents[1].event.version_name, 'testVersionName1'); }); }); @@ -1527,7 +1527,10 @@ describe('setVersionName', function() { var amplitude2 = new AmplitudeClient(); amplitude2.init(apiKey); - assert.deepEqual(amplitude2._unsentEvents, amplitude._unsentEvents); + assert.deepEqual( + amplitude2._unsentEvents.map(({event}) => event), + amplitude._unsentEvents.map(({event}) => event) + ); }); it('should not save events', function() { @@ -1592,7 +1595,7 @@ describe('setVersionName', function() { assert.lengthOf(server.requests, 1); var unsentEvents = amplitude._unsentEvents; assert.lengthOf(unsentEvents, 5); - assert.deepEqual(unsentEvents[4].event_properties, {index: 14}); + assert.deepEqual(unsentEvents[4].event.event_properties, {index: 14}); // remaining 5 events should be sent by the delayed sendEvent call clock.tick(eventUploadPeriodMillis); @@ -1812,9 +1815,6 @@ describe('setVersionName', function() { }; amplitude.logEvent('test', null, callback); assert.lengthOf(server.requests, 0); - assert.equal(counter, 1); - assert.equal(value, 0); - assert.equal(message, 'No request sent'); // check that request is made after delay, but callback is not run a second time clock.tick(eventUploadPeriodMillis); @@ -1822,6 +1822,8 @@ describe('setVersionName', function() { server.respondWith('success'); server.respond(); assert.equal(counter, 1); + assert.equal(value, 200); + assert.equal(message, 'success'); }); it ('should run callback once and only after all events are uploaded', function () { @@ -1915,22 +1917,44 @@ describe('setVersionName', function() { assert.equal(message, 'success'); }); - it ('should run callback if server returns something other than 200 and 413', function () { - var counter = 0; - var value = -1; - var message = ''; - var callback = function (status, response) { - counter++; - value = status; - message = response; - }; + it ('should _not_ run callback when the server returns a 500', function () { + const callback = sinon.spy(); amplitude.logEvent('test', null, callback); - server.respondWith([404, {}, 'Not found']); + server.respondWith([500, {}, 'Not found']); server.respond(); - assert.equal(counter, 1); - assert.equal(value, 404); - assert.equal(message, 'Not found'); + assert.isFalse(callback.calledOnce); + }); + + it('should run the callback when the server finally returns a 200 after a 500', function () { + const callback = sinon.spy(); + + amplitude.logEvent('test', null, callback); + server.respondWith([500, {}, 'Not found']); + server.respond(); + // The SDK retries failed events when a new event is sent + amplitude.logEvent('test2'); + server.respondWith([200, {}, 'success']); + server.respond(); + + assert.isTrue(callback.calledOnce); + }); + + it('should run the callback when the server finally returns a 413 after a 500', function () { + const callback = sinon.spy(); + + amplitude.logEvent('test', null, callback); + server.respondWith([500, {}, 'Not found']); + server.respond(); + // The SDK retries failed events when a new event is sent + amplitude.logEvent('test2'); + server.respondWith([413, {}, '']); + server.respond(); + // The SDK will try to shrink the payload in half until its down to one event before giving up + server.respondWith([413, {}, '']); + server.respond(); + + assert.isTrue(callback.calledOnce); }); it('should send 3 identify events', function() { @@ -2097,7 +2121,7 @@ describe('setVersionName', function() { amplitude.identify(new Identify().add('photoCount', 1)); amplitude.logEvent('test'); - delete amplitude._unsentEvents[0].sequence_number; // delete sequence number to simulate old event + delete amplitude._unsentEvents[0].event.sequence_number; // delete sequence number to simulate old event amplitude._sequenceNumber = 1; // reset sequence number amplitude.identify(new Identify().add('photoCount', 2)); @@ -2297,7 +2321,7 @@ describe('setVersionName', function() { amplitude.init(apiKey, null, {batchEvents: true}); amplitude.identify(identify); - assert.deepEqual(amplitude._unsentIdentifys[0].user_properties, {'$set': {'10': 10}}); + assert.deepEqual(amplitude._unsentIdentifys[0].event.user_properties, {'$set': {'10': 10}}); }); it('should ignore event and user properties with too many items', function() { @@ -2347,14 +2371,14 @@ describe('setVersionName', function() { amplitude1.logEvent('test5'); // the event ids should all be sequential since amplitude1 and amplitude2 have synchronized cookies - var eventId = amplitude1._unsentEvents[0]['event_id']; - assert.equal(amplitude2._unsentEvents[0]['event_id'], eventId + 1); - assert.equal(amplitude1._unsentEvents[1]['event_id'], eventId + 2); - assert.equal(amplitude2._unsentEvents[1]['event_id'], eventId + 3); + var eventId = amplitude1._unsentEvents[0].event['event_id']; + assert.equal(amplitude2._unsentEvents[0].event['event_id'], eventId + 1); + assert.equal(amplitude1._unsentEvents[1].event['event_id'], eventId + 2); + assert.equal(amplitude2._unsentEvents[1].event['event_id'], eventId + 3); - var sequenceNumber = amplitude1._unsentEvents[0]['sequence_number']; - assert.equal(amplitude2._unsentIdentifys[0]['sequence_number'], sequenceNumber + 4); - assert.equal(amplitude1._unsentEvents[2]['sequence_number'], sequenceNumber + 5); + var sequenceNumber = amplitude1._unsentEvents[0].event['sequence_number']; + assert.equal(amplitude2._unsentIdentifys[0].event['sequence_number'], sequenceNumber + 4); + assert.equal(amplitude1._unsentEvents[2].event['sequence_number'], sequenceNumber + 5); }); it('should handle groups input', function() { @@ -2362,6 +2386,7 @@ describe('setVersionName', function() { var value = -1; var message = ''; var callback = function (status, response) { + console.log('called callback', status, response); counter++; value = status; message = response; diff --git a/test/amplitude.js b/test/amplitude.js index 136be27e..7f2b8cdd 100644 --- a/test/amplitude.js +++ b/test/amplitude.js @@ -121,12 +121,12 @@ describe('Amplitude', function() { assert.lengthOf(app2._unsentEvents, 1); assert.lengthOf(app2._unsentIdentifys, 0); - assert.deepEqual(amplitude.getInstance()._unsentEvents[0].event_type, 'amplitude event'); - assert.deepEqual(amplitude.getInstance()._unsentEvents[1].event_type, 'amplitude event2'); + assert.deepEqual(amplitude.getInstance()._unsentEvents[0].event.event_type, 'amplitude event'); + assert.deepEqual(amplitude.getInstance()._unsentEvents[1].event.event_type, 'amplitude event2'); assert.deepEqual(amplitude.getInstance()._unsentIdentifys, []); assert.deepEqual(app1._unsentEvents, []); - assert.deepEqual(app1._unsentIdentifys[0].user_properties, {'$set':{'key':'value'}}); - assert.deepEqual(app2._unsentEvents[0].event_type, 'app2 event'); + assert.deepEqual(app1._unsentIdentifys[0].event.user_properties, {'$set':{'key':'value'}}); + assert.deepEqual(app2._unsentEvents[0].event.event_type, 'app2 event'); assert.deepEqual(app2._unsentIdentifys, []); assert.equal(amplitude.getInstance()._eventId, 2); @@ -331,8 +331,8 @@ describe('Amplitude', function() { amplitude2.init(apiKey, null, {batchEvents: true}); // check event loaded into memory - assert.deepEqual(amplitude2.getInstance()._unsentEvents, JSON.parse(existingEvent)); - assert.deepEqual(amplitude2.getInstance()._unsentIdentifys, JSON.parse(existingIdentify)); + assert.deepEqual(amplitude2.getInstance()._unsentEvents.map(({event}) => event), JSON.parse(existingEvent)); + assert.deepEqual(amplitude2.getInstance()._unsentIdentifys.map(({event}) => event), JSON.parse(existingIdentify)); // check local storage keys are still same for default instance assert.equal(localStorage.getItem('amplitude_unsent_' + apiKey), existingEvent); @@ -368,8 +368,8 @@ describe('Amplitude', function() { } // check that event loaded into memory - assert.deepEqual(amplitude2.getInstance()._unsentEvents[0].event_properties, {}); - assert.deepEqual(amplitude2.getInstance()._unsentEvents[1].event_properties, expected); + assert.deepEqual(amplitude2.getInstance()._unsentEvents[0].event.event_properties, {}); + assert.deepEqual(amplitude2.getInstance()._unsentEvents[1].event.event_properties, expected); }); it('should validate user properties when loading saved identifys from localStorage', function() { @@ -397,7 +397,7 @@ describe('Amplitude', function() { } // check that event loaded into memory - assert.deepEqual(amplitude2.getInstance()._unsentIdentifys[0].user_properties, {'$set': expected}); + assert.deepEqual(amplitude2.getInstance()._unsentIdentifys[0].event.user_properties, {'$set': expected}); }); it ('should load saved events from localStorage new keys and send events', function() { @@ -476,8 +476,8 @@ describe('Amplitude', function() { } // check that event loaded into memory - assert.deepEqual(amplitude2.getInstance()._unsentEvents[0].event_properties, {}); - assert.deepEqual(amplitude2.getInstance()._unsentEvents[1].event_properties, expected); + assert.deepEqual(amplitude2.getInstance()._unsentEvents[0].event.event_properties, {}); + assert.deepEqual(amplitude2.getInstance()._unsentEvents[1].event.event_properties, expected); }); }); @@ -631,12 +631,12 @@ describe('setVersionName', function() { amplitude.init(apiKey, null, {batchEvents: true}); amplitude.setVersionName('testVersionName1'); amplitude.logEvent('testEvent1'); - assert.equal(amplitude.getInstance()._unsentEvents[0].version_name, 'testVersionName1'); + assert.equal(amplitude.getInstance()._unsentEvents[0].event.version_name, 'testVersionName1'); // should ignore non-string values amplitude.setVersionName(15000); amplitude.logEvent('testEvent2'); - assert.equal(amplitude.getInstance()._unsentEvents[1].version_name, 'testVersionName1'); + assert.equal(amplitude.getInstance()._unsentEvents[1].event.version_name, 'testVersionName1'); }); }); @@ -1003,7 +1003,10 @@ describe('setVersionName', function() { var amplitude2 = new Amplitude(); amplitude2.init(apiKey); - assert.deepEqual(amplitude2.getInstance()._unsentEvents, amplitude.getInstance()._unsentEvents); + assert.deepEqual( + amplitude2.getInstance()._unsentEvents.map(({event}) => event), + amplitude.getInstance()._unsentEvents.map(({event}) => event) + ); }); it('should not save events', function() { @@ -1068,7 +1071,7 @@ describe('setVersionName', function() { assert.lengthOf(server.requests, 1); var unsentEvents = amplitude.getInstance()._unsentEvents; assert.lengthOf(unsentEvents, 5); - assert.deepEqual(unsentEvents[4].event_properties, {index: 14}); + assert.deepEqual(unsentEvents[4].event.event_properties, {index: 14}); // remaining 5 events should be sent by the delayed sendEvent call clock.tick(eventUploadPeriodMillis); @@ -1288,15 +1291,14 @@ describe('setVersionName', function() { }; amplitude.logEvent('test', null, callback); assert.lengthOf(server.requests, 0); - assert.equal(counter, 1); - assert.equal(value, 0); - assert.equal(message, 'No request sent'); // check that request is made after delay, but callback is not run a second time clock.tick(eventUploadPeriodMillis); assert.lengthOf(server.requests, 1); server.respondWith('success'); server.respond(); + assert.equal(value, 200); + assert.equal(message, 'success'); assert.equal(counter, 1); }); @@ -1391,24 +1393,6 @@ describe('setVersionName', function() { assert.equal(message, 'success'); }); - it ('should run callback if server returns something other than 200 and 413', function () { - var counter = 0; - var value = -1; - var message = ''; - var callback = function (status, response) { - counter++; - value = status; - message = response; - }; - - amplitude.logEvent('test', null, callback); - server.respondWith([404, {}, 'Not found']); - server.respond(); - assert.equal(counter, 1); - assert.equal(value, 404); - assert.equal(message, 'Not found'); - }); - it('should send 3 identify events', function() { amplitude.init(apiKey, null, {batchEvents: true, eventUploadThreshold: 3}); assert.equal(amplitude.getInstance()._unsentCount(), 0); @@ -1573,7 +1557,7 @@ describe('setVersionName', function() { amplitude.identify(new Identify().add('photoCount', 1)); amplitude.logEvent('test'); - delete amplitude.getInstance()._unsentEvents[0].sequence_number; // delete sequence number to simulate old event + delete amplitude.getInstance()._unsentEvents[0].event.sequence_number; // delete sequence number to simulate old event amplitude.getInstance()._sequenceNumber = 1; // reset sequence number amplitude.identify(new Identify().add('photoCount', 2)); @@ -1768,12 +1752,12 @@ describe('setVersionName', function() { }); }); - it('should validate user propeorties', function() { + it('should validate user properties', function() { var identify = new Identify().set(10, 10); amplitude.init(apiKey, null, {batchEvents: true}); amplitude.identify(identify); - assert.deepEqual(amplitude.getInstance()._unsentIdentifys[0].user_properties, {'$set': {'10': 10}}); + assert.deepEqual(amplitude.getInstance()._unsentIdentifys[0].event.user_properties, {'$set': {'10': 10}}); }); it('should synchronize event data across multiple amplitude instances that share the same cookie', function() { @@ -1791,14 +1775,14 @@ describe('setVersionName', function() { amplitude1.logEvent('test5'); // the event ids should all be sequential since amplitude1 and amplitude2 have synchronized cookies - var eventId = amplitude1.getInstance()._unsentEvents[0]['event_id']; - assert.equal(amplitude2.getInstance()._unsentEvents[0]['event_id'], eventId + 1); - assert.equal(amplitude1.getInstance()._unsentEvents[1]['event_id'], eventId + 2); - assert.equal(amplitude2.getInstance()._unsentEvents[1]['event_id'], eventId + 3); - - var sequenceNumber = amplitude1.getInstance()._unsentEvents[0]['sequence_number']; - assert.equal(amplitude2.getInstance()._unsentIdentifys[0]['sequence_number'], sequenceNumber + 4); - assert.equal(amplitude1.getInstance()._unsentEvents[2]['sequence_number'], sequenceNumber + 5); + var eventId = amplitude1.getInstance()._unsentEvents[0].event['event_id']; + assert.equal(amplitude2.getInstance()._unsentEvents[0].event['event_id'], eventId + 1); + assert.equal(amplitude1.getInstance()._unsentEvents[1].event['event_id'], eventId + 2); + assert.equal(amplitude2.getInstance()._unsentEvents[1].event['event_id'], eventId + 3); + + var sequenceNumber = amplitude1.getInstance()._unsentEvents[0].event['sequence_number']; + assert.equal(amplitude2.getInstance()._unsentIdentifys[0].event['sequence_number'], sequenceNumber + 4); + assert.equal(amplitude1.getInstance()._unsentEvents[2].event['sequence_number'], sequenceNumber + 5); }); it('should handle groups input', function() {