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