Edgewall Software

LudvigStrigeus: NewWorkflow_r1516.diff

File NewWorkflow_r1516.diff, 31.9 KB (added by ludde, 7 years ago)
  • htdocs/css/roadmap.css

     
    99} 
    1010div.progress :link:hover, div.progress :visited:hover { background: #fff } 
    1111div.progress .closed:link, div.progress .closed:visited { background: #bae0ba } 
     12div.progress .resolved:link, div.progress .resolved:visited { background: #bacaf4 } 
    1213p.percent { font-size: 10px; line-height: 2.4em; margin: 0.9em 0 0 } 
    1314 
    1415/* Styles for the roadmap view */ 
  • trac/Milestone.py

     
    5757        tickets.append(ticket) 
    5858    return tickets 
    5959 
    60  
    61 def get_query_links(env, milestone, grouped_by='component', group=None): 
    62     q = {} 
    63     if not group: 
    64         q['all_tickets'] = env.href.query(milestone=milestone) 
    65         q['active_tickets'] = env.href.query(milestone=milestone, 
    66                                              status=('new', 'assigned', 'reopened')) 
    67         q['closed_tickets'] = env.href.query(milestone=milestone, status='closed') 
    68     else: 
    69         q['all_tickets'] = env.href.query(milestone=milestone, grouped_by=group) 
    70         q['active_tickets'] = env.href.query(milestone=milestone, 
    71                                              grouped_by=group, 
    72                                              status=('new', 'assigned', 'reopened')) 
    73         q['closed_tickets'] = env.href.query(milestone=milestone, grouped_by=group, 
    74                                              status='closed') 
    75     return q 
    76  
    77  
    78 def calc_ticket_stats(tickets): 
     60def get_tickets_progressbar(tickets, env, milestone, grouped_by='component', group=None): 
    7961    total_cnt = len(tickets) 
    80     active = [ticket for ticket in tickets if ticket['status'] != 'closed'] 
    81     active_cnt = len(active) 
    82     closed_cnt = total_cnt - active_cnt 
    83  
    84     percent_active, percent_closed = 0, 0 
     62    closed_cnt = len([ticket for ticket in tickets if ticket['status'] in ('verified', 'closed')]) 
     63    resolved_cnt = len([ticket for ticket in tickets if ticket['status'] == 'resolved']) 
     64    active_cnt = total_cnt - closed_cnt - resolved_cnt 
     65 
     66    # Determine percentages 
     67    percent_active, percent_resolved, percent_closed = 0, 0, 0 
    8568    if total_cnt > 0: 
    86         percent_active = round(float(active_cnt) / float(total_cnt) * 100) 
    87         percent_closed = round(float(closed_cnt) / float(total_cnt) * 100) 
    88         if percent_active + percent_closed > 100: 
    89             percent_closed -= 1 
    90  
     69        percent_resolved = round(float(resolved_cnt) / float(total_cnt) * 100) 
     70        percent_closed = round(float(closed_cnt) / float(total_cnt) * 100) 
     71        percent_active = 100 - percent_resolved - percent_closed 
     72 
     73    # Determine the links 
     74    extra = {} 
     75    if group: extra = {grouped_by:group} # Additional flags 
     76    closed_href = env.href.query(milestone=milestone, status=('verified', 'closed'), **extra) 
     77    resolved_href = env.href.query(milestone=milestone, status=('resolved',),**extra) 
     78    active_href = env.href.query(milestone=milestone, status=('new', 'assigned', 'reopened'),**extra) 
     79    all_href = env.href.query(milestone=milestone, **extra) 
     80 
     81    # Construct result     
     82    progress = [ 
     83        {'name': 'closed',  'capsname': 'Closed',   'pct': percent_closed,  'cnt': closed_cnt, 'url': closed_href}, 
     84        {'name': 'resolved','capsname': 'Resolved', 'pct': percent_resolved,'cnt': resolved_cnt, 'url': resolved_href}, 
     85        {'name': 'active',  'capsname': 'Active',   'pct': percent_active,  'cnt': active_cnt , 'url': active_href}] 
    9186    return { 
    9287        'total_tickets': total_cnt, 
    93         'active_tickets': active_cnt, 
    94         'percent_active': percent_active, 
    95         'closed_tickets': closed_cnt, 
    96         'percent_closed': percent_closed 
     88        'bar': progress, 
     89        'all_tickets_url' : all_href, 
     90        'right_text': '%d%%' % percent_closed 
    9791    } 
    9892 
    9993 
     
    333327        req.hdf['milestone.stats.grouped_by'] = by 
    334328 
    335329        tickets = get_tickets_for_milestone(self.env, self.db, id, by) 
    336         stats = calc_ticket_stats(tickets) 
     330        stats = get_tickets_progressbar(tickets, self.env, milestone['name']) 
    337331        req.hdf['milestone.stats'] = stats 
    338         queries = get_query_links(self.env, milestone['name']) 
    339         req.hdf['milestone.queries'] = queries 
    340332 
    341333        groups = self.get_groups(by) 
    342334        group_no = 0 
     
    353345                if percent_total > max_percent_total: 
    354346                    max_percent_total = percent_total 
    355347            req.hdf['%s.percent_total' % prefix] = percent_total * 100 
    356             stats = calc_ticket_stats(group_tickets) 
     348            stats = get_tickets_progressbar(group_tickets, self.env, milestone['name'], by, group) 
    357349            req.hdf[prefix] = stats 
    358             queries = get_query_links(self.env, milestone['name'], by, group) 
    359             req.hdf['%s.queries' % prefix] = queries 
    360350            group_no += 1 
    361351        req.hdf['milestone.stats.max_percent_total'] = max_percent_total * 100 
  • trac/scripts/admin.py

     
    3232 
    3333from trac import perm, util 
    3434from trac.env import Environment 
    35 import trac.siteconfig 
     35import trac 
    3636 
    3737def my_sum(list): 
    3838    """Python2.2 doesn't have sum()""" 
  • trac/Timeline.py

     
    5252            sql.append("SELECT time,id,'','newticket',summary,reporter" 
    5353                       " FROM ticket WHERE time>=%s AND time<=%s") 
    5454            params += (start, stop) 
    55             sql.append("SELECT time,ticket,'','reopenedticket','',author " 
     55            sql.append("SELECT time,ticket,'',(newvalue || 'ticket'),'',author " 
    5656                       "FROM ticket_change WHERE field='status' " 
    57                        "AND newvalue='reopened' AND time>=%s AND time<=%s") 
     57                       "AND newvalue IN ('reopened', 'verified') AND time>=%s AND time<=%s") 
    5858            params += (start, stop) 
    59             sql.append("SELECT t1.time,t1.ticket,t2.newvalue,'closedticket'," 
     59            sql.append("SELECT t1.time,t1.ticket,t2.newvalue,(t1.newvalue || 'ticket')," 
    6060                       "t3.newvalue,t1.author" 
    6161                       " FROM ticket_change t1" 
    62                        "   INNER JOIN ticket_change t2 ON t1.ticket = t2.ticket" 
    63                        "     AND t1.time = t2.time" 
     62                       "   LEFT OUTER JOIN ticket_change t2 ON t1.ticket = t2.ticket" 
     63                       "     AND t1.time = t2.time AND t2.field = 'resolution'" 
    6464                       "   LEFT OUTER JOIN ticket_change t3 ON t1.time = t3.time" 
    6565                       "     AND t1.ticket = t3.ticket AND t3.field = 'comment'" 
    66                        " WHERE t1.field = 'status' AND t1.newvalue = 'closed'" 
    67                        "   AND t2.field = 'resolution'" 
     66                       " WHERE t1.field = 'status' AND t1.newvalue IN ('closed', 'resolved')" 
    6867                       "   AND t1.time >= %s AND t1.time <= %s") 
    6968            params += (start,stop) 
    7069        if 'wiki' in filters: 
     
    243242        return item 
    244243    _render_reopenedticket = _render_ticket 
    245244    _render_newticket = _render_ticket 
    246     _render_closedticket = _render_ticket 
     245    _render_closedticket = _render_ticket 
     246    _render_resolvedticket = _render_ticket 
     247    _render_verifiedticket = _render_ticket 
    247248 
    248249    def _render_milestone(self, req, item): 
    249250        absurls = req.args.get('format') == 'rss' 
  • trac/Roadmap.py

     
    9292            tickets = Milestone.get_tickets_for_milestone(self.env, self.db, 
    9393                                                          milestone['name'], 
    9494                                                          'owner') 
    95             stats = Milestone.calc_ticket_stats(tickets) 
     95            stats = Milestone.get_tickets_progressbar(tickets, self.env, milestone['name']) 
    9696            req.hdf['roadmap.milestones.%s.stats' % milestone_no] = stats 
    97             queries = Milestone.get_query_links(self.env, milestone['name']) 
    98             req.hdf['roadmap.milestones.%s.queries' % milestone_no] = queries 
    9997            milestone['tickets'] = tickets # for the iCalendar view 
    10098            milestone_no += 1 
    10199 
     
    116114            status = ticket['status'] 
    117115            if status == 'new' or status == 'reopened' and not ticket['owner']: 
    118116                return 'NEEDS-ACTION' 
    119             elif status == 'assigned' or status == 'reopened': 
     117            elif status != 'closed': 
    120118                return 'IN-PROCESS' 
    121119            elif status == 'closed': 
    122120                if ticket['resolution'] == 'fixed': return 'COMPLETED' 
  • trac/web/cgi_frontend.py

     
    3737        self.__environ = environ 
    3838        self.__input = input 
    3939        self.__output = output 
    40  
     40         
    4141        self.method = self.__environ.get('REQUEST_METHOD') 
    4242        self.remote_addr = self.__environ.get('REMOTE_ADDR') 
    4343        self.remote_user = self.__environ.get('REMOTE_USER') 
  • trac/Changeset.py

     
    6868            'time': time.asctime(time.localtime(chgset.date)), 
    6969            'author': util.escape(chgset.author or 'anonymous'), 
    7070            'message': wiki_to_html(util.wiki_escape_newline(chgset.message or '--'), 
    71                                     req.hdf, self.env, self.db) 
     71                                    req.hdf, self.env, self.db, ignore_missing_links=1) 
    7272        } 
    7373 
    7474        oldest_rev = repos.oldest_rev 
  • trac/Ticket.py

     
    275275            hdf['%s.height' % pfx] = f['height'] 
    276276        i += 1 
    277277 
     278def get_default_owner(ticket, db): 
     279    cursor = db.cursor() 
     280    # Owner defaults to the component owner 
     281    if ticket.get('component'): 
     282        cursor.execute('SELECT owner FROM component ' 
     283                       'WHERE name=%s', ticket['component']) 
     284        owner = cursor.fetchone()[0] 
     285        if owner: return owner 
     286    # Otherwise use the milestone owner 
     287    if ticket.get('milestone'): 
     288        cursor.execute('SELECT owner FROM milestone ' 
     289                       'WHERE name=%s', ticket['milestone']) 
     290        owner = cursor.fetchone()[0] 
     291        if owner: return owner 
     292    # Otherwise use the empty owner 
     293    return '' 
    278294 
    279295class NewticketModule(Module): 
    280296 
     
    286302        ticket.populate(req.args) 
    287303        ticket.setdefault('reporter', req.authname) 
    288304 
    289         # The owner field defaults to the component owner 
    290         cursor = self.db.cursor() 
    291         if ticket.get('component') and ticket.get('owner', '') == '': 
    292             cursor.execute('SELECT owner FROM component ' 
    293                            'WHERE name=%s', ticket['component']) 
    294             owner = cursor.fetchone()[0] 
    295             ticket['owner'] = owner 
     305        # Determine default owner if the owner is not set explicitly 
     306        if not ticket.get('owner'): 
     307            ticket['owner'] = get_default_owner(ticket, self.db) 
    296308 
    297309        tktid = ticket.insert(self.db) 
    298310 
     
    333345 
    334346        req.hdf['title'] = 'New Ticket' 
    335347        req.hdf['newticket'] = dict(zip(ticket.keys(), 
    336                                     map(lambda x: util.escape(x), ticket.values()))) 
     348                         map(lambda x: util.escape(x), ticket.values()))) 
    337349 
    338350        util.sql_to_hdf(self.db, "SELECT name FROM component ORDER BY name", 
    339351                        req.hdf, 'newticket.components') 
     
    364376 
    365377        req.display('newticket.cs') 
    366378 
    367 def available_actions(ticket, perm_): 
    368     """ Returns the actions that can be performed on the ticket""" 
    369     actions = { 
    370         'new':      ['leave', 'resolve', 'reassign', 'accept'], 
    371         'assigned': ['leave', 'resolve', 'reassign'          ], 
    372         'reopened': ['leave', 'resolve', 'reassign'          ], 
    373         'closed':   ['leave',                        'reopen'] 
    374     } 
    375     perm_map = { 
    376         'resolve': perm.TICKET_MODIFY, 
    377         'reassign': perm.TICKET_CHGPROP, 
    378         'accept': perm.TICKET_CHGPROP, 
    379         'reopen': perm.TICKET_CREATE 
    380     } 
    381     def has_permission(action): 
    382         if not action in perm_map: 
    383             return 1 
    384         return perm_.has_permission(perm_map[action]) 
    385     return filter(has_permission, actions.get(ticket['status'], ['leave'])) 
    386379 
     380class TicketModule (Module): 
     381    def get_ticket_actions(self, ticket): 
     382        """ Returns the actions that can be performed on the ticket""" 
     383        actions_simple = { 
     384            'new':      ['leave', 'resolve', 'reassign', 'accept'], 
     385            'assigned': ['leave', 'resolve', 'reassign'          ], 
     386            'reopened': ['leave', 'resolve', 'reassign'          ], 
     387            'closed':   ['leave',                        'reopen'] 
     388        } 
     389        actions_complex = { 
     390            'new':      ['leave','reassign','resolve',                          'accept'], 
     391            'assigned': ['leave','reassign','resolve',                                  ], 
     392            'reopened': ['leave','reassign','resolve',                                  ], 
     393            'resolved': ['leave','reassign',          'reopen',         'close','verify'], 
     394            'verified': ['leave','reassign',          'reopen','retest','close'         ], 
     395            'closed':   ['leave',                     'reopen','retest'                 ] 
     396        } 
     397        perm_map = { 
     398            'resolve': perm.TICKET_MODIFY, 
     399            'reassign': perm.TICKET_CHGPROP, 
     400            'accept': perm.TICKET_CHGPROP, 
     401            'reopen': perm.TICKET_CREATE, 
     402            'close' : perm.TICKET_MODIFY, 
     403            'verify': perm.TICKET_MODIFY, 
     404            'retest': perm.TICKET_CHGPROP, 
     405        } 
     406        complex = self.env.config.get('ticket', 'complex_workflow', 0) 
     407        actions = complex and actions_complex or actions_simple 
     408        def has_perm(p): 
     409            return p not in perm_map or self.perm.has_permission(perm_map[p]) 
     410        return filter(has_perm, actions.get(ticket['status'], ['leave'])) 
    387411 
    388 class TicketModule(Module): 
    389  
     412    def do_ticket_action(self, ticket, action, req): 
     413        if action == 'accept': 
     414            ticket['status'] =  'assigned' 
     415            ticket['owner'] = req.authname 
     416        elif action == 'resolve': 
     417            complex = self.env.config.get('ticket', 'complex_workflow', 0) 
     418            ticket['status'] = complex and 'resolved' or 'closed' 
     419            ticket['resolution'] = req.args.get('resolve_resolution') 
     420        elif action == 'reassign': 
     421            ticket['owner'] = req.args.get('reassign_owner') or get_default_owner(ticket, self.db) 
     422            if ticket['status'] == 'assigned': 
     423                ticket['status'] = 'new' 
     424        elif action == 'reopen': 
     425            ticket['status'] = 'reopened' 
     426            ticket['resolution'] = '' 
     427        elif action == 'verify': 
     428            ticket['status'] = 'verified' 
     429        elif action == 'close': 
     430            ticket['status'] = 'closed' 
     431        elif action == 'retest': 
     432            ticket['status'] = 'resolved' 
     433             
    390434    def save_changes(self, req, id): 
    391435        if self.perm.has_permission(perm.TICKET_CHGPROP): 
    392436            # TICKET_CHGPROP gives permission to edit the ticket 
    393437            if not req.args.get('summary'): 
    394438                raise util.TracError('Tickets must contain summary.') 
    395439 
    396             ticket = Ticket(self.db, id) 
    397440            if 'description' in req.args or 'reporter' in req.args: 
    398441                self.perm.assert_permission(perm.TICKET_ADMIN) 
    399  
     442 
     443            ticket = Ticket(self.db, id) 
    400444            ticket.populate(req.args) 
    401445 
    402446        elif self.perm.has_permission(perm.TICKET_APPEND): 
     
    408452 
    409453        # Do any action on the ticket? 
    410454        action = req.args.get('action', 'leave') 
    411         if action not in available_actions(ticket, self.perm): 
     455        if action not in self.get_ticket_actions(ticket): 
    412456            raise util.TracError('Invalid action') 
    413457 
    414         # TODO: this should not be hard-coded like this 
    415         if action == 'accept': 
    416             ticket['status'] =  'assigned' 
    417             ticket['owner'] = req.authname 
    418         if action == 'resolve': 
    419             ticket['status'] = 'closed' 
    420             ticket['resolution'] = req.args.get('resolve_resolution') 
    421         elif action == 'reassign': 
    422             ticket['owner'] = req.args.get('reassign_owner') 
    423             ticket['status'] = 'new' 
    424         elif action == 'reopen': 
    425             ticket['status'] = 'reopened' 
    426             ticket['resolution'] = '' 
     458        # Do the action 
     459        self.do_ticket_action(ticket, action, req) 
    427460 
    428461        now = int(time.time()) 
    429462        ticket.save_changes(self.db, req.args.get('author', req.authname), 
     
    441474    def insert_ticket_data(self, req, id, ticket, reporter_id): 
    442475        """Insert ticket data into the hdf""" 
    443476        req.hdf['ticket'] = dict(zip(ticket.keys(), 
    444                                  map(lambda x: util.escape(x), ticket.values()))) 
     477                         map(lambda x: util.escape(x), ticket.values()))) 
    445478 
    446479        util.sql_to_hdf(self.db, "SELECT name FROM component ORDER BY name", 
    447480                        req.hdf, 'ticket.components') 
     
    520553            req.hdf['ticket.attach_href'] = self.env.href.attachment('ticket', id) 
    521554 
    522555        # Add the possible actions to hdf 
    523         for action in available_actions(ticket, self.perm): 
     556        for action in self.get_ticket_actions(ticket): 
    524557            req.hdf['ticket.actions.' + action] = '1' 
    525558 
    526559    def render(self, req): 
    527         self.perm.assert_permission(perm.TICKET_VIEW) 
     560        self.perm.assert_permission (perm.TICKET_VIEW) 
    528561 
    529         action = req.args.get('action', 'view') 
     562        action = req.args.get('action', None) 
    530563        preview = req.args.has_key('preview') 
    531564 
    532565        if not req.args.has_key('id'): 
     
    534567 
    535568        id = int(req.args.get('id')) 
    536569 
    537         if not preview \ 
    538            and action in ('leave', 'accept', 'reopen', 'resolve', 'reassign'): 
     570        if not preview and action: 
    539571            self.save_changes(req, id) 
    540572 
    541573        ticket = Ticket(self.db, id) 
  • trac/WikiFormatter.py

     
    136136    env = None 
    137137    absurls = 0 
    138138 
    139     def __init__(self, env, db, absurls=0): 
     139    def __init__(self, env, db, absurls=0, ignore_missing_links=0): 
    140140        self.env = env 
    141         self.db = db 
     141        self.db = db 
     142        self.ignore_missing_links = ignore_missing_links 
    142143        self._href = absurls and env.abs_href or env.href 
    143144        self._local = env.config.get('project', 'url', '') or env.abs_href.base 
    144145 
     
    252253            page = page[:page.find('#')] 
    253254        page = urllib.unquote(page) 
    254255        text = urllib.unquote(text) 
    255         if not self.env._wiki_pages.has_key(page): 
    256             return '<a class="missing wiki" href="%s" rel="nofollow">%s?</a>' \ 
    257                    % (self._href.wiki(page) + anchor, text) 
    258         else: 
     256        if self.env._wiki_pages.has_key(page): 
    259257            return '<a class="wiki" href="%s">%s</a>' \ 
    260258                   % (self._href.wiki(page) + anchor, text) 
     259        elif self.ignore_missing_links: 
     260            return text 
     261        else: 
     262            return '<a class="missing wiki" href="%s" rel="nofollow">%s?</a>' \ 
     263                   % (self._href.wiki(page) + anchor, text) 
    261264 
    262265    def _make_changeset_link(self, rev, text): 
    263266        cursor = self.db.cursor() 
     
    390393                                         'meta|param|doctype)') 
    391394    _htmlproc_disallow_attribute = re.compile('(?i)<[^>]*\s+(on\w+)=') 
    392395 
    393     def __init__(self, hdf, env, db, absurls=0): 
    394         CommonFormatter.__init__(self, env, db, absurls) 
     396    def __init__(self, hdf, env, db, absurls=0, ignore_missing_links=0): 
     397        CommonFormatter.__init__(self, env, db, absurls, ignore_missing_links) 
    395398        self.hdf = hdf 
    396399        self.anchors = [] 
    397400 
     
    636639        self.close_indentation() 
    637640        self.close_list() 
    638641 
    639  
    640 def wiki_to_html(wikitext, hdf, env, db, absurls=0): 
     642def wiki_to_html(wikitext, hdf, env, db, absurls=0, ignore_missing_links=0): 
    641643    out = StringIO.StringIO() 
    642     Formatter(hdf, env, db, absurls).format(wikitext, out) 
     644    Formatter(hdf, env, db, absurls, ignore_missing_links).format(wikitext, out) 
    643645    return out.getvalue() 
    644646 
    645  
    646 def wiki_to_oneliner(wikitext, env, db, absurls=0): 
     647def wiki_to_oneliner(wikitext, env, db, absurls=0, ignore_missing_links=0): 
    647648    out = StringIO.StringIO() 
    648     OneLinerFormatter(env, db, absurls).format(wikitext, out) 
     649    OneLinerFormatter(env, db, absurls, ignore_missing_links).format(wikitext, out) 
    649650    return out.getvalue() 
  • templates/roadmap.cs

     
    3939    with:stats = milestone.stats ?><?cs 
    4040     if:#stats.total_tickets > #0 ?> 
    4141      <div class="progress"> 
    42        <a class="closed" href="<?cs 
    43          var:milestone.queries.closed_tickets ?>" style="width: <?cs 
    44          var:#stats.percent_closed ?>%" title="<?cs 
    45          var:#stats.closed_tickets ?> of <?cs 
     42       <?cs each:bar = stats.bar ?> 
     43       <a class="<?cs var:bar.name ?>" href="<?cs 
     44         var:bar.url ?>" style="width: <?cs 
     45         var:#bar.pct ?>%" title="<?cs 
     46         var:#bar.cnt ?> of <?cs 
    4647         var:#stats.total_tickets ?> ticket<?cs 
    47          if:#stats.total_tickets != #1 ?>s<?cs /if ?> closed"></a> 
    48        <a class="open" href="<?cs 
    49          var:milestone.queries.active_tickets ?>" style="width: <?cs 
    50          var:#stats.percent_active ?>%" title="<?cs 
    51          var:#stats.active_tickets ?> of <?cs 
    52          var:#stats.total_tickets ?> ticket<?cs 
    53          if:#stats.total_tickets != #1 ?>s<?cs /if ?> active"></a> 
     48         if:#stats.total_tickets != #1 ?>s<?cs /if ?> <?cs var:bar.name ?>"></a> 
     49       <?cs /each ?> 
    5450      </div> 
    55       <p class="percent"><?cs var:#stats.percent_closed ?>%</p> 
     51      <p class="percent"><?cs var:stats.right_text ?></p> 
    5652      <dl> 
    57        <dt>Active tickets:</dt> 
    58        <dd><a href="<?cs var:milestone.queries.active_tickets ?>"><?cs 
    59          var:stats.active_tickets ?></a></dd> 
    60        <dt>Closed tickets:</dt> 
    61        <dd><a href="<?cs var:milestone.queries.closed_tickets ?>"><?cs 
    62          var:stats.closed_tickets ?></a></dd> 
     53       <?cs each:bar = stats.bar ?> 
     54       <dt><?cs var:bar.capsname ?> tickets:</dt> 
     55       <dd><a href="<?cs var:bar.url ?>"><?cs var:bar.cnt ?></a></dd> 
     56       <?cs /each ?> 
    6357      </dl><?cs 
    6458     /if ?><?cs 
    6559    /with ?> 
  • templates/ticket.cs

     
    3030<div id="content" class="ticket"> 
    3131 
    3232 <h1>Ticket #<?cs var:ticket.id ?> <?cs 
    33  if:ticket.status == 'closed' ?>(Closed: <?cs var:ticket.resolution ?>)<?cs 
     33 if:ticket.resolution ?>(<?cs var:ticket.status ?>: <?cs var:ticket.resolution ?>)<?cs 
    3434 elif:ticket.status != 'new' ?>(<?cs var:ticket.status ?>)<?cs 
    3535 /if ?></h1> 
    3636 
     
    205205 </fieldset><?cs /if ?> 
    206206 
    207207 <?cs if:ticket.actions.accept || ticket.actions.reopen || 
    208          ticket.actions.resolve || ticket.actions.reassign ?> 
     208         ticket.actions.resolve || ticket.actions.reassign || 
     209         ticket.actions.retest || ticket.actions.verify || 
     210         ticket.actions.close ?> 
    209211 <fieldset id="action"> 
    210212  <legend>Action</legend><?cs 
    211213  if:!ticket.action ?><?cs set:ticket.action = 'leave' ?><?cs 
     
    216218     /if ?> /><?cs 
    217219  /def ?> 
    218220  <?cs call:action_radio('leave') ?> 
    219    <label for="leave">leave as <?cs var:ticket.status ?></label><br /><?cs 
     221  <label for="leave">leave as <?cs var:ticket.status ?></label><br /><?cs 
    220222  if:ticket.actions.accept ?><?cs 
    221223   call:action_radio('accept') ?> 
    222224   <label for="accept">accept ticket</label><br /><?cs 
     
    225227   call:action_radio('reopen') ?> 
    226228   <label for="reopen">reopen ticket</label><br /><?cs 
    227229  /if ?><?cs 
     230  if:ticket.actions.retest ?><?cs 
     231    call:action_radio('retest') ?> 
     232    <label for="retest">retest ticket</label><br /><?cs 
     233  /if ?><?cs 
     234  if:ticket.actions.verify ?><?cs 
     235    call:action_radio('verify') ?> 
     236    <label for="verify">verify ticket</label><br /><?cs 
     237  /if ?><?cs 
     238  if:ticket.actions.close ?><?cs 
     239    call:action_radio('close') ?> 
     240    <label for="close">close ticket</label><br /><?cs 
     241  /if ?><?cs 
    228242  if:ticket.actions.resolve ?><?cs 
    229    call:action_radio('resolve') ?> 
     243    call:action_radio('resolve') ?> 
    230244   <label for="resolve">resolve</label> 
    231245   <label for="resolve_resolution">as:</label> 
    232246   <?cs call:hdf_select(enums.resolution, "resolve_resolution", 
     
    237251   <label for="reassign">reassign</label> 
    238252   <label>to:<?cs 
    239253   if:len(ticket.users) ?><?cs 
    240     call:hdf_select(ticket.users, "reassign_owner", ticket.reassign_owner, 0) ?><?cs 
     254     call:hdf_select(ticket.users, "reassign_owner", ticket.reassign_owner, 1) ?><?cs 
    241255   else ?> 
    242256    <input type="text" id="reassign_owner" name="reassign_owner" size="40" value="<?cs 
    243257      var:ticket.reassign_owner ?>" /><?cs 
     
    259273   </script><?cs 
    260274  /if ?> 
    261275 </fieldset> 
     276 <?cs else ?> 
     277  <input type="hidden" name="action" value="leave" /> 
    262278 <?cs /if ?> 
    263  
    264279 <script type="text/javascript" src="<?cs 
    265280   var:htdocs_location ?>js/wikitoolbar.js"></script> 
    266281 
  • templates/newticket.cs

     
    22<?cs include "header.cs" ?> 
    33<?cs include "macros.cs" ?> 
    44<script type="text/javascript"> 
    5 addEvent(window, 'load', function() { document.getElementById('summary').focus()});  
     5addEvent(window, 'load', function() { document.getElementById('summary').focus()}); 
    66</script> 
    77 
    88<div id="ctxtnav" class="nav"></div> 
  • templates/milestone.cs

     
    9494   <select name="target" id="target"> 
    9595    <option value="">None</option><?cs 
    9696     each:other = milestones ?><?cs if:other != milestone.name ?> 
    97       <option><?cs var:other ?></option><?cs  
     97      <option><?cs var:other ?></option><?cs 
    9898     /if ?><?cs /each ?> 
    9999   </select> 
    100100   <div class="buttons"> 
     
    121121   with:stats = milestone.stats ?><?cs 
    122122    if:#stats.total_tickets > #0 ?> 
    123123     <div class="progress"> 
    124       <a class="closed" href="<?cs 
    125         var:milestone.queries.closed_tickets ?>" style="width: <?cs 
    126         var:#stats.percent_closed ?>%" title="<?cs 
    127         var:#stats.closed_tickets ?> of <?cs 
    128         var:#stats.total_tickets ?> ticket<?cs 
    129         if:#stats.total_tickets != #1 ?>s<?cs /if ?> closed"></a> 
    130       <a class="open" href="<?cs 
    131         var:milestone.queries.active_tickets ?>" style="width: <?cs 
    132         var:#stats.percent_active ?>%" title="<?cs 
    133         var:#stats.active_tickets ?> of <?cs 
    134         var:#stats.total_tickets ?> ticket<?cs 
    135         if:#stats.total_tickets != #1 ?>s<?cs /if ?> active"></a> 
     124       <?cs each:bar = stats.bar ?> 
     125       <a class="<?cs var:bar.name ?>" href="<?cs 
     126         var:bar.url ?>" style="width: <?cs 
     127         var:#bar.pct ?>%" title="<?cs 
     128         var:#bar.cnt ?> of <?cs 
     129         var:#stats.total_tickets ?> ticket<?cs 
     130         if:#stats.total_tickets != #1 ?>s<?cs /if ?> <?cs var:bar.name ?>"></a> 
     131       <?cs /each ?> 
    136132     </div> 
    137      <p class="percent"><?cs var:#stats.percent_closed ?>%</p> 
     133     <p class="percent"><?cs var:stats.right_text ?></p> 
    138134     <dl> 
    139       <dt>Active tickets:</dt> 
    140       <dd><a href="<?cs var:milestone.queries.active_tickets ?>"><?cs 
    141         var:stats.active_tickets ?></a></dd> 
    142       <dt>Closed tickets:</dt> 
    143       <dd><a href="<?cs var:milestone.queries.closed_tickets ?>"><?cs 
    144         var:stats.closed_tickets ?></a></dd> 
     135       <?cs each:bar = stats.bar ?> 
     136       <dt><?cs var:bar.capsname ?> tickets:</dt> 
     137       <dd><a href="<?cs var:bar.url ?>"><?cs var:bar.cnt ?></a></dd> 
     138       <?cs /each ?> 
    145139     </dl><?cs 
    146140    /if ?><?cs 
    147141   /with ?> 
     
    165159     each:group = milestone.stats.groups ?> 
    166160      <tr> 
    167161       <th scope="row"><a href="<?cs 
    168          var:group.queries.all_tickets ?>"><?cs var:group.name ?></a></th> 
     162         var:group.all_tickets_url ?>"><?cs var:group.name ?></a></th> 
    169163       <td nowrap=nowrap><?cs if:#group.total_tickets ?> 
    170164        <div class="progress" style="width: <?cs 
    171165          var:#group.percent_total * #80 / #milestone.stats.max_percent_total ?>%"> 
    172          <a class="closed" href="<?cs 
    173            var:group.queries.closed_tickets ?>" style="width: <?cs 
    174            var:#group.percent_closed ?>%" title="<?cs 
    175           var:group.closed_tickets ?> of <?cs 
    176           var:group.total_tickets ?> ticket<?cs 
    177           if:group.total_tickets != #1 ?>s<?cs /if ?> closed"></a> 
    178          <a class="open" href="<?cs 
    179            var:group.queries.active_tickets ?>" style="width: <?cs 
    180            var:#group.percent_active - 1 ?>%" title="<?cs 
    181           var:group.active_tickets ?> of <?cs 
    182           var:group.total_tickets ?> ticket<?cs 
    183           if:group.total_tickets != 1 ?>s<?cs /if ?> active"></a> 
     166                                 <?cs each:bar = group.bar ?> 
     167                                 <a class="<?cs var:bar.name ?>" href="<?cs 
     168                                         var:bar.url ?>" style="width: <?cs 
     169                                         var:#bar.pct ?>%" title="<?cs 
     170                                         var:#bar.cnt ?> of <?cs 
     171                                         var:#group.total_tickets ?> ticket<?cs 
     172                                         if:#group.total_tickets != #1 ?>s<?cs /if ?> <?cs var:bar.name ?>"></a> 
     173                                 <?cs /each ?> 
    184174        </div> 
    185         <p class="percent"><?cs var:group.closed_tickets ?>/<?cs 
    186          var:group.total_tickets ?></p> 
     175        <p class="percent"><?cs set:sep="" ?><?cs each:bar = group.bar ?><?cs var:sep ?><?cs var:#bar.cnt ?><?cs set:sep="/" ?><?cs /each ?></p> 
    187176       <?cs /if ?></td> 
    188177      </tr><?cs 
    189178     /each ?> 
  • templates/timeline.cs

     
    5959   set:imessage = '' ?><?cs 
    6060  /if ?><?cs 
    6161  call:tlitem(item.href, 'closedticket', 
     62              'Ticket <em>#' + item.idata + '</em> closed by ' + item.author, 
     63              item.tdata + imessage) ?><?cs 
     64 elif:item.type == 'resolvedticket' ?><?cs 
     65  if:item.message ?><?cs 
     66   set:imessage = ' - ' + item.message ?><?cs 
     67  else ?><?cs 
     68   set:imessage = '' ?><?cs 
     69  /if ?><?cs 
     70  call:tlitem(item.href, 'closedticket', 
    6271              'Ticket <em>#' + item.idata + '</em> resolved by ' + item.author, 
    6372              item.tdata + imessage) ?><?cs 
    6473 elif:item.type == 'reopenedticket' ?><?cs 
    6574  call:tlitem(item.href, 'newticket', 
    6675              'Ticket <em>#' + item.idata + '</em> reopened by ' + item.author, 
    6776              '') ?><?cs 
     77 elif:item.type == 'verifiedticket' ?><?cs 
     78  call:tlitem(item.href, 'newticket', 
     79              'Ticket <em>#' + item.idata + '</em> verified by ' + item.author, 
     80              '') ?><?cs 
    6881 elif:item.type == 'wiki' ?><?cs 
    6982  call:tlitem(item.href, 'wiki', 
    7083              '<em>' + item.tdata + '</em> edited by ' + item.author, 
     
    7891 
    7992<div id="help"> 
    8093 <hr /> 
    81  <strong>Note:</strong> See <a href="<?cs var:trac.href.wiki ?>/TracTimeline">TracTimeline</a>  
     94 <strong>Note:</strong> See <a href="<?cs var:trac.href.wiki ?>/TracTimeline">TracTimeline</a> 
    8295 for information about the timeline view. 
    8396</div> 
    8497