Mailing List Archive

Updated patch for ticket #301
Folks,

I have updated my previous patch for ticket #301.

Mainly the changes are as follows:

* Now this patch applies to the latest revision in the Subversion trunk
(850).

* The whole behavior is now controlled by the option in trac.ini. Without
this option, Trac behaves exactly like it used to. Note, that it still
requires new schema!

* In my previous patch I did not realize that I had to update Roadmap and
Milestone pages. Now I have updated them as well.

To use this patch, you need to do the following.
[.Don't use it for production yet! I think it works, but I am still testing
it. If you find bugs or if you think that some behavior is odd, please let me
know.]

1. Apply the patch to the revision 850.

2. Modify your SQLite schema as follows:
a. Add 'component.dev_owner' and 'component.qa_owner' and populate them
from 'component.owner'
a. Remove 'component.owner'
c. Add 'ticket.dev_owner' and 'ticket.qa_owner' and populate them from
'ticket.owner' (DO NOT remove 'ticket.owner'!)
[.I have modified trac-admin to behave correctly, but in case you have an
existing installation, you would have to modify SQLite by hand, since I have
not written a migration script (due to lack of SQLite skill on my part)]

3. Create a new file 'htdocs/resolvedticket.png'. This is the icon seen in
the 'Timeline'. [.I have no idea how to do graphics - I just copied
'closedticket.png' on my system.]

4. Add the following 2 lines to your trac.ini file:

[workflow]
ticket_workflow = resolve-then-close

Without these lines, Trac behaves as if there is no change (except for
slightly modified SQLite schema). Note, that while you can always turn the
behavior on, you should be carefull when turning it off, because if there are
any bugs in the 'resolved' state (but not 'closed') when you turn it off, the
total in the Roadmap/Milestone will be wrong untill all 'resolved' bugs are
'closed'.

I have not modified any of the reports. I think the ones provided are just a
sample, and so I am sure everyone will have their own reports that they will
implement anyways.

Finally, I am curious to know if this new workflow (with intermediate state
between closed and resolved for QA verification) is usefull to anyone? Would
you like to see it implemented differently?

Thanks,

Alik



__________________________________
Do you Yahoo!?
Yahoo! Mail - 50x more storage than other providers!
http://promotions.yahoo.com/new_mail-------------- next part --------------
diff -Naur trac-old/trunk/htdocs/css/roadmap.css trac-new/trunk/htdocs/css/roadmap.css
--- trac-old/trunk/htdocs/css/roadmap.css 2004-08-18 07:36:49.000000000 +0000
+++ trac-new/trunk/htdocs/css/roadmap.css 2004-08-18 07:35:41.000000000 +0000
@@ -62,12 +62,12 @@
#stats thead th { color: #999 }
#stats td, #stats th { padding: 0 .25em }
#stats th.tickets { text-align: center }
-#stats th.open, #stats th.closed {
+#stats th.open, #stats th.closed, #stats th.resolved {
font-size: smaller;
text-align: center;
width: 6em;
}
-#stats td.open, #stats td.closed {
+#stats td.open, #stats td.closed, #stats td.resolved {
color: #888;
text-align: right;
padding-right: 1.5em;
diff -Naur trac-old/trunk/htdocs/css/timeline.css trac-new/trunk/htdocs/css/timeline.css
--- trac-old/trunk/htdocs/css/timeline.css 2004-08-18 07:36:49.000000000 +0000
+++ trac-new/trunk/htdocs/css/timeline.css 2004-08-18 07:35:41.000000000 +0000
@@ -46,6 +46,9 @@
.timeline dt.newticket, .timeline dt.newticket a {
background-image: url(../newticket.png);
}
+.timeline dt.resolvedticket, .timeline dt.resolvedticket a {
+ background-image: url(../resolvedticket.png);
+}
.timeline dt.closedticket, .timeline dt.closedticket a {
background-image: url(../closedticket.png);
}
diff -Naur trac-old/trunk/scripts/trac-admin trac-new/trunk/scripts/trac-admin
--- trac-old/trunk/scripts/trac-admin 2004-08-18 07:36:49.000000000 +0000
+++ trac-new/trunk/scripts/trac-admin 2004-08-17 23:16:10.000000000 +0000
@@ -290,10 +290,10 @@

