timeline with immutable objects

stable
Juanfran 2015-05-06 10:55:32 +02:00
parent c2c40ac7a4
commit 2eef2a35f4
13 changed files with 238 additions and 118 deletions

View File

@ -1,3 +0,0 @@
section.profile-timeline(ng-controller="ProfileTimeline as ctrl")
div(infinite-scroll="ctrl.loadTimeline()", infinite-scroll-distance="3", infinite-scroll-disabled='ctrl.loadingData')
div(ng-repeat="timeline in ctrl.timelineList", tg-profile-timeline-item="timeline")

View File

@ -21,7 +21,7 @@ class ProfileTimelineItemTitle
if param == "username"
user = timeline.data.user
title_attr = @translate.instant('COMMON.SEE_USER_PROFILE', {username: user.username})
url = 'user-profile:username=activity.user.username'
url = 'user-profile:username=vm.activity.user.username'
return @._getLink(url, user.username, title_attr)
else if param == 'field_name'
@ -30,12 +30,12 @@ class ProfileTimelineItemTitle
return @translate.instant(@._fieldTranslationKey[field_name])
else if param == 'project_name'
url = 'project:project=activity.project.slug'
url = 'project:project=vm.activity.project.slug'
return @._getLink(url, timeline.data.project.name)
else if param == 'sprint_name'
url = 'project-taskboard:project=activity.project.slug,sprint=activity.sprint.slug'
url = 'project-taskboard:project=vm.activity.project.slug,sprint=vm.activity.sprint.slug'
return @._getLink(url, timeline.data.milestone.name)

View File

@ -52,7 +52,7 @@ describe "tgProfileTimelineItemTitle", ->
.returns('user-param')
usernamelink = sinon.match ((value) ->
return value.username == '<a tg-nav="user-profile:username=activity.user.username" title="user-param">xx</a>'
return value.username == '<a tg-nav="user-profile:username=vm.activity.user.username" title="user-param">xx</a>'
), "usernamelink"
mockTranslate.instant
@ -113,7 +113,7 @@ describe "tgProfileTimelineItemTitle", ->
}
projectparam = sinon.match ((value) ->
return value.project_name == '<a tg-nav="project:project=activity.project.slug" title="project_name">project_name</a>'
return value.project_name == '<a tg-nav="project:project=vm.activity.project.slug" title="project_name">project_name</a>'
), "projectparam"
mockTranslate.instant
@ -141,7 +141,7 @@ describe "tgProfileTimelineItemTitle", ->
}
milestoneparam = sinon.match ((value) ->
return value.sprint_name == '<a tg-nav="project-taskboard:project=activity.project.slug,sprint=activity.sprint.slug" title="milestone_name">milestone_name</a>'
return value.sprint_name == '<a tg-nav="project-taskboard:project=vm.activity.project.slug,sprint=vm.activity.sprint.slug" title="milestone_name">milestone_name</a>'
), "milestoneparam"
mockTranslate.instant

View File

