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 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
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)
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('"', '"') 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\"> 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\"> %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\"> 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> — 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> — 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> — 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)
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> </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\"> </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\"> </td>" print "<td class=\"buttons\"></td>" print "</tr>" for user in sorted(sprint.members): print "<tr class=\"userline\">" print "<td class=\"username\">%s <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\"> </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>"
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)
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()}'
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'})
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 })
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)
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
def set_availability(): availability = Availability.from_dict(request.get_json()) availability.insert_or_update() return Response(status=200)
def delete_availability(): availability = Availability.from_json_str(request.args.get('availability')) availability.delete() return Response(status=200)
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)