Skip to content

Commit 71f3c31

Browse files
committed
add shared sequence number
1 parent e6bb6fb commit 71f3c31

File tree

4 files changed

+107
-25
lines changed

4 files changed

+107
-25
lines changed

amplitude.js

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ var DEFAULT_OPTIONS = {
152152
var LocalStorageKeys = {
153153
LAST_EVENT_ID: 'amplitude_lastEventId',
154154
LAST_IDENTIFY_ID: 'amplitude_lastIdentifyId',
155+
LAST_SEQUENCE_NUMBER: 'amplitude_lastSequenceNumber',
155156
LAST_EVENT_TIME: 'amplitude_lastEventTime',
156157
SESSION_ID: 'amplitude_sessionId'
157158
};
@@ -168,6 +169,7 @@ var Amplitude = function() {
168169

169170
Amplitude.prototype._eventId = 0;
170171
Amplitude.prototype._identifyId = 0;
172+
Amplitude.prototype._sequenceNumber = 0;
171173
Amplitude.prototype._sending = false;
172174
Amplitude.prototype._lastEventTime = null;
173175
Amplitude.prototype._sessionId = null;
@@ -244,6 +246,7 @@ Amplitude.prototype.init = function(apiKey, opt_userId, opt_config, callback) {
244246
this._sessionId = parseInt(localStorage.getItem(LocalStorageKeys.SESSION_ID)) || null;
245247
this._eventId = localStorage.getItem(LocalStorageKeys.LAST_EVENT_ID) || 0;
246248
this._identifyId = localStorage.getItem(LocalStorageKeys.LAST_IDENTIFY_ID) || 0;
249+
this._sequenceNumber = localStorage.getItem(LocalStorageKeys.LAST_SEQUENCE_NUMBER) || 0;
247250
var now = new Date().getTime();
248251
if (!this._sessionId || !this._lastEventTime || now - this._lastEventTime > this.options.sessionTimeout) {
249252
this._newSession = true;
@@ -286,6 +289,11 @@ Amplitude.prototype.nextIdentifyId = function() {
286289
return this._identifyId;
287290
};
288291

292+
Amplitude.prototype.nextSequenceNumber = function() {
293+
this._sequenceNumber++;
294+
return this._sequenceNumber;
295+
};
296+
289297
// returns the number of unsent events and identifys
290298
Amplitude.prototype._unsentCount = function() {
291299
return this._unsentEvents.length + this._unsentIdentifys.length;
@@ -552,7 +560,8 @@ Amplitude.prototype._logEvent = function(eventType, eventProperties, apiProperti
552560
library: {
553561
name: 'amplitude-js',
554562
version: this.__VERSION__
555-
}
563+
},
564+
sequence_number: this.nextSequenceNumber() // for ordering events and identifys
556565
// country: null
557566
};
558567

@@ -722,14 +731,17 @@ Amplitude.prototype._mergeEventsAndIdentifys = function(numEvents) {
722731
event = this._unsentIdentifys[identifyIndex++];
723732
maxIdentifyId = event.event_id;
724733

725-
// case 3: need to compare timestamps
734+
// case 3: need to compare sequence numbers
726735
} else {
727-
if (this._unsentIdentifys[identifyIndex].timestamp <= this._unsentEvents[eventIndex].timestamp) {
728-
event = this._unsentIdentifys[identifyIndex++];
729-
maxIdentifyId = event.event_id;
730-
} else {
736+
// events logged before v2.5.0 won't have a sequence number, put those first
737+
if (!('sequence_number' in this._unsentEvents[eventIndex]) ||
738+
this._unsentEvents[eventIndex].sequence_number <
739+
this._unsentIdentifys[identifyIndex].sequence_number) {
731740
event = this._unsentEvents[eventIndex++];
732741
maxEventId = event.event_id;
742+
} else {
743+
event = this._unsentIdentifys[identifyIndex++];
744+
maxIdentifyId = event.event_id;
733745
}
734746
}
735747

amplitude.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/amplitude.js

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ var DEFAULT_OPTIONS = {
4040
var LocalStorageKeys = {
4141
LAST_EVENT_ID: 'amplitude_lastEventId',
4242
LAST_IDENTIFY_ID: 'amplitude_lastIdentifyId',
43+
LAST_SEQUENCE_NUMBER: 'amplitude_lastSequenceNumber',
4344
LAST_EVENT_TIME: 'amplitude_lastEventTime',
4445
SESSION_ID: 'amplitude_sessionId'
4546
};
@@ -56,6 +57,7 @@ var Amplitude = function() {
5657

5758
Amplitude.prototype._eventId = 0;
5859
Amplitude.prototype._identifyId = 0;
60+
Amplitude.prototype._sequenceNumber = 0;
5961
Amplitude.prototype._sending = false;
6062
Amplitude.prototype._lastEventTime = null;
6163
Amplitude.prototype._sessionId = null;
@@ -132,6 +134,7 @@ Amplitude.prototype.init = function(apiKey, opt_userId, opt_config, callback) {
132134
this._sessionId = parseInt(localStorage.getItem(LocalStorageKeys.SESSION_ID)) || null;
133135
this._eventId = localStorage.getItem(LocalStorageKeys.LAST_EVENT_ID) || 0;
134136
this._identifyId = localStorage.getItem(LocalStorageKeys.LAST_IDENTIFY_ID) || 0;
137+
this._sequenceNumber = localStorage.getItem(LocalStorageKeys.LAST_SEQUENCE_NUMBER) || 0;
135138
var now = new Date().getTime();
136139
if (!this._sessionId || !this._lastEventTime || now - this._lastEventTime > this.options.sessionTimeout) {
137140
this._newSession = true;
@@ -174,6 +177,11 @@ Amplitude.prototype.nextIdentifyId = function() {
174177
return this._identifyId;
175178
};
176179

180+
Amplitude.prototype.nextSequenceNumber = function() {
181+
this._sequenceNumber++;
182+
return this._sequenceNumber;
183+
};
184+
177185
// returns the number of unsent events and identifys
178186
Amplitude.prototype._unsentCount = function() {
179187
return this._unsentEvents.length + this._unsentIdentifys.length;
@@ -440,7 +448,8 @@ Amplitude.prototype._logEvent = function(eventType, eventProperties, apiProperti
440448
library: {
441449
name: 'amplitude-js',
442450
version: this.__VERSION__
443-
}
451+
},
452+
sequence_number: this.nextSequenceNumber() // for ordering events and identifys
444453
// country: null
445454
};
446455

@@ -610,14 +619,17 @@ Amplitude.prototype._mergeEventsAndIdentifys = function(numEvents) {
610619
event = this._unsentIdentifys[identifyIndex++];
611620
maxIdentifyId = event.event_id;
612621

613-
// case 3: need to compare timestamps
622+
// case 3: need to compare sequence numbers
614623
} else {
615-
if (this._unsentIdentifys[identifyIndex].timestamp <= this._unsentEvents[eventIndex].timestamp) {
616-
event = this._unsentIdentifys[identifyIndex++];
617-
maxIdentifyId = event.event_id;
618-
} else {
624+
// events logged before v2.5.0 won't have a sequence number, put those first
625+
if (!('sequence_number' in this._unsentEvents[eventIndex]) ||
626+
this._unsentEvents[eventIndex].sequence_number <
627+
this._unsentIdentifys[identifyIndex].sequence_number) {
619628
event = this._unsentEvents[eventIndex++];
620629
maxEventId = event.event_id;
630+
} else {
631+
event = this._unsentIdentifys[identifyIndex++];
632+
maxIdentifyId = event.event_id;
621633
}
622634
}
623635

test/amplitude.js

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,7 @@ describe('Amplitude', function() {
716716
assert.isTrue('$add' in events[i].user_properties);
717717
assert.deepEqual(events[i].user_properties['$add'], {'photoCount': 1});
718718
assert.equal(events[i].event_id, i+1);
719+
assert.equal(events[i].sequence_number, i+1);
719720
}
720721

721722
// send response and check that remove events works properly
@@ -746,6 +747,7 @@ describe('Amplitude', function() {
746747
for (var i = 0; i < 3; i++) {
747748
assert.equal(events[i].event_type, 'test');
748749
assert.equal(events[i].event_id, i+1);
750+
assert.equal(events[i].sequence_number, i+1);
749751
}
750752

751753
// send response and check that remove events works properly
@@ -773,14 +775,16 @@ describe('Amplitude', function() {
773775
var events = JSON.parse(querystring.parse(server.requests[0].requestBody).e);
774776
assert.lengthOf(events, 2);
775777

776-
// if identify and event have same timestamp, identify comes first
777-
assert.equal(events[0].event_type, '$identify');
778+
// event should come before identify - maintain order using sequence number
779+
assert.equal(events[0].event_type, 'test');
778780
assert.equal(events[0].event_id, 1);
779-
assert.isTrue('$add' in events[0].user_properties);
780-
assert.deepEqual(events[0].user_properties['$add'], {'photoCount': 1});
781-
assert.equal(events[1].event_type, 'test');
781+
assert.deepEqual(events[0].user_properties, {});
782+
assert.equal(events[0].sequence_number, 1);
783+
assert.equal(events[1].event_type, '$identify');
782784
assert.equal(events[1].event_id, 1);
783-
assert.deepEqual(events[1].user_properties, {});
785+
assert.isTrue('$add' in events[1].user_properties);
786+
assert.deepEqual(events[1].user_properties['$add'], {'photoCount': 1});
787+
assert.equal(events[1].sequence_number, 2);
784788

785789
// send response and check that remove events works properly
786790
server.respondWith('success');
@@ -819,18 +823,72 @@ describe('Amplitude', function() {
819823
// verify the correct coalescing
820824
assert.equal(events[0].event_type, 'test1');
821825
assert.deepEqual(events[0].user_properties, {});
826+
assert.equal(events[0].sequence_number, 1);
822827
assert.equal(events[1].event_type, '$identify');
823828
assert.isTrue('$add' in events[1].user_properties);
824829
assert.deepEqual(events[1].user_properties['$add'], {'photoCount': 1});
830+
assert.equal(events[1].sequence_number, 2);
825831
assert.equal(events[2].event_type, 'test2');
826832
assert.deepEqual(events[2].user_properties, {});
833+
assert.equal(events[2].sequence_number, 3);
827834
assert.equal(events[3].event_type, 'test3');
828835
assert.deepEqual(events[3].user_properties, {});
829-
assert.equal(events[4].event_type, '$identify');
830-
assert.isTrue('$add' in events[4].user_properties);
831-
assert.deepEqual(events[4].user_properties['$add'], {'photoCount': 2});
832-
assert.equal(events[5].event_type, 'test4');
833-
assert.deepEqual(events[5].user_properties, {});
836+
assert.equal(events[3].sequence_number, 4);
837+
assert.equal(events[4].event_type, 'test4');
838+
assert.deepEqual(events[4].user_properties, {});
839+
assert.equal(events[4].sequence_number, 5);
840+
assert.equal(events[5].event_type, '$identify');
841+
assert.isTrue('$add' in events[5].user_properties);
842+
assert.deepEqual(events[5].user_properties['$add'], {'photoCount': 2});
843+
assert.equal(events[5].sequence_number, 6);
844+
845+
// send response and check that remove events works properly
846+
server.respondWith('success');
847+
server.respond();
848+
assert.equal(amplitude._unsentCount(), 0);
849+
assert.lengthOf(amplitude._unsentEvents, 0);
850+
assert.lengthOf(amplitude._unsentIdentifys, 0);
851+
});
852+
853+
it('should merged events supporting backwards compatability', function() {
854+
// events logged before v2.5.0 won't have sequence number, should get priority
855+
amplitude.init(apiKey, null, {batchEvents: true, eventUploadThreshold: 3});
856+
assert.equal(amplitude._unsentCount(), 0);
857+
858+
amplitude.identify(new Identify().add('photoCount', 1));
859+
amplitude.logEvent('test');
860+
delete amplitude._unsentEvents[0].sequence_number; // delete sequence number to simulate old event
861+
amplitude._sequenceNumber = 1; // reset sequence number
862+
amplitude.identify(new Identify().add('photoCount', 2));
863+
864+
// verify some internal counters
865+
assert.equal(amplitude._eventId, 1);
866+
assert.equal(amplitude._identifyId, 2);
867+
assert.equal(amplitude._unsentCount(), 3);
868+
assert.lengthOf(amplitude._unsentEvents, 1);
869+
assert.lengthOf(amplitude._unsentIdentifys, 2);
870+
871+
assert.lengthOf(server.requests, 1);
872+
var events = JSON.parse(querystring.parse(server.requests[0].requestBody).e);
873+
assert.lengthOf(events, 3);
874+
875+
// event should come before identify - prioritize events with no sequence number
876+
assert.equal(events[0].event_type, 'test');
877+
assert.equal(events[0].event_id, 1);
878+
assert.deepEqual(events[0].user_properties, {});
879+
assert.isFalse('sequence_number' in events[0]);
880+
881+
assert.equal(events[1].event_type, '$identify');
882+
assert.equal(events[1].event_id, 1);
883+
assert.isTrue('$add' in events[1].user_properties);
884+
assert.deepEqual(events[1].user_properties['$add'], {'photoCount': 1});
885+
assert.equal(events[1].sequence_number, 1);
886+
887+
assert.equal(events[2].event_type, '$identify');
888+
assert.equal(events[2].event_id, 2);
889+
assert.isTrue('$add' in events[2].user_properties);
890+
assert.deepEqual(events[2].user_properties['$add'], {'photoCount': 2});
891+
assert.equal(events[2].sequence_number, 2);
834892

835893
// send response and check that remove events works properly
836894
server.respondWith('success');

0 commit comments

Comments
 (0)