Merge pull request #570 from taigaio/timeline-issues

fix several timeline bugs
stable
Alejandro 2015-07-20 10:34:16 +02:00
commit 23fc1372d3
19 changed files with 427 additions and 337 deletions

View File

@ -71,16 +71,39 @@ NavigationUrlsDirective = ($navurls, $auth, $q, $location) ->
parseNav = (data, $scope) -> parseNav = (data, $scope) ->
[name, params] = _.map(data.split(":"), trim) [name, params] = _.map(data.split(":"), trim)
if params if params
params = _.map(params.split(","), trim) # split by 'xxx='
# example
# project=vm.timeline.getIn(['data', 'project', 'slug']), ref=vm.timeline.getIn(['obj', 'ref'])
# ["", "project", "vm.timeline.getIn(['data', 'project', 'slug']), ", "ref", "vm.timeline.getIn(['obj', 'ref'])"]
result = params.split(/(\w+)=/)
# remove empty string
result = _.filter result, (str) -> return str.length
# remove , at the end of the string
result = _.map result, (str) -> return trim(str.replace(/,$/g, ''))
params = []
index = 0
# ['param1', 'value'] => [{'param1': 'value'}]
while index < result.length
obj = {}
obj[result[index]] = result[index + 1]
params.push obj
index = index + 2
else else
params = [] params = []
values = _.map(params, (x) -> trim(x.split("=")[1]))
values = _.map params, (param) -> _.values(param)[0]
promises = _.map(values, (x) -> bindOnceP($scope, x)) promises = _.map(values, (x) -> bindOnceP($scope, x))
return $q.all(promises).then -> return $q.all(promises).then ->
options = {} options = {}
for item in params for param in params
[key, value] = _.map(item.split("="), trim) key = Object.keys(param)[0]
value = param[key]
options[key] = $scope.$eval(value) options[key] = $scope.$eval(value)
return [name, options] return [name, options]

View File

@ -1248,6 +1248,7 @@
"MILESTONE_UPDATED": "{{username}} has updated the sprint {{obj_name}}", "MILESTONE_UPDATED": "{{username}} has updated the sprint {{obj_name}}",
"US_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the US {{obj_name}}", "US_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the US {{obj_name}}",
"US_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the US {{obj_name}} to {{new_value}}", "US_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the US {{obj_name}} to {{new_value}}",
"US_UPDATED_POINTS": "{{username}} has updated the '{{role_name}}' role of the US {{obj_name}} to {{new_value}}",
"ISSUE_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the issue {{obj_name}}", "ISSUE_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the issue {{obj_name}}",
"ISSUE_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the issue {{obj_name}} to {{new_value}}", "ISSUE_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the issue {{obj_name}} to {{new_value}}",
"TASK_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the task {{obj_name}} to {{new_value}}", "TASK_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the task {{obj_name}} to {{new_value}}",

View File

@ -35,4 +35,4 @@ describe "tgFeedbackService", ->
params = { params = {
"class": "lightbox lightbox-feedback lightbox-generic-form" "class": "lightbox lightbox-feedback lightbox-generic-form"
} }
expect(mocks.tgLightboxFactory.create.calledWith("tg-lb-feedback", params)).to.be.true() expect(mocks.tgLightboxFactory.create.calledWith("tg-lb-feedback", params)).to.be.true

View File

@ -87,7 +87,7 @@ describe "dropdownUserDirective", () ->
vm.logout() vm.logout()
expect(mockTgAuth.logout.callCount).to.be.equal(1) expect(mockTgAuth.logout.callCount).to.be.equal(1)
expect(mockTgLocation.path.callCount).to.be.equal(1) expect(mockTgLocation.path.callCount).to.be.equal(1)
expect(mockTgLocation.path.calledWith("/login")).to.be.true() expect(mockTgLocation.path.calledWith("/login")).to.be.true
it "dropdown user send feedback", () -> it "dropdown user send feedback", () ->
elm = createDirective() elm = createDirective()

View File

@ -1,5 +1,5 @@
// timeline-attachment directive // timeline-attachment directive
div.activity-image-attachment div.activity-image-attachment
blockquote blockquote
a(href="{{::attachment.url}}", title="See {{::attachment.filename}}", target="_blank") a(href="{{::attachment.get('url')}}", title="See {{::attachment.get('filename')}}", target="_blank")
img(ng-src="{{::attachment.thumb_url || attachment.url}}", alt="{{::attachment.filename}}") img(ng-src="{{::attachment.get('thumb_url') || attachment.get('url')}}", alt="{{::attachment.get('filename')}}")

View File

@ -8,7 +8,7 @@ UserTimelineAttachmentDirective = (template, $compile) ->
return url.indexOf(extension, url - extension.length) != -1 return url.indexOf(extension, url - extension.length) != -1
link = (scope, el) -> link = (scope, el) ->
is_image = isImage(scope.attachment.url) is_image = isImage(scope.attachment.get('url'))
if is_image if is_image
templateHtml = template.get("user-timeline/user-timeline-attachment/user-timeline-attachment-image.html") templateHtml = template.get("user-timeline/user-timeline-attachment/user-timeline-attachment-image.html")

View File

@ -32,9 +32,9 @@ describe "userTimelineAttachmentDirective", () ->
compile = $compile compile = $compile
it "attachment image template", () -> it "attachment image template", () ->
scope.attachment = { scope.attachment = Immutable.fromJS({
url: "path/path/file.jpg" url: "path/path/file.jpg"
} })
mockTgTemplate.get mockTgTemplate.get
.withArgs("user-timeline/user-timeline-attachment/user-timeline-attachment-image.html") .withArgs("user-timeline/user-timeline-attachment/user-timeline-attachment-image.html")
@ -45,9 +45,9 @@ describe "userTimelineAttachmentDirective", () ->
expect(elm.find('#image')).to.have.length(1) expect(elm.find('#image')).to.have.length(1)
it "attachment file template", () -> it "attachment file template", () ->
scope.attachment = { scope.attachment = Immutable.fromJS({
url: "path/path/file.pdf" url: "path/path/file.pdf"
} })
mockTgTemplate.get mockTgTemplate.get
.withArgs("user-timeline/user-timeline-attachment/user-timeline-attachment.html") .withArgs("user-timeline/user-timeline-attachment/user-timeline-attachment.html")

View File

@ -1,5 +1,5 @@
div.single-attachment div.single-attachment
blockquote blockquote
a(ng-href="{{ attachment.url }}", title="Click to download {{ attachment.filename }}", target="_blank") a(ng-href="{{ attachment.get('url') }}", title="Click to download {{ attachment.get('filename') }}", target="_blank")
span.icon.icon-document span.icon.icon-document
span {{attachment.filename}} span {{attachment.get('filename')}}

View File

