Changeset 2581

Show
Ignore:
Timestamp:
08/20/07 12:25:18 (1 year ago)
Author:
khundeen
Message:

NEW: Ticket#1601
- Add More project statistics (backlog metrics, changeset stats)

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • tracmetrixplugin/0.11/tracmetrixplugin/mdashboard.py

    r2570 r2581  
    117117    event_history = cursor.fetchall() 
    118118     
    119     env.log.info("event_history = %s" % (event_history,)) 
     119    #env.log.info("event_history = %s" % (event_history,)) 
    120120     
    121121    # TODO The tricky thing about this is that we have to deterimine 5 different type of ticket. 
     
    287287     
    288288    matplotlib.use('Agg') 
    289     cla() 
    290     fig = figure(
     289    #cla() 
     290    fig = figure(figsize = (6,4)
    291291    ax = fig.add_subplot(111) # Create supplot with key 111        
     292    ax.cla() 
    292293    ax.plot(numdates, tkt_cummulative_table['Enter'], 'b-') 
    293294    ax.plot(numdates, tkt_cummulative_table['Leave'], 'r-')  
     
    298299    ax.fmt_xdata = DateFormatter('%Y-%m-%d %H:%M:%S') 
    299300    labels = ax.get_xticklabels() 
    300     setp(labels, rotation=45, fontsize=8
     301    setp(labels, rotation=45, fontsize=6
    301302     
    302303    xlabel('Dates (day)') 
    303304    ylabel('Counts (times)') 
    304305    title('Cummulative flow chart for ticket status history') 
    305     legend(('Ticket Entered', 'Ticket Left', 'Ticket Completed'), loc='upper left') 
     306    legend(('Ticket Entered', 'Ticket Left', 'Ticket Completed'), loc='best') 
    306307     
    307308    filename = "cummulativeflow_%s" % (milestone.name,) 
     
    487488            tkt_history = collect_tickets_status_history(self.env, db, everytickets, milestone) 
    488489             
     490            if tkt_history != {}: 
    489491                             
    490             # Sort the key in the history list 
    491             # returns sorted list of tuple of (key, value) 
    492             sorted_events = sorted(tkt_history.items(), key=lambda(k,v):(k)) 
    493      
    494             #debug   
    495             for event in sorted_events: 
    496                 self.env.log.info("date: %s: event: %s" % (format_date(to_datetime(event[0])), event[1])) 
    497      
    498            
    499             # Get first date that ticket enter the milestone 
    500             min_time = min(sorted_events)[0] #in Epoch Seconds 
    501             begin_date = datetime.fromtimestamp(min_time, utc).date()  
    502              
    503             if milestone.completed != None: 
    504                 end_date = milestone.completed         
    505             else: 
    506                 end_date = datetime.now(utc).date() 
    507              
    508             #self.env.log.info("begindate: Timezone %s:%s, UTC:%s)" % \ 
    509             #                  (req.tz,datetime.fromtimestamp(min_time, req.tz).date(), \ 
    510             #                   datetime.fromtimestamp(min_time, utc).date()))  
    511      
    512             #self.env.log.info("enddate: UTC:%s" % (end_date,)) 
    513  
    514              
    515          
    516             # this is array of date in numpy 
    517             numdates = drange(begin_date, end_date + timedelta(days=1), timedelta(days=1)) 
    518              
    519             tkt_history_table = make_ticket_history_table(self.env, numdates, sorted_events) 
    520      
    521             #debug 
    522             #self.env.log.info("tkt_history_table: %s", (tkt_history_table,))    
    523              
    524             #Create a data for the cumulative flow chart. 
    525             tkt_cummulative_table = make_cummulative_data(self.env, tkt_history_table) 
    526              
    527             #debug 
    528             #self.env.log.info(tkt_cummulative_table)    
    529          
    530             # creat list of dateobject from dates 
    531             dates = [] 
    532             for numdate in numdates: 
    533                  
    534                 utc_date = num2date(numdate) 
    535                 dates.append(utc_date) 
    536                 #self.env.log.info("%s: %s" % (utc_date, format_date(utc_date, tzinfo=utc))) 
    537              
    538             data['tickethistory'] = tkt_cummulative_table 
    539             data['dates'] = dates 
    540              
    541             create_cummulative_chart(self.env, milestone, numdates, tkt_cummulative_table) 
     492                # Sort the key in the history list 
     493                # returns sorted list of tuple of (key, value) 
     494                sorted_events = sorted(tkt_history.items(), key=lambda(k,v):(k)) 
     495         
     496                #debug   
     497                self.env.log.info("sorted_event content") 
     498                for event in sorted_events: 
     499                    self.env.log.info("date: %s: event: %s" % (format_date(to_datetime(event[0])), event[1])) 
     500         
     501               
     502                # Get first date that ticket enter the milestone 
     503                min_time = min(sorted_events)[0] #in Epoch Seconds 
     504                begin_date = datetime.fromtimestamp(min_time, utc).date()  
     505                 
     506                if milestone.completed != None: 
     507                    end_date = milestone.completed         
     508                else: 
     509                    end_date = datetime.now(utc).date() 
     510                 
     511                #self.env.log.info("begindate: Timezone %s:%s, UTC:%s)" % \ 
     512                #                  (req.tz,datetime.fromtimestamp(min_time, req.tz).date(), \ 
     513                #                   datetime.fromtimestamp(min_time, utc).date()))  
     514         
     515                #self.env.log.info("enddate: UTC:%s" % (end_date,)) 
     516     
     517                 
     518             
     519                # this is array of date in numpy 
     520                numdates = drange(begin_date, end_date + timedelta(days=1), timedelta(days=1)) 
     521                 
     522                tkt_history_table = make_ticket_history_table(self.env, numdates, sorted_events) 
     523         
     524                #debug 
     525                #self.env.log.info("tkt_history_table: %s", (tkt_history_table,))    
     526                 
     527                #Create a data for the cumulative flow chart. 
     528                tkt_cummulative_table = make_cummulative_data(self.env, tkt_history_table) 
     529                 
     530                #debug 
     531                #self.env.log.info(tkt_cummulative_table)    
     532             
     533                # creat list of dateobject from dates 
     534                dates = [] 
     535                for numdate in numdates: 
     536                     
     537                    utc_date = num2date(numdate) 
     538                    dates.append(utc_date) 
     539                    #self.env.log.info("%s: %s" % (utc_date, format_date(utc_date, tzinfo=utc))) 
     540                 
     541                data['tickethistory'] = tkt_cummulative_table 
     542                data['dates'] = dates 
     543                 
     544                create_cummulative_chart(self.env, milestone, numdates, tkt_cummulative_table) 
    542545 
    543546         
  • tracmetrixplugin/0.11/tracmetrixplugin/model.py

    r2570 r2581  
    11import sys 
     2import os 
     3 
    24from datetime import timedelta, datetime 
    35from trac.core import * 
    46from trac.ticket import Ticket, model 
    5 from trac.util.datefmt import utc 
     7from trac.util.datefmt import utc, to_timestamp, to_datetime 
    68from trac.ticket.roadmap import ITicketGroupStatsProvider, TicketGroupStats 
     9 
     10from bisect import bisect 
     11import matplotlib 
     12from pylab import * 
     13from matplotlib.dates import DayLocator, HourLocator, DateFormatter, drange 
    714 
    815 
     
    5360        return stat 
    5461 
    55  
    56 class TicketTypeGroupStatsProvider(Component): 
    57     implements(ITicketGroupStatsProvider) 
    58  
    59     def get_ticket_group_stats(self, ticket_ids): 
     62    def get_ticket_resolution_group_stats(self, ticket_ids): 
     63         
     64        # ticket_ids is a list of ticket id as number. 
     65        total_cnt = len(ticket_ids) 
     66        if total_cnt: 
     67            str_ids = [str(x) for x in sorted(ticket_ids)] # create list of ticket id as string 
     68            cursor = self.env.get_db_cnx().cursor()  # get database connection     
     69             
     70            type_count = [] # list of dictionary with key name and count 
     71             
     72            for type in model.Resolution.select(self.env): 
     73             
     74                count = cursor.execute("SELECT count(1) FROM ticket " 
     75                                        "WHERE status = 'closed' AND resolution = '%s' AND id IN " 
     76                                        "(%s)" % (type.name, ",".join(str_ids))) # execute query and get cursor obj. 
     77                count = 0 
     78                for cnt, in cursor: 
     79                    count = cnt 
     80 
     81                if count > 0: 
     82                    type_count.append({'name':type.name,'count':count}) 
     83 
     84        else: 
     85            type_count = [] 
     86         
     87        stat = TicketGroupStats('ticket resolution', 'ticket') 
     88         
     89         
     90        for type in type_count: 
     91                 
     92            if type['name'] == 'fixed': # default ticket type 'defect' 
     93         
     94                stat.add_interval(type['name'], type['count'], 
     95                                  {'type': type['name']}, 'value', True) 
     96             
     97            else: 
     98                stat.add_interval(type['name'], type['count'], 
     99                                  {'type': type['name']}, 'waste', False) 
     100                           
     101        stat.refresh_calcs() 
     102        return stat 
     103 
     104    def get_ticket_type_group_stats(self, ticket_ids): 
    60105         
    61106        # ticket_ids is a list of ticket id as number. 
     
    79124                    type_count.append({'name':type.name,'count':count}) 
    80125 
     126        else: 
     127            type_count = [] 
    81128         
    82129        stat = TicketGroupStats('ticket type', 'ticket') 
     130         
    83131         
    84132        for type in type_count: 
     
    96144        return stat 
    97145 
     146class TicketTypeGroupStatsProvider(Component): 
     147    implements(ITicketGroupStatsProvider) 
     148 
     149    def get_ticket_group_stats(self, ticket_ids): 
     150         
     151        # ticket_ids is a list of ticket id as number. 
     152        total_cnt = len(ticket_ids) 
     153        if total_cnt: 
     154            str_ids = [str(x) for x in sorted(ticket_ids)] # create list of ticket id as string 
     155            cursor = self.env.get_db_cnx().cursor()  # get database connection     
     156             
     157            type_count = [] # list of dictionary with key name and count 
     158             
     159            for type in model.Type.select(self.env): 
     160             
     161                count = cursor.execute("SELECT count(1) FROM ticket " 
     162                                        "WHERE type = '%s' AND id IN " 
     163                                        "(%s)" % (type.name, ",".join(str_ids))) # execute query and get cursor obj. 
     164                count = 0 
     165                for cnt, in cursor: 
     166                    count = cnt 
     167 
     168                if count > 0: 
     169                    type_count.append({'name':type.name,'count':count}) 
     170 
     171        else: 
     172            type_count = [] 
     173         
     174        stat = TicketGroupStats('ticket type', 'ticket') 
     175         
     176         
     177        for type in type_count: 
     178                 
     179            if type['name'] != 'defect': # default ticket type 'defect' 
     180         
     181                stat.add_interval(type['name'], type['count'], 
     182                                  {'type': type['name']}, 'value', True) 
     183             
     184            else: 
     185                stat.add_interval(type['name'], type['count'], 
     186                                  {'type': type['name']}, 'waste', False) 
     187                           
     188        stat.refresh_calcs() 
     189        return stat 
     190 
    98191class TicketGroupMetrics(object): 
    99192     
     
    151244    def get_tickets_created_during(self, start_date, end_date): 
    152245         
     246        end_date = end_date.replace(hour=23, minute=59, second=59) 
     247         
    153248        tkt_ids = [] 
    154249         
     
    161256    def get_remaning_opened_ticket_on(self, end_date): 
    162257      
     258        end_date = end_date.replace(hour=23, minute=59, second=59) 
     259      
    163260        tkt_ids = [] 
    164261         
     
    169266             
    170267                if ticket.values['status'] == 'closed':         
    171                      
    172268                     
    173269                    was_opened = True         
     
    189285                # Assume that ticket that is not closed are opened 
    190286                else: 
    191                     # only add the ticket that was modified before the end date 
    192                     if end_date >= ticket.time_changed: 
     287                    # only add the ticket that was created before the end date 
     288                    if end_date >= ticket.time_created: 
    193289                        tkt_ids.append(ticket.id) 
    194              
    195              
    196         self.env.log.info(tkt_ids) 
    197          
     290                     
    198291        return tkt_ids 
    199      
    200          
     292           
    201293     
    202294    def get_tickets_closed_during(self, start_date, end_date): 
     295         
     296        end_date = end_date.replace(hour=23, minute=59, second=59) 
    203297         
    204298        tkt_ids = [] 
     
    228322                closed_tickets, 
    229323                float(len(closed_tickets)) * 100 / float(len(opened_tickets))) 
    230      
     324         
     325    def get_daily_backlog_history(self, start_date, end_date): 
     326        """ 
     327            returns list of tuple (date,stats) 
     328                date is date value in epoc time 
     329                stats is dictionary of {'created':[], 'opened':[], 'closed':[]} 
     330        """     
     331         
     332        # this is array of date in numpy 
     333        numdates = drange(start_date, end_date + timedelta(days=1), timedelta(days=1)) 
     334         
     335#        for date in numdates: 
     336#            self.env.log.info(num2date(date)) 
     337         
     338        end_date = end_date.replace(hour=23, minute=59, second=59) 
     339         
     340         
     341        # each key is the list of list of ticket.  The index of the list is corresponding 
     342        # to the index of the date in numdates list. 
     343        backlog_stats = {'created':[], 'opened':[], 'closed':[]} 
     344         
     345        # initialize backlog_stats 
     346         
     347        for date in numdates: 
     348            for key in backlog_stats: 
     349                backlog_stats[key].append([]) 
     350         
     351        # start by getting the list of opened ticket at the end of the start date.         
     352        backlog_stats['opened'][0] = self.get_remaning_opened_ticket_on(start_date) 
     353         
     354        for ticket in self.tickets: 
     355         
     356            # only consider the ticket that was created before end dates. 
     357            if ticket.time_created <= end_date: 
     358                 
     359                # only track the ticket that create since start_date 
     360                if ticket.time_created >= start_date: 
     361                    # determine index 
     362                    date = ticket.time_created.date()             
     363                    #get index of day in the dates list 
     364                    index = bisect(numdates, date2num(date)) - 1                 
     365                 
     366                    # add ticket created ticket list 
     367                    backlog_stats['created'][index].append(ticket.id) 
     368             
     369                for t, author, field, oldvalue, newvalue, permanent in ticket.get_changelog(): 
     370 
     371                    # determine index 
     372                    date = t.date()             
     373                    #get index of day in the dates list 
     374                    index = bisect(numdates, date2num(date)) - 1    
     375                     
     376                    if field == 'status' and start_date <= t <= end_date: 
     377                         
     378                        if newvalue == 'closed': 
     379                            # add ticket created ticket list 
     380                            backlog_stats['closed'][index].append(ticket.id) 
     381                         
     382                        elif newvalue == 'reopen': 
     383                            backlog_stats['opened'][index].append(ticket.id) 
     384 
     385        # update opened ticket list 
     386        for idx, list in enumerate(backlog_stats['opened']): 
     387             
     388            if idx > 0: 
     389                 
     390                # merge list of opened ticket from previous day 
     391                list.extend(backlog_stats['opened'][idx-1]) 
     392                 
     393                # add created ticket to opened ticket list 
     394                list.extend(backlog_stats['created'][idx]) 
     395         
     396                # remove closed ticket from opened ticket list. 
     397                for id in backlog_stats['closed'][idx]: 
     398                    list.remove(id) 
     399                 
     400                list.sort() 
     401                 
     402#        for idx, numdate in enumerate(numdates): 
     403#            self.env.log.info(num2date(numdate)) 
     404#            self.env.log.info(backlog_stats['created'][idx]) 
     405#            self.env.log.info(backlog_stats['opened'][idx]) 
     406#            self.env.log.info(backlog_stats['closed'][idx])                                                         
     407                                                                               
     408        return (numdates, backlog_stats) 
     409             
     410    def get_daily_backlog_chart(self, backlog_history): 
     411         
     412        numdates = backlog_history[0] 
     413        backlog_stats = backlog_history[1] 
     414         
     415        # create counted list. 
     416        opened_tickets_dataset = [len(list) for list in backlog_stats['opened']] 
     417        created_tickets_dataset = [len(list) for list in backlog_stats['created']] 
     418         
     419        # need to add create and closed ticket for charting purpose. We want to show 
     420        # closed tickets on top of opened ticket in bar chart. 
     421        closed_tickets_dataset = [] 
     422        for i in range(len(created_tickets_dataset)): 
     423            closed_tickets_dataset.append(created_tickets_dataset[i] + len(backlog_stats['closed'][i])) 
     424                 
     425        bmi_dataset = [] 
     426        for i in range(len(opened_tickets_dataset)): 
     427            bmi_dataset.append(float(closed_tickets_dataset[i])*100/float(opened_tickets_dataset[i])) 
     428     
     429#        for idx, numdate in enumerate(numdates): 
     430#            self.env.log.info("%s: %s, %s, %s" % (num2date(numdate),  
     431#                                                    closed_tickets_dataset[idx], 
     432#                                                    opened_tickets_dataset[idx], 
     433#                                                    created_tickets_dataset[idx])) 
     434         
     435        matplotlib.use('Agg') 
     436        #cla() 
     437        fig = figure(figsize = (6,4)) 
     438        ax = fig.add_subplot(111) # Create supplot with key 111        
     439        line1 = ax.plot_date(numdates, opened_tickets_dataset, '-') 
     440        line2 = ax.bar(numdates, closed_tickets_dataset, 0.5, color='#bae0ba')  
     441        line3 = ax.bar(numdates, created_tickets_dataset, 0.5, color='#9966ff')  
     442        ax.set_xlim( numdates[0], numdates[-1] ) 
     443        ax.xaxis.set_major_locator(DayLocator()) 
     444        ax.xaxis.set_major_formatter( DateFormatter('%Y-%m-%d')) 
     445        labels = ax.get_xticklabels() 
     446        setp(labels, rotation=45, fontsize=6) 
     447         
     448        xlabel('Dates (day)') 
     449        ylabel('Number of tickets') 
     450        title('Opened/Closed Tickets') 
     451        ax.legend((line1, line2[0], line3[0]), ('Opened Tickets', 'Closed/day', 'Created/day'), loc='best') 
     452         
     453        filename = "dailybacklog" 
     454        path = os.path.join(self.env.path, 'cache', 'tracmetrixplugin', filename) 
     455         
     456        fig.savefig(path) 
     457         
     458        return path 
     459             
    231460class TicketMetrics(object): 
    232461     
     
    349578            stdev = (sdsq / (len(self.sequence) - 1 or 1)) ** .5 
    350579            return stdev 
     580         
     581class ChangesetsStats: 
     582     
     583    def __init__(self, env, start_date=None, stop_date=None): 
     584     
     585        self.env = env 
     586         
     587        self.start_date = start_date 
     588        self.stop_date = stop_date 
     589        self.first_rev = self.last_rev = None 
     590         
     591        if start_date != None and stop_date !=None: 
     592            self.set_date_range(start_date, stop_date) 
     593     
     594    def set_date_range(self, start_date, stop_date):  
     595 
     596        cursor = self.env.get_db_cnx().cursor() 
     597        cursor.execute("SELECT rev, time, author FROM revision " 
     598                       "WHERE time >= %s AND time < %s ORDER BY time", 
     599                       (to_timestamp(start_date), to_timestamp(stop_date))) 
     600         
     601        self.changesets = [] 
     602        for rev, time, author in cursor: 
     603            self.changesets.append((rev,time,author)) 
     604         
     605        self.start_date = start_date 
     606        self.stop_date = stop_date     
     607        self.first_rev = self.changesets[0][0] 
     608        self.last_rev = self.changesets[-1][0] 
     609     
     610    def get_commit_by_date(self): 
     611         
     612        numdates = drange(self.start_date, self.stop_date, timedelta(days=1)) 
     613 
     614        numcommits = [0 for i in numdates] 
     615         
     616        for rev, time, author in self.changesets: 
     617             
     618            date = to_datetime(time, utc).date() 
     619            #get index of day in the dates list 
     620            index = bisect(numdates, date2num(date)) - 1      
     621             
     622            numcommits[index] += 1 
     623         
     624        return (numdates, numcommits) 
     625     
     626     
     627    def get_commit_by_date_chart(self, commit_history): 
     628         
     629        numdates = commit_history[0] 
     630        numcommits = commit_history[1] 
     631         
     632        matplotlib.use('Agg') 
     633        #cla() 
     634        fig = figure(figsize = (6,4)) 
     635        ax = fig.add_subplot(111) # Create supplot with key 111        
     636        line1 = ax.bar(numdates, numcommits, 0.5, color='#9966ff')  
     637        ax.set_xlim( numdates[0], numdates[-1] ) 
     638        ax.xaxis.set_major_locator(DayLocator()) 
     639        ax.xaxis.set_major_formatter( DateFormatter('%Y-%m-%d')) 
     640        labels = ax.get_xticklabels() 
     641        setp(labels, rotation=45, fontsize=6) 
     642         
     643        xlabel('Dates (day)') 
     644        ylabel('Number of commits') 
     645        title('Commits by Date') 
     646         
     647        filename = "commitsbydate" 
     648        path = os.path.join(self.env.path, 'cache', 'tracmetrixplugin', filename) 
     649         
     650        fig.savefig(path) 
     651         
     652        return path 
     653          
     654         
  • tracmetrixplugin/0.11/tracmetrixplugin/templates/mdashboard.html

    r2529 r2581  
    5656 
    5757      </div> 
    58        
    59        
     58 
     59         <div> <img src="${href.mdashboard('%s/cummulativeflow.png' % (milestone.name,))}"/></div>      
    6060      <div class="table"> 
    6161        <h2>Ticket History Table</h2> 
     
    8181          </div> 
    8282    </div> 
    83      
    84      
    85  
    86         <div>   <img src="${href.mdashboard('%s/cummulativeflow.png' % (milestone.name,))}"/></div> 
    87  
    8883 
    8984 
  • tracmetrixplugin/0.11/tracmetrixplugin/templates/pdashboard.html

    r2570 r2581  
    2020 
    2121          <div class="info"> 
    22         <h2>Project Tickets Statistics</h2>   
    23         <py:if test="proj_progress_stat.stats.count">${progress_bar(proj_progress_stat.stats, proj_progress_stat.interval_hrefs, stats_href=proj_progress_stat.stats_href)}</py:if> 
    24                  
    25                 <h2>Project Backlog Metrics</h2> 
    26                 <table style="width: 800px" class="listing project stats"> 
    27               <thead> 
    28                     <tr> 
    29                           <th style="width: 90px" class="style1">Month</th> 
    30                           <th style="width: 200px">Ticket Created</th> 
    31                           <th style="width: 200px">Ticket Opened</th> 
    32                           <th style="width: 200px">Ticket Closed</th> 
    33                           <th style="width: 90px">Backlog Management Index (Closed/Opened)</th> 
    34                         </tr> 
    35                   </thead> 
    36                   <tbody> 
    37                         <tr py:for="bmi in project_bmi_stats" class="statistics"> 
    38                           <td style="width: 90px" class="style1">${bmi[0]}</td> 
    39                           <td style="width: 200px">${len(bmi[1])} <br/> ${wiki_to_html(context(), ', '.join(['#%s' % i for i in bmi[1]]))}</td> 
    40                           <td style="width: 200px">${len(bmi[2])} <br/> ${wiki_to_html(context(), ', '.join(['#%s' % i for i in bmi[2]]))}</td> 
    41                           <td style="width: 200px">${len(bmi[3])} <br/> ${wiki_to_html(context(), ', '.join(['#%s' % i for i in bmi[3]]))}</td> 
    42                           <td style="width: 90px">${"%.2f %%" % (bmi[4],)}</td> 
    43                         </tr> 
    44                   </tbody>                 
    45                 </table> 
     22        <h2>Project Tickets Statistics</h2>  
     23          <br/><b>Tickets by Status</b> 
     24            <py:if test="proj_progress_stat.stats.count">${progress_bar(proj_progress_stat.stats, proj_progress_stat.interval_hrefs, stats_href=proj_progress_stat.stats_href)}</py:if> 
     25                   
     26          <br/><b>Tickets by Resolution</b>   
     27            <py:if test="proj_progress_stat.stats.count">${progress_bar(proj_closed_stat.stats, proj_closed_stat.interval_hrefs, stats_href=proj_closed_stat.stats_href)}</py:if> 
     28       
     29      </div> 
     30       
     31      <div> 
     32       
     33        <h2>Project Backlog Metrics</h2> 
     34         
     35        <h3> Daily Backlog Statistics </h3> 
     36                <img src="${href.pdashboard('dailybacklog.png')}" alt="Backlog Graph"/> 
     37                         
     38        <h3> Monthly Backlog Metrics </h3> 
     39                  <table style="width: 800px" class="listing project stats"> 
     40                <thead> 
     41                      <tr> 
     42                            <th style="width: 90px">Month</th> 
     43                            <th style="width: 200px">Ticket Created</th> 
     44                            <th style="width: 200px">Ticket Opened</th> 
     45                            <th style="width: 200px">Ticket Closed</th> 
     46                            <th style="width: 90px">Backlog Management Index (Closed/Opened)</th> 
     47                          </tr> 
     48                    </thead> 
     49                    <tbody> 
     50                          <tr py:for="bmi in project_bmi_stats" > 
     51                            <td style="width: 90px">${bmi[0]}</td> 
     52                            <td style="width: 200px">${len(bmi[1])} ${wiki_to_html(context(), ', '.join(['#%s' % i for i in bmi[1]]))}</td> 
     53                            <td style="width: 200px">${len(bmi[2])} ${wiki_to_html(context(), ', '.join(['#%s' % i for i in bmi[2]]))}</td> 
     54                            <td style="width: 200px">${len(bmi[3])} ${wiki_to_html(context(), ', '.join(['#%s' % i for i in bmi[3]]))}</td> 
     55                            <td style="width: 90px">${"%.2f %%" % (float(len(bmi[3])) * 100 / float(len(bmi[2])),)}</td> 
     56                          </tr> 
     57                    </tbody>               
     58                  </table> 
    4659      </div> 
    4760      <br/> 
     
    138151    </div> 
    139152 
     153        <div> 
     154          <h2> Repository Statistics </h2> 
     155            <img src="${href.pdashboard('commitsbydate.png')}" alt="Commits by Date"/> 
     156        </div> 
     157 
    140158    <div id="help"><strong>Note:</strong> See 
    141159      <a href="${href.wiki('TracRoadmap')}">TracRoadmap</a> for help on using 
  • tracmetrixplugin/0.11/tracmetrixplugin/web_ui.py

    r2570 r2581  
    3737    cursor = env.get_db_cnx().cursor() 
    3838     
    39     cursor.execute("SELECT id FROM ticket") 
     39    cursor.execute("SELECT id FROM ticket ORDER BY id") 
    4040 
    4141    tkt_ids = [id for id , in cursor] 
     
    4545def last_day_of_month(year, month): 
    4646     
    47     return datetime(year, month+1, 1, tzinfo=utc) - timedelta(seconds=1) 
    48      
     47    return datetime(year, month+1, 1, tzinfo=utc) - timedelta(days=1) 
    4948 
    5049class PDashboard(Component): 
     
    8079        self.env.log.info("pdashboard match request %s" % (req.path_info,))   
    8180                 
    82         return re.match(r'/pdashboard/?', req.path_info) is not None 
     81        urlcomp = req.path_info.split('/') 
     82         
     83        self.env.log.info(urlcomp) 
     84                 
     85        if urlcomp[1] == 'pdashboard': 
     86            if len(urlcomp) == 3: #url has 2  
     87                req.args['imagename'] = urlcomp[2] 
     88            else: 
     89                req.args['imagename'] = None 
     90                     
     91            return True 
    8392 
    8493    def process_request(self, req): 
    8594        req.perm.require('ROADMAP_VIEW') 
    8695 
     96        db = self.env.get_db_cnx() 
     97         
     98        filename = req.args.get('imagename') 
     99                     
     100        if filename != None:     
     101             
     102            self.env.log.info("request for image") 
     103            path = os.path.join(self.env.path, 'cache', 'tracmetrixplugin', filename) 
     104            req.send_file(path, mimeview.get_mimetype(path)) 
     105             
     106        else: 
     107             
     108            self.env.log.info("request mdashboard") 
     109            add_stylesheet(req, 'pd/css/dashboard.css')   
     110             
     111            return self._render_view(req, db)  
     112         
     113 
     114    def _render_view(self, req, db): 
     115         
    87116        showall = req.args.get('show') == 'all' 
    88  
    89         db = self.env.get_db_cnx() 
    90          
     117                 
    91118        # Get list of milestone object for the project 
    92119        milestones = list(Milestone.select(self.env, showall, db)) 
     
    104131            'description': self.env.project_description 
    105132        } 
    106  
    107          
    108133         
    109134        data = { 
     
    127152                                                         for interval in proj_stat.intervals]} 
    128153 
    129                  
     154        closed_stat = self.stats_provider.get_ticket_resolution_group_stats(project_tickets) 
     155 
     156        data['proj_closed_stat'] = {'stats': closed_stat, 
     157                                      'stats_href': req.href.query(closed_stat.qry_args), 
     158                                      'interval_hrefs': [req.href.query(interval['qry_args']) 
     159                                                         for interval in closed_stat.intervals]} 
     160 
     161 
    130162        tkt_group_metrics = TicketGroupMetrics(self.env, project_tickets)       
    131163         
     
    145177        last_day = last_day_of_month(today.year, today.month-1) 
    146178        bmi_stats.append(tkt_group_metrics.get_bmi_monthly_stats(first_day, last_day)) 
     179         
     180        # get daily stat from today and a month back 
     181        last_day = datetime(today.year, today.month, today.day, tzinfo=utc) 
     182        first_day = datetime(today.year, today.month-1, today.day, tzinfo=utc) 
     183         
     184        backlog_history = tkt_group_metrics.get_daily_backlog_history(first_day, last_day) 
     185        daily_backlog_chart_path = tkt_group_metrics.get_daily_backlog_chart(backlog_history) 
     186         
     187        changeset_group_stats = ChangesetsStats(self.env, first_day, last_day) 
     188        commits_by_date = changeset_group_stats.get_commit_by_date() 
     189        commits_by_date_chart = changeset_group_stats.get_commit_by_date_chart(commits_by_date) 
    147190         
    148191        data['project_bmi_stats'] = bmi_stats