# ## Component
_help_component = [.('component list', 'Show available components'),
- ('component add <name> <owner>', 'Add a new component'),
+ ('component add <name> [<dev_owner> <qa_owner> | <owner>]', 'Add a new component'),
('component rename <name> <newname>', 'Rename a component'),
('component remove <name>', 'Remove/uninstall component'),
- ('component chown <name> <owner>', 'Change component ownership')]
+ ('component chown <name> [<dev_owner> <qa_owner> | <owner>]', 'Change component ownership')]

def complete_component (self, text, line, begidx, endidx):
if begidx in [16,17]:
@@ -309,10 +309,15 @@
try:
if arg[0] == 'list':
self._do_component_list()
+ elif arg[0] == 'add' and len(arg)==4:
+ name = arg[1]
+ dev_owner = arg[2]
+ qa_owner = arg[3]
+ self._do_component_add(name, dev_owner, qa_owner)
elif arg[0] == 'add' and len(arg)==3:
name = arg[1]
owner = arg[2]
- self._do_component_add(name, owner)
+ self._do_component_add(name, owner, owner)
elif arg[0] == 'rename' and len(arg)==3:
name = arg[1]
newname = arg[2]
@@ -323,7 +328,12 @@
elif arg[0] == 'chown' and len(arg)==3:
name = arg[1]
owner = arg[2]
- self._do_component_set_owner(name, owner)
+ self._do_component_set_owner(name, owner, owner)
+ elif arg[0] == 'chown' and len(arg)==4:
+ name = arg[1]
+ dev_owner = arg[2]
+ qa_owner = arg[3]
+ self._do_component_set_owner(name, dev_owner, qa_owner)
else:
self.do_help ('component')
except Exception, e:
@@ -333,9 +343,9 @@
data = self.db_execsql('SELECT name, owner FROM component')
self.print_listing(['Name', 'Owner'], data)

- def _do_component_add(self, name, owner):
- data = self.db_execsql("INSERT INTO component VALUES('%s', '%s')"
- % (name, owner))
+ def _do_component_add(self, name, dev_owner, qa_owner):
+ data = self.db_execsql("INSERT INTO component VALUES('%s', '%s', '%s')"
+ % (name, dev_owner, qa_owner))

def _do_component_rename(self, name, newname):
cnx = self.db_open()
@@ -350,9 +360,9 @@
data = self.db_execsql("DELETE FROM component WHERE name='%s'"
% (name))

- def _do_component_set_owner(self, name, owner):
- data = self.db_execsql("UPDATE component SET owner='%s' WHERE name='%s'"
- % (owner,name))
+ def _do_component_set_owner(self, name, dev_owner, qa_owner):
+ data = self.db_execsql("UPDATE component SET dev_owner='%s', qa_owner='%s' WHERE name='%s'"
+ % (dev_owner,qa_owner,name))


