Changeset 3276

Show
Ignore:
Timestamp:
02/26/08 15:19:05 (11 months ago)
Author:
mape
Message:

Imported new 0.6 version

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • timevisualizerplugin/tags/TimeVisualizer_0.6/release_notes.txt

    r2871 r3276  
    11 
    2 TracTimeVisualizerPlugin 
     2TracTimeVisualizerPlugin v. 0.6 
    33-------------------------------------------------------------------------------- 
    44 
    5 This is a plugin to render burn down graphs from ticket history information. 
     5This is a Trac plugin to render burn down graphs from ticket history 
     6information. 
    67 
     8 
     9Usage 
     10-------------------------------------------------------------------------------- 
     11 
     121. uninstall previous installation if such exist, e.g. remove 
     13   `./TracTimeVisualizerPlugin-0.5-py2.4.egg` from 
     14   `/usr/lib/python2.4/site-packages/easy-install.pth` 
     15 
     162. stop web server, e.g. `apache -k stop` 
     17 
     183. Download & install latest plugin:: 
     19 
     20    svn co http://trac-hacks.org/svn/timevisualizerplugin/tags/TimeVisualizer_0.6 
     21    cd TimeVisualizer_0.6 
     22    python setup.py install 
     23 
     244. congigure custom fields to trac.ini, e.g.:: 
     25 
     26    [ticket-custom] 
     27    workleft = text 
     28    workleft.label = WL 
     29    workleft.order = 0 
     30    workleft.value = 0 
     31 
     325. define to trac.ini, where from graph is rendered:: 
     33 
     34    [timevisualizer] 
     35    calc_fields = workleft 
     36 
     37  or make timing and estimation plugin compatible:: 
     38 
     39    [timevisualizer] 
     40    calc_fields = estimatedhours-totalhours 
     41 
     426. enable plugin component in trac.ini:: 
     43 
     44    [components] 
     45    tractimevisualizerplugin.* = enabled 
     46 
     477. Start your web server, e.g. `apache2 -k start` 
     48 
     49Using ISO 8601 format 
     50~~~~~~~~~~~~~~~~~~~~~ 
     51 
     52To enable ISO 8601 dates, times & timedeltas, configure trac.ini:: 
     53 
     54    [timevisualizer] 
     55    time_format = iso8601 
     56 
     57Format can be also set in the actual query. Then just use new formats, e.g. 
     58burndown from 2007 2nd half using one week time delta 
     59 
     60 time_format=iso8601&datestart=2007-07&dateend=2008-01&timeinterval=7D 
     61 
     62Dates shall be passed in form YYYY-MM-DDThh:mm:ss. Components can be left 
     63out from right side (but not from left). For example 2008 is same as 
     64`2008-01-01T00:00:00Z` 
     65 
     66Timedelta shall be passed in form xxYxxMxDTxxHxxMxxS. Any component is allowed, 
     67e.g. 4 days and 5 seconds period is `4DT5S`. 
     68 
     69Version 0.6 
     70-------------------------------------------------------------------------------- 
     71 
     72- Implementation is now Trac 0.11 (dev-r6368) compatible (closes #1956) 
     73- Support added for ISO8601 datatime and period formats (closes #2149). All 
     74  users having users from more than one time zone should migrate burndown macros 
     75  asap. 
     76- Sql routines rewritten => ticket footprint at certain 'revision' may contain 
     77  all ticket fields, including custom ones! Requires small source tuning 
     78  before installation though. This is preparation for custom graph data 
     79  suppliers planned for v.0.7. 
     80- Debug logging was simplified 
     81- Usage documentation moved to pluginwrapper so that WikiMacros page 
     82  or `[[MacroList(BurnDown)]]` renders documentation (requires that 
     83  PythonOptimize disabled is enabled for mod_python). 
    784 
    885Version 0.5 
     
    1087 
    1188- Timing And Estimation plugin no more required, still compatible by default 
    12  
    1389- Burndown graph can be now rendered from custom fields through configuration. 
    1490  Two options available: 
  • timevisualizerplugin/tags/TimeVisualizer_0.6/tractimevisualizerplugin/impl.py

    r2871 r3276  
    1 """This is the actual implementation of SVGRenderer component 
    2  
    3 The actual implementation was moved to this implementation file - this way the wrapper can use reload implementation on each request. This is needed when using mod python - another option would be cgi but it is slow. With reloading & mod python the development becomes extremely fast - modify source, save & reload web page. 
     1"""This file provides actual Burndown renderer implementation 
     2 
     3The actual implementation was moved to this implementation file - this way the 
     4wrapper can use reload implementation on each request. This is needed when using 
     5mod python - another option would be cgi but it is slow. With reloading & mod 
     6python the development becomes extremely fast - modify source, save & reload web 
     7page. 
    48""" 
    59 
     10# ============================================================================== 
    611class NullOut: 
    712    def write(self, data): 
    813        pass 
    914 
    10 def build_svg(db, options, debug=None): 
    11     """= Function build_svg = 
    12  
    13 This function does all the work. Options for method are: 
    14  
    15  * db::trac database instance, usually taken from environment 
    16  * targetmilestone - only tickets data bount to given milestone name is included 
    17  * targetcomponent - only ticket data bound to given component name is included 
    18  * targetticket - only data in given ticket # is included 
    19  * timeinterval - time interval lines as seconds in graph, 3600 = 1h, 86400 = 1d 
    20  * timestart - filters out ticket data before this timestamp 
    21  * timeend - filters out ticket data after this timestamp 
    22  * datestart - overrides timestart if passed, e.g. '8/14/07' 
    23  * dateend - overrides timeend when passed - e.g. '8/20/07' 
    24  * hidedates - any non empty string causes start and end times not to be rendered to the graph 
    25  * hidehours - any non empty string causes hours not to be rendered to the graph 
    26   
    27 Note that filtering tickets that are not belonging to any milestone or component is not currently possible, because there is no way to indicate 'NULL' milestone or component! 
    28 """ 
    29  
    30     if not debug: 
    31         debug = NullOut() 
    32  
     15# ============================================================================== 
     16import trac 
     17from calendar import timegm 
     18import time 
     19import sys 
     20import StringIO 
     21import datetime 
     22 
     23#http://en.wikipedia.org/wiki/ISO_8601 
     24#http://wiki.python.org/moin/WorkingWithTime 
     25#http://seehuhn.de/pages/pdate 
     26#http://cheeseshop.python.org/pypi/iso8601/0.1.2 
     27#http://www.w3.org/TR/NOTE-datetime 
     28#http://hydracen.com/dx/iso8601.htm 
     29#http://www.cl.cam.ac.uk/%7emgk25/iso-time.html 
     30# 12:00Z = 13:00+01:00 = 0700-0500 
     31 
     32# parse period, e.g. P18Y9M4DT11H9M8S 
     33# return seconds. 
     34# note: this gives very approximate result, e.g. 1 month is very obfuscating == 365/12 D 
     35def parse_iso8601_period(text): 
     36    result = 0 
     37    text = text.replace('P','') 
     38    text = text.replace('T','') 
     39    text = text.upper() 
     40    # => 18Y9M4D11H9M8S 
     41    a = ( 
     42      'Y', 365 * 24 * 60 * 60, 
     43      'M', 365 * 24 * 60 * 60 / 12, 
     44      'W', 7 * 24 * 60 * 60, 
     45      'D', 24 * 60 * 60, 
     46      'H', 60 * 60, 
     47      'M', 60, 
     48      'S', 1 ) 
     49    for i in range(0,len(a),2): 
     50        tmp = text.split(a[i]) 
     51        if len(tmp) > 1: 
     52            text = tmp[1] 
     53            result += int(tmp[0]) * a[i+1] 
     54    return result 
     55 
     56def parse_iso8601_datetime(text): 
     57    # todo: expecting zero zone => let user pass in his/her zone? 
     58    seconds = None 
     59    text = text.strip() 
     60    format = '' 
     61    for append in ('%Y', '-%m', '-%d', ' %H', ':%M', ':%S'): 
     62        format += append 
     63        try: 
     64            date = time.strptime(text, format) 
     65            seconds = timegm(date) 
     66            break 
     67        except ValueError: 
     68            continue 
     69    if seconds == None: 
     70        raise ValueError, "'%s' is not ISO 8601 date format (YYYY-MM-DD hh:mm:ss)." % text 
     71    return seconds 
     72 
     73def format_iso8601_datetime(secs): 
     74    return time.strftime('%Y-%m-%d %H:%M:%SZ',time.gmtime(secs)) 
     75 
     76# this wrapper is needed for trac.util.parse_date, cos trac 0.10 does return seconds but 0.11 returns datetime object 
     77def trac_parse_date(obj): 
     78    result = trac.util.parse_date(obj) 
     79    if isinstance(result,datetime.datetime): 
     80        result = timegm(result.timetuple()) 
     81    return result 
     82 
     83# ============================================================================== 
     84def build_svg(db, options): 
     85    """trac db instance (usually taken from environment) is mandatory.""" 
    3386    def strtotype(val, type): 
    3487        if isinstance(val, str) or isinstance(val, unicode): 
     
    3689        return val 
    3790 
     91    # todo: take filters as functions => let caller to build them 
    3892    targetmilestone=options.get('targetmilestone', None) 
    3993    targetticket=strtotype(options.get('targetticket', None), int) 
    4094    targetcomponent=options.get('targetcomponent', None) 
    41     time_interval=strtotype(options.get('timeinterval', 3600*24), int) 
    4295    timestart=strtotype(options.get('timestart', 0), int) 
    4396    timeend=strtotype(options.get('timeend',0), int) 
     
    49102    calc_fields = calc_fields_str.split('-') 
    50103 
    51     print>>debug, "********************** serving ***********************" 
    52  
    53     import trac 
     104    print "********************** serving ***********************" 
     105    format_datetime = trac.util.format_datetime 
     106    parse_date = trac_parse_date 
     107    parse_interval = int 
     108 
     109    if options.get('time_format') == 'iso8601': 
     110        format_datetime = format_iso8601_datetime 
     111        parse_date = parse_iso8601_datetime 
     112        parse_interval = parse_iso8601_period 
     113 
     114    time_interval=strtotype(options.get('timeinterval', 86400), parse_interval) 
     115    assert time_interval > 0, "'timeinterval' must be at least one second, now %d" % time_interval 
     116 
    54117    # override timestart if datestart given 
    55118    try: 
    56         if datestart: timestart = trac.util.parse_date(datestart) 
     119        if datestart: timestart = parse_date(datestart) 
    57120    except Exception: 
    58         raise Exception, "invalid startdate: '%s', expected format: '%s'" % (str(datestart), trac.util.get_date_format_hint()
     121        raise Exception, "invalid startdate: " + str(sys.exc_info()[1]
    59122 
    60123    # override timeend if dateend given 
    61124    try: 
    62         if dateend: timeend = trac.util.parse_date(dateend) 
     125        if dateend: timeend = parse_date(dateend) 
    63126    except Exception: 
    64         raise Exception, "invalid dateend: '%s', expected format: '%s'" % (str(dateend), trac.util.get_date_format_hint()
    65  
    66     # this dict stores tickets. Tickets are updated on each 'revision' visited - so this is somekinf of snapshot of ticket data at given time 
     127        raise Exception, "invalid dateend: " + str(sys.exc_info()[1]
     128 
     129    # this dict stores tickets. Tickets are updated on each 'revision' visited - so this contains snapshot of tickets at certain time 
    67130    tickets = {} 
    68  
    69     def calc_hours(tickets,max_time): 
    70         """Calculates totalhours of tickets on current tickets state taking filters in the account.""" 
    71         result = 0.0 
    72         for ticket in tickets.values(): 
    73             if targetmilestone and ticket['milestone'] != targetmilestone: continue 
    74             if targetticket and ticket['ticket'] != targetticket: continue 
    75             if targetcomponent and ticket['component'] != targetcomponent: continue 
    76             if len(calc_fields) == 2: 
    77                 result += ticket[calc_fields[0]] - ticket[calc_fields[1]] 
    78             else: 
    79                 result += ticket[calc_fields[0]] 
    80             #print>>debug, ticket 
    81         return result 
    82131 
    83132    def tofloat(obj): 
     
    91140        return float(obj) #fallback 
    92141 
     142    def calc_hours(tickets): 
     143        """Calculates totalhours of tickets on current tickets state taking filters in the account.""" 
     144        result = 0.0 
     145        for ticket in tickets.values(): 
     146            if targetmilestone and ticket['milestone'] != targetmilestone: continue 
     147            if targetticket and ticket['id'] != targetticket: continue 
     148            if targetcomponent and ticket['component'] != targetcomponent: continue 
     149            if len(calc_fields) == 2: 
     150                result += tofloat(ticket[calc_fields[0]]) - tofloat(ticket[calc_fields[1]]) 
     151            else: 
     152                result += tofloat(ticket[calc_fields[0]]) 
     153        return result 
     154 
    93155    cursor = db.cursor() 
    94156 
     
    97159    # ---------------------------------------------------- 
    98160 
    99     if len(calc_fields) == 2: 
    100         sql = """ 
    101             SELECT t.id, t.time, t.status, t.milestone, est.value, th.value, t.component 
    102             FROM ticket t 
    103                 LEFT OUTER JOIN ticket_custom est ON (t.id = est.ticket AND est.name = '%s') 
    104                 LEFT OUTER JOIN ticket_custom th ON (t.id = th.ticket AND th.name = '%s') 
    105             ORDER BY t.id 
    106             """ % (calc_fields[0], calc_fields[1]) 
    107  
    108         cursor.execute(sql) 
    109         for (ticket,time, status,milestone,estimatedhours,totalhours,component) in cursor.fetchall(): 
    110             tickets[ticket] = {'ticket':ticket, 
    111                                'time':time, 
    112                                'status':status, 
    113                                'milestone':milestone, 
    114                                calc_fields[0]:tofloat(estimatedhours), 
    115                                calc_fields[1]:tofloat(totalhours), 
    116                                'component':component} 
    117     else: 
    118         sql = """ 
    119             SELECT t.id, t.time, t.status, t.milestone, cust.value, t.component 
    120             FROM ticket t 
    121                 LEFT OUTER JOIN ticket_custom cust ON (t.id = cust.ticket AND cust.name = '%s') 
    122             ORDER BY t.id 
    123             """ % (calc_fields[0]) 
    124  
    125         cursor.execute(sql) 
    126         for (ticket,time, status,milestone,workleft,component) in cursor.fetchall(): 
    127             tickets[ticket] = {'ticket':ticket, 
    128                                'time':time, 
    129                                'status':status, 
    130                                'milestone':milestone, 
    131                                calc_fields[0]:tofloat(workleft), 
    132                                'component':component} 
     161    ticket_fields = ( 
     162        'id', 
     163#        'type', 
     164#        'time',       # created 
     165#        'changetime', # modified 
     166        'component', 
     167#        'severity', 
     168#        'priority', 
     169#        'owner', 
     170#        'reporter', 
     171#        'cc', 
     172#        'version', 
     173        'milestone', 
     174#        'status', 
     175#        'resolution', 
     176#        'summary', 
     177#        'description', 
     178#        'keywords' 
     179        ) 
     180 
     181    # todo: fetch from db/env 
     182    custom_ticket_fields = calc_fields 
     183 
     184    sql = StringIO.StringIO() 
     185    sql.write('SELECT \n') 
     186    for i in range(len(ticket_fields)): 
     187        if i>0 : sql.write(',\n') 
     188        sql.write('  t.%s AS %s' % (ticket_fields[i], ticket_fields[i])) 
     189 
     190    for i in range(len(custom_ticket_fields)): 
     191        sql.write(',\n') 
     192        sql.write('  j%d.value AS %s' % (i, custom_ticket_fields[i])) 
     193 
     194    sql.write('\nFROM ticket t\n') 
     195    for i in range(len(custom_ticket_fields)): 
     196        sql.write("  LEFT OUTER JOIN ticket_custom j%d ON(t.id=j%d.ticket AND j%d.name='%s')\n" % (i,i,i,custom_ticket_fields[i])) 
     197 
     198    sql.write('ORDER BY t.id') 
     199    #print sql.getvalue() 
     200 
     201    cursor.execute(sql.getvalue()) 
     202    tickets = {} 
     203    all_fields = [] 
     204    all_fields += ticket_fields 
     205    all_fields += custom_ticket_fields 
     206    it = cursor.fetchall() 
     207    for row in it: 
     208        ticket = {} 
     209        tickets[row[0]] = ticket 
     210        for i in range(len(all_fields)): 
     211            ticket[all_fields[i]] = row[i] 
    133212 
    134213    # ---------------------------------------------------- 
     
    139218    result = [] 
    140219    def process_time(time): 
    141         hours = calc_hours(tickets,time) # todo: remove time from call 
     220        hours = calc_hours(tickets) 
    142221        if len(result) >= 2 and result[-1] == hours: 
    143222            # update timestamp 
     
    151230        del tickets[id] 
    152231 
     232    process_ticket_change_sql = """ 
     233        SELECT field, oldvalue 
     234        FROM ticket_change 
     235        WHERE time=%d AND ticket=%d AND field IN (""" + "'" + "','".join(all_fields) + "'" + """) 
     236        ORDER BY time desc""" 
     237 
    153238    def process_ticket_change(t, id): 
    154         sql = """ 
    155             SELECT ticket, time, field, oldvalue, newvalue 
    156             FROM ticket_change 
    157             WHERE time=%d AND ticket=%d 
    158             ORDER BY time desc""" % (t,id) 
     239        sql = process_ticket_change_sql % (t,id) 
    159240 
    160241        cursor.execute(sql) 
    161242        data = cursor.fetchall() 
    162         # print>>debug, "ticket_change data:" 
    163         #for line in data: 
    164         #    print>>debug, line 
    165  
    166         for (ticket,time,field,oldvalue,newvalue) in data: 
     243        for (field,oldvalue) in data: 
    167244            # we iterate backwards, thus we save old values 
    168             if field == calc_fields[0]: 
    169                 tickets[ticket][calc_fields[0]] = tofloat(oldvalue) 
    170             elif len(calc_fields) == 0 and field == calc_fields[1]: 
    171                 tickets[ticket][calc_fields[1]] = tofloat(oldvalue) 
    172             elif field == 'milestone': 
    173                 tickets[ticket]['milestone'] = oldvalue 
    174             elif field == 'component': 
    175                 tickets[ticket]['component'] = oldvalue 
     245            tickets[id][field] = oldvalue 
    176246 
    177247    # -- find out timestamps (revisions) and bind processors for them 
    178248 
    179249    timestamps = {} 
     250 
     251    # creation times 
    180252    cursor.execute("SELECT DISTINCT time, id from ticket ORDER BY time") 
    181253    for line in cursor.fetchall(): 
    182254        timestamps[int(line[0])] = [(process_ticket_create, int(line[1]))] 
    183255 
     256    # modified times 
    184257    cursor.execute("SELECT DISTINCT time, ticket from ticket_change ORDER BY time") 
    185258    for line in cursor.fetchall(): 
     
    188261        timestamps[int(line[0])] = item 
    189262 
    190     # -- process each timestamp from oldest to newest (direction is importat cos we know only the latest time) 
     263    # -- process each timestamp from oldest to newest (direction is importat cos we know only the latest state) 
    191264 
    192265    tmp = [] + timestamps.keys() 
     
    194267    tmp.reverse() # from oldest to youngest 
    195268    for t in tmp: 
    196         # print status after changes first (remember backward iterating) 
    197269        process_time(t) 
    198270        for processor in timestamps[t]: 
     
    213285    if len(result) < 4: 
    214286        return NO_DATA 
    215      
     287 
    216288    # last item is zero and timestamp is updated to first ticket creation, so it needs to be 'scaled' to first item where hours change 
    217289    result[-2] = result[-4] 
    218290 
    219     #print>>debug, "result=",str(result) 
     291    #print, "result=",str(result) 
    220292 
    221293    # if start or end times are defined, drop results outside of them 
     
    252324    if timeend: 
    253325        largesttime = timeend - smallesttime 
    254  
    255  
    256  
    257326 
    258327    maxhours = 0.1; # there was division by zero if all hours are zero (should not happen anymore though) 
     
    302371    if not hidedates: 
    303372        # draw graph start & end dates 
    304         import trac 
    305373        # http://www.w3.org/TR/SVG11/text.html#AlignmentProperties 
    306         svg += '<text x="0" y="99%%" text-anchor="start" style="font-family:verdana;">%s</text>' % (trac.util.format_datetime(smallesttime)) 
    307         svg += '<text x="100%%" y="99%%" text-anchor="end" style="font-family:verdana;">%s</text>' % (trac.util.format_datetime(smallesttime+largesttime)) 
     374        svg += '<text x="0" y="99%%" text-anchor="start" style="font-family:verdana;">%s</text>' % (format_datetime(smallesttime)) 
     375        svg += '<text x="100%%" y="99%%" text-anchor="end" style="font-family:verdana;">%s</text>' % (format_datetime(smallesttime+largesttime)) 
    308376 
    309377    svg += "</svg>\n" 
     
    316384        return build_svg(db, args) 
    317385 
    318     from trac.env import open_environment 
    319     env = open_environment("c:\\scm\\trac\\visualizerdemo") 
     386    env = trac.env.open_environment("/var/scm/trac/tvdemo1") 
    320387    db = env.get_db_cnx() 
    321     import sys 
    322     svg = build_svg_paramlist(db, milestone='mile1', time_interval=3600*24, debug=sys.stdout, datestart="8/1/07", dateend="10/1/07") 
    323  
    324     FILE = open("c:\\test.svg", "w") 
     388    svg = build_svg_paramlist(db, milestone='milestone1', time_interval=3600*24, datestart="8/1/07", dateend="10/1/07") 
     389 
     390    FILE = open("test.svg", "w") 
    325391    FILE.write(svg) 
    326392    FILE.close() 
     
    328394def process_request(plugin, req): 
    329395    """Renders a svg graph based on request attributes and returns a http response (or traceback in case of error)""" 
    330     class MyDebug: 
    331         out = "" 
    332         def write(self, data): 
    333             self.out += data 
     396 
     397    old_sys_stdout = sys.stdout 
    334398 
    335399    import tractimevisualizerplugin 
    336     debug = None; 
    337400    if tractimevisualizerplugin.DEVELOPER_MODE: 
    338         debug = MyDebug() 
     401        sys.stdout = StringIO.StringIO() 
     402    else: 
     403        sys.stdout = NullOut() 
    339404    try: 
    340         #print>>debug, dir(req) 
    341         from trac.web import RequestDone 
    342         from trac.util.datefmt import http_date 
    343         from time import time 
    344  
    345405        req.send_response(200) 
    346406        req.send_header('Content-Type', "image/svg+xml") 
    347         req.send_header('Last-Modified', http_date(time())) 
     407        req.send_header('Last-Modified', trac.util.datefmt.http_date(time.time())) 
    348408        req.end_headers() 
    349409 
     
    351411            db = plugin.env.get_db_cnx() 
    352412            args = req.args.copy() 
    353             args['calc_fields'] = plugin.env.config.get('timevisualizer','calc_fields','estimatedhours-totalhours') 
    354             req.write(build_svg(db, args, debug)) 
    355         raise RequestDone 
     413            if not args.get('calc_fields'): 
     414                args['calc_fields'] = plugin.env.config.get('timevisualizer','calc_fields','estimatedhours-totalhours') 
     415            if not args.get('time_format'): 
     416                args['time_format'] = plugin.env.config.get('timevisualizer','time_format', None) 
     417            req.write(build_svg(db, args)) 
     418        raise trac.web.RequestDone 
    356419    finally: 
    357         if debug: 
    358             plugin.log.debug(debug.out) 
     420        log = sys.stdout 
     421        sys.stdout = old_sys_stdout 
     422        if isinstance(log, StringIO.StringIO): 
     423            plugin.log.debug(log.getvalue()) 
  • timevisualizerplugin/tags/TimeVisualizer_0.6/tractimevisualizerplugin/__init__.py

    r2871 r3276  
    55DEVELOPER_MODE=False 
    66 
    7 __version__ = '0.5
     7__version__ = '0.6
    88__url__ = 'http://trac-hacks.org/wiki/TimeVisualizerPlugin' 
    99__author__ = 'Markus Pelkonen' 
    10 __copyright__ = 'Copyright (C) 2007 Markus Pelkonen' 
     10__copyright__ = 'Copyright (C) 2008 Markus Pelkonen' 
    1111__license__ = 'BSD' 
    1212__license_long__ = __copyright__ + """ 
  • timevisualizerplugin/tags/TimeVisualizer_0.6/tractimevisualizerplugin/pluginwrapper.py

    r2607 r3276  
    3232 
    3333class BurnDownMacro(WikiMacroBase): 
    34     """Renders iframe sets the content to be svg created by burndownimage
     34    """This macro renders iframe and sets to be svg image to be created on the fly based on given filters
    3535 
    36 Takes three options: width, height and query. Query is the the format of http query and is passed to SVGRenderer
     36Macro takes three options: width, height and query
    3737 
    38 Example macro usage: 
     38Query is the the format of http query and is passed to SVGRenderer as is. Query parameters are used as follows: 
     39 
     40To include ticket history from certain milestone/component/or certain ticket, use filters: 
     41 
     42 * targetmilestone - only tickets data bound to given milestone name are included 
     43 * targetcomponent - only ticket data bound to given component name are included 
     44 * targetticket - only data in given ticket # is included 
     45 
     46To limit burndown to certain time period, use following filters: 
     47 
     48 * timestart - filters out ticket data before this timestamp 
     49 * timeend - filters out ticket data after this timestamp 
     50 * datestart - overrides timestart if passed, e.g. '8/14/07' 
     51 * dateend - overrides timeend when passed - e.g. '8/20/07' 
     52 
     53To override L&F of the generated svg: 
     54 
     55 * timeinterval - time interval lines (horisontal) as seconds in graph, 3600 = 1h, 86400 = 1 day 
     56 * hidedates - any non empty string causes start and end times not to be rendered to the graph (X-axis) 
     57 * hidehours - any non empty string causes hours not to be rendered to the graph (Y-axis) 
     58 
     59To use ISO8601 time in date & time parameters, override with `time_format=iso8601` 
     60 
     61To override which fields is used to calucalte Y-value at certain change, define `calc_fields`, e.g. 
     62`calc_fields=workleft` or `calc_fields=estimatedhours-totalhours` 
     63 
     64Few use examples: 
     65 1. Simplest ever case: all tickets from the whole project history 
    3966{{{ 
    40 [[BurnDown(width=600,height=200,query=targetmilestone=milestone1&dateend=8/31/07)]] 
     67[[BurnDown]] 
     68}}} 
     69 
     70 1. Example macro usage using old pre 0.6 time format: 
     71{{{ 
     72[[BurnDown(width=600,height=200,query=targetmilestone=mymilestone&dateend=8/31/07)]] 
     73}}} 
     74 
     75 2. Example macro usage using ISO 8601 format: 
     76{{{ 
     77[[BurnDown(width=600,height=200,query=targetmilestone=2007-12&time_format=iso8601&datestart=2007-12&dateend=2008-01&timeinterval=1D)]] 
    4178}}} 
    4279"""