Skip to content
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

Looping 2.5 | Repeating Blocks for List Items #1106

Merged
merged 114 commits into from
Jun 26, 2023
Merged
Show file tree
Hide file tree
Changes from 93 commits
Commits
Show all changes
114 commits
Select commit Hold shift + click to select a range
443d266
Initial repeating blocks test schema
kylelawsonAND Apr 25, 2023
08fed55
Schema for repeating blocks and section summary incomplete + ListColl…
kylelawsonAND Apr 27, 2023
4ab2f3f
Repeating blocks are presented to the user and can be answered howeve…
kylelawsonAND Apr 28, 2023
4311b76
List Collector now always enters add_block & both add_block and edit_…
kylelawsonAND May 2, 2023
66c3e1a
Merge branch 'main' into feat/looping-2.5-simple-list-collector
kylelawsonAND May 2, 2023
3e952d8
make format
kylelawsonAND May 2, 2023
d0b49d6
edit_block does not lead to repeating_blocks. All repeating_blocks sh…
kylelawsonAND May 3, 2023
fe76286
Merge remote-tracking branch 'origin/main' into feat/looping-2.5
kylelawsonAND May 4, 2023
ba48c67
Merge branch 'feat/looping-2.5' into feat/looping-2.5-simple-list-col…
kylelawsonAND May 4, 2023
ada2974
Blocks in repeating_blocks calculated upfront. Answers from repeating…
kylelawsonAND May 4, 2023
63611a1
Reverting list_collector handler and fixing invalid use of set
kylelawsonAND May 4, 2023
508101c
Return to summary reused throughout ListActions
kylelawsonAND May 5, 2023
281c470
Adding list item completion progress to progress_store and loading th…
kylelawsonAND May 15, 2023
7bda62b
Looping 2.5 - Simple Repeating Blocks in List Collector (No Item Prog…
kylelawsonAND May 16, 2023
641eea5
Merge branch 'main' into feat/looping-2.5
kylelawsonAND May 16, 2023
06569ac
Merge branch 'feat/looping-2.5' into feat/looping-2.5-list-item-progress
kylelawsonAND May 16, 2023
80e0ac1
Fixing existing questionnaire store tests broken by adding list item …
kylelawsonAND May 16, 2023
3387d04
Added remove list item progress to progress store and called from que…
kylelawsonAND May 16, 2023
551ff4f
List Item Progress tracked with the section and list item id keyed pr…
kylelawsonAND May 17, 2023
bf6134e
List Item Progress starts with list item add, adding the add block to…
kylelawsonAND May 17, 2023
08b0304
Make response expiry date mandatory (#1104)
petechd May 18, 2023
fa0a1d7
Reverting change to serialize() method naming + removing dev only chn…
kylelawsonAND May 18, 2023
18d09f4
Bind additional contexts to flush requests (#1108)
petechd May 18, 2023
6f3109b
Addressing comments to simplify progress evaluation logic + adding ca…
kylelawsonAND May 19, 2023
cee23dc
Schemas v3.56.0 (#1110)
MebinAbraham May 23, 2023
b35d6ec
Addressing comments regarding common usage of first incomplete repeat…
kylelawsonAND May 23, 2023
6557ef0
Augmenting comment in capture_dependent_sections_for_list(self, list_…
kylelawsonAND May 23, 2023
800c15f
Bug fix to repeating block cache, list section deps reverted to dict …
kylelawsonAND May 24, 2023
ddd30d3
Removing get_completed_block_ids from questionnaire store updater as …
kylelawsonAND May 24, 2023
5b64601
Preliminary impl for list item row icons - breaks tests
kylelawsonAND May 24, 2023
e70f16c
Merge branch 'main' into feat/looping-2.5
kylelawsonAND May 25, 2023
2462682
Feat/looping 2.5 list item progress (#1105)
kylelawsonAND May 25, 2023
aa0008d
Merge branch 'feat/looping-2.5' into feat/looping-2.5-list-item-progr…
kylelawsonAND May 25, 2023
5b561d9
Fixing tests broken by list collector icon render changes
kylelawsonAND May 25, 2023
5b35bff
Fixing incorrect formatting and rewording a type hint
kylelawsonAND May 25, 2023
127b3eb
Cleaned up evaluation of list item icon.
kylelawsonAND May 25, 2023
36fb6c7
Schemas v3.57.0 (#1113)
MebinAbraham May 25, 2023
b360362
Reverting accidental reversion of a file
kylelawsonAND May 25, 2023
fdc0f75
Schemas v3.57.1 (#1115)
MebinAbraham May 26, 2023
c2439d8
Schemas v3.58.0 (#1116)
MebinAbraham May 26, 2023
cc12488
Implement "progress" value source (#1044)
ONS-Guilhem-Forey May 26, 2023
82b2abe
Fix handling of invalid values in form numerical inputs (#1111)
petechd May 30, 2023
ccc82dd
Passing only section id and bool for repeating blocks for list contex…
kylelawsonAND May 30, 2023
c9e928f
Adding missing list context constructor arg
kylelawsonAND May 30, 2023
9014456
Enforcing kwargs on _build_list_items_context
kylelawsonAND May 30, 2023
8c1272d
Merge branch 'main' into feat/looping-2.5
kylelawsonAND May 30, 2023
b37363a
Merge conflict resolution with main.
kylelawsonAND May 30, 2023
5824f3f
Running make format
kylelawsonAND May 30, 2023
0dafeb1
Adding list collector with repeating blocks fixture and testing the r…
kylelawsonAND May 31, 2023
fe398e2
Completing unit testing of additions to QuestionnaireSchema
kylelawsonAND May 31, 2023
f404619
Update Chromedriver to version 113 (#1118)
katie-gardner Jun 1, 2023
278a464
ListContext unit tests
kylelawsonAND Jun 1, 2023
eee27b1
Updating list collector summary to correctly acquire related answers …
kylelawsonAND Jun 2, 2023
4b9050a
Enforcing kwargs on update_section_status and make format
kylelawsonAND Jun 2, 2023
12fb8ff
Feat/Grand calculated summary (#1107)
katie-gardner Jun 2, 2023
69cfc90
Integration tests started
kylelawsonAND Jun 5, 2023
d5f7173
Fix dynamic answers functional test (#1121)
petechd Jun 6, 2023
d76e9de
Repeating blocks integration test suite complete
kylelawsonAND Jun 6, 2023
ab19672
Schemas v3.59.0 (#1130)
berroar Jun 7, 2023
ffbc72d
Make format
kylelawsonAND Jun 8, 2023
f045fd8
running and fixing mypy
kylelawsonAND Jun 8, 2023
7bc7881
Merge branch 'main' into feat/looping-2.5
kylelawsonAND Jun 8, 2023
e3c8379
Merge with main and format and lint
kylelawsonAND Jun 8, 2023
accdc6a
Fixing linting errors and pointing to relevant validator
kylelawsonAND Jun 8, 2023
3ad65a6
Fixing validator tag typo
kylelawsonAND Jun 8, 2023
a2986c0
Schemas v3.60.0 (#1133)
berroar Jun 8, 2023
0518298
Fixing mocker patch of renamed method
kylelawsonAND Jun 8, 2023
3b9934a
make format
kylelawsonAND Jun 8, 2023
090e652
Update to chromedriver v114 (#1134)
berroar Jun 9, 2023
f035587
Functional test progress
kylelawsonAND Jun 9, 2023
1dcf278
Add DESNZ theme (#1131)
petechd Jun 12, 2023
d04325c
Schemas v3.61.0 (#1139)
berroar Jun 13, 2023
7ba801e
Adding functional test for editing repeating block answers from secti…
kylelawsonAND Jun 13, 2023
cb61664
Asserting completing list items in func tests and editing from sectio…
kylelawsonAND Jun 14, 2023
da95c6b
Merge branch 'main' into feat/looping-2.5
kylelawsonAND Jun 14, 2023
dee36f5
Misc type hinting
kylelawsonAND Jun 14, 2023
f4a4939
Misc type hinting
kylelawsonAND Jun 14, 2023
7772bdc
Formatting
kylelawsonAND Jun 14, 2023
6326c5d
Consolidating duplicate func test helper method and fixing typos.
kylelawsonAND Jun 14, 2023
d1e0a4a
Merge branch 'feature-prepop' into feat/looping-2.5
kylelawsonAND Jun 15, 2023
cdd794b
Linting, formatting and merging with feat prepop.
kylelawsonAND Jun 15, 2023
398156a
Fixing broken tests
kylelawsonAND Jun 15, 2023
7b337f1
Merge branch 'feature-prepop' into feat/looping-2.5
kylelawsonAND Jun 15, 2023
6b54da6
Merge branch 'main' into feature-prepop-update-with-main
kylelawsonAND Jun 15, 2023
af56745
Linting, formatting and cleaning up merge with main
kylelawsonAND Jun 15, 2023
af51ddd
Merge branch 'feature-prepop-update-with-main' into feat/looping-2.5
kylelawsonAND Jun 16, 2023
9e27f42
Merge branch 'feature-prepop' into feat/looping-2.5
kylelawsonAND Jun 16, 2023
4f4e416
ListRepeatingBlock now extends to ListAction and does not invoke pare…
kylelawsonAND Jun 16, 2023
59a2071
Addressing PR comments 16-6-23
kylelawsonAND Jun 16, 2023
b4eb3f4
Reverting local test changes.
kylelawsonAND Jun 16, 2023
4316be3
Reverting local test changes by formatting
kylelawsonAND Jun 16, 2023
20975e9
Reverting local test changes by formatting
kylelawsonAND Jun 16, 2023
6f947e5
Adding additional docs and type hints to ProgressStore
kylelawsonAND Jun 19, 2023
8ffe90a
Addressing PR comments.
kylelawsonAND Jun 19, 2023
c3b2425
Fixing list collector (no RP) icons
kylelawsonAND Jun 19, 2023
62ebf67
Removing person icon change.
kylelawsonAND Jun 19, 2023
377b218
Adding repeating blocks to the submission payload and renaming list n…
kylelawsonAND Jun 20, 2023
e090354
Adding test coverage for repeating block answers in submission payload.
kylelawsonAND Jun 20, 2023
5eb24da
Reformat
kylelawsonAND Jun 20, 2023
f1aa2f7
Adding func test and schema for a repeating blocks LC on a hub questi…
kylelawsonAND Jun 20, 2023
1a0942e
Fixing test broken in prev commit.
kylelawsonAND Jun 21, 2023
55f72ac
Reformatting.
kylelawsonAND Jun 21, 2023
8ad4cd7
Updating naming of progress store methods follwoing convos.
kylelawsonAND Jun 21, 2023
175de45
Updating comment
kylelawsonAND Jun 22, 2023
6b5460b
Updating comment
kylelawsonAND Jun 22, 2023
d74b098
Formatting
kylelawsonAND Jun 22, 2023
539346c
Addressing comments
kylelawsonAND Jun 22, 2023
a9f9b5f
Remvovign whitespace from schema
kylelawsonAND Jun 22, 2023
59db77c
Amending naming and type hinting in list collector block summary and…
kylelawsonAND Jun 23, 2023
50bec33
Changing to unit and currency in simple RB schema.
kylelawsonAND Jun 23, 2023
fc1551c
Formatting
kylelawsonAND Jun 23, 2023
4bd1f26
Removing redundant test schema
kylelawsonAND Jun 26, 2023
b5f2b87
Using latest validator
kylelawsonAND Jun 26, 2023
0a671f8
Merge branch 'feature-prepop' into feat/looping-2.5
kylelawsonAND Jun 26, 2023
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
138 changes: 78 additions & 60 deletions app/data_models/progress_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from app.data_models.progress import Progress, ProgressDictType
from app.questionnaire.location import Location

SectionKeyType = tuple[str, Optional[str]]
ProgressKeyType = tuple[str, Optional[str]]


@dataclass
Expand All @@ -20,34 +20,42 @@ def __iter__(self) -> Iterator[tuple[str]]:

class ProgressStore:
"""
An object that stores and updates references to sections and blocks
An object that stores and updates references to sections and list items
that have been started.
"""

def __init__(
self, in_progress_sections: Optional[Iterable[ProgressDictType]] = None
self,
in_progress_sections_and_list_items: Iterable[ProgressDictType] | None = None,
) -> None:
"""
Instantiate a ProgressStore object that tracks the status of sections and its completed blocks
Instantiate a ProgressStore object that tracks the progress status of sections and list items,
and their completed blocks.
- Standard Sections are keyed by Section ID, and a None List Item ID
- Repeating Sections (dynamic Sections created for List Items) are keyed by their randomly generated
Section ID, and their List Item ID
- List Items are keyed by the Section ID for the Section in which their List Collector appears,
and the List Item ID.
Args:
in_progress_sections: A list of hierarchical dict containing the section status and completed blocks
in_progress_sections_and_list_items: A list of hierarchical dict containing the completion status
and completed blocks
"""
self._is_dirty: bool = False
self._is_routing_backwards: bool = False
self._progress: MutableMapping[SectionKeyType, Progress] = self._build_map(
in_progress_sections or []
self._progress: MutableMapping[ProgressKeyType, Progress] = self._build_map(
in_progress_sections_and_list_items or []
)

def __contains__(self, section_key: SectionKeyType) -> bool:
return section_key in self._progress
def __contains__(self, progress_key: ProgressKeyType) -> bool:
return progress_key in self._progress

@staticmethod
def _build_map(section_progress_list: Iterable[ProgressDictType]) -> MutableMapping:
def _build_map(progress_list: Iterable[ProgressDictType]) -> MutableMapping:
"""
Builds the progress_store's data structure from a list of progress dictionaries.

The `section_key` is tuple consisting of `section_id` and the `list_item_id`.
The `section_progress` is a mutableMapping created from the Progress object.
The `progress_key` is tuple consisting of `section_id` and the `list_item_id`.
The `progress` is a mutableMapping created from the Progress object.

Example structure:
{
Expand All @@ -62,10 +70,10 @@ def _build_map(section_progress_list: Iterable[ProgressDictType]) -> MutableMapp

return {
(
section_progress["section_id"],
section_progress.get("list_item_id"),
): Progress.from_dict(section_progress)
for section_progress in section_progress_list
progress["section_id"],
petechd marked this conversation as resolved.
Show resolved Hide resolved
progress.get("list_item_id"),
): Progress.from_dict(progress)
for progress in progress_list
}

@property
Expand All @@ -76,87 +84,97 @@ def is_dirty(self) -> bool:
def is_routing_backwards(self) -> bool:
return self._is_routing_backwards

def is_section_complete(
self, section_id: str, list_item_id: Optional[str] = None
def is_section_or_list_item_complete(
berroar marked this conversation as resolved.
Show resolved Hide resolved
self, section_id: str, list_item_id: str | None = None
) -> bool:
return (section_id, list_item_id) in self.section_keys(
"""
Return True if the CompletionStatus of the Section or List Item specified by the given section_id and
list_item_id is COMPLETED or INDIVIDUAL_RESPONSE_REQUESTED, else False.
"""
return (section_id, list_item_id) in self.progress_keys(
statuses={
CompletionStatus.COMPLETED,
CompletionStatus.INDIVIDUAL_RESPONSE_REQUESTED,
}
)

def section_keys(
def progress_keys(
self,
statuses: Optional[Iterable[str]] = None,
section_ids: Optional[Iterable[str]] = None,
) -> list[SectionKeyType]:
statuses: Iterable[str] | None = None,
section_ids: Iterable[str] | None = None,
) -> list[ProgressKeyType]:
if not statuses:
statuses = {*CompletionStatus()}

section_keys = [
progress_keys = [
section_key
for section_key, section_progress in self._progress.items()
if section_progress.status in statuses
]

if section_ids is None:
return section_keys
return progress_keys

return [
section_key
for section_key in section_keys
if any(section_id in section_key for section_id in section_ids)
progress_key
for progress_key in progress_keys
if any(section_id in progress_key for section_id in section_ids)
]

def update_section_status(
self, section_status: str, section_id: str, list_item_id: Optional[str] = None
def update_section_or_list_item_completion_status(
self,
completion_status: str,
section_id: str,
list_item_id: str | None = None,
) -> bool:
"""
Updates the completion status of the section or list item specified by the key based on the given section id and list item id.
"""
updated = False
section_key = (section_id, list_item_id)
if section_key in self._progress:
if self._progress[section_key].status != section_status:
if self._progress[section_key].status != completion_status:
updated = True
self._progress[section_key].status = section_status
self._progress[section_key].status = completion_status
self._is_dirty = True

elif section_status == CompletionStatus.INDIVIDUAL_RESPONSE_REQUESTED:
elif completion_status == CompletionStatus.INDIVIDUAL_RESPONSE_REQUESTED:
self._progress[section_key] = Progress(
section_id=section_id,
list_item_id=list_item_id,
block_ids=[],
status=section_status,
status=completion_status,
)
self._is_dirty = True

return updated

def get_section_status(
self, section_id: str, list_item_id: Optional[str] = None
def get_section_or_list_item_status(
self, section_id: str, list_item_id: str | None = None
) -> str:
section_key = (section_id, list_item_id)
if section_key in self._progress:
return self._progress[section_key].status
progress_key = (section_id, list_item_id)
if progress_key in self._progress:
return self._progress[progress_key].status

return CompletionStatus.NOT_STARTED

def get_block_status(
self, *, block_id: str, section_id: str, list_item_id: str | None = None
) -> str:
section_blocks = self.get_completed_block_ids(
section_or_list_item_blocks = self.get_completed_block_ids(
section_id=section_id, list_item_id=list_item_id
)
if block_id in section_blocks:
if block_id in section_or_list_item_blocks:
petechd marked this conversation as resolved.
Show resolved Hide resolved
return CompletionStatus.COMPLETED

return CompletionStatus.NOT_STARTED

def get_completed_block_ids(
self, *, section_id: str, list_item_id: str | None = None
) -> list[str]:
section_key = (section_id, list_item_id)
if section_key in self._progress:
return self._progress[section_key].block_ids
progress_key = (section_id, list_item_id)
if progress_key in self._progress:
return self._progress[progress_key].block_ids

return []

Expand All @@ -171,12 +189,12 @@ def add_completed_location(self, location: Location) -> None:
if location.block_id not in completed_block_ids:
completed_block_ids.append(location.block_id) # type: ignore

section_key = (section_id, list_item_id)
progress_key = (section_id, list_item_id)

if section_key in self._progress:
self._progress[section_key].block_ids = completed_block_ids
if progress_key in self._progress:
self._progress[progress_key].block_ids = completed_block_ids
else:
self._progress[section_key] = Progress(
self._progress[progress_key] = Progress(
section_id=section_id,
list_item_id=list_item_id,
block_ids=completed_block_ids,
Expand All @@ -186,15 +204,15 @@ def add_completed_location(self, location: Location) -> None:
self._is_dirty = True

def remove_completed_location(self, location: Location) -> bool:
section_key = (location.section_id, location.list_item_id)
progress_key = (location.section_id, location.list_item_id)
if (
section_key in self._progress
and location.block_id in self._progress[section_key].block_ids
progress_key in self._progress
and location.block_id in self._progress[progress_key].block_ids
):
self._progress[section_key].block_ids.remove(location.block_id)
self._progress[progress_key].block_ids.remove(location.block_id)

if not self._progress[section_key].block_ids:
self._progress[section_key].status = CompletionStatus.IN_PROGRESS
if not self._progress[progress_key].block_ids:
self._progress[progress_key].status = CompletionStatus.IN_PROGRESS

self._is_dirty = True
return True
Expand All @@ -208,14 +226,14 @@ def remove_progress_for_list_item_id(self, list_item_id: str) -> None:
*Not efficient.*
"""

section_keys_to_delete = [
progress_keys_to_delete = [
(section_id, progress_list_item_id)
for section_id, progress_list_item_id in self._progress
if progress_list_item_id == list_item_id
]

for section_key in section_keys_to_delete:
del self._progress[section_key]
for progress_key in progress_keys_to_delete:
del self._progress[progress_key]

self._is_dirty = True

Expand All @@ -231,9 +249,9 @@ def clear(self) -> None:
self._is_dirty = True

def started_section_keys(
self, section_ids: Optional[Iterable[str]] = None
) -> list[SectionKeyType]:
return self.section_keys(
self, section_ids: Iterable[str] | None = None
) -> list[ProgressKeyType]:
return self.progress_keys(
statuses={CompletionStatus.COMPLETED, CompletionStatus.IN_PROGRESS},
section_ids=section_ids,
)
28 changes: 17 additions & 11 deletions app/jinja_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,6 @@ def map_summary_item_config(
calculated_question: Optional[dict[str, list]],
remove_link_text: str | None = None,
remove_link_aria_label: str | None = None,
icon: Optional[str] = None,
) -> list[Union[dict[str, list], SummaryRow]]:
rows: list[Union[dict[str, list], SummaryRow]] = []

Expand Down Expand Up @@ -613,7 +612,6 @@ def map_summary_item_config(
else:
list_collector_rows = map_list_collector_config(
list_items=block["list"]["list_items"],
icon=icon,
edit_link_text=edit_link_text,
edit_link_aria_label=edit_link_aria_label,
remove_link_text=remove_link_text,
Expand All @@ -639,17 +637,17 @@ def map_summary_item_config_processor() -> dict[str, Callable]:
# pylint: disable=too-many-locals
@blueprint.app_template_filter() # type: ignore
def map_list_collector_config(
list_items: list[dict[str, Union[str, int]]],
icon: Optional[str],
list_items: list[dict[str, str | int]],
render_icon: bool = False,
edit_link_text: str = "",
edit_link_aria_label: str = "",
remove_link_text: Optional[str] = None,
remove_link_aria_label: Optional[str] = None,
related_answers: Optional[dict] = None,
item_label: Optional[str] = None,
item_anchor: Optional[str] = None,
) -> list[Union[dict[str, list], SummaryRow]]:
rows: list[Union[dict[str, list], SummaryRow]] = []
remove_link_text: str | None = None,
remove_link_aria_label: str | None = None,
related_answers: dict | None = None,
item_label: str | None = None,
item_anchor: str | None = None,
) -> list[dict[str, list] | SummaryRow]:
rows: list[dict[str, list] | SummaryRow] = []

for index, list_item in enumerate(list_items, start=1):
item_name = list_item.get("item_title")
Expand Down Expand Up @@ -695,6 +693,14 @@ def map_list_collector_config(
}
)

icon = (
petechd marked this conversation as resolved.
Show resolved Hide resolved
"check"
if render_icon
and list_item.get("repeating_blocks")
and list_item.get("is_complete")
else None
)

row_item = {
"iconType": icon,
"actions": actions,
Expand Down
2 changes: 1 addition & 1 deletion app/questionnaire/path_finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ def _remove_current_blocks_answers_for_backwards_routing(
)

self.progress_store.remove_location_for_backwards_routing(this_location)
self.progress_store.update_section_status(
self.progress_store.update_section_or_list_item_completion_status(
CompletionStatus.IN_PROGRESS, this_location.section_id
)

Expand Down
19 changes: 18 additions & 1 deletion app/questionnaire/questionnaire_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"ListEditQuestion",
"ListRemoveQuestion",
"PrimaryPersonListAddOrEditQuestion",
"ListRepeatingBlock",
]

RELATIONSHIP_CHILDREN = ["UnrelatedQuestion"]
Expand Down Expand Up @@ -81,6 +82,7 @@ def __init__(
] = defaultdict(set)
self._language_code = language_code
self._questionnaire_json = questionnaire_json
self._repeating_blocks_by_id: dict[str, ImmutableDict] = {}
self.dynamic_answers_parent_block_ids: set[str] = set()

# The ordering here is required as they depend on each other.
Expand Down Expand Up @@ -290,6 +292,14 @@ def _get_blocks_by_id(self) -> dict[str, ImmutableDict]:
nested_block_id = nested_block["id"]
blocks[nested_block_id] = nested_block
self._parent_id_map[nested_block_id] = block_id
if repeating_blocks := block.get("repeating_blocks"):
for repeating_block in repeating_blocks:
repeating_block_id = repeating_block["id"]
blocks[repeating_block_id] = repeating_block
self._parent_id_map[repeating_block_id] = block_id
self._repeating_blocks_by_id[
katie-gardner marked this conversation as resolved.
Show resolved Hide resolved
repeating_block_id
] = repeating_block

return blocks

Expand Down Expand Up @@ -707,6 +717,9 @@ def get_first_block_id_for_section(self, section_id: str) -> str | None:
def get_blocks(self) -> Iterable[ImmutableDict]:
return self._blocks_by_id.values()

def get_repeating_blocks(self) -> dict[str, ImmutableDict]:
return self._repeating_blocks_by_id
katie-gardner marked this conversation as resolved.
Show resolved Hide resolved

def get_block(self, block_id: str) -> ImmutableDict | None:
return self._blocks_by_id.get(block_id)

Expand Down Expand Up @@ -988,7 +1001,11 @@ def _block_for_answer(self, answer_id: str) -> ImmutableDict | None:
parent_block_id = self._parent_id_map[block_id]
parent_block = self.get_block(parent_block_id)

if parent_block and parent_block["type"] == "ListCollector":
if (
parent_block
and parent_block["type"] == "ListCollector"
and block_id not in self._repeating_blocks_by_id
):
return parent_block

return self.get_block(block_id)
Expand Down
Loading