Skip to content

Commit

Permalink
Merge pull request #1467 from strictdoc-project/mettta/_#1271_UID_aut…
Browse files Browse the repository at this point in the history
…ogeneration

UI: add UID autogeneration button to section form
  • Loading branch information
mettta authored Nov 18, 2023
2 parents 1ac7122 + 61f287b commit ce9cce1
Show file tree
Hide file tree
Showing 43 changed files with 553 additions and 233 deletions.
15 changes: 3 additions & 12 deletions strictdoc/commands/manage_autouid_command.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import sys
from collections import Counter

from strictdoc.backend.sdoc.errors.document_tree_error import DocumentTreeError
from strictdoc.backend.sdoc.writer import SDWriter
Expand All @@ -14,7 +13,6 @@
from strictdoc.helpers.parallelizer import Parallelizer
from strictdoc.helpers.string import (
create_safe_acronym,
create_safe_title_string,
)


Expand Down Expand Up @@ -45,17 +43,10 @@ def execute(*, project_config: ProjectConfig, parallelizer: Parallelizer):
document_acronym = create_safe_acronym(
document_stats_.document.title
)
section_uids_so_far = Counter()
for section in document_stats_.sections_without_uid:
section_title = create_safe_title_string(section.title)
auto_uid = f"SECTION-{document_acronym}-{section_title}"

count_so_far = section_uids_so_far[auto_uid]
section_uids_so_far[auto_uid] += 1

if count_so_far >= 1:
auto_uid += f"-{section_uids_so_far[auto_uid]}"

auto_uid = document_tree_stats.get_auto_section_uid(
document_acronym, section
)
section.uid = auto_uid
section.reserved_uid = auto_uid

Expand Down
23 changes: 19 additions & 4 deletions strictdoc/core/analyzers/document_stats.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from collections import Counter
from dataclasses import dataclass, field
from typing import Dict, List

from strictdoc.backend.sdoc.models.document import Document
from strictdoc.backend.sdoc.models.requirement import Requirement
from strictdoc.backend.sdoc.models.section import Section
from strictdoc.helpers.string import create_safe_title_string


@dataclass
Expand All @@ -19,15 +21,15 @@ class DocumentStats:
default_factory=dict
)
sections_without_uid: List[Section] = field(default_factory=list)
section_uids_so_far: Counter = field(default_factory=Counter)


@dataclass
class DocumentTreeStats:
single_document_stats: List[DocumentStats] = field(default_factory=list)
single_document_stats: List[DocumentStats]

requirements_per_prefix: Dict[str, SinglePrefixRequirements] = field(
default_factory=dict
)
requirements_per_prefix: Dict[str, SinglePrefixRequirements]
section_uids_so_far: Counter

def get_next_requirement_uid(self, prefix) -> str:
next_number = self.get_next_requirement_uid_number(prefix)
Expand All @@ -48,3 +50,16 @@ def get_next_requirement_uid_number(self, prefix) -> int:
else 1
)
return next_number

def get_auto_section_uid(
self, document_acronym: str, section: Section
) -> str:
section_title = create_safe_title_string(section.title)
auto_uid = f"SECTION-{document_acronym}-{section_title}"

count_so_far = self.section_uids_so_far[auto_uid]
self.section_uids_so_far[auto_uid] += 1

if count_so_far >= 1:
auto_uid += f"-{self.section_uids_so_far[auto_uid]}"
return auto_uid
13 changes: 10 additions & 3 deletions strictdoc/core/analyzers/document_uid_analyzer.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from collections import Counter
from typing import Dict, List

from strictdoc.backend.sdoc.models.document import Document
Expand All @@ -21,7 +22,7 @@ class DocumentUIDAnalyzer:
def analyze_document_tree(traceability_index: TraceabilityIndex):
global_requirements_per_prefix: Dict[str, SinglePrefixRequirements] = {}
document_tree_stats: List[DocumentStats] = []

section_uids_so_far = Counter()
for document in traceability_index.document_tree.document_list:
document_stats: DocumentStats = (
DocumentUIDAnalyzer.analyze_document(document)
Expand All @@ -42,10 +43,12 @@ def analyze_document_tree(traceability_index: TraceabilityIndex):
prefix_requirements_.requirements_uid_numbers
)
document_tree_stats.append(document_stats)

for section_uid_ in document_stats.section_uids_so_far:
section_uids_so_far[section_uid_] += 1
return DocumentTreeStats(
single_document_stats=document_tree_stats,
requirements_per_prefix=global_requirements_per_prefix,
section_uids_so_far=section_uids_so_far,
)

