def expand_macro(self, formatter, name, content): args, kwargs = parse_args(content) pattern = args[0] if len(args) > 1 and args[1].strip().upper() == "COMPLETED": completed = "AND completed>0" else: completed = "AND completed=0" if len(args) > 2 and args[2].strip().upper() == "ASC": ordering = "ASC" else: ordering = "DESC" db = self.env.get_db_cnx() cursor = db.cursor() print """ SELECT name FROM milestone WHERE name %s %s ORDER BY name %s """ % (db.like(), completed, ordering) cursor.execute(""" SELECT name FROM milestone WHERE name %s %s ORDER BY name %s """ % (db.like(), completed, ordering), (pattern,) ) out = StringIO() for name, in cursor.fetchall(): wikitext = """ == [milestone:%(milestonename)s %(milestonename)s] == [[TicketQuery(milestone=%(milestonename)s,order=id,desc=0,format=table,col=summary|owner|ticket_status|type|status)]] """ % {'milestonename': name} Formatter(self.env, formatter.context).format(wikitext, out) return Markup(out.getvalue())
def expand_macro(self, formatter, name, content): if not content: return '' args, kwargs = parse_args(content) if len(args) > 1: return system_message("Number of args can't be greater than 1") if args[0] == 'author': page = WikiPage(self.env, formatter.context.resource, 1) text = page.author elif args[0] == 'version': page = WikiPage(self.env, formatter.context.resource) text = str(page.version) elif args[0] == 'changed_by': page = WikiPage(self.env, formatter.context.resource) text = page.author elif args[0] == 'comment': page = WikiPage(self.env, formatter.context.resource) text = page.comment elif args[0] == 'changed_ts': page = WikiPage(self.env, formatter.context.resource) text = format_datetime(page.time) else: return system_message("Unkwown argument %s" % args[0]) return format_to_oneliner(self.env, formatter.context, text)
def expand_macro(self, formatter, name, content): args, kwargs = parse_args(content) pattern = args[0] if len(args) > 1 and args[1].strip().upper() == "COMPLETED": completed = "AND completed>0" else: completed = "AND completed=0" if len(args) > 2 and args[2].strip().upper() == "ASC": ordering = "ASC" else: ordering = "DESC" db = self.env.get_db_cnx() cursor = db.cursor() print """ SELECT name FROM milestone WHERE name %s %s ORDER BY name %s """ % (db.like(), completed, ordering) cursor.execute( """ SELECT name FROM milestone WHERE name %s %s ORDER BY name %s """ % (db.like(), completed, ordering), (pattern, )) out = StringIO() for name, in cursor.fetchall(): wikitext = """ == [milestone:%(milestonename)s %(milestonename)s] == [[TicketQuery(milestone=%(milestonename)s,order=id,desc=0,format=table,col=summary|owner|ticket_status|type|status)]] """ % { 'milestonename': name } Formatter(self.env, formatter.context).format(wikitext, out) return Markup(out.getvalue())
def expand_macro(self, formatter, name, content): args, kw = parse_args(content) page = kw.get('page', '') sort = kw.get('sort', 'DESC') order = kw.get('order', 'time') self.env.log.error('sort %s, order %s' % (sort, order)) attachment_type = "" wiki_path = "" if content: argv = [arg.strip() for arg in content.split(',')] if len(argv) > 0: attachment_type = argv[0] db = self.env.get_db_cnx() if db == None: return "No DB connection" attachmentFormattedList = "" cursor = db.cursor() if attachment_type == None or attachment_type == "": cursor.execute("SELECT type,id,filename,size,time,description," "author,ipnr FROM attachment") elif page == None or page == "": cursor.execute( "SELECT type,id,filename,size,time,description," "author,ipnr FROM attachment WHERE type=%s ORDER " "BY " + order + " " + sort, (attachment_type, )) else: cursor.execute( "SELECT type,id,filename,size,time,description," "author,ipnr FROM attachment WHERE type=%s and " "id=%s ORDER BY " + order + " " + sort, (attachment_type, page)) formatters = { "wiki": formatter.href.wiki, "ticket": formatter.href.ticket } types = {"wiki": "", "ticket": "ticket "} return tag.ul([ tag.li( tag.a(filename, href=formatter.href.attachment(type + "/" + id + "/" + filename)), " (", tag.span(pretty_size(size), title=size), ") - ", tag.em(format_datetime(time)), " - added by ", tag.em(author), " to ", tag.a(types[type] + " " + id, href=formatters[type](id)), " ") for type, id, filename, size, time, description, author, ipnr in cursor ]) return attachmentFormattedList
def expand_macro(self, formatter, name, content): args, kw = parse_args(content) page = kw.get('page', '') sort = kw.get('sort', 'DESC') order = kw.get('order', 'time') self.env.log.error('sort %s, order %s' % (sort, order)) attachment_type = "" wiki_path = "" if content: argv = [arg.strip() for arg in content.split(',')] if len(argv) > 0: attachment_type = argv[0] db = self.env.get_db_cnx() if db == None: return "No DB connection" attachmentFormattedList="" cursor = db.cursor() if attachment_type == None or attachment_type == "": cursor.execute("SELECT type,id,filename,size,time,description," "author,ipnr FROM attachment") elif page == None or page == "": cursor.execute("SELECT type,id,filename,size,time,description," "author,ipnr FROM attachment WHERE type=%s ORDER " "BY " + order + " " + sort, (attachment_type,)) else: cursor.execute("SELECT type,id,filename,size,time,description," "author,ipnr FROM attachment WHERE type=%s and " "id=%s ORDER BY " + order + " " + sort, (attachment_type, page)) formatters={"wiki": formatter.href.wiki, "ticket": formatter.href.ticket} types={"wiki": "", "ticket": "ticket "} return tag.ul( [tag.li( tag.a(filename, href=formatter.href.attachment(type + "/" + id + "/" + filename)), " (", tag.span(pretty_size(size), title=size), ") - ", tag.em(format_datetime(time)), " - added by ", tag.em(author), " to ", tag.a(types[type] + " " + id, href=formatters[type](id)), " ") for type,id,filename,size,time,description,author,ipnr in cursor]) return attachmentFormattedList
def expand_macro(self, formatter, name, content): args, kwargs = parse_args(content, strict=False) milestones = filter(lambda m: not args or any(map(lambda a: m.name.startswith(a), args)), Milestone.select(self.env)) req = formatter.req template = "listmilestones.html" data = {'milestones' : milestones} content_type = 'text/html' dispatcher = RequestDispatcher(self.env) response = dispatcher._post_process_request(req, template, data, content_type) # API < 1.1.2 does not return a method. if (len(response) == 3): template, data, content_type = response return Markup(Chrome(self.env).render_template(formatter.req, template, data, content_type=content_type, fragment=True)) template, data, content_type, method = response return Markup(Chrome(self.env).render_template(formatter.req, template, data, content_type=content_type, fragment=True, method=method))
def expand_macro(self, formatter, name, content): args, kwargs = parse_args(content, strict=False) milestones = filter(lambda m: not args or any(map(lambda a: m.name.startswith(a), args)), Milestone.select(self.env)) req = formatter.req template = "listmilestones.html" data = {'milestones' : milestones} content_type = 'text/html' dispatcher = RequestDispatcher(self.env) response = dispatcher._post_process_request(req, template, data, content_type) # API < 1.1.2 does not return a method. if (len(response) == 3): template, data, content_type = response return Markup(Chrome(self.env).render_template(formatter.req, template, data, content_type=content_type, fragment=True)) template, data, content_type, method = response return Markup(Chrome(self.env).render_template(formatter.req, template, data, content_type=content_type, fragment=True, method=method))
def expand_macro(self, formatter, name, content): largs, kwargs = parse_args(content) try: num = int(largs[0]) - 1 if num < 0: raise Exception except: raise TracError("Argument must be a positive integer!") option = self.env.config.get if 'raw' in kwargs: vraw = kwargs['raw'].lower() if vraw in ('yes', 'true', '1', 'on'): raw = True elif vraw in ('no', 'false', '0', 'off'): raw = False else: raw = self.raw res = formatter.resource id = res.id type = res.realm db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute( "SELECT filename FROM attachment WHERE type=%s " \ "AND id=%s ORDER BY time", (type,id) ) attmnts = cursor.fetchall() if len(attmnts) < num + 1 or not attmnts[num] or not attmnts[num][0]: raise TracError("Attachment #%i doesn't exists!" % (num + 1)) filename = attmnts[num][0] wikilink = "attachment:'" + filename + "'" if raw: wikilink = "raw-" + wikilink if kwargs.get('format', self.format) == 'short': wikilink = "[%s %s]" % (wikilink, filename) return format_to_oneliner(self.env, formatter.context, wikilink)
def expand_macro(self, formatter, name, content): largs, kwargs = parse_args(content) try: num = int(largs[0]) - 1 if num < 0: raise Exception except: raise TracError("Argument must be a positive integer!") option = self.env.config.get if 'raw' in kwargs: vraw = kwargs['raw'].lower() if vraw in ('yes','true','1','on'): raw = True elif vraw in ('no','false','0','off'): raw = False else: raw = self.raw res = formatter.resource id = res.id type = res.realm db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute( "SELECT filename FROM attachment WHERE type=%s " \ "AND id=%s ORDER BY time", (type,id) ) attmnts = cursor.fetchall() if len(attmnts) < num + 1 or not attmnts[num] or not attmnts[num][0]: raise TracError("Attachment #%i doesn't exists!" % (num+1)) filename = attmnts[num][0] wikilink = "attachment:'" + filename + "'" if raw: wikilink = "raw-" + wikilink if kwargs.get('format', self.format) == 'short': wikilink = "[%s %s]" % (wikilink,filename) return format_to_oneliner(self.env, formatter.context, wikilink)
def expand_macro(self, formatter, name, args): args = parse_args(args) # args is a tuple with a list of unnamed parameters (which is empty as # we don't support them), and a dictionary (named parameters) args = _get_args_defaults(formatter.env, args[1]) timezone = args.pop('timezone') if timezone == 'utc' : timezone = utc elif timezone == 'local': timezone = LocalTimeZone() else: raise Exception('parameter "timezone" was either "utc" nor ' '"local", it was: %s' % timezone) title = args.pop('title') days = int(args.pop('days')) width = int(args.pop('width')) height = int(args.pop('height')) statuses = args.pop('statuses').split('|') init_stat = args.pop('init_status') today = datetime.datetime.combine( datetime.date.today(), # last microsecond :-) of today datetime.time(23, 59, 59, 999999, tzinfo=timezone)) time_start = today - timedelta(days=days) ts_start = to_timestamp(time_start) sql_start = ts_start * 1000000 ts_end = to_timestamp(today) sql_end = ts_end * 1000000 # values for the template: data = {} data['title'] = title data['width'] = width data['height'] = height data['id'] = ''.join(str(random.randint(0,9)) for i in range(15)) # calculate db extra restrictions from extra parameters extra_parameters = [] extra_sql = '' not_allowed = re.compile(r'[^a-zA-Z0-9_]') for stat in statuses: if not_allowed.search(stat): raise Exception('a status contained not allowed characters') statuses_sql = ','.join("'{0}'".format(stat) for stat in statuses) if args: extra_sql_constraints = [] for key, value in args.items(): if not_allowed.search(key): raise Exception('a paramter contained not allowed characters') if value[0] == '!': extra_sql_constraints.append("{0} <> %s".format(key)) extra_parameters.append(value[1:]) else: extra_sql_constraints.append("{0} = %s".format(key)) extra_parameters.append(value) extra_sql = u' AND '.join(extra_sql_constraints) if hasattr(self.env, 'get_read_db'): db = self.env.get_read_db() else: db = self.env.get_db_cnx() cursor = db.cursor() series = { 'openedTickets': {}, 'closedTickets': {}, 'reopenedTickets': {}, 'openTickets': {} } # NOTE on casting times: in the sql statements below, we use: # CAST((time / 86400) AS int) * 86400 AS date # which homogenize all times during a day to the same day. Example: # The following two values will have the same value # 1386280739 (2013-12-05 22:58:59) -> 2013-12-05 00:00:00 # 1386270739 (2013-12-05 20:12:19) -> 2013-12-05 00:00:00 # number of created tickets for the time period, grouped by day # a day has 86400 seconds if init_stat in statuses: sql = 'SELECT COUNT(DISTINCT id), ' \ 'CAST((time / 86400000000) AS int) * 86400 AS date ' \ 'FROM ticket WHERE {0} {1} time BETWEEN %s AND %s ' \ 'GROUP BY date ORDER BY date ASC'.format( extra_sql, ' AND ' if extra_sql else '', statuses_sql) cursor.execute(sql, tuple(extra_parameters) + (sql_start, sql_end)) for count, timestamp in cursor: # flot needs the time in milliseconds, not seconds, see # https://github.com/flot/flot/blob/master/API.md#time-series-data series['openedTickets'][timestamp*1000] = float(count) # number of reopened tickets for the time period, grouped by day # a day has 86400 seconds cursor.execute("SELECT COUNT(DISTINCT tc.ticket), " "CAST((tc.time / 86400000000) AS int) * 86400 as date " "FROM ticket_change tc JOIN ticket t ON t.id = tc.ticket " "WHERE {0} {1} field = 'status' AND newvalue in ({2}) AND oldvalue NOT IN ({2}) " "AND tc.time BETWEEN %s AND %s " "GROUP BY date ORDER BY date ASC".format( extra_sql, ' AND ' if extra_sql else '', statuses_sql), tuple(extra_parameters) + (sql_start, sql_end)) for count, timestamp in cursor: # flot needs the time in milliseconds, not seconds, see # https://github.com/flot/flot/blob/master/API.md#time-series-data series['reopenedTickets'][float(timestamp*1000)] = float(count) # number of closed tickets for the time period, grouped by day (ms) cursor.execute("SELECT COUNT(DISTINCT ticket), " "CAST((tc.time / 86400000000) AS int) * 86400 AS date " "FROM ticket_change tc JOIN ticket t ON t.id = tc.ticket " "WHERE {0} {1} tc.field = 'status' AND tc.newvalue not in ({2}) AND tc.oldvalue in ({2})" "AND tc.time BETWEEN %s AND %s " \ "GROUP BY date ORDER BY date ASC".format( extra_sql, ' AND ' if extra_sql else '', statuses_sql), tuple(extra_parameters) + (sql_start, sql_end)) for count, timestamp in cursor: # flot needs the time in milliseconds, not seconds, see # https://github.com/flot/flot/blob/master/API.md#time-series-data series['closedTickets'][float(timestamp*1000)] = -float(count) # calculate number of open tickets for each day # number of open tickets up to now cursor.execute( "SELECT COUNT(*) FROM ticket " "WHERE {0} {1} status in ({2})".format( extra_sql, ' AND ' if extra_sql else '', statuses_sql), tuple(extra_parameters)) open_tickets = cursor.fetchone()[0] series['openTickets'][ts_end * 1000] = open_tickets formatter.env.log.debug('ts_end = {0}, ts_start = {1}'.format(ts_end, ts_start)) for day_ms in range(long(math.floor(ts_end / 86400.0) * 86400000), long(ts_start * 1000), -86400000): open_tickets -= series['closedTickets'].get(day_ms, 0) open_tickets -= series['openedTickets'].get(day_ms, 0) open_tickets -= series['reopenedTickets'].get(day_ms, 0) series['openTickets'][day_ms] = open_tickets # sort all series and put them in data for i in series: keys = series[i].keys() keys.sort() data[i] = json.dumps([(k, series[i][k]) for k in keys]) # generate output # NOTE: if you change the namespace below, you also need to change it # when using in get_htdocs_dirs add_javascript(formatter.req, 'stsm/js/excanvas.min.js') add_javascript(formatter.req, 'stsm/js/jquery.flot.min.js') add_javascript(formatter.req, 'stsm/js/jquery.flot.stack.min.js') add_javascript(formatter.req, 'stsm/js/simpleticketstats.js') template = Chrome(self.env).load_template( 'simpleticketstats_macro.html', method='text') formatter.env.log.debug(data) return Markup(template.generate(**data))
def expand_macro(self, formatter, name, content): args, kwargs = parse_args(content) if not args: page_name = get_resource_name(self.env, formatter.resource) mode = 'normal' elif len(args) == 1: page_name = args[0] mode = 'normal' else: page_name = args[0] mode = 'delta' db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("SELECT author, time FROM wiki WHERE name = '%s' " "ORDER BY version DESC LIMIT 1" % page_name) row = cursor.fetchone() if not row: raise TracError('Wiki page "%s" not found.' % page_name) username = row[0] time_int = row[1] # see if there's a fullname associated with username cursor.execute("SELECT value FROM session_attribute " "WHERE sid = '%s' AND name = 'name'" % username) row = cursor.fetchone() if not row: author = username else: author = row[0] if mode == 'delta': last_mod = to_datetime(time_int) now = to_datetime(None) elapsed = now - last_mod if elapsed.days == 0: if elapsed.seconds / 3600 > 1.5: count = elapsed.seconds / 3600 unit = 'hour' elif elapsed.seconds / 60 > 1.5: count = elapsed.seconds / 60 unit = 'minute' else: count = elapsed.seconds unit = 'second' elif elapsed.days / 3650 > 1.5: count = elapsed.days / 3650 unit = 'decade' elif elapsed.days / 365 > 1.5: count = elapsed.days / 365 unit = 'year' elif elapsed.days / 30 > 1.5: count = elapsed.days / 30 unit = 'month' elif elapsed.days / 7 > 1.5: count = elapsed.days / 7 unit = 'week' else: count = elapsed.days unit = 'day' text = "" + repr(count) + " " + unit if (count != 1 and count != -1): text += "s" else: text = format_datetime(time_int, '%c') return Markup(text)
def expand_macro(self, formatter, name, content, args=None): if content is not None: content = content.strip() if not args and not content: raw_actions = self.config.options('ticket-workflow') else: is_macro = args is None if is_macro: kwargs = parse_args(content)[1] file = kwargs.get('file') else: file = args.get('file') if not file and not content: raise ProcessorError("Invalid argument(s).") if file: print(file) text = RepositoryManager(self.env).read_file_by_path(file) if text is None: raise ProcessorError( tag_("The file %(file)s does not exist.", file=tag.code(file))) elif is_macro: text = '\n'.join(line.lstrip() for line in content.split(';')) else: text = content if '[ticket-workflow]' not in text: text = '[ticket-workflow]\n' + text parser = RawConfigParser() try: parser.readfp(io.StringIO(text)) except ParsingError as e: if is_macro: raise MacroError(exception_to_unicode(e)) else: raise ProcessorError(exception_to_unicode(e)) raw_actions = list(parser.items('ticket-workflow')) actions = parse_workflow_config(raw_actions) states = list( {state for action in actions.itervalues() for state in action['oldstates']} | {action['newstate'] for action in actions.itervalues()}) action_labels = [attrs['label'] for attrs in actions.values()] action_names = list(actions) edges = [] for name, action in actions.items(): new_index = states.index(action['newstate']) name_index = action_names.index(name) for old_state in action['oldstates']: old_index = states.index(old_state) edges.append((old_index, new_index, name_index)) args = args or {} width = args.get('width', 800) height = args.get('height', 600) graph = {'nodes': states, 'actions': action_labels, 'edges': edges, 'width': width, 'height': height} graph_id = '%012x' % id(graph) req = formatter.req add_script(req, 'common/js/excanvas.js', ie_if='IE') add_script(req, 'common/js/workflow_graph.js') add_script_data(req, {'graph_%s' % graph_id: graph}) return tag( tag.div('', class_='trac-workflow-graph trac-noscript', id='trac-workflow-graph-%s' % graph_id, style="display:inline-block;width:%spx;height:%spx" % (width, height)), tag.noscript( tag.div(_("Enable JavaScript to display the workflow graph."), class_='system-message')))
def expand_macro(self, formatter, name, content): args, kwargs = parse_args(content) if not args: page_name = get_resource_name(self.env, formatter.resource) mode = 'normal' elif len(args) == 1: page_name = args[0] mode = 'normal' else: page_name = args[0] mode = 'delta' db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("SELECT author, time FROM wiki WHERE name = '%s' " "ORDER BY version DESC LIMIT 1" % page_name) row = cursor.fetchone() if not row: raise TracError('Wiki page "%s" not found.' % page_name) username = row[0] time_int = row[1] # see if there's a fullname associated with username cursor.execute("SELECT value FROM session_attribute " "WHERE sid = '%s' AND name = 'name'" % username) row = cursor.fetchone() if not row: author = username else: author = row[0] if mode == 'delta': last_mod = to_datetime(time_int) now = to_datetime(None) elapsed = now - last_mod if elapsed.days == 0: if elapsed.seconds / 3600 > 1.5: count = elapsed.seconds / 3600 unit = 'hour' elif elapsed.seconds / 60 > 1.5: count = elapsed.seconds / 60 unit = 'minute' else: count = elapsed.seconds unit = 'second' elif elapsed.days / 3650 > 1.5: count = elapsed.days / 3650 unit = 'decade' elif elapsed.days / 365 > 1.5: count = elapsed.days / 365 unit = 'year' elif elapsed.days / 30 > 1.5: count = elapsed.days / 30 unit = 'month' elif elapsed.days / 7 > 1.5: count = elapsed.days / 7 unit = 'week' else: count = elapsed.days unit = 'day' text = "" + repr(count) + " " + unit if (count != 1 and count != -1): text += "s" else: text = format_datetime(time_int, '%c') return Markup(text)