@ -1,31 +1,33 @@
class ProfileTimelineItemController
@.$inject = [
"$scope",
"$sce",
"tgProfileTimelineItemType",
"tgProfileTimelineItemTitle"
]
constructor: (@scope, @sce, @profileTimelineItemType, @profileTimelineItemTitle) ->
event = @parseEventType(@scope.vm.timeline.event_type)
type = @profileTimelineItemType.getType(@scope.vm.timeline, event)
constructor: (@sce, @profileTimelineItemType, @profileTimelineItemTitle) ->
timeline = @.timeline
event = @.parseEventType(timeline.event_type)
type = @profileTimelineItemType.getType(timeline, event)
@.activity = {}
@.activity.user = @scope.vm.timeline.data.user
@.activity.project = @scope.vm.timeline.data.project
@.activity.sprint = @scope.vm.timeline.data.milestone
@.activity.title = @profileTimelineItemTitle.getTitle(@scope.vm.timeline, event, type)
@.activity.created_formated = moment(@scope.vm.timeline.created).fromNow()
@.activity.user = timeline.data.user
@.activity.project = timeline.data.project
@.activity.sprint = timeline.data.milestone
@.activity.title = @profileTimelineItemTitle.getTitle(timeline, event, type)
@.activity.created_formated = moment(timeline.created).fromNow()
#test
@.activity.obj = @.getObject(timeline, event)
if type.description
@.activity.description = @sce.trustAsHtml(type.description(@scope.vm.timeline))
@.activity.description = @sce.trustAsHtml(type.description(timeline))
if type.member
@.activity.member = type.member(@scope.vm.timeline)
@.activity.member = type.member(timeline)
if @scope.vm.timeline.data.values_diff?.attachments
@.activity.attachments = @scope.vm.timeline.data.values_diff.attachments.new
if timeline.data.values_diff?.attachments
@.activity.attachments = timeline.data.values_diff.attachments.new
parseEventType: (event_type) ->
event_type = event_type.split(".")
@ -36,5 +38,9 @@ class ProfileTimelineItemController
type: event_type[2]
}
getObject: (timeline, event) ->
if timeline.data[event.obj]
return timeline.data[event.obj]
angular.module("taigaProfile")
.controller("ProfileTimelineItem", ProfileTimelineItemController)

View File

@ -72,7 +72,7 @@ describe "ProfileTimelineItemController", ->
it "basic activity fields filled", () ->
timeline = scope.vm.timeline
myCtrl = controller("ProfileTimelineItem", {$scope: scope})
myCtrl = controller("ProfileTimelineItem", {$scope: scope}, {timeline: timeline})
expect(myCtrl.activity.user).to.be.equal(timeline.data.user)
expect(myCtrl.activity.project).to.be.equal(timeline.data.project)
@ -94,7 +94,7 @@ describe "ProfileTimelineItemController", ->
mockType.description.withArgs(timeline).returns(description)
mockType.member.withArgs(timeline).returns(member)
myCtrl = controller("ProfileTimelineItem", {$scope: scope})
myCtrl = controller("ProfileTimelineItem", {$scope: scope}, {timeline: timeline})
expect(myCtrl.activity.description).to.be.an('object') # $sce.trustAsHtml
expect(myCtrl.activity.member).to.be.equal(member)

View File

@ -14,7 +14,7 @@ div.activity-image
.profile-member-picture
img(ng-src="{{::vm.activity.member.user.photo}}", alt="{{::vm.activity.member.user.name}}")
.activity-member-info
a(tg-nav="user-profile:username=activity.member.user.username", title="{{::vm.activity.member.user.name }}")
a(tg-nav="user-profile:username=vm.activity.member.user.username", title="{{::vm.activity.member.user.name }}")
span {{::vm.activity.member.user.name}}
p {{::vm.activity.member.role.name}}

View File

@ -25,59 +25,26 @@ mixOf = @.taiga.mixOf
class ProfileTimelineController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.FiltersMixin)
@.$inject = [
"$tgResources",
"$tgAuth"
"$tgAuth",
"tgProfileTimelineService"
]
_valid_fields: [
'status',
'subject',
'description',
'assigned_to',
'points',
'severity',
'priority',
'type',
'attachments',
'milestone',
'is_blocked',
'is_iocaine',
'content_diff',
'name',
'estimated_finish',
'estimated_start'
]
constructor: (@rs, @auth) ->
@.timelineList = []
@.pagination = {page: 1}
constructor: (@auth, @profileTimelineService) ->
@.timelineList = Immutable.List()
@.page = 1
@.loadingData = false
_isValidField: (values) =>
return _.some values, (value) => @._valid_fields.indexOf(value) != -1
_filterValidTimelineItems: (timeline) =>
if timeline.data.values_diff
values = Object.keys(timeline.data.values_diff)
if values && values.length
if !@._isValidField(values)
return false
else if values[0] == 'attachments' && timeline.data.values_diff.attachments.new.length == 0
return false
return true
loadTimeline: () ->
user = @auth.getUser()
@.loadingData = true
return @rs.timeline.profile(user.id, @.pagination).then (result) =>
newTimelineList = _.filter result.data, @._filterValidTimelineItems
@.timelineList = @timelineList.concat(newTimelineList)
@.pagination.page++
@.loadingData = false
@profileTimelineService
.getTimeline(user.id, @.page)
.then (newTimelineList) =>
@.timelineList = @.timelineList.concat(newTimelineList)
@.page++
@.loadingData = false
angular.module("taigaProfile")
.controller("ProfileTimeline", ProfileTimelineController)

