Ejemplo n.º 1
0
def newGroupPost(handler, p_group, p_name):
	def die(msg):
		print msg
		done()

	handler.title('New Group')
	requirePriv(handler, 'User')
	handler.wrappers = False

	predid = to_int(p_group, 'group', die)
	pred = Group.load(predid)
	if not pred:
		die("No group with ID <b>%d</b>" % predid)
	elif p_name.strip() == '':
		die("Group must have a non-empty name")

	group = Group(pred.sprint.id, p_name, pred.seq + 1)
	group.save()

	handler.responseCode = 299
	delay(handler, """
<script type=\"text/javascript\">
$(document).ready(function() {
	$('#group%d').effect('highlight', {}, 3000);
});
</script>""" % group.id)
	print "/sprints/%d#group%d" % (group.sprint.id, group.id)
	Event.newGroup(handler, group)
Ejemplo n.º 2
0
def deleteGroupPost(handler, id, p_newGroup = None):
	def die(msg):
		print msg
		done()

	handler.title('Manage Group')
	requirePriv(handler, 'User')
	handler.wrappers = False

	id = to_int(id, 'id', die)
	if p_newGroup is not None:
		p_newGroup = to_int(p_newGroup, 'newGroup', die)

	group = Group.load(id)
	if not group:
		ErrorBox.die('Invalid Group', "No group with ID <b>%d</b>" % id)
	if not group.deletable:
		ErrorBox.die('Invalid Group', "Group is not deletable")
	if len(group.sprint.getGroups()) == 1:
		ErrorBox.die('Invalid Group', "Cannot delete the last group in a sprint")

	tasks = group.getTasks()
	newGroup = None
	if p_newGroup == 0: # Delete tasks
		for task in tasks:
			task.deleted = True
			task.revise()
	elif p_newGroup is not None: # Move tasks
		newGroup = Group.load(p_newGroup)
		if not newGroup:
			ErrorBox.die('Invalid Group', "No group with ID <b>%d</b>" % p_newGroup)

		# Move all the tasks to the end of the new group
		seq = len(newGroup.getTasks())
		for task in tasks:
			seq += 1
			task.move(seq, newGroup)
	elif len(tasks):
		ErrorBox.die('Invalid Request', "Missing new group request argument")

	sprintid = group.sprintid
	group.delete()
	Event.deleteGroup(handler, group)
	redirect("/sprints/%d%s" % (sprintid, "#group%d" % newGroup.id if newGroup else ''))
Ejemplo n.º 3
0
def newTaskPost(handler, p_group, p_name, p_goal, p_status, p_hours, p_assigned=[]):
    def die(msg):
        print msg
        done()

    requirePriv(handler, "User")
    handler.wrappers = False

    groupid = to_int(p_group, "group", die)
    group = Group.load(groupid)
    if not group or group.sprint.isHidden(handler.session["user"]):
        die("No group with ID <b>%d</b>" % groupid)

    sprint = group.sprint
    if 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")

    if p_name.strip() == "":
        die("Task must have a non-empty name")

    assignedids = set(to_int(i, "assigned", die) for i in p_assigned)
    assigned = set(User.load(assignedid) for assignedid in assignedids)
    if assigned == set():
        assigned.add(handler.session["user"] if handler.session["user"] in sprint.members else sprint.owner)
    if not all(assigned):
        die("Invalid assignee")

    goalid = to_int(p_goal, "goal", die)
    if goalid != 0:
        goal = Goal.load(goalid)
        if not goal:
            die("No goal with ID <b>%d</b>" % goalid)
        if goal.sprint != group.sprint:
            die("Goal does not belong to the correct sprint")

    hours = to_int(p_hours, "hours", die)

    task = Task(groupid, group.sprintid, handler.session["user"].id, goalid, p_name, p_status, hours)
    task.assigned |= assigned
    task.save()

    handler.responseCode = 299
    delay(
        handler,
        """
<script type=\"text/javascript\">
$(document).ready(function() {
	$('#task%d').effect('highlight', {}, 3000);
});
</script>"""
        % task.id,
    )
    delay(handler, SuccessBox("Added task <b>%s</b>" % task.safe.name, close=3, fixed=True))
    Event.newTask(handler, task)
