From 331b14f13f3adcf149eb6eb7202f8763bfb73ce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Fri, 23 Oct 2015 16:50:57 +0200 Subject: [PATCH] Show likes, votes and watches list in user profile --- app/coffee/modules/resources/users.coffee | 2 +- .../profile/includes/profile-favorites.jade | 6 - .../profile-contacts/profile-contacts.jade | 50 +- .../profile-favs/items/items.directive.coffee | 20 + .../profile/profile-favs/items/project.jade | 34 + .../profile/profile-favs/items/ticket.jade | 80 +++ .../profile-favs.controller.coffee | 168 +++++ .../profile-favs.controller.spec.coffee | 679 ++++++++++++++++++ .../profile-favs.directive.coffee | 50 ++ .../profile/profile-favs/profile-favs.jade | 80 +++ .../profile/profile-favs/profile-favs.scss | 43 ++ .../profile-projects/profile-projects.jade | 67 +- .../profile-tab/profile-tab.directive.coffee | 4 +- .../profile/profile-tabs/profile-tabs.jade | 10 +- app/modules/profile/profile.jade | 48 +- .../profile/styles/profile-favorites.scss | 21 - .../projects-resource.service.coffee | 19 + .../resources/users-resource.service.coffee | 43 +- app/modules/services/app-meta.service.coffee | 21 +- .../services/app-meta.service.spec.coffee | 19 +- .../services/current-user.service.coffee | 16 +- .../services/current-user.service.spec.coffee | 37 +- app/modules/services/user.service.coffee | 12 + app/modules/services/user.service.spec.coffee | 82 ++- 24 files changed, 1494 insertions(+), 117 deletions(-) delete mode 100644 app/modules/profile/includes/profile-favorites.jade create mode 100644 app/modules/profile/profile-favs/items/items.directive.coffee create mode 100644 app/modules/profile/profile-favs/items/project.jade create mode 100644 app/modules/profile/profile-favs/items/ticket.jade create mode 100644 app/modules/profile/profile-favs/profile-favs.controller.coffee create mode 100644 app/modules/profile/profile-favs/profile-favs.controller.spec.coffee create mode 100644 app/modules/profile/profile-favs/profile-favs.directive.coffee create mode 100644 app/modules/profile/profile-favs/profile-favs.jade create mode 100644 app/modules/profile/profile-favs/profile-favs.scss delete mode 100644 app/modules/profile/styles/profile-favorites.scss diff --git a/app/coffee/modules/resources/users.coffee b/app/coffee/modules/resources/users.coffee index 3db62b96..cd7fca23 100644 --- a/app/coffee/modules/resources/users.coffee +++ b/app/coffee/modules/resources/users.coffee @@ -28,7 +28,7 @@ resourceProvider = ($http, $urls) -> service = {} service.contacts = (userId, options={}) -> - url = $urls.resolve("contacts", userId) + url = $urls.resolve("user-contacts", userId) httpOptions = {headers: {}} if not options.enablePagination diff --git a/app/modules/profile/includes/profile-favorites.jade b/app/modules/profile/includes/profile-favorites.jade deleted file mode 100644 index f0a723c7..00000000 --- a/app/modules/profile/includes/profile-favorites.jade +++ /dev/null @@ -1,6 +0,0 @@ -section.profile-favorites - nav.profile-favorites-filters - a.active(href="", title="No Filter") all - a(href="", title="Only show your team") projects - a(href="", title="Only show people you follow") US - a(href="", title="Only show people follow you") tasks diff --git a/app/modules/profile/profile-contacts/profile-contacts.jade b/app/modules/profile/profile-contacts/profile-contacts.jade index 265c26d7..06dfb26e 100644 --- a/app/modules/profile/profile-contacts/profile-contacts.jade +++ b/app/modules/profile/profile-contacts/profile-contacts.jade @@ -11,32 +11,30 @@ section.profile-contacts div(ng-if="vm.isCurrentUser") p(translate="USER.PROFILE.CURRENT_USER_CONTACTS_EMPTY") p(translate="USER.PROFILE.CURRENT_USER_CONTACTS_EMPTY_EXPLAIN") + //- + nav.profile-contact-filters + a.active(href="", title="No Filter") all + a(href="", title="Only show your team") team + a(href="", title="Only show people you follow") following + a(href="", title="Only show people follow you") followers - // nav.profile-contact-filters - // a.active(href="", title="No Filter") all - // a(href="", title="Only show your team") team - // a(href="", title="Only show people you follow") following - // a(href="", title="Only show people follow you") followers + div.list-itemtype-user(tg-repeat="contact in ::vm.contacts") + a.list-itemtype-avatar(tg-nav="user-profile:username=contact.get('username')", title="{{::contact.get('name')}}") + img(ng-src="{{::contact.get('photo')}}", alt="{{::contact.get('full_name')}}") - div.profile-contact-single(tg-repeat="contact in ::vm.contacts") - div.profile-contact-picture - a(tg-nav="user-profile:username=contact.get('username')", title="{{::contact.get('name') }}") - img(ng-src="{{::contact.get('photo')}}", alt="{{::contact.get('full_name')}}") + div.list-itemtype-user-data + h2 + a(tg-nav="user-profile:username=contact.get('username')", title="{{::contact.get('full_name_display') }}") {{::contact.get('full_name_display')}} + + p {{::contact.get('roles').join(", ")}} + p.extra-info(ng-if="contact.get('bio')") {{::contact.get('bio')}} - div.profile-contact-data - h1 - a(tg-nav="user-profile:username=contact.get('username')", title="{{::contact.get('full_name_display') }}") - | {{::contact.get('full_name_display')}} - - p(ng-if="contact.bio") {{::contact.get('bio')}} - - div.extra-info - span.position {{::contact.get('roles').join(", ")}} - // span.location todo - // div.profile-project-stats - // div.stat-projects(title="2 projects") - // span.icon.icon-project - // span.stat-num 2 - // div.stat-viewer(title="2 followers") - // span.icon.icon-open-eye - // span.stat-num 4 + //- + span.location todo + div.profile-project-stats + div.stat-projects(title="2 projects") + span.icon.icon-project + span.stat-num 2 + div.stat-viewer(title="2 followers") + span.icon.icon-open-eye + span.stat-num 4 diff --git a/app/modules/profile/profile-favs/items/items.directive.coffee b/app/modules/profile/profile-favs/items/items.directive.coffee new file mode 100644 index 00000000..5d7418ae --- /dev/null +++ b/app/modules/profile/profile-favs/items/items.directive.coffee @@ -0,0 +1,20 @@ +FavItemDirective = -> + link = (scope, el, attrs, ctrl) -> + scope.vm = {item: scope.item} + + templateUrl = (el, attrs) -> + if attrs.itemType == "project" + return "profile/profile-favs/items/project.html" + else # if attr.itemType in ["userstory", "task", "issue"] + return "profile/profile-favs/items/ticket.html" + + return { + scope: { + "item": "=tgFavItem" + } + link: link + templateUrl: templateUrl + } + + +angular.module("taigaProfile").directive("tgFavItem", FavItemDirective) diff --git a/app/modules/profile/profile-favs/items/project.jade b/app/modules/profile/profile-favs/items/project.jade new file mode 100644 index 00000000..2cb121d9 --- /dev/null +++ b/app/modules/profile/profile-favs/items/project.jade @@ -0,0 +1,34 @@ +.list-itemtype-project + .list-itemtype-project-data + h2 + a( + href="#" + tg-nav="project:project=vm.item.get('slug')" + title="{{ ::vm.item.get('name') }}" + ) {{ ::vm.item.get('name') }} + span.private(ng-if="::project.get('is_private')", title="{{'PROJECT.PRIVATE' | translate}}") + p {{ ::vm.item.get('description') }} + + .list-itemtype-project-tags.tags-container(ng-if="::vm.item.get('tags_colors').size") + span.tag( + tg-repeat="tag in ::vm.item.get('tags_colors')" + style='border-left: 5px solid {{ ::tag.get("color") }};' + ) + span.tag-name {{ ::tag.get('name') }} + + .list-itemtype-track + span.list-itemtype-track-likers( + ng-class="{'active': vm.item.get('is_fan')}" + title="{{ 'PROJECT.LIKE_BUTTON.COUNTER_TITLE'|translate:{total:vm.item.get(\"total_fans\")||0}:'messageformat' }}" + ) + span.icon + include ../../../../svg/like.svg + span {{ ::vm.item.get('total_fans') }} + + span.list-itemtype-track-watchers( + ng-class="{'active': vm.item.get('is_watcher')}" + title="{{ 'PROJECT.WATCH_BUTTON.COUNTER_TITLE'|translate:{total:vm.item.get(\"total_watchers\")||0}:'messageformat' }}" + ) + span.icon + include ../../../../svg/watch.svg + span {{ ::vm.item.get('total_watchers') }} diff --git a/app/modules/profile/profile-favs/items/ticket.jade b/app/modules/profile/profile-favs/items/ticket.jade new file mode 100644 index 00000000..f8051bc4 --- /dev/null +++ b/app/modules/profile/profile-favs/items/ticket.jade @@ -0,0 +1,80 @@ +div.list-itemtype-ticket + a.list-itemtype-avatar( + href="" + ng-if="::vm.item.get('assigned_to')" + tg-nav="user-profile:username=vm.item.get('assigned_to_username')" + title="{{ ::vm.item.get('assigned_to_full_name') }}" + ) + img( + ng-src="{{ ::vm.item.get('assigned_to_photo') }}", + alt="{{ ::vm.item.get('assigned_to_full_name') }}" + ) + + a.list-itemtype-avatar( + href="" + ng-if="::!vm.item.get('assigned_to')", + title="{{ 'COMMON.ASSIGNED_TO.NOT_ASSIGNED'|translate }}" + ) + img( + src="/images/unnamed.png", + alt="{{ 'COMMON.ASSIGNED_TO.NOT_ASSIGNED'|translate }}" + ) + + div.list-itemtype-ticket-data + p + span.ticket-project + | {{:: vm.item.get('project_name') }} + span.ticket-type( + ng-if="::vm.item.get('type') === 'userstory'" + translate="COMMON.USER_STORY" + ) + span.ticket-type( + ng-if="::vm.item.get('type') === 'task'" + translate="COMMON.TASK" + ) + span.ticket-type( + ng-if="::vm.item.get('type') === 'issue'" + translate="COMMON.ISSUE" + ) + span.ticket-status(ng-style="::{'color': vm.item.get('status_color')}") + | {{:: vm.item.get('status') }} + h2 + span.ticket-id(tg-bo-ref="vm.item.get('ref')") + a.ticket-title( + href="#" + ng-if="::vm.item.get('type') === 'userstory'" + tg-nav="project-userstories-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') === 'task'" + tg-nav="project-tasks-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') === 'issue'" + tg-nav="project-issues-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') }} + + div.list-itemtype-track + span.list-itemtype-track-likers( + ng-class="{'active': vm.item.get('is_voter')}", + title="{{ 'COMMON.VOTE_BUTTON.COUNTER_TITLE'|translate:{total:vm.item.get(\"total_voters\")||0}:'messageformat' }}" + ) + span.icon + include ../../../../svg/upvote.svg + span {{ ::vm.item.get('total_voters') }} + + span.list-itemtype-track-watchers( + ng-class="{'active': vm.item.get('is_watcher')}" + title="{{ 'COMMON.WATCH_BUTTON.COUNTER_TITLE'|translate:{total:vm.item.get(\"total_watchers\")||0}:'messageformat' }}" + ) + span.icon + include ../../../../svg/watch.svg + span {{ ::vm.item.get('total_watchers') }} diff --git a/app/modules/profile/profile-favs/profile-favs.controller.coffee b/app/modules/profile/profile-favs/profile-favs.controller.coffee new file mode 100644 index 00000000..b4ba78e0 --- /dev/null +++ b/app/modules/profile/profile-favs/profile-favs.controller.coffee @@ -0,0 +1,168 @@ +debounceLeading = @.taiga.debounceLeading + +class FavsBaseController + constructor: -> + @._init() + + #@._getItems = null # Define in inheritance classes + # + _init: -> + @.enableFilterByAll = true + @.enableFilterByProjects = true + @.enableFilterByUserStories = true + @.enableFilterByTasks = true + @.enableFilterByIssues = true + @.enableFilterByTextQuery = true + + @._resetList() + @.q = null + @.type = null + + _resetList: -> + @.items = Immutable.List() + @.scrollDisabled = false + @._page = 1 + + _enableLoadingSpinner: -> + @.isLoading = true + + _disableLoadingSpinner: -> + @.isLoading = false + + _enableScroll : -> + @.scrollDisabled = false + + _disableScroll : -> + @.scrollDisabled = true + + _checkIfHasMorePages: (hasNext) -> + if hasNext + @._page += 1 + @._enableScroll() + else + @._disableScroll() + + _checkIfHasNoResults: -> + @.hasNoResults = @.items.size == 0 + + loadItems: -> + @._enableLoadingSpinner() + @._disableScroll() + + @._getItems(@.user.get("id"), @._page, @.type, @.q) + .then (response) => + @.items = @.items.concat(response.get("data")) + + @._checkIfHasMorePages(response.get("next")) + @._checkIfHasNoResults() + @._disableLoadingSpinner() + + return @.items + .catch => + @._disableLoadingSpinner() + + return @.items + + ################################################ + ## Filtre actions + ################################################ + filterByTextQuery: debounceLeading 500, -> + @._resetList() + @.loadItems() + + showAll: -> + if @.type isnt null + @.type = null + @._resetList() + @.loadItems() + + showProjectsOnly: -> + if @.type isnt "project" + @.type = "project" + @._resetList() + @.loadItems() + + showUserStoriesOnly: -> + if @.type isnt "userstory" + @.type = "userstory" + @._resetList() + @.loadItems() + + showTasksOnly: -> + if @.type isnt "task" + @.type = "task" + @._resetList() + @.loadItems() + + showIssuesOnly: -> + if @.type isnt "issue" + @.type = "issue" + @._resetList() + @.loadItems() + + +#################################################### +## Liked +#################################################### + +class ProfileLikedController extends FavsBaseController + @.$inject = [ + "tgUserService", + ] + + constructor: (@userService) -> + super() + @.enableFilterByAll = false + @.enableFilterByProjects = false + @.enableFilterByUserStories = false + @.enableFilterByTasks = false + @.enableFilterByIssues = false + @.enableFilterByTextQuery = true + @._getItems = @userService.getLiked + + +angular.module("taigaProfile") + .controller("ProfileLiked", ProfileLikedController) + +#################################################### +## Voted +#################################################### + +class ProfileVotedController extends FavsBaseController + @.$inject = [ + "tgUserService", + ] + + constructor: (@userService) -> + super() + @.enableFilterByAll = true + @.enableFilterByProjects = false + @.enableFilterByUserStories = true + @.enableFilterByTasks = true + @.enableFilterByIssues = true + @.enableFilterByTextQuery = true + @._getItems = @userService.getVoted + + +angular.module("taigaProfile") + .controller("ProfileVoted", ProfileVotedController) + + + +#################################################### +## Watched +#################################################### + +class ProfileWatchedController extends FavsBaseController + @.$inject = [ + "tgUserService", + ] + + constructor: (@userService) -> + super() + @._getItems = @userService.getWatched + + +angular.module("taigaProfile") + .controller("ProfileWatched", ProfileWatchedController) + diff --git a/app/modules/profile/profile-favs/profile-favs.controller.spec.coffee b/app/modules/profile/profile-favs/profile-favs.controller.spec.coffee new file mode 100644 index 00000000..99e98b99 --- /dev/null +++ b/app/modules/profile/profile-favs/profile-favs.controller.spec.coffee @@ -0,0 +1,679 @@ +describe "ProfileLiked", -> + $controller = null + provide = null + $rootScope = null + mocks = {} + + user = Immutable.fromJS({id: 2}) + + _mockUserService = () -> + mocks.userServices = { + getLiked: sinon.stub() + } + + provide.value "tgUserService", mocks.userServices + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockUserService() + + return null + + _inject = (callback) -> + inject (_$controller_, _$rootScope_) -> + $rootScope = _$rootScope_ + $controller = _$controller_ + + beforeEach -> + module "taigaProfile" + _mocks() + _inject() + + it "load paginated items", (done) -> + $scope = $rootScope.$new() + ctrl = $controller("ProfileLiked", $scope, {user: user}) + + items1 = Immutable.fromJS({ + data: [ + {id: 1}, + {id: 2}, + {id: 3} + ], + next: true + }) + items2 = Immutable.fromJS({ + data: [ + {id: 4}, + {id: 5}, + ], + next: false + }) + + mocks.userServices.getLiked.withArgs(user.get("id"), 1, null, null).promise().resolve(items1) + mocks.userServices.getLiked.withArgs(user.get("id"), 2, null, null).promise().resolve(items2) + + 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.loadItems().then () => + expectItems = items1.get("data") + + expect(ctrl.items.equals(expectItems)).to.be.true + expect(ctrl.scrollDisabled).to.be.false + expect(ctrl.type).to.be.null + expect(ctrl.q).to.be.null + + ctrl.loadItems().then () => + expectItems = expectItems.concat(items2.get("data")) + + expect(ctrl.items.equals(expectItems)).to.be.true + expect(ctrl.scrollDisabled).to.be.true + expect(ctrl.type).to.be.null + expect(ctrl.q).to.be.null + done() + + it "filter items by text query", (done) -> + $scope = $rootScope.$new() + ctrl = $controller("ProfileLiked", $scope, {user: user}) + + textQuery = "_test_" + + items = Immutable.fromJS({ + data: [ + {id: 1}, + {id: 2}, + {id: 3} + ], + next: true + }) + + mocks.userServices.getLiked.withArgs(user.get("id"), 1, null, textQuery).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.q = textQuery + + ctrl.loadItems().then () => + expectItems = items.get("data") + + expect(ctrl.items.equals(expectItems)).to.be.true + expect(ctrl.scrollDisabled).to.be.false + expect(ctrl.type).to.be.null + expect(ctrl.q).to.be.equal(textQuery) + done() + + it "shou loading spinner during the call to the api", (done) -> + $scope = $rootScope.$new() + ctrl = $controller("ProfileLiked", $scope, {user: user}) + + items = Immutable.fromJS({ + data: [ + {id: 1}, + {id: 2}, + {id: 3} + ], + next: true + }) + + mockPromise = mocks.userServices.getLiked.withArgs(user.get("id"), 1, null, null).promise() + + expect(ctrl.isLoading).to.be.undefined + + promise = ctrl.loadItems() + + expect(ctrl.isLoading).to.be.true + + mockPromise.resolve(items) + + promise.then () => + expect(ctrl.isLoading).to.be.false + done() + + it "shou no results placeholder", (done) -> + $scope = $rootScope.$new() + ctrl = $controller("ProfileLiked", $scope, {user: user}) + + items = Immutable.fromJS({ + data: [], + next: false + }) + + mocks.userServices.getLiked.withArgs(user.get("id"), 1, null, null).promise().resolve(items) + + expect(ctrl.hasNoResults).to.be.undefined + + ctrl.loadItems().then () => + expect(ctrl.hasNoResults).to.be.true + done() + + +describe "ProfileVoted", -> + $controller = null + provide = null + $rootScope = null + mocks = {} + + user = Immutable.fromJS({id: 2}) + + _mockUserService = () -> + mocks.userServices = { + getVoted: sinon.stub() + } + + provide.value "tgUserService", mocks.userServices + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockUserService() + + return null + + _inject = (callback) -> + inject (_$controller_, _$rootScope_) -> + $rootScope = _$rootScope_ + $controller = _$controller_ + + beforeEach -> + module "taigaProfile" + _mocks() + _inject() + + it "load paginated items", (done) -> + $scope = $rootScope.$new() + ctrl = $controller("ProfileVoted", $scope, {user: user}) + + items1 = Immutable.fromJS({ + data: [ + {id: 1}, + {id: 2}, + {id: 3} + ], + next: true + }) + items2 = Immutable.fromJS({ + data: [ + {id: 4}, + {id: 5}, + ], + next: false + }) + + mocks.userServices.getVoted.withArgs(user.get("id"), 1, null, null).promise().resolve(items1) + mocks.userServices.getVoted.withArgs(user.get("id"), 2, null, null).promise().resolve(items2) + + 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.loadItems().then () => + expectItems = items1.get("data") + + expect(ctrl.items.equals(expectItems)).to.be.true + expect(ctrl.scrollDisabled).to.be.false + expect(ctrl.type).to.be.null + expect(ctrl.q).to.be.null + + ctrl.loadItems().then () => + expectItems = expectItems.concat(items2.get("data")) + + expect(ctrl.items.equals(expectItems)).to.be.true + expect(ctrl.scrollDisabled).to.be.true + expect(ctrl.type).to.be.null + expect(ctrl.q).to.be.null + done() + + it "filter items by text query", (done) -> + $scope = $rootScope.$new() + ctrl = $controller("ProfileVoted", $scope, {user: user}) + + textQuery = "_test_" + + items = Immutable.fromJS({ + data: [ + {id: 1}, + {id: 2}, + {id: 3} + ], + next: true + }) + + mocks.userServices.getVoted.withArgs(user.get("id"), 1, null, textQuery).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.q = textQuery + + ctrl.loadItems().then () => + expectItems = items.get("data") + + expect(ctrl.items.equals(expectItems)).to.be.true + expect(ctrl.scrollDisabled).to.be.false + expect(ctrl.type).to.be.null + expect(ctrl.q).to.be.equal(textQuery) + done() + + it "show only items of user stories", (done) -> + $scope = $rootScope.$new() + ctrl = $controller("ProfileVoted", $scope, {user: user}) + + type = "userstory" + + 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.showUserStoriesOnly().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 tasks", (done) -> + $scope = $rootScope.$new() + ctrl = $controller("ProfileVoted", $scope, {user: user}) + + type = "task" + + 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.showTasksOnly().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 issues", (done) -> + $scope = $rootScope.$new() + ctrl = $controller("ProfileVoted", $scope, {user: user}) + + type = "issue" + + 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.showIssuesOnly().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 "shou loading spinner during the call to the api", (done) -> + $scope = $rootScope.$new() + ctrl = $controller("ProfileVoted", $scope, {user: user}) + + items = Immutable.fromJS({ + data: [ + {id: 1}, + {id: 2}, + {id: 3} + ], + next: true + }) + + mockPromise = mocks.userServices.getVoted.withArgs(user.get("id"), 1, null, null).promise() + + expect(ctrl.isLoading).to.be.undefined + + promise = ctrl.loadItems() + + expect(ctrl.isLoading).to.be.true + + mockPromise.resolve(items) + + promise.then () => + expect(ctrl.isLoading).to.be.false + done() + + it "shou no results placeholder", (done) -> + $scope = $rootScope.$new() + ctrl = $controller("ProfileVoted", $scope, {user: user}) + + items = Immutable.fromJS({ + data: [], + next: false + }) + + mocks.userServices.getVoted.withArgs(user.get("id"), 1, null, null).promise().resolve(items) + + expect(ctrl.hasNoResults).to.be.undefined + + ctrl.loadItems().then () => + expect(ctrl.hasNoResults).to.be.true + done() + +describe "ProfileWatched", -> + $controller = null + provide = null + $rootScope = null + mocks = {} + + user = Immutable.fromJS({id: 2}) + + _mockUserService = () -> + mocks.userServices = { + getWatched: sinon.stub() + } + + provide.value "tgUserService", mocks.userServices + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockUserService() + + return null + + _inject = (callback) -> + inject (_$controller_, _$rootScope_) -> + $rootScope = _$rootScope_ + $controller = _$controller_ + + beforeEach -> + module "taigaProfile" + _mocks() + _inject() + + it "load paginated items", (done) -> + $scope = $rootScope.$new() + ctrl = $controller("ProfileWatched", $scope, {user: user}) + + items1 = Immutable.fromJS({ + data: [ + {id: 1}, + {id: 2}, + {id: 3} + ], + next: true + }) + items2 = Immutable.fromJS({ + data: [ + {id: 4}, + {id: 5}, + ], + next: false + }) + + mocks.userServices.getWatched.withArgs(user.get("id"), 1, null, null).promise().resolve(items1) + mocks.userServices.getWatched.withArgs(user.get("id"), 2, null, null).promise().resolve(items2) + + 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.loadItems().then () => + expectItems = items1.get("data") + + expect(ctrl.items.equals(expectItems)).to.be.true + expect(ctrl.scrollDisabled).to.be.false + expect(ctrl.type).to.be.null + expect(ctrl.q).to.be.null + + ctrl.loadItems().then () => + expectItems = expectItems.concat(items2.get("data")) + + expect(ctrl.items.equals(expectItems)).to.be.true + expect(ctrl.scrollDisabled).to.be.true + expect(ctrl.type).to.be.null + expect(ctrl.q).to.be.null + done() + + it "filter items by text query", (done) -> + $scope = $rootScope.$new() + ctrl = $controller("ProfileWatched", $scope, {user: user}) + + textQuery = "_test_" + + items = Immutable.fromJS({ + data: [ + {id: 1}, + {id: 2}, + {id: 3} + ], + next: true + }) + + mocks.userServices.getWatched.withArgs(user.get("id"), 1, null, textQuery).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.q = textQuery + + ctrl.loadItems().then () => + expectItems = items.get("data") + + expect(ctrl.items.equals(expectItems)).to.be.true + expect(ctrl.scrollDisabled).to.be.false + expect(ctrl.type).to.be.null + expect(ctrl.q).to.be.equal(textQuery) + done() + + it "show only items of projects", (done) -> + $scope = $rootScope.$new() + ctrl = $controller("ProfileWatched", $scope, {user: user}) + + type = "project" + + 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.showProjectsOnly().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}) + + type = "userstory" + + 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.showUserStoriesOnly().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 tasks", (done) -> + $scope = $rootScope.$new() + ctrl = $controller("ProfileWatched", $scope, {user: user}) + + type = "task" + + 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.showTasksOnly().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 issues", (done) -> + $scope = $rootScope.$new() + ctrl = $controller("ProfileWatched", $scope, {user: user}) + + type = "issue" + + 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.showIssuesOnly().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 "shou loading spinner during the call to the api", (done) -> + $scope = $rootScope.$new() + ctrl = $controller("ProfileWatched", $scope, {user: user}) + + items = Immutable.fromJS({ + data: [ + {id: 1}, + {id: 2}, + {id: 3} + ], + next: true + }) + + mockPromise = mocks.userServices.getWatched.withArgs(user.get("id"), 1, null, null).promise() + + expect(ctrl.isLoading).to.be.undefined + + promise = ctrl.loadItems() + + expect(ctrl.isLoading).to.be.true + + mockPromise.resolve(items) + + promise.then () => + expect(ctrl.isLoading).to.be.false + done() + + it "shou no results placeholder", (done) -> + $scope = $rootScope.$new() + ctrl = $controller("ProfileWatched", $scope, {user: user}) + + items = Immutable.fromJS({ + data: [], + next: false + }) + + mocks.userServices.getWatched.withArgs(user.get("id"), 1, null, null).promise().resolve(items) + + expect(ctrl.hasNoResults).to.be.undefined + + ctrl.loadItems().then () => + expect(ctrl.hasNoResults).to.be.true + done() diff --git a/app/modules/profile/profile-favs/profile-favs.directive.coffee b/app/modules/profile/profile-favs/profile-favs.directive.coffee new file mode 100644 index 00000000..91bfe690 --- /dev/null +++ b/app/modules/profile/profile-favs/profile-favs.directive.coffee @@ -0,0 +1,50 @@ +base = { + scope: {}, + bindToController: { + user: "=" + type: "@" + q: "@" + scrollDisabled: "@" + isLoading: "@" + hasNoResults: "@" + } + controller: null, # Define in directives + controllerAs: "vm", + templateUrl: "profile/profile-favs/profile-favs.html", +} + + +#################################################### +## Liked +#################################################### + +ProfileLikedDirective = () -> + return _.extend({}, base, { + controller: "ProfileLiked" + }) + +angular.module("taigaProfile").directive("tgProfileLiked", ProfileLikedDirective) + + +#################################################### +## Voted +#################################################### + +ProfileVotedDirective = () -> + return _.extend({}, base, { + controller: "ProfileVoted" + }) + +angular.module("taigaProfile").directive("tgProfileVoted", ProfileVotedDirective) + + +#################################################### +## Watched +#################################################### + +ProfileWatchedDirective = () -> + return _.extend({}, base, { + controller: "ProfileWatched" + }) + +angular.module("taigaProfile").directive("tgProfileWatched", ProfileWatchedDirective) diff --git a/app/modules/profile/profile-favs/profile-favs.jade b/app/modules/profile/profile-favs/profile-favs.jade new file mode 100644 index 00000000..38d976e3 --- /dev/null +++ b/app/modules/profile/profile-favs/profile-favs.jade @@ -0,0 +1,80 @@ +section.profile-favs + div.profile-filter + div.searchbox(ng-if="::vm.enableFilterByTextQuery") + span.icon-search + input( + type="text" + ng-model="vm.q" + ng-change="vm.filterByTextQuery()" + placeholder="{{ 'USER.PROFILE_FAVS.FILTER_INPUT_PLACEHOLDER'|translate }}" + ) + + div.filters + a( + href="" + ng-if="::vm.enableFilterByAll" + ng-click="vm.showAll()" + ng-class="{active: vm.type === null}" + title="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_ALL_TITLE'|translate }}" + translate="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_ALL'|translate }}" + ) + a( + href="" + ng-if="::vm.enableFilterByProjects" + ng-click="vm.showProjectsOnly()" + ng-class="{active: vm.type === 'project'}" + title="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_PROJECTS_TITLE'|translate }}" + translate="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_PROJECTS'|translate }}" + ) + a( + href="" + ng-if="::vm.enableFilterByUserStories" + ng-click="vm.showUserStoriesOnly()" + ng-class="{active: vm.type === 'userstory'}", + title="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_USER_STORIES_TITLE'|translate }}" + translate="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_USER_STORIES'|translate }}" + ) + a( + href="" + ng-if="::vm.enableFilterByTasks" + ng-click="vm.showTasksOnly()" + ng-class="{active: vm.type === 'task'}" + title="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_TASKS_TITLE'|translate }}" + translate="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_TASKS'|translate }}" + ) + a( + href="" + ng-if="::vm.enableFilterByIssues" + ng-click="vm.showIssuesOnly()" + ng-class="{active: vm.type === 'issue'}" + title="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_ISSUES_TITLE'|translate }}" + translate="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_ISSUES'|translate }}" + ) + + div( + infinite-scroll="vm.loadItems()" + infinite-scroll-distance="2" + infinite-scroll-disabled="vm.scrollDisabled" + ) + div( + tg-repeat="item in vm.items track by $index" + ng-switch="item.get('type')" + ) + div(ng-switch-when="project", tg-fav-item="item", item-type="project") + div(ng-switch-when="userstory", tg-fav-item="item", item-type="userstory") + div(ng-switch-when="task", tg-fav-item="item", item-type="task") + div(ng-switch-when="issue", tg-fav-item="item", item-type="issue") + + div(ng-if="vm.isLoading") + div.spin + img( + src="/svg/spinner-circle.svg" + alt="{{ 'COMMON.LOADING'|translate }}" + ) + + .empty-search-results(ng-if="vm.hasNoResults && !vm.isLoading") + img( + src="../../images/search-empty.png" + alt="{{ 'USER.PROFILE_FAVS.EMPTY_TITLE' | translate }}" + ) + p.title {{ 'USER.PROFILE_FAVS.EMPTY_TITLE' | translate }} diff --git a/app/modules/profile/profile-favs/profile-favs.scss b/app/modules/profile/profile-favs/profile-favs.scss new file mode 100644 index 00000000..e37d4542 --- /dev/null +++ b/app/modules/profile/profile-favs/profile-favs.scss @@ -0,0 +1,43 @@ +.profile-favs { + border-top: 1px solid $whitish; +} + +.profile-filter { + align-items: center; + background: $whitish; + display: flex; + justify-content: space-between; + margin: 1rem 0; + padding: .5rem 1rem; + .searchbox { + align-items: center; + display: flex; + flex: 1; + .icon-search { + color: grayer; + margin-right: .5rem; + } + input { + border: 0; + border-bottom: 1px solid transparent; + flex: 1; + margin-right: 1rem; + &:focus { + border-bottom: 1px solid $gray-light; + outline: none; + transition: border-bottom .3s ease-in; + } + } + } + .filters { + a { + color: $gray-light; + display: inline-block; + padding: 0 .5rem; + &:hover, + &.active { + color: $blackish; + } + } + } +} diff --git a/app/modules/profile/profile-projects/profile-projects.jade b/app/modules/profile/profile-projects/profile-projects.jade index 4b5a8bd5..c9d10a0f 100644 --- a/app/modules/profile/profile-projects/profile-projects.jade +++ b/app/modules/profile/profile-projects/profile-projects.jade @@ -3,37 +3,56 @@ section.profile-projects div.spin img(src="/svg/spinner-circle.svg", alt="Loading...") - div.empty-tab(ng-if="vm.projects && !vm.projects.size") + .empty-tab(ng-if="vm.projects && !vm.projects.size") include ../../../svg/hide.svg - p(translate="USER.PROFILE.PROJECTS_EMPTY", translate-values="{username: vm.user.get('full_name_display')}") + p( + translate="USER.PROFILE.PROJECTS_EMPTY" + translate-values="{username: vm.user.get('full_name_display')}" + ) - div.project-list-single(tg-repeat="project in vm.projects") - div.project-list-single-left + .list-itemtype-project(tg-repeat="project in vm.projects") + .list-itemtype-project-left - div.project-list-single-title - h1 - a(href="#", tg-nav="project:project=project.get('slug')", title="{{ ::project.get('name') }}") {{::project.get('name')}} + .project-list-single-title + h2 + a( + href="#" + tg-nav="project:project=project.get('slug')" + title="{{ ::project.get('name') }}" + ) {{::project.get('name')}} p {{ ::project.get('description') | limitTo:300 }} - div.project-list-single-tags.tags-container(ng-if="::project.get('tags').size") - span.tag(style='border-left: 5px solid {{::tag.get("color")}};', tg-repeat="tag in ::project.get('colorized_tags')") + .list-itemtype-project-tags.tags-container(ng-if="::project.get('tags').size") + span.tag( + style='border-left: 5px solid {{::tag.get("color")}};' + tg-repeat="tag in ::project.get('colorized_tags')" + ) span.tag-name {{::tag.get('name')}} - div.project-list-single-right + .list-itemtype-project-right - div.project-list-single-members - a(tg-repeat="contact in ::project.get('contacts')", tg-nav="user-profile:username=contact.get('username')", title="{{::contact.get('full_name')}}") + .list-itemtype-track + span.list-itemtype-track-likers( + ng-class="{'active': project.get('is_fan')}" + title="{{ 'PROJECT.LIKE_BUTTON.COUNTER_TITLE'|translate:{total:project.get(\"total_fans\")||0}:'messageformat' }}" + ) + span.icon + include ../../../svg/like.svg + span {{ ::project.get('total_fans') }} + + span.list-itemtype-track-watchers( + ng-class="{'active': project.get('is_watcher')}" + title="{{ 'PROJECT.WATCH_BUTTON.COUNTER_TITLE'|translate:{total:project.get(\"total_watchers\")||0}:'messageformat' }}" + ) + span.icon + include ../../../svg/watch.svg + span {{ ::project.get('total_watchers') }} + + .list-itemtype-project-members + a( + tg-repeat="contact in ::project.get('contacts')" + tg-nav="user-profile:username=contact.get('username')" + title="{{::contact.get('full_name')}}" + ) img(ng-src="{{::contact.get('photo')}}") - - // div.project-list-single-right - // div.project-list-single-stats - // div.stat-comments(title="2 comments") - // span.icon.icon-comment - // span.stat-num 2 - // div.stat-favorite.active(title="2 favorites") - // span.icon.icon-star-fill - // span.stat-num 4 - // div.stat-viewer(title="2 followers") - // span.icon.icon-open-eye - // span.stat-num 4 diff --git a/app/modules/profile/profile-tab/profile-tab.directive.coffee b/app/modules/profile/profile-tab/profile-tab.directive.coffee index 8312ddd4..3e4983da 100644 --- a/app/modules/profile/profile-tab/profile-tab.directive.coffee +++ b/app/modules/profile/profile-tab/profile-tab.directive.coffee @@ -2,10 +2,12 @@ ProfileTabDirective = () -> link = (scope, element, attrs, ctrl, transclude) -> scope.tab = {} + attrs.$observe "tgProfileTab", (name) -> + scope.tab.name = name + attrs.$observe "tabTitle", (title) -> scope.tab.title = title - scope.tab.name = attrs.tgProfileTab scope.tab.icon = attrs.tabIcon scope.tab.active = !!attrs.tabActive diff --git a/app/modules/profile/profile-tabs/profile-tabs.jade b/app/modules/profile/profile-tabs/profile-tabs.jade index 00390cd8..ba10af3a 100644 --- a/app/modules/profile/profile-tabs/profile-tabs.jade +++ b/app/modules/profile/profile-tabs/profile-tabs.jade @@ -1,7 +1,13 @@ div nav.profile-content-tabs - a.tab(ng-repeat="tab in ::vm.tabs", href="", title="{{tab.title}}", ng-class="{active: tab.active}" ng-click="vm.toggleTab(tab)") + a.tab( + href="" + ng-repeat="tab in ::vm.tabs" + title="{{tab.title}}" + ng-click="vm.toggleTab(tab)" + ng-class="{active: tab.active}" + ) span.icon(ng-class="::tab.icon") span {{::tab.name}} - ng-transclude \ No newline at end of file + ng-transclude diff --git a/app/modules/profile/profile.jade b/app/modules/profile/profile.jade index 18058610..dc5ce3f7 100644 --- a/app/modules/profile/profile.jade +++ b/app/modules/profile/profile.jade @@ -2,16 +2,48 @@ div.profile.centered(ng-if="vm.user") div(tg-profile-bar, user="vm.user", isCurrentUser="vm.isCurrentUser") 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, user="vm.user", current-user="vm.isCurrentUser") + div( + tg-profile-tab="{{'USER.PROFILE.TABS.ACTIVITY_TAB' | translate}}" + tab-title="{{'USER.PROFILE.TABS.ACTIVITY_TAB_TITLE' | translate}}" + tab-icon="icon-timeline" + tab-active + ) + div(tg-user-timeline, user="vm.user", current-user="vm.isCurrentUser") - div(tab-disabled="{{vm.isCurrentUser}}", tg-profile-tab="projects", tab-title="{{'USER.PROFILE.PROJECTS_TAB' | translate}}", tab-icon="icon-project") - div(tg-profile-projects, user="vm.user") + div( + tg-profile-tab="{{'USER.PROFILE.TABS.PROJECTS_TAB' | translate}}" + tab-title="{{'USER.PROFILE.TABS.PROJECTS_TAB_TITLE' | translate}}" + tab-icon="icon-project" + tab-disabled="{{vm.isCurrentUser}}" + ) + div(tg-profile-projects, user="vm.user") - div(tg-profile-tab="contacts", tab-title="{{'USER.PROFILE.CONTACTS_TAB' | translate}}", tab-icon="icon-team") - div(tg-profile-contacts, user="vm.user") + div( + tg-profile-tab="{{'USER.PROFILE.TABS.LIKES_TAB' | translate}}" + tab-title="{{'USER.PROFILE.TABS.LIKES_TAB_TITLE' | translate}}" + tab-icon="icon-heart" + ) + div(tg-profile-liked, user="vm.user") - // div(tg-profile-tab="favorites", tab-title="{{'USER.PROFILE.FAVORITES_TAB' | translate}}", tab-icon="icon-star-fill") - // include includes/profile-favorites + div( + tg-profile-tab="{{'USER.PROFILE.TABS.VOTES_TAB' | translate}}" + tab-title="{{'USER.PROFILE.TABS.VOTES_TAB_TITLE' | translate}}" + tab-icon="icon-caret-up" + ) + div(tg-profile-voted, user="vm.user") + + div( + tg-profile-tab="{{'USER.PROFILE.TABS.WATCHED_TAB' | translate}}" + tab-title="{{'USER.PROFILE.TABS.WATCHED_TAB_TITLE' | translate}}" + tab-icon="icon-eye" + ) + div(tg-profile-watched, user="vm.user") + + div( + tg-profile-tab="{{'USER.PROFILE.TABS.CONTACTS_TAB' | translate}}" + tab-title="{{'USER.PROFILE.TABS.CONTACTS_TAB_TITLE' | translate}}" + tab-icon="icon-team" + ) + div(tg-profile-contacts, user="vm.user") include includes/profile-sidebar diff --git a/app/modules/profile/styles/profile-favorites.scss b/app/modules/profile/styles/profile-favorites.scss deleted file mode 100644 index 43212cd5..00000000 --- a/app/modules/profile/styles/profile-favorites.scss +++ /dev/null @@ -1,21 +0,0 @@ -.profile-favorites { - border-top: 1px solid $whitish; - display: flex; - flex-direction: column; - .profile-favorites-filters { - align-self: flex-start; - display: flex; - a { - border-bottom: 2px solid $white; - color: $gray-light; - display: inline-block; - padding: 1rem 1.5rem; - transition: all .2s linear; - &:hover, - &.active { - border-bottom: 2px solid $gray-light; - color: $primary; - } - } - } -} diff --git a/app/modules/resources/projects-resource.service.coffee b/app/modules/resources/projects-resource.service.coffee index 806bc550..6a674676 100644 --- a/app/modules/resources/projects-resource.service.coffee +++ b/app/modules/resources/projects-resource.service.coffee @@ -51,6 +51,25 @@ Resource = (urlsService, http, paginateResponseService) -> result = Immutable.fromJS(result) return paginateResponseService(result) + service.likeProject = (projectId) -> + url = urlsService.resolve("project-like", projectId) + return http.post(url) + + service.unlikeProject = (projectId) -> + url = urlsService.resolve("project-unlike", projectId) + return http.post(url) + + service.watchProject = (projectId, notifyPolicy) -> + data = { + notify_policy: notifyPolicy + } + url = urlsService.resolve("project-watch", projectId) + return http.post(url, data) + + service.unwatchProject = (projectId) -> + url = urlsService.resolve("project-unwatch", projectId) + return http.post(url) + return () -> return {"projects": service} diff --git a/app/modules/resources/users-resource.service.coffee b/app/modules/resources/users-resource.service.coffee index 57e0a8eb..ed89921e 100644 --- a/app/modules/resources/users-resource.service.coffee +++ b/app/modules/resources/users-resource.service.coffee @@ -19,7 +19,7 @@ Resource = (urlsService, http, paginateResponseService) -> return Immutable.fromJS(result.data) service.getStats = (userId) -> - url = urlsService.resolve("stats", userId) + url = urlsService.resolve("user-stats", userId) httpOptions = { headers: { @@ -32,7 +32,7 @@ Resource = (urlsService, http, paginateResponseService) -> return Immutable.fromJS(result.data) service.getContacts = (userId) -> - url = urlsService.resolve("contacts", userId) + url = urlsService.resolve("user-contacts", userId) httpOptions = { headers: { @@ -44,6 +44,45 @@ Resource = (urlsService, http, paginateResponseService) -> .then (result) -> return Immutable.fromJS(result.data) + service.getLiked = (userId, page, type, q) -> + url = urlsService.resolve("user-liked", userId) + + params = {} + params.page = page if page? + params.type = type if type? + params.q = q if q? + + return http.get(url, params) + .then (result) -> + result = Immutable.fromJS(result) + return paginateResponseService(result) + + service.getVoted = (userId, page, type, q) -> + url = urlsService.resolve("user-voted", userId) + + params = {} + params.page = page if page? + params.type = type if type? + params.q = q if q? + + return http.get(url, params) + .then (result) -> + result = Immutable.fromJS(result) + return paginateResponseService(result) + + service.getWatched = (userId, page, type, q) -> + url = urlsService.resolve("user-watched", userId) + + params = {} + params.page = page if page? + params.type = type if type? + params.q = q if q? + + return http.get(url, params) + .then (result) -> + result = Immutable.fromJS(result) + return paginateResponseService(result) + service.getProfileTimeline = (userId, page) -> params = { page: page diff --git a/app/modules/services/app-meta.service.coffee b/app/modules/services/app-meta.service.coffee index eee818b0..545c1099 100644 --- a/app/modules/services/app-meta.service.coffee +++ b/app/modules/services/app-meta.service.coffee @@ -3,7 +3,13 @@ taiga = @.taiga truncate = taiga.truncate -class AppMetaService extends taiga.Service = -> +class AppMetaService + @.$inject = [ + "$rootScope" + ] + + constructor: (@rootScope) -> + _set: (key, value) -> return if not key @@ -60,12 +66,19 @@ class AppMetaService extends taiga.Service = -> @.setOpenGraphMetas(title, description) addMobileViewport: () -> - $('head').append( - '' + $("head").append( + "" ) removeMobileViewport: () -> - $('meta[name="viewport"]').remove() + $("meta[name=\"viewport\"]").remove() + + setfn: (fn) -> + @._listener() if @.listener + + @._listener = @rootScope.$watchCollection fn, (metas) => + @.setAll(metas.title, metas.description) angular.module("taigaCommon").service("tgAppMetaService", AppMetaService) diff --git a/app/modules/services/app-meta.service.spec.coffee b/app/modules/services/app-meta.service.spec.coffee index ede2294d..c5f6f3cb 100644 --- a/app/modules/services/app-meta.service.spec.coffee +++ b/app/modules/services/app-meta.service.spec.coffee @@ -1,13 +1,15 @@ describe "AppMetaService", -> appMetaService = null + $rootScope = null data = { title: "--title--", description: "--description--" } _inject = () -> - inject (_tgAppMetaService_) -> + inject (_tgAppMetaService_, _$rootScope_) -> appMetaService = _tgAppMetaService_ + $rootScope = _$rootScope_ beforeEach -> module "taigaCommon" @@ -53,3 +55,18 @@ describe "AppMetaService", -> expect($("meta[property='og:description']")).to.have.attr("content", data.description) expect($("meta[property='og:image']")).to.have.attr("content", "#{window.location.origin}/images/logo-color.png") expect($("meta[property='og:url']")).to.have.attr("content", window.location.href) + + it "set function to set the metas", () -> + fn = () -> + return { + title: 'test', + description: 'test2' + } + + + appMetaService.setAll = sinon.stub() + appMetaService.setfn(fn) + + $rootScope.$digest() + + expect(appMetaService.setAll).to.have.been.calledWith('test', 'test2') diff --git a/app/modules/services/current-user.service.coffee b/app/modules/services/current-user.service.coffee index 21d7c217..69442831 100644 --- a/app/modules/services/current-user.service.coffee +++ b/app/modules/services/current-user.service.coffee @@ -49,13 +49,7 @@ class CurrentUserService loadProjects: () -> return @projectsService.getProjectsByUserId(@._user.get("id")) - .then (projects) => - @._projects = @._projects.set("all", projects) - @._projects = @._projects.set("recents", projects.slice(0, 10)) - - @._projectsById = Immutable.fromJS(groupBy(projects.toJS(), (p) -> p.id)) - - return @.projects + .then (projects) => @.setProjects(projects) disableJoyRide: (section) -> if section @@ -96,4 +90,12 @@ class CurrentUserService @.loadProjects() ]) + setProjects: (projects) -> + @._projects = @._projects.set("all", projects) + @._projects = @._projects.set("recents", projects.slice(0, 10)) + + @._projectsById = Immutable.fromJS(groupBy(projects.toJS(), (p) -> p.id)) + + return @.projects + angular.module("taigaCommon").service("tgCurrentUserService", CurrentUserService) diff --git a/app/modules/services/current-user.service.spec.coffee b/app/modules/services/current-user.service.spec.coffee index 7a418ff0..f547558e 100644 --- a/app/modules/services/current-user.service.spec.coffee +++ b/app/modules/services/current-user.service.spec.coffee @@ -92,7 +92,7 @@ describe "tgCurrentUserService", -> it "bulkUpdateProjectsOrder and reload projects", (done) -> fakeData = [{id: 1, id: 2}] - currentUserService.loadProjects = sinon.spy() + currentUserService.loadProjects = sinon.stub() mocks.projectsService.bulkUpdateProjectsOrder.withArgs(fakeData).promise().resolve() @@ -101,6 +101,41 @@ describe "tgCurrentUserService", -> done() + it "loadProject and set it", (done) -> + user = Immutable.fromJS({id: 1, name: "fake1"}) + project = Immutable.fromJS({id: 2, name: "fake2"}) + + currentUserService._user = user + currentUserService.setProjects = sinon.stub() + + mocks.projectsService.getProjectsByUserId.withArgs(1).promise().resolve(project) + + currentUserService.loadProjects().then () -> + expect(currentUserService.setProjects).to.have.been.calledWith(project) + + done() + + it "setProject", () -> + projectsRaw = [ + {id: 1, name: "fake1"}, + {id: 2, name: "fake2"}, + {id: 3, name: "fake3"}, + {id: 4, name: "fake4"} + ] + projectsRawById = { + 1: {id: 1, name: "fake1"}, + 2: {id: 2, name: "fake2"}, + 3: {id: 3, name: "fake3"}, + 4: {id: 4, name: "fake4"} + } + projects = Immutable.fromJS(projectsRaw) + + currentUserService.setProjects(projects) + + expect(currentUserService.projects.get('all').toJS()).to.be.eql(projectsRaw) + expect(currentUserService.projects.get('recents').toJS()).to.be.eql(projectsRaw) + expect(currentUserService.projectsById.toJS()).to.be.eql(projectsRawById) + it "is authenticated", () -> currentUserService.getUser = sinon.stub() currentUserService.getUser.returns({}) diff --git a/app/modules/services/user.service.coffee b/app/modules/services/user.service.coffee index f6c803d8..cc376569 100644 --- a/app/modules/services/user.service.coffee +++ b/app/modules/services/user.service.coffee @@ -1,9 +1,12 @@ taiga = @.taiga +bindMethods = taiga.bindMethods + class UserService extends taiga.Service @.$inject = ["tgResources"] constructor: (@rs) -> + bindMethods(@) getUserByUserName: (username) -> return @rs.users.getUserByUsername(username) @@ -11,6 +14,15 @@ class UserService extends taiga.Service getContacts: (userId) -> return @rs.users.getContacts(userId) + getLiked: (userId, pageNumber, objectType, textQuery) -> + return @rs.users.getLiked(userId, pageNumber, objectType, textQuery) + + getVoted: (userId, pageNumber, objectType, textQuery) -> + return @rs.users.getVoted(userId, pageNumber, objectType, textQuery) + + getWatched: (userId, pageNumber, objectType, textQuery) -> + return @rs.users.getWatched(userId, pageNumber, objectType, textQuery) + getStats: (userId) -> return @rs.users.getStats(userId) diff --git a/app/modules/services/user.service.spec.coffee b/app/modules/services/user.service.spec.coffee index eb8156a7..9645350a 100644 --- a/app/modules/services/user.service.spec.coffee +++ b/app/modules/services/user.service.spec.coffee @@ -32,19 +32,6 @@ describe "UserService", -> _mocks() _inject() - it "get user contacts", () -> - userId = 2 - - contacts = [ - {id: 1}, - {id: 2}, - {id: 3} - ] - - mocks.resources.users.getContacts.withArgs(userId).returns(true) - - expect(userService.getContacts(userId)).to.be.true - it "attach user contacts to projects", (done) -> userId = 2 @@ -88,6 +75,75 @@ describe "UserService", -> $rootScope.$apply() + it "get user liked", (done) -> + userId = 2 + pageNumber = 1 + objectType = null + textQuery = null + + liked = [ + {id: 1}, + {id: 2}, + {id: 3} + ] + + mocks.resources.users.getLiked = sinon.stub() + mocks.resources.users.getLiked.withArgs(userId, pageNumber, objectType, textQuery) + .promise() + .resolve(liked) + + userService.getLiked(userId, pageNumber, objectType, textQuery).then (_liked_) -> + expect(_liked_).to.be.eql(liked) + done() + + $rootScope.$apply() + + it "get user voted", (done) -> + userId = 2 + pageNumber = 1 + objectType = null + textQuery = null + + voted = [ + {id: 1}, + {id: 2}, + {id: 3} + ] + + mocks.resources.users.getVoted = sinon.stub() + mocks.resources.users.getVoted.withArgs(userId, pageNumber, objectType, textQuery) + .promise() + .resolve(voted) + + userService.getVoted(userId, pageNumber, objectType, textQuery).then (_voted_) -> + expect(_voted_).to.be.eql(voted) + done() + + $rootScope.$apply() + + it "get user watched", (done) -> + userId = 2 + pageNumber = 1 + objectType = null + textQuery = null + + watched = [ + {id: 1}, + {id: 2}, + {id: 3} + ] + + mocks.resources.users.getWatched = sinon.stub() + mocks.resources.users.getWatched.withArgs(userId, pageNumber, objectType, textQuery) + .promise() + .resolve(watched) + + userService.getWatched(userId, pageNumber, objectType, textQuery).then (_watched_) -> + expect(_watched_).to.be.eql(watched) + done() + + $rootScope.$apply() + it "get user by username", (done) -> username = "username-1"