diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 22ce0317..6f7a4823 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -847,6 +847,8 @@ "FILTER_TYPE_ALL_TITLE": "Show all", "FILTER_TYPE_PROJECTS": "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_TITLES": "Show only user stories", "FILTER_TYPE_TASKS": "Tasks", @@ -1202,7 +1204,8 @@ "SPRINT_ORDER": "sprint order", "KANBAN_ORDER": "kanban order", "TASKBOARD_ORDER": "taskboard order", - "US_ORDER": "us order" + "US_ORDER": "us order", + "COLOR": "color" } }, "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}}", "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}}", + "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}}", "MILESTONE_UPDATED": "{{username}} has updated the sprint {{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_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}}", + "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_ISSUE": "{{username}} has commented in the issue {{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", "US_ADDED_MILESTONE": "{{username}} has added the US {{obj_name}} to {{sprint_name}}", "US_MOVED": "{{username}} has moved the US {{obj_name}}", diff --git a/app/modules/history/history/history-diff.jade b/app/modules/history/history/history-diff.jade index aa6d53c8..58ea6d6a 100644 --- a/app/modules/history/history/history-diff.jade +++ b/app/modules/history/history/history-diff.jade @@ -43,6 +43,11 @@ ) include history-templates/history-custom-attributes +.diff-wrapper( + ng-if="vm.type == 'color'" +) + include history-templates/history-color + .diff-wrapper( ng-if="vm.type == 'team_requirement'" ) @@ -57,5 +62,3 @@ ng-if="vm.type == 'is_blocked'" ) include history-templates/blocked - - diff --git a/app/modules/history/history/history-templates/history-color.jade b/app/modules/history/history/history-templates/history-color.jade new file mode 100644 index 00000000..d97314bb --- /dev/null +++ b/app/modules/history/history/history-templates/history-color.jade @@ -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]}} diff --git a/app/modules/profile/profile-favs/items/ticket.jade b/app/modules/profile/profile-favs/items/ticket.jade index b5539492..60bee680 100644 --- a/app/modules/profile/profile-favs/items/ticket.jade +++ b/app/modules/profile/profile-favs/items/ticket.jade @@ -24,6 +24,10 @@ p span.ticket-project | {{:: vm.item.get('project_name') }} + span.ticket-type( + ng-if="::vm.item.get('type') === 'epic'" + translate="COMMON.EPIC" + ) span.ticket-type( ng-if="::vm.item.get('type') === 'userstory'" translate="COMMON.USER_STORY" @@ -44,6 +48,12 @@ ) h2 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( href="#" ng-if="::vm.item.get('type') === 'userstory'" diff --git a/app/modules/profile/profile-favs/profile-favs.controller.coffee b/app/modules/profile/profile-favs/profile-favs.controller.coffee index f47f7823..2fc3ac7c 100644 --- a/app/modules/profile/profile-favs/profile-favs.controller.coffee +++ b/app/modules/profile/profile-favs/profile-favs.controller.coffee @@ -28,6 +28,7 @@ class FavsBaseController _init: -> @.enableFilterByAll = true @.enableFilterByProjects = true + @.enableFilterByEpics = true @.enableFilterByUserStories = true @.enableFilterByTasks = true @.enableFilterByIssues = true @@ -101,6 +102,12 @@ class FavsBaseController @._resetList() @.loadItems() + showEpicsOnly: -> + if @.type isnt "epic" + @.type = "epic" + @._resetList() + @.loadItems() + showUserStoriesOnly: -> if @.type isnt "userstory" @.type = "userstory" @@ -134,6 +141,7 @@ class ProfileLikedController extends FavsBaseController @.tabName = 'likes' @.enableFilterByAll = false @.enableFilterByProjects = false + @.enableFilterByEpics = false @.enableFilterByUserStories = false @.enableFilterByTasks = false @.enableFilterByIssues = false @@ -158,6 +166,7 @@ class ProfileVotedController extends FavsBaseController @.tabName = 'upvotes' @.enableFilterByAll = true @.enableFilterByProjects = false + @.enableFilterByEpics = true @.enableFilterByUserStories = true @.enableFilterByTasks = true @.enableFilterByIssues = true diff --git a/app/modules/profile/profile-favs/profile-favs.controller.spec.coffee b/app/modules/profile/profile-favs/profile-favs.controller.spec.coffee index 89ccd289..a761fe64 100644 --- a/app/modules/profile/profile-favs/profile-favs.controller.spec.coffee +++ b/app/modules/profile/profile-favs/profile-favs.controller.spec.coffee @@ -11,7 +11,7 @@ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # 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 . # # File: profile-favs.controller.spec.coffee @@ -127,7 +127,7 @@ describe "ProfileLiked", -> expect(ctrl.q).to.be.equal(textQuery) 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() ctrl = $controller("ProfileLiked", $scope, {user: user}) @@ -154,7 +154,7 @@ describe "ProfileLiked", -> expect(ctrl.isLoading).to.be.false done() - it "shou no results placeholder", (done) -> + it "show no results placeholder", (done) -> $scope = $rootScope.$new() ctrl = $controller("ProfileLiked", $scope, {user: user}) @@ -282,6 +282,37 @@ describe "ProfileVoted", -> expect(ctrl.q).to.be.equal(textQuery) 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) -> $scope = $rootScope.$new() ctrl = $controller("ProfileVoted", $scope, {user: user}) @@ -375,7 +406,7 @@ describe "ProfileVoted", -> expect(ctrl.q).to.be.null 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() ctrl = $controller("ProfileVoted", $scope, {user: user}) @@ -402,7 +433,7 @@ describe "ProfileVoted", -> expect(ctrl.isLoading).to.be.false done() - it "shou no results placeholder", (done) -> + it "show no results placeholder", (done) -> $scope = $rootScope.$new() ctrl = $controller("ProfileVoted", $scope, {user: user}) @@ -560,6 +591,37 @@ describe "ProfileWatched", -> expect(ctrl.q).to.be.null 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) -> $scope = $rootScope.$new() ctrl = $controller("ProfileWatched", $scope, {user: user}) @@ -653,7 +715,7 @@ describe "ProfileWatched", -> expect(ctrl.q).to.be.null 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() ctrl = $controller("ProfileWatched", $scope, {user: user}) @@ -680,7 +742,7 @@ describe "ProfileWatched", -> expect(ctrl.isLoading).to.be.false done() - it "shou no results placeholder", (done) -> + it "show no results placeholder", (done) -> $scope = $rootScope.$new() ctrl = $controller("ProfileWatched", $scope, {user: user}) diff --git a/app/modules/profile/profile-favs/profile-favs.jade b/app/modules/profile/profile-favs/profile-favs.jade index 0317836b..7f4d1d59 100644 --- a/app/modules/profile/profile-favs/profile-favs.jade +++ b/app/modules/profile/profile-favs/profile-favs.jade @@ -26,6 +26,14 @@ section.profile-favs title="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_PROJECTS_TITLE'|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( href="" ng-if="::vm.enableFilterByUserStories" @@ -64,6 +72,11 @@ section.profile-favs tg-fav-item="item" item-type="project" ) + div( + ng-switch-when="epic" + tg-fav-item="item" + item-type="epic" + ) div( ng-switch-when="userstory" tg-fav-item="item" diff --git a/app/modules/user-timeline/user-timeline-item/user-timeline-item-title.service.coffee b/app/modules/user-timeline/user-timeline-item/user-timeline-item-title.service.coffee index b19769ed..84a189c3 100644 --- a/app/modules/user-timeline/user-timeline-item/user-timeline-item-title.service.coffee +++ b/app/modules/user-timeline/user-timeline-item/user-timeline-item-title.service.coffee @@ -35,7 +35,8 @@ class UserTimelineItemTitle 'priority': 'ISSUES.FIELDS.PRIORITY', 'type': 'ISSUES.FIELDS.TYPE', 'is_iocaine': 'TASK.FIELDS.IS_IOCAINE', - 'is_blocked': 'COMMON.FIELDS.IS_BLOCKED' + 'is_blocked': 'COMMON.FIELDS.IS_BLOCKED', + 'color': 'COMMON.FIELDS.COLOR' } _params: { @@ -89,6 +90,18 @@ class UserTimelineItemTitle 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 = @._getTimelineObj(timeline, 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'])"], "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'])"], - "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] _getLink: (url, text, title) -> @@ -153,7 +166,6 @@ class UserTimelineItemTitle timeline_type.translate_params.forEach (param) => params[param] = @._translateTitleParams(param, timeline, event) - return params getTitle: (timeline, event, type) -> diff --git a/app/modules/user-timeline/user-timeline-item/user-timeline-item-type.service.coffee b/app/modules/user-timeline/user-timeline-item/user-timeline-item-type.service.coffee index 5306ac5f..b13d6de4 100644 --- a/app/modules/user-timeline/user-timeline-item/user-timeline-item-type.service.coffee +++ b/app/modules/user-timeline/user-timeline-item/user-timeline-item-type.service.coffee @@ -82,6 +82,18 @@ timelineType = (timeline, event) -> key: 'TIMELINE.MILESTONE_CREATED', 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 check: (timeline, event) -> return timeline.getIn(['data', 'comment']) && event.obj == 'userstory' @@ -109,6 +121,15 @@ timelineType = (timeline, event) -> text = timeline.getIn(['data', 'comment_html']) 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 check: (timeline, event) -> return timeline.hasIn(['data', 'value_diff']) && @@ -258,6 +279,22 @@ timelineType = (timeline, event) -> key: 'TIMELINE.TASK_UPDATED_WITH_US_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 check: (timeline, event) -> return event.obj == 'user' && event.type == 'create' diff --git a/app/modules/user-timeline/user-timeline/user-timeline.service.coffee b/app/modules/user-timeline/user-timeline/user-timeline.service.coffee index 61adc2a4..aecf5724 100644 --- a/app/modules/user-timeline/user-timeline/user-timeline.service.coffee +++ b/app/modules/user-timeline/user-timeline/user-timeline.service.coffee @@ -47,7 +47,8 @@ class UserTimelineService extends taiga.Service # customs 'blocked', 'moveInBacklog', - 'milestone' + 'milestone', + 'color' ] _invalid: [