msgram 3.2.0__tar.gz → 3.2.2__tar.gz
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.
- {msgram-3.2.0 → msgram-3.2.2}/PKG-INFO +2 -2
- {msgram-3.2.0 → msgram-3.2.2}/msgram.egg-info/PKG-INFO +2 -2
- {msgram-3.2.0 → msgram-3.2.2}/msgram.egg-info/requires.txt +1 -1
- {msgram-3.2.0 → msgram-3.2.2}/pyproject.toml +2 -2
- msgram-3.2.2/src/cli/aggregate_metrics.py +147 -0
- {msgram-3.2.0 → msgram-3.2.2}/src/cli/commands/cmd_calculate.py +13 -7
- {msgram-3.2.0 → msgram-3.2.2}/src/cli/commands/cmd_diff.py +21 -11
- {msgram-3.2.0 → msgram-3.2.2}/src/cli/commands/cmd_extract.py +6 -1
- {msgram-3.2.0 → msgram-3.2.2}/src/cli/commands/cmd_norm_diff.py +36 -8
- {msgram-3.2.0 → msgram-3.2.2}/src/cli/jsonReader/jsonReader.py +9 -2
- {msgram-3.2.0 → msgram-3.2.2}/src/cli/parsers.py +37 -20
- msgram-3.2.2/src/cli/resources/characteristic.py +40 -0
- {msgram-3.2.0 → msgram-3.2.2}/src/cli/resources/measure.py +9 -7
- {msgram-3.2.0 → msgram-3.2.2}/src/cli/resources/subcharacteristic.py +14 -10
- {msgram-3.2.0 → msgram-3.2.2}/src/cli/utils.py +22 -15
- {msgram-3.2.0 → msgram-3.2.2}/tests/system/test_extract.py +3 -3
- msgram-3.2.2/tests/unit/test_aggregate.py +168 -0
- {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_calculate.py +88 -49
- {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_diff.py +18 -53
- {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_jsonReader.py +3 -1
- {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_norm_diff.py +69 -10
- {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_parser.py +2 -0
- {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_tsqmi.py +1 -1
- msgram-3.2.0/src/cli/aggregate_metrics.py +0 -229
- msgram-3.2.0/src/cli/resources/characteristic.py +0 -37
- msgram-3.2.0/tests/unit/test_aggregate.py +0 -223
- {msgram-3.2.0 → msgram-3.2.2}/LICENSE +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/README.md +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/msgram.egg-info/SOURCES.txt +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/msgram.egg-info/dependency_links.txt +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/msgram.egg-info/entry_points.txt +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/msgram.egg-info/top_level.txt +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/setup.cfg +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/src/__init__.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/src/cli/__init__.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/src/cli/cli.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/src/cli/commands/__init__.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/src/cli/commands/cmd_init.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/src/cli/commands/cmd_list.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/src/cli/exceptions/__init__.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/src/cli/exceptions/exceptions.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/src/cli/jsonReader/__init__.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/src/cli/resources/__init__.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/src/cli/resources/metrics.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/src/cli/resources/tsqmi.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/src/config/__init__.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/src/config/settings.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/src/config/setup_log.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/tests/__init__.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/tests/system/__init__.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/tests/system/test_help.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/tests/system/test_init.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/tests/system/test_list.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/tests/unit/__init__.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/tests/unit/data/file_reader_response.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_characteristic.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_extract.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_init.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_list.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_measures.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_setup_log.py +0 -0
- {msgram-3.2.0 → msgram-3.2.2}/tests/unit/test_subcharacteristic.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: msgram
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.2
|
|
4
4
|
Summary: The msgram CLI is a command-line interface to use MeasureSoftGram software
|
|
5
5
|
Author-email: MeasureSoftGram <measuresoftgram@gmail.com>
|
|
6
6
|
License: GNU AFFERO GENERAL PUBLIC LICENSE
|
|
@@ -684,7 +684,7 @@ Requires-Dist: setuptools~=72.1.0
|
|
|
684
684
|
Requires-Dist: python-dotenv
|
|
685
685
|
Requires-Dist: rich
|
|
686
686
|
Requires-Dist: validators~=0.33.0
|
|
687
|
-
Requires-Dist: msgram-core~=1.4.
|
|
687
|
+
Requires-Dist: msgram-core~=1.4.9
|
|
688
688
|
Requires-Dist: msgram-parser~=1.2.1
|
|
689
689
|
Requires-Dist: anytree
|
|
690
690
|
Provides-Extra: dev
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: msgram
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.2
|
|
4
4
|
Summary: The msgram CLI is a command-line interface to use MeasureSoftGram software
|
|
5
5
|
Author-email: MeasureSoftGram <measuresoftgram@gmail.com>
|
|
6
6
|
License: GNU AFFERO GENERAL PUBLIC LICENSE
|
|
@@ -684,7 +684,7 @@ Requires-Dist: setuptools~=72.1.0
|
|
|
684
684
|
Requires-Dist: python-dotenv
|
|
685
685
|
Requires-Dist: rich
|
|
686
686
|
Requires-Dist: validators~=0.33.0
|
|
687
|
-
Requires-Dist: msgram-core~=1.4.
|
|
687
|
+
Requires-Dist: msgram-core~=1.4.9
|
|
688
688
|
Requires-Dist: msgram-parser~=1.2.1
|
|
689
689
|
Requires-Dist: anytree
|
|
690
690
|
Provides-Extra: dev
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "msgram"
|
|
7
|
-
version = "3.2.
|
|
7
|
+
version = "3.2.2"
|
|
8
8
|
description = "The msgram CLI is a command-line interface to use MeasureSoftGram software"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -32,7 +32,7 @@ dependencies = [
|
|
|
32
32
|
"python-dotenv",
|
|
33
33
|
"rich",
|
|
34
34
|
"validators~=0.33.0",
|
|
35
|
-
"msgram-core~=1.4.
|
|
35
|
+
"msgram-core~=1.4.9",
|
|
36
36
|
"msgram-parser~=1.2.1",
|
|
37
37
|
"anytree"
|
|
38
38
|
]
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
from src.cli.utils import print_error, print_info
|
|
5
|
+
|
|
6
|
+
metrics = {}
|
|
7
|
+
metrics["sonar"] = [
|
|
8
|
+
"tests",
|
|
9
|
+
"test_failures",
|
|
10
|
+
"test_errors",
|
|
11
|
+
"coverage",
|
|
12
|
+
"test_execution_time",
|
|
13
|
+
"functions",
|
|
14
|
+
"complexity",
|
|
15
|
+
"comment_lines_density",
|
|
16
|
+
"duplicated_lines_density",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
metrics["github"] = [
|
|
20
|
+
"resolved_issues",
|
|
21
|
+
"total_issues",
|
|
22
|
+
"sum_ci_feedback_times",
|
|
23
|
+
"total_builds",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
measures = {}
|
|
27
|
+
measures["sonarqube"] = [
|
|
28
|
+
"passed_tests",
|
|
29
|
+
"test_builds",
|
|
30
|
+
"test_errors",
|
|
31
|
+
"test_coverage",
|
|
32
|
+
"non_complex_file_density",
|
|
33
|
+
"commented_file_density",
|
|
34
|
+
"duplication_absense",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
measures["github"] = ["team_throughput", "ci_feedback_time"]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def should_process_metrics(config):
|
|
41
|
+
for characteristic in config.get("characteristics", []):
|
|
42
|
+
for subcharacteristic in characteristic.get("subcharacteristics", []):
|
|
43
|
+
for measure in subcharacteristic.get("measures", []):
|
|
44
|
+
if (
|
|
45
|
+
measure.get("key") not in measures["sonarqube"]
|
|
46
|
+
and measure.get("key") not in measures["github"]
|
|
47
|
+
):
|
|
48
|
+
return False
|
|
49
|
+
return True
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def read_msgram(file_path):
|
|
53
|
+
try:
|
|
54
|
+
with open(file_path, "r") as file:
|
|
55
|
+
return json.load(file)
|
|
56
|
+
except IsADirectoryError as e:
|
|
57
|
+
print_error(f"> [red] Error: {e}")
|
|
58
|
+
return False
|
|
59
|
+
except FileNotFoundError as e:
|
|
60
|
+
print_error(f"> [red] Error: {e}")
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def list_msgram_files(folder_path):
|
|
65
|
+
try:
|
|
66
|
+
if not os.path.isdir(folder_path):
|
|
67
|
+
raise NotADirectoryError(f"{folder_path} is not a directory.")
|
|
68
|
+
|
|
69
|
+
msgram_files = [
|
|
70
|
+
file for file in os.listdir(folder_path) if file.endswith(".msgram")
|
|
71
|
+
]
|
|
72
|
+
return msgram_files
|
|
73
|
+
|
|
74
|
+
except NotADirectoryError as e:
|
|
75
|
+
print_error(f"> [red] Error: {e}")
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def save_metrics(file_name, metrics):
|
|
80
|
+
directory = os.path.dirname(file_name)
|
|
81
|
+
|
|
82
|
+
os.makedirs(directory, exist_ok=True)
|
|
83
|
+
|
|
84
|
+
output_file_path = os.path.join(
|
|
85
|
+
directory, os.path.basename(file_name).replace(".msgram", ".metrics")
|
|
86
|
+
)
|
|
87
|
+
with open(output_file_path, "w") as output_file:
|
|
88
|
+
json.dump(metrics, output_file, indent=2)
|
|
89
|
+
|
|
90
|
+
print_info(f"> [blue] Metrics saved to: {output_file_path}\n")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def process_metrics(folder_path, msgram_files):
|
|
94
|
+
processed_files = []
|
|
95
|
+
|
|
96
|
+
for file in msgram_files:
|
|
97
|
+
print_info(f"> [blue] Processing {file}")
|
|
98
|
+
metrics_dict = read_msgram(os.path.join(folder_path, file))
|
|
99
|
+
|
|
100
|
+
if not metrics_dict:
|
|
101
|
+
print_error(f"> [red] Error to read metrics in: {folder_path}\n")
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
processed_files.append((file, metrics_dict))
|
|
105
|
+
|
|
106
|
+
return processed_files
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def aggregate_metrics(input_format, folder_path, config: json):
|
|
110
|
+
msgram_files = list_msgram_files(folder_path)
|
|
111
|
+
|
|
112
|
+
if not msgram_files:
|
|
113
|
+
print_error("> [red]Error: Can not read msgram files in provided directory")
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
github_files = [file for file in msgram_files if file.startswith("github_")]
|
|
117
|
+
sonar_files = [file for file in msgram_files if file not in github_files]
|
|
118
|
+
file_content = {}
|
|
119
|
+
|
|
120
|
+
result = []
|
|
121
|
+
|
|
122
|
+
have_metrics = False
|
|
123
|
+
|
|
124
|
+
if should_process_metrics(config):
|
|
125
|
+
result = process_metrics(
|
|
126
|
+
folder_path, github_files if input_format == "github" else sonar_files
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
if not result:
|
|
130
|
+
print_error("> [red]Error: Unexpected result from process_github_metrics")
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
have_metrics = True
|
|
134
|
+
else:
|
|
135
|
+
print_error("> [red]Error: Unexpected measures from should_process_metrics")
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
if not have_metrics:
|
|
139
|
+
print_error(
|
|
140
|
+
f"> [red]Error: No metrics where found in the .msgram files from the type: {input_format}"
|
|
141
|
+
)
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
for filename, file_content in result:
|
|
145
|
+
save_metrics(os.path.join(folder_path, filename), file_content)
|
|
146
|
+
|
|
147
|
+
return True
|
|
@@ -34,26 +34,31 @@ def read_config_file(config_path):
|
|
|
34
34
|
exit(1)
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
def calculate_metrics(extracted_path, config):
|
|
37
|
+
def calculate_metrics(input_format, extracted_path, config):
|
|
38
38
|
data_calculated = []
|
|
39
39
|
|
|
40
40
|
if not extracted_path.is_file():
|
|
41
|
-
if not aggregate_metrics(extracted_path, config):
|
|
41
|
+
if not aggregate_metrics(input_format, extracted_path, config):
|
|
42
42
|
print_error(
|
|
43
43
|
"> [red] Failed to aggregate metrics, calculate was not performed. \n"
|
|
44
44
|
)
|
|
45
45
|
return data_calculated, False
|
|
46
46
|
|
|
47
|
-
for file, file_name in read_multiple_files(
|
|
47
|
+
for file, file_name in read_multiple_files(
|
|
48
|
+
extracted_path, input_format, "metrics"
|
|
49
|
+
):
|
|
50
|
+
if file_name.startswith("github_"):
|
|
51
|
+
file_name = file_name[len("github_") :]
|
|
48
52
|
result = calculate_all(file, file_name, config)
|
|
49
53
|
data_calculated.append(result)
|
|
50
54
|
|
|
51
55
|
return data_calculated, True
|
|
52
56
|
else:
|
|
53
57
|
try:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
file_name = extracted_path.name
|
|
59
|
+
if extracted_path.name.startswith("github_"):
|
|
60
|
+
file_name = file_name[len("github_") :]
|
|
61
|
+
result = calculate_all(open_json_file(extracted_path), file_name, config)
|
|
57
62
|
return result, True
|
|
58
63
|
except exceptions.MeasureSoftGramCLIException as e:
|
|
59
64
|
print_error(f"[red]Error calculating {extracted_path}: {e}\n")
|
|
@@ -63,6 +68,7 @@ def calculate_metrics(extracted_path, config):
|
|
|
63
68
|
def command_calculate(args):
|
|
64
69
|
try:
|
|
65
70
|
output_format: str = args["output_format"]
|
|
71
|
+
input_format: str = args["input_format"]
|
|
66
72
|
config_path = args["config_path"]
|
|
67
73
|
extracted_path = args["extracted_path"]
|
|
68
74
|
|
|
@@ -80,7 +86,7 @@ def command_calculate(args):
|
|
|
80
86
|
|
|
81
87
|
print_info("\n> [blue] Reading extracted files:[/]")
|
|
82
88
|
|
|
83
|
-
data_calculated, success = calculate_metrics(extracted_path, config)
|
|
89
|
+
data_calculated, success = calculate_metrics(input_format, extracted_path, config)
|
|
84
90
|
|
|
85
91
|
if success:
|
|
86
92
|
print_info("\n[#A9A9A9]All calculations performed[/] successfully!")
|
|
@@ -17,20 +17,18 @@ from src.cli.utils import (
|
|
|
17
17
|
print_rule,
|
|
18
18
|
)
|
|
19
19
|
from src.cli.exceptions import exceptions
|
|
20
|
-
from src.config.settings import DEFAULT_CONFIG_PATH
|
|
20
|
+
from src.config.settings import DEFAULT_CONFIG_PATH
|
|
21
21
|
|
|
22
22
|
logger = logging.getLogger("msgram")
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def read_config_file(config_path):
|
|
26
26
|
try:
|
|
27
|
-
json = open_json_file(config_path
|
|
27
|
+
json = open_json_file(config_path)
|
|
28
28
|
|
|
29
29
|
return sorted(json, key=lambda x: x["key"])
|
|
30
30
|
except exceptions.MeasureSoftGramCLIException as e:
|
|
31
|
-
print_error(
|
|
32
|
-
f"[red]Error reading msgram_diff.json config file in {config_path}: {e}\n"
|
|
33
|
-
)
|
|
31
|
+
print_error(f"[red]Error reading config file in {config_path}: {e}\n")
|
|
34
32
|
print_rule()
|
|
35
33
|
exit(1)
|
|
36
34
|
|
|
@@ -107,8 +105,8 @@ def calculate_diff(planned, calculated):
|
|
|
107
105
|
def command_diff(args):
|
|
108
106
|
try:
|
|
109
107
|
output_format: str = args["output_format"]
|
|
110
|
-
config_path = args["
|
|
111
|
-
extracted_calculation = args["
|
|
108
|
+
config_path = args["rp_path"]
|
|
109
|
+
extracted_calculation = args["rd_path"]
|
|
112
110
|
|
|
113
111
|
except KeyError as e:
|
|
114
112
|
logger.error(f"KeyError: args[{e}] - non-existent parameters")
|
|
@@ -130,6 +128,8 @@ def command_diff(args):
|
|
|
130
128
|
|
|
131
129
|
if success:
|
|
132
130
|
print_info("\n[#A9A9A9]Diff calculation performed[/] successfully!")
|
|
131
|
+
else:
|
|
132
|
+
exit(1)
|
|
133
133
|
|
|
134
134
|
show_results(output_format, diff_calculated, config_path)
|
|
135
135
|
print_rule()
|
|
@@ -167,7 +167,7 @@ def show_tabulate(data_calculated):
|
|
|
167
167
|
|
|
168
168
|
|
|
169
169
|
def export_json(data_calculated: list, file_path: Path = DEFAULT_CONFIG_PATH):
|
|
170
|
-
file_path = file_path.joinpath("calc_diff_msgram.json")
|
|
170
|
+
file_path = file_path.parent.joinpath("calc_diff_msgram.json")
|
|
171
171
|
with open(file_path, "w", encoding="utf-8") as write_file:
|
|
172
172
|
json.dump(
|
|
173
173
|
data_calculated,
|
|
@@ -178,10 +178,17 @@ def export_json(data_calculated: list, file_path: Path = DEFAULT_CONFIG_PATH):
|
|
|
178
178
|
|
|
179
179
|
|
|
180
180
|
def export_csv(data_calculated: list, file_path: Path = Path("DEFAULT_CONFIG_PATH")):
|
|
181
|
-
file_path = file_path.joinpath("calc_diff_msgram.csv")
|
|
181
|
+
file_path = file_path.parent.joinpath("calc_diff_msgram.csv")
|
|
182
182
|
|
|
183
183
|
with open(file_path, "w", newline="") as csv_file:
|
|
184
|
-
fieldnames = [
|
|
184
|
+
fieldnames = [
|
|
185
|
+
"repository",
|
|
186
|
+
"version",
|
|
187
|
+
"characteristic",
|
|
188
|
+
"planned",
|
|
189
|
+
"developed",
|
|
190
|
+
"diff",
|
|
191
|
+
]
|
|
185
192
|
writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
|
|
186
193
|
|
|
187
194
|
writer.writeheader()
|
|
@@ -191,7 +198,10 @@ def export_csv(data_calculated: list, file_path: Path = Path("DEFAULT_CONFIG_PAT
|
|
|
191
198
|
{
|
|
192
199
|
"repository": data["repository"],
|
|
193
200
|
"version": data["version"],
|
|
194
|
-
|
|
201
|
+
"characteristic": row.get("key", ""),
|
|
202
|
+
"planned": row.get("planned", ""),
|
|
203
|
+
"developed": row.get("developed", ""),
|
|
204
|
+
"diff": row.get("diff", ""),
|
|
195
205
|
}
|
|
196
206
|
)
|
|
197
207
|
|
|
@@ -102,7 +102,7 @@ def command_extract(args):
|
|
|
102
102
|
if repository_path and output_origin == "github":
|
|
103
103
|
filters = {
|
|
104
104
|
"labels": label if label else "US,User Story,User Stories",
|
|
105
|
-
"workflows": workflows
|
|
105
|
+
"workflows": workflows.split(",") if workflows else "build",
|
|
106
106
|
"dates": filter_date if filter_date else None,
|
|
107
107
|
}
|
|
108
108
|
result = parser.parse(
|
|
@@ -132,6 +132,11 @@ def command_extract(args):
|
|
|
132
132
|
logger.debug(f"extracted_path: {extracted_path}")
|
|
133
133
|
|
|
134
134
|
files = list(data_path.glob("*.json"))
|
|
135
|
+
|
|
136
|
+
if not files:
|
|
137
|
+
print_warn(f"No JSON files found in the specified data_path: {data_path}\n")
|
|
138
|
+
sys.exit(1)
|
|
139
|
+
|
|
135
140
|
valid_files = len(files)
|
|
136
141
|
|
|
137
142
|
print_info(f"\n> Extract and save metrics [[blue ]{output_origin}[/]]:")
|
|
@@ -43,26 +43,38 @@ def read_calculated_file(file_path):
|
|
|
43
43
|
|
|
44
44
|
def command_norm_diff(args):
|
|
45
45
|
try:
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
rp_path = args["rp_path"]
|
|
47
|
+
rd_path = args["rd_path"]
|
|
48
48
|
except KeyError as e:
|
|
49
49
|
logger.error(f"KeyError: args[{e}] - non-existent parameters")
|
|
50
50
|
print_error(f"KeyError: args[{e}] - non-existent parameters")
|
|
51
51
|
exit(1)
|
|
52
52
|
|
|
53
|
-
planned_data = read_planned_file(
|
|
54
|
-
calculated_data = read_calculated_file(
|
|
53
|
+
planned_data = read_planned_file(rp_path, sort_key="key")
|
|
54
|
+
calculated_data = read_calculated_file(rd_path)
|
|
55
55
|
|
|
56
|
-
planned_vector, calculated_vector = extract_values(
|
|
56
|
+
planned_vector, calculated_vector = extract_values(
|
|
57
|
+
planned_data, calculated_data, rp_path, rd_path
|
|
58
|
+
)
|
|
57
59
|
norm_diff_value = norm_diff(planned_vector, calculated_vector)
|
|
58
60
|
|
|
59
|
-
print_info("\n[#A9A9A9]Norm diff calculation performed successfully![/]")
|
|
60
|
-
|
|
61
|
+
print_info("\n[#A9A9A9]Norm diff calculation performed successfully![/]\n")
|
|
62
|
+
|
|
63
|
+
print_info(
|
|
64
|
+
"[#A9A9A9]The norm_diff value indicates the difference between the observed "
|
|
65
|
+
"quality (Rd) and the planned target (Rp). A norm_diff of 0 means that the "
|
|
66
|
+
"observed quality perfectly aligns with the planned target. If norm_diff is "
|
|
67
|
+
"not equal to 0, it shows a deviation from the target. In this case, you "
|
|
68
|
+
"should determine whether the performance is above or below the planned "
|
|
69
|
+
"quality. For a detailed analysis of these differences, use the msgram diff "
|
|
70
|
+
"command.[/]\n"
|
|
71
|
+
)
|
|
72
|
+
|
|
61
73
|
print(f"Norm Diff: {norm_diff_value}")
|
|
62
74
|
print_rule()
|
|
63
75
|
|
|
64
76
|
|
|
65
|
-
def extract_values(planned_data, calculated_data):
|
|
77
|
+
def extract_values(planned_data, calculated_data, rp_path, rd_path):
|
|
66
78
|
try:
|
|
67
79
|
planned = planned_data
|
|
68
80
|
calculated = []
|
|
@@ -86,6 +98,22 @@ def extract_values(planned_data, calculated_data):
|
|
|
86
98
|
planned_values = [planned_dict[key] for key in planned_keys]
|
|
87
99
|
calculated_values = [calculated_dict[key] for key in planned_keys]
|
|
88
100
|
|
|
101
|
+
for value in planned_values:
|
|
102
|
+
if value > 1 or value < 0:
|
|
103
|
+
print_error(
|
|
104
|
+
f"[red]The values informed in the .json file {rp_path} must be between 0 and 1.\n"
|
|
105
|
+
)
|
|
106
|
+
print_rule()
|
|
107
|
+
exit(1)
|
|
108
|
+
|
|
109
|
+
for value in calculated_values:
|
|
110
|
+
if value > 1 or value < 0:
|
|
111
|
+
print_error(
|
|
112
|
+
f"[red]The values informed in the .json file {rd_path} must be between 0 and 1.\n"
|
|
113
|
+
)
|
|
114
|
+
print_rule()
|
|
115
|
+
exit(1)
|
|
116
|
+
|
|
89
117
|
return (np.array(planned_values), np.array(calculated_values))
|
|
90
118
|
except exceptions.MeasureSoftGramCLIException as e:
|
|
91
119
|
print_error(f"[red]Error extracting values: {e}\n")
|
|
@@ -29,8 +29,15 @@ def file_reader(path_file):
|
|
|
29
29
|
return components
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
def read_multiple_files(directory: Path, pattern: str):
|
|
33
|
-
|
|
32
|
+
def read_multiple_files(directory: Path, format: str, pattern: str):
|
|
33
|
+
if format == "github":
|
|
34
|
+
glob_pattern = f"github_*.{pattern}"
|
|
35
|
+
elif format == "sonarqube":
|
|
36
|
+
glob_pattern = f"[!github_]*.{pattern}"
|
|
37
|
+
else:
|
|
38
|
+
glob_pattern = f"*.{pattern}"
|
|
39
|
+
|
|
40
|
+
for path_file in directory.glob(glob_pattern):
|
|
34
41
|
try:
|
|
35
42
|
yield open_json_file(path_file), path_file.name
|
|
36
43
|
except exceptions.MeasureSoftGramCLIException:
|
|
@@ -170,34 +170,51 @@ def create_parser():
|
|
|
170
170
|
help="Path to the config directory",
|
|
171
171
|
)
|
|
172
172
|
|
|
173
|
+
parser_calculate.add_argument(
|
|
174
|
+
"-in",
|
|
175
|
+
"--input_format",
|
|
176
|
+
required=True,
|
|
177
|
+
type=str,
|
|
178
|
+
choices=AVAILABLE_IMPORTS,
|
|
179
|
+
default="sonarqube",
|
|
180
|
+
help=(
|
|
181
|
+
"Format of .msgram files. Valid values are: " + ", ".join(AVAILABLE_IMPORTS)
|
|
182
|
+
),
|
|
183
|
+
)
|
|
184
|
+
|
|
173
185
|
parser_calculate.add_argument(
|
|
174
186
|
"-o",
|
|
175
187
|
"--output_format",
|
|
176
188
|
type=str,
|
|
177
189
|
choices=AVAILABLE_GEN_FORMATS,
|
|
178
190
|
default="csv",
|
|
179
|
-
help=(
|
|
191
|
+
help=(
|
|
192
|
+
"The format of the output (export) values are: "
|
|
193
|
+
+ ", ".join(SUPPORTED_FORMATS)
|
|
194
|
+
),
|
|
180
195
|
)
|
|
181
196
|
parser_calculate.set_defaults(func=command_calculate) # function command calculate
|
|
182
197
|
|
|
183
|
-
# =====================================< COMMAND
|
|
198
|
+
# =====================================< COMMAND norm_diff >=====================================
|
|
184
199
|
parser_norm_diff = subparsers.add_parser(
|
|
185
|
-
"
|
|
200
|
+
"norm_diff",
|
|
186
201
|
help="Calculate the norm difference between the planned metrics and the developed.",
|
|
187
202
|
)
|
|
188
203
|
|
|
189
204
|
parser_norm_diff.add_argument(
|
|
190
|
-
"-
|
|
191
|
-
"--
|
|
205
|
+
"-rp",
|
|
206
|
+
"--rp_path",
|
|
192
207
|
type=lambda p: Path(p).absolute(),
|
|
193
|
-
help="Path to the json with the planned
|
|
208
|
+
help="Path to the .json file with the planned/wished values for the quality "
|
|
209
|
+
"characteristics of a release. Quality requirements goals for a release.",
|
|
194
210
|
)
|
|
195
211
|
|
|
196
212
|
parser_norm_diff.add_argument(
|
|
197
|
-
"-
|
|
198
|
-
"--
|
|
213
|
+
"-rd",
|
|
214
|
+
"--rd_path",
|
|
199
215
|
type=lambda p: Path(p).absolute(),
|
|
200
|
-
help="Path to the json with the calculated
|
|
216
|
+
help="Path to the .json file with the model-calculated values for a release's "
|
|
217
|
+
"quality characteristics observed/developed.",
|
|
201
218
|
)
|
|
202
219
|
|
|
203
220
|
parser_norm_diff.set_defaults(
|
|
@@ -211,28 +228,28 @@ def create_parser():
|
|
|
211
228
|
)
|
|
212
229
|
|
|
213
230
|
parser_calculate.add_argument(
|
|
214
|
-
"-
|
|
215
|
-
"--
|
|
231
|
+
"-rd",
|
|
232
|
+
"--rd_path",
|
|
216
233
|
type=lambda p: Path(p).absolute(),
|
|
217
|
-
help="Path to the calculated
|
|
234
|
+
help="Path to the .json file with the model-calculated values for a release's quality"
|
|
235
|
+
"characteristics observed/developed.",
|
|
218
236
|
)
|
|
219
237
|
|
|
220
238
|
parser_calculate.add_argument(
|
|
221
|
-
"-
|
|
222
|
-
"--
|
|
239
|
+
"-rp",
|
|
240
|
+
"--rp_path",
|
|
223
241
|
type=lambda p: Path(p).absolute(),
|
|
224
|
-
|
|
225
|
-
|
|
242
|
+
help="Path to the .json file with the planned/wished values for the quality characteristics"
|
|
243
|
+
"of a release. Quality requirements goals for a release.",
|
|
226
244
|
)
|
|
227
245
|
|
|
228
246
|
parser_calculate.add_argument(
|
|
229
|
-
"-
|
|
247
|
+
"-of",
|
|
230
248
|
"--output_format",
|
|
231
249
|
type=str,
|
|
232
250
|
choices=AVAILABLE_GEN_FORMATS,
|
|
233
|
-
|
|
234
|
-
help=("The format of the output (export) values are: ".join(SUPPORTED_FORMATS)),
|
|
251
|
+
help=("The format of the output (export) values is tabular"),
|
|
235
252
|
)
|
|
236
|
-
parser_calculate.set_defaults(func=command_diff)
|
|
253
|
+
parser_calculate.set_defaults(func=command_diff)
|
|
237
254
|
|
|
238
255
|
return parser
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from src.cli.resources.subcharacteristic import get_subcharacteristic_value
|
|
2
|
+
|
|
3
|
+
from resources import calculate_characteristics as core_calculate
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_characteristic_value(chars, tsqmi):
|
|
7
|
+
char_calculated = []
|
|
8
|
+
for char in tsqmi:
|
|
9
|
+
char_key = char["key"]
|
|
10
|
+
|
|
11
|
+
found = any(char_key == m["key"] for m in chars)
|
|
12
|
+
if found:
|
|
13
|
+
char_calculated.append(
|
|
14
|
+
{
|
|
15
|
+
"key": char_key,
|
|
16
|
+
"value": {m["key"]: m["value"] for m in chars}[char_key],
|
|
17
|
+
"weight": char["weight"],
|
|
18
|
+
}
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
return char_calculated
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def calculate_characteristics(config, subchars):
|
|
25
|
+
characteristics = config["characteristics"]
|
|
26
|
+
calculate_infos = []
|
|
27
|
+
|
|
28
|
+
for char in characteristics:
|
|
29
|
+
char_info = {
|
|
30
|
+
"key": char["key"],
|
|
31
|
+
"subcharacteristics": get_subcharacteristic_value(
|
|
32
|
+
subchars, char["subcharacteristics"]
|
|
33
|
+
),
|
|
34
|
+
}
|
|
35
|
+
if len(char_info["subcharacteristics"]) > 0:
|
|
36
|
+
calculate_infos.append(char_info)
|
|
37
|
+
|
|
38
|
+
headers = ["Id", "Name", "Description", "Value", "Created at"]
|
|
39
|
+
|
|
40
|
+
return core_calculate({"characteristics": calculate_infos}), headers
|
|
@@ -12,13 +12,15 @@ def get_measure_value(measures, subchar):
|
|
|
12
12
|
measures_calculated = []
|
|
13
13
|
for measure in subchar:
|
|
14
14
|
measure_key = measure["key"]
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
found = any(measure_key == m["key"] for m in measures)
|
|
16
|
+
if found:
|
|
17
|
+
measures_calculated.append(
|
|
18
|
+
{
|
|
19
|
+
"key": measure_key,
|
|
20
|
+
"value": {m["key"]: m["value"] for m in measures}[measure_key],
|
|
21
|
+
"weight": measure["weight"],
|
|
22
|
+
}
|
|
23
|
+
)
|
|
22
24
|
|
|
23
25
|
return measures_calculated
|
|
24
26
|
|
|
@@ -8,15 +8,17 @@ def get_subcharacteristic_value(subchars, char):
|
|
|
8
8
|
for subchar in char:
|
|
9
9
|
subchar_key = subchar["key"]
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
subchar_key,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
found = any(subchar_key == m["key"] for m in subchars)
|
|
12
|
+
if found:
|
|
13
|
+
subchar_calculated.append(
|
|
14
|
+
{
|
|
15
|
+
"key": subchar_key,
|
|
16
|
+
"value": {m["key"]: m["value"] for m in subchars}.get(
|
|
17
|
+
subchar_key, None
|
|
18
|
+
),
|
|
19
|
+
"weight": subchar["weight"],
|
|
20
|
+
}
|
|
21
|
+
)
|
|
20
22
|
|
|
21
23
|
return subchar_calculated
|
|
22
24
|
|
|
@@ -32,7 +34,9 @@ def calculate_subcharacteristics(config, measures):
|
|
|
32
34
|
"key": subchar["key"],
|
|
33
35
|
"measures": get_measure_value(measures, subchar["measures"]),
|
|
34
36
|
}
|
|
35
|
-
|
|
37
|
+
if len(subchar_info["measures"]) > 0:
|
|
38
|
+
calculate_infos.append(subchar_info)
|
|
36
39
|
|
|
37
40
|
headers = ["Id", "Name", "Description", "Value", "Created at"]
|
|
41
|
+
|
|
38
42
|
return core_calculate({"subcharacteristics": calculate_infos}), headers
|