Add latency checking and sorting of inserted rows to find session.

This commit is contained in:
Jonathon Wilson 2012-12-01 16:50:28 -07:00
parent 04d059368a
commit f4afd9bcb1
9 changed files with 379 additions and 85 deletions

View File

@ -0,0 +1,21 @@
(function(context,$) {
context.JK = context.JK || {};
context.JK.FakeJamClient = function(app) {
var logger = context.JK.logger;
logger.info("*** Fake JamClient instance initialized. ***");
function testLatency(client, callback) {
var response = {
id: client.id,
latency: 50
};
callback(response);
}
// Javascript Bridge seems to camel-case
this.TestLatency = testLatency;
};
})(window,jQuery);

View File

@ -4,32 +4,73 @@
context.JK.FindSessionScreen = function(app) {
var logger = context.JK.logger;
var sessionLatency;
var sessions = {};
var selectors = {
TABLE_BODY: '#findSession-tableBody'
};
function loadSessions() {
$.ajax({
type: "GET",
url: "/api/sessions",
// TODO: Change to buildLatencyMap
success: renderSessions
success: startSessionLatencyChecks
});
}
function renderSessions(response) {
$tb = $('#findSession-tableBody');
$tb.empty();
var rowTemplate = $('#template-findSession-row').html();
$.each(response, function() {
var vals = {
id: this.id,
participants: this.participants.length,
description: this.description || "(No description)"
};
var row = JK.fillTemplate(rowTemplate, vals);
$tb.append(row);
function startSessionLatencyChecks(response) {
sessionLatency.subscribe(latencyResponse);
$.each(response, function(index, session) {
sessions[session.id] = session;
sessionLatency.sessionPings(session);
});
}
function latencyResponse(sessionId) {
renderSession(sessionId);
}
/**
* Not used normally. Allows modular unit testing
* of the renderSession method without having to do
* as much heavy setup.
*/
function setSession(session) { sessions[session.id] = session; }
/**
* Render a single session line into the table.
* It will be inserted at the appropriate place according to the
* sortScore in sessionLatency.
*/
function renderSession(sessionId) {
$tb = $(selectors.TABLE_BODY);
var rowTemplate = $('#template-findSession-row').html();
var session = sessions[sessionId];
var latencyInfo = sessionLatency.sessionInfo(sessionId);
var vals = {
id: session.id,
genres: session.genres.join (','),
sortScore: latencyInfo.sortScore,
participants: session.participants.length,
description: session.description || "(No description)"
};
var row = JK.fillTemplate(rowTemplate, vals);
var insertedEarly = false;
$.each($('tr', $tb), function() {
$this = $(this);
var rowSortScore = parseInt($this.attr('data-sortScore'), 10);
if (vals.sortScore > rowSortScore) {
$this.before(row);
insertedEarly = true;
}
});
if (!(insertedEarly)) {
return $tb.append(row);
}
}
function afterShow(data) {
$tb = $(selectors.TABLE_BODY);
$tb.empty();
loadSessions();
}
@ -47,20 +88,30 @@
$('#findSession-tableBody').on("click", '[action="delete"]', deleteSession);
}
this.afterShow = afterShow;
this.initialize = function() {
if ("jamClient" in context) {
sessionLatency = new context.JK.SessionLatency(jamClient);
/**
* Initialize, providing an instance of the SessionLatency class.
*/
function initialize(_sessionLatency) {
if (_sessionLatency) {
sessionLatency = _sessionLatency;
} else {
logger.warn("No jamClient available. Session pings will not be performed.");
logger.warn("No sessionLatency provided.");
}
screenBindings = {
'afterShow': afterShow
};
app.bindScreen('findSession', screenBindings);
events();
};
}
this.initialize = initialize;
this.afterShow = afterShow;
this.renderSession = renderSession;
// Following exposed for easier testing.
this.setSession = setSession;
this.selectors = selectors;
};
})(window,jQuery);

View File

@ -8,6 +8,7 @@
var sessionPingsOut = {};
var clientsToSessions = {};
var sessionLatency = {};
var subscribers = [];
function getSortScore(sessionId) {
return sessionLatency[sessionId].sortScore;
@ -51,7 +52,7 @@
sessionPingsOut[session.id] = 0;
}
sessionPingsOut[session.id]++;
jamClient.testLatency(client, clientPingResponse);
jamClient.TestLatency(client, clientPingResponse);
});
}
@ -59,6 +60,9 @@
var sessionId = clientsToSessions[response.id];
sessionPingsOut[sessionId]--;
updateSessionLatency(sessionId, response);
$.each(subscribers, function() {
this(sessionId);
});
}
function updateSessionLatency(sessionId, latencyResponse) {
@ -96,9 +100,14 @@
return sessionLatency[sessionId];
}
function subscribe(cb) {
subscribers.push(cb);
}
this.sessionPings = sessionPings;
this.sessionInfo = sessionInfo;
this.getSortScore = getSortScore;
this.subscribe = subscribe;
return this;
};

