Changeset 4877

Show
Ignore:
Timestamp:
11/26/08 13:57:40 (1 month ago)
Author:
bobbysmith007
Message:

re #4150

Well after more work than anticipated, I have swapped out my uber-crappy struckthrough comment with one of the trac renderd diffs.

What it took:

  • Having a plain text version of the estimate so that when diffing we are only diffing what we care about
  • Pulling the diff_div.html template from trac and filling it with the appropriate data, then rendering that to a string
  • which gets included on a ticket when the estimate changes
  • Had to store the plain text version of the estimate in the db as well
Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • estimatorplugin/0.11/estimatorplugin/api.py

    r3415 r4877  
    77from trac.env import IEnvironmentSetupParticipant 
    88 
    9 dbversion = 1 
     9dbversion = 2 
    1010dbkey = 'EstimatorPluginDbVersion' 
    1111 
     
    3030        """ 
    3131        ver = dbhelper.get_system_value(self.env, dbkey) 
    32         ans = (not ver) or (ver < dbversion) 
    33         self.log.debug('Estimator needs upgrade? %s [installed version:%s  pluginversion:%s '%(ans, ver, dbversion)) 
     32        ans = (not ver) or (int(ver) < dbversion) 
     33        self.log.debug('Estimator needs upgrade? %s [installed version:%s  pluginversion:%s ] '%(ans, ver, dbversion)) 
    3434        return ans 
    3535 
     
    5353                     communication DECIMAL, 
    5454                     tickets VARCHAR(512), 
    55                      comment VARCHAR(8000) 
     55                     comment text 
    5656                 )""",[]), 
    5757                ("""CREATE TABLE estimate_line_item( 
     
    6262                     high DECIMAL 
    6363                )""",[])) 
     64 
     65        if ver < 2: 
     66            self.log.debug('Creating Estimate and Estimate_Line_Item tables (Version 1)') 
     67            success = success and dbhelper.execute_in_trans(self.env, (""" ALTER TABLE estimate ADD COLUMN diffcomment text ; """,[])) 
     68 
    6469        # SHOULD BE LAST IN THIS FUNCTION 
    6570        if success: 
  • estimatorplugin/0.11/estimatorplugin/estimator.py

    r3361 r4877  
    22 
    33estimateUpdate = """ 
    4 UPDATE estimate SET rate=%s, variability=%s, communication=%s, tickets=%s, comment=%s 
     4UPDATE estimate SET rate=%s, variability=%s, communication=%s, tickets=%s, comment=%s, diffcomment=%s 
    55WHERE id=%s 
    66""" 
    77estimateInsert = """ 
    8 INSERT INTO estimate (rate, variability, communication, tickets, comment, id) 
    9 VALUES(%s,%s,%s,%s,%s, %s) 
     8INSERT INTO estimate (rate, variability, communication, tickets, comment, diffcomment, id) 
     9VALUES(%s,%s,%s,%s,%s,%s, %s) 
    1010""" 
    1111lineItemInsert = """ 
     
    4242    return dbhelper.get_scalar(env, "SELECT COMMENT FROM estimate WHERE ID=%s", 0, id) 
    4343 
     44def getTextEstimate(env, id): 
     45    return dbhelper.get_scalar(env, "SELECT DIFFCOMMENT FROM estimate WHERE ID=%s", 0, id) 
     46 
    4447estimateChangeTicketComment = """ 
    4548INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue) 
  • estimatorplugin/0.11/estimatorplugin/htdocs/estimate.css

    r3054 r4877  
    1 .message {  
     1.infomessage {  
    22  color:green; 
    33  border:2px solid green; 
     
    77} 
    88 
    9 .numberCell{  
     9table.estimateBody tr td { 
     10   border-top:1px solid #CCC; 
     11
     12 
     13table.estimateBody tr.lineItemFooter th { 
     14   border-top:1px solid #CCC; 
     15
     16 
     17.numberCell, table.estimateBody tr td.numberCell {  
    1018  text-align:right; 
    1119  border:1px solid blue; 
     
    2533text-decoration: line-through; 
    2634} 
     35 
  • estimatorplugin/0.11/estimatorplugin/htdocs/estimate.js

    r3059 r4877  
    2424} 
    2525 
    26 function evenDeeperClone(node){ 
     26function chromeCleanerPredicate ( node ){ 
     27   if(node && node.tagName){ 
     28      //its all text FF plugin adds chrome images 
     29      if (node.tagName.toLowerCase() == "img" && node.src.search('chrome://') == 0) return false; 
     30   } 
     31   return true; 
     32
     33 
     34function evenDeeperClone(node /* pred */){ 
    2735   //firefox 2 has a bug where it wont clone textarea values sometimes 
     36   // Optional pred arg determines whether or not a node should be cloned 
     37   var pred = arguments[1] || chromeCleanerPredicate; 
    2838   var kid, cloned; 
     39   if (pred && !pred(node)) return null; 
    2940   var newNode = node.cloneNode(false); 
    3041   for(var i=0 ; kid=node.childNodes[i] ; i++){ 
    3142      if(kid.tagName && kid.tagName.toLowerCase()=='textarea'){ 
    32          cloned = cn('textarea'); 
    33          cloned.value = kid.value
     43         cloned = cn('div'); 
     44         cloned.innerHTML = kid.value.replace('\r\n','<br />', 'g').replace('\r','<br />', 'g').replace('\n','<br />', 'g')
    3445      }else{ 
    3546         cloned = evenDeeperClone(kid); 
    36       }  
    37       newNode.appendChild(cloned); 
     47      } 
     48      if(cloned) newNode.appendChild(cloned); 
    3849   } 
    3950   return newNode; 
     
    5162   var tr = cn('tr', {}, 
    5263             cn('td', {}, 
    53                 cn('textarea', {id:uid("description"), name:uid("description"), cols:30, style:"height: 34px;"}, 
     64                cn('textarea', {id:uid("description"), name:uid("description"), style:"height: 68px; width:100%;"}, 
    5465                   valFn('description'))), 
    5566             cn('td', { valign:'top'}, 
     
    165176      } 
    166177   } 
    167    if (parent.id) parent.id = ""; 
     178   if (parent.id){ 
     179      parent.className = (parent.className || "") +" "+ parent.id; 
     180      parent.id = ""; 
     181   } 
    168182   return parent; 
    169183} 
     
    183197   return elem; 
    184198} 
     199 
     200function prepareComment( preview ){ 
     201   var s = ""; 
     202   function walker ( node ){ 
     203      for (var i=0, kid ; kid = node.childNodes[i]; i++ ){ 
     204         if(kid && kid.tagName){ 
     205            var tn = kid.tagName.toLowerCase(); 
     206            if (tn == 'table'){ 
     207               var print_sep = false; 
     208               for(var row,j=0 ; row = kid.rows[j] ; j++){ 
     209                  if (row.className == "lineItemheader") print_sep = true; 
     210                  if (row.className == "lineItemFooter") print_sep = false; 
     211                  for(var cell, k=0 ; cell = row.cells[k] ; k++){ 
     212                     var val = (cell.textContent || cell.innerText); 
     213                     if(val) s += val + ((k==0 && print_sep) ? "\n|  " : 
     214                                         (print_sep ? "  |  " : "\t")); 
     215                  } 
     216                  s += "\n"; 
     217                  if(print_sep)s+="---------------------------\n"; 
     218               } 
     219               s += "\n"; 
     220            } 
     221         } 
     222      } 
     223   } 
     224   walker(preview); 
     225   return s; 
     226} 
     227 
    185228function preparePreview(){ 
    186229   var preview = $$('estimateoutput'); 
     
    188231   preview.appendChild(removeFirstRow(removeInputsAndIds(evenDeeperClone($$('estimateParams'))))); 
    189232   preview.appendChild(removeInputsAndIds(evenDeeperClone($$('estimateBody')))); 
     233   //alert(prepareComment( preview )); 
     234   $$('diffcomment').value = prepareComment( preview ); 
    190235   $$('comment').value = preview.innerHTML; 
    191236} 
  • estimatorplugin/0.11/estimatorplugin/macro_provider.py

    r3361 r4877  
    2222        if html: 
    2323            add_stylesheet(req, "Estimate/estimate.css") 
     24            add_stylesheet(req, "common/css/diff.css") 
    2425            html+= '<br /><a href="%s?id=%s">edit this estimate</a>' % (req.href.Estimate(), id) 
    2526        else: 
  • estimatorplugin/0.11/estimatorplugin/templates/estimate.html

    r3059 r4877  
    3737          </tr> 
    3838          <tr> 
    39             <td class="fieldLabel" ><label for="rate">Rate:</label></td> 
     39            <td class="fieldLabel" ><label for="rate">         Rate:</label></td> 
    4040            <td><input id="rate" name="rate" type="text" onkeyup="runCalculation()" 
    4141                      value="${estimate.rate}" 
     
    4343          </tr> 
    4444          <tr> 
    45             <td class="fieldLabel" ><label for="variability">Variability:</label></td> 
     45            <td class="fieldLabel" ><label for="variability">  Variability:</label></td> 
    4646            <td><input id="variability" name="variability" type="text" onkeyup="runCalculation()" 
    4747                      value="${estimate.variability}" 
     
    6868 
    6969          <tr id="lineItemFooter"> 
    70             <th class="fieldLabel" >Total:</th> 
     70            <th class="fieldLabel" >         Total:</th> 
    7171            <td id="lowTotal" class="numberCell" ></td> 
    7272            <td id="highTotal" class="numberCell"></td> 
     
    7575          </tr> 
    7676          <tr> 
    77             <th class="fieldLabel">Adjusted Hours:</th> 
     77            <th class="fieldLabel" >Adjusted Hours:</th> 
    7878            <td id="lowAdjusted" class="numberCell" ></td> 
    7979            <td id="highAdjusted" class="numberCell" ></td> 
     
    8282          </tr> 
    8383          <tr> 
    84             <th class="fieldLabel">Cost:</th> 
     84            <th class="fieldLabel">          Cost:</th> 
    8585            <td id="lowCost" class="numberCell" ></td> 
    8686            <td id="highCost" class="numberCell" ></td> 
     
    9999 
    100100            </div> 
     101            <textarea id="diffcomment" name="diffcomment" style="display:none;"></textarea> 
    101102            <textarea id="comment" name="comment" style="display:none;"></textarea> 
    102103        </div> 
  • estimatorplugin/0.11/estimatorplugin/webui.py

    r3361 r4877  
    1111from trac.ticket import Ticket 
    1212import datetime 
     13from trac.web.chrome import Chrome 
    1314from trac.util.datefmt import utc, to_timestamp 
     15from trac.versioncontrol.diff import get_diff_options, diff_blocks 
     16from genshi.template import TemplateLoader 
     17from genshi.filters.transform import Transformer 
     18from trac.web.api import ITemplateStreamFilter 
     19 
     20 
     21# class EstimatorTicketStyleApplication(Component): 
     22#     implements(ITemplateStreamFilter) 
     23     
     24#     def __init__(self): 
     25#         pass 
     26 
     27#     # ITemplateStreamFilter 
     28#     def filter_stream(self, req, method, filename, stream, data): 
     29#         self.log.debug("EstimatorTicketStyleApplication executing")  
     30#         if not filename == 'ticket.html': 
     31#             self.log.debug("EstimatorTicketStyleApplication not the correct template") 
     32#             return stream 
     33#         #stream = stream | Transformer('//link[ends-with(@href,"trac.css")]').after( 
     34#         stream = stream | Transformer('//link[@href="/projects/test/chrome/common/css/trac.css")]').after( 
     35 
     36#             tag.link(type="text/css", rel="stylesheet",  
     37#                        href=req.href.chrome("common", "css" , "diff.css"))() 
     38#             ) 
     39#         return stream 
     40 
     41 
    1442 
    1543class EstimationsPage(Component): 
     
    1745    def __init__(self): 
    1846        pass 
     47 
     48    def get_diffs(self, req, old_text, new_text, id): 
     49        diff_style, diff_options, diff_data = get_diff_options(req) 
     50        diff_context = 3 
     51        for option in diff_options: 
     52            if option.startswith('-U'): 
     53                diff_context = int(option[2:]) 
     54                break 
     55        if diff_context < 0: 
     56            diff_context = None 
     57        diffs = diff_blocks(old_text.splitlines(), new_text.splitlines(), context=diff_context, 
     58                            tabwidth=2, 
     59                            ignore_blank_lines=True, 
     60                            ignore_case=True, 
     61                            ignore_space_changes=True) 
     62         
     63        chrome = Chrome(self.env) 
     64        loader = TemplateLoader(chrome.get_all_templates_dirs()) 
     65        tmpl = loader.load('diff_div.html') 
     66         
     67        title = "Estimate:%s Changed" %id 
     68        changes=[{'diffs': diffs, 'props': [], 
     69                  'title': title, 'href': req.href('Estimate', id=id), 
     70                  'new': {'path':title, 'rev':'', 'shortrev': '', 'href': req.href('Estimate', id=id)}, 
     71                  'old': {'path':"", 'rev':'', 'shortrev': '', 'href': ''}}] 
     72 
     73        data = chrome.populate_data(req, 
     74                                    { 'changes':changes , 'no_id':True, 'diff':diff_data, 
     75                                      'longcol': '', 'shortcol': ''}) 
     76        # diffs = diff_blocks("Russ Tyndall", "Russ Foobar Tyndall", context=None, ignore_blank_lines=True, ignore_case=True, ignore_space_changes=True) 
     77        # data = c._default_context_data.copy () 
     78        diff_data['style']='sidebyside'; 
     79        data.update({ 'changes':changes , 'no_id':True, 'diff':diff_data, 
     80                      'longcol': '', 'shortcol': ''}) 
     81        stream = tmpl.generate(**data) 
     82        return stream.render() 
     83 
     84 
    1985    def load(self, id, addMessage, data): 
    2086        try: 
     
    38104    def line_item_hash_from_args(self, args): 
    39105        not_line_items=['__FORM_TOKEN','tickets','variability','communication', 
    40                         'rate', 'id', 'comment'
     106                        'rate', 'id', 'comment', 'diffcomment'
    41107        itemReg = re.compile(r"(\D+)(\d+)") 
    42108        lineItems = {} 
     
    50116        return lineItems 
    51117 
    52     def notify_old_tickets(self, req, id, addMessage, changer): 
    53         try: 
     118    def notify_old_tickets(self, req, id, addMessage, changer, new_text): 
     119        #try: 
    54120            estimate_rs = getEstimateResultSet(self.env, id) 
    55121            tickets = estimate_rs.value('tickets', 0) 
    56             comment = estimate_rs.value('comment', 0) 
     122            old_text = estimate_rs.value('diffcomment', 0) 
    57123            tickets = [int(t.strip()) for t in tickets.split(',')] 
    58             self.log.debug('Notifying old tickets of estimate change: %s' % tickets) 
     124            self.log.debug('About to render the diffs for tickets: %s ' % (tickets, )) 
     125            comment = """{{{ 
     126#!html 
     127%s 
     128}}} """ % self.get_diffs(req, old_text, new_text, id) 
     129            self.log.debug('Notifying old tickets of estimate change: %s \n %s' % (tickets, comment)) 
    59130            return [(estimateChangeTicketComment, 
    60131                     [t, 
     
    63134                      to_timestamp(datetime.datetime.now(utc)) - 1, 
    64135                      req.authname, 
    65                       "{{{\n#!html\n<del>%s</del>\n}}}" % comment]) 
     136                      comment 
     137                      ]) 
    66138                    for t in tickets] 
    67         except Exception, e: 
     139        #except Exception, e: 
    68140            self.log.error("Error saving old ticket changes: %s" % e) 
    69141            addMessage("Tickets must be numbers") 
     
    89161    def save_from_form (self, req, addMessage): 
    90162        #try: 
    91  
    92163            args = req.args 
    93164            tickets = args["tickets"] 
     
    103174            else: 
    104175                self.log.debug('Saving edited estimate') 
    105                 old_tickets = self.notify_old_tickets(req, id, addMessage, req.authname
     176                old_tickets = self.notify_old_tickets(req, id, addMessage, req.authname, args['diffcomment']
    106177                sql = estimateUpdate 
    107178            self.log.debug('Old Tickets to Update: %r' % old_tickets) 
    108179            estimate_args = [args['rate'], args['variability'], 
    109180                             args['communication'], tickets, 
    110                              args['comment'], id] 
     181                             args['comment'], args['diffcomment'], id] 
    111182            saveEstimate = (sql, estimate_args) 
    112183            saveLineItems = [] 
     
    148219                if self.notify_new_tickets( req, id, tickets, addMessage): 
    149220                    addMessage("Estimate Saved!") 
    150                     req.redirect(req.href.Estimate()+'?id=%s'%id) 
     221                    req.redirect(req.href.Estimate()+'?id=%s&justsaved=true'%id) 
    151222            else: 
    152223                addMessage("Failed to save! %s" % result) 
     
    167238        # for tickets with only old estimates on them, we would still like to apply style 
    168239        url = req.href.Estimate() 
    169         style = req.href.chrome('Estimate/estimate.css') 
     240        #style = req.href.chrome('Estimate/estimate.css') 
    170241        if req.perm.has_permission("TICKET_MODIFY"): 
    171242            yield 'mainnav', "Estimate", \ 
    172                   Markup('<a href="%s">%s</a><link type="text/css" href="%s" rel="stylesheet">' % 
    173                          (url , "Estimate", style)) 
    174         yield 'mainnav', "Estimate-style", \ 
    175               Markup('<link type="text/css" href="%s" rel="stylesheet">' % 
    176                      (style)) 
     243                  Markup('<a href="%s">%s</a>' % 
     244                         (url , "Estimate")) 
    177245 
    178246    # IRequestHandler methods 
     
    203271        if req.args.has_key('id') and req.args['id'].strip() != '': 
    204272            self.load(int(req.args['id']), addMessage, data) 
    205  
     273             
     274        if req.args.has_key('justsaved'): 
     275            tickets = ''.join( 
     276                ['<a href="%s/%s" >#%s</a>' % (req.href.ticket(), i.strip(), i.strip()) 
     277                 for i in data['estimate']['tickets'].split(',')]) 
     278            addMessage("Estimate saved and added to tickets: "+tickets) 
    206279 
    207280        add_script(req, "Estimate/JSHelper.js")