Task edit and visualization

stable
Alejandro Alonso 2014-07-12 10:29:20 +02:00
parent 8f6510748c
commit 32bd362499
14 changed files with 466 additions and 8 deletions

View File

@ -35,6 +35,13 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide,
$routeProvider.when("/project/:pslug/us/:usref/edit",
{templateUrl: "/partials/us-detail-edit.html"})
# Tasks
$routeProvider.when("/project/:pslug/tasks/:taskref",
{templateUrl: "/partials/task-detail.html"})
$routeProvider.when("/project/:pslug/tasks/:taskref/edit",
{templateUrl: "/partials/task-detail-edit.html"})
# Issues
$routeProvider.when("/project/:pslug/issues", {templateUrl: "/partials/issues.html"})
$routeProvider.when("/project/:pslug/issues/:issueref",
@ -105,6 +112,7 @@ modules = [
"taigaTaskboard",
"taigaIssues",
"taigaUserStories",
"taigaTasks",
"taigaSearch",
"taigaAdmin",
"taigaNavMenu",

View File

@ -62,6 +62,9 @@ urls = {
"project-userstories-detail": "/project/:project/us/:ref",
"project-userstories-detail-edit": "/project/:project/us/:ref/edit",
"project-tasks-detail": "/project/:project/tasks/:ref",
"project-tasks-detail-edit": "/project/:project/tasks/:ref/edit",
# Admin
"project-admin-home": "/project/:project/admin/project-profile/details",
"project-admin-project-profile-details": "/project/:project/admin/project-profile/details"

View File

@ -79,6 +79,7 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
#If description was modified take only the description_html field
if historyResult.values_diff.description?
historyResult.values_diff.description = historyResult.values_diff.description_html
delete historyResult.values_diff.description_html
delete historyResult.values_diff.description_diff
@ -225,6 +226,7 @@ TagLineDirective = ($log) ->
return
tags = _.clone($model.$modelValue, false)
tags = [] if not tags?
tags.push(value)
target.val("")

View File

@ -158,6 +158,7 @@ EditAssignedToDirective = ->
editingElement = null
updateScopeFilteringUsers = (searchingText) ->
console.log "updateScopeFilteringUsers", searchingText
usersById = _.clone($scope.usersById, false)
# Exclude selected user
if $scope.selectedUser?
@ -165,7 +166,7 @@ EditAssignedToDirective = ->
# Filter text
usersById = _.filter usersById, (user) ->
return _.contains(user.full_name_display, searchingText)
return _.contains(user.full_name_display.toUpperCase(), searchingText.toUpperCase())
# Return max of 5 elements
users = _.map(usersById, (user) -> user)

View File

@ -25,6 +25,9 @@ taiga = @.taiga
resourceProvider = ($repo, $http, $urls) ->
service = {}
service.get = (projectId, taskId) ->
return $repo.queryOne("tasks", taskId)
service.list = (projectId, sprintId=null, userStoryId=null) ->
params = {project: projectId}
params.milestone = sprintId if sprintId
@ -37,6 +40,9 @@ resourceProvider = ($repo, $http, $urls) ->
return $http.post(url, params).then (result) ->
return result.data
service.history = (taskId) ->
return $repo.queryOneRaw("history/task", taskId)
return (instance) ->
instance.tasks = service

View File

@ -0,0 +1,22 @@
###
# 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/tasks.coffee
###
module = angular.module("taigaTasks", [])

View File

@ -0,0 +1,242 @@
###
# 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/tasks/detail.coffee
###
taiga = @.taiga
mixOf = @.taiga.mixOf
groupBy = @.taiga.groupBy
module = angular.module("taigaTasks")
#############################################################################
## Task Detail Controller
#############################################################################
class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
@.$inject = [
"$scope",
"$rootScope",
"$tgRepo",
"$tgConfirm",
"$tgResources",
"$routeParams",
"$q",
"$location"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location) ->
@scope.taskRef = @params.taskref
@scope.sectionName = "Backlog"
promise = @.loadInitialData()
promise.then null, ->
console.log "FAIL" #TODO
loadProject: ->
return @rs.projects.get(@scope.projectId).then (project) =>
@scope.project = project
@scope.statusList = project.task_statuses
@scope.statusById = groupBy(project.task_statuses, (x) -> x.id)
@scope.membersById = groupBy(project.memberships, (x) -> x.user)
return project
loadTask: ->
return @rs.tasks.get(@scope.projectId, @scope.taskId).then (task) =>
@scope.task = task
@scope.commentModel = task
# @scope.previousUrl = "/project/#{@scope.project.slug}/tasks/#{@scope.task.neighbors.previous.ref}" if @scope.task.neighbors.previous.id?
# @scope.nextUrl = "/project/#{@scope.project.slug}/tasks/#{@scope.task.neighbors.next.ref}" if @scope.task.neighbors.next.id?
loadHistory: ->
return @rs.tasks.history(@scope.taskId).then (history) =>
_.each history.results, (historyResult) ->
#If description was modified take only the description_html field
if historyResult.values_diff.description?
historyResult.values_diff.description = historyResult.values_diff.description_html
if historyResult.values_diff.is_iocaine
historyResult.values_diff.is_iocaine = _.map(historyResult.values_diff.is_iocaine, (v) -> {true: 'Yes', false: 'No'}[v])
delete historyResult.values_diff.description_html
delete historyResult.values_diff.description_diff
@scope.history = history.results
@scope.comments = _.filter(history.results, (historyEntry) -> historyEntry.comment != "")
loadInitialData: ->
params = {
pslug: @params.pslug
taskref: @params.taskref
}
promise = @repo.resolve(params).then (data) =>
@scope.projectId = data.project
@scope.taskId = data.task
return data
return promise.then(=> @.loadProject())
.then(=> @.loadUsersAndRoles())
.then(=> @.loadTask())
.then(=> @.loadHistory())
getUserFullName: (userId) ->
return @scope.usersById[userId]?.full_name_display
getUserAvatar: (userId) ->
return @scope.usersById[userId]?.photo
countChanges: (comment) ->
return Object.keys(comment.values_diff).length
getChangeText: (change) ->
if _.isArray(change)
return change.join(", ")
return change
buildChangesText: (comment) ->
size = @.countChanges(comment)
#TODO: i18n
if size == 1
return "Made #{size} change"
return "Made #{size} changes"
block: ->
@rootscope.$broadcast("block", @scope.task)
unblock: ->
@rootscope.$broadcast("unblock", @scope.task)
delete: ->
#TODO: i18n
title = "Delete Task"
subtitle = @scope.task.subject
@confirm.ask(title, subtitle).then =>
@.repo.remove(@scope.task).then =>
@location.path("/project/#{@scope.project.slug}/backlog")
module.controller("TaskDetailController", TaskDetailController)
#############################################################################
## Task Main Directive
#############################################################################
TaskDirective = ($tgrepo, $log, $location, $confirm) ->
linkSidebar = ($scope, $el, $attrs, $ctrl) ->
link = ($scope, $el, $attrs) ->
$ctrl = $el.controller()
linkSidebar($scope, $el, $attrs, $ctrl)
$el.on "click", ".save-task", (event) ->
$tgrepo.save($scope.task).then ->
$confirm.notify("success")
$location.path("/project/#{$scope.project.slug}/tasks/#{$scope.task.ref}")
$el.on "click", ".add-comment a.button-green", (event) ->
event.preventDefault()
$tgrepo.save($scope.task).then ->
$ctrl.loadHistory()
$el.on "focus", ".add-comment textarea", (event) ->
$(this).addClass('active')
$el.on "click", ".us-activity-tabs li a", (event) ->
$el.find(".us-activity-tabs li a").toggleClass("active")
$el.find(".us-activity section").toggleClass("hidden")
return {link:link}
module.directive("tgTaskDetail", ["$tgRepo", "$log", "$tgLocation", "$tgConfirm", TaskDirective])
#############################################################################
## Task status directive
#############################################################################
TaskStatusDirective = () ->
#TODO: i18n
template = _.template("""
<h1>
<span>
<% if (status.is_closed) { %>
Closed
<% } else { %>
Open
<% } %>
<span class="us-detail-status" style="color:<%= status.color %>"><%= status.name %></span>
</h1>
<div class="issue-data">
<div class="status-data <% if (editable) { %>clickable<% } %>">
<span class="level" style="background-color:<%= status.color %>"></span>
<span class="status-status"><%= status.name %></span>
<span class="level-name">status</span>
</div>
</div>
""")
selectionStatusTemplate = _.template("""
<ul class="popover pop-status">
<% _.each(statuses, function(status) { %>
<li><a href="" class="status" title="<%- status.name %>"
data-status-id="<%- status.id %>"><%- status.name %></a></li>
<% }); %>
</ul>
""")
link = ($scope, $el, $attrs, $model) ->
editable = $attrs.editable?
renderTaskstatus = (task) ->
status = $scope.statusById[task.status]
html = template({
editable: editable
status: status
})
$el.html(html)
$el.find(".status-data").append(selectionStatusTemplate({statuses:$scope.statusList}))
$scope.$watch $attrs.ngModel, (task) ->
if task?
renderTaskstatus(task)
if editable
$el.on "click", ".status-data", (event) ->
event.preventDefault()
event.stopPropagation()
$el.find(".pop-status").show()
body = angular.element("body")
body.one "click", (event) ->
$el.find(".popover").hide()
$el.on "click", ".status", (event) ->
event.preventDefault()
event.stopPropagation()
target = angular.element(event.currentTarget)
$model.$modelValue.status = target.data("status-id")
renderTaskstatus($model.$modelValue)
$el.find(".popover").hide()
return {link:link, require:"ngModel"}
module.directive("tgTaskStatus", TaskStatusDirective)

View File

@ -16,7 +16,7 @@
# 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/us.coffee
# File: modules/userstories.coffee
###
module = angular.module("taigaUserStories", [])

View File

@ -79,6 +79,13 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
#If description was modified take only the description_html field
if historyResult.values_diff.description?
historyResult.values_diff.description = historyResult.values_diff.description_html
if historyResult.values_diff.client_requirement
historyResult.values_diff.client_requirement = _.map(historyResult.values_diff.client_requirement, (v) -> {true: 'Yes', false: 'No'}[v])
if historyResult.values_diff.team_requirement
historyResult.values_diff.team_requirement = _.map(historyResult.values_diff.team_requirement, (v) -> {true: 'Yes', false: 'No'}[v])
delete historyResult.values_diff.description_html
delete historyResult.values_diff.description_diff
@ -266,6 +273,8 @@ UsStatusDetailDirective = () ->
totalTasks = $scope.tasks.length
totalClosedTasks = _.filter($scope.tasks, (task) => $scope.taskStatusById[task.status].is_closed).length
usProgress = 0
usProgress = 100 * totalClosedTasks / totalTasks if totalTasks > 0
html = template({
editable: editable
status: status
@ -273,7 +282,7 @@ UsStatusDetailDirective = () ->
rolePoints: rolePoints
totalTasks: totalTasks
totalClosedTasks: totalClosedTasks
usProgress: 100 * totalClosedTasks / totalTasks
usProgress: usProgress
})
$el.html(html)
$el.find(".status-data").append(selectionStatusTemplate({statuses:$scope.statusList}))

View File

@ -0,0 +1,52 @@
extends dummy-layout
block head
title Taiga Project management web application with scrum in mind!
block content
div.wrapper(tg-task-detail, ng-controller="TaskDetailController as ctrl",
ng-init="section='backlog'")
div.main.us-detail
div.us-detail-header
include views/components/mainTitle
a.button.button-green.save-task(href="", title="Save") Save
section.us-story-main-data
div.us-title
input(type="text", ng-model="task.subject")
div.blocked-warning(ng-show="task.is_blocked")
span.icon.icon-warning
p.blocked Blocked!
p(tg-bind-html="task.blocked_note || 'This task is blocked'")
a.button.button-red.button-block.unblock(ng-click="ctrl.unblock()", href="", title="Unblock US") Unblock
div.user-story-tags(tg-tag-line, editable="true", ng-model="task.tags")
section.us-content
textarea(placeholder="Write a description of your task", ng-model="task.description", tg-markitup)
// include views/modules/attachments
textarea(ng-model="task.comment", placeholder="Write here a new commet")
sidebar.menu-secondary.sidebar
section.us-status(tg-task-status, ng-model="task", editable="true")
section.us-assigned-to(tg-assigned-to, ng-model="task", editable="true")
section.watchers(tg-watchers, ng-model="task.watchers", editable="true")
section.us-detail-settings
fieldset
label.clickable.button.button-green(for="is-iocaine", ng-class="{true:'active', false:''}[task.is_iocaine]") Iocaine
input(ng-model="task.is_iocaine", type="checkbox", id="is-iocaine", name="is-iocaine")
a.button.button-gray.clickable(ng-show="!task.is_blocked", ng-click="ctrl.block()") Block
a.button.button-red(ng-click="ctrl.delete()", href="") Delete
div.lightbox.lightbox_block.hidden(tg-lb-block, title="Blocking task", ng-model="task")
include views/modules/lightbox_block
div.lightbox.lightbox_select_user.hidden(tg-lb-edit-assigned-to)
include views/modules/lightbox-assigned-to
div.lightbox.lightbox_select_user.hidden(tg-lb-add-watcher)
include views/modules/lightbox_users

View File

@ -0,0 +1,57 @@
extends dummy-layout
block head
title Taiga Project management web application with scrum in mind!
block content
div.wrapper(tg-task-detail, ng-controller="TaskDetailController as ctrl",
ng-init="section='backlog'")
div.main.us-detail
div.us-detail-header
include views/components/mainTitle
a.button.button-green(href="", title="Edit", tg-nav="project-tasks-detail-edit:project=project.slug,ref=task.ref") Edit
section.us-story-main-data
div.us-title
h2.us-title-text
span.us-number(tg-bo-html="task.ref")
span.us-name(ng-bind="task.subject")
div.issue-nav
a.icon.icon-arrow-left(ng-show="nextUrl", href="{{ nextUrl }}", title="next task")
a.icon.icon-arrow-right(ng-show="previousUrl",href="{{ previousUrl }}", title="previous task")
div.blocked-warning(ng-show="task.is_blocked")
span.icon.icon-warning
p.blocked Blocked!
p(tg-bind-html="task.blocked_note || 'This task is blocked'")
div.user-story-tags(tg-tag-line, ng-model="task.tags")
section.us-content.wysiwyg(tg-bind-html="task.description_html")
// include views/modules/attachments
section.us-activity
ul.us-activity-tabs
li
a.active(href="#")
span.icon.icon-bulk
span.tab-title Comments
li
a(href="#")
span.icon.icon-issues
span.tab-title Activity
include views/modules/comments
include views/modules/activity
sidebar.menu-secondary.sidebar
section.us-status(tg-task-status, ng-model="task")
section.us-assigned-to(tg-assigned-to, ng-model="task")
section.watchers(tg-watchers, ng-model="task.watchers")
section.us-detail-settings
span.button.button-gray(href="", title="Is iocaine", ng-class="task.is_iocaine ? 'active' : ''") Iocaine
div.lightbox.lightbox_block.hidden
include views/modules/lightbox_block

View File

@ -0,0 +1,55 @@
extends dummy-layout
block head
title Taiga Project management web application with scrum in mind!
block content
div.wrapper(tg-us-detail, ng-controller="UserStoryDetailController as ctrl",
ng-init="section='backlog'")
div.main.us-detail
div.us-detail-header
include views/components/mainTitle
a.button.button-green.save-us(href="", title="Save") Save
section.us-story-main-data
div.us-title
input(type="text", ng-model="us.subject")
div.blocked-warning(ng-show="us.is_blocked")
span.icon.icon-warning
p.blocked Blocked!
p(tg-bind-html="us.blocked_note || 'This user story is blocked'")
a.button.button-red.button-block.unblock(ng-click="ctrl.unblock()", href="", title="Unblock US") Unblock
div.user-story-tags(tg-tag-line, editable="true", ng-model="us.tags")
section.us-content
textarea(placeholder="Write a description of your user story", ng-model="us.description", tg-markitup)
// include views/modules/attachments
textarea(ng-model="us.comment", placeholder="Write here a new commet")
sidebar.menu-secondary.sidebar
section.us-status(tg-us-status-detail, ng-model="us", editable="true")
section.us-assigned-to(tg-assigned-to, ng-model="us", editable="true")
section.watchers(tg-watchers, ng-model="us.watchers", editable="true")
section.us-detail-settings
fieldset
label.clickable.button.button-green(for="client-requirement", ng-class="{true:'active', false:''}[us.client_requirement]") Client requirement
input(ng-model="us.client_requirement", type="checkbox", id="client-requirement", name="client-requirement")
fieldset
label.clickable.button.button-green(for="team-requirement", ng-class="{true:'active', false:''}[us.team_requirement]") Team requirement
input(ng-model="us.team_requirement", type="checkbox", id="team-requirement", name="team-requirement")
a.button.button-gray.clickable(ng-show="!us.is_blocked", ng-click="ctrl.block()") Block
a.button.button-red(ng-click="ctrl.delete()", href="") Delete
div.lightbox.lightbox_block.hidden(tg-lb-block, title="Blocking issue", ng-model="us")
include views/modules/lightbox_block
div.lightbox.lightbox_select_user.hidden(tg-lb-edit-assigned-to)
include views/modules/lightbox-assigned-to
div.lightbox.lightbox_select_user.hidden(tg-lb-add-watcher)
include views/modules/lightbox_users

View File

@ -8,6 +8,6 @@ div.taskboard-task-inner
figcaption {{ usersById[task.assigned_to].full_name_display }}
p.taskboard-text
span.task-num(ng-bind="task.ref")
a.task-name(href="", title="See task details", ng-bind="task.subject")
a.task-name(tg-nav="project-tasks-detail:project=project.slug,ref=task.ref" href="", title="See task details", ng-bind="task.subject")
a.icon.icon-edit(href="", title="Edit task", ng-click="ctrl.editTask(task)")
a.icon.icon-drag-h(href="", title="Drag&Drop")

View File

@ -44,6 +44,7 @@ paths = {
"app/coffee/modules/taskboard/*.coffee",
"app/coffee/modules/issues/*.coffee",
"app/coffee/modules/userstories/*.coffee",
"app/coffee/modules/tasks/*.coffee",
"app/coffee/modules/admin/*.coffee",
"app/coffee/modules/locales/*.coffee",
"app/coffee/modules/base/*.coffee",