View File

@ -4,9 +4,13 @@
<p layout-link="home">Home</p>
<table>
<tr>
<th>Members</th>
<th>Name</th>
<th>Delete</th>
<th>Genre</th>
<th>Description</th>
<th>Musicians</th>
<th>Audience</th>
<th>Latency</th>
<th>Listen</th>
<th>Join</th>
</tr>
<tbody id="findSession-tableBody">
</tbody>
@ -14,9 +18,13 @@
</div>
<script type="text/template" id="template-findSession-row">
<tr>
<tr data-sortScore="{sortScore}">
<td>{genres}</td>
<td>{description}</td>
<td>{participants}</td>
<td><a href="#/session/{id}">{description}</a></td>
<td><a action="delete" action-id="{id}">X</a></td>
<td>TODO Audience</td>
<td>TODO Latency</td>
<td>TODO Listen</td>
<td><a href="#/session/{id}">Join</a></td>
</tr>
</script>

View File

@ -19,6 +19,11 @@
JK = JK || {};
// The following allows testing of native (jamClient) functionality
// with a fake. Uncomment when developing without the native client.
window.jamClient = new JK.FakeJamClient();
<% if current_user %>
JK.currentUserId = '<%= current_user.id %>';
<% else %>
@ -40,7 +45,11 @@
createSessionScreen.initialize();
var findSessionScreen = new JK.FindSessionScreen(jk);
findSessionScreen.initialize();
var sessionLatency = null;
if ("jamClient" in window) {
sessionLatency = new JK.SessionLatency(window.jamClient);
}
findSessionScreen.initialize(sessionLatency);
var sessionScreen = new JK.SessionScreen(jk);
sessionScreen.initialize();

View File