@ -14,74 +14,93 @@ class UserTimelineItemTitle
'severity': 'ISSUES.FIELDS.SEVERITY', 'severity': 'ISSUES.FIELDS.SEVERITY',
'priority': 'ISSUES.FIELDS.PRIORITY', 'priority': 'ISSUES.FIELDS.PRIORITY',
'type': 'ISSUES.FIELDS.TYPE', 'type': 'ISSUES.FIELDS.TYPE',
'is_iocaine': 'TASK.FIELDS.IS_IOCAINE' 'is_iocaine': 'TASK.FIELDS.IS_IOCAINE',
'is_blocked': 'COMMON.FIELDS.IS_BLOCKED'
}
_params: {
username: (timeline, event) ->
user = timeline.getIn(['data', 'user'])
if user.get('is_profile_visible')
title_attr = @translate.instant('COMMON.SEE_USER_PROFILE', {username: user.get('username')})
url = "user-profile:username=vm.timeline.getIn(['data', 'user', 'username'])"
return @._getLink(url, user.get('name'), title_attr)
else
return @._getUsernameSpan(user.get('name'))
field_name: (timeline, event) ->
field_name = timeline.getIn(['data', 'value_diff', 'key'])
return @translate.instant(@._fieldTranslationKey[field_name])
project_name: (timeline, event) ->
url = "project:project=vm.timeline.getIn(['data', 'project', 'slug'])"
return @._getLink(url, timeline.getIn(["data", "project", "name"]))
new_value: (timeline, event) ->
if _.isArray(timeline.getIn(["data", "value_diff", "value"]).toJS())
value = timeline.getIn(["data", "value_diff", "value"]).get(1)
# assigned to unasigned
if value == null && timeline.getIn(["data", "value_diff", "key"]) == 'assigned_to'
value = @translate.instant('ACTIVITY.VALUES.UNASSIGNED')
return value
else
return timeline.getIn(["data", "value_diff", "value"]).first().get(1)
sprint_name: (timeline, event) ->
url = "project-taskboard:project=vm.timeline.getIn(['data', 'project', 'slug']),sprint=vm.timeline.getIn(['data', 'milestone', 'slug'])"
return @._getLink(url, timeline.getIn(['data', 'milestone', 'name']))
us_name: (timeline, event) ->
obj = @._getTimelineObj(timeline, event).get('userstory')
event_us = {obj: 'parent_userstory'}
url = @._getDetailObjUrl(event_us)
text = '#' + obj.get('ref') + ' ' + obj.get('subject')
return @._getLink(url, text)
obj_name: (timeline, event) ->
obj = @._getTimelineObj(timeline, event)
url = @._getDetailObjUrl(event)
if event.obj == 'wikipage'
text = unslugify(obj.get('slug'))
else if event.obj == 'milestone'
text = obj.get('name')
else
text = '#' + obj.get('ref') + ' ' + obj.get('subject')
return @._getLink(url, text)
role_name: (timeline, event) ->
return timeline.getIn(['data', 'value_diff', 'value']).keySeq().first()
} }
constructor: (@translate) -> constructor: (@translate) ->
_translateTitleParams: (param, timeline, event) -> _translateTitleParams: (param, timeline, event) ->
if param == "username" return @._params[param].call(this, timeline, event)
user = timeline.data.user
title_attr = @translate.instant('COMMON.SEE_USER_PROFILE', {username: user.username})
url = 'user-profile:username=vm.activity.user.username'
return @._getLink(url, user.name, title_attr)
else if param == 'field_name'
field_name = Object.keys(timeline.data.values_diff)[0]
return @translate.instant(@._fieldTranslationKey[field_name])
else if param == 'project_name'
url = 'project:project=vm.activity.project.slug'
return @._getLink(url, timeline.data.project.name)
else if param == 'new_value'
field_name = Object.keys(timeline.data.values_diff)[0]
return timeline.data.values_diff[field_name][1]
else if param == 'sprint_name'
url = 'project-taskboard:project=vm.activity.project.slug,sprint=vm.activity.sprint.slug'
return @._getLink(url, timeline.data.milestone.name)
else if param == 'us_name'
obj = @._getTimelineObj(timeline, event).userstory
event_us = {obj: 'parent_userstory'}
url = @._getDetailObjUrl(event_us)
text = '#' + obj.ref + ' ' + obj.subject
return @._getLink(url, text)
else if param == 'obj_name'
obj = @._getTimelineObj(timeline, event)
url = @._getDetailObjUrl(event)
if event.obj == 'wikipage'
text = unslugify(obj.slug)
else if event.obj == 'milestone'
text = obj.name
else
text = '#' + obj.ref + ' ' + obj.subject
return @._getLink(url, text)
_getTimelineObj: (timeline, event) -> _getTimelineObj: (timeline, event) ->
return timeline.data[event.obj] return timeline.getIn(['data', event.obj])
_getDetailObjUrl: (event) -> _getDetailObjUrl: (event) ->
url = { url = {
"issue": ["project-issues-detail", ":project=vm.activity.project.slug,ref=vm.activity.obj.ref"], "issue": ["project-issues-detail", ":project=vm.timeline.getIn(['data', 'project', 'slug']),ref=vm.timeline.getIn(['obj', 'ref'])"],
"wikipage": ["project-wiki-page", ":project=vm.activity.project.slug,slug=vm.activity.obj.slug"], "wikipage": ["project-wiki-page", ":project=vm.timeline.getIn(['data', 'project', 'slug']),slug=vm.timeline.getIn(['obj', 'ref'])"],
"task": ["project-tasks-detail", ":project=vm.activity.project.slug,ref=vm.activity.obj.ref"], "task": ["project-tasks-detail", ":project=vm.timeline.getIn(['data', 'project', 'slug']),ref=vm.timeline.getIn(['obj', 'ref'])"],
"userstory": ["project-userstories-detail", ":project=vm.activity.project.slug,ref=vm.activity.obj.ref"], "userstory": ["project-userstories-detail", ":project=vm.timeline.getIn(['data', 'project', 'slug']),ref=vm.timeline.getIn(['obj', 'ref'])"],
"parent_userstory": ["project-userstories-detail", ":project=vm.activity.project.slug,ref=vm.activity.obj.userstory.ref"], "parent_userstory": ["project-userstories-detail", ":project=vm.timeline.getIn(['data', 'project', 'slug']),ref=vm.timeline.getIn(['obj', 'userstory', 'ref'])"],
"milestone": ["project-taskboard", ":project=vm.activity.project.slug,sprint=vm.activity.obj.slug"] "milestone": ["project-taskboard", ":project=vm.timeline.getIn(['data', 'project', 'slug']),ref=vm.timeline.getIn(['obj', 'ref'])"]
} }
return url[event.obj][0] + url[event.obj][1] return url[event.obj][0] + url[event.obj][1]
@ -95,6 +114,14 @@ class UserTimelineItemTitle
.attr('title', title) .attr('title', title)
.prop('outerHTML') .prop('outerHTML')
_getUsernameSpan: (text) ->
title = title || text
return $('<span>')
.addClass('username')
.text(text)
.prop('outerHTML')
_getParams: (timeline, event, timeline_type) -> _getParams: (timeline, event, timeline_type) ->
params = {} params = {}

View File

