コード例 #1
0
ファイル: sprints.py プロジェクト: mrozekma/Sprint
def showMetrics(handler, id):
	requirePriv(handler, 'User')
	id = int(id)
	sprint = Sprint.load(id)
	if not sprint or sprint.isHidden(handler.session['user']):
		ErrorBox.die('Sprints', "No sprint with ID <b>%d</b>" % id)
	elif not sprint.canView(handler.session['user']):
		ErrorBox.die('Private', "You must be a sprint member to view this sprint")

	context = {}
	context['sprint'] = sprint
	context['allTasks'] = sprint.getTasks(includeDeleted = True)
	context['tasks'] = tasks = filter(lambda task: not task.deleted, context['allTasks'])
	context['revisions'] = {(task.id, day): task.getRevisionAt(day) for task, day in product(context['allTasks'], sprint.getDays())}

	handler.title(sprint.safe.name)
	drawNavArrows(sprint, handler.session['user'], 'metrics')

	print "<style type=\"text/css\">"
	print "h2 a {color: #000;}"
	print "</style>"

	charts = [
		('general', 'Hours (general)', HoursChart('chart-general', **context)),
		('commitment-by-user', 'Commitment (by user)', CommitmentByUserChart(**context)),
		('earned-value', 'Earned Value', EarnedValueChart('chart-earned-value', **context)),
		('by-user', 'Hours (by user)', HoursByUserChart('chart-by-user', **context)),
		('status', 'Task status', StatusChart('chart-status', **context)),
		('commitment', 'Total commitment', CommitmentChart('chart-commitment', **context)),
		('goals', 'Sprint goals', GoalsChart(**context)),
	]

	Chart.include()
	map(lambda (anchor, title, chart): chart.js(), charts)
	print tabs(sprint, 'metrics')

	if not sprint.isOver():
		noHours = filter(lambda task: task.stillOpen() and task.hours == 0, tasks)
		if noHours != []:
			print WarningBox("There are <a href=\"/sprints/%d?search=status:not-started,in-progress,blocked hours:0\">open tasks with no hour estimate</a>. These unestimated tasks can artifically lower the tasking lines in the following charts" % sprint.id)

	for anchor, title, chart in charts:
		print "<a name=\"%s\">" % anchor
		print "<h2><a href=\"#%s\">%s</a></h2>" % (anchor, title)
		chart.placeholder()

	print "<br><br>"
コード例 #2
0
ファイル: sprints.py プロジェクト: mrozekma/Sprint
def showSprintHistory(handler, id, assigned = None):
	requirePriv(handler, 'User')
	id = int(id)
	sprint = Sprint.load(id)
	if not sprint or sprint.isHidden(handler.session['user']):
		ErrorBox.die('Sprints', "No sprint with ID <b>%d</b>" % id)
	elif not sprint.canView(handler.session['user']):
		ErrorBox.die('Private', "You must be a sprint member to view this sprint")
	tasks = sprint.getTasks(includeDeleted = True)

	handler.title(sprint.safe.name)
	drawNavArrows(sprint, handler.session['user'], 'history')

	Chart.include()
	chart = TaskChart('chart', sprint.getTasks())
	chart.js()
	print "<script type=\"text/javascript\">"
	tasksByAssigned = {member.username: [task.id for task in tasks if member in task.assigned] for member in sprint.members}
	print "var tasks_by_assigned = %s;" % toJS(tasksByAssigned)
	print "$(document).ready(function() {"
	if assigned:
		print "    $('%s').addClass('selected');" % ', '.join("#filter-assigned a[assigned=\"%s\"]" % username for username in assigned.split(','))
	print "    setup_filter_buttons();"
	print "    apply_filters();"
	print "});"
	print "</script>"

	print tabs(sprint, 'history')
	if len(tasks) == 0:
		print ErrorBox("This sprint has no tasks")
		print "<br>"
		return

	print "<div id=\"filter-assigned\">"
	print "<a class=\"fancy danger\" href=\"#\"><img src=\"/static/images/cross.png\">&nbsp;None</a>"
	for member in sorted(sprint.members):
		print "<a class=\"fancy\" assigned=\"%s\" href=\"/sprints/%d/history?assigned=%s\"><img src=\"%s\">&nbsp;%s</a>" % (member.username, id, member.username, member.getAvatar(16), member.username)
	print "</div><br>"

	chart.placeholder()
	showHistory(tasks, True)
	print "<br>"
コード例 #3
0
ファイル: users.py プロジェクト: mrozekma/Sprint
def user(handler, username):
	user = User.load(username = username)
	if not user:
		ErrorBox.die('User', "No user named <b>%s</b>" % stripTags(username))

	Markdown.head('form#message-form .body pre code')
	print "<script src=\"/static/jquery.typing-0.2.0.min.js\" type=\"text/javascript\"></script>"
	print "<script src=\"/static/users.js\" type=\"text/javascript\"></script>"
	Chart.include()
	undelay(handler)

	handler.title(user.safe.username)
	handler.replace('$bodytitle$', '', 1)
	print "<img src=\"%s\" class=\"gravatar\">" % user.getAvatar(64)
	print "<h1>%s</h1>" % user.safe.username
	if isDevMode(handler):
		print "<div class=\"debugtext\">User ID: %d</div>" % user.id
	print "<div class=\"clear\"></div>"

	if handler.session['user'] and handler.session['user'].hasPrivilege('Admin'):
		print "<h3>Admin</h3>"
		print "<form method=\"post\" action=\"/admin/users\">"
		print "<input type=\"hidden\" name=\"username\" value=\"%s\">" % user.username
		print "<button type=\"submit\" class=\"btn\" name=\"action\" value=\"resetpw\">Reset password</button>"
		print "<button type=\"submit\" class=\"btn\" name=\"action\" value=\"impersonate\">Impersonate</button>"
		print "<button type=\"submit\" class=\"btn\" name=\"action\" value=\"sessions\">Manage sessions</button>"
		print "<button type=\"submit\" class=\"btn\" name=\"action\" value=\"privileges\">Manage privileges</button>"
		print "</form>"

	if user == handler.session['user']:
		print "<h3>Avatar</h3>"
		if user.hasLocalAvatar():
			print "Your avatar is currently <a href=\"/users/%s/avatar/set\">locally hosted</a>" % user.username
		else:
			print "Your avatar can be changed at <a href=\"http://gravatar.com/\" target=\"_new\">http://gravatar.com/</a>. It must be associated with the e-mail <b>%s</b>, and be rated PG. You can also host an avatar <a href=\"/users/%s/avatar/set\">locally</a>, if necessary" % (user.getEmail(), user.username)

		print "<h3>Authentication</h3>"
		print "Your sprint tool password can be changed <a href=\"/resetpw\">here</a>.",
		if settings.kerberosRealm:
			print "You can also use your %s kerberos password to login" % settings.kerberosRealm,
		print "<br><br>"
		if user.hotpKey == '':
			print "You also have the option to use two-factor authentication via <a href=\"http://en.wikipedia.org/wiki/HOTP\">HOTP</a>. You can use <a href=\"http://support.google.com/a/bin/answer.py?hl=en&answer=1037451\">Google Authenticator</a> to generate verification codes<br><br>"
			print "<form method=\"post\" action=\"/security/two-factor\">"
			print "<button type=\"submit\" class=\"btn danger\" name=\"action\" value=\"enable\">Enable two-factor authentication</button>"
			print "</form>"
		else:
			print "You are currently using two-factor authentication<br><br>"
			print "<form method=\"post\" action=\"/security/two-factor\">"
			print "<button type=\"submit\" class=\"btn danger\" name=\"action\" value=\"enable\">Reset HOTP key</button>"
			print "<button type=\"submit\" class=\"btn danger\" name=\"action\" value=\"disable\">Disable two-factor authentication</button>"
			print "</form>"

		print "<h3>Messages</h3>"
		print "Your inbox and sent messages can be viewed <a href=\"/messages/inbox\">here</a><br>"

	print "<h3>Last seen</h3>"
	if not user.lastseen:
		print "Never"
	elif dateToTs(getNow()) - user.lastseen < 60:
		print "Just now"
	else:
		print "%s ago" % timesince(tsToDate(user.lastseen))

	if handler.session['user'] and handler.session['user'] != user:
		print "<h3>Message</h3>"
		print "<small>(Messages are formatted in <a target=\"_blank\" href=\"/help/markdown\">markdown</a>)</small>"
		print "<form id=\"message-form\" method=\"post\" action=\"/messages/send\">"
		print "<input type=\"hidden\" name=\"userid\" value=\"%d\">" % user.id
		print "<textarea name=\"body\" class=\"large\"></textarea>"
		print "<div class=\"body markdown\"><div id=\"preview\"></div></div>"
		print Button('Send').post().positive()
		print "</form>"

	print "<h3>Project distribution</h3>"
	sprints = filter(lambda s: user in s.members, Sprint.loadAllActive())
	sprintHours = map(lambda s: (s, Availability(s).getAllForward(getNow(), user)), sprints)
	projectHours = map(lambda (p, g): (p, sum(hours for sprint, hours in g)), groupby(sprintHours, lambda (s, a): s.project))

	# For now at least, don't show projects with no hours
	projectHours = filter(lambda (p, h): h > 0, projectHours)
	if len(projectHours) > 0:
		chart = Chart('chart')
		chart.title.text = ''
		chart.tooltip.formatter = "function() {return '<b>' + this.point.name + '</b>: ' + this.point.y + '%';}"
		chart.plotOptions.pie.allowPointSelect = True
		chart.plotOptions.pie.cursor = 'pointer'
		chart.plotOptions.pie.dataLabels.enabled = False
		chart.plotOptions.pie.showInLegend = True
		chart.credits.enabled = False
		chart.series = seriesList = []

		series = {
			'type': 'pie',
			'name': '',
			'data': []
		}
		seriesList.append(series)

		total = sum(hours for project, hours in projectHours)
		for project, hours in projectHours:
			series['data'].append([project.name, float("%2.2f" % (100 * hours / total))])

		chart.js()
		chart.placeholder()
	else:
		print "Not a member of any active sprints"
コード例 #4
0
ファイル: tasks.py プロジェクト: nolandda/Sprint
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>"
コード例 #5
0
ファイル: groups.py プロジェクト: mrozekma/Sprint
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>"