@staticmethod
Expand All @@ -56,7 +59,11 @@ def analyze_document(
document_iterator = DocumentCachingIterator(document)
for node in document_iterator.all_content():
if isinstance(node, Section):
if node.reserved_uid is None:
if node.reserved_uid is not None:
this_document_stats.section_uids_so_far[
node.reserved_uid
] += 1
else:
this_document_stats.sections_without_uid.append(node)
continue
if not isinstance(node, Requirement):
Expand Down
6 changes: 6 additions & 0 deletions strictdoc/core/traceability_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ def get_node_by_mid(self, node_id: MID) -> Any:
assert isinstance(node_id, MID), node_id
return self._map_id_to_node[node_id]

def get_node_by_mid_weak(self, node_id: MID) -> Optional[Any]:
assert isinstance(node_id, MID), node_id
if node_id not in self._map_id_to_node:
return None
return self._map_id_to_node[node_id]

def has_requirements(self):
return len(self.requirements_connections.keys()) > 0

Expand Down
14 changes: 8 additions & 6 deletions strictdoc/core/transforms/section.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,11 @@ def perform(self):
section.reserved_uid
]

section.uid = form_object.section_uid
section.reserved_uid = form_object.section_uid
section_uid = assert_cast(form_object.section_uid, str)
section.uid = section_uid
section.reserved_uid = section_uid
traceability_index.requirements_connections[
section.reserved_uid
section_uid
] = RequirementConnections(
requirement=section,
document=section.document,
Expand Down Expand Up @@ -300,10 +301,11 @@ def perform(self):
)

