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 112 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
208 changes: 138 additions & 70 deletions app/data_models/progress_store.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from dataclasses import astuple, dataclass
from typing import Iterable, Iterator, MutableMapping, Optional
from typing import Iterable, Iterator, MutableMapping

from app.data_models.progress import Progress, ProgressDictType
from app.questionnaire.location import Location

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


@dataclass
Expand All @@ -20,34 +20,52 @@ 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_repeating_blocks: 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 & Repeating Sections,
and their completed blocks, as well as Repeating Blocks for List Items.
- Standard Sections are keyed by Section ID, and a None List Item ID
- Repeating Sections (dynamic Sections created for List Items that have been added using a List Collector)
are keyed by their Section ID, and the List Item ID of the item it is the section for.
- Repeating Blocks for List Items are keyed by the Section ID for the Section in which their List Collector
appears, and the List Item ID. Repeating Blocks progress is only tracked if the List Collector
that created the List Item has Repeating Blocks, and progress of the Repeating Blocks for a List Item
indicates if all required Repeating Blocks from the List Collector have been completed for the List Item.
Args:
in_progress_sections: A list of hierarchical dict containing the section status and completed blocks
in_progress_sections_and_repeating_blocks: A list of hierarchical dict containing the completion status
and completed blocks of Sections, Repeating Sections and List Items
"""
self._is_dirty: bool = False
self._is_routing_backwards: bool = False
self._progress: MutableMapping[SectionKeyType, Progress] = self._build_map(
in_progress_sections or []
)
self._section_and_repeating_blocks_progress: MutableMapping[
ProgressKeyType, Progress
] = self._build_map(in_progress_sections_and_repeating_blocks or [])

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

@staticmethod
def _build_map(section_progress_list: Iterable[ProgressDictType]) -> MutableMapping:
def _build_map(
section_and_repeating_blocks_progress_list: Iterable[ProgressDictType],
) -> MutableMapping:
"""
Builds the progress_store's data structure from a list of progress dictionaries.
Builds the ProgressStore'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 +80,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 section_and_repeating_blocks_progress_list
}

@property
Expand All @@ -76,91 +94,130 @@ 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_repeating_blocks_progress_complete(
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.section_and_repeating_blocks_progress_keys(
statuses={
CompletionStatus.COMPLETED,
CompletionStatus.INDIVIDUAL_RESPONSE_REQUESTED,
}
)

def section_keys(
def section_and_repeating_blocks_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]:
"""
Return the Keys of the Section and Repeating Blocks progresses stored in this ProgressStore.
"""
if not statuses:
statuses = {*CompletionStatus()}

section_keys = [
progress_keys = [
section_key
for section_key, section_progress in self._progress.items()
for section_key, section_progress in self._section_and_repeating_blocks_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_repeating_blocks_progress_completion_status(
self,
completion_status: str,
section_id: str,
list_item_id: str | None = None,
) -> bool:
"""
Updates the completion status of the section or repeating blocks for a 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 section_key in self._section_and_repeating_blocks_progress:
if (
self._section_and_repeating_blocks_progress[section_key].status
!= completion_status
):
updated = True
self._progress[section_key].status = section_status
self._section_and_repeating_blocks_progress[
section_key
].status = completion_status
self._is_dirty = True

elif section_status == CompletionStatus.INDIVIDUAL_RESPONSE_REQUESTED:
self._progress[section_key] = Progress(
elif completion_status == CompletionStatus.INDIVIDUAL_RESPONSE_REQUESTED:
self._section_and_repeating_blocks_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_repeating_blocks_progress_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
"""
Return the CompletionStatus of the Section or Repeating Blocks for a list item,
specified by the given section_id and list_item_id.
Returns NOT_STARTED if the progress does not exist
"""
progress_key = (section_id, list_item_id)
if progress_key in self._section_and_repeating_blocks_progress:
return self._section_and_repeating_blocks_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(
"""
Return the completion status of the block specified by the given block_id,
if it is part of the progress of the given Section or Repeating Blocks for list item
specified by the given section_id or list_item_id
"""
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 blocks:
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
"""
Return the block ids recorded as part of the progress for the Section or Repeating Blocks
for list item specified by the given section_id and list_item_id
"""
progress_key = (section_id, list_item_id)
if progress_key in self._section_and_repeating_blocks_progress:
return self._section_and_repeating_blocks_progress[progress_key].block_ids

return []

def add_completed_location(self, location: Location) -> None:
"""
Adds the block from the given Location, to the progress specified by the
section id and list item id within the Location.
"""
section_id = location.section_id
list_item_id = location.list_item_id

Expand All @@ -171,12 +228,14 @@ 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._section_and_repeating_blocks_progress:
self._section_and_repeating_blocks_progress[
progress_key
].block_ids = completed_block_ids
else:
self._progress[section_key] = Progress(
self._section_and_repeating_blocks_progress[progress_key] = Progress(
section_id=section_id,
list_item_id=list_item_id,
block_ids=completed_block_ids,
Expand All @@ -186,15 +245,24 @@ 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)
"""
Removes the block in the given Location, from the progress specified by the
section id and list item id within the Location if it exists in the store.
"""
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._section_and_repeating_blocks_progress
and location.block_id
in self._section_and_repeating_blocks_progress[progress_key].block_ids
):
self._progress[section_key].block_ids.remove(location.block_id)
self._section_and_repeating_blocks_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._section_and_repeating_blocks_progress[progress_key].block_ids:
self._section_and_repeating_blocks_progress[
progress_key
].status = CompletionStatus.IN_PROGRESS

self._is_dirty = True
return True
Expand All @@ -208,32 +276,32 @@ 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
for section_id, progress_list_item_id in self._section_and_repeating_blocks_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._section_and_repeating_blocks_progress[progress_key]

self._is_dirty = True

def serialize(self) -> list[Progress]:
return list(self._progress.values())
return list(self._section_and_repeating_blocks_progress.values())

def remove_location_for_backwards_routing(self, location: Location) -> None:
self.remove_completed_location(location=location)
self._is_routing_backwards = True

def clear(self) -> None:
self._progress.clear()
self._section_and_repeating_blocks_progress.clear()
self._is_dirty = True

def started_section_keys(
self, section_ids: Optional[Iterable[str]] = None
) -> list[SectionKeyType]:
return self.section_keys(
def started_section_and_repeating_blocks_progress_keys(
self, section_ids: Iterable[str] | None = None
) -> list[ProgressKeyType]:
return self.section_and_repeating_blocks_progress_keys(
statuses={CompletionStatus.COMPLETED, CompletionStatus.IN_PROGRESS},
section_ids=section_ids,
)
Loading