@ -32,14 +32,15 @@ describe "tgUserTimelineItemTitle", ->
_setup() _setup()
it "title with username", () -> it "title with username", () ->
timeline = { timeline = Immutable.fromJS({
data: { data: {
user: { user: {
username: 'xx', username: 'xx',
name: 'oo' name: 'oo',
is_profile_visible: true
} }
} }
} })
event = {} event = {}
@ -49,11 +50,45 @@ describe "tgUserTimelineItemTitle", ->
} }
mockTranslate.instant mockTranslate.instant
.withArgs('COMMON.SEE_USER_PROFILE', {username: timeline.data.user.username}) .withArgs('COMMON.SEE_USER_PROFILE', {username: timeline.getIn(['data', 'user', 'username'])})
.returns('user-param') .returns('user-param')
usernamelink = sinon.match ((value) -> usernamelink = sinon.match ((value) ->
return value.username == '<a tg-nav="user-profile:username=vm.activity.user.username" title="user-param">oo</a>' return value.username == '<a tg-nav="user-profile:username=vm.timeline.getIn([\'data\', \'user\', \'username\'])" title="user-param">oo</a>'
), "usernamelink"
mockTranslate.instant
.withArgs('TITLE_USER_NAME', usernamelink)
.returns('title_ok')
title = mySvc.getTitle(timeline, event, type)
expect(title).to.be.equal("title_ok")
it "title with username not visible", () ->
timeline = Immutable.fromJS({
data: {
user: {
username: 'xx',
name: 'oo',
is_profile_visible: false
}
}
})
event = {}
type = {
key: 'TITLE_USER_NAME',
translate_params: ['username']
}
mockTranslate.instant
.withArgs('COMMON.SEE_USER_PROFILE', {username: timeline.getIn(['data', 'user', 'username'])})
.returns('user-param')
usernamelink = sinon.match ((value) ->
return value.username == '<span class="username">oo</span>'
), "usernamelink" ), "usernamelink"
mockTranslate.instant mockTranslate.instant
@ -65,13 +100,13 @@ describe "tgUserTimelineItemTitle", ->
expect(title).to.be.equal("title_ok") expect(title).to.be.equal("title_ok")
it "title with a field name", () -> it "title with a field name", () ->
timeline = { timeline = Immutable.fromJS({
data: { data: {
values_diff: { value_diff: {
status: {} key: 'status'
} }
} }
} })
event = {} event = {}
@ -92,19 +127,19 @@ describe "tgUserTimelineItemTitle", ->
.withArgs('TITLE_FIELD', fieldparam) .withArgs('TITLE_FIELD', fieldparam)
.returns('title_ok') .returns('title_ok')
title = mySvc.getTitle(timeline, event, type) title = mySvc.getTitle(timeline, event, type)
expect(title).to.be.equal("title_ok") expect(title).to.be.equal("title_ok")
it "title with new value", () -> it "title with new value", () ->
timeline = { timeline = Immutable.fromJS({
data: { data: {
values_diff: { value_diff: {
status: ['old', 'new'] key: 'status',
value: ['old', 'new']
} }
} }
} })
event = {} event = {}
@ -122,13 +157,13 @@ describe "tgUserTimelineItemTitle", ->
expect(title).to.be.equal("new_value_ok") expect(title).to.be.equal("new_value_ok")
it "title with project name", () -> it "title with project name", () ->
timeline = { timeline = Immutable.fromJS({
data: { data: {
project: { project: {
name: "project_name" name: "project_name"
} }
} }
} })
event = {} event = {}
@ -138,7 +173,7 @@ describe "tgUserTimelineItemTitle", ->
} }
projectparam = sinon.match ((value) -> projectparam = sinon.match ((value) ->
return value.project_name == '<a tg-nav="project:project=vm.activity.project.slug" title="project_name">project_name</a>' return value.project_name == '<a tg-nav="project:project=vm.timeline.getIn([\'data\', \'project\', \'slug\'])" title="project_name">project_name</a>'
), "projectparam" ), "projectparam"
mockTranslate.instant mockTranslate.instant
@ -150,13 +185,13 @@ describe "tgUserTimelineItemTitle", ->
expect(title).to.be.equal("title_ok") expect(title).to.be.equal("title_ok")
it "title with sprint name", () -> it "title with sprint name", () ->
timeline = { timeline = Immutable.fromJS({
data: { data: {
milestone: { milestone: {
name: "milestone_name" name: "milestone_name"
} }
} }
} })
event = {} event = {}
@ -166,7 +201,7 @@ describe "tgUserTimelineItemTitle", ->
} }
milestoneparam = sinon.match ((value) -> milestoneparam = sinon.match ((value) ->
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>' return value.sprint_name == '<a tg-nav="project-taskboard:project=vm.timeline.getIn([\'data\', \'project\', \'slug\']),sprint=vm.timeline.getIn([\'data\', \'milestone\', \'slug\'])" title="milestone_name">milestone_name</a>'
), "milestoneparam" ), "milestoneparam"
mockTranslate.instant mockTranslate.instant
@ -178,14 +213,14 @@ describe "tgUserTimelineItemTitle", ->
expect(title).to.be.equal("title_ok") expect(title).to.be.equal("title_ok")
it "title with object", () -> it "title with object", () ->
timeline = { timeline = Immutable.fromJS({
data: { data: {
issue: { issue: {
ref: '123', ref: '123',
subject: 'subject' subject: 'subject'
} }
} }
} })
event = { event = {
obj: 'issue', obj: 'issue',
@ -197,7 +232,7 @@ describe "tgUserTimelineItemTitle", ->
} }
objparam = sinon.match ((value) -> objparam = sinon.match ((value) ->
return value.obj_name == '<a tg-nav="project-issues-detail:project=vm.activity.project.slug,ref=vm.activity.obj.ref" title="#123 subject">#123 subject</a>' return value.obj_name == '<a tg-nav="project-issues-detail:project=vm.timeline.getIn([\'data\', \'project\', \'slug\']),ref=vm.timeline.getIn([\'obj\', \'ref\'])" title="#123 subject">#123 subject</a>'
), "objparam" ), "objparam"
mockTranslate.instant mockTranslate.instant
@ -209,13 +244,13 @@ describe "tgUserTimelineItemTitle", ->
expect(title).to.be.equal("title_ok") expect(title).to.be.equal("title_ok")
it "title obj wiki", () -> it "title obj wiki", () ->
timeline = { timeline = Immutable.fromJS({
data: { data: {
wikipage: { wikipage: {
slug: 'slug-wiki', slug: 'slug-wiki',
} }
} }
} })
event = { event = {
obj: 'wikipage', obj: 'wikipage',
@ -227,7 +262,7 @@ describe "tgUserTimelineItemTitle", ->
} }
objparam = sinon.match ((value) -> objparam = sinon.match ((value) ->
return value.obj_name == '<a tg-nav="project-wiki-page:project=vm.activity.project.slug,slug=vm.activity.obj.slug" title="Slug wiki">Slug wiki</a>' return value.obj_name == '<a tg-nav="project-wiki-page:project=vm.timeline.getIn([\'data\', \'project\', \'slug\']),slug=vm.timeline.getIn([\'obj\', \'ref\'])" title="Slug wiki">Slug wiki</a>'
), "objparam" ), "objparam"
mockTranslate.instant mockTranslate.instant
@ -239,13 +274,13 @@ describe "tgUserTimelineItemTitle", ->
expect(title).to.be.equal("title_ok") expect(title).to.be.equal("title_ok")
it "title obj milestone", () -> it "title obj milestone", () ->
timeline = { timeline = Immutable.fromJS({
data: { data: {
milestone: { milestone: {
name: 'milestone_name', name: 'milestone_name',
} }
} }
} })
event = { event = {
obj: 'milestone', obj: 'milestone',
@ -257,7 +292,7 @@ describe "tgUserTimelineItemTitle", ->
} }
objparam = sinon.match ((value) -> objparam = sinon.match ((value) ->
return value.obj_name == '<a tg-nav="project-taskboard:project=vm.activity.project.slug,sprint=vm.activity.obj.slug" title="milestone_name">milestone_name</a>' return value.obj_name == '<a tg-nav="project-taskboard:project=vm.timeline.getIn([\'data\', \'project\', \'slug\']),ref=vm.timeline.getIn([\'obj\', \'ref\'])" title="milestone_name">milestone_name</a>'
), "objparam" ), "objparam"
mockTranslate.instant mockTranslate.instant
@ -269,7 +304,7 @@ describe "tgUserTimelineItemTitle", ->
expect(title).to.be.equal("title_ok") expect(title).to.be.equal("title_ok")
it "task title with us_name", () -> it "task title with us_name", () ->
timeline = { timeline = Immutable.fromJS({
data: { data: {
task: { task: {
name: 'task_name', name: 'task_name',
@ -279,7 +314,7 @@ describe "tgUserTimelineItemTitle", ->
} }
} }
} }
} })
event = { event = {
obj: 'task', obj: 'task',
@ -291,41 +326,7 @@ describe "tgUserTimelineItemTitle", ->
} }
objparam = sinon.match ((value) -> objparam = sinon.match ((value) ->
return value.us_name == '<a tg-nav="project-userstories-detail:project=vm.activity.project.slug,ref=vm.activity.obj.userstory.ref" title="#2 subject">#2 subject</a>' return value.us_name == '<a tg-nav="project-userstories-detail:project=vm.timeline.getIn([\'data\', \'project\', \'slug\']),ref=vm.timeline.getIn([\'obj\', \'userstory\', \'ref\'])" title="#2 subject">#2 subject</a>'
), "objparam"
mockTranslate.instant
.withArgs('TITLE_OBJ', objparam)
.returns('title_ok')
title = mySvc.getTitle(timeline, event, type)
expect(title).to.be.equal("title_ok")
it "task title with us_name", () ->
timeline = {
data: {
task: {
name: 'task_name',
userstory: {
ref: 2
subject: 'subject'
}
}
}
}
event = {
obj: 'task',
}
type = {
key: 'TITLE_OBJ',
translate_params: ['us_name']
}
objparam = sinon.match ((value) ->
return value.us_name == '<a tg-nav="project-userstories-detail:project=vm.activity.project.slug,ref=vm.activity.obj.userstory.ref" title="#2 subject">#2 subject</a>'
), "objparam" ), "objparam"
mockTranslate.instant mockTranslate.instant

