aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Tom Willemse2013-07-04 21:51:31 +0200
committerGravatar Tom Willemse2013-07-04 21:51:31 +0200
commitc65cde9e8e55b39ff468e4b5ce76d51fb0468d7e (patch)
tree5f9fe787c0b381cf9feef1dace2bf269f74e0e84
parentb8fad180475a847dab845a2cc7bcf56ab274ee46 (diff)
downloadscrumli-c65cde9e8e55b39ff468e4b5ce76d51fb0468d7e.tar.gz
scrumli-c65cde9e8e55b39ff468e4b5ce76d51fb0468d7e.zip
Add tasks to stories
-rw-r--r--defmodule.lisp6
-rw-r--r--pg-datastore.lisp36
-rw-r--r--scrumli.lisp7
-rw-r--r--static/js/main.js76
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>);
}