## Permission
diff -Naur trac-old/trunk/templates/milestone.cs trac-new/trunk/templates/milestone.cs
--- trac-old/trunk/templates/milestone.cs 2004-08-18 07:36:50.000000000 +0000
+++ trac-new/trunk/templates/milestone.cs 2004-08-18 07:36:07.000000000 +0000
@@ -88,19 +88,25 @@
summary="Shows the milestone completion status grouped by component">
<thead><tr>
<th>&nbsp;</th>
- <th class="tickets" scope="col" colspan="2">Tickets</th>
+ <th class="tickets" scope="col" colspan="<?cs if:ticket_workflow == 'resolve-then-close' ?>3<?cs else ?>2<?cs /if ?>">Tickets</th>
<th>&nbsp;</th>
</tr><tr>
<th class="name" scope="col">Component</th>
<th class="open" scope="col">Active</th>
- <th class="closed" scope="col">Resolved</th>
- <th class="progress" scope="col">Percent Resolved</th>
+ <?cs if:ticket_workflow == 'resolve-then-close' ?>
+ <th class="resolved" scope="col">Resolved</th>
+ <?cs /if ?>
+ <th class="closed" scope="col">Closed</th>
+ <th class="progress" scope="col">Percent Closed</th>
</tr></thead>
<tbody>
<?cs each:component = milestone.stats.components ?>
<tr class="<?cs if:name(component) % 2 ?>odd<?cs else ?>even<?cs /if ?>">
<th class="name" scope="row"><?cs var:component.name ?></th>
<td class="open tickets"><?cs var:component.active_tickets ?></td>
+ <?cs if:ticket_workflow == 'resolve-then-close' ?>
+ <td class="resolved tickets"><?cs var:component.resolved_tickets ?></td>
+ <?cs /if ?>
<td class="closed tickets"><?cs var:component.closed_tickets ?></td>
<td class="progress">
<?cs if:#component.total_tickets ?>
@@ -117,6 +123,9 @@
<tfoot><tr>
<th class="name" scope="row">Total</th>
<td class="open tickets"><?cs var:stats.active_tickets ?></td>
+ <?cs if:ticket_workflow == 'resolve-then-close' ?>
+ <td class="resolved tickets"><?cs var:stats.resolved_tickets ?></td>
+ <?cs /if ?>
<td class="closed tickets"><?cs var:stats.closed_tickets ?></td>
<td class="progress">
<?cs if:#stats.total_tickets ?>
diff -Naur trac-old/trunk/templates/roadmap.cs trac-new/trunk/templates/roadmap.cs
--- trac-old/trunk/templates/roadmap.cs 2004-08-18 07:36:50.000000000 +0000
+++ trac-new/trunk/templates/roadmap.cs 2004-08-18 07:36:07.000000000 +0000
@@ -29,7 +29,11 @@
<dl>
<dt>Active tickets:</dt>
<dd><?cs var:stats.active_tickets ?></dd>
- <dt>Resolved tickets:</dt>
+ <?cs if:ticket_workflow == 'resolve-then-close' ?>
+ <dt>Resolved tickets:</dt>
+ <dd><?cs var:stats.resolved_tickets ?></dd>
+ <?cs /if ?>
+ <dt>Closed tickets:</dt>
<dd><?cs var:stats.closed_tickets ?></dd>
</dl>
<?cs /if ?>
diff -Naur trac-old/trunk/templates/ticket.cs trac-new/trunk/templates/ticket.cs
--- trac-old/trunk/templates/ticket.cs 2004-08-18 07:36:50.000000000 +0000
+++ trac-new/trunk/templates/ticket.cs 2004-08-18 07:36:07.000000000 +0000
@@ -43,7 +43,7 @@
<?cs call:ticketprop("Priority", ticket.priority) ?>
<?cs call:ticketprop("Reporter", ticket.reporter) ?>
<?cs call:ticketprop("Severity", ticket.severity) ?>
- <?cs if ticket.status == "assigned"?>
+ <?cs if ticket.status == "assigned" || ticket.status=="resolved-assigned" ?>
<?cs call:ticketprop("Assigned to", ticket.owner + " (accepted)") ?>
<?cs else ?>
<?cs call:ticketprop("Assigned to", ticket.owner) ?>
@@ -261,11 +261,11 @@
<?cs /def ?>
<?cs call:action_radio('leave') ?>
&nbsp;<label for="leave">leave as <?cs var:ticket.status ?></label><br />
- <?cs if $ticket.status == "new" ?>
+ <?cs if $ticket.status == "new" || $ticket.status == "resolved-new" || $ticket.status == "reopened" ?>
<?cs call:action_radio('accept') ?>
&nbsp;<label for="accept">accept ticket</label><br />
<?cs /if ?>
- <?cs if $ticket.status == "closed" ?>
+ <?cs if $ticket.status == "closed" || $ticket.status == "resolved-new" || $ticket.status == "resolved-assigned" ?>
<?cs call:action_radio('reopen') ?>
&nbsp;<label for="reopen">reopen ticket</label><br />
<?cs /if ?>
@@ -279,6 +279,12 @@
<option>duplicate</option>
<option>worksforme</option>
</select><br />
+ <?cs /if ?>
+ <?cs if $ticket.status == "resolved-new" || $ticket.status == "resolved-assigned" ?>
+ <?cs call:action_radio('close') ?>
+ &nbsp;<label for="reopen">close ticket</label><br />
+ <?cs /if ?>
+ <?cs if $ticket.status == "new" || $ticket.status == "assigned" || $ticket.status == "reopened" || $ticket.status == "resolved-new" || ticket.status == "resolved-assigned" ?>
<?cs call:action_radio('reassign') ?>
&nbsp;<label for="reassign">reassign ticket to:</label>
&nbsp;<input type="text" id="reassign_owner" name="reassign_owner"
diff -Naur trac-old/trunk/templates/timeline.cs trac-new/trunk/templates/timeline.cs
--- trac-old/trunk/templates/timeline.cs 2004-08-18 07:36:50.000000000 +0000
+++ trac-new/trunk/templates/timeline.cs 2004-08-18 07:36:07.000000000 +0000
@@ -74,7 +74,7 @@
<?cs elif:item.type == #3 ?><!-- Closed ticket -->
<?cs if:item.message ?><?cs set:imessage=' - '+$item.message ?><?cs /if ?>
<?cs call:tlitem(item.href, 'closedticket',
- 'Ticket <em>#'+$item.idata+'</em> resolved by '+$item.author,
+ 'Ticket <em>#'+$item.idata+'</em> closed by '+$item.author,
$item.tdata+$imessage) ?>
<?cs elif:item.type == #4 ?><!-- Reopened ticket -->
<?cs call:tlitem(item.href, 'newticket',
@@ -85,6 +85,11 @@
<?cs elif:item.type == #6 ?><!-- milestone -->
<?cs call:tlitem(item.href, 'milestone',
'<em>Milestone '+$item.message+'</em> reached', '') ?>
+ <?cs elif:item.type == #7 ?><!-- Resolved ticket -->
+ <?cs if:item.message ?><?cs set:imessage=' - '+$item.message ?><?cs /if ?>
+ <?cs call:tlitem(item.href, 'resolvedticket',
+ 'Ticket <em>#'+$item.idata+'</em> resolved by '+$item.author,
+ $item.tdata+$imessage) ?>
<?cs /if ?>
<?cs /each ?>
<?cs if:len(timeline.items) ?></dl><?cs /if ?>
diff -Naur trac-old/trunk/templates/timeline_rss.cs trac-new/trunk/templates/timeline_rss.cs
--- trac-old/trunk/templates/timeline_rss.cs 2004-08-18 07:36:50.000000000 +0000
+++ trac-new/trunk/templates/timeline_rss.cs 2004-08-18 07:36:07.000000000 +0000
@@ -43,7 +43,7 @@
$item.href, $item.msg_escwiki)
?><?cs elif:item.type == #3
?><!-- Closed ticket --> <?cs call:rss_item('Ticket',
- 'Ticket #'+$item.idata+' resolved: '+$item.shortmsg,
+ 'Ticket #'+$item.idata+' closed: '+$item.shortmsg,
$item.href, $item.msg_escwiki)
?><?cs elif:item.type == #4
?><!-- Reopened ticket --><?cs call:rss_item('Ticket',
@@ -58,8 +58,12 @@
<?cs call:rss_item('Milestone',
'Milestone ' + $item.message.rss + ' reached.',
'',
- 'Milestone ' + $item.tdata + ' reached.') ?>
+ 'Milestone ' + $item.tdata + ' reached.')
+ ?><?cs elif:item.type == #7
+ ?><!-- Resolved ticket --> <?cs call:rss_item('Ticket',
+ 'Ticket #'+$item.idata+' resolved: '+$item.shortmsg,
+ $item.href, $item.msg_escwiki) ?>
<?cs /if ?>
<?cs /each ?>
</channel>
-</rss>
\ No newline at end of file
+</rss>
diff -Naur trac-old/trunk/trac/Milestone.py trac-new/trunk/trac/Milestone.py
--- trac-old/trunk/trac/Milestone.py 2004-08-18 07:36:49.000000000 +0000
+++ trac-new/trunk/trac/Milestone.py 2004-08-18 07:35:53.000000000 +0000
@@ -46,9 +46,11 @@

