-
Notifications
You must be signed in to change notification settings - Fork 3
/
burngen.py
211 lines (178 loc) · 7.45 KB
/
burngen.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# burngen.py
# Author: Marco Rubio
# Date : 2/14/2018
# Desc : Creates a burn down plot from the wekan mongodb data
from pymongo import MongoClient
import datetime
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import matplotlib.cbook as cbook
import time # time.strptime # For datetime compairons
import re
## Generate a burndown chart from any wekan server.
# Multiple charts can be made for each Board and multiple boards can be
# concatinated to give you a bigger perspective of a project.
#
# Total hours are taken from all cards of a given board.
# Time left is calculated given the cards under the list with the word
# 'done' in its title.
#
# current implementations are case insensative.
# usage: TBA
class BurnChart:
def __init__(self, url='localhost', port=27019):
# Configuration Variables
self.URL = url # URL of wekan server
self.PORT = port # Port of mongodb database
self.client = MongoClient(self.URL, self.PORT)
self.db = self.client.wekan
self.fig, self.ax = plt.subplots()
# format the coords message box
def price(x):
return '%1f Hours' % x
# Format the axis labels
self.ax.format_xdata = mdates.DateFormatter('%Y-%m-%d')
self.ax.format_ydata = price
self.ax.grid(True)
# rotates and right aligns the x labels, and moves the bottom of the
# axes up to make room for them
self.fig.autofmt_xdate()
## Get number in parenthesis or return 0
#
# @param string String to parse for parenthesis database
# @return intiger contained in parenthesis
def get_parenthesis(self, string):
start = string.find("(") # Verify we have (
end = string.find(")") # Verify we have )
value= 0
if(start != -1):
if(end != -1):
value= int(string[start+1:end])
return value
## Get the board id given its title
#
# @param title Title to search format
# @param whole_word Should the title match 100%, set to 1 if true
# @return string containing the board id
def get_board_id(self, title, whole_word=0):
if(whole_word): # Find a board that matches title 100%
board = self.db.boards.find({'title':title})
else: # Find any board with the title included
regx = re.compile(".*"+title+".*", re.IGNORECASE)
board = self.db.boards.find({'title':regx})
return board[0]['_id']
## Get the list id containing the title.
#
# @param title Title to search format
# @param board_id Board to look under
# @param whole_word Should the title match 100%, set to 1 if true
# @return string containing the lsit id
def get_list_id(self, title, board_id="", whole_word=0):
if(whole_word): # Find a list that matches title 100%
lst = self.db.lists.find({'title':title})
else: # Find any list with the title included
regx = re.compile(".*"+title+".*", re.IGNORECASE)
lst = self.db.lists.find({'title':regx}, {'boardId':board_id})
return lst[0]['_id']
## Get the cards contained in ether or the given board and/or list
#
# @param board_id Board to look under
# @param list_id List to look under
# @return array of card objects
def get_cards(self, board_id="", list_id=""):
if(list_id == "" and board_id==""):
cards = self.db.cards.find()
elif(list_id == ""):
cards = self.db.cards.find({'boardId':board_id})
elif(board_id == ""):
cards = self.db.cards.find({'listId':list_id})
else:
cards = self.db.cards.find({'listId':list_id,'boardId':board_id})
return cards
## Sets the title of the plot
#
# @param title title to give the chart
def set_title(self, title):
plt.title(title)
## Create a timeline numpy arrays from the provided cards.
#
# Sorting is generated using sort_by and also used to create daily
# progress running total. The generated timeline will use the parenthesis
# values to calculate the running total. commont sorting values would be
# `createdAt` and `dateLastActivity`.
#
# @param cards input card cursor object array to convert
# @param sort_by string of value to sort by
# @return tupple containing the dates and running total
def create_timeline(self, cards, date_column):
dates = [datetime.datetime(2000,1,1)] # Start from a date before 9/11
total = [0]
for card in cards:
expected = self.get_parenthesis(card['title'])
if(expected):
if(card[date_column].date() > dates[-1].date()):
dates.append(card[date_column])
total.append(total[-1]+expected)
else:
total[-1] += expected
dates.pop(0) # Remove the initial dummy element
total.pop(0) # Remove the initial dummy element
return np.array(dates), np.array(total) # Convert to numpy arrays
## Generate a plot from the provided data
#
# The data is expected to be formated in tupple form with the dates as
# the first element and the running total as the second element
# @param data data with date as first element and running total as second
def create_plot(self, data):
# Extract data so its easier to identify
dates = data[0]
total = data[1]
# Locate specific markers for generating tick markers later
#days = mdates.DayLocator() # every day
#hours = mdates.HourLocator(interval=6) # every 6 hours
#dayFmt = mdates.DateFormatter('%b-%d')
# Create our plots
#fig, ax = plt.subplots()
self.ax.plot(dates, total)
# format the ticks
#ax.xaxis.set_major_locator(days)
#ax.xaxis.set_major_formatter(dayFmt)
#ax.xaxis.set_minor_locator(hours)
# Get maximum points to scale things properly
#fst = dates.min()
#snd = dates.max()
#datemin = datetime.date(fst.year, fst.month, fst.day)
#datemax = datetime.date(snd.year, snd.month, snd.day)
#ax.set_xlim(datemin, datemax)
## Adds burn down chart to current list of burndown charts
#
# @param board_title board title to get content from
def create_chart(self, board_title):
self.set_title(board_title + " Burndown")
# Get board id and the 'done' list id
board_id = self.get_board_id(board_title)
done_list_id = self.get_list_id("done", board_id)
# Get cards
cards = self.get_cards(board_id).sort("createdAt")
done_cards = self.get_cards(board_id, done_list_id).sort("dateLastActivity")
"""
print(board_id)
print(done_list_id)
for card in done_cards:
print(card['title'])
done_cards.rewind()
"""
# Create timeline
data = self.create_timeline(cards, "createdAt")
done_data = self.create_timeline(done_cards, "dateLastActivity")
# Create plots
self.create_plot(data)
self.create_plot(done_data)
self.render()
## Renders/draws the charts that are in the list of burndown charts
def render(self):
plt.show()
if __name__ == "__main__":
BC = BurnChart()
BC.create_chart("Module Polishing")