@ -3,11 +3,6 @@
describe("FindSession", function() {
var fss;
var ajaxSpy;
var selectors = {
sessions: '#findSession-tableBody'
};
var appFake = {
clientId: '12345',
@ -16,34 +11,130 @@
ajaxError: function() { console.debug("ajaxError"); }
};
var jamClientFake = {
TestLatency: function(client, cb) {
cb({id: client.id, latency: 50});
}
};
beforeEach(function() {
fss = null;
// Use the actual screen markup
jasmine.getFixtures().fixturesPath = '/app/views/clients';
loadFixtures('_findSession.html.erb');
spyOn(appFake, 'notify');
fss = new context.JK.FindSessionScreen(appFake);
fss.initialize();
});
describe("loadSessions", function() {
beforeEach(function() {
spyOn($, "ajax");
});
it("should query ajax for sessions", function() {
fss.afterShow({});
expect($.ajax).toHaveBeenCalled();
});
});
var sessionLatencyReal;
describe("renderSessions", function() {
describe("RealSessionLatency", function() {
beforeEach(function() {
spyOn($, "ajax").andCallFake(function(opts) {
opts.success(TestResponses.getSessions);
sessionLatencyReal = new JK.SessionLatency(jamClientFake);
spyOn(sessionLatencyReal, 'sessionPings').andCallThrough();
fss = new context.JK.FindSessionScreen(appFake);
fss.initialize(sessionLatencyReal);
$(fss.selectors.TABLE_BODY).empty();
});
describe("loadSessions", function() {
beforeEach(function() {
spyOn($, "ajax");
});
it("should query ajax for sessions", function() {
fss.afterShow({});
expect($.ajax).toHaveBeenCalled();
});
});
it("should output table rows for sessions", function() {
fss.afterShow({});
expect($(selectors.sessions + ' tr').length).toEqual(1);
describe("afterShow flow", function() {
beforeEach(function() {
spyOn($, "ajax").andCallFake(function(opts) {
opts.success(TestGetSessionResponses.oneOfEach);
});
});
it("should output table rows for sessions", function() {
fss.afterShow({});
expect($(fss.selectors.TABLE_BODY + ' tr').length).toEqual(5);
});
it("should call sessionPings", function() {
fss.afterShow({});
expect(sessionLatencyReal.sessionPings).toHaveBeenCalled();
});
});
});
describe("FakeSessionLatency", function() {
beforeEach(function() {
sessionInfoResponses = {
"1": {id:"1", sortScore: 3},
"2": {id:"2", sortScore: 2}
};
sessionLatencyFake = {
sessionInfo: null
};
spyOn(sessionLatencyFake, 'sessionInfo').andCallFake(function(id) {
return sessionInfoResponses[id];
});
fss = new context.JK.FindSessionScreen(appFake);
fss.initialize(sessionLatencyFake);
$(fss.selectors.TABLE_BODY).empty();
});
describe("renderSession", function() {
describe("layout", function() {
var tbody;
beforeEach(function() {
var session = TestGetSessionResponses.oneOfEach[0];
fss.setSession(session);
fss.renderSession(session.id);
tbody = $(fss.selectors.TABLE_BODY);
});
it("single session should render", function() {
expect($('tr', tbody).length).toEqual(1);
});
it("Should render genre", function() {
expect($('tr td', tbody).first().text()).toEqual('classical');
});
it("Should render description", function() {
expect($('tr td', tbody).first().next().text()).toEqual('Invited');
});
it("Should render musician count", function() {
expect($('tr td', tbody).first().next().next().text()).toEqual('1');
});
// TODO - test audience
// TODO - test latency
// TODO - test Listen
it("Should render join link", function() {
expect($('tr td', tbody).last().text()).toEqual('Join');
});
});
it("higher sortScore inserted before lower sortScore", function() {
var sessionLow = TestGetSessionResponses.oneOfEach[1];
var sessionHigh = TestGetSessionResponses.oneOfEach[0];
fss.setSession(sessionLow);
fss.setSession(sessionHigh);
fss.renderSession(sessionLow.id);
fss.renderSession(sessionHigh.id);
var tbody = $(fss.selectors.TABLE_BODY);
expect($('tr', tbody).length).toEqual(2);
expect($('tr', tbody).first().attr('data-sortScore')).toEqual('3');
expect($('tr', tbody).first().next().attr('data-sortScore')).toEqual('2');
});
});
});

View File

@ -0,0 +1,120 @@
window.TestGetSessionResponses = {
oneOfEach: [
// Session 1 - you're invited to this.
{
"id": "1",
"description": "Invited",
"musician_access": true,
"genres" : [ "classical" ],
"participants": [
{
"client_id": "0f8f7987-29a0-4e5d-a60c-6b23103e3533",
"ip_address":"1.1.1.1",
"user_id" : "02303020402042040",
"tracks" : [
{
"id" : "xxxx",
"instrument_id" : "electric guitar",
"sound" : "mono"
}
]
}
],
"invitations" : [
{
"id" : "3948797598275987",
"sender_id" : "02303020402042040"
}
]
},
// Session 2 - no invite, but friend #1 (good latency)
{
"id": "2",
"description": "Friends 1",
"musician_access": true,
"genres" : [ "blues" ],
"participants": [
{
"client_id": "0f8f7987-29a0-4e5d-a60c-6b23103e3533",
"ip_address":"1.1.1.1",
"user_id" : "02303020402042040",
"tracks" : [
{
"id" : "xxxx",
"instrument_id" : "electric guitar",
"sound" : "mono"
}
]
}
]
},
// Session 3 - no invite, but friend #2 (med latency)
{
"id": "3",
"description": "Friends 2",
"musician_access": true,
"genres" : [ "blues" ],
"participants": [
{
"client_id": "0f8f7987-29a0-4e5d-a60c-6b23103e3533",
"ip_address":"1.1.1.1",
"user_id" : "02303020402042040",
"tracks" : [
{
"id" : "xxxx",
"instrument_id" : "electric guitar",
"sound" : "mono"
}
]
}
]
},
// Session 4 - no invite, no friends 1
{
"id": "4",
"description": "Anonymous 1",
"musician_access": true,
"genres" : [ "blues" ],
"participants": [
{
"client_id": "0f8f7987-29a0-4e5d-a60c-6b23103e3533",
"ip_address":"1.1.1.1",
"tracks" : [
{
"id" : "xxxx",
"instrument_id" : "electric guitar",
"sound" : "mono"
}
]
}
]
},
// Session 5 - no invite, no friends 2
{
"id": "5",
"description": "Anonymous 2",
"musician_access": true,
"genres" : [ "blues" ],
"participants": [
{
"client_id": "0f8f7987-29a0-4e5d-a60c-6b23103e3533",
"ip_address":"1.1.1.1",
"tracks" : [
{
"id" : "xxxx",
"instrument_id" : "electric guitar",
"sound" : "mono"
}
]
}
]
}
]
};

View File

@ -1,34 +1,4 @@
window.TestResponses = {
getSessions: [
{
"id": "1234",
"description": "Hello",
"musician_access": true,
"genres" : [ "classical" ],
"participants": [
{
"client_id": "0f8f7987-29a0-4e5d-a60c-6b23103e3533",
"ip_address":"1.1.1.1",
"user_id" : "02303020402042040", // NOTE THIS WILL BE UNDEFINED (ABSENT) IF THIS CLIENT IS NOT YOUR FRIEND
"tracks" : [
{
"id" : "xxxx",
"instrument_id" : "electric guitar",
"sound" : "mono"
}
]
}
],
"invitations" : [
{
"id" : "3948797598275987",
"sender_id" : "02303020402042040"
}
]
}
],
sessionPost: {
"id": "1234",
"description": "Hello",

View File

@ -5,7 +5,7 @@
var sessionLatency; // Instance of SessionLatency class for test
fakeJamClient = {
testLatency: function() {} // Will be overridden by test cases
TestLatency: function() {} // Will be overridden by test cases
};
var sessions = [
@ -33,16 +33,16 @@
beforeEach(function() {
callCount = 0;
sessionLatency = new context.JK.SessionLatency(fakeJamClient);
spyOn(fakeJamClient, "testLatency").andCallFake(function(client, callback) {
spyOn(fakeJamClient, "TestLatency").andCallFake(function(client, callback) {
callback(testLatencyResponses[client.id]);
callCount++;
});
});
describe("SessionPings", function() {
it("should call jamClient.testLatency and compute new average", function() {
it("should call jamClient.TestLatency and compute new average", function() {
sessionLatency.sessionPings(sessions[0]);
expect(fakeJamClient.testLatency).toHaveBeenCalled();
expect(fakeJamClient.TestLatency).toHaveBeenCalled();
var info = sessionLatency.sessionInfo(sessions[0].id);
expect(info.averageLatency).toEqual(35);
});
@ -87,6 +87,21 @@
});
});
describe("Register for Events", function() {
it("should register successfully", function() {
var cb = jasmine.createSpy();
sessionLatency.subscribe(cb);
});
it("should invoke callback on latency result", function() {
var cb = jasmine.createSpy("Latency Subscription Callback");
sessionLatency.subscribe(cb);
$.each(sessions, function(index, session) {
sessionLatency.sessionPings(session);
});
expect(cb).toHaveBeenCalled();
});
});
});