From 32bd3624991bd12f88a13fefd2240a6205c5d0b5 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Sat, 12 Jul 2014 10:29:20 +0200 Subject: [PATCH] Task edit and visualization --- app/coffee/app.coffee | 8 + app/coffee/modules/base.coffee | 3 + app/coffee/modules/issues/detail.coffee | 6 +- app/coffee/modules/issues/lightboxes.coffee | 3 +- app/coffee/modules/resources/tasks.coffee | 6 + app/coffee/modules/tasks.coffee | 22 ++ app/coffee/modules/tasks/detail.coffee | 242 ++++++++++++++++++ app/coffee/modules/userstories.coffee | 2 +- app/coffee/modules/userstories/detail.coffee | 15 +- app/partials/task-detail-edit.jade | 52 ++++ app/partials/task-detail.jade | 57 +++++ app/partials/us-detail-edit.jade | 55 ++++ .../views/components/taskboard-task.jade | 2 +- gulpfile.coffee | 1 + 14 files changed, 466 insertions(+), 8 deletions(-) create mode 100644 app/coffee/modules/tasks.coffee create mode 100644 app/coffee/modules/tasks/detail.coffee create mode 100644 app/partials/task-detail-edit.jade create mode 100644 app/partials/task-detail.jade create mode 100644 app/partials/us-detail-edit.jade diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 226e035b..9efd40a4 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -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", diff --git a/app/coffee/modules/base.coffee b/app/coffee/modules/base.coffee index 81ccbccf..09cc968f 100644 --- a/app/coffee/modules/base.coffee +++ b/app/coffee/modules/base.coffee @@ -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" diff --git a/app/coffee/modules/issues/detail.coffee b/app/coffee/modules/issues/detail.coffee index 2a3c5d2d..75a46273 100644 --- a/app/coffee/modules/issues/detail.coffee +++ b/app/coffee/modules/issues/detail.coffee @@ -79,8 +79,9 @@ 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 + + delete historyResult.values_diff.description_html + delete historyResult.values_diff.description_diff @scope.history = history.results @scope.comments = _.filter(history.results, (historyEntry) -> historyEntry.comment != "") @@ -225,6 +226,7 @@ TagLineDirective = ($log) -> return tags = _.clone($model.$modelValue, false) + tags = [] if not tags? tags.push(value) target.val("") diff --git a/app/coffee/modules/issues/lightboxes.coffee b/app/coffee/modules/issues/lightboxes.coffee index 8f417ad8..859c9aa4 100644 --- a/app/coffee/modules/issues/lightboxes.coffee +++ b/app/coffee/modules/issues/lightboxes.coffee @@ -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) diff --git a/app/coffee/modules/resources/tasks.coffee b/app/coffee/modules/resources/tasks.coffee index b1fb1345..6b0a705e 100644 --- a/app/coffee/modules/resources/tasks.coffee +++ b/app/coffee/modules/resources/tasks.coffee @@ -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 diff --git a/app/coffee/modules/tasks.coffee b/app/coffee/modules/tasks.coffee new file mode 100644 index 00000000..2adeb9a4 --- /dev/null +++ b/app/coffee/modules/tasks.coffee @@ -0,0 +1,22 @@ +### +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino Garcia +# Copyright (C) 2014 David Barragán Merino +# +# 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 . +# +# File: modules/tasks.coffee +### + +module = angular.module("taigaTasks", []) diff --git a/app/coffee/modules/tasks/detail.coffee b/app/coffee/modules/tasks/detail.coffee new file mode 100644 index 00000000..39386b74 --- /dev/null +++ b/app/coffee/modules/tasks/detail.coffee @@ -0,0 +1,242 @@ +### +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino Garcia +# Copyright (C) 2014 David Barragán Merino +# +# 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 . +# +# 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(""" +

+ + <% if (status.is_closed) { %> + Closed + <% } else { %> + Open + <% } %> + <%= status.name %> +

+
+
+ + <%= status.name %> + status +
+
+ """) + selectionStatusTemplate = _.template(""" + + """) + + 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) diff --git a/app/coffee/modules/userstories.coffee b/app/coffee/modules/userstories.coffee index fb201db4..ddf38d8b 100644 --- a/app/coffee/modules/userstories.coffee +++ b/app/coffee/modules/userstories.coffee @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # -# File: modules/us.coffee +# File: modules/userstories.coffee ### module = angular.module("taigaUserStories", []) diff --git a/app/coffee/modules/userstories/detail.coffee b/app/coffee/modules/userstories/detail.coffee index 5ead6633..569937c4 100644 --- a/app/coffee/modules/userstories/detail.coffee +++ b/app/coffee/modules/userstories/detail.coffee @@ -79,8 +79,15 @@ 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 - delete historyResult.values_diff.description_html - delete historyResult.values_diff.description_diff + + 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 @scope.history = history.results @scope.comments = _.filter(history.results, (historyEntry) -> historyEntry.comment != "") @@ -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})) diff --git a/app/partials/task-detail-edit.jade b/app/partials/task-detail-edit.jade new file mode 100644 index 00000000..254b2d69 --- /dev/null +++ b/app/partials/task-detail-edit.jade @@ -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 diff --git a/app/partials/task-detail.jade b/app/partials/task-detail.jade new file mode 100644 index 00000000..fc906887 --- /dev/null +++ b/app/partials/task-detail.jade @@ -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 diff --git a/app/partials/us-detail-edit.jade b/app/partials/us-detail-edit.jade new file mode 100644 index 00000000..5731a63b --- /dev/null +++ b/app/partials/us-detail-edit.jade @@ -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 diff --git a/app/partials/views/components/taskboard-task.jade b/app/partials/views/components/taskboard-task.jade index fbe7c3b6..1054d29c 100644 --- a/app/partials/views/components/taskboard-task.jade +++ b/app/partials/views/components/taskboard-task.jade @@ -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") diff --git a/gulpfile.coffee b/gulpfile.coffee index 8c18e506..bd7479e0 100644 --- a/gulpfile.coffee +++ b/gulpfile.coffee @@ -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",