profile timeline with the new front architecture
parent
8f70c26165
commit
67fb2a6dd0
|
@ -140,7 +140,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
|
||||||
|
|
||||||
# User profile
|
# User profile
|
||||||
$routeProvider.when("/profile",
|
$routeProvider.when("/profile",
|
||||||
{templateUrl: "profile/public-profile.html"})
|
{templateUrl: "profile/profile.html"})
|
||||||
|
|
||||||
# Auth
|
# Auth
|
||||||
$routeProvider.when("/login",
|
$routeProvider.when("/login",
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
###
|
|
||||||
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
|
||||||
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
|
|
||||||
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as
|
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
|
||||||
# License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
# File: modules/team.coffee
|
|
||||||
###
|
|
||||||
|
|
||||||
module = angular.module("taigaProfile", [])
|
|
|
@ -1,24 +0,0 @@
|
||||||
###
|
|
||||||
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
|
||||||
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
|
|
||||||
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as
|
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
|
||||||
# License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
# File: modules/team/main.coffee
|
|
||||||
###
|
|
||||||
|
|
||||||
taiga = @.taiga
|
|
||||||
|
|
||||||
module = angular.module("taigaProfile")
|
|
|
@ -1,26 +0,0 @@
|
||||||
class ProfileTabsController
|
|
||||||
constructor: (@scope) ->
|
|
||||||
@scope.tabs = []
|
|
||||||
|
|
||||||
addTab: (tab) ->
|
|
||||||
@scope.tabs.push(tab)
|
|
||||||
|
|
||||||
ProfileTabsController.$inject = ["$scope"]
|
|
||||||
|
|
||||||
ProfileTabsDirective = () ->
|
|
||||||
link = ($scope, $el, $attrs) ->
|
|
||||||
$scope.toggleTab = (tab) ->
|
|
||||||
_.map $scope.tabs, (tab) => tab.active = false
|
|
||||||
|
|
||||||
tab.active = true
|
|
||||||
|
|
||||||
return {
|
|
||||||
scope: {}
|
|
||||||
controller: ProfileTabsController
|
|
||||||
templateUrl: "profile/profile-tabs.html"
|
|
||||||
link: link
|
|
||||||
transclude: true
|
|
||||||
}
|
|
||||||
|
|
||||||
angular.module("taigaProfile")
|
|
||||||
.directive("tgProfileTabs", ProfileTabsDirective)
|
|
|
@ -1,29 +0,0 @@
|
||||||
TimelineAttachmentDirective = ($tgTemplate, $compile) ->
|
|
||||||
validFileExtensions = [".jpg", ".jpeg", ".bmp", ".gif", ".png"]
|
|
||||||
|
|
||||||
isImage = (url) ->
|
|
||||||
url = url.toLowerCase()
|
|
||||||
|
|
||||||
return _.some validFileExtensions, (extension) =>
|
|
||||||
return url.indexOf(extension, url - extension.length) != -1
|
|
||||||
|
|
||||||
link = ($scope, $el, $attrs) ->
|
|
||||||
is_image = isImage($scope.attachment.url)
|
|
||||||
|
|
||||||
if is_image
|
|
||||||
template = $tgTemplate.get("profile/timeline/timeline-attachment-image.html")
|
|
||||||
else
|
|
||||||
template = $tgTemplate.get("profile/timeline/timeline-attachment.html")
|
|
||||||
|
|
||||||
$el.html(template)
|
|
||||||
$compile($el.contents())($scope)
|
|
||||||
|
|
||||||
return {
|
|
||||||
link: link
|
|
||||||
scope: {
|
|
||||||
attachment: "=tgTimelineAttachment"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
angular.module("taigaProfile")
|
|
||||||
.directive("tgTimelineAttachment", ["$tgTemplate", "$compile", TimelineAttachmentDirective])
|
|
|
@ -1,3 +1,3 @@
|
||||||
section.profile-timeline(ng-controller="ProfileTimeline as ctrl")
|
section.profile-timeline(ng-controller="ProfileTimeline as ctrl")
|
||||||
div(infinite-scroll="ctrl.loadTimeline()", infinite-scroll-distance="3", infinite-scroll-disabled='ctrl.loadingData')
|
div(infinite-scroll="ctrl.loadTimeline()", infinite-scroll-distance="3", infinite-scroll-disabled='ctrl.loadingData')
|
||||||
div(ng-repeat="timeline in ctrl.timelineList", tg-timeline-item="timeline")
|
div(ng-repeat="timeline in ctrl.timelineList", tg-profile-timeline-item="timeline")
|
|
@ -0,0 +1,14 @@
|
||||||
|
class ProfileTabsController
|
||||||
|
constructor: () ->
|
||||||
|
@tabs = []
|
||||||
|
|
||||||
|
addTab: (tab) ->
|
||||||
|
@tabs.push(tab)
|
||||||
|
|
||||||
|
toggleTab: (tab) ->
|
||||||
|
_.map @tabs, (tab) -> tab.active = false
|
||||||
|
|
||||||
|
tab.active = true
|
||||||
|
|
||||||
|
angular.module("taigaProfile")
|
||||||
|
.controller("ProfileTabs", ProfileTabsController)
|
|
@ -0,0 +1,41 @@
|
||||||
|
describe "ProfileTabsController", ->
|
||||||
|
myCtrl = scope = null
|
||||||
|
|
||||||
|
beforeEach ->
|
||||||
|
module "taigaProfile"
|
||||||
|
|
||||||
|
inject ($controller) ->
|
||||||
|
scope = {}
|
||||||
|
myCtrl = $controller "ProfileTabs",
|
||||||
|
$scope: scope
|
||||||
|
|
||||||
|
it "tabs should be an array", () ->
|
||||||
|
expect(myCtrl.tabs).is.an("array")
|
||||||
|
|
||||||
|
it "add new tab", () ->
|
||||||
|
tab = {"fakeTab": true}
|
||||||
|
|
||||||
|
myCtrl.addTab(tab)
|
||||||
|
|
||||||
|
expect(myCtrl.tabs[0]).to.be.eql(tab)
|
||||||
|
|
||||||
|
it "toggleTab, mark the tab passed as parameter to active", () ->
|
||||||
|
fakeTabs = [
|
||||||
|
{id: 1},
|
||||||
|
{id: 2},
|
||||||
|
{id: 3}
|
||||||
|
]
|
||||||
|
|
||||||
|
myCtrl.tabs = fakeTabs
|
||||||
|
|
||||||
|
myCtrl.toggleTab(fakeTabs[1])
|
||||||
|
|
||||||
|
expect(myCtrl.tabs[0].active).to.be.false
|
||||||
|
expect(myCtrl.tabs[1].active).to.be.true
|
||||||
|
expect(myCtrl.tabs[2].active).to.be.false
|
||||||
|
|
||||||
|
myCtrl.toggleTab(fakeTabs[0])
|
||||||
|
|
||||||
|
expect(myCtrl.tabs[0].active).to.be.true
|
||||||
|
expect(myCtrl.tabs[1].active).to.be.false
|
||||||
|
expect(myCtrl.tabs[2].active).to.be.false
|
|
@ -0,0 +1,11 @@
|
||||||
|
ProfileTabsDirective = () ->
|
||||||
|
return {
|
||||||
|
scope: {}
|
||||||
|
controller: "ProfileTabs"
|
||||||
|
controllerAs: "vm"
|
||||||
|
templateUrl: "profile/profile-tabs/profile-tabs.html"
|
||||||
|
transclude: true
|
||||||
|
}
|
||||||
|
|
||||||
|
angular.module("taigaProfile")
|
||||||
|
.directive("tgProfileTabs", ProfileTabsDirective)
|
|
@ -0,0 +1,7 @@
|
||||||
|
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)")
|
||||||
|
span.icon(ng-class="::tab.icon")
|
||||||
|
span {{::tab.name}}
|
||||||
|
|
||||||
|
ng-transclude
|
|
@ -0,0 +1,34 @@
|
||||||
|
ProfileTimelineAttachmentDirective = (template, $compile) ->
|
||||||
|
validFileExtensions = [".jpg", ".jpeg", ".bmp", ".gif", ".png"]
|
||||||
|
|
||||||
|
isImage = (url) ->
|
||||||
|
url = url.toLowerCase()
|
||||||
|
|
||||||
|
return _.some validFileExtensions, (extension) ->
|
||||||
|
return url.indexOf(extension, url - extension.length) != -1
|
||||||
|
|
||||||
|
link = (scope, el) ->
|
||||||
|
is_image = isImage(scope.attachment.url)
|
||||||
|
|
||||||
|
if is_image
|
||||||
|
template = template.get("profile/profile-timeline-attachment/profile-timeline-attachment-image.html")
|
||||||
|
else
|
||||||
|
template = template.get("profile/profile-timeline-attachment/profile-timeline-attachment.html")
|
||||||
|
|
||||||
|
el.html(template)
|
||||||
|
$compile(el.contents())(scope)
|
||||||
|
|
||||||
|
return {
|
||||||
|
link: link
|
||||||
|
scope: {
|
||||||
|
attachment: "=tgProfileTimelineAttachment"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfileTimelineAttachmentDirective.$inject = [
|
||||||
|
"$tgTemplate",
|
||||||
|
"$compile"
|
||||||
|
]
|
||||||
|
|
||||||
|
angular.module("taigaProfile")
|
||||||
|
.directive("tgProfileTimelineAttachment", ProfileTimelineAttachmentDirective)
|
|
@ -0,0 +1,58 @@
|
||||||
|
describe "profileTimelineAttachmentDirective", () ->
|
||||||
|
element = scope = compile = provide = null
|
||||||
|
mockTgTemplate = null
|
||||||
|
template = "<div tg-profile-timeline-attachment='attachment'></div>"
|
||||||
|
|
||||||
|
_mockTgTemplate= () ->
|
||||||
|
mockTgTemplate = {
|
||||||
|
get: sinon.stub()
|
||||||
|
}
|
||||||
|
|
||||||
|
provide.value "$tgTemplate", mockTgTemplate
|
||||||
|
|
||||||
|
_mocks = () ->
|
||||||
|
module ($provide) ->
|
||||||
|
provide = $provide
|
||||||
|
_mockTgTemplate()
|
||||||
|
|
||||||
|
return null
|
||||||
|
|
||||||
|
createDirective = () ->
|
||||||
|
elm = compile(template)(scope)
|
||||||
|
|
||||||
|
return elm
|
||||||
|
|
||||||
|
beforeEach ->
|
||||||
|
module "taigaProfile"
|
||||||
|
|
||||||
|
_mocks()
|
||||||
|
|
||||||
|
inject ($rootScope, $compile) ->
|
||||||
|
scope = $rootScope.$new()
|
||||||
|
compile = $compile
|
||||||
|
|
||||||
|
it "attachment image template", () ->
|
||||||
|
scope.attachment = {
|
||||||
|
url: "path/path/file.jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
mockTgTemplate.get
|
||||||
|
.withArgs("profile/profile-timeline-attachment/profile-timeline-attachment-image.html")
|
||||||
|
.returns("<div id='image'></div>")
|
||||||
|
|
||||||
|
elm = createDirective()
|
||||||
|
|
||||||
|
expect(elm.find('#image')).to.have.length(1)
|
||||||
|
|
||||||
|
it "attachment file template", () ->
|
||||||
|
scope.attachment = {
|
||||||
|
url: "path/path/file.pdf"
|
||||||
|
}
|
||||||
|
|
||||||
|
mockTgTemplate.get
|
||||||
|
.withArgs("profile/profile-timeline-attachment/profile-timeline-attachment.html")
|
||||||
|
.returns("<div id='file'></div>")
|
||||||
|
|
||||||
|
elm = createDirective()
|
||||||
|
|
||||||
|
expect(elm.find('#file')).to.have.length(1)
|
|
@ -0,0 +1,3 @@
|
||||||
|
p TODO: ATTACHMENT
|
||||||
|
p
|
||||||
|
a(ng-href="attachment.url") {{attachment.filename}}
|
|
@ -0,0 +1,90 @@
|
||||||
|
class ProfileTimelineItemTitle
|
||||||
|
@.$inject = [
|
||||||
|
"$translate"
|
||||||
|
]
|
||||||
|
|
||||||
|
_fieldTranslationKey: {
|
||||||
|
'status': 'COMMON.FIELDS.STATUS',
|
||||||
|
'subject': 'COMMON.FIELDS.SUBJECT',
|
||||||
|
'description': 'COMMON.FIELDS.DESCRIPTION',
|
||||||
|
'points': 'COMMON.FIELDS.POINTS',
|
||||||
|
'severity': 'ISSUES.FIELDS.SEVERITY',
|
||||||
|
'priority': 'ISSUES.FIELDS.PRIORITY',
|
||||||
|
'type': 'ISSUES.FIELDS.TYPE',
|
||||||
|
'is_iocaine': 'TASK.FIELDS.IS_IOCAINE'
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor: (@translate) ->
|
||||||
|
|
||||||
|
|
||||||
|
_translateTitleParams: (param, timeline, event) ->
|
||||||
|
if param == "username"
|
||||||
|
user = timeline.data.user
|
||||||
|
title_attr = @translate.instant('COMMON.SEE_USER_PROFILE', {username: user.username})
|
||||||
|
url = 'user-profile:username=activity.user.username'
|
||||||
|
return @._getLink(url, user.username, title_attr)
|
||||||
|
|
||||||
|
else if param == 'field_name'
|
||||||
|
field_name = Object.keys(timeline.data.values_diff)[0]
|
||||||
|
|
||||||
|
return @translate.instant(@._fieldTranslationKey[field_name])
|
||||||
|
|
||||||
|
else if param == 'project_name'
|
||||||
|
url = 'project:project=activity.project.slug'
|
||||||
|
|
||||||
|
return @._getLink(url, timeline.data.project.name)
|
||||||
|
|
||||||
|
else if param == 'sprint_name'
|
||||||
|
url = 'project-taskboard:project=activity.project.slug,sprint=activity.sprint.slug'
|
||||||
|
|
||||||
|
return @._getLink(url, timeline.data.milestone.name)
|
||||||
|
|
||||||
|
else if param == 'obj_name'
|
||||||
|
obj = @._getTimelineObj(timeline, event)
|
||||||
|
url = @._getDetailObjUrl(event)
|
||||||
|
|
||||||
|
if event.obj == 'wikipage'
|
||||||
|
text = obj.slug
|
||||||
|
else if event.obj == 'milestone'
|
||||||
|
text = obj.name
|
||||||
|
else
|
||||||
|
text = '#' + obj.ref + ' ' + obj.subject
|
||||||
|
|
||||||
|
return @._getLink(url, text)
|
||||||
|
|
||||||
|
_getTimelineObj: (timeline, event) ->
|
||||||
|
return timeline.data[event.obj]
|
||||||
|
|
||||||
|
_getDetailObjUrl: (event) ->
|
||||||
|
url = {
|
||||||
|
"issue": ["project-issues-detail", ":project=vm.activity.project.slug,ref=vm.activity.obj.ref"],
|
||||||
|
"wikipage": ["project-wiki-page", ":project=vm.activity.project.slug,slug=vm.activity.obj.slug"],
|
||||||
|
"task": ["project-tasks-detail", ":project=vm.activity.project.slug,ref=vm.activity.obj.ref"],
|
||||||
|
"userstory": ["project-userstories-detail", ":project=vm.activity.project.slug,ref=vm.activity.obj.ref"],
|
||||||
|
"milestone": ["project-taskboard", ":project=vm.activity.project.slug,sprint=vm.activity.obj.slug"]
|
||||||
|
}
|
||||||
|
|
||||||
|
return url[event.obj][0] + url[event.obj][1]
|
||||||
|
|
||||||
|
_getLink: (url, text, title) ->
|
||||||
|
title = title || text
|
||||||
|
|
||||||
|
return $('<a>')
|
||||||
|
.attr('tg-nav', url)
|
||||||
|
.text(text)
|
||||||
|
.attr('title', title)
|
||||||
|
.prop('outerHTML')
|
||||||
|
|
||||||
|
_getParams: (timeline, event, timeline_type) ->
|
||||||
|
params = {}
|
||||||
|
|
||||||
|
timeline_type.translate_params.forEach (param) =>
|
||||||
|
params[param] = @._translateTitleParams(param, timeline, event)
|
||||||
|
|
||||||
|
return params
|
||||||
|
|
||||||
|
getTitle: (timeline, event, type) ->
|
||||||
|
return @translate.instant(type.key, @._getParams(timeline, event, type))
|
||||||
|
|
||||||
|
angular.module("taigaProfile")
|
||||||
|
.service("tgProfileTimelineItemTitle", ProfileTimelineItemTitle)
|
|
@ -0,0 +1,244 @@
|
||||||
|
describe "tgProfileTimelineItemTitle", ->
|
||||||
|
mySvc = null
|
||||||
|
mockTranslate = null
|
||||||
|
timeline = event = type = null
|
||||||
|
|
||||||
|
_mockTranslate = () ->
|
||||||
|
_provide (provide) ->
|
||||||
|
mockTranslate = {
|
||||||
|
instant: sinon.stub()
|
||||||
|
}
|
||||||
|
|
||||||
|
provide.value "$translate", mockTranslate
|
||||||
|
|
||||||
|
_provide = (callback) ->
|
||||||
|
module ($provide) ->
|
||||||
|
callback($provide)
|
||||||
|
return null
|
||||||
|
|
||||||
|
_mocks = () ->
|
||||||
|
_mockTranslate()
|
||||||
|
|
||||||
|
_inject = ->
|
||||||
|
inject (_tgProfileTimelineItemTitle_) ->
|
||||||
|
mySvc = _tgProfileTimelineItemTitle_
|
||||||
|
|
||||||
|
_setup = ->
|
||||||
|
_mocks()
|
||||||
|
_inject()
|
||||||
|
|
||||||
|
beforeEach ->
|
||||||
|
module "taigaProfile"
|
||||||
|
_setup()
|
||||||
|
|
||||||
|
it "title with username", () ->
|
||||||
|
timeline = {
|
||||||
|
data: {
|
||||||
|
user: {
|
||||||
|
username: 'xx'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event = {}
|
||||||
|
|
||||||
|
type = {
|
||||||
|
key: 'TITLE_USER_NAME',
|
||||||
|
translate_params: ['username']
|
||||||
|
}
|
||||||
|
|
||||||
|
mockTranslate.instant
|
||||||
|
.withArgs('COMMON.SEE_USER_PROFILE', {username: timeline.data.user.username})
|
||||||
|
.returns('user-param')
|
||||||
|
|
||||||
|
usernamelink = sinon.match ((value) ->
|
||||||
|
return value.username == '<a tg-nav="user-profile:username=activity.user.username" title="user-param">xx</a>'
|
||||||
|
), "usernamelink"
|
||||||
|
|
||||||
|
mockTranslate.instant
|
||||||
|
.withArgs('TITLE_USER_NAME', usernamelink)
|
||||||
|
.returns('title_ok')
|
||||||
|
|
||||||
|
title = mySvc.getTitle(timeline, event, type)
|
||||||
|
|
||||||
|
expect(title).to.be.equal("title_ok")
|
||||||
|
|
||||||
|
it "title with a field name", () ->
|
||||||
|
timeline = {
|
||||||
|
data: {
|
||||||
|
values_diff: {
|
||||||
|
status: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event = {}
|
||||||
|
|
||||||
|
type = {
|
||||||
|
key: 'TITLE_FIELD',
|
||||||
|
translate_params: ['field_name']
|
||||||
|
}
|
||||||
|
|
||||||
|
mockTranslate.instant
|
||||||
|
.withArgs('COMMON.FIELDS.STATUS')
|
||||||
|
.returns('field-params')
|
||||||
|
|
||||||
|
fieldparam = sinon.match ((value) ->
|
||||||
|
return value.field_name == 'field-params'
|
||||||
|
), "fieldparam"
|
||||||
|
|
||||||
|
mockTranslate.instant
|
||||||
|
.withArgs('TITLE_FIELD', fieldparam)
|
||||||
|
.returns('title_ok')
|
||||||
|
|
||||||
|
|
||||||
|
title = mySvc.getTitle(timeline, event, type)
|
||||||
|
|
||||||
|
expect(title).to.be.equal("title_ok")
|
||||||
|
|
||||||
|
it "title with project name", () ->
|
||||||
|
timeline = {
|
||||||
|
data: {
|
||||||
|
project: {
|
||||||
|
name: "project_name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event = {}
|
||||||
|
|
||||||
|
type = {
|
||||||
|
key: 'TITLE_PROJECT',
|
||||||
|
translate_params: ['project_name']
|
||||||
|
}
|
||||||
|
|
||||||
|
projectparam = sinon.match ((value) ->
|
||||||
|
return value.project_name == '<a tg-nav="project:project=activity.project.slug" title="project_name">project_name</a>'
|
||||||
|
), "projectparam"
|
||||||
|
|
||||||
|
mockTranslate.instant
|
||||||
|
.withArgs('TITLE_PROJECT', projectparam)
|
||||||
|
.returns('title_ok')
|
||||||
|
|
||||||
|
title = mySvc.getTitle(timeline, event, type)
|
||||||
|
|
||||||
|
expect(title).to.be.equal("title_ok")
|
||||||
|
|
||||||
|
it "title with sprint name", () ->
|
||||||
|
timeline = {
|
||||||
|
data: {
|
||||||
|
milestone: {
|
||||||
|
name: "milestone_name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event = {}
|
||||||
|
|
||||||
|
type = {
|
||||||
|
key: 'TITLE_MILESTONE',
|
||||||
|
translate_params: ['sprint_name']
|
||||||
|
}
|
||||||
|
|
||||||
|
milestoneparam = sinon.match ((value) ->
|
||||||
|
return value.sprint_name == '<a tg-nav="project-taskboard:project=activity.project.slug,sprint=activity.sprint.slug" title="milestone_name">milestone_name</a>'
|
||||||
|
), "milestoneparam"
|
||||||
|
|
||||||
|
mockTranslate.instant
|
||||||
|
.withArgs('TITLE_MILESTONE', milestoneparam)
|
||||||
|
.returns('title_ok')
|
||||||
|
|
||||||
|
title = mySvc.getTitle(timeline, event, type)
|
||||||
|
|
||||||
|
expect(title).to.be.equal("title_ok")
|
||||||
|
|
||||||
|
it "title with object", () ->
|
||||||
|
timeline = {
|
||||||
|
data: {
|
||||||
|
issue: {
|
||||||
|
ref: '123',
|
||||||
|
subject: 'subject'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event = {
|
||||||
|
obj: 'issue',
|
||||||
|
}
|
||||||
|
|
||||||
|
type = {
|
||||||
|
key: 'TITLE_OBJ',
|
||||||
|
translate_params: ['obj_name']
|
||||||
|
}
|
||||||
|
|
||||||
|
objparam = sinon.match ((value) ->
|
||||||
|
return value.obj_name == '<a tg-nav="project-issues-detail:project=vm.activity.project.slug,ref=vm.activity.obj.ref" title="#123 subject">#123 subject</a>'
|
||||||
|
), "objparam"
|
||||||
|
|
||||||
|
mockTranslate.instant
|
||||||
|
.withArgs('TITLE_OBJ', objparam)
|
||||||
|
.returns('title_ok')
|
||||||
|
|
||||||
|
title = mySvc.getTitle(timeline, event, type)
|
||||||
|
|
||||||
|
expect(title).to.be.equal("title_ok")
|
||||||
|
|
||||||
|
it "title obj wiki", () ->
|
||||||
|
timeline = {
|
||||||
|
data: {
|
||||||
|
wikipage: {
|
||||||
|
slug: 'slugwiki',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event = {
|
||||||
|
obj: 'wikipage',
|
||||||
|
}
|
||||||
|
|
||||||
|
type = {
|
||||||
|
key: 'TITLE_OBJ',
|
||||||
|
translate_params: ['obj_name']
|
||||||
|
}
|
||||||
|
|
||||||
|
objparam = sinon.match ((value) ->
|
||||||
|
return value.obj_name == '<a tg-nav="project-wiki-page:project=vm.activity.project.slug,slug=vm.activity.obj.slug" title="slugwiki">slugwiki</a>'
|
||||||
|
), "objparam"
|
||||||
|
|
||||||
|
mockTranslate.instant
|
||||||
|
.withArgs('TITLE_OBJ', objparam)
|
||||||
|
.returns('title_ok')
|
||||||
|
|
||||||
|
title = mySvc.getTitle(timeline, event, type)
|
||||||
|
|
||||||
|
expect(title).to.be.equal("title_ok")
|
||||||
|
|
||||||
|
it "title obj milestone", () ->
|
||||||
|
timeline = {
|
||||||
|
data: {
|
||||||
|
milestone: {
|
||||||
|
name: 'milestone_name',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event = {
|
||||||
|
obj: 'milestone',
|
||||||
|
}
|
||||||
|
|
||||||
|
type = {
|
||||||
|
key: 'TITLE_OBJ',
|
||||||
|
translate_params: ['obj_name']
|
||||||
|
}
|
||||||
|
|
||||||
|
objparam = sinon.match ((value) ->
|
||||||
|
return value.obj_name == '<a tg-nav="project-taskboard:project=vm.activity.project.slug,sprint=vm.activity.obj.slug" title="milestone_name">milestone_name</a>'
|
||||||
|
), "objparam"
|
||||||
|
|
||||||
|
mockTranslate.instant
|
||||||
|
.withArgs('TITLE_OBJ', objparam)
|
||||||
|
.returns('title_ok')
|
||||||
|
|
||||||
|
title = mySvc.getTitle(timeline, event, type)
|
||||||
|
|
||||||
|
expect(title).to.be.equal("title_ok")
|
|
@ -150,126 +150,8 @@ timelineType = (timeline, event) ->
|
||||||
return _.find types, (obj) ->
|
return _.find types, (obj) ->
|
||||||
return obj.check(timeline, event, field_name)
|
return obj.check(timeline, event, field_name)
|
||||||
|
|
||||||
TimelineItemDirective = ($tgTemplate, $compile, $navUrls, $translate, $sce) ->
|
class ProfileTimelineType
|
||||||
fieldTranslationKey = {
|
getType: (timeline, event) -> timelineType(timeline, event)
|
||||||
'status': 'COMMON.FIELDS.STATUS',
|
|
||||||
'subject': 'COMMON.FIELDS.SUBJECT',
|
|
||||||
'description': 'COMMON.FIELDS.DESCRIPTION',
|
|
||||||
'points': 'COMMON.FIELDS.POINTS',
|
|
||||||
'severity': 'ISSUES.FIELDS.SEVERITY',
|
|
||||||
'priority': 'ISSUES.FIELDS.PRIORITY',
|
|
||||||
'type': 'ISSUES.FIELDS.TYPE',
|
|
||||||
'is_iocaine': 'TASK.FIELDS.IS_IOCAINE'
|
|
||||||
}
|
|
||||||
|
|
||||||
parseEventType = (event_type) ->
|
|
||||||
event_type = event_type.split(".")
|
|
||||||
|
|
||||||
return {
|
|
||||||
section: event_type[0],
|
|
||||||
obj: event_type[1],
|
|
||||||
type: event_type[2]
|
|
||||||
}
|
|
||||||
|
|
||||||
getDetailObjUrl = (event) ->
|
|
||||||
url = {
|
|
||||||
"issue": ["project-issues-detail", ":project=activity.project.slug,ref=activity.obj.ref"],
|
|
||||||
"wikipage": ["project-wiki-page", ":project=activity.project.slug,slug=activity.obj.slug"],
|
|
||||||
"task": ["project-tasks-detail", ":project=activity.project.slug,ref=activity.obj.ref"],
|
|
||||||
"userstory": ["project-userstories-detail", ":project=activity.project.slug,ref=activity.obj.ref"],
|
|
||||||
"milestone": ["project-taskboard", ":project=activity.project.slug,sprint=activity.obj.slug"]
|
|
||||||
}
|
|
||||||
|
|
||||||
return url[event.obj][0] + url[event.obj][1]
|
|
||||||
|
|
||||||
getLink = (url, text, title) ->
|
|
||||||
title = title || text
|
|
||||||
|
|
||||||
return $('<a>')
|
|
||||||
.attr('tg-nav', url)
|
|
||||||
.text(text)
|
|
||||||
.attr('title', title)
|
|
||||||
.prop('outerHTML')
|
|
||||||
|
|
||||||
translate_params = {
|
|
||||||
username: (timeline) ->
|
|
||||||
user = timeline.data.user
|
|
||||||
title_attr = $translate.instant('COMMON.SEE_USER_PROFILE', {username: user.username})
|
|
||||||
url = 'user-profile:username=activity.user.username'
|
|
||||||
return getLink(url, user.username, title_attr)
|
|
||||||
|
|
||||||
field_name: (timeline) ->
|
|
||||||
field_name = Object.keys(timeline.data.values_diff)[0]
|
|
||||||
|
|
||||||
return $translate.instant(fieldTranslationKey[field_name])
|
|
||||||
|
|
||||||
project_name: (timeline) ->
|
|
||||||
url = 'project:project=activity.project.slug'
|
|
||||||
|
|
||||||
return getLink(url, timeline.data.project.name)
|
|
||||||
|
|
||||||
sprint_name: (timeline) ->
|
|
||||||
url = 'project-taskboard:project=activity.project.slug,sprint=activity.sprint.slug'
|
|
||||||
|
|
||||||
return getLink(url, timeline.data.milestone.name)
|
|
||||||
|
|
||||||
obj_name: (timeline, event) ->
|
|
||||||
obj = getTimelineObj(timeline, event)
|
|
||||||
url = getDetailObjUrl(event)
|
|
||||||
|
|
||||||
if event.obj == 'wikipage'
|
|
||||||
text = obj.slug
|
|
||||||
else if event.obj == 'milestone'
|
|
||||||
text = obj.name
|
|
||||||
else
|
|
||||||
text = '#' + obj.ref + ' ' + obj.subject
|
|
||||||
|
|
||||||
return getLink(url, text)
|
|
||||||
}
|
|
||||||
|
|
||||||
getTimelineObj = (timeline, event) ->
|
|
||||||
return timeline.data[event.obj]
|
|
||||||
|
|
||||||
getParams = (timeline, event, timeline_type) ->
|
|
||||||
params = {}
|
|
||||||
|
|
||||||
timeline_type.translate_params.forEach (param) ->
|
|
||||||
params[param] = translate_params[param](timeline, event)
|
|
||||||
|
|
||||||
return params
|
|
||||||
|
|
||||||
getTitle = (timeline, event, type) ->
|
|
||||||
return $translate.instant(type.key, getParams(timeline, event, type))
|
|
||||||
|
|
||||||
link = ($scope, $el, $attrs) ->
|
|
||||||
event = parseEventType($scope.timeline.event_type)
|
|
||||||
type = timelineType($scope.timeline, event)
|
|
||||||
|
|
||||||
$scope.activity = {}
|
|
||||||
|
|
||||||
$scope.activity.obj = getTimelineObj($scope.timeline, event)
|
|
||||||
$scope.activity.user = $scope.timeline.data.user
|
|
||||||
$scope.activity.project = $scope.timeline.data.project
|
|
||||||
$scope.activity.sprint = $scope.timeline.data.milestone
|
|
||||||
$scope.activity.title = getTitle($scope.timeline, event, type)
|
|
||||||
$scope.activity.created_formated = moment($scope.timeline.created).fromNow()
|
|
||||||
|
|
||||||
if type.description
|
|
||||||
$scope.activity.description = $sce.trustAsHtml(type.description($scope.timeline))
|
|
||||||
|
|
||||||
if type.member
|
|
||||||
$scope.activity.member = type.member($scope.timeline)
|
|
||||||
|
|
||||||
if $scope.timeline.data.values_diff?.attachments
|
|
||||||
$scope.activity.attachments = $scope.timeline.data.values_diff.attachments.new
|
|
||||||
|
|
||||||
return {
|
|
||||||
link: link
|
|
||||||
templateUrl: "profile/timeline/timeline-item.html"
|
|
||||||
scope: {
|
|
||||||
timeline: "=tgTimelineItem"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
angular.module("taigaProfile")
|
angular.module("taigaProfile")
|
||||||
.directive("tgTimelineItem", ["$tgTemplate", "$compile", "$tgNavUrls", "$translate", "$sce", TimelineItemDirective])
|
.service("tgProfileTimelineItemType", ProfileTimelineType)
|
|
@ -0,0 +1,31 @@
|
||||||
|
describe "tgProfileTimelineItemType", ->
|
||||||
|
mySvc = null
|
||||||
|
|
||||||
|
_provide = (callback) ->
|
||||||
|
module ($provide) ->
|
||||||
|
callback($provide)
|
||||||
|
return null
|
||||||
|
|
||||||
|
_inject = ->
|
||||||
|
inject (_tgProfileTimelineItemType_) ->
|
||||||
|
mySvc = _tgProfileTimelineItemType_
|
||||||
|
|
||||||
|
_setup = ->
|
||||||
|
_inject()
|
||||||
|
|
||||||
|
beforeEach ->
|
||||||
|
module "taigaProfile"
|
||||||
|
_setup()
|
||||||
|
|
||||||
|
it "get the timeline type", () ->
|
||||||
|
timeline = {
|
||||||
|
data: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
event = {
|
||||||
|
obj: 'membership'
|
||||||
|
}
|
||||||
|
|
||||||
|
type = mySvc.getType(timeline, event)
|
||||||
|
|
||||||
|
expect(type.key).to.be.equal("TIMELINE.NEW_MEMBER")
|
|
@ -0,0 +1,40 @@
|
||||||
|
class ProfileTimelineItemController
|
||||||
|
@.$inject = [
|
||||||
|
"$scope",
|
||||||
|
"$sce",
|
||||||
|
"tgProfileTimelineItemType",
|
||||||
|
"tgProfileTimelineItemTitle"
|
||||||
|
]
|
||||||
|
|
||||||
|
constructor: (@scope, @sce, @profileTimelineItemType, @profileTimelineItemTitle) ->
|
||||||
|
event = @parseEventType(@scope.vm.timeline.event_type)
|
||||||
|
type = @profileTimelineItemType.getType(@scope.vm.timeline, event)
|
||||||
|
|
||||||
|
@.activity = {}
|
||||||
|
|
||||||
|
@.activity.user = @scope.vm.timeline.data.user
|
||||||
|
@.activity.project = @scope.vm.timeline.data.project
|
||||||
|
@.activity.sprint = @scope.vm.timeline.data.milestone
|
||||||
|
@.activity.title = @profileTimelineItemTitle.getTitle(@scope.vm.timeline, event, type)
|
||||||
|
@.activity.created_formated = moment(@scope.vm.timeline.created).fromNow()
|
||||||
|
|
||||||
|
if type.description
|
||||||
|
@.activity.description = @sce.trustAsHtml(type.description(@scope.vm.timeline))
|
||||||
|
|
||||||
|
if type.member
|
||||||
|
@.activity.member = type.member(@scope.vm.timeline)
|
||||||
|
|
||||||
|
if @scope.vm.timeline.data.values_diff?.attachments
|
||||||
|
@.activity.attachments = @scope.vm.timeline.data.values_diff.attachments.new
|
||||||
|
|
||||||
|
parseEventType: (event_type) ->
|
||||||
|
event_type = event_type.split(".")
|
||||||
|
|
||||||
|
return {
|
||||||
|
section: event_type[0],
|
||||||
|
obj: event_type[1],
|
||||||
|
type: event_type[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
angular.module("taigaProfile")
|
||||||
|
.controller("ProfileTimelineItem", ProfileTimelineItemController)
|
|
@ -0,0 +1,101 @@
|
||||||
|
describe "ProfileTimelineItemController", ->
|
||||||
|
controller = scope = provide = null
|
||||||
|
timeline = event = null
|
||||||
|
mockTgProfileTimelineItemType = null
|
||||||
|
mockTgProfileTimelineItemTitle = null
|
||||||
|
mockType = null
|
||||||
|
|
||||||
|
_mockTgProfileTimelineItemType = () ->
|
||||||
|
mockTgProfileTimelineItemType = {
|
||||||
|
getType: sinon.stub()
|
||||||
|
}
|
||||||
|
|
||||||
|
mockType = {
|
||||||
|
description: sinon.stub(),
|
||||||
|
member: sinon.stub()
|
||||||
|
}
|
||||||
|
|
||||||
|
mockTgProfileTimelineItemType.getType.withArgs(timeline).returns(mockType)
|
||||||
|
|
||||||
|
provide.value "tgProfileTimelineItemType", mockTgProfileTimelineItemType
|
||||||
|
|
||||||
|
_mockTgProfileTimelineItemTitle = () ->
|
||||||
|
mockTgProfileTimelineItemTitle = {
|
||||||
|
getTitle: sinon.stub()
|
||||||
|
}
|
||||||
|
|
||||||
|
mockTgProfileTimelineItemTitle.getTitle.withArgs(timeline, event, mockType).returns("fakeTitle")
|
||||||
|
|
||||||
|
provide.value "tgProfileTimelineItemTitle", mockTgProfileTimelineItemTitle
|
||||||
|
|
||||||
|
_mocks = () ->
|
||||||
|
module ($provide) ->
|
||||||
|
provide = $provide
|
||||||
|
_mockTgProfileTimelineItemType()
|
||||||
|
_mockTgProfileTimelineItemTitle()
|
||||||
|
|
||||||
|
return null
|
||||||
|
|
||||||
|
_setup = () ->
|
||||||
|
event = {
|
||||||
|
section: 'issues',
|
||||||
|
obj: 'issue',
|
||||||
|
type: 'created'
|
||||||
|
}
|
||||||
|
|
||||||
|
timeline = {
|
||||||
|
event_type: 'issues.issue.created',
|
||||||
|
data: {
|
||||||
|
user: 'user_fake',
|
||||||
|
project: 'project_fake',
|
||||||
|
milestone: 'milestone_fake',
|
||||||
|
created: new Date().getTime(),
|
||||||
|
values_diff: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scope = {
|
||||||
|
vm: {
|
||||||
|
timeline: timeline
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach ->
|
||||||
|
module "taigaProfile"
|
||||||
|
|
||||||
|
_setup()
|
||||||
|
_mocks()
|
||||||
|
|
||||||
|
inject ($controller) ->
|
||||||
|
controller = $controller
|
||||||
|
|
||||||
|
it "basic activity fields filled", () ->
|
||||||
|
timeline = scope.vm.timeline
|
||||||
|
|
||||||
|
myCtrl = controller("ProfileTimelineItem", {$scope: scope})
|
||||||
|
|
||||||
|
expect(myCtrl.activity.user).to.be.equal(timeline.data.user)
|
||||||
|
expect(myCtrl.activity.project).to.be.equal(timeline.data.project)
|
||||||
|
expect(myCtrl.activity.sprint).to.be.equal(timeline.data.milestone)
|
||||||
|
expect(myCtrl.activity.title).to.be.equal("fakeTitle")
|
||||||
|
expect(myCtrl.activity.created_formated).to.have.length.above(1)
|
||||||
|
|
||||||
|
it "all activity fields filled", () ->
|
||||||
|
timeline = scope.vm.timeline
|
||||||
|
|
||||||
|
attachment = "fakeAttachment"
|
||||||
|
timeline.data.values_diff.attachments = {
|
||||||
|
new: attachment
|
||||||
|
}
|
||||||
|
|
||||||
|
description = "fakeDescription"
|
||||||
|
member = "fakeMember"
|
||||||
|
|
||||||
|
mockType.description.withArgs(timeline).returns(description)
|
||||||
|
mockType.member.withArgs(timeline).returns(member)
|
||||||
|
|
||||||
|
myCtrl = controller("ProfileTimelineItem", {$scope: scope})
|
||||||
|
|
||||||
|
expect(myCtrl.activity.description).to.be.an('object') # $sce.trustAsHtml
|
||||||
|
expect(myCtrl.activity.member).to.be.equal(member)
|
||||||
|
expect(myCtrl.activity.attachments).to.be.equal(attachment)
|
|
@ -0,0 +1,13 @@
|
||||||
|
ProfileTimelineItemDirective = () ->
|
||||||
|
return {
|
||||||
|
controllerAs: "vm"
|
||||||
|
controller: "ProfileTimelineItem"
|
||||||
|
bindToController: true
|
||||||
|
templateUrl: "profile/profile-timeline-item/profile-timeline-item.html"
|
||||||
|
scope: {
|
||||||
|
timeline: "=tgProfileTimelineItem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
angular.module("taigaProfile")
|
||||||
|
.directive("tgProfileTimelineItem", ProfileTimelineItemDirective)
|
|
@ -0,0 +1,22 @@
|
||||||
|
div.activity-image
|
||||||
|
span.activity-date {{::vm.activity.created_formated}}
|
||||||
|
div.activity-info
|
||||||
|
div.profile-contact-picture
|
||||||
|
a(tg-nav="user-profile:username=vm.activity.user.username", title="{{::vm.activity.user.name }}")
|
||||||
|
img(ng-src="{{::vm.activity.user.photo}}", alt="{{::vm.activity.user.name}}")
|
||||||
|
|
||||||
|
p(tg-compile-html="vm.activity.title")
|
||||||
|
|
||||||
|
.activity-comment-quote(ng-if="::vm.activity.description")
|
||||||
|
p(ng-bind-html="vm.activity.description")
|
||||||
|
|
||||||
|
.activity-member-view(ng-if="::vm.activity.member")
|
||||||
|
.profile-member-picture
|
||||||
|
img(ng-src="{{::vm.activity.member.user.photo}}", alt="{{::vm.activity.member.user.name}}")
|
||||||
|
.activity-member-info
|
||||||
|
a(tg-nav="user-profile:username=activity.member.user.username", title="{{::vm.activity.member.user.name }}")
|
||||||
|
span {{::vm.activity.member.user.name}}
|
||||||
|
p {{::vm.activity.member.role.name}}
|
||||||
|
|
||||||
|
div(ng-repeat="attachment in vm.activity.attachments")
|
||||||
|
div(tg-profile-timeline-attachment="vm.attachment")
|
|
@ -29,21 +29,39 @@ class ProfileTimelineController extends mixOf(taiga.Controller, taiga.PageMixin,
|
||||||
"$tgAuth"
|
"$tgAuth"
|
||||||
]
|
]
|
||||||
|
|
||||||
valid_fields: ['status', 'subject', 'description', 'assigned_to', 'points', 'severity', 'priority', 'type', 'attachments', 'milestone', 'is_blocked', 'is_iocaine', 'content_diff', 'name', 'estimated_finish', 'estimated_start']
|
_valid_fields: [
|
||||||
|
'status',
|
||||||
|
'subject',
|
||||||
|
'description',
|
||||||
|
'assigned_to',
|
||||||
|
'points',
|
||||||
|
'severity',
|
||||||
|
'priority',
|
||||||
|
'type',
|
||||||
|
'attachments',
|
||||||
|
'milestone',
|
||||||
|
'is_blocked',
|
||||||
|
'is_iocaine',
|
||||||
|
'content_diff',
|
||||||
|
'name',
|
||||||
|
'estimated_finish',
|
||||||
|
'estimated_start'
|
||||||
|
]
|
||||||
|
|
||||||
constructor: (@rs, @auth) ->
|
constructor: (@rs, @auth) ->
|
||||||
@timelineList = []
|
@.timelineList = []
|
||||||
@pagination = {page: 1}
|
@.pagination = {page: 1}
|
||||||
|
@.loadingData = false
|
||||||
|
|
||||||
isValidField: (values) =>
|
_isValidField: (values) =>
|
||||||
return _.some values, (value) => @valid_fields.indexOf(value) != -1
|
return _.some values, (value) => @._valid_fields.indexOf(value) != -1
|
||||||
|
|
||||||
filterValidTimelineItems: (timeline) =>
|
_filterValidTimelineItems: (timeline) =>
|
||||||
if timeline.data.values_diff
|
if timeline.data.values_diff
|
||||||
values = Object.keys(timeline.data.values_diff)
|
values = Object.keys(timeline.data.values_diff)
|
||||||
|
|
||||||
if values && values.length
|
if values && values.length
|
||||||
if !@isValidField(values)
|
if !@._isValidField(values)
|
||||||
return false
|
return false
|
||||||
else if values[0] == 'attachments' && timeline.data.values_diff.attachments.new.length == 0
|
else if values[0] == 'attachments' && timeline.data.values_diff.attachments.new.length == 0
|
||||||
return false
|
return false
|
||||||
|
@ -53,14 +71,13 @@ class ProfileTimelineController extends mixOf(taiga.Controller, taiga.PageMixin,
|
||||||
loadTimeline: () ->
|
loadTimeline: () ->
|
||||||
user = @auth.getUser()
|
user = @auth.getUser()
|
||||||
|
|
||||||
@loadingData = true
|
@.loadingData = true
|
||||||
|
|
||||||
return @rs.timeline.profile(user.id, @pagination).then (result) =>
|
return @rs.timeline.profile(user.id, @.pagination).then (result) =>
|
||||||
newTimelineList = _.filter result.data, @filterValidTimelineItems
|
newTimelineList = _.filter result.data, @._filterValidTimelineItems
|
||||||
|
@.timelineList = @timelineList.concat(newTimelineList)
|
||||||
@timelineList = @timelineList.concat(newTimelineList)
|
@.pagination.page++
|
||||||
@pagination.page++
|
@.loadingData = false
|
||||||
@loadingData = false
|
|
||||||
|
|
||||||
angular.module("taigaProfile")
|
angular.module("taigaProfile")
|
||||||
.controller("ProfileTimeline", ProfileTimelineController)
|
.controller("ProfileTimeline", ProfileTimelineController)
|
|
@ -0,0 +1,116 @@
|
||||||
|
describe "ProfileTimelineController", ->
|
||||||
|
myCtrl = scope = $q = provide = null
|
||||||
|
|
||||||
|
mockUser = {id: 3}
|
||||||
|
|
||||||
|
_mockTgResources = () ->
|
||||||
|
provide.value "$tgResources", {
|
||||||
|
timeline: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
_mockTgAuth = () ->
|
||||||
|
provide.value "$tgAuth", {
|
||||||
|
getUser: () ->
|
||||||
|
return mockUser
|
||||||
|
}
|
||||||
|
|
||||||
|
_mocks = () ->
|
||||||
|
module ($provide) ->
|
||||||
|
provide = $provide
|
||||||
|
_mockTgResources()
|
||||||
|
_mockTgAuth()
|
||||||
|
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
beforeEach ->
|
||||||
|
module "taigaProfile"
|
||||||
|
_mocks()
|
||||||
|
|
||||||
|
inject ($controller, _$q_) ->
|
||||||
|
$q = _$q_
|
||||||
|
myCtrl = $controller "ProfileTimeline"
|
||||||
|
|
||||||
|
it "timelineList should be an array", () ->
|
||||||
|
expect(myCtrl.timelineList).is.an("array")
|
||||||
|
|
||||||
|
|
||||||
|
it "pagination starts at 1", () ->
|
||||||
|
expect(myCtrl.pagination.page).to.be.equal(1)
|
||||||
|
|
||||||
|
describe "load timeline", () ->
|
||||||
|
thenStub = timelineList = null
|
||||||
|
|
||||||
|
beforeEach () ->
|
||||||
|
timelineList = {
|
||||||
|
data: [
|
||||||
|
{ # valid item
|
||||||
|
data: {
|
||||||
|
values_diff: {
|
||||||
|
"status": "xx",
|
||||||
|
"subject": "xx"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ # invalid item
|
||||||
|
data: {
|
||||||
|
values_diff: {
|
||||||
|
"fake": "xx"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ # invalid item
|
||||||
|
data: {
|
||||||
|
values_diff: {
|
||||||
|
"fake2": "xx"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ # valid item
|
||||||
|
data: {
|
||||||
|
values_diff: {
|
||||||
|
"fake2": "xx",
|
||||||
|
"milestone": "xx"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
thenStub = sinon.stub()
|
||||||
|
|
||||||
|
profileStub = sinon.stub()
|
||||||
|
.withArgs(mockUser.id, myCtrl.pagination)
|
||||||
|
.returns({
|
||||||
|
then: thenStub
|
||||||
|
})
|
||||||
|
|
||||||
|
myCtrl.rs.timeline.profile = profileStub
|
||||||
|
|
||||||
|
it "the loadingData variable must be true during the timeline load", () ->
|
||||||
|
expect(myCtrl.loadingData).to.be.false
|
||||||
|
|
||||||
|
myCtrl.loadTimeline()
|
||||||
|
|
||||||
|
expect(myCtrl.loadingData).to.be.true
|
||||||
|
|
||||||
|
thenStub.callArgWith(0, timelineList)
|
||||||
|
|
||||||
|
expect(myCtrl.loadingData).to.be.false
|
||||||
|
|
||||||
|
it "pagiantion increase one every call to loadTimeline", () ->
|
||||||
|
expect(myCtrl.pagination.page).to.equal(1)
|
||||||
|
|
||||||
|
myCtrl.loadTimeline()
|
||||||
|
|
||||||
|
thenStub.callArgWith(0, timelineList)
|
||||||
|
|
||||||
|
expect(myCtrl.pagination.page).to.equal(2)
|
||||||
|
|
||||||
|
it "filter the invalid timeline items", () ->
|
||||||
|
myCtrl.loadTimeline()
|
||||||
|
|
||||||
|
thenStub.callArgWith(0, timelineList)
|
||||||
|
|
||||||
|
expect(myCtrl.timelineList[0]).to.be.equal(timelineList.data[0])
|
||||||
|
expect(myCtrl.timelineList[1]).to.be.equal(timelineList.data[3])
|
|
@ -1,21 +1,21 @@
|
||||||
include ../includes/components/beta
|
include ../../partials/includes/components/beta
|
||||||
div.profile.centered
|
div.profile.centered
|
||||||
include ../includes/modules/profile/profile-bar
|
include includes/profile-bar
|
||||||
div.main
|
div.main
|
||||||
div.hero
|
div.hero
|
||||||
div(tg-profile-tabs)
|
div(tg-profile-tabs)
|
||||||
div.content-wrapper
|
div.content-wrapper
|
||||||
div.content
|
div.content
|
||||||
div(tg-profile-tab="activity", tab-title="Activity Tab", tab-icon="icon-timeline", tab-active)
|
div(tg-profile-tab="activity", tab-title="Activity Tab", tab-icon="icon-timeline", tab-active)
|
||||||
include ../includes/modules/profile/profile-timeline
|
include includes/profile-timeline
|
||||||
|
|
||||||
div(tg-profile-tab="projects", tab-title="Projects Tab", tab-icon="icon-project")
|
div(tg-profile-tab="projects", tab-title="Projects Tab", tab-icon="icon-project")
|
||||||
include ../includes/modules/profile/profile-projects
|
include includes/profile-projects
|
||||||
|
|
||||||
div(tg-profile-tab="contacts", tab-title="Contacts Tab", tab-icon="icon-team")
|
div(tg-profile-tab="contacts", tab-title="Contacts Tab", tab-icon="icon-team")
|
||||||
include ../includes/modules/profile/profile-contacts
|
include includes/profile-contacts
|
||||||
|
|
||||||
div(tg-profile-tab="favorities", tab-title="Favorites Tab", tab-icon="icon-star-fill")
|
div(tg-profile-tab="favorities", tab-title="Favorites Tab", tab-icon="icon-star-fill")
|
||||||
include ../includes/modules/profile/profile-favorites
|
include includes/profile-favorites
|
||||||
|
|
||||||
include ../includes/modules/profile/profile-sidebar
|
include includes/profile-sidebar
|
|
@ -0,0 +1 @@
|
||||||
|
module = angular.module("taigaProfile", [])
|
|
@ -1,7 +0,0 @@
|
||||||
div
|
|
||||||
nav.profile-content-tabs
|
|
||||||
a.tab(ng-repeat="tab in tabs", href="", title="{{::tab.title}}", ng-class="{active: tab.active}" ng-click="toggleTab(tab)")
|
|
||||||
span.icon(ng-class="::tab.icon")
|
|
||||||
span {{::tab.name}}
|
|
||||||
|
|
||||||
ng-transclude
|
|
|
@ -1,3 +0,0 @@
|
||||||
p TODO: ATTACHMENT
|
|
||||||
p
|
|
||||||
a(ng-href="attachment.url"){{attachment.filename}}
|
|
|
@ -1,22 +0,0 @@
|
||||||
div.activity-image
|
|
||||||
span.activity-date {{::activity.created_formated}}
|
|
||||||
div.activity-info
|
|
||||||
div.profile-contact-picture
|
|
||||||
a(tg-nav="user-profile:username=activity.user.username", title="{{::activity.user.name }}")
|
|
||||||
img(ng-src="{{::activity.user.photo}}", alt="{{::activity.user.name}}")
|
|
||||||
|
|
||||||
p(tg-compile-html="activity.title")
|
|
||||||
|
|
||||||
.activity-comment-quote(ng-if="::activity.description")
|
|
||||||
p(ng-bind-html="activity.description")
|
|
||||||
|
|
||||||
.activity-member-view(ng-if="::activity.member")
|
|
||||||
.profile-member-picture
|
|
||||||
img(ng-src="{{::activity.member.user.photo}}", alt="{{::activity.member.user.name}}")
|
|
||||||
.activity-member-info
|
|
||||||
a(tg-nav="user-profile:username=activity.member.user.username", title="{{::activity.member.user.name }}")
|
|
||||||
span {{::activity.member.user.name}}
|
|
||||||
p {{::activity.member.role.name}}
|
|
||||||
|
|
||||||
div(ng-repeat="attachment in activity.attachments")
|
|
||||||
div(tg-timeline-attachment="attachment")
|
|
|
@ -42,7 +42,8 @@ paths.htmlPartials = [
|
||||||
paths.tmp + "partials/**/*.html",
|
paths.tmp + "partials/**/*.html",
|
||||||
paths.tmp + "plugins/**/*.html",
|
paths.tmp + "plugins/**/*.html",
|
||||||
paths.tmp + "modules/**/*.html",
|
paths.tmp + "modules/**/*.html",
|
||||||
"!" + paths.tmp + "partials/includes/**/*.html"
|
"!" + paths.tmp + "partials/includes/**/*.html",
|
||||||
|
"!" + paths.tmp + "/modules/**/includes/**/*.html"
|
||||||
];
|
];
|
||||||
|
|
||||||
paths.images = paths.app + "images/**/*";
|
paths.images = paths.app + "images/**/*";
|
||||||
|
|
Loading…
Reference in New Issue