StrictDoc Documentation
strictdoc/features/project_statistics/generator.py
Source file coverage
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
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
                                            requirement
74
                                        )
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
                                            requirement
84
                                        )
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
                                None
101
                            ] += 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())