Epics in timeline

stable
Alejandro Alonso 2016-09-09 14:35:31 +02:00 committed by David Barragán Merino
parent e8a2155177
commit 19d0a5d0ac
10 changed files with 179 additions and 15 deletions

View File

@ -847,6 +847,8 @@
"FILTER_TYPE_ALL_TITLE": "Show all", "FILTER_TYPE_ALL_TITLE": "Show all",
"FILTER_TYPE_PROJECTS": "Projects", "FILTER_TYPE_PROJECTS": "Projects",
"FILTER_TYPE_PROJECT_TITLES": "Show only projects", "FILTER_TYPE_PROJECT_TITLES": "Show only projects",
"FILTER_TYPE_EPICS": "Epics",
"FILTER_TYPE_EPIC_TITLES": "Show only epics",
"FILTER_TYPE_USER_STORIES": "Stories", "FILTER_TYPE_USER_STORIES": "Stories",
"FILTER_TYPE_USER_STORIES_TITLES": "Show only user stories", "FILTER_TYPE_USER_STORIES_TITLES": "Show only user stories",
"FILTER_TYPE_TASKS": "Tasks", "FILTER_TYPE_TASKS": "Tasks",
@ -1202,7 +1204,8 @@
"SPRINT_ORDER": "sprint order", "SPRINT_ORDER": "sprint order",
"KANBAN_ORDER": "kanban order", "KANBAN_ORDER": "kanban order",
"TASKBOARD_ORDER": "taskboard order", "TASKBOARD_ORDER": "taskboard order",
"US_ORDER": "us order" "US_ORDER": "us order",
"COLOR": "color"
} }
}, },
"BACKLOG": { "BACKLOG": {
@ -1576,6 +1579,8 @@
"TASK_CREATED_WITH_US": "{{username}} has created a new task {{obj_name}} in {{project_name}} which belongs to the US {{us_name}}", "TASK_CREATED_WITH_US": "{{username}} has created a new task {{obj_name}} in {{project_name}} which belongs to the US {{us_name}}",
"WIKI_CREATED": "{{username}} has created a new wiki page {{obj_name}} in {{project_name}}", "WIKI_CREATED": "{{username}} has created a new wiki page {{obj_name}} in {{project_name}}",
"MILESTONE_CREATED": "{{username}} has created a new sprint {{obj_name}} in {{project_name}}", "MILESTONE_CREATED": "{{username}} has created a new sprint {{obj_name}} in {{project_name}}",
"EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}",
"EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}",
"NEW_PROJECT": "{{username}} created the project {{project_name}}", "NEW_PROJECT": "{{username}} created the project {{project_name}}",
"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}}",
@ -1588,9 +1593,12 @@
"TASK_UPDATED_WITH_US": "{{username}} has updated the attribute \"{{field_name}}\" of the task {{obj_name}} which belongs to the US {{us_name}}", "TASK_UPDATED_WITH_US": "{{username}} has updated the attribute \"{{field_name}}\" of the task {{obj_name}} which belongs to the US {{us_name}}",
"TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the task {{obj_name}} which belongs to the US {{us_name}} to {{new_value}}", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the task {{obj_name}} which belongs to the US {{us_name}} to {{new_value}}",
"WIKI_UPDATED": "{{username}} has updated the wiki page {{obj_name}}", "WIKI_UPDATED": "{{username}} has updated the wiki page {{obj_name}}",
"EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}",
"EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}",
"NEW_COMMENT_US": "{{username}} has commented in the US {{obj_name}}", "NEW_COMMENT_US": "{{username}} has commented in the US {{obj_name}}",
"NEW_COMMENT_ISSUE": "{{username}} has commented in the issue {{obj_name}}", "NEW_COMMENT_ISSUE": "{{username}} has commented in the issue {{obj_name}}",
"NEW_COMMENT_TASK": "{{username}} has commented in the task {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} has commented in the task {{obj_name}}",
"NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}",
"NEW_MEMBER": "{{project_name}} has a new member", "NEW_MEMBER": "{{project_name}} has a new member",
"US_ADDED_MILESTONE": "{{username}} has added the US {{obj_name}} to {{sprint_name}}", "US_ADDED_MILESTONE": "{{username}} has added the US {{obj_name}} to {{sprint_name}}",
"US_MOVED": "{{username}} has moved the US {{obj_name}}", "US_MOVED": "{{username}} has moved the US {{obj_name}}",

View File

@ -43,6 +43,11 @@
) )
include history-templates/history-custom-attributes include history-templates/history-custom-attributes
.diff-wrapper(
ng-if="vm.type == 'color'"
)
include history-templates/history-color
.diff-wrapper( .diff-wrapper(
ng-if="vm.type == 'team_requirement'" ng-if="vm.type == 'team_requirement'"
) )
@ -57,5 +62,3 @@
ng-if="vm.type == 'is_blocked'" ng-if="vm.type == 'is_blocked'"
) )
include history-templates/blocked include history-templates/blocked

