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

Implement "progress" value source #1044

Merged
merged 105 commits into from
May 26, 2023
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
105 commits
Select commit Hold shift + click to select a range
67f0c7f
Add support for progress value source
ONS-Guilhem-Forey Mar 31, 2023
80ef59a
Revert schema validator branch for CI
ONS-Guilhem-Forey Mar 31, 2023
5b0044d
Add progress functional test
berroar Apr 3, 2023
a082b29
Update test
berroar Apr 3, 2023
af7e9a4
Fix conflicts
berroar Apr 3, 2023
033cee8
Repeating functional test
berroar Apr 3, 2023
c1ac3fc
Add repeating calculated summary tests
berroar Apr 3, 2023
6b14ad9
Additonal calculated summary test
berroar Apr 4, 2023
d64ffaa
Add section enabled test
berroar Apr 4, 2023
64e3a6d
Add rule evaluator test
berroar Apr 5, 2023
04eb72a
Check if section is repeating, fix bug and add tests
berroar Apr 5, 2023
f6ee3dc
Merge branch 'main' into feat/questionnaire-progress-route
berroar Apr 5, 2023
fb4ac49
Flip dependencies
berroar Apr 6, 2023
4c9dec9
Merge branch 'main' into feat/questionnaire-progress-route
berroar Apr 6, 2023
0d5986b
PR comments
berroar Apr 12, 2023
1df4bd9
Update app/questionnaire/questionnaire_store_updater.py
berroar Apr 12, 2023
7a04f75
Lint
berroar Apr 12, 2023
fb2a621
Update app/questionnaire/questionnaire_schema.py
berroar Apr 12, 2023
9eb233d
Add type ignore
berroar Apr 12, 2023
6ad9dbd
Update progress section dependency lookup
berroar Apr 12, 2023
38d9646
Update block dependency capture
berroar Apr 13, 2023
de25000
Merge branch 'main' into feat/questionnaire-progress-route
berroar Apr 13, 2023
f33ea89
Refactor block dependencies:
berroar Apr 14, 2023
38942ae
Fix tests
berroar Apr 14, 2023
a4951d3
Type hints
berroar Apr 17, 2023
e5d83e3
Update schema
berroar Apr 17, 2023
2db799d
Update test schema
berroar Apr 17, 2023
addc69e
Merge branch 'main' into feat/questionnaire-progress-route
berroar Apr 19, 2023
2f39c15
Update app/questionnaire/questionnaire_schema.py
berroar Apr 20, 2023
9f625f5
Fix type hints and PR comment
berroar Apr 20, 2023
f949475
Use list instead of sets for progress
berroar Apr 21, 2023
301bfff
Add tests
berroar Apr 24, 2023
6e68a48
Add test schema
berroar Apr 24, 2023
47b5abf
Fix test schema
berroar Apr 24, 2023
9a50aec
Fix ids
berroar Apr 24, 2023
1ed2026
Address PR comments
berroar Apr 25, 2023
8001243
Merge branch 'main' into feat/questionnaire-progress-route
berroar Apr 25, 2023
5ea3ea1
Move call
berroar Apr 25, 2023
779448f
Move update progress call
berroar Apr 27, 2023
a7b37dc
Fix conflicts
berroar Apr 27, 2023
16f1b02
Initial OrderedSet changes
berroar May 2, 2023
7658e2d
Update tests and type hints
berroar May 3, 2023
9d4a2f2
Translations
berroar May 3, 2023
122aa5e
Pin dependencies
berroar May 3, 2023
e7b584b
Merge branch 'main' into feat/questionnaire-progress-route
berroar May 3, 2023
39e2bd2
Fix type hints from dependency update
berroar May 3, 2023
bc3a38d
Merge branch 'main' into feat/questionnaire-progress-route
berroar May 3, 2023
3e4e289
Use ordered set in updater
berroar May 4, 2023
788edcb
Merge branch 'feat/questionnaire-progress-route' of github.com:ONSdig…
berroar May 4, 2023
7fc36b1
Type hints
berroar May 4, 2023
c6119ae
Add docstrings
berroar May 4, 2023
7600ab2
PR comments
berroar May 4, 2023
9343725
Use ordered dict
berroar May 4, 2023
4339d48
Use default dict for progress methods
berroar May 4, 2023
1e57c38
Refactor questionnaire store updater
berroar May 4, 2023
7fa9e51
PR comments
berroar May 4, 2023
66f37c1
Refactor value source resolver
berroar May 4, 2023
e655b63
Missing type alias
berroar May 4, 2023
67a937a
Fix chained dependency bug
berroar May 5, 2023
84d0e0a
Merge branch 'main' into feat/questionnaire-progress-route
berroar May 5, 2023
d114b08
Add cross section block dependency journey
berroar May 9, 2023
b89a2a9
Merge branch 'feat/questionnaire-progress-route' of github.com:ONSdig…
berroar May 11, 2023
bd69d91
Merge branch 'main' into feat/questionnaire-progress-route
berroar May 11, 2023
9dd812f
Move capture of nested dependencies
berroar May 11, 2023
d3cf02c
Update tests and fix calc summary issue
berroar May 11, 2023
8ed037d
Merge branch 'feat/questionnaire-progress-route' of github.com:ONSdig…
berroar May 11, 2023
0ce0df9
Fix tests
berroar May 12, 2023
905ed5c
Rever test
berroar May 15, 2023
a2e0836
Fix merge conflicts
berroar May 15, 2023
3c923c0
Revert change to test
berroar May 15, 2023
8dbf40c
PR comments and fix tests
berroar May 15, 2023
59d1536
Fix conflict issue
berroar May 16, 2023
b6b63bc
Remove method
berroar May 16, 2023
1c9cf8f
Enforce keyword args
berroar May 16, 2023
1c4da26
Fix logic
berroar May 16, 2023
d7202ce
Add tests
berroar May 16, 2023
96b1e49
Update test
berroar May 17, 2023
df52f9c
Pass dependent section
berroar May 17, 2023
7449ef0
PR comments
berroar May 17, 2023
c2f764a
PR suggestions
berroar May 18, 2023
62bcfe7
Fix for cross section block dependencies
berroar May 18, 2023
0d1e632
Merge branch 'main' into feat/questionnaire-progress-route
berroar May 18, 2023
608f817
Merge branch 'main' into feat/questionnaire-progress-route
berroar May 19, 2023
672697a
PR comments
berroar May 19, 2023
1c2289b
Merge branch 'feat/questionnaire-progress-route' of github.com:ONSdig…
berroar May 19, 2023
b1925c1
Type hint
berroar May 19, 2023
d712a72
Refactor method
berroar May 19, 2023
1cda2eb
Add comment to test
berroar May 19, 2023
a0e3359
Use ImmutableDicts
berroar May 19, 2023
bdd237b
Update if
berroar May 22, 2023
16522fa
Update test schema
berroar May 22, 2023
36c0dd8
Update if
berroar May 22, 2023
b35dede
Update dependency capture
berroar May 22, 2023
dcb3057
Value source resolver comments
berroar May 22, 2023
72970b3
Add comment
berroar May 22, 2023
49db1a3
Update schemas/test/en/test_progress_value_source_blocks.json
berroar May 23, 2023
189ae72
Update cross section schema
berroar May 23, 2023
5eddbbc
Fix validation
berroar May 23, 2023
19b6123
Add intro to test schema
berroar May 23, 2023
88db927
Remove whitespace
berroar May 23, 2023
476615b
Merge branch 'main' into feat/questionnaire-progress-route
berroar May 23, 2023
3558dc8
Update description
berroar May 25, 2023
7206d31
Merge branch 'main' into feat/questionnaire-progress-route
berroar May 25, 2023
e92244c
Merge branch 'main' into feat/questionnaire-progress-route
berroar May 26, 2023
283e2b1
Merge branch 'main' into feat/questionnaire-progress-route
berroar May 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
17 changes: 15 additions & 2 deletions app/data_models/progress_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,19 @@ def get_section_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_id=section_id, list_item_id=list_item_id
)
if block_id in section_blocks:
return CompletionStatus.COMPLETED

