Esempio n. 1
0
    def __init__(self, first_name, last_name, email, phone_number):

        self.__first_name = first_name
        self.__last_name = last_name
        self.__email = email
        self.__phone_number = phone_number
        self.dob = None
        self.__availability = Availability()
Esempio n. 2
0
	def getWarnings(self):
		rtn = {}

		from Availability import Availability
		tasks = self.getTasks()
		avail = Availability(self)
		timestamp = tsToDate(self.start)
		userAvails = dict((user, avail.getAllForward(timestamp, user)) for user in self.members)

		# Users with 0 availability
		zeroes = [user for (user, hours) in userAvails.iteritems() if hours == 0]
		if zeroes != []:
			rtn['no-availability'] = zeroes

		# Users with >100% commitment
		overcommitted = filter(lambda user: userAvails[user] < sum(task.effectiveHours() for task in tasks if user in task.assigned), self.members)
		if overcommitted != []:
			rtn['overcommitted'] = overcommitted

		# Users with no tasks
		noTasks = filter(lambda user: filter(lambda task: user in task.assigned, tasks) == [], self.members)
		if noTasks != []:
			rtn['users-no-tasks'] = noTasks

		# No sprint goals, or too many tasks (at least 10 tasks and more than 20% of all tasks) without a goal
		unaffiliated = filter(lambda task: not task.goal, tasks)
		if filter(lambda goal: goal.name != '', self.getGoals()) == []:
			rtn['no-sprint-goals'] = True
		elif len(unaffiliated) >= 10 and len(unaffiliated) / len(tasks) > .20:
			rtn['tasks-without-goals'] = unaffiliated

		# Goals with no tasks
		noTasks = filter(lambda goal: goal.name and filter(lambda task: task.goal == goal, tasks) == [], self.getGoals())
		if noTasks != []:
			rtn['goals-no-tasks'] = noTasks

		# Open tasks with 0 hours
		noHours = filter(lambda task: task.stillOpen() and task.hours == 0, tasks)
		if noHours != []:
			rtn['open-without-hours'] = noHours

		# Closed tasks with >0 hours
		haveHours = filter(lambda task: not task.stillOpen() and task.status != 'deferred' and task.hours > 0, tasks)
		if haveHours != []:
			rtn['closed-with-hours'] = haveHours

		# Tasks with too many hours
		tooManyHours = filter(lambda task: task.hours > 24, tasks)
		if tooManyHours != []:
			rtn['too-many-hours'] = tooManyHours

		return rtn
Esempio n. 3
0
def sprintAvailabilityPost(handler, id, p_hours):
	def die(msg):
		print msg
		done()

	handler.wrappers = False
	id = int(id)

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

	sprint = Sprint.load(id)
	if not sprint or sprint.isHidden(handler.session['user']):
		die("There is no sprint with ID %d" % id)

	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")

	avail = Availability(sprint)
	for k, hours in p_hours.items():
		userid, timestamp = map(int, k.split(',', 1))
		hours = int(hours)

		user = User.load(userid)
		if not user in sprint.members:
			die("Trying to set availability of non-member %s" % user.safe.username)
		time = tsToDate(timestamp)
		if not sprint.start <= timestamp <= sprint.end:
			die("Trying to set availability outside of sprint window")

		avail.set(user, time, hours)

	handler.responseCode = 299
	delay(handler, SuccessBox("Updated availability", close = 3, fixed = True))
	Event.sprintAvailUpdate(handler, sprint)