View File

@ -0,0 +1,9 @@
.diff-status-wrapper
span.key(
translate="ACTIVITY.FIELDS.COLOR"
)
span.diff(ng-if="vm.diff[0]") {{vm.diff[0]}}
tg-svg(
svg-icon="icon-arrow-right"
)
span.diff(ng-if="vm.diff[1]") {{vm.diff[1]}}

View File

@ -24,6 +24,10 @@
p p
span.ticket-project span.ticket-project
| {{:: vm.item.get('project_name') }} | {{:: vm.item.get('project_name') }}
span.ticket-type(
ng-if="::vm.item.get('type') === 'epic'"
translate="COMMON.EPIC"
)
span.ticket-type( span.ticket-type(
ng-if="::vm.item.get('type') === 'userstory'" ng-if="::vm.item.get('type') === 'userstory'"
translate="COMMON.USER_STORY" translate="COMMON.USER_STORY"
@ -44,6 +48,12 @@
) )
h2 h2
span.ticket-id(tg-bo-ref="vm.item.get('ref')") span.ticket-id(tg-bo-ref="vm.item.get('ref')")
a.ticket-title(
href="#"
ng-if="::vm.item.get('type') === 'epic'"
tg-nav="project-epics-detail:project=vm.item.get('project_slug'),ref=vm.item.get('ref')"
title="#{{ ::vm.item.get('ref') }} {{ ::vm.item.get('subject') }}"
) {{ ::vm.item.get('subject') }}
a.ticket-title( a.ticket-title(
href="#" href="#"
ng-if="::vm.item.get('type') === 'userstory'" ng-if="::vm.item.get('type') === 'userstory'"

View File

@ -28,6 +28,7 @@ class FavsBaseController
_init: -> _init: ->
@.enableFilterByAll = true @.enableFilterByAll = true
@.enableFilterByProjects = true @.enableFilterByProjects = true
@.enableFilterByEpics = true
@.enableFilterByUserStories = true @.enableFilterByUserStories = true
@.enableFilterByTasks = true @.enableFilterByTasks = true
@.enableFilterByIssues = true @.enableFilterByIssues = true
@ -101,6 +102,12 @@ class FavsBaseController
@._resetList() @._resetList()
@.loadItems() @.loadItems()
showEpicsOnly: ->
if @.type isnt "epic"
@.type = "epic"
@._resetList()
@.loadItems()
showUserStoriesOnly: -> showUserStoriesOnly: ->
if @.type isnt "userstory" if @.type isnt "userstory"
@.type = "userstory" @.type = "userstory"
@ -134,6 +141,7 @@ class ProfileLikedController extends FavsBaseController
@.tabName = 'likes' @.tabName = 'likes'
@.enableFilterByAll = false @.enableFilterByAll = false
@.enableFilterByProjects = false @.enableFilterByProjects = false
@.enableFilterByEpics = false
@.enableFilterByUserStories = false @.enableFilterByUserStories = false
@.enableFilterByTasks = false @.enableFilterByTasks = false
@.enableFilterByIssues = false @.enableFilterByIssues = false
@ -158,6 +166,7 @@ class ProfileVotedController extends FavsBaseController
@.tabName = 'upvotes' @.tabName = 'upvotes'
@.enableFilterByAll = true @.enableFilterByAll = true
@.enableFilterByProjects = false @.enableFilterByProjects = false
@.enableFilterByEpics = true
@.enableFilterByUserStories = true @.enableFilterByUserStories = true
@.enableFilterByTasks = true @.enableFilterByTasks = true
@.enableFilterByIssues = true @.enableFilterByIssues = true

View File

@ -11,7 +11,7 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details. # GNU Affero General Public License for more details.
# #
# You should have received a copy of the GNU Affero General Public License # You showld have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
# File: profile-favs.controller.spec.coffee # File: profile-favs.controller.spec.coffee
@ -127,7 +127,7 @@ describe "ProfileLiked", ->
expect(ctrl.q).to.be.equal(textQuery) expect(ctrl.q).to.be.equal(textQuery)
done() done()
it "shou loading spinner during the call to the api", (done) -> it "show loading spinner during the call to the api", (done) ->
$scope = $rootScope.$new() $scope = $rootScope.$new()
ctrl = $controller("ProfileLiked", $scope, {user: user}) ctrl = $controller("ProfileLiked", $scope, {user: user})
@ -154,7 +154,7 @@ describe "ProfileLiked", ->
expect(ctrl.isLoading).to.be.false expect(ctrl.isLoading).to.be.false
done() done()
it "shou no results placeholder", (done) -> it "show no results placeholder", (done) ->
$scope = $rootScope.$new() $scope = $rootScope.$new()
ctrl = $controller("ProfileLiked", $scope, {user: user}) ctrl = $controller("ProfileLiked", $scope, {user: user})
@ -282,6 +282,37 @@ describe "ProfileVoted", ->
expect(ctrl.q).to.be.equal(textQuery) expect(ctrl.q).to.be.equal(textQuery)
done() done()
it "show only items of epics", (done) ->
$scope = $rootScope.$new()
ctrl = $controller("ProfileVoted", $scope, {user: user})
type = "epic"
items = Immutable.fromJS({
data: [
{id: 1},
{id: 2},
{id: 3}
],
next: true
})
mocks.userServices.getVoted.withArgs(user.get("id"), 1, type, null).promise().resolve(items)
expect(ctrl.items.size).to.be.equal(0)
expect(ctrl.scrollDisabled).to.be.false
expect(ctrl.type).to.be.null
expect(ctrl.q).to.be.null
ctrl.showEpicsOnly().then () =>
expectItems = items.get("data")
expect(ctrl.items.equals(expectItems)).to.be.true
expect(ctrl.scrollDisabled).to.be.false
expect(ctrl.type).to.be.type
expect(ctrl.q).to.be.null
done()
it "show only items of user stories", (done) -> it "show only items of user stories", (done) ->
$scope = $rootScope.$new() $scope = $rootScope.$new()
ctrl = $controller("ProfileVoted", $scope, {user: user}) ctrl = $controller("ProfileVoted", $scope, {user: user})
@ -375,7 +406,7 @@ describe "ProfileVoted", ->
expect(ctrl.q).to.be.null expect(ctrl.q).to.be.null
done() done()
it "shou loading spinner during the call to the api", (done) -> it "show loading spinner during the call to the api", (done) ->
$scope = $rootScope.$new() $scope = $rootScope.$new()
ctrl = $controller("ProfileVoted", $scope, {user: user}) ctrl = $controller("ProfileVoted", $scope, {user: user})
@ -402,7 +433,7 @@ describe "ProfileVoted", ->
expect(ctrl.isLoading).to.be.false expect(ctrl.isLoading).to.be.false
done() done()
it "shou no results placeholder", (done) -> it "show no results placeholder", (done) ->
$scope = $rootScope.$new() $scope = $rootScope.$new()
ctrl = $controller("ProfileVoted", $scope, {user: user}) ctrl = $controller("ProfileVoted", $scope, {user: user})
@ -560,6 +591,37 @@ describe "ProfileWatched", ->
expect(ctrl.q).to.be.null expect(ctrl.q).to.be.null
done() done()
it "show only items of epics", (done) ->
$scope = $rootScope.$new()
ctrl = $controller("ProfileWatched", $scope, {user: user})
type = "epic"
items = Immutable.fromJS({
data: [
{id: 1},
{id: 2},
{id: 3}
],
next: true
})
mocks.userServices.getWatched.withArgs(user.get("id"), 1, type, null).promise().resolve(items)
expect(ctrl.items.size).to.be.equal(0)
expect(ctrl.scrollDisabled).to.be.false
expect(ctrl.type).to.be.null
expect(ctrl.q).to.be.null
ctrl.showEpicsOnly().then () =>
expectItems = items.get("data")
expect(ctrl.items.equals(expectItems)).to.be.true
expect(ctrl.scrollDisabled).to.be.false
expect(ctrl.type).to.be.type
expect(ctrl.q).to.be.null
done()
it "show only items of user stories", (done) -> it "show only items of user stories", (done) ->
$scope = $rootScope.$new() $scope = $rootScope.$new()
ctrl = $controller("ProfileWatched", $scope, {user: user}) ctrl = $controller("ProfileWatched", $scope, {user: user})
@ -653,7 +715,7 @@ describe "ProfileWatched", ->
expect(ctrl.q).to.be.null expect(ctrl.q).to.be.null
done() done()
it "shou loading spinner during the call to the api", (done) -> it "show loading spinner during the call to the api", (done) ->
$scope = $rootScope.$new() $scope = $rootScope.$new()
ctrl = $controller("ProfileWatched", $scope, {user: user}) ctrl = $controller("ProfileWatched", $scope, {user: user})
@ -680,7 +742,7 @@ describe "ProfileWatched", ->
expect(ctrl.isLoading).to.be.false expect(ctrl.isLoading).to.be.false
done() done()
it "shou no results placeholder", (done) -> it "show no results placeholder", (done) ->
$scope = $rootScope.$new() $scope = $rootScope.$new()
ctrl = $controller("ProfileWatched", $scope, {user: user}) ctrl = $controller("ProfileWatched", $scope, {user: user})

View File

@ -26,6 +26,14 @@ section.profile-favs
title="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_PROJECTS_TITLE'|translate }}" title="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_PROJECTS_TITLE'|translate }}"
translate="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_PROJECTS'|translate }}" translate="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_PROJECTS'|translate }}"
) )
a(
href=""
ng-if="::vm.enableFilterByEpics"
ng-click="vm.showEpicsOnly()"
ng-class="{active: vm.type === 'epic'}"
title="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_EPICS_TITLE'|translate }}"
translate="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_EPICS'|translate }}"
)
a( a(
href="" href=""
ng-if="::vm.enableFilterByUserStories" ng-if="::vm.enableFilterByUserStories"
@ -64,6 +72,11 @@ section.profile-favs
tg-fav-item="item" tg-fav-item="item"
item-type="project" item-type="project"
) )
div(
ng-switch-when="epic"
tg-fav-item="item"
item-type="epic"
)
div( div(
ng-switch-when="userstory" ng-switch-when="userstory"
tg-fav-item="item" tg-fav-item="item"

View File

@ -35,7 +35,8 @@ class UserTimelineItemTitle
'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' 'is_blocked': 'COMMON.FIELDS.IS_BLOCKED',
'color': 'COMMON.FIELDS.COLOR'
} }
_params: { _params: {
@ -89,6 +90,18 @@ class UserTimelineItemTitle
return @._getLink(url, text) return @._getLink(url, text)
related_us_name: (timeline, event) ->
obj = timeline.getIn(["data", "userstory"])
url = "project-userstories-detail:project=timeline.getIn(['data', 'userstory', 'project', 'slug']),ref=timeline.getIn(['data', 'userstory', 'ref'])"
text = '#' + obj.get('ref') + ' ' + obj.get('subject')
return @._getLink(url, text)
epic_name: (timeline, event) ->
obj = timeline.getIn(["data", "epic"])
url = "project-epics-detail:project=timeline.getIn(['data', 'project', 'slug']),ref=timeline.getIn(['data', 'epic', 'ref'])"
text = '#' + obj.get('ref') + ' ' + obj.get('subject')
return @._getLink(url, text)
obj_name: (timeline, event) -> obj_name: (timeline, event) ->
obj = @._getTimelineObj(timeline, event) obj = @._getTimelineObj(timeline, event)
url = @._getDetailObjUrl(event) url = @._getDetailObjUrl(event)
@ -122,9 +135,9 @@ class UserTimelineItemTitle
"task": ["project-tasks-detail", ":project=timeline.getIn(['data', 'project', 'slug']),ref=timeline.getIn(['obj', 'ref'])"], "task": ["project-tasks-detail", ":project=timeline.getIn(['data', 'project', 'slug']),ref=timeline.getIn(['obj', 'ref'])"],
"userstory": ["project-userstories-detail", ":project=timeline.getIn(['data', 'project', 'slug']),ref=timeline.getIn(['obj', 'ref'])"], "userstory": ["project-userstories-detail", ":project=timeline.getIn(['data', 'project', 'slug']),ref=timeline.getIn(['obj', 'ref'])"],
"parent_userstory": ["project-userstories-detail", ":project=timeline.getIn(['data', 'project', 'slug']),ref=timeline.getIn(['obj', 'userstory', 'ref'])"], "parent_userstory": ["project-userstories-detail", ":project=timeline.getIn(['data', 'project', 'slug']),ref=timeline.getIn(['obj', 'userstory', 'ref'])"],
"milestone": ["project-taskboard", ":project=timeline.getIn(['data', 'project', 'slug']),sprint=timeline.getIn(['obj', 'slug'])"] "milestone": ["project-taskboard", ":project=timeline.getIn(['data', 'project', 'slug']),sprint=timeline.getIn(['obj', 'slug'])"],
"epic": ["project-epics-detail", ":project=timeline.getIn(['data', 'project', 'slug']),ref=timeline.getIn(['obj', 'ref'])"]
} }
return url[event.obj][0] + url[event.obj][1] return url[event.obj][0] + url[event.obj][1]
_getLink: (url, text, title) -> _getLink: (url, text, title) ->
@ -153,7 +166,6 @@ class UserTimelineItemTitle
timeline_type.translate_params.forEach (param) => timeline_type.translate_params.forEach (param) =>
params[param] = @._translateTitleParams(param, timeline, event) params[param] = @._translateTitleParams(param, timeline, event)
return params return params
getTitle: (timeline, event, type) -> getTitle: (timeline, event, type) ->

