Skip to content

UI: autocomplete-for-tags #2273

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 58 additions & 16 deletions strictdoc/backend/sdoc/models/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,22 @@
SDocDocumentContentIF,
SDocDocumentFromFileIF,
SDocDocumentIF,
SDocElementIF,
SDocNodeIF,
SDocSectionIF,
)
from strictdoc.backend.sdoc.models.node import SDocNode
from strictdoc.backend.sdoc.models.type_system import (
GrammarElementField,
GrammarElementFieldMultipleChoice,
GrammarElementFieldSingleChoice,
GrammarElementFieldTag,
)
from strictdoc.core.document_meta import DocumentMeta
from strictdoc.helpers.auto_described import auto_described
from strictdoc.helpers.cast import assert_cast
from strictdoc.helpers.mid import MID
from strictdoc.helpers.ordered_set import OrderedSet


@auto_described
Expand Down Expand Up @@ -79,6 +83,55 @@ def __init__(
SDocDocumentFromFileIF
] = None

def iterate_nodes(
self, element_type: Optional[str] = None
) -> Generator[SDocNodeIF, None, None]:
"""
Iterates over all non-[TEXT] nodes in the document. If element_type
is given, then only nodes of type `element_type` are returned.
Otherwise, all element types are returned.
"""
task_list: List[SDocElementIF] = list(self.section_contents)
while task_list:
node = task_list.pop(0)

if isinstance(node, SDocDocumentFromFileIF):
yield from node.iterate_nodes(element_type)

if isinstance(node, SDocNodeIF):
if node.node_type != "TEXT":
if element_type is None or node.node_type == element_type:
yield node

task_list.extend(node.section_contents)

def has_any_requirements(self) -> bool:
return any(True for _ in self.iterate_nodes())

def collect_options_for_tag(
self, element_type: str, field_name: str
) -> List[str]:
"""
Returns the list of existing options for a tag field in this document.
"""
option_set: OrderedSet[str] = OrderedSet()

for nodeif in self.iterate_nodes(element_type):
node = assert_cast(nodeif, SDocNode)
if field_name in node.ordered_fields_lookup:
node_field = node.ordered_fields_lookup[field_name][0]
field_value = node_field.get_text_value()
if field_value:
options = [
option.strip()
for option in field_value.split(",")
if option.strip()
]
for option in options:
option_set.add(option)

return list(option_set)

@property
def uid(self) -> Optional[str]:
return self.config.uid
Expand Down Expand Up @@ -152,21 +205,6 @@ def has_any_toc_nodes(self) -> bool:
return True
return False

def has_any_requirements(self) -> bool:
task_list: List[SDocDocumentContentIF] = list(self.section_contents)
while len(task_list) > 0:
section_or_requirement = task_list.pop(0)
if isinstance(section_or_requirement, SDocDocumentFromFileIF):
if section_or_requirement.has_any_requirements():
return True
continue
if isinstance(section_or_requirement, SDocNodeIF):
if section_or_requirement.node_type == "TEXT":
continue
return True
task_list.extend(section_or_requirement.section_contents)
return False

