Create vote and watch buttons in story, task and issue detail pages

stable
David Barragán Merino 2015-10-23 16:42:17 +02:00
parent 2a4b3ab81a
commit d1349c4272
18 changed files with 843 additions and 159 deletions

View File

@ -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)

View File

@ -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"

View File

@ -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")

View File

@ -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}

View File

@ -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}

View File

@ -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) ->

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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 }}

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)