View File

@ -82,6 +82,18 @@ timelineType = (timeline, event) ->
key: 'TIMELINE.MILESTONE_CREATED', key: 'TIMELINE.MILESTONE_CREATED',
translate_params: ['username', 'project_name', 'obj_name'] translate_params: ['username', 'project_name', 'obj_name']
}, },
{ # NewEpic
check: (timeline, event) ->
return event.obj == 'epic' && event.type == 'create'
key: 'TIMELINE.EPIC_CREATED',
translate_params: ['username', 'project_name', 'obj_name']
},
{ # NewEpicRelatedUserstory
check: (timeline, event) ->
return event.obj == 'relateduserstory' && event.type == 'create'
key: 'TIMELINE.EPIC_RELATED_USERSTORY_CREATED',
translate_params: ['username', 'project_name', 'related_us_name', 'epic_name']
},
{ # NewUsComment { # NewUsComment
check: (timeline, event) -> check: (timeline, event) ->
return timeline.getIn(['data', 'comment']) && event.obj == 'userstory' return timeline.getIn(['data', 'comment']) && event.obj == 'userstory'
@ -109,6 +121,15 @@ timelineType = (timeline, event) ->
text = timeline.getIn(['data', 'comment_html']) text = timeline.getIn(['data', 'comment_html'])
return $($.parseHTML(text)).text() return $($.parseHTML(text)).text()
}, },
{ # NewEpicComment
check: (timeline, event) ->
return timeline.getIn(['data', 'comment']) && event.obj == 'epic'
key: 'TIMELINE.NEW_COMMENT_EPIC'
translate_params: ['username', 'obj_name'],
description: (timeline) ->
text = timeline.getIn(['data', 'comment_html'])
return $($.parseHTML(text)).text()
},
{ # UsMove { # UsMove
check: (timeline, event) -> check: (timeline, event) ->
return timeline.hasIn(['data', 'value_diff']) && return timeline.hasIn(['data', 'value_diff']) &&
@ -258,6 +279,22 @@ timelineType = (timeline, event) ->
key: 'TIMELINE.TASK_UPDATED_WITH_US_NEW_VALUE', key: 'TIMELINE.TASK_UPDATED_WITH_US_NEW_VALUE',
translate_params: ['username', 'field_name', 'obj_name', 'us_name', 'new_value'] translate_params: ['username', 'field_name', 'obj_name', 'us_name', 'new_value']
}, },
{ # EpicUpdated description
check: (timeline, event) ->
return event.obj == 'epic' &&
event.type == 'change' &&
timeline.hasIn(['data', 'value_diff']) &&
timeline.getIn(['data', 'value_diff', 'key']) == 'description_diff'
key: 'TIMELINE.EPIC_UPDATED',
translate_params: ['username', 'field_name', 'obj_name']
},
{ # EpicUpdated general
check: (timeline, event) ->
return event.obj == 'epic' &&
event.type == 'change'
key: 'TIMELINE.EPIC_UPDATED_WITH_NEW_VALUE',
translate_params: ['username', 'field_name', 'obj_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'

View File

@ -47,7 +47,8 @@ class UserTimelineService extends taiga.Service
# customs # customs
'blocked', 'blocked',
'moveInBacklog', 'moveInBacklog',
'milestone' 'milestone',
'color'
] ]
_invalid: [ _invalid: [