Ejemplo n.º 4
0
def renameGroupPost(handler, id, p_name):
	def die(msg):
		print msg
		done()

	handler.title('Manage Group')
	requirePriv(handler, 'User')

	id = int(id)
	group = Group.load(id)
	if not group:
		ErrorBox.die('Invalid Group', "No group with ID <b>%d</b>" % id)
	elif p_name.strip() == '':
		ErrorBox.die('Invalid Name', "Group must have a non-empty name")

	oldName = group.name
	group.name = p_name
	group.save()
	Event.renameGroup(handler, group, oldName)
	redirect("/sprints/%d#group%d" % (group.sprintid, group.id))
Ejemplo n.º 5
0
def assignGroupGoalPost(handler, id, p_goal):
	def die(msg):
		print msg
		done()

	handler.title('Manage Group')
	requirePriv(handler, 'User')
	handler.wrappers = False

	id = int(id)
	group = Group.load(id)
	if not group:
		ErrorBox.die('Invalid Group', "No group with ID <b>%d</b>" % id)

	if p_goal == '0':
		goal = None
	else:
		goal = Goal.load(int(p_goal))
		if not goal:
			ErrorBox.die('Invalid Goal', "No goal with ID <b>%d</b>" % int(p_goal))
		elif not goal.sprint == group.sprint:
			ErrorBox.die('Invalid Goal', "Selected goal is not part of the correct sprint")

	for task in group.getTasks():
		if task.goal != goal:
			task.goal = goal

			if task.creator == handler.session['user'] and (dateToTs(getNow()) - task.timestamp) < 5*60:
				task.save()
			else:
				task.saveRevision(handler.session['user'])

			#TODO Event
			# NO

	redirect("/sprints/%d#group%d" % (group.sprintid, group.id))
Ejemplo n.º 6
0
def newGroup(handler, after):
	handler.title('New Group')
	requirePriv(handler, 'User')
	afterID = int(after)

	afterGroup = Group.load(afterID)
	if not afterGroup:
		ErrorBox.die('Invalid Group', "No group with ID <b>%d</b>" % afterID)

	print "<style type=\"text/css\">"
	print "table.list td.right > * {width: 400px;}"
	print "table.list td.right button {width: 200px;}" # Half of the above value
	print "</style>"
	print "<script type=\"text/javascript\">"
	print "next_url = '/sprints/%d';" % afterGroup.sprint.id
	print "</script>"
	print "<script src=\"/static/groups.js\" type=\"text/javascript\"></script>"

	print InfoBox('', id = 'post-status')

	print "<form method=\"post\" action=\"/groups/new\">"
	print "<table class=\"list\">"
	print "<tr><td class=\"left\">Sprint:</td><td class=\"right\"><select id=\"selectSprint\" disabled><option>%s</option></select></td></tr>" % afterGroup.sprint
	print "<tr><td class=\"left\">Predecessor:</td><td class=\"right\">"
	print "<select id=\"select-group\" name=\"group\" size=\"5\">"
	for sGroup in afterGroup.sprint.getGroups('name'):
		print "<option value=\"%d\"%s>%s</option>" % (sGroup.id, ' selected' if sGroup == afterGroup else '', sGroup.safe.name)
	print "</select>"
	print "</td></tr>"
	print "<tr><td class=\"left\">Name:</td><td class=\"right\"><input type=\"text\" name=\"name\" class=\"defaultfocus\"></td></tr>"
	print "<tr><td class=\"left\">&nbsp;</td><td class=\"right\">"
	print Button('Save', id = 'save-button', type = 'button').positive()
	print Button('Cancel', id = 'cancel-button', type = 'button').negative()
	print "</td></tr>"
	print "</table>"
	print "</form>"