def calc_ticket_stats(tickets):
total_cnt = len(tickets)
- active = [ticket for ticket in tickets if ticket['status'] != 'closed']
+ active = [ticket for ticket in tickets if ticket['status'] != 'closed' and ticket['status'] != 'resolved-new' and ticket['status'] != 'resolved-assigned']
active_cnt = len(active)
- closed_cnt = total_cnt - active_cnt
+ resolved = [ticket for ticket in tickets if ticket['status'] == 'resolved-new' or ticket['status'] == 'resolved-assigned']
+ resolved_cnt = len(resolved)
+ closed_cnt = total_cnt - active_cnt - resolved_cnt

percent_complete = 0
if total_cnt > 0:
@@ -57,6 +59,7 @@
return {
'total_tickets': total_cnt,
'active_tickets': active_cnt,
+ 'resolved_tickets': resolved_cnt,
'closed_tickets': closed_cnt,
'percent_complete': percent_complete
}
@@ -150,13 +153,13 @@

def get_components(self):
cursor = self.db.cursor ()
- cursor.execute("SELECT name, owner FROM component ORDER BY name")
+ cursor.execute("SELECT name, dev_owner, qa_owner FROM component ORDER BY name")
components = []
while 1:
row = cursor.fetchone()
if not row:
break
- component = ( row['name'], row['owner'] )
+ component = ( row['name'], row['dev_owner'], row['qa_owner'] )
components.append(component)
return components

