diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 96965a00..748a5b4a 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -66,7 +66,17 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven ) $routeProvider.when("/project/:pslug/", - {templateUrl: "project/project.html"}) + { + templateUrl: "projects/project/project-page.html", + resolve: { + loader: tgLoaderProvider.add(true), + pageParams: -> { + "authRequired": true + } + }, + controller: "Page" + } + ) $routeProvider.when("/project/:pslug/search", {templateUrl: "search/search.html", reloadOnSearch: false}) diff --git a/app/js/tg-repeat.js b/app/js/tg-repeat.js index 8b6528a8..72c21e0e 100644 --- a/app/js/tg-repeat.js +++ b/app/js/tg-repeat.js @@ -13,6 +13,16 @@ if (immutable_collection.toJS) { collection = immutable_collection.toJS(); } + $scope[aliasAs] = collection; -> $scope[aliasAs] = immutable_collection; + +value = collection[key]; +immutable_value = immutable_collection.get(key); #x2 + + +updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); +-> (x2) +updateScope(block.scope, index, valueIdentifier, immutable_value, keyIdentifier, key, collectionLength); + --copy from angular copy angular hashKey copy angular createMap @@ -205,7 +215,7 @@ nextBlockOrder, elementsToRemove; if (aliasAs) { - $scope[aliasAs] = collection; + $scope[aliasAs] = immutable_collection; } if (isArrayLike(collection)) { collectionKeys = collection; @@ -226,6 +236,7 @@ for (index = 0; index < collectionLength; index++) { key = (collection === collectionKeys) ? index : collectionKeys[index]; value = collection[key]; + immutable_value = immutable_collection.get(key); trackById = trackByIdFn(key, value, index); if (lastBlockMap[trackById]) { // found previously seen block @@ -265,6 +276,7 @@ for (index = 0; index < collectionLength; index++) { key = (collection === collectionKeys) ? index : collectionKeys[index]; value = collection[key]; + immutable_value = immutable_collection.get(key); block = nextBlockOrder[index]; if (block.scope) { // if we have already seen this object, then we need to reuse the @@ -279,7 +291,7 @@ $animate.move(getBlockNodes(block.clone), null, jqLite(previousNode)); } previousNode = getBlockEnd(block); - updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); + updateScope(block.scope, index, valueIdentifier, immutable_value, keyIdentifier, key, collectionLength); } else { // new item which we don't know about $transclude(function ngRepeatTransclude(clone, scope) { @@ -295,7 +307,7 @@ // by a directive with templateUrl when its template arrives. block.clone = clone; nextBlockMap[block.id] = block; - updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); + updateScope(block.scope, index, valueIdentifier, immutable_value, keyIdentifier, key, collectionLength); }); } } diff --git a/app/modules/home/duties/duty.directive.coffee b/app/modules/home/duties/duty.directive.coffee index 0cb45456..d6eae86f 100644 --- a/app/modules/home/duties/duty.directive.coffee +++ b/app/modules/home/duties/duty.directive.coffee @@ -1,18 +1,18 @@ -DutyDirective = (navurls, projectsService, $translate) -> +DutyDirective = (navurls, $translate) -> link = (scope, el, attrs, ctrl) -> scope.vm = {} scope.vm.duty = scope.duty scope.vm.getDutyType = () -> if scope.vm.duty - if scope.vm.duty._name == "userstories" + if scope.vm.duty.get('_name') == "userstories" return $translate.instant("COMMON.USER_STORY") - if scope.vm.duty._name == "tasks" + if scope.vm.duty.get('_name') == "tasks" return $translate.instant("COMMON.TASK") - if scope.vm.duty._name == "issues" + if scope.vm.duty.get('_name') == "issues" return $translate.instant("COMMON.ISSUE") - directive = { + return { templateUrl: "home/duties/duty.html" scope: { "duty": "=tgDuty" @@ -20,11 +20,8 @@ DutyDirective = (navurls, projectsService, $translate) -> link: link } - return directive - DutyDirective.$inject = [ "$tgNavUrls", - "tgProjectsService", "$translate" ] diff --git a/app/modules/home/duties/duty.directive.spec.coffee b/app/modules/home/duties/duty.directive.spec.coffee index c8dbb867..d8a81aad 100644 --- a/app/modules/home/duties/duty.directive.spec.coffee +++ b/app/modules/home/duties/duty.directive.spec.coffee @@ -48,7 +48,7 @@ describe "dutyDirective", () -> compile = $compile it "duty directive scope content", () -> - scope.duty = { + scope.duty = Immutable.fromJS({ project: 1 ref: 1 _name: "userstories" @@ -56,7 +56,7 @@ describe "dutyDirective", () -> photo: "http://jstesting.taiga.io/photo" full_name_display: "Taiga testing js" } - } + }) mockTgProjectsService.projectsById.get .withArgs("1") @@ -72,4 +72,5 @@ describe "dutyDirective", () -> elm = createDirective() scope.$apply() + expect(elm.isolateScope().vm.getDutyType()).to.be.equal("User story translated") diff --git a/app/modules/home/duties/duty.jade b/app/modules/home/duties/duty.jade index b1b94e8a..6bd557d1 100644 --- a/app/modules/home/duties/duty.jade +++ b/app/modules/home/duties/duty.jade @@ -1,13 +1,13 @@ -a(href="{{ ::vm.duty.url }}", title="{{ ::duty.subject }}") +a(href="{{ ::vm.duty.get('url') }}", title="{{ ::duty.get('subject') }}") img.avatar( - src="{{ ::vm.duty.assigned_to_extra_info.photo }}" - title="{{ ::vm.duty.assigned_to_extra_info.full_name_display }}") + src="{{ ::vm.duty.get('assigned_to_extra_info').get('photo') }}" + title="{{ ::vm.duty.get('assigned_to_extra_info').get('full_name_display') }}") div.duty-data div span.duty-type {{ ::vm.getDutyType() }} - span.duty-status(ng-style="{'color': vm.duty.status_extra_info.color}") {{ ::vm.duty.status_extra_info.name }} + span.duty-status(ng-style="{'color': vm.duty.get('status_extra_info').get('color')}") {{ ::vm.duty.get('status_extra_info').get('name') }} span.duty-title - span.duty-id(tg-bo-ref="duty.ref") - span.duty-name {{ ::duty.subject }} - div.duty-project {{ ::vm.duty.projectName}} + span.duty-id(tg-bo-ref="duty.get('ref')") + span.duty-name {{ ::duty.get('subject') }} + div.duty-project {{ ::vm.duty.get('projectName')}} diff --git a/app/modules/home/home.jade b/app/modules/home/home.jade index 40f04c9d..c73a751b 100644 --- a/app/modules/home/home.jade +++ b/app/modules/home/home.jade @@ -13,7 +13,7 @@ div.home-wrapper.centered include ../../svg/hide.svg p(translate="HOME.EMPTY_WATCHING") - section.watching(ng-show="vm.watching") + section.watching(ng-show="vm.watching.size") div.duty-single(tg-duty="duty", tg-repeat="duty in vm.watching", ng-class="{blocked: duty.is_blocked}") aside.project-list(tg-home-project-list) diff --git a/app/modules/home/home.service.coffee b/app/modules/home/home.service.coffee index 0d1615ba..36207c45 100644 --- a/app/modules/home/home.service.coffee +++ b/app/modules/home/home.service.coffee @@ -1,5 +1,5 @@ class HomeService extends taiga.Service - @.$inject = ["$q", "$tgNavUrls", "$tgResources", "$rootScope", "$projectUrl", "$tgAuth"] + @.$inject = ["$q", "$tgNavUrls", "tgResources", "$rootScope", "$projectUrl", "$tgAuth"] constructor: (@q, @navurls, @rs, @rootScope, @projectUrl, @auth) -> @._workInProgress = Immutable.Map() @@ -11,27 +11,64 @@ class HomeService extends taiga.Service @.fetchWorkInProgress() attachProjectInfoToWorkInProgress: (projectsById) -> - _attachProjectInfoToDuty = (duty) => - project = projectsById.get(String(duty.project)) + _attachProjectInfoToDuty = (duty, objType) => + project = projectsById.get(String(duty.get('project'))) ctx = { - project: project.slug - ref: duty.ref + project: project.get('slug') + ref: duty.get('ref') } - duty.url = @navurls.resolve("project-#{duty._name}-detail", ctx) - duty.projectName = project.name + + url = @navurls.resolve("project-#{objType}-detail", ctx) + + duty = duty.set('url', url) + duty = duty.set('projectName', project.get('name')) + duty = duty.set("_name", objType) + return duty - @._workInProgress = Immutable.fromJS({ - assignedTo: { - userStories: _.map(_.clone(@.assignedToUserStories), _attachProjectInfoToDuty) - tasks: _.map(_.clone(@.assignedToTasks), _attachProjectInfoToDuty) - issues: _.map(_.clone(@.assignedToIssues), _attachProjectInfoToDuty) - } - watching: { - userStories: _.map(_.clone(@.watchingUserStories), _attachProjectInfoToDuty) - tasks: _.map(_.clone(@.watchingTasks), _attachProjectInfoToDuty) - issues: _.map(_.clone(@.watchingIssues), _attachProjectInfoToDuty) - } + assignedTo = Immutable.Map() + + if @.assignedToUserStories + _duties = @.assignedToUserStories.map (duty) -> + return _attachProjectInfoToDuty(duty, "userstories") + + assignedTo = assignedTo.set("userStories", _duties) + + if @.assignedToTasks + _duties = @.assignedToTasks.map (duty) -> + return _attachProjectInfoToDuty(duty, "tasks") + + assignedTo = assignedTo.set("tasks", _duties) + + if @.assignedToIssues + _duties = @.assignedToIssues.map (duty) -> + return _attachProjectInfoToDuty(duty, "issues") + + assignedTo = assignedTo.set("issues", _duties) + + watching = Immutable.Map() + + if @.watchingUserStories + _duties = @.watchingUserStories.map (duty) -> + return _attachProjectInfoToDuty(duty, "userstories") + + watching = watching.set("userStories", _duties) + + if @.watchingTasks + _duties = @.watchingTasks.map (duty) -> + return _attachProjectInfoToDuty(duty, "tasks") + + watching = watching.set("tasks", _duties) + + if @.watchingIssues + _duties = @.watchingIssues.map (duty) -> + return _attachProjectInfoToDuty(duty, "issues") + + watching = watching.set("issues", _duties) + + @._workInProgress = Immutable.Map({ + assignedTo: assignedTo, + watching: watching }) getWorkInProgress: () -> diff --git a/app/modules/home/home.service.spec.coffee b/app/modules/home/home.service.spec.coffee index 3803a6b4..167c5a61 100644 --- a/app/modules/home/home.service.spec.coffee +++ b/app/modules/home/home.service.spec.coffee @@ -58,7 +58,7 @@ describe "tgHome", -> then: mocks.thenStubWatchingIssues }) - provide.value "$tgResources", mocks.resources + provide.value "tgResources", mocks.resources _mockProjectUrl = () -> mocks.projectUrl = {get: sinon.stub()} @@ -83,7 +83,7 @@ describe "tgHome", -> provide.value "$tgNavUrls", mocks.tgNavUrls _inject = (callback) -> - inject (_$q_, _$tgResources_, _$rootScope_, _$projectUrl_, _$timeout_, _tgHomeService_) -> + inject (_$timeout_, _tgHomeService_) -> timeout = _$timeout_ homeService = _tgHomeService_ callback() if callback @@ -107,12 +107,12 @@ describe "tgHome", -> describe "fetch items", -> it "work in progress filled", () -> - mocks.thenStubAssignedToUserstories.callArg(0, [{"id": 1}]) - mocks.thenStubAssignedToTasks.callArg(0, [{"id": 2}]) - mocks.thenStubAssignedToIssues.callArg(0, [{"id": 3}]) - mocks.thenStubWatchingUserstories.callArg(0, [{"id": 4}]) - mocks.thenStubWatchingTasks.callArg(0, [{"id": 5}]) - mocks.thenStubWatchingIssues.callArg(0, [{"id": 6}]) + mocks.thenStubAssignedToUserstories.callArg(0, Immutable.fromJS([{"id": 1}])) + mocks.thenStubAssignedToTasks.callArg(0, Immutable.fromJS([{"id": 2}])) + mocks.thenStubAssignedToIssues.callArg(0, Immutable.fromJS([{"id": 3}])) + mocks.thenStubWatchingUserstories.callArg(0, Immutable.fromJS([{"id": 4}])) + mocks.thenStubWatchingTasks.callArg(0, Immutable.fromJS([{"id": 5}])) + mocks.thenStubWatchingIssues.callArg(0, Immutable.fromJS([{"id": 6}])) timeout.flush() expect(homeService.workInProgress.toJS()).to.be.eql({ @@ -136,23 +136,23 @@ describe "tgHome", -> it "project info filled", () -> duty = { id: 66 - _name: "userstories" ref: 123 project: 1 } - mocks.thenStubAssignedToUserstories.callArg(0, [duty]) - mocks.thenStubAssignedToTasks.callArg(0) - mocks.thenStubAssignedToIssues.callArg(0) - mocks.thenStubWatchingUserstories.callArg(0) - mocks.thenStubWatchingTasks.callArg(0) - mocks.thenStubWatchingIssues.callArg(0) + + mocks.thenStubAssignedToUserstories.callArg(0, Immutable.fromJS([duty])) + mocks.thenStubAssignedToTasks.callArg(0, Immutable.fromJS([])) + mocks.thenStubAssignedToIssues.callArg(0, Immutable.fromJS([])) + mocks.thenStubWatchingUserstories.callArg(0, Immutable.fromJS([])) + mocks.thenStubWatchingTasks.callArg(0, Immutable.fromJS([])) + mocks.thenStubWatchingIssues.callArg(0, Immutable.fromJS([])) timeout.flush() projectsById = { - get: () -> { + get: () -> Immutable.fromJS({ name: "Testing project" slug: "testing-project" - } + }) } mocks.tgNavUrls.resolve @@ -160,6 +160,7 @@ describe "tgHome", -> .returns("/testing-project/us/123") homeService.attachProjectInfoToWorkInProgress(projectsById) + expect(homeService.workInProgress.toJS()).to.be.eql({ assignedTo: { userStories: [ diff --git a/app/modules/home/projects/home-project-list.jade b/app/modules/home/projects/home-project-list.jade index 5fadc4d1..e9ebb4cf 100644 --- a/app/modules/home/projects/home-project-list.jade +++ b/app/modules/home/projects/home-project-list.jade @@ -1,12 +1,12 @@ ul.home-project-list(ng-show="vm.projects.size") li.home-project-list-single(tg-bind-scope, tg-repeat="project in vm.projects") - a(href="#", tg-nav="project:project=project.slug") + a(href="#", tg-nav="project:project=project.get('slug')") h2.home-project-list-single-title - span.project-name(ng-bind="::project.name", title="{{ ::project.name }}") - span.private(ng-if="project.is_private", title="{{'PROJECT.PRIVATE' | translate}}") + span.project-name(title="{{ ::project.get('name') }}") {{::project.get('name')}} + span.private(ng-if="project.get('is_private')", title="{{'PROJECT.PRIVATE' | translate}}") include ../../../svg/lock.svg - p {{ ::project.description | limitTo:150 }} - span(ng-if="::project.description.length > 150") ... + p {{ ::project.get('description') | limitTo:150 }} + span(ng-if="::project.get('description').size > 150") ... a.see-more-projects-btn.button-gray(ng-show="vm.projects.size", href="#", tg-nav="projects", title="{{'PROJECT.NAVIGATION.SEE_MORE_PROJECTS' | translate}}", translate="PROJECT.NAVIGATION.SEE_MORE_PROJECTS") section.projects-empty(ng-hide="vm.projects.size") include ../../../svg/empty-project.svg diff --git a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade index 12cdf753..9a1b78ff 100644 --- a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade +++ b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade @@ -5,8 +5,7 @@ div.navbar-dropdown.dropdown-project-list ul a(href="#", tg-repeat="project in vm.projects", - ng-bind="::project.name" - tg-nav="project:project=project.slug") + tg-nav="project:project=project.get('slug')") {{::project.get("name")}} a.see-more-projects-btn.button-gray( href="#", diff --git a/app/modules/profile/profile-bar/profile-bar.controller.coffee b/app/modules/profile/profile-bar/profile-bar.controller.coffee index b9ea7323..ba41bd15 100644 --- a/app/modules/profile/profile-bar/profile-bar.controller.coffee +++ b/app/modules/profile/profile-bar/profile-bar.controller.coffee @@ -11,6 +11,6 @@ class ProfileBarController loadStats: () -> return @userService.getStats(@.user.id).then (stats) => - @.stats = stats.toJS() + @.stats = stats angular.module("taigaProfile").controller("ProfileBar", ProfileBarController) diff --git a/app/modules/profile/profile-bar/profile-bar.controller.spec.coffee b/app/modules/profile/profile-bar/profile-bar.controller.spec.coffee index 4a37e0ee..9d2b7067 100644 --- a/app/modules/profile/profile-bar/profile-bar.controller.spec.coffee +++ b/app/modules/profile/profile-bar/profile-bar.controller.spec.coffee @@ -58,4 +58,4 @@ describe "ProfileBar", -> $rootScope.$apply() - expect(ctrl.stats).to.be.eql(stats.toJS()) + expect(ctrl.stats.toJS()).to.be.eql(stats.toJS()) diff --git a/app/modules/profile/profile-bar/profile-bar.jade b/app/modules/profile/profile-bar/profile-bar.jade index 640d3fb7..2c9c38a3 100644 --- a/app/modules/profile/profile-bar/profile-bar.jade +++ b/app/modules/profile/profile-bar/profile-bar.jade @@ -9,7 +9,7 @@ section.profile-bar // span(translate="USER.PROFILE.FOLLOW") div.profile-data h1 {{::vm.user.full_name}} - h2 {{::vm.stats.roles.join(", ")}} + h2 {{::vm.stats.get('roles').join(", ")}} // div.location // include ../../../svg/location.svg // span Madrid @@ -19,13 +19,13 @@ section.profile-bar // These values in profile stats are not defined yet in UX. Please ask div.profile-stats div.stat - span.stat-number {{::vm.stats.projects}} + span.stat-number {{::vm.stats.get('projects')}} span.stat-name(translate="USER.PROFILE.PROJECTS") div.stat - span.stat-number {{::vm.stats.closed_userstories}} + span.stat-number {{::vm.stats.get('closed_userstories')}} span.stat-name(translate="USER.PROFILE.CLOSED_US") div.stat - span.stat-number {{::vm.stats.contacts}} + span.stat-number {{::vm.stats.get('contacts')}} span.stat-name(translate="USER.PROFILE.CONTACTS") // TODO Hide until organizations come // div.profile-organizations @@ -36,5 +36,5 @@ section.profile-bar // div.organization // div.organization - div.profile-quote(ng-if="vm.user.bio") + div.profile-quote(ng-if="::vm.user.bio") span {{::vm.user.bio}} diff --git a/app/modules/profile/profile-contacts/profile-contacts.jade b/app/modules/profile/profile-contacts/profile-contacts.jade index e81fef75..bb57baa2 100644 --- a/app/modules/profile/profile-contacts/profile-contacts.jade +++ b/app/modules/profile/profile-contacts/profile-contacts.jade @@ -7,19 +7,19 @@ section.profile-contacts div.profile-contact-single(tg-repeat="contact in ::vm.contacts") div.profile-contact-picture - a(tg-nav="user-profile:username=contact.username", title="{{::contact.name }}") - img(ng-src='{{::contact.photo}}', alt='{{::contact.full_name}}') + 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.profile-contact-data h1 - a(tg-nav="user-profile:username=contact.username", title="{{::contact.name }}") - | {{::contact.full_name}} + a(tg-nav="user-profile:username=contact.get('username')", title="{{::contact.get('name') }}") + | {{::contact.get('full_name')}} // span.your-contact Your contact - p(ng-if="contact.bio") {{::contact.bio}} + p(ng-if="contact.bio") {{::contact.get('bio')}} div.extra-info - span.position {{::contact.roles.join(", ")}} + span.position {{::contact.get('roles').join(", ")}} // span.location todo // div.profile-project-stats // div.stat-projects(title="2 projects") diff --git a/app/modules/profile/profile-projects/profile-projects.jade b/app/modules/profile/profile-projects/profile-projects.jade index b7a98cec..10774900 100644 --- a/app/modules/profile/profile-projects/profile-projects.jade +++ b/app/modules/profile/profile-projects/profile-projects.jade @@ -4,16 +4,16 @@ section.profile-projects div.project-list-single-title h1 - a(href="#", tg-nav="project:project=project.slug", title="{{ ::project.name }}") {{::project.name}} - p {{ ::project.description | limitTo:300 }} + 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 - span.tag(style='border-left: 5px solid {{::tag.color}};', ng-repeat="tag in ::project.colorized_tags") - span.tag-name {{::tag.name}} + 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-members - a(ng-repeat="contact in ::project.contacts", tg-nav="user-profile:username=contact.username", title="{{::contact.full_name}}") - img(ng-src="{{::contact.photo}}") + 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 diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.coffee b/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.coffee index 8145dd74..f17bcfeb 100644 --- a/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.coffee +++ b/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.coffee @@ -6,7 +6,7 @@ class ProfileTimelineItemController ] constructor: (@sce, @profileTimelineItemType, @profileTimelineItemTitle) -> - timeline = @.timeline + timeline = @.timeline.toJS() event = @.parseEventType(timeline.event_type) type = @profileTimelineItemType.getType(timeline, event) diff --git a/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.spec.coffee b/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.spec.coffee index 32edcf26..7b127951 100644 --- a/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.spec.coffee +++ b/app/modules/profile/profile-timeline-item/profile-timeline-item.controller.spec.coffee @@ -71,8 +71,9 @@ describe "ProfileTimelineItemController", -> it "basic activity fields filled", () -> timeline = scope.vm.timeline + timeline_immutable = Immutable.fromJS(timeline) - myCtrl = controller("ProfileTimelineItem", {$scope: scope}, {timeline: timeline}) + myCtrl = controller("ProfileTimelineItem", {$scope: scope}, {timeline: timeline_immutable}) expect(myCtrl.activity.user).to.be.equal(timeline.data.user) expect(myCtrl.activity.project).to.be.equal(timeline.data.project) @@ -94,7 +95,9 @@ describe "ProfileTimelineItemController", -> mockType.description.withArgs(timeline).returns(description) mockType.member.withArgs(timeline).returns(member) - myCtrl = controller("ProfileTimelineItem", {$scope: scope}, {timeline: timeline}) + timeline_immutable = Immutable.fromJS(timeline) + + myCtrl = controller("ProfileTimelineItem", {$scope: scope}, {timeline: timeline_immutable}) expect(myCtrl.activity.description).to.be.an('object') # $sce.trustAsHtml expect(myCtrl.activity.member).to.be.equal(member) diff --git a/app/modules/projects/listing/projects-listing.directive.coffee b/app/modules/projects/listing/projects-listing.directive.coffee index 1ae889f8..61e7ccab 100644 --- a/app/modules/projects/listing/projects-listing.directive.coffee +++ b/app/modules/projects/listing/projects-listing.directive.coffee @@ -19,7 +19,7 @@ ProjectsListingDirective = (projectsService) -> sorted_project_ids = _.map(scope.vm.projects.toArray(), (p) -> p.id) sorted_project_ids = _.without(sorted_project_ids, project.id) - sorted_project_ids.splice(index, 0, project.id) + sorted_project_ids.splice(index, 0, project.get('id')) sortData = [] for value, index in sorted_project_ids sortData.push({"project_id": value, "order":index}) diff --git a/app/modules/projects/listing/projects-listing.jade b/app/modules/projects/listing/projects-listing.jade index 452a7009..2dfaa4c7 100644 --- a/app/modules/projects/listing/projects-listing.jade +++ b/app/modules/projects/listing/projects-listing.jade @@ -15,15 +15,15 @@ div.project-list-wrapper.centered div.project-list-single-left div.project-list-single-title h1 - a(href="#", tg-nav="project:project=project.slug") - h1.project-name(ng-bind="::project.name", title="{{ ::project.name }}") - span.private(ng-if="project.is_private", title="{{'PROJECT.PRIVATE' | translate}}") + a(href="#", tg-nav="project:project=project.get('slug')") + h1.project-name(title="{{ ::project.get('name') }}") {{project.get('name')}} + span.private(ng-if="project.get('is_private')", title="{{'PROJECT.PRIVATE' | translate}}") include ../../../svg/lock.svg - p {{ ::project.description | limitTo:300 }} - span(ng-if="::project.description.length > 300") ... + p {{ ::project.get('description') | limitTo:300 }} + span(ng-if="::project.get('description').length > 300") ... - div.project-list-single-tags.tags-container(ng-if="::project.tags") - div.tags-block(tg-colorize-tags="project.tags", tg-colorize-tags-type="backlog") + div.project-list-single-tags.tags-container(ng-if="::project.get('tags').size") + div.tags-block(tg-colorize-tags="project.get('tags')", tg-colorize-tags-type="backlog") div.project-list-single-right span.drag.icon.icon-drag-v diff --git a/app/modules/projects/project/project-page.jade b/app/modules/projects/project/project-page.jade new file mode 100644 index 00000000..d86e8356 --- /dev/null +++ b/app/modules/projects/project/project-page.jade @@ -0,0 +1 @@ +div(tg-project) diff --git a/app/modules/projects/project/project.controller.coffee b/app/modules/projects/project/project.controller.coffee new file mode 100644 index 00000000..098b0727 --- /dev/null +++ b/app/modules/projects/project/project.controller.coffee @@ -0,0 +1,17 @@ +class ProjectController + @.$inject = [ + "tgProjectsService", + "$routeParams", + "$appTitle" + ] + + constructor: (@projectsService, @routeParams, @appTitle) -> + projectSlug = @routeParams.pslug + + @projectsService.getProjectBySlug(projectSlug) + .then (project) => + @appTitle.set(project.get("name")) + + @.project = project + +angular.module("taigaProjects").controller("Project", ProjectController) diff --git a/app/modules/projects/project/project.controller.spec.coffee b/app/modules/projects/project/project.controller.spec.coffee new file mode 100644 index 00000000..8e8b75c3 --- /dev/null +++ b/app/modules/projects/project/project.controller.spec.coffee @@ -0,0 +1,84 @@ +describe "ProfileBar", -> + $controller = null + $q = null + provide = null + $rootScope = null + mocks = {} + + _mockProjectsService = () -> + mocks.projectService = { + getProjectBySlug: sinon.stub() + } + + provide.value "tgProjectsService", mocks.projectService + + _mockAppTitle = () -> + mocks.appTitle = { + set: sinon.stub() + } + + provide.value "$appTitle", mocks.appTitle + + _mockRouteParams = () -> + provide.value "$routeParams", { + pslug: "project-slug" + } + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockProjectsService() + _mockRouteParams() + _mockAppTitle() + + return null + + _inject = (callback) -> + inject (_$controller_, _$q_, _$rootScope_) -> + $q = _$q_ + $rootScope = _$rootScope_ + $controller = _$controller_ + + beforeEach -> + module "taigaProjects" + _mocks() + _inject() + + it "set page title", () -> + project = Immutable.fromJS({ + name: "projectName" + }) + + thenStub = sinon.stub() + + mocks.projectService.getProjectBySlug.withArgs("project-slug").returns({ + then: thenStub + }) + + ctrl = $controller("Project") + + thenStub.callArg(0, project) + + $rootScope.$apply() + + expect(mocks.appTitle.set.withArgs("projectName")).to.be.calledOnce + + + it "set local project variable", () -> + project = Immutable.fromJS({ + name: "projectName" + }) + + thenStub = sinon.stub() + + mocks.projectService.getProjectBySlug.withArgs("project-slug").returns({ + then: thenStub + }) + + ctrl = $controller("Project") + + thenStub.callArg(0, project) + + $rootScope.$apply() + + expect(ctrl.project).to.be.equal(project) diff --git a/app/modules/projects/project/project.directive.coffee b/app/modules/projects/project/project.directive.coffee new file mode 100644 index 00000000..e3dd8298 --- /dev/null +++ b/app/modules/projects/project/project.directive.coffee @@ -0,0 +1,8 @@ +ProjectDirective = () -> + return { + templateUrl: "projects/project/project.html", + controllerAs: "vm", + controller: "Project" + } + +angular.module("taigaProjects").directive("tgProject", ProjectDirective) diff --git a/app/modules/projects/project/project.jade b/app/modules/projects/project/project.jade new file mode 100644 index 00000000..339d0880 --- /dev/null +++ b/app/modules/projects/project/project.jade @@ -0,0 +1,24 @@ +div.wrapper + div.main.centered.single-project + section.single-project-intro + h1 + span.green(class="project-name") {{::vm.project.get("name")}} + span.private(ng-if="::vm.project.get('is_private')", title="{{'PROJECT.PRIVATE' | translate}}") + include ../../../svg/lock.svg + p.description {{vm.project.get('description')}} + div.project-list-single-tags.tags-container(ng-if="::vm.project.get('tags').size") + div.tags-block(tg-colorize-tags="vm.project.get('tags')", tg-colorize-tags-type="backlog") + + div.project-data + section.timeline + span TODO. Missing the amazing timeline around!!dfdfdf + section.involved-data + h2.title Team + ul.involved-team + a(href="", title="{{::member.get('full_name')}}", tg-repeat="member in ::vm.project.get('memberships')") + img(ng-src="{{::member.get('photo')}}", alt="{{::member.get('full_name')}}") + + // h2.title Organizations + // div.involved-organization + // a(href="", title="User Name") + // img(src="https://s3.amazonaws.com/uifaces/faces/twitter/dan_higham/48.jpg", alt="{{member.full_name}}") diff --git a/app/modules/projects/projects.service.coffee b/app/modules/projects/projects.service.coffee index e02191c0..ff1c4e40 100644 --- a/app/modules/projects/projects.service.coffee +++ b/app/modules/projects/projects.service.coffee @@ -2,7 +2,7 @@ taiga = @.taiga groupBy = @.taiga.groupBy class ProjectsService extends taiga.Service - @.$inject = ["$tgResources", "$rootScope", "$projectUrl", "tgLightboxFactory"] + @.$inject = ["tgResources", "$rootScope", "$projectUrl", "tgLightboxFactory"] constructor: (@rs, @rootScope, @projectUrl, @lightboxFactory) -> @._currentUserProjects = Immutable.Map() @@ -18,30 +18,40 @@ class ProjectsService extends taiga.Service getCurrentUserProjects: -> return @._currentUserProjectsPromise + getProjectBySlug: (projectSlug) -> + return @rs.projects.getProjectBySlug(projectSlug) + + getProjectStats: (projectId) -> + return @rs.projects.getProjectStats(projectId) + fetchProjects: -> if not @._inProgress @._inProgress = true - @._currentUserProjectsPromise = @rs.projects.listByMember(@rootScope.user?.id) + @._currentUserProjectsPromise = @rs.users.getProjects(@rootScope.user?.id) @._currentUserProjectsPromise.then (projects) => - _.map projects, (project) => - project.url = @projectUrl.get(project) + projects = projects.map (project) => + url = @projectUrl.get(project.toJS()) - project.colorized_tags = [] + project = project.set("url", url) + colorized_tags = [] - if project.tags - tags = project.tags.sort() + if project.get("tags") + tags = project.get("tags").sort() - project.colorized_tags = _.map tags, (tag) -> - color = project.tags_colors[tag] + colorized_tags = tags.map (tag) -> + color = project.get("tags_colors").get(tag) return {name: tag, color: color} - @._currentUserProjects = Immutable.fromJS({ - all: projects, - recents: projects.slice(0, 10) - }) + project = project.set("colorized_tags", colorized_tags) - @._currentUserProjectsById = Immutable.fromJS(groupBy(projects, (p) -> p.id)) + return project + + + @._currentUserProjects = @._currentUserProjects.set("all", projects) + @._currentUserProjects = @._currentUserProjects.set("recents", projects.slice(0, 10)) + + @._currentUserProjectsById = Immutable.fromJS(groupBy(projects.toJS(), (p) -> p.id)) return @.projects diff --git a/app/modules/projects/projects.service.spec.coffee b/app/modules/projects/projects.service.spec.coffee index 0ba28182..0bc3eb70 100644 --- a/app/modules/projects/projects.service.spec.coffee +++ b/app/modules/projects/projects.service.spec.coffee @@ -5,18 +5,19 @@ describe "tgProjects", -> _mockResources = () -> mocks.resources = {} - mocks.resources.projects = { - listByMember: sinon.stub() + mocks.resources.users = { + getProjects: sinon.stub() } mocks.thenStub = sinon.stub() mocks.finallyStub = sinon.stub() - mocks.resources.projects.listByMember.withArgs(10).returns({ + + mocks.resources.users.getProjects.withArgs(10).returns({ then: mocks.thenStub finally: mocks.finallyStub }) - provide.value "$tgResources", mocks.resources + provide.value "tgResources", mocks.resources _mockRootScope = () -> provide.value "$rootScope", {user: {id: 10}} @@ -64,22 +65,22 @@ describe "tgProjects", -> beforeEach -> projects = [ - {"id": 1, tags: ['xx', 'yy', 'aa'], tags_colors: {xx: "red", yy: "blue", aa: "white"}}, - {"id": 2}, - {"id": 3}, - {"id": 4}, - {"id": 5}, - {"id": 6}, - {"id": 7}, - {"id": 8}, - {"id": 9}, - {"id": 10}, - {"id": 11}, - {"id": 12}, + {"id": 1, url: 'url-1', tags: ['xx', 'yy', 'aa'], tags_colors: {xx: "red", yy: "blue", aa: "white"}, colorized_tags: [{name: 'aa', color: 'white'}, {name: 'xx', color: 'red'}, {name: 'yy', color: 'blue'}]}, + {"id": 2, url: 'url-2'}, + {"id": 3, url: 'url-3'}, + {"id": 4, url: 'url-4'}, + {"id": 5, url: 'url-5'}, + {"id": 6, url: 'url-6'}, + {"id": 7, url: 'url-7'}, + {"id": 8, url: 'url-8'}, + {"id": 9, url: 'url-9'}, + {"id": 10, url: 'url-10'}, + {"id": 11, url: 'url-11'}, + {"id": 12, url: 'url-12'} ] it "all & recents filled", () -> - mocks.thenStub.callArg(0, projects) + mocks.thenStub.callArg(0, Immutable.fromJS(projects)) expect(projectsService.currentUserProjects.get("all").toJS()).to.be.eql(projects) expect(projectsService.currentUserProjects.get("recents").toJS()).to.be.eql(projects.slice(0, 10)) @@ -87,7 +88,7 @@ describe "tgProjects", -> it "_inProgress change to false when tgResources end", () -> expect(projectsService._inProgress).to.be.true - mocks.thenStub.callArg(0, projects) + mocks.thenStub.callArg(0, Immutable.fromJS(projects)) mocks.finallyStub.callArg(0) expect(projectsService._inProgress).to.be.false @@ -96,25 +97,25 @@ describe "tgProjects", -> projectsService.fetchProjects() projectsService.fetchProjects() - mocks.thenStub.callArg(0, projects) + mocks.thenStub.callArg(0, Immutable.fromJS(projects)) - expect(mocks.resources.projects.listByMember).have.been.calledOnce + expect(mocks.resources.users.getProjects).have.been.calledOnce it "group projects by id", () -> - mocks.thenStub.callArg(0, projects) + mocks.thenStub.callArg(0, Immutable.fromJS(projects)) expect(projectsService.currentUserProjectsById.size).to.be.equal(12) expect(projectsService.currentUserProjectsById.toJS()[1].id).to.be.equal(projects[0].id) it "add urls in the project object", () -> - mocks.thenStub.callArg(0, projects) + mocks.thenStub.callArg(0, Immutable.fromJS(projects)) expect(projectsService.currentUserProjectsById.toJS()[1].url).to.be.equal("url-1") expect(projectsService.currentUserProjects.get("all").toJS()[0].url).to.be.equal("url-1") expect(projectsService.currentUserProjects.get("recents").toJS()[0].url).to.be.equal("url-1") it "add sorted colorized_tags project object", () -> - mocks.thenStub.callArg(0, projects) + mocks.thenStub.callArg(0, Immutable.fromJS(projects)) tags = [ {name: "aa", color: "white"}, @@ -152,6 +153,7 @@ describe "tgProjects", -> thenStub = sinon.stub() + mocks.resources.projects = {} mocks.resources.projects.bulkUpdateOrder = sinon.stub() mocks.resources.projects.bulkUpdateOrder.withArgs(projects_order).returns({ then: thenStub @@ -164,3 +166,21 @@ describe "tgProjects", -> thenStub.callArg(0) expect(projectsService.fetchProjects).to.have.been.calledOnce + + it "getProjectBySlug", () -> + projectSlug = "project-slug" + + mocks.resources.projects = {} + mocks.resources.projects.getProjectBySlug = sinon.stub() + mocks.resources.projects.getProjectBySlug.withArgs(projectSlug).returns(true) + + expect(projectsService.getProjectBySlug(projectSlug)).to.be.true + + it "getProjectStats", () -> + projectId = 3 + + mocks.resources.projects = {} + mocks.resources.projects.getProjectStats = sinon.stub() + mocks.resources.projects.getProjectStats.withArgs(projectId).returns(true) + + expect(projectsService.getProjectStats(projectId)).to.be.true diff --git a/app/modules/resources/issues-resource.service.coffee b/app/modules/resources/issues-resource.service.coffee new file mode 100644 index 00000000..37213c60 --- /dev/null +++ b/app/modules/resources/issues-resource.service.coffee @@ -0,0 +1,23 @@ +Resource = (urlsService, http) -> + service = {} + + service.listInAllProjects = (params) -> + url = urlsService.resolve("issues") + + httpOptions = { + headers: { + "x-disable-pagination": "1" + } + } + + return http.get(url, params, httpOptions) + .then (result) -> + return Immutable.fromJS(result.data) + + return () -> + return {"issues": service} + +Resource.$inject = ["$tgUrls", "$tgHttp"] + +module = angular.module("taigaResources2") +module.factory("tgIssuesResource", Resource) diff --git a/app/modules/resources/projects-resource.service.coffee b/app/modules/resources/projects-resource.service.coffee new file mode 100644 index 00000000..8c061b23 --- /dev/null +++ b/app/modules/resources/projects-resource.service.coffee @@ -0,0 +1,31 @@ +Resource = (urlsService, http) -> + service = {} + + service.getProjectBySlug = (projectSlug) -> + url = urlsService.resolve("projects") + + url = "#{url}/by_slug?slug=#{projectSlug}" + + return http.get(url) + .then (result) -> + return Immutable.fromJS(result.data) + + service.getProjectStats = (projectId) -> + url = urlsService.resolve("projects") + url = "#{url}/#{projectId}" + + return http.get(url) + .then (result) -> + return Immutable.fromJS(result.data) + + service.bulkUpdateOrder = (bulkData) -> + url = urlsService.resolve("bulk-update-projects-order") + return http.post(url, bulkData) + + return () -> + return {"projects": service} + +Resource.$inject = ["$tgUrls", "$tgHttp"] + +module = angular.module("taigaResources2") +module.factory("tgProjectsResources", Resource) diff --git a/app/modules/resources/resources.coffee b/app/modules/resources/resources.coffee index 197a95c7..b3fecee0 100644 --- a/app/modules/resources/resources.coffee +++ b/app/modules/resources/resources.coffee @@ -1,5 +1,9 @@ services = [ - "tgProjectsResources" + "tgProjectsResources", + "tgUsersResources", + "tgUserstoriesResource", + "tgTasksResource", + "tgIssuesResource" ] Resources = ($injector) -> diff --git a/app/modules/resources/tasks-resource.service.coffee b/app/modules/resources/tasks-resource.service.coffee new file mode 100644 index 00000000..d122cebf --- /dev/null +++ b/app/modules/resources/tasks-resource.service.coffee @@ -0,0 +1,23 @@ +Resource = (urlsService, http) -> + service = {} + + service.listInAllProjects = (params) -> + url = urlsService.resolve("tasks") + + httpOptions = { + headers: { + "x-disable-pagination": "1" + } + } + + return http.get(url, params, httpOptions) + .then (result) -> + return Immutable.fromJS(result.data) + + return () -> + return {"tasks": service} + +Resource.$inject = ["$tgUrls", "$tgHttp"] + +module = angular.module("taigaResources2") +module.factory("tgTasksResource", Resource) diff --git a/app/modules/resources/users-resource.service.coffee b/app/modules/resources/users-resource.service.coffee index 40abd63c..8587e7e1 100644 --- a/app/modules/resources/users-resource.service.coffee +++ b/app/modules/resources/users-resource.service.coffee @@ -43,4 +43,4 @@ Resource = (urlsService, http) -> Resource.$inject = ["$tgUrls", "$tgHttp"] module = angular.module("taigaResources2") -module.factory("tgProjectsResources", Resource) +module.factory("tgUsersResources", Resource) diff --git a/app/modules/resources/userstories-resource.service.coffee b/app/modules/resources/userstories-resource.service.coffee new file mode 100644 index 00000000..dccd35fd --- /dev/null +++ b/app/modules/resources/userstories-resource.service.coffee @@ -0,0 +1,23 @@ +Resource = (urlsService, http) -> + service = {} + + service.listInAllProjects = (params) -> + url = urlsService.resolve("userstories") + + httpOptions = { + headers: { + "x-disable-pagination": "1" + } + } + + return http.get(url, params, httpOptions) + .then (result) -> + return Immutable.fromJS(result.data) + + return () -> + return {"userstories": service} + +Resource.$inject = ["$tgUrls", "$tgHttp"] + +module = angular.module("taigaResources2") +module.factory("tgUserstoriesResource", Resource) diff --git a/karma.conf.js b/karma.conf.js index 3487e4cf..bc96834e 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -32,7 +32,7 @@ module.exports = function(config) { // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { - '**/*.coffee': ['coffee'], + '**/*.spec.coffee': ['coffee'], 'dist/js/app.js': ['sourcemap'] },