View File

@ -1,13 +1,17 @@
describe "ProfileTimelineController", ->
myCtrl = scope = $q = provide = null
mocks = {}
mockUser = {id: 3}
_mockTgResources = () ->
provide.value "$tgResources", {
timeline: {}
_mockProfileTimeline = () ->
mocks.profileTimelineService = {
getTimeline: sinon.stub()
}
provide.value "tgProfileTimelineService", mocks.profileTimelineService
_mockTgAuth = () ->
provide.value "$tgAuth", {
getUser: () ->
@ -17,7 +21,7 @@ describe "ProfileTimelineController", ->
_mocks = () ->
module ($provide) ->
provide = $provide
_mockTgResources()
_mockProfileTimeline()
_mockTgAuth()
return null
@ -32,60 +36,31 @@ describe "ProfileTimelineController", ->
myCtrl = $controller "ProfileTimeline"
it "timelineList should be an array", () ->
expect(myCtrl.timelineList).is.an("array")
expect(myCtrl.timelineList.toJS()).is.an("array")
it "pagination starts at 1", () ->
expect(myCtrl.pagination.page).to.be.equal(1)
expect(myCtrl.page).to.be.equal(1)
describe "load timeline", () ->
thenStub = timelineList = null
beforeEach () ->
timelineList = {
data: [
{ # valid item
data: {
values_diff: {
"status": "xx",
"subject": "xx"
}
}
},
{ # invalid item
data: {
values_diff: {
"fake": "xx"
}
}
},
{ # invalid item
data: {
values_diff: {
"fake2": "xx"
}
}
},
{ # valid item
data: {
values_diff: {
"fake2": "xx",
"milestone": "xx"
}
}
}
]
}
timelineList = Immutable.fromJS([
{ fake: "fake"},
{ fake: "fake"},
{ fake: "fake"},
{ fake: "fake"}
])
thenStub = sinon.stub()
profileStub = sinon.stub()
.withArgs(mockUser.id, myCtrl.pagination)
.withArgs(mockUser.id, myCtrl.page)
.returns({
then: thenStub
})
myCtrl.rs.timeline.profile = profileStub
mocks.profileTimelineService.getTimeline = profileStub
it "the loadingData variable must be true during the timeline load", () ->
expect(myCtrl.loadingData).to.be.false
@ -99,18 +74,17 @@ describe "ProfileTimelineController", ->
expect(myCtrl.loadingData).to.be.false
it "pagiantion increase one every call to loadTimeline", () ->
expect(myCtrl.pagination.page).to.equal(1)
expect(myCtrl.page).to.equal(1)
myCtrl.loadTimeline()
thenStub.callArgWith(0, timelineList)
expect(myCtrl.pagination.page).to.equal(2)
expect(myCtrl.page).to.equal(2)
it "filter the invalid timeline items", () ->
it "timeline items", () ->
myCtrl.loadTimeline()
thenStub.callArgWith(0, timelineList)
expect(myCtrl.timelineList[0]).to.be.equal(timelineList.data[0])
expect(myCtrl.timelineList[1]).to.be.equal(timelineList.data[3])
expect(myCtrl.timelineList.size).to.be.eql(4)

View File

@ -0,0 +1,9 @@
ProfileTimelineDirective = ->
return {
templateUrl: "profile/profile-timeline/profile-timeline.html",
controller: "ProfileTimeline",
controllerAs: "vm",
scope: {}
}
angular.module("taigaProfile").directive("tgProfileTimeline", ProfileTimelineDirective)

View File

@ -0,0 +1,3 @@
section.profile-timeline
div(infinite-scroll="vm.loadTimeline()", infinite-scroll-distance="3", infinite-scroll-disabled='vm.loadingData')
div(tg-repeat="timeline in vm.timelineList", tg-profile-timeline-item="timeline")

View File

@ -0,0 +1,51 @@
taiga = @.taiga
class ProfileTimelineService extends taiga.Service
@.$inject = ["$tgResources"]
constructor: (@rs) ->
_valid_fields: [
'status',
'subject',
'description',
'assigned_to',
'points',
'severity',
'priority',
'type',
'attachments',
'milestone',
'is_blocked',
'is_iocaine',
'content_diff',
'name',
'estimated_finish',
'estimated_start'
]
_isValidField: (values) =>
return _.some values, (value) => @._valid_fields.indexOf(value) != -1
_filterValidTimelineItems: (timeline) =>
if timeline.data.values_diff
values = Object.keys(timeline.data.values_diff)
if values && values.length
if !@._isValidField(values)
return false
else if values[0] == 'attachments' &&
timeline.data.values_diff.attachments.new.length == 0
return false
return true
getTimeline: (userId, page) ->
return @rs.timeline.profile(userId, page)
.then (result) =>
newTimelineList = _.filter result.data, @._filterValidTimelineItems
return Immutable.fromJS(newTimelineList)
angular.module("taigaProjects").service("tgProfileTimelineService", ProfileTimelineService)

View File

@ -0,0 +1,113 @@
describe "tgProfileTimelineService", ->
provide = null
$q = null
$rootScope = null
profileTimelineService = null
mocks = {}
_mockResources = () ->
mocks.resources = {}
mocks.resources.timeline = {
profile: sinon.stub()
}
provide.value "$tgResources", mocks.resources
_mocks = () ->
module ($provide) ->
provide = $provide
_mockResources()
return null
_setup = ->
_mocks()
_inject = (callback) ->
inject (_tgProfileTimelineService_, _$q_, _$rootScope_) ->
profileTimelineService = _tgProfileTimelineService_
$q = _$q_
$rootScope = _$rootScope_
callback() if callback
beforeEach ->
module "taigaProjects"
_setup()
_inject()
it "filter invalid timeline items", (done) ->
valid_items = {
data: [
{ # valid item
data: {
values_diff: {
"status": "xx",
"subject": "xx"
}
}
},
{ # invalid item
data: {
values_diff: {
"fake": "xx"
}
}
},
{ # invalid item
data: {
values_diff: {
"fake2": "xx"
}
}
},
{ # valid item
data: {
values_diff: {
"fake2": "xx",
"milestone": "xx"
}
}
},
{ # invalid item
data: {
values_diff: {
attachments: {
new: []
}
}
}
},
{ # valid item
data: {
values_diff: {
attachments: {
new: [1, 2]
}
}
}
}
]
}
userId = 3
page = 2
mocks.resources.timeline.profile = (_userId_, _page_) ->
expect(_userId_).to.be.equal(userId)
expect(_page_).to.be.equal(page)
return $q (resolve, reject) ->
resolve(valid_items)
profileTimelineService.getTimeline(userId, page)
.then (_items_) ->
items = _items_.toJS()
expect(items[0]).to.be.eql(valid_items.data[0])
expect(items[1]).to.be.eql(valid_items.data[3])
expect(items[2]).to.be.eql(valid_items.data[5])
done()
$rootScope.$apply()

View File

@ -7,7 +7,7 @@ div.profile.centered
div.content-wrapper
div.content
div(tg-profile-tab="activity", tab-title="Activity Tab", tab-icon="icon-timeline", tab-active)
include includes/profile-timeline
div(tg-profile-timeline)
div(tg-profile-tab="projects", tab-title="Projects Tab", tab-icon="icon-project")
div(tg-profile-projects)