Ejemplo n.º 7
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
Ejemplo n.º 8
0
def newTaskImportPost(handler, group, source, p_data):
    def die(msg):
        print msg
        done()

    handler.title("Import Tasks")
    requirePriv(handler, "User")
    handler.wrappers = False

    id = int(group)
    group = Group.load(id)
    if not group or group.sprint.isHidden(handler.session["user"]):
        die("No group with ID <b>%d</b>" % id)

    sprint = group.sprint
    if 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")

    id = int(source)
    source = Sprint.load(id)
    if not source:
        die("No sprint with ID <b>%d</b>" % id)

    try:
        data = fromJS(p_data)
    except ValueError:
        die("Improperly encoded data")
    if not isinstance(data, list) or not all(
        set(task.keys()) == {"name", "assigned", "status", "groupid", "hours"} for task in data
    ):
        die("Improperly encoded data")
    usernames = {user.username for user in sprint.members}
    if not all(
        set(task["assigned"].split(" ")) <= usernames
        and task["status"] in statuses
        and isinstance(task["groupid"], int)
        and Group.load(task["groupid"]) is not None
        and isinstance(task["hours"], int)
        and task["hours"] >= 0
        for task in data
    ):
        die("Invalid data")

    dataByGroup = {}
    for task in data:
        if task["groupid"] not in dataByGroup:
            dataByGroup[task["groupid"]] = []
        dataByGroup[task["groupid"]].append(task)

    newGroups = {}  # old sprint's group ID -> new sprint's new Group object
    for groupid in dataByGroup:
        oldGroup = Group.load(groupid)
        group = Group.load(sprintid=sprint.id, name=oldGroup.name)
        if not group:  # No group in this sprint with the right name
            if groupid in newGroups:  # Already made a new group
                group = newGroups[groupid]
            else:  # Need a new group
                group = newGroups[groupid] = Group(sprint.id, oldGroup.name)
                group.save()

        for taskData in dataByGroup[groupid]:
            task = Task(
                group.id,
                sprint.id,
                handler.session["user"].id,
                0,
                taskData["name"],
                taskData["status"],
                taskData["hours"],
                {User.load(username=username).id for username in taskData["assigned"].split(" ")},
            )
            task.save()
            Event.newTask(handler, task)

    numGroups, numTasks = len(newGroups), len(data)
    if numGroups > 0 and numGroups > 0:
        delay(
            handler,
            SuccessBox(
                "Added %d %s, %d %s"
                % (numGroups, "group" if numGroups == 1 else "groups", numTasks, "task" if numTasks == 1 else "tasks"),
                close=3,
                fixed=True,
            ),
        )
    elif numGroups > 0:
        delay(
            handler,
            SuccessBox("Added %d %s" % (numGroups, "group" if numGroups == 1 else "groups"), close=3, fixed=True),
        )
    elif numTasks > 0:
        delay(
            handler, SuccessBox("Added %d %s" % (numTasks, "task" if numTasks == 1 else "tasks"), close=3, fixed=True)
        )
    else:
        delay(handler, WarningBox("No changes", close=3, fixed=True))
    handler.responseCode = 299
