Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f1cede4
Adds a route for fetching issues with optional parameters
discorick Apr 18, 2016
dc2bce4
Begins working on the client-side session service and tests
discorick Apr 19, 2016
275b732
Implements events & handlers for browser focus blur as an injected "b…
discorick Apr 19, 2016
40c2205
Tweaks the focus logic and tests
discorick Apr 20, 2016
1952937
Adds logged_in endpoint, tweaks issues api endpoint
discorick Apr 20, 2016
9b0f528
Implements basic board syncing service (sans success/fail handlers) a…
discorick Apr 20, 2016
51aba7a
Fixes a bug related to misunderstanding the RSVP.all contract
discorick Apr 21, 2016
f072790
Uses correct method to flatten the array
discorick Apr 21, 2016
04c9148
Fixes flash sync messages from display an x on sync progress
discorick Apr 21, 2016
5829e45
Make sure messageData is always a new object instance, ensures only o…
discorick Apr 21, 2016
224d420
Implements session checking logic in the application controller
discorick Apr 21, 2016
251b281
Adds #/sync-issues route to manually sync the board
discorick Apr 21, 2016
cb862ac
Fixes problem where array proxy wasn't propagating changes to the mod…
discorick Apr 22, 2016
5e9bbc1
Removes logged_in route, throttles the board sync @30 seconds
discorick Apr 22, 2016
0edf225
Gives syncing message 1 second (to adjust for insta-syncs) shortens s…
discorick Apr 22, 2016
e43f3e1
Implements a failure message for board syncing
discorick Apr 22, 2016
8b69116
styles the progress flash message more subtley
discorick Apr 23, 2016
533e684
Use same-as-background shadow on progress message
dahlbyk Apr 25, 2016
3ac1cad
Force refresh after 24hours of no focus
discorick Apr 25, 2016
87ca4b1
Gracefully returns if no flash can be found
discorick May 3, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/assets/stylesheets/_colors.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ $lighterGrey: #CCC;
$lightGrey: #999;
$grey: #666;
$darkGrey: #444;
$navGrey: #F3F3F3;

$red: red;

Expand Down
6 changes: 6 additions & 0 deletions app/assets/stylesheets/components/_flash-message.scss
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@
background: $hb-red;
}

.progress.message {
background: $navGrey;
box-shadow: -1px 2px 2px rgba($navGrey, 0.5);
color: $hb-purple-dark;
}

.hb-spinner {
position: relative;
float: right;
Expand Down
5 changes: 5 additions & 0 deletions app/controllers/api/issues_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ def issue
render json: api.issue(params[:number])
end

def issues
api = huboard.board(params[:user], params[:repo])
render json: api.issues(params[:label], params[:options])
end

def details
api = huboard.board(params[:user], params[:repo])
render json: api.issue(params[:number]).activities
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@

#Issues
get 'issues/:number' => 'issues#issue'
get 'issues' => 'issues#issues'
get 'issues/:number/details' => 'issues#details'
get 'issues/:number/status' => 'issues#status'
post 'issues' => 'issues#open_issue'
Expand Down
16 changes: 16 additions & 0 deletions ember-app/app/controllers/application.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Ember from 'ember';
import BoardSubscriptions from "app/mixins/subscriptions/board";
import Messaging from "app/mixins/messaging";
import { throttledObserver } from 'app/utilities/observers';

var ApplicationController = Ember.Controller.extend(
BoardSubscriptions, Messaging, {
Expand All @@ -24,6 +25,21 @@ var ApplicationController = Ember.Controller.extend(

//Fix the need to delay event subscriptions
subscribeDisabled: true,

//Browser Session Checker
boardSyncing: Ember.inject.service(),
checkBrowserSession: throttledObserver(function(){
var lastFocus = this.get('browser-session.lastFocus');
var _self = this;
if(lastFocus >= 30000){
var since = new Date(new Date().getTime() - lastFocus);
return this.get('boardSyncing').syncIssues(this.get('model.board'), {since: since.toISOString()});
}

if(lastFocus >= 8.64e+7){ //One Day
this.send('sessionErrorHandler');
}
},'browser-session.lastFocus', 30000).on('init')
});

export default ApplicationController;
14 changes: 14 additions & 0 deletions ember-app/app/initializers/browser-session.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import BrowserSession from 'app/services/browser-session';

export function initialize(container, application){
application.register('browser-session:main', BrowserSession);
application.inject('controller', 'browser-session', 'browser-session:main');
application.inject('component', 'browser-session', 'browser-session:main');
}

export default {
name: 'browser-session',
after: 'advanceReadiness',
initialize: initialize
}

3 changes: 3 additions & 0 deletions ember-app/app/models/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ var HuBoardModel = Ember.Object.extend(
_onInit: function(){
this.set('content', this.get('data'));
}.on('init'),
onDataChanged: function(){
this.set('content', this.get('data'));
}.observes('data'),
ajax: ajax
});

Expand Down
11 changes: 10 additions & 1 deletion ember-app/app/models/new/board.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,16 @@ var Board = Model.extend({
return issue.data.assignee && issue.data.assignee.login === assignee.login;
});
});
}).property("assignees.[]", "issues.@each.assignee")
}).property("assignees.[]", "issues.@each.assignee"),
fetchIssues: function(options){
var promises = this.get('repos').map((repo)=>{
return repo.fetchIssues(options);
});

return Ember.RSVP.all(promises).then((issues)=>{
return _.flatten(issues);
});
}
});

