Path:
strictdoc/features/project_statistics/generator.py
Lines:
325
Non-empty lines:
291
Non-empty lines covered with requirements:
268 / 291 (92.1%)
Functions:
2
Functions covered by requirements:
1 / 2 (50.0%)
1
from datetime import datetime
2
from typing import List, Union
3
4
from markupsafe import Markup
5
6
from strictdoc.backend.sdoc.models.node import SDocNode
7
from strictdoc.backend.sdoc_source_code.models.source_file_info import (
8
SourceFileTraceabilityInfo,
9
)10
from strictdoc.core.document_iterator import SDocDocumentIterator
11
from strictdoc.core.project_config import ProjectConfig
12
from strictdoc.core.traceability_index import TraceabilityIndex
13
from strictdoc.export.html.html_templates import HTMLTemplates
14
from strictdoc.export.html.renderers.link_renderer import LinkRenderer
15
from strictdoc.features.project_statistics.metric import Metric, MetricSection
16
from strictdoc.features.project_statistics.models.project_tree_stats import (
17
DocumentTreeStats,
18
)19
from strictdoc.features.project_statistics.view_object import (
20
ProjectStatisticsViewObject,
21
)22
from strictdoc.helpers.cast import assert_cast
23
from strictdoc.helpers.git_client import GitClient
24
25
26
class ProgressStatisticsGenerator:
27
@staticmethod- "6.8.1. Display project statistics" (REQUIREMENT)
28
def export(
29
project_config: ProjectConfig,
30
traceability_index: TraceabilityIndex,
31
link_renderer: LinkRenderer,
32
html_templates: HTMLTemplates,
33
) -> Markup:
34
"""
35
Export default project statistics to an HTML page.36
37
@relation(SDOC-SRS-97, scope=function)38
"""39
40
git_client = GitClient()
41
42
document_tree_stats: DocumentTreeStats = DocumentTreeStats()
43
44
document_tree_stats.total_documents = len(
45
traceability_index.document_tree.document_list
46
)47
document_tree_stats.git_commit_hash = git_client.get_commit_hash()
48
49
for document in traceability_index.document_tree.document_list:
50
document_iterator = SDocDocumentIterator(document)
51
for node, _ in document_iterator.all_content(print_fragments=False):
52
if isinstance(node, SDocNode) and node.node_type == "SECTION":
53
document_tree_stats.total_sections += 1
54
if not node.has_any_text_nodes():
55
document_tree_stats.sections_without_text_nodes += 1
56
57
elif isinstance(node, SDocNode):
58
if (
59
node.is_normative_node()
60
and node.node_type == "REQUIREMENT"
61
):62
requirement: SDocNode = assert_cast(node, SDocNode)
63
document_tree_stats.total_requirements += 1
64
65
if requirement.reserved_uid is None:
66
document_tree_stats.requirements_no_uid += 1
67
68
if requirement.reserved_status != "Backlog":
69
if document.config.root:
70
if (
71
len(
72
traceability_index.get_children_requirements(
73
requirement74
)75
)76
== 0
77
):78
document_tree_stats.requirements_root_no_links += 1
79
else:
80
if (
81
len(
82
traceability_index.get_parent_requirements(
83
requirement84
)85
)86
== 0
87
):88
document_tree_stats.requirements_no_links += 1
89
90
# RATIONALE.91
if (
92
requirement.ordered_fields_lookup.get("RATIONALE")
93
is None
94
):95
document_tree_stats.requirements_no_rationale += 1
96
97
# STATUS.98
if requirement.reserved_status is None:
99
document_tree_stats.requirements_status_breakdown[
100
None101
] += 1
102
else:
103
document_tree_stats.requirements_status_breakdown[
104
requirement.reserved_status
105
] += 1
106
107
for requirement_field_ in node.enumerate_fields():
108
field_value = requirement_field_.get_text_value()
109
if "TBD" in field_value:
110
document_tree_stats.total_tbd += 1
111
if "TBC" in field_value:
112
document_tree_stats.total_tbc += 1
113
114
document_tree_stats.sort_requirements_status_breakdown()
115
116
if traceability_index.document_tree.source_tree is not None:
117
for (
118
source_file_119
) in traceability_index.document_tree.source_tree.source_files:
120
document_tree_stats.total_source_files += 1
121
source_file_info_: SourceFileTraceabilityInfo = traceability_index.get_file_traceability_index().get_coverage_info(
122
source_file_.in_doctree_source_file_rel_path_posix
123
)124
if source_file_info_.get_coverage() == 100:
125
document_tree_stats.total_source_files_complete_coverage += 1
126
elif source_file_info_.get_coverage() == 0:
127
document_tree_stats.total_source_files_no_coverage += 1
128
else:
129
document_tree_stats.total_source_files_partial_coverage += 1
130
131
#132
# Create all metrics.133
#134
135
metrics: List[Union[Metric, MetricSection]] = []
136
137
section = MetricSection(name="General information", metrics=[])
138
metrics.append(section)
139
140
section.metrics.append(
141
Metric(name="Project name", value=project_config.project_title)
142
)143
section.metrics.append(
144
Metric(
145
name="Statistics generation date",
146
value=datetime.today().strftime("%Y-%m-%d %H:%M:%S"),
147
)148
)149
section.metrics.append(
150
Metric(
151
name="Last modification of project data",
152
value=traceability_index.strictdoc_last_update.strftime(
153
"%Y-%m-%d %H:%M:%S"
154
),155
)156
)157
section.metrics.append(
158
Metric(
159
name="Git commit/release",
160
value=document_tree_stats.git_commit_hash,
161
)162
)163
section.metrics.append(
164
Metric(
165
name="Total documents",
166
value=str(document_tree_stats.total_documents),
167
)168
)169
170
section = MetricSection(name="Sections", metrics=[])
171
metrics.append(section)
172
173
section.metrics.append(
174
Metric(
175
name="Total sections",
176
value=str(document_tree_stats.total_sections),
177
link="search?q=node.is_section()",
178
)179
)180
section.metrics.append(
181
Metric(
182
name="Sections without any text",
183
value=str(document_tree_stats.sections_without_text_nodes),
184
link="search?q=(node.is_section() and not node.contains_any_text)",
185
)186
)187
188
section = MetricSection(name="Requirements", metrics=[])
189
metrics.append(section)
190
191
section.metrics.append(
192
Metric(
193
name="Total requirements",
194
value=str(document_tree_stats.total_requirements),
195
link="search?q=node.is_requirement()",
196
)197
)198
section.metrics.append(
199
Metric(
200
name="Requirements with no UID",
201
value=str(document_tree_stats.requirements_no_uid),
202
link='search?q=(node.is_requirement() and node["UID"] == None)',
203
)204
)205
section.metrics.append(
206
Metric(
207
name="Root-level requirements not connected to by any requirement",
208
value=str(document_tree_stats.requirements_root_no_links),
209
link='search?q=(node.is_requirement() and node.is_root and node["STATUS"] != "Backlog" and not node.has_child_requirements)',
210
)211
)212
section.metrics.append(
213
Metric(
214
name="Non-root-level requirements not connected to any parent requirement",
215
value=str(document_tree_stats.requirements_no_links),
216
link='search?q=(node.is_requirement() and not node.is_root and node["STATUS"] != "Backlog" and not node.has_parent_requirements)',
217
)218
)219
section.metrics.append(
220
Metric(
221
name="Requirements with no RATIONALE",
222
value=str(document_tree_stats.requirements_no_rationale),
223
link='search?q=(node.is_requirement() and node["RATIONALE"] == None)',
224
)225
)226
227
#228
# If there are requirement statuses, collect status metrics.229
#230
statuses = document_tree_stats.requirements_status_breakdown.keys()
231
if any(key is not None for key in statuses):
232
section = MetricSection(
233
name="Requirements status breakdown", metrics=[]
234
)235
metrics.append(section)
236
237
for (
238
status_,
239
status_count_,
240
) in document_tree_stats.requirements_status_breakdown.items():
241
status_query_value = (
242
f'"{status_}"' if status_ is not None else "None"
243
)244
section.metrics.append(
245
Metric(
246
name=f"Requirements with status {status_}",
247
value=str(status_count_),
248
link='search?q=(node.is_requirement() and node["STATUS"] == '
249
+ status_query_value
250
+ ")",
251
)252
)253
254
#255
# Source files metrics.256
#257
258
section = MetricSection(name="Source files", metrics=[])
259
metrics.append(section)
260
261
section.metrics.append(
262
Metric(
263
name="Total source files",
264
value=str(document_tree_stats.total_source_files),
265
link="search?q=node.is_source_file()",
266
)267
)268
section.metrics.append(
269
Metric(
270
name="Total source files with complete requirements coverage",
271
value=str(
272
document_tree_stats.total_source_files_complete_coverage
273
),274
link="search?q=node.is_source_file_with_complete_coverage()",
275
)276
)277
section.metrics.append(
278
Metric(
279
name="Total source files with partial requirements coverage",
280
value=str(
281
document_tree_stats.total_source_files_partial_coverage
282
),283
link="search?q=node.is_source_file_with_partial_coverage()",
284
)285
)286
section.metrics.append(
287
Metric(
288
name="Total source files with no requirements coverage",
289
value=str(document_tree_stats.total_source_files_no_coverage),
290
link="search?q=node.is_source_file_with_no_coverage()",
291
)292
)293
294
#295
# TBD/TBC metrics.296
#297
298
section = MetricSection(name="TBD/TBC", metrics=[])
299
metrics.append(section)
300
301
section.metrics.append(
302
Metric(
303
name="Total TBD",
304
value=str(document_tree_stats.total_tbd),
305
link='search?q=node.contains("TBD")',
306
)307
)308
section.metrics.append(
309
Metric(
310
name="Total TBC",
311
value=str(document_tree_stats.total_tbc),
312
link='search?q=node.contains("TBC")',
313
)314
)315
316
#317
# Return the final view object.318
#319
view_object = ProjectStatisticsViewObject(
320
traceability_index=traceability_index,
321
project_config=project_config,
322
link_renderer=link_renderer,
323
metrics=metrics,
324
)325
return view_object.render_screen(html_templates.jinja_environment())