View File

@ -6,10 +6,10 @@ timelineType = (timeline, event) ->
key: 'TIMELINE.NEW_MEMBER', key: 'TIMELINE.NEW_MEMBER',
translate_params: ['project_name'] translate_params: ['project_name']
member: (timeline) -> member: (timeline) ->
return { return Immutable.Map({
user: timeline.data.user, user: timeline.getIn(['data', 'user']),
role: timeline.data.role role: timeline.getIn(['data', 'role'])
} })
}, },
{ # NewProject { # NewProject
check: (timeline, event) -> check: (timeline, event) ->
@ -17,11 +17,13 @@ timelineType = (timeline, event) ->
key: 'TIMELINE.NEW_PROJECT', key: 'TIMELINE.NEW_PROJECT',
translate_params: ['username', 'project_name'], translate_params: ['username', 'project_name'],
description: (timeline) -> description: (timeline) ->
return timeline.data.project.description return timeline.getIn(['data', 'project', 'description'])
}, },
{ # NewAttachment { # NewAttachment
check: (timeline, event) -> check: (timeline, event) ->
return event.type == 'change' && timeline.data.values_diff.attachments return event.type == 'change' &&
timeline.hasIn(['data', 'value_diff']) &&
timeline.getIn(['data', 'value_diff', 'key']) == 'attachments'
key: 'TIMELINE.UPLOAD_ATTACHMENT', key: 'TIMELINE.UPLOAD_ATTACHMENT',
translate_params: ['username', 'obj_name'] translate_params: ['username', 'obj_name']
}, },
@ -45,13 +47,13 @@ timelineType = (timeline, event) ->
}, },
{ # NewTask { # NewTask
check: (timeline, event) -> check: (timeline, event) ->
return event.obj == 'task' && event.type == 'create' && !timeline.data.task.userstory return event.obj == 'task' && event.type == 'create' && !timeline.getIn(['data', 'task', 'userstory'])
key: 'TIMELINE.TASK_CREATED', key: 'TIMELINE.TASK_CREATED',
translate_params: ['username', 'project_name', 'obj_name'] translate_params: ['username', 'project_name', 'obj_name']
}, },
{ # NewTask with US { # NewTask with US
check: (timeline, event) -> check: (timeline, event) ->
return event.obj == 'task' && event.type == 'create' && timeline.data.task.userstory return event.obj == 'task' && event.type == 'create' && timeline.getIn(['data', 'task', 'userstory'])
key: 'TIMELINE.TASK_CREATED_WITH_US', key: 'TIMELINE.TASK_CREATED_WITH_US',
translate_params: ['username', 'project_name', 'obj_name', 'us_name'] translate_params: ['username', 'project_name', 'obj_name', 'us_name']
}, },
@ -63,41 +65,45 @@ timelineType = (timeline, event) ->
}, },
{ # NewUsComment { # NewUsComment
check: (timeline, event) -> check: (timeline, event) ->
return timeline.data.comment && event.obj == 'userstory' return timeline.getIn(['data', 'comment']) && event.obj == 'userstory'
key: 'TIMELINE.NEW_COMMENT_US', key: 'TIMELINE.NEW_COMMENT_US',
translate_params: ['username', 'obj_name'], translate_params: ['username', 'obj_name'],
description: (timeline) -> description: (timeline) ->
return $(timeline.data.comment_html).text() return $(timeline.getIn(['data', 'comment_html'])).text()
}, },
{ # NewIssueComment { # NewIssueComment
check: (timeline, event) -> check: (timeline, event) ->
return timeline.data.comment && event.obj == 'issue' return timeline.getIn(['data', 'comment']) && event.obj == 'issue'
key: 'TIMELINE.NEW_COMMENT_ISSUE', key: 'TIMELINE.NEW_COMMENT_ISSUE',
translate_params: ['username', 'obj_name'], translate_params: ['username', 'obj_name'],
description: (timeline) -> description: (timeline) ->
return $(timeline.data.comment_html).text() return $(timeline.getIn(['data', 'comment_html'])).text()
}, },
{ # NewTaskComment { # NewTaskComment
check: (timeline, event) -> check: (timeline, event) ->
return timeline.data.comment && event.obj == 'task' return timeline.getIn(['data', 'comment']) && event.obj == 'task'
key: 'TIMELINE.NEW_COMMENT_TASK' key: 'TIMELINE.NEW_COMMENT_TASK'
translate_params: ['username', 'obj_name'], translate_params: ['username', 'obj_name'],
description: (timeline) -> description: (timeline) ->
return $(timeline.data.comment_html).text() return $(timeline.getIn(['data', 'comment_html'])).text()
}, },
{ # UsToMilestone { # UsToMilestone
check: (timeline, event, field_name) -> check: (timeline, event) ->
if field_name == 'milestone' && event.type == 'change' if timeline.hasIn(['data', 'value_diff']) &&
return timeline.data.values_diff.milestone[0] == null timeline.getIn(['data', 'value_diff', 'key']) == 'milestone' &&
event.type == 'change'
return timeline.getIn(['data', 'value_diff', 'value']).get(0) == null
return false return false
key: 'TIMELINE.US_ADDED_MILESTONE', key: 'TIMELINE.US_ADDED_MILESTONE',
translate_params: ['username', 'obj_name', 'sprint_name'] translate_params: ['username', 'obj_name', 'sprint_name']
}, },
{ # UsToBacklog { # UsToBacklog
check: (timeline, event, field_name) -> check: (timeline, event) ->
if field_name == 'milestone' && event.type == 'change' if timeline.hasIn(['data', 'value_diff']) &&
return timeline.data.values_diff.milestone[1] == null timeline.getIn(['data', 'value_diff', 'key']) == 'milestone' &&
event.type == 'change'
return timeline.getIn(['data', 'value_diff', 'value']).get(1) == null
return false return false
key: 'TIMELINE.US_REMOVED_FROM_MILESTONE', key: 'TIMELINE.US_REMOVED_FROM_MILESTONE',
@ -105,22 +111,26 @@ timelineType = (timeline, event) ->
}, },
{ # Blocked { # Blocked
check: (timeline, event) -> check: (timeline, event) ->
if event.type == 'change' && timeline.data.values_diff.is_blocked if timeline.hasIn(['data', 'value_diff']) &&
return timeline.data.values_diff.is_blocked[1] == true timeline.getIn(['data', 'value_diff', 'key']) == 'blocked' &&
event.type == 'change'
return timeline.getIn(['data', 'value_diff', 'value', 'is_blocked']).get(1) == true
return false return false
key: 'TIMELINE.BLOCKED', key: 'TIMELINE.BLOCKED',
translate_params: ['username', 'obj_name'], translate_params: ['username', 'obj_name'],
description: (timeline) -> description: (timeline) ->
if timeline.data.values_diff.blocked_note_html if timeline.hasIn(['data', 'value_diff', 'value', 'blocked_note_html'])
return $(timeline.data.values_diff.blocked_note_html[1]).text() return $(timeline.getIn(['data', 'value_diff', 'value', 'blocked_note_html']).get(1)).text()
else else
return false return false
}, },
{ # UnBlocked { # UnBlocked
check: (timeline, event) -> check: (timeline, event) ->
if event.type == 'change' && timeline.data.values_diff.is_blocked if timeline.hasIn(['data', 'value_diff']) &&
return timeline.data.values_diff.is_blocked[1] == false timeline.getIn(['data', 'value_diff', 'key']) == 'blocked' &&
event.type == 'change'
return timeline.getIn(['data', 'value_diff', 'value', 'is_blocked']).get(1) == false
return false return false
key: 'TIMELINE.UNBLOCKED', key: 'TIMELINE.UNBLOCKED',
@ -138,74 +148,83 @@ timelineType = (timeline, event) ->
key: 'TIMELINE.WIKI_UPDATED', key: 'TIMELINE.WIKI_UPDATED',
translate_params: ['username', 'obj_name'] translate_params: ['username', 'obj_name']
}, },
{ # UsUpdated { # UsUpdated points
check: (timeline, event) -> check: (timeline, event) ->
return event.obj == 'userstory' && return event.obj == 'userstory' &&
event.type == 'change' && event.type == 'change' &&
!timeline.data.values_diff.description_diff timeline.hasIn(['data', 'value_diff']) &&
key: 'TIMELINE.US_UPDATED_WITH_NEW_VALUE', timeline.getIn(['data', 'value_diff', 'key']) == 'points'
translate_params: ['username', 'field_name', 'obj_name', 'new_value'] key: 'TIMELINE.US_UPDATED_POINTS',
translate_params: ['username', 'field_name', 'obj_name', 'new_value', 'role_name']
}, },
{ # UsUpdated description { # UsUpdated description
check: (timeline, event) -> check: (timeline, event) ->
return event.obj == 'userstory' && return event.obj == 'userstory' &&
event.type == 'change' && event.type == 'change' &&
timeline.data.values_diff.description_diff timeline.hasIn(['data', 'value_diff']) &&
timeline.getIn(['data', 'value_diff', 'key']) == 'description_diff'
key: 'TIMELINE.US_UPDATED', key: 'TIMELINE.US_UPDATED',
translate_params: ['username', 'field_name', 'obj_name'] translate_params: ['username', 'field_name', 'obj_name']
}, },
{ # IssueUpdated { # UsUpdated general
check: (timeline, event) -> check: (timeline, event) ->
return event.obj == 'issue' && return event.obj == 'userstory' &&
event.type == 'change' && event.type == 'change'
!timeline.data.values_diff.description_diff key: 'TIMELINE.US_UPDATED_WITH_NEW_VALUE',
key: 'TIMELINE.ISSUE_UPDATED_WITH_NEW_VALUE',
translate_params: ['username', 'field_name', 'obj_name', 'new_value'] translate_params: ['username', 'field_name', 'obj_name', 'new_value']
}, },
{ # IssueUpdated description { # IssueUpdated description
check: (timeline, event) -> check: (timeline, event) ->
return event.obj == 'issue' && return event.obj == 'issue' &&
event.type == 'change' && event.type == 'change' &&
timeline.data.values_diff.description_diff timeline.hasIn(['data', 'value_diff']) &&
timeline.getIn(['data', 'value_diff', 'key']) == 'description_diff'
key: 'TIMELINE.ISSUE_UPDATED', key: 'TIMELINE.ISSUE_UPDATED',
translate_params: ['username', 'field_name', 'obj_name'] translate_params: ['username', 'field_name', 'obj_name']
}, },
{ # TaskUpdated { # IssueUpdated general
check: (timeline, event) -> check: (timeline, event) ->
return event.obj == 'task' && return event.obj == 'issue' &&
event.type == 'change' && event.type == 'change'
!timeline.data.task.userstory && key: 'TIMELINE.ISSUE_UPDATED_WITH_NEW_VALUE',
!timeline.data.values_diff.description_diff
key: 'TIMELINE.TASK_UPDATED_WITH_NEW_VALUE',
translate_params: ['username', 'field_name', 'obj_name', 'new_value'] translate_params: ['username', 'field_name', 'obj_name', 'new_value']
}, },
{ # TaskUpdated description { # TaskUpdated description
check: (timeline, event) -> check: (timeline, event) ->
return event.obj == 'task' && return event.obj == 'task' &&
event.type == 'change' && event.type == 'change' &&
!timeline.data.task.userstory && !timeline.getIn('data', 'task', 'userstory') &&
timeline.data.values_diff.description_diff timeline.hasIn(['data', 'value_diff']) &&
timeline.getIn(['data', 'value_diff', 'key']) == 'description_diff'
key: 'TIMELINE.TASK_UPDATED', key: 'TIMELINE.TASK_UPDATED',
translate_params: ['username', 'field_name', 'obj_name'] translate_params: ['username', 'field_name', 'obj_name']
}, },
{ # TaskUpdated with US
check: (timeline, event) ->
return event.obj == 'task' &&
event.type == 'change' &&
timeline.data.task.userstory &&
!timeline.data.values_diff.description_diff
key: 'TIMELINE.TASK_UPDATED_WITH_US_NEW_VALUE',
translate_params: ['username', 'field_name', 'obj_name', 'us_name', 'new_value']
},
{ # TaskUpdated with US description { # TaskUpdated with US description
check: (timeline, event) -> check: (timeline, event) ->
return event.obj == 'task' && return event.obj == 'task' &&
event.type == 'change' && event.type == 'change' &&
timeline.data.task.userstory && timeline.getIn('data', 'task', 'userstory') &&
timeline.data.values_diff.description_diff timeline.hasIn(['data', 'value_diff']) &&
timeline.getIn(['data', 'value_diff', 'key']) == 'description_diff'
key: 'TIMELINE.TASK_UPDATED_WITH_US', key: 'TIMELINE.TASK_UPDATED_WITH_US',
translate_params: ['username', 'field_name', 'obj_name', 'us_name'] translate_params: ['username', 'field_name', 'obj_name', 'us_name']
}, },
{ # TaskUpdated general
check: (timeline, event) ->
return event.obj == 'task' &&
event.type == 'change' &&
!timeline.getIn(['data', 'task', 'userstory'])
key: 'TIMELINE.TASK_UPDATED_WITH_NEW_VALUE',
translate_params: ['username', 'field_name', 'obj_name', 'new_value']
},
{ # TaskUpdated with US
check: (timeline, event) ->
return event.obj == 'task' &&
event.type == 'change' &&
timeline.getIn(['data', 'task', 'userstory'])
key: 'TIMELINE.TASK_UPDATED_WITH_US_NEW_VALUE',
translate_params: ['username', 'field_name', 'obj_name', 'us_name', 'new_value']
},
{ # New User { # New User
check: (timeline, event) -> check: (timeline, event) ->
return event.obj == 'user' && event.type == 'create' return event.obj == 'user' && event.type == 'create'
@ -214,11 +233,8 @@ timelineType = (timeline, event) ->
} }
] ]
if timeline.data.values_diff
field_name = Object.keys(timeline.data.values_diff)[0]
return _.find types, (obj) -> return _.find types, (obj) ->
return obj.check(timeline, event, field_name) return obj.check(timeline, event)
class UserTimelineType class UserTimelineType
getType: (timeline, event) -> timelineType(timeline, event) getType: (timeline, event) -> timelineType(timeline, event)

