NewWorkflow: patch-newworkflow-0_8_2-r1752.diff
| File patch-newworkflow-0_8_2-r1752.diff, 100.1 KB (added by pkou at ua.fm, 7 years ago) |
|---|
-
htdocs/css/timeline.css
40 40 /* Apply icon background-image twice to avoid hover-flicker in IE/Win */ 41 41 dt.changeset, dt.changeset a { background-image: url(../changeset.png) !important } 42 42 dt.newticket, dt.newticket a { background-image: url(../newticket.png) !important } 43 dt.resolvedticket, dt.resolvedticket a { background-image: url(../resolvedticket.png) !important } 44 dt.reopenedticket, dt.reopenedticket a { background-image: url(../reopenedticket.png) !important } 43 45 dt.closedticket, dt.closedticket a { background-image: url(../closedticket.png) !important } 44 46 dt.wiki, dt.wiki a { background-image: url(../wiki.png) !important } 45 47 dt.milestone, dt.milestone a { background-image: url(../milestone.png) !important } -
wiki-default/TracIni
27 27 See also: TracLogging 28 28 29 29 == [ticket] == 30 || workflow || Ticket workflow class. If not specified, it is ''trac.workflows.SimpleWorkflow'' || 30 31 || default_version || Default version for newly created tickets || 31 32 || default_severity || Default severity for newly created tickets || 32 33 || default_priority || Default priority for newly created tickets || -
wiki-default/TracAdmin
28 28 permission add <user> <action> [action] [...] -- Add a new permission rule 29 29 permission remove <user> <action> [action] [...] -- Remove permission rule 30 30 component list -- Show available components 31 component add <name> <owner> -- Add a new component31 component add <name> <owner> [<qaowner>] -- Add a new component 32 32 component rename <name> <newname> -- Rename a component 33 33 component remove <name> -- Remove/uninstall component 34 component chown <name> <owner> -- Change component ownership34 component chown <name> <owner> [<qaowner>] -- Change component ownership 35 35 priority list -- Show possible ticket priorities 36 36 priority add <value> -- Add a priority value option 37 37 priority change <value> <newvalue> -- Change a priority value … … 46 46 version time <name> <time> -- Set version date (Format: "Jun 3, 2003") 47 47 version remove <name> -- Remove version 48 48 milestone list -- Show milestones 49 milestone add <name> [ time]-- Add milestone49 milestone add <name> [<owner> [time]] -- Add milestone 50 50 milestone rename <name> <newname> -- Rename milestone 51 51 milestone time <name> <time> -- Set milestone date (Format: "Jun 3, 2003") 52 52 milestone remove <name> -- Remove milestone 53 milestone chown <name> <owner> -- Change milestone ownership 53 54 }}} 54 55 55 56 == Interactive Mode == -
scripts/trac-admin
292 292 293 293 # ## Component 294 294 _help_component = [('component list', 'Show available components'), 295 ('component add <name> <owner> ', 'Add a new component'),295 ('component add <name> <owner> [<qaowner>]', 'Add a new component'), 296 296 ('component rename <name> <newname>', 'Rename a component'), 297 297 ('component remove <name>', 'Remove/uninstall component'), 298 ('component chown <name> <owner> ', 'Change component ownership')]298 ('component chown <name> <owner> [<qaowner>]', 'Change component ownership')] 299 299 300 300 def complete_component (self, text, line, begidx, endidx): 301 301 if begidx in [16,17]: … … 311 311 try: 312 312 if arg[0] == 'list': 313 313 self._do_component_list() 314 elif arg[0] == 'add' and len(arg) ==3:314 elif arg[0] == 'add' and len(arg) in [3,4]: 315 315 name = arg[1] 316 316 owner = arg[2] 317 self._do_component_add(name, owner) 317 if len(arg) == 4: 318 qaowner = arg[3] 319 else: 320 qaowner = owner 321 self._do_component_add(name, owner, qaowner) 318 322 elif arg[0] == 'rename' and len(arg)==3: 319 323 name = arg[1] 320 324 newname = arg[2] … … 322 326 elif arg[0] == 'remove' and len(arg)==2: 323 327 name = arg[1] 324 328 self._do_component_remove(name) 325 elif arg[0] == 'chown' and len(arg) ==3:329 elif arg[0] == 'chown' and len(arg) in [3,4]: 326 330 name = arg[1] 327 331 owner = arg[2] 328 self._do_component_set_owner(name, owner) 332 if len(arg) == 4: 333 qaowner = arg[3] 334 else: 335 qaowner = owner 336 self._do_component_set_owner(name, owner, qaowner) 329 337 else: 330 338 self.do_help ('component') 331 339 except Exception, e: 332 340 print 'Component %s failed:' % arg[0], e 333 341 334 342 def _do_component_list(self): 335 data = self.db_execsql('SELECT name, owner FROM component')336 self.print_listing(['Name', 'Owner' ], data)343 data = self.db_execsql('SELECT name, owner, qaowner FROM component') 344 self.print_listing(['Name', 'Owner', 'QA Owner'], data) 337 345 338 def _do_component_add(self, name, owner ):339 data = self.db_execsql("INSERT INTO component VALUES('%s', '%s' )"340 % (name, owner))346 def _do_component_add(self, name, owner, qaowner): 347 data = self.db_execsql("INSERT INTO component VALUES('%s', '%s', '%s')" 348 % (name,owner,qaowner)) 341 349 342 350 def _do_component_rename(self, name, newname): 343 351 cnx = self.db_open() … … 362 370 data = self.db_execsql("DELETE FROM component WHERE name='%s'" 363 371 % (name)) 364 372 365 def _do_component_set_owner(self, name, owner ):373 def _do_component_set_owner(self, name, owner, qaowner): 366 374 cnx = self.db_open() 367 375 cursor = cnx.cursor () 368 376 cursor.execute('SELECT name FROM component WHERE name=%s', name) 369 377 data = cursor.fetchone() 370 378 if not data: 371 379 raise Exception("No such component '%s'" % name) 372 data = self.db_execsql("UPDATE component SET owner='%s' WHERE name='%s'"373 % (owner, name))380 data = self.db_execsql("UPDATE component SET owner='%s', qaowner='%s' WHERE name='%s'" 381 % (owner,qaowner,name)) 374 382 375 383 376 384 ## Permission … … 797 805 798 806 ## Milestone 799 807 _help_milestone = [('milestone list', 'Show milestones'), 800 ('milestone add <name> [ time]', 'Add milestone'),808 ('milestone add <name> [<owner> [time]]', 'Add milestone'), 801 809 ('milestone rename <name> <newname>', 802 810 'Rename milestone'), 811 ('milestone chown <name> <newowner>', 'Change milestone owner'), 803 812 ('milestone time <name> <time>', 'Set milestone date (Format: "Jun 3, 2003")'), 804 813 ('milestone remove <name>', 'Remove milestone')] 805 814 … … 807 816 808 817 if begidx in [15,17]: 809 818 comp = self.get_milestone_list () 819 elif begidx > 15 and line.startswith('milestone chown '): 820 comp = self.get_user_list() 810 821 elif begidx < 15: 811 comp = ['list','add','rename',' time','remove']822 comp = ['list','add','rename','chown','time','remove'] 812 823 return self.word_complete(text, comp) 813 824 814 825 def do_milestone(self, line): 815 self._do_mile_ver('milestone', line) 826 type = 'milestone' 827 arg = self.arg_tokenize(line) 828 try: 829 if arg[0] == 'list': 830 self._do_milestone_list() 831 elif arg[0] == 'add' and len(arg) in [2,3,4]: 832 name = arg[1] 833 self._do_mile_ver_add(type, name) 834 if len(arg) >= 3: 835 owner = arg[2] 836 self._do_mile_ver_chown(type, name, owner) 837 if len(arg) >= 4: 838 time = arg[3] 839 self._do_mile_ver_time(type, name, time) 840 elif arg[0] == 'rename' and len(arg)==3: 841 name = arg[1] 842 newname = arg[2] 843 self._do_mile_ver_rename(type, name, newname) 844 elif arg[0] == 'chown' and len(arg)==3: 845 name = arg[1] 846 owner = arg[2] 847 self._do_mile_ver_chown(type, name, owner) 848 elif arg[0] == 'time' and len(arg)==3: 849 name = arg[1] 850 time = arg[2] 851 self._do_mile_ver_time(type, name, time) 852 elif arg[0] == 'remove' and len(arg)==2: 853 name = arg[1] 854 self._do_mile_ver_remove(type, name) 855 else: 856 self.do_help (type) 857 except Exception, e: 858 print 'Command %s failed:' % arg[0], e 816 859 860 def _do_milestone_list(self): 861 data = self.db_execsql("SELECT name,owner,time FROM milestone ORDER BY time,name") 862 data = map(lambda x: (x[0], x[1], x[2] and time.strftime('%c', time.localtime(x[2]))), data) 863 #print data 864 self.print_listing(['Name', 'Owner', 'Time'], data) 817 865 866 818 867 ## Version 819 868 _help_version = [('version list', 'Show versions'), 820 869 ('version add <name> [time]', 'Add version'), … … 834 883 def do_version(self, line): 835 884 self._do_mile_ver('version', line) 836 885 837 # Milestone and Version are identical, methods886 # Milestone and Version are almost identical, methods 838 887 839 888 def _do_mile_ver(self, type, line): 840 889 arg = self.arg_tokenize(line) … … 925 974 else: 926 975 print >> sys.stderr, 'Unknown time format' 927 976 977 def _do_mile_ver_chown(self, type, name, owner): 978 data = self.db_execsql("UPDATE %s SET owner='%s' WHERE name='%s'" 979 % (type, owner, name)); 980 928 981 _help_upgrade = [('upgrade', 'Upgrade database to current version.')] 929 982 def do_upgrade(self, line): 930 983 arg = self.arg_tokenize(line) -
setup.py
206 206 author_email="info@edgewall.com", 207 207 license=LICENSE, 208 208 url=URL, 209 packages=['trac', 'trac.upgrades', 'trac.wikimacros', 'trac.mimeviewers'], 209 packages=['trac', 'trac.upgrades', 'trac.wikimacros', 'trac.mimeviewers', 210 'trac.workflows'], 210 211 data_files=[(_p('share/trac/templates'), glob('templates/*')), 211 212 (_p('share/trac/htdocs'), glob(_p('htdocs/*.*')) + [_p('htdocs/README')]), 212 213 (_p('share/trac/htdocs/css'), glob(_p('htdocs/css/*'))), -
trac/db_default.py
21 21 22 22 23 23 # Database version identifier. Used for automatic upgrades. 24 db_version = 724 db_version = 8 25 25 26 26 def __mkreports(reps): 27 27 """Utility function used to create report data in same syntax as the … … 125 125 ); 126 126 CREATE TABLE component ( 127 127 name text PRIMARY KEY, 128 owner text 128 owner text, 129 qaowner text 129 130 ); 130 131 CREATE TABLE milestone ( 131 132 id integer PRIMARY KEY, 132 133 name text, 133 134 time integer, 135 owner text, 134 136 descr text, 135 137 UNIQUE(name) 136 138 ); … … 209 211 """, 210 212 """ 211 213 SELECT p.value AS __color__, 212 versionAS __group__,213 id AS ticket, summary, component, version, severity,214 (CASE WHEN IFNULL(version, '') = '' THEN 'Not Specified' ELSE 'Version ' || version END) AS __group__, 215 id AS ticket, summary, component, milestone, severity, 214 216 (CASE status WHEN 'assigned' THEN owner||' *' ELSE owner END) AS owner, 215 217 time AS created, 216 218 changetime AS _changetime, description AS _description, … … 218 220 FROM ticket t, enum p 219 221 WHERE status IN ('new', 'assigned', 'reopened') 220 222 AND p.name = t.priority AND p.type = 'priority' 221 ORDER BY ( version IS NULL),version, p.value, severity, time223 ORDER BY (IFNULL(version, '') = '') DESC,version, p.value, severity, time 222 224 """), 223 225 #---------------------------------------------------------------------------- 224 ('A llTickets by Milestone',226 ('Active Tickets by Milestone', 225 227 """ 226 228 This report shows how to color results by priority, 227 229 while grouping results by milestone. … … 231 233 """, 232 234 """ 233 235 SELECT p.value AS __color__, 234 milestone||' Release'AS __group__,236 (CASE WHEN IFNULL(milestone, '') = '' THEN 'Not Assigned' ELSE milestone||' Release' END) AS __group__, 235 237 id AS ticket, summary, component, version, severity, 236 238 (CASE status WHEN 'assigned' THEN owner||' *' ELSE owner END) AS owner, 237 239 time AS created, … … 240 242 FROM ticket t, enum p 241 243 WHERE status IN ('new', 'assigned', 'reopened') 242 244 AND p.name = t.priority AND p.type = 'priority' 243 ORDER BY ( milestone IS NULL),milestone, p.value, severity, time245 ORDER BY (IFNULL(milestone, '') = '') DESC,milestone, p.value, severity, time 244 246 """), 245 247 #---------------------------------------------------------------------------- 246 248 ('Assigned, Active Tickets by Owner', … … 248 250 List assigned tickets, group by ticket owner, sorted by priority. 249 251 """, 250 252 """ 251 252 253 SELECT p.value AS __color__, 253 ownerAS __group__,254 id AS ticket, summary, component, milestone, severity, time AS created,254 (CASE WHEN IFNULL(owner, '') = '' THEN 'Not Assigned' ELSE owner END) AS __group__, 255 id AS ticket, summary, component, version, milestone, severity, time AS created, 255 256 changetime AS _changetime, description AS _description, 256 257 reporter AS _reporter 257 258 FROM ticket t,enum p 258 259 WHERE status = 'assigned' 259 260 AND p.name=t.priority AND p.type='priority' 260 ORDER BY owner, p.value, severity, time261 ORDER BY (IFNULL(owner, '') = '') DESC, owner, p.value, severity, time 261 262 """), 262 263 #---------------------------------------------------------------------------- 263 264 ('Assigned, Active Tickets by Owner (Full Description)', … … 268 269 """ 269 270 SELECT p.value AS __color__, 270 271 owner AS __group__, 271 id AS ticket, summary, component, milestone, severity, time AS created,272 id AS ticket, summary, component, version, milestone, severity, time AS created, 272 273 description AS _description_, 273 274 changetime AS _changetime, reporter AS _reporter 274 275 FROM ticket t, enum p … … 283 284 """, 284 285 """ 285 286 SELECT p.value AS __color__, 286 t.milestoneAS __group__,287 (CASE WHEN IFNULL(t.milestone, '') = '' THEN 'Not Assigned' ELSE t.milestone || ' Release' END) AS __group__, 287 288 (CASE status 288 289 WHEN 'closed' THEN 'color: #777; background: #ddd; border-color: #ccc;' 289 290 ELSE 290 291 (CASE owner WHEN '$USER' THEN 'font-weight: bold' END) 291 292 END) AS __style__, 292 293 id AS ticket, summary, component, status, 293 resolution,version, severity, priority, owner, 294 (CASE WHEN resolution ISNULL THEN '' ELSE resolution END) AS resolution, 295 version, severity, priority, owner, 294 296 changetime AS modified, 295 297 time AS _time,reporter AS _reporter 296 298 FROM ticket t,enum p 297 299 WHERE p.name=t.priority AND p.type='priority' 298 ORDER BY ( milestone IS NULL), milestone DESC, (status = 'closed'),300 ORDER BY (IFNULL(milestone, '') = '') DESC, milestone DESC, (status = 'closed'), 299 301 (CASE status WHEN 'closed' THEN modified ELSE -p.value END) DESC 300 302 """), 301 303 #---------------------------------------------------------------------------- … … 307 309 """, 308 310 """ 309 311 SELECT p.value AS __color__, 310 (CASE status WHEN 'assigned' THEN 'Assigned' ELSE 'Owned' END) AS __group__, 311 id AS ticket, summary, component, version, milestone, 312 severity, priority, time AS created, 312 (CASE WHEN status <> 'new' THEN 'Assigned' 313 ELSE 'Owned in ' || IFNULL(milestone, 'N/A') 314 END) AS __group__, 315 id AS ticket, summary, component, status, resolution, version, milestone, 316 priority, time AS created, 313 317 changetime AS _changetime, description AS _description, 314 318 reporter AS _reporter 315 319 FROM ticket t, enum p 316 WHERE t.status IN ('new', 'assigned', 'reopened')320 WHERE t.status <> 'closed' 317 321 AND p.name = t.priority AND p.type = 'priority' AND owner = '$USER' 318 ORDER BY (status = 'assigned') DESC, p.value, milestone, severity, time 322 ORDER BY (status = 'new'), (IFNULL(milestone, '') = '') DESC, 323 milestone, p.value, resolution, time 319 324 """), 320 325 #---------------------------------------------------------------------------- 321 326 ('Active Tickets, Mine first', … … 338 343 WHERE status IN ('new', 'assigned', 'reopened') 339 344 AND p.name = t.priority AND p.type = 'priority' 340 345 ORDER BY (owner = '$USER') DESC, p.value, milestone, severity, time 346 """), 347 #---------------------------------------------------------------------------- 348 ('Open Tickets, Mine first', 349 """ 350 * List all not closed tickets by priority. 351 * Show all tickets owned by the logged in user in a group first. 352 """, 353 """ 354 SELECT p.value AS __color__, 355 (CASE owner 356 WHEN '$USER' THEN 'My Tickets' 357 ELSE 'Open Tickets' 358 END) AS __group__, 359 id AS ticket, summary, component, status, version, milestone, severity, 360 (CASE status WHEN 'assigned' THEN owner||' *' ELSE owner END) AS owner, 361 time AS created, 362 changetime AS _changetime, description AS _description, 363 reporter AS _reporter 364 FROM ticket t, enum p 365 WHERE status <> 'closed' 366 AND p.name = t.priority AND p.type = 'priority' 367 ORDER BY (owner = '$USER') DESC, p.value, milestone, severity, time 368 """), 369 #---------------------------------------------------------------------------- 370 ('Open Tickets by Version', 371 """ 372 * List all not closed tickets by priority. 373 * Group results by version. 374 """, 375 """ 376 SELECT p.value AS __color__, 377 (CASE WHEN IFNULL(version, '') = '' THEN 'Not Specified' ELSE 'Version ' || version END) AS __group__, 378 id AS ticket, summary, component, status, milestone, severity, 379 (CASE status WHEN 'assigned' THEN owner||' *' ELSE owner END) AS owner, 380 time AS created, 381 changetime AS _changetime, description AS _description, 382 reporter AS _reporter 383 FROM ticket t, enum p 384 WHERE status <> 'closed' 385 AND p.name = t.priority AND p.type = 'priority' 386 ORDER BY (IFNULL(version, '') = '') desc,version, p.value, severity, time 387 """), 388 #---------------------------------------------------------------------------- 389 ('Open Tickets by Milestone', 390 """ 391 * List all not closed tickets by priority. 392 * Group results by milestone. 393 """, 394 """ 395 SELECT p.value AS __color__, 396 (CASE WHEN IFNULL(milestone, '') = '' THEN 'Not Assigned' ELSE milestone||' Release' END) AS __group__, 397 id AS ticket, summary, component, status, version, severity, 398 (CASE status WHEN 'assigned' THEN owner||' *' ELSE owner END) AS owner, 399 time AS created, 400 changetime AS _changetime, description AS _description, 401 reporter AS _reporter 402 FROM ticket t, enum p 403 WHERE status <> 'closed' 404 AND p.name = t.priority AND p.type = 'priority' 405 ORDER BY (IFNULL(milestone, '') = '') DESC,milestone, p.value, severity, time 406 """), 407 #---------------------------------------------------------------------------- 408 ('Open Tickets by Owner', 409 """ 410 List not closed tickets, group by ticket owner, sorted by priority. 411 """, 412 """ 413 SELECT p.value AS __color__, 414 (CASE WHEN IFNULL(owner, '') = '' THEN 'Not Assigned' ELSE owner END) AS __group__, 415 id AS ticket, summary, component, status, version, milestone, severity, 416 time AS created, changetime AS _changetime, description AS _description, 417 reporter AS _reporter 418 FROM ticket t,enum p 419 WHERE status <> 'closed' 420 AND p.name=t.priority AND p.type='priority' 421 ORDER BY (IFNULL(owner, '') = '') DESC, owner, p.value, severity, time 422 """), 423 #---------------------------------------------------------------------------- 424 ('Open Tickets by Status', 425 """ 426 * List all not closed tickets by priority. 427 * Group results by status. 428 """, 429 """ 430 SELECT p.value AS __color__, 431 status AS __group__, 432 id AS ticket, summary, component, version, milestone, severity, owner, 433 time AS created, 434 changetime AS _changetime, description AS _description, 435 reporter AS _reporter 436 FROM ticket t, enum q, enum p 437 WHERE status <> 'closed' 438 AND q.name = t.status AND q.type = 'status' 439 AND p.name = t.priority AND p.type = 'priority' 440 ORDER BY q.value, p.value, severity, time 441 """), 442 #---------------------------------------------------------------------------- 443 ('Resolved Tickets, Mine first', 444 """ 445 * List all resolved tickets by priority. 446 * Show all tickets owned by the logged in user in a group first. 447 """, 448 """ 449 SELECT p.value AS __color__, 450 (CASE owner 451 WHEN '$USER' THEN 'My Tickets' 452 ELSE 'Active Tickets' 453 END) AS __group__, 454 id AS ticket, summary, component, version, milestone, severity, 455 (CASE status WHEN 'assigned' THEN owner||' *' ELSE owner END) AS owner, 456 time AS created, 457 changetime AS _changetime, description AS _description, 458 reporter AS _reporter 459 FROM ticket t, enum p 460 WHERE status = 'resolved' 461 AND p.name = t.priority AND p.type = 'priority' 462 ORDER BY (owner = '$USER') DESC, p.value, milestone, severity, time 463 """), 464 #---------------------------------------------------------------------------- 465 ('Resolved Tickets by Milestone', 466 """ 467 List resolved tickets, sorted by priority, grouped by milestone 468 """, 469 """ 470 SELECT p.value AS __color__, 471 (CASE WHEN IFNULL(milestone, '') = '' THEN 'Not Assigned' ELSE milestone||' Release' END) AS __group__, 472 id AS ticket, summary, component, version, severity, 473 (CASE status WHEN 'assigned' THEN owner||' *' ELSE owner END) AS owner, 474 time AS created, 475 changetime AS _changetime, description AS _description, 476 reporter AS _reporter 477 FROM ticket t, enum p 478 WHERE status = 'resolved' 479 AND p.name = t.priority AND p.type = 'priority' 480 ORDER BY (IFNULL(milestone, '') = '') DESC,milestone, p.value, severity, time 481 """), 482 #---------------------------------------------------------------------------- 483 ('Resolved Tickets by Owner', 484 """ 485 List resolved tickets, group by ticket owner, sorted by priority. 486 """, 487 """ 488 SELECT p.value AS __color__, 489 (CASE WHEN IFNULL(owner, '') = '' THEN 'Not Assigned' ELSE owner END) AS __group__, 490 id AS ticket, summary, component, version, milestone, severity, time AS created, 491 changetime AS _changetime, description AS _description, 492 reporter AS _reporter 493 FROM ticket t,enum p 494 WHERE status = 'resolved' 495 AND p.name=t.priority AND p.type='priority' 496 ORDER BY (IFNULL(owner, '') = '') DESC, owner, p.value, severity, time 497 """), 498 #---------------------------------------------------------------------------- 499 ('Completed Tickets by Milestone (Full Description)', 500 """ 501 Release Notes: List verified and closed tickets, group by milestone, include description. 502 """, 503 """ 504 SELECT p.value AS __color__, 505 (CASE WHEN IFNULL(milestone, '') = '' THEN 'Not Assigned' ELSE milestone||' Release' END) AS __group__, 506 id AS ticket, summary, component, status, version, severity, time AS created, 507 description AS _description_, 508 changetime AS _changetime, reporter AS _reporter 509 FROM ticket t, enum p 510 WHERE status IN ('verified', 'closed') 511 AND p.name = t.priority AND p.type = 'priority' 512 ORDER BY (IFNULL(milestone, '') = '') DESC,milestone, p.value, severity, time 341 513 """)) 342 514 343 515 … … 347 519 348 520 # (table, (column1, column2), ((row1col1, row1col2), (row2col1, row2col2))) 349 521 data = (('component', 350 ('name', 'owner' ),351 (('component1', 'somebody' ),352 ('component2', 'somebody' ))),522 ('name', 'owner', 'qaowner'), 523 (('component1', 'somebody', 'qasomebody'), 524 ('component2', 'somebody', 'qasomebody'))), 353 525 ('milestone', 354 526 ('name', 'time'), 355 527 (('', 0), … … 367 539 (('status', 'new', 1), 368 540 ('status', 'assigned', 2), 369 541 ('status', 'reopened', 3), 370 ('status', 'closed', 4), 542 ('status', 'resolved', 4), 543 ('status', 'verified', 5), 544 ('status', 'closed', 6), 371 545 ('resolution', 'fixed', 1), 372 546 ('resolution', 'invalid', 2), 373 547 ('resolution', 'wontfix', 3), … … 426 600 ('project', 'footer', 427 601 ' Visit the Trac open source project at<br />' 428 602 '<a href="http://trac.edgewall.com/">http://trac.edgewall.com/</a>'), 603 ('ticket', 'workflow', 'trac.workflows.QaRmtWorkflow'), 429 604 ('ticket', 'default_version', ''), 430 605 ('ticket', 'default_severity', 'normal'), 431 606 ('ticket', 'default_priority', 'normal'), -
trac/Milestone.py
60 60 if not group: 61 61 queries['all_tickets'] = env.href.query({'milestone': milestone}) 62 62 queries['active_tickets'] = env.href.query({ 63 'milestone': milestone, 'status': ['new', 'assigned', 'reopened' ]63 'milestone': milestone, 'status': ['new', 'assigned', 'reopened', 'resolved'] 64 64 }) 65 65 queries['closed_tickets'] = env.href.query({ 66 'milestone': milestone, 'status': 'closed'66 'milestone': milestone, 'status': ['closed', 'verified'] 67 67 }) 68 68 else: 69 69 queries['all_tickets'] = env.href.query({ … … 71 71 }) 72 72 queries['active_tickets'] = env.href.query({ 73 73 'milestone': milestone, grouped_by: group, 74 'status': ['new', 'assigned', 'reopened' ]74 'status': ['new', 'assigned', 'reopened', 'resolved'] 75 75 }) 76 76 queries['closed_tickets'] = env.href.query({ 77 77 'milestone': milestone, grouped_by: group, 78 'status': 'closed'78 'status': ['closed', 'verified'] 79 79 }) 80 80 return queries 81 81 82 82 def calc_ticket_stats(tickets): 83 83 total_cnt = len(tickets) 84 active = [ticket for ticket in tickets if ticket['status'] != 'closed' ]84 active = [ticket for ticket in tickets if ticket['status'] != 'closed' and ticket['status'] != 'verified'] 85 85 active_cnt = len(active) 86 86 closed_cnt = total_cnt - active_cnt 87 87 … … 116 116 if datestr: 117 117 date = self.parse_date(datestr) 118 118 descr = self.args.get('descr', '') 119 owner = self.args.get('owner', '') 119 120 if not id: 120 self.create_milestone(name, date, descr )121 self.create_milestone(name, date, descr, owner) 121 122 else: 122 self.update_milestone(id, name, date, descr )123 self.update_milestone(id, name, date, descr, owner) 123 124 elif id: 124 125 self.req.redirect(self.env.href.milestone(id)) 125 126 else: … … 141 142 'Invalid Date Format') 142 143 return seconds 143 144 144 def create_milestone(self, name, date=0, descr='' ):145 def create_milestone(self, name, date=0, descr='', owner=''): 145 146 self.perm.assert_permission(perm.MILESTONE_CREATE) 146 147 if not name: 147 148 raise TracError('You must provide a name for the milestone.', 148 149 'Required Field Missing') 149 150 cursor = self.db.cursor() 150 151 self.log.debug("Creating new milestone '%s'" % name) 151 cursor.execute("INSERT INTO milestone (id, name, time, descr ) "152 "VALUES (NULL, %s, %d, %s )", name, date, descr)152 cursor.execute("INSERT INTO milestone (id, name, time, descr, owner) " 153 "VALUES (NULL, %s, %d, %s, %s)", name, date, descr, owner) 153 154 self.db.commit() 154 155 self.req.redirect(self.env.href.milestone(name)) 155 156 … … 178 179 else: 179 180 self.req.redirect(self.env.href.milestone(id)) 180 181 181 def update_milestone(self, id, name, date, descr ):182 def update_milestone(self, id, name, date, descr, owner): 182 183 self.perm.assert_permission(perm.MILESTONE_MODIFY) 183 184 cursor = self.db.cursor() 184 185 self.log.info("Updating milestone '%s'" % id) … … 188 189 cursor.execute('UPDATE ticket SET milestone = %s ' 189 190 'WHERE milestone = %s', name, id) 190 191 cursor.execute("UPDATE milestone SET name = %s, time = %d, " 191 "descr = %s WHERE name = %s",192 name, date, descr, id)192 "descr = %s, owner = %s WHERE name = %s", 193 name, date, descr, owner, id) 193 194 self.db.commit() 194 195 self.req.redirect(self.env.href.milestone(name)) 195 196 else: … … 222 223 223 224 def get_milestone(self, name): 224 225 cursor = self.db.cursor() 225 cursor.execute("SELECT name, time, descr FROM milestone "226 cursor.execute("SELECT name, time, descr, owner FROM milestone " 226 227 "WHERE name = %s ORDER BY time, name", name) 227 228 row = cursor.fetchone() 228 229 cursor.close() … … 237 238 t = row['time'] and int(row['time']) 238 239 if t > 0: 239 240 milestone['date'] = time.strftime('%x', time.localtime(t)) 241 milestone['owner'] = row['owner'] or '' 240 242 return milestone 241 243 242 244 def render(self): -
trac/Timeline.py
52 52 REOPENED_TICKET = 4 53 53 WIKI = 5 54 54 MILESTONE = 6 55 VERIFIED_TICKET = 7 56 RESOLVED_TICKET = 8 57 RETESTED_TICKET = 9 55 58 56 59 q = [] 57 60 if changeset: … … 60 63 "FROM revision WHERE time>=%s AND time<=%s" % 61 64 (start, stop)) 62 65 if tickets: 66 # New tickets 63 67 q.append("SELECT time, id AS idata, '' AS tdata, 2 AS type, " 64 68 "summary AS message, reporter AS author " 65 69 "FROM ticket WHERE time>=%s AND time<=%s" % 66 70 (start, stop)) 67 q.append("SELECT time, ticket AS idata, '' AS tdata, 4 AS type, " 68 "'' AS message, author " 69 "FROM ticket_change WHERE field='status' " 70 "AND newvalue='reopened' AND time>=%s AND time<=%s" % 71 (start, stop)) 71 # Reopened tickets 72 72 q.append("SELECT t1.time AS time, t1.ticket AS idata," 73 " '' AS tdata, 4 AS type," 74 " t3.newvalue AS message, t1.author AS author" 75 " FROM ticket_change t1" 76 " LEFT OUTER JOIN ticket_change t3 ON t1.time = t3.time" 77 " AND t1.ticket = t3.ticket AND t3.field = 'comment'" 78 " WHERE t1.field = 'status' AND t1.newvalue = 'reopened'" 79 " AND t1.time >= %s AND t1.time <= %s" % (start,stop)) 80 # Closed tickets (including resolution field for old workflow) 81 q.append("SELECT t1.time AS time, t1.ticket AS idata," 73 82 " t2.newvalue AS tdata, 3 AS type," 74 83 " t3.newvalue AS message, t1.author AS author" 75 84 " FROM ticket_change t1" 76 " INNER JOIN ticket_change t2 ON t1.ticket = t2.ticket"77 " AND t1.time = t2.time "85 " LEFT OUTER JOIN ticket_change t2 ON t1.ticket = t2.ticket" 86 " AND t1.time = t2.time AND t2.field = 'resolution'" 78 87 " LEFT OUTER JOIN ticket_change t3 ON t1.time = t3.time" 79 88 " AND t1.ticket = t3.ticket AND t3.field = 'comment'" 80 89 " WHERE t1.field = 'status' AND t1.newvalue = 'closed'" 81 " AND t2.field = 'resolution'"82 90 " AND t1.time >= %s AND t1.time <= %s" % (start,stop)) 91 # Verified tickets (including resolution field for customized workflows) 92 q.append("SELECT t1.time AS time, t1.ticket AS idata," 93 " t2.newvalue AS tdata, 7 AS type," 94 " t3.newvalue AS message, t1.author AS author" 95 " FROM ticket_change t1" 96 " LEFT OUTER JOIN ticket_change t2 ON t1.ticket = t2.ticket" 97 " AND t1.time = t2.time AND t2.field = 'resolution'" 98 " LEFT OUTER JOIN ticket_change t3 ON t1.time = t3.time" 99 " AND t1.ticket = t3.ticket AND t3.field = 'comment'" 100 " WHERE t1.field = 'status' AND t1.newvalue = 'verified'" 101 " AND t1.oldvalue<>'closed'" 102 " AND t1.time >= %s AND t1.time <= %s" % (start,stop)) 103 # Resolved tickets (including resolution field) 104 q.append("SELECT t1.time AS time, t1.ticket AS idata," 105 " t2.newvalue AS tdata, 8 AS type," 106 " t3.newvalue AS message, t1.author AS author" 107 " FROM ticket_change t1" 108 " LEFT OUTER JOIN ticket_change t2 ON t1.ticket = t2.ticket" 109 " AND t1.time = t2.time AND t2.field = 'resolution'" 110 " LEFT OUTER JOIN ticket_change t3 ON t1.time = t3.time" 111 " AND t1.ticket = t3.ticket AND t3.field = 'comment'" 112 " WHERE t1.field = 'status' AND t1.newvalue = 'resolved'" 113 " AND t1.oldvalue NOT IN ('verified', 'closed')" 114 " AND t1.time >= %s AND t1.time <= %s" % (start,stop)) 115 # Retested tickets 116 q.append("SELECT t1.time AS time, t1.ticket AS idata," 117 " '' AS tdata, 9 AS type," 118 " t3.newvalue AS message, t1.author AS author" 119 " FROM ticket_change t1" 120 " LEFT OUTER JOIN ticket_change t3 ON t1.time = t3.time" 121 " AND t1.ticket = t3.ticket AND t3.field = 'comment'" 122 " WHERE t1.field = 'status' AND t1.newvalue = 'resolved'" 123 " AND t1.oldvalue IN ('verified', 'closed')" 124 " AND t1.time >= %s AND t1.time <= %s" % (start,stop)) 83 125 if wiki: 84 126 q.append("SELECT time, -1 AS idata, name AS tdata, 5 AS type, " 85 127 "comment AS message, author " … … 87 129 (start, stop)) 88 130 if milestone: 89 131 q.append("SELECT time, -1 AS idata, '' AS tdata, 6 AS type, " 90 "name AS message, ''AS author "132 "name AS message, owner AS author " 91 133 "FROM milestone WHERE time>=%s AND time<=%s" % 92 134 (start, stop)) 93 135 -
trac/workflows/Base.py
1 # -*- coding: iso8859-1 -*- 2 # 3 # Copyright (C) 2003, 2004 Edgewall Software 4 # Copyright (C) 2004 Pavel Kourochka <pkou@ua.fm> 5 # 6 # Trac is free software; you can redistribute it and/or 7 # modify it under the terms of the GNU General Public License as 8 # published by the Free Software Foundation; either version 2 of the 9 # License, or (at your option) any later version. 10 # 11 # Trac is distributed in the hope that it will be useful, 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 # General Public License for more details. 15 # 16 # You should have received a copy of the GNU General Public License 17 # along with this program; if not, write to the Free Software 18 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 19 # 20 # Author: Pavel Kourochka <pkou@ua.fm> 21 # 22 # Abstract workflow definition 23 24 class WorkflowBase: 25 """ 26 Generic workflow class for Trac. 27 """ 28 29 def __init__(self, env, db, user): 30 """ 31 Constructor for workflow class. 32 """ 33 self.env = env 34 self.db = db 35 self.user = user 36 37 def get_actions(self, ticket): 38 """ 39 For existing tickets only. 40 Return the list of available actions for specified ticket. 41 """ 42 raise NotImplementedError 43 44 def do_action(self, ticket, action, args): 45 """ 46 For new and existing tickets. 47 Perform action on a ticket. For new tickets, action name is 'create'. 48 """ 49 raise NotImplementedError 50 51 def get_actions_template(self, ticket): 52 """ 53 For new and existing tickets. 54 Return the name of ClearSilver template file for the workflow. 55 Return None if no additional template is required. 56 """ 57 return None 58 59 def init_template(self, ticket, hdf): 60 """ 61 For new and existing tickets. 62 Initialize ClearSilver variables for actions template. 63 Called if get_actions_template() returns file name only. 64 """ 65 pass 66 67 def validate(self, ticket, args): 68 """ 69 For new and existing tickets. 70 Validate ticket. 71 Return list of Wiki strings that describe errors in the ticket. 72 """ 73 return [] 74 75 def on_create(self, ticket): 76 """ 77 For new tickets only. 78 Update ticket fields just before inserting the ticket into database. 79 """ 80 pass 81 82 def on_save(self, ticket): 83 """ 84 For existing tickets only. 85 Update ticket fields just before saving the ticket into database. 86 """ 87 pass -
trac/workflows/QaWorkflow.py
1 # -*- coding: iso8859-1 -*- 2 # 3 # Copyright (C) 2003, 2004, 2005 Edgewall Software 4 # Copyright (C) 2005 Pavel Kourochka <pkou@ua.fm> 5 # 6 # Trac is free software; you can redistribute it and/or 7 # modify it under the terms of the GNU General Public License as 8 # published by the Free Software Foundation; either version 2 of the 9 # License, or (at your option) any later version. 10 # 11 # Trac is distributed in the hope that it will be useful, 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 # General Public License for more details. 15 # 16 # You should have received a copy of the GNU General Public License 17 # along with this program; if not, write to the Free Software 18 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 19 # 20 # Author: Pavel Kourochka <pkou@ua.fm> 21 # 22 # Workflow definition for development and QA teams 23 # 24 # Note: The workflow is similar to QaRmtWorkflow except that is removes 25 # 'verified' status from actions. 26 27 from trac.workflows.QaRmtWorkflow import QaRmtWorkflow 28 29 class QaWorkflow(QaRmtWorkflow): 30 31 def get_actions(self, ticket): 32 actions = { 33 'new': ['leave','reassign','resolve' ,'accept'], 34 'assigned': ['leave','reassign','resolve' ], 35 'reopened': ['leave','reassign','resolve' ,'accept'], 36 'resolved': ['leave','reassign' ,'reopen' ,'close' ], 37 'closed': ['leave' ,'reopen','retest' ], 38 # Keep the unused state in order to allow ticket processing if workflow 39 # is changed dynamically for existing project. 40 'verified': ['leave','reassign' ,'reopen','retest','close' ] 41 } 42 return actions.get(ticket['status'], ['leave']) -
trac/workflows/TimeTrackWorkflow.py
1 # -*- coding: iso8859-1 -*- 2 # 3 # Copyright (C) 2003, 2004 Edgewall Software 4 # Copyright (C) 2004 Pavel Kourochka <pkou@ua.fm> 5 # 6 # Trac is free software; you can redistribute it and/or 7 # modify it under the terms of the GNU General Public License as 8 # published by the Free Software Foundation; either version 2 of the 9 # License, or (at your option) any later version. 10 # 11 # Trac is distributed in the hope that it will be useful, 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 # General Public License for more details. 15 # 16 # You should have received a copy of the GNU General Public License 17 # along with this program; if not, write to the Free Software 18 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 19 # 20 # Author: Pavel Kourochka <pkou@ua.fm> 21 # 22 # Simple time tracker for any workflow 23 # 24 # Usage: 25 # 1. Add custom field for time calculation 26 # file: project-path/conf/trac.ini 27 # section: [ticket-custom] 28 # parameters: 29 # totaltime = text 30 # totaltime.label = Total time 31 # 2. Enable the workflow 32 # file: project-path/conf/trac.ini 33 # section: [ticket] 34 # parameter: 35 # workflow = trac.workflows.TimeTracWorkflow 36 # optional parameter (base workflow that is used for time tracking): 37 # timetrack_workflow = <base workflow, simple workflow by default> 38 # optional parameter (custom field name that is used for time tracking): 39 # timetrack_timefld = <custom field for time, 'totaltime' by default> 40 # optional parameter (require time tracking for every action): 41 # timetrack_required = <'yes' or 'no', default is 'no'> 42 43 from trac.workflows.Base import WorkflowBase 44 45 class TimeTrackWorkflow(WorkflowBase): 46 47 def __init__(self, env, db, user): 48 WorkflowBase.__init__(self, env, db, user) 49 50 modulename = env.get_config('ticket', 'timetrack_workflow', \ 51 'trac.workflows.SimpleWorkflow') 52 i = modulename.rfind('.') 53 if i == -1: 54 classname = modulename 55 else: 56 classname = modulename[i+1:] 57 58 module = __import__(modulename, globals(), locals(), [classname]) 59 constructor = getattr(module, classname) 60 self.workflow_proxy = constructor(env, db, user) 61 62 if not isinstance(self.workflow_proxy, WorkflowBase): 63 raise EnvironmentError, "Workflow class %s from %s must be " \ 64 "descendant of class WorkflowBase from " \ 65 "trac.workflows.base" \ 66 % (classname, modulename) 67 68 self.workflow_timefldname = env.get_config( \ 69 'ticket', 'timetrack_timefld', 'totaltime') 70 if env.get_config('ticket-custom', self.workflow_timefldname, '') \ 71 != 'text': 72 raise EnvironmentError, "TimeTrackWorkflow requires custom " \ 73 "field %s. Type of the field is 'text'" \ 74 % self.workflow_timefldname 75 self.workflow_timefld = 'custom_' + self.workflow_timefldname 76 77 self.time_required = env.get_config('ticket', 'timetrack_required', 'no') 78 79 def get_actions(self, ticket): 80 return self.workflow_proxy.get_actions(ticket) 81 82 def do_action(self, ticket, action, args): 83 self.workflow_proxy.do_action(ticket, action, args) 84 85 curtime = ticket[self.workflow_timefld] or '0' 86 delta = args.get('time_delta') or '0' 87 try: 88 ticket[self.workflow_timefld] = \ 89 str(int(curtime, 10) + int(delta, 10)) 90 except: 91 self.env.log.warning('TimeTrackWorkflow: invalid time data') 92 93 def get_actions_template(self, ticket): 94 return 'ticket_workflow_timetrack.cs' 95 96 def init_template(self, ticket, hdf): 97 proxytpl = self.workflow_proxy.get_actions_template(ticket) 98 if proxytpl: 99 self.workflow_proxy.init_template(ticket, hdf) 100 hdf.setValue('ticket.workflow.proxy_template', proxytpl) 101 102 def validate(self, ticket, args): 103 err = self.workflow_proxy.validate(ticket, args) 104 105 curtimestr = ticket[self.workflow_timefld] 106 try: 107 curtime = int(curtimestr or '0', 10) 108 except: 109 curtime = -1 110 if curtime < 0: 111 err.append("Total time (custom field '''%s''') must be " \ 112 "non-negative decimal number. Value '{{{%s}}}' " \ 113 "is incorrect." % (self.workflow_timefldname, \ 114 curtimestr)) 115 116 deltastr = args.get('time_delta') 117 try: 118 delta = int(deltastr or '0', 10) 119 except: 120 delta = -1 121 if delta < 0: 122 err.append("Time that is spent on an action must be " \ 123 "non-negative decimal number. Value '{{{%s}}}' " \ 124 "is incorrect." % deltastr) 125 126 if self.time_required == 'yes' and not deltastr: 127 err.append("Time that is spent on an action must be entered.") 128 129 return err 130 131 def on_create(self, ticket): 132 self.workflow_proxy.on_create(ticket) 133 134 def on_save(self, ticket): 135 self.workflow_proxy.on_save(ticket) -
trac/workflows/QaRmtWorkflow.py
1 # -*- coding: iso8859-1 -*- 2 # 3 # Copyright (C) 2003, 2004 Edgewall Software 4 # Copyright (C) 2004 Pavel Kourochka <pkou@ua.fm> 5 # 6 # Trac is free software; you can redistribute it and/or 7 # modify it under the terms of the GNU General Public License as 8 # published by the Free Software Foundation; either version 2 of the 9 # License, or (at your option) any later version. 10 # 11 # Trac is distributed in the hope that it will be useful, 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 # General Public License for more details. 15 # 16 # You should have received a copy of the GNU General Public License 17 # along with this program; if not, write to the Free Software 18 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 19 # 20 # Author: Pavel Kourochka <pkou@ua.fm> 21 # 22 # Workflow definition for development, QA, and release management teams 23 24 from trac.workflows.SimpleWorkflow import SimpleWorkflow 25 26 class QaRmtWorkflow(SimpleWorkflow): 27 28 def get_actions(self, ticket): 29 actions = { 30 'new': ['leave','reassign','resolve' ,'accept'], 31 'assigned': ['leave','reassign','resolve' ], 32 'reopened': ['leave','reassign','resolve' ,'accept'], 33 'resolved': ['leave','reassign' ,'reopen' ,'verify'], 34 'verified': ['leave','reassign' ,'reopen','retest','close' ], 35 'closed': ['leave' ,'reopen','retest' ] 36 } 37 return actions.get(ticket['status'], ['leave']) 38 39 def do_action(self, ticket, action, args): 40 if action == 'accept': 41 ticket['status'] = 'assigned' 42 ticket['owner'] = self.user 43 elif action == 'resolve': 44 ticket['status'] = 'resolved' 45 ticket['resolution'] = args.get('resolve_resolution') 46 ticket['owner'] = '' 47 elif action == 'verify': 48 ticket['status'] = 'verified' 49 ticket['owner'] = '' 50 elif action == 'close': 51 ticket['status'] = 'closed' 52 elif action == 'reassign': 53 newowner = args.get('reassign_owner') 54 if ticket['owner'] != newowner: 55 if ticket['status'] == 'assigned': ticket['status'] = 'new' 56 ticket['owner'] = newowner 57 elif action == 'reopen': 58 ticket['status'] = 'reopened' 59 ticket['resolution'] = '' 60 ticket['owner'] = '' 61 elif action == 'retest': 62 ticket['status'] = 'resolved' 63 ticket['owner'] = '' 64 65 def get_actions_template(self, ticket): 66 if ticket.has_key('id'): 67 return 'ticket_workflow_qarmt.cs' 68 else: 69 return None 70 71 def on_create(self, ticket): 72 SimpleWorkflow.on_create(self, ticket) 73 74 # The owner field defaults to the milestone owner if 75 # the component does not have any owner 76 cursor = self.db.cursor() 77 if ticket.get('owner', '') == '': 78 cursor.execute('SELECT owner FROM milestone ' 79 'WHERE name=%s', ticket.get('milestone', '')) 80 ticket['owner'] = cursor.fetchone()[0] or '' 81 82 def on_save(self, ticket): 83 SimpleWorkflow.on_save(self, ticket) 84 if not ticket._old: return # Not modified 85 86 cursor = self.db.cursor() 87 status = ticket.get('status', 'new') 88 component = ticket.get('component', '') 89 milestone = ticket.get('milestone', '') 90 91 # If the milestone is changed on a 'new' ticket then owner field 92 # is updated accordingly if the component does not have any owner. 93 # (related to #623). 94 if status == 'new' and ticket._old.has_key('milestone') and \ 95 not ticket._old.has_key('component') and \ 96 not ticket._old.has_key('owner'): 97 cursor.execute('SELECT owner FROM component ' 98 'WHERE name=%s', component) 99 if not cursor.fetchone()[0]: 100 cursor.execute('SELECT owner FROM milestone ' 101 'WHERE name=%s', ticket._old['milestone']) 102 row = cursor.fetchone() 103 if row: 104 old_owner = row[0] 105 if ticket['owner'] == old_owner: 106 cursor.execute('SELECT owner FROM milestone ' 107 'WHERE name=%s', milestone) 108 ticket['owner'] = cursor.fetchone()[0] or '' 109 110 # 1. The owner field defaults to the component owner for active tickets 111 if ticket.get('owner', '') == '' and status in ['new', 'reopened']: 112 cursor.execute('SELECT owner FROM component ' 113 'WHERE name=%s', component) 114 newowner = cursor.fetchone()[0] 115 if newowner: ticket['owner'] = newowner 116 117 # 2. The owner field defaults to component QA owner for testing tickets 118 if ticket.get('owner', '') == '' and status == 'resolved': 119 cursor.execute('SELECT qaowner FROM component ' 120 'WHERE name=%s', component) 121 newowner = cursor.fetchone()[0] 122 if newowner: ticket['owner'] = newowner 123 124 # 3. The owner field defaults to milestone owner for open tickets 125 if ticket.get('owner', '') == '' and status != 'closed': 126 cursor.execute('SELECT owner FROM milestone ' 127 'WHERE name=%s', milestone) 128 newowner = cursor.fetchone()[0] 129 if newowner: ticket['owner'] = newowner 130 131 # 4. The owner field defaults to reporter for verified tickets 132 if ticket.get('owner', '') == '' and status == 'verified': 133 reporter = ticket.get('reporter', '') 134 if reporter: ticket['owner'] = reporter -
trac/workflows/__init__.py
1 __all__ = ['Base', 'SimpleWorkflow', 'TimeTrackWorkflow', 'QaWorkflow', 'QaRmtWorkflow'] -
trac/workflows/SimpleWorkflow.py
1 # -*- coding: iso8859-1 -*- 2 # 3 # Copyright (C) 2003, 2004 Edgewall Software 4 # Copyright (C) 2003, 2004 Jonas Borgström <jonas@edgewall.com> 5 # 6 # Trac is free software; you can redistribute it and/or 7 # modify it under the terms of the GNU General Public License as 8 # published by the Free Software Foundation; either version 2 of the 9 # License, or (at your option) any later version. 10 # 11 # Trac is distributed in the hope that it will be useful, 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 # General Public License for more details. 15 # 16 # You should have received a copy of the GNU General Public License 17 # along with this program; if not, write to the Free Software 18 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 19 # 20 # Author: Pavel Kourochka <pkou@ua.fm> 21 # 22 # Simple workflow definition (as in Trac 0.8) 23 24 from trac.workflows.Base import WorkflowBase 25 26 class SimpleWorkflow(WorkflowBase): 27 28 def get_actions(self, ticket): 29 actions = { 30 'new': ['leave' ,'resolve' ,'reassign' ,'accept'], 31 'assigned': ['leave' ,'resolve' ,'reassign' ], 32 'reopened': ['leave' ,'resolve' ,'reassign' ], 33 'closed': ['leave' ,'reopen'], 34 # Keep actions for not supported statuses in order to 35 # allow dynamic workflow changes 36 'resolved': ['leave' ,'resolve' ,'reopen'], 37 'verified': ['leave' ,'resolve' ,'reopen'] 38 } 39 return actions.get(ticket['status'], ['leave']) 40 41 def do_action(self, ticket, action, args): 42 if action == 'accept': 43 ticket['status'] = 'assigned' 44 ticket['owner'] = self.user 45 elif action == 'resolve': 46 ticket['status'] = 'closed' 47 ticket['resolution'] = args.get('resolve_resolution') 48 elif action == 'reassign': 49 ticket['owner'] = args.get('reassign_owner') 50 ticket['status'] = 'new' 51 elif action == 'reopen': 52 ticket['status'] = 'reopened' 53 ticket['resolution'] = '' 54 55 def get_actions_template(self, ticket): 56 if ticket.has_key('id'): 57 return 'ticket_workflow_simple.cs' 58 else: 59 return None 60 61 def init_template(self, ticket, hdf): 62 WorkflowBase.init_template(self, ticket, hdf) 63 if ticket.has_key('id'): 64 for a in self.get_actions(ticket): 65 hdf.setValue('ticket.workflow.action.' + a, '1') 66 67 def validate(self, ticket, args): 68 err = WorkflowBase.validate(self, ticket, args) 69 if not ticket.get('summary'): 70 err.append("The ticket must contain '''Summary''' field.") 71 return err 72 73 def on_create(self, ticket): 74 WorkflowBase.on_create(self, ticket) 75 76 # The owner field defaults to the component owner 77 cursor = self.db.cursor() 78 if ticket.get('owner', '') == '': 79 cursor.execute('SELECT owner FROM component ' 80 'WHERE name=%s', ticket.get('component', '')) 81 ticket['owner'] = cursor.fetchone()[0] or '' 82 83 def on_save(self, ticket): 84 WorkflowBase.on_save(self, ticket) 85 if not ticket._old: return # Not modified 86 87 # If the component is changed on a 'new' ticket then owner field 88 # is updated accordingly. (#623). 89 cursor = self.db.cursor() 90 if ticket['status'] == 'new' and ticket._old.has_key('component') and \ 91 not ticket._old.has_key('owner'): 92 cursor.execute('SELECT owner FROM component ' 93 'WHERE name=%s', ticket._old['component']) 94 row = cursor.fetchone() 95 # If the old component has been removed from the database 96 # then we just leave the owner as is. 97 if row: 98 old_owner = row[0] 99 if ticket['owner'] == old_owner: 100 cursor.execute('SELECT owner FROM component ' 101 'WHERE name=%s', ticket['component']) 102 ticket['owner'] = cursor.fetchone()[0] -
trac/Roadmap.py
49 49 icalhref += '&show=all' 50 50 self.req.hdf.setValue('roadmap.href.list', 51 51 self.env.href.roadmap()) 52 query = "SELECT name, time, descr FROM milestone " \52 query = "SELECT name, time, descr, owner FROM milestone " \ 53 53 "WHERE name != '' " \ 54 54 "ORDER BY (IFNULL(time, 0) = 0) ASC, time ASC, name" 55 55 else: 56 56 self.req.hdf.setValue('roadmap.showall', '1') 57 57 self.req.hdf.setValue('roadmap.href.list', 58 58 self.env.href.roadmap('all')) 59 query = "SELECT name, time, descr FROM milestone " \59 query = "SELECT name, time, descr, owner FROM milestone " \ 60 60 "WHERE name != '' " \ 61 61 "AND (time IS NULL OR time = 0 OR time > %d) " \ 62 62 "ORDER BY (IFNULL(time, 0) = 0) ASC, time ASC, name" % time() … … 75 75 milestone = { 76 76 'name': row['name'], 77 77 'href': self.env.href.milestone(row['name']), 78 'owner': row['owner'] or '', 78 79 'time': row['time'] and int(row['time']) 79 80 } 80 81 descr = row['descr'] … … 114 115 status = ticket['status'] 115 116 if status == 'new' or status == 'reopened' and not ticket['owner']: 116 117 return 'NEEDS-ACTION' 117 elif status == 'assigned' or status == 'reopened':118 elif status != 'closed': 118 119 return 'IN-PROCESS' 119 120 elif status == 'closed': 120 121 if ticket['resolution'] == 'fixed': return 'COMPLETED' -
trac/upgrades/db8.py
1 sql = """ 2 -- Add statuses 'resolved' and 'verified' 3 UPDATE enum SET value = 6 WHERE type = 'status' AND name = 'closed'; 4 INSERT INTO enum (type, name, value) VALUES ('status', 'resolved', 4); 5 INSERT INTO enum (type, name, value) VALUES ('status', 'verified', 5); 6 7 -- Add QA Contact to 'component' 8 CREATE TEMPORARY TABLE component_backup AS SELECT * FROM component; 9 DROP TABLE component; 10 CREATE TABLE component ( 11 name text PRIMARY KEY, 12 owner text, 13 qaowner text 14 ); 15 INSERT INTO component SELECT name, owner, owner AS qaowner FROM component_backup; 16 DROP TABLE component_backup; 17 18 -- Add Release Manager Contact to 'milestone' 19 CREATE TEMPORARY TABLE milestone_backup AS SELECT * FROM milestone; 20 DROP TABLE milestone; 21 CREATE TABLE milestone ( 22 id integer PRIMARY KEY, 23 name text, 24 time integer, 25 owner text, 26 descr text, 27 UNIQUE(name) 28 ); 29 INSERT INTO milestone SELECT id, name, time, '' AS owner, descr FROM milestone_backup; 30 DROP TABLE milestone_backup; 31 32 -- Modify 'All Tickets by Version' report 33 UPDATE report SET sql = ' 34 SELECT p.value AS __color__, 35 (CASE WHEN IFNULL(version, '''') = '''' THEN ''Not Specified'' ELSE ''Version '' || version END) AS __group__, 36 id AS ticket, summary, component, milestone, severity, 37 (CASE status WHEN ''assigned'' THEN owner||'' *'' ELSE owner END) AS owner, 38 time AS created, 39 changetime AS _changetime, description AS _description, 40 reporter AS _reporter 41 FROM ticket t, enum p 42 WHERE status IN (''new'', ''assigned'', ''reopened'') 43 AND p.name = t.priority AND p.type = ''priority'' 44 ORDER BY (IFNULL(version, '''') = '''') DESC,version, p.value, severity, time 45 ' WHERE title = 'Active Tickets by Version'; 46 47 -- Modify 'All Tickets by Milestone' report 48 UPDATE report SET sql = ' 49 SELECT p.value AS __color__, 50 (CASE WHEN IFNULL(milestone, '''') = '''' THEN ''Not Assigned'' ELSE milestone||'' Release'' END) AS __group__, 51 id AS ticket, summary, component, version, severity, 52 (CASE status WHEN ''assigned'' THEN owner||'' *'' ELSE owner END) AS owner, 53 time AS created, 54 changetime AS _changetime, description AS _description, 55 reporter AS _reporter 56 FROM ticket t, enum p 57 WHERE status IN (''new'', ''assigned'', ''reopened'') 58 AND p.name = t.priority AND p.type = ''priority'' 59 ORDER BY (IFNULL(milestone, '''') = '''') DESC,milestone, p.value, severity, time 60 ', title = 'Active Tickets by Milestone' 61 WHERE title = 'All Tickets by Milestone'; 62 63 -- Modify 'Assigned, Active Tickets by Owner' report 64 UPDATE report SET sql = ' 65 SELECT p.value AS __color__, 66 (CASE WHEN IFNULL(owner, '''') = '''' THEN ''Not Assigned'' ELSE owner END) AS __group__, 67 id AS ticket, summary, component, version, milestone, severity, time AS created, 68 changetime AS _changetime, description AS _description, 69 reporter AS _reporter 70 FROM ticket t,enum p 71 WHERE status = ''assigned'' 72 AND p.name=t.priority AND p.type=''priority'' 73 ORDER BY (IFNULL(owner, '''') = '''') DESC, owner, p.value, severity, time 74 ' WHERE title = 'Assigned, Active Tickets by Owner'; 75 76 -- Modify 'Assigned, Active Tickets by Owner (Full Description)' report 77 UPDATE report SET sql = ' 78 SELECT p.value AS __color__, 79 owner AS __group__, 80 id AS ticket, summary, component, version, milestone, severity, time AS created, 81 description AS _description_, 82 changetime AS _changetime, reporter AS _reporter 83 FROM ticket t, enum p 84 WHERE status = ''assigned'' 85 AND p.name = t.priority AND p.type = ''priority'' 86 ORDER BY owner, p.value, severity, time 87 ' WHERE title = 'Assigned, Active Tickets by Owner (Full Description)'; 88 89 -- Modify 'All Tickets By Milestone (Including closed)' report 90 UPDATE report SET sql = ' 91 SELECT p.value AS __color__, 92 (CASE WHEN IFNULL(t.milestone, '''') = '''' THEN ''Not Assigned'' ELSE t.milestone || '' Release'' END) AS __group__, 93 (CASE status 94 WHEN ''closed'' THEN ''color: #777; background: #ddd; border-color: #ccc;'' 95 ELSE 96 (CASE owner WHEN ''$USER'' THEN ''font-weight: bold'' END) 97 END) AS __style__, 98 id AS ticket, summary, component, status, 99 (CASE WHEN resolution ISNULL THEN '' ELSE resolution END) AS resolution, 100 version, severity, priority, owner, 101 changetime AS modified, 102 time AS _time,reporter AS _reporter 103 FROM ticket t,enum p 104 WHERE p.name=t.priority AND p.type=''priority'' 105 ORDER BY (IFNULL(milestone, '''') = '''') DESC, milestone DESC, (status = ''closed''), 106 (CASE status WHEN ''closed'' THEN modified ELSE -p.value END) DESC 107 ' WHERE title = 'All Tickets By Milestone (Including closed)'; 108 109 -- Modify 'My Tickets' report 110 UPDATE report SET sql = ' 111 SELECT p.value AS __color__, 112 (CASE WHEN status <> ''new'' THEN ''Assigned'' 113 ELSE ''Owned in '' || IFNULL(milestone, ''N/A'') 114 END) AS __group__, 115 id AS ticket, summary, component, status, resolution, version, milestone, 116 priority, time AS created, 117 changetime AS _changetime, description AS _description, 118 reporter AS _reporter 119 FROM ticket t, enum p 120 WHERE t.status <> ''closed'' 121 AND p.name = t.priority AND p.type = ''priority'' AND owner = ''$USER'' 122 ORDER BY (status = ''new''), (IFNULL(milestone, '''') = '''') DESC, 123 milestone, p.value, resolution, time 124 ' WHERE title = 'My Tickets'; 125 126 -- New reports 127 128 INSERT INTO report VALUES(NULL,NULL,'Open Tickets, Mine first',' 129 SELECT p.value AS __color__, 130 (CASE owner 131 WHEN ''$USER'' THEN ''My Tickets'' 132 ELSE ''Open Tickets'' 133 END) AS __group__, 134 id AS ticket, summary, component, status, version, milestone, severity, 135 (CASE status WHEN ''assigned'' THEN owner||'' *'' ELSE owner END) AS owner, 136 time AS created, 137 changetime AS _changetime, description AS _description, 138 reporter AS _reporter 139 FROM ticket t, enum p 140 WHERE status <> ''closed'' 141 AND p.name = t.priority AND p.type = ''priority'' 142 ORDER BY (owner = ''$USER'') DESC, p.value, milestone, severity, time 143 ',' 144 * List all not closed tickets by priority. 145 * Show all tickets owned by the logged in user in a group first. 146 '); 147 148 INSERT INTO report VALUES(NULL,NULL,'Open Tickets by Version',' 149 SELECT p.value AS __color__, 150 (CASE WHEN IFNULL(version, '''') = '''' THEN ''Not Specified'' ELSE ''Version '' || version END) AS __group__, 151 id AS ticket, summary, component, status, milestone, severity, 152 (CASE status WHEN ''assigned'' THEN owner||'' *'' ELSE owner END) AS owner, 153 time AS created, 154 changetime AS _changetime, description AS _description, 155 reporter AS _reporter 156 FROM ticket t, enum p 157 WHERE status <> ''closed'' 158 AND p.name = t.priority AND p.type = ''priority'' 159 ORDER BY (IFNULL(version, '''') = '''') desc,version, p.value, severity, time 160 ',' 161 * List all not closed tickets by priority. 162 * Group results by version. 163 '); 164 165 INSERT INTO report VALUES(NULL,NULL,'Open Tickets by Milestone',' 166 SELECT p.value AS __color__, 167 (CASE WHEN IFNULL(milestone, '''') = '''' THEN ''Not Assigned'' ELSE milestone||'' Release'' END) AS __group__, 168 id AS ticket, summary, component, status, version, severity, 169 (CASE status WHEN ''assigned'' THEN owner||'' *'' ELSE owner END) AS owner, 170 time AS created, 171 changetime AS _changetime, description AS _description, 172 reporter AS _reporter 173 FROM ticket t, enum p 174 WHERE status <> ''closed'' 175 AND p.name = t.priority AND p.type = ''priority'' 176 ORDER BY (IFNULL(milestone, '''') = '''') DESC,milestone, p.value, severity, time 177 ',' 178 * List all not closed tickets by priority. 179 * Group results by milestone. 180 '); 181 182 INSERT INTO report VALUES(NULL,NULL,'Open Tickets by Owner',' 183 SELECT p.value AS __color__, 184 (CASE WHEN IFNULL(owner, '''') = '''' THEN ''Not Assigned'' ELSE owner END) AS __group__, 185 id AS ticket, summary, component, status, version, milestone, severity, 186 time AS created, changetime AS _changetime, description AS _description, 187 reporter AS _reporter 188 FROM ticket t,enum p 189 WHERE status <> ''closed'' 190 AND p.name=t.priority AND p.type=''priority'' 191 ORDER BY (IFNULL(owner, '''') = '''') DESC, owner, p.value, severity, time 192 ',' 193 List not closed tickets, group by ticket owner, sorted by priority. 194 '); 195 196 INSERT INTO report VALUES(NULL,NULL,'Open Tickets by Status',' 197 SELECT p.value AS __color__, 198 status AS __group__, 199 id AS ticket, summary, component, version, milestone, severity, owner, 200 time AS created, 201 changetime AS _changetime, description AS _description, 202 reporter AS _reporter 203 FROM ticket t, enum q, enum p 204 WHERE status <> ''closed'' 205 AND q.name = t.status AND q.type = ''status'' 206 AND p.name = t.priority AND p.type = ''priority'' 207 ORDER BY q.value, p.value, severity, time 208 ',' 209 * List all not closed tickets by priority. 210 * Group results by status. 211 '); 212 213 INSERT INTO report VALUES(NULL,NULL,'Resolved Tickets, Mine first',' 214 SELECT p.value AS __color__, 215 (CASE owner 216 WHEN ''$USER'' THEN ''My Tickets'' 217 ELSE ''Active Tickets'' 218 END) AS __group__, 219 id AS ticket, summary, component, version, milestone, severity, 220 (CASE status WHEN ''assigned'' THEN owner||'' *'' ELSE owner END) AS owner, 221 time AS created, 222 changetime AS _changetime, description AS _description, 223 reporter AS _reporter 224 FROM ticket t, enum p 225 WHERE status = ''resolved'' 226 AND p.name = t.priority AND p.type = ''priority'' 227 ORDER BY (owner = ''$USER'') DESC, p.value, milestone, severity, time 228 ',' 229 * List all resolved tickets by priority. 230 * Show all tickets owned by the logged in user in a group first. 231 '); 232 233 INSERT INTO report VALUES(NULL,NULL,'Resolved Tickets by Milestone',' 234 SELECT p.value AS __color__, 235 (CASE WHEN IFNULL(milestone, '''') = '''' THEN ''Not Assigned'' ELSE milestone||'' Release'' END) AS __group__, 236 id AS ticket, summary, component, version, severity, 237 (CASE status WHEN ''assigned'' THEN owner||'' *'' ELSE owner END) AS owner, 238 time AS created, 239 changetime AS _changetime, description AS _description, 240 reporter AS _reporter 241 FROM ticket t, enum p 242 WHERE status = ''resolved'' 243 AND p.name = t.priority AND p.type = ''priority'' 244 ORDER BY (IFNULL(milestone, '''') = '''') DESC,milestone, p.value, severity, time 245 ',' 246 List resolved tickets, sorted by priority, grouped by milestone 247 '); 248 249 INSERT INTO report VALUES(NULL,NULL,'Resolved Tickets by Owner',' 250 SELECT p.value AS __color__, 251 (CASE WHEN IFNULL(owner, '''') = '''' THEN ''Not Assigned'' ELSE owner END) AS __group__, 252 id AS ticket, summary, component, version, milestone, severity, time AS created, 253 changetime AS _changetime, description AS _description, 254 reporter AS _reporter 255 FROM ticket t,enum p 256 WHERE status = ''resolved'' 257 AND p.name=t.priority AND p.type=''priority'' 258 ORDER BY (IFNULL(owner, '''') = '''') DESC, owner, p.value, severity, time 259 ',' 260 List resolved tickets, group by ticket owner, sorted by priority. 261 '); 262 263 INSERT INTO report VALUES(NULL,NULL,'Completed Tickets by Milestone (Full Description)',' 264 SELECT p.value AS __color__, 265 (CASE WHEN IFNULL(milestone, '''') = '''' THEN ''Not Assigned'' ELSE milestone||'' Release'' END) AS __group__, 266 id AS ticket, summary, component, status, version, severity, time AS created, 267 description AS _description_, 268 changetime AS _changetime, reporter AS _reporter 269 FROM ticket t, enum p 270 WHERE status IN (''verified'', ''closed'') 271 AND p.name = t.priority AND p.type = ''priority'' 272 ORDER BY (IFNULL(milestone, '''') = '''') DESC,milestone, p.value, severity, time 273 ',' 274 Release Notes: List verified and closed tickets, group by milestone, include description. 275 '); 276 """ 277 278 def do_upgrade(env, ver, cursor): 279 cursor.execute(sql) -
trac/upgrades/__init__.py
1 __all__ = ['db2', 'db3', 'db4', 'db5', 'db6', 'db7' ]1 __all__ = ['db2', 'db3', 'db4', 'db5', 'db6', 'db7', 'db8'] -
trac/Ticket.py
51 51 return 52 52 if not self._old.has_key(name): 53 53 self._old[name] = self.get(name, None) 54 elif self._old[name] == value: 55 del self._old[name] 54 56 self.data[name] = value 55 57 56 58 def _forget_changes(self): … … 130 132 131 133 if not self._old and not comment: return # Not modified 132 134 133 # If the component is changed on a 'new' ticket then owner field134 # is updated accordingly. (#623).135 if self['status'] == 'new' and self._old.has_key('component') and \136 not self._old.has_key('owner'):137 cursor.execute('SELECT owner FROM component '138 'WHERE name=%s', self._old['component'])139 row = cursor.fetchone()140 # If the old component has been removed from the database141 # then we just leave the owner as is.142 if row:143 old_owner = row[0]144 if self['owner'] == old_owner:145 cursor.execute('SELECT owner FROM component '146 'WHERE name=%s', self['component'])147 self['owner'] = cursor.fetchone()[0]148 149 135 for name in self._old.keys(): 150 136 if name[:7] == 'custom_': 151 137 fname = name[7:] … … 268 254 i += 1 269 255 270 256 257 def get_workflow(env, db, user): 258 # from trac.workflows.Simple import SimpleWorkflow 259 # return SimpleWorkflow(env, db, user) 260 modulename = env.get_config('ticket', 'workflow', \ 261 'trac.workflows.SimpleWorkflow') 262 i = modulename.rfind('.') 263 if i == -1: 264 classname = modulename 265 else: 266 classname = modulename[i+1:] 267 268 module = __import__(modulename, globals(), locals(), [classname]) 269 constructor = getattr(module, classname) 270 workflow = constructor(env, db, user) 271 272 from workflows.Base import WorkflowBase 273 if not isinstance(workflow, WorkflowBase): 274 raise EnvironmentError, "Workflow class %s from %s must be " \ 275 "descendant of class WorkflowBase from " \ 276 "trac.workflows.base" \ 277 % (classname, modulename) 278 279 return workflow 280 281 271 282 class NewticketModule(Module): 272 283 template_name = 'newticket.cs' 273 284 274 def create_ticket(self): 275 if not self.args.get('summary'): 276 raise util.TracError('Tickets must contain Summary.') 277 278 ticket = Ticket() 279 ticket.populate(self.args) 285 def create_ticket(self, ticket, workflow): 280 286 ticket.setdefault('reporter',self.req.authname) 281 287 282 # The owner field defaults to the component owner 283 cursor = self.db.cursor() 284 if ticket.get('component') and ticket.get('owner', '') == '': 285 cursor.execute('SELECT owner FROM component ' 286 'WHERE name=%s', ticket['component']) 287 owner = cursor.fetchone()[0] 288 ticket['owner'] = owner 289 288 workflow.on_create(ticket) 290 289 tktid = ticket.insert(self.db) 291 290 292 291 # Notify … … 298 297 def render (self): 299 298 self.perm.assert_permission(perm.TICKET_CREATE) 300 299 301 if self.args.has_key('create'): 302 self.create_ticket() 300 ticket = Ticket() 303 301 304 ticket = Ticket() 302 preview = self.args.has_key('preview') 303 do_create = self.args.has_key('create') 305 304 ticket.populate(self.args) 305 306 workflow = get_workflow(self.env, self.db, self.req.authname) 307 308 # Validate the ticket 309 err = [] 310 if preview or do_create: 311 err.extend(workflow.validate(ticket, self.args)) 312 if len(err) != 0: preview = 1 313 314 # Create the ticket if not in preview mode 315 if not preview and do_create: 316 workflow.do_action(ticket, 'create', self.args) 317 self.create_ticket(ticket, workflow) 318 306 319 ticket.setdefault('component', 307 320 self.env.get_config('ticket', 'default_component')) 308 321 ticket.setdefault('milestone', … … 324 337 evals = util.mydict(zip(ticket.keys(), 325 338 map(lambda x: util.escape(x), ticket.values()))) 326 339 util.add_to_hdf(evals, self.req.hdf, 'newticket') 340 if len(err) != 0: 341 self.req.hdf.setValue('newticket.workflow.error', 342 wiki_to_html(' * ' + '\n * '.join(err), 343 self.req.hdf, self.env, self.db)) 344 tpl = workflow.get_actions_template(ticket) 345 if tpl: 346 self.req.hdf.setValue('newticket.workflow.template', tpl) 347 workflow.init_template(ticket, self.req.hdf) 327 348 328 349 util.sql_to_hdf(self.db, 'SELECT name FROM component ORDER BY name', 329 350 self.req.hdf, 'newticket.components') … … 338 359 class TicketModule (Module): 339 360 template_name = 'ticket.cs' 340 361 341 def save_changes (self, id):362 def save_changes (self, ticket, workflow): 342 363 self.perm.assert_permission (perm.TICKET_MODIFY) 343 ticket = Ticket(self.db, id)344 364 345 if not self.args.get('summary'):346 raise util.TracError('Tickets must contain Summary.')347 348 365 if self.args.has_key('description'): 349 366 self.perm.assert_permission (perm.TICKET_ADMIN) 350 367 351 368 if self.args.has_key('reporter'): 352 369 self.perm.assert_permission (perm.TICKET_ADMIN) 353 370 354 # TODO: this should not be hard-coded like this355 action = self.args.get('action', None)356 if action == 'accept':357 ticket['status'] = 'assigned'358 ticket['owner'] = self.req.authname359 if action == 'resolve':360 ticket['status'] = 'closed'361 ticket['resolution'] = self.args.get('resolve_resolution')362 elif action == 'reassign':363 ticket['owner'] = self.args.get('reassign_owner')364 ticket['status'] = 'new'365 elif action == 'reopen':366 ticket['status'] = 'reopened'367 ticket['resolution'] = ''368 369 ticket.populate(self.args)370 371 371 now = int(time.time()) 372 372 373 workflow.on_save(ticket) 373 374 ticket.save_changes(self.db, 374 375 self.args.get('author', self.req.authname), 375 376 self.args.get('comment'), … … 377 378 378 379 tn = TicketNotifyEmail(self.env) 379 380 tn.notify(ticket, newticket=0, modtime=now) 380 self.req.redirect(self.env.href.ticket( id))381 self.req.redirect(self.env.href.ticket(ticket['id'])) 381 382 382 383 def insert_ticket_data(self, hdf, id, ticket, reporter_id): 383 384 """Insert ticket data into the hdf""" … … 437 438 def render (self): 438 439 self.perm.assert_permission (perm.TICKET_VIEW) 439 440 440 action = self.args.get('action', 'view')441 preview = self.args.has_key('preview')442 443 441 if not self.args.has_key('id'): 444 442 self.req.redirect(self.env.href.wiki()) 445 443 446 444 id = int(self.args.get('id')) 445 ticket = Ticket(self.db, id) 447 446 448 if not preview \ 449 and action in ['leave', 'accept', 'reopen', 'resolve', 'reassign']: 450 self.save_changes (id) 447 action = self.args.get('action', None) 448 preview = self.args.has_key('preview') 449 if action or preview: 450 ticket.populate(self.args) 451 451 452 ticket = Ticket(self.db, id) 452 workflow = get_workflow(self.env, self.db, self.req.authname) 453 454 # Validate ticket 455 err = [] 456 if action or preview: 457 actions = workflow.get_actions(ticket) 458 if action not in actions: 459 err.append("Invalid action '''%s''' is performed on the ticket. " \ 460 "Allowed actions are <''%s''>." % \ 461 (action, ', '.join(actions))) 462 err.extend(workflow.validate(ticket, self.args)) 463 if len(err) != 0: preview = 1 464 465 # Save changes if not in preview mode 466 if not preview and action: 467 workflow.do_action(ticket, action, self.args) 468 self.save_changes(ticket, workflow) 469 453 470 reporter_id = util.get_reporter_id(self.req) 454 471 455 472 if preview: 456 # Use user supplied values 457 for field in Ticket.std_fields: 458 if self.args.has_key(field) and field != 'reporter': 459 ticket[field] = self.args.get(field) 460 self.req.hdf.setValue('ticket.action', action) 473 if action: self.req.hdf.setValue('ticket.action', action) 461 474 reporter_id = self.args.get('author') 462 475 comment = self.args.get('comment') 463 476 if comment: … … 468 481 self.req.hdf, self.env, self.db)) 469 482 470 483 self.insert_ticket_data(self.req.hdf, id, ticket, reporter_id) 484 if len(err) != 0: 485 self.req.hdf.setValue('ticket.workflow.error', 486 wiki_to_html(' * ' + '\n * '.join(err), 487 self.req.hdf, self.env, self.db)) 488 tpl = workflow.get_actions_template(ticket) 489 if tpl: 490 self.req.hdf.setValue('ticket.workflow.template', tpl) 491 workflow.init_template(ticket, self.req.hdf) 471 492 472 493 cursor = self.db.cursor() 473 494 cursor.execute("SELECT max(id) FROM ticket") -
trac/WikiFormatter.py
125 125 elif row[1] == 'closed': 126 126 return '<a href="%s" title="CLOSED : %s"><del>#%d</del></a>' % (self._href.ticket(number), summary, number) 127 127 else: 128 return '<a href="%s" title="%s ">#%d</a>' % (self._href.ticket(number), summary, number)128 return '<a href="%s" title="%s : %s">#%d</a>' % (self._href.ticket(number), row[1].upper(), summary, number) 129 129 130 130 def _changesethref_formatter(self, match, fullmatch): 131 131 number = int(match[1:-1]) … … 158 158 elif row[1] == 'closed': 159 159 return self._href.ticket(args), '<del>%s:%s</del>' % (module, args), 0, 'CLOSED: ' + summary 160 160 else: 161 return self._href.ticket(args), '%s:%s' % (module, args), 0, summary161 return self._href.ticket(args), '%s:%s' % (module, args), 0, row[1].upper() + ': ' + summary 162 162 else: 163 163 return self._href.ticket(args), '%s:%s' % (module, args), 1, '' 164 164 elif module == 'wiki': -
templates/ticket_workflow_timetrack.cs
1 <?cs 2 if ticket.workflow.proxy_template ?><?cs 3 include ticket.workflow.proxy_template ?><?cs 4 /if ?> 5 6 <div> 7 <label for="time_delta">action time:</label> 8 <input type="text" id="time_delta" name="time_delta" size="10" 9 value="<?cs var args.time_delta ?>" /> 10 </div> -
templates/ticket_workflow_qarmt.cs
1 <?cs 2 if !ticket.action ?><?cs 3 set:ticket.action = 'leave' ?><?cs 4 /if ?><?cs 5 def action_radio(id) ?> 6 <input type="radio" id="<?cs var id ?>" name="action" value="<?cs var id ?>" 7 <?cs if $ticket.action == $id ?> checked="checked"<?cs /if ?> /><?cs 8 /def ?> 9 10 <?cs 11 if ticket.workflow.action.leave ?><?cs 12 call:action_radio('leave') ?> 13 <label for="leave">leave as <?cs var:ticket.status ?></label><br /><?cs 14 /if ?><?cs 15 if ticket.workflow.action.accept ?><?cs 16 call action_radio('accept') ?> 17 <label for="accept">accept ticket</label><br /><?cs 18 /if ?><?cs 19 if ticket.workflow.action.resolve ?><?cs 20 call:action_radio('resolve') ?> 21 <label for="resolve">resolve</label> 22 <label for="resolve_resolution">as:</label><?cs 23 call:hdf_select(enums.resolution, "resolve_resolution", 24 args.resolve_resolution) ?><br /><?cs 25 /if ?><?cs 26 if ticket.workflow.action.verify ?><?cs 27 call action_radio('verify') ?> 28 <label for="verify">verify ticket</label><br /><?cs 29 /if ?><?cs 30 if ticket.workflow.action.close ?><?cs 31 call action_radio('close') ?> 32 <label for="close">close ticket</label><br /><?cs 33 /if ?><?cs 34 if ticket.workflow.action.reopen ?><?cs 35 call:action_radio('reopen') ?> 36 <label for="reopen">reopen ticket</label><br /><?cs 37 /if ?><?cs 38 if ticket.workflow.action.retest ?><?cs 39 call:action_radio('retest') ?> 40 <label for="retest">retest ticket</label><br /><?cs 41 /if ?><?cs 42 if ticket.workflow.action.reassign ?><?cs 43 call:action_radio('reassign') ?> 44 <label for="reassign">reassign</label> 45 <label for="reassign_owner">to:</label><?cs 46 if:args.reassign_to ?><?cs 47 set default_owner=args.reassign_to ?><?cs 48 else ?><?cs 49 set default_owner=trac.authname ?><?cs 50 /if ?><?cs 51 if:len(ticket.userlist) ?><?cs 52 call:hdf_select(ticket.userlist, "reassign_owner", default_owner) ?><?cs 53 else ?> 54 <input type="text" id="reassign_owner" name="reassign_owner" size="40" 55 value="<?cs var:default_owner ?>" /><?cs 56 /if ?><?cs 57 /if ?> 58 59 <?cs 60 if ticket.workflow.action.resolve || ticket.workflow.action.reassign ?> 61 <script type="text/javascript"><?cs 62 if ticket.workflow.action.resolve ?> 63 var resolve = document.getElementById("resolve");<?cs 64 /if ?><?cs 65 if ticket.workflow.action.reassign ?> 66 var reassign = document.getElementById("reassign");<?cs 67 /if ?> 68 var updateActionFields = function() {<?cs 69 if ticket.workflow.action.resolve ?> 70 enableControl('resolve_resolution', resolve.checked);<?cs 71 /if ?><?cs 72 if ticket.workflow.action.reassign ?> 73 enableControl('reassign_owner', reassign.checked);<?cs 74 /if ?> 75 }; 76 addEvent(window, 'load', updateActionFields);<?cs 77 if ticket.workflow.action.leave ?> 78 addEvent(document.getElementById("leave"), 'click', updateActionFields);<?cs 79 /if ?><?cs 80 if ticket.workflow.action.accept ?> 81 addEvent(document.getElementById("accept"), 'click', updateActionFields);<?cs 82 /if ?><?cs 83 if ticket.workflow.action.resolve ?> 84 addEvent(resolve, 'click', updateActionFields);<?cs 85 /if ?><?cs 86 if ticket.workflow.action.verify ?> 87 addEvent(document.getElementById("verify"), 'click', updateActionFields);<?cs 88 /if ?><?cs 89 if ticket.workflow.action.close ?> 90 addEvent(document.getElementById("close"), 'click', updateActionFields);<?cs 91 /if ?><?cs 92 if ticket.workflow.action.reopen ?> 93 addEvent(document.getElementById("reopen"), 'click', updateActionFields);<?cs 94 /if ?><?cs 95 if ticket.workflow.action.retest ?> 96 addEvent(document.getElementById("retest"), 'click', updateActionFields);<?cs 97 /if ?><?cs 98 if ticket.workflow.action.reassign ?> 99 addEvent(reassign, 'click', updateActionFields);<?cs 100 /if ?> 101 </script> 102 <?cs /if ?> -
templates/ticket.cs
33 33 <div id="ticket"> 34 34 <div class="date"><?cs var:ticket.opened ?></div> 35 35 <h1>Ticket #<?cs var:ticket.id ?> <?cs 36 if:ticket. status == 'closed' ?>(Closed: <?cs var:ticket.resolution ?>)<?cs36 if:ticket.resolution ?>(<?cs var:ticket.status ?>: <?cs var:ticket.resolution ?>)<?cs 37 37 elif:ticket.status != 'new' ?>(<?cs var:ticket.status ?>)<?cs 38 38 /if ?></h1> 39 39 <h2><?cs var:ticket.summary ?></h2> … … 207 207 </div><?cs /if ?> 208 208 </fieldset> 209 209 210 <fieldset id="action"> 211 <legend>Action</legend><?cs 212 if:!ticket.action ?><?cs set:ticket.action = 'leave' ?><?cs 213 /if ?><?cs 214 def:action_radio(id) ?> 215 <input type="radio" id="<?cs var:id ?>" name="action" value="<?cs 216 var:id ?>"<?cs if:$ticket.action == $id ?> checked="checked"<?cs 217 /if ?> /><?cs 218 /def ?> 219 <?cs call:action_radio('leave') ?> 220 <label for="leave">leave as <?cs var:ticket.status ?></label><br /><?cs 221 if $ticket.status == "new" ?> 222 <?cs call:action_radio('accept') ?> 223 <label for="accept">accept ticket</label><br /><?cs 224 /if ?><?cs 225 if $ticket.status == "closed" ?> 226 <?cs call:action_radio('reopen') ?> 227 <label for="reopen">reopen ticket</label><br /><?cs 228 /if ?><?cs 229 if $ticket.status == "new" || $ticket.status == "assigned" || $ticket.status == "reopened" ?> 230 <?cs call:action_radio('resolve') ?> 231 <label for="resolve">resolve</label> 232 <label for="resolve_resolution">as:</label> 233 <?cs call:hdf_select(enums.resolution, "resolve_resolution", args.resolve_resolution) ?><br /> 234 <?cs call:action_radio('reassign') ?> 235 <label for="reassign">reassign</label> 236 <label for="reassign_owner">to:</label> 237 <input type="text" id="reassign_owner" name="reassign_owner" size="40" value="<?cs 238 if:args.reassign_to ?><?cs var:args.reassign_to ?><?cs 239 else ?><?cs var:trac.authname ?><?cs /if ?>" /><?cs 240 /if ?><?cs 241 if $ticket.status == "new" || $ticket.status == "assigned" || $ticket.status == "reopened" ?> 242 <script type="text/javascript"> 243 var resolve = document.getElementById("resolve"); 244 var reassign = document.getElementById("reassign"); 245 var updateActionFields = function() { 246 enableControl('resolve_resolution', resolve.checked); 247 enableControl('reassign_owner', reassign.checked); 248 }; 249 addEvent(window, 'load', updateActionFields); 250 addEvent(document.getElementById("leave"), 'click', updateActionFields);<?cs 251 if $ticket.status == "new" ?> 252 addEvent(document.getElementById("accept"), 'click', updateActionFields);<?cs 253 /if ?> 254 addEvent(resolve, 'click', updateActionFields); 255 addEvent(reassign, 'click', updateActionFields); 256 </script><?cs 257 /if ?> 258 </fieldset> 210 <?cs if ticket.workflow.template ?> 211 <fieldset id="action"> 212 <legend>Action</legend> 213 <?cs include ticket.workflow.template ?> 214 </fieldset> 215 <?cs /if ?> 259 216 217 <?cs if ticket.workflow.error ?> 218 <div class="system-message"> 219 <h2>Ticket Error</h2> 220 <p class="message"><?cs var ticket.workflow.error ?></p> 221 <strong>The ticket will not be saved.</strong> 222 </div> 223 <?cs /if ?> 224 225 260 226 <div class="buttons"> 261 227 <input type="reset" value="Reset" /> 262 228 <input type="submit" name="preview" value="Preview" /> -
templates/roadmap.cs
22 22 var:milestone.name ?></em></a></h2> 23 23 <p class="date"><?cs if:milestone.date ?> 24 24 <?cs var:milestone.date ?><?cs else ?>No date set<?cs /if ?> 25 <?cs if:milestone.owner ?> (<?cs var:milestone.owner ?>)<?cs /if ?> 25 26 </p> 26 27 <?cs with:stats = milestone.stats ?> 27 28 <?cs if:#stats.total_tickets > #0 ?> -
templates/ticket_workflow_simple.cs
1 <?cs 2 if !ticket.action ?><?cs 3 set:ticket.action = 'leave' ?><?cs 4 /if ?><?cs 5 def action_radio(id) ?> 6 <input type="radio" id="<?cs var id ?>" name="action" value="<?cs var id ?>" 7 <?cs if $ticket.action == $id ?> checked="checked"<?cs /if ?> /><?cs 8 /def ?> 9 10 <?cs 11 if ticket.workflow.action.leave ?><?cs 12 call:action_radio('leave') ?> 13 <label for="leave">leave as <?cs var:ticket.status ?></label><br /><?cs 14 /if ?><?cs 15 if ticket.workflow.action.accept ?><?cs 16 call action_radio('accept') ?> 17 <label for="accept">accept ticket</label><br /><?cs 18 /if ?><?cs 19 if ticket.workflow.action.resolve ?><?cs 20 call:action_radio('resolve') ?> 21 <label for="resolve">resolve</label> 22 <label for="resolve_resolution">as:</label><?cs 23 call:hdf_select(enums.resolution, "resolve_resolution", 24 args.resolve_resolution) ?><br /><?cs 25 /if ?><?cs 26 if ticket.workflow.action.reopen ?><?cs 27 call:action_radio('reopen') ?> 28 <label for="reopen">reopen ticket</label><br /><?cs 29 /if ?><?cs 30 if ticket.workflow.action.reassign ?><?cs 31 call:action_radio('reassign') ?> 32 <label for="reassign">reassign</label> 33 <label for="reassign_owner">to:</label><?cs 34 if:args.reassign_to ?><?cs 35 set default_owner=args.reassign_to ?><?cs 36 else ?><?cs 37 set default_owner=trac.authname ?><?cs 38 /if ?><?cs 39 if:len(ticket.userlist) ?><?cs 40 call:hdf_select(ticket.userlist, "reassign_owner", default_owner) ?><?cs 41 else ?> 42 <input type="text" id="reassign_owner" name="reassign_owner" size="40" 43 value="<?cs var:default_owner ?>" /><?cs 44 /if ?><?cs 45 /if ?> 46 47 <?cs 48 if ticket.workflow.action.resolve || ticket.workflow.action.reassign ?> 49 <script type="text/javascript"><?cs 50 if ticket.workflow.action.resolve ?> 51 var resolve = document.getElementById("resolve");<?cs 52 /if ?><?cs 53 if ticket.workflow.action.reassign ?> 54 var reassign = document.getElementById("reassign");<?cs 55 /if ?> 56 var updateActionFields = function() {<?cs 57 if ticket.workflow.action.resolve ?> 58 enableControl('resolve_resolution', resolve.checked);<?cs 59 /if ?><?cs 60 if ticket.workflow.action.reassign ?> 61 enableControl('reassign_owner', reassign.checked);<?cs 62 /if ?> 63 }; 64 addEvent(window, 'load', updateActionFields);<?cs 65 if ticket.workflow.action.leave ?> 66 addEvent(document.getElementById("leave"), 'click', updateActionFields);<?cs 67 /if ?><?cs 68 if ticket.workflow.action.accept ?> 69 addEvent(document.getElementById("accept"), 'click', updateActionFields);<?cs 70 /if ?><?cs 71 if ticket.workflow.action.resolve ?> 72 addEvent(resolve, 'click', updateActionFields);<?cs 73 /if ?><?cs 74 if ticket.workflow.action.reopen ?> 75 addEvent(document.getElementById("reopen"), 'click', updateActionFields);<?cs 76 /if ?><?cs 77 if ticket.workflow.action.reassign ?> 78 addEvent(reassign, 'click', updateActionFields);<?cs 79 /if ?> 80 </script> 81 <?cs /if ?> -
templates/timeline_rss.cs
43 43 $item.href, $item.msg_escwiki) 44 44 ?><?cs elif:item.type == #3 45 45 ?><!-- Closed ticket --> <?cs call:rss_item('Ticket', 46 'Ticket #'+$item.idata+' resolved: '+$item.shortmsg,46 'Ticket #'+$item.idata+' closed: '+$item.shortmsg, 47 47 $item.href, $item.msg_escwiki) 48 48 ?><?cs elif:item.type == #4 49 49 ?><!-- Reopened ticket --><?cs call:rss_item('Ticket', 50 50 '#'+$item.idata+' reopened: '+$item.shortmsg, 51 51 $item.href, $item.msg_escwiki) 52 ?><?cs elif:item.type == #7 53 ?><!-- Verified ticket --><?cs call:rss_item('Ticket', 54 '#'+$item.idata+' verified: '+$item.shortmsg, 55 $item.href, $item.msg_escwiki) 56 ?><?cs elif:item.type == #8 57 ?><!-- Resolved ticket --> <?cs call:rss_item('Ticket', 58 'Ticket #'+$item.idata+' resolved: '+$item.shortmsg, 59 $item.href, $item.msg_escwiki) 60 ?><?cs elif:item.type == #9 61 ?><!-- Retested ticket --><?cs call:rss_item('Ticket', 62 '#'+$item.idata+' retested: '+$item.shortmsg, 63 $item.href, $item.msg_escwiki) 52 64 ?><?cs elif:item.type == #5 53 65 ?><!-- Wiki change --><?cs call:rss_item('Wiki', 54 66 $item.tdata+" page edited.", -
templates/newticket.cs
69 69 </div><?cs /if ?> 70 70 </fieldset> 71 71 72 <?cs if newticket.workflow.template ?> 73 <fieldset id="action"> 74 <legend>Action</legend> 75 <?cs include newticket.workflow.template ?> 76 </fieldset> 77 <?cs /if ?> 78 79 <?cs if newticket.workflow.error ?> 80 <div class="system-message"> 81 <h2>Ticket Error</h2> 82 <p class="message"><?cs var newticket.workflow.error ?></p> 83 <strong>The ticket will not be created.</strong> 84 </div> 85 <?cs /if ?> 86 72 87 <div class="buttons"> 73 <input type="submit" value="Preview" /> 88 <input type="submit" name="preview" value="Preview" /> 74 89 <input type="submit" name="create" value="Submit ticket" /> 75 90 </div> 76 91 </form> -
templates/milestone.cs
56 56 var:milestone.name ?>" /> 57 57 </div> 58 58 <div class="field"> 59 <label for="owner">Owner of the milestone (Release Manager):</label><br /> 60 <input type="text" id="owner" name="owner" size="32" value="<?cs 61 var:milestone.owner ?>" /> 62 </div> 63 <div class="field"> 59 64 <label for="datemode">Completion date:</label><br /> 60 65 <select name="datemode" id="datemode" 61 66 onchange="enableControl('date',this.value=='manual'); … … 108 113 <?cs else ?> 109 114 <em class="date"><?cs if:milestone.date ?> 110 115 <?cs var:milestone.date ?><?cs else ?>No date set<?cs /if ?> 116 <?cs if:milestone.owner ?> (<?cs var:milestone.owner ?>)<?cs /if ?> 111 117 </em> 112 118 <div class="descr"><?cs var:milestone.descr ?></div> 113 119 <?cs /if ?> … … 120 126 <thead><tr> 121 127 <th class="name" rowspan="2"><?cs var:milestone.stats.grouped_by ?></th> 122 128 <th class="tickets" scope="col" colspan="2">Tickets</th> 123 <th class="progress" rowspan="2">Percent Resolved</th>129 <th class="progress" rowspan="2">Percent Completed</th> 124 130 </tr><tr> 125 131 <th class="open" scope="col">Active</th> 126 132 <th class="closed" scope="col">Closed</th> -
templates/timeline.cs
60 60 61 61 <?cs each:item = timeline.items ?> 62 62 <?cs call:day_separator(item.date) ?> 63 <?cs if:item.tdata && item.message ?> 64 <?cs set:ticketmsg = $item.tdata + ' - ' + $item.message ?> 65 <?cs elif:item.tdata ?> 66 <?cs set:ticketmsg = $item.tdata ?> 67 <?cs else ?> 68 <?cs set:ticketmsg = $item.message ?> 69 <?cs /if ?> 63 70 <?cs if:item.type == #1 ?><!-- Changeset --> 64 71 <?cs call:tlitem(item.href, 'changeset', 65 72 'Changeset <em>['+$item.idata+']</em> by '+$item.author,$item.node_list+item.message) ?> … … 67 74 <?cs call:tlitem(item.href, 'newticket', 68 75 'Ticket <em>#'+$item.idata+'</em> created by '+$item.author, item.message) ?> 69 76 <?cs elif:item.type == #3 ?><!-- Closed ticket --> 70 <?cs if:item.message ?>71 <?cs set:imessage = ' - ' + $item.message ?>72 <?cs else ?>73 <?cs set:imessage = '' ?>74 <?cs /if ?>75 77 <?cs call:tlitem(item.href, 'closedticket', 76 'Ticket <em>#'+$item.idata+'</em> resolved by '+$item.author, 77 $item.tdata+$imessage) ?> 78 'Ticket <em>#'+$item.idata+'</em> closed by '+$item.author, $ticketmsg) ?> 78 79 <?cs elif:item.type == #4 ?><!-- Reopened ticket --> 79 <?cs call:tlitem(item.href, 'newticket', 80 'Ticket <em>#'+$item.idata+'</em> reopened by '+$item.author, '') ?> 80 <?cs call:tlitem(item.href, 'reopenedticket', 81 'Ticket <em>#'+$item.idata+'</em> reopened by '+$item.author, item.message) ?> 82 <?cs elif:item.type == #7 ?><!-- Verified ticket --> 83 <?cs call:tlitem(item.href, 'closedticket', 84 'Ticket <em>#'+$item.idata+'</em> verified by '+$item.author, $ticketmsg) ?> 85 <?cs elif:item.type == #8 ?><!-- Resolved ticket --> 86 <?cs call:tlitem(item.href, 'resolvedticket', 87 'Ticket <em>#'+$item.idata+'</em> resolved by '+$item.author, $ticketmsg) ?> 88 <?cs elif:item.type == #9 ?><!-- Retested ticket --> 89 <?cs call:tlitem(item.href, 'reopenedticket', 90 'Ticket <em>#'+$item.idata+'</em> retested by '+$item.author, item.message) ?> 81 91 <?cs elif:item.type == #5 ?><!-- Wiki change --> 82 92 <?cs call:tlitem(item.href, 'wiki', 83 93 '<em>'+$item.tdata+'</em> edited by '+$item.author, item.message) ?>