Ejemplo n.º 9
0
def newTaskImport(handler, group, source=None, assigned=None):
    # 'assigned' is ignored, it's just in case the user gets here from a filtered backlog
    handler.title("New Tasks")
    requirePriv(handler, "User")
    id = int(group)

    print tabs.format(id).where("import")

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

    sprint = group.sprint
    if not (sprint.isActive() or sprint.isPlanning()):
        ErrorBox.die("Sprint Closed", "Unable to modify inactive sprint")
    elif not sprint.canEdit(handler.session["user"]):
        ErrorBox.die("Permission Denied", "You don't have permission to modify this sprint")

    sprints = sprint.project.getSprints()
    sprintIdx = sprints.index(sprint)
    prevSprint = sprints[sprintIdx - 1] if sprintIdx > 0 else None

    if not source:
        print "Select a sprint to import from:<br><br>"
        print '<form method="get" action="/tasks/new/import">'
        print '<input type="hidden" name="group" value="%d">' % group.id
        print '<select name="source" id="import-source">'
        for projectIter in Project.getAllSorted(handler.session["user"], sprint.project):
            print '<optgroup label="%s">' % projectIter.safe.name
            for sprintIter in projectIter.getSprints():
                print '<option value="%d"%s>%s</option>' % (
                    sprintIter.id,
                    " selected" if sprintIter == prevSprint else "",
                    sprintIter.safe.name,
                )
            print "</optgroup>"
        print "</select>"
        print "<br><br>"
        print Button("Next").positive().post()
        print "</form>"
    else:
        id = int(source)
        source = Sprint.load(id)
        if not source:
            ErrorBox.die("Invalid Sprint", "No sprint with ID <b>%d</b>" % id)

        print '<script type="text/javascript">'
        nextURL = "/sprints/%d" % sprint.id
        if assigned:
            nextURL += "?search=assigned:%s" % stripTags(assigned.replace(" ", ","))
        print "next_url = %s;" % toJS(nextURL)
        print 'post_url = "/tasks/new/import?group=%d&source=%d";' % (group.id, source.id)
        print "scrummaster = %s;" % toJS(sprint.owner.username)
        print "TaskTable.init();"
        print "</script>"

        print '<b>Source sprint</b>: <a href="/sprints/%d">%s</a><br>' % (source.id, source.name)
        print '<b>Target sprint</b>: <a href="/sprints/%d">%s</a><br><br>' % (sprint.id, sprint.name)
        print "All incomplete tasks are listed here, with their current values from the source sprint. You can change any of the fields before importing. Only checked tasks will be imported<br><br>"

        assignedList = [sprint.owner] + list(sprint.members - {sprint.owner})
        print TaskTable(
            source,
            editable=True,
            assignedList=assignedList,
            checkbox=True,
            status=True,
            name=True,
            assigned=True,
            hours=True,
            debug=isDevMode(handler),
        )

        print InfoBox("Loading...", id="post-status", close=True)
        print Button("Import", id="save-button", type="button").positive()
        print Button("Cancel", id="cancel-button", type="button").negative()
        print "</form><br><br>"