View File

@ -5,28 +5,27 @@ class UserTimelineItemController
] ]
constructor: (@userTimelineItemType, @userTimelineItemTitle) -> constructor: (@userTimelineItemType, @userTimelineItemTitle) ->
timeline = @.timeline.toJS() event = @.parseEventType(@.timeline.get('event_type'))
type = @userTimelineItemType.getType(@.timeline, event)
event = @.parseEventType(timeline.event_type) title = @userTimelineItemTitle.getTitle(@.timeline, event, type)
type = @userTimelineItemType.getType(timeline, event)
@.activity = {} @.timeline = @.timeline.set('title_html', title)
@.activity.user = timeline.data.user @.timeline = @.timeline.set('obj', @.getObject(@.timeline, event))
@.activity.project = timeline.data.project
@.activity.sprint = timeline.data.milestone
@.activity.title = @userTimelineItemTitle.getTitle(timeline, event, type)
@.activity.created_formated = moment(timeline.created).fromNow()
@.activity.obj = @.getObject(timeline, event)
if type.description if type.description
@.activity.description = type.description(timeline) @.timeline = @.timeline.set('description', type.description(@.timeline))
if type.member if type.member
@.activity.member = type.member(timeline) @.timeline = @.timeline.set('member', type.member(@.timeline))
if timeline.data.values_diff?.attachments if @.timeline.hasIn(['data', 'value_diff', 'attachments', 'new'])
@.activity.attachments = timeline.data.values_diff.attachments.new @.timeline = @.timeline.set('attachments', @.timeline.getIn(['data', 'value_diff', 'attachments', 'new']))
getObject: (timeline, event) ->
if timeline.get('data').get(event.obj)
return timeline.get('data').get(event.obj)
parseEventType: (event_type) -> parseEventType: (event_type) ->
event_type = event_type.split(".") event_type = event_type.split(".")
@ -37,9 +36,5 @@ class UserTimelineItemController
type: event_type[2] type: event_type[2]
} }
getObject: (timeline, event) ->
if timeline.data[event.obj]
return timeline.data[event.obj]
angular.module("taigaUserTimeline") angular.module("taigaUserTimeline")
.controller("UserTimelineItem", UserTimelineItemController) .controller("UserTimelineItem", UserTimelineItemController)