return CompletionStatus.NOT_STARTED

def get_completed_block_ids(
self, section_id: str, list_item_id: Optional[str] = None
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:
Expand All @@ -151,7 +162,9 @@ def add_completed_location(self, location: Location) -> None:
section_id = location.section_id
list_item_id = location.list_item_id

completed_block_ids = self.get_completed_block_ids(section_id, list_item_id)
completed_block_ids = self.get_completed_block_ids(
section_id=section_id, list_item_id=list_item_id
)

if location.block_id not in completed_block_ids:
completed_block_ids.append(location.block_id) # type: ignore
Expand Down
3 changes: 2 additions & 1 deletion app/publisher/publisher.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import google.auth
from google.cloud.pubsub import PublisherClient
from google.cloud.pubsub_v1 import publisher
from google.cloud.pubsub_v1.futures import Future
from structlog import get_logger

Expand All @@ -21,7 +22,7 @@ def __init__(self):
self._client = PublisherClient()
_, self._project_id = google.auth.default()

def _publish(self, topic_id, message):
def _publish(self, topic_id, message) -> "publisher.futures.Future":
petechd marked this conversation as resolved.
Show resolved Hide resolved
logger.info("publishing message", topic_id=topic_id)
# pylint: disable=no-member
topic_path = self._client.topic_path(self._project_id, topic_id)
Expand Down
12 changes: 7 additions & 5 deletions app/questionnaire/path_finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def routing_path(

if section:
when_rules_block_dependencies = self.get_when_rules_block_dependencies(
section["id"]
section_id
)
blocks = self._get_not_skipped_blocks_in_section(
current_location,
Expand All @@ -64,11 +64,13 @@ def get_when_rules_block_dependencies(self, section_id: str) -> list[str]:
"""NB: At present when rules block dependencies does not fully support repeating sections.
It is supported when the section is dependent i.e the current section is repeating and building the routing path for sections that are not,
It isn't supported if it needs to build the path for repeating sections"""
dependencies_for_section = (
self.schema.get_all_when_rules_section_dependencies_for_section(section_id)
)

return [
block_id
for dependent_section in self.schema.when_rules_section_dependencies_by_section.get(
section_id, {}
)
for dependent_section in dependencies_for_section
for block_id in self.routing_path(dependent_section)
if (dependent_section, None) in self.progress_store.started_section_keys()
]
Expand Down Expand Up @@ -191,9 +193,9 @@ def _evaluate_routing_rules(
self.list_store,
self.metadata,
self.response_metadata,
progress_store=self.progress_store,
location=this_location,
routing_path_block_ids=routing_path_block_ids,
progress_store=self.progress_store,
)
for rule in routing_rules:
rule_valid = (
Expand Down
202 changes: 179 additions & 23 deletions app/questionnaire/questionnaire_schema.py
kylelawsonAND marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from app.forms import error_messages
from app.questionnaire.rules.operator import OPERATION_MAPPING
from app.utilities.make_immutable import make_immutable
from app.utilities.mappings import get_mappings_with_key
from app.utilities.mappings import get_flattened_mapping_values, get_mappings_with_key
MebinAbraham marked this conversation as resolved.
Show resolved Hide resolved

DEFAULT_LANGUAGE_CODE = "en"

Expand Down Expand Up @@ -58,6 +58,12 @@ def __init__(
set
)
self._when_rules_section_dependencies_by_section: dict[str, set[str]] = {}
self._when_rules_section_dependencies_by_section_for_progress_value_source: dict[
str, list[str]
] = {}
self._when_rules_block_dependencies_by_section_for_progress_value_source: dict[
str, dict[str, list[str]]
] = {}
self.calculated_summary_section_dependencies_by_block: dict[
str, dict[str, set[str]]
] = defaultdict(lambda: defaultdict(set))
Expand Down Expand Up @@ -89,6 +95,22 @@ def when_rules_section_dependencies_by_section(
) -> ImmutableDict[str, set[str]]:
return ImmutableDict(self._when_rules_section_dependencies_by_section)

@cached_property
def when_rules_section_dependencies_by_section_for_progress_value_source(
self,
) -> ImmutableDict[str, list[str]]:
return ImmutableDict(
self._when_rules_section_dependencies_by_section_for_progress_value_source
)

@cached_property
katie-gardner marked this conversation as resolved.
Show resolved Hide resolved
def when_rules_block_dependencies_by_section_for_progress_value_source(
self,
) -> ImmutableDict[str, dict[str, list[str]]]:
return ImmutableDict(
self._when_rules_block_dependencies_by_section_for_progress_value_source
)

@cached_property
def when_rules_section_dependencies_by_answer(self) -> ImmutableDict[str, set[str]]:
return ImmutableDict(self._when_rules_section_dependencies_by_answer)
Expand Down Expand Up @@ -166,6 +188,28 @@ def is_view_submitted_response_enabled(self) -> bool:
is_enabled: bool = schema.get("view_response", False)
return is_enabled

def get_all_when_rules_section_dependencies_for_section(
self, section_id: str
) -> set[str]:
section_dependencies = self.when_rules_section_dependencies_by_section_for_progress_value_source.get(
section_id, []
)
progress_section_dependencies = set(section_dependencies)

all_section_dependencies = self.when_rules_section_dependencies_by_section.get(
section_id, set()
).union(progress_section_dependencies)

block_dependencies: dict = (
self.when_rules_block_dependencies_by_section_for_progress_value_source.get(
section_id, {}
)
)

section_ids = get_flattened_mapping_values(block_dependencies)

return all_section_dependencies.union(section_ids)

def _get_sections_by_id(self) -> dict[str, ImmutableDict]:
return {section["id"]: section for section in self.json.get("sections", [])}

Expand Down Expand Up @@ -839,17 +883,113 @@ def _populate_when_rules_section_dependencies(self) -> None:
when_rules = self.get_values_for_key(section, "when")
rules: list = list(when_rules)

if rules_section_dependencies := self._get_rules_section_dependencies(
section["id"], rules
):
(
rules_section_dependencies,
rule_section_dependencies_for_progress_value_source,
rule_block_dependencies_for_progress_value_source,
) = self._get_rules_section_dependencies(section["id"], rules)

if rules_section_dependencies:
self._when_rules_section_dependencies_by_section[
section["id"]
] = rules_section_dependencies

if rule_section_dependencies_for_progress_value_source:
MebinAbraham marked this conversation as resolved.
Show resolved Hide resolved
for key in rule_section_dependencies_for_progress_value_source:
if (
key
in self._when_rules_section_dependencies_by_section_for_progress_value_source
):
self._when_rules_section_dependencies_by_section_for_progress_value_source[
key
].append(
*rule_section_dependencies_for_progress_value_source[key]
)
else:
self._when_rules_section_dependencies_by_section_for_progress_value_source[
key
] = rule_section_dependencies_for_progress_value_source[
key
]

if rule_block_dependencies_for_progress_value_source:
MebinAbraham marked this conversation as resolved.
Show resolved Hide resolved
self._populate_block_dependencies_for_progress_value_source(
rule_block_dependencies_for_progress_value_source
)

def _populate_block_dependencies_for_progress_value_source(
self,
rule_block_dependencies_for_progress_value_source: dict[
str, dict[str, list[str]]
],
) -> None:
dependencies = (
self._when_rules_block_dependencies_by_section_for_progress_value_source
)

for (
dependent_section,
section_dependencies_by_block,
) in rule_block_dependencies_for_progress_value_source.items():
if dependent_section in dependencies:
for block_id, section_ids in section_dependencies_by_block.items():
if block_id in dependencies[dependent_section]:
dependencies[dependent_section][block_id].extend(section_ids)
else:
katie-gardner marked this conversation as resolved.
Show resolved Hide resolved
dependencies[
dependent_section
] = rule_block_dependencies_for_progress_value_source[dependent_section]

def _get_section_and_block_ids_dependencies_for_progress_source_and_answer_ids_from_rule(
self, current_section_id: str, rule: Mapping
) -> tuple[set[str], dict[str, dict[str, list[str] | dict[str, list[str]]]]]:
answer_id_list: set[str] = set()
dependencies_ids_for_progress_value_source: dict[
str, dict[str, list[str] | dict[str, list[str]]]
] = {
"sections": {},
"blocks": {},
}
identifier: str | None = rule.get("identifier")
source: str | None = rule.get("source")
selector: str | None = rule.get("selector")

if source == "answers" and identifier:
answer_id_list.add(identifier)
elif source == "calculated_summary" and identifier:
MebinAbraham marked this conversation as resolved.
Show resolved Hide resolved
calculated_summary_block = self.get_block(identifier)
# Type Ignore: Calculated summary block will exist at this point
calculated_summary_answer_ids = get_calculated_summary_answer_ids(
calculated_summary_block # type: ignore
petechd marked this conversation as resolved.
Show resolved Hide resolved
)
answer_id_list.update(calculated_summary_answer_ids)
elif source == "progress" and identifier and selector:
if selector == "section":
katie-gardner marked this conversation as resolved.
Show resolved Hide resolved
if identifier != current_section_id:
dependencies_ids_for_progress_value_source["sections"][
identifier
] = [current_section_id]
elif selector == "block" and (
section_id := self.get_section_id_for_block_id(identifier)
):
# Type ignore: The identifier key will return a list
if section_id != current_section_id:
dependencies_ids_for_progress_value_source["blocks"][section_id] = {
identifier: []
}
dependencies_ids_for_progress_value_source["blocks"][section_id][identifier].append(current_section_id) # type: ignore

return answer_id_list, dependencies_ids_for_progress_value_source

def _get_rules_section_dependencies(
self, current_section_id: str, rules: Union[Mapping, Sequence]
) -> set[str]:
rules_section_dependencies: set[str] = set()
self, current_section_id: str, rules: Mapping | Sequence
) -> tuple[set[str], dict[str, list[str]], dict[str, dict[str, list[str]]]]:
"""
Returns a set of sections ids that the current sections depends on.
"""
section_dependencies: set[str] = set()
section_dependencies_for_progress_value_source: dict = {}
block_dependencies_for_progress_value_source: dict = {}

if isinstance(rules, Mapping) and QuestionnaireSchema.has_operator(rules):
rules = self.get_operands(rules)
Expand All @@ -858,18 +998,21 @@ def _get_rules_section_dependencies(
if not isinstance(rule, Mapping):
continue

answer_id_list: list = []
identifier: Optional[str] = rule.get("identifier")
source: Optional[str] = rule.get("source")

if source == "answers" and identifier:
answer_id_list.append(identifier)
elif source == "calculated_summary" and identifier:
calculated_summary_block = self.get_block(identifier)
calculated_summary_answer_ids = get_calculated_summary_answer_ids(
calculated_summary_block # type: ignore
)
answer_id_list.extend(iter(calculated_summary_answer_ids))
[
answer_id_list,
dependencies_for_progress_value_source,
] = self._get_section_and_block_ids_dependencies_for_progress_source_and_answer_ids_from_rule(
current_section_id, rule
)

section_dependencies_for_progress_value_source |= (
dependencies_for_progress_value_source["sections"]
)
block_dependencies_for_progress_value_source |= (
dependencies_for_progress_value_source["blocks"]
)

# Type Ignore: Added to this method as the block will exist at this point
for answer_id in answer_id_list:
block = self.get_block_for_answer_id(answer_id) # type: ignore
section_id = self.get_section_id_for_block_id(block["id"]) # type: ignore
Expand All @@ -878,14 +1021,27 @@ def _get_rules_section_dependencies(
self._when_rules_section_dependencies_by_answer[answer_id].add(
current_section_id
)
rules_section_dependencies.add(section_id) # type: ignore
section_dependencies.add(section_id) # type: ignore
berroar marked this conversation as resolved.
Show resolved Hide resolved

if QuestionnaireSchema.has_operator(rule):
rules_section_dependencies.update(
self._get_rules_section_dependencies(current_section_id, rule)
(
nested_section_dependencies,
nested_section_dependencies_for_progress_value_source,
nested_block_dependencies_for_progress_value_source,
) = self._get_rules_section_dependencies(current_section_id, rule)
section_dependencies.update(nested_section_dependencies)
section_dependencies_for_progress_value_source |= (
nested_section_dependencies_for_progress_value_source
)
block_dependencies_for_progress_value_source |= (
nested_block_dependencies_for_progress_value_source
)

return rules_section_dependencies
return (
section_dependencies,
section_dependencies_for_progress_value_source,
block_dependencies_for_progress_value_source,
)

def _populate_calculated_summary_section_dependencies(self) -> None:
for section in self.get_sections():
Expand Down
Loading