Ejemplo n.º 10
0
def newTaskMany(handler, group, p_body, dryrun=False):
    def die(msg):
        print msg
        done()

    handler.wrappers = False
    requirePriv(handler, "User")
    id = int(group)

    defaultGroup = Group.load(id)
    if not defaultGroup or defaultGroup.sprint.isHidden(handler.session["user"]):
        die("No group with ID <b>%d</b>" % id)

    sprint = defaultGroup.sprint
    if 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")

    group = defaultGroup
    groups = [group]
    newGroups = []
    tasks = {group: []}
    sep = "|"
    lines = map(lambda x: x.strip(" \r\n"), p_body.split("\n"))
    errors = []
    defaultAssigned = {handler.session["user"] if handler.session["user"] in sprint.members else sprint.owner}

    for line in lines:
        if line == "":
            group = defaultGroup
        elif line[0] == "#":  # Comment
            continue
        elif len(line) == 1:  # Separator
            sep = line[0]
        elif (
            line[0] == "[" and line[-1] == "]" and set(line[1:-1].split(" ")) <= set(u.username for u in sprint.members)
        ):  # Default assigned
            defaultAssigned = {User.load(username=username) for username in line[1:-1].split(" ")}
        elif line[-1] == ":":  # Group
            line = line[:-1]
            group = Group.load(sprintid=sprint.id, name=line)
            if not group:  # Check if new group already defined
                for newGroup in newGroups:
                    if newGroup.name == line:
                        group = newGroup
                        break
            if not group:  # Make new group
                group = Group(sprint.id, line)
                newGroups.append(group)
                group.id = -len(newGroups)
            if not group in groups:  # First time this group has been used in the script
                groups.append(group)
                tasks[group] = []
        else:
            parts = line.split(sep)
            name, assigned, status, hours = None, None, None, None
            if not 2 <= len(parts) <= 4:
                errors.append("Unable to parse (field count mismatch): %s" % stripTags(line))
                continue
            for part in parts:
                part = part.strip()
                if part == "":
                    errors.append("Unable to parse (empty field): %s" % stripTags(line))
                    continue

                    # Hours
                if hours is None:
                    try:
                        hours = int(part)
                        continue
                    except ValueError:
                        pass

                    # Status
                if status is None and part.lower() in statuses:
                    status = part.lower()
                    continue

                    # Assigned
                if assigned is None and set(part.split(" ")) <= set(u.username for u in sprint.members):
                    assigned = set(User.load(username=username) for username in part.split(" "))
                    continue

                    # Name
                if name is None:
                    name = part
                    continue

                errors.append("Unable to parse (no field match on '%s'): %s" % (stripTags(part), stripTags(line)))

            if assigned is None:
                assigned = defaultAssigned
            if status is None:
                status = "not started"
            if name is None or hours is None:
                errors.append("Unable to parse (missing required fields): %s" % stripTags(line))
            if not any(v is None for v in (name, assigned, status, hours)):
                tasks[group].append((name, assigned, status, hours))

    if dryrun:
        handler.log = False
        numTasks = sum(len(taskSet) for taskSet in tasks.values())
        taskHours = sum(
            hours for taskSet in tasks.values() for name, assigned, status, hours in taskSet if status != "deferred"
        )
        ownTaskHours = sum(
            hours
            for taskSet in tasks.values()
            for name, assigned, status, hours in taskSet
            if status != "deferred" and handler.session["user"] in assigned
        )
        avail = Availability(sprint)
        availHours = avail.getAllForward(getNow().date(), handler.session["user"])
        usedHours = sum(task.effectiveHours() for task in sprint.getTasks() if handler.session["user"] in task.assigned)
        availHours -= usedHours
        if errors:
            print ErrorBox("<br>".join(errors))
        if numTasks:
            box = InfoBox
            stats = "Adding %s " % pluralize(numTasks, "task", "tasks")
            if newGroups:
                stats += "and %s " % pluralize(len(newGroups), "group", "groups")
            stats += "for a total of %s" % pluralize(taskHours, "hour", "hours")
            if ownTaskHours != taskHours:
                stats += ", %s yours" % pluralize(ownTaskHours, "hour", "hours")
            if ownTaskHours:
                if availHours == 0:
                    stats += ". You have no future availability for these tasks"
                    box = WarningBox
                elif availHours < 0:
                    stats += ". You are already overcommitted by %s" % pluralize(-availHours, "hour", "hours")
                    box = WarningBox
                else:
                    stats += ", %d%% of your future availability" % (100 * ownTaskHours / availHours)
                    box = WarningBox if ownTaskHours > availHours else InfoBox
            print box(stats)
        elif not errors:
            print InfoBox('Waiting for tasks. Click "Help" above if needed')

        groupedTasks = OrderedDict(
            (
                group,
                [
                    Task(
                        group.id,
                        sprint.id,
                        handler.session["user"].id,
                        0,
                        name,
                        status,
                        hours,
                        {user.id for user in assigned},
                        1,
                        id=0,
                    )
                    for name, assigned, status, hours in tasks[group]
                ],
            )
            for group in groups
        )
        print TaskTable(sprint, False, tasks=groupedTasks, status=True, name=True, assigned=True, hours=True)

    elif errors:
        die("There are unparseable lines in the task script. See the preview for more information")
    else:
        # There's some weirdness in the way groups auto-sequence that breaks when multiple groups are made without saving
        seq = maxOr(group.seq for group in sprint.getGroups()) + 1
        for group in newGroups:
            group.seq = seq
            seq += 1

        for group in groups:
            # Changing a group's ID will change its hash, so this pulls from tasks before saving the group in case it's new
            groupTasks = tasks[group]
            if group in newGroups:
                group.id = 0
            group.save()
            for name, assigned, status, hours in groupTasks:
                task = Task(group.id, group.sprint.id, handler.session["user"].id, 0, name, status, hours)
                task.assigned |= assigned
                task.save()
                Event.newTask(handler, task)

        numGroups = len(newGroups)
        numTasks = sum(map(lambda g: len(g), tasks.values()))
        if numGroups > 0 and numGroups > 0:
            delay(
                handler,
                SuccessBox(
                    "Added %d %s, %d %s"
                    % (
                        numGroups,
                        "group" if numGroups == 1 else "groups",
                        numTasks,
                        "task" if numTasks == 1 else "tasks",
                    ),
                    close=3,
                    fixed=True,
                ),
            )
        elif numGroups > 0:
            delay(
                handler,
                SuccessBox("Added %d %s" % (numGroups, "group" if numGroups == 1 else "groups"), close=3, fixed=True),
            )
        elif numTasks > 0:
            delay(
                handler,
                SuccessBox("Added %d %s" % (numTasks, "task" if numTasks == 1 else "tasks"), close=3, fixed=True),
            )
        else:
            delay(handler, WarningBox("No changes", close=3, fixed=True))
        handler.responseCode = 299
