metripy 0.3.0__py3-none-any.whl → 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of metripy might be problematic. Click here for more details.
- metripy/Application/Application.py +2 -3
- metripy/Application/Config/Config.py +4 -3
- metripy/Application/Config/File/ConfigFileReaderFactory.py +2 -1
- metripy/Application/Config/File/ConfigFileReaderInterface.py +70 -3
- metripy/Application/Config/File/JsonConfigFileReader.py +2 -72
- metripy/Application/Config/File/YamlConfigFileReader.py +17 -0
- metripy/Application/Config/Parser.py +0 -2
- metripy/Application/Config/ProjectConfig.py +4 -3
- metripy/Application/Info.py +9 -2
- metripy/LangAnalyzer/Python/PythonAnalyzer.py +1 -1
- metripy/Metric/Code/Segmentor.py +2 -0
- metripy/Report/Html/DependencyPageRenderer.py +21 -0
- metripy/Report/Html/FilesPageRenderer.py +28 -0
- metripy/Report/Html/GitAnalysisPageRenderer.py +55 -0
- metripy/Report/Html/IndexPageRenderer.py +40 -0
- metripy/Report/Html/PageRenderer.py +43 -0
- metripy/Report/Html/PageRendererFactory.py +37 -0
- metripy/Report/Html/Reporter.py +34 -334
- metripy/Report/Html/TopOffendersPageRenderer.py +84 -0
- metripy/Report/Html/TrendsPageRenderer.py +114 -0
- metripy/Report/ReporterFactory.py +4 -2
- {metripy-0.3.0.dist-info → metripy-0.3.1.dist-info}/METADATA +1 -1
- {metripy-0.3.0.dist-info → metripy-0.3.1.dist-info}/RECORD +27 -18
- {metripy-0.3.0.dist-info → metripy-0.3.1.dist-info}/WHEEL +0 -0
- {metripy-0.3.0.dist-info → metripy-0.3.1.dist-info}/entry_points.txt +0 -0
- {metripy-0.3.0.dist-info → metripy-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {metripy-0.3.0.dist-info → metripy-0.3.1.dist-info}/top_level.txt +0 -0
metripy/Report/Html/Reporter.py
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
|
-
import json
|
|
2
1
|
import os
|
|
3
2
|
import shutil
|
|
4
3
|
from datetime import datetime
|
|
5
4
|
|
|
6
|
-
from py_template_engine import TemplateEngine
|
|
7
5
|
|
|
8
6
|
from metripy.Application.Config.ReportConfig import ReportConfig
|
|
9
7
|
from metripy.Component.Output.CliOutput import CliOutput
|
|
10
|
-
from metripy.Dependency.Dependency import Dependency
|
|
11
|
-
from metripy.Metric.Code.FileMetrics import FileMetrics
|
|
12
|
-
from metripy.Metric.Code.Segmentor import Segmentor
|
|
13
|
-
from metripy.Metric.FileTree.FileTreeParser import FileTreeParser
|
|
14
8
|
from metripy.Metric.ProjectMetrics import ProjectMetrics
|
|
9
|
+
from metripy.Report.Html.PageRendererFactory import PageRendererFactory
|
|
15
10
|
from metripy.Report.ReporterInterface import ReporterInterface
|
|
16
11
|
|
|
17
12
|
|
|
@@ -22,6 +17,11 @@ class Reporter(ReporterInterface):
|
|
|
22
17
|
self.config: ReportConfig = config
|
|
23
18
|
self.output = output
|
|
24
19
|
self.template_dir = os.path.join(os.getcwd(), "templates/html_report")
|
|
20
|
+
self.project_name = project_name
|
|
21
|
+
|
|
22
|
+
self.page_renderer_factory = PageRendererFactory(
|
|
23
|
+
self.template_dir, self.config.path, self.project_name
|
|
24
|
+
)
|
|
25
25
|
|
|
26
26
|
self.global_template_args = {
|
|
27
27
|
"project_name": project_name,
|
|
@@ -64,361 +64,61 @@ class Reporter(ReporterInterface):
|
|
|
64
64
|
# shutil.copytree(os.path.join(self.template_dir, "fonts"), os.path.join(self.config.path, "fonts"), dirs_exist_ok=True)
|
|
65
65
|
|
|
66
66
|
# copy logo, lies 2 down from the templates directory
|
|
67
|
-
shutil.copy(
|
|
67
|
+
shutil.copy(
|
|
68
|
+
os.path.join(self.template_dir, "../..", "logo.svg"),
|
|
69
|
+
os.path.join(self.config.path, "images", "logo.svg"),
|
|
70
|
+
)
|
|
68
71
|
|
|
69
72
|
# Render main pages
|
|
70
73
|
self.render_index_page(metrics)
|
|
71
74
|
self.render_files_page(metrics)
|
|
75
|
+
self.render_top_offenders_page(metrics)
|
|
72
76
|
self.render_git_analysis_page(metrics)
|
|
73
77
|
self.render_dependencies_page(metrics)
|
|
74
|
-
self.render_top_offenders_page(metrics)
|
|
75
78
|
self.render_trends_page(metrics)
|
|
76
79
|
|
|
77
80
|
self.output.writeln(
|
|
78
81
|
f"<success>HTML report generated in {self.config.path} directory</success>"
|
|
79
82
|
)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
engine = TemplateEngine(os.path.join(self.template_dir, template_name))
|
|
83
|
-
content = engine.render(**data)
|
|
84
|
-
with open(os.path.join(self.config.path, template_name), "w") as file:
|
|
85
|
-
file.write(content)
|
|
86
|
-
|
|
87
|
-
def render_trends_page(self, metrics: ProjectMetrics):
|
|
88
|
-
def compile(file: FileMetrics) -> dict:
|
|
89
|
-
return {
|
|
90
|
-
"name": file.full_name,
|
|
91
|
-
"path": file.full_name,
|
|
92
|
-
"complexity_current": file.totalCc,
|
|
93
|
-
"complexity_prev": round(file.trend.historical_totalCc, 2),
|
|
94
|
-
"complexity_delta": round(file.trend.totalCc_delta, 2),
|
|
95
|
-
"maintainability_current": round(file.maintainabilityIndex, 2),
|
|
96
|
-
"maintainability_prev": round(
|
|
97
|
-
file.trend.historical_maintainabilityIndex, 2
|
|
98
|
-
),
|
|
99
|
-
"maintainability_delta": round(
|
|
100
|
-
file.trend.maintainabilityIndex_delta, 2
|
|
101
|
-
),
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
# Top improved complexity (complexity went down - negative delta)
|
|
105
|
-
top_improved_complexity = [
|
|
106
|
-
x
|
|
107
|
-
for x in metrics.file_metrics
|
|
108
|
-
if x.trend is not None and x.trend.totalCc_delta < 0
|
|
109
|
-
]
|
|
110
|
-
top_improved_complexity = sorted(
|
|
111
|
-
top_improved_complexity, key=lambda x: x.trend.totalCc_delta
|
|
112
|
-
)[:10]
|
|
113
|
-
|
|
114
|
-
# Top worsened complexity (complexity went up - positive delta)
|
|
115
|
-
top_worsened_complexity = [
|
|
116
|
-
x
|
|
117
|
-
for x in metrics.file_metrics
|
|
118
|
-
if x.trend is not None and x.trend.totalCc_delta > 0
|
|
119
|
-
]
|
|
120
|
-
top_worsened_complexity = sorted(
|
|
121
|
-
top_worsened_complexity, key=lambda x: x.trend.totalCc_delta, reverse=True
|
|
122
|
-
)[:10]
|
|
123
|
-
|
|
124
|
-
# Top improved maintainability (maintainability went up - positive delta)
|
|
125
|
-
top_improved_maintainability = [
|
|
126
|
-
x
|
|
127
|
-
for x in metrics.file_metrics
|
|
128
|
-
if x.trend is not None and round(x.trend.maintainabilityIndex_delta, 2) > 0
|
|
129
|
-
]
|
|
130
|
-
top_improved_maintainability = sorted(
|
|
131
|
-
top_improved_maintainability,
|
|
132
|
-
key=lambda x: x.trend.maintainabilityIndex_delta,
|
|
133
|
-
reverse=True,
|
|
134
|
-
)[:10]
|
|
135
|
-
|
|
136
|
-
# Top worsened maintainability (maintainability went down - negative delta)
|
|
137
|
-
top_worsened_maintainability = [
|
|
138
|
-
x
|
|
139
|
-
for x in metrics.file_metrics
|
|
140
|
-
if x.trend is not None and round(x.trend.maintainabilityIndex_delta, 2) < 0
|
|
141
|
-
]
|
|
142
|
-
top_worsened_maintainability = sorted(
|
|
143
|
-
top_worsened_maintainability,
|
|
144
|
-
key=lambda x: x.trend.maintainabilityIndex_delta,
|
|
145
|
-
)[:10]
|
|
146
|
-
|
|
147
|
-
trend_data = {
|
|
148
|
-
# Segment distributions for each metric
|
|
149
|
-
"loc_segments_current": metrics.total_code_metrics.segmentation_data[
|
|
150
|
-
"loc"
|
|
151
|
-
].to_dict_with_percent(),
|
|
152
|
-
"loc_segments_prev": metrics.total_code_metrics.trend.historical_segmentation_data[
|
|
153
|
-
"loc"
|
|
154
|
-
].to_dict_with_percent(),
|
|
155
|
-
"complexity_segments_current": metrics.total_code_metrics.segmentation_data[
|
|
156
|
-
"complexity"
|
|
157
|
-
].to_dict_with_percent(),
|
|
158
|
-
"complexity_segments_prev": metrics.total_code_metrics.trend.historical_segmentation_data[
|
|
159
|
-
"complexity"
|
|
160
|
-
].to_dict_with_percent(),
|
|
161
|
-
"maintainability_segments_current": metrics.total_code_metrics.segmentation_data[
|
|
162
|
-
"maintainability"
|
|
163
|
-
].to_dict_with_percent(),
|
|
164
|
-
"maintainability_segments_prev": metrics.total_code_metrics.trend.historical_segmentation_data[
|
|
165
|
-
"maintainability"
|
|
166
|
-
].to_dict_with_percent(),
|
|
167
|
-
"method_size_segments_current": metrics.total_code_metrics.segmentation_data[
|
|
168
|
-
"methodSize"
|
|
169
|
-
].to_dict_with_percent(),
|
|
170
|
-
"method_size_segments_prev": metrics.total_code_metrics.trend.historical_segmentation_data[
|
|
171
|
-
"methodSize"
|
|
172
|
-
].to_dict_with_percent(),
|
|
173
|
-
"top_improved_complexity": [compile(x) for x in top_improved_complexity],
|
|
174
|
-
"top_improved_maintainability": [
|
|
175
|
-
compile(x) for x in top_improved_maintainability
|
|
176
|
-
],
|
|
177
|
-
"top_worsened_complexity": [compile(x) for x in top_worsened_complexity],
|
|
178
|
-
"top_worsened_maintainability": [
|
|
179
|
-
compile(x) for x in top_worsened_maintainability
|
|
180
|
-
],
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
self.output.writeln("<info>Rendering trends page</info>")
|
|
184
|
-
self.render_template(
|
|
185
|
-
"trends.html",
|
|
186
|
-
{
|
|
187
|
-
"has_trend_data": metrics.total_code_metrics.trend is not None,
|
|
188
|
-
"trend_data": trend_data,
|
|
189
|
-
"project_name": "Metripy",
|
|
190
|
-
"last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
191
|
-
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
192
|
-
"author": "Metripy",
|
|
193
|
-
"version": "1.0.0",
|
|
194
|
-
},
|
|
83
|
+
self.output.writeln(
|
|
84
|
+
f"<success>Open HTML report: {self.config.path}/index.html</success>"
|
|
195
85
|
)
|
|
196
86
|
|
|
197
87
|
def render_index_page(self, metrics: ProjectMetrics):
|
|
198
|
-
git_stats_data = {}
|
|
199
|
-
if metrics.git_metrics:
|
|
200
|
-
git_stats_data = metrics.git_metrics.get_commit_stats_per_month()
|
|
201
|
-
|
|
202
88
|
self.output.writeln("<info>Rendering index page</info>")
|
|
203
|
-
self.
|
|
204
|
-
"index.html",
|
|
205
|
-
{
|
|
206
|
-
"git_stats_data": json.dumps(git_stats_data, indent=4),
|
|
207
|
-
"total_code_metrics": metrics.total_code_metrics.to_dict(),
|
|
208
|
-
"has_total_code_metrics_trend": metrics.total_code_metrics.trend
|
|
209
|
-
is not None,
|
|
210
|
-
"total_code_metrics_trend": (
|
|
211
|
-
metrics.total_code_metrics.trend.to_dict()
|
|
212
|
-
if metrics.total_code_metrics.trend
|
|
213
|
-
else None
|
|
214
|
-
),
|
|
215
|
-
"segmentation_data": json.dumps(
|
|
216
|
-
metrics.total_code_metrics.to_dict_segmentation(), indent=4
|
|
217
|
-
),
|
|
218
|
-
"segmentation_data_trend": (
|
|
219
|
-
json.dumps(
|
|
220
|
-
metrics.total_code_metrics.trend.to_dict_segmentation(),
|
|
221
|
-
indent=4,
|
|
222
|
-
)
|
|
223
|
-
if metrics.total_code_metrics.trend
|
|
224
|
-
else None
|
|
225
|
-
),
|
|
226
|
-
"project_name": "Metripy",
|
|
227
|
-
"last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
228
|
-
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
229
|
-
"author": "Metripy",
|
|
230
|
-
"version": "1.0.0",
|
|
231
|
-
},
|
|
232
|
-
)
|
|
89
|
+
self.page_renderer_factory.create_index_page_renderer().render(metrics)
|
|
233
90
|
self.output.writeln("<success>Done rendering index page</success>")
|
|
234
91
|
|
|
92
|
+
def render_files_page(self, metrics: ProjectMetrics):
|
|
93
|
+
"""Render the files page with file details and analysis"""
|
|
94
|
+
self.output.writeln("<info>Rendering files page</info>")
|
|
95
|
+
self.page_renderer_factory.create_files_page_renderer().render(metrics)
|
|
96
|
+
self.output.writeln("<success>Files page generated successfully</success>")
|
|
97
|
+
|
|
235
98
|
def render_top_offenders_page(self, metrics: ProjectMetrics):
|
|
236
99
|
self.output.writeln("<info>Rendering top offenders page</info>")
|
|
237
|
-
|
|
238
|
-
orderedByTotalCc = sorted(
|
|
239
|
-
metrics.file_metrics, key=lambda x: x.totalCc, reverse=True
|
|
240
|
-
)[:10]
|
|
241
|
-
orderedByMI = sorted(
|
|
242
|
-
metrics.file_metrics, key=lambda x: x.maintainabilityIndex, reverse=False
|
|
243
|
-
)[:10]
|
|
244
|
-
orderedByLoc = sorted(metrics.file_metrics, key=lambda x: x.loc, reverse=True)[
|
|
245
|
-
:10
|
|
246
|
-
]
|
|
247
|
-
|
|
248
|
-
all_functions: list = []
|
|
249
|
-
for fm in metrics.file_metrics:
|
|
250
|
-
all_functions.extend(fm.function_nodes)
|
|
251
|
-
|
|
252
|
-
functionsOrderedByCc = sorted(
|
|
253
|
-
all_functions, key=lambda x: x.complexity, reverse=True
|
|
254
|
-
)[:10]
|
|
255
|
-
functionsOrderedByMi = sorted(
|
|
256
|
-
all_functions, key=lambda x: x.maintainability_index, reverse=False
|
|
257
|
-
)[:10]
|
|
258
|
-
functionsOrderedByLoc = sorted(
|
|
259
|
-
all_functions, key=lambda x: x.get_loc(), reverse=True
|
|
260
|
-
)[:10]
|
|
261
|
-
|
|
262
|
-
# TODO maintainability index per function, we dont calc yet
|
|
263
|
-
|
|
264
|
-
self.render_template(
|
|
265
|
-
"top_offenders.html",
|
|
266
|
-
Reporter._stringify_values(
|
|
267
|
-
{
|
|
268
|
-
"file_loc_offenders": [
|
|
269
|
-
{**e.to_dict(), "status": Segmentor.get_loc_segment(e.loc)}
|
|
270
|
-
for e in orderedByLoc
|
|
271
|
-
],
|
|
272
|
-
"file_cc_offenders": [
|
|
273
|
-
{
|
|
274
|
-
**e.to_dict(),
|
|
275
|
-
"status": Segmentor.get_complexity_segment(e.totalCc),
|
|
276
|
-
}
|
|
277
|
-
for e in orderedByTotalCc
|
|
278
|
-
],
|
|
279
|
-
"file_mi_offenders": [
|
|
280
|
-
{
|
|
281
|
-
**e.to_dict(),
|
|
282
|
-
"status": Segmentor.get_maintainability_segment(
|
|
283
|
-
e.maintainabilityIndex
|
|
284
|
-
),
|
|
285
|
-
}
|
|
286
|
-
for e in orderedByMI
|
|
287
|
-
],
|
|
288
|
-
"function_size_offenders": [
|
|
289
|
-
{
|
|
290
|
-
**e.to_dict(),
|
|
291
|
-
"status": Segmentor.get_method_size_segment(e.get_loc()),
|
|
292
|
-
}
|
|
293
|
-
for e in functionsOrderedByLoc
|
|
294
|
-
],
|
|
295
|
-
"function_cc_offenders": [
|
|
296
|
-
{
|
|
297
|
-
**e.to_dict(),
|
|
298
|
-
"status": Segmentor.get_complexity_segment(e.complexity),
|
|
299
|
-
}
|
|
300
|
-
for e in functionsOrderedByCc
|
|
301
|
-
],
|
|
302
|
-
"function_mi_offenders": [
|
|
303
|
-
{
|
|
304
|
-
**e.to_dict(),
|
|
305
|
-
"status": Segmentor.get_maintainability_segment(
|
|
306
|
-
e.maintainability_index
|
|
307
|
-
),
|
|
308
|
-
}
|
|
309
|
-
for e in functionsOrderedByMi
|
|
310
|
-
],
|
|
311
|
-
"project_name": "Metripy",
|
|
312
|
-
"last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
313
|
-
}
|
|
314
|
-
),
|
|
315
|
-
)
|
|
100
|
+
self.page_renderer_factory.create_top_offenders_page_renderer().render(metrics)
|
|
316
101
|
self.output.writeln(
|
|
317
102
|
"<success>Top offenders page generated successfully</success>"
|
|
318
103
|
)
|
|
319
104
|
|
|
105
|
+
def render_git_analysis_page(self, metrics: ProjectMetrics):
|
|
106
|
+
"""Render the git analysis page with comprehensive git data"""
|
|
107
|
+
self.output.writeln("<info>Rendering git analysis page</info>")
|
|
108
|
+
self.page_renderer_factory.create_git_analysis_page_renderer().render(metrics)
|
|
109
|
+
self.output.writeln(
|
|
110
|
+
"<success>Git analysis page generated successfully</success>"
|
|
111
|
+
)
|
|
112
|
+
|
|
320
113
|
def render_dependencies_page(self, metrics: ProjectMetrics):
|
|
321
114
|
"""Render the dependencies page with dependency details and stats"""
|
|
322
|
-
if not metrics.dependencies:
|
|
323
|
-
self.output.writeln("<success>No dependencies to render</success>")
|
|
324
|
-
return
|
|
325
|
-
|
|
326
115
|
self.output.writeln("<info>Rendering dependencies page</info>")
|
|
327
|
-
|
|
328
|
-
dependencies = metrics.dependencies if metrics.dependencies is not None else []
|
|
329
|
-
|
|
330
|
-
license_by_type = Dependency.get_lisence_distribution(dependencies)
|
|
331
|
-
|
|
332
|
-
self.render_template(
|
|
333
|
-
"dependencies.html",
|
|
334
|
-
{
|
|
335
|
-
"dependencies": [d.to_dict() for d in dependencies],
|
|
336
|
-
"project_name": "Metripy",
|
|
337
|
-
"last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
338
|
-
},
|
|
339
|
-
)
|
|
116
|
+
self.page_renderer_factory.create_dependency_page_renderer().render(metrics)
|
|
340
117
|
self.output.writeln(
|
|
341
118
|
"<success>Dependencies page generated successfully</success>"
|
|
342
119
|
)
|
|
343
120
|
|
|
344
|
-
def
|
|
345
|
-
"
|
|
346
|
-
self.
|
|
347
|
-
|
|
348
|
-
file_names = []
|
|
349
|
-
file_details = {}
|
|
350
|
-
for file_metrics in metrics.file_metrics:
|
|
351
|
-
file_name = file_metrics.full_name
|
|
352
|
-
file_details[file_name] = file_metrics.to_dict()
|
|
353
|
-
file_names.append(file_name)
|
|
354
|
-
|
|
355
|
-
filetree = FileTreeParser.parse(file_names, shorten=True)
|
|
356
|
-
|
|
357
|
-
self.render_template(
|
|
358
|
-
"files.html",
|
|
359
|
-
{
|
|
360
|
-
"filetree": json.dumps(filetree.to_dict()),
|
|
361
|
-
"file_details": json.dumps(file_details),
|
|
362
|
-
"project_name": "Metripy",
|
|
363
|
-
"last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
364
|
-
},
|
|
365
|
-
)
|
|
366
|
-
self.output.writeln("<success>Files page generated successfully</success>")
|
|
367
|
-
|
|
368
|
-
@staticmethod
|
|
369
|
-
def _stringify_values(obj):
|
|
370
|
-
if isinstance(obj, dict):
|
|
371
|
-
return {
|
|
372
|
-
key: Reporter._stringify_values(value) for key, value in obj.items()
|
|
373
|
-
}
|
|
374
|
-
elif isinstance(obj, list):
|
|
375
|
-
return [Reporter._stringify_values(item) for item in obj]
|
|
376
|
-
else:
|
|
377
|
-
return str(obj)
|
|
378
|
-
|
|
379
|
-
def render_git_analysis_page(self, metrics: ProjectMetrics):
|
|
380
|
-
"""Render the git analysis page with comprehensive git data"""
|
|
381
|
-
if not metrics.git_metrics:
|
|
382
|
-
self.output.writeln("<success>No git metrics to render</success>")
|
|
383
|
-
return
|
|
384
|
-
|
|
385
|
-
self.output.writeln("<info>Rendering git analysis page</info>")
|
|
386
|
-
try:
|
|
387
|
-
# Render git analysis template
|
|
388
|
-
self.render_template(
|
|
389
|
-
"git_analysis.html",
|
|
390
|
-
Reporter._stringify_values(
|
|
391
|
-
{
|
|
392
|
-
"git_analysis": metrics.git_metrics.to_dict(),
|
|
393
|
-
"git_analysis_json": json.dumps(
|
|
394
|
-
metrics.git_metrics.get_contributors_dict(), indent=4
|
|
395
|
-
),
|
|
396
|
-
"git_stats_data": json.dumps(
|
|
397
|
-
metrics.git_metrics.get_commit_stats_per_month(), indent=4
|
|
398
|
-
), # git commit graph
|
|
399
|
-
"git_churn_data": json.dumps(
|
|
400
|
-
metrics.git_metrics.get_churn_per_month(), indent=4
|
|
401
|
-
), # git chrun graph
|
|
402
|
-
"git_silos_data": metrics.git_metrics.get_silos_list()[
|
|
403
|
-
:10
|
|
404
|
-
], # silos list
|
|
405
|
-
"git_contributors": metrics.git_metrics.get_contributors_list()[
|
|
406
|
-
:10
|
|
407
|
-
], # contributors list
|
|
408
|
-
"git_hotspots_data": metrics.git_metrics.get_hotspots_list()[
|
|
409
|
-
:10
|
|
410
|
-
], # hotspots list
|
|
411
|
-
"project_name": "Metripy",
|
|
412
|
-
"last_updated": metrics.git_metrics.get_analysis_start_date(),
|
|
413
|
-
}
|
|
414
|
-
),
|
|
415
|
-
)
|
|
416
|
-
|
|
417
|
-
self.output.writeln(
|
|
418
|
-
"<success>Git analysis page generated successfully</success>"
|
|
419
|
-
)
|
|
420
|
-
except Exception as e:
|
|
421
|
-
raise e
|
|
422
|
-
self.output.writeln(
|
|
423
|
-
f"<error>Error generating git analysis page: {e}</error>"
|
|
424
|
-
)
|
|
121
|
+
def render_trends_page(self, metrics: ProjectMetrics):
|
|
122
|
+
self.output.writeln("<info>Rendering trends page</info>")
|
|
123
|
+
self.page_renderer_factory.create_trends_page_renderer().render(metrics)
|
|
124
|
+
self.output.writeln("<success>Trends page generated successfully</success>")
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from metripy.Metric.Code.Segmentor import Segmentor
|
|
2
|
+
from metripy.Metric.ProjectMetrics import ProjectMetrics
|
|
3
|
+
from metripy.Report.Html.PageRenderer import PageRenderer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TopOffendersPageRenderer(PageRenderer):
|
|
7
|
+
def __init__(self, template_dir: str, output_dir: str, project_name: str):
|
|
8
|
+
super().__init__(template_dir, output_dir, project_name)
|
|
9
|
+
|
|
10
|
+
def render(self, metrics: ProjectMetrics):
|
|
11
|
+
orderedByTotalCc = sorted(
|
|
12
|
+
metrics.file_metrics, key=lambda x: x.totalCc, reverse=True
|
|
13
|
+
)[:10]
|
|
14
|
+
orderedByMI = sorted(
|
|
15
|
+
metrics.file_metrics, key=lambda x: x.maintainabilityIndex, reverse=False
|
|
16
|
+
)[:10]
|
|
17
|
+
orderedByLoc = sorted(metrics.file_metrics, key=lambda x: x.loc, reverse=True)[
|
|
18
|
+
:10
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
all_functions: list = []
|
|
22
|
+
for fm in metrics.file_metrics:
|
|
23
|
+
all_functions.extend(fm.function_nodes)
|
|
24
|
+
|
|
25
|
+
functionsOrderedByCc = sorted(
|
|
26
|
+
all_functions, key=lambda x: x.complexity, reverse=True
|
|
27
|
+
)[:10]
|
|
28
|
+
functionsOrderedByMi = sorted(
|
|
29
|
+
all_functions, key=lambda x: x.maintainability_index, reverse=False
|
|
30
|
+
)[:10]
|
|
31
|
+
functionsOrderedByLoc = sorted(
|
|
32
|
+
all_functions, key=lambda x: x.get_loc(), reverse=True
|
|
33
|
+
)[:10]
|
|
34
|
+
|
|
35
|
+
# TODO maintainability index per function, we dont calc yet
|
|
36
|
+
|
|
37
|
+
self.render_template(
|
|
38
|
+
"top_offenders.html",
|
|
39
|
+
{
|
|
40
|
+
"file_loc_offenders": [
|
|
41
|
+
{**e.to_dict(), "status": Segmentor.get_loc_segment(e.loc)}
|
|
42
|
+
for e in orderedByLoc
|
|
43
|
+
],
|
|
44
|
+
"file_cc_offenders": [
|
|
45
|
+
{
|
|
46
|
+
**e.to_dict(),
|
|
47
|
+
"status": Segmentor.get_complexity_segment(e.totalCc),
|
|
48
|
+
}
|
|
49
|
+
for e in orderedByTotalCc
|
|
50
|
+
],
|
|
51
|
+
"file_mi_offenders": [
|
|
52
|
+
{
|
|
53
|
+
**e.to_dict(),
|
|
54
|
+
"status": Segmentor.get_maintainability_segment(
|
|
55
|
+
e.maintainabilityIndex
|
|
56
|
+
),
|
|
57
|
+
}
|
|
58
|
+
for e in orderedByMI
|
|
59
|
+
],
|
|
60
|
+
"function_size_offenders": [
|
|
61
|
+
{
|
|
62
|
+
**e.to_dict(),
|
|
63
|
+
"status": Segmentor.get_method_size_segment(e.get_loc()),
|
|
64
|
+
}
|
|
65
|
+
for e in functionsOrderedByLoc
|
|
66
|
+
],
|
|
67
|
+
"function_cc_offenders": [
|
|
68
|
+
{
|
|
69
|
+
**e.to_dict(),
|
|
70
|
+
"status": Segmentor.get_complexity_segment(e.complexity),
|
|
71
|
+
}
|
|
72
|
+
for e in functionsOrderedByCc
|
|
73
|
+
],
|
|
74
|
+
"function_mi_offenders": [
|
|
75
|
+
{
|
|
76
|
+
**e.to_dict(),
|
|
77
|
+
"status": Segmentor.get_maintainability_segment(
|
|
78
|
+
e.maintainability_index
|
|
79
|
+
),
|
|
80
|
+
}
|
|
81
|
+
for e in functionsOrderedByMi
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from metripy.Metric.Code.FileMetrics import FileMetrics
|
|
2
|
+
from metripy.Metric.ProjectMetrics import ProjectMetrics
|
|
3
|
+
from metripy.Report.Html.PageRenderer import PageRenderer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TrendsPageRenderer(PageRenderer):
|
|
7
|
+
def __init__(self, template_dir: str, output_dir: str, project_name: str):
|
|
8
|
+
super().__init__(template_dir, output_dir, project_name)
|
|
9
|
+
|
|
10
|
+
def _compile_trend_item(self, file: FileMetrics) -> dict:
|
|
11
|
+
return {
|
|
12
|
+
"name": file.full_name,
|
|
13
|
+
"path": file.full_name,
|
|
14
|
+
"complexity_current": file.totalCc,
|
|
15
|
+
"complexity_prev": round(file.trend.historical_totalCc, 2),
|
|
16
|
+
"complexity_delta": round(file.trend.totalCc_delta, 2),
|
|
17
|
+
"maintainability_current": round(file.maintainabilityIndex, 2),
|
|
18
|
+
"maintainability_prev": round(
|
|
19
|
+
file.trend.historical_maintainabilityIndex, 2
|
|
20
|
+
),
|
|
21
|
+
"maintainability_delta": round(file.trend.maintainabilityIndex_delta, 2),
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
def render(self, metrics: ProjectMetrics):
|
|
25
|
+
# Top improved complexity (complexity went down - negative delta)
|
|
26
|
+
top_improved_complexity = [
|
|
27
|
+
x
|
|
28
|
+
for x in metrics.file_metrics
|
|
29
|
+
if x.trend is not None and x.trend.totalCc_delta < 0
|
|
30
|
+
]
|
|
31
|
+
top_improved_complexity = sorted(
|
|
32
|
+
top_improved_complexity, key=lambda x: x.trend.totalCc_delta
|
|
33
|
+
)[:10]
|
|
34
|
+
|
|
35
|
+
# Top worsened complexity (complexity went up - positive delta)
|
|
36
|
+
top_worsened_complexity = [
|
|
37
|
+
x
|
|
38
|
+
for x in metrics.file_metrics
|
|
39
|
+
if x.trend is not None and x.trend.totalCc_delta > 0
|
|
40
|
+
]
|
|
41
|
+
top_worsened_complexity = sorted(
|
|
42
|
+
top_worsened_complexity, key=lambda x: x.trend.totalCc_delta, reverse=True
|
|
43
|
+
)[:10]
|
|
44
|
+
|
|
45
|
+
# Top improved maintainability (maintainability went up - positive delta)
|
|
46
|
+
top_improved_maintainability = [
|
|
47
|
+
x
|
|
48
|
+
for x in metrics.file_metrics
|
|
49
|
+
if x.trend is not None and round(x.trend.maintainabilityIndex_delta, 2) > 0
|
|
50
|
+
]
|
|
51
|
+
top_improved_maintainability = sorted(
|
|
52
|
+
top_improved_maintainability,
|
|
53
|
+
key=lambda x: x.trend.maintainabilityIndex_delta,
|
|
54
|
+
reverse=True,
|
|
55
|
+
)[:10]
|
|
56
|
+
|
|
57
|
+
# Top worsened maintainability (maintainability went down - negative delta)
|
|
58
|
+
top_worsened_maintainability = [
|
|
59
|
+
x
|
|
60
|
+
for x in metrics.file_metrics
|
|
61
|
+
if x.trend is not None and round(x.trend.maintainabilityIndex_delta, 2) < 0
|
|
62
|
+
]
|
|
63
|
+
top_worsened_maintainability = sorted(
|
|
64
|
+
top_worsened_maintainability,
|
|
65
|
+
key=lambda x: x.trend.maintainabilityIndex_delta,
|
|
66
|
+
)[:10]
|
|
67
|
+
|
|
68
|
+
trend_data = {
|
|
69
|
+
# Segment distributions for each metric
|
|
70
|
+
"loc_segments_current": metrics.total_code_metrics.segmentation_data[
|
|
71
|
+
"loc"
|
|
72
|
+
].to_dict_with_percent(),
|
|
73
|
+
"loc_segments_prev": metrics.total_code_metrics.trend.historical_segmentation_data[
|
|
74
|
+
"loc"
|
|
75
|
+
].to_dict_with_percent(),
|
|
76
|
+
"complexity_segments_current": metrics.total_code_metrics.segmentation_data[
|
|
77
|
+
"complexity"
|
|
78
|
+
].to_dict_with_percent(),
|
|
79
|
+
"complexity_segments_prev": metrics.total_code_metrics.trend.historical_segmentation_data[
|
|
80
|
+
"complexity"
|
|
81
|
+
].to_dict_with_percent(),
|
|
82
|
+
"maintainability_segments_current": metrics.total_code_metrics.segmentation_data[
|
|
83
|
+
"maintainability"
|
|
84
|
+
].to_dict_with_percent(),
|
|
85
|
+
"maintainability_segments_prev": metrics.total_code_metrics.trend.historical_segmentation_data[
|
|
86
|
+
"maintainability"
|
|
87
|
+
].to_dict_with_percent(),
|
|
88
|
+
"method_size_segments_current": metrics.total_code_metrics.segmentation_data[
|
|
89
|
+
"methodSize"
|
|
90
|
+
].to_dict_with_percent(),
|
|
91
|
+
"method_size_segments_prev": metrics.total_code_metrics.trend.historical_segmentation_data[
|
|
92
|
+
"methodSize"
|
|
93
|
+
].to_dict_with_percent(),
|
|
94
|
+
"top_improved_complexity": [
|
|
95
|
+
self._compile_trend_item(x) for x in top_improved_complexity
|
|
96
|
+
],
|
|
97
|
+
"top_improved_maintainability": [
|
|
98
|
+
self._compile_trend_item(x) for x in top_improved_maintainability
|
|
99
|
+
],
|
|
100
|
+
"top_worsened_complexity": [
|
|
101
|
+
self._compile_trend_item(x) for x in top_worsened_complexity
|
|
102
|
+
],
|
|
103
|
+
"top_worsened_maintainability": [
|
|
104
|
+
self._compile_trend_item(x) for x in top_worsened_maintainability
|
|
105
|
+
],
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
self.render_template(
|
|
109
|
+
"trends.html",
|
|
110
|
+
{
|
|
111
|
+
"has_trend_data": metrics.total_code_metrics.trend is not None,
|
|
112
|
+
"trend_data": trend_data,
|
|
113
|
+
},
|
|
114
|
+
)
|
|
@@ -8,9 +8,11 @@ from metripy.Report.Json.JsonReporter import JsonReporter
|
|
|
8
8
|
|
|
9
9
|
class ReporterFactory:
|
|
10
10
|
@staticmethod
|
|
11
|
-
def create(
|
|
11
|
+
def create(
|
|
12
|
+
config: ReportConfig, output: CliOutput, project_name: str
|
|
13
|
+
) -> ReporterInterface:
|
|
12
14
|
if config.type == "html":
|
|
13
|
-
return HtmlReporter(config, output)
|
|
15
|
+
return HtmlReporter(config, output, project_name)
|
|
14
16
|
elif config.type == "json":
|
|
15
17
|
return JsonReporter(config, output)
|
|
16
18
|
elif config.type == "csv":
|