Board.reopenClass({
Expand Down
6 changes: 5 additions & 1 deletion ember-app/app/models/new/repo.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,11 @@ var Repo = Model.extend({
},
assigneesLength: function(){
return this.get("assignees.length");
}.property("assignees.[]")
}.property("assignees.[]"),
fetchIssues: function(options){
var url = `/api/${this.get('data.repo.full_name')}/issues`
return Ember.$.getJSON(url,{ options: options });
}
});

export default Repo;
1 change: 1 addition & 0 deletions ember-app/app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Router.map(function() {

});

this.resource("sync-issues");
this.route("unauthorized");
});

Expand Down
18 changes: 18 additions & 0 deletions ember-app/app/routes/sync-issues.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Ember from 'ember';

var SyncIssuesRoute = Ember.Route.extend({
boardSyncing: Ember.inject.service(),

model: function(){
var repo = this.modelFor("application");
return repo;
},

afterModel: function (model){
var since = new Date(new Date().getTime() - 3600000);
this.get('boardSyncing').syncIssues(model.get('board'), {since: since.toISOString()});
this.transitionTo('application');
}
});

export default SyncIssuesRoute;
77 changes: 77 additions & 0 deletions ember-app/app/services/board-syncing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import Ember from 'ember';

var BoardSyncingService = Ember.Service.extend({

//Sync Notifier
flashMessages: Ember.inject.service(),
syncFlashNotifier: function(){
if(this.get('syncInProgress')){
this.get('flashMessages').add(this.messageData());
} else {
var flash = this.get('flashMessages.queue').find((flash)=>{
return flash.identifier === 'sync-message';
});
if(!flash){ return; }
Ember.set(flash.progress, 'status', false);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there any need to guard against flash being null here?

}
}.observes('syncInProgress'),
messageData: function(){
return {
message: 'syncing your board, please wait...',
sticky: true,
type: 'progress',
identifier: 'sync-message',
progress: {
status: true,
callback: function(){
setTimeout(()=>{
this.set('message', 'sync complete!');
this.get('flash')._setTimer('timer', 'destroyMessage', 2000);
}, 1000);
}
}
};
},

//Issue Syncing
syncIssues: function(board, opts){
if(this.get('syncInProgress')){ return; }
var _self = this;
_self.set('syncInProgress', true);

board.fetchIssues(opts).then((issues)=>{
_self.issueSuccess(board, issues);
_self.set('syncInProgress', false);
}, (error)=>{
_self.issueFail();
_self.set('syncInProgress', false);
});
},
issueSuccess: function(board, issues){
if(!issues.length){ return; }
Ember.run.once(()=>{
board.get('issues').forEach((issue)=>{
issues.forEach((i)=>{
if(i.id === issue.get('id')){
Ember.set(issue, 'data', i);
};
});
});
});
},
issueFail: function(error){
var flash = this.get('flashMessages.queue').find((flash)=>{
return flash.identifier === 'sync-message';
});
if(!flash){ return; }
flash.progress.callback = function(){
setTimeout(()=>{
this.set('flash.type', 'warning');
this.set('message', 'unable to sync your board, try refreshing');
this.set('flash.sticky', true);
}, 1000);
};
}
});