Ejemplo n.º 11
0
def newTaskMany(handler, group, assigned=None):
    handler.title("New Tasks")
    requirePriv(handler, "User")
    id = int(group)

    body = ""
    if "many-upload" in handler.session:
        body = handler.session["many-upload"]
        del handler.session["many-upload"]
    elif assigned:
        body = "[%s]\n" % stripTags(assigned)

    defaultGroup = Group.load(id)
    if not defaultGroup or defaultGroup.sprint.isHidden(handler.session["user"]):
        ErrorBox.die("Invalid Group", "No group with ID <b>%d</b>" % id)
    sprint = defaultGroup.sprint

    print '<script src="/static/jquery.typing-0.2.0.min.js" type="text/javascript"></script>'
    print '<script type="text/javascript">'
    nextURL = "/sprints/%d" % sprint.id
    if assigned:
        nextURL += "?search=assigned:%s" % stripTags(assigned.replace(" ", ","))
    print "next_url = %s;" % toJS(nextURL)
    print "TaskTable.init();"
    print "</script>"

    print tabs.format(id).where("many")

    if not (sprint.isActive() or sprint.isPlanning()):
        ErrorBox.die("Sprint Closed", "Unable to modify inactive sprint")
    elif not sprint.canEdit(handler.session["user"]):
        ErrorBox.die("Permission Denied", "You don't have permission to modify this sprint")

    help = ResponseWriter()
    print "Each line needs to match the following syntax. Unparseable lines generate an error message in the preview and must be resolved before saving"
    print "<ul>"
    print "<li><b>X</b> &mdash; A single character changes the field separator to that character. The exception is #, which starts a comment. The default field separator is |, so that's used in the examples here</li>"
    print '<li><b>X...X:</b> &mdash; A line ending in a colon is a group name. All tasks after that line will be added to that group. If no group of that name exists, it will be created (the preview will label that group as "(NEW)"). A blank line switches back to the default group, which is the group you clicked the new task button on, %s' % defaultGroup.safe.name
    print "<li><b>X...X|X...X[|X...X[|X...X]]</b> &mdash; 2-4 fields are a new task. The fields can appear in any order:<ul>"
    print "<li><b>name</b> &mdash; The name of the task</li>"
    print "<li><b>hours</b> &mdash; The number of hours this task will take</li>"
    print "<li><b>assignee</b> &mdash; The person assigned to this task. If multiple people, separate usernames with spaces. This field is optional as long as <b>status</b> is also omitted; it defaults to the current user if a sprint member, or the scrummaster otherwise, unless overridden (see below)</li>"
    print '<li><b>status</b> &mdash; The initial status of the task. This field is optional; it defaults to "not started"</li>'
    print "</ul></li>"
    print "<li><b>[X...X]</b> &mdash; A username (or space-separated list of usernames) wrapped in brackets makes that user or group the default assignee for all tasks that don't specify an assignee</li>"
    print "<li><b>#...</b> &mdash; A line starting with a hash character is a comment, and is ignored. You can only comment out entire lines; a hash within a line does not start a comment at that point</li>"
    print "</ul>"
    print "You can also use the form above the textarea to upload a text file. The file will be used to fill the textarea, so it should match the syntax described above"
    print CollapsibleBox("Help", help.done())

    print CollapsibleBox(
        "Groups",
        "<ul>%s</ul>"
        % "".join(
            "<li>%s</li>" % ("<b>%s</b> (default)" if group == defaultGroup else "%s") % group.safe.name
            for group in sprint.getGroups()
        ),
    )

    print '<form id="upload-tasks" method="post" enctype="multipart/form-data" action="/tasks/new/many/upload?group=%d">' % defaultGroup.id
    print '<input type="file" name="data"><br><br>'
    print "</form>"

    print '<form id="write-tasks" method="post" action="/tasks/new/many?group=%d">' % defaultGroup.id
    print '<textarea id="many-body" name="body" class="defaultfocus">%s</textarea>' % body

    print '<div id="preview"></div>'
    print InfoBox("Loading...", id="post-status", close=True)
    print '<div id="new-task-many-buttons">'
    print Button("Save All", id="save-button", type="button").positive()
    print Button("Cancel", id="cancel-button", type="button").negative()
    print "</div>"
    print "</form>"
