Changeset 2552

Show
Ignore:
Timestamp:
08/07/07 19:42:12 (1 year ago)
Author:
coling
Message:

WorkLogPlugin:

Refs #1898 by applying modified patch.

Bits of the patch not submitted:

  • Does not modify the display of the worklog page and keeps it as a single user overview.
  • Does not allow the storage of a comment when starting work (only stopping)

Bits changed:

  • The display of the comment on the timeline was moved to the detail area which is more appropriate
  • Comments on the worklog should now support WikiFormatting

Bits added:

  • Added a db schema version wrapper as per reportmanager.py and my other plugins.
  • Added a CVS output to the worklog page (VERY VERY rudimentary - mime time is just text for debugging and there is no attempt at quoting and delimiting the data properly)

Bits to do in the future:

  • Make the CSV output better and properly quote/delimit things (a comma in a comment will totally break things right now).
  • Remove the comment field from Stop Work button area as it looks UGLY ;)
    • On this note, when clicking Stop Work it should display a popup type window, that:
      • Allows a selection of a date/time in the past to Stop Work (incase you forgot and overran)
      • Display the comment field.

I've probably forgotten some detail in this commit message and I've not tested this as much as I would usually so there may be some bugs.

Thanks loads to krugerm for the initial patch. Keep up the good work. Hope you agree with my changes here :)

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • worklogplugin/0.10/worklog/api.py

    r2458 r2552  
    11import re 
    2 import dbhelper 
    32import time 
    43from usermanual import * 
     
    1716class WorkLogSetupParticipant(Component): 
    1817    implements(IEnvironmentSetupParticipant) 
     18 
     19    db_version_key = None 
     20    db_version = None 
     21    db_installed_version = None 
    1922     
    2023    """Extension point interface for components that need to participate in the 
     
    2225    additional database tables.""" 
    2326    def __init__(self): 
    24         pass 
     27        self.db_version_key = 'WorklogPlugin_Db_Version' 
     28        self.db_version = 2 
     29        self.db_installed_version = None 
     30 
     31        # Initialise database schema version tracking. 
     32        db = self.env.get_db_cnx() 
     33        cursor = db.cursor() 
     34        cursor.execute("SELECT value FROM system WHERE name=%s", (self.db_version_key,)) 
     35        try: 
     36            self.db_installed_version = int(cursor.fetchone()[0]) 
     37        except: 
     38            self.db_installed_version = 0 
     39            cursor.execute("INSERT INTO system (name,value) VALUES(%s,%s)", 
     40                           (self.db_version_key, self.db_installed_version)) 
     41            db.commit() 
     42            db.close() 
     43 
    2544     
    2645    def environment_created(self): 
     
    2948            self.upgrade_environment(None) 
    3049             
    31     def db_needs_upgrade(self): 
    32         work_log = dbhelper.db_table_exists(self.env.get_db_cnx(), 'work_log'); 
    33         return not work_log 
     50    def system_needs_upgrade(self): 
     51        return self.db_installed_version < self.db_version 
    3452         
    35     def db_do_upgrade(self): 
    36         work_log = dbhelper.db_table_exists(self.env.get_db_cnx(), 'work_log'); 
    37         if not work_log: 
    38             print "Creating work_log table" 
    39             sql = """ 
    40             CREATE TABLE work_log ( 
    41             user text, 
    42             ticket integer, 
    43             lastchange integer, 
    44             starttime integer, 
    45             endtime integer 
    46             ); 
    47             """ 
    48             dbhelper.execute_non_query(self.env.get_db_cnx(), sql) 
     53    def do_db_upgrade(self): 
     54        # Legacy support hack (supports upgrades from revisions r2495 or before) 
     55        if self.db_installed_version == 0: 
     56            db = self.env.get_db_cnx() 
     57            cursor = db.cursor() 
     58            try: 
     59                cursor.execute('SELECT * FROM work_log LIMIT 1') 
     60                # We've succeeded so we actually have version 1 
     61                self.db_installed_version = 1 
     62            except: 
     63                pass 
     64            db.close() 
     65        # End Legacy support hack 
     66 
     67        db = self.env.get_db_cnx() 
     68        cursor = db.cursor() 
     69 
     70        # Do the staged updates 
     71        try: 
     72            if self.db_installed_version < 1: 
     73                print 'Creating work_log table' 
     74                cursor.execute('CREATE TABLE work_log (' 
     75                               'user       TEXT,' 
     76                               'ticket     INTEGER,' 
     77                               'lastchange INTEGER,' 
     78                               'starttime  INTEGER,' 
     79                               'endtime    INTEGER' 
     80                               ')') 
     81 
     82            if self.db_installed_version < 2: 
     83                print 'Updating work_log table (v2)' 
     84                cursor.execute('ALTER TABLE work_log ' 
     85                               'ADD COLUMN comment TEXT') 
     86 
     87            #if self.db_installed_version < 3: 
     88            #    print 'Updating work_log table (v3)' 
     89            #    cursor.execute('...') 
     90             
     91            # Updates complete, set the version 
     92            cursor.execute("UPDATE system SET value=%s WHERE name=%s",  
     93                           (self.db_version, self.db_version_key)) 
     94            db.commit() 
     95            db.close() 
     96        except Exception, e: 
     97            self.log.error("WorklogPlugin Exception: %s" % (e,)); 
     98            db.rollback() 
     99 
     100 
    49101     
    50102    def needs_user_man(self): 
    51         maxversion = dbhelper.get_scalar(self.env.get_db_cnx(), 
    52                                          "SELECT MAX(version) FROM wiki WHERE name like %s", 0, 
    53                                          user_manual_wiki_title) 
    54         if (not maxversion) or maxversion < user_manual_version: 
    55             return True 
    56         return False 
     103        db = self.env.get_db_cnx() 
     104        cursor = db.cursor() 
     105        try: 
     106            cursor.execute('SELECT MAX(version) FROM wiki WHERE name=%s', (user_manual_wiki_title,)) 
     107            maxversion = int(cursor.fetchone()[0]) 
     108        except: 
     109            maxversion = 0 
     110        db.close() 
     111 
     112        return maxversion < user_manual_version 
    57113 
    58114    def do_user_man_update(self): 
    59  
    60115        when = int(time.time()) 
    61         sql = """ 
    62         INSERT INTO wiki (name,version,time,author,ipnr,text,comment,readonly) 
    63         VALUES ( %s, %s, %s, 'Timing and Estimation Plugin', '127.0.0.1', %s,'',0) 
    64         """ 
    65         dbhelper.execute_non_query(self.env.get_db_cnx(),sql, 
    66                                    user_manual_wiki_title, 
    67                                    user_manual_version, 
    68                                    when, 
    69                                    user_manual_content) 
    70              
     116        db = self.env.get_db_cnx() 
     117        cursor = db.cursor() 
     118        cursor.execute('INSERT INTO wiki (name,version,time,author,ipnr,text,comment,readonly) ' 
     119                       'VALUES (%s, %s, %s, %s, %s, %s, %s, %s)', 
     120                       (user_manual_wiki_title, user_manual_version, when, 
     121                        'WorkLog Plugin', '127.0.0.1', user_manual_content, 
     122                        '', 0)) 
     123        db.commit() 
     124        db.close() 
    71125         
    72126    def environment_needs_upgrade(self, db): 
     
    77131 
    78132        """ 
    79         return (self.db_needs_upgrade() \ 
     133        return (self.system_needs_upgrade() \ 
    80134                or self.needs_user_man()) 
    81135             
     
    91145            return True 
    92146        print "Worklog needs an upgrade" 
    93         if self.db_needs_upgrade(): 
     147        if self.system_needs_upgrade(): 
    94148            p("Upgrading Database") 
    95             self.db_do_upgrade() 
     149            self.do_db_upgrade() 
    96150        if self.needs_user_man(): 
    97151            p("Upgrading usermanual") 
  • worklogplugin/0.10/worklog/htdocs/worklogplugin.css

    r2425 r2552  
    1 #content.worklog 
    2 { 
    3   width: 100%; 
    4 } 
    51#worklogmanual 
    62{ 
     
    84  float: right; 
    95} 
    10  
     6#worklog_comment  
     7
     8  font-size: 70%; 
     9  color: green; 
     10
     11#worklog_time_delta  
     12
     13  font-size: 70%; 
     14  color: green; 
     15
     16table#worklog_report  
     17
     18  width: 99%; 
     19  clear: both; 
     20
    1121#content.worklog table th 
    1222{ 
     
    2636{ 
    2737  color: #999; 
    28 } 
    29 #messages{  
    3038} 
    3139.message 
  • worklogplugin/0.10/worklog/manager.py

    r2495 r2552  
    129129            self.stop_work() 
    130130            self.explanation = '' 
    131              
     131  
    132132        cursor = db.cursor() 
    133133        cursor.execute('INSERT INTO work_log (user, ticket, lastchange, starttime, endtime) ' 
     
    137137 
    138138     
    139     def stop_work(self, stoptime=None): 
     139    def stop_work(self, stoptime=None, comment=''): 
    140140        active = self.get_active_task() 
    141141        if not active: 
     
    158158        cursor = db.cursor() 
    159159        cursor.execute('UPDATE work_log ' 
    160                        'SET endtime=%s, lastchange=%s
     160                       'SET endtime=%s, lastchange=%s, comment=%s
    161161                       'WHERE user=%s AND lastchange=%s AND endtime=0', 
    162                        (stoptime, stoptime, self.authname, active['lastchange'])) 
     162                       (stoptime, stoptime, comment, self.authname, active['lastchange'])) 
    163163 
    164164        message = '' 
    165         if self.config.getbool('worklog', 'comment'): 
     165        # Leave a comment if the user has configured this or if they have entered 
     166        # a work log comment. 
     167        if self.config.getbool('worklog', 'comment') or comment: 
    166168            started = datetime.fromtimestamp(active['starttime']) 
    167169            finished = datetime.fromtimestamp(stoptime) 
    168             message = '%s worked on this ticket for %s between %s %s and %s %s' % \ 
     170            message = '%s worked on this ticket for %s between %s %s and %s %s.' % \ 
    169171                      (self.authname, pretty_timedelta(started, finished), \ 
    170172                       format_date(active['starttime']), format_time(active['starttime']), \ 
    171173                       format_date(stoptime), format_time(stoptime)) 
     174            if comment: 
     175                message += "\n[[BR]]\n" + comment 
     176             
    172177        if self.config.getbool('worklog', 'timingandestimation') and \ 
    173178               self.config.get('ticket-custom', 'hours'): 
     
    185190            db = self.env.get_db_cnx() 
    186191            tckt = Ticket(self.env, active['ticket'], db) 
    187             tckt['hours'] = str(float(delta) / 60) 
     192            # This hideous hack is here because I don't yet know how to do variable-DP rounding in python - sorry! 
     193            # It's meant to round to 2 DP, so please replace it if you know how.  Many thanks, MK. 
     194            tckt['hours'] = str(float(int(100 * float(delta) / 60) / 100.0)) 
    188195            self.save_ticket(tckt, db, message) 
    189196            message = '' 
     
    225232     
    226233        task = {} 
    227         cursor.execute('SELECT wl.user, wl.ticket, t.summary, wl.lastchange, wl.starttime, wl.endtime
     234        cursor.execute('SELECT wl.user, wl.ticket, t.summary, wl.lastchange, wl.starttime, wl.endtime, wl.comment
    228235                       'FROM work_log wl ' 
    229236                       'LEFT JOIN ticket t ON wl.ticket=t.id ' 
    230237                       'WHERE wl.user=%s AND wl.lastchange=%s', (self.authname, lastchange)) 
    231238 
    232         for user,ticket,summary,lastchange,starttime,endtime in cursor: 
     239        for user,ticket,summary,lastchange,starttime,endtime,comment in cursor: 
     240            if not comment: 
     241                comment = '' 
     242             
    233243            task['user'] = user 
    234244            task['ticket'] = ticket 
     
    237247            task['starttime'] = float(starttime) 
    238248            task['endtime'] = float(endtime) 
     249            task['comment'] = comment 
    239250        return task 
    240251     
  • worklogplugin/0.10/worklog/templates/worklog.cs

    r2452 r2552  
    1111  </div> 
    1212 
    13   <table border="0" cellspacing="0" cellpadding="0"
     13  <table border="0" cellspacing="0" cellpadding="0" id="worklog_report"
    1414    <tr> 
    1515      <th>User</th> 
    1616      <th>Activity</th> 
    17       <th>Last Change</th> 
     17      <th>Time</th> 
     18      <th>Comment</th> 
    1819    </tr> 
    1920    <?cs each:log = worklog.worklog ?> 
     
    2324      <td><a href="<?cs var:log.ticket_url ?>">#<?cs var:log.ticket ?></a>: <?cs var:log.summary ?></td> 
    2425      <?cs else ?> 
    25       <td><em>Idle</em> <small>(Last worked on: <a href="<?cs var:log.ticket_url ?>">#<?cs var:log.ticket ?></a>: <?cs var:log.summary ?>)</small></td>       
     26      <td><em>Idle</em> <small>(Last worked on: <a href="<?cs var:log.ticket_url ?>">#<?cs var:log.ticket ?></a>: <?cs var:log.summary ?>)</small></td> 
    2627      <?cs /if ?> 
    27       <td><?cs var:log.delta ?></td> 
     28      <td><span id="worklog_time_delta"><?cs var:log.delta ?></span></td> 
     29      <td><span id="worklog_comment"><?cs var:log.comment ?></span></td> 
    2830    </tr> 
    2931    <?cs /each ?> 
  • worklogplugin/0.10/worklog/templates/worklog_webadminui.cs

    r2494 r2552  
    1010    <div class="field"> 
    1111      <input type="checkbox" id="comment" name="comment" value="1" <?cs var:settings.comment ?>> 
    12       <label for="comment">Automatically add a comment when you stop work on a ticket?</label> 
     12      <label for="comment">Automatically add a comment when you stop work on a ticket?</label><br /> 
     13      <small>Please note that if you leave a comment when you stop work, then a comment is always added to the ticket to ensure all information is present on the ticket.</small> 
    1314    </div> 
    1415    <div class="field"> 
     
    3031    <div class="field"> 
    3132      <label for="roundup">Round up to nearest minute</label>&nbsp; 
    32       <input type="text" id="roundup" name="roundup" size="4" value="<?cs var:settings.roundup ?>"><br
     33      <input type="text" id="roundup" name="roundup" size="4" value="<?cs var:settings.roundup ?>"><br /
    3334      <small>This only applies when integrating with the  
    3435             <a href="http://www.trac-hacks.org/wiki/TimingAndEstimationPlugin">Timing and Estimation Plugin</a></small> 
  • worklogplugin/0.10/worklog/timeline_hook.py

    r2495 r2552  
    88from trac.web.href import Href 
    99from trac.Timeline import ITimelineEventProvider 
     10from trac.wiki.formatter import wiki_to_html, wiki_to_oneliner 
    1011 
    1112class WorkLogTimelineAddon(Component): 
     
    2829            cursor = db.cursor() 
    2930 
    30             cursor.execute("""SELECT wl.user,wl.ticket,wl.time,wl.starttime,wl.kind,wl.humankind,t.summary 
     31            cursor.execute("""SELECT wl.user,wl.ticket,wl.time,wl.starttime,wl.comment,wl.kind,wl.humankind,t.summary 
    3132                             FROM ( 
    3233                              
    33                              SELECT user, ticket, starttime AS time, starttime, 'workstart' AS kind, 'started' AS humankind 
     34                             SELECT user, ticket, starttime AS time, starttime, comment, 'workstart' AS kind, 'started' AS humankind 
    3435                             FROM work_log 
    3536 
    3637                             UNION 
    3738 
    38                              SELECT user, ticket, endtime AS time, starttime, 'workstop' AS kind, 'stopped' AS humankind 
     39                             SELECT user, ticket, endtime AS time, starttime, comment, 'workstop' AS kind, 'stopped' AS humankind 
    3940                             FROM work_log 
    4041 
     
    4546                           % (start, stop)) 
    4647            previous_update = None 
    47             for user,ticket,time,starttime,kind,humankind,summary in cursor: 
     48            for user,ticket,time,starttime,comment,kind,humankind,summary in cursor: 
    4849                summary = Markup.escape(summary) 
    4950                time = float(time) 
    5051                starttime = float(starttime) 
    51                  
    5252                if format == 'rss': 
    53                     title = Markup('%s %s working on Ticket #%s (%s)' % \ 
    54                                    (user, humankind, ticket, summary)) 
     53                    title = Markup('%s %s working on Ticket #%s (%s): %s' % \ 
     54                                   (user, humankind, ticket, summary, comment)) 
    5555                else: 
    5656                    title = Markup('%s %s working on Ticket <em title="%s">#%s</em>' % \ 
     
    6060                    started = datetime.fromtimestamp(starttime) 
    6161                    finished = datetime.fromtimestamp(time) 
    62                     message = 'Time spent: ' + pretty_timedelta(started, finished) 
     62                    if comment: 
     63                        message =  wiki_to_oneliner(comment, self.env, req, shorten=True) + Markup('<br />(Time spent: %s)' % pretty_timedelta(started, finished)) 
     64                    else: 
     65                        message = 'Time spent: %s' % pretty_timedelta(started, finished) 
    6366                yield kind, href.ticket(ticket), title, time, user, message 
    6467 
  • worklogplugin/0.10/worklog/uihooks_ticket.py

    r2494 r2552  
    7171  f.setAttribute('action', '""" + req.href.worklog() + """') 
    7272  f.setAttribute('class', 'inlinebuttons'); 
     73   
     74  var h_url = document.createElement('input'); 
     75  h_url.setAttribute('type', 'hidden'); 
     76  h_url.setAttribute('name', 'source_url'); 
     77  h_url.setAttribute('value', location.pathname); 
     78  f.appendChild(h_url); 
     79   
    7380  var h = document.createElement('input'); 
    7481  h.setAttribute('type', 'hidden'); 
     
    7683  h.setAttribute('value', '""" + str(ticket) + """'); 
    7784  f.appendChild(h); 
     85   
    7886  var h2 = document.createElement('input'); 
    7987  h2.setAttribute('type', 'hidden'); 
    8088  h2.setAttribute('name', '__FORM_TOKEN'); 
    8189  h2.setAttribute('value', '""" + str(req.incookie['trac_form_token'].value) + """'); 
    82   f.appendChild(h2); 
     90  f.appendChild(h2);""" 
     91   
     92        if stop: 
     93            script += """ 
     94  var s_comment_label = document.createElement('span'); 
     95  s_comment_label.id = 'worklog_comment'; 
     96  s_comment_label.innerHTML = 'Comment: '; 
     97  f.appendChild(s_comment_label); 
     98   
     99  var s_comment = document.createElement('input'); 
     100  s_comment.type = 'text'; 
     101  s_comment.size = '50'; 
     102  s_comment.name = 'comment'; 
     103  f.appendChild(s_comment);""" 
     104   
     105        script += """ 
    83106  var s = document.createElement('input'); 
    84107  s.setAttribute('type', 'submit');""" 
     
    156179                    button_js = self.get_button_js(req, ticket) 
    157180                elif task and task['ticket'] == ticket: 
    158                     # We are currnetly working on this, so display the stop button... 
     181                    # We are currently working on this, so display the stop button... 
    159182                    button_js = self.get_button_js(req, ticket, True) 
    160183 
  • worklogplugin/0.10/worklog/usermanual.py

    r2380 r2552  
    11user_manual_title = "Work Log Plugin User Manual" 
    2 user_manual_version = 1 
     2user_manual_version = 2 
    33user_manual_wiki_title = "WorkLogPluginUserManual" 
    44user_manual_content = """ 
    5 = Timing and Estimation Plugin User Manual = 
     5= !WorkLog Plugin User Manual = 
     6This is a plugin that adds a Work Log capability to Trac. 
    67 
    7 This plugin provides a way to register which ticket you are currently working on. By doing this you keep the rest of your team in the loop so as to reduce the duplication of work
     8Basically, it allows you to register the fact you have started work on a ticket which effectively allows you to clock on and clock off
    89 
    9 It provides an extension to the XMLRPC plugin which allows the integration with Desktop applications which makes interaction with this plugin seemless 
     10It uses javascript to add a button to the ticket page to allow you to start/stop working on a given ticket. 
     11 
     12If the [http://trac-hacks.org/wiki/TimingAndEstimationPlugin TimingAndEstimationPlugin] is installed then when you clock off, the time spent on the ticket will be recorded. 
     13 
     14If you visit the Work Log page (a new navigation entry), you will see a list of people (developers) and which tickets they are currently working on. Work log events are also logged to the Timeline for a historical view.  
     15 
     16In the future it will also provide an extension to the XMLRPC plugin which allows the integration with Desktop applications which will make interaction with this plugin seemless. 
    1017""" 
  • worklogplugin/0.10/worklog/webui.py

    r2495 r2552  
    1414     INavigationContributor, ITemplateProvider 
    1515from trac.web.href import Href 
    16  
     16from trac.wiki.formatter import wiki_to_html 
     17from trac.util.text import CRLF 
    1718 
    1819class WorkLogPage(Component): 
     
    4041        db = self.env.get_db_cnx() 
    4142        cursor = db.cursor() 
    42         cursor.execute('SELECT wl.user, s.value, wl.starttime, wl.endtime, wl.ticket, t.summary ' 
     43        cursor.execute('SELECT wl.user, s.value, wl.starttime, wl.endtime, wl.ticket, wl.comment, t.summary ' 
    4344                       'FROM (SELECT user,MAX(lastchange) lastchange FROM work_log GROUP BY user) wlt ' 
    4445                       'LEFT JOIN work_log wl ON wlt.user=wl.user AND wlt.lastchange=wl.lastchange ' 
     
    4849 
    4950        log = {} 
    50         for (user,name,starttime,endtime,ticket,summary) in cursor: 
     51        for (user,name,starttime,endtime,ticket,comment,summary) in cursor: 
    5152            starttime = float(starttime) 
    5253            endtime = float(endtime) 
     
    5758            if name: 
    5859                dispname = '%s (%s)' % (name, user) 
     60                         
     61            finished = '' 
     62            delta = '' 
     63            if not endtime == 0: 
     64                finished = datetime.fromtimestamp(endtime) 
     65                delta = 'Worked for %s (between %s %s and %s %s)' % \ 
     66                        (pretty_timedelta(started, finished), 
     67                         format_date(starttime), format_time(starttime), 
     68                         format_date(endtime), format_time(endtime)) 
     69            else: 
     70                delta = 'Started %s ago (%s %s)' % \ 
     71                        (pretty_timedelta(started), 
     72                         format_date(starttime), format_time(starttime)) 
     73                          
     74 
     75            minutes_elapsed = 0 
     76            if endtime == 0: 
     77                minutes_elapsed = int((int(time()) - starttime) / 60) 
     78            else: 
     79                minutes_elapsed = int((endtime - starttime) / 60) 
     80                 
    5981            log[user] = { "name": dispname, 
    6082                          "ticket": ticket, 
    6183                          "ticket_url": req.href.ticket(ticket), 
     84                          "comment": wiki_to_html(comment, self.env, req), 
    6285                          "summary": summary, 
    6386                          "started": started, 
    64                           "delta": 'Started ' + format_date(starttime) + " " + format_time(starttime) + \ 
    65                                    " (" + pretty_timedelta(started) + " ago)" 
     87                          "delta": delta, 
     88                          "finished": finished, 
     89                          "minutes_elapsed": minutes_elapsed 
    6690                          } 
     91                 
     92        return log 
    6793 
    68             if not endtime == 0: 
    69                 finished = datetime.fromtimestamp(endtime) 
    70                 log[user]["finished"] = finished 
    71                 log[user]["delta"] = 'Worked on from ' + format_date(starttime) + " " + format_time(starttime) + " - " + format_date(endtime) + " " + format_time(endtime) + \ 
    72                                    " (" + pretty_timedelta(started, finished) + ")" 
     94    def worklog_csv(self, req): 
     95        # Headers 
     96        req.write('user,full_name,starttime,endtime,ticket,ticket_summary,work_comment') 
     97        req.write(CRLF) 
     98 
     99        # Rows 
     100        db = self.env.get_db_cnx() 
     101        cursor = db.cursor() 
     102        cursor.execute('SELECT wl.user, s.value, wl.starttime, wl.endtime, wl.ticket, wl.comment, t.summary ' 
     103                       'FROM work_log wl ' 
     104                       'LEFT JOIN ticket t ON wl.ticket=t.id ' 
     105                       'LEFT JOIN session_attribute s ON wl.user=s.sid AND s.name=\'name\' ' 
     106                       'ORDER BY wl.lastchange DESC, wl.user') 
     107        for (user,name,starttime,endtime,ticket,comment,summary) in cursor: 
     108            if not comment: 
     109                comment = '' 
     110             
     111            req.write(user + ',') 
     112            req.write(name + ',') 
     113            req.write(str(starttime) + ',') 
     114            req.write(str(endtime) + ',') 
     115            req.write(str(ticket) + ',') 
     116            req.write(summary + ',') 
     117            req.write(comment) 
     118            req.write(CRLF) 
     119 
    73120 
    74121                 
    75         return log 
    76          
    77122    # IRequestHandler methods 
    78123    def match_request(self, req): 
     
    90135            return None 
    91136 
     137        if req.args.has_key('format') and req.args['format'] == 'csv': 
     138            req.send_header('Content-Type', 'text/plain;charset=utf-8') 
     139            self.worklog_csv(req) 
     140            return None 
     141 
    92142        if req.method == 'POST': 
    93143            mgr = WorkLogManager(self.env, self.config, req.authname) 
     
    97147                else: 
    98148                    addMessage('You are now working on ticket #%s.' % (req.args['ticket'],)) 
     149                 
     150                req.redirect(req.args['source_url']) 
     151                return None 
     152                 
    99153            elif req.args.has_key('stopwork'): 
    100154                stoptime = None 
    101155                if req.args.has_key('stoptime'): 
    102156                    stoptime = int(req.args['stoptime']) 
    103                 if not mgr.stop_work(stoptime): 
     157 
     158                comment = '' 
     159                if req.args.has_key('comment'): 
     160                    comment = str(req.args['comment']) 
     161 
     162                if not mgr.stop_work(stoptime, comment): 
    104163                    addMessage(mgr.get_explanation()) 
    105164                else: 
    106165                    addMessage('You have stopped working.') 
     166                 
     167                req.redirect(req.args['source_url']) 
     168                return None 
    107169         
     170        # no POST, so they're just wanting a list of the worklog entries 
    108171        req.hdf["worklog"] = {"messages": messages, 
    109172                              "worklog": self.get_worklog(req),