From c65cde9e8e55b39ff468e4b5ce76d51fb0468d7e Mon Sep 17 00:00:00 2001 From: Tom Willemse Date: Thu, 4 Jul 2013 21:51:31 +0200 Subject: [PATCH] Add tasks to stories --- defmodule.lisp | 6 ++++ pg-datastore.lisp | 36 ++++++++++++++++++++-- scrumli.lisp | 7 +++++ static/js/main.js | 76 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+), 2 deletions(-) diff --git a/defmodule.lisp b/defmodule.lisp index d3756c9..2cc9148 100644 --- a/defmodule.lisp +++ b/defmodule.lisp @@ -12,9 +12,15 @@ (define-method get-story (id) "Get a story from the datastore.") + (define-method get-tasks-for-story (id) + "Get the tasks associated with a story.") + (define-method post-story (role necessity title content reporter) "Post a new story.") + (define-method post-task (story-id description reporter) + "Post a new task for a story.") + (define-method story-get-state (id) "Get the state of a story.") diff --git a/pg-datastore.lisp b/pg-datastore.lisp index 8cfdbeb..d6b3e54 100644 --- a/pg-datastore.lisp +++ b/pg-datastore.lisp @@ -17,10 +17,27 @@ (:metaclass dao-class) (:keys id)) +(defclass task () + ((id :col-type serial :reader story-id) + (state :col-type string :reader state :initform "TODO") + (description :col-type string :reader description :initarg :description) + (priority :col-type integer :reader priority :initarg :priority) + (reporter :col-type string :reader reporter :initarg :reporter) + (assignee :col-type string :reader assignee :initarg :assignee) + (story-id :col-type integer :reader story-id :initarg :story-id)) + (:metaclass dao-class) + (:keys id)) + +(deftable task + (!dao-def) + (!foreign 'story 'story-id 'id)) + (defmethod datastore-init ((datastore pg-datastore)) (with-connection (connection-spec datastore) (unless (table-exists-p 'story) - (execute (dao-table-definition 'story))))) + (execute (dao-table-definition 'story))) + (unless (table-exists-p 'task) + (execute (dao-table-definition 'task))))) (defmethod datastore-get-all-stories ((datastore pg-datastore)) (with-connection (connection-spec datastore) @@ -28,7 +45,10 @@ (defmethod datastore-get-story ((datastore pg-datastore) id) (with-connection (connection-spec datastore) - (query (:select :* :from 'story :where (:= 'id id)) :alist))) + (append (query (:select :* :from 'story :where (:= 'id id)) :alist) + `((tasks . ,(query (:select :* :from 'task + :where (:= 'story-id id)) + :alists)))))) (defmethod datastore-post-story ((datastore pg-datastore) role necessity title content reporter) @@ -42,6 +62,18 @@ :content content :assignee "" :reporter reporter))) (save-dao obj)))) +(defmethod datastore-post-task + ((datastore pg-datastore) story-id description reporter) + (with-connection (connection-spec datastore) + (let ((obj (make-instance + 'task :description description + :priority (+ 1 (query (:select + (:coalesce (:max 'priority) 0) + :from 'task) :single)) + :reporter reporter :story-id (parse-integer story-id) + :assignee ""))) + (save-dao obj)))) + (defmethod datastore-story-get-state ((datastore pg-datastore) id) (with-connection (connection-spec datastore) (query (:select 'state :from 'story :where (:= 'id id)) :single))) diff --git a/scrumli.lisp b/scrumli.lisp index ef2b66a..0f563d9 100644 --- a/scrumli.lisp +++ b/scrumli.lisp @@ -80,6 +80,13 @@ 200) 403)) +(define-route tasks-new ("stories/:id/tasks/new" :method :post) + (if (logged-in-p) + (let ((description (hunchentoot:post-parameter "description"))) + (post-task id description (hunchentoot:session-value :username)) + 200) + 403)) + (define-route stories-state ("stories/state" :method :post) (if (logged-in-p) (let* ((id (hunchentoot:post-parameter "id")) diff --git a/static/js/main.js b/static/js/main.js index 0bc589a..a3092e4 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -8,12 +8,88 @@ var StateIcon = React.createClass({ } }); +var StoryTaskRow = React.createClass({ + render: function() { + var state = " " + this.props.task.state; + + return ( + + + + + + + + + {state} + + + + {this.props.task.description} + + + ); + + } +}); + +var StoryTaskTable = React.createClass({ + render: function() { + var taskNodes = this.props.tasks.map(function (task) { + return ; + }); + return ( + + {taskNodes} +
+ ); + } +}); + +var StoryTaskForm = React.createClass({ + handleSubmit: React.autoBind(function() { + var text = this.refs.text.getDOMNode().value.trim(); + + this.props.onTaskSubmit({description: text}); + + this.refs.text.getDOMNode().value = ''; + + return false; + }), + render: function() { + return ( +
+
+ New task +
+ + +
+
+
+ ); + } +}); + var StoryData = React.createClass({ + handleTaskSubmit: React.autoBind(function (task) { + $.ajax({ + url: "/stories/" + this.props.data.id + "/tasks/new", + type: "POST", + data: task, + dataType: 'json', + mimeType: 'textPlain' + }); + }), render: function() { if (this.props.data) { return (
Assignee: {this.props.data.assignee}
{this.props.data.content}
+ +
); }