Ejemplo n.º 12
0
def newTaskSingle(handler, group, assigned=""):
    handler.title("New Task")
    requirePriv(handler, "User")
    id = int(group)
    assigned = assigned.split(" ")

    print tabs.format(id).where("single")

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

    sprint = group.sprint
    if not (sprint.isActive() or sprint.isPlanning()):
        ErrorBox.die("Sprint closed", "Unable to modify inactive sprint")
    elif not sprint.canEdit(handler.session["user"]):
        ErrorBox.die("Permission denied", "You don't have permission to modify this sprint")

    print '<script type="text/javascript">'
    nextURL = "/sprints/%d" % group.sprint.id
    if assigned:
        nextURL += "?search=assigned:%s" % ",".join(assigned)
    nextURL += "#group%d" % group.id
    print "next_url = %s;" % toJS(nextURL)
    print "</script>"

    print InfoBox("", id="post-status", close=True)

    print '<form method="post" action="/tasks/new/single">'
    print '<table class="list">'
    print '<tr><td class="left">Sprint:</td><td class="right"><select id="selectSprint" disabled><option>%s</option></select></td></tr>' % group.sprint
    print '<tr><td class="left">Group:</td><td class="right">'
    print '<select id="select-group" name="group" size="5">'
    for sGroup in group.sprint.getGroups("name"):
        print '<option value="%d"%s>%s</option>' % (sGroup.id, " selected" if sGroup == group else "", sGroup.safe.name)
    print "</select>"
    print "</td></tr>"
    print '<tr><td class="left">Name:</td><td class="right"><input type="text" name="name" class="defaultfocus"></td></tr>'
    print '<tr><td class="left">Sprint Goal:</td><td class="right">'
    print '<select id="select-goal" name="goal" size="5">'
    print '<option value="0" selected>None</option>'
    for goal in group.sprint.getGoals():
        print '<option value="%d">%s</option>' % (goal.id, goal.safe.name)
    print "</select>"
    print "</td></tr>"
    print '<tr><td class="left">Status:</td><td class="right">'
    print '<select id="select-status" name="status" size="10">'
    first = True
    for statusSet in statusMenu:
        for name in statusSet:
            print '<option value="%s"%s>%s</option>' % (name, " selected" if first else "", statuses[name].text)
            first = False
    print "</status>"
    print "</td></tr>"
    print '<tr><td class="left">Assigned:</td><td class="right">'
    print '<select id="select-assigned" name="assigned[]" data-placeholder="Choose assignees (or leave blank to self-assign)" size="10" multiple>'
    for user in sorted(group.sprint.members):
        print '<option value="%d"%s>%s</option>' % (
            user.id,
            " selected" if user.username in assigned else "",
            user.safe.username,
        )
    print "</select>"
    print "</td></tr>"
    print '<tr><td class="left">Hours:</td><td class="right"><input type="text" name="hours" value="8"></td></tr>'
    print '<tr><td class="left">&nbsp;</td><td class="right">'
    print Button("Save", id="save-button", type="button").positive()
    print Button("Cancel", id="cancel-button", type="button").negative()
    print "</td></tr>"
    print "</table>"
    print "</form>"
Ejemplo n.º 13
0
def editGroup(handler, id):
	requirePriv(handler, 'User')
	handler.title('Manage Group')

	id = int(id)
	group = Group.load(id)
	if not group:
		ErrorBox.die('Invalid Group', "No group with ID <b>%d</b>" % id)
	tasks = group.getTasks()

	chart = GroupGoalsChart(group)
	Chart.include()
	chart.js()

	print "<style type=\"text/css\">"
	print "table.list td.left {position: relative; top: 4px;}"
	print "table.list td.right *, button {width: 400px;}"
	print "table.list td.right button {width: 200px;}" # Half of the above value
	print "</style>"

	print "<h2>Edit Group</h2>"
	print "<form method=\"post\" action=\"/groups/%d/rename\">" % id
	print "<table class=\"list\">"
	print "<tr><td class=\"left\">Name:</td><td class=\"right\"><input type=\"text\" name=\"name\" value=\"%s\">" % (group.safe.name)
	print "<tr><td class=\"left\">Predecessor:</td><td class=\"right\">"

	#TODO Waiting on group moving
	print "<select name=\"predecessor\" disabled>"
	print "<option>%s</option>" % ('None' if group.seq == 1 else Group.load(sprintid = group.sprintid, seq = group.seq-1).safe.name)
	print "</select>"

	print "</td></tr>"
	print "<tr><td class=\"left\">&nbsp;</td><td class=\"right\">"
	print Button('Save', type = 'submit').positive()
	print Button('Cancel', "/sprints/%d#group%d" % (group.sprintid, id), type = 'button').negative()
	print "</td></tr>"
	print "</table>"
	print "</form>"

	print "<h2>Assign Goal</h2>"
	print "This is a quick way to set all the tasks in the group to the same sprint goal. The current breakdown is:"
	chart.placeholder()

	# The default is whichever goal currently has the most occurrences
	defaultGoal = max((sum(task.goal == goal or False for task in tasks), goal) for goal in group.sprint.getGoals() + [None])[1]

	print "<form method=\"post\" action=\"/groups/%d/goal\">" % id
	for goal in group.sprint.getGoals():
		if goal.name:
			print "<input type=\"radio\" id=\"goal%d\" name=\"goal\" value=\"%d\"%s>&nbsp;<label for=\"goal%d\"><img class=\"bumpdown\" src=\"/static/images/tag-%s.png\">&nbsp;%s</label><br>" % (goal.id, goal.id, ' checked' if goal == defaultGoal else '', goal.id, goal.color, goal.name)
	print "<input type=\"radio\" id=\"goal0\" name=\"goal\" value=\"0\"%s>&nbsp;<label for=\"goal0\"><img class=\"bumpdown\" src=\"/static/images/tag-none.png\">&nbsp;Other</label><br><br>" % ('' if defaultGoal else ' checked')
	print Button('Assign', type = 'submit').positive()
	print "</form>"

	print "<h2>Delete Group</h2>"
	if len(group.sprint.getGroups()) == 1:
		print "You can't delete the last group in a sprint"
	elif not group.deletable:
		print "This group is undeletable"
	else:
		print "<form method=\"post\" action=\"/groups/%d/delete\">" % id
		if len(tasks):
			print "This group has %d %s. Move %s to the <select name=\"newGroup\">" % (len(tasks), 'task' if len(tasks) == 1 else 'tasks', 'it' if len(tasks) == 1 else 'them')
			for thisGroup in group.sprint.getGroups('name'):
				if group == thisGroup: continue
				print "<option value=\"%d\">%s</option>" % (thisGroup.id, thisGroup.safe.name)
			print "<option value=\"0\">None (delete)</option>"
			print "</select> group<br><br>"
		print Button('Delete', type = 'submit')
		print "</form>"