diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index ab483e4b..201e2bf5 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -181,8 +181,9 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven }, access: { requiresLogin: true - } - controller: "Profile" + }, + controller: "Profile", + controllerAs: "vm" } ) diff --git a/app/locales/locale-en.json b/app/locales/locale-en.json index e01a2924..af4e1029 100644 --- a/app/locales/locale-en.json +++ b/app/locales/locale-en.json @@ -1151,6 +1151,7 @@ "ISSUE_CREATED": "{{username}} has created a new Issue in {{project_name}} {{obj_name}}", "TASK_CREATED": "{{username}} has created a new Task in {{project_name}} {{obj_name}}", "WIKI_CREATED": "{{username}} has created a new Wiki page in {{project_name}} {{obj_name}}", + "MILESTONE_CREATED": "{{username}} has created a new Milestone in {{project_name}} {{obj_name}}", "NEW_PROJECT": "{{username}} has a new project {{project_name}}", "MILESTONE_UPDATED": "{{username}} has updated the milestone {{obj_name}}", "US_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the US {{obj_name}}", diff --git a/app/modules/profile/profile-bar/profile-bar.directive.coffee b/app/modules/profile/profile-bar/profile-bar.directive.coffee index 2c5e053e..a236ba61 100644 --- a/app/modules/profile/profile-bar/profile-bar.directive.coffee +++ b/app/modules/profile/profile-bar/profile-bar.directive.coffee @@ -2,7 +2,8 @@ ProfileBarDirective = () -> return { templateUrl: "profile/profile-bar/profile-bar.html", controller: "ProfileBar", - controllerAs: "vm" + controllerAs: "vm", + scope: {} } diff --git a/app/modules/profile/profile-projects/profile-projects.controller.coffee b/app/modules/profile/profile-projects/profile-projects.controller.coffee index 6b47fc54..fe691826 100644 --- a/app/modules/profile/profile-projects/profile-projects.controller.coffee +++ b/app/modules/profile/profile-projects/profile-projects.controller.coffee @@ -14,6 +14,7 @@ class ProfileProjectsController return @userService.attachUserContactsToProjects(userId, projects) .then (projects) => @.projects = projects + console.log @.projects.toJS() angular.module("taigaProfile") .controller("ProfileProjects", ProfileProjectsController) diff --git a/app/modules/profile/profile-tab/profile-tab.directive.coffee b/app/modules/profile/profile-tab/profile-tab.directive.coffee index 50625f32..a15c0f30 100644 --- a/app/modules/profile/profile-tab/profile-tab.directive.coffee +++ b/app/modules/profile/profile-tab/profile-tab.directive.coffee @@ -11,20 +11,12 @@ ProfileTabDirective = () -> ctrl.addTab(scope.tab) - scope.$watch "tab.active", (active) -> - if active - transclude scope, (clone, scope) -> - element.append(clone) - else - element.children().each (idx, elm) -> - scope.$$childHead.$destroy() - elm.remove() - return { - scope: {} - require: "^tgProfileTabs" - link: link - transclude: true + templateUrl: "profile/profile-tab/profile-tab.html", + scope: {}, + require: "^tgProfileTabs", + link: link, + transclude: true, replace: true } diff --git a/app/modules/profile/profile-tab/profile-tab.jade b/app/modules/profile/profile-tab/profile-tab.jade new file mode 100644 index 00000000..ff646ec6 --- /dev/null +++ b/app/modules/profile/profile-tab/profile-tab.jade @@ -0,0 +1,2 @@ +div(ng-if="tab.active") + ng-transclude diff --git a/app/modules/profile/profile.controller.coffee b/app/modules/profile/profile.controller.coffee index 04c6ddc4..141bcda1 100644 --- a/app/modules/profile/profile.controller.coffee +++ b/app/modules/profile/profile.controller.coffee @@ -5,8 +5,8 @@ class ProfilePageController extends taiga.Controller ] constructor: (@appTitle, @auth) -> - user = @auth.getUser() + @.user = @auth.userData - @appTitle.set(user.username) + @appTitle.set(@.user.get('username')) angular.module("taigaProfile").controller("Profile", ProfilePageController) diff --git a/app/modules/profile/profile.controller.spec.coffee b/app/modules/profile/profile.controller.spec.coffee index 6637d7e4..f054c617 100644 --- a/app/modules/profile/profile.controller.spec.coffee +++ b/app/modules/profile/profile.controller.spec.coffee @@ -23,7 +23,7 @@ describe "ProfileController", -> stub = sinon.stub() mocks.auth = { - getUser: sinon.stub() + userData: Immutable.fromJS({username: "UserName"}) } provide.value "$tgAuth", mocks.auth @@ -44,14 +44,14 @@ describe "ProfileController", -> inject ($controller) -> controller = $controller - it "define projects", () -> - user = { - username: "UserName" - } - - mocks.auth.getUser.returns(user) - + it "define user", () -> ctrl = controller "Profile", $scope: {} - expect(mocks.appTitle.set.withArgs(user.username)).to.be.calledOnce + expect(ctrl.user).to.be.equal(mocks.auth.userData) + + it "define projects", () -> + ctrl = controller "Profile", + $scope: {} + + expect(mocks.appTitle.set.withArgs("UserName")).to.be.calledOnce diff --git a/app/modules/profile/profile.jade b/app/modules/profile/profile.jade index 5183265d..6a315e06 100644 --- a/app/modules/profile/profile.jade +++ b/app/modules/profile/profile.jade @@ -4,7 +4,7 @@ div.profile.centered div.main div.timeline-wrapper(tg-profile-tabs) div(tg-profile-tab="activity", tab-title="{{'USER.PROFILE.ACTIVITY_TAB' | translate}}", tab-icon="icon-timeline", tab-active) - div(tg-user-timeline) + div(tg-user-timeline, userId="vm.user.get('id')") div(tg-profile-tab="projects", tab-title="{{'USER.PROFILE.PROJECTS_TAB' | translate}}", tab-icon="icon-project") div(tg-profile-projects) diff --git a/app/modules/projects/project/project.controller.coffee b/app/modules/projects/project/project.controller.coffee index 098b0727..f130a358 100644 --- a/app/modules/projects/project/project.controller.coffee +++ b/app/modules/projects/project/project.controller.coffee @@ -2,11 +2,13 @@ class ProjectController @.$inject = [ "tgProjectsService", "$routeParams", - "$appTitle" + "$appTitle", + "$tgAuth" ] - constructor: (@projectsService, @routeParams, @appTitle) -> + constructor: (@projectsService, @routeParams, @appTitle, @auth) -> projectSlug = @routeParams.pslug + @.user = @auth.userData @projectsService.getProjectBySlug(projectSlug) .then (project) => diff --git a/app/modules/projects/project/project.controller.spec.coffee b/app/modules/projects/project/project.controller.spec.coffee index 8e8b75c3..43528df7 100644 --- a/app/modules/projects/project/project.controller.spec.coffee +++ b/app/modules/projects/project/project.controller.spec.coffee @@ -1,4 +1,4 @@ -describe "ProfileBar", -> +describe "ProjectController", -> $controller = null $q = null provide = null @@ -19,6 +19,13 @@ describe "ProfileBar", -> provide.value "$appTitle", mocks.appTitle + _mockAuth = () -> + mocks.auth = { + userData: Immutable.fromJS({username: "UserName"}) + } + + provide.value "$tgAuth", mocks.auth + _mockRouteParams = () -> provide.value "$routeParams", { pslug: "project-slug" @@ -30,6 +37,7 @@ describe "ProfileBar", -> _mockProjectsService() _mockRouteParams() _mockAppTitle() + _mockAuth() return null @@ -44,6 +52,18 @@ describe "ProfileBar", -> _mocks() _inject() + it "set local user", () -> + thenStub = sinon.stub() + + mocks.projectService.getProjectBySlug.withArgs("project-slug").returns({ + then: thenStub + }) + + ctrl = $controller "Project", + $scope: {} + + expect(ctrl.user).to.be.equal(mocks.auth.userData) + it "set page title", () -> project = Immutable.fromJS({ name: "projectName" @@ -59,8 +79,6 @@ describe "ProfileBar", -> thenStub.callArg(0, project) - $rootScope.$apply() - expect(mocks.appTitle.set.withArgs("projectName")).to.be.calledOnce @@ -79,6 +97,4 @@ describe "ProfileBar", -> thenStub.callArg(0, project) - $rootScope.$apply() - expect(ctrl.project).to.be.equal(project) diff --git a/app/modules/projects/project/project.jade b/app/modules/projects/project/project.jade index a8f0be4d..29dbcb30 100644 --- a/app/modules/projects/project/project.jade +++ b/app/modules/projects/project/project.jade @@ -10,8 +10,8 @@ div.wrapper div.tags-block(tg-colorize-tags="vm.project.get('tags')", tg-colorize-tags-type="backlog") div.project-data - section.timeline - div(tg-user-timeline) + section.timeline(ng-if="vm.project") + div(tg-user-timeline, projectId="vm.project.get('id')", userId="vm.user.get('id')") section.involved-data h2.title {{"PROJECT.SECTION.TEAM" | translate}} ul.involved-team diff --git a/app/modules/resources/projects-resource.service.coffee b/app/modules/resources/projects-resource.service.coffee index 8c061b23..14f59b0d 100644 --- a/app/modules/resources/projects-resource.service.coffee +++ b/app/modules/resources/projects-resource.service.coffee @@ -22,6 +22,17 @@ Resource = (urlsService, http) -> url = urlsService.resolve("bulk-update-projects-order") return http.post(url, bulkData) + service.getTimeline = (projectId, page) -> + params = { + page: page + } + + url = urlsService.resolve("timeline-project") + url = "#{url}/#{projectId}" + + return http.get(url, params).then (result) -> + return Immutable.fromJS(result.data) + return () -> return {"projects": service} diff --git a/app/modules/resources/users-resource.service.coffee b/app/modules/resources/users-resource.service.coffee index 8badf44f..e75490ee 100644 --- a/app/modules/resources/users-resource.service.coffee +++ b/app/modules/resources/users-resource.service.coffee @@ -45,7 +45,7 @@ Resource = (urlsService, http) -> url = urlsService.resolve("timeline-profile") url = "#{url}/#{userId}" - return http.get(url, params).then (result) => + return http.get(url, params).then (result) -> return Immutable.fromJS(result.data) return () -> 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 ba7f60b3..6e15edbf 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 @@ -49,6 +49,12 @@ timelineType = (timeline, event) -> key: 'TIMELINE.TASK_CREATED', translate_params: ['username', 'project_name', 'obj_name'] }, + { # NewMilestone + check: (timeline, event) -> + return event.obj == 'milestone' && event.type == 'create' + key: 'TIMELINE.MILESTONE_CREATED', + translate_params: ['username', 'project_name', 'obj_name'] + }, { # NewUsComment check: (timeline, event) -> return timeline.data.comment && event.obj == 'userstory' diff --git a/app/modules/user-timeline/user-timeline/user-timeline.controller.coffee b/app/modules/user-timeline/user-timeline/user-timeline.controller.coffee index dffa696d..8dbcb903 100644 --- a/app/modules/user-timeline/user-timeline/user-timeline.controller.coffee +++ b/app/modules/user-timeline/user-timeline/user-timeline.controller.coffee @@ -25,26 +25,32 @@ mixOf = @.taiga.mixOf class UserTimelineController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.FiltersMixin) @.$inject = [ - "$tgAuth", "tgUserTimelineService" ] - constructor: (@auth, @userTimelineService) -> + constructor: (@userTimelineService) -> @.timelineList = Immutable.List() @.page = 1 @.loadingData = false loadTimeline: () -> - user = @auth.getUser() - @.loadingData = true - @userTimelineService - .getTimeline(user.id, @.page) - .then (newTimelineList) => - @.timelineList = @.timelineList.concat(newTimelineList) - @.page++ - @.loadingData = false + if @.projectId + @userTimelineService + .getProjectTimeline(@.projectId, @.page) + .then (newTimelineList) => + @._timelineLoaded(newTimelineList) + else + @userTimelineService + .getTimeline(@.userId, @.page) + .then (newTimelineList) => + @._timelineLoaded(newTimelineList) + + _timelineLoaded: (newTimelineList) -> + @.timelineList = @.timelineList.concat(newTimelineList) + @.page++ + @.loadingData = false angular.module("taigaUserTimeline") .controller("UserTimeline", UserTimelineController) diff --git a/app/modules/user-timeline/user-timeline/user-timeline.controller.spec.coffee b/app/modules/user-timeline/user-timeline/user-timeline.controller.spec.coffee index 3979a071..c0dc090c 100644 --- a/app/modules/user-timeline/user-timeline/user-timeline.controller.spec.coffee +++ b/app/modules/user-timeline/user-timeline/user-timeline.controller.spec.coffee @@ -1,5 +1,5 @@ describe "UserTimelineController", -> - myCtrl = scope = $q = provide = null + controller = scope = $q = provide = null mocks = {} @@ -7,42 +7,37 @@ describe "UserTimelineController", -> _mockUserTimeline = () -> mocks.userTimelineService = { - getTimeline: sinon.stub() + getTimeline: sinon.stub(), + getProjectTimeline: sinon.stub() } provide.value "tgUserTimelineService", mocks.userTimelineService - _mockTgAuth = () -> - provide.value "$tgAuth", { - getUser: () -> - return mockUser - } - _mocks = () -> module ($provide) -> provide = $provide _mockUserTimeline() - _mockTgAuth() return null - beforeEach -> module "taigaUserTimeline" _mocks() inject ($controller, _$q_) -> $q = _$q_ - myCtrl = $controller "UserTimeline" + controller = $controller it "timelineList should be an array", () -> + myCtrl = controller "UserTimeline" expect(myCtrl.timelineList.toJS()).is.an("array") it "pagination starts at 1", () -> + myCtrl = controller "UserTimeline" expect(myCtrl.page).to.be.equal(1) describe "load timeline", () -> - thenStub = timelineList = null + timelineList = null beforeEach () -> timelineList = Immutable.fromJS([ @@ -52,6 +47,10 @@ describe "UserTimelineController", -> { fake: "fake"} ]) + it "the loadingData variable must be true during the timeline load", () -> + myCtrl = controller "UserTimeline" + myCtrl.userId = mockUser.id + thenStub = sinon.stub() mocks.userTimelineService.getTimeline = sinon.stub() @@ -60,7 +59,6 @@ describe "UserTimelineController", -> then: thenStub }) - it "the loadingData variable must be true during the timeline load", () -> expect(myCtrl.loadingData).to.be.false myCtrl.loadTimeline() @@ -72,6 +70,17 @@ describe "UserTimelineController", -> expect(myCtrl.loadingData).to.be.false it "pagiantion increase one every call to loadTimeline", () -> + myCtrl = controller "UserTimeline" + myCtrl.userId = mockUser.id + + thenStub = sinon.stub() + + mocks.userTimelineService.getTimeline = sinon.stub() + .withArgs(mockUser.id, myCtrl.page) + .returns({ + then: thenStub + }) + expect(myCtrl.page).to.equal(1) myCtrl.loadTimeline() @@ -81,8 +90,39 @@ describe "UserTimelineController", -> expect(myCtrl.page).to.equal(2) it "timeline items", () -> + myCtrl = controller "UserTimeline" + myCtrl.userId = mockUser.id + + thenStub = sinon.stub() + + mocks.userTimelineService.getTimeline = sinon.stub() + .withArgs(mockUser.id, myCtrl.page) + .returns({ + then: thenStub + }) + myCtrl.loadTimeline() thenStub.callArgWith(0, timelineList) expect(myCtrl.timelineList.size).to.be.eql(4) + + it "project timeline items", () -> + myCtrl = controller "UserTimeline" + myCtrl.userId = mockUser.id + myCtrl.projectId = 4 + + thenStub = sinon.stub() + + mocks.userTimelineService.getProjectTimeline = sinon.stub() + .withArgs(4, myCtrl.page) + .returns({ + then: thenStub + }) + + myCtrl.loadTimeline() + + thenStub.callArgWith(0, timelineList) + + expect(myCtrl.timelineList.size).to.be.eql(4) + expect(myCtrl.page).to.equal(2) diff --git a/app/modules/user-timeline/user-timeline/user-timeline.directive.coffee b/app/modules/user-timeline/user-timeline/user-timeline.directive.coffee index dda6e07a..9c15161e 100644 --- a/app/modules/user-timeline/user-timeline/user-timeline.directive.coffee +++ b/app/modules/user-timeline/user-timeline/user-timeline.directive.coffee @@ -3,7 +3,11 @@ UserTimelineDirective = -> templateUrl: "user-timeline/user-timeline/user-timeline.html", controller: "UserTimeline", controllerAs: "vm", - scope: {} + scope: { + projectId: "=projectid", + userId: "=userid" + }, + bindToController: true } angular.module("taigaProfile").directive("tgUserTimeline", UserTimelineDirective) 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 8c4381e0..b0deddda 100644 --- a/app/modules/user-timeline/user-timeline/user-timeline.service.coffee +++ b/app/modules/user-timeline/user-timeline/user-timeline.service.coffee @@ -55,4 +55,10 @@ class UserTimelineService extends taiga.Service .then (result) => return result.filter (timeline) => @._filterValidTimelineItems(timeline) + + getProjectTimeline: (projectId, page) -> + return @rs.projects.getTimeline(projectId, page) + .then (result) => + return result.filter (timeline) => @._filterValidTimelineItems(timeline) + angular.module("taigaUserTimeline").service("tgUserTimelineService", UserTimelineService) diff --git a/app/modules/user-timeline/user-timeline/user-timeline.service.spec.coffee b/app/modules/user-timeline/user-timeline/user-timeline.service.spec.coffee index 9cc2f2c9..bec9174d 100644 --- a/app/modules/user-timeline/user-timeline/user-timeline.service.spec.coffee +++ b/app/modules/user-timeline/user-timeline/user-timeline.service.spec.coffee @@ -12,6 +12,10 @@ describe "tgUserTimelineService", -> getTimeline: sinon.stub() } + mocks.resources.projects = { + getTimeline: sinon.stub() + } + provide.value "tgResources", mocks.resources _mocks = () -> @@ -36,74 +40,74 @@ describe "tgUserTimelineService", -> _setup() _inject() - it "filter invalid timeline items", (done) -> - valid_items = [ - { # valid item - event_type: "xx.tt.create", - data: { - values_diff: { - "status": "xx", - "subject": "xx" - } + valid_items = [ + { # valid item + event_type: "xx.tt.create", + data: { + values_diff: { + "status": "xx", + "subject": "xx" } - }, - { # invalid item - event_type: "xx.tt.create", - data: { - values_diff: { - "fake": "xx" - } + } + }, + { # invalid item + event_type: "xx.tt.create", + data: { + values_diff: { + "fake": "xx" } - }, - { # invalid item - event_type: "xx.tt.create", - data: { - values_diff: { - "fake2": "xx" - } + } + }, + { # invalid item + event_type: "xx.tt.create", + data: { + values_diff: { + "fake2": "xx" } - }, - { # valid item - event_type: "xx.tt.create", - data: { - values_diff: { - "fake2": "xx", - "milestone": "xx" - } + } + }, + { # valid item + event_type: "xx.tt.create", + data: { + values_diff: { + "fake2": "xx", + "milestone": "xx" } - }, - { # invalid item - event_type: "xx.tt.create", - data: { - values_diff: { - attachments: { - new: [] - } - } - } - }, - { # valid item - event_type: "xx.tt.create", - data: { - values_diff: { - attachments: { - new: [1, 2] - } - } - } - }, - { # invalid item - event_type: "xx.tt.delete", - data: { - values_diff: { - attachments: { - new: [1, 2] - } + } + }, + { # invalid item + event_type: "xx.tt.create", + data: { + values_diff: { + attachments: { + new: [] } } } - ] + }, + { # valid item + event_type: "xx.tt.create", + data: { + values_diff: { + attachments: { + new: [1, 2] + } + } + } + }, + { # invalid item + event_type: "xx.tt.delete", + data: { + values_diff: { + attachments: { + new: [1, 2] + } + } + } + } + ] + it "filter invalid user timeline items", (done) -> userId = 3 page = 2 @@ -126,3 +130,27 @@ describe "tgUserTimelineService", -> done() $rootScope.$apply() + + it "filter invalid project timeline items", (done) -> + projectId = 3 + page = 2 + + mocks.resources.projects.getTimeline = (_projectId_, _page_) -> + expect(_projectId_).to.be.equal(projectId) + expect(_page_).to.be.equal(page) + + return $q (resolve, reject) -> + resolve(Immutable.fromJS(valid_items)) + + userTimelineService.getProjectTimeline(projectId, page) + .then (_items_) -> + items = _items_.toJS() + + expect(items).to.have.length(3) + 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]) + + done() + + $rootScope.$apply()