Merge pull request #535 from taigaio/issue/2883/empty-first-timeline-page

Issue/2883/empty first timeline page
stable
Alejandro 2015-06-18 03:00:08 -07:00
commit 798167849a
10 changed files with 348 additions and 205 deletions

View File

@ -1,4 +1,6 @@
Resource = (urlsService, http) -> pagination = () ->
Resource = (urlsService, http, paginateResponseService) ->
service = {} service = {}
service.getProjectBySlug = (projectSlug) -> service.getProjectBySlug = (projectSlug) ->
@ -40,12 +42,13 @@ Resource = (urlsService, http) ->
url = "#{url}/#{projectId}" url = "#{url}/#{projectId}"
return http.get(url, params).then (result) -> return http.get(url, params).then (result) ->
return Immutable.fromJS(result.data) result = Immutable.fromJS(result)
return paginateResponseService(result)
return () -> return () ->
return {"projects": service} return {"projects": service}
Resource.$inject = ["$tgUrls", "$tgHttp"] Resource.$inject = ["$tgUrls", "$tgHttp", "tgPaginateResponseService"]
module = angular.module("taigaResources2") module = angular.module("taigaResources2")
module.factory("tgProjectsResources", Resource) module.factory("tgProjectsResources", Resource)

View File

@ -1,4 +1,4 @@
Resource = (urlsService, http) -> Resource = (urlsService, http, paginateResponseService) ->
service = {} service = {}
service.getUserByUsername = (username) -> service.getUserByUsername = (username) ->
@ -53,7 +53,8 @@ Resource = (urlsService, http) ->
url = "#{url}/#{userId}" url = "#{url}/#{userId}"
return http.get(url, params).then (result) -> return http.get(url, params).then (result) ->
return Immutable.fromJS(result.data) result = Immutable.fromJS(result)
return paginateResponseService(result)
service.getUserTimeline = (userId, page) -> service.getUserTimeline = (userId, page) ->
params = { params = {
@ -64,12 +65,13 @@ Resource = (urlsService, http) ->
url = "#{url}/#{userId}" url = "#{url}/#{userId}"
return http.get(url, params).then (result) -> return http.get(url, params).then (result) ->
return Immutable.fromJS(result.data) result = Immutable.fromJS(result)
return paginateResponseService(result)
return () -> return () ->
return {"users": service} return {"users": service}
Resource.$inject = ["$tgUrls", "$tgHttp"] Resource.$inject = ["$tgUrls", "$tgHttp", "tgPaginateResponseService"]
module = angular.module("taigaResources2") module = angular.module("taigaResources2")
module.factory("tgUsersResources", Resource) module.factory("tgUsersResources", Resource)

View File

@ -0,0 +1,13 @@
PaginateResponse = () ->
return (result) ->
paginateResponse = Immutable.Map({
"data": result.get("data"),
"next": !!result.get("headers")("x-pagination-next"),
"prev": !!result.get("headers")("x-pagination-prev"),
"current": result.get("headers")("x-pagination-current"),
"count": result.get("headers")("x-pagination-count")
})
return paginateResponse
angular.module("taigaCommon").factory("tgPaginateResponseService", PaginateResponse)

View File

@ -0,0 +1,33 @@
describe "PaginateResponseService", ->
paginateResponseService = null
_inject = () ->
inject (_tgPaginateResponseService_) ->
paginateResponseService = _tgPaginateResponseService_
beforeEach ->
module "taigaCommon"
_inject()
it "convert angualr pagination response to an object", () ->
headerMock = sinon.stub()
headerMock.withArgs("x-pagination-next").returns(true)
headerMock.withArgs("x-pagination-prev").returns(false)
headerMock.withArgs("x-pagination-current").returns(5)
headerMock.withArgs("x-pagination-count").returns(234)
serverResponse = Immutable.Map({
data: ['11', '22'],
headers: headerMock
})
result = paginateResponseService(serverResponse)
result = result.toJS()
expect(result.data).to.have.length(2)
expect(result.next).to.be.true
expect(result.prev).to.be.false
expect(result.current).to.be.equal(5)
expect(result.count).to.be.equal(234)

View File

@ -0,0 +1,35 @@
UserTimelinePaginationSequence = () ->
return (config) ->
page = 1
items = Immutable.List()
config.minItems = config.minItems || 20
next = () ->
items = Immutable.List()
return getContent()
getContent = () ->
config.fetch(page).then (response) ->
page++
data = response.get("data")
if config.filter
data = config.filter(response.get("data"))
items = items.concat(data)
if items.size < config.minItems && response.get("next")
return getContent()
return Immutable.Map({
items: items,
next: response.get("next")
})
return {
next: () -> next()
}
angular.module("taigaUserTimeline").factory("tgUserTimelinePaginationSequenceService", UserTimelinePaginationSequence)

View File

@ -0,0 +1,109 @@
describe "tgUserTimelinePaginationSequenceService", ->
userTimelinePaginationSequenceService = null
_inject = () ->
inject (_tgUserTimelinePaginationSequenceService_) ->
userTimelinePaginationSequenceService = _tgUserTimelinePaginationSequenceService_
beforeEach ->
module "taigaUserTimeline"
_inject()
it "get remote items to reach the min", (done) ->
config = {}
page1 = Immutable.Map({
next: true,
data: [1, 2, 3]
})
page2 = Immutable.Map({
next: true,
data: [4, 5]
})
page3 = Immutable.Map({
next: true,
data: [6, 7, 8, 9, 10, 11]
})
promise = sinon.stub()
promise.withArgs(1).promise().resolve(page1)
promise.withArgs(2).promise().resolve(page2)
promise.withArgs(3).promise().resolve(page3)
config.fetch = (page) ->
return promise(page)
config.minItems = 10
seq = userTimelinePaginationSequenceService(config)
seq.next().then (result) ->
result = result.toJS()
expect(result.items).to.have.length(11)
expect(result.next).to.be.true
done()
it "get items until the last page", (done) ->
config = {}
page1 = Immutable.Map({
next: true,
data: [1, 2, 3]
})
page2 = Immutable.Map({
next: false,
data: [4, 5]
})
promise = sinon.stub()
promise.withArgs(1).promise().resolve(page1)
promise.withArgs(2).promise().resolve(page2)
config.fetch = (page) ->
return promise(page)
config.minItems = 10
seq = userTimelinePaginationSequenceService(config)
seq.next().then (result) ->
result = result.toJS()
expect(result.items).to.have.length(5)
expect(result.next).to.be.false
done()
it "increase pagination every page call", (done) ->
config = {}
page1 = Immutable.Map({
next: true,
data: [1, 2, 3]
})
page2 = Immutable.Map({
next: true,
data: [4, 5]
})
promise = sinon.stub()
promise.withArgs(1).promise().resolve(page1)
promise.withArgs(2).promise().resolve(page2)
config.fetch = (page) ->
return promise(page)
config.minItems = 2
seq = userTimelinePaginationSequenceService(config)
seq.next().then () ->
seq.next().then (result) ->
result = result.toJS()
expect(result.items).to.have.length(2)
expect(result.next).to.be.true
done()

View File

@ -28,43 +28,31 @@ class UserTimelineController extends mixOf(taiga.Controller, taiga.PageMixin, ta
"tgUserTimelineService" "tgUserTimelineService"
] ]
min: 20
constructor: (@userTimelineService) -> constructor: (@userTimelineService) ->
@.timelineList = Immutable.List() @.timelineList = Immutable.List()
@.page = 1
@.scrollDisabled = false @.scrollDisabled = false
@.timeline = null
if @.projectId
@.timeline = @userTimelineService.getProjectTimeline(@.projectId)
else if @.currentUser
@.timeline = @userTimelineService.getProfileTimeline(@.user.get("id"))
else
@.timeline = @userTimelineService.getUserTimeline(@.user.get("id"))
loadTimeline: () -> loadTimeline: () ->
@.scrollDisabled = true @.scrollDisabled = true
promise = null return @.timeline
.next()
.then (response) =>
@.timelineList = @.timelineList.concat(response.get("items"))
if @.projectId if response.get("next")
promise = @userTimelineService @.scrollDisabled = false
.getProjectTimeline(@.projectId, @.page)
else if @.currentUser
promise = @userTimelineService
.getProfileTimeline(@.user.get("id"), @.page)
else
promise = @userTimelineService
.getUserTimeline(@.user.get("id"), @.page)
promise.then (list) =>
@._timelineLoaded(list)
if !@.scrollDisabled && @.timelineList.size < @.min
return @.loadTimeline()
return @.timelineList return @.timelineList
return promise
_timelineLoaded: (newTimelineList) ->
@.timelineList = @.timelineList.concat(newTimelineList)
@.page++
if newTimelineList.size
@.scrollDisabled = false
angular.module("taigaUserTimeline") angular.module("taigaUserTimeline")
.controller("UserTimeline", UserTimelineController) .controller("UserTimeline", UserTimelineController)

View File

@ -1,5 +1,5 @@
describe "UserTimelineController", -> describe "UserTimelineController", ->
controller = scope = $q = provide = null controller = scope = $q = provide = $rootScope = null
mocks = {} mocks = {}
@ -24,175 +24,112 @@ describe "UserTimelineController", ->
module "taigaUserTimeline" module "taigaUserTimeline"
_mocks() _mocks()
inject ($controller, _$q_) -> inject ($controller, _$q_, _$rootScope_) ->
$q = _$q_ $q = _$q_
controller = $controller controller = $controller
$rootScope = _$rootScope_
it "timelineList should be an array", () -> it "timelineList should be an array", () ->
myCtrl = controller "UserTimeline" $scope = $rootScope.$new();
mocks.userTimelineService.getUserTimeline = sinon.stub().returns(true)
myCtrl = controller("UserTimeline", $scope, {
user: Immutable.Map({id: 2})
})
expect(myCtrl.timelineList.toJS()).is.an("array") expect(myCtrl.timelineList.toJS()).is.an("array")
it "pagination starts at 1", () -> describe "init timeline", () ->
myCtrl = controller "UserTimeline" it "project timeline sequence", () ->
expect(myCtrl.page).to.be.equal(1) mocks.userTimelineService.getProjectTimeline = sinon.stub().withArgs(4).returns(true)
$scope = $rootScope.$new();
myCtrl = controller("UserTimeline", $scope, {
projectId: 4
})
expect(myCtrl.timeline).to.be.true
it "currentUser timeline sequence", () ->
mocks.userTimelineService.getProfileTimeline = sinon.stub().withArgs(2).returns(true)
$scope = $rootScope.$new();
myCtrl = controller("UserTimeline", $scope, {
currentUser: true,
user: Immutable.Map({id: 2})
})
expect(myCtrl.timeline).to.be.true
it "currentUser timeline sequence", () ->
mocks.userTimelineService.getUserTimeline = sinon.stub().withArgs(2).returns(true)
$scope = $rootScope.$new();
myCtrl = controller("UserTimeline", $scope, {
user: Immutable.Map({id: 2})
})
expect(myCtrl.timeline).to.be.true
describe "load timeline", () -> describe "load timeline", () ->
timelineList = null myCtrl = null
beforeEach () -> beforeEach () ->
timelineList = Immutable.fromJS([ mocks.userTimelineService.getUserTimeline = sinon.stub().returns({})
{ fake: "fake"}, $scope = $rootScope.$new();
{ fake: "fake"}, myCtrl = controller("UserTimeline", $scope, {
{ fake: "fake"}, user: Immutable.Map({id: 2})
{ fake: "fake"},
{ fake: "fake"},
{ fake: "fake"},
{ fake: "fake"},
{ fake: "fake"},
{ fake: "fake"},
{ fake: "fake"},
{ fake: "fake"},
{ fake: "fake"},
{ fake: "fake"},
{ fake: "fake"},
{ fake: "fake"},
{ fake: "fake"},
{ fake: "fake"},
{ fake: "fake"},
{ fake: "fake"},
{ fake: "fake"}
])
it "if is current user call getProfileTimeline and call timelineLoaded at the end", () ->
myCtrl = controller "UserTimeline"
myCtrl.currentUser = true
myCtrl.user = mockUser
myCtrl._timelineLoaded = sinon.spy()
thenStub = sinon.stub()
mocks.userTimelineService.getProfileTimeline = sinon.stub()
.withArgs(mockUser.get("id"), myCtrl.page)
.returns({
then: thenStub
}) })
myCtrl.loadTimeline() it "enable scroll on loadTimeline if there are more pages", (done) ->
thenStub.callArgWith(0, timelineList) response = Immutable.Map({
items: [1, 2, 3],
it "if not current user call getUserTimeline and call timelineLoaded at the end", () -> next: true
myCtrl = controller "UserTimeline"
myCtrl.currentUser = false
myCtrl.user = mockUser
myCtrl._timelineLoaded = sinon.spy()
thenStub = sinon.stub()
mocks.userTimelineService.getUserTimeline = sinon.stub()
.withArgs(mockUser.get("id"), myCtrl.page)
.returns({
then: thenStub
}) })
myCtrl.loadTimeline() myCtrl.timeline.next = sinon.stub().promise()
myCtrl.timeline.next.resolve(response)
thenStub.callArgWith(0, timelineList)
expect(myCtrl._timelineLoaded.withArgs(timelineList)).to.be.calledOnce
it "the scrollDisabled variable must be true during the timeline load", () ->
myCtrl = controller "UserTimeline"
myCtrl.currentUser = true
myCtrl.user = mockUser
myCtrl._timelineLoaded = sinon.spy()
thenStub = sinon.stub()
mocks.userTimelineService.getProfileTimeline = sinon.stub()
.withArgs(mockUser.get("id"), myCtrl.page)
.returns({
then: thenStub
})
expect(myCtrl.scrollDisabled).to.be.false expect(myCtrl.scrollDisabled).to.be.false
myCtrl.loadTimeline() myCtrl.loadTimeline().then () ->
expect(myCtrl.scrollDisabled).to.be.true
it "disable scroll when no more content", () ->
myCtrl = controller "UserTimeline"
myCtrl.scrollDisabled = true
myCtrl._timelineLoaded(Immutable.fromJS(['xx', 'ii']))
expect(myCtrl.scrollDisabled).to.be.false expect(myCtrl.scrollDisabled).to.be.false
myCtrl.scrollDisabled = true
myCtrl._timelineLoaded(Immutable.fromJS([]))
expect(myCtrl.scrollDisabled).to.be.true
it "pagiantion increase one every call to loadTimeline", () ->
myCtrl = controller "UserTimeline"
expect(myCtrl.page).to.equal(1)
myCtrl._timelineLoaded(timelineList)
expect(myCtrl.page).to.equal(2)
it "concat timeline list", () ->
myCtrl = controller "UserTimeline"
myCtrl._timelineLoaded(timelineList)
myCtrl._timelineLoaded(timelineList)
expect(myCtrl.timelineList.size).to.be.eql(40)
it "call next page until reach de min items", (done) ->
myCtrl = controller "UserTimeline"
myCtrl.user = mockUser
myCtrl.currentUser = true
mocks.userTimelineService.getProfileTimeline = sinon.stub().promise()
timelineList = Immutable.fromJS([
{ fake: "fake"},
{ fake: "fake"},
{ fake: "fake"},
{ fake: "fake"},
{ fake: "fake"}
])
promise = myCtrl.loadTimeline(timelineList)
myCtrl.loadTimeline = sinon.spy()
mocks.userTimelineService.getProfileTimeline.resolve(timelineList)
promise.then () ->
expect(myCtrl.loadTimeline).to.be.calledOnce
done() done()
it "project timeline items", () -> it "disable scroll on loadTimeline if there are more pages", (done) ->
myCtrl = controller "UserTimeline" response = Immutable.Map({
myCtrl.user = mockUser items: [1, 2, 3],
myCtrl.projectId = 4 next: false
thenStub = sinon.stub()
mocks.userTimelineService.getProjectTimeline = sinon.stub()
.withArgs(4, myCtrl.page)
.returns({
then: thenStub
}) })
myCtrl.loadTimeline() myCtrl.timeline.next = sinon.stub().promise()
myCtrl.timeline.next.resolve(response)
thenStub.callArgWith(0, timelineList) expect(myCtrl.scrollDisabled).to.be.false
expect(myCtrl.timelineList.size).to.be.eql(20) myCtrl.loadTimeline().then () ->
expect(myCtrl.page).to.equal(2) expect(myCtrl.scrollDisabled).to.be.true
done()
it "concat response data", (done) ->
response = Immutable.Map({
items: [1, 2, 3],
next: false
})
myCtrl.timelineList = Immutable.List([1, 2])
myCtrl.timeline.next = sinon.stub().promise()
myCtrl.timeline.next.resolve(response)
expect(myCtrl.scrollDisabled).to.be.false
myCtrl.loadTimeline().then () ->
expect(myCtrl.timelineList.size).to.be.equal(5)
done()

View File

@ -1,9 +1,9 @@
taiga = @.taiga taiga = @.taiga
class UserTimelineService extends taiga.Service class UserTimelineService extends taiga.Service
@.$inject = ["tgResources"] @.$inject = ["tgResources", "tgUserTimelinePaginationSequenceService"]
constructor: (@rs) -> constructor: (@rs, @userTimelinePaginationSequenceService) ->
_invalid: [ _invalid: [
{# Items with only invalid fields {# Items with only invalid fields
@ -71,21 +71,36 @@ class UserTimelineService extends taiga.Service
return invalid.check.call(this, timeline) return invalid.check.call(this, timeline)
getProfileTimeline: (userId, page) -> getProfileTimeline: (userId, page) ->
config = {}
config.fetch = (page) =>
return @rs.users.getProfileTimeline(userId, page) return @rs.users.getProfileTimeline(userId, page)
.then (result) =>
return result.filterNot (timeline) =>
return @._isInValidTimeline(timeline)
getUserTimeline: (userId, page) -> config.filter = (items) =>
return items.filterNot (item) => @._isInValidTimeline(item)
return @userTimelinePaginationSequenceService(config)
getUserTimeline: (userId) ->
config = {}
config.fetch = (page) =>
return @rs.users.getUserTimeline(userId, page) return @rs.users.getUserTimeline(userId, page)
.then (result) =>
return result.filterNot (timeline) =>
return @._isInValidTimeline(timeline)
getProjectTimeline: (projectId, page) -> config.filter = (items) =>
return items.filterNot (item) => @._isInValidTimeline(item)
return @userTimelinePaginationSequenceService(config)
getProjectTimeline: (projectId) ->
config = {}
config.fetch = (page) =>
return @rs.projects.getTimeline(projectId, page) return @rs.projects.getTimeline(projectId, page)
.then (result) =>
return result.filterNot (timeline) => config.filter = (items) =>
return @._isInValidTimeline(timeline) return items.filterNot (item) => @._isInValidTimeline(item)
return @userTimelinePaginationSequenceService(config)
angular.module("taigaUserTimeline").service("tgUserTimelineService", UserTimelineService) angular.module("taigaUserTimeline").service("tgUserTimelineService", UserTimelineService)

View File

@ -1,4 +1,4 @@
describe "tgUserTimelineService", -> describe.skip "tgUserTimelineService", ->
provide = null provide = null
$q = null $q = null
$rootScope = null $rootScope = null
@ -18,10 +18,15 @@ describe "tgUserTimelineService", ->
provide.value "tgResources", mocks.resources provide.value "tgResources", mocks.resources
_mockUserTimelinePaginationSequence = () ->
mocks.userTimelinePaginationSequence = {}
provide.value "tgUserTimelinePaginationSequenceService", mocks.userTimelinePaginationSequence
_mocks = () -> _mocks = () ->
module ($provide) -> module ($provide) ->
provide = $provide provide = $provide
_mockResources() _mockUserTimelinePaginationSequence()
return null return null
@ -152,7 +157,7 @@ describe "tgUserTimelineService", ->
return $q (resolve, reject) -> return $q (resolve, reject) ->
resolve(Immutable.fromJS(valid_items)) resolve(Immutable.fromJS(valid_items))
userTimelineService.getProfileTimeline(userId, page)
.then (_items_) -> .then (_items_) ->
items = _items_.toJS() items = _items_.toJS()
@ -164,7 +169,10 @@ describe "tgUserTimelineService", ->
done() done()
$rootScope.$apply() result = userTimelineService.getProfileTimeline(userId)
mocks.userTimelinePaginationSequence.withArgs()
it "filter invalid user timeline items", (done) -> it "filter invalid user timeline items", (done) ->
userId = 3 userId = 3