@@ -226,6 +229,8 @@
self.req.hdf.setValue('milestone.href.edit',
self.env.href.milestone(id, 'edit'))

+ self.req.hdf.setValue('ticket_workflow', self.env.get_config('workflow', 'ticket_workflow', ''))
+
milestone = self.get_milestone(id)
self.req.hdf.setValue('title', 'Milestone %s' % milestone['name'])
add_dict_to_hdf(milestone, self.req.hdf, 'milestone')
@@ -236,7 +241,7 @@

components = self.get_components()
comp_no = 0
- for component, owner in components:
+ for component, dev_owner, qa_owner in components:
prefix = 'milestone.stats.components.%s' % comp_no
self.req.hdf.setValue('%s.name' % prefix, component)
comp_tickets = [t for t in tickets if t['component'] == component]
diff -Naur trac-old/trunk/trac/Roadmap.py trac-new/trunk/trac/Roadmap.py
--- trac-old/trunk/trac/Roadmap.py 2004-08-18 07:36:49.000000000 +0000
+++ trac-new/trunk/trac/Roadmap.py 2004-08-18 07:35:53.000000000 +0000
@@ -35,6 +35,8 @@
self.perm.assert_permission(perm.ROADMAP_VIEW)
self.req.hdf.setValue('title', 'Roadmap')

+ self.req.hdf.setValue('ticket_workflow', self.env.get_config('workflow', 'ticket_workflow', ''))
+
if self.perm.has_permission(perm.MILESTONE_CREATE):
self.req.hdf.setValue('roadmap.href.newmilestone',
self.env.href.milestone(None, 'new'))
diff -Naur trac-old/trunk/trac/Ticket.py trac-new/trunk/trac/Ticket.py
--- trac-old/trunk/trac/Ticket.py 2004-08-18 07:36:50.000000000 +0000
+++ trac-new/trunk/trac/Ticket.py 2004-08-18 07:35:53.000000000 +0000
@@ -36,7 +36,7 @@
class Ticket(UserDict):
std_fields = [.'time', 'component', 'severity', 'priority', 'milestone',
'reporter', 'owner', 'cc', 'url', 'version', 'status', 'resolution',
- 'keywords', 'summary', 'description', 'reporter']
+ 'keywords', 'summary', 'description', 'reporter', 'dev_owner', 'qa_owner']

