| 4954 |
tlittlef |
1 |
import json, sys, datetime
|
|
|
2 |
from urllib.request import urlopen, Request
|
| 4380 |
tlittlef |
3 |
|
|
|
4 |
CRUCIBLE_URL_TEMPLATE = 'http://cds:8060/rest-service/reviews-v1'
|
| 4866 |
tlittlef |
5 |
RESULTS_KEY = 'results'
|
|
|
6 |
GEOMETRY_KEY = 'geometry'
|
|
|
7 |
LOCATION_KEY = 'location'
|
|
|
8 |
LAT_KEY = 'lat'
|
|
|
9 |
LNG_KEY = 'lng'
|
| 4380 |
tlittlef |
10 |
|
|
|
11 |
|
|
|
12 |
class CrucibleExtractor :
|
|
|
13 |
def __init__(self) :
|
|
|
14 |
pass
|
|
|
15 |
self.selected_reviews = {}
|
|
|
16 |
self.runtime_defects = {}
|
|
|
17 |
self.maintenance_defects = {}
|
|
|
18 |
self.security_defects = {}
|
|
|
19 |
self.unclassified_defect_reviews = [ ]
|
|
|
20 |
self.total_defects = 0
|
| 4420 |
tlittlef |
21 |
self.verbose = False
|
| 4380 |
tlittlef |
22 |
def url_to_object(self, url, top=None) :
|
| 4954 |
tlittlef |
23 |
req = Request(url)
|
| 4380 |
tlittlef |
24 |
req.add_header('Accept','application/json')
|
|
|
25 |
req.add_header('Authorization', 'Basic dGxpdHRsZWY6MCRtb3JQRVRI')
|
| 4954 |
tlittlef |
26 |
resp = urlopen(req).read()
|
|
|
27 |
retval = json.loads(str(resp,'utf-8'))
|
| 4380 |
tlittlef |
28 |
if top :
|
|
|
29 |
retval = retval[top]
|
|
|
30 |
return retval
|
|
|
31 |
def get_selected_review_ids(self,min_create_date, max_create_date) :
|
|
|
32 |
url = CRUCIBLE_URL_TEMPLATE
|
| 4866 |
tlittlef |
33 |
reviewData = self.url_to_object(url,"reviewData")
|
| 4380 |
tlittlef |
34 |
for r in reviewData :
|
|
|
35 |
review_id = r["permaId"]["id"]
|
|
|
36 |
review_create_date = r["createDate"]
|
|
|
37 |
if review_create_date>=min_create_date and review_create_date<=max_create_date :
|
| 4866 |
tlittlef |
38 |
self.selected_reviews[review_id] = [ r['projectKey'], None, ]
|
| 4380 |
tlittlef |
39 |
else :
|
|
|
40 |
pass
|
|
|
41 |
return self.selected_reviews.keys()
|
|
|
42 |
def get_review_summary(self,review_id) :
|
|
|
43 |
url = CRUCIBLE_URL_TEMPLATE + "/" + review_id + "/comments"
|
|
|
44 |
projectKey = str(self.selected_reviews[review_id][0])
|
| 4866 |
tlittlef |
45 |
reviewDetails = self.url_to_object(url,'comments')
|
| 4380 |
tlittlef |
46 |
retval = [
|
|
|
47 |
projectKey,
|
|
|
48 |
0, # non-defect comments
|
|
|
49 |
0, # runtime defects
|
|
|
50 |
0, # maintenance defects
|
|
|
51 |
0, # security defects
|
|
|
52 |
0, # unclassified defects
|
|
|
53 |
]
|
|
|
54 |
for c in reviewDetails:
|
| 4954 |
tlittlef |
55 |
metrics_keys = list(c['metrics'].keys())
|
| 4866 |
tlittlef |
56 |
if c['defectRaised'] is False :
|
| 4380 |
tlittlef |
57 |
retval[1] += 1
|
| 4954 |
tlittlef |
58 |
elif len(metrics_keys) != 1 : # unclassified - either none of the drop downs or more than one selected
|
| 4380 |
tlittlef |
59 |
retval[5] += 1
|
|
|
60 |
self.unclassified_defect_reviews += [ review_id ]
|
|
|
61 |
self.total_defects += 1
|
|
|
62 |
else :
|
| 4954 |
tlittlef |
63 |
defect_category_code = metrics_keys[0]
|
| 4866 |
tlittlef |
64 |
defect_type = c['metrics'][defect_category_code]['value']
|
| 4380 |
tlittlef |
65 |
category_defect_map = None
|
| 4866 |
tlittlef |
66 |
if defect_category_code == 'metric-57' : # maintenance
|
| 4380 |
tlittlef |
67 |
retval[3] += 1
|
|
|
68 |
category_defect_map = self.maintenance_defects
|
| 4866 |
tlittlef |
69 |
elif defect_category_code == 'metric-95' : # runtime
|
| 4380 |
tlittlef |
70 |
retval[2] += 1
|
|
|
71 |
category_defect_map = self.runtime_defects
|
| 4866 |
tlittlef |
72 |
elif defect_category_code == 'metric-97' : # security
|
| 4380 |
tlittlef |
73 |
retval[4] += 1
|
|
|
74 |
category_defect_map = self.security_defects
|
| 4420 |
tlittlef |
75 |
else :
|
| 4866 |
tlittlef |
76 |
print("Unexpected defect_category_code %s" % ( c['metrics'].keys()[0], ))
|
| 4420 |
tlittlef |
77 |
retval[5] += 1
|
|
|
78 |
self.unclassified_defect_reviews += [ review_id ]
|
|
|
79 |
self.total_defects += 1
|
| 4765 |
tlittlef |
80 |
if category_defect_map is None :
|
|
|
81 |
pass
|
|
|
82 |
elif defect_type in category_defect_map.keys() :
|
| 4380 |
tlittlef |
83 |
category_defect_map[defect_type] += 1
|
|
|
84 |
else :
|
|
|
85 |
category_defect_map[defect_type] = 1
|
|
|
86 |
self.total_defects += 1
|
|
|
87 |
self.selected_reviews[review_id][1] = retval[2:]
|
|
|
88 |
return retval
|
| 4381 |
tlittlef |
89 |
def summarize_by_project(self, month_prefix) :
|
|
|
90 |
start_date = month_prefix + "-00"
|
|
|
91 |
end_date = month_prefix + "-99"
|
| 4420 |
tlittlef |
92 |
if self.verbose :
|
| 4866 |
tlittlef |
93 |
print("Getting review ids")
|
| 4380 |
tlittlef |
94 |
review_ids = self.get_selected_review_ids(start_date, end_date)
|
|
|
95 |
project_summaries = {}
|
|
|
96 |
overall_summary = [ 0, ] * 6
|
|
|
97 |
for review_id in sorted(review_ids) :
|
|
|
98 |
if True :
|
|
|
99 |
review_summary = self.get_review_summary(review_id)
|
| 4420 |
tlittlef |
100 |
if self.verbose :
|
| 4866 |
tlittlef |
101 |
print("%-12s: %s" % ( review_id, self.selected_reviews[review_id][1], ))
|
| 4380 |
tlittlef |
102 |
project_id = review_summary[0]
|
|
|
103 |
if project_id in project_summaries.keys() :
|
|
|
104 |
project_summary = project_summaries[project_id]
|
|
|
105 |
else :
|
|
|
106 |
project_summary = [ 0,] * 6
|
|
|
107 |
project_summary[0] += 1
|
|
|
108 |
overall_summary[0] += 1
|
|
|
109 |
for i in range(1,6) :
|
|
|
110 |
project_summary[i] += review_summary[i]
|
|
|
111 |
overall_summary[i] += review_summary[i]
|
|
|
112 |
project_summaries[project_id] = project_summary
|
|
|
113 |
REPORT_PRINTF_FORMAT = "%-12s %4s %4s %4s %4s %4s %4s"
|
|
|
114 |
REPORT_FORMAT_SEPARATOR = ( '-------','---','---','---','---','---','---')
|
| 4866 |
tlittlef |
115 |
print ( REPORT_PRINTF_FORMAT % REPORT_FORMAT_SEPARATOR )
|
|
|
116 |
print ( REPORT_PRINTF_FORMAT % ( "Project", "rev", "com", "run", "mnt", "sec", "oth" ) )
|
|
|
117 |
print ( REPORT_PRINTF_FORMAT % REPORT_FORMAT_SEPARATOR )
|
| 4380 |
tlittlef |
118 |
for project_id in sorted(project_summaries.keys()) :
|
|
|
119 |
ps = project_summaries[project_id]
|
| 4866 |
tlittlef |
120 |
print ( REPORT_PRINTF_FORMAT % ( project_id, ps[0], ps[1], ps[2], ps[3], ps[4], ps[5] ) )
|
|
|
121 |
print ( REPORT_PRINTF_FORMAT % REPORT_FORMAT_SEPARATOR )
|
| 4380 |
tlittlef |
122 |
ps = overall_summary
|
| 4866 |
tlittlef |
123 |
print ( REPORT_PRINTF_FORMAT % ( "TOTAL", ps[0], ps[1], ps[2], ps[3], ps[4], ps[5] ) )
|
| 4380 |
tlittlef |
124 |
def summarize_for_category(self,category_name, category_types) :
|
|
|
125 |
REPORT_PRINTF_FORMAT = "%-75s %6s %6s"
|
|
|
126 |
REPORT_FORMAT_SEPARATOR = ( '-----------------------','------','------')
|
| 4866 |
tlittlef |
127 |
print ( REPORT_PRINTF_FORMAT % REPORT_FORMAT_SEPARATOR )
|
|
|
128 |
print ( REPORT_PRINTF_FORMAT % ( "%s defects" % (category_name),"count","%") )
|
|
|
129 |
print ( REPORT_PRINTF_FORMAT % REPORT_FORMAT_SEPARATOR )
|
| 4380 |
tlittlef |
130 |
count_for_category = 0
|
|
|
131 |
percent_for_category = 0.0
|
|
|
132 |
for d in sorted(category_types.keys()) :
|
|
|
133 |
count_for_type = category_types[d]
|
|
|
134 |
percent_for_type = int( (1000.0 * count_for_type)/self.total_defects) * 0.1
|
|
|
135 |
count_for_category += count_for_type
|
|
|
136 |
percent_for_category += percent_for_type
|
| 4866 |
tlittlef |
137 |
d = d.replace("\u2013","-")
|
| 4954 |
tlittlef |
138 |
print ( REPORT_PRINTF_FORMAT % (
|
|
|
139 |
d,
|
|
|
140 |
"%6d" % (count_for_type,),
|
|
|
141 |
"%6.1f" % (percent_for_type,),
|
|
|
142 |
) )
|
| 4866 |
tlittlef |
143 |
print ( REPORT_PRINTF_FORMAT % REPORT_FORMAT_SEPARATOR )
|
| 4954 |
tlittlef |
144 |
print ( REPORT_PRINTF_FORMAT % (
|
|
|
145 |
"Total %s" % (category_name,),
|
|
|
146 |
"%6d" % (count_for_category,),
|
|
|
147 |
"%6.1f" % (percent_for_category,),
|
|
|
148 |
) )
|
| 4866 |
tlittlef |
149 |
print ( REPORT_PRINTF_FORMAT % REPORT_FORMAT_SEPARATOR )
|
| 4380 |
tlittlef |
150 |
def summarize_by_defect_type(self) :
|
|
|
151 |
self.summarize_for_category("maintenance",self.maintenance_defects)
|
|
|
152 |
self.summarize_for_category("runtime",self.runtime_defects)
|
|
|
153 |
self.summarize_for_category("security",self.security_defects)
|
|
|
154 |
def report_on_unclassified_defects(self) :
|
| 4866 |
tlittlef |
155 |
print ( "Reviews with unclassified defects: %s" % (self.unclassified_defect_reviews,) )
|
| 4380 |
tlittlef |
156 |
|
|
|
157 |
if __name__ == "__main__" :
|
|
|
158 |
extractor = CrucibleExtractor()
|
| 4420 |
tlittlef |
159 |
|
|
|
160 |
if sys.argv[1] == "--usage" :
|
| 4866 |
tlittlef |
161 |
print ( "crucible_reporting.py YYYY-MM" )
|
|
|
162 |
print ( " report stats for specified month" )
|
|
|
163 |
print ( "crucible_reporting.py" )
|
|
|
164 |
print ( " report stats for previous month" )
|
| 4420 |
tlittlef |
165 |
sys.exit(0)
|
|
|
166 |
elif sys.argv[1] == "--verbose" :
|
|
|
167 |
extractor.verbose = True
|
|
|
168 |
sys.argv = sys.argv[1:]
|
|
|
169 |
|
| 4381 |
tlittlef |
170 |
if len(sys.argv) < 2 :
|
|
|
171 |
today = datetime.date.today()
|
|
|
172 |
if today.month!= 1 :
|
|
|
173 |
month_prefix = "%04d-%02d" % (today.year, today.month-1)
|
|
|
174 |
else :
|
|
|
175 |
month_prefix = "%04d-%02d" % (today.year-1, 12)
|
| 4866 |
tlittlef |
176 |
print ( "Generating report for previous month (" + month_prefix + ")" )
|
| 4381 |
tlittlef |
177 |
else :
|
|
|
178 |
month_prefix = sys.argv[1]
|
|
|
179 |
|
|
|
180 |
extractor.summarize_by_project(month_prefix)
|
| 4380 |
tlittlef |
181 |
extractor.summarize_by_defect_type()
|
|
|
182 |
extractor.report_on_unclassified_defects()
|
|
|
183 |
|
|
|
184 |
|