Edgewall Software

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)

Patch for the changes: Synchonized with milestone:0.8.2

  • htdocs/css/timeline.css

     
    4040/* Apply icon background-image twice to avoid hover-flicker in IE/Win */ 
    4141dt.changeset, dt.changeset a { background-image: url(../changeset.png) !important } 
    4242dt.newticket, dt.newticket a { background-image: url(../newticket.png) !important } 
     43dt.resolvedticket, dt.resolvedticket a { background-image: url(../resolvedticket.png) !important } 
     44dt.reopenedticket, dt.reopenedticket a { background-image: url(../reopenedticket.png) !important } 
    4345dt.closedticket, dt.closedticket a { background-image: url(../closedticket.png) !important } 
    4446dt.wiki, dt.wiki a { background-image: url(../wiki.png) !important } 
    4547dt.milestone, dt.milestone a { background-image: url(../milestone.png) !important } 
  • wiki-default/TracIni

     
    2727See also: TracLogging 
    2828 
    2929== [ticket] == 
     30|| workflow || Ticket workflow class.  If not specified, it is ''trac.workflows.SimpleWorkflow'' || 
    3031|| default_version   || Default version for newly created tickets || 
    3132|| default_severity  || Default severity for newly created tickets || 
    3233|| default_priority  || Default priority for newly created tickets || 
  • wiki-default/TracAdmin

     
    2828permission add <user> <action> [action] [...]     -- Add a new permission rule                              
    2929permission remove <user> <action> [action] [...]  -- Remove permission rule                                 
    3030component list                                    -- Show available components                              
    31 component add <name> <owner>                      -- Add a new component                                    
     31component add <name> <owner> [<qaowner>]          -- Add a new component                                    
    3232component rename <name> <newname>                 -- Rename a component                                     
    3333component remove <name>                           -- Remove/uninstall component                             
    34 component chown <name> <owner>                    -- Change component ownership                             
     34component chown <name> <owner> [<qaowner>]        -- Change component ownership                             
    3535priority list                                     -- Show possible ticket priorities                        
    3636priority add <value>                              -- Add a priority value option                            
    3737priority change <value> <newvalue>                -- Change a priority value                                
     
    4646version time <name> <time>                        -- Set version date (Format: "Jun 3, 2003")               
    4747version remove <name>                             -- Remove version                                         
    4848milestone list                                    -- Show milestones                                        
    49 milestone add <name> [time]                       -- Add milestone                                          
     49milestone add <name> [<owner> [time]]             -- Add milestone                                          
    5050milestone rename <name> <newname>                 -- Rename milestone                                       
    5151milestone time <name> <time>                      -- Set milestone date (Format: "Jun 3, 2003")             
    5252milestone remove <name>                           -- Remove milestone                                       
     53milestone chown <name> <owner>                    -- Change milestone ownership 
    5354}}} 
    5455 
    5556== Interactive Mode == 
  • scripts/trac-admin

     
    292292 
    293293#    ## Component 
    294294    _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'), 
    296296                       ('component rename <name> <newname>', 'Rename a component'), 
    297297                       ('component remove <name>', 'Remove/uninstall component'), 
    298                        ('component chown <name> <owner>', 'Change component ownership')] 
     298                       ('component chown <name> <owner> [<qaowner>]', 'Change component ownership')] 
    299299 
    300300    def complete_component (self, text, line, begidx, endidx): 
    301301        if begidx in [16,17]: 
     
    311311        try: 
    312312            if arg[0]  == 'list': 
    313313                self._do_component_list() 
    314             elif arg[0] == 'add' and len(arg)==3: 
     314            elif arg[0] == 'add' and len(arg) in [3,4]: 
    315315                name = arg[1] 
    316316                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) 
    318322            elif arg[0] == 'rename' and len(arg)==3: 
    319323                name = arg[1] 
    320324                newname = arg[2] 
     
    322326            elif arg[0] == 'remove'  and len(arg)==2: 
    323327                name = arg[1] 
    324328                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]: 
    326330                name = arg[1] 
    327331                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) 
    329337            else:     
    330338                self.do_help ('component') 
    331339        except Exception, e: 
    332340            print 'Component %s failed:' % arg[0], e 
    333341 
    334342    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) 
    337345 
    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)) 
    341349 
    342350    def _do_component_rename(self, name, newname): 
    343351        cnx = self.db_open() 
     
    362370        data = self.db_execsql("DELETE FROM component WHERE name='%s'" 
    363371                               % (name)) 
    364372 
    365     def _do_component_set_owner(self, name, owner): 
     373    def _do_component_set_owner(self, name, owner, qaowner): 
    366374        cnx = self.db_open() 
    367375        cursor = cnx.cursor () 
    368376        cursor.execute('SELECT name FROM component WHERE name=%s', name) 
    369377        data = cursor.fetchone() 
    370378        if not data: 
    371379            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)) 
    374382 
    375383 
    376384    ## Permission 
     
    797805 
    798806    ## Milestone 
    799807    _help_milestone = [('milestone list', 'Show milestones'), 
    800                        ('milestone add <name> [time]', 'Add milestone'), 
     808                       ('milestone add <name> [<owner> [time]]', 'Add milestone'), 
    801809                       ('milestone rename <name> <newname>', 
    802810                        'Rename milestone'), 
     811                       ('milestone chown <name> <newowner>', 'Change milestone owner'), 
    803812                       ('milestone time <name> <time>', 'Set milestone date (Format: "Jun 3, 2003")'), 
    804813                       ('milestone remove <name>', 'Remove milestone')] 
    805814 
     
    807816 
    808817        if begidx in [15,17]: 
    809818            comp = self.get_milestone_list () 
     819        elif begidx > 15 and line.startswith('milestone chown '): 
     820            comp = self.get_user_list() 
    810821        elif begidx < 15: 
    811             comp = ['list','add','rename','time','remove'] 
     822            comp = ['list','add','rename','chown','time','remove'] 
    812823        return self.word_complete(text, comp) 
    813824 
    814825    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 
    816859 
     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) 
    817865 
     866 
    818867    ## Version 
    819868    _help_version = [('version list', 'Show versions'), 
    820869                       ('version add <name> [time]', 'Add version'), 
     
    834883    def do_version(self, line): 
    835884        self._do_mile_ver('version', line) 
    836885 
    837     # Milestone and Version are identical,  methods 
     886    # Milestone and Version are almost identical,  methods 
    838887 
    839888    def _do_mile_ver(self, type, line): 
    840889        arg = self.arg_tokenize(line) 
     
    925974        else: 
    926975            print >> sys.stderr, 'Unknown time format' 
    927976 
     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 
    928981    _help_upgrade = [('upgrade', 'Upgrade database to current version.')] 
    929982    def do_upgrade(self, line): 
    930983        arg = self.arg_tokenize(line) 
  • setup.py

     
    206206      author_email="info@edgewall.com", 
    207207      license=LICENSE, 
    208208      url=URL, 
    209       packages=['trac', 'trac.upgrades', 'trac.wikimacros', 'trac.mimeviewers'], 
     209      packages=['trac', 'trac.upgrades', 'trac.wikimacros', 'trac.mimeviewers', 
     210                'trac.workflows'], 
    210211      data_files=[(_p('share/trac/templates'), glob('templates/*')), 
    211212                  (_p('share/trac/htdocs'), glob(_p('htdocs/*.*')) + [_p('htdocs/README')]), 
    212213                  (_p('share/trac/htdocs/css'), glob(_p('htdocs/css/*'))), 
  • trac/db_default.py

     
    2121 
    2222 
    2323# Database version identifier. Used for automatic upgrades. 
    24 db_version = 7 
     24db_version = 8 
    2525 
    2626def __mkreports(reps): 
    2727    """Utility function used to create report data in same syntax as the 
     
    125125); 
    126126CREATE TABLE component ( 
    127127         name            text PRIMARY KEY, 
    128          owner           text 
     128         owner           text, 
     129         qaowner         text 
    129130); 
    130131CREATE TABLE milestone ( 
    131132         id              integer PRIMARY KEY, 
    132133         name            text, 
    133134         time            integer, 
     135         owner           text, 
    134136         descr           text, 
    135137         UNIQUE(name) 
    136138); 
     
    209211""", 
    210212""" 
    211213SELECT p.value AS __color__, 
    212    version AS __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,  
    214216   (CASE status WHEN 'assigned' THEN owner||' *' ELSE owner END) AS owner, 
    215217   time AS created, 
    216218   changetime AS _changetime, description AS _description, 
     
    218220  FROM ticket t, enum p 
    219221  WHERE status IN ('new', 'assigned', 'reopened')  
    220222AND p.name = t.priority AND p.type = 'priority' 
    221   ORDER BY (version IS NULL),version, p.value, severity, time 
     223  ORDER BY (IFNULL(version, '') = '') DESC,version, p.value, severity, time 
    222224"""), 
    223225#---------------------------------------------------------------------------- 
    224 ('All Tickets by Milestone', 
     226('Active Tickets by Milestone', 
    225227""" 
    226228This report shows how to color results by priority, 
    227229while grouping results by milestone. 
     
    231233""", 
    232234""" 
    233235SELECT p.value AS __color__, 
    234    milestone||' Release' AS __group__, 
     236   (CASE WHEN IFNULL(milestone, '') = '' THEN 'Not Assigned' ELSE milestone||' Release' END) AS __group__, 
    235237   id AS ticket, summary, component, version, severity,  
    236238   (CASE status WHEN 'assigned' THEN owner||' *' ELSE owner END) AS owner, 
    237239   time AS created, 
     
    240242  FROM ticket t, enum p 
    241243  WHERE status IN ('new', 'assigned', 'reopened')  
    242244AND p.name = t.priority AND p.type = 'priority' 
    243   ORDER BY (milestone IS NULL),milestone, p.value, severity, time 
     245  ORDER BY (IFNULL(milestone, '') = '') DESC,milestone, p.value, severity, time 
    244246"""), 
    245247#---------------------------------------------------------------------------- 
    246248('Assigned, Active Tickets by Owner', 
     
    248250List assigned tickets, group by ticket owner, sorted by priority. 
    249251""", 
    250252""" 
    251  
    252253SELECT p.value AS __color__, 
    253    owner AS __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, 
    255256   changetime AS _changetime, description AS _description, 
    256257   reporter AS _reporter 
    257258  FROM ticket t,enum p 
    258259  WHERE status = 'assigned' 
    259260AND p.name=t.priority AND p.type='priority' 
    260   ORDER BY owner, p.value, severity, time 
     261  ORDER BY (IFNULL(owner, '') = '') DESC, owner, p.value, severity, time 
    261262"""), 
    262263#---------------------------------------------------------------------------- 
    263264('Assigned, Active Tickets by Owner (Full Description)', 
     
    268269""" 
    269270SELECT p.value AS __color__, 
    270271   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, 
    272273   description AS _description_, 
    273274   changetime AS _changetime, reporter AS _reporter 
    274275  FROM ticket t, enum p 
     
    283284""", 
    284285""" 
    285286SELECT p.value AS __color__, 
    286    t.milestone AS __group__, 
     287   (CASE WHEN IFNULL(t.milestone, '') = '' THEN 'Not Assigned' ELSE t.milestone || ' Release' END) AS __group__, 
    287288   (CASE status  
    288289      WHEN 'closed' THEN 'color: #777; background: #ddd; border-color: #ccc;' 
    289290      ELSE  
    290291        (CASE owner WHEN '$USER' THEN 'font-weight: bold' END) 
    291292    END) AS __style__, 
    292293   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, 
    294296   changetime AS modified, 
    295297   time AS _time,reporter AS _reporter 
    296298  FROM ticket t,enum p 
    297299  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'),  
    299301        (CASE status WHEN 'closed' THEN modified ELSE -p.value END) DESC 
    300302"""), 
    301303#---------------------------------------------------------------------------- 
     
    307309""", 
    308310""" 
    309311SELECT 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, 
    313317   changetime AS _changetime, description AS _description, 
    314318   reporter AS _reporter 
    315319  FROM ticket t, enum p 
    316   WHERE t.status IN ('new', 'assigned', 'reopened')  
     320  WHERE t.status <> 'closed'  
    317321AND 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 
    319324"""), 
    320325#---------------------------------------------------------------------------- 
    321326('Active Tickets, Mine first', 
     
    338343  WHERE status IN ('new', 'assigned', 'reopened')  
    339344AND p.name = t.priority AND p.type = 'priority' 
    340345  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""" 
     354SELECT 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'  
     366AND 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""" 
     376SELECT 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' 
     385AND 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""" 
     395SELECT 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'  
     404AND 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""" 
     410List not closed tickets, group by ticket owner, sorted by priority. 
     411""", 
     412""" 
     413SELECT 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' 
     420AND 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""" 
     430SELECT 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'  
     438AND q.name = t.status AND q.type = 'status' 
     439AND 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""" 
     449SELECT 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'  
     461AND 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""" 
     467List resolved tickets, sorted by priority, grouped by milestone 
     468""", 
     469""" 
     470SELECT 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'  
     479AND 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""" 
     485List resolved tickets, group by ticket owner, sorted by priority. 
     486""", 
     487""" 
     488SELECT 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' 
     495AND 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""" 
     501Release Notes: List verified and closed tickets, group by milestone, include description. 
     502""", 
     503""" 
     504SELECT 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') 
     511AND p.name = t.priority AND p.type = 'priority' 
     512  ORDER BY (IFNULL(milestone, '') = '') DESC,milestone, p.value, severity, time 
    341513""")) 
    342514 
    343515 
     
    347519 
    348520# (table, (column1, column2), ((row1col1, row1col2), (row2col1, row2col2))) 
    349521data = (('component', 
    350              ('name', 'owner'), 
    351                (('component1', 'somebody'), 
    352                 ('component2', 'somebody'))), 
     522             ('name', 'owner', 'qaowner'), 
     523               (('component1', 'somebody', 'qasomebody'), 
     524                ('component2', 'somebody', 'qasomebody'))), 
    353525           ('milestone', 
    354526             ('name', 'time'), 
    355527               (('', 0),  
     
    367539               (('status', 'new', 1), 
    368540                ('status', 'assigned', 2), 
    369541                ('status', 'reopened', 3), 
    370                 ('status', 'closed', 4), 
     542                ('status', 'resolved', 4), 
     543                ('status', 'verified', 5), 
     544                ('status', 'closed', 6), 
    371545                ('resolution', 'fixed', 1), 
    372546                ('resolution', 'invalid', 2), 
    373547                ('resolution', 'wontfix', 3), 
     
    426600  ('project', 'footer', 
    427601   ' Visit the Trac open source project at<br />' 
    428602   '<a href="http://trac.edgewall.com/">http://trac.edgewall.com/</a>'), 
     603  ('ticket', 'workflow', 'trac.workflows.QaRmtWorkflow'), 
    429604  ('ticket', 'default_version', ''), 
    430605  ('ticket', 'default_severity', 'normal'), 
    431606  ('ticket', 'default_priority', 'normal'), 
  • trac/Milestone.py

     
    6060    if not group: 
    6161        queries['all_tickets'] = env.href.query({'milestone': milestone}) 
    6262        queries['active_tickets'] = env.href.query({ 
    63             'milestone': milestone, 'status': ['new', 'assigned', 'reopened'] 
     63            'milestone': milestone, 'status': ['new', 'assigned', 'reopened', 'resolved'] 
    6464        }) 
    6565        queries['closed_tickets'] = env.href.query({ 
    66             'milestone': milestone, 'status': 'closed' 
     66            'milestone': milestone, 'status': ['closed', 'verified'] 
    6767        }) 
    6868    else: 
    6969        queries['all_tickets'] = env.href.query({ 
     
    7171        }) 
    7272        queries['active_tickets'] = env.href.query({ 
    7373            'milestone': milestone, grouped_by: group, 
    74             'status': ['new', 'assigned', 'reopened'] 
     74            'status': ['new', 'assigned', 'reopened', 'resolved'] 
    7575        }) 
    7676        queries['closed_tickets'] = env.href.query({ 
    7777            'milestone': milestone, grouped_by: group, 
    78             'status': 'closed' 
     78            'status': ['closed', 'verified'] 
    7979        }) 
    8080    return queries 
    8181 
    8282def calc_ticket_stats(tickets): 
    8383    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'] 
    8585    active_cnt = len(active) 
    8686    closed_cnt = total_cnt - active_cnt 
    8787 
     
    116116                if datestr: 
    117117                    date = self.parse_date(datestr) 
    118118            descr = self.args.get('descr', '') 
     119            owner = self.args.get('owner', '') 
    119120            if not id: 
    120                 self.create_milestone(name, date, descr) 
     121                self.create_milestone(name, date, descr, owner) 
    121122            else: 
    122                 self.update_milestone(id, name, date, descr) 
     123                self.update_milestone(id, name, date, descr, owner) 
    123124        elif id: 
    124125            self.req.redirect(self.env.href.milestone(id)) 
    125126        else: 
     
    141142                            'Invalid Date Format') 
    142143        return seconds 
    143144 
    144     def create_milestone(self, name, date=0, descr=''): 
     145    def create_milestone(self, name, date=0, descr='', owner=''): 
    145146        self.perm.assert_permission(perm.MILESTONE_CREATE) 
    146147        if not name: 
    147148            raise TracError('You must provide a name for the milestone.', 
    148149                            'Required Field Missing') 
    149150        cursor = self.db.cursor() 
    150151        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) 
    153154        self.db.commit() 
    154155        self.req.redirect(self.env.href.milestone(name)) 
    155156 
     
    178179        else: 
    179180            self.req.redirect(self.env.href.milestone(id)) 
    180181 
    181     def update_milestone(self, id, name, date, descr): 
     182    def update_milestone(self, id, name, date, descr, owner): 
    182183        self.perm.assert_permission(perm.MILESTONE_MODIFY) 
    183184        cursor = self.db.cursor() 
    184185        self.log.info("Updating milestone '%s'" % id) 
     
    188189            cursor.execute('UPDATE ticket SET milestone = %s ' 
    189190                            'WHERE milestone = %s', name, id) 
    190191            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) 
    193194            self.db.commit() 
    194195            self.req.redirect(self.env.href.milestone(name)) 
    195196        else: 
     
    222223 
    223224    def get_milestone(self, name): 
    224225        cursor = self.db.cursor() 
    225         cursor.execute("SELECT name, time, descr FROM milestone " 
     226        cursor.execute("SELECT name, time, descr, owner FROM milestone " 
    226227                       "WHERE name = %s ORDER BY time, name", name) 
    227228        row = cursor.fetchone() 
    228229        cursor.close() 
     
    237238        t = row['time'] and int(row['time']) 
    238239        if t > 0: 
    239240            milestone['date'] = time.strftime('%x', time.localtime(t)) 
     241        milestone['owner'] = row['owner'] or '' 
    240242        return milestone 
    241243 
    242244    def render(self): 
  • trac/Timeline.py

     
    5252        REOPENED_TICKET = 4 
    5353        WIKI = 5 
    5454        MILESTONE = 6 
     55        VERIFIED_TICKET = 7 
     56        RESOLVED_TICKET = 8 
     57        RETESTED_TICKET = 9 
    5558 
    5659        q = [] 
    5760        if changeset: 
     
    6063                     "FROM revision WHERE time>=%s AND time<=%s" % 
    6164                     (start, stop)) 
    6265        if tickets: 
     66            # New tickets 
    6367            q.append("SELECT time, id AS idata, '' AS tdata, 2 AS type, " 
    6468                     "summary AS message, reporter AS author " 
    6569                     "FROM ticket WHERE time>=%s AND time<=%s" % 
    6670                     (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 
    7272            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," 
    7382                     "       t2.newvalue AS tdata, 3 AS type," 
    7483                     "       t3.newvalue AS message, t1.author AS author" 
    7584                     " 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'" 
    7887                     "   LEFT OUTER JOIN ticket_change t3 ON t1.time = t3.time" 
    7988                     "     AND t1.ticket = t3.ticket AND t3.field = 'comment'" 
    8089                     " WHERE t1.field = 'status' AND t1.newvalue = 'closed'" 
    81                      "   AND t2.field = 'resolution'" 
    8290                     "   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)) 
    83125        if wiki: 
    84126            q.append("SELECT time, -1 AS idata, name AS tdata, 5 AS type, " 
    85127                     "comment AS message, author " 
     
    87129                     (start, stop)) 
    88130        if milestone: 
    89131            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 "  
    91133                     "FROM milestone WHERE time>=%s AND time<=%s" % 
    92134                     (start, stop)) 
    93135 
  • 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 
     24class 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 
     27from trac.workflows.QaRmtWorkflow import QaRmtWorkflow 
     28 
     29class 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 
     43from trac.workflows.Base import WorkflowBase 
     44 
     45class 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 
     24from trac.workflows.SimpleWorkflow import SimpleWorkflow 
     25 
     26class 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 
     24from trac.workflows.Base import WorkflowBase 
     25 
     26class 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

     
    4949            icalhref += '&show=all' 
    5050            self.req.hdf.setValue('roadmap.href.list', 
    5151                                   self.env.href.roadmap()) 
    52             query = "SELECT name, time, descr FROM milestone " \ 
     52            query = "SELECT name, time, descr, owner FROM milestone " \ 
    5353                    "WHERE name != '' " \ 
    5454                    "ORDER BY (IFNULL(time, 0) = 0) ASC, time ASC, name" 
    5555        else: 
    5656            self.req.hdf.setValue('roadmap.showall', '1') 
    5757            self.req.hdf.setValue('roadmap.href.list', 
    5858                                   self.env.href.roadmap('all')) 
    59             query = "SELECT name, time, descr FROM milestone " \ 
     59            query = "SELECT name, time, descr, owner FROM milestone " \ 
    6060                    "WHERE name != '' " \ 
    6161                    "AND (time IS NULL OR time = 0 OR time > %d) " \ 
    6262                    "ORDER BY (IFNULL(time, 0) = 0) ASC, time ASC, name" % time() 
     
    7575            milestone = { 
    7676                'name': row['name'], 
    7777                'href': self.env.href.milestone(row['name']), 
     78                'owner': row['owner'] or '', 
    7879                'time': row['time'] and int(row['time']) 
    7980            } 
    8081            descr = row['descr'] 
     
    114115            status = ticket['status'] 
    115116            if status == 'new' or status == 'reopened' and not ticket['owner']: 
    116117                return 'NEEDS-ACTION' 
    117             elif status == 'assigned' or status == 'reopened': 
     118            elif status != 'closed': 
    118119                return 'IN-PROCESS' 
    119120            elif status == 'closed': 
    120121                if ticket['resolution'] == 'fixed': return 'COMPLETED' 
  • trac/upgrades/db8.py

     
     1sql = """ 
     2-- Add statuses 'resolved' and 'verified' 
     3UPDATE enum SET value = 6 WHERE type = 'status' AND name = 'closed'; 
     4INSERT INTO enum (type, name, value) VALUES ('status', 'resolved', 4); 
     5INSERT INTO enum (type, name, value) VALUES ('status', 'verified', 5); 
     6 
     7-- Add QA Contact to 'component' 
     8CREATE TEMPORARY TABLE component_backup AS SELECT * FROM component; 
     9DROP TABLE component; 
     10CREATE TABLE component ( 
     11         name            text PRIMARY KEY, 
     12         owner           text, 
     13         qaowner         text 
     14); 
     15INSERT INTO component SELECT name, owner, owner AS qaowner FROM component_backup; 
     16DROP TABLE component_backup; 
     17 
     18-- Add Release Manager Contact to 'milestone' 
     19CREATE TEMPORARY TABLE milestone_backup AS SELECT * FROM milestone; 
     20DROP TABLE milestone; 
     21CREATE TABLE milestone ( 
     22         id              integer PRIMARY KEY, 
     23         name            text, 
     24         time            integer, 
     25         owner           text, 
     26         descr           text, 
     27         UNIQUE(name) 
     28); 
     29INSERT INTO milestone SELECT id, name, time, '' AS owner, descr FROM milestone_backup; 
     30DROP TABLE milestone_backup; 
     31 
     32-- Modify 'All Tickets by Version' report 
     33UPDATE report SET sql = ' 
     34SELECT 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'')  
     43AND 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 
     48UPDATE report SET sql = ' 
     49SELECT 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'')  
     58AND 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' 
     61WHERE title = 'All Tickets by Milestone'; 
     62 
     63-- Modify 'Assigned, Active Tickets by Owner' report 
     64UPDATE report SET sql = ' 
     65SELECT 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'' 
     72AND 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 
     77UPDATE report SET sql = ' 
     78SELECT 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'' 
     85AND 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 
     90UPDATE report SET sql = ' 
     91SELECT 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 
     110UPDATE report SET sql = ' 
     111SELECT 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''  
     121AND 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 
     128INSERT INTO report VALUES(NULL,NULL,'Open Tickets, Mine first',' 
     129SELECT 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''  
     141AND 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 
     148INSERT INTO report VALUES(NULL,NULL,'Open Tickets by Version',' 
     149SELECT 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'' 
     158AND 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 
     165INSERT INTO report VALUES(NULL,NULL,'Open Tickets by Milestone',' 
     166SELECT 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''  
     175AND 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 
     182INSERT INTO report VALUES(NULL,NULL,'Open Tickets by Owner',' 
     183SELECT 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'' 
     190AND p.name=t.priority AND p.type=''priority'' 
     191  ORDER BY (IFNULL(owner, '''') = '''') DESC, owner, p.value, severity, time 
     192',' 
     193List not closed tickets, group by ticket owner, sorted by priority. 
     194'); 
     195 
     196INSERT INTO report VALUES(NULL,NULL,'Open Tickets by Status',' 
     197SELECT 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''  
     205AND q.name = t.status AND q.type = ''status'' 
     206AND 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 
     213INSERT INTO report VALUES(NULL,NULL,'Resolved Tickets, Mine first',' 
     214SELECT 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''  
     226AND 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 
     233INSERT INTO report VALUES(NULL,NULL,'Resolved Tickets by Milestone',' 
     234SELECT 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''  
     243AND p.name = t.priority AND p.type = ''priority'' 
     244  ORDER BY (IFNULL(milestone, '''') = '''') DESC,milestone, p.value, severity, time 
     245',' 
     246List resolved tickets, sorted by priority, grouped by milestone 
     247'); 
     248 
     249INSERT INTO report VALUES(NULL,NULL,'Resolved Tickets by Owner',' 
     250SELECT 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'' 
     257AND p.name=t.priority AND p.type=''priority'' 
     258  ORDER BY (IFNULL(owner, '''') = '''') DESC, owner, p.value, severity, time 
     259',' 
     260List resolved tickets, group by ticket owner, sorted by priority. 
     261'); 
     262 
     263INSERT INTO report VALUES(NULL,NULL,'Completed Tickets by Milestone (Full Description)',' 
     264SELECT 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'') 
     271AND p.name = t.priority AND p.type = ''priority'' 
     272  ORDER BY (IFNULL(milestone, '''') = '''') DESC,milestone, p.value, severity, time 
     273',' 
     274Release Notes: List verified and closed tickets, group by milestone, include description. 
     275'); 
     276""" 
     277 
     278def 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

     
    5151            return 
    5252        if not self._old.has_key(name): 
    5353            self._old[name] = self.get(name, None) 
     54        elif self._old[name] == value: 
     55            del self._old[name] 
    5456        self.data[name] = value 
    5557 
    5658    def _forget_changes(self): 
     
    130132 
    131133        if not self._old and not comment: return # Not modified 
    132134 
    133         # If the component is changed on a 'new' ticket then owner field 
    134         # 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 database 
    141             # 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  
    149135        for name in self._old.keys(): 
    150136            if name[:7] == 'custom_': 
    151137                fname = name[7:] 
     
    268254        i += 1 
    269255 
    270256 
     257def 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 
    271282class NewticketModule(Module): 
    272283    template_name = 'newticket.cs' 
    273284 
    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): 
    280286        ticket.setdefault('reporter',self.req.authname) 
    281287 
    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) 
    290289        tktid = ticket.insert(self.db) 
    291290 
    292291        # Notify 
     
    298297    def render (self): 
    299298        self.perm.assert_permission(perm.TICKET_CREATE) 
    300299 
    301         if self.args.has_key('create'): 
    302             self.create_ticket() 
     300        ticket = Ticket() 
    303301 
    304         ticket = Ticket() 
     302        preview = self.args.has_key('preview') 
     303        do_create = self.args.has_key('create') 
    305304        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 
    306319        ticket.setdefault('component', 
    307320                          self.env.get_config('ticket', 'default_component')) 
    308321        ticket.setdefault('milestone', 
     
    324337        evals = util.mydict(zip(ticket.keys(), 
    325338                                map(lambda x: util.escape(x), ticket.values()))) 
    326339        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) 
    327348 
    328349        util.sql_to_hdf(self.db, 'SELECT name FROM component ORDER BY name', 
    329350                        self.req.hdf, 'newticket.components') 
     
    338359class TicketModule (Module): 
    339360    template_name = 'ticket.cs' 
    340361 
    341     def save_changes (self, id): 
     362    def save_changes (self, ticket, workflow): 
    342363        self.perm.assert_permission (perm.TICKET_MODIFY) 
    343         ticket = Ticket(self.db, id) 
    344364 
    345         if not self.args.get('summary'): 
    346             raise util.TracError('Tickets must contain Summary.') 
    347  
    348365        if self.args.has_key('description'): 
    349366            self.perm.assert_permission (perm.TICKET_ADMIN) 
    350367 
    351368        if self.args.has_key('reporter'): 
    352369            self.perm.assert_permission (perm.TICKET_ADMIN) 
    353370 
    354         # TODO: this should not be hard-coded like this 
    355         action = self.args.get('action', None) 
    356         if action == 'accept': 
    357             ticket['status'] =  'assigned' 
    358             ticket['owner'] = self.req.authname 
    359         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  
    371371        now = int(time.time()) 
    372372 
     373        workflow.on_save(ticket) 
    373374        ticket.save_changes(self.db, 
    374375                            self.args.get('author', self.req.authname), 
    375376                            self.args.get('comment'), 
     
    377378 
    378379        tn = TicketNotifyEmail(self.env) 
    379380        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'])) 
    381382 
    382383    def insert_ticket_data(self, hdf, id, ticket, reporter_id): 
    383384        """Insert ticket data into the hdf""" 
     
    437438    def render (self): 
    438439        self.perm.assert_permission (perm.TICKET_VIEW) 
    439440 
    440         action = self.args.get('action', 'view') 
    441         preview = self.args.has_key('preview') 
    442  
    443441        if not self.args.has_key('id'): 
    444442            self.req.redirect(self.env.href.wiki()) 
    445443 
    446444        id = int(self.args.get('id')) 
     445        ticket = Ticket(self.db, id) 
    447446 
    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) 
    451451 
    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 
    453470        reporter_id = util.get_reporter_id(self.req) 
    454471 
    455472        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) 
    461474            reporter_id = self.args.get('author') 
    462475            comment = self.args.get('comment') 
    463476            if comment: 
     
    468481                                               self.req.hdf, self.env, self.db)) 
    469482 
    470483        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) 
    471492 
    472493        cursor = self.db.cursor() 
    473494        cursor.execute("SELECT max(id) FROM ticket") 
  • trac/WikiFormatter.py

     
    125125            elif row[1] == 'closed': 
    126126                return '<a href="%s" title="CLOSED : %s"><del>#%d</del></a>' % (self._href.ticket(number), summary, number) 
    127127            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) 
    129129 
    130130    def _changesethref_formatter(self, match, fullmatch): 
    131131        number = int(match[1:-1]) 
     
    158158                elif row[1] == 'closed': 
    159159                    return self._href.ticket(args), '<del>%s:%s</del>' % (module, args), 0, 'CLOSED: ' + summary 
    160160                else: 
    161                     return self._href.ticket(args), '%s:%s' % (module, args), 0, summary 
     161                    return self._href.ticket(args), '%s:%s' % (module, args), 0, row[1].upper() + ': ' + summary 
    162162            else: 
    163163                return self._href.ticket(args), '%s:%s' % (module, args), 1, '' 
    164164        elif module == 'wiki': 
  • templates/ticket_workflow_timetrack.cs

     
     1<?cs 
     2if 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 
     2if !ticket.action ?><?cs 
     3  set:ticket.action = 'leave' ?><?cs 
     4/if ?><?cs 
     5def 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 
     11if 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 
     15if ticket.workflow.action.accept ?><?cs 
     16  call action_radio('accept') ?> 
     17  <label for="accept">accept ticket</label><br /><?cs 
     18/if ?><?cs 
     19if 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 
     26if ticket.workflow.action.verify ?><?cs 
     27  call action_radio('verify') ?> 
     28  <label for="verify">verify ticket</label><br /><?cs 
     29/if ?><?cs 
     30if ticket.workflow.action.close ?><?cs 
     31  call action_radio('close') ?> 
     32  <label for="close">close ticket</label><br /><?cs 
     33/if ?><?cs 
     34if ticket.workflow.action.reopen ?><?cs 
     35  call:action_radio('reopen') ?> 
     36  <label for="reopen">reopen ticket</label><br /><?cs 
     37/if ?><?cs 
     38if ticket.workflow.action.retest ?><?cs 
     39  call:action_radio('retest') ?> 
     40  <label for="retest">retest ticket</label><br /><?cs 
     41/if ?><?cs 
     42if 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 
     60if 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

     
    3333<div id="ticket"> 
    3434 <div class="date"><?cs var:ticket.opened ?></div> 
    3535 <h1>Ticket #<?cs var:ticket.id ?> <?cs 
    36  if:ticket.status == 'closed' ?>(Closed: <?cs var:ticket.resolution ?>)<?cs 
     36 if:ticket.resolution ?>(<?cs var:ticket.status ?>: <?cs var:ticket.resolution ?>)<?cs 
    3737 elif:ticket.status != 'new' ?>(<?cs var:ticket.status ?>)<?cs 
    3838 /if ?></h1> 
    3939 <h2><?cs var:ticket.summary ?></h2> 
     
    207207  </div><?cs /if ?> 
    208208 </fieldset> 
    209209 
    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 ?> 
    259216 
     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 
    260226 <div class="buttons"> 
    261227  <input type="reset" value="Reset" />&nbsp; 
    262228  <input type="submit" name="preview" value="Preview" />&nbsp; 
  • templates/roadmap.cs

     
    2222      var:milestone.name ?></em></a></h2> 
    2323    <p class="date"><?cs if:milestone.date ?> 
    2424     <?cs var:milestone.date ?><?cs else ?>No date set<?cs /if ?> 
     25     <?cs if:milestone.owner ?>&nbsp;(<?cs var:milestone.owner ?>)<?cs /if ?> 
    2526    </p> 
    2627    <?cs with:stats = milestone.stats ?> 
    2728     <?cs if:#stats.total_tickets > #0 ?> 
  • templates/ticket_workflow_simple.cs

     
     1<?cs 
     2if !ticket.action ?><?cs 
     3  set:ticket.action = 'leave' ?><?cs 
     4/if ?><?cs 
     5def 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 
     11if 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 
     15if ticket.workflow.action.accept ?><?cs 
     16  call action_radio('accept') ?> 
     17  <label for="accept">accept ticket</label><br /><?cs 
     18/if ?><?cs 
     19if 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 
     26if ticket.workflow.action.reopen ?><?cs 
     27  call:action_radio('reopen') ?> 
     28  <label for="reopen">reopen ticket</label><br /><?cs 
     29/if ?><?cs 
     30if 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 
     48if 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

     
    4343                             $item.href, $item.msg_escwiki)  
    4444        ?><?cs elif:item.type == #3 
    4545        ?><!-- Closed ticket --> <?cs call:rss_item('Ticket', 
    46                              'Ticket #'+$item.idata+' resolved: '+$item.shortmsg, 
     46                             'Ticket #'+$item.idata+' closed: '+$item.shortmsg, 
    4747                             $item.href, $item.msg_escwiki)  
    4848        ?><?cs elif:item.type == #4  
    4949        ?><!-- Reopened ticket --><?cs call:rss_item('Ticket', 
    5050                             '#'+$item.idata+' reopened: '+$item.shortmsg, 
    5151                             $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)  
    5264        ?><?cs elif:item.type == #5  
    5365        ?><!-- Wiki change --><?cs call:rss_item('Wiki', 
    5466                             $item.tdata+" page edited.", 
  • templates/newticket.cs

     
    6969  </div><?cs /if ?> 
    7070 </fieldset> 
    7171 
     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 
    7287 <div class="buttons"> 
    73   <input type="submit" value="Preview" />&nbsp; 
     88  <input type="submit" name="preview" value="Preview" />&nbsp; 
    7489  <input type="submit" name="create" value="Submit ticket" /> 
    7590 </div> 
    7691</form> 
  • templates/milestone.cs

     
    5656      var:milestone.name ?>" /> 
    5757   </div> 
    5858   <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"> 
    5964    <label for="datemode">Completion date:</label><br /> 
    6065    <select name="datemode" id="datemode" 
    6166        onchange="enableControl('date',this.value=='manual'); 
     
    108113 <?cs else ?> 
    109114  <em class="date"><?cs if:milestone.date ?> 
    110115   <?cs var:milestone.date ?><?cs else ?>No date set<?cs /if ?> 
     116   <?cs if:milestone.owner ?>&nbsp;(<?cs var:milestone.owner ?>)<?cs /if ?> 
    111117  </em> 
    112118  <div class="descr"><?cs var:milestone.descr ?></div> 
    113119 <?cs /if ?> 
     
    120126  <thead><tr> 
    121127   <th class="name" rowspan="2"><?cs var:milestone.stats.grouped_by ?></th> 
    122128   <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> 
    124130  </tr><tr> 
    125131   <th class="open" scope="col">Active</th> 
    126132   <th class="closed" scope="col">Closed</th> 
  • templates/timeline.cs

     
    6060 
    6161<?cs each:item = timeline.items ?> 
    6262 <?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 ?> 
    6370 <?cs if:item.type == #1 ?><!-- Changeset --> 
    6471  <?cs call:tlitem(item.href, 'changeset', 
    6572    'Changeset <em>['+$item.idata+']</em> by '+$item.author,$item.node_list+item.message) ?> 
     
    6774  <?cs call:tlitem(item.href, 'newticket', 
    6875    'Ticket <em>#'+$item.idata+'</em> created by '+$item.author, item.message) ?> 
    6976 <?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 ?> 
    7577  <?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) ?> 
    7879 <?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) ?> 
    8191 <?cs elif:item.type == #5 ?><!-- Wiki change --> 
    8292  <?cs call:tlitem(item.href, 'wiki', 
    8393    '<em>'+$item.tdata+'</em> edited by '+$item.author, item.message) ?>