def __init__(self, *args):
UserDict.__init__(self)
@@ -133,13 +133,16 @@
# is updated accordingly. (#623).
if self['status'] == 'new' and self._old.has_key('component') and \
not self._old.has_key('owner'):
- cursor.execute('SELECT owner FROM component '
+ cursor.execute('SELECT dev_owner, qa_owner FROM component '
'WHERE name=%s', self._old['component'])
- old_owner = cursor.fetchone()[0]
+ old_owner = cursor.fetchdone()[0]
if self['owner'] == old_owner:
- cursor.execute('SELECT owner FROM component '
+ cursor.execute('SELECT dev_owner, qa_owner FROM component '
'WHERE name=%s', self['component'])
- self['owner'] = cursor.fetchone()[0]
+ row = cursor.fetchone()
+ self['dev_owner'] = row[0]
+ self['qa_owner'] = row[1]
+ self['owner'] = self['dev_owner']


for name in self._old.keys():
@@ -237,11 +240,14 @@

# The owner field defaults to the component owner
cursor = self.db.cursor()
- if ticket.get('component') and ticket.get('owner', '') == '':
- cursor.execute('SELECT owner FROM component '
+ if ticket.get('component'):
+ cursor.execute('SELECT dev_owner, qa_owner FROM component '
'WHERE name=%s', ticket['component'])
- owner = cursor.fetchone()[0]
- ticket['owner'] = owner
+ row = cursor.fetchone()
+ ticket['dev_owner'] = row[0]
+ ticket['qa_owner'] = row[1]
+ if ticket.get('owner', '') == '':
+ ticket['owner'] = ticket['dev_owner']

tktid = ticket.insert(self.db)

@@ -309,14 +315,32 @@
# TODO: this should not be hard-coded like this
action = self.args.get('action', None)
if action == 'accept':
- ticket['status'] = 'assigned'
- ticket['owner'] = self.req.authname
- if action == 'resolve':
+ if ticket['status'] == 'resolved-new':
+ ticket['status'] = 'resolved-assigned'
+ ticket['owner'] = self.req.authname
+ ticket['qa_owner'] = self.req.authname
+ else:
+ ticket['status'] = 'assigned'
+ ticket['owner'] = self.req.authname
+ ticket['dev_owner'] = self.req.authname
+ elif action == 'resolve':
+ if self.env.get_config('workflow', 'ticket_workflow', '') == 'resolve-then-close':
+ ticket['status'] = 'resolved-new'
+ ticket['resolution'] = self.args.get('resolve_resolution')
+ else:
+ ticket['status'] = 'closed'
+ ticket['resolution'] = self.args.get('resolve_resolution')
+ elif action == 'close':
ticket['status'] = 'closed'
- ticket['resolution'] = self.args.get('resolve_resolution')
elif action == 'reassign':
- ticket['owner'] = self.args.get('reassign_owner')
- ticket['status'] = 'new'
+ if ticket['status'] == 'resolved-new' or ticket['status'] == 'resolved-assigned':
+ ticket['owner'] = self.args.get('reassign_owner')
+ ticket['qa_owner'] = self.args.get('reassign_owner')
+ ticket['status'] = 'resolved-new'
+ else:
+ ticket['owner'] = self.args.get('reassign_owner')
+ ticket['dev_owner'] = self.args.get('reassign_owner')
+ ticket['status'] = 'new'
elif action == 'reopen':
ticket['status'] = 'reopened'
ticket['resolution'] = ''
@@ -395,7 +419,7 @@
id = int(self.args.get('id'))

if not preview \
- and action in ['leave', 'accept', 'reopen', 'resolve', 'reassign']:
+ and action in ['leave', 'accept', 'reopen', 'resolve', 'reassign', 'close']:
self.save_changes (id)

ticket = Ticket(self.db, id)
diff -Naur trac-old/trunk/trac/Timeline.py trac-new/trunk/trac/Timeline.py
--- trac-old/trunk/trac/Timeline.py 2004-08-18 07:36:49.000000000 +0000
+++ trac-new/trunk/trac/Timeline.py 2004-08-18 07:35:53.000000000 +0000
@@ -51,6 +51,7 @@
REOPENED_TICKET = 4
WIKI = 5
MILESTONE = 6
+ RESOLVED_TICKET = 3

q = []
if changeset:
@@ -69,16 +70,25 @@
"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,"
+ " t2.newvalue AS tdata, 7 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"
- " WHERE t1.field = 'status' AND t1.newvalue = 'closed'"
+ " WHERE t1.field = 'status' AND t1.newvalue = 'resolved-new'"
" AND t2.field = 'resolution' AND t3.field = 'comment'"
" AND t1.time >= %s AND t1.time <= %s" % (start,stop))
+ q.append("SELECT t1.time AS time, t1.ticket AS idata,"
+ " 'closed' AS tdata, 3 AS type,"
+ " t2.newvalue AS message, t1.author AS author"
+ " FROM ticket_change t1"
+ " LEFT OUTER JOIN ticket_change t2 ON t1.time = t2.time"
+ " AND t1.ticket = t2.ticket"
+ " WHERE t1.field = 'status' AND t1.newvalue = 'closed'"
+ " AND t2.field = 'comment'"
+ " 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 "
diff -Naur trac-old/trunk/trac/db_default.py trac-new/trunk/trac/db_default.py
--- trac-old/trunk/trac/db_default.py 2004-08-18 07:36:49.000000000 +0000
+++ trac-new/trunk/trac/db_default.py 2004-08-18 07:35:53.000000000 +0000
@@ -94,7 +94,9 @@
resolution text,
summary text, -- one-line summary
description text, -- problem description (long)
- keywords text
+ keywords text,
+ dev_owner text,
+ qa_owner text
);
CREATE TABLE ticket_change (
ticket integer,
@@ -125,7 +127,8 @@
);
CREATE TABLE component (
name text PRIMARY KEY,
- owner text
+ dev_owner text,
+ qa_owner text
);
CREATE TABLE milestone (
id integer PRIMARY KEY,
@@ -347,9 +350,9 @@

# (table, (column1, column2), ((row1col1, row1col2), (row2col1, row2col2)))
data = (('component',
- ('name', 'owner'),
- (('component1', 'somebody'),
- ('component2', 'somebody'))),
+ ('name', 'dev_owner', 'qa_owner'),
+ (('component1', 'somebody', 'somebody'),
+ ('component2', 'somebody', 'somebody'))),
('milestone',
('name', 'time'),
(('', 0),