Esempio n. 4
0
def showBacklog(handler, id, search = None, devEdit = False):
	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")

	# Redirect to search help page if searched for empty string
	if search == '':
		redirect('/help/search')

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

	tasks = sprint.getTasks()
	editable = sprint.canEdit(handler.session['user']) or (devEdit and isDevMode(handler))
	search = Search(sprint, search)

	print "<script src=\"/settings/sprints.js\" type=\"text/javascript\"></script>"

	print "<script type=\"text/javascript\">"
	print "var sprintid = %d;" % id
	print "var currentUser = %s;" % toJS(handler.session['user'].username if handler.session['user'] else None)
	print "var totalTasks = %d;" % len(tasks)

	# True is a placeholder for the dynamic tokens (status, assigned)
	print "var searchTokens = %s;" % toJS(filter(None, [search.getBaseString() if search.hasBaseString() else None] + [True] + ["%s:%s" % (filt.getKey(), filt.value) for filt in search.getAll() if filt.getKey() not in ('status', 'assigned')]))
	print "var searchDescriptions = %s;" % toJS(filter(None, ["matching %s" % search.getBaseString() if search.hasBaseString() else None] + [True] + [filt.description() for filt in search.getAll()]))

	print "TaskTable.init({link_hours_status: %s});" % toJS(not sprint.isPlanning())
	print "$('document').ready(function() {"
	if search.has('assigned'):
		print "    $('%s').addClass('selected');" % ', '.join("#filter-assigned a[assigned=\"%s\"]" % user.username for user in search.get('assigned').users + ([handler.session['user']] if search.get('assigned').currentUser else []))
	if search.has('status'):
		print "    $('%s').addClass('selected');" % ', '.join("#filter-status a[status=\"%s\"]" % status.name for status in search.get('status').statuses)
	print "    apply_filters();"
	print "});"
	print "</script>"

	print "<div id=\"selected-task-box\">"
	print "<span></span>"
	print Button('history', id = 'selected-history').positive()
	print Button('highlight', id = 'selected-highlight').positive()
	print Button('mass edit', id = 'selected-edit').positive()
	print Button('cancel', id = 'selected-cancel') #.negative()
	print "</div>"

	print "<div class=\"backlog-tabs\">"
	print tabs(sprint, 'backlog')
	print "<input type=\"text\" id=\"search\" value=\"%s\">" % search.getFullString().replace('"', '&quot;')
	print "</div>"

	undelay(handler)

	print InfoBox('Loading...', id = 'post-status', close = True)

	avail = Availability(sprint) if sprint.isActive() else None
	dayStart = Weekday.today().date()
	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):
		cls = ['fancy']
		if not sprint.isPlanning() and avail and avail.get(member, dayStart) == 0:
			cls.append('away')
		print "<a class=\"%s\" assigned=\"%s\" href=\"/sprints/%d?search=assigned:%s\"><img src=\"%s\">&nbsp;%s</a>" % (' '.join(cls), member.username, id, member.username, member.getAvatar(16), member.username)
	print "</div><br>"

	print "<div id=\"filter-status\">"
	print "<a class=\"fancy danger\" href=\"#\"><img src=\"/static/images/cross.png\">&nbsp;None</a>"
	for status in sorted(statuses.values()):
		print "<a class=\"fancy\" status=\"%s\" href=\"/sprints/%d?search=status:%s\"><img src=\"%s\">%s</a>" % (status.name, id, status.name.replace(' ', '-'), status.getIcon(), status.text)
	print "</div><br>"

	if handler.session['user'].hasPrivilege('Admin') and 'deleted' in sprint.flags:
		print "<form method=\"post\" action=\"/admin/projects/%d/cancel-deletion/%d\">" % (sprint.project.id, sprint.id)
		print WarningBox("This sprint is flagged for deletion during nightly cleanup. %s" % Button('Cancel').mini().post())
		print "</form>"
	if sprint.isPlanning():
		if sprint.isActive():
			print InfoBox("Today is <b>sprint planning</b> &mdash; tasks aren't finalized until the end of the day")
		else:
			daysTillPlanning = (tsToDate(sprint.start) - getNow()).days + 1
			print InfoBox("The sprint has <b>not begun</b> &mdash; planning is %s. All changes are considered to have been made midnight of plan day" % ('tomorrow' if daysTillPlanning == 1 else "in %d days" % daysTillPlanning))
	elif sprint.isReview():
		print InfoBox("Today is <b>sprint review</b> &mdash; this is the last day to make changes to the backlog. All open tasks will be deferred at the end of the day")
	elif 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>" % sprint.id)

	tasks = search.filter(tasks)

	if isDevMode(handler):
		print Button('#all-tasks borders', "javascript:$('#all-tasks, #all-tasks tr td').css('border', '1px solid #f00').css('border-collapse', 'collapse');").negative()
		if not editable:
			print Button('make editable', "/sprints/%d?devEdit" % id).negative()
		elif devEdit:
			print Button('make uneditable', "/sprints/%d" % id).negative()

		print "<div class=\"debugtext\">"
		print "start: %d (%s)<br>" % (sprint.start, tsToDate(sprint.start))
		print "end: %d (%s)<br>" % (sprint.end, tsToDate(sprint.end))
		print "</div>"

	showing = ResponseWriter()
	print "<span id=\"task-count\"></span>"
	# save-search href set by update_task_count()
	print "<a class=\"save-search\"><img src=\"/static/images/save.png\" title=\"Save search\"></a>"
	print "<a class=\"cancel-search\" href=\"/sprints/%d\"><img src=\"/static/images/cross.png\" title=\"Clear search\"></a>" % id
	showing = showing.done()

	print TaskTable(sprint, editable = editable, tasks = tasks, tableID = 'all-tasks', dateline = showing, taskClasses = {task: ['highlight'] for task in (search.get('highlight').tasks if search.has('highlight') else [])}, debug = isDevMode(handler), groupActions = True, taskModActions = True, index = True, goal = True, status = True, name = True, assigned = True, historicalHours = True, hours = True, devEdit = devEdit)
Esempio n. 5
0
def showAvailability(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")
	tasks = sprint.getTasks()

	handler.title(sprint.safe.name)
	drawNavArrows(sprint, handler.session['user'], 'availability')
	print tabs(sprint, 'availability')

	print "<script type=\"text/javascript\">"
	print "var sprintid = %d;" % id
	print "</script>"

	print InfoBox('Loading...', id = 'post-status', close = True)

	avail = Availability(sprint)
	oneday = timedelta(1)
	start, end = tsToDate(sprint.start), tsToDate(sprint.end)
	editable = sprint.canEdit(handler.session['user'])

	print "<form method=\"post\" action=\"/sprints/%d/availability\">" % sprint.id

	# This is in case the user presses enter
	print Button('', id = 'save-button2', type = 'button')

	print "<table class=\"availability\">"
	print "<tr class=\"dateline\">"
	print "<td>&nbsp;</td>"
	for day in sprint.getDays():
		print "<td>%s<br>%s</td>" % (day.strftime('%d'), day.strftime('%a'))
		if day.weekday() == 4:
			print "<td class=\"spacer\">&nbsp;</td>"
	print "<td class=\"buttons\"></td>" # The stylesheet counts cells from the right, so it's important that this be here to match the other rows
	print "</tr>"
	if editable:
		print "<tr class=\"dateline\">"
		print "<td class=\"buttons\">"
		if editable:
			print Button('set all 8', id = 'set-all-8', type = 'button').info()
		print "</td>"
		for day in sprint.getDays():
			print "<td class=\"buttons\"><img src=\"/static/images/clipboard.png\" title=\"Copy first down\"></td>"
			if day.weekday() == 4:
				print "<td class=\"spacer\">&nbsp;</td>"
		print "<td class=\"buttons\"></td>"
		print "</tr>"

	for user in sorted(sprint.members):
		print "<tr class=\"userline\">"
		print "<td class=\"username\">%s&nbsp;<img src=\"%s\"></td>" % (user.safe.username, user.getAvatar(16))
		for day in sprint.getDays():
			if editable:
				print "<td><input type=\"text\" name=\"hours[%d,%d]\" value=\"%d\"></td>" % (user.id, dateToTs(day), avail.get(user, day))
			else:
				print "<td style=\"text-align: center\">%d</td>" % avail.get(user, day)

			if day.weekday() == 4:
				print "<td class=\"spacer\">&nbsp;</td>"
		print "<td class=\"buttons\">"
		if editable:
			print "<img src=\"/static/images/clipboard.png\" title=\"Copy first right\">"
		print "</td>"
		print "</tr>"

	if editable:
		print "<tr><td>%s</td></tr>" % Button('Save', id = 'save-button', type = 'button').positive()
	print "</table>"
	print "</form>"
Esempio n. 6
0
def sprintInfoPost(handler, id, p_name, p_start, p_end, p_goals, p_members = None, p_clear = [], p_private = False, p_hidden = False):
	def die(msg):
		print msg
		done()

	handler.wrappers = False

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

	id = to_int(id, 'id', die)
	p_members = to_int(p_members, 'members', die)
	sprint = Sprint.load(id)
	if not sprint or sprint.isHidden(handler.session['user']):
		die("There is no sprint with ID %d" % id)
	if sprint.owner != handler.session['user']:
		die("You must be the scrummaster to modify sprint information")

	try:
		start = re.match("^(\d{1,2})/(\d{1,2})/(\d{4})$", p_start)
		if not start:
			raise ValueError
		month, day, year = map(int, start.groups())
		start = datetime(year, month, day, 0, 0, 0)
	except ValueError:
		die("Malformed start date: %s" % stripTags(p_start))

	try:
		end = re.match("^(\d{1,2})/(\d{1,2})/(\d{4})$", p_end)
		if not end:
			raise ValueError
		month, day, year = map(int, end.groups())
		end = datetime(year, month, day, 23, 59, 59)
	except ValueError:
		die("Malformed end date: %s" % stripTags(p_end))

	msg = Sprint.validateDates(start, end, tsToDate(sprint.start), tsToDate(sprint.end))
	if msg:
		die(msg)

	goals = map(Goal.load, to_int(p_goals.keys(), 'goals', die))
	if not all(goals):
		die("One or more goals do not exist")

	members = set(map(User.load, p_members)) if p_members else set()
	if not all(members):
		die("One or more members do not exist")
	if sprint.owner not in members:
		die("The scrummaster (%s) must be a sprint member" % sprint.owner)

	tasks = sprint.getTasks()
	changedTasks = set()
	avail = Availability(sprint)
	addMembers = set(members) - set(sprint.members)
	delMembers = set(sprint.members) - set(members)
	for user in delMembers:
		for task in filter(lambda task: user in task.assigned, tasks):
			print "Removing %s from %d<br>" % (user, task.id)
			task.assigned -= {user}
			if len(task.assigned) == 0:
				print "Adding %s to %d<br>" % (sprint.owner, task.id)
				task.assigned = {sprint.owner}
			changedTasks.add(task)
		avail.delete(user)
		sprint.members -= {user}

	# For event dispatching
	changes = OrderedDict([
		('name', None if sprint.name == p_name else p_name),
		('start', None if tsToDate(sprint.start) == start else start),
		('end', None if tsToDate(sprint.end) == end else end),
		('addMembers', addMembers),
		('delMembers', delMembers),

		# Updated later
		('addGoals', []),
		('removeGoals', []),
		('addFlags', []),
		('removeFlags', []),
	])

	sprint.members |= addMembers
	sprint.name = p_name
	p_private = (p_private or p_hidden) # Hidden implies Private
	for flagName, flagValue in (('private', p_private), ('hidden', p_hidden)):
		if flagValue and flagName not in sprint.flags:
			sprint.flags.add(flagName)
			changes['addFlags'].append(flagName)
		elif not flagValue and flagName in sprint.flags:
			sprint.flags.remove(flagName)
			changes['removeFlags'].append(flagName)

	if dateToTs(start) != sprint.start or dateToTs(end) != sprint.end:
		sprint.start = dateToTs(start)
		sprint.end = dateToTs(end)
		avail.trim()

	sprint.save()

	for id in p_goals:
		goal = Goal.load(int(id))
		if goal.name != p_goals[id]:
			if goal.name:
				changes['removeGoals'].append(goal.name)
			if p_goals[id]:
				changes['addGoals'].append(p_goals[id])

		goal.name = p_goals[id]
		goal.save()

	if start:
		for task in sprint.getTasks(includeDeleted = True):
			for rev in task.getRevisions():
				if rev.timestamp < sprint.start:
					rev.timestamp = sprint.start
					rev.save()
				else:
					break
		for task in changedTasks:
			if task.timestamp < sprint.start:
				task.timestamp = sprint.start

	if p_clear:
		ids = [to_int(goalid, 'p_clear', die) for goalid in p_clear]
		for task in tasks:
			if task.goal and task.goal.id in ids:
				task.goal = None
				changedTasks.add(task)

	for task in changedTasks:
		print "Saving new revision for %d<br>" % task.id
		task.saveRevision(handler.session['user'])

	handler.responseCode = 299
	delay(handler, SuccessBox("Updated info", close = 3, fixed = True))
	Event.sprintInfoUpdate(handler, sprint, changes)
Esempio n. 7
0
class User():
    def __init__(self, first_name, last_name, email, phone_number):

        self.__first_name = first_name
        self.__last_name = last_name
        self.__email = email
        self.__phone_number = phone_number
        self.dob = None
        self.__availability = Availability()

    def get_name(self):
        return f'{self.__first_name} {self.__last_name}'

    def set_first_name(self, new_name):
        self.__first_name = new_name

    def set_last_name(self, new_name):
        self.__last_name = new_name

    def get_email(self):
        return self.__email

    def set_email(self, new_email):
        self.__email = new_email

    def get_phone(self):
        return self.__phone_number

    def set_phone_number(self, new_number):
        self.__phone_number = new_number

    def get_dob(self):
        return self.__dob

    def set_dob(self, dob_date):
        self.__dob = dob_date

    def get_availability(self, day, time):
        return self.__availability.is_available(day, time)

    def set_availability(self, start_mon, end_mon, start_tue, end_tue,
                         start_wed, end_wed, start_thu, end_thu, start_fri,
                         end_fri, start_sat, end_sat, start_sun, end_sun):

        #use Availability class implementation
        self.__availability.set_mon(start_mon, end_mon)
        self.__availability.set_tue(start_tue, end_tue)
        self.__availability.set_wed(start_wed, start_wed)
        self.__availability.set_thu(start_thu, start_thu)
        self.__availability.set_fri(start_fri, start_fri)
        self.__availability.set_sat(start_sat, start_sat)
        self.__availability.set_sun(start_sun, start_sun)

    def to_string(self):
        return f'Name: {self.get_name()}, Email: {self.get_email()}, Phone Number: {self.get_phone()}'
Esempio n. 8
0
	def placeholder(self):
		avail = Availability(self.sprint)
		for user in sorted(self.sprint.members):
			hours = sum(t.effectiveHours() for t in self.tasks if user in t.assigned)
			total = avail.getAllForward(getNow().date(), user)
			print ProgressBar("<a style=\"color: #000\" href=\"/sprints/%d?search=assigned:%s\">%s</a>" % (self.sprint.id, user.safe.username, user.safe.username), hours, total, zeroDivZero = True, style = {100.01: 'progress-current-red'})
Esempio n. 9
0
	def __init__(self, placeholder, sprint, allTasks, revisions, **_):
		Chart.__init__(self, placeholder)
		days = [day for day in sprint.getDays()]
		now = Weekday.today()
		futureStarts = minOr(filter(lambda day: day > now, days), None)
		futureIndex = days.index(futureStarts) if futureStarts else None

		self.chart.defaultSeriesType = 'line'
		self.chart.zoomType = 'x'
		self.title.text = ''
		self.plotOptions.line.dataLabels.enabled = True
		self.tooltip.shared = True
		self.credits.enabled = False
		with self.xAxis as xAxis:
			xAxis.tickmarkPlacement = 'on'
			xAxis.maxZoom = 1
			xAxis.title.text = 'Day'
			# Future bar
			if futureIndex is not None:
				xAxis.plotBands = [{
					'color': '#DDD',
					'from': futureIndex - 0.75,
					'to': len(days) - 0.5
				}]
		self.yAxis.min = 0
		self.yAxis.title.text = 'Hours'
		self.series = seriesList = []

		taskSeries = {
			'id': 'taskSeries',
			'name': 'Tasking',
			'data': [],
			'color': '#4572a7',
			'zIndex': 2
		}
		seriesList.append(taskSeries)

		availSeries = {
			'name': 'Availability',
			'data': [],
			'color': '#aa4643',
			'zIndex': 2
		}
		seriesList.append(availSeries)

		flagSeries = {
			'type': 'flags',
			'data': [],
			'color': '#4572a7',
			'shape': 'flag',
			'onSeries': 'taskSeries',
			'showInLegend': False,
			'y': 16
		}
		seriesList.append(flagSeries)

		if futureIndex == 0:
			futureIndex = 1

		statusToday, hoursToday = None, None
		for day in days[:futureIndex]:
			tasksToday = [revisions[t.id, day] for t in allTasks]
			statusYesterday, hoursYesterday = statusToday, hoursToday
			statusToday = {t: t.status for t in tasksToday if t and not t.deleted}
			hoursToday = {t: t.manHours() for t in tasksToday if t and not t.deleted}
			taskSeries['data'].append(sum(hoursToday.values()))

			if hoursYesterday:
				hoursDiff = {t: hoursToday.get(t, 0) - hoursYesterday.get(t, 0) for t in hoursToday}
				largeChanges = [t for t, h in hoursDiff.iteritems() if abs(h) >= 16]
				if largeChanges:
					texts = []
					for t in largeChanges:
						if t not in hoursYesterday:
							texts.append("<span style=\"color: #f00\">(New +%d)</span> %s" % (t.effectiveHours(), t.name))
						elif hoursDiff[t] > 0:
							texts.append("<span style=\"color: #f00\">(+%d)</span> %s" % (hoursDiff[t], t.name))
						else:
							if t.status in ('in progress', 'not started'):
								texts.append("<span style=\"color: #0a0\">(%d)</span> %s" % (hoursDiff[t], t.name))
							elif t.status == 'complete':
								texts.append("<span style=\"color: #0a0\">(Complete %d)</span> %s" % (hoursDiff[t], t.name))
							else:
								texts.append("<span style=\"color: #999\">(%s %d)</span> %s" % (statuses[t.status].getRevisionVerb(statusYesterday.get(t, 'not started')), hoursDiff[t], t.name))
					flagSeries['data'].append({'x': days.index(day), 'title': alphabet[len(flagSeries['data']) % len(alphabet)], 'text': '<br>'.join(texts)})

		avail = Availability(sprint)
		for day in days:
			availSeries['data'].append(avail.getAllForward(day))

		setupTimeline(self, sprint, ['Projected tasking'])

		# Add commitment percentage to the axis label
		labels = self.xAxis.categories.get()
		for i in range(len(labels)):
			# For future percentages, use today's hours (i.e. don't use the projected hours)
			needed = taskSeries['data'][min(i, futureIndex - 1) if futureIndex else i][1]
			thisAvail = availSeries['data'][i][1]
			pcnt = "%d" % (needed * 100 / thisAvail) if thisAvail > 0 else "inf"
			labels[i] += "<br>%s%%" % pcnt
		self.xAxis.categories = labels
		self.xAxis.labels.formatter = "function() {return this.value.replace('inf', '\u221e');}"

		# Trendline
		data = self.series[0].data.get()
		dailyAvail = dict((day, avail.getAll(day)) for day in days)
		totalAvail = 0
		for daysBack in range(1, (futureIndex or 0) + 1):
			midPoint = [futureIndex - daysBack, data[futureIndex - daysBack][1]]
			if dailyAvail[days[midPoint[0]]] > 0:
				daysBack = min(daysBack + 2, futureIndex)
				startPoint = [futureIndex - daysBack, data[futureIndex - daysBack][1]]
				totalAvail = sum(dailyAvail[day] for day in days[startPoint[0] : midPoint[0]])
				break

		if totalAvail > 0 and startPoint[0] != midPoint[0]:
			slope = (midPoint[1] - startPoint[1]) / (midPoint[0] - startPoint[0])
			slopePerAvail = slope * (midPoint[0] - startPoint[0]) / totalAvail
			points, total = [], midPoint[1]
			total = taskSeries['data'][futureIndex - 1][1]
			points.append([futureIndex - 1, total])
			for i in range(futureIndex, len(days)):
				total += slopePerAvail * dailyAvail[days[i]]
				points.append([i, total])
			seriesList.append({
				'name': 'Projected tasking',
				'data': points,
				'color': '#666',
				'dataLabels': {'formatter': "function() {return (this.point.x == %d) ? parseInt(this.y, 10) : null;}" % (len(days) - 1)},
				'marker': {'symbol': 'circle'},
				'zIndex': 1
			})
Esempio n. 10
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)
Esempio n. 11
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
Esempio n. 12
0
def set_availability():
    availability = Availability.from_dict(request.get_json())
    availability.insert_or_update()
    return Response(status=200)
Esempio n. 13
0
def delete_availability():
    availability = Availability.from_json_str(request.args.get('availability'))
    availability.delete()
    return Response(status=200)
Esempio n. 14
0
def get_availabilities():
    shipping_id = request.args.get('shipping_id')
    availabilities = Availability.get_all_availabilities_of_shipping(shipping_id)
    res = json.dumps([x.to_dict() for x in availabilities])
    return Response(status=200, response=res)