# -*- coding: iso8859-1 -*- # # Copyright (C) 2003, 2004 Edgewall Software # Copyright (C) 2003, 2004 Jonas Borgström # # Trac is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2 of the # License, or (at your option) any later version. # # Trac is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # # Author: Jonas Borgström import time import string import urllib import perm import util import sys from Module import Module from Wiki import wiki_to_oneliner,wiki_to_html class Timeline (Module): template_name = 'timeline.cs' template_rss_name = 'timeline_rss.cs' def get_info (self, start, stop, maxrows, tickets, changeset, wiki, milestone): cursor = self.db.cursor () tickets = tickets and self.perm.has_permission(perm.TICKET_VIEW) changeset = changeset and self.perm.has_permission(perm.CHANGESET_VIEW) wiki = wiki and self.perm.has_permission(perm.WIKI_VIEW) milestone = milestone and self.perm.has_permission(perm.MILESTONE_VIEW) if tickets == changeset == wiki == milestone == 0: return [] CHANGESET = 1 NEW_TICKET = 2 CLOSED_TICKET = 3 REOPENED_TICKET = 4 WIKI = 5 MILESTONE = 6 q = [] if changeset: q.append("SELECT time, rev AS idata, '' AS tdata, 1 AS type, " " message, author " "FROM revision WHERE time>=%s AND time<=%s" % (start, stop)) if tickets: q.append("SELECT time, id AS idata, '' AS tdata, 2 AS type, " "summary AS message, reporter AS author " "FROM ticket WHERE time>=%s AND time<=%s" % (start, stop)) q.append("SELECT time, ticket AS idata, '' AS tdata, 4 AS type, " "'' AS message, author " "FROM ticket_change WHERE field='status' " "AND newvalue='reopened' AND time>=%s AND time<=%s" % (start, stop)) q.append("SELECT t1.time AS time, t1.ticket AS idata," " t2.newvalue AS tdata, 3 AS type," " t3.newvalue AS message, t1.author AS author" " FROM ticket_change t1" " INNER JOIN ticket_change t2 ON t1.ticket = t2.ticket" " AND t1.time = t2.time" " LEFT OUTER JOIN ticket_change t3 ON t1.time = t3.time" " AND t1.ticket = t3.ticket AND t3.field = 'comment'" " WHERE t1.field = 'status' AND t1.newvalue = 'closed'" " AND t2.field = 'resolution'" " AND t1.time >= %s AND t1.time <= %s" % (start,stop)) if wiki: q.append("SELECT time, -1 AS idata, name AS tdata, 5 AS type, " "comment AS message, author " "FROM wiki WHERE time>=%s AND time<=%s" % (start, stop)) if milestone: q.append("SELECT time, -1 AS idata, '' AS tdata, 6 AS type, " "name AS message, '' AS author " "FROM milestone WHERE time>=%s AND time<=%s" % (start, stop)) q_str = string.join(q, ' UNION ALL ') q_str += ' ORDER BY time DESC' if maxrows: q_str += ' LIMIT %d' % maxrows cursor.execute(q_str) # Make the data more HDF-friendly info = [] while 1: row = cursor.fetchone() if not row: break t = time.localtime(int(row['time'])) gmt = time.gmtime(int(row['time'])) item = {'time': time.strftime('%H:%M', t), 'date': time.strftime('%x', t), 'datetime': time.strftime('%a, %d %b %Y %H:%M:%S GMT', gmt), 'idata': int(row['idata']), 'tdata': row['tdata'], 'type': int(row['type']), 'message': row['message'] or '', 'author': util.escape(row['author'] or 'anonymous') } if item['type'] == CHANGESET: item['href'] = self.env.href.changeset(item['idata']) msg = item['message'] item['shortmsg'] = util.escape(util.shorten_line(msg)) item['msg_nowiki'] = util.escape(msg) item['msg_escwiki'] = util.escape(wiki_to_html(msg, self.req.hdf, self.env, self.db, absurls=1)) item['message'] = wiki_to_oneliner(msg, self.req.hdf, self.env, self.db,absurls=1) try: max_node = int(self.env.get_config('timeline', 'changeset_show_files', 0)) except ValueError, e: self.env.log.warning("Invalid 'changeset_show_files' value, " "please edit trac.ini : %s" % e) max_node = 0 if max_node != 0: cursor_node = self.db.cursor () cursor_node.execute("SELECT name, change " "FROM node_change WHERE rev=%d" % item['idata']) node_list = '' node_data = '' node_count = 0; while 1: row_node = cursor_node.fetchone() if not row_node: break if node_count != 0: node_list += ', ' if (max_node != -1) and (node_count >= max_node): node_list += '...' break if row_node['change'] == 'A': node_data = '' + row_node['name'] + "" elif row_node['change'] == 'M': node_data = '' + row_node['name'] + "" elif row_node['change'] == 'D': node_data = '' + row_node['name'] + "" node_list += node_data node_count += 1 item['node_list'] = node_list + ': ' elif item['type'] == WIKI: item['href'] = self.env.href.wiki(row['tdata']) item['message'] = wiki_to_oneliner(util.shorten_line(item['message']), self.req.hdf, self.env, self.db, absurls=1) elif item['type'] == MILESTONE: item['href'] = self.env.href.milestone(item['message']) item['message'] = util.escape(item['message']) else: # TICKET item['href'] = self.env.href.ticket(item['idata']) msg = item['message'] item['shortmsg'] = util.escape(util.shorten_line(msg)) item['message'] = wiki_to_oneliner( util.shorten_line(item['message']), self.req.hdf, self.env, self.db, absurls=1) item['msg_escwiki'] = util.escape(wiki_to_html(msg, self.req.hdf, self.env, self.db, absurls=1)) # Kludges for RSS item['author.rss'] = item['author'] if item['author.rss'].find('@') == -1: item['author.rss'] = '' item['message.rss'] = util.escape(item['message'] or '') info.append(item) return info def render (self): self.perm.assert_permission(perm.TIMELINE_VIEW) self.add_link('alternate', '?daysback=90&max=50&format=rss', 'RSS Feed', 'application/rss+xml', 'rss') _from = self.args.get('from', '') _daysback = self.args.get('daysback', '') # Parse the from date and adjust the timestamp to the last second of the day t = time.localtime() if _from: try: t = time.strptime(_from, '%x') except: pass _from = time.mktime((t[0], t[1], t[2], 23, 59, 59, t[6], t[7], t[8])) try: daysback = int(_daysback) assert daysback >= 0 except: daysback = 30 self.req.hdf.setValue('timeline.from', time.strftime('%x', time.localtime(_from))) self.req.hdf.setValue('timeline.daysback', str(daysback)) stop = _from start = stop - daysback * 86400 maxrows = int(self.args.get('max', 0)) wiki = self.args.has_key('wiki') ticket = self.args.has_key('ticket') changeset = self.args.has_key('changeset') milestone = self.args.has_key('milestone') if not (wiki or ticket or changeset or milestone): wiki = ticket = changeset = milestone = 1 if wiki: self.req.hdf.setValue('timeline.wiki', 'checked') if ticket: self.req.hdf.setValue('timeline.ticket', 'checked') if changeset: self.req.hdf.setValue('timeline.changeset', 'checked') if milestone: self.req.hdf.setValue('timeline.milestone', 'checked') info = self.get_info (start, stop, maxrows, ticket, changeset, wiki, milestone) util.add_dictlist_to_hdf(info, self.req.hdf, 'timeline.items') self.req.hdf.setValue('title', 'Timeline') def display_rss(self): self.req.display(self.template_rss_name, 'text/xml')