timeline with immutable objects
parent
c2c40ac7a4
commit
2eef2a35f4
|
@ -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")
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
ProfileTimelineDirective = ->
|
||||
return {
|
||||
templateUrl: "profile/profile-timeline/profile-timeline.html",
|
||||
controller: "ProfileTimeline",
|
||||
controllerAs: "vm",
|
||||
scope: {}
|
||||
}
|
||||
|
||||
angular.module("taigaProfile").directive("tgProfileTimeline", ProfileTimelineDirective)
|
|
@ -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")
|
|
@ -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)
|
|
@ -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()
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue