Exemplo n.º 1
0
def taskEditPost(handler, ids, p_hours, p_status, p_goal, p_assigned=[], p_include={}):
    handler.title("Edit tasks")
    requirePriv(handler, "Write")

    allIDs = map(int, uniq(ids.split(",")))
    ids = map(lambda i: to_int(i, "include", ErrorBox.die), p_include.keys())
    if not set(ids) <= set(allIDs):
        ErrorBox.die("Included tasks don't match query arguments")

    tasks = dict((id, Task.load(id)) for id in ids)
    if not all(tasks.values()):
        ids = [str(id) for (id, task) in tasks.iteritems() if not task]
        ErrorBox.die(
            "No %s with %s %s"
            % ("task" if len(ids) == 1 else "tasks", "ID" if len(ids) == 1 else "IDs", ", ".join(ids))
        )
    tasks = [tasks[id] for id in ids]
    if len(set(task.sprint for task in tasks)) > 1:
        ErrorBox.die("All tasks must be in the same sprint")
    sprint = (tasks[0] if len(tasks) > 0 else Task.load(allIDs[0])).sprint
    if sprint.isHidden(handler.session["user"]):
        ErrorBox.die(
            "No %s with %s %s"
            % ("task" if len(ids) == 1 else "tasks", "ID" if len(ids) == 1 else "IDs", ", ".join(ids))
        )
    if not sprint.canEdit(handler.session["user"]):
        ErrorBox.die("You don't have permission to modify this sprint")

    assignedids = set(to_int(i, "assigned", ErrorBox.die) for i in p_assigned)

    changes = {
        "assigned": False if assignedids == set() else {User.load(assignedid) for assignedid in assignedids},
        "hours": False if p_hours == "" else int(p_hours),
        "status": False if p_status == "" else p_status,
        "goal": False if p_goal == "" else Goal.load(int(p_goal)),
    }

    if changes["assigned"] and not all(changes["assigned"]):
        ErrorBox.die("Invalid assignee")
    if changes["assigned"] and not set(changes["assigned"]).issubset(sprint.members):
        ErrorBox.die("Unable to assign tasks to non-sprint members")
    if changes["goal"] and changes["goal"].sprint != sprint:
        ErrorBox.die("Unable to set goal to a goal outside the sprint")

    changed = set()
    for task in tasks:
        for field, value in changes.iteritems():
            if value is not False and getattr(task, field) != value:
                setattr(task, field, value)
                changed.add(task)
                Event.taskUpdate(handler, task, field, value)

    if len(changed) == 0:
        delay(handler, WarningBox("No changes necessary", close=3, fixed=True))
    else:
        for task in changed:
            task.saveRevision(handler.session["user"])
        delay(handler, SuccessBox("Updated %d %s" % (len(changed), "task" if len(changed) == 1 else "tasks")))
    redirect("/sprints/%d" % sprint.id)
Exemplo n.º 2
0
def newNoteModify(handler, taskid, id, p_action):
    handler.title("New Note")
    requirePriv(handler, "User")

    if p_action != "delete":
        ErrorBox.die("Invalid Action", "Unrecognized action <b>%s</b>" % p_action)

    taskid = int(taskid)
    task = Task.load(taskid)
    if not task or task.sprint.isHidden(handler.session["user"]):
        ErrorBox.die("Invalid Task", "No task with ID <b>%d</b>" % taskid)

    id = int(id)
    note = Note.load(id)
    if not note:
        ErrorBox.die("Invalid Note", "No note with ID <b>%d</b>" % noteid)
    elif note.task != task:  # Doesn't really matter, but shouldn't happen
        ErrorBox.die("Task mismatch", "Note/task mismatch")
    elif note.user != handler.session["user"]:
        ErrorBox.die("Permission denied", "Notes can only be deleted by their creators")

    note.delete()
    delay(handler, SuccessBox("Deleted note", close=3))
    Event.deleteNote(handler, note)
    redirect("/tasks/%d" % task.id)
Exemplo n.º 3
0
def taskByID(context, id):
	id = to_int(id, 'id', fail)
	context['task'] = Task.load(id)
	if not context['task']:
		fail("No task with ID %d" % id)
	print context['task'].safe.name
	return ('task', "[task %d] $" % id)
Exemplo n.º 4
0
def newNotePost(handler, taskid, p_body, dryrun=False):
    handler.title("New Note")
    if dryrun:
        handler.wrappers = False
    requirePriv(handler, "User")

    taskid = int(taskid)
    task = Task.load(taskid)
    if not task or task.sprint.isHidden(handler.session["user"]):
        ErrorBox.die("Invalid Task", "No task with ID <b>%d</b>" % taskid)

    note = Note(task.id, handler.session["user"].id, p_body)

    if dryrun:
        print note.render()
    else:
        if p_body == "":
            ErrorBox.die("Empty Body", "No note provided")
        note.save()
        Event.newNote(handler, note)
        redirect("/tasks/%d#note%d" % (task.id, note.id))
Exemplo n.º 5
0
def sprintPost(handler, sprintid, p_id, p_rev_id, p_field, p_value):
	def die(msg):
		print msg
		done()

	handler.wrappers = False
	sprintid = to_int(sprintid, 'sprintid', die)
	p_id = to_int(p_id, 'id', die)
	p_rev_id = to_int(p_rev_id, 'rev_id', die)

	if not handler.session['user']:
		die("You must be logged in to modify tasks")

	sprint = Sprint.load(sprintid)
	if not sprint or sprint.isHidden(handler.session['user']):
		die("There is no sprint with ID %d" % sprintid)
	elif not (sprint.isActive() or sprint.isPlanning()):
		die("Unable to modify inactive sprint")
	elif not sprint.canEdit(handler.session['user']):
		die("You don't have permission to modify this sprint")

	# Special case group moves; p_id is the group ID, not task
	task = None
	if p_field != 'groupmove':
		task = Task.load(p_id)
		if not task:
			die("Task %d does not exist" % p_id)
		if task.sprint != sprint:
			die("Attempting to modify task outside the specified sprint")
		if task.revision != p_rev_id: #TODO Implement collision support
			die("Collision with %s detected. Changes not saved" % task.creator)

	if p_value.strip() == '':
		die("Value cannot be empty")

	if p_field in ['status', 'name', 'goal', 'assigned', 'hours', 'deleted']:
		for case in switch(p_field):
			if case('status') or case('name'):
				parsedValue = p_value
				break
			elif case('goal'):
				parsedValue = None
				if p_value != '0':
					parsedValue = Goal.load(to_int(p_value, 'goal', die))
					if not parsedValue:
						die("Unknown goal: <b>%s</b>" % stripTags(p_value))
					if parsedValue.sprint != sprint:
						die("Attempting to use goal outside the specified sprint")
				break
			elif case('assigned'):
				parsedValue = set(User.load(username = username) for username in p_value.split(' '))
				if not all(parsedValue):
					die("Unknown user(s): <b>%s</b>" % stripTags(p_value))
				break
			elif case('hours'):
				parsedValue = int(p_value)
				break
			elif case('deleted'):
				parsedValue = True if p_value == 'true' else False if p_value == 'false' else die("Bad value for field 'deleted'")
				break

		if task.__getattribute__(p_field) != parsedValue: # Only save if the field has changed
			task.__setattr__(p_field, parsedValue)

			# Is this within the 5-minute window, by the same user?
			# If we're in pre-planning, the task's timestamp will be in the future, so (ts - task.timestamp) will be negative, which satisfies the check
			if task.creator == handler.session['user'] and (dateToTs(getNow()) - task.timestamp) < 5*60:
				task.save()
			else:
				task.saveRevision(handler.session['user'])

			Event.taskUpdate(handler, task, p_field, parsedValue)

	elif p_field == 'taskmove':
		if ':' not in p_value:
			die("Malformed value")
		newGroupID, newSeq = map(lambda i: to_int(i, 'value', die), p_value.split(':', 1))
		newGroup = Group.load(newGroupID)
		if not newGroup:
			die("No group with ID %d" % newGroupID)
		maxSeq = len(Task.loadAll(groupid = newGroup.id))
		if task.group != newGroup:
			maxSeq += 1
		if not 1 <= newSeq <= maxSeq:
			die("Bad sequence number")

		task.move(newSeq, newGroup)

	elif p_field == 'groupmove':
		group = Group.load(p_id)
		if not group:
			die("Group %d does not exist" % p_id)
		if group.sprint != sprint:
			die("Attempting to modify group outside the specified sprint")

		newSeq = to_int(p_value, 'value', die)
		if not 1 <= newSeq <= len(sprint.getGroups()):
			die("Bad sequence number")

		group.move(newSeq)

	else:
		die("Unexpected field name: %s" % stripTags(p_field))

	handler.responseCode = 299
	if task is not None:
		print task.revision
Exemplo n.º 6
0
def distributeUpdate(handler, p_sprint, p_targetUser=None, p_task=None):
    def die(msg):
        print toJS({"error": msg})
        done()

    handler.title("Distribute Tasks")
    requirePriv(handler, "Write")
    handler.wrappers = False
    handler.contentType = "application/json"

    sprintid = int(p_sprint)
    sprint = Sprint.load(sprintid)
    if not sprint or sprint.isHidden(handler.session["user"]):
        die("No sprint with ID %d" % sprintid)
    if not sprint.canEdit(handler.session["user"]):
        die("Unable to edit sprint")

        # Make changes
    if p_targetUser != None and p_task != None:
        task = Task.load(int(p_task))
        if not task:
            die("Invalid task ID")

        if p_targetUser == "deferred":
            task.status = "deferred"
            task.hours = 0
            if task.creator == handler.session["user"] and (dateToTs(getNow()) - task.timestamp) < 5 * 60:
                task.save()
            else:
                task.saveRevision(handler.session["user"])
            Event.taskUpdate(handler, task, "status", task.status)
        else:
            userid = to_int(p_targetUser, "targetUser", die)
            user = User.load(userid)
            if not user:
                die("No user with ID %d" % userid)

            task.assigned = {user}
            if task.creator == handler.session["user"] and (dateToTs(getNow()) - task.timestamp) < 5 * 60:
                task.save()
            else:
                task.saveRevision(handler.session["user"])
            Event.taskUpdate(handler, task, "assigned", task.assigned)

    def makeTaskMap(task):
        return {
            "id": task.id,
            "groupid": task.group.id,
            "hours": task.hours,
            "name": task.name,
            "important": task.hours > 8,
            "team": len(task.assigned) > 1,
        }

        # Return current info

    tasks = filter(lambda task: task.stillOpen(), sprint.getTasks())
    avail = Availability(sprint)

    deferredTasks = filter(lambda task: task.status == "deferred", sprint.getTasks())
    m = {
        "deferred": {
            "username": "******",
            "groups": [
                {"id": group.id, "name": group.name}
                for group in sorted(
                    (group for group in set(task.group for task in deferredTasks)), key=lambda group: group.seq
                )
            ],
            "tasks": [makeTaskMap(task) for task in deferredTasks],
        }
    }

    for user in sprint.members:
        userTasks = filter(lambda task: user in task.assigned, tasks)
        m[user.id] = {
            "username": user.username,
            "hours": sum(task.hours for task in userTasks),
            "availability": avail.getAllForward(getNow().date(), user),
            "groups": [
                {"id": group.id, "name": group.name}
                for group in sorted(
                    (group for group in set(task.group for task in userTasks)), key=lambda group: group.seq
                )
            ],
            "tasks": [makeTaskMap(task) for task in userTasks],
        }

    print toJS(m)
Exemplo n.º 7
0
def task(handler, ids):
    requirePriv(handler, "User")
    Chart.include()

    Markdown.head(".note .text .body pre code")
    print '<script src="/static/jquery.typing-0.2.0.min.js" type="text/javascript"></script>'
    undelay(handler)

    tasks = {}
    if "," not in ids:  # Single ID
        ids = [int(ids)]
        tasks[ids[0]] = Task.load(ids[0])

        def header(task, text, level):
            if level == 1:
                handler.title(text)
            else:
                print "<h%d>%s</h%d>" % (level, text, level)

    else:  # Many IDs
        ids = map(int, uniq(ids.split(",")))
        tasks = {id: Task.load(id) for id in ids}
        handler.title("Task Information")

        if not all(tasks.values()):
            ids = [str(id) for (id, task) in tasks.iteritems() if not task]
            ErrorBox.die(
                "No %s with %s %s"
                % ("task" if len(ids) == 1 else "tasks", "ID" if len(ids) == 1 else "IDs", ", ".join(ids))
            )

        if len(set(task.sprint for task in tasks.values())) == 1:  # All in the same sprint
            print '<small>(<a href="/sprints/%d?search=highlight:%s">Show in backlog view</a>)</small><br><br>' % (
                tasks.values()[0].sprint.id,
                ",".join(map(str, ids)),
            )

        for id in ids:
            print '<a href="#task%d">%s</a><br>' % (id, tasks[id].safe.name)

        def header(task, text, level):
            if level == 1:
                print "<hr>"
                print '<a name="task%d"></a>' % task.id
                print '<a href="#task%d"><h2>%s</h2></a>' % (task.id, text)
            else:
                print "<h%d>%s</h%d>" % (level + 1, text, level + 1)

    for id in ids:
        task = tasks[id]
        if not task or task.sprint.isHidden(handler.session["user"]):
            ErrorBox.die("Tasks", "No task with ID <b>%d</b>" % id)
        elif not task.sprint.canView(handler.session["user"]):
            ErrorBox.die("Private", "You must be a sprint member to view this sprint's tasks")
        revs = task.getRevisions()
        startRev = task.getStartRevision()

        header(task, task.safe.name, 1)

        header(task, "Info", 2)
        print 'Part of <a href="/sprints/%d">%s</a>, <a href="/sprints/%d#group%d">%s</a>' % (
            task.sprintid,
            task.sprint,
            task.sprintid,
            task.groupid,
            task.group,
        ),
        if task.goal:
            print 'to meet the goal&nbsp;&nbsp;<img class="bumpdown" src="/static/images/tag-%s.png">&nbsp;<a href="/sprints/%d?search=goal:%s">%s</a>' % (
                task.goal.color,
                task.sprintid,
                task.goal.color,
                task.goal.safe.name,
            ),
        print "<br>"
        print "Assigned to %s<br>" % ", ".join(map(str, task.assigned))
        print "Last changed %s ago<br><br>" % timesince(tsToDate(task.timestamp))
        hours, total, lbl = task.hours, startRev.hours, "<b>%s</b>" % statuses[task.status].text
        if task.deleted:
            if task.sprint.canEdit(handler.session["user"]):
                print '<form method="post" action="/sprints/%d">' % task.sprint.id
                print '<input type="hidden" name="id" value="%d">' % task.id
                print '<input type="hidden" name="rev_id" value="%d">' % task.revision
                print '<input type="hidden" name="field" value="deleted">'
                print '<input type="hidden" name="value" value="false">'
                print "Deleted (%s)" % Button("undelete", id="undelete").mini().positive()
                print "</form>"
            else:
                print "Deleted"
            print "<br>"
        elif task.status == "complete":
            print ProgressBar(lbl, total - hours, total, zeroDivZero=True, style="progress-current-green")
        elif task.status in ("blocked", "canceled", "deferred", "split"):
            hours = filter(lambda rev: rev.hours > 0, revs)
            hours = hours[-1].hours if len(hours) > 0 else 0
            print ProgressBar(lbl, total - hours, total, zeroDivZero=True, style="progress-current-red")
        else:
            print ProgressBar(lbl, total - hours, total, zeroDivZero=True)

        header(task, "Notes", 2)
        for note in task.getNotes():
            print '<div id="note%d" class="note">' % note.id
            print '<form method="post" action="/tasks/%d/notes/%d/modify">' % (id, note.id)
            print '<div class="avatar"><img src="%s"></div>' % note.user.getAvatar()
            print '<div class="text">'
            print '<div class="title"><a class="timestamp" href="#note%d">%s</a> by <span class="author">%s</span>' % (
                note.id,
                tsToDate(note.timestamp).replace(microsecond=0),
                note.user.safe.username,
            )
            if note.user == handler.session["user"]:
                print '<button name="action" value="delete" class="fancy mini danger">delete</button>'
            print "</div>"
            print '<div class="body markdown">%s</div>' % note.render()
            print "</div>"
            print "</form>"
            print "</div>"

        print '<div class="note new-note">'
        print '<form method="post" action="/tasks/%d/notes/new">' % id
        print '<div class="avatar"><div><img src="%s"></div></div>' % handler.session["user"].getAvatar()
        print '<div class="text">'
        print '<div class="title">'
        print "<b>New note</b>"
        print '<a target="_blank" href="/help/markdown" class="fancy mini">help</a>'
        print "</div>"
        print '<div class="body"><textarea name="body" class="large"></textarea></div>'
        print Button("Post").post().positive()
        print "<hr>"
        print '<div class="body markdown"><div id="preview"></div></div>'
        print "</div>"
        print "</form>"
        print "</div>"

        print '<button class="btn start-new-note">Add Note</button>'
        print '<div class="clear"></div>'

        header(task, "History", 2)
        chart = TaskChart("chart%d" % id, task)
        chart.js()

        chart.placeholder()
        showHistory(task, False)
        print "<br>"
Exemplo n.º 8
0
def taskEdit(handler, ids):
    handler.title("Edit tasks")
    requirePriv(handler, "Write")

    ids = map(int, uniq(ids.split(",")))
    tasks = dict((id, Task.load(id)) for id in ids)
    if not all(tasks.values()):
        ids = [str(id) for (id, task) in tasks.iteritems() if not task]
        ErrorBox.die(
            "No %s with %s %s"
            % ("task" if len(ids) == 1 else "tasks", "ID" if len(ids) == 1 else "IDs", ", ".join(ids))
        )
    tasks = [tasks[id] for id in ids]
    if len(set(task.sprint for task in tasks)) > 1:
        ErrorBox.die("All tasks must be in the same sprint")
    sprint = tasks[0].sprint
    if sprint.isHidden(handler.session["user"]):
        ErrorBox.die(
            "No %s with %s %s"
            % ("task" if len(ids) == 1 else "tasks", "ID" if len(ids) == 1 else "IDs", ", ".join(ids))
        )
    if not (sprint.isActive() or sprint.isPlanning()):
        ErrorBox.die("You can't mass-edit tasks from an inactive sprint")
    elif not sprint.canEdit(handler.session["user"]):
        ErrorBox.die("You don't have permission to modify this sprint")

    print "<h3>New values</h3>"
    print '<form method="post" action="/tasks/%s/edit">' % ",".join(map(str, ids))
    print '<table id="task-edit-values" class="list">'
    print '<tr><td class="left">Assigned:</td><td class="right">'
    print '<select id="select-assigned" name="assigned[]" data-placeholder="(unchanged)" multiple>'
    for user in sorted(sprint.members):
        print '<option value="%d">%s</option>' % (user.id, user.safe.username)
    print "</select>"
    print "</td></tr>"
    print '<tr><td class="left">Hours:</td><td class="right"><input type="text" name="hours" class="hours"></td></tr>'
    print '<tr><td class="left">Status:</td><td class="right"><select name="status">'
    print '<option value="">(unchanged)</option>'
    for statusSet in statusMenu:
        for name in statusSet:
            print '<option value="%s">%s</option>' % (name, statuses[name].text)
    print "</select></td></tr>"
    print '<tr><td class="left">Sprint Goal:</td><td class="right"><select name="goal">'
    print '<option value="">(unchanged)</option>'
    print '<option value="0">None</option>'
    for goal in sprint.getGoals():
        print '<option value="%d">%s</option>' % (goal.id, goal.safe.name)
    print "</select></td></tr>"
    print '<tr><td class="left">&nbsp;</td><td class="right">'
    print Button("Save", type="submit").positive()
    print Button("Cancel", url="/sprints/%d" % sprint.id, type="button").negative()
    print "</td></tr>"
    print "</table>"
    print "<br>"

    print "<h3>Current values</h3>"
    print '<table border=0 cellspacing=0 cellpadding=2 class="task-edit">'
    for task in tasks:
        print '<tr><td class="task-name" colspan="4"><input type="checkbox" id="task%d" name="include[%d]" checked="true">&nbsp;<label for="task%d">%s</label></td></tr>' % (
            task.id,
            task.id,
            task.id,
            task.safe.name,
        )
        print '<tr class="task-fields">'
        print '<td class="task-assigned">%s</td>' % ", ".join(map(str, task.assigned))
        print '<td class="task-hours"><img src="/static/images/time-icon.png">&nbsp;%d %s</td>' % (
            task.hours,
            "hour" if task.hours == 1 else "hours",
        )
        print '<td class="task-status"><img class="status" src="%s">&nbsp;%s</td>' % (task.stat.icon, task.stat.text)
        print '<td class="task-goal"><img class="goal" src="/static/images/tag-%s.png">&nbsp;%s</td>' % (
            (task.goal.color, task.goal.safe.name) if task.goal else ("none", "None")
        )
        print "</tr>"
    print "</table>"
    print "</form>"