Skip to content

Commit 934ab73

Browse files
committed
Project statistics: add requirement status breakdown table
Closes #2206 Closes #2196
1 parent b167718 commit 934ab73

File tree

10 files changed

+251
-97
lines changed

10 files changed

+251
-97
lines changed

strictdoc/export/html/generators/project_statistics.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,13 @@ def export(
8484

8585
# STATUS
8686
if requirement.reserved_status is None:
87-
document_tree_stats.requirements_status_none += 1
87+
document_tree_stats.requirements_status_breakdown[
88+
None
89+
] += 1
8890
else:
89-
if requirement.reserved_status == "Backlog":
90-
document_tree_stats.requirements_status_backlog += 1
91-
elif requirement.reserved_status == "Draft":
92-
document_tree_stats.requirements_status_draft += 1
93-
elif requirement.reserved_status == "Active":
94-
document_tree_stats.requirements_status_active += 1
95-
else:
96-
document_tree_stats.requirements_status_other += 1
91+
document_tree_stats.requirements_status_breakdown[
92+
requirement.reserved_status
93+
] += 1
9794

9895
for requirement_field_ in node.enumerate_fields():
9996
field_value = requirement_field_.get_text_value()
@@ -102,6 +99,8 @@ def export(
10299
if "TBC" in field_value:
103100
document_tree_stats.total_tbc += 1
104101

102+
document_tree_stats.sort_requirements_status_breakdown()
103+
105104
view_object = ProjectStatisticsViewObject(
106105
document_tree_stats=document_tree_stats,
107106
traceability_index=traceability_index,

strictdoc/export/html/generators/view_objects/project_statistics_view_object.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,7 @@ def is_empty_tree(self) -> bool:
5555

5656
def get_datetime(self) -> str:
5757
return datetime.today().strftime("%Y-%m-%d %H:%M:%S")
58+
59+
def document_status_have_status(self) -> bool:
60+
statuses = self.document_tree_stats.requirements_status_breakdown.keys()
61+
return any(key is not None for key in statuses)

strictdoc/export/html/generators/view_objects/project_tree_stats.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# mypy: disable-error-code="arg-type"
1+
from collections import defaultdict
22
from dataclasses import dataclass, field
3-
from typing import List, Optional
3+
from typing import Dict, List, Optional
44

55
from strictdoc.backend.sdoc.models.node import SDocNode
66

@@ -30,13 +30,15 @@ class DocumentTreeStats:
3030
requirements_no_rationale: int = 0
3131

3232
# STATUS
33-
requirements_status_none: int = 0
34-
requirements_status_draft: int = 0
35-
requirements_status_backlog: int = 0
36-
requirements_status_active: int = 0
37-
requirements_status_other: int = 0
38-
39-
# Document-level stats.
40-
document_level_stats: List[DocumentStats] = field(
41-
default_factory=DocumentStats
33+
requirements_status_breakdown: Dict[Optional[str], int] = field(
34+
default_factory=lambda: defaultdict(int)
4235
)
36+
37+
def sort_requirements_status_breakdown(self) -> None:
38+
self.requirements_status_breakdown = dict(
39+
sorted(
40+
self.requirements_status_breakdown.items(),
41+
key=lambda item: item[1],
42+
reverse=True,
43+
)
44+
)
Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,16 @@
1-
{# table_key_value #}
1+
{% assert key_value_pair is defined %}
22

3-
<div class="sdoc-table_key_value">
4-
{% for obj in table_key_value %}
5-
6-
{% if obj["Section"] is defined %}
7-
<div class="sdoc-table_key_value-section">{{ obj["Section"] }}</div>
3+
{% if key_value_pair["Section"] is defined %}
4+
<div class="sdoc-table_key_value-section">{{ key_value_pair["Section"] }}</div>
5+
{% else %}
6+
{% if view_object.project_config.is_activated_search() and key_value_pair["Link"] is defined %}
7+
<a
8+
class="sdoc-table_key_value-key"
9+
data-testid="search-{{ key_value_pair["Key"].lower().replace(" ", "-") }}"
10+
href="{{ key_value_pair["Link"] }}">{{ key_value_pair["Key"] }}
11+
</a>
812
{% else %}
9-
{% if view_object.project_config.is_activated_search() and obj["Link"] is defined %}
10-
<a
11-
class="sdoc-table_key_value-key"
12-
data-testid="search-{{ obj["Key"].lower().replace(" ", "-") }}"
13-
href="{{ obj["Link"] }}">{{ obj["Key"] }}
14-
</a>
15-
{% else %}
16-
<div class="sdoc-table_key_value-key">{{ obj["Key"] }}</div>
17-
{% endif %}
18-
<div class="sdoc-table_key_value-value">{{ obj["Value"] }}</div>
13+
<div class="sdoc-table_key_value-key">{{ key_value_pair["Key"] }}</div>
1914
{% endif %}
20-
21-
{% endfor %}
22-
</div>
15+
<div class="sdoc-table_key_value-value">{{ key_value_pair["Value"] }}</div>
16+
{% endif %}
Lines changed: 135 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,190 @@
11
<div class="main">
2-
{% with table_key_value = [
3-
{"Section":"General information"},
2+
3+
<div class="sdoc-table_key_value">
4+
5+
{% with key_value_pair =
6+
{
7+
"Section": "General information"
8+
}
9+
%}
10+
{% include "components/table_key_value/index.jinja" %}
11+
{% endwith %}
12+
13+
{% with key_value_pair =
414
{
515
"Key":"Project name",
616
"Value": view_object.project_config.project_title,
7-
},
17+
}
18+
%}
19+
{% include "components/table_key_value/index.jinja" %}
20+
{% endwith %}
21+
22+
{% with key_value_pair =
823
{
924
"Key":"Statistics generation date",
1025
"Value": view_object.get_datetime(),
11-
},
26+
}
27+
%}
28+
{% include "components/table_key_value/index.jinja" %}
29+
{% endwith %}
30+
31+
{% with key_value_pair =
1232
{
1333
"Key":"Last modification of project data",
1434
"Value": view_object.traceability_index.strictdoc_last_update.strftime("%Y-%m-%d %H:%M:%S"),
15-
},
35+
}
36+
%}
37+
{% include "components/table_key_value/index.jinja" %}
38+
{% endwith %}
39+
40+
{% with key_value_pair =
1641
{
1742
"Key":"Git commit/release",
1843
"Value": view_object.document_tree_stats.git_commit_hash,
19-
},
44+
}
45+
%}
46+
{% include "components/table_key_value/index.jinja" %}
47+
{% endwith %}
48+
49+
{% with key_value_pair =
2050
{
2151
"Key":"Total documents",
2252
"Value": view_object.document_tree_stats.total_documents,
23-
},
24-
{"Section":"Sections"},
53+
}
54+
%}
55+
{% include "components/table_key_value/index.jinja" %}
56+
{% endwith %}
57+
58+
{% with key_value_pair =
59+
{"Section":"Sections"}
60+
%}
61+
{% include "components/table_key_value/index.jinja" %}
62+
{% endwith %}
63+
64+
{% with key_value_pair =
2565
{
2666
"Key":"Total sections",
2767
"Link": view_object.render_url('search?q=node.is_section'),
2868
"Value": view_object.document_tree_stats.total_sections,
29-
},
69+
}
70+
%}
71+
{% include "components/table_key_value/index.jinja" %}
72+
{% endwith %}
73+
74+
{% with key_value_pair =
3075
{
3176
"Key":"Sections without any text",
3277
"Link": view_object.render_url('search?q=(node.is_section and not node.contains_any_text)'),
3378
"Value": view_object.document_tree_stats.sections_without_text_nodes,
34-
},
35-
{"Section":"Requirements"},
79+
}
80+
%}
81+
{% include "components/table_key_value/index.jinja" %}
82+
{% endwith %}
83+
84+
{% with key_value_pair =
85+
{"Section":"Requirements"}
86+
%}
87+
{% include "components/table_key_value/index.jinja" %}
88+
{% endwith %}
89+
90+
{% with key_value_pair =
3691
{
3792
"Key":"Total requirements",
3893
"Link": view_object.render_url('search?q=node.is_requirement'),
3994
"Value": view_object.document_tree_stats.total_requirements,
40-
},
95+
}
96+
%}
97+
{% include "components/table_key_value/index.jinja" %}
98+
{% endwith %}
99+
100+
{% with key_value_pair =
41101
{
42102
"Key":"Requirements with no UID",
43103
"Link": view_object.render_url('search?q=(node.is_requirement and node["UID"] == None)'),
44104
"Value": view_object.document_tree_stats.requirements_no_uid,
45-
},
105+
}
106+
%}
107+
{% include "components/table_key_value/index.jinja" %}
108+
{% endwith %}
109+
110+
{% with key_value_pair =
46111
{
47112
"Key":"Root-level requirements not connected to by any requirement",
48113
"Link": view_object.render_url('search?q=(node.is_requirement and node.is_root and node["STATUS"] != "Backlog" and not node.has_child_requirements)'),
49114
"Value": view_object.document_tree_stats.requirements_root_no_links,
50-
},
115+
}
116+
%}
117+
{% include "components/table_key_value/index.jinja" %}
118+
{% endwith %}
119+
120+
{% with key_value_pair =
51121
{
52122
"Key":"Non-root-level requirements not connected to any parent requirement",
53123
"Link": view_object.render_url('search?q=(node.is_requirement and not node.is_root and node["STATUS"] != "Backlog" and not node.has_parent_requirements)'),
54124
"Value": view_object.document_tree_stats.requirements_no_links,
55-
},
125+
}
126+
%}
127+
{% include "components/table_key_value/index.jinja" %}
128+
{% endwith %}
129+
130+
{% with key_value_pair =
56131
{
57132
"Key":"Requirements with no RATIONALE",
58133
"Link": view_object.render_url('search?q=(node.is_requirement and node["RATIONALE"] == None)'),
59134
"Value": view_object.document_tree_stats.requirements_no_rationale,
60-
},
61-
{"Section":"Requirements status"},
62-
{
63-
"Key":"Requirements with no Status",
64-
"Link": view_object.render_url('search?q=(node.is_requirement and node["STATUS"] == None)'),
65-
"Value": view_object.document_tree_stats.requirements_status_none,
66-
},
67-
{
68-
"Key":"Requirements with status Active",
69-
"Link": view_object.render_url('search?q=(node.is_requirement and node["STATUS"] == "Active")'),
70-
"Value": view_object.document_tree_stats.requirements_status_active,
71-
},
72-
{
73-
"Key":"Requirements with status Draft",
74-
"Link": view_object.render_url('search?q=(node.is_requirement and node["STATUS"] == "Draft")'),
75-
"Value": view_object.document_tree_stats.requirements_status_draft,
76-
},
77-
{
78-
"Key":"Requirements with status Backlog",
79-
"Link": view_object.render_url('search?q=(node.is_requirement and node["STATUS"] == "Backlog")'),
80-
"Value": view_object.document_tree_stats.requirements_status_backlog,
81-
},
82-
{
83-
"Key":"Requirements with all other statuses",
84-
"Link": view_object.render_url('search?q=(node.is_requirement and node["STATUS"] != None and node["STATUS"] != "Backlog" and node["STATUS"] != "Active" and node["STATUS"] != "Draft")'),
85-
"Value": view_object.document_tree_stats.requirements_status_other,
86-
},
87-
{"Section":"TBD/TBC"},
135+
}
136+
%}
137+
{% include "components/table_key_value/index.jinja" %}
138+
{% endwith %}
139+
140+
{% if view_object.document_status_have_status() %}
141+
142+
{% with key_value_pair =
143+
{"Section": "Requirements status breakdown"}
144+
%}
145+
{% include "components/table_key_value/index.jinja" %}
146+
{% endwith %}
147+
148+
{% for status_, status_count_ in view_object.document_tree_stats.requirements_status_breakdown.items() %}
149+
{% with key_value_pair =
150+
{
151+
"Key":"Requirements with status "~status_,
152+
"Link": view_object.render_url('search?q=(node.is_requirement and node["STATUS"] == status_)'),
153+
"Value": status_count_,
154+
}
155+
%}
156+
{% include "components/table_key_value/index.jinja" %}
157+
{% endwith %}
158+
{% endfor %}
159+
160+
{% endif %}
161+
162+
{% with key_value_pair =
163+
{"Section":"TBD/TBC"}
164+
%}
165+
{% include "components/table_key_value/index.jinja" %}
166+
{% endwith %}
167+
168+
{% with key_value_pair =
88169
{
89170
"Key":"Total TBD",
90171
"Link": view_object.render_url('search?q=node.contains("TBD")'),
91172
"Value": view_object.document_tree_stats.total_tbd,
92-
},
173+
}
174+
%}
175+
{% include "components/table_key_value/index.jinja" %}
176+
{% endwith %}
177+
178+
{% with key_value_pair =
93179
{
94180
"Key":"Total TBC",
95181
"Link": view_object.render_url('search?q=node.contains("TBC")'),
96182
"Value": view_object.document_tree_stats.total_tbc,
97-
},
98-
] %}
183+
}
184+
%}
99185
{% include "components/table_key_value/index.jinja" %}
100186
{% endwith %}
187+
188+
</div>{# / .sdoc-table_key_value #}
189+
101190
</div>{# / .main #}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
RUN: %strictdoc export %S --output-dir Output | filecheck %s --dump-input=fail
1+
RUN: %strictdoc export %S --output-dir Output | filecheck %s
22
CHECK: Published: Hello world doc
33

44
RUN: %check_exists --file "%S/Output/html/project_statistics.html"
55

6-
RUN: %cat "%S/Output/html/project_statistics.html" | filecheck %s --dump-input=fail --check-prefix CHECK-HTML
6+
RUN: %cat "%S/Output/html/project_statistics.html" | filecheck %s --check-prefix CHECK-HTML
77
CHECK-HTML: Test project
88
CHECK-HTML: Total documents
99
CHECK-HTML: Total requirements
10+
CHECK-HTML-NOT: Requirements status breakdown
Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,12 @@
11
# This test ensures that the TEXT nodes DO NOT contribute to the requirements statistics.
22

3-
RUN: %strictdoc export %S --output-dir Output | filecheck %s --dump-input=fail
3+
RUN: %strictdoc export %S --output-dir Output | filecheck %s
44
CHECK: Published: Hello world doc
55

66
RUN: %check_exists --file "%S/Output/html/project_statistics.html"
77

8-
RUN: %cat "%S/Output/html/project_statistics.html" | filecheck %s --dump-input=fail --check-prefix CHECK-HTML
8+
RUN: %cat "%S/Output/html/project_statistics.html" | filecheck %s --check-prefix CHECK-HTML
99
CHECK-HTML: Total requirements
1010
CHECK-HTML: 3
1111
CHECK-HTML: Requirements with no UID
1212
CHECK-HTML: 3
13-
CHECK-HTML: Root-level requirements not connected to by any requirement
14-
CHECK-HTML: 0
15-
CHECK-HTML: Requirements with no RATIONALE
16-
CHECK-HTML: 3
17-
CHECK-HTML: Requirements with no Status
18-
CHECK-HTML: 3
19-
CHECK-HTML: Requirements with status Active
20-
CHECK-HTML: 0

0 commit comments

Comments
 (0)