View File

@ -43,16 +43,23 @@ describe "UserTimelineItemController", ->
type: 'created' type: 'created'
} }
timeline = { timeline = Immutable.fromJS({
event_type: 'issues.issue.created', event_type: 'issues.issue.created',
data: { data: {
user: 'user_fake', user: 'user_fake',
project: 'project_fake', project: 'project_fake',
milestone: 'milestone_fake', milestone: 'milestone_fake',
created: new Date().getTime(), created: new Date().getTime(),
values_diff: {} issue: {
id: 2
},
value_diff: {
attachments: {
new: "fakeAttachment"
}
}
} }
} })
scope = { scope = {
vm: { vm: {
@ -69,36 +76,19 @@ describe "UserTimelineItemController", ->
inject ($controller) -> inject ($controller) ->
controller = $controller controller = $controller
it "basic activity fields filled", () ->
timeline = scope.vm.timeline
timeline_immutable = Immutable.fromJS(timeline)
myCtrl = controller("UserTimelineItem", {$scope: scope}, {timeline: timeline_immutable})
expect(myCtrl.activity.user).to.be.equal(timeline.data.user)
expect(myCtrl.activity.project).to.be.equal(timeline.data.project)
expect(myCtrl.activity.sprint).to.be.equal(timeline.data.milestone)
expect(myCtrl.activity.title).to.be.equal("fakeTitle")
expect(myCtrl.activity.created_formated).to.have.length.above(1)
it "all activity fields filled", () -> it "all activity fields filled", () ->
timeline = scope.vm.timeline timeline = scope.vm.timeline
attachment = "fakeAttachment"
timeline.data.values_diff.attachments = {
new: attachment
}
description = "fakeDescription" description = "fakeDescription"
member = "fakeMember" member = "fakeMember"
mockType.description.withArgs(timeline).returns(description) mockType.description.returns(description)
mockType.member.withArgs(timeline).returns(member) mockType.member.returns(member)
timeline_immutable = Immutable.fromJS(timeline) myCtrl = controller("UserTimelineItem", {$scope: scope}, {timeline: timeline})
myCtrl = controller("UserTimelineItem", {$scope: scope}, {timeline: timeline_immutable}) expect(myCtrl.timeline.get('title_html')).to.be.equal("fakeTitle")
expect(myCtrl.timeline.get('obj')).to.be.equal(myCtrl.timeline.getIn(["data", "issue"]))
expect(myCtrl.activity.description).to.be.equal(description) expect(myCtrl.timeline.get("description")).to.be.equal(description)
expect(myCtrl.activity.member).to.be.equal(member) expect(myCtrl.timeline.get("member")).to.be.equal(member)
expect(myCtrl.activity.attachments).to.be.equal(attachment) expect(myCtrl.timeline.get("attachments")).to.be.equal("fakeAttachment")

View File

@ -1,22 +1,29 @@
div.activity-item div.activity-item
span.activity-date {{::vm.activity.created_formated}} span.activity-date {{::vm.timeline.get('created') | momentFromNow}}
div.activity-info(tg-user-timeline-title="vm.timeline")
div.activity-info div.activity-info
div.profile-contact-picture // profile image with url
a(tg-nav="user-profile:username=vm.activity.user.username", title="{{::vm.activity.user.name }}") div.profile-contact-picture(ng-if="vm.timeline.getIn(['data', 'user', 'is_profile_visible'])")
img(ng-src="{{::vm.activity.user.photo || '/images/user-noimage.png'}}", alt="{{::vm.activity.user.name}}") a(tg-nav="user-profile:username=vm.timeline.getIn(['data', 'user', 'username'])", title="{{::vm.timeline.getIn(['data', 'user', 'name']) }}")
img(ng-src="{{::vm.timeline.getIn(['data', 'user', 'photo']) || '/images/user-noimage.png'}}", alt="{{::vm.timeline.getIn(['data', 'user', 'name'])}}")
// profile image without url
div.profile-contact-picture(ng-if="!vm.timeline.getIn(['data', 'user', 'is_profile_visible'])")
img(ng-src="{{::vm.timeline.getIn(['data', 'user', 'photo']) || '/images/user-noimage.png'}}", alt="{{::vm.timeline.getIn(['data', 'user', 'name'])}}")
p(tg-compile-html="vm.activity.title") p(tg-compile-html="vm.timeline.get('title_html')")
blockquote.activity-comment-quote(ng-if="::vm.activity.description") blockquote.activity-comment-quote(ng-if="::vm.timeline.get('description')")
| {{::vm.activity.description | limitTo:300}} | {{::vm.timeline.get('description') | limitTo:300}}
.activity-member-view(ng-if="::vm.activity.member") .activity-member-view(ng-if="::vm.timeline.has('member')")
a.profile-member-picture(tg-nav="user-profile:username=vm.activity.member.user.username", title="{{::vm.activity.member.user.name }}") a.profile-member-picture(tg-nav="user-profile:username=vm.timeline.getIn(['member', 'user', 'username'])", title="{{::vm.timeline.getIn(['member', 'user', 'name'])}}")
img(ng-src="{{::vm.activity.member.user.photo}}", alt="{{::vm.activity.member.user.name}}") img(ng-src="{{::vm.timeline.getIn(['member', 'user', 'photo'])}}", alt="{{::vm.timeline.getIn(['member','user', 'name'])}}")
.activity-member-info .activity-member-info
a(tg-nav="user-profile:username=vm.activity.member.user.username", title="{{::vm.activity.member.user.name }}") a(tg-nav="user-profile:username=vm.timeline.getIn(['member', 'user', 'username'])", title="{{::vm.timeline.getIn(['member','user', 'name'])}}")
span {{::vm.activity.member.user.name}} span {{::vm.timeline.getIn(['member','user', 'name'])}}
p {{::vm.activity.member.role.name}} p {{::vm.timeline.getIn(['member','role', 'name'])}}
div(ng-repeat="attachment in vm.activity.attachments") div(ng-repeat="attachment in vm.timeline.get('attachments')")
div(tg-user-timeline-attachment="attachment") div(tg-user-timeline-attachment="attachment")

View File

@ -6,15 +6,16 @@
p { p {
margin-bottom: 0; margin-bottom: 0;
} }
a { a,
.username {
color: $primary; color: $primary;
&:first-child { &:first-child {
@extend %bold; @extend %bold;
color: $gray; color: $gray;
} }
&:hover { }
color: $primary-light; a:hover {
} color: $primary-light;
} }
blockquote { blockquote {
line-height: 1.4rem; line-height: 1.4rem;

View File

@ -5,19 +5,37 @@ class UserTimelineService extends taiga.Service
constructor: (@rs, @userTimelinePaginationSequenceService) -> constructor: (@rs, @userTimelinePaginationSequenceService) ->
_valid_fields: [
'status',
'subject',
'description_diff',
'assigned_to',
'points',
'severity',
'priority',
'type',
'attachments',
'milestone',
'is_iocaine',
'content_diff',
'name',
'estimated_finish',
'estimated_start',
'blocked'
]
_invalid: [ _invalid: [
{# Items with only invalid fields {# Items with only invalid fields
check: (timeline) -> check: (timeline) ->
values_diff = timeline.get("data").get("values_diff") value_diff = timeline.get("data").get("value_diff")
if values_diff if value_diff
values = Object.keys(values_diff.toJS()) fieldKey = value_diff.get('key')
if values && values.length if @._valid_fields.indexOf(fieldKey) == -1
if _.every(values, (value) => @._valid_fields.indexOf(value) == -1)
return true return true
else if values[0] == 'attachments' && else if fieldKey == 'attachments' &&
values_diff.get('attachments').get('new').size == 0 value_diff.get('value').get('new').size == 0
return true return true
return false return false
@ -39,42 +57,57 @@ class UserTimelineService extends taiga.Service
{# Task milestone {# Task milestone
check: (timeline) -> check: (timeline) ->
event = timeline.get('event_type').split(".") event = timeline.get('event_type').split(".")
value_diff = timeline.get("data").get("value_diff")
if event[1] == "task" && event[2] == "change" if value_diff &&
return timeline.get("data").get("values_diff").get("milestone") event[1] == "task" &&
event[2] == "change" &&
value_diff.get("key") == "milestone"
return timeline.get("data").get("value_diff").get("value")
return false return false
} }
] ]
_valid_fields: [
'status',
'subject',
'description_diff',
'assigned_to',
'points',
'severity',
'priority',
'type',
'attachments',
'milestone',
'is_blocked',
'is_iocaine',
'content_diff',
'name',
'estimated_finish',
'estimated_start'
]
_isInValidTimeline: (timeline) -> _isInValidTimeline: (timeline) ->
return _.some @._invalid, (invalid) => return _.some @._invalid, (invalid) =>
return invalid.check.call(this, timeline) return invalid.check.call(this, timeline)
# create a entry per every item in the values_diff
_splitChanges: (response) ->
newdata = Immutable.List()
response.get('data').forEach (item) ->
data = item.get('data')
values_diff = data.get('values_diff')
if values_diff && values_diff.count()
# blocked/unblocked change must be a single change
if values_diff.has('is_blocked')
values_diff = Immutable.Map({'blocked': values_diff})
values_diff.forEach (value, key) ->
obj = Immutable.Map({
key: key,
value: value
})
newItem = item.setIn(['data', 'value_diff'], obj)
newItem = newItem.deleteIn(['data', 'values_diff'])
newdata = newdata.push(newItem)
else
newItem = item.deleteIn(['data', 'values_diff'])
newdata = newdata.push(newItem)
return response.set('data', newdata)
getProfileTimeline: (userId) -> getProfileTimeline: (userId) ->
config = {} config = {}
config.fetch = (page) => config.fetch = (page) =>
return @rs.users.getProfileTimeline(userId, page) return @rs.users.getProfileTimeline(userId, page)
.then (response) =>
return @._splitChanges(response)
config.filter = (items) => config.filter = (items) =>
return items.filterNot (item) => @._isInValidTimeline(item) return items.filterNot (item) => @._isInValidTimeline(item)
@ -86,6 +119,8 @@ class UserTimelineService extends taiga.Service
config.fetch = (page) => config.fetch = (page) =>
return @rs.users.getUserTimeline(userId, page) return @rs.users.getUserTimeline(userId, page)
.then (response) =>
return @._splitChanges(response)
config.filter = (items) => config.filter = (items) =>
return items.filterNot (item) => @._isInValidTimeline(item) return items.filterNot (item) => @._isInValidTimeline(item)
@ -97,6 +132,7 @@ class UserTimelineService extends taiga.Service
config.fetch = (page) => config.fetch = (page) =>
return @rs.projects.getTimeline(projectId, page) return @rs.projects.getTimeline(projectId, page)
.then (response) => return @._splitChanges(response)
config.filter = (items) => config.filter = (items) =>
return items.filterNot (item) => @._isInValidTimeline(item) return items.filterNot (item) => @._isInValidTimeline(item)

View File

@ -7,7 +7,9 @@ describe "tgUserTimelineService", ->
mocks.resources = {} mocks.resources = {}
mocks.resources.users = { mocks.resources.users = {
getTimeline: sinon.stub() getTimeline: sinon.stub(),
getProfileTimeline: sinon.stub(),
getUserTimeline: sinon.stub()
} }
mocks.resources.projects = { mocks.resources.projects = {
@ -147,73 +149,66 @@ describe "tgUserTimelineService", ->
userId = 3 userId = 3
page = 2 page = 2
mocks.resources.users.getProfileTimeline = (_userId_) -> response = Immutable.fromJS({
expect(_userId_).to.be.equal(userId) data: valid_items
})
return Immutable.fromJS(valid_items) mocks.resources.users.getProfileTimeline.withArgs(userId).promise().resolve(response)
mocks.userTimelinePaginationSequence.generate = (config) -> mocks.userTimelinePaginationSequence.generate = (config) ->
all = config.fetch() return config.fetch().then (res) ->
expect(all.size).to.be.equal(11) expect(res.get('data').size).to.be.equal(14)
items = config.filter(all).toJS() items = config.filter(res.get('data'))
expect(items).to.have.length(4) expect(items.size).to.be.equal(5)
expect(items[0]).to.be.eql(valid_items[0])
expect(items[1]).to.be.eql(valid_items[3])
expect(items[2]).to.be.eql(valid_items[5])
expect(items[3]).to.be.eql(valid_items[9])
return true return true
result = userTimelineService.getProfileTimeline(userId) result = userTimelineService.getProfileTimeline(userId)
expect(result).to.be.true
return expect(result).to.be.eventually.true
it "filter invalid user timeline items", () -> it "filter invalid user timeline items", () ->
userId = 3 userId = 3
page = 2 page = 2
mocks.resources.users.getUserTimeline = (_userId_) -> response = Immutable.fromJS({
expect(_userId_).to.be.equal(userId) data: valid_items
})
return Immutable.fromJS(valid_items) mocks.resources.users.getUserTimeline.withArgs(userId).promise().resolve(response)
mocks.userTimelinePaginationSequence.generate = (config) -> mocks.userTimelinePaginationSequence.generate = (config) ->
all = config.fetch() return config.fetch().then (res) ->
expect(all.size).to.be.equal(11) expect(res.get('data').size).to.be.equal(14)
items = config.filter(all).toJS() items = config.filter(res.get('data'))
expect(items).to.have.length(4) expect(items.size).to.be.equal(5)
expect(items[0]).to.be.eql(valid_items[0])
expect(items[1]).to.be.eql(valid_items[3])
expect(items[2]).to.be.eql(valid_items[5])
expect(items[3]).to.be.eql(valid_items[9])
return true return true
result = userTimelineService.getUserTimeline(userId) result = userTimelineService.getUserTimeline(userId)
expect(result).to.be.true
it "filter invalid user timeline items", () -> return expect(result).to.be.eventually.true
it "filter invalid project timeline items", () ->
userId = 3 userId = 3
page = 2 page = 2
mocks.resources.projects.getTimeline = (_userId_) -> response = Immutable.fromJS({
expect(_userId_).to.be.equal(userId) data: valid_items
})
return Immutable.fromJS(valid_items) mocks.resources.projects.getTimeline.withArgs(userId).promise().resolve(response)
mocks.userTimelinePaginationSequence.generate = (config) -> mocks.userTimelinePaginationSequence.generate = (config) ->
all = config.fetch() return config.fetch().then (res) ->
expect(all.size).to.be.equal(11) expect(res.get('data').size).to.be.equal(14)
items = config.filter(all).toJS() items = config.filter(res.get('data'))
expect(items).to.have.length(4) expect(items.size).to.be.equal(5)
expect(items[0]).to.be.eql(valid_items[0])
expect(items[1]).to.be.eql(valid_items[3])
expect(items[2]).to.be.eql(valid_items[5])
expect(items[3]).to.be.eql(valid_items[9])
return true return true
result = userTimelineService.getProjectTimeline(userId) result = userTimelineService.getProjectTimeline(userId)
expect(result).to.be.true expect(result).to.be.eventually.true

View File

@ -14,7 +14,7 @@ module.exports = function(config) {
// frameworks to use // frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['mocha', 'sinon-chai'], frameworks: ['mocha', 'chai', 'chai-as-promised', 'sinon-chai'],
// list of files / patterns to load in the browser // list of files / patterns to load in the browser

View File

@ -12,8 +12,8 @@
"author": "Kaleidos OpenSource SL", "author": "Kaleidos OpenSource SL",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"repository": { "repository": {
"type":"git", "type": "git",
"url":"https://github.com/taigaio/taiga-front.git" "url": "https://github.com/taigaio/taiga-front.git"
}, },
"scripts": { "scripts": {
"scss-lint": "gulp scss-lint --fail", "scss-lint": "gulp scss-lint --fail",
@ -57,12 +57,10 @@
"gulp-wrap": "^0.11.0", "gulp-wrap": "^0.11.0",
"inquirer": "^0.8.2", "inquirer": "^0.8.2",
"karma": "^0.12.31", "karma": "^0.12.31",
"karma-chai": "^0.1.0", "karma-chai-plugins": "^0.6.0",
"karma-chrome-launcher": "^0.1.7", "karma-chrome-launcher": "^0.1.7",
"karma-coffee-preprocessor": "^0.2.1", "karma-coffee-preprocessor": "^0.2.1",
"karma-mocha": "^0.1.10", "karma-mocha": "^0.1.10",
"karma-sinon": "^1.0.4",
"karma-sinon-chai": "^0.3.0",
"karma-sourcemap-loader": "^0.3.4", "karma-sourcemap-loader": "^0.3.4",
"minimist": "^1.1.1", "minimist": "^1.1.1",
"mocha": "^2.2.4", "mocha": "^2.2.4",