export default BoardSyncingService;
31 changes: 31 additions & 0 deletions ember-app/app/services/browser-session.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import Ember from 'ember';

var BrowserSessionService = Ember.Service.extend(Ember.Evented, {
initEventObservers: function(){
var _self = this;
Ember.$(window).on('focus blur', (e)=>{
_self[`${e.type}Handlers`].forEach((h) => _self[h]());
});
}.on('init'),
setLastFocus: function(){
var before = this.get('lastBlur');
var now = new Date().getTime();
this.set('lastFocus', (now - before));
}.on('didFocusBrowser'),

//Focus Handlers
focusHandlers: ['sendFocusEvent'],
sendFocusEvent: function(){
this.trigger('didFocusBrowser');
},

//Blur Handlers
blurHandlers: ['updateLastBlur'],
lastBlur: new Date().getTime(),
updateLastBlur: function(){
var time = new Date().getTime();
this.set('lastBlur', time);
}
});

export default BrowserSessionService;
13 changes: 8 additions & 5 deletions ember-app/app/templates/components/flash/hb-message.hbs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
{{#if flash.sticky}}
<i class='ui-icon ui-icon-x-thin'></i>
{{#if progress}}
{{hb-spinner}}
{{/if}}

{{#unless progress}}
{{#if flash.sticky}}
<i class='ui-icon ui-icon-x-thin'></i>
{{/if}}
{{/unless}}

<div class='message-copy'>{{truncate message 50}}</div>

{{#if progress}}
{{hb-spinner}}
{{/if}}
64 changes: 64 additions & 0 deletions ember-app/tests/unit/services/board-syncing-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import Ember from 'ember';
import {
moduleFor,
test
} from 'ember-qunit';

var sut;
var fakeServer;
var fakeResponse;
moduleFor('service:board-syncing', {
setup: function(){
sut = this.subject();
}
});

test('syncs the boards issues successfuly', (assert)=>{
var issues = ['issue1', 'issue2'];
var success = $.ajax().then(()=>{return issues});
var board = { fetchIssues: sinon.stub().returns(success) };
sut.syncFlashNotifier = sinon.stub();
sut.issueSuccess = sinon.stub();

var done = assert.async();
sut.syncIssues(board, {});
setTimeout(()=>{
assert.ok(board.fetchIssues.calledWith({}));
assert.ok(sut.issueSuccess.calledWith(board, issues));
assert.ok(sut.get('syncInProgress') === false);
done();
}, 10);
});

test('fails gracefully on syncing the boards issues', (assert)=>{
var fail = $.ajax('fail');
var board = { fetchIssues: sinon.stub().returns(fail) };
sut.syncFlashNotifier = sinon.stub();
sut.issueFail = sinon.stub();

var done = assert.async();
sut.syncIssues(board, {});
setTimeout(()=>{
assert.ok(board.fetchIssues.calledWith({}));
assert.ok(sut.issueFail.called);
assert.ok(sut.get('syncInProgress') === false);
done();
}, 10);
});

test('sends a flash notifier on sync', (assert)=> {
var flash = { add: sinon.stub() };
sut.messageData = sinon.stub();
sut.set('flashMessages', flash);
sut.set('syncInProgress', true);

assert.ok(sut.get('flashMessages').add.calledWith(sut.messageData()));
});

test('clears flash when sync is finished', (assert)=> {
var flash = { queue: [sut.messageData()] };
sut.set('flashMessages', flash);
sut.set('syncInProgress', false);

assert.ok(flash.queue[0].progress.status === false);
});
Loading