diff options
author | Tom Willemse | 2013-07-04 21:51:31 +0200 |
---|---|---|
committer | Tom Willemse | 2013-07-04 21:51:31 +0200 |
commit | c65cde9e8e55b39ff468e4b5ce76d51fb0468d7e (patch) | |
tree | 5f9fe787c0b381cf9feef1dace2bf269f74e0e84 | |
parent | b8fad180475a847dab845a2cc7bcf56ab274ee46 (diff) | |
download | scrumli-c65cde9e8e55b39ff468e4b5ce76d51fb0468d7e.tar.gz scrumli-c65cde9e8e55b39ff468e4b5ce76d51fb0468d7e.zip |
Add tasks to stories
-rw-r--r-- | defmodule.lisp | 6 | ||||
-rw-r--r-- | pg-datastore.lisp | 36 | ||||
-rw-r--r-- | scrumli.lisp | 7 | ||||
-rw-r--r-- | 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 ( + <tr> + <td class="span1"> + <i class="icon-arrow-up"></i> + <i class="icon-arrow-down"></i> + </td> + <td class="span2"> + <span> + <StateIcon state={this.props.task.state} /> + {state} + </span> + </td> + <td> + {this.props.task.description} + </td> + </tr> + ); + + } +}); + +var StoryTaskTable = React.createClass({ + render: function() { + var taskNodes = this.props.tasks.map(function (task) { + return <StoryTaskRow task={task} />; + }); + return ( + <table class="table table-striped"> + {taskNodes} + </table> + ); + } +}); + +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 ( + <form onSubmit={this.handleSubmit}> + <fieldset> + <legend>New task</legend> + <div class="input-append"> + <input type="text" ref="text" class="input-medium" /> + <button type="submit" class="btn btn-primary"> + Send + </button> + </div> + </fieldset> + </form> + ); + } +}); + 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 (<div> Assignee: {this.props.data.assignee} <pre>{this.props.data.content}</pre> + <StoryTaskTable tasks={this.props.data.tasks} /> + <StoryTaskForm onTaskSubmit={this.handleTaskSubmit} /> </div>); } |