Commit 59663f29 authored by Christian Wulf's avatar Christian Wulf

merged

parents 5e28a159 fcfa80d6
......@@ -3,6 +3,7 @@ import scrumboard.Color
import scrumboard.ProductBacklog
import scrumboard.Project
import scrumboard.Sprint
import scrumboard.Task
import scrumboard.UserStory
import scrumboard.UserStoryStatus;
import scrumboard.shiro.User
......@@ -11,9 +12,9 @@ import scrumboard.shiro.Role
class BootStrap {
def init = { servletContext ->
createUsersAndRoles()
def ffi = createUsersAndRoles()
createExampleProject()
createExampleProject(ffi)
}
private createUsersAndRoles() {
......@@ -22,7 +23,7 @@ class BootStrap {
def ffi = new User(username: "ffi", passwordHash: new Sha256Hash("ffi").toHex())
ffi.addToRoles(developerRole)
ffi.save(failOnError: true)
ffi = ffi.save(failOnError: true)
def guestRole = new Role(name: "Gast")
guestRole.addToPermissions("*:*")
......@@ -30,11 +31,12 @@ class BootStrap {
def guest = new User(username: "Gast", passwordHash: new Sha256Hash("gast").toHex())
guest.addToRoles(guestRole)
guest.save(failOnError: true)
ffi
}
private createExampleProject() {
private createExampleProject(ffi) {
def fmcockpit = new Project(name: "FMcockpit")
def ffi = User.findByUsername("ffi")
fmcockpit.setProductBacklog(createBacklog(ffi))
......@@ -46,15 +48,18 @@ class BootStrap {
private createBacklog(User ffi) {
def productBacklog = new ProductBacklog()
def userStoryInBacklog = new UserStory("V-1", "Energiekataster Layer")
userStoryInBacklog.description = "Energiekataster Layer im GIS um über jeder VE die Energieausweise direkt zu sehen."
userStoryInBacklog.author = ffi
userStoryInBacklog.editor = ffi
userStoryInBacklog.points = 3
userStoryInBacklog.status = UserStoryStatus.IN_BACKLOG
userStoryInBacklog.color = new Color(250,60,0)
def userStory = new UserStory("V-1", "Energiekataster Layer")
userStory.description = "Energiekataster Layer im GIS um über jeder VE die Energieausweise direkt zu sehen."
userStory.author = ffi
userStory.points = 3
userStory.color = new Color(250,60,0)
userStory.status = UserStoryStatus.IN_BACKLOG
productBacklog.addToUserstories(userStoryInBacklog)
def task = new Task("Definiere X in GIS", "Definiere X und lege Y an.")
task.author = ffi
userStory.addToTasks(task)
productBacklog.addToUserstories(userStory)
productBacklog
}
......@@ -64,19 +69,23 @@ class BootStrap {
sprintOne.due_date = new Date()
def userStoryInSprint = new UserStory("V-2", "Volltextsuche im GIS")
userStoryInSprint.description = "Volltextsuche im GIS ermglichen"
userStoryInSprint.author = ffi
userStoryInSprint.editor = ffi
userStoryInSprint.points = 2
userStoryInSprint.status = UserStoryStatus.OPEN
userStoryInSprint.color = new Color(250,180,0)
def userStory = new UserStory("V-2", "Volltextsuche im GIS")
userStory.description = "Volltextsuche im GIS ermglichen"
userStory.author = ffi
userStory.points = 2
userStory.status = UserStoryStatus.OPEN
userStory.color = new Color(250,180,0)
productBacklog.addToUserstories(userStoryInSprint)
def task = new Task("Menue links bei GIS", "Definiere Menue links vom GIS.")
task.author = ffi
userStory.addToTasks(task)
// def userStoryInSprint = productBacklog.userstories.find { it.shortId == "V-2" }
task = new Task("Passe Suche an", "Suchergebnisse sollen in dem Menue erscheinen.")
task.author = ffi
userStory.addToTasks(task)
sprintOne.addToUserstories(userStoryInSprint)
productBacklog.addToUserstories(userStory)
sprintOne.addToUserstories(userStory)
sprintOne
}
......
......@@ -34,6 +34,7 @@ grails.project.dependency.resolution = {
dependencies {
// specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes eg.
runtime "postgresql:postgresql:9.2-1000.jdbc4"
// runtime 'mysql:mysql-connector-java:5.1.20'
}
......
dataSource {
pooled = true
driverClassName = "org.h2.Driver"
username = "sa"
password = ""
driverClassName = "org.postgresql.Driver"
dialect = net.sf.hibernate.dialect.PostgreSQLDialect
}
hibernate {
cache.use_second_level_cache = true
......@@ -15,18 +14,25 @@ environments {
dataSource {
dbCreate = "create-drop" // one of 'create', 'create-drop', 'update', 'validate', ''
url = "jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000"
driverClassName = "org.h2.Driver"
username = "sa"
password = ""
}
}
test {
dataSource {
dbCreate = "update"
url = "jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000"
dbCreate = "create-drop"
url = "jdbc:postgresql://127.0.0.1:5432/scrumboard"
username = "postgres"
password = "ESNAdmin2012"
}
}
production {
dataSource {
dbCreate = "update"
url = "jdbc:h2:prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000"
dbCreate = "create-drop"
url = "jdbc:postgresql://127.0.0.1:5432/scrumboard"
username = "postgres"
password = "ESNAdmin2012"
pooled = true
properties {
maxActive = -1
......
......@@ -8,11 +8,20 @@ class SprintController {
def sprints = projects[0].sprints
def currentSprint = sprints.iterator().next()
def sprintUserStories = currentSprint.userstories
sprintUserStories = sprintUserStories.sort()
sprintUserStories = sprintUserStories.sort(true)
def sprintTasks = []
sprintUserStories.each {
sprintTasks.addAll(it.tasks)
}
sprintTasks = sprintTasks.sort(true)
render(view: "show", model: [projects: projects,
currentSprint: currentSprint,
sprintUserStories: sprintUserStories])
sprintUserStories: sprintUserStories,
sprintTasks: sprintTasks])
}
def create(String name) {
......
package scrumboard
import org.apache.shiro.SecurityUtils
import org.apache.shiro.subject.Subject
import scrumboard.shiro.User
......@@ -10,14 +11,15 @@ class UserStoryController {
Project project = Project.find {true} // TODO get current project; do not search in db
def currentUser = User.findByUsername(SecurityUtils.subject?.principal)
def userStory = UserStory.get(id) ?: new UserStory(currentUser, project.productBacklog)
def userStory = UserStory.read(id) ?: new UserStory(currentUser, project.productBacklog)
render ( template:"create", model: [userStory : userStory, storyPointMapping: project.storyPointMapping])
}
def create() {
Project project = Project.find {true} // TODO get current project; do not search in db
def currentUser = User.findByUsername(SecurityUtils.subject?.principal)
def userStory = new UserStory()
userStory.title = params.title
userStory.description = params.description
......@@ -25,8 +27,8 @@ class UserStoryController {
userStory.points = Double.valueOf(params.points)
userStory.status = UserStoryStatus.IN_BACKLOG
userStory.color = Color.DEFAULT
userStory.author = User.findByUsername(params.author)
userStory.editor = User.findByUsername(params.editor)
userStory.author = currentUser
userStory.editor = currentUser
userStory.productBacklog = project.productBacklog
userStory.save(failOnError:true)
......@@ -51,20 +53,81 @@ class UserStoryController {
userStory.assignedToSprint = null
}
userStory.save()
render ""
}
def moveTask(int id, String to) {
def task = Task.get(id)
Subject currentSubject = SecurityUtils.getSubject()
String username = currentSubject.getPrincipal()
User currentUser = User.findByUsername(username)
if (to == "opensprint") {
userStory.status = UserStoryStatus.OPEN
task.status = UserStoryStatus.OPEN
task.editor = null
task.started_date = null
task.finished_date = null
}
if (to == "inprogresssprint") {
userStory.status = UserStoryStatus.IN_PROGRESS
task.status = UserStoryStatus.IN_PROGRESS
task.editor = currentUser
task.started_date = new Date()
task.finished_date = null
}
if (to == "donesprint") {
userStory.status = UserStoryStatus.DONE
task.status = UserStoryStatus.DONE
task.editor = currentUser
if (!task.started_date)
task.started_date = new Date()
task.finished_date = new Date()
}
userStory.save()
task.save()
render ""
}
def showTask(int taskId) {
def task = Task.read(taskId)
render (template:"showTask", model: [task : task])
}
def updateTask(int taskId, String title, String description) {
def task = Task.get(taskId)
task.title = title
task.description = description
task.save()
render ""
}
def deleteTask(int taskId) {
def task = Task.get(taskId)
task.delete()
render ""
}
def createTask(int userStoryId) {
Subject currentSubject = SecurityUtils.getSubject()
String username = currentSubject.getPrincipal()
User currentUser = User.findByUsername(username)
def task = new Task("New Task", "")
task.author = currentUser
def parent = UserStory.get(userStoryId)
parent.addToTasks(task)
parent.save()
task = task.save()
render (template:"showTask", model: [task : task])
}
}
package scrumboard
import scrumboard.shiro.User;
class Task implements Comparable {
static final int maxShortDescription = 60
String title
String description
UserStoryStatus status = UserStoryStatus.OPEN
User author
User editor
Date started_date
Date finished_date
static belongsTo = [parent: UserStory]
static constraints = {
editor nullable: true
started_date nullable: true
finished_date nullable: true
}
Task(title, description) {
this.title = title
this.description = description
}
def String toString() {title};
int compareTo(obj) {
if (parent.compareTo(obj.parent) == 0)
title.compareTo(obj.title)
else
parent.compareTo(obj.parent)
}
def String getShortDescription() {
def result = description
if (description.size() > maxShortDescription) {
def indexOfSpace = description.indexOf(" ", maxShortDescription)
if (indexOfSpace != -1 && indexOfSpace < maxShortDescription + 40) {
result = description.substring(0, indexOfSpace)
} else {
result = description[0..maxShortDescription]
}
result += " [...]"
}
result
}
}
......@@ -17,12 +17,15 @@ class UserStory implements Comparable {
User author
User editor
Sprint assignedToSprint
static belongsTo = [productBacklog: ProductBacklog]
static hasMany = [ tasks: Task ]
static constraints = {
assignedToSprint nullable: true
editor nullable: true
}
UserStory(User user, ProductBacklog productBacklog) {
......@@ -41,7 +44,7 @@ class UserStory implements Comparable {
this.title = title
}
def String toString() {shortId + ": " + title};
def String toString() {shortId + ": " + title}
int compareTo(obj) {
shortId.compareTo(obj.shortId)
......
......@@ -10,5 +10,13 @@ class User {
username(nullable: false, blank: false, unique: true)
}
static mapping = {
table 'userCustom'
}
def hasRole(Role r) {
roles.contains(r)
}
def String toString() {username};
}
package scrumboard
class MovableColumnTaskTagLib {
def movableColumnTask = { attrs ->
def name = attrs["name"]
out << """ \$( ".${name}" ).sortable({
connectWith: ".${name}",
cursor: 'pointer',
start: function(event, ui) {
item${name} = ui.item;
newList${name} = ui.item.parent().parent();
},
stop: function(event, ui) {
\$.ajax({
url: "/scrumboard/userStory/moveTask?id=" + item${name}.find(".hidden").text() + "&to=" + newList${name}.attr('id')
});
},
change: function(event, ui) {
if(ui.sender) newList${name} = ui.placeholder.parent().parent();
}
});
\$( ".${name}" ).disableSelection();"""
}
}
package scrumboard
class MovableColumnTagLib {
def movableColumn = { attrs ->
class MovableColumnUserStoryTagLib {
def movableColumnUserStory = { attrs ->
def name = attrs["name"]
out << """ \$( ".${name}" ).sortable({
......
package scrumboard
class NoteTaskTagLib {
def noteTask = { attrs ->
def tasks = attrs["tasks"]
if (attrs["status"]) {
tasks = tasks.findAll {
task -> task.status.toString() == attrs["status"]
}
}
tasks.eachWithIndex { item, index ->
if (index % attrs["columnlength"].toInteger() == attrs["columnindex"].toInteger()) {
out << """<div class='ui-widget ui-corner-all task'>
<div class='hidden'>${item.id}</div>
<div class='ui-widget-header ui-corner-top taskHeader' style='background: ${item.parent.color.toHex()} !important;'>${item}</div>
<div class='ui-widget-content ui-corner-bottom' >${item.getShortDescription()}</div>
</div> """
}
}
}
}
package scrumboard
import scrumboard.UserStory
import scrumboard.UserStoryStatus
class NoteTagLib {
def note = { attrs ->
class NoteUserStoryTagLib {
def noteUserStory = { attrs ->
def userStories = attrs["userStories"]
def productBacklog = attrs["productBacklog"] ?: false
if (attrs["status"]) {
userStories = userStories.findAll {
userStory -> userStory.status.toString() == attrs["status"]
}
userStories = userStories.findAll { userStory ->
userStory.status.toString() == attrs["status"]
}
}
userStories.eachWithIndex { UserStory item, index ->
if (index % attrs["columnlength"].toInteger() == attrs["columnindex"].toInteger()) {
out << getNewUserStoryString(item, productBacklog)
}
}
}
private static getNewUserStoryString(item, productBacklog) {
def colorClass = determineColor(item, productBacklog)
def style = (productBacklog) ? "" : "background: ${item.color.toHex()} !important;"
"""<div class='ui-widget ui-corner-all'>
<div class='hidden'>${item.id}</div>
<div class='ui-widget-header ui-corner-top ${colorClass}' style='${style}'>${item}</div>
<div class='ui-widget-content ui-corner-bottom' >${item.getShortDescription()}</div>
</div> """
"""<div class='ui-widget ui-corner-all userStory'>
<div class='hidden'>${item.id}</div>
<div class='ui-widget-header ui-corner-top userStoryHeader ${colorClass}' style='${style}'>${item}</div>
<div class='ui-widget-content ui-corner-bottom' >${item.getShortDescription()}</div>
</div>
"""
}
private static determineColor(UserStory userStory, productBacklog) {
if (productBacklog) {
if (!userStory.assignedToSprint) {
......
package scrumboard
class ShowTaskDialogTagLib {
def showTaskDialog = { attrs ->
out << """\$( ".task" ).dblclick(function() {
\$( '.ui-dialog-titlebar' ).attr('style', ("background", \$(this).find('.taskHeader').attr('style')))
\$.ajax({
url: "/scrumboard/userStory/showTask?taskId=" + \$(this).find(".hidden").text()
}).done(function ( data ) {
\$( "#dialog-showTask" ).html(data);
\$( "#dialog-showTask" ).dialog( "open" );
});
});
\$( '#dialog-showTask' ).dialog({
autoOpen: false,
height: 600,
width: 650,
modal: true,
resizable: false,
buttons: {
'Save': function() {
\$.ajax({
url: "/scrumboard/userStory/updateTask?" + \$('#showTaskForm').serialize()
}).done(function ( data ) {
\$( '#dialog-showTask' ).dialog( 'close' );
location.reload();
});
},
'Delete': function() {
\$("#dialog-sureToDelete").dialog({
bgiframe: true,
autoOpen: true,
height: 150,
modal: true,
resizable: false,
buttons: {
Yes: function() {
\$.ajax({
url: "/scrumboard/userStory/deleteTask?taskId=" + \$('#taskId').val()
}).done(function ( data ) {
\$(this).dialog('close');
\$( '#dialog-showTask' ).dialog( 'close' );
location.reload();
});
},
No: function() {
\$(this).dialog('close');
}
}
});
},
Cancel: function() {
\$( this ).dialog( 'close' );
}
},
close: function() {
}
});"""
}
}
......@@ -2,7 +2,7 @@ package scrumboard
class ShowUserStoryDialogTagLib {
def showUserStoryDialog = { attrs ->
out << """\$( ".ui-widget" ).dblclick(function() {
out << """\$( ".userStory" ).dblclick(function() {
\$.ajax({
url: "/scrumboard/userStory/show?id=" + \$(this).find(".hidden").text()
}).done(function ( data ) {
......
<!doctype html>
<html>
<head>
<meta name="layout" content="main">
<style>
.ui-widget-header .ui-icon {
float: right;
}
.ui-sortable-placeholder {
border: 1px dotted gray;
visibility: visible !important;
height: 50px !important;
}
.ui-sortable-placeholder * {
visibility: hidden;
}
</style>
<meta name="layout" content="main">
<style>
.ui-widget-header .ui-icon { float: right; }
.ui-sortable-placeholder { border: 1px dotted gray; visibility: visible !important; height: 50px !important; }
.ui-sortable-placeholder * { visibility: hidden; }
</style>
</head>
<body>
<h1 id="backloglabel">
<img id="AddUserStory" style="cursor: pointer;" alt="Add"
src="/scrumboard/images/add.png"></img>&nbsp;&nbsp;&nbsp;Backlog
<h1 id="backloglabel"><img id="AddUserStory" style="cursor: pointer;" alt="Add" src="/scrumboard/images/add.png"></img>&nbsp;&nbsp;&nbsp;Backlog</h1>
<hr id="boarddivider" width="1px" size="768px" class="centerVerticalLine"></hr>
<h1 id="sprintbackloglabel"><img id="AddSprint" style="cursor: pointer;" alt="Add" src="/scrumboard/images/add.png"></img>&nbsp;&nbsp;&nbsp;<g:select name="sprints"
from="${sprints}" style="margin-top:-6px;"/>&nbsp;&nbsp;&nbsp;<a href="/scrumboard/sprint/show?id=0"><img id="ShowSprint" alt="show" src="/scrumboard/images/east-mini.png"></img></a>
</h1>
<hr id="boarddivider" width="1px" size="768px"
class="centerVerticalLine"></hr>
<h1 id="sprintbackloglabel">
<img id="AddSprint" style="cursor: pointer;" alt="Add"
src="/scrumboard/images/add.png"></img>&nbsp;&nbsp;&nbsp;
<g:select name="sprints" from="${sprints}" style="margin-top:-6px;" />
&nbsp;&nbsp;&nbsp;<a href="/scrumboard/sprint/show?id=0"><img
id="ShowSprint" alt="show" src="/scrumboard/images/east-mini.png"></img></a>
</h1>
<div id="backlog">
<div id="columnbacklogone" class="column">
<g:note userStories="${backlogUserStories}" columnindex="0"
columnlength="4" productBacklog="true" />
</div>
<div id="columnbacklogtwo" class="column">
<g:note userStories="${backlogUserStories}" columnindex="1"
columnlength="4" productBacklog="true" />
</div>
<div id="columnbacklogthree" class="column">
<g:note userStories="${backlogUserStories}" columnindex="2"
columnlength="4" productBacklog="true" />
</div>
<div id="columnbacklogfour" class="column">
<g:note userStories="${backlogUserStories}" columnindex="3"
columnlength="4" productBacklog="true" />
</div>
<div id="backlog">
<div id="columnbacklogone" class="column">
<g:noteUserStory userStories="${backlogUserStories}" columnindex="0" columnlength="4" productBacklog="true" />
</div>
<div id="sprintbacklog">
<div id="columnsprintbacklogone" class="column">
<g:note userStories="${sprintUserStories}" columnindex="0"
columnlength="4" />
</div>
<div id="columnsprintbacklogtwo" class="column">
<g:note userStories="${sprintUserStories}" columnindex="1"
columnlength="4" />
</div>
<div id="columnsprintbacklogthree" class="column">
<g:note userStories="${sprintUserStories}" columnindex="2"
columnlength="4" />
</div>
<div id="columnsprintbacklogfour" class="column">
<g:note userStories="${sprintUserStories}" columnindex="3"
columnlength="4" />
</div>
<div id="dialog-addSprint" title="Create new sprint">
<form>
<fieldset>
<label for="name">Name</label> <input type="text" name="name"
id="name" class="text ui-widget-content ui-corner-all" />
</fieldset>
</form>
</div>
<div id="columnbacklogtwo" class="column">
<g:noteUserStory userStories="${backlogUserStories}" columnindex="1" columnlength="4" productBacklog="true" />
</div>
<div id="columnbacklogthree" class="column">
<g:noteUserStory userStories="${backlogUserStories}" columnindex="2" columnlength="4" productBacklog="true" />
</div>
<div id="columnbacklogfour" class="column">
<g:noteUserStory userStories="${backlogUserStories}" columnindex="3" columnlength="4" productBacklog="true" />
</div>
</div>
<div id="dialog-showUserStory" title="Show User Story"></div>
<script type="text/javascript">
$(function() {
<g:movableColumn name="column"/>
$(".ui-widget").find(".ui-widget-header").prepend(
"<span class='icon'>+</span>").end();
</