Create vote and watch buttons in story, task and issue detail pages
parent
2a4b3ab81a
commit
d1349c4272
|
@ -26,6 +26,7 @@ toString = @.taiga.toString
|
|||
joinStr = @.taiga.joinStr
|
||||
groupBy = @.taiga.groupBy
|
||||
bindOnce = @.taiga.bindOnce
|
||||
bindMethods = @.taiga.bindMethods
|
||||
|
||||
module = angular.module("taigaIssues")
|
||||
|
||||
|
@ -52,6 +53,8 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
|
||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
|
||||
@log, @appMetaService, @analytics, @navUrls, @translate) ->
|
||||
bindMethods(@)
|
||||
|
||||
@scope.issueRef = @params.issueref
|
||||
@scope.sectionName = @translate.instant("ISSUES.SECTION_NAME")
|
||||
@.initializeEventHandlers()
|
||||
|
@ -144,6 +147,49 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
@.fillUsersAndRoles(project.members, project.roles)
|
||||
@.loadIssue()
|
||||
|
||||
###
|
||||
# Note: This methods (onUpvote() and onDownvote()) are related to tg-vote-button.
|
||||
# See app/modules/components/vote-button for more info
|
||||
###
|
||||
onUpvote: ->
|
||||
onSuccess = =>
|
||||
@.loadIssue()
|
||||
@rootscope.$broadcast("object:updated")
|
||||
onError = =>
|
||||
@confirm.notify("error")
|
||||
|
||||
return @rs.issues.upvote(@scope.issueId).then(onSuccess, onError)
|
||||
|
||||
onDownvote: ->
|
||||
onSuccess = =>
|
||||
@.loadIssue()
|
||||
@rootscope.$broadcast("object:updated")
|
||||
onError = =>
|
||||
@confirm.notify("error")
|
||||
|
||||
return @rs.issues.downvote(@scope.issueId).then(onSuccess, onError)
|
||||
|
||||
###
|
||||
# Note: This methods (onWatch() and onUnwatch()) are related to tg-watch-button.
|
||||
# See app/modules/components/watch-button for more info
|
||||
###
|
||||
onWatch: ->
|
||||
onSuccess = =>
|
||||
@.loadIssue()
|
||||
@rootscope.$broadcast("object:updated")
|
||||
onError = =>
|
||||
@confirm.notify("error")
|
||||
|
||||
return @rs.issues.watch(@scope.issueId).then(onSuccess, onError)
|
||||
|
||||
onUnwatch: ->
|
||||
onSuccess = =>
|
||||
@.loadIssue()
|
||||
@rootscope.$broadcast("object:updated")
|
||||
onError = =>
|
||||
@confirm.notify("error")
|
||||
|
||||
return @rs.issues.unwatch(@scope.issueId).then(onSuccess, onError)
|
||||
|
||||
module.controller("IssueDetailController", IssueDetailController)
|
||||
|
||||
|
|
|
@ -37,8 +37,11 @@ urls = {
|
|||
"users-change-password": "/users/change_password"
|
||||
"users-change-email": "/users/change_email"
|
||||
"users-cancel-account": "/users/cancel"
|
||||
"contacts": "/users/%s/contacts"
|
||||
"stats": "/users/%s/stats"
|
||||
"user-stats": "/users/%s/stats"
|
||||
"user-liked": "/users/%s/liked"
|
||||
"user-voted": "/users/%s/voted"
|
||||
"user-watched": "/users/%s/watched"
|
||||
"user-contacts": "/users/%s/contacts"
|
||||
|
||||
# User - Notification
|
||||
"permissions": "/permissions"
|
||||
|
@ -63,6 +66,10 @@ urls = {
|
|||
"project-templates": "/project-templates"
|
||||
"project-modules": "/projects/%s/modules"
|
||||
"bulk-update-projects-order": "/projects/bulk_update_order"
|
||||
"project-like": "/projects/%s/like"
|
||||
"project-unlike": "/projects/%s/unlike"
|
||||
"project-watch": "/projects/%s/watch"
|
||||
"project-unwatch": "/projects/%s/unwatch"
|
||||
|
||||
# Project Values - Choises
|
||||
"userstory-statuses": "/userstory-statuses"
|
||||
|
@ -83,16 +90,28 @@ urls = {
|
|||
"bulk-update-us-sprint-order": "/userstories/bulk_update_sprint_order"
|
||||
"bulk-update-us-kanban-order": "/userstories/bulk_update_kanban_order"
|
||||
"userstories-filters": "/userstories/filters_data"
|
||||
"userstory-upvote": "/userstories/%s/upvote"
|
||||
"userstory-downvote": "/userstories/%s/downvote"
|
||||
"userstory-watch": "/userstories/%s/watch"
|
||||
"userstory-unwatch": "/userstories/%s/unwatch"
|
||||
|
||||
# Tasks
|
||||
"tasks": "/tasks"
|
||||
"bulk-create-tasks": "/tasks/bulk_create"
|
||||
"bulk-update-task-taskboard-order": "/tasks/bulk_update_taskboard_order"
|
||||
"task-upvote": "/tasks/%s/upvote"
|
||||
"task-downvote": "/tasks/%s/downvote"
|
||||
"task-watch": "/tasks/%s/watch"
|
||||
"task-unwatch": "/tasks/%s/unwatch"
|
||||
|
||||
# Issues
|
||||
"issues": "/issues"
|
||||
"bulk-create-issues": "/issues/bulk_create"
|
||||
"issues-filters": "/issues/filters_data"
|
||||
"issue-upvote": "/issues/%s/upvote"
|
||||
"issue-downvote": "/issues/%s/downvote"
|
||||
"issue-watch": "/issues/%s/watch"
|
||||
"issue-unwatch": "/issues/%s/unwatch"
|
||||
|
||||
# Wiki pages
|
||||
"wiki": "/wiki"
|
||||
|
|
|
@ -55,6 +55,22 @@ resourceProvider = ($repo, $http, $urls, $storage, $q) ->
|
|||
params = {project_id: projectId, bulk_issues: data}
|
||||
return $http.post(url, params)
|
||||
|
||||
service.upvote = (issueId) ->
|
||||
url = $urls.resolve("issue-upvote", issueId)
|
||||
return $http.post(url)
|
||||
|
||||
service.downvote = (issueId) ->
|
||||
url = $urls.resolve("issue-downvote", issueId)
|
||||
return $http.post(url)
|
||||
|
||||
service.watch = (issueId) ->
|
||||
url = $urls.resolve("issue-watch", issueId)
|
||||
return $http.post(url)
|
||||
|
||||
service.unwatch = (issueId) ->
|
||||
url = $urls.resolve("issue-unwatch", issueId)
|
||||
return $http.post(url)
|
||||
|
||||
service.stats = (projectId) ->
|
||||
return $repo.queryOneRaw("projects", "#{projectId}/issues_stats")
|
||||
|
||||
|
|
|
@ -57,6 +57,22 @@ resourceProvider = ($repo, $http, $urls, $storage) ->
|
|||
return $http.post(url, params).then (result) ->
|
||||
return result.data
|
||||
|
||||
service.upvote = (taskId) ->
|
||||
url = $urls.resolve("task-upvote", taskId)
|
||||
return $http.post(url)
|
||||
|
||||
service.downvote = (taskId) ->
|
||||
url = $urls.resolve("task-downvote", taskId)
|
||||
return $http.post(url)
|
||||
|
||||
service.watch = (taskId) ->
|
||||
url = $urls.resolve("task-watch", taskId)
|
||||
return $http.post(url)
|
||||
|
||||
service.unwatch = (taskId) ->
|
||||
url = $urls.resolve("task-unwatch", taskId)
|
||||
return $http.post(url)
|
||||
|
||||
service.bulkUpdateTaskTaskboardOrder = (projectId, data) ->
|
||||
url = $urls.resolve("bulk-update-task-taskboard-order")
|
||||
params = {project_id: projectId, bulk_tasks: data}
|
||||
|
|
|
@ -67,6 +67,22 @@ resourceProvider = ($repo, $http, $urls, $storage) ->
|
|||
|
||||
return $http.post(url, data)
|
||||
|
||||
service.upvote = (userStoryId) ->
|
||||
url = $urls.resolve("userstory-upvote", userStoryId)
|
||||
return $http.post(url)
|
||||
|
||||
service.downvote = (userStoryId) ->
|
||||
url = $urls.resolve("userstory-downvote", userStoryId)
|
||||
return $http.post(url)
|
||||
|
||||
service.watch = (userStoryId) ->
|
||||
url = $urls.resolve("userstory-watch", userStoryId)
|
||||
return $http.post(url)
|
||||
|
||||
service.unwatch = (userStoryId) ->
|
||||
url = $urls.resolve("userstory-unwatch", userStoryId)
|
||||
return $http.post(url)
|
||||
|
||||
service.bulkUpdateBacklogOrder = (projectId, data) ->
|
||||
url = $urls.resolve("bulk-update-us-backlog-order")
|
||||
params = {project_id: projectId, bulk_stories: data}
|
||||
|
|
|
@ -23,6 +23,7 @@ taiga = @.taiga
|
|||
|
||||
mixOf = @.taiga.mixOf
|
||||
groupBy = @.taiga.groupBy
|
||||
bindMethods = @.taiga.bindMethods
|
||||
|
||||
module = angular.module("taigaTasks")
|
||||
|
||||
|
@ -50,6 +51,8 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
|
||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
|
||||
@log, @appMetaService, @navUrls, @analytics, @translate) ->
|
||||
bindMethods(@)
|
||||
|
||||
@scope.taskRef = @params.taskref
|
||||
@scope.sectionName = @translate.instant("TASK.SECTION_NAME")
|
||||
@.initializeEventHandlers()
|
||||
|
@ -145,6 +148,50 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
@.fillUsersAndRoles(project.members, project.roles)
|
||||
@.loadTask().then(=> @q.all([@.loadSprint(), @.loadUserStory()]))
|
||||
|
||||
###
|
||||
# Note: This methods (onUpvote() and onDownvote()) are related to tg-vote-button.
|
||||
# See app/modules/components/vote-button for more info
|
||||
###
|
||||
onUpvote: ->
|
||||
onSuccess = =>
|
||||
@.loadTask()
|
||||
@rootscope.$broadcast("object:updated")
|
||||
onError = =>
|
||||
@confirm.notify("error")
|
||||
|
||||
return @rs.tasks.upvote(@scope.taskId).then(onSuccess, onError)
|
||||
|
||||
onDownvote: ->
|
||||
onSuccess = =>
|
||||
@.loadTask()
|
||||
@rootscope.$broadcast("object:updated")
|
||||
onError = =>
|
||||
@confirm.notify("error")
|
||||
|
||||
return @rs.tasks.downvote(@scope.taskId).then(onSuccess, onError)
|
||||
|
||||
###
|
||||
# Note: This methods (onWatch() and onUnwatch()) are related to tg-watch-button.
|
||||
# See app/modules/components/watch-button for more info
|
||||
###
|
||||
onWatch: ->
|
||||
onSuccess = =>
|
||||
@.loadTask()
|
||||
@rootscope.$broadcast("object:updated")
|
||||
onError = =>
|
||||
@confirm.notify("error")
|
||||
|
||||
return @rs.tasks.watch(@scope.taskId).then(onSuccess, onError)
|
||||
|
||||
onUnwatch: ->
|
||||
onSuccess = =>
|
||||
@.loadTask()
|
||||
@rootscope.$broadcast("object:updated")
|
||||
onError = =>
|
||||
@confirm.notify("error")
|
||||
|
||||
return @rs.tasks.unwatch(@scope.taskId).then(onSuccess, onError)
|
||||
|
||||
module.controller("TaskDetailController", TaskDetailController)
|
||||
|
||||
|
||||
|
@ -195,7 +242,7 @@ module.directive("tgTaskStatusDisplay", ["$tgTemplate", "$compile", TaskStatusDi
|
|||
## Task status button directive
|
||||
#############################################################################
|
||||
|
||||
TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $compile, $translate) ->
|
||||
TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $compile, $translate, $template) ->
|
||||
# Display the status of Task and you can edit it.
|
||||
#
|
||||
# Example:
|
||||
|
@ -206,21 +253,7 @@ TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $co
|
|||
# - scope.statusById object
|
||||
# - $scope.project.my_permissions
|
||||
|
||||
template = _.template("""
|
||||
<div class="status-data <% if(editable){ %>clickable<% }%>">
|
||||
<span class="level" style="background-color:<%- status.color %>"></span>
|
||||
<span class="status-status"><%- status.name %></span>
|
||||
<% if(editable){ %><span class="icon icon-arrow-bottom"></span><% }%>
|
||||
<span class="level-name" translate="COMMON.FIELDS.STATUS"></span>
|
||||
|
||||
<ul class="popover pop-status">
|
||||
<% _.each(statuses, function(st) { %>
|
||||
<li><a href="" class="status" title="<%- st.name %>"
|
||||
data-status-id="<%- st.id %>"><%- st.name %></a></li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
</div>
|
||||
""")
|
||||
template = $template.get("us/us-status-button.html", true)
|
||||
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
isEditable = ->
|
||||
|
@ -288,7 +321,7 @@ TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $co
|
|||
}
|
||||
|
||||
module.directive("tgTaskStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue",
|
||||
"$compile", "$translate", TaskStatusButtonDirective])
|
||||
"$compile", "$translate", "$tgTemplate", TaskStatusButtonDirective])
|
||||
|
||||
|
||||
TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qqueue, $compile) ->
|
||||
|
|
|
@ -24,6 +24,7 @@ taiga = @.taiga
|
|||
mixOf = @.taiga.mixOf
|
||||
groupBy = @.taiga.groupBy
|
||||
bindOnce = @.taiga.bindOnce
|
||||
bindMethods = @.taiga.bindMethods
|
||||
|
||||
module = angular.module("taigaUserStories")
|
||||
|
||||
|
@ -50,6 +51,8 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
|
||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
|
||||
@log, @appMetaService, @navUrls, @analytics, @translate) ->
|
||||
bindMethods(@)
|
||||
|
||||
@scope.usRef = @params.usref
|
||||
@scope.sectionName = @translate.instant("US.SECTION_NAME")
|
||||
@.initializeEventHandlers()
|
||||
|
@ -182,6 +185,50 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
|||
@.fillUsersAndRoles(project.members, project.roles)
|
||||
@.loadUs().then(=> @q.all([@.loadSprint(), @.loadTasks()]))
|
||||
|
||||
###
|
||||
# Note: This methods (onUpvote() and onDownvote()) are related to tg-vote-button.
|
||||
# See app/modules/components/vote-button for more info
|
||||
###
|
||||
onUpvote: ->
|
||||
onSuccess = =>
|
||||
@.loadUs()
|
||||
@rootscope.$broadcast("object:updated")
|
||||
onError = =>
|
||||
@confirm.notify("error")
|
||||
|
||||
return @rs.userstories.upvote(@scope.usId).then(onSuccess, onError)
|
||||
|
||||
onDownvote: ->
|
||||
onSuccess = =>
|
||||
@.loadUs()
|
||||
@rootscope.$broadcast("object:updated")
|
||||
onError = =>
|
||||
@confirm.notify("error")
|
||||
|
||||
return @rs.userstories.downvote(@scope.usId).then(onSuccess, onError)
|
||||
|
||||
###
|
||||
# Note: This methods (onWatch() and onUnwatch()) are related to tg-watch-button.
|
||||
# See app/modules/components/watch-button for more info
|
||||
###
|
||||
onWatch: ->
|
||||
onSuccess = =>
|
||||
@.loadUs()
|
||||
@rootscope.$broadcast("object:updated")
|
||||
onError = =>
|
||||
@confirm.notify("error")
|
||||
|
||||
return @rs.userstories.watch(@scope.usId).then(onSuccess, onError)
|
||||
|
||||
onUnwatch: ->
|
||||
onSuccess = =>
|
||||
@.loadUs()
|
||||
@rootscope.$broadcast("object:updated")
|
||||
onError = =>
|
||||
@confirm.notify("error")
|
||||
|
||||
return @rs.userstories.unwatch(@scope.usId).then(onSuccess, onError)
|
||||
|
||||
module.controller("UserStoryDetailController", UserStoryDetailController)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
class VoteButtonController
|
||||
@.$inject = [
|
||||
"tgCurrentUserService",
|
||||
]
|
||||
|
||||
constructor: (@currentUserService) ->
|
||||
@.user = @currentUserService.getUser()
|
||||
@.isMouseOver = false
|
||||
@.loading = false
|
||||
|
||||
showTextWhenMouseIsOver: ->
|
||||
@.isMouseOver = true
|
||||
|
||||
showTextWhenMouseIsLeave: ->
|
||||
@.isMouseOver = false
|
||||
|
||||
toggleVote: ->
|
||||
@.loading = true
|
||||
|
||||
if not @.item.is_voter
|
||||
promise = @._upvote()
|
||||
else
|
||||
promise = @._downvote()
|
||||
|
||||
promise.finally () => @.loading = false
|
||||
|
||||
return promise
|
||||
|
||||
_upvote: ->
|
||||
@.onUpvote().then =>
|
||||
@.showTextWhenMouseIsLeave()
|
||||
|
||||
_downvote: ->
|
||||
@.onDownvote()
|
||||
|
||||
angular.module("taigaComponents").controller("VoteButton", VoteButtonController)
|
|
@ -0,0 +1,82 @@
|
|||
describe "VoteButton", ->
|
||||
provide = null
|
||||
$controller = null
|
||||
$rootScope = null
|
||||
mocks = {}
|
||||
|
||||
_mockCurrentUser = () ->
|
||||
mocks.currentUser = {
|
||||
getUser: sinon.stub()
|
||||
}
|
||||
|
||||
provide.value "tgCurrentUserService", mocks.currentUser
|
||||
|
||||
_mocks = ->
|
||||
mocks = {
|
||||
onUpvote: sinon.stub(),
|
||||
onDownvote: sinon.stub()
|
||||
}
|
||||
|
||||
module ($provide) ->
|
||||
provide = $provide
|
||||
_mockCurrentUser()
|
||||
return null
|
||||
|
||||
_inject = (callback) ->
|
||||
inject (_$controller_, _$rootScope_) ->
|
||||
$rootScope = _$rootScope_
|
||||
$controller = _$controller_
|
||||
|
||||
_setup = ->
|
||||
_mocks()
|
||||
_inject()
|
||||
|
||||
beforeEach ->
|
||||
module "taigaComponents"
|
||||
_setup()
|
||||
|
||||
it "upvote", (done) ->
|
||||
$scope = $rootScope.$new()
|
||||
|
||||
mocks.onUpvote = sinon.stub().promise()
|
||||
|
||||
ctrl = $controller("VoteButton", $scope, {
|
||||
item: {is_voter: false}
|
||||
onUpvote: mocks.onUpvote
|
||||
onDownvote: mocks.onDownvote
|
||||
})
|
||||
|
||||
promise = ctrl.toggleVote()
|
||||
|
||||
expect(ctrl.loading).to.be.true;
|
||||
|
||||
mocks.onUpvote.resolve()
|
||||
|
||||
promise.finally () ->
|
||||
expect(mocks.onUpvote).to.be.calledOnce
|
||||
expect(ctrl.loading).to.be.false;
|
||||
|
||||
done()
|
||||
|
||||
it "downvote", (done) ->
|
||||
$scope = $rootScope.$new()
|
||||
|
||||
mocks.onDownvote = sinon.stub().promise()
|
||||
|
||||
ctrl = $controller("VoteButton", $scope, {
|
||||
item: {is_voter: true}
|
||||
onUpvote: mocks.onUpvote
|
||||
onDownvote: mocks.onDownvote
|
||||
})
|
||||
|
||||
promise = ctrl.toggleVote()
|
||||
|
||||
expect(ctrl.loading).to.be.true;
|
||||
|
||||
mocks.onDownvote.resolve()
|
||||
|
||||
promise.finally () ->
|
||||
expect(mocks.onDownvote).to.be.calledOnce
|
||||
expect(ctrl.loading).to.be.false;
|
||||
|
||||
done()
|
|
@ -0,0 +1,14 @@
|
|||
VoteButtonDirective = ->
|
||||
return {
|
||||
scope: {}
|
||||
controller: "VoteButton",
|
||||
bindToController: {
|
||||
item: "=",
|
||||
onUpvote: "=",
|
||||
onDownvote: "="
|
||||
}
|
||||
controllerAs: "vm",
|
||||
templateUrl: "components/vote-button/vote-button.html",
|
||||
}
|
||||
|
||||
angular.module("taigaComponents").directive("tgVoteButton", VoteButtonDirective)
|
|
@ -0,0 +1,15 @@
|
|||
a(
|
||||
href=""
|
||||
title="{{ 'COMMON.VOTE_BUTTON.BUTTON_TITLE' | translate }}"
|
||||
ng-if="::vm.user"
|
||||
ng-click="vm.toggleVote()"
|
||||
ng-class="{'active': vm.item.is_voter, 'is-hover': vm.item.is_voter && vm.isMouseOver, 'disable': !vm.user}"
|
||||
ng-mouseover="vm.showTextWhenMouseIsOver()"
|
||||
ng-mouseleave="vm.showTextWhenMouseIsLeave()"
|
||||
)
|
||||
span.track-icon
|
||||
include ../../../svg/upvote.svg
|
||||
span.track-button-counter(
|
||||
title="{{ 'COMMON.VOTE_BUTTON.COUNTER_TITLE'|translate:{total:vm.item.total_voters||0}:'messageformat' }}",
|
||||
tg-loading="vm.loading"
|
||||
) {{ vm.item.total_voters }}
|
|
@ -0,0 +1,36 @@
|
|||
class WatchButtonController
|
||||
@.$inject = [
|
||||
"tgCurrentUserService",
|
||||
]
|
||||
|
||||
constructor: (@currentUserService) ->
|
||||
@.user = @currentUserService.getUser()
|
||||
@.isMouseOver = false
|
||||
@.loading = false
|
||||
|
||||
showTextWhenMouseIsOver: ->
|
||||
@.isMouseOver = true
|
||||
|
||||
showTextWhenMouseIsLeave: ->
|
||||
@.isMouseOver = false
|
||||
|
||||
toggleWatch: ->
|
||||
@.loading = true
|
||||
|
||||
if not @.item.is_watcher
|
||||
promise = @._watch()
|
||||
else
|
||||
promise = @._unwatch()
|
||||
|
||||
promise.finally () => @.loading = false
|
||||
|
||||
return promise
|
||||
|
||||
_watch: ->
|
||||
@.onWatch().then =>
|
||||
@.showTextWhenMouseIsLeave()
|
||||
|
||||
_unwatch: ->
|
||||
@.onUnwatch()
|
||||
|
||||
angular.module("taigaComponents").controller("WatchButton", WatchButtonController)
|
|
@ -0,0 +1,83 @@
|
|||
describe "WatchButton", ->
|
||||
provide = null
|
||||
$controller = null
|
||||
$rootScope = null
|
||||
mocks = {}
|
||||
|
||||
_mockCurrentUser = () ->
|
||||
mocks.currentUser = {
|
||||
getUser: sinon.stub()
|
||||
}
|
||||
|
||||
provide.value "tgCurrentUserService", mocks.currentUser
|
||||
|
||||
_mocks = ->
|
||||
mocks = {
|
||||
onWatch: sinon.stub(),
|
||||
onUnwatch: sinon.stub()
|
||||
}
|
||||
|
||||
module ($provide) ->
|
||||
provide = $provide
|
||||
_mockCurrentUser()
|
||||
return null
|
||||
|
||||
_inject = (callback) ->
|
||||
inject (_$controller_, _$rootScope_) ->
|
||||
$rootScope = _$rootScope_
|
||||
$controller = _$controller_
|
||||
|
||||
_setup = ->
|
||||
_mocks()
|
||||
_inject()
|
||||
|
||||
beforeEach ->
|
||||
module "taigaComponents"
|
||||
_setup()
|
||||
|
||||
it "watch", (done) ->
|
||||
$scope = $rootScope.$new()
|
||||
|
||||
mocks.onWatch = sinon.stub().promise()
|
||||
|
||||
ctrl = $controller("WatchButton", $scope, {
|
||||
item: {is_watcher: false}
|
||||
onWatch: mocks.onWatch
|
||||
onUnwatch: mocks.onUnwatch
|
||||
})
|
||||
|
||||
|
||||
promise = ctrl.toggleWatch()
|
||||
|
||||
expect(ctrl.loading).to.be.true;
|
||||
|
||||
mocks.onWatch.resolve()
|
||||
|
||||
promise.finally () ->
|
||||
expect(mocks.onWatch).to.be.calledOnce
|
||||
expect(ctrl.loading).to.be.false;
|
||||
|
||||
done()
|
||||
|
||||
it "unwatch", (done) ->
|
||||
$scope = $rootScope.$new()
|
||||
|
||||
mocks.onUnwatch = sinon.stub().promise()
|
||||
|
||||
ctrl = $controller("WatchButton", $scope, {
|
||||
item: {is_watcher: true}
|
||||
onWatch: mocks.onWatch
|
||||
onUnwatch: mocks.onUnwatch
|
||||
})
|
||||
|
||||
promise = ctrl.toggleWatch()
|
||||
|
||||
expect(ctrl.loading).to.be.true;
|
||||
|
||||
mocks.onUnwatch.resolve()
|
||||
|
||||
promise.finally () ->
|
||||
expect(mocks.onUnwatch).to.be.calledOnce
|
||||
expect(ctrl.loading).to.be.false;
|
||||
|
||||
done()
|
|
@ -0,0 +1,14 @@
|
|||
WatchButtonDirective = ->
|
||||
return {
|
||||
scope: {}
|
||||
controller: "WatchButton",
|
||||
bindToController: {
|
||||
item: "=",
|
||||
onWatch: "=",
|
||||
onUnwatch: "="
|
||||
}
|
||||
controllerAs: "vm",
|
||||
templateUrl: "components/watch-button/watch-button.html",
|
||||
}
|
||||
|
||||
angular.module("taigaComponents").directive("tgWatchButton", WatchButtonDirective)
|
|
@ -0,0 +1,44 @@
|
|||
mixin counter
|
||||
span.track-button-counter(
|
||||
title="{{ 'COMMON.WATCH_BUTTON.COUNTER_TITLE'|translate:{total:vm.item.watchers.length||0}:'messageformat' }}",
|
||||
tg-loading="vm.loading"
|
||||
)
|
||||
| {{ vm.item.watchers.length }}
|
||||
|
||||
|
||||
//- Registered user button
|
||||
a.track-button.watch-button.watch-container(
|
||||
href=""
|
||||
title="{{ 'COMMON.WATCH_BUTTON.BUTTON_TITLE' | translate }}"
|
||||
ng-if="::vm.user"
|
||||
ng-click="vm.toggleWatch()"
|
||||
ng-class="{'active': vm.item.is_watcher, 'is-hover': vm.item.is_watcher && vm.isMouseOver}"
|
||||
ng-mouseover="vm.showTextWhenMouseIsOver()"
|
||||
ng-mouseleave="vm.showTextWhenMouseIsLeave()"
|
||||
)
|
||||
span.track-inner
|
||||
span.track-icon
|
||||
include ../../../svg/watch.svg
|
||||
span(
|
||||
ng-if="!vm.item.is_watcher",
|
||||
translate="COMMON.WATCH_BUTTON.WATCH"
|
||||
)
|
||||
span(
|
||||
ng-if="vm.item.is_watcher && !vm.isMouseOver",
|
||||
translate="COMMON.WATCH_BUTTON.WATCHING"
|
||||
)
|
||||
span(
|
||||
ng-if="vm.item.is_watcher && vm.isMouseOver",
|
||||
translate="COMMON.WATCH_BUTTON.UNWATCH"
|
||||
)
|
||||
+counter
|
||||
|
||||
//- Anonymous user button
|
||||
span.track-button.watch-button.watch-container(
|
||||
ng-if="::!vm.user"
|
||||
)
|
||||
span.track-inner
|
||||
span.track-icon
|
||||
include ../../../svg/watch.svg
|
||||
span(translate="COMMON.WATCH_BUTTON.WATCHERS")
|
||||
+counter
|
|
@ -1,74 +1,115 @@
|
|||
doctype html
|
||||
|
||||
div.wrapper(ng-controller="IssueDetailController as ctrl",
|
||||
ng-init="section='issues'")
|
||||
div.wrapper(
|
||||
ng-controller="IssueDetailController as ctrl",
|
||||
ng-init="section='issues'"
|
||||
)
|
||||
tg-project-menu
|
||||
|
||||
div.main.us-detail
|
||||
div.us-detail-header.header-with-actions
|
||||
include ../includes/components/mainTitle
|
||||
|
||||
section.us-story-main-data
|
||||
div.us-title(ng-class="{blocked: issue.is_blocked}")
|
||||
h2.us-title-text
|
||||
span.us-number(tg-bo-ref="issue.ref")
|
||||
span.us-name(tg-editable-subject, ng-model="issue", required-perm="modify_issue")
|
||||
header
|
||||
tg-vote-button.upvote-btn(
|
||||
item="issue"
|
||||
on-upvote="ctrl.onUpvote"
|
||||
on-downvote="ctrl.onDownvote"
|
||||
)
|
||||
.us-title(ng-class="{blocked: issue.is_blocked}")
|
||||
h2.us-title-text
|
||||
span.us-number(tg-bo-ref="issue.ref")
|
||||
span.us-name(tg-editable-subject, ng-model="issue", required-perm="modify_issue")
|
||||
|
||||
p.us-related-task(ng-if="issue.generated_user_stories.length")
|
||||
| {{ 'ISSUES.PROMOTED'|translate }}
|
||||
a(ng-repeat="us in issue.generated_user_stories",
|
||||
tg-check-permission="view_us", href="",
|
||||
tg-bo-title="'#' + us.ref + ' ' + us.subject",
|
||||
tg-nav="project-userstories-detail:project=project.slug,ref=us.ref")
|
||||
span(tg-bo-ref="us.ref")
|
||||
p.us-related-task(ng-if="issue.generated_user_stories.length")
|
||||
| {{ 'ISSUES.PROMOTED'|translate }}
|
||||
a(
|
||||
href=""
|
||||
ng-repeat="us in issue.generated_user_stories"
|
||||
tg-check-permission="view_us"
|
||||
tg-bo-title="'#' + us.ref + ' ' + us.subject"
|
||||
tg-nav="project-userstories-detail:project=project.slug,ref=us.ref"
|
||||
)
|
||||
span(tg-bo-ref="us.ref")
|
||||
|
||||
p.external-reference(ng-if="issue.external_reference")
|
||||
| {{ 'ISSUES.EXTERNAL_REFERENCE'|translate }}
|
||||
a(target="_blank", tg-bo-href="issue.external_reference[1]",
|
||||
title="{{'ISSUES.GO_TO_EXTERNAL_REFERENCE' | translate}}")
|
||||
span {{ issue.external_reference[1] }}
|
||||
p.external-reference(ng-if="issue.external_reference")
|
||||
| {{ 'ISSUES.EXTERNAL_REFERENCE'|translate }}
|
||||
a(
|
||||
target="_blank"
|
||||
tg-bo-href="issue.external_reference[1]"
|
||||
title="{{'ISSUES.GO_TO_EXTERNAL_REFERENCE' | translate}}"
|
||||
)
|
||||
span {{ issue.external_reference[1] }}
|
||||
|
||||
p.block-desc-container(ng-show="issue.is_blocked")
|
||||
span.block-description-title(translate="COMMON.BLOCKED")
|
||||
span.block-description(ng-bind="issue.blocked_note || ('ISSUES.BLOCKED' | translate)")
|
||||
p.block-desc-container(ng-show="issue.is_blocked")
|
||||
span.block-description-title(translate="COMMON.BLOCKED")
|
||||
span.block-description(ng-bind="issue.blocked_note || ('ISSUES.BLOCKED' | translate)")
|
||||
|
||||
div.issue-nav
|
||||
a.icon.icon-arrow-left(ng-show="previousUrl", tg-bo-href="previousUrl",
|
||||
title="{{'ISSUES.TITLE_PREVIOUS_ISSUE' | translate}}")
|
||||
a.icon.icon-arrow-right(ng-show="nextUrl", tg-bo-href="nextUrl",
|
||||
title="{{'ISSUES.TITLE_NEXT_ISSUE' | translate}}")
|
||||
div.issue-nav
|
||||
a.icon.icon-arrow-left(
|
||||
ng-show="previousUrl"
|
||||
tg-bo-href="previousUrl"
|
||||
title="{{'ISSUES.TITLE_PREVIOUS_ISSUE' | translate}}"
|
||||
)
|
||||
a.icon.icon-arrow-right(
|
||||
ng-show="nextUrl"
|
||||
tg-bo-href="nextUrl"
|
||||
title="{{'ISSUES.TITLE_NEXT_ISSUE' | translate}}"
|
||||
)
|
||||
|
||||
div.tags-block(tg-tag-line, ng-model="issue", required-perm="modify_issue")
|
||||
|
||||
section.duty-content(tg-editable-description, ng-model="issue", required-perm="modify_issue")
|
||||
|
||||
// Custom Fields
|
||||
tg-custom-attributes-values(ng-model="issue", type="issue", project="project", required-edition-perm="modify_issue")
|
||||
tg-custom-attributes-values(
|
||||
ng-model="issue"
|
||||
type="issue"
|
||||
project="project"
|
||||
required-edition-perm="modify_issue"
|
||||
)
|
||||
|
||||
tg-attachments(ng-model="issue", type="issue")
|
||||
tg-history(ng-model="issue", type="issue")
|
||||
|
||||
sidebar.menu-secondary.sidebar
|
||||
section.us-status
|
||||
h1(tg-issue-status-display, ng-model="issue")
|
||||
tg-created-by-display.us-created-by(ng-model="issue")
|
||||
div.duty-data-container
|
||||
div.duty-data(tg-issue-type-button, ng-model="issue")
|
||||
div.duty-data(tg-issue-severity-button, ng-model="issue")
|
||||
div.duty-data(tg-issue-priority-button, ng-model="issue")
|
||||
div.duty-data(tg-issue-status-button, ng-model="issue")
|
||||
sidebar.menu-secondary.sidebar.ticket-data
|
||||
section.status
|
||||
.ticket-title(tg-issue-status-display, ng-model="issue")
|
||||
tg-created-by-display.ticket-created-by(ng-model="issue")
|
||||
div.ticket-data-container
|
||||
div.ticket-status(tg-issue-type-button, ng-model="issue")
|
||||
div.ticket-status(tg-issue-severity-button, ng-model="issue")
|
||||
div.ticket-status(tg-issue-priority-button, ng-model="issue")
|
||||
div.ticket-status(tg-issue-status-button, ng-model="issue")
|
||||
|
||||
section.duty-assigned-to(tg-assigned-to, ng-model="issue", required-perm="modify_issue")
|
||||
section.ticket-assigned-to(tg-assigned-to, ng-model="issue", required-perm="modify_issue")
|
||||
|
||||
section.watchers(tg-watchers, ng-model="issue", required-perm="modify_issue")
|
||||
section.track-buttons-container.ticket-track-buttons
|
||||
|
||||
section.us-detail-settings
|
||||
tg-promote-issue-to-us-button(tg-check-permission="add_us", ng-model="issue")
|
||||
tg-block-button(tg-check-permission="modify_issue", ng-model="issue")
|
||||
tg-delete-button(tg-check-permission="delete_issue",
|
||||
on-delete-title="{{'ISSUES.ACTION_DELETE' | translate}}",
|
||||
on-delete-go-to-url="onDeleteGoToUrl",
|
||||
ng-model="issue")
|
||||
div.watch-button
|
||||
tg-watch-button(
|
||||
item="issue"
|
||||
on-watch="ctrl.onWatch"
|
||||
on-unwatch="ctrl.onUnwatch"
|
||||
)
|
||||
|
||||
div.lightbox.lightbox-block(tg-lb-block, title="ISSUES.LIGHTBOX_TITLE_BLOKING_ISSUE", ng-model="issue")
|
||||
div.ticket-watchers(
|
||||
tg-watchers
|
||||
ng-model="issue"
|
||||
required-perm="modify_issue"
|
||||
)
|
||||
|
||||
section.ticket-detail-settings
|
||||
tg-promote-issue-to-us-button(tg-check-permission="add_us", ng-model="issue")
|
||||
tg-block-button(tg-check-permission="modify_issue", ng-model="issue")
|
||||
tg-delete-button(
|
||||
tg-check-permission="delete_issue",
|
||||
on-delete-title="{{'ISSUES.ACTION_DELETE' | translate}}",
|
||||
on-delete-go-to-url="onDeleteGoToUrl",
|
||||
ng-model="issue"
|
||||
)
|
||||
|
||||
div.lightbox.lightbox-block(tg-lb-block, ng-model="issue", title="ISSUES.LIGHTBOX_TITLE_BLOKING_ISSUE")
|
||||
div.lightbox.lightbox-select-user(tg-lb-assignedto)
|
||||
div.lightbox.lightbox-select-user(tg-lb-watchers)
|
||||
|
|
|
@ -1,76 +1,130 @@
|
|||
doctype html
|
||||
|
||||
div.wrapper(ng-controller="TaskDetailController as ctrl",
|
||||
ng-init="section='backlog-kanban'")
|
||||
div.wrapper(
|
||||
ng-controller="TaskDetailController as ctrl"
|
||||
ng-init="section='backlog-kanban'"
|
||||
)
|
||||
tg-project-menu
|
||||
|
||||
div.main.us-detail
|
||||
div.us-detail-header.header-with-actions
|
||||
include ../includes/components/mainTitle
|
||||
.action-buttons
|
||||
a.button-gray(
|
||||
tg-check-permission="view_milestones",
|
||||
href="", title="{{'TASK.TITLE_LINK_TASKBOARD' | translate}}",
|
||||
tg-nav="project-taskboard:project=project.slug,sprint=sprint.slug",
|
||||
ng-if="sprint && project.is_backlog_activated", translate="TASK.LINK_TASKBOARD")
|
||||
href=""
|
||||
title="{{'TASK.TITLE_LINK_TASKBOARD' | translate}}"
|
||||
tg-check-permission="view_milestones"
|
||||
tg-nav="project-taskboard:project=project.slug,sprint=sprint.slug"
|
||||
ng-if="sprint && project.is_backlog_activated"
|
||||
translate="TASK.LINK_TASKBOARD"
|
||||
)
|
||||
|
||||
section.us-story-main-data
|
||||
div.us-title(ng-class="{blocked: task.is_blocked}")
|
||||
h2.us-title-text
|
||||
span.us-number(tg-bo-ref="task.ref")
|
||||
span.us-name(tg-editable-subject, ng-model="task", required-perm="modify_task")
|
||||
header
|
||||
tg-vote-button.upvote-btn(
|
||||
item="task",
|
||||
on-upvote="ctrl.onUpvote",
|
||||
on-downvote="ctrl.onDownvote"
|
||||
)
|
||||
div.us-title(ng-class="{blocked: task.is_blocked}")
|
||||
h2.us-title-text
|
||||
span.us-number(tg-bo-ref="task.ref")
|
||||
span.us-name(
|
||||
tg-editable-subject
|
||||
ng-model="task"
|
||||
required-perm="modify_task"
|
||||
)
|
||||
|
||||
h3.us-related-task(ng-if="us")
|
||||
| {{ 'TASK.OWNER_US'|translate }}
|
||||
a(tg-check-permission="view_us", href="", title="{{'TASK.TITLE_LINK_GO_OWNER' | translate}}",
|
||||
tg-nav="project-userstories-detail:project=project.slug,ref=us.ref")
|
||||
span(tg-bo-ref="us.ref")
|
||||
span(tg-bo-bind="us.subject")
|
||||
h3.us-related-task(ng-if="us")
|
||||
| {{ 'TASK.OWNER_US'|translate }}
|
||||
a(
|
||||
href=""
|
||||
tg-check-permission="view_us"
|
||||
tg-nav="project-userstories-detail:project=project.slug,ref=us.ref"
|
||||
title="{{'TASK.TITLE_LINK_GO_OWNER' | translate}}"
|
||||
)
|
||||
span(tg-bo-ref="us.ref")
|
||||
span(tg-bo-bind="us.subject")
|
||||
|
||||
p.external-reference(ng-if="task.external_reference")
|
||||
a(target="_blank", tg-bo-href="task.external_reference[1]",
|
||||
title="{{'TASK.TITLE_LINK_GO_ORIGIN' | translate}}")
|
||||
| {{ "TASK.ORIGIN_US"| translate }}
|
||||
span {{ task.external_reference[1] }}
|
||||
p.external-reference(ng-if="task.external_reference")
|
||||
a(
|
||||
tg-bo-href="task.external_reference[1]",
|
||||
target="_blank"
|
||||
title="{{'TASK.TITLE_LINK_GO_ORIGIN' | translate}}"
|
||||
)
|
||||
| {{ "TASK.ORIGIN_US"| translate }}
|
||||
span {{ task.external_reference[1] }}
|
||||
|
||||
p.block-desc-container(ng-show="task.is_blocked")
|
||||
span.block-description-title(translate="COMMON.BLOCKED")
|
||||
span.block-description(ng-bind="task.blocked_note || ('TASK.BLOCKED_DESCRIPTION' | translate)")
|
||||
p.block-desc-container(ng-show="task.is_blocked")
|
||||
span.block-description-title(translate="COMMON.BLOCKED")
|
||||
span.block-description(
|
||||
ng-bind="task.blocked_note || ('TASK.BLOCKED_DESCRIPTION' | translate)"
|
||||
)
|
||||
|
||||
div.issue-nav
|
||||
a.icon.icon-arrow-left(ng-show="previousUrl", tg-bo-href="previousUrl",
|
||||
title="{{'TASK.PREVIOUS' | translate}}")
|
||||
a.icon.icon-arrow-right(ng-show="nextUrl", tg-bo-href="nextUrl",
|
||||
title="{{'TASK.NEXT' | translate}}")
|
||||
div.issue-nav
|
||||
a.icon.icon-arrow-left(
|
||||
ng-show="previousUrl"
|
||||
tg-bo-href="previousUrl"
|
||||
title="{{'TASK.PREVIOUS' | translate}}"
|
||||
)
|
||||
a.icon.icon-arrow-right(
|
||||
ng-show="nextUrl"
|
||||
tg-bo-href="nextUrl"
|
||||
title="{{'TASK.NEXT' | translate}}"
|
||||
)
|
||||
|
||||
div.tags-block(tg-tag-line, ng-model="task", required-perm="modify_task")
|
||||
|
||||
section.duty-content(tg-editable-description, ng-model="task", required-perm="modify_task")
|
||||
|
||||
// Custom Fields
|
||||
tg-custom-attributes-values(ng-model="task", type="task", project="project", required-edition-perm="modify_task")
|
||||
tg-custom-attributes-values(
|
||||
ng-model="task"
|
||||
type="task"
|
||||
project="project"
|
||||
required-edition-perm="modify_task"
|
||||
)
|
||||
|
||||
tg-attachments(ng-model="task", type="task")
|
||||
tg-history(ng-model="task", type="task")
|
||||
|
||||
sidebar.menu-secondary.sidebar
|
||||
section.us-status
|
||||
h1(tg-task-status-display, ng-model="task")
|
||||
div.us-created-by(tg-created-by-display, ng-model="task")
|
||||
div.duty-data-container
|
||||
div.duty-data(tg-task-status-button, ng-model="task")
|
||||
sidebar.menu-secondary.sidebar.ticket-data
|
||||
|
||||
section.duty-assigned-to(tg-assigned-to, ng-model="task", required-perm="modify_task")
|
||||
section.status
|
||||
|
||||
section.watchers(tg-watchers, ng-model="task", required-perm="modify_task")
|
||||
.ticket-title(tg-task-status-display, ng-model="task")
|
||||
|
||||
section.us-detail-settings
|
||||
tg-task-is-iocaine-button(ng-model="task")
|
||||
tg-block-button(tg-check-permission="modify_task", ng-model="task")
|
||||
tg-delete-button(tg-check-permission="delete_task",
|
||||
on-delete-title="{{'TASK.TITLE_DELETE_ACTION' | translate}}",
|
||||
on-delete-go-to-url="onDeleteGoToUrl",
|
||||
ng-model="task")
|
||||
.ticket-created-by(tg-created-by-display, ng-model="task")
|
||||
|
||||
div.lightbox.lightbox-block(tg-lb-block, title="TASK.LIGHTBOX_TITLE_BLOKING_TASK", ng-model="task")
|
||||
.ticket-data-container
|
||||
.ticket-status(tg-task-status-button, ng-model="task")
|
||||
|
||||
section.ticket-assigned-to(tg-assigned-to, ng-model="task", required-perm="modify_task")
|
||||
|
||||
section.track-buttons-container.ticket-track-buttons
|
||||
div.watch-button
|
||||
tg-watch-button(
|
||||
item="task"
|
||||
on-watch="ctrl.onWatch"
|
||||
on-unwatch="ctrl.onUnwatch"
|
||||
)
|
||||
|
||||
div.ticket-watchers(
|
||||
tg-watchers,
|
||||
ng-model="task",
|
||||
required-perm="modify_task"
|
||||
)
|
||||
|
||||
section.ticket-detail-settings
|
||||
tg-task-is-iocaine-button(ng-model="task")
|
||||
tg-block-button(tg-check-permission="modify_task", ng-model="task")
|
||||
tg-delete-button(
|
||||
tg-check-permission="delete_task"
|
||||
on-delete-title="{{'TASK.TITLE_DELETE_ACTION' | translate}}"
|
||||
on-delete-go-to-url="onDeleteGoToUrl"
|
||||
ng-model="task"
|
||||
)
|
||||
|
||||
div.lightbox.lightbox-block(tg-lb-block, ng-model="task", title="TASK.LIGHTBOX_TITLE_BLOKING_TASK")
|
||||
div.lightbox.lightbox-select-user(tg-lb-assignedto)
|
||||
div.lightbox.lightbox-select-user(tg-lb-watchers)
|
||||
|
|
|
@ -1,80 +1,152 @@
|
|||
doctype html
|
||||
|
||||
div.wrapper(ng-controller="UserStoryDetailController as ctrl",
|
||||
ng-init="section='backlog-kanban'")
|
||||
div.wrapper(
|
||||
ng-controller="UserStoryDetailController as ctrl",
|
||||
ng-init="section='backlog-kanban'"
|
||||
)
|
||||
tg-project-menu
|
||||
|
||||
div.main.us-detail
|
||||
div.us-detail-header.header-with-actions
|
||||
include ../includes/components/mainTitle
|
||||
.action-buttons
|
||||
a.button-gray(
|
||||
tg-check-permission="view_milestones",
|
||||
href="", title="{{'US.TITLE_LINK_TASKBOARD' | translate}}",
|
||||
tg-nav="project-taskboard:project=project.slug,sprint=sprint.slug",
|
||||
ng-if="sprint && project.is_backlog_activated", translate="US.LINK_TASKBOARD")
|
||||
href=""
|
||||
tg-check-permission="view_milestones"
|
||||
tg-nav="project-taskboard:project=project.slug,sprint=sprint.slug"
|
||||
ng-if="sprint && project.is_backlog_activated"
|
||||
title="{{'US.TITLE_LINK_TASKBOARD' | translate}}"
|
||||
translate="US.LINK_TASKBOARD"
|
||||
)
|
||||
|
||||
section.us-story-main-data
|
||||
div.us-title(ng-class="{blocked: us.is_blocked}")
|
||||
h2.us-title-text
|
||||
span.us-number(tg-bo-ref="us.ref")
|
||||
span.us-name(tg-editable-subject, ng-model="us", required-perm="modify_us")
|
||||
header
|
||||
tg-vote-button.upvote-btn(
|
||||
item="us"
|
||||
on-upvote="ctrl.onUpvote"
|
||||
on-downvote="ctrl.onDownvote"
|
||||
)
|
||||
div.us-title(ng-class="{blocked: us.is_blocked}")
|
||||
h2.us-title-text
|
||||
span.us-number(tg-bo-ref="us.ref")
|
||||
span.us-name(tg-editable-subject, ng-model="us", required-perm="modify_us")
|
||||
|
||||
p.us-related-task(ng-if="us.origin_issue")
|
||||
| {{ 'US.PROMOTED'|translate }}
|
||||
a(tg-check-permission="view_us", href="", title="{{'US.TITLE_LINK_GO_TO_ISSUE' | translate}}",
|
||||
tg-nav="project-issues-detail:project=project.slug,ref=us.origin_issue.ref"
|
||||
tg-bo-title="'#' + us.origin_issue.ref + ' ' + us.origin_issue.subject")
|
||||
span(tg-bo-ref="us.origin_issue.ref")
|
||||
p.us-related-task(ng-if="us.origin_issue")
|
||||
| {{ 'US.PROMOTED'|translate }}
|
||||
a(
|
||||
href=""
|
||||
tg-check-permission="view_us"
|
||||
tg-nav="project-issues-detail:project=project.slug,ref=us.origin_issue.ref"
|
||||
tg-bo-title="'#' + us.origin_issue.ref + ' ' + us.origin_issue.subject"
|
||||
title="{{'US.TITLE_LINK_GO_TO_ISSUE' | translate}}"
|
||||
)
|
||||
span(tg-bo-ref="us.origin_issue.ref")
|
||||
|
||||
p.external-reference(ng-if="us.external_reference")
|
||||
| {{ 'US.EXTERNAL_REFERENCE'|translate }}
|
||||
a(target="_blank", tg-bo-href="us.external_reference[1]",
|
||||
title="{{'US.GO_TO_EXTERNAL_REFERENCE' | translate}}")
|
||||
span {{ us.external_reference[1] }}
|
||||
p.external-reference(ng-if="us.external_reference")
|
||||
| {{ 'US.EXTERNAL_REFERENCE'|translate }}
|
||||
a(
|
||||
tg-bo-href="us.external_reference[1]",
|
||||
title="{{'US.GO_TO_EXTERNAL_REFERENCE' | translate}}"
|
||||
target="_blank"
|
||||
)
|
||||
span {{ us.external_reference[1] }}
|
||||
|
||||
p.block-desc-container(ng-show="us.is_blocked")
|
||||
span.block-description-title(translate="COMMON.BLOCKED")
|
||||
span.block-description(ng-bind="us.blocked_note || ('US.BLOCKED' | translate)")
|
||||
div.issue-nav
|
||||
a.icon.icon-arrow-left(ng-show="previousUrl", tg-bo-href="previousUrl",
|
||||
title="{{'US.PREVIOUS' | translate}}")
|
||||
a.icon.icon-arrow-right(ng-show="nextUrl", tg-bo-href="nextUrl",
|
||||
title="{{'US.NEXT' | translate}}")
|
||||
p.block-desc-container(ng-show="us.is_blocked")
|
||||
span.block-description-title(translate="COMMON.BLOCKED")
|
||||
span.block-description(ng-bind="us.blocked_note || ('US.BLOCKED' | translate)")
|
||||
div.issue-nav
|
||||
a.icon.icon-arrow-left(
|
||||
ng-show="previousUrl"
|
||||
tg-bo-href="previousUrl"
|
||||
title="{{'US.PREVIOUS' | translate}}"
|
||||
)
|
||||
a.icon.icon-arrow-right(
|
||||
ng-show="nextUrl"
|
||||
tg-bo-href="nextUrl"
|
||||
title="{{'US.NEXT' | translate}}"
|
||||
)
|
||||
|
||||
div.tags-block(tg-tag-line, ng-model="us", required-perm="modify_us")
|
||||
|
||||
section.duty-content(tg-editable-description, ng-model="us", required-perm="modify_us")
|
||||
|
||||
// Custom Fields
|
||||
tg-custom-attributes-values(ng-model="us", type="userstory", project="project", required-edition-perm="modify_us")
|
||||
tg-custom-attributes-values(
|
||||
ng-model="us"
|
||||
type="userstory"
|
||||
project="project"
|
||||
required-edition-perm="modify_us"
|
||||
)
|
||||
|
||||
include ../includes/modules/related-tasks
|
||||
|
||||
tg-attachments(ng-model="us", type="us")
|
||||
tg-history(ng-model="us", type="us")
|
||||
tg-attachments(
|
||||
ng-model="us"
|
||||
type="us"
|
||||
)
|
||||
tg-history(
|
||||
ng-model="us"
|
||||
type="us"
|
||||
)
|
||||
|
||||
sidebar.menu-secondary.sidebar
|
||||
section.us-status
|
||||
h1(tg-us-status-display, ng-model="us")
|
||||
div.us-detail-progress-bar(tg-us-tasks-progress-display, ng-model="tasks")
|
||||
tg-created-by-display.us-created-by(ng-model="us")
|
||||
sidebar.menu-secondary.sidebar.ticket-data
|
||||
section
|
||||
div.ticket-title(
|
||||
tg-us-status-display
|
||||
ng-model="us"
|
||||
)
|
||||
|
||||
tg-created-by-display.ticket-created-by(ng-model="us")
|
||||
|
||||
//div.ticket-detail-progress-bar(tg-us-tasks-progress-display, ng-model="tasks")
|
||||
|
||||
div.ticket-data-container
|
||||
div.ticket-status(
|
||||
tg-us-status-button
|
||||
ng-model="us"
|
||||
)
|
||||
|
||||
section.ticket-estimation
|
||||
tg-us-estimation(ng-model="us")
|
||||
div.duty-data-container
|
||||
div.duty-data(tg-us-status-button, ng-model="us")
|
||||
|
||||
section.duty-assigned-to(tg-assigned-to, ng-model="us", required-perm="modify_us")
|
||||
section.ticket-assigned-to(
|
||||
tg-assigned-to
|
||||
ng-model="us"
|
||||
required-perm="modify_us"
|
||||
)
|
||||
|
||||
section.watchers(tg-watchers, ng-model="us", required-perm="modify_us")
|
||||
section.track-buttons-container.ticket-track-buttons
|
||||
div.watch-button
|
||||
tg-watch-button(
|
||||
item="us"
|
||||
on-watch="ctrl.onWatch"
|
||||
on-unwatch="ctrl.onUnwatch"
|
||||
)
|
||||
|
||||
section.us-detail-settings
|
||||
div.ticket-watchers(
|
||||
tg-watchers
|
||||
ng-model="us"
|
||||
required-perm="modify_us"
|
||||
)
|
||||
|
||||
section.ticket-detail-settings
|
||||
tg-us-team-requirement-button(ng-model="us")
|
||||
tg-us-client-requirement-button(ng-model="us")
|
||||
tg-block-button(tg-check-permission="modify_us", ng-model="us")
|
||||
tg-delete-button(tg-check-permission="delete_us",
|
||||
on-delete-title="{{'Delete User Story' | translate}}",
|
||||
on-delete-go-to-url="onDeleteGoToUrl",
|
||||
ng-model="us")
|
||||
tg-block-button(
|
||||
tg-check-permission="modify_us"
|
||||
ng-model="us"
|
||||
)
|
||||
tg-delete-button(
|
||||
tg-check-permission="delete_us"
|
||||
on-delete-title="{{'Delete User Story' | translate}}"
|
||||
on-delete-go-to-url="onDeleteGoToUrl"
|
||||
ng-model="us"
|
||||
)
|
||||
|
||||
div.lightbox.lightbox-block(tg-lb-block, title="{{ 'US.LIGHTBOX_TITLE_BLOKING_US' | translate }}", ng-model="us")
|
||||
div.lightbox.lightbox-block(
|
||||
tg-lb-block
|
||||
title="{{ 'US.LIGHTBOX_TITLE_BLOKING_US' | translate }}"
|
||||
ng-model="us"
|
||||
)
|
||||
div.lightbox.lightbox-select-user(tg-lb-assignedto)
|
||||
div.lightbox.lightbox-select-user(tg-lb-watchers)
|
||||
|
|
Loading…
Reference in New Issue