if len(form_object.section_uid) > 0:
section.uid = form_object.section_uid
section.reserved_uid = form_object.section_uid
section_uid = assert_cast(form_object.section_uid, str)
section.uid = section_uid
section.reserved_uid = section_uid
traceability_index.requirements_connections[
section.reserved_uid
section_uid
] = RequirementConnections(
requirement=section,
document=document,
Expand Down
170 changes: 151 additions & 19 deletions strictdoc/export/html/form_objects/section_form_object.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import html
from collections import defaultdict
from typing import Dict

from starlette.datastructures import FormData

from strictdoc.backend.sdoc.models.section import Section
from strictdoc.export.html.form_objects.requirement_form_object import (
RequirementFormField,
RequirementFormFieldType,
)
from strictdoc.helpers.auto_described import auto_described
from strictdoc.helpers.cast import assert_cast
from strictdoc.helpers.form_data import parse_form_data
from strictdoc.helpers.mid import MID
from strictdoc.helpers.string import sanitize_html_form_field
from strictdoc.server.error_object import ErrorObject


Expand All @@ -11,44 +22,165 @@ class SectionFormObject(ErrorObject):
def __init__(
self,
*,
section_uid: str,
section_mid: str,
section_title: str,
section_statement: str,
section_uid_field: RequirementFormField,
section_title_field: RequirementFormField,
section_statement_field: RequirementFormField,
):
assert isinstance(section_uid, str)
assert isinstance(section_mid, str)
assert isinstance(section_title, str)
assert isinstance(section_statement, str)
assert isinstance(section_uid_field, RequirementFormField)
assert isinstance(section_title_field, RequirementFormField)
assert isinstance(section_statement_field, RequirementFormField)

super().__init__()
self.section_uid: str = section_uid
self.section_mid: str = section_mid
self.section_title: str = section_title
self.section_statement: str = html.escape(section_statement)
self.section_statement_unescaped: str = section_statement
self.section_uid_field: RequirementFormField = section_uid_field
self.section_title_field: RequirementFormField = section_title_field
self.section_statement_field: RequirementFormField = (
section_statement_field
)

@property
def section_uid(self):
return self.section_uid_field.field_unescaped_value

@property
def section_title(self):
return self.section_title_field.field_unescaped_value

@property
def section_statement_unescaped(self):
return self.section_statement_field.field_unescaped_value

@staticmethod
def create_new():
return SectionFormObject(
section_uid="",
section_mid=MID.create().get_string_value(),
section_title="",
section_statement="",
section_uid_field=RequirementFormField(
field_mid=MID.create().get_string_value(),
field_name="UID",
field_type=RequirementFormFieldType.SINGLELINE,
field_unescaped_value="",
field_escaped_value="",
),
section_title_field=RequirementFormField(
field_mid=MID.create().get_string_value(),
field_name="TITLE",
field_type=RequirementFormFieldType.SINGLELINE,
field_unescaped_value="",
field_escaped_value="",
),
section_statement_field=RequirementFormField(
field_mid=MID.create().get_string_value(),
field_name="STATEMENT",
field_type=RequirementFormFieldType.MULTILINE,
field_unescaped_value="",
field_escaped_value="",
),
)

@staticmethod
def create_from_section(*, section: Section):
statement = (
uid_field_value = (
section.reserved_uid if section.reserved_uid is not None else ""
)
uid_escaped_field_value = html.escape(uid_field_value)

title_field_value = section.title if section.title is not None else ""
title_escaped_field_value = html.escape(title_field_value)

statement_field_value = (
section.free_texts[0].get_parts_as_text()
if len(section.free_texts) > 0
else ""
)
statement_escaped_field_value = html.escape(statement_field_value)
return SectionFormObject(
section_uid=section.reserved_uid
if section.reserved_uid is not None
else "",
section_mid=section.mid.get_string_value(),
section_title=section.title,
section_statement=statement,
section_uid_field=RequirementFormField(
field_mid=MID.create().get_string_value(),
field_name="UID",
field_type=RequirementFormFieldType.SINGLELINE,
field_unescaped_value=uid_field_value,
field_escaped_value=uid_escaped_field_value,
),
section_title_field=RequirementFormField(
field_mid=MID.create().get_string_value(),
field_name="TITLE",
field_type=RequirementFormFieldType.SINGLELINE,
field_unescaped_value=title_field_value,
field_escaped_value=title_escaped_field_value,
),
section_statement_field=RequirementFormField(
field_mid=MID.create().get_string_value(),
field_name="STATEMENT",
field_type=RequirementFormFieldType.MULTILINE,
field_unescaped_value=statement_field_value,
field_escaped_value=statement_escaped_field_value,
),
)

@staticmethod
def create_from_request(
*,
section_mid: str,
request_form_data: FormData,
) -> "SectionFormObject":
request_form_data_as_list = [
(field_name, field_value)
for field_name, field_value in request_form_data.multi_items()
]
request_form_dict: Dict = assert_cast(
parse_form_data(request_form_data_as_list), dict
)
requirement_dict = request_form_dict["requirement"]
requirement_fields_dict = requirement_dict["fields"]

requirement_fields = defaultdict(list)
for _, field_dict in requirement_fields_dict.items():
field_name = field_dict["name"]
field_value = field_dict["value"]
requirement_fields[field_name].append(field_value)

uid_field_value = requirement_fields["UID"][0]
sanitized_uid_field_value: str = sanitize_html_form_field(
uid_field_value, multiline=False
)
section_uid_field = RequirementFormField(
field_mid=MID.create().get_string_value(),
field_name="UID",
field_type=RequirementFormFieldType.SINGLELINE,
field_unescaped_value=sanitized_uid_field_value,
field_escaped_value=html.escape(sanitized_uid_field_value),
)

title_field_value = requirement_fields["TITLE"][0]
sanitized_title_field_value: str = sanitize_html_form_field(
title_field_value, multiline=False
)
section_title_field = RequirementFormField(
field_mid=MID.create().get_string_value(),
field_name="TITLE",
field_type=RequirementFormFieldType.SINGLELINE,
field_unescaped_value=sanitized_title_field_value,
field_escaped_value=html.escape(sanitized_title_field_value),
)

statement_field_value = requirement_fields["STATEMENT"][0]
sanitized_statement_field_value: str = sanitize_html_form_field(
statement_field_value, multiline=True
)
section_statement_field = RequirementFormField(
field_mid=MID.create().get_string_value(),
field_name="STATEMENT",
field_type=RequirementFormFieldType.MULTILINE,
field_unescaped_value=sanitized_statement_field_value,
field_escaped_value=html.escape(sanitized_statement_field_value),
)
form_object = SectionFormObject(
section_mid=section_mid,
section_uid_field=section_uid_field,
section_title_field=section_title_field,
section_statement_field=section_statement_field,
)
return form_object
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@

<turbo-stream action="before" target="requirement_{{ requirement_mid }}__new_comment">
<template>
{% include "components/requirement_form/row_with_comment.jinja" %}
{% include "components/form/row/row_with_comment.jinja" %}
</template>
</turbo-stream>
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@

<turbo-stream action="before" target="requirement_{{ requirement_mid }}__new_relation">
<template>
{% include "components/requirement_form/row_with_relation.jinja" %}
{% include "components/form/row/row_with_relation.jinja" %}
</template>
</turbo-stream>
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

<turbo-stream action="{{ replace_action }}" target="article-{{ target_node_mid }}">
<template>
{% include "components/requirement_form/index.jinja" %}
{% include "screens/document/document/frame_requirement_form.jinja" %}
</template>
</turbo-stream>
Loading

0 comments on commit ce9cce1

Please sign in to comment.