From 8dd8e632b1561aed135fd5d5feb7a4e90e42dc69 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 10 Jul 2014 14:57:30 +0200 Subject: [PATCH] Initial integration of userstory page --- app/coffee/app.coffee | 12 +- app/coffee/modules/base.coffee | 3 + app/coffee/modules/issues/detail.coffee | 1 + .../modules/resources/userstories.coffee | 6 + app/coffee/modules/userstories.coffee | 22 ++ app/coffee/modules/userstories/detail.coffee | 202 ++++++++++++++++++ app/partials/us-detail.jade | 105 +++++---- .../views/components/backlog-row.jade | 2 +- app/partials/views/modules/comments.jade | 2 +- gulpfile.coffee | 1 + 10 files changed, 296 insertions(+), 60 deletions(-) create mode 100644 app/coffee/modules/userstories.coffee create mode 100644 app/coffee/modules/userstories/detail.coffee diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 017e7669..226e035b 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -26,8 +26,17 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $routeProvider.when("/project/:pslug/backlog", {templateUrl: "/partials/backlog.html"}) $routeProvider.when("/project/:pslug/taskboard/:id", {templateUrl: "/partials/taskboard.html"}) - $routeProvider.when("/project/:pslug/issues", {templateUrl: "/partials/issues.html"}) $routeProvider.when("/project/:pslug/search", {templateUrl: "/partials/search.html"}) + + # User stories + $routeProvider.when("/project/:pslug/us/:usref", + {templateUrl: "/partials/us-detail.html"}) + + $routeProvider.when("/project/:pslug/us/:usref/edit", + {templateUrl: "/partials/us-detail-edit.html"}) + + # Issues + $routeProvider.when("/project/:pslug/issues", {templateUrl: "/partials/issues.html"}) $routeProvider.when("/project/:pslug/issues/:issueref", {templateUrl: "/partials/issues-detail.html"}) @@ -95,6 +104,7 @@ modules = [ "taigaBacklog", "taigaTaskboard", "taigaIssues", + "taigaUserStories", "taigaSearch", "taigaAdmin", "taigaNavMenu", diff --git a/app/coffee/modules/base.coffee b/app/coffee/modules/base.coffee index f97a65b3..81ccbccf 100644 --- a/app/coffee/modules/base.coffee +++ b/app/coffee/modules/base.coffee @@ -59,6 +59,9 @@ urls = { "project-issues-detail": "/project/:project/issues/:ref", "project-issues-detail-edit": "/project/:project/issues/:ref/edit", + "project-userstories-detail": "/project/:project/us/:ref", + "project-userstories-detail-edit": "/project/:project/us/: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 09a6f1cf..d7fc4c02 100644 --- a/app/coffee/modules/issues/detail.coffee +++ b/app/coffee/modules/issues/detail.coffee @@ -69,6 +69,7 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin) loadIssue: -> return @rs.issues.get(@scope.projectId, @scope.issueId).then (issue) => @scope.issue = issue + @scope.commentModel = issue @scope.previousUrl = "/project/#{@scope.project.slug}/issues/#{@scope.issue.neighbors.previous.ref}" if @scope.issue.neighbors.previous.id? @scope.nextUrl = "/project/#{@scope.project.slug}/issues/#{@scope.issue.neighbors.next.ref}" if @scope.issue.neighbors.next.id? diff --git a/app/coffee/modules/resources/userstories.coffee b/app/coffee/modules/resources/userstories.coffee index 229278d1..a10d51ef 100644 --- a/app/coffee/modules/resources/userstories.coffee +++ b/app/coffee/modules/resources/userstories.coffee @@ -24,6 +24,9 @@ taiga = @.taiga resourceProvider = ($repo, $http, $urls) -> service = {} + service.get = (projectId, usId) -> + return $repo.queryOne("userstories", usId) + service.listUnassigned = (projectId) -> params = {"project": projectId, "milestone": "null"} return $repo.queryMany("userstories", params) @@ -38,6 +41,9 @@ resourceProvider = ($repo, $http, $urls) -> params = {projectId: projectId, bulkStories: data} return $http.post(url, params) + service.history = (usId) -> + return $repo.queryOneRaw("history/userstory", usId) + return (instance) -> instance.userstories = service diff --git a/app/coffee/modules/userstories.coffee b/app/coffee/modules/userstories.coffee new file mode 100644 index 00000000..fb201db4 --- /dev/null +++ b/app/coffee/modules/userstories.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/us.coffee +### + +module = angular.module("taigaUserStories", []) diff --git a/app/coffee/modules/userstories/detail.coffee b/app/coffee/modules/userstories/detail.coffee new file mode 100644 index 00000000..1ff3eb14 --- /dev/null +++ b/app/coffee/modules/userstories/detail.coffee @@ -0,0 +1,202 @@ +### +# 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/userstories/detail.coffee +### + +taiga = @.taiga + +mixOf = @.taiga.mixOf +groupBy = @.taiga.groupBy + +module = angular.module("taigaUserStories") + +############################################################################# +## User story Detail Controller +############################################################################# + +class UserStoryDetailController 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.issueRef = @params.issueref + @scope.sectionName = "User Story" + + promise = @.loadInitialData() + promise.then null, -> + console.log "FAIL" #TODO + + loadProject: -> + return @rs.projects.get(@scope.projectId).then (project) => + @scope.project = project + @scope.statusList = project.issue_statuses + @scope.statusById = groupBy(project.us_statuses, (x) -> x.id) + @scope.severityList = project.severities + @scope.severityById = groupBy(project.severities, (x) -> x.id) + @scope.priorityList = project.priorities + @scope.priorityById = groupBy(project.priorities, (x) -> x.id) + @scope.membersById = groupBy(project.memberships, (x) -> x.user) + return project + + loadUs: -> + return @rs.userstories.get(@scope.projectId, @scope.usId).then (us) => + @scope.us = us + @scope.commentModel = us + @scope.previousUrl = "/project/#{@scope.project.slug}/us/#{@scope.us.neighbors.previous.ref}" if @scope.us.neighbors.previous.id? + @scope.nextUrl = "/project/#{@scope.project.slug}/us/#{@scope.us.neighbors.next.ref}" if @scope.us.neighbors.next.id? + + loadHistory: -> + return @rs.userstories.history(@scope.usId).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 + 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 + usref: @params.usref + } + + promise = @repo.resolve(params).then (data) => + @scope.projectId = data.project + @scope.usId = data.us + return data + + return promise.then(=> @.loadProject()) + .then(=> @.loadUsersAndRoles()) + .then(=> @.loadUs()) + .then(=> @.loadHistory()) + +module.controller("UserStoryDetailController", UserStoryDetailController) + + + +############################################################################# +## User story Main Directive +############################################################################# + +UsDirective = ($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-us", (event) -> + $tgrepo.save($scope.us).then -> + $confirm.notify("success") + $location.path("/project/#{$scope.project.slug}/us/#{$scope.us.ref}") + + $el.on "click", ".add-comment a.button-green", (event) -> + event.preventDefault() + $tgrepo.save($scope.us).then -> + $ctrl.loadHistory() + + $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("tgUsDetail", ["$tgRepo", "$log", "$tgLocation", "$tgConfirm", UsDirective]) + + +############################################################################# +## User story status directive +############################################################################# + +UsStatusDetailDirective = () -> + #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? + + renderUsstatus = (us) -> + status = $scope.statusById[us.status] + html = template({ + editable: editable + status: status + }) + $el.html(html) + $el.find(".status-data").append(selectionStatusTemplate({statuses:$scope.statusList})) + + $scope.$watch $attrs.ngModel, (us) -> + if us? + renderUsstatus(us) + + 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") + renderUsstatus($model.$modelValue) + $el.find(".popover").hide() + + return {link:link, require:"ngModel"} + +module.directive("tgUsStatusDetail", UsStatusDetailDirective) diff --git a/app/partials/us-detail.jade b/app/partials/us-detail.jade index 2c9d8589..94de9d1f 100644 --- a/app/partials/us-detail.jade +++ b/app/partials/us-detail.jade @@ -1,39 +1,35 @@ -extends layout +extends dummy-layout block head title Taiga Project management web application with scrum in mind! block content - div.wrapper + div.wrapper(tg-us-detail, ng-controller="UserStoryDetailController as ctrl", + ng-init="section='backlog'") div.main.us-detail div.us-detail-header - h1 - span ProjectName - span.green User Story - a.button.button-green(href="", title="Edit") Edit + include views/components/mainTitle + a.button.button-green(href="", title="Edit", tg-nav="project-userstories-detail-edit:project=project.slug,ref=us.ref") Edit + section.us-story-main-data - h2.us-title - span.us-number 125 - span.us-name Tagear contenido dentro de las catas privadas - div.blocked-warning + div.us-title + h2.us-title-text + span.us-number(tg-bo-html="us.ref") + span.us-name(ng-bind="us.subject") + div.issue-nav + a.icon.icon-arrow-left(ng-show="nextUrl", href="{{ nextUrl }}", title="next user story") + a.icon.icon-arrow-right(ng-show="previousUrl",href="{{ previousUrl }}", title="previous user story") + + div.blocked-warning(ng-show="us.is_blocked") span.icon.icon-warning p.blocked Blocked! - p We need Pilar to make a prototype out of this or we are not sure what to show at this part. - a.button.button-red.button-block(href="", title="Unblock US") Unblock - div.user-story-tags - - for(var y = 0; y < 6; y++) - include views/components/tag - input(type="text", placeholder="Add Tag") - section.us-content - p Hay que cambiar el texto "Hola NombreDelUsuario" por "Nivel de conexion al XX%" - p La propuesta que esperábamos de UX debía incluir nombre y nivel de conexión. Esperamos nueva propuesta corregida donde aparezca tanto el nombre como el % de conexión - p Test de aceptación - ul - li Entro en la aplicación - li Compruebo que el indicador crece - p Prototipos
share.axure.com/lalala - p Prototipos
share.axure.com/lalala - include views/modules/attachments + p(tg-bind-html="us.blocked_note || 'This user story is blocked'") + + div.user-story-tags(tg-tag-line, ng-model="us.tags") + + section.us-content.wysiwyg(tg-bind-html="us.description_html") + + //include views/modules/attachments section.us-activity ul.us-activity-tabs li @@ -45,36 +41,31 @@ block content a(href="#") span.icon.icon-issues span.tab-title Activity - //-include views/modules/comments + include views/modules/comments include views/modules/activity + sidebar.menu-secondary.sidebar - h1 - span Open - span.us-detail-status In progress - div.us-detail-progress-bar - div.current-progress - span.tasks-completed 6/7 tasks completed - ul.points-per-role - li.total - span.points 10 - span.role total - - for(var x=0; x<5; x++) - li.total - span.points 10 - span.role UX - include views/components/assigned-to - section.watchers - div.watchers-header - span.title watchers - a.icon.icon-plus(href="", title="Add watcher") - div.watchers-content - - for(var y=0; y<5; y++) - div.watcher-single - div.watcher-avatar - a.avatar(href="", title="Assigned to") - img(src="http://thecodeplayer.com/u/uifaces/32.jpg", alt="username") - a.watcher-name(href="", title="Jesús Espino") Jesús Espino - section.us-detail-settings - span.button.button-gray(href="", title="Client requirement") Client requirement - span.button.button-gray(href="", title="Team requirement") Team requirement - span.button.button-red(href="", title="Block") Block + section.us-status(tg-us-status-detail, ng-model="us") + + div.us-detail-progress-bar + div.current-progress + span.tasks-completed 6/7 tasks completed + ul.points-per-role + li.total + span.points 10 + span.role total + - for(var x=0; x<5; x++) + li.total + span.points 10 + span.role UX + + section.us-assigned-to(tg-assigned-to, ng-model="us") + section.watchers(tg-watchers, ng-model="us.watchers") + + section.us-detail-settings + span.button.button-gray(href="", title="Client requirement") Client requirement + span.button.button-gray(href="", title="Team requirement") Team requirement + span.button.button-red(href="", title="Block") Block + + div.lightbox.lightbox_block.hidden + include views/modules/lightbox_block diff --git a/app/partials/views/components/backlog-row.jade b/app/partials/views/components/backlog-row.jade index cb900348..c1e5d5fc 100644 --- a/app/partials/views/components/backlog-row.jade +++ b/app/partials/views/components/backlog-row.jade @@ -4,7 +4,7 @@ div.row.us-item-row(ng-repeat="us in visibleUserstories|orderBy:order track by u span.tag(ng-repeat="tag in us.tags") {{ tag }} div.user-story-name input(type="checkbox", name="") - a(href="", title="{{ us.subject }}") {{ us.subject }} + a.clickable(tg-nav="project-userstories-detail:project=project.slug,ref=us.ref", title="{{ us.subject }}") {{ us.subject }} span.us-settings a.icon.icon-edit(href="", ng-click="ctrl.editUserStory(us)", title="Edit") a.icon.icon-delete(href="", ng-click="ctrl.deleteUserStory(us)", title="Delete") diff --git a/app/partials/views/modules/comments.jade b/app/partials/views/modules/comments.jade index 76e7255b..d8a5e9b4 100644 --- a/app/partials/views/modules/comments.jade +++ b/app/partials/views/modules/comments.jade @@ -1,6 +1,6 @@ section.us-comments div.add-comment - textarea(placeholder="Write here a new commet", ng-model="issue.comment") + textarea(placeholder="Write here a new commet", ng-model="commentModel.comment") a.button.button-green(href="", title="Comment") Comment div.comment-list div.comment-single(tg-comment, ng-repeat="comment in comments") diff --git a/gulpfile.coffee b/gulpfile.coffee index 97aa7c26..8c18e506 100644 --- a/gulpfile.coffee +++ b/gulpfile.coffee @@ -43,6 +43,7 @@ paths = { "app/coffee/modules/backlog/*.coffee", "app/coffee/modules/taskboard/*.coffee", "app/coffee/modules/issues/*.coffee", + "app/coffee/modules/userstories/*.coffee", "app/coffee/modules/admin/*.coffee", "app/coffee/modules/locales/*.coffee", "app/coffee/modules/base/*.coffee",