Add tasks to stories
This commit is contained in:
parent
b8fad18047
commit
c65cde9e8e
4 changed files with 123 additions and 2 deletions
|
@ -12,9 +12,15 @@
|
||||||
(define-method get-story (id)
|
(define-method get-story (id)
|
||||||
"Get a story from the datastore.")
|
"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)
|
(define-method post-story (role necessity title content reporter)
|
||||||
"Post a new story.")
|
"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)
|
(define-method story-get-state (id)
|
||||||
"Get the state of a story.")
|
"Get the state of a story.")
|
||||||
|
|
||||||
|
|
|
@ -17,10 +17,27 @@
|
||||||
(:metaclass dao-class)
|
(:metaclass dao-class)
|
||||||
(:keys id))
|
(: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))
|
(defmethod datastore-init ((datastore pg-datastore))
|
||||||
(with-connection (connection-spec datastore)
|
(with-connection (connection-spec datastore)
|
||||||
(unless (table-exists-p 'story)
|
(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))
|
(defmethod datastore-get-all-stories ((datastore pg-datastore))
|
||||||
(with-connection (connection-spec datastore)
|
(with-connection (connection-spec datastore)
|
||||||
|
@ -28,7 +45,10 @@
|
||||||
|
|
||||||
(defmethod datastore-get-story ((datastore pg-datastore) id)
|
(defmethod datastore-get-story ((datastore pg-datastore) id)
|
||||||
(with-connection (connection-spec datastore)
|
(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
|
(defmethod datastore-post-story
|
||||||
((datastore pg-datastore) role necessity title content reporter)
|
((datastore pg-datastore) role necessity title content reporter)
|
||||||
|
@ -42,6 +62,18 @@
|
||||||
:content content :assignee "" :reporter reporter)))
|
:content content :assignee "" :reporter reporter)))
|
||||||
(save-dao obj))))
|
(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)
|
(defmethod datastore-story-get-state ((datastore pg-datastore) id)
|
||||||
(with-connection (connection-spec datastore)
|
(with-connection (connection-spec datastore)
|
||||||
(query (:select 'state :from 'story :where (:= 'id id)) :single)))
|
(query (:select 'state :from 'story :where (:= 'id id)) :single)))
|
||||||
|
|
|
@ -80,6 +80,13 @@
|
||||||
200)
|
200)
|
||||||
403))
|
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)
|
(define-route stories-state ("stories/state" :method :post)
|
||||||
(if (logged-in-p)
|
(if (logged-in-p)
|
||||||
(let* ((id (hunchentoot:post-parameter "id"))
|
(let* ((id (hunchentoot:post-parameter "id"))
|
||||||
|
|
|
@ -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({
|
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() {
|
render: function() {
|
||||||
if (this.props.data) {
|
if (this.props.data) {
|
||||||
return (<div>
|
return (<div>
|
||||||
Assignee: {this.props.data.assignee}
|
Assignee: {this.props.data.assignee}
|
||||||
<pre>{this.props.data.content}</pre>
|
<pre>{this.props.data.content}</pre>
|
||||||
|
<StoryTaskTable tasks={this.props.data.tasks} />
|
||||||
|
<StoryTaskForm onTaskSubmit={this.handleTaskSubmit} />
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue