def _get_action_controls(self, req, tickets): action_controls = [] ts = TicketSystem(self.env) tickets_by_action = {} for t in tickets: ticket = Ticket(self.env, t['id']) actions = ts.get_available_actions(req, ticket) for action in actions: tickets_by_action.setdefault(action, []).append(ticket) sorted_actions = sorted(set(tickets_by_action.keys())) for action in sorted_actions: first_label = None hints = [] widgets = [] ticket = tickets_by_action[action][0] for controller in self._get_action_controllers( req, ticket, action): label, widget, hint = controller.render_ticket_action_control( req, ticket, action) if not first_label: first_label = label widgets.append(widget) hints.append(hint) action_controls.append((action, first_label, tag(widgets), hints)) return action_controls
def filter_stream(self, req, method, filename, stream, data): logger = self.env.log if filename == 'ticket.html': ticket_sys = TicketSystem(self.env) custom_fields = [field.get('name') for field in ticket_sys.get_ticket_fields()\ if field.get('custom') is True] all_fields = self.default_fields + custom_fields # Populate de all_fields array so the javascript code can # hide/show them. script = "<script>\n" for field_name in all_fields: script += ("all_fields.push('%s');\n" % field_name) i = 1 while len(self.config.getlist(self.config_section, ('ticket_type_%d' % i))) > 0 : ticket_type = self.config.getlist(self.config_section, ('ticket_type_%d' % i)) visible_fields = self.config.getlist(self.config_section, ('visible_fields_%d' % i)) required_fields = self.config.getlist(self.config_section, ('required_fields_%d' % i)) if len(visible_fields) > 0 : script += ("types['%s'] = ['%s'];\n" % ("".join(ticket_type), "','".join(visible_fields))) script += ("types_required['%s'] = ['%s'];\n" % ("".join(ticket_type), "','".join(required_fields))) i += 1 script += "</script>\n" stream |= Transformer("//div[@id='footer']").after(MarkupTemplate(script).generate()) return stream
def _get_action_controls(self, req, tickets): action_controls = [] ts = TicketSystem(self.env) tickets_by_action = {} for t in tickets: ticket = Ticket(self.env, t['id']) available_actions = ts.get_available_actions(req, ticket) for action in available_actions: tickets_by_action.setdefault(action, []).append(ticket) # Sort the allowed actions by the 'default' key. allowed_actions = set(tickets_by_action.keys()) workflow = ConfigurableTicketWorkflow(self.env) all_actions = sorted( ((action['default'], name) for name, action in workflow.get_all_actions().iteritems()), reverse=True) sorted_actions = [ action[1] for action in all_actions if action[1] in allowed_actions ] for action in sorted_actions: first_label = None hints = [] widgets = [] ticket = tickets_by_action[action][0] for controller in self._get_action_controllers( req, ticket, action): label, widget, hint = controller.render_ticket_action_control( req, ticket, action) if not first_label: first_label = label widgets.append(widget) hints.append(hint) action_controls.append((action, first_label, tag(widgets), hints)) return action_controls
def _get_action_controls(self, req, tickets): action_controls = [] ts = TicketSystem(self.env) tickets_by_action = {} for t in tickets: ticket = Ticket(self.env, t['id']) actions = ts.get_available_actions(req, ticket) for action in actions: tickets_by_action.setdefault(action, []).append(ticket) sorted_actions = sorted(set(tickets_by_action.keys())) for action in sorted_actions: first_label = None hints = [] widgets = [] ticket = tickets_by_action[action][0] for controller in self._get_action_controllers(req, ticket, action): label, widget, hint = controller.render_ticket_action_control( req, ticket, action) if not first_label: first_label = label widgets.append(widget) hints.append(hint) action_controls.append((action, first_label, tag(widgets), hints)) return action_controls
def _generate_form(self, req, data): batchFormData = dict(data) batchFormData['query_href']= req.session['query_href'] \ or req.href.query() batchFormData['notify_enabled'] = self.config.getbool( 'notification', 'smtp_enabled', False) ticketSystem = TicketSystem(self.env) fields = [] for field in ticketSystem.get_ticket_fields(): if field['name'] not in ('summary', 'reporter', 'description'): fields.append(field) if field['name'] == 'owner' \ and hasattr(ticketSystem, 'eventually_restrict_owner'): ticketSystem.eventually_restrict_owner(field) fields.sort(key=lambda f: f['name']) batchFormData['fields'] = fields add_script(req, 'batchmod/js/batchmod.js') add_stylesheet(req, 'batchmod/css/batchmod.css') stream = Chrome(self.env).render_template(req, 'batchmod.html', batchFormData, fragment=True) return stream.select('//form[@id="batchmod_form"]')
def process_request(self, req): if req.path_info == '/' and MobileDetect(req).is_mobile(): req.redict('/mobile') else: ts = TicketSystem(self.env) fields = ts.get_ticket_fields() return 'test.html', {"JSON": custom_json, "fields": fields}, None
def meta_getTypeDefinition(self, req): ticket_system = TicketSystem(self.env) fields = ticket_system.get_ticket_fields() # remove ticket type as this should be a parameter of the request # XXX Trac doesn't support fields based on ticket type fields = [field for field in fields if field['name'] != 'type'] return fields
def meta_getTypeDefinition(self, req): ticket_system = TicketSystem(self.env) fields = ticket_system.get_ticket_fields() # remove ticket type as this should be a parameter of the request # XXX Trac doesn't support fields based on ticket type fields = [ field for field in fields if field['name'] != 'type' ] return fields
def process_request(self, req): if req.path_info == '/': try: from mobiledetect import MobileDetect if MobileDetect( useragent=req.get_header('user-agent')).is_mobile(): req.redirect('/mobile') except ImportError: pass else: ts = TicketSystem(self.env) fields = ts.get_ticket_fields() return 'test.html', {"JSON": custom_json, "fields": fields}, None
def filter_stream(self, req, method, filename, stream, formdata): '''Add workflows to query/report output''' if filename != 'query.html' and filename != 'report_view.html': return stream if not (req.perm.has_permission('TICKET_ADMIN') or req.perm.has_permission('TICKET_GRID_WORKFLOW')): return stream ts = TicketSystem(self.env) add_script(req, 'gridflow/gridflow.js') html = stream.render() js = '' tickets = [] copy = genshi.XML(html) nodes = genshi.path.Path('//td[contains(@class, "ticket")]//a/text()') tickets += [int(a[1][1:]) for a in nodes.select(copy)] copy = genshi.XML(html) nodes = genshi.path.Path('//td[contains(@class, "id")]//a/text()') tickets += [int(a[1][1:]) for a in nodes.select(copy)] copy = genshi.XML(html) tktDict = {} for tno in tickets: tktDict[tno] = {'labels': [], 'widgets': [], 'actions': []} tkt = trac.ticket.Ticket(self.env, tno) actions = ts.get_available_actions(req, tkt) for action in actions: for controller in self._get_action_controllers( req, tkt, action): (label, widget, hint) = controller.render_ticket_action_control( req, tkt, action) tktDict[tno]['actions'].append(action) tktDict[tno]['labels'].append(label) tktDict[tno]['widgets'].append(widget.generate().render()) js += 'tktDict = ' + repr(tktDict).replace(", u'", ", '").replace( "[u'", "['") + ';\n' js += 'baseURL = "%s";\n' % req.base_url script = genshi.builder.tag.script(js, type="text/javascript") xpath = '//head' copy |= Transformer(xpath).append(script) return copy
def _get_action_controllers(self, req, ticket, action): """Generator yielding the controllers handling the given `action`""" for controller in TicketSystem(self.env).action_controllers: actions = [a for w, a in controller.get_ticket_actions(req, ticket) or []] if action in actions: yield controller
def get_available_actions(env, action='resolve'): # The list should not have duplicates. ts = TicketSystem(env) for controller in ts.action_controllers: if isinstance(controller, ConfigurableTicketWorkflow): return controller.actions.get(action) return None
def _get_options(self, field_name): """Return a list of options for the given [dynvars] field: [dynvars] myfield.options = value1|value2|value3 If no [dynvars] field is found, a select field is searched and its options returned. For the milestone field, completed milestones are omitted. If no select field is found, then an empty list is returned.""" # look for [dynvars] field for key, val in self.env.config.options('dynvars'): if key == field_name + '.options': return val.split('|') # handle milestone special - skip completed milestones if field_name == 'milestone': return [''] + [ m.name for m in Milestone.select(self.env, include_completed=False) ] # lookup select field for field in TicketSystem(self.env).get_ticket_fields(): if field['name'] == field_name and 'options' in field: return field['options'] return []
def __init__(self, env, constraints=None, order=None, desc=0, group=None, groupdesc=0, verbose=0): self.env = env self.constraints = constraints or {} self.order = order self.desc = desc self.group = group self.groupdesc = groupdesc self.verbose = verbose self.fields = TicketSystem(self.env).get_ticket_fields() self.cols = [] # lazily initialized if self.order != 'id' \ and self.order not in [f['name'] for f in self.fields]: # order by priority by default self.order = 'priority' if self.group not in [f['name'] for f in self.fields]: self.group = None
def _process_request(self, req): '''Intercept and execute GridFlow AJAX callbacks.''' if not req.perm.has_permission('TICKET_ADMIN') and \ not req.perm.has_permission('TICKET_GRID_WORKFLOW'): raise Exception('Permission denied') id = req.args.get('id') action = req.args.get('action') ticket = Ticket(self.env, id) ts = TicketSystem(self.env) validActions = ts.get_available_actions(req, ticket) if action not in validActions: req.send_response(500) req.send_header('Content-Type', 'text/plain') req.end_headers() req.write('"%s" is not a valid action for #%d\n' % (action, id)) return changes, problems = self.get_ticket_changes(req, ticket, action) if problems: req.send_response(500) req.send_header('Content-Type', 'text/plain') req.end_headers() req.write('Problems: %s\n' % problems) return self._apply_ticket_changes(ticket, changes) valid, reasons = self._validate_ticket(req, ticket) if not valid: req.send_response(500) req.send_header('Content-Type', 'text/plain') req.end_headers() req.write('Changes not valid:\n') for reason in reasons: req.write('* ' + reason + '\n') return self._save(req, ticket, action) req.send_response(200) req.send_header('Content-Type', 'text/plain') req.end_headers() req.write('OK') return
def _add_ticket_fields(self, req): for field in TicketSystem(self.env).get_ticket_fields(): name = field['name'] del field['name'] if name in ('summary', 'reporter', 'description', 'type', 'status', 'resolution', 'owner'): field['skip'] = True req.hdf['ticket.fields.' + name] = field
def _do_save(self, req, db, ticket): if req.perm.has_permission('TICKET_CHGPROP'): # TICKET_CHGPROP gives permission to edit the ticket if not req.args.get('summary'): raise TracError('Tickets must contain summary.') if req.args.has_key('description') or req.args.has_key('reporter'): req.perm.assert_permission('TICKET_ADMIN') ticket.populate(req.args) else: req.perm.assert_permission('TICKET_APPEND') # Mid air collision? if int(req.args.get('ts')) != ticket.time_changed: raise TracError("Sorry, can not save your changes. " "This ticket has been modified by someone else " "since you started", 'Mid Air Collision') # Do any action on the ticket? action = req.args.get('action') actions = TicketSystem(self.env).get_available_actions(ticket, req.perm) if action not in actions: raise TracError('Invalid action') # TODO: this should not be hard-coded like this if action == 'accept': ticket['status'] = 'assigned' ticket['owner'] = req.authname if action == 'resolve': ticket['status'] = 'closed' ticket['resolution'] = req.args.get('resolve_resolution') elif action == 'reassign': ticket['owner'] = req.args.get('reassign_owner') ticket['status'] = 'new' elif action == 'reopen': ticket['status'] = 'reopened' ticket['resolution'] = '' self._validate_ticket(req, ticket) now = int(time.time()) cnum = req.args.get('cnum') replyto = req.args.get('replyto') internal_cnum = cnum if cnum and replyto: # record parent.child relationship internal_cnum = '%s.%s' % (replyto, cnum) if ticket.save_changes(get_reporter_id(req, 'author'), req.args.get('comment'), when=now, db=db, cnum=internal_cnum): db.commit() try: tn = TicketNotifyEmail(self.env) tn.notify(ticket, newticket=False, modtime=now) except Exception, e: self.log.exception("Failure sending notification on change to " "ticket #%s: %s" % (ticket.id, e))
def _get_action_controllers(self, req, ticket, action): for controller in TicketSystem(self.env).action_controllers: actions = [ action for weight, action in controller.get_ticket_actions( req, ticket) ] if action in actions: yield controller
def _generate_form(self, req, data): batchFormData = dict(data) batchFormData["query_href"] = req.session["query_href"] or req.href.query() ticketSystem = TicketSystem(self.env) fields = [] for field in ticketSystem.get_ticket_fields(): if field["name"] not in ("summary", "reporter", "description"): fields.append(field) if field["name"] == "owner" and hasattr(ticketSystem, "eventually_restrict_owner"): ticketSystem.eventually_restrict_owner(field) fields.sort(key=lambda f: f["name"]) batchFormData["fields"] = fields add_script(req, "batchmod/js/batchmod.js") add_stylesheet(req, "batchmod/css/batchmod.css") stream = Chrome(self.env).render_template(req, "batchmod.html", batchFormData, fragment=True) return stream.select('//form[@id="batchmod_form"]')
def filter_stream(self, req, method, filename, stream, formdata): '''Add workflows to query/report output''' if filename != 'query.html' and filename != 'report_view.html': return stream if not (req.perm.has_permission('TICKET_ADMIN') or req.perm.has_permission('TICKET_GRID_WORKFLOW')): return stream ts = TicketSystem(self.env) add_script(req, 'gridflow/gridflow.js') html = stream.render() js = '' tickets = [] copy = genshi.XML(html) nodes = genshi.path.Path('//td[contains(@class, "ticket")]//a/text()') tickets += [int(a[1][1:]) for a in nodes.select(copy)] copy = genshi.XML(html) nodes = genshi.path.Path('//td[contains(@class, "id")]//a/text()') tickets += [int(a[1][1:]) for a in nodes.select(copy)] copy = genshi.XML(html); tktDict = {} for tno in tickets: tktDict[tno] = {'labels': [], 'widgets': [], 'actions': []} tkt = trac.ticket.Ticket(self.env, tno) actions = ts.get_available_actions(req, tkt) for action in actions: for controller in self._get_action_controllers(req, tkt, action): (label, widget, hint) = controller.render_ticket_action_control(req, tkt, action) tktDict[tno]['actions'].append(action) tktDict[tno]['labels'].append(label) tktDict[tno]['widgets'].append(widget.generate().render()) js += 'tktDict = ' + repr(tktDict).replace(", u'", ", '").replace("[u'", "['") + ';\n' js += 'baseURL = "%s";\n' % req.base_url script = genshi.builder.tag.script(js, type="text/javascript") xpath = '//head' copy |= Transformer(xpath).append(script) return copy
def __init__(self, env, tkt_id=None, db=None): self.env = env self.fields = TicketSystem(self.env).get_ticket_fields() self.values = {} if tkt_id: self._fetch_ticket(tkt_id, db) else: self._init_defaults(db) self.id = self.time_created = self.time_changed = None self._old = {}
def _get_new_ticket_values(self, req, env): """Pull all of the new values out of the post data.""" values = {} for field in TicketSystem(env).get_ticket_fields(): name = field['name'] if name not in ('summary', 'reporter', 'description'): value = req.args.get('batchmod_value_' + name) if value is not None: values[name] = value return values
def _get_groups(env, db, by='component'): for field in TicketSystem(env).get_ticket_fields(): if field['name'] == by: if field.has_key('options'): return field['options'] else: cursor = db.cursor() cursor.execute("SELECT DISTINCT %s FROM ticket ORDER BY %s" % (by, by)) return [row[0] for row in cursor] return []
def _render_view(self, req, db, milestone): req.hdf['title'] = 'Milestone %s' % milestone.name req.hdf['milestone.mode'] = 'view' req.hdf['milestone'] = milestone_to_hdf(self.env, db, req, milestone) available_groups = [] component_group_available = False for field in TicketSystem(self.env).get_ticket_fields(): if field['type'] == 'select' and field['name'] != 'milestone' \ or field['name'] == 'owner': available_groups.append({ 'name': field['name'], 'label': field['label'] }) if field['name'] == 'component': component_group_available = True req.hdf['milestone.stats.available_groups'] = available_groups if component_group_available: by = req.args.get('by', 'component') else: by = req.args.get('by', available_groups[0]['name']) req.hdf['milestone.stats.grouped_by'] = by tickets = get_tickets_for_milestone(self.env, db, milestone.name, by) stats = calc_ticket_stats(tickets) req.hdf['milestone.stats'] = stats for key, value in get_query_links(req, milestone.name).items(): req.hdf['milestone.queries.' + key] = value groups = _get_groups(self.env, db, by) group_no = 0 max_percent_total = 0 for group in groups: group_tickets = [t for t in tickets if t[by] == group] if not group_tickets: continue prefix = 'milestone.stats.groups.%s' % group_no req.hdf['%s.name' % prefix] = group percent_total = 0 if len(tickets) > 0: percent_total = float(len(group_tickets)) / float(len(tickets)) if percent_total > max_percent_total: max_percent_total = percent_total req.hdf['%s.percent_total' % prefix] = percent_total * 100 stats = calc_ticket_stats(group_tickets) req.hdf[prefix] = stats for key, value in \ get_query_links(req, milestone.name, by, group).items(): req.hdf['%s.queries.%s' % (prefix, key)] = value group_no += 1 req.hdf['milestone.stats.max_percent_total'] = max_percent_total * 100
class TicketSystemApi(object): """Manages interaction with Trac ticket system""" def __init__(self, env): self.ticket_system = TicketSystem(env) self.custom_fields = self.ticket_system.get_custom_fields() def get_custom_field_label(self, custom_field_name): """For the given custom field name, get the corresponding label.""" for custom_field in self.custom_fields: if custom_field["name"] == custom_field_name: return custom_field["label"]
def _get_new_ticket_values(self, req): """Pull all of the new values out of the post data.""" values = {} for field in TicketSystem(self.env).get_ticket_fields(): name = field['name'] if name not in ('id', 'resolution', 'status', 'owner', 'time', 'changetime', 'summary', 'reporter', 'description') and field['type'] != 'textarea': value = req.args.get('batchmod_value_' + name) if value is not None: values[name] = value return values
def _generate_form(self, req, data): batchFormData = dict(data) batchFormData['query_href']= req.session['query_href'] \ or req.href.query() batchFormData['notify_enabled'] = self.config.getbool('notification', 'smtp_enabled', False) ticketSystem = TicketSystem(self.env) fields = [] for field in ticketSystem.get_ticket_fields(): if field['name'] not in ('summary', 'reporter', 'description'): fields.append(field) if field['name'] == 'owner' \ and hasattr(ticketSystem, 'eventually_restrict_owner'): ticketSystem.eventually_restrict_owner(field) fields.sort(key=lambda f: f['name']) batchFormData['fields']=fields add_script(req, 'batchmod/js/batchmod.js') add_stylesheet(req, 'batchmod/css/batchmod.css') stream = Chrome(self.env).render_template(req, 'batchmod.html', batchFormData, fragment=True) return stream.select('//form[@id="batchmod_form"]')
def get_value_and_options(self, req, target, key): """Returns the preference value for the given key if configured for being set by user preference. If no user preference has been set yet, the target field's default value is returned.""" value = '' options = [] for field in TicketSystem(self.env).get_ticket_fields(): if field['name'] == target: value = field.get('value', value) options = field.get('options', options) break if key in self._pref_defaults: value = req.session.get(PREFIX + key + '.value', value) return value, options
def _get_constraints(self, req): constraints = {} ticket_fields = [ f['name'] for f in TicketSystem(self.env).get_ticket_fields() ] # A special hack for Safari/WebKit, which will not submit dynamically # created check-boxes with their real value, but with the default value # 'on'. See also htdocs/query.js#addFilter() checkboxes = [k for k in req.args.keys() if k.startswith('__')] if checkboxes: import cgi for checkbox in checkboxes: (real_k, real_v) = checkbox[2:].split(':', 2) req.args.list.append(cgi.MiniFieldStorage(real_k, real_v)) # For clients without JavaScript, we remove constraints here if # requested remove_constraints = {} to_remove = [ k[10:] for k in req.args.keys() if k.startswith('rm_filter_') ] if to_remove: # either empty or containing a single element match = re.match(r'(\w+?)_(\d+)$', to_remove[0]) if match: remove_constraints[match.group(1)] = int(match.group(2)) else: remove_constraints[to_remove[0]] = -1 for field in [k for k in req.args.keys() if k in ticket_fields]: vals = req.args[field] if not isinstance(vals, (list, tuple)): vals = [vals] vals = map(lambda x: x.value, vals) if vals: mode = req.args.get(field + '_mode') if mode: vals = map(lambda x: mode + x, vals) if remove_constraints.has_key(field): idx = remove_constraints[field] if idx >= 0: del vals[idx] if not vals: continue else: continue constraints[field] = vals return constraints
def delete(self, db=None): db, handle_ta = self._get_db_for_write(db) Attachment.delete_all(self.env, 'ticket', self.id, db) cursor = db.cursor() cursor.execute("DELETE FROM ticket WHERE id=%s", (self.id, )) cursor.execute("DELETE FROM ticket_change WHERE ticket=%s", (self.id, )) cursor.execute("DELETE FROM ticket_custom WHERE ticket=%s", (self.id, )) if handle_ta: db.commit() for listener in TicketSystem(self.env).change_listeners: listener.ticket_deleted(self)
def _get_new_ticket_values(self, req): """Pull all of the new values out of the post data.""" values = {} for field in TicketSystem(self.env).get_ticket_fields(): name = field['name'] if name not in ('id', 'resolution', 'status', 'owner', 'time', 'changetime', 'summary', 'description') + \ (('reporter',) if 'TICKET_ADMIN' not in req.perm else ()) \ and field['type'] != 'textarea': value = req.args.get('batchmod_value_' + name) if value is not None: values[name] = self._parse_field_value(req, field, value) return values
def get_statistics_source(self, active=None): stats_source = [{ 'value': None, 'label': _('Number of tickets'), 'active': not active }] fields = TicketSystem(self.env).get_ticket_fields() for field in fields: if field['name'] in self._calculate_statistics_on: stats_source.append({ 'value': field['name'], 'label': field['label'], 'active': field['name'] == active }) return stats_source
def get_tickets_for_milestone(env, db, milestone, field='component'): cursor = db.cursor() fields = TicketSystem(env).get_ticket_fields() if field in [f['name'] for f in fields if not f.get('custom')]: cursor.execute( "SELECT id,status,%s FROM ticket WHERE milestone like %%s " "ORDER BY %s" % (field, field), (milestone + '%', )) else: cursor.execute( "SELECT id,status,value FROM ticket LEFT OUTER " "JOIN ticket_custom ON (id=ticket AND name=%s) " "WHERE milestone like %s ORDER BY value", (field, milestone + '%')) tickets = [] for tkt_id, status, fieldval in cursor: tickets.append({'id': tkt_id, 'status': status, field: fieldval}) return tickets
def _batch_modify(self, req): tickets = req.session['query_tickets'].split(' ') comment = req.args.get('comment', '') values = {} for field in TicketSystem(self.env).get_ticket_fields(): name = field['name'] if name not in ('summary', 'reporter', \ 'description', 'type', 'status', 'resolution', 'owner'): if req.args.has_key('bm_' + name): values[name] = req.args.get(name) for id in tickets: t = Ticket(self.env, id) t.populate(values) t.save_changes(req.authname, comment)
def _do_checkdates(self): ts = TicketSystem(self.env) now = datetime.now(utc) hard_mail = [] soft_mail = [] with self.env.db_query as db: cursor = db.cursor() cursor.execute("SELECT id FROM ticket WHERE status != 'closed'") for row in cursor: ticket_id = row[0] t = Ticket(self.env, ticket_id, db) t['id'] = ticket_id if t['due_date']: due_diff = parse_date(t['due_date']) - now if due_diff <= self.diff_threshold: hard_mail.append(t) if t['soft_due_date']: soft_due_diff = parse_date(t['soft_due_date']) - now if soft_due_diff <= self.soft_diff_threshold: soft_mail.append(t) if len(hard_mail) == 0 and len(soft_mail) == 0: return mail_body = "This is your friendly Ticket Due Date Mailer for today.\n\n" hard_mail=sorted(hard_mail, key=lambda ticket: ticket['due_date']) soft_mail=sorted(soft_mail, key=lambda ticket: ticket['soft_due_date']) if len(hard_mail) != 0: mail_body += "The following tickets are nearing their HARD DEADLINE:\n" mail_body += " id due_date component summary\n" for ticket in hard_mail: mail_body += " % 4d %s % 12s %s\n" % (ticket['id'], ticket['due_date'], ticket['component'], ticket['summary']) mail_body += "\n" if len(soft_mail) != 0: mail_body += "The following tickets are nearing their SOFT DEADLINE:\n" mail_body += " id soft_due_date component summary\n" for ticket in soft_mail: mail_body += " % 4d %s % 12s %s\n" % (ticket['id'], ticket['soft_due_date'], ticket['component'], ticket['summary']) mail_body += "\n" mail_body += "See you next time!\n" mail_body += "Trac Assistent" print mail_body.encode('utf-8')
def grouped_stats_data(env, stats_provider, tickets, by, per_group_stats_data): """Get the `tickets` stats data grouped by ticket field `by`. `per_group_stats_data(gstat, group_name)` should return a data dict to include for the group with field value `group_name`. """ group_names = [] for field in TicketSystem(env).get_ticket_fields(): if field['name'] == by: if 'options' in field: group_names = field['options'] if field.get('optional'): group_names.insert(0, '') else: group_names = [ name for name, in env.db_query(""" SELECT DISTINCT COALESCE(%s, '') FROM ticket ORDER BY COALESCE(%s, '') """ % (by, by)) ] max_count = 0 data = [] for name in group_names: values = (name, ) if name else (None, name) group_tickets = [t for t in tickets if t[by] in values] if not group_tickets: continue gstat = get_ticket_stats(stats_provider, group_tickets) if gstat.count > max_count: max_count = gstat.count gs_dict = {'name': name} gs_dict.update(per_group_stats_data(gstat, name)) data.append(gs_dict) for gs_dict in data: percent = 1.0 if max_count: gstat = gs_dict['stats'] percent = float(gstat.count) / float(max_count) * 100 gs_dict['percent_of_max_total'] = percent return data
def _render_view(self, req, milestone): available_groups = [] component_group_available = False project_id = req.data['project_id'] ticket_fields = TicketSystem(self.env).get_ticket_fields(pid=project_id) # collect fields that can be used for grouping for name, field in ticket_fields.iteritems(): if field['type'] == 'select' and name != 'milestone' \ or name in ('owner', 'reporter'): available_groups.append({'name': name, 'label': field['label']}) if name == 'component': component_group_available = True # determine the field currently used for grouping by = None if component_group_available: by = 'component' elif available_groups: by = available_groups[0]['name'] by = req.args.get('by', by) db = self.env.get_read_db() tickets = get_tickets_for_milestone(self.env, db, milestone, by) stat = get_ticket_stats(self.stats_provider, tickets, project_id) tstat = get_ticket_stats(self.tickettype_stats_provider, tickets, project_id) # Data for milestone and timeline data = { 'milestone': milestone, 'tickethistory' : [], 'dates' : [], 'ticketstat' : {}, 'yui_base_url': self.tm.yui_base_url, '_': _, } data.update(milestone_stats_data(self.env, req, stat, milestone)) ticketstat = {'name':'ticket type'} ticketstat.update(milestone_stats_data(self.env, req, tstat, milestone)) data['ticketstat'] = ticketstat # get list of ticket ids that in the milestone everytickets = get_every_tickets_in_milestone(db, project_id, milestone.name) if everytickets: tkt_history = collect_tickets_status_history(db, everytickets, milestone) if tkt_history: # Sort the key in the history list # returns sorted list of tuple of (key, value) sorted_events = sorted(tkt_history.items(), key=lambda(t,events):(t)) # Get first date that ticket enter the milestone min_time = sorted_events[0][0] begin_date = to_datetime(min_time, tzinfo=req.tz) if milestone.is_completed: end_date = milestone.completed else: end_date = None end_date = to_datetime(end_date, tzinfo=req.tz) dates = list(date_generator(begin_date, end_date)) #Create a data for the cumulative flow chart. date_history = prepare_to_cumulate(sorted_events) tkt_cumulative_table = make_cumulative_data(dates, date_history) #prepare Yahoo datasource for comulative flow chart dscumulative = '' for idx, date in enumerate(dates): dscumulative = dscumulative + '{ date: "%s", enter: %d, leave: %d, finish: %d}, ' \ % (format_date(date,tzinfo=utc), tkt_cumulative_table['Enter'][idx], \ tkt_cumulative_table['Leave'][idx], tkt_cumulative_table['Finish'][idx]) data['tickethistory'] = tkt_cumulative_table data['dates'] = dates data['dscumulative'] = '[ ' + dscumulative + ' ];' return 'mdashboard.html', data, None