def get_display_title(
self,
include_toc_number: bool = True, # noqa: ARG002
Expand Down Expand Up @@ -223,7 +261,7 @@ def get_grammar_element_field_for(
field: GrammarElementField = element.fields_map[field_name]
return field

def get_options_for_choice(
def get_options_for_field(
self, element_type: str, field_name: str
) -> List[str]:
"""
Expand All @@ -237,4 +275,8 @@ def get_options_for_choice(
field, GrammarElementFieldMultipleChoice
):
return field.options

if isinstance(field, GrammarElementFieldTag):
return self.collect_options_for_tag(element_type, field_name)

raise AssertionError(f"Must not reach here: {field}")
41 changes: 26 additions & 15 deletions strictdoc/backend/sdoc/models/document_from_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
@relation(SDOC-SRS-109, scope=file)
"""

from typing import List, Optional, Union
from typing import Generator, List, Optional, Union

from strictdoc.backend.sdoc.document_reference import DocumentReference
from strictdoc.backend.sdoc.models.document import SDocDocument
Expand Down Expand Up @@ -34,21 +34,32 @@ def __init__(
self.resolved_full_path_to_document_file: Optional[str] = None
self.resolved_document: Optional[SDocDocumentIF] = None

def has_any_requirements(self) -> bool:
def iterate_nodes(
self, element_type: Optional[str] = None
) -> Generator[SDocNodeIF, None, None]:
"""
Iterates over all non-[TEXT] nodes in the document. If element_type
is given, then only nodes of type `element_type` are returned.
Otherwise, all element types are returned.
"""

task_list: List[SDocElementIF] = list(self.section_contents)
while len(task_list) > 0:
element = task_list.pop(0)
if isinstance(element, SDocNodeIF):
if element.is_normative_node():
return True
continue

if isinstance(element, SDocDocumentFromFileIF):
if element.has_any_requirements():
return True

task_list.extend(element.section_contents)
return False
while task_list:
node = task_list.pop(0)

if isinstance(node, SDocDocumentFromFileIF):
yield from node.iterate_nodes(element_type)

if isinstance(node, SDocNodeIF):
if node.node_type == "TEXT":
continue
if element_type is None or node.node_type == element_type:
yield node

task_list.extend(node.section_contents)

def has_any_requirements(self) -> bool:
return any(True for _ in self.iterate_nodes())

@property
def section_contents(self) -> List[SDocDocumentIF]:
Expand Down
5 changes: 4 additions & 1 deletion strictdoc/backend/sdoc/models/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ class SDocDocumentFromFileIF(ABC):
ng_resolved_custom_level: Optional[str]

@abstractmethod
def has_any_requirements(self) -> bool:
def iterate_nodes(
self,
element_type: Optional[str] = None,
) -> Generator[SDocNodeIF, None, None]:
raise NotImplementedError # pragma: no cover

@property
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,14 @@ def is_autocompletable(self) -> bool:
return self.field_gef_type in (
RequirementFieldType.SINGLE_CHOICE,
RequirementFieldType.MULTIPLE_CHOICE,
RequirementFieldType.TAG,
)

def is_multiplechoice(self) -> bool:
return self.field_gef_type == RequirementFieldType.MULTIPLE_CHOICE
return self.field_gef_type in (
RequirementFieldType.MULTIPLE_CHOICE,
RequirementFieldType.TAG,
)

def get_input_field_name(self):
return f"requirement[fields][{self.field_mid}][value]"
Expand Down
11 changes: 7 additions & 4 deletions strictdoc/server/routers/main_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -2772,7 +2772,7 @@ def get_autocomplete_field_results(
field_name: Optional[str] = None,
):
"""
Returns matches of possible values of a SingleChoice or MultiChoice field.
Returns matches of possible values of a SingleChoice, MultiChoice or tag field.
The field is identified by the document_mid, the element_type, and the field_name.
"""
output = ""
Expand All @@ -2787,7 +2787,7 @@ def get_autocomplete_field_results(
)
)
if document:
all_options = document.get_options_for_choice(
all_options = document.get_options_for_field(
element_type, field_name
)
field: GrammarElementField = (
Expand All @@ -2796,8 +2796,11 @@ def get_autocomplete_field_results(
)
)

if field.gef_type == RequirementFieldType.MULTIPLE_CHOICE:
# MultipleChoice: We split the query into its parts:
if field.gef_type in (
RequirementFieldType.MULTIPLE_CHOICE,
RequirementFieldType.TAG,
):
# MultipleChoice/Tag: We split the query into its parts:
#
# Example User input: "Some Value, Another Value, Yet ano|".
# parts = ['some value', 'another value', 'yet ano'] # noqa: ERA001
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
[DOCUMENT]
TITLE: Document 1

[GRAMMAR]
ELEMENTS:
- TAG: TEXT
FIELDS:
- TITLE: UID
TYPE: String
REQUIRED: False
- TITLE: STATEMENT
TYPE: String
REQUIRED: True
- TAG: REQUIREMENT
FIELDS:
- TITLE: UID
TYPE: String
REQUIRED: False
- TITLE: LEVEL
TYPE: String
REQUIRED: False
- TITLE: TAGS
TYPE: Tag
REQUIRED: False
- TITLE: TITLE
TYPE: String
REQUIRED: False
- TITLE: STATEMENT
TYPE: String
REQUIRED: False
- TITLE: RATIONALE
TYPE: String
REQUIRED: False
- TITLE: COMMENT
TYPE: String
REQUIRED: False
RELATIONS:
- TYPE: Parent
- TYPE: File

[REQUIREMENT]
UID: REQ-1
TAGS: Tag1
TITLE: Requirement 1 ABC
STATEMENT: >>>
Shall test foo.
<<<

[REQUIREMENT]
UID: REQ-2
TAGS: Tag2
TITLE: Requirement 2 XYZ
STATEMENT: >>>
Shall test bar.
<<<

[REQUIREMENT]
UID: REQ-3
TAGS: Tag1, Tag2
TITLE: New Requirement
STATEMENT: >>>
Shall test foo 2.
<<<
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
[DOCUMENT]
TITLE: Document 1

[GRAMMAR]
ELEMENTS:
- TAG: TEXT
FIELDS:
- TITLE: UID
TYPE: String
REQUIRED: False
- TITLE: STATEMENT
TYPE: String
REQUIRED: True
- TAG: REQUIREMENT
FIELDS:
- TITLE: UID
TYPE: String
REQUIRED: False
- TITLE: LEVEL
TYPE: String
REQUIRED: False
- TITLE: TAGS
TYPE: Tag
REQUIRED: False
- TITLE: TITLE
TYPE: String
REQUIRED: False
- TITLE: STATEMENT
TYPE: String
REQUIRED: False
- TITLE: RATIONALE
TYPE: String
REQUIRED: False
- TITLE: COMMENT
TYPE: String
REQUIRED: False
RELATIONS:
- TYPE: Parent
- TYPE: File

[REQUIREMENT]
UID: REQ-1
TAGS: Tag1
TITLE: Requirement 1 ABC
STATEMENT: >>>
Shall test foo.
<<<

[REQUIREMENT]
UID: REQ-2
TITLE: Requirement 2 XYZ
STATEMENT: >>>
Shall test bar.
<<<

Loading
Loading