From 08b030406a1312fa3ed40463645febad749d07b8 Mon Sep 17 00:00:00 2001
From: petechd <53475968+petechd@users.noreply.github.com>
Date: Thu, 18 May 2023 10:52:36 +0100
Subject: [PATCH 1/9] Make response expiry date mandatory (#1104)
---
app/data_models/metadata_proxy.py | 2 +-
app/data_models/questionnaire_store.py | 6 ++----
app/utilities/metadata_parser.py | 2 +-
app/utilities/metadata_parser_v2.py | 2 +-
tests/app/data_model/conftest.py | 6 +++++-
tests/app/data_model/test_metadata_proxy.py | 6 ++++++
tests/app/parser/conftest.py | 10 ++++++++++
tests/app/submitter/conftest.py | 3 +++
tests/app/views/handlers/conftest.py | 6 ++++++
.../test_individual_response_fulfilment_request.py | 2 ++
.../handlers/test_question_with_dynamic_answers.py | 2 ++
tests/functional/jwt_helper.js | 5 +++++
tests/integration/create_token.py | 2 ++
tests/integration/routes/test_errors.py | 2 ++
tests/integration/routes/test_jwt_authentication.py | 2 ++
tests/integration/test_flush_data.py | 2 ++
16 files changed, 52 insertions(+), 8 deletions(-)
diff --git a/app/data_models/metadata_proxy.py b/app/data_models/metadata_proxy.py
index 3eed9a85f5..28e12dee3d 100644
--- a/app/data_models/metadata_proxy.py
+++ b/app/data_models/metadata_proxy.py
@@ -49,11 +49,11 @@ class MetadataProxy:
case_id: str
collection_exercise_sid: str
response_id: str
+ response_expires_at: datetime
survey_metadata: Optional[SurveyMetadata] = None
schema_url: Optional[str] = None
schema_name: Optional[str] = None
language_code: Optional[str] = None
- response_expires_at: Optional[datetime] = None
channel: Optional[str] = None
region_code: Optional[str] = None
version: Optional[AuthPayloadVersion] = None
diff --git a/app/data_models/questionnaire_store.py b/app/data_models/questionnaire_store.py
index f4352112c7..1848a3d4b2 100644
--- a/app/data_models/questionnaire_store.py
+++ b/app/data_models/questionnaire_store.py
@@ -93,12 +93,10 @@ def save(self) -> None:
collection_exercise_sid = (
self.collection_exercise_sid or self._metadata["collection_exercise_sid"]
)
- response_expires_at = self._metadata.get("response_expires_at")
+ response_expires_at = self._metadata["response_expires_at"]
self._storage.save(
data=data,
collection_exercise_sid=collection_exercise_sid,
submitted_at=self.submitted_at,
- expires_at=parse_iso_8601_datetime(response_expires_at)
- if response_expires_at
- else None,
+ expires_at=parse_iso_8601_datetime(response_expires_at),
)
diff --git a/app/utilities/metadata_parser.py b/app/utilities/metadata_parser.py
index 537adae896..f0d2681aa1 100644
--- a/app/utilities/metadata_parser.py
+++ b/app/utilities/metadata_parser.py
@@ -65,7 +65,7 @@ class RunnerMetadataSchema(Schema, StripWhitespaceMixin):
) # type:ignore
case_type = VALIDATORS["string"](required=False) # type:ignore
response_expires_at = VALIDATORS["iso_8601_date_string"](
- required=False,
+ required=True,
validate=lambda x: parse_iso_8601_datetime(x) > datetime.now(tz=timezone.utc),
) # type:ignore
diff --git a/app/utilities/metadata_parser_v2.py b/app/utilities/metadata_parser_v2.py
index 366f997435..51fa8eaa8d 100644
--- a/app/utilities/metadata_parser_v2.py
+++ b/app/utilities/metadata_parser_v2.py
@@ -89,7 +89,7 @@ class RunnerMetadataSchema(Schema, StripWhitespaceMixin):
required=False, validate=validate.Length(min=1)
) # type:ignore
response_expires_at = VALIDATORS["iso_8601_date_string"](
- required=False,
+ required=True,
validate=lambda x: parse_iso_8601_datetime(x) > datetime.now(tz=timezone.utc),
) # type:ignore
region_code = VALIDATORS["string"](
diff --git a/tests/app/data_model/conftest.py b/tests/app/data_model/conftest.py
index 50905ab2f7..4d71f6623d 100644
--- a/tests/app/data_model/conftest.py
+++ b/tests/app/data_model/conftest.py
@@ -6,6 +6,7 @@
from app.data_models.progress_store import CompletionStatus
from app.data_models.session_store import SessionStore
from app.storage import storage_encryption
+from tests.app.parser.conftest import get_response_expires_at
@pytest.fixture
@@ -81,7 +82,10 @@ def store_to_serialize(answer_store):
@pytest.fixture
def basic_input():
return {
- "METADATA": {"test": True},
+ "METADATA": {
+ "test": True,
+ "response_expires_at": get_response_expires_at(),
+ },
"ANSWERS": [{"answer_id": "test", "value": "test"}],
"LISTS": [],
"PROGRESS": [
diff --git a/tests/app/data_model/test_metadata_proxy.py b/tests/app/data_model/test_metadata_proxy.py
index 73804f5860..79dce605c7 100644
--- a/tests/app/data_model/test_metadata_proxy.py
+++ b/tests/app/data_model/test_metadata_proxy.py
@@ -12,6 +12,7 @@
"tx_id": "tx_id",
"collection_exercise_sid": "collection_exercise_sid",
"case_id": "case_id",
+ "response_expires_at": "2023-04-24T10:46:32+00:00",
}
METADATA_V2 = {
@@ -22,6 +23,7 @@
"tx_id": "tx_id",
"collection_exercise_sid": "collection_exercise_sid",
"case_id": "case_id",
+ "response_expires_at": "2023-04-24T10:46:32+00:00",
"survey_metadata": {
"data": {
"ru_ref": "432423423423",
@@ -46,6 +48,10 @@
MetadataProxy.from_dict(METADATA_V2)["schema_name"],
METADATA_V2["schema_name"],
),
+ (
+ MetadataProxy.from_dict(METADATA_V2)["response_expires_at"],
+ METADATA_V2["response_expires_at"],
+ ),
(MetadataProxy.from_dict(METADATA_V1)["non_existing"], None),
(MetadataProxy.from_dict(METADATA_V2)["non_existing"], None),
),
diff --git a/tests/app/parser/conftest.py b/tests/app/parser/conftest.py
index d408e09103..ab7bbc6e26 100644
--- a/tests/app/parser/conftest.py
+++ b/tests/app/parser/conftest.py
@@ -1,5 +1,6 @@
# pylint: disable=redefined-outer-name
import uuid
+from datetime import datetime, timedelta, timezone
import pytest
@@ -37,6 +38,7 @@ def fake_metadata_runner():
"response_id": str(uuid.uuid4()),
"account_service_url": "https://ras.ons.gov.uk",
"case_id": str(uuid.uuid4()),
+ "response_expires_at": get_response_expires_at(),
}
@@ -48,6 +50,7 @@ def fake_business_metadata_runner():
metadata["eq_id"] = "mbs"
metadata["form_type"] = "0253"
+ metadata["response_expires_at"] = get_response_expires_at()
return metadata
@@ -67,6 +70,7 @@ def fake_metadata_full():
"return_by": "2016-07-07",
"case_ref": "1000000000000001",
"case_id": str(uuid.uuid4()),
+ "response_expires_at": get_response_expires_at(),
}
return dict(fake_metadata_runner(), **fake_questionnaire_claims)
@@ -84,6 +88,7 @@ def fake_metadata_runner_v2():
"case_id": str(uuid.uuid4()),
"version": AuthPayloadVersion.V2.value,
"survey_metadata": {"data": {"key": "value"}},
+ "response_expires_at": get_response_expires_at(),
}
@@ -103,6 +108,7 @@ def fake_metadata_full_v2_business():
"case_ref": "1000000000000001",
"ru_ref": "123456789",
"form_type": "I",
+ "response_expires_at": get_response_expires_at(),
}
metadata = fake_metadata_runner_v2()
@@ -140,3 +146,7 @@ def fake_questionnaire_metadata_requirements_full():
{"name": "ref_p_end_date", "type": "string"},
{"name": "account_service_url", "type": "url", "optional": True},
]
+
+
+def get_response_expires_at() -> str:
+ return (datetime.now(tz=timezone.utc) + timedelta(days=1)).isoformat()
diff --git a/tests/app/submitter/conftest.py b/tests/app/submitter/conftest.py
index 67a15ef3e1..c92d9da656 100644
--- a/tests/app/submitter/conftest.py
+++ b/tests/app/submitter/conftest.py
@@ -15,6 +15,7 @@
from app.questionnaire.questionnaire_schema import QuestionnaireSchema
from app.settings import ACCOUNT_SERVICE_BASE_URL_SOCIAL
from app.submitter import RabbitMQSubmitter
+from tests.app.parser.conftest import get_response_expires_at
METADATA_V1 = MetadataProxy.from_dict(
{
@@ -39,6 +40,7 @@
"display_address": "68 Abingdon Road, Goathill",
"case_ref": "1000000000000001",
"jti": str(uuid.uuid4()),
+ "response_expires_at": get_response_expires_at(),
}
)
@@ -69,6 +71,7 @@
"region_code": "GB-ENG",
"channel": "RH",
"jti": str(uuid.uuid4()),
+ "response_expires_at": get_response_expires_at(),
}
)
diff --git a/tests/app/views/handlers/conftest.py b/tests/app/views/handlers/conftest.py
index 003f9e973a..6fde601054 100644
--- a/tests/app/views/handlers/conftest.py
+++ b/tests/app/views/handlers/conftest.py
@@ -11,6 +11,7 @@
from app.data_models.session_data import SessionData
from app.data_models.session_store import SessionStore
from app.questionnaire import QuestionnaireSchema
+from tests.app.parser.conftest import get_response_expires_at
time_to_freeze = datetime.now(timezone.utc).replace(second=0, microsecond=0)
tx_id = str(uuid.uuid4())
@@ -39,6 +40,7 @@
channel = "H"
case_ref = "1000000000000001"
region_code = "GB_WLS"
+response_expires_at = get_response_expires_at()
@pytest.fixture
@@ -126,6 +128,7 @@ def metadata():
"region_code": region_code,
"case_id": case_id,
"language_code": language_code,
+ "response_expires_at": response_expires_at,
}
)
@@ -143,6 +146,7 @@ def metadata_v2():
"channel": channel,
"region_code": region_code,
"account_service_url": "account_service_url",
+ "response_expires_at": get_response_expires_at(),
"survey_metadata": {
"data": {
"period_id": period_id,
@@ -210,6 +214,7 @@ def mock_questionnaire_store(mocker):
"schema_name": schema_name,
"account_service_url": "account_service_url",
"response_id": "response_id",
+ "response_expires_at": get_response_expires_at(),
}
)
return questionnaire_store
@@ -231,6 +236,7 @@ def mock_questionnaire_store_v2(mocker):
"channel": channel,
"region_code": region_code,
"account_service_url": "account_service_url",
+ "response_expires_at": get_response_expires_at(),
"survey_metadata": {
"data": {
"period_id": period_id,
diff --git a/tests/app/views/handlers/test_individual_response_fulfilment_request.py b/tests/app/views/handlers/test_individual_response_fulfilment_request.py
index ae295f8d3f..ff9f6c8007 100644
--- a/tests/app/views/handlers/test_individual_response_fulfilment_request.py
+++ b/tests/app/views/handlers/test_individual_response_fulfilment_request.py
@@ -14,6 +14,7 @@
GB_WLS_REGION_CODE,
IndividualResponseFulfilmentRequest,
)
+from tests.app.parser.conftest import get_response_expires_at
DUMMY_MOBILE_NUMBER = "07700900258"
@@ -27,6 +28,7 @@ def test_sms_fulfilment_request_payload():
response_id="response_id",
account_service_url="account_service_url",
collection_exercise_sid="collection_exercise_sid",
+ response_expires_at=get_response_expires_at(),
)
fulfilment_request = IndividualResponseFulfilmentRequest(
diff --git a/tests/app/views/handlers/test_question_with_dynamic_answers.py b/tests/app/views/handlers/test_question_with_dynamic_answers.py
index 4e771b7788..22beb7ffbc 100644
--- a/tests/app/views/handlers/test_question_with_dynamic_answers.py
+++ b/tests/app/views/handlers/test_question_with_dynamic_answers.py
@@ -8,6 +8,7 @@
from app.utilities.schema import load_schema_from_name
from app.views.handlers.question import Question
+from ...parser.conftest import get_response_expires_at
from .conftest import set_storage_data
@@ -35,6 +36,7 @@ def test_question_with_dynamic_answers(storage, language, mocker):
questionnaire_store.list_store = ListStore(
[{"items": ["tUJzGV", "vhECeh"], "name": "supermarkets"}]
)
+ questionnaire_store.set_metadata({"response_expires_at": get_response_expires_at()})
schema = load_schema_from_name("test_dynamic_answers_list_source")
mocker.patch(
diff --git a/tests/functional/jwt_helper.js b/tests/functional/jwt_helper.js
index 678b7d8d8d..75f458fda9 100644
--- a/tests/functional/jwt_helper.js
+++ b/tests/functional/jwt_helper.js
@@ -90,6 +90,9 @@ export function generateToken(
const iat = KJUR.jws.IntDate.get("now");
const exp = KJUR.jws.IntDate.get("now") + 1800;
const caseId = uuidv4();
+ const currentDate = new Date();
+ currentDate.setUTCDate(currentDate.getUTCDate() + 1);
+ const isoDate = currentDate.toISOString();
if (version === "v2") {
payload = {
@@ -106,6 +109,7 @@ export function generateToken(
account_service_url: "http://localhost:8000",
survey_metadata: getSurveyMetadata(theme, userId, displayAddress, periodId, periodStr),
version: "v2",
+ response_expires_at: isoDate,
};
} else {
payload = {
@@ -131,6 +135,7 @@ export function generateToken(
region_code: regionCode,
language_code: languageCode,
account_service_url: "http://localhost:8000",
+ response_expires_at: isoDate,
};
}
diff --git a/tests/integration/create_token.py b/tests/integration/create_token.py
index 1c88bf92bb..c611ad9918 100644
--- a/tests/integration/create_token.py
+++ b/tests/integration/create_token.py
@@ -5,6 +5,7 @@
from app.authentication.auth_payload_version import AuthPayloadVersion
from app.keys import KEY_PURPOSE_AUTHENTICATION
+from tests.app.parser.conftest import get_response_expires_at
ACCOUNT_SERVICE_URL = "http://upstream.url"
@@ -88,6 +89,7 @@ def _get_payload_with_params(
payload_vars["exp"] = payload_vars["iat"] + float(3600) # one hour from now
payload_vars["jti"] = str(uuid4())
payload_vars["case_id"] = str(uuid4())
+ payload_vars["response_expires_at"] = get_response_expires_at()
for key, value in extra_payload.items():
payload_vars[key] = value
diff --git a/tests/integration/routes/test_errors.py b/tests/integration/routes/test_errors.py
index db4414e8cb..4d5d2502ac 100644
--- a/tests/integration/routes/test_errors.py
+++ b/tests/integration/routes/test_errors.py
@@ -6,6 +6,7 @@
ACCOUNT_SERVICE_BASE_URL_SOCIAL,
ONS_URL,
)
+from tests.app.parser.conftest import get_response_expires_at
from tests.integration.create_token import ACCOUNT_SERVICE_URL
from tests.integration.integration_test_case import IntegrationTestCase
@@ -31,6 +32,7 @@ class TestErrors(IntegrationTestCase): # pylint: disable=too-many-public-method
"language_code": "en",
"account_service_url": "http://correct.place",
"roles": [],
+ "response_expires_at": get_response_expires_at(),
}
def test_errors_404(self):
diff --git a/tests/integration/routes/test_jwt_authentication.py b/tests/integration/routes/test_jwt_authentication.py
index ff15a28325..0d1a9eff39 100644
--- a/tests/integration/routes/test_jwt_authentication.py
+++ b/tests/integration/routes/test_jwt_authentication.py
@@ -13,6 +13,7 @@
TEST_DO_NOT_USE_SR_PUBLIC_KEY,
TEST_DO_NOT_USE_UPSTREAM_PRIVATE_KEY,
)
+from tests.app.parser.conftest import get_response_expires_at
from tests.integration.app_context_test_case import AppContextTestCase
from tests.integration.integration_test_case import (
EQ_USER_AUTHENTICATION_RRM_PRIVATE_KEY_KID,
@@ -85,6 +86,7 @@ def create_payload():
"ru_name": "Test",
"return_by": "2016-09-09",
"account_service_url": "http://upstream.url/",
+ "response_expires_at": get_response_expires_at(),
}
diff --git a/tests/integration/test_flush_data.py b/tests/integration/test_flush_data.py
index 3ca7de5d95..9fa232c81e 100644
--- a/tests/integration/test_flush_data.py
+++ b/tests/integration/test_flush_data.py
@@ -3,6 +3,7 @@
from mock import patch
+from tests.app.parser.conftest import get_response_expires_at
from tests.integration.integration_test_case import IntegrationTestCase
@@ -179,6 +180,7 @@ def test_flush_data_successful_v2(
"lists": [],
},
"started_at": "2023-02-07T11:42:32.380784+00:00",
+ "response_expires_at": get_response_expires_at(),
}
self.launchSurveyV2("test_textfield")
form_data = {"name-answer": "Joe Bloggs"}
From 18d09f497485e5f5e56e5ab3f528b80a8974dc3f Mon Sep 17 00:00:00 2001
From: petechd <53475968+petechd@users.noreply.github.com>
Date: Thu, 18 May 2023 15:40:32 +0100
Subject: [PATCH 2/9] Bind additional contexts to flush requests (#1108)
---
app/routes/flush.py | 9 +++++-
tests/integration/test_flush_data.py | 47 ++++++++++++++++++++++++++++
2 files changed, 55 insertions(+), 1 deletion(-)
diff --git a/app/routes/flush.py b/app/routes/flush.py
index 2ff8897a86..28cb2093b2 100644
--- a/app/routes/flush.py
+++ b/app/routes/flush.py
@@ -55,7 +55,14 @@ def flush_data() -> Response:
user = _get_user(decrypted_token["response_id"])
if metadata := get_metadata(user):
- contextvars.bind_contextvars(tx_id=metadata.tx_id)
+ contextvars.bind_contextvars(
+ tx_id=metadata.tx_id,
+ ce_id=metadata.collection_exercise_sid,
+ )
+ if schema_name := metadata.schema_name:
+ contextvars.bind_contextvars(schema_name=schema_name)
+ if schema_url := metadata.schema_url:
+ contextvars.bind_contextvars(schema_url=schema_url)
if _submit_data(user):
return Response(status=200)
return Response(status=404)
diff --git a/tests/integration/test_flush_data.py b/tests/integration/test_flush_data.py
index 9fa232c81e..121c9ad9e1 100644
--- a/tests/integration/test_flush_data.py
+++ b/tests/integration/test_flush_data.py
@@ -1,11 +1,15 @@
import time
import uuid
+from httmock import HTTMock, urlmatch
from mock import patch
+from app.utilities.schema import get_schema_path_map
from tests.app.parser.conftest import get_response_expires_at
from tests.integration.integration_test_case import IntegrationTestCase
+SCHEMA_PATH_MAP = get_schema_path_map(include_test_schemas=True)
+
class TestFlushData(IntegrationTestCase):
def setUp(self):
@@ -30,6 +34,14 @@ def tearDown(self):
super().tearDown()
+ @staticmethod
+ @urlmatch(netloc=r"eq-survey-register", path=r"\/my-test-schema")
+ def schema_url_mock(_url, _request):
+ schema_path = SCHEMA_PATH_MAP["test"]["en"]["test_textfield"]
+
+ with open(schema_path, encoding="utf8") as json_data:
+ return json_data.read()
+
def test_flush_data_successful(self):
self.post(
url="/flush?token="
@@ -193,3 +205,38 @@ def test_flush_data_successful_v2(
self.assertStatusOK()
mock_convert_answers_v2.assert_called_once()
mock_convert_answers.assert_not_called()
+
+ def test_flush_logs_output(self):
+ with self.assertLogs() as logs:
+ self.post(
+ url=f"/flush?token={self.token_generator.create_token(schema_name='test_textfield', payload=self.get_payload())}"
+ )
+
+ flush_log = logs.output[5]
+
+ self.assertIn("successfully flushed answers", flush_log)
+ self.assertIn("tx_id", flush_log)
+ self.assertIn("ce_id", flush_log)
+ self.assertIn("schema_name", flush_log)
+ self.assertNotIn("schema_url", flush_log)
+
+ def test_flush_logs_output_schema_url(self):
+ schema_url = "http://eq-survey-register.url/my-test-schema"
+ token = self.token_generator.create_token_with_schema_url(
+ "test_textfield", schema_url
+ )
+ with HTTMock(self.schema_url_mock):
+ self.get(url=f"/session?token={token}")
+ self.assertStatusOK()
+ with self.assertLogs() as logs:
+ self.post(
+ url=f"/flush?token={self.token_generator.create_token_with_schema_url('test_textfield', schema_url, payload=self.get_payload())}"
+ )
+
+ flush_log = logs.output[6]
+
+ self.assertIn("successfully flushed answers", flush_log)
+ self.assertIn("tx_id", flush_log)
+ self.assertIn("ce_id", flush_log)
+ self.assertIn("schema_name", flush_log)
+ self.assertIn("schema_url", flush_log)
From cee23dc0833e88ab6d85eb46ef9974878db09f7f Mon Sep 17 00:00:00 2001
From: Mebin Abraham <35296336+MebinAbraham@users.noreply.github.com>
Date: Tue, 23 May 2023 10:16:52 +0100
Subject: [PATCH 3/9] Schemas v3.56.0 (#1110)
---
.schemas-version | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.schemas-version b/.schemas-version
index 304e303931..436ebfe052 100644
--- a/.schemas-version
+++ b/.schemas-version
@@ -1 +1 @@
-v3.55.0
+v3.56.0
From 36fb6c7bb45d04c9cdf363a6169d4c6d258e4039 Mon Sep 17 00:00:00 2001
From: Mebin Abraham <35296336+MebinAbraham@users.noreply.github.com>
Date: Thu, 25 May 2023 13:35:41 +0100
Subject: [PATCH 4/9] Schemas v3.57.0 (#1113)
---
.schemas-version | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.schemas-version b/.schemas-version
index 436ebfe052..5af1c91d2a 100644
--- a/.schemas-version
+++ b/.schemas-version
@@ -1 +1 @@
-v3.56.0
+v3.57.0
From fdc0f756efcaaf9e92a4b41f4763475d06cbc70f Mon Sep 17 00:00:00 2001
From: Mebin Abraham <35296336+MebinAbraham@users.noreply.github.com>
Date: Fri, 26 May 2023 08:02:18 +0100
Subject: [PATCH 5/9] Schemas v3.57.1 (#1115)
---
.schemas-version | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.schemas-version b/.schemas-version
index 5af1c91d2a..6337654f6b 100644
--- a/.schemas-version
+++ b/.schemas-version
@@ -1 +1 @@
-v3.57.0
+v3.57.1
From c2439d82a0a1884054bcda846c61878d5834ab3e Mon Sep 17 00:00:00 2001
From: Mebin Abraham <35296336+MebinAbraham@users.noreply.github.com>
Date: Fri, 26 May 2023 12:12:03 +0100
Subject: [PATCH 6/9] Schemas v3.58.0 (#1116)
---
.schemas-version | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.schemas-version b/.schemas-version
index 6337654f6b..6d07408b02 100644
--- a/.schemas-version
+++ b/.schemas-version
@@ -1 +1 @@
-v3.57.1
+v3.58.0
From cc12488abd2b2fa636005a99ba4c8b0987e8e2cd Mon Sep 17 00:00:00 2001
From: Guilhem <122792081+ONS-Guilhem-Forey@users.noreply.github.com>
Date: Fri, 26 May 2023 14:16:47 +0100
Subject: [PATCH 7/9] Implement "progress" value source (#1044)
Co-authored-by: Rhys Berrow <47635349+berroar@users.noreply.github.c
---
Pipfile | 3 +-
Pipfile.lock | 1734 +++++++++--------
app/data_models/progress_store.py | 33 +-
app/jinja_filters.py | 17 +-
app/publisher/publisher.py | 3 +-
app/questionnaire/dependencies.py | 3 +-
app/questionnaire/path_finder.py | 12 +-
app/questionnaire/placeholder_parser.py | 4 +-
app/questionnaire/placeholder_transforms.py | 10 +-
app/questionnaire/questionnaire_schema.py | 241 ++-
.../questionnaire_store_updater.py | 190 +-
app/questionnaire/router.py | 7 +-
app/questionnaire/rules/rule_evaluator.py | 2 +-
app/questionnaire/value_source_resolver.py | 89 +-
app/survey_config/business_config.py | 2 +-
app/survey_config/social_survey_config.py | 2 +-
app/survey_config/survey_config.py | 2 +-
app/translations/messages.pot | 16 +-
app/utilities/mappings.py | 6 +-
.../contexts/calculated_summary_context.py | 4 +-
app/views/contexts/summary/answer.py | 2 +-
app/views/handlers/calculated_summary.py | 7 +
app/views/handlers/individual_response.py | 2 +-
app/views/handlers/list_action.py | 1 -
app/views/handlers/question.py | 3 +
...block_value_source_repeating_sections.json | 392 ++++
...ction_value_source_repeating_sections.json | 392 ++++
.../en/test_progress_value_source_blocks.json | 210 ++
...ess_value_source_blocks_cross_section.json | 224 +++
...gress_value_source_calculated_summary.json | 517 +++++
...ue_source_calculated_summary_extended.json | 1129 +++++++++++
...peating_sections_chained_dependencies.json | 490 +++++
...ress_value_source_section_enabled_hub.json | 110 ++
...ue_source_section_enabled_hub_complex.json | 251 +++
...s_value_source_section_enabled_no_hub.json | 114 ++
tests/app/data_model/test_progress_store.py | 2 +-
.../data_model/test_questionnaire_store.py | 10 +-
tests/app/questionnaire/conftest.py | 19 +
.../rules/test_rule_evaluator.py | 43 +-
.../questionnaire/test_placeholder_parser.py | 3 +-
.../test_questionnaire_schema.py | 88 +
.../test_questionnaire_store_updater.py | 151 +-
.../test_value_source_resolver.py | 13 +-
.../views/contexts/summary/test_question.py | 2 +-
.../progress/progress_value_source_blocks.js | 193 ++
.../progress_value_source_repeating.js | 199 ++
tests/integration/integration_test_case.py | 4 +
...stionnaire_progress_value_source_blocks.py | 213 ++
...rogress_value_source_calculated_summary.py | 417 ++++
...ress_value_source_in_repeating_sections.py | 533 +++++
...e_progress_value_source_section_enabled.py | 176 ++
.../integration/test_application_variables.py | 6 +-
52 files changed, 7268 insertions(+), 1028 deletions(-)
create mode 100644 schemas/test/en/test_progress_block_value_source_repeating_sections.json
create mode 100644 schemas/test/en/test_progress_section_value_source_repeating_sections.json
create mode 100644 schemas/test/en/test_progress_value_source_blocks.json
create mode 100644 schemas/test/en/test_progress_value_source_blocks_cross_section.json
create mode 100644 schemas/test/en/test_progress_value_source_calculated_summary.json
create mode 100644 schemas/test/en/test_progress_value_source_calculated_summary_extended.json
create mode 100644 schemas/test/en/test_progress_value_source_repeating_sections_chained_dependencies.json
create mode 100644 schemas/test/en/test_progress_value_source_section_enabled_hub.json
create mode 100644 schemas/test/en/test_progress_value_source_section_enabled_hub_complex.json
create mode 100644 schemas/test/en/test_progress_value_source_section_enabled_no_hub.json
create mode 100644 tests/functional/spec/features/progress/progress_value_source_blocks.js
create mode 100644 tests/functional/spec/features/progress/progress_value_source_repeating.js
create mode 100644 tests/integration/questionnaire/test_questionnaire_progress_value_source_blocks.py
create mode 100644 tests/integration/questionnaire/test_questionnaire_progress_value_source_calculated_summary.py
create mode 100644 tests/integration/questionnaire/test_questionnaire_progress_value_source_in_repeating_sections.py
create mode 100644 tests/integration/questionnaire/test_questionnaire_progress_value_source_section_enabled.py
diff --git a/Pipfile b/Pipfile
index aa1c5a8c06..33b9d009cf 100644
--- a/Pipfile
+++ b/Pipfile
@@ -38,7 +38,7 @@ pytest-mock = "*"
[packages]
colorama = "*"
-flask = "*"
+flask = "==2.2.2"
flask-babel = "*"
flask-login = "*"
flask-wtf = "*"
@@ -72,6 +72,7 @@ google-cloud-tasks = "*"
simplejson = "*"
markupsafe = "*"
pdfkit = "*"
+ordered-set = "*"
[requires]
python_version = "3.10"
diff --git a/Pipfile.lock b/Pipfile.lock
index 53ccf6d636..7f8c8b727d 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "65c2887dc528a01d63be44fc18d26e792c56501ddfb961c4cfb01a7ce7c925b7"
+ "sha256": "6048e514736937d357347a60fbe097ce97d92ec7b8b5dc1b1c8050d4d106d61b"
},
"pipfile-spec": 6,
"requires": {
@@ -21,40 +21,40 @@
"sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15",
"sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"
],
- "markers": "python_version >= '3.6'",
+ "markers": "python_full_version <= '3.11.2'",
"version": "==4.0.2"
},
"babel": {
"hashes": [
- "sha256:1ad3eca1c885218f6dce2ab67291178944f810a10a9b5f3cb8382a5a232b64fe",
- "sha256:5ef4b3226b0180dedded4229651c8b0e1a3a6a2837d45a073272f313e4cf97f6"
+ "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610",
+ "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"
],
- "markers": "python_version >= '3.6'",
- "version": "==2.11.0"
+ "markers": "python_version >= '3.7'",
+ "version": "==2.12.1"
},
"blinker": {
"hashes": [
- "sha256:1eb563df6fdbc39eeddc177d953203f99f097e9bf0e2b8f9f3cf18b6ca425e36",
- "sha256:923e5e2f69c155f2cc42dafbbd70e16e3fde24d2d4aa2ab72fbe386238892462"
+ "sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213",
+ "sha256:c3d739772abb7bc2860abf5f2ec284223d9ad5c76da018234f6f50d6f31ab1f0"
],
"index": "pypi",
- "version": "==1.5"
+ "version": "==1.6.2"
},
"boto3": {
"hashes": [
- "sha256:9e23ec51e1fe162ecb5efee41bd9346d90ebbb8ff094ef8088223305d48ffaec",
- "sha256:9fc94b3078f5047c1fc40529fa12eb82a2432bb1dca0ee37b1c54f902f46191f"
+ "sha256:6648aff15d19927cd26db47eb56362ccd313a1ddbd7aaa3235ef05d05d398252",
+ "sha256:fe8248b80c4f0fdaed8b8779467c4431a5e52177e02ccd137d51ec51194ebb00"
],
"index": "pypi",
- "version": "==1.26.65"
+ "version": "==1.26.125"
},
"botocore": {
"hashes": [
- "sha256:77fff109e1bdbf030d8400ccab9d28454c03c7be62c1696a239b21792b0873df",
- "sha256:8710f53af0e20f08f36a3bf434d18bc7ceba5d9835495b02aedbedd35df5de9a"
+ "sha256:3005a7ffee083315e69938acdf1bfeaf9e21fe1fe1643d6573ee817721f4ffcd",
+ "sha256:ac87b63e9aa4231cd28941945024a0c4470c184c60334ebe5e1cae3544c785ed"
],
"markers": "python_version >= '3.7'",
- "version": "==1.29.72"
+ "version": "==1.29.125"
},
"brotli": {
"hashes": [
@@ -230,97 +230,84 @@
},
"charset-normalizer": {
"hashes": [
- "sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b",
- "sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42",
- "sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d",
- "sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b",
- "sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a",
- "sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59",
- "sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154",
- "sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1",
- "sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c",
- "sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a",
- "sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d",
- "sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6",
- "sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b",
- "sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b",
- "sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783",
- "sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5",
- "sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918",
- "sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555",
- "sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639",
- "sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786",
- "sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e",
- "sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed",
- "sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820",
- "sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8",
- "sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3",
- "sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541",
- "sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14",
- "sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be",
- "sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e",
- "sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76",
- "sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b",
- "sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c",
- "sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b",
- "sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3",
- "sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc",
- "sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6",
- "sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59",
- "sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4",
- "sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d",
- "sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d",
- "sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3",
- "sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a",
- "sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea",
- "sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6",
- "sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e",
- "sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603",
- "sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24",
- "sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a",
- "sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58",
- "sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678",
- "sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a",
- "sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c",
- "sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6",
- "sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18",
- "sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174",
- "sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317",
- "sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f",
- "sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc",
- "sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837",
- "sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41",
- "sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c",
- "sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579",
- "sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753",
- "sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8",
- "sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291",
- "sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087",
- "sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866",
- "sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3",
- "sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d",
- "sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1",
- "sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca",
- "sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e",
- "sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db",
- "sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72",
- "sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d",
- "sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc",
- "sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539",
- "sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d",
- "sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af",
- "sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b",
- "sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602",
- "sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f",
- "sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478",
- "sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c",
- "sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e",
- "sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479",
- "sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7",
- "sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8"
- ],
- "markers": "python_full_version >= '3.6.0'",
- "version": "==3.0.1"
+ "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6",
+ "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1",
+ "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e",
+ "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373",
+ "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62",
+ "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230",
+ "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be",
+ "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c",
+ "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0",
+ "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448",
+ "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f",
+ "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649",
+ "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d",
+ "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0",
+ "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706",
+ "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a",
+ "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59",
+ "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23",
+ "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5",
+ "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb",
+ "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e",
+ "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e",
+ "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c",
+ "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28",
+ "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d",
+ "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41",
+ "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974",
+ "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce",
+ "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f",
+ "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1",
+ "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d",
+ "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8",
+ "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017",
+ "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31",
+ "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7",
+ "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8",
+ "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e",
+ "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14",
+ "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd",
+ "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d",
+ "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795",
+ "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b",
+ "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b",
+ "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b",
+ "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203",
+ "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f",
+ "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19",
+ "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1",
+ "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a",
+ "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac",
+ "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9",
+ "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0",
+ "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137",
+ "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f",
+ "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6",
+ "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5",
+ "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909",
+ "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f",
+ "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0",
+ "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324",
+ "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755",
+ "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb",
+ "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854",
+ "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c",
+ "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60",
+ "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84",
+ "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0",
+ "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b",
+ "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1",
+ "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531",
+ "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1",
+ "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11",
+ "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326",
+ "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df",
+ "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"
+ ],
+ "markers": "python_full_version >= '3.7.0'",
+ "version": "==3.1.0"
},
"click": {
"hashes": [
@@ -348,32 +335,28 @@
},
"cryptography": {
"hashes": [
- "sha256:0f8da300b5c8af9f98111ffd512910bc792b4c77392a9523624680f7956a99d4",
- "sha256:35f7c7d015d474f4011e859e93e789c87d21f6f4880ebdc29896a60403328f1f",
- "sha256:4789d1e3e257965e960232345002262ede4d094d1a19f4d3b52e48d4d8f3b885",
- "sha256:5aa67414fcdfa22cf052e640cb5ddc461924a045cacf325cd164e65312d99502",
- "sha256:5d2d8b87a490bfcd407ed9d49093793d0f75198a35e6eb1a923ce1ee86c62b41",
- "sha256:6687ef6d0a6497e2b58e7c5b852b53f62142cfa7cd1555795758934da363a965",
- "sha256:6f8ba7f0328b79f08bdacc3e4e66fb4d7aab0c3584e0bd41328dce5262e26b2e",
- "sha256:706843b48f9a3f9b9911979761c91541e3d90db1ca905fd63fee540a217698bc",
- "sha256:807ce09d4434881ca3a7594733669bd834f5b2c6d5c7e36f8c00f691887042ad",
- "sha256:83e17b26de248c33f3acffb922748151d71827d6021d98c70e6c1a25ddd78505",
- "sha256:96f1157a7c08b5b189b16b47bc9db2332269d6680a196341bf30046330d15388",
- "sha256:aec5a6c9864be7df2240c382740fcf3b96928c46604eaa7f3091f58b878c0bb6",
- "sha256:b0afd054cd42f3d213bf82c629efb1ee5f22eba35bf0eec88ea9ea7304f511a2",
- "sha256:c5caeb8188c24888c90b5108a441c106f7faa4c4c075a2bcae438c6e8ca73cef",
- "sha256:ced4e447ae29ca194449a3f1ce132ded8fcab06971ef5f618605aacaa612beac",
- "sha256:d1f6198ee6d9148405e49887803907fe8962a23e6c6f83ea7d98f1c0de375695",
- "sha256:e124352fd3db36a9d4a21c1aa27fd5d051e621845cb87fb851c08f4f75ce8be6",
- "sha256:e422abdec8b5fa8462aa016786680720d78bdce7a30c652b7fadf83a4ba35336",
- "sha256:ef8b72fa70b348724ff1218267e7f7375b8de4e8194d1636ee60510aae104cd0",
- "sha256:f0c64d1bd842ca2633e74a1a28033d139368ad959872533b1bab8c80e8240a0c",
- "sha256:f24077a3b5298a5a06a8e0536e3ea9ec60e4c7ac486755e5fb6e6ea9b3500106",
- "sha256:fdd188c8a6ef8769f148f88f859884507b954cc64db6b52f66ef199bb9ad660a",
- "sha256:fe913f20024eb2cb2f323e42a64bdf2911bb9738a15dba7d3cce48151034e3a8"
- ],
- "index": "pypi",
- "version": "==39.0.1"
+ "sha256:05dc219433b14046c476f6f09d7636b92a1c3e5808b9a6536adf4932b3b2c440",
+ "sha256:0dcca15d3a19a66e63662dc8d30f8036b07be851a8680eda92d079868f106288",
+ "sha256:142bae539ef28a1c76794cca7f49729e7c54423f615cfd9b0b1fa90ebe53244b",
+ "sha256:3daf9b114213f8ba460b829a02896789751626a2a4e7a43a28ee77c04b5e4958",
+ "sha256:48f388d0d153350f378c7f7b41497a54ff1513c816bcbbcafe5b829e59b9ce5b",
+ "sha256:4df2af28d7bedc84fe45bd49bc35d710aede676e2a4cb7fc6d103a2adc8afe4d",
+ "sha256:4f01c9863da784558165f5d4d916093737a75203a5c5286fde60e503e4276c7a",
+ "sha256:7a38250f433cd41df7fcb763caa3ee9362777fdb4dc642b9a349721d2bf47404",
+ "sha256:8f79b5ff5ad9d3218afb1e7e20ea74da5f76943ee5edb7f76e56ec5161ec782b",
+ "sha256:956ba8701b4ffe91ba59665ed170a2ebbdc6fc0e40de5f6059195d9f2b33ca0e",
+ "sha256:a04386fb7bc85fab9cd51b6308633a3c271e3d0d3eae917eebab2fac6219b6d2",
+ "sha256:a95f4802d49faa6a674242e25bfeea6fc2acd915b5e5e29ac90a32b1139cae1c",
+ "sha256:adc0d980fd2760c9e5de537c28935cc32b9353baaf28e0814df417619c6c8c3b",
+ "sha256:aecbb1592b0188e030cb01f82d12556cf72e218280f621deed7d806afd2113f9",
+ "sha256:b12794f01d4cacfbd3177b9042198f3af1c856eedd0a98f10f141385c809a14b",
+ "sha256:c0764e72b36a3dc065c155e5b22f93df465da9c39af65516fe04ed3c68c92636",
+ "sha256:c33c0d32b8594fa647d2e01dbccc303478e16fdd7cf98652d5b3ed11aa5e5c99",
+ "sha256:cbaba590180cba88cb99a5f76f90808a624f18b169b90a4abb40c1fd8c19420e",
+ "sha256:d5a1bd0e9e2031465761dfa920c16b0065ad77321d8a8c1f5ee331021fda65e9"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==40.0.2"
},
"deprecated": {
"hashes": [
@@ -388,16 +371,16 @@
"sha256:224e32b03eb46be70e12ef6d64e0be123a64e621ab4c0822ff6d450d52a540b9",
"sha256:89141536394f909066cabd112e3e1a37e4e654db00a25308b0f130bc3152eb46"
],
- "markers": "python_version >= '3.7' and python_version < '4'",
+ "markers": "python_version >= '3.7' and python_version < '4.0'",
"version": "==2.3.0"
},
"email-validator": {
"hashes": [
- "sha256:49a72f5fa6ed26be1c964f0567d931d10bf3fdeeacdf97bc26ef1cd2a44e0bda",
- "sha256:d178c5c6fa6c6824e9b04f199cf23e79ac15756786573c190d2ad13089411ad2"
+ "sha256:1ff6e86044200c56ae23595695c54e9614f4a9551e0e393614f764860b3d7900",
+ "sha256:2466ba57cda361fb7309fd3d5a225723c788ca4bbad32a0ebd5373b99730285c"
],
"index": "pypi",
- "version": "==1.3.1"
+ "version": "==2.0.0.post2"
},
"flask": {
"hashes": [
@@ -409,11 +392,11 @@
},
"flask-babel": {
"hashes": [
- "sha256:ceb8c82039954a6b29da33ec5deb84878b78069d1ea628b21cac3f8233e9189c",
- "sha256:d408cace25514bea8b92e898fd7e55877fbac79b71bc230e266ff515408eba38"
+ "sha256:be015772c5d7f046f3b99c508dcf618636eb93d21b713b356db79f3e79f69f39",
+ "sha256:deb3ee272d5adf97f5974ed09ab501243d63e7fb4a047501a00de4bd4aca4830"
],
"index": "pypi",
- "version": "==3.0.1"
+ "version": "==3.1.0"
},
"flask-compress": {
"hashes": [
@@ -519,11 +502,11 @@
},
"google-auth": {
"hashes": [
- "sha256:5045648c821fb72384cdc0e82cc326df195f113a33049d9b62b74589243d2acc",
- "sha256:ed7057a101af1146f0554a769930ac9de506aeca4fd5af6543ebe791851a9fbd"
+ "sha256:ce311e2bc58b130fddf316df57c9b3943c2a7b4f6ec31de9663a9333e4064efc",
+ "sha256:f586b274d3eb7bd932ea424b1c702a30e0393a2e2bc4ca3eae8263ffd8be229f"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
- "version": "==2.16.0"
+ "version": "==2.17.3"
},
"google-cloud-core": {
"hashes": [
@@ -535,35 +518,35 @@
},
"google-cloud-datastore": {
"hashes": [
- "sha256:3d11652cb98cfb77147a61a74adefdcd3018c5e06f1ed723ec32c55e1dc9597d",
- "sha256:8a4b2d5b9dcaad1af8be79ad6ebd00386f3f907685f1ec5c2456f072584203b0"
+ "sha256:4c2f0e8825482a8998c8c5b672c86206a6d988e449f251bb9bf17dae9a056c2e",
+ "sha256:788c512ea6d63012e711e0371a0e425ec852990051aca5c9c9a73804c448b1af"
],
"index": "pypi",
- "version": "==2.13.2"
+ "version": "==2.15.1"
},
"google-cloud-pubsub": {
"hashes": [
- "sha256:a035d63ef209a28edd54c8c1fcf66cc25b207ed6edb1259a973b909c723ecf80",
- "sha256:e2714f07b750458beaf5b07b670ea7b6058ee155c021c987d0b8e6a40bf3446f"
+ "sha256:587da7d535ca858ceeed7036205355e5a6dd3e44ea4abc96f4e50d1abfd8843b",
+ "sha256:e41ec9635cc68dbd238f572f295b3d0bae6340c66ba31a70dfb890950ab33c2b"
],
"index": "pypi",
- "version": "==2.14.0"
+ "version": "==2.16.0"
},
"google-cloud-storage": {
"hashes": [
- "sha256:1ac2d58d2d693cb1341ebc48659a3527be778d9e2d8989697a2746025928ff17",
- "sha256:f78a63525e72dd46406b255bbdf858a22c43d6bad8dc5bdeb7851a42967e95a1"
+ "sha256:248e210c13bc109909160248af546a91cb2dabaf3d7ebbf04def9dd49f02dbb6",
+ "sha256:4388da1ff5bda6d729f26dbcaf1bfa020a2a52a7b91f0a8123edbda51660802c"
],
"index": "pypi",
- "version": "==2.7.0"
+ "version": "==2.8.0"
},
"google-cloud-tasks": {
"hashes": [
- "sha256:56109cfd9f65864dd548f6248e48dee9919d6bfe658f8065ab18b11b84e7171f",
- "sha256:da4463e739403d53b653713337ea29cf9381b7012bbb946a18e5c6a8b50f6940"
+ "sha256:616f2c32945cf68e812375a32f8a0f1d9cc39f4a39de03942593af6d13af7c20",
+ "sha256:99b845055c7a1b27b73349429f2d6f6a0eb9f409b4cdeec89df99e16bd197833"
],
"index": "pypi",
- "version": "==2.12.1"
+ "version": "==2.13.1"
},
"google-crc32c": {
"hashes": [
@@ -641,19 +624,19 @@
},
"google-resumable-media": {
"hashes": [
- "sha256:15b8a2e75df42dc6502d1306db0bce2647ba6013f9cd03b6e17368c0886ee90a",
- "sha256:831e86fd78d302c1a034730a0c6e5369dd11d37bad73fa69ca8998460d5bae8d"
+ "sha256:218931e8e2b2a73a58eb354a288e03a0fd5fb1c4583261ac6e4c078666468c93",
+ "sha256:da1bd943e2e114a56d85d6848497ebf9be6a14d3db23e9fc57581e7c3e8170ec"
],
"markers": "python_version >= '3.7'",
- "version": "==2.4.1"
+ "version": "==2.5.0"
},
"googleapis-common-protos": {
"hashes": [
- "sha256:c727251ec025947d545184ba17e3578840fc3a24a0516a020479edab660457df",
- "sha256:ca3befcd4580dab6ad49356b46bf165bb68ff4b32389f028f1abd7c10ab9519a"
+ "sha256:4168fcb568a826a52f23510412da405abd93f4d23ba544bb68d943b14ba3cb44",
+ "sha256:b287dc48449d1d41af0c69f4ea26242b5ae4c3d7249a38b0984c86a4caffff1f"
],
"markers": "python_version >= '3.7'",
- "version": "==1.58.0"
+ "version": "==1.59.0"
},
"greenlet": {
"hashes": [
@@ -731,62 +714,62 @@
},
"grpcio": {
"hashes": [
- "sha256:094e64236253590d9d4075665c77b329d707b6fca864dd62b144255e199b4f87",
- "sha256:0dc5354e38e5adf2498312f7241b14c7ce3484eefa0082db4297189dcbe272e6",
- "sha256:0e1a9e1b4a23808f1132aa35f968cd8e659f60af3ffd6fb00bcf9a65e7db279f",
- "sha256:0fb93051331acbb75b49a2a0fd9239c6ba9528f6bdc1dd400ad1cb66cf864292",
- "sha256:16c71740640ba3a882f50b01bf58154681d44b51f09a5728180a8fdc66c67bd5",
- "sha256:172405ca6bdfedd6054c74c62085946e45ad4d9cec9f3c42b4c9a02546c4c7e9",
- "sha256:17ec9b13cec4a286b9e606b48191e560ca2f3bbdf3986f91e480a95d1582e1a7",
- "sha256:22b011674090594f1f3245960ced7386f6af35485a38901f8afee8ad01541dbd",
- "sha256:24ac1154c4b2ab4a0c5326a76161547e70664cd2c39ba75f00fc8a2170964ea2",
- "sha256:257478300735ce3c98d65a930bbda3db172bd4e00968ba743e6a1154ea6edf10",
- "sha256:29cb97d41a4ead83b7bcad23bdb25bdd170b1e2cba16db6d3acbb090bc2de43c",
- "sha256:2b170eaf51518275c9b6b22ccb59450537c5a8555326fd96ff7391b5dd75303c",
- "sha256:31bb6bc7ff145e2771c9baf612f4b9ebbc9605ccdc5f3ff3d5553de7fc0e0d79",
- "sha256:3c2b3842dcf870912da31a503454a33a697392f60c5e2697c91d133130c2c85d",
- "sha256:3f9b0023c2c92bebd1be72cdfca23004ea748be1813a66d684d49d67d836adde",
- "sha256:471d39d3370ca923a316d49c8aac66356cea708a11e647e3bdc3d0b5de4f0a40",
- "sha256:49d680356a975d9c66a678eb2dde192d5dc427a7994fb977363634e781614f7c",
- "sha256:4c4423ea38a7825b8fed8934d6d9aeebdf646c97e3c608c3b0bcf23616f33877",
- "sha256:506b9b7a4cede87d7219bfb31014d7b471cfc77157da9e820a737ec1ea4b0663",
- "sha256:538d981818e49b6ed1e9c8d5e5adf29f71c4e334e7d459bf47e9b7abb3c30e09",
- "sha256:59dffade859f157bcc55243714d57b286da6ae16469bf1ac0614d281b5f49b67",
- "sha256:5a6ebcdef0ef12005d56d38be30f5156d1cb3373b52e96f147f4a24b0ddb3a9d",
- "sha256:5dca372268c6ab6372d37d6b9f9343e7e5b4bc09779f819f9470cd88b2ece3c3",
- "sha256:6df3b63538c362312bc5fa95fb965069c65c3ea91d7ce78ad9c47cab57226f54",
- "sha256:6f0b89967ee11f2b654c23b27086d88ad7bf08c0b3c2a280362f28c3698b2896",
- "sha256:75e29a90dc319f0ad4d87ba6d20083615a00d8276b51512e04ad7452b5c23b04",
- "sha256:7942b32a291421460d6a07883033e392167d30724aa84987e6956cd15f1a21b9",
- "sha256:9235dcd5144a83f9ca6f431bd0eccc46b90e2c22fe27b7f7d77cabb2fb515595",
- "sha256:97d67983189e2e45550eac194d6234fc38b8c3b5396c153821f2d906ed46e0ce",
- "sha256:9ff42c5620b4e4530609e11afefa4a62ca91fa0abb045a8957e509ef84e54d30",
- "sha256:a8a0b77e992c64880e6efbe0086fe54dfc0bbd56f72a92d9e48264dcd2a3db98",
- "sha256:aacb54f7789ede5cbf1d007637f792d3e87f1c9841f57dd51abf89337d1b8472",
- "sha256:bc59f7ba87972ab236f8669d8ca7400f02a0eadf273ca00e02af64d588046f02",
- "sha256:cc2bece1737b44d878cc1510ea04469a8073dbbcdd762175168937ae4742dfb3",
- "sha256:cd3baccea2bc5c38aeb14e5b00167bd4e2373a373a5e4d8d850bd193edad150c",
- "sha256:dad6533411d033b77f5369eafe87af8583178efd4039c41d7515d3336c53b4f1",
- "sha256:e223a9793522680beae44671b9ed8f6d25bbe5ddf8887e66aebad5e0686049ef",
- "sha256:e473525c28251558337b5c1ad3fa969511e42304524a4e404065e165b084c9e4",
- "sha256:e4ef09f8997c4be5f3504cefa6b5c6cc3cf648274ce3cede84d4342a35d76db6",
- "sha256:e6dfc2b6567b1c261739b43d9c59d201c1b89e017afd9e684d85aa7a186c9f7a",
- "sha256:eacad297ea60c72dd280d3353d93fb1dcca952ec11de6bb3c49d12a572ba31dd",
- "sha256:f1158bccbb919da42544a4d3af5d9296a3358539ffa01018307337365a9a0c64",
- "sha256:f1fec3abaf274cdb85bf3878167cfde5ad4a4d97c68421afda95174de85ba813",
- "sha256:f96ace1540223f26fbe7c4ebbf8a98e3929a6aa0290c8033d12526847b291c0f",
- "sha256:fbdbe9a849854fe484c00823f45b7baab159bdd4a46075302281998cb8719df5"
- ],
- "index": "pypi",
- "version": "==1.51.1"
+ "sha256:02000b005bc8b72ff50c477b6431e8886b29961159e8b8d03c00b3dd9139baed",
+ "sha256:031bbd26656e0739e4b2c81c172155fb26e274b8d0312d67aefc730bcba915b6",
+ "sha256:1209d6b002b26e939e4c8ea37a3d5b4028eb9555394ea69fb1adbd4b61a10bb8",
+ "sha256:125ed35aa3868efa82eabffece6264bf638cfdc9f0cd58ddb17936684aafd0f8",
+ "sha256:1382bc499af92901c2240c4d540c74eae8a671e4fe9839bfeefdfcc3a106b5e2",
+ "sha256:16bca8092dd994f2864fdab278ae052fad4913f36f35238b2dd11af2d55a87db",
+ "sha256:1c59d899ee7160638613a452f9a4931de22623e7ba17897d8e3e348c2e9d8d0b",
+ "sha256:1d109df30641d050e009105f9c9ca5a35d01e34d2ee2a4e9c0984d392fd6d704",
+ "sha256:1fa7d6ddd33abbd3c8b3d7d07c56c40ea3d1891ce3cd2aa9fa73105ed5331866",
+ "sha256:21c4a1aae861748d6393a3ff7867473996c139a77f90326d9f4104bebb22d8b8",
+ "sha256:224166f06ccdaf884bf35690bf4272997c1405de3035d61384ccb5b25a4c1ca8",
+ "sha256:2262bd3512ba9e9f0e91d287393df6f33c18999317de45629b7bd46c40f16ba9",
+ "sha256:2585b3c294631a39b33f9f967a59b0fad23b1a71a212eba6bc1e3ca6e6eec9ee",
+ "sha256:27fb030a4589d2536daec5ff5ba2a128f4f155149efab578fe2de2cb21596d3d",
+ "sha256:30fbbce11ffeb4f9f91c13fe04899aaf3e9a81708bedf267bf447596b95df26b",
+ "sha256:3930669c9e6f08a2eed824738c3d5699d11cd47a0ecc13b68ed11595710b1133",
+ "sha256:3b170e441e91e4f321e46d3cc95a01cb307a4596da54aca59eb78ab0fc03754d",
+ "sha256:3db71c6f1ab688d8dfc102271cedc9828beac335a3a4372ec54b8bf11b43fd29",
+ "sha256:48cb7af77238ba16c77879009003f6b22c23425e5ee59cb2c4c103ec040638a5",
+ "sha256:49eace8ea55fbc42c733defbda1e4feb6d3844ecd875b01bb8b923709e0f5ec8",
+ "sha256:533eaf5b2a79a3c6f35cbd6a095ae99cac7f4f9c0e08bdcf86c130efd3c32adf",
+ "sha256:5942a3e05630e1ef5b7b5752e5da6582460a2e4431dae603de89fc45f9ec5aa9",
+ "sha256:62117486460c83acd3b5d85c12edd5fe20a374630475388cfc89829831d3eb79",
+ "sha256:650f5f2c9ab1275b4006707411bb6d6bc927886874a287661c3c6f332d4c068b",
+ "sha256:6dc1e2c9ac292c9a484ef900c568ccb2d6b4dfe26dfa0163d5bc815bb836c78d",
+ "sha256:73c238ef6e4b64272df7eec976bb016c73d3ab5a6c7e9cd906ab700523d312f3",
+ "sha256:775a2f70501370e5ba54e1ee3464413bff9bd85bd9a0b25c989698c44a6fb52f",
+ "sha256:860fcd6db7dce80d0a673a1cc898ce6bc3d4783d195bbe0e911bf8a62c93ff3f",
+ "sha256:87f47bf9520bba4083d65ab911f8f4c0ac3efa8241993edd74c8dd08ae87552f",
+ "sha256:960b176e0bb2b4afeaa1cd2002db1e82ae54c9b6e27ea93570a42316524e77cf",
+ "sha256:a7caf553ccaf715ec05b28c9b2ab2ee3fdb4036626d779aa09cf7cbf54b71445",
+ "sha256:a947d5298a0bbdd4d15671024bf33e2b7da79a70de600ed29ba7e0fef0539ebb",
+ "sha256:a97b0d01ae595c997c1d9d8249e2d2da829c2d8a4bdc29bb8f76c11a94915c9a",
+ "sha256:b7655f809e3420f80ce3bf89737169a9dce73238af594049754a1128132c0da4",
+ "sha256:c33744d0d1a7322da445c0fe726ea6d4e3ef2dfb0539eadf23dce366f52f546c",
+ "sha256:c55a9cf5cba80fb88c850915c865b8ed78d5e46e1f2ec1b27692f3eaaf0dca7e",
+ "sha256:d2f62fb1c914a038921677cfa536d645cb80e3dd07dc4859a3c92d75407b90a5",
+ "sha256:d8ae6e0df3a608e99ee1acafaafd7db0830106394d54571c1ece57f650124ce9",
+ "sha256:e355ee9da9c1c03f174efea59292b17a95e0b7b4d7d2a389265f731a9887d5a9",
+ "sha256:e3e526062c690517b42bba66ffe38aaf8bc99a180a78212e7b22baa86902f690",
+ "sha256:eb0807323572642ab73fd86fe53d88d843ce617dd1ddf430351ad0759809a0ae",
+ "sha256:ebff0738be0499d7db74d20dca9f22a7b27deae31e1bf92ea44924fd69eb6251",
+ "sha256:ed36e854449ff6c2f8ee145f94851fe171298e1e793f44d4f672c4a0d78064e7",
+ "sha256:ed3d458ded32ff3a58f157b60cc140c88f7ac8c506a1c567b2a9ee8a2fd2ce54",
+ "sha256:f4a7dca8ccd8023d916b900aa3c626f1bd181bd5b70159479b142f957ff420e4"
+ ],
+ "index": "pypi",
+ "version": "==1.54.0"
},
"grpcio-status": {
"hashes": [
- "sha256:a52cbdc4b18f325bfc13d319ae7c7ae7a0fee07f3d9a005504d6097896d7a495",
- "sha256:ac2617a3095935ebd785e2228958f24b10a0d527a0c9eb5a0863c784f648a816"
+ "sha256:96968314e0c8576b2b631be3917c665964c8018900cb980d58a736fbff828578",
+ "sha256:b50305d52c0df6169493cca5f2e39b9b4d773b3f30d4a7a6b6dd7c18cb89007c"
],
"markers": "python_version >= '3.6'",
- "version": "==1.51.1"
+ "version": "==1.54.0"
},
"gunicorn": {
"hashes": [
@@ -930,13 +913,21 @@
"index": "pypi",
"version": "==3.19.0"
},
+ "ordered-set": {
+ "hashes": [
+ "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562",
+ "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8"
+ ],
+ "index": "pypi",
+ "version": "==4.1.0"
+ },
"packaging": {
"hashes": [
- "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2",
- "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"
+ "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61",
+ "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"
],
"markers": "python_version >= '3.7'",
- "version": "==23.0"
+ "version": "==23.1"
},
"pdfkit": {
"hashes": [
@@ -965,59 +956,38 @@
},
"protobuf": {
"hashes": [
- "sha256:1f22ac0ca65bb70a876060d96d914dae09ac98d114294f77584b0d2644fa9c30",
- "sha256:237216c3326d46808a9f7c26fd1bd4b20015fb6867dc5d263a493ef9a539293b",
- "sha256:27f4d15021da6d2b706ddc3860fac0a5ddaba34ab679dc182b60a8bb4e1121cc",
- "sha256:299ea899484ee6f44604deb71f424234f654606b983cb496ea2a53e3c63ab791",
- "sha256:3d164928ff0727d97022957c2b849250ca0e64777ee31efd7d6de2e07c494717",
- "sha256:6ab80df09e3208f742c98443b6166bcb70d65f52cfeb67357d52032ea1ae9bec",
- "sha256:78a28c9fa223998472886c77042e9b9afb6fe4242bd2a2a5aced88e3f4422aa7",
- "sha256:7cd532c4566d0e6feafecc1059d04c7915aec8e182d1cf7adee8b24ef1e2e6ab",
- "sha256:89f9149e4a0169cddfc44c74f230d7743002e3aa0b9472d8c28f0388102fc4c2",
- "sha256:a53fd3f03e578553623272dc46ac2f189de23862e68565e83dde203d41b76fc5",
- "sha256:b135410244ebe777db80298297a97fbb4c862c881b4403b71bac9d4107d61fd1",
- "sha256:b98d0148f84e3a3c569e19f52103ca1feacdac0d2df8d6533cf983d1fda28462",
- "sha256:d1736130bce8cf131ac7957fa26880ca19227d4ad68b4888b3be0dea1f95df97",
- "sha256:f45460f9ee70a0ec1b6694c6e4e348ad2019275680bd68a1d9314b8c7e01e574"
+ "sha256:13233ee2b9d3bd9a5f216c1fa2c321cd564b93d8f2e4f521a85b585447747997",
+ "sha256:23452f2fdea754a8251d0fc88c0317735ae47217e0d27bf330a30eec2848811a",
+ "sha256:52f0a78141078077cfe15fe333ac3e3a077420b9a3f5d1bf9b5fe9d286b4d881",
+ "sha256:70659847ee57a5262a65954538088a1d72dfc3e9882695cab9f0c54ffe71663b",
+ "sha256:7760730063329d42a9d4c4573b804289b738d4931e363ffbe684716b796bde51",
+ "sha256:7cf56e31907c532e460bb62010a513408e6cdf5b03fb2611e4b67ed398ad046d",
+ "sha256:8b54f56d13ae4a3ec140076c9d937221f887c8f64954673d46f63751209e839a",
+ "sha256:d14fc1a41d1a1909998e8aff7e80d2a7ae14772c4a70e4bf7db8a36690b54425",
+ "sha256:d4b66266965598ff4c291416be429cef7989d8fae88b55b62095a2331511b3fa",
+ "sha256:e0e630d8e6a79f48c557cd1835865b593d0547dce221c66ed1b827de59c66c97",
+ "sha256:ecae944c6c2ce50dda6bf76ef5496196aeb1b85acb95df5843cd812615ec4b61",
+ "sha256:f08aa300b67f1c012100d8eb62d47129e53d1150f4469fd78a29fa3cb68c66f2",
+ "sha256:f2f4710543abec186aee332d6852ef5ae7ce2e9e807a3da570f36de5a732d88e"
],
"markers": "python_version >= '3.7'",
- "version": "==4.21.12"
+ "version": "==4.22.3"
},
"pyasn1": {
"hashes": [
- "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
- "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576",
- "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf",
- "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7",
- "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d",
- "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00",
- "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8",
- "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86",
- "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12",
- "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776",
- "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba",
- "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2",
- "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"
- ],
- "version": "==0.4.8"
+ "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57",
+ "sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
+ "version": "==0.5.0"
},
"pyasn1-modules": {
"hashes": [
- "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8",
- "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199",
- "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811",
- "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed",
- "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4",
- "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e",
- "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74",
- "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb",
- "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45",
- "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd",
- "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0",
- "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d",
- "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405"
- ],
- "version": "==0.2.8"
+ "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c",
+ "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
+ "version": "==0.3.0"
},
"pycparser": {
"hashes": [
@@ -1090,10 +1060,10 @@
},
"pytz": {
"hashes": [
- "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0",
- "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a"
+ "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588",
+ "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"
],
- "version": "==2022.7.1"
+ "version": "==2023.3"
},
"pyyaml": {
"hashes": [
@@ -1143,19 +1113,19 @@
},
"redis": {
"hashes": [
- "sha256:a010f6cb7378065040a02839c3f75c7e0fb37a87116fb4a95be82a95552776c7",
- "sha256:e6206448e2f8a432871d07d432c13ed6c2abcf6b74edb436c99752b1371be387"
+ "sha256:2c19e6767c474f2e85167909061d525ed65bea9301c0770bb151e041b7ac89a2",
+ "sha256:73ec35da4da267d6847e47f68730fdd5f62e2ca69e3ef5885c6a78a9374c3893"
],
"index": "pypi",
- "version": "==4.4.2"
+ "version": "==4.5.4"
},
"requests": {
"hashes": [
- "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa",
- "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"
+ "sha256:e8f3c9be120d3333921d213eef078af392fba3933ab7ed2d1cba3b56f2568c3b",
+ "sha256:f2e34a75f4749019bb0e3effb66683630e4ffeaf75819fb51bebef1bf5aef059"
],
"index": "pypi",
- "version": "==2.28.2"
+ "version": "==2.29.0"
},
"rsa": {
"hashes": [
@@ -1183,90 +1153,102 @@
},
"setuptools": {
"hashes": [
- "sha256:95f00380ef2ffa41d9bba85d95b27689d923c93dfbafed4aecd7cf988a25e012",
- "sha256:bb6d8e508de562768f2027902929f8523932fcd1fb784e6d573d2cafac995a48"
+ "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b",
+ "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990"
],
"markers": "python_version >= '3.7'",
- "version": "==67.3.2"
+ "version": "==67.7.2"
},
"simplejson": {
"hashes": [
- "sha256:04a4b9a297cccbc9e1d66fe652fbffd55b36d6579c43132e821d315957302194",
- "sha256:063db62a9251e61ea0c17e49c3e7bed465bfcc5359655abcb8c0bc6130a4e0d4",
- "sha256:070ab073ce72f1624107dfd6d095c87ac32aafe7ba54a5c5055a3dd83cb06e51",
- "sha256:099bbd3b5b4ea83159a980348cd481a34984dee5fe1b9fac31a9137158f46960",
- "sha256:0baf8c60efef74944ed4adb034d14bcf737731576f0e4c3c56fb875ea256af69",
- "sha256:0e7c3fae6c9540064e06a653780b4f263675cd69ca6841345029fee3e27e9bb5",
- "sha256:141782a0a25c1792627575b37b4951583358ccc7137623aa45947f8425ee8d96",
- "sha256:14b35fb90083218e59df5dba733c7086655f2938f3fcabe36ad849623941d660",
- "sha256:169c2c7446ef33439c304a6aa5b7b5a2dbc938c9c2dd882dd3f2553f9518ebf6",
- "sha256:16cc750d19852fa5ebafd55da86fa357f87991e07b4e2afb37a5975dfdde0153",
- "sha256:1907d49d70c75530976119c13785db91168d2599288debaca7d25da9cd2f3747",
- "sha256:1b79e2607ac5ba98381c2e068727acc1e4dd385a6d216914c0613f8f568a06a5",
- "sha256:1e49c84df6e71e3c23169d3df481565dd607cbee4aa1e0af15c493cccad7c745",
- "sha256:23fce984045804194f513a2739dcd82be350198470d5ade5058da019a48cf3f8",
- "sha256:24823364fee93bab141621b3a2e10612e31be7ca58788bf9b2cd2b1ce37ab07d",
- "sha256:290bbcdcbb37af3f7e43378f592ab7a9168fca640da6af63d42cdb535f96bbf2",
- "sha256:2a1b3222bc8f6ac91b5ebe3263111c7dc4dc4b01c52f0153f5bb1f3ef3bf0023",
- "sha256:2b0f6de11f5ce4b80f51bc49d08b898602e190547f8efe4e44af8ae3cda7779d",
- "sha256:2be75f4cb9951efeb2616e16f944ee4f9a09768475a3f5c40a6ac4dc5ee68dfd",
- "sha256:2c7ee643ee93684bf76196e2d84a2090c6df8f01737a016e869b579593827b6e",
- "sha256:37bdef13412c0bc338db2993a38f3911d5bd2a0ba8d00b3bc66d1063edd7c33e",
- "sha256:3bab9ea49ff477c926c5787f79ec47cf51c7ffb15c9d8dd0f09e728807d44f4b",
- "sha256:44d6c52d4f5c0c087a6e88a92bf9f94234321d21be32c6471ba39856e304bbe3",
- "sha256:4b8d4d958c5ab3489d1174917a7fad82da642560c39ce559a624e63deaaa36b1",
- "sha256:4de9fed1166aeedee44150fa83bc059aca6b612940281f8b5a39374781f16196",
- "sha256:502d86fbfe914263642479b87ed61af3b27b9e039df77acd2416cfccfc892e68",
- "sha256:508342d7227ed66beecfbba7a38b46e1a713faeb034216f43f03ec5c175e0622",
- "sha256:50f4b6d52f3a2d1cffd11834a1fe7f9516f0e3f20cbe78027aa88ff990fad7d6",
- "sha256:52465a5578cfc2c5e374a574df14dfb75e04c6cb6a100b7abc8bf6c89bea8f5e",
- "sha256:55aa983575b0aef143845f5bfbb35075475eccaebf7d4b30f4037a2fe8414666",
- "sha256:55df3dfd8777bf134e1078d2f195352432a77f23ccb90b92b08218123d56adc9",
- "sha256:56f186d44a9f625b5e5d9ba4b9551e263604000a7df60cb373b3e789ca603b2a",
- "sha256:5780e3929435a8d39671537174f8ce0ccafb4f6e0c748ffe139916ffbdca39d3",
- "sha256:59a629240cfbc5b4f390a8578dca74ae77ab617de971862acb946822d2eb1b11",
- "sha256:5b009342e712026ffabe8a471d5b4a4ff2a038687387e74eae601574c04dae33",
- "sha256:62628ea5df8c830d00a7417d5ecd949a1b24a8d0a5063a2a77f7ec7522110a0f",
- "sha256:694332fd6fd10fe8868c2508583220d1a1a7be9ff049dab5bd6b9aedfb9edc50",
- "sha256:6a49665169c18f27a0fc10935466332ee7406ee14ced8dc0a1b4d465547299aa",
- "sha256:6b997739fdbc9b7030ff490fc8e5f8c144b8ec80f3605eff643983672bb8cfde",
- "sha256:6bd81d10cb3384f64242316da8a2b2f88618776bc1ef38bcc79f1afe8ad36616",
- "sha256:6c4c56c5abb82e22877b913186e5c0fd7d9eef0c930719e28fa451d3f11defb4",
- "sha256:6fe1173b4146641c872bafa6f9a21f3a2012f502d54fbb523a76e6320024fae9",
- "sha256:75eb555dc349d0cbe2c95ea2be665b306c6ac6d5b64e3a3920af9b805ecdb5f7",
- "sha256:7c26fe63755ecc59c502ddde8e58ce8b765bf4fdd3f5858d2b7c8ab28bc2a9c8",
- "sha256:7e73d9d6af3c29b60a92e28b3144d951110f59a3d05fc402c3f6c5248b883400",
- "sha256:7ff65b475091084e5bdb7f26e9c555956be7355b573ce494fa96f9f8e34541ac",
- "sha256:8209c40279ed9b2cd5fbe2d617a29a074e90ea97fce7c07a0128a01cb3e8afc5",
- "sha256:88f59a07873dc1f06fd9e6712dd71286f1b297a066ad2fd9110ad080d3cb011c",
- "sha256:96ade36640734b54176c4765d00a60767bd7fae5b7a5b3574accc055ac18e34c",
- "sha256:9cf299fbb7d476676dfea372a3262654af98694bd1df35b060ce0fe1b68087f1",
- "sha256:a2960b95f3ba822d077d1afa7e1fea9799cfb2990028cf010e666f64195ecb5a",
- "sha256:a80bd9a3db88a76a401155c64e3499376c702307c2206cb381cc2a8dd9cc4f1f",
- "sha256:aad323e92cb1bd3b1db6f57c007dca964d13c52247ad844203ce381e94066601",
- "sha256:ab5bdf0b8d07f7fd603b2d0c1982412cd9f8ade997088ddced251f7e656c7fd4",
- "sha256:b0352428b35da859a98770949e7353866ae65463026f1c8e4c89a6395d4b5fd7",
- "sha256:b2c4e8b65987f3c6529149495d28e23efe213e94dc3659176c4ab22d18a9ee4a",
- "sha256:bcd9eac304a133ee4af58e68c5ded4c5ba663d3ee4602e8613359b776a1f8c8f",
- "sha256:c3b696770b504f881f271f97b94a687487ec1ef20bfbd5f20d92bbab7a85952d",
- "sha256:c4514675f6571da8190fea52a110bca686fa844972e8b2b3bc07ace9e632ee4f",
- "sha256:c98fddc374468158778a8afb3fd7296412a2b2fc34cebba64212ac3e018e7382",
- "sha256:cde5a3ff5e0bd5d6da676314dfae86c9e99bff77bca03d30223c9718a58f9e83",
- "sha256:cf7168b2046db0eceb83d8ed2ee31c0847ce18b2d8baf3e93de9560f3921a8c3",
- "sha256:d774782159347d66563cd7ac18b9dd37010438a825160cde4818caa18110a746",
- "sha256:d990ea42ba908cb57a3df97d283aa26c1822f10a0a60e250b54ee21cd08c48d0",
- "sha256:e762e9d8556fa9f3a99f8a278eeba50a35b5f554b82deeb282ddbdd85816e638",
- "sha256:e8a4750e8db92109e6f1f7783a7faae4254d6d5dc28a41ff7eff7d2265f0586b",
- "sha256:eb81cfef0c0039010f0212f4e5eb6909641b8a54c761584054ac97fd7bd0c21a",
- "sha256:ebb53837c5ffcb6100646018565d3f1afed6f4b185b14b2c9cbccf874fe40157",
- "sha256:efa70fd9b6c7b57b048ecadb909683acd535cddebc5b22f3c05ba3b369739caf",
- "sha256:f73bae5e315adf7bc8cb7f0a13a1e9e33bead42e8ce174be83ac9ecc2513c86a",
- "sha256:f89f078114cacedb9a3392615cc099cf02a51efa7507f90e2006bf7ec38c880d",
- "sha256:f9f72d2b539512f382a48cc9ad6cea2d3a572e71e92c40e03d2140041eeaa233",
- "sha256:fc8df5831b645e96a318ea51a66ce6e2bb869eebc3fa9a860bbf67aecd270055"
- ],
- "index": "pypi",
- "version": "==3.18.3"
+ "sha256:081ea6305b3b5e84ae7417e7f45956db5ea3872ec497a584ec86c3260cda049e",
+ "sha256:08be5a241fdf67a8e05ac7edbd49b07b638ebe4846b560673e196b2a25c94b92",
+ "sha256:0c16ec6a67a5f66ab004190829eeede01c633936375edcad7cbf06d3241e5865",
+ "sha256:0ccb2c1877bc9b25bc4f4687169caa925ffda605d7569c40e8e95186e9a5e58b",
+ "sha256:17a963e8dd4d81061cc05b627677c1f6a12e81345111fbdc5708c9f088d752c9",
+ "sha256:199a0bcd792811c252d71e3eabb3d4a132b3e85e43ebd93bfd053d5b59a7e78b",
+ "sha256:1cb19eacb77adc5a9720244d8d0b5507421d117c7ed4f2f9461424a1829e0ceb",
+ "sha256:203412745fed916fc04566ecef3f2b6c872b52f1e7fb3a6a84451b800fb508c1",
+ "sha256:2098811cd241429c08b7fc5c9e41fcc3f59f27c2e8d1da2ccdcf6c8e340ab507",
+ "sha256:22b867205cd258050c2625325fdd9a65f917a5aff22a23387e245ecae4098e78",
+ "sha256:23fbb7b46d44ed7cbcda689295862851105c7594ae5875dce2a70eeaa498ff86",
+ "sha256:2541fdb7467ef9bfad1f55b6c52e8ea52b3ce4a0027d37aff094190a955daa9d",
+ "sha256:3231100edee292da78948fa0a77dee4e5a94a0a60bcba9ed7a9dc77f4d4bb11e",
+ "sha256:344a5093b71c1b370968d0fbd14d55c9413cb6f0355fdefeb4a322d602d21776",
+ "sha256:37724c634f93e5caaca04458f267836eb9505d897ab3947b52f33b191bf344f3",
+ "sha256:3844305bc33d52c4975da07f75b480e17af3558c0d13085eaa6cc2f32882ccf7",
+ "sha256:390f4a8ca61d90bcf806c3ad644e05fa5890f5b9a72abdd4ca8430cdc1e386fa",
+ "sha256:3a4480e348000d89cf501b5606415f4d328484bbb431146c2971123d49fd8430",
+ "sha256:3b652579c21af73879d99c8072c31476788c8c26b5565687fd9db154070d852a",
+ "sha256:3e0902c278243d6f7223ba3e6c5738614c971fd9a887fff8feaa8dcf7249c8d4",
+ "sha256:412e58997a30c5deb8cab5858b8e2e5b40ca007079f7010ee74565cc13d19665",
+ "sha256:44cdb4e544134f305b033ad79ae5c6b9a32e7c58b46d9f55a64e2a883fbbba01",
+ "sha256:46133bc7dd45c9953e6ee4852e3de3d5a9a4a03b068bd238935a5c72f0a1ce34",
+ "sha256:46e89f58e4bed107626edce1cf098da3664a336d01fc78fddcfb1f397f553d44",
+ "sha256:4710806eb75e87919b858af0cba4ffedc01b463edc3982ded7b55143f39e41e1",
+ "sha256:476c8033abed7b1fd8db62a7600bf18501ce701c1a71179e4ce04ac92c1c5c3c",
+ "sha256:48600a6e0032bed17c20319d91775f1797d39953ccfd68c27f83c8d7fc3b32cb",
+ "sha256:4d3025e7e9ddb48813aec2974e1a7e68e63eac911dd5e0a9568775de107ac79a",
+ "sha256:547ea86ca408a6735335c881a2e6208851027f5bfd678d8f2c92a0f02c7e7330",
+ "sha256:54fca2b26bcd1c403146fd9461d1da76199442297160721b1d63def2a1b17799",
+ "sha256:5673d27806085d2a413b3be5f85fad6fca4b7ffd31cfe510bbe65eea52fff571",
+ "sha256:58ee5e24d6863b22194020eb62673cf8cc69945fcad6b283919490f6e359f7c5",
+ "sha256:5ca922c61d87b4c38f37aa706520328ffe22d7ac1553ef1cadc73f053a673553",
+ "sha256:5db86bb82034e055257c8e45228ca3dbce85e38d7bfa84fa7b2838e032a3219c",
+ "sha256:6277f60848a7d8319d27d2be767a7546bc965535b28070e310b3a9af90604a4c",
+ "sha256:6424d8229ba62e5dbbc377908cfee9b2edf25abd63b855c21f12ac596cd18e41",
+ "sha256:65dafe413b15e8895ad42e49210b74a955c9ae65564952b0243a18fb35b986cc",
+ "sha256:66389b6b6ee46a94a493a933a26008a1bae0cfadeca176933e7ff6556c0ce998",
+ "sha256:66d780047c31ff316ee305c3f7550f352d87257c756413632303fc59fef19eac",
+ "sha256:69a8b10a4f81548bc1e06ded0c4a6c9042c0be0d947c53c1ed89703f7e613950",
+ "sha256:6a561320485017ddfc21bd2ed5de2d70184f754f1c9b1947c55f8e2b0163a268",
+ "sha256:6aa7ca03f25b23b01629b1c7f78e1cd826a66bfb8809f8977a3635be2ec48f1a",
+ "sha256:6b79642a599740603ca86cf9df54f57a2013c47e1dd4dd2ae4769af0a6816900",
+ "sha256:6e7c70f19405e5f99168077b785fe15fcb5f9b3c0b70b0b5c2757ce294922c8c",
+ "sha256:70128fb92932524c89f373e17221cf9535d7d0c63794955cc3cd5868e19f5d38",
+ "sha256:73d0904c2471f317386d4ae5c665b16b5c50ab4f3ee7fd3d3b7651e564ad74b1",
+ "sha256:74bf802debe68627227ddb665c067eb8c73aa68b2476369237adf55c1161b728",
+ "sha256:79c748aa61fd8098d0472e776743de20fae2686edb80a24f0f6593a77f74fe86",
+ "sha256:79d46e7e33c3a4ef853a1307b2032cfb7220e1a079d0c65488fbd7118f44935a",
+ "sha256:7e78d79b10aa92f40f54178ada2b635c960d24fc6141856b926d82f67e56d169",
+ "sha256:8090e75653ea7db75bc21fa5f7bcf5f7bdf64ea258cbbac45c7065f6324f1b50",
+ "sha256:87b190e6ceec286219bd6b6f13547ca433f977d4600b4e81739e9ac23b5b9ba9",
+ "sha256:889328873c35cb0b2b4c83cbb83ec52efee5a05e75002e2c0c46c4e42790e83c",
+ "sha256:8f8d179393e6f0cf6c7c950576892ea6acbcea0a320838c61968ac7046f59228",
+ "sha256:919bc5aa4d8094cf8f1371ea9119e5d952f741dc4162810ab714aec948a23fe5",
+ "sha256:926957b278de22797bfc2f004b15297013843b595b3cd7ecd9e37ccb5fad0b72",
+ "sha256:93f5ac30607157a0b2579af59a065bcfaa7fadeb4875bf927a8f8b6739c8d910",
+ "sha256:96ade243fb6f3b57e7bd3b71e90c190cd0f93ec5dce6bf38734a73a2e5fa274f",
+ "sha256:9f14ecca970d825df0d29d5c6736ff27999ee7bdf5510e807f7ad8845f7760ce",
+ "sha256:a755f7bfc8adcb94887710dc70cc12a69a454120c6adcc6f251c3f7b46ee6aac",
+ "sha256:a79b439a6a77649bb8e2f2644e6c9cc0adb720fc55bed63546edea86e1d5c6c8",
+ "sha256:aa9d614a612ad02492f704fbac636f666fa89295a5d22b4facf2d665fc3b5ea9",
+ "sha256:ad071cd84a636195f35fa71de2186d717db775f94f985232775794d09f8d9061",
+ "sha256:b0e9a5e66969f7a47dc500e3dba8edc3b45d4eb31efb855c8647700a3493dd8a",
+ "sha256:b438e5eaa474365f4faaeeef1ec3e8d5b4e7030706e3e3d6b5bee6049732e0e6",
+ "sha256:b46aaf0332a8a9c965310058cf3487d705bf672641d2c43a835625b326689cf4",
+ "sha256:c39fa911e4302eb79c804b221ddec775c3da08833c0a9120041dd322789824de",
+ "sha256:ca56a6c8c8236d6fe19abb67ef08d76f3c3f46712c49a3b6a5352b6e43e8855f",
+ "sha256:cb502cde018e93e75dc8fc7bb2d93477ce4f3ac10369f48866c61b5e031db1fd",
+ "sha256:cd4d50a27b065447c9c399f0bf0a993bd0e6308db8bbbfbc3ea03b41c145775a",
+ "sha256:d125e754d26c0298715bdc3f8a03a0658ecbe72330be247f4b328d229d8cf67f",
+ "sha256:d300773b93eed82f6da138fd1d081dc96fbe53d96000a85e41460fe07c8d8b33",
+ "sha256:d396b610e77b0c438846607cd56418bfc194973b9886550a98fd6724e8c6cfec",
+ "sha256:d61482b5d18181e6bb4810b4a6a24c63a490c3a20e9fbd7876639653e2b30a1a",
+ "sha256:d9f2c27f18a0b94107d57294aab3d06d6046ea843ed4a45cae8bd45756749f3a",
+ "sha256:dc2b3f06430cbd4fac0dae5b2974d2bf14f71b415fb6de017f498950da8159b1",
+ "sha256:dc935d8322ba9bc7b84f99f40f111809b0473df167bf5b93b89fb719d2c4892b",
+ "sha256:e333c5b62e93949f5ac27e6758ba53ef6ee4f93e36cc977fe2e3df85c02f6dc4",
+ "sha256:e765b1f47293dedf77946f0427e03ee45def2862edacd8868c6cf9ab97c8afbd",
+ "sha256:ed18728b90758d171f0c66c475c24a443ede815cf3f1a91e907b0db0ebc6e508",
+ "sha256:eff87c68058374e45225089e4538c26329a13499bc0104b52b77f8428eed36b2",
+ "sha256:f05d05d99fce5537d8f7a0af6417a9afa9af3a6c4bb1ba7359c53b6257625fcb",
+ "sha256:f253edf694ce836631b350d758d00a8c4011243d58318fbfbe0dd54a6a839ab4",
+ "sha256:f41915a4e1f059dfad614b187bc06021fefb5fc5255bfe63abf8247d2f7a646a",
+ "sha256:f96def94576f857abf58e031ce881b5a3fc25cbec64b2bc4824824a8a4367af9"
+ ],
+ "index": "pypi",
+ "version": "==3.19.1"
},
"six": {
"hashes": [
@@ -1278,11 +1260,11 @@
},
"structlog": {
"hashes": [
- "sha256:b403f344f902b220648fa9f286a23c0cc5439a5844d271fec40562dbadbc70ad",
- "sha256:e7509391f215e4afb88b1b80fa3ea074be57a5a17d794bd436a5c949da023333"
+ "sha256:270d681dd7d163c11ba500bc914b2472d2b50a8ef00faa999ded5ff83a2f906b",
+ "sha256:79b9e68e48b54e373441e130fa447944e6f87a05b35de23138e475c05d0f7e0e"
],
"index": "pypi",
- "version": "==22.3.0"
+ "version": "==23.1.0"
},
"ua-parser": {
"hashes": [
@@ -1294,11 +1276,11 @@
},
"urllib3": {
"hashes": [
- "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72",
- "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"
+ "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305",
+ "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
- "version": "==1.26.14"
+ "version": "==1.26.15"
},
"uwsgi": {
"hashes": [
@@ -1309,81 +1291,92 @@
},
"werkzeug": {
"hashes": [
- "sha256:2e1ccc9417d4da358b9de6f174e3ac094391ea1d4fbef2d667865d819dfd0afe",
- "sha256:56433961bc1f12533306c624f3be5e744389ac61d722175d543e1751285da612"
+ "sha256:4866679a0722de00796a74086238bb3b98d90f423f05de039abb09315487254a",
+ "sha256:a987caf1092edc7523edb139edb20c70571c4a8d5eed02e0b547b4739174d091"
],
- "markers": "python_version >= '3.7'",
- "version": "==2.2.3"
+ "markers": "python_version >= '3.8'",
+ "version": "==2.3.3"
},
"wrapt": {
"hashes": [
- "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3",
- "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b",
- "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4",
- "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2",
- "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656",
- "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3",
- "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff",
- "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310",
- "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a",
- "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57",
- "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069",
- "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383",
- "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe",
- "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87",
- "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d",
- "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b",
- "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907",
- "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f",
- "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0",
- "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28",
- "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1",
- "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853",
- "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc",
- "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3",
- "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3",
- "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164",
- "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1",
- "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c",
- "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1",
- "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7",
- "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1",
- "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320",
- "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed",
- "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1",
- "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248",
- "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c",
- "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456",
- "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77",
- "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef",
- "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1",
- "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7",
- "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86",
- "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4",
- "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d",
- "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d",
- "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8",
- "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5",
- "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471",
- "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00",
- "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68",
- "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3",
- "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d",
- "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735",
- "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d",
- "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569",
- "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7",
- "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59",
- "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5",
- "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb",
- "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b",
- "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f",
- "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462",
- "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015",
- "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"
+ "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0",
+ "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420",
+ "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a",
+ "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c",
+ "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079",
+ "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923",
+ "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f",
+ "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1",
+ "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8",
+ "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86",
+ "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0",
+ "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364",
+ "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e",
+ "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c",
+ "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e",
+ "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c",
+ "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727",
+ "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff",
+ "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e",
+ "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29",
+ "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7",
+ "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72",
+ "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475",
+ "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a",
+ "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317",
+ "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2",
+ "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd",
+ "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640",
+ "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98",
+ "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248",
+ "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e",
+ "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d",
+ "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec",
+ "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1",
+ "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e",
+ "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9",
+ "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92",
+ "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb",
+ "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094",
+ "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46",
+ "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29",
+ "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd",
+ "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705",
+ "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8",
+ "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975",
+ "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb",
+ "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e",
+ "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b",
+ "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418",
+ "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019",
+ "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1",
+ "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba",
+ "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6",
+ "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2",
+ "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3",
+ "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7",
+ "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752",
+ "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416",
+ "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f",
+ "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1",
+ "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc",
+ "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145",
+ "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee",
+ "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a",
+ "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7",
+ "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b",
+ "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653",
+ "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0",
+ "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90",
+ "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29",
+ "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6",
+ "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034",
+ "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09",
+ "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559",
+ "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==1.14.1"
+ "version": "==1.15.0"
},
"wtforms": {
"hashes": [
@@ -1402,126 +1395,120 @@
},
"zope.interface": {
"hashes": [
- "sha256:008b0b65c05993bb08912f644d140530e775cf1c62a072bf9340c2249e613c32",
- "sha256:0217a9615531c83aeedb12e126611b1b1a3175013bbafe57c702ce40000eb9a0",
- "sha256:0fb497c6b088818e3395e302e426850f8236d8d9f4ef5b2836feae812a8f699c",
- "sha256:17ebf6e0b1d07ed009738016abf0d0a0f80388e009d0ac6e0ead26fc162b3b9c",
- "sha256:311196634bb9333aa06f00fc94f59d3a9fddd2305c2c425d86e406ddc6f2260d",
- "sha256:3218ab1a7748327e08ef83cca63eea7cf20ea7e2ebcb2522072896e5e2fceedf",
- "sha256:404d1e284eda9e233c90128697c71acffd55e183d70628aa0bbb0e7a3084ed8b",
- "sha256:4087e253bd3bbbc3e615ecd0b6dd03c4e6a1e46d152d3be6d2ad08fbad742dcc",
- "sha256:40f4065745e2c2fa0dff0e7ccd7c166a8ac9748974f960cd39f63d2c19f9231f",
- "sha256:5334e2ef60d3d9439c08baedaf8b84dc9bb9522d0dacbc10572ef5609ef8db6d",
- "sha256:604cdba8f1983d0ab78edc29aa71c8df0ada06fb147cea436dc37093a0100a4e",
- "sha256:6373d7eb813a143cb7795d3e42bd8ed857c82a90571567e681e1b3841a390d16",
- "sha256:655796a906fa3ca67273011c9805c1e1baa047781fca80feeb710328cdbed87f",
- "sha256:65c3c06afee96c654e590e046c4a24559e65b0a87dbff256cd4bd6f77e1a33f9",
- "sha256:696f3d5493eae7359887da55c2afa05acc3db5fc625c49529e84bd9992313296",
- "sha256:6e972493cdfe4ad0411fd9abfab7d4d800a7317a93928217f1a5de2bb0f0d87a",
- "sha256:7579960be23d1fddecb53898035a0d112ac858c3554018ce615cefc03024e46d",
- "sha256:765d703096ca47aa5d93044bf701b00bbce4d903a95b41fff7c3796e747b1f1d",
- "sha256:7e66f60b0067a10dd289b29dceabd3d0e6d68be1504fc9d0bc209cf07f56d189",
- "sha256:8a2ffadefd0e7206adc86e492ccc60395f7edb5680adedf17a7ee4205c530df4",
- "sha256:959697ef2757406bff71467a09d940ca364e724c534efbf3786e86eee8591452",
- "sha256:9d783213fab61832dbb10d385a319cb0e45451088abd45f95b5bb88ed0acca1a",
- "sha256:a16025df73d24795a0bde05504911d306307c24a64187752685ff6ea23897cb0",
- "sha256:a2ad597c8c9e038a5912ac3cf166f82926feff2f6e0dabdab956768de0a258f5",
- "sha256:bfee1f3ff62143819499e348f5b8a7f3aa0259f9aca5e0ddae7391d059dce671",
- "sha256:d169ccd0756c15bbb2f1acc012f5aab279dffc334d733ca0d9362c5beaebe88e",
- "sha256:d514c269d1f9f5cd05ddfed15298d6c418129f3f064765295659798349c43e6f",
- "sha256:d692374b578360d36568dd05efb8a5a67ab6d1878c29c582e37ddba80e66c396",
- "sha256:dbaeb9cf0ea0b3bc4b36fae54a016933d64c6d52a94810a63c00f440ecb37dd7",
- "sha256:dc26c8d44472e035d59d6f1177eb712888447f5799743da9c398b0339ed90b1b",
- "sha256:e1574980b48c8c74f83578d1e77e701f8439a5d93f36a5a0af31337467c08fcf",
- "sha256:e74a578172525c20d7223eac5f8ad187f10940dac06e40113d62f14f3adb1e8f",
- "sha256:e945de62917acbf853ab968d8916290548df18dd62c739d862f359ecd25842a6",
- "sha256:f0980d44b8aded808bec5059018d64692f0127f10510eca71f2f0ace8fb11188",
- "sha256:f98d4bd7bbb15ca701d19b93263cc5edfd480c3475d163f137385f49e5b3a3a7",
- "sha256:fb68d212efd057596dee9e6582daded9f8ef776538afdf5feceb3059df2d2e7b"
+ "sha256:042f2381118b093714081fd82c98e3b189b68db38ee7d35b63c327c470ef8373",
+ "sha256:0ec9653825f837fbddc4e4b603d90269b501486c11800d7c761eee7ce46d1bbb",
+ "sha256:12175ca6b4db7621aedd7c30aa7cfa0a2d65ea3a0105393e05482d7a2d367446",
+ "sha256:1592f68ae11e557b9ff2bc96ac8fc30b187e77c45a3c9cd876e3368c53dc5ba8",
+ "sha256:23ac41d52fd15dd8be77e3257bc51bbb82469cf7f5e9a30b75e903e21439d16c",
+ "sha256:424d23b97fa1542d7be882eae0c0fc3d6827784105264a8169a26ce16db260d8",
+ "sha256:4407b1435572e3e1610797c9203ad2753666c62883b921318c5403fb7139dec2",
+ "sha256:48f4d38cf4b462e75fac78b6f11ad47b06b1c568eb59896db5b6ec1094eb467f",
+ "sha256:4c3d7dfd897a588ec27e391edbe3dd320a03684457470415870254e714126b1f",
+ "sha256:5171eb073474a5038321409a630904fd61f12dd1856dd7e9d19cd6fe092cbbc5",
+ "sha256:5a158846d0fca0a908c1afb281ddba88744d403f2550dc34405c3691769cdd85",
+ "sha256:6ee934f023f875ec2cfd2b05a937bd817efcc6c4c3f55c5778cbf78e58362ddc",
+ "sha256:790c1d9d8f9c92819c31ea660cd43c3d5451df1df61e2e814a6f99cebb292788",
+ "sha256:809fe3bf1a91393abc7e92d607976bbb8586512913a79f2bf7d7ec15bd8ea518",
+ "sha256:87b690bbee9876163210fd3f500ee59f5803e4a6607d1b1238833b8885ebd410",
+ "sha256:89086c9d3490a0f265a3c4b794037a84541ff5ffa28bb9c24cc9f66566968464",
+ "sha256:99856d6c98a326abbcc2363827e16bd6044f70f2ef42f453c0bd5440c4ce24e5",
+ "sha256:aab584725afd10c710b8f1e6e208dbee2d0ad009f57d674cb9d1b3964037275d",
+ "sha256:af169ba897692e9cd984a81cb0f02e46dacdc07d6cf9fd5c91e81f8efaf93d52",
+ "sha256:b39b8711578dcfd45fc0140993403b8a81e879ec25d53189f3faa1f006087dca",
+ "sha256:b3f543ae9d3408549a9900720f18c0194ac0fe810cecda2a584fd4dca2eb3bb8",
+ "sha256:d0583b75f2e70ec93f100931660328965bb9ff65ae54695fb3fa0a1255daa6f2",
+ "sha256:dfbbbf0809a3606046a41f8561c3eada9db811be94138f42d9135a5c47e75f6f",
+ "sha256:e538f2d4a6ffb6edfb303ce70ae7e88629ac6e5581870e66c306d9ad7b564a58",
+ "sha256:eba51599370c87088d8882ab74f637de0c4f04a6d08a312dce49368ba9ed5c2a",
+ "sha256:ee4b43f35f5dc15e1fec55ccb53c130adb1d11e8ad8263d68b1284b66a04190d",
+ "sha256:f2363e5fd81afb650085c6686f2ee3706975c54f331b426800b53531191fdf28",
+ "sha256:f299c020c6679cb389814a3b81200fe55d428012c5e76da7e722491f5d205990",
+ "sha256:f72f23bab1848edb7472309e9898603141644faec9fd57a823ea6b4d1c4c8995",
+ "sha256:fa90bac61c9dc3e1a563e5babb3fd2c0c1c80567e815442ddbe561eadc803b30"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==5.5.2"
+ "markers": "python_version >= '3.7'",
+ "version": "==6.0"
}
},
"develop": {
"astroid": {
"hashes": [
- "sha256:0e0e3709d64fbffd3037e4ff403580550f14471fd3eaae9fa11cc9a5c7901153",
- "sha256:a3cf9f02c53dd259144a7e8f3ccd75d67c9a8c716ef183e0c1f291bc5d7bb3cf"
+ "sha256:a1b8543ef9d36ea777194bc9b17f5f8678d2c56ee6a45b2c2f17eec96f242347",
+ "sha256:c81e1c7fbac615037744d067a9bb5f9aeb655edf59b63ee8b59585475d6f80d8"
],
"markers": "python_full_version >= '3.7.2'",
- "version": "==2.14.2"
+ "version": "==2.15.4"
},
"async-timeout": {
"hashes": [
"sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15",
"sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"
],
- "markers": "python_version >= '3.6'",
+ "markers": "python_full_version <= '3.11.2'",
"version": "==4.0.2"
},
"attrs": {
"hashes": [
- "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836",
- "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"
+ "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04",
+ "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"
],
- "markers": "python_version >= '3.6'",
- "version": "==22.2.0"
+ "markers": "python_version >= '3.7'",
+ "version": "==23.1.0"
},
"beautifulsoup4": {
"hashes": [
- "sha256:0e79446b10b3ecb499c1556f7e228a53e64a2bfcebd455f370d8927cb5b59e39",
- "sha256:bc4bdda6717de5a2987436fb8d72f45dc90dd856bdfd512a1314ce90349a0106"
+ "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da",
+ "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"
],
"index": "pypi",
- "version": "==4.11.2"
+ "version": "==4.12.2"
},
"black": {
"hashes": [
- "sha256:0052dba51dec07ed029ed61b18183942043e00008ec65d5028814afaab9a22fd",
- "sha256:0680d4380db3719ebcfb2613f34e86c8e6d15ffeabcf8ec59355c5e7b85bb555",
- "sha256:121ca7f10b4a01fd99951234abdbd97728e1240be89fde18480ffac16503d481",
- "sha256:162e37d49e93bd6eb6f1afc3e17a3d23a823042530c37c3c42eeeaf026f38468",
- "sha256:2a951cc83ab535d248c89f300eccbd625e80ab880fbcfb5ac8afb5f01a258ac9",
- "sha256:2bf649fda611c8550ca9d7592b69f0637218c2369b7744694c5e4902873b2f3a",
- "sha256:382998821f58e5c8238d3166c492139573325287820963d2f7de4d518bd76958",
- "sha256:49f7b39e30f326a34b5c9a4213213a6b221d7ae9d58ec70df1c4a307cf2a1580",
- "sha256:57c18c5165c1dbe291d5306e53fb3988122890e57bd9b3dcb75f967f13411a26",
- "sha256:7a0f701d314cfa0896b9001df70a530eb2472babb76086344e688829efd97d32",
- "sha256:8178318cb74f98bc571eef19068f6ab5613b3e59d4f47771582f04e175570ed8",
- "sha256:8b70eb40a78dfac24842458476135f9b99ab952dd3f2dab738c1881a9b38b753",
- "sha256:9880d7d419bb7e709b37e28deb5e68a49227713b623c72b2b931028ea65f619b",
- "sha256:9afd3f493666a0cd8f8df9a0200c6359ac53940cbde049dcb1a7eb6ee2dd7074",
- "sha256:a29650759a6a0944e7cca036674655c2f0f63806ddecc45ed40b7b8aa314b651",
- "sha256:a436e7881d33acaf2536c46a454bb964a50eff59b21b51c6ccf5a40601fbef24",
- "sha256:a59db0a2094d2259c554676403fa2fac3473ccf1354c1c63eccf7ae65aac8ab6",
- "sha256:a8471939da5e824b891b25751955be52ee7f8a30a916d570a5ba8e0f2eb2ecad",
- "sha256:b0bd97bea8903f5a2ba7219257a44e3f1f9d00073d6cc1add68f0beec69692ac",
- "sha256:b6a92a41ee34b883b359998f0c8e6eb8e99803aa8bf3123bf2b2e6fec505a221",
- "sha256:bb460c8561c8c1bec7824ecbc3ce085eb50005883a6203dcfb0122e95797ee06",
- "sha256:bfffba28dc52a58f04492181392ee380e95262af14ee01d4bc7bb1b1c6ca8d27",
- "sha256:c1c476bc7b7d021321e7d93dc2cbd78ce103b84d5a4cf97ed535fbc0d6660648",
- "sha256:c91dfc2c2a4e50df0026f88d2215e166616e0c80e86004d0003ece0488db2739",
- "sha256:e6663f91b6feca5d06f2ccd49a10f254f9298cc1f7f49c46e498a0771b507104"
- ],
- "index": "pypi",
- "version": "==23.1.0"
+ "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5",
+ "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915",
+ "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326",
+ "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940",
+ "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b",
+ "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30",
+ "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c",
+ "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c",
+ "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab",
+ "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27",
+ "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2",
+ "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961",
+ "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9",
+ "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb",
+ "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70",
+ "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331",
+ "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2",
+ "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266",
+ "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d",
+ "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6",
+ "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b",
+ "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925",
+ "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8",
+ "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4",
+ "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"
+ ],
+ "index": "pypi",
+ "version": "==23.3.0"
},
"boto3": {
"hashes": [
- "sha256:9e23ec51e1fe162ecb5efee41bd9346d90ebbb8ff094ef8088223305d48ffaec",
- "sha256:9fc94b3078f5047c1fc40529fa12eb82a2432bb1dca0ee37b1c54f902f46191f"
+ "sha256:6648aff15d19927cd26db47eb56362ccd313a1ddbd7aaa3235ef05d05d398252",
+ "sha256:fe8248b80c4f0fdaed8b8779467c4431a5e52177e02ccd137d51ec51194ebb00"
],
"index": "pypi",
- "version": "==1.26.65"
+ "version": "==1.26.125"
},
"botocore": {
"hashes": [
- "sha256:77fff109e1bdbf030d8400ccab9d28454c03c7be62c1696a239b21792b0873df",
- "sha256:8710f53af0e20f08f36a3bf434d18bc7ceba5d9835495b02aedbedd35df5de9a"
+ "sha256:3005a7ffee083315e69938acdf1bfeaf9e21fe1fe1643d6573ee817721f4ffcd",
+ "sha256:ac87b63e9aa4231cd28941945024a0c4470c184c60334ebe5e1cae3544c785ed"
],
"markers": "python_version >= '3.7'",
- "version": "==1.29.72"
+ "version": "==1.29.125"
},
"certifi": {
"hashes": [
@@ -1602,97 +1589,84 @@
},
"charset-normalizer": {
"hashes": [
- "sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b",
- "sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42",
- "sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d",
- "sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b",
- "sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a",
- "sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59",
- "sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154",
- "sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1",
- "sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c",
- "sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a",
- "sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d",
- "sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6",
- "sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b",
- "sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b",
- "sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783",
- "sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5",
- "sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918",
- "sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555",
- "sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639",
- "sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786",
- "sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e",
- "sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed",
- "sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820",
- "sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8",
- "sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3",
- "sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541",
- "sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14",
- "sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be",
- "sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e",
- "sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76",
- "sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b",
- "sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c",
- "sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b",
- "sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3",
- "sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc",
- "sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6",
- "sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59",
- "sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4",
- "sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d",
- "sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d",
- "sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3",
- "sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a",
- "sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea",
- "sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6",
- "sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e",
- "sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603",
- "sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24",
- "sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a",
- "sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58",
- "sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678",
- "sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a",
- "sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c",
- "sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6",
- "sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18",
- "sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174",
- "sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317",
- "sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f",
- "sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc",
- "sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837",
- "sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41",
- "sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c",
- "sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579",
- "sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753",
- "sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8",
- "sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291",
- "sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087",
- "sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866",
- "sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3",
- "sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d",
- "sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1",
- "sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca",
- "sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e",
- "sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db",
- "sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72",
- "sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d",
- "sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc",
- "sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539",
- "sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d",
- "sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af",
- "sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b",
- "sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602",
- "sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f",
- "sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478",
- "sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c",
- "sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e",
- "sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479",
- "sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7",
- "sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8"
- ],
- "markers": "python_full_version >= '3.6.0'",
- "version": "==3.0.1"
+ "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6",
+ "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1",
+ "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e",
+ "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373",
+ "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62",
+ "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230",
+ "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be",
+ "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c",
+ "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0",
+ "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448",
+ "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f",
+ "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649",
+ "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d",
+ "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0",
+ "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706",
+ "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a",
+ "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59",
+ "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23",
+ "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5",
+ "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb",
+ "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e",
+ "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e",
+ "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c",
+ "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28",
+ "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d",
+ "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41",
+ "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974",
+ "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce",
+ "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f",
+ "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1",
+ "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d",
+ "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8",
+ "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017",
+ "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31",
+ "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7",
+ "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8",
+ "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e",
+ "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14",
+ "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd",
+ "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d",
+ "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795",
+ "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b",
+ "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b",
+ "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b",
+ "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203",
+ "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f",
+ "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19",
+ "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1",
+ "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a",
+ "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac",
+ "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9",
+ "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0",
+ "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137",
+ "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f",
+ "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6",
+ "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5",
+ "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909",
+ "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f",
+ "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0",
+ "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324",
+ "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755",
+ "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb",
+ "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854",
+ "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c",
+ "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60",
+ "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84",
+ "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0",
+ "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b",
+ "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1",
+ "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531",
+ "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1",
+ "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11",
+ "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326",
+ "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df",
+ "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"
+ ],
+ "markers": "python_full_version >= '3.7.0'",
+ "version": "==3.1.0"
},
"click": {
"hashes": [
@@ -1707,89 +1681,85 @@
"toml"
],
"hashes": [
- "sha256:04481245ef966fbd24ae9b9e537ce899ae584d521dfbe78f89cad003c38ca2ab",
- "sha256:0c45948f613d5d18c9ec5eaa203ce06a653334cf1bd47c783a12d0dd4fd9c851",
- "sha256:10188fe543560ec4874f974b5305cd1a8bdcfa885ee00ea3a03733464c4ca265",
- "sha256:218fe982371ac7387304153ecd51205f14e9d731b34fb0568181abaf7b443ba0",
- "sha256:29571503c37f2ef2138a306d23e7270687c0efb9cab4bd8038d609b5c2393a3a",
- "sha256:2a60d6513781e87047c3e630b33b4d1e89f39836dac6e069ffee28c4786715f5",
- "sha256:2bf1d5f2084c3932b56b962a683074a3692bce7cabd3aa023c987a2a8e7612f6",
- "sha256:3164d31078fa9efe406e198aecd2a02d32a62fecbdef74f76dad6a46c7e48311",
- "sha256:32df215215f3af2c1617a55dbdfb403b772d463d54d219985ac7cd3bf124cada",
- "sha256:33d1ae9d4079e05ac4cc1ef9e20c648f5afabf1a92adfaf2ccf509c50b85717f",
- "sha256:33ff26d0f6cc3ca8de13d14fde1ff8efe1456b53e3f0273e63cc8b3c84a063d8",
- "sha256:38da2db80cc505a611938d8624801158e409928b136c8916cd2e203970dde4dc",
- "sha256:3b155caf3760408d1cb903b21e6a97ad4e2bdad43cbc265e3ce0afb8e0057e73",
- "sha256:3b946bbcd5a8231383450b195cfb58cb01cbe7f8949f5758566b881df4b33baf",
- "sha256:3baf5f126f30781b5e93dbefcc8271cb2491647f8283f20ac54d12161dff080e",
- "sha256:4b14d5e09c656de5038a3f9bfe5228f53439282abcab87317c9f7f1acb280352",
- "sha256:51b236e764840a6df0661b67e50697aaa0e7d4124ca95e5058fa3d7cbc240b7c",
- "sha256:63ffd21aa133ff48c4dff7adcc46b7ec8b565491bfc371212122dd999812ea1c",
- "sha256:6a43c7823cd7427b4ed763aa7fb63901ca8288591323b58c9cd6ec31ad910f3c",
- "sha256:755e89e32376c850f826c425ece2c35a4fc266c081490eb0a841e7c1cb0d3bda",
- "sha256:7a726d742816cb3a8973c8c9a97539c734b3a309345236cd533c4883dda05b8d",
- "sha256:7c7c0d0827e853315c9bbd43c1162c006dd808dbbe297db7ae66cd17b07830f0",
- "sha256:7ed681b0f8e8bcbbffa58ba26fcf5dbc8f79e7997595bf071ed5430d8c08d6f3",
- "sha256:7ee5c9bb51695f80878faaa5598040dd6c9e172ddcf490382e8aedb8ec3fec8d",
- "sha256:8361be1c2c073919500b6601220a6f2f98ea0b6d2fec5014c1d9cfa23dd07038",
- "sha256:8ae125d1134bf236acba8b83e74c603d1b30e207266121e76484562bc816344c",
- "sha256:9817733f0d3ea91bea80de0f79ef971ae94f81ca52f9b66500c6a2fea8e4b4f8",
- "sha256:98b85dd86514d889a2e3dd22ab3c18c9d0019e696478391d86708b805f4ea0fa",
- "sha256:9ccb092c9ede70b2517a57382a601619d20981f56f440eae7e4d7eaafd1d1d09",
- "sha256:9d58885215094ab4a86a6aef044e42994a2bd76a446dc59b352622655ba6621b",
- "sha256:b643cb30821e7570c0aaf54feaf0bfb630b79059f85741843e9dc23f33aaca2c",
- "sha256:bc7c85a150501286f8b56bd8ed3aa4093f4b88fb68c0843d21ff9656f0009d6a",
- "sha256:beeb129cacea34490ffd4d6153af70509aa3cda20fdda2ea1a2be870dfec8d52",
- "sha256:c31b75ae466c053a98bf26843563b3b3517b8f37da4d47b1c582fdc703112bc3",
- "sha256:c4e4881fa9e9667afcc742f0c244d9364d197490fbc91d12ac3b5de0bf2df146",
- "sha256:c5b15ed7644ae4bee0ecf74fee95808dcc34ba6ace87e8dfbf5cb0dc20eab45a",
- "sha256:d12d076582507ea460ea2a89a8c85cb558f83406c8a41dd641d7be9a32e1274f",
- "sha256:d248cd4a92065a4d4543b8331660121b31c4148dd00a691bfb7a5cdc7483cfa4",
- "sha256:d47dd659a4ee952e90dc56c97d78132573dc5c7b09d61b416a9deef4ebe01a0c",
- "sha256:d4a5a5879a939cb84959d86869132b00176197ca561c664fc21478c1eee60d75",
- "sha256:da9b41d4539eefd408c46725fb76ecba3a50a3367cafb7dea5f250d0653c1040",
- "sha256:db61a79c07331e88b9a9974815c075fbd812bc9dbc4dc44b366b5368a2936063",
- "sha256:ddb726cb861c3117a553f940372a495fe1078249ff5f8a5478c0576c7be12050",
- "sha256:ded59300d6330be27bc6cf0b74b89ada58069ced87c48eaf9344e5e84b0072f7",
- "sha256:e2617759031dae1bf183c16cef8fcfb3de7617f394c813fa5e8e46e9b82d4222",
- "sha256:e5cdbb5cafcedea04924568d990e20ce7f1945a1dd54b560f879ee2d57226912",
- "sha256:ec8e767f13be637d056f7e07e61d089e555f719b387a7070154ad80a0ff31801",
- "sha256:ef382417db92ba23dfb5864a3fc9be27ea4894e86620d342a116b243ade5d35d",
- "sha256:f2cba5c6db29ce991029b5e4ac51eb36774458f0a3b8d3137241b32d1bb91f06",
- "sha256:f5b4198d85a3755d27e64c52f8c95d6333119e49fd001ae5798dac872c95e0f8",
- "sha256:ffeeb38ee4a80a30a6877c5c4c359e5498eec095878f1581453202bfacc8fbc2"
+ "sha256:0342a28617e63ad15d96dca0f7ae9479a37b7d8a295f749c14f3436ea59fdcb3",
+ "sha256:066b44897c493e0dcbc9e6a6d9f8bbb6607ef82367cf6810d387c09f0cd4fe9a",
+ "sha256:10b15394c13544fce02382360cab54e51a9e0fd1bd61ae9ce012c0d1e103c813",
+ "sha256:12580845917b1e59f8a1c2ffa6af6d0908cb39220f3019e36c110c943dc875b0",
+ "sha256:156192e5fd3dbbcb11cd777cc469cf010a294f4c736a2b2c891c77618cb1379a",
+ "sha256:1637253b11a18f453e34013c665d8bf15904c9e3c44fbda34c643fbdc9d452cd",
+ "sha256:292300f76440651529b8ceec283a9370532f4ecba9ad67d120617021bb5ef139",
+ "sha256:30dcaf05adfa69c2a7b9f7dfd9f60bc8e36b282d7ed25c308ef9e114de7fc23b",
+ "sha256:338aa9d9883aaaad53695cb14ccdeb36d4060485bb9388446330bef9c361c252",
+ "sha256:373ea34dca98f2fdb3e5cb33d83b6d801007a8074f992b80311fc589d3e6b790",
+ "sha256:38c0a497a000d50491055805313ed83ddba069353d102ece8aef5d11b5faf045",
+ "sha256:40cc0f91c6cde033da493227797be2826cbf8f388eaa36a0271a97a332bfd7ce",
+ "sha256:4436cc9ba5414c2c998eaedee5343f49c02ca93b21769c5fdfa4f9d799e84200",
+ "sha256:509ecd8334c380000d259dc66feb191dd0a93b21f2453faa75f7f9cdcefc0718",
+ "sha256:5c587f52c81211d4530fa6857884d37f514bcf9453bdeee0ff93eaaf906a5c1b",
+ "sha256:5f3671662dc4b422b15776cdca89c041a6349b4864a43aa2350b6b0b03bbcc7f",
+ "sha256:6599bf92f33ab041e36e06d25890afbdf12078aacfe1f1d08c713906e49a3fe5",
+ "sha256:6e8a95f243d01ba572341c52f89f3acb98a3b6d1d5d830efba86033dd3687ade",
+ "sha256:706ec567267c96717ab9363904d846ec009a48d5f832140b6ad08aad3791b1f5",
+ "sha256:780551e47d62095e088f251f5db428473c26db7829884323e56d9c0c3118791a",
+ "sha256:7ff8f3fb38233035028dbc93715551d81eadc110199e14bbbfa01c5c4a43f8d8",
+ "sha256:828189fcdda99aae0d6bf718ea766b2e715eabc1868670a0a07bf8404bf58c33",
+ "sha256:857abe2fa6a4973f8663e039ead8d22215d31db613ace76e4a98f52ec919068e",
+ "sha256:883123d0bbe1c136f76b56276074b0c79b5817dd4238097ffa64ac67257f4b6c",
+ "sha256:8877d9b437b35a85c18e3c6499b23674684bf690f5d96c1006a1ef61f9fdf0f3",
+ "sha256:8e575a59315a91ccd00c7757127f6b2488c2f914096077c745c2f1ba5b8c0969",
+ "sha256:97072cc90f1009386c8a5b7de9d4fc1a9f91ba5ef2146c55c1f005e7b5c5e068",
+ "sha256:9a22cbb5ede6fade0482111fa7f01115ff04039795d7092ed0db43522431b4f2",
+ "sha256:a063aad9f7b4c9f9da7b2550eae0a582ffc7623dca1c925e50c3fbde7a579771",
+ "sha256:a08c7401d0b24e8c2982f4e307124b671c6736d40d1c39e09d7a8687bddf83ed",
+ "sha256:a0b273fe6dc655b110e8dc89b8ec7f1a778d78c9fd9b4bda7c384c8906072212",
+ "sha256:a2b3b05e22a77bb0ae1a3125126a4e08535961c946b62f30985535ed40e26614",
+ "sha256:a66e055254a26c82aead7ff420d9fa8dc2da10c82679ea850d8feebf11074d88",
+ "sha256:aa387bd7489f3e1787ff82068b295bcaafbf6f79c3dad3cbc82ef88ce3f48ad3",
+ "sha256:ae453f655640157d76209f42c62c64c4d4f2c7f97256d3567e3b439bd5c9b06c",
+ "sha256:b5016e331b75310610c2cf955d9f58a9749943ed5f7b8cfc0bb89c6134ab0a84",
+ "sha256:b9a4ee55174b04f6af539218f9f8083140f61a46eabcaa4234f3c2a452c4ed11",
+ "sha256:bd3b4b8175c1db502adf209d06136c000df4d245105c8839e9d0be71c94aefe1",
+ "sha256:bebea5f5ed41f618797ce3ffb4606c64a5de92e9c3f26d26c2e0aae292f015c1",
+ "sha256:c10fbc8a64aa0f3ed136b0b086b6b577bc64d67d5581acd7cc129af52654384e",
+ "sha256:c2c41c1b1866b670573657d584de413df701f482574bad7e28214a2362cb1fd1",
+ "sha256:cf97ed82ca986e5c637ea286ba2793c85325b30f869bf64d3009ccc1a31ae3fd",
+ "sha256:d1f25ee9de21a39b3a8516f2c5feb8de248f17da7eead089c2e04aa097936b47",
+ "sha256:d2fbc2a127e857d2f8898aaabcc34c37771bf78a4d5e17d3e1f5c30cd0cbc62a",
+ "sha256:dc945064a8783b86fcce9a0a705abd7db2117d95e340df8a4333f00be5efb64c",
+ "sha256:ddc5a54edb653e9e215f75de377354e2455376f416c4378e1d43b08ec50acc31",
+ "sha256:e8834e5f17d89e05697c3c043d3e58a8b19682bf365048837383abfe39adaed5",
+ "sha256:ef9659d1cda9ce9ac9585c045aaa1e59223b143f2407db0eaee0b61a4f266fb6",
+ "sha256:f6f5cab2d7f0c12f8187a376cc6582c477d2df91d63f75341307fcdcb5d60303",
+ "sha256:f81c9b4bd8aa747d417407a7f6f0b1469a43b36a85748145e144ac4e8d303cb5",
+ "sha256:f99ef080288f09ffc687423b8d60978cf3a465d3f404a18d1a05474bd8575a47"
],
"markers": "python_version >= '3.7'",
- "version": "==7.1.0"
+ "version": "==7.2.5"
},
"cryptography": {
"hashes": [
- "sha256:0f8da300b5c8af9f98111ffd512910bc792b4c77392a9523624680f7956a99d4",
- "sha256:35f7c7d015d474f4011e859e93e789c87d21f6f4880ebdc29896a60403328f1f",
- "sha256:4789d1e3e257965e960232345002262ede4d094d1a19f4d3b52e48d4d8f3b885",
- "sha256:5aa67414fcdfa22cf052e640cb5ddc461924a045cacf325cd164e65312d99502",
- "sha256:5d2d8b87a490bfcd407ed9d49093793d0f75198a35e6eb1a923ce1ee86c62b41",
- "sha256:6687ef6d0a6497e2b58e7c5b852b53f62142cfa7cd1555795758934da363a965",
- "sha256:6f8ba7f0328b79f08bdacc3e4e66fb4d7aab0c3584e0bd41328dce5262e26b2e",
- "sha256:706843b48f9a3f9b9911979761c91541e3d90db1ca905fd63fee540a217698bc",
- "sha256:807ce09d4434881ca3a7594733669bd834f5b2c6d5c7e36f8c00f691887042ad",
- "sha256:83e17b26de248c33f3acffb922748151d71827d6021d98c70e6c1a25ddd78505",
- "sha256:96f1157a7c08b5b189b16b47bc9db2332269d6680a196341bf30046330d15388",
- "sha256:aec5a6c9864be7df2240c382740fcf3b96928c46604eaa7f3091f58b878c0bb6",
- "sha256:b0afd054cd42f3d213bf82c629efb1ee5f22eba35bf0eec88ea9ea7304f511a2",
- "sha256:c5caeb8188c24888c90b5108a441c106f7faa4c4c075a2bcae438c6e8ca73cef",
- "sha256:ced4e447ae29ca194449a3f1ce132ded8fcab06971ef5f618605aacaa612beac",
- "sha256:d1f6198ee6d9148405e49887803907fe8962a23e6c6f83ea7d98f1c0de375695",
- "sha256:e124352fd3db36a9d4a21c1aa27fd5d051e621845cb87fb851c08f4f75ce8be6",
- "sha256:e422abdec8b5fa8462aa016786680720d78bdce7a30c652b7fadf83a4ba35336",
- "sha256:ef8b72fa70b348724ff1218267e7f7375b8de4e8194d1636ee60510aae104cd0",
- "sha256:f0c64d1bd842ca2633e74a1a28033d139368ad959872533b1bab8c80e8240a0c",
- "sha256:f24077a3b5298a5a06a8e0536e3ea9ec60e4c7ac486755e5fb6e6ea9b3500106",
- "sha256:fdd188c8a6ef8769f148f88f859884507b954cc64db6b52f66ef199bb9ad660a",
- "sha256:fe913f20024eb2cb2f323e42a64bdf2911bb9738a15dba7d3cce48151034e3a8"
- ],
- "index": "pypi",
- "version": "==39.0.1"
+ "sha256:05dc219433b14046c476f6f09d7636b92a1c3e5808b9a6536adf4932b3b2c440",
+ "sha256:0dcca15d3a19a66e63662dc8d30f8036b07be851a8680eda92d079868f106288",
+ "sha256:142bae539ef28a1c76794cca7f49729e7c54423f615cfd9b0b1fa90ebe53244b",
+ "sha256:3daf9b114213f8ba460b829a02896789751626a2a4e7a43a28ee77c04b5e4958",
+ "sha256:48f388d0d153350f378c7f7b41497a54ff1513c816bcbbcafe5b829e59b9ce5b",
+ "sha256:4df2af28d7bedc84fe45bd49bc35d710aede676e2a4cb7fc6d103a2adc8afe4d",
+ "sha256:4f01c9863da784558165f5d4d916093737a75203a5c5286fde60e503e4276c7a",
+ "sha256:7a38250f433cd41df7fcb763caa3ee9362777fdb4dc642b9a349721d2bf47404",
+ "sha256:8f79b5ff5ad9d3218afb1e7e20ea74da5f76943ee5edb7f76e56ec5161ec782b",
+ "sha256:956ba8701b4ffe91ba59665ed170a2ebbdc6fc0e40de5f6059195d9f2b33ca0e",
+ "sha256:a04386fb7bc85fab9cd51b6308633a3c271e3d0d3eae917eebab2fac6219b6d2",
+ "sha256:a95f4802d49faa6a674242e25bfeea6fc2acd915b5e5e29ac90a32b1139cae1c",
+ "sha256:adc0d980fd2760c9e5de537c28935cc32b9353baaf28e0814df417619c6c8c3b",
+ "sha256:aecbb1592b0188e030cb01f82d12556cf72e218280f621deed7d806afd2113f9",
+ "sha256:b12794f01d4cacfbd3177b9042198f3af1c856eedd0a98f10f141385c809a14b",
+ "sha256:c0764e72b36a3dc065c155e5b22f93df465da9c39af65516fe04ed3c68c92636",
+ "sha256:c33c0d32b8594fa647d2e01dbccc303478e16fdd7cf98652d5b3ed11aa5e5c99",
+ "sha256:cbaba590180cba88cb99a5f76f90808a624f18b169b90a4abb40c1fd8c19420e",
+ "sha256:d5a1bd0e9e2031465761dfa920c16b0065ad77321d8a8c1f5ee331021fda65e9"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==40.0.2"
},
"dill": {
"hashes": [
@@ -1801,11 +1771,11 @@
},
"exceptiongroup": {
"hashes": [
- "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e",
- "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"
+ "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e",
+ "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"
],
"markers": "python_version < '3.11'",
- "version": "==1.1.0"
+ "version": "==1.1.1"
},
"execnet": {
"hashes": [
@@ -1817,11 +1787,11 @@
},
"fakeredis": {
"hashes": [
- "sha256:854d758794dab9953be16b9a0f7fbd4bbd6b6964db7a9684e163291c1342ece6",
- "sha256:e2f7a88dad23be1191ad6212008e170d75c2d63dde979c2694be8cbfd917428e"
+ "sha256:69a504328a89e5e5f2d05a4236b570fb45244c96997c5002c8c6a0503b95f289",
+ "sha256:e0fef512b8ec49679d373456aa4698a4103005ecd7ca0b13170a2c1d3af949c5"
],
"index": "pypi",
- "version": "==2.7.1"
+ "version": "==2.11.2"
},
"flake8": {
"hashes": [
@@ -2057,51 +2027,51 @@
},
"mock": {
"hashes": [
- "sha256:c41cfb1e99ba5d341fbcc5308836e7d7c9786d302f995b2c271ce2144dece9eb",
- "sha256:e3ea505c03babf7977fd21674a69ad328053d414f05e6433c30d8fa14a534a6b"
+ "sha256:06f18d7d65b44428202b145a9a36e99c2ee00d1eb992df0caf881d4664377891",
+ "sha256:0e0bc5ba78b8db3667ad636d964eb963dc97a59f04c6f6214c5f0e4a8f726c56"
],
"index": "pypi",
- "version": "==5.0.1"
+ "version": "==5.0.2"
},
"moto": {
"hashes": [
- "sha256:1b361ece638c74a657325378a259276f368aafce2f8be84f8143e69fa93ce8ec",
- "sha256:63431733d2a02c7bd652ad71ec1da442a0e0d580cbac5eeb50d440a2ce066eac"
+ "sha256:c3ecc2dda1a7b3a3c46655490bc6a4660b7bb47e31eaed7bbd54adeb01f8471f",
+ "sha256:df5b52eff70bf125ee03ea72c4e01f2daff243796f984a534c3dee92a0b93522"
],
"index": "pypi",
- "version": "==4.1.2"
+ "version": "==4.1.8"
},
"mypy": {
"hashes": [
- "sha256:01b1b9e1ed40544ef486fa8ac022232ccc57109f379611633ede8e71630d07d2",
- "sha256:0ab090d9240d6b4e99e1fa998c2d0aa5b29fc0fb06bd30e7ad6183c95fa07593",
- "sha256:14d776869a3e6c89c17eb943100f7868f677703c8a4e00b3803918f86aafbc52",
- "sha256:1ace23f6bb4aec4604b86c4843276e8fa548d667dbbd0cb83a3ae14b18b2db6c",
- "sha256:2efa963bdddb27cb4a0d42545cd137a8d2b883bd181bbc4525b568ef6eca258f",
- "sha256:2f6ac8c87e046dc18c7d1d7f6653a66787a4555085b056fe2d599f1f1a2a2d21",
- "sha256:3ae4c7a99e5153496243146a3baf33b9beff714464ca386b5f62daad601d87af",
- "sha256:3cfad08f16a9c6611e6143485a93de0e1e13f48cfb90bcad7d5fde1c0cec3d36",
- "sha256:4e5175026618c178dfba6188228b845b64131034ab3ba52acaffa8f6c361f805",
- "sha256:50979d5efff8d4135d9db293c6cb2c42260e70fb010cbc697b1311a4d7a39ddb",
- "sha256:5cd187d92b6939617f1168a4fe68f68add749902c010e66fe574c165c742ed88",
- "sha256:5cfca124f0ac6707747544c127880893ad72a656e136adc935c8600740b21ff5",
- "sha256:5e398652d005a198a7f3c132426b33c6b85d98aa7dc852137a2a3be8890c4072",
- "sha256:67cced7f15654710386e5c10b96608f1ee3d5c94ca1da5a2aad5889793a824c1",
- "sha256:7306edca1c6f1b5fa0bc9aa645e6ac8393014fa82d0fa180d0ebc990ebe15964",
- "sha256:7cc2c01dfc5a3cbddfa6c13f530ef3b95292f926329929001d45e124342cd6b7",
- "sha256:87edfaf344c9401942883fad030909116aa77b0fa7e6e8e1c5407e14549afe9a",
- "sha256:8845125d0b7c57838a10fd8925b0f5f709d0e08568ce587cc862aacce453e3dd",
- "sha256:92024447a339400ea00ac228369cd242e988dd775640755fa4ac0c126e49bb74",
- "sha256:a86b794e8a56ada65c573183756eac8ac5b8d3d59daf9d5ebd72ecdbb7867a43",
- "sha256:bb2782a036d9eb6b5a6efcdda0986774bf798beef86a62da86cb73e2a10b423d",
- "sha256:be78077064d016bc1b639c2cbcc5be945b47b4261a4f4b7d8923f6c69c5c9457",
- "sha256:c7cf862aef988b5fbaa17764ad1d21b4831436701c7d2b653156a9497d92c83c",
- "sha256:e0626db16705ab9f7fa6c249c017c887baf20738ce7f9129da162bb3075fc1af",
- "sha256:f34495079c8d9da05b183f9f7daec2878280c2ad7cc81da686ef0b484cea2ecf",
- "sha256:fe523fcbd52c05040c7bee370d66fee8373c5972171e4fbc323153433198592d"
+ "sha256:023fe9e618182ca6317ae89833ba422c411469156b690fde6a315ad10695a521",
+ "sha256:031fc69c9a7e12bcc5660b74122ed84b3f1c505e762cc4296884096c6d8ee140",
+ "sha256:2de7babe398cb7a85ac7f1fd5c42f396c215ab3eff731b4d761d68d0f6a80f48",
+ "sha256:2e93a8a553e0394b26c4ca683923b85a69f7ccdc0139e6acd1354cc884fe0128",
+ "sha256:390bc685ec209ada4e9d35068ac6988c60160b2b703072d2850457b62499e336",
+ "sha256:3a2d219775a120581a0ae8ca392b31f238d452729adbcb6892fa89688cb8306a",
+ "sha256:3efde4af6f2d3ccf58ae825495dbb8d74abd6d176ee686ce2ab19bd025273f41",
+ "sha256:4a99fe1768925e4a139aace8f3fb66db3576ee1c30b9c0f70f744ead7e329c9f",
+ "sha256:4b41412df69ec06ab141808d12e0bf2823717b1c363bd77b4c0820feaa37249e",
+ "sha256:4c8d8c6b80aa4a1689f2a179d31d86ae1367ea4a12855cc13aa3ba24bb36b2d8",
+ "sha256:4d19f1a239d59f10fdc31263d48b7937c585810288376671eaf75380b074f238",
+ "sha256:4e4a682b3f2489d218751981639cffc4e281d548f9d517addfd5a2917ac78119",
+ "sha256:695c45cea7e8abb6f088a34a6034b1d273122e5530aeebb9c09626cea6dca4cb",
+ "sha256:701189408b460a2ff42b984e6bd45c3f41f0ac9f5f58b8873bbedc511900086d",
+ "sha256:70894c5345bea98321a2fe84df35f43ee7bb0feec117a71420c60459fc3e1eed",
+ "sha256:8293a216e902ac12779eb7a08f2bc39ec6c878d7c6025aa59464e0c4c16f7eb9",
+ "sha256:8d26b513225ffd3eacece727f4387bdce6469192ef029ca9dd469940158bc89e",
+ "sha256:a197ad3a774f8e74f21e428f0de7f60ad26a8d23437b69638aac2764d1e06a6a",
+ "sha256:bea55fc25b96c53affab852ad94bf111a3083bc1d8b0c76a61dd101d8a388cf5",
+ "sha256:c9a084bce1061e55cdc0493a2ad890375af359c766b8ac311ac8120d3a472950",
+ "sha256:d0e9464a0af6715852267bf29c9553e4555b61f5904a4fc538547a4d67617937",
+ "sha256:d8e9187bfcd5ffedbe87403195e1fc340189a68463903c39e2b63307c9fa0394",
+ "sha256:eaeaa0888b7f3ccb7bcd40b50497ca30923dba14f385bde4af78fac713d6d6f6",
+ "sha256:f46af8d162f3d470d8ffc997aaf7a269996d205f9d746124a179d3abe05ac602",
+ "sha256:f70a40410d774ae23fcb4afbbeca652905a04de7948eaf0b1789c8d1426b72d1",
+ "sha256:fe91be1c51c90e2afe6827601ca14353bbf3953f343c2129fa1e247d55fd95ba"
],
"index": "pypi",
- "version": "==1.0.0"
+ "version": "==1.2.0"
},
"mypy-extensions": {
"hashes": [
@@ -2113,19 +2083,19 @@
},
"packaging": {
"hashes": [
- "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2",
- "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"
+ "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61",
+ "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"
],
"markers": "python_version >= '3.7'",
- "version": "==23.0"
+ "version": "==23.1"
},
"pathspec": {
"hashes": [
- "sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229",
- "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"
+ "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687",
+ "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"
],
"markers": "python_version >= '3.7'",
- "version": "==0.11.0"
+ "version": "==0.11.1"
},
"pep8": {
"hashes": [
@@ -2137,11 +2107,11 @@
},
"platformdirs": {
"hashes": [
- "sha256:8a1228abb1ef82d788f74139988b137e78692984ec7b08eaa6c65f1723af28f9",
- "sha256:b1d5eb14f221506f50d6604a561f4c5786d9e80355219694a1b244bcd96f4567"
+ "sha256:47692bc24c1958e8b0f13dd727307cff1db103fca36399f457da8e05f222fdc4",
+ "sha256:7954a68d0ba23558d753f73437c55f89027cf8f5108c19844d4b82e5af396335"
],
"markers": "python_version >= '3.7'",
- "version": "==3.0.0"
+ "version": "==3.5.0"
},
"pluggy": {
"hashes": [
@@ -2176,11 +2146,11 @@
},
"pylint": {
"hashes": [
- "sha256:bad9d7c36037f6043a1e848a43004dfd5ea5ceb05815d713ba56ca4503a9fe37",
- "sha256:ffe7fa536bb38ba35006a7c8a6d2efbfdd3d95bbf21199cad31f76b1c50aaf30"
+ "sha256:761907349e699f8afdcd56c4fe02f3021ab5b3a0fc26d19a9bfdc66c7d0d5cd5",
+ "sha256:a6cbb4c6e96eab4a3c7de7c6383c512478f58f88d95764507d84c899d656a89a"
],
"index": "pypi",
- "version": "==2.16.1"
+ "version": "==2.17.3"
},
"pylint-mccabe": {
"hashes": [
@@ -2232,11 +2202,11 @@
},
"pytest": {
"hashes": [
- "sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5",
- "sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42"
+ "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362",
+ "sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3"
],
"index": "pypi",
- "version": "==7.2.1"
+ "version": "==7.3.1"
},
"pytest-cov": {
"hashes": [
@@ -2264,19 +2234,19 @@
},
"pytest-sugar": {
"hashes": [
- "sha256:30e5225ed2b3cc988a8a672f8bda0fc37bcd92d62e9273937f061112b3f2186d",
- "sha256:c4793495f3c32e114f0f5416290946c316eb96ad5a3684dcdadda9267e59b2b8"
+ "sha256:8cb5a4e5f8bbcd834622b0235db9e50432f4cbd71fef55b467fe44e43701e062",
+ "sha256:f1e74c1abfa55f7241cf7088032b6e378566f16b938f3f08905e2cf4494edd46"
],
"index": "pypi",
- "version": "==0.9.6"
+ "version": "==0.9.7"
},
"pytest-xdist": {
"hashes": [
- "sha256:40fdb8f3544921c5dfcd486ac080ce22870e71d82ced6d2e78fa97c2addd480c",
- "sha256:70a76f191d8a1d2d6be69fc440cdf85f3e4c03c08b520fd5dc5d338d6cf07d89"
+ "sha256:1849bd98d8b242b948e472db7478e090bf3361912a8fed87992ed94085f54727",
+ "sha256:37290d161638a20b672401deef1cba812d110ac27e35d213f091d15b8beb40c9"
],
"index": "pypi",
- "version": "==3.1.0"
+ "version": "==3.2.1"
},
"python-dateutil": {
"hashes": [
@@ -2286,29 +2256,75 @@
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.2"
},
+ "pyyaml": {
+ "hashes": [
+ "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf",
+ "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293",
+ "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b",
+ "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57",
+ "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b",
+ "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4",
+ "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07",
+ "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba",
+ "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9",
+ "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287",
+ "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513",
+ "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0",
+ "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782",
+ "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0",
+ "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92",
+ "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f",
+ "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2",
+ "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc",
+ "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1",
+ "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c",
+ "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86",
+ "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4",
+ "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c",
+ "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34",
+ "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b",
+ "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d",
+ "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c",
+ "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb",
+ "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7",
+ "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737",
+ "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3",
+ "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d",
+ "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358",
+ "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53",
+ "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78",
+ "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803",
+ "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a",
+ "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f",
+ "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
+ "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
+ ],
+ "index": "pypi",
+ "version": "==6.0"
+ },
"redis": {
"hashes": [
- "sha256:a010f6cb7378065040a02839c3f75c7e0fb37a87116fb4a95be82a95552776c7",
- "sha256:e6206448e2f8a432871d07d432c13ed6c2abcf6b74edb436c99752b1371be387"
+ "sha256:2c19e6767c474f2e85167909061d525ed65bea9301c0770bb151e041b7ac89a2",
+ "sha256:73ec35da4da267d6847e47f68730fdd5f62e2ca69e3ef5885c6a78a9374c3893"
],
"index": "pypi",
- "version": "==4.4.2"
+ "version": "==4.5.4"
},
"requests": {
"hashes": [
- "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa",
- "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"
+ "sha256:e8f3c9be120d3333921d213eef078af392fba3933ab7ed2d1cba3b56f2568c3b",
+ "sha256:f2e34a75f4749019bb0e3effb66683630e4ffeaf75819fb51bebef1bf5aef059"
],
"index": "pypi",
- "version": "==2.28.2"
+ "version": "==2.29.0"
},
"responses": {
"hashes": [
- "sha256:396acb2a13d25297789a5866b4881cf4e46ffd49cc26c43ab1117f40b973102e",
- "sha256:dcf294d204d14c436fddcc74caefdbc5764795a40ff4e6a7740ed8ddbf3294be"
+ "sha256:8a3a5915713483bf353b6f4079ba8b2a29029d1d1090a503c70b0dc5d9d0c7bd",
+ "sha256:c4d9aa9fc888188f0c673eff79a8dadbe2e75b7fe879dc80a221a06e0a68138f"
],
"index": "pypi",
- "version": "==0.22.0"
+ "version": "==0.23.1"
},
"s3transfer": {
"hashes": [
@@ -2335,27 +2351,19 @@
},
"soupsieve": {
"hashes": [
- "sha256:49e5368c2cda80ee7e84da9dbe3e110b70a4575f196efb74e51b94549d921955",
- "sha256:e28dba9ca6c7c00173e34e4ba57448f0688bb681b7c5e8bf4971daafc093d69a"
+ "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8",
+ "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"
],
"markers": "python_version >= '3.7'",
- "version": "==2.4"
+ "version": "==2.4.1"
},
"termcolor": {
"hashes": [
- "sha256:91ddd848e7251200eac969846cbae2dacd7d71c2871e92733289e7e3666f48e7",
- "sha256:dfc8ac3f350788f23b2947b3e6cfa5a53b630b612e6cd8965a015a776020b99a"
+ "sha256:3afb05607b89aed0ffe25202399ee0867ad4d3cb4180d98aaf8eefa6a5f7d475",
+ "sha256:b5b08f68937f138fe92f6c089b99f1e2da0ae56c52b78bf7075fd95420fd9a5a"
],
"markers": "python_version >= '3.7'",
- "version": "==2.2.0"
- },
- "toml": {
- "hashes": [
- "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
- "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
- ],
- "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==0.10.2"
+ "version": "==2.3.0"
},
"tomli": {
"hashes": [
@@ -2367,72 +2375,65 @@
},
"tomlkit": {
"hashes": [
- "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b",
- "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"
+ "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171",
+ "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"
],
- "markers": "python_version >= '3.6'",
- "version": "==0.11.6"
+ "markers": "python_version >= '3.7'",
+ "version": "==0.11.8"
},
"types-pyopenssl": {
"hashes": [
- "sha256:6ca54d593f8b946f9570f9ed7457c41da3b518feff5e344851941a6209bea62b",
- "sha256:847ab17a16475a882dc29898648a6a35ad0d3e11a5bba5aa8ab2f3435a8647cb"
+ "sha256:20b80971b86240e8432a1832bd8124cea49c3088c7bfc77dfd23be27ffe4a517",
+ "sha256:b050641aeff6dfebf231ad719bdac12d53b8ee818d4afb67b886333484629957"
],
- "version": "==23.0.0.3"
+ "version": "==23.1.0.2"
},
"types-python-dateutil": {
"hashes": [
- "sha256:4a6f4cc19ce4ba1a08670871e297bf3802f55d4f129e6aa2443f540b6cf803d2",
- "sha256:cfb7d31021c6bce6f3362c69af6e3abb48fe3e08854f02487e844ff910deec2a"
+ "sha256:355b2cb82b31e556fd18e7b074de7c350c680ab80608f0cc55ba6770d986d67d",
+ "sha256:fe5b545e678ec13e3ddc83a0eee1545c1b5e2fba4cfc39b276ab6f4e7604a923"
],
"index": "pypi",
- "version": "==2.8.19.6"
+ "version": "==2.8.19.12"
},
"types-pyyaml": {
"hashes": [
- "sha256:ade6e328a5a3df816c47c912c2e1e946ae2bace90744aa73111ee6834b03a314",
- "sha256:de3bacfc4e0772d9b1baf007c37354f3c34c8952e90307d5155b6de0fc183a67"
+ "sha256:5aed5aa66bd2d2e158f75dda22b059570ede988559f030cf294871d3b647e3e8",
+ "sha256:c51b1bd6d99ddf0aa2884a7a328810ebf70a4262c292195d3f4f9a0005f9eeb6"
],
"index": "pypi",
- "version": "==6.0.12.4"
+ "version": "==6.0.12.9"
},
"types-redis": {
"hashes": [
- "sha256:57f8b3706afe47ef36496d70a97a3783560e6cb19e157be12985dbb31de1d853",
- "sha256:8b40d6bf3a54352d4cb2aa7d01294c572a39d40a9d289b96bdf490b51d3a42d2"
+ "sha256:2db530f54facec3149147bfe61d5ac24f5fe4e871823d95a601cd2c1d775d8a0",
+ "sha256:bf04192f415b2b42ecefd70bb4b91eb0352e48f2716a213e038e35c096a639c2"
],
"index": "pypi",
- "version": "==4.4.0.6"
+ "version": "==4.5.4.1"
},
"types-requests": {
"hashes": [
- "sha256:19622ace35a5da1838ee9cad0df4a50c7e3a420f8a37e8357ce870fed492fa81",
- "sha256:b4adf81c6bcd2f9fa0f6ab8669e96fad7f04429d6e22c486b2b3382d729ca364"
+ "sha256:4cf6e323e856c779fbe8815bb977a5bf5d6c5034713e4c17ff2a9a20610f5b27",
+ "sha256:c86f4a955d943d2457120dbe719df24ef0924e11177164d10a0373cf311d7b4d"
],
"index": "pypi",
- "version": "==2.28.11.11"
+ "version": "==2.29.0.0"
},
"types-simplejson": {
"hashes": [
- "sha256:857adb13190abd65d0d103be965152e79a6842c5663e01e8681bde1895713f52",
- "sha256:f8a8428f753574fa3b7eb290756776f0fb6b4ad9cae72bf8c0c9534eeda6398c"
+ "sha256:38982991785db84c78f9012f5b9a03069ec3be020f468fb8fcd950b23e3a3341",
+ "sha256:64aff3d151104e0458bfc9d64ffb78efd0157ff740a9f00904a30a5465f7c013"
],
"index": "pypi",
- "version": "==3.18.0.0"
- },
- "types-toml": {
- "hashes": [
- "sha256:306b1bb8b5bbc5f1b60387dbcc4b489e79f8490ce20e93af5f422a68b470d94b",
- "sha256:c8748dd225b28eb80ce712e2d7d61b57599815e7b48d07ef53df51ed148fa6b1"
- ],
- "version": "==0.10.8.4"
+ "version": "==3.19.0.0"
},
"types-urllib3": {
"hashes": [
- "sha256:35586727cbd7751acccf2c0f34a88baffc092f435ab62458f10776466590f2d5",
- "sha256:a6c23c41bd03e542eaee5423a018f833077b51c4bf9ceb5aa544e12b812d5604"
+ "sha256:3ba3d3a8ee46e0d5512c6bd0594da4f10b2584b47a470f8422044a2ab462f1df",
+ "sha256:a1557355ce8d350a555d142589f3001903757d2d36c18a66f588d9659bbc917d"
],
- "version": "==1.26.25.6"
+ "version": "==1.26.25.12"
},
"typing-extensions": {
"hashes": [
@@ -2444,89 +2445,100 @@
},
"urllib3": {
"hashes": [
- "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72",
- "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"
+ "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305",
+ "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
- "version": "==1.26.14"
+ "version": "==1.26.15"
},
"werkzeug": {
"hashes": [
- "sha256:2e1ccc9417d4da358b9de6f174e3ac094391ea1d4fbef2d667865d819dfd0afe",
- "sha256:56433961bc1f12533306c624f3be5e744389ac61d722175d543e1751285da612"
+ "sha256:4866679a0722de00796a74086238bb3b98d90f423f05de039abb09315487254a",
+ "sha256:a987caf1092edc7523edb139edb20c70571c4a8d5eed02e0b547b4739174d091"
],
- "markers": "python_version >= '3.7'",
- "version": "==2.2.3"
+ "markers": "python_version >= '3.8'",
+ "version": "==2.3.3"
},
"wrapt": {
"hashes": [
- "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3",
- "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b",
- "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4",
- "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2",
- "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656",
- "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3",
- "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff",
- "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310",
- "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a",
- "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57",
- "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069",
- "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383",
- "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe",
- "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87",
- "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d",
- "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b",
- "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907",
- "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f",
- "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0",
- "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28",
- "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1",
- "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853",
- "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc",
- "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3",
- "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3",
- "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164",
- "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1",
- "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c",
- "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1",
- "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7",
- "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1",
- "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320",
- "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed",
- "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1",
- "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248",
- "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c",
- "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456",
- "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77",
- "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef",
- "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1",
- "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7",
- "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86",
- "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4",
- "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d",
- "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d",
- "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8",
- "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5",
- "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471",
- "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00",
- "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68",
- "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3",
- "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d",
- "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735",
- "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d",
- "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569",
- "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7",
- "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59",
- "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5",
- "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb",
- "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b",
- "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f",
- "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462",
- "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015",
- "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"
+ "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0",
+ "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420",
+ "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a",
+ "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c",
+ "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079",
+ "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923",
+ "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f",
+ "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1",
+ "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8",
+ "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86",
+ "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0",
+ "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364",
+ "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e",
+ "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c",
+ "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e",
+ "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c",
+ "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727",
+ "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff",
+ "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e",
+ "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29",
+ "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7",
+ "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72",
+ "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475",
+ "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a",
+ "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317",
+ "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2",
+ "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd",
+ "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640",
+ "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98",
+ "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248",
+ "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e",
+ "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d",
+ "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec",
+ "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1",
+ "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e",
+ "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9",
+ "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92",
+ "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb",
+ "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094",
+ "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46",
+ "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29",
+ "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd",
+ "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705",
+ "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8",
+ "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975",
+ "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb",
+ "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e",
+ "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b",
+ "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418",
+ "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019",
+ "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1",
+ "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba",
+ "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6",
+ "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2",
+ "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3",
+ "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7",
+ "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752",
+ "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416",
+ "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f",
+ "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1",
+ "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc",
+ "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145",
+ "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee",
+ "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a",
+ "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7",
+ "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b",
+ "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653",
+ "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0",
+ "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90",
+ "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29",
+ "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6",
+ "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034",
+ "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09",
+ "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559",
+ "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==1.14.1"
+ "version": "==1.15.0"
},
"xmltodict": {
"hashes": [
diff --git a/app/data_models/progress_store.py b/app/data_models/progress_store.py
index 29eef569a6..ae5a286d6a 100644
--- a/app/data_models/progress_store.py
+++ b/app/data_models/progress_store.py
@@ -111,16 +111,16 @@ def section_keys(
def update_section_status(
self, section_status: str, section_id: str, list_item_id: Optional[str] = None
- ) -> None:
+ ) -> bool:
+ updated = False
section_key = (section_id, list_item_id)
if section_key in self._progress:
- self._progress[section_key].status = section_status
- self._is_dirty = True
+ if self._progress[section_key].status != section_status:
+ updated = True
+ self._progress[section_key].status = section_status
+ self._is_dirty = True
- elif (
- section_status == CompletionStatus.INDIVIDUAL_RESPONSE_REQUESTED
- and section_key not in self._progress
- ):
+ elif section_status == CompletionStatus.INDIVIDUAL_RESPONSE_REQUESTED:
self._progress[section_key] = Progress(
section_id=section_id,
list_item_id=list_item_id,
@@ -129,6 +129,8 @@ def update_section_status(
)
self._is_dirty = True
+ return updated
+
def get_section_status(
self, section_id: str, list_item_id: Optional[str] = None
) -> str:
@@ -138,8 +140,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:
@@ -151,7 +164,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
diff --git a/app/jinja_filters.py b/app/jinja_filters.py
index 29d7e18b20..8bfe24b0f1 100644
--- a/app/jinja_filters.py
+++ b/app/jinja_filters.py
@@ -2,7 +2,7 @@
import re
from datetime import datetime
from decimal import Decimal
-from typing import Any, Callable, Mapping, Optional, Union
+from typing import Any, Callable, Literal, Mapping, Optional, TypeAlias, Union
import flask
import flask_babel
@@ -18,6 +18,7 @@
blueprint = flask.Blueprint("filters", __name__)
FormType = Mapping[str, Mapping[str, Any]]
AnswerType = Mapping[str, Any]
+UnitLengthType: TypeAlias = Literal["short", "long", "narrow"]
def mark_safe(context: nodes.EvalContext, value: str) -> Union[Markup, str]:
@@ -69,7 +70,9 @@ def format_percentage(value: Union[int, float, Decimal]) -> str:
def format_unit(
- unit: str, value: Union[int, float, Decimal], length: str = "short"
+ unit: str,
+ value: int | float | Decimal,
+ length: UnitLengthType = "short",
) -> str:
formatted_unit: str = units.format_unit(
value=value,
@@ -80,7 +83,7 @@ def format_unit(
return formatted_unit
-def format_unit_input_label(unit: str, unit_length: str = "short") -> str:
+def format_unit_input_label(unit: str, unit_length: UnitLengthType = "short") -> str:
"""
This function is used to only get the unit of measurement text. If the unit_length
is long then only the plural form of the word is returned (e.g., Hours, Years, etc).
@@ -97,8 +100,9 @@ def format_unit_input_label(unit: str, unit_length: str = "short") -> str:
locale=flask_babel.get_locale(),
).replace("2 ", "")
else:
+ # Type ignore: We pass an empty string as the value so that we just return the unit label
unit_label = units.format_unit(
- value="",
+ value="", # type: ignore
measurement_unit=unit,
length=unit_length,
locale=flask_babel.get_locale(),
@@ -183,7 +187,10 @@ def get_format_date_range(start_date: Markup, end_date: Markup) -> Markup:
@blueprint.app_context_processor
def format_unit_processor() -> (
- dict[str, Callable[[str, Union[int, Decimal], str], str]]
+ dict[
+ str,
+ Callable[[str, int | float | Decimal, UnitLengthType], str],
+ ]
):
return {"format_unit": format_unit}
diff --git a/app/publisher/publisher.py b/app/publisher/publisher.py
index 11abdf72c5..6b909d0c47 100644
--- a/app/publisher/publisher.py
+++ b/app/publisher/publisher.py
@@ -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
@@ -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":
logger.info("publishing message", topic_id=topic_id)
# pylint: disable=no-member
topic_path = self._client.topic_path(self._project_id, topic_id)
diff --git a/app/questionnaire/dependencies.py b/app/questionnaire/dependencies.py
index 1fc380ea7a..c79ecc1009 100644
--- a/app/questionnaire/dependencies.py
+++ b/app/questionnaire/dependencies.py
@@ -2,6 +2,7 @@
from typing import TYPE_CHECKING, Mapping, Sequence
+from ordered_set import OrderedSet
from werkzeug.datastructures import MultiDict
from app.data_models import ProgressStore
@@ -30,7 +31,7 @@ def get_block_ids_for_calculated_summary_dependencies(
]
if block_id := location.block_id:
- dependents = dependent_sections[block_id]
+ dependents = OrderedSet(dependent_sections[block_id])
else:
dependents = get_flattened_mapping_values(dependent_sections)
diff --git a/app/questionnaire/path_finder.py b/app/questionnaire/path_finder.py
index 47583b3fe4..f09a19a247 100644
--- a/app/questionnaire/path_finder.py
+++ b/app/questionnaire/path_finder.py
@@ -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,
@@ -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()
]
@@ -194,9 +196,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 = (
diff --git a/app/questionnaire/placeholder_parser.py b/app/questionnaire/placeholder_parser.py
index f196f9ef1e..8c2ef0d43d 100644
--- a/app/questionnaire/placeholder_parser.py
+++ b/app/questionnaire/placeholder_parser.py
@@ -3,6 +3,8 @@
from decimal import Decimal
from typing import TYPE_CHECKING, Any, Mapping, MutableMapping, Sequence, TypeAlias
+from ordered_set import OrderedSet
+
from app.data_models import ProgressStore
from app.data_models.answer_store import AnswerStore
from app.data_models.list_store import ListStore
@@ -105,7 +107,7 @@ def __call__(
return self._placeholder_map
def _get_value_source_resolver(
- self, routing_path_block_ids: set[str] | None = None
+ self, routing_path_block_ids: OrderedSet[str] | None = None
) -> ValueSourceResolver:
return ValueSourceResolver(
answer_store=self._answer_store,
diff --git a/app/questionnaire/placeholder_transforms.py b/app/questionnaire/placeholder_transforms.py
index 5cdd6a45d8..7b2c660a71 100644
--- a/app/questionnaire/placeholder_transforms.py
+++ b/app/questionnaire/placeholder_transforms.py
@@ -1,6 +1,6 @@
from datetime import date, datetime, timezone
from decimal import Decimal
-from typing import TYPE_CHECKING, Sequence, Sized
+from typing import TYPE_CHECKING, Literal, Sequence, Sized
from urllib.parse import quote
from babel import units
@@ -41,9 +41,7 @@ def __init__(
input_date_format = "%Y-%m-%d"
- def format_currency(
- self, number: float | str | None = None, currency: str = "GBP"
- ) -> str:
+ def format_currency(self, number: Decimal | float, currency: str = "GBP") -> str:
formatted_currency: str = format_currency(number, currency, locale=self.locale)
return formatted_currency
@@ -137,8 +135,8 @@ def format_percentage(value: int | Decimal | str) -> str:
def format_unit(
self,
unit: str,
- value: int | Decimal | str,
- unit_length: str | None = None,
+ value: int | Decimal,
+ unit_length: Literal["short", "long", "narrow"] | None = None,
) -> str:
length = unit_length or "short"
formatted_unit: str = units.format_unit(
diff --git a/app/questionnaire/questionnaire_schema.py b/app/questionnaire/questionnaire_schema.py
index 17ec2348de..2f57e8874f 100644
--- a/app/questionnaire/questionnaire_schema.py
+++ b/app/questionnaire/questionnaire_schema.py
@@ -1,10 +1,12 @@
+# pylint: disable=too-many-lines
from collections import defaultdict
from copy import deepcopy
from dataclasses import dataclass
from functools import cached_property
-from typing import Any, Generator, Iterable, Mapping, Sequence
+from typing import Any, Generator, Iterable, Mapping, Sequence, TypeAlias
from flask_babel import force_locale
+from ordered_set import OrderedSet
from werkzeug.datastructures import ImmutableDict, MultiDict
from app.data_models.answer import Answer
@@ -12,7 +14,7 @@
from app.questionnaire.rules.operator import OPERATION_MAPPING
from app.questionnaire.schema_utils import get_answers_from_question
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
DEFAULT_LANGUAGE_CODE = "en"
@@ -27,6 +29,8 @@
QuestionSchemaType = Mapping
+DependencyDictType: TypeAlias = dict[str, OrderedSet[str]]
+
class InvalidSchemaConfigurationException(Exception):
pass
@@ -59,9 +63,19 @@ 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: defaultdict[
+ str, OrderedSet[str]
+ ] = defaultdict(
+ OrderedSet
+ )
+ self._when_rules_block_dependencies_by_section_for_progress_value_source: defaultdict[
+ str, DependencyDictType
+ ] = defaultdict(
+ lambda: defaultdict(OrderedSet)
+ )
self.calculated_summary_section_dependencies_by_block: dict[
- str, dict[str, set[str]]
- ] = defaultdict(lambda: defaultdict(set))
+ str, DependencyDictType
+ ] = defaultdict(lambda: defaultdict(OrderedSet))
self._when_rules_section_dependencies_by_answer: dict[
str, set[str]
] = defaultdict(set)
@@ -91,6 +105,56 @@ 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_for_progress(
+ self,
+ ) -> ImmutableDict[str, set[str]]:
+ """
+ This method flips the dependencies that were captured for progress value sources so that they can be
+ evaluated properly for when rules, this is because for when rules we need to check for dependencies in
+ previous sections, whereas for progress we are checking for dependent blocks/sections in "future" sections
+ """
+ when_rules_section_dependencies_for_progress = defaultdict(set)
+ for (
+ section,
+ dependent,
+ ) in (
+ self._when_rules_block_dependencies_by_section_for_progress_value_source.items()
+ ):
+ section_dependents = get_flattened_mapping_values(dependent)
+ for dependent_section in section_dependents:
+ when_rules_section_dependencies_for_progress[dependent_section].add(
+ section
+ )
+
+ for (
+ section,
+ dependents,
+ ) in (
+ self._when_rules_section_dependencies_by_section_for_progress_value_source.items()
+ ):
+ for dependent_section in dependents:
+ when_rules_section_dependencies_for_progress[dependent_section].add(
+ section
+ )
+ return ImmutableDict(when_rules_section_dependencies_for_progress)
+
+ @cached_property
+ def when_rules_section_dependencies_by_section_for_progress_value_source(
+ self,
+ ) -> ImmutableDict[str, OrderedSet[str]]:
+ return ImmutableDict(
+ self._when_rules_section_dependencies_by_section_for_progress_value_source
+ )
+
+ @cached_property
+ def when_rules_block_dependencies_by_section_for_progress_value_source(
+ self,
+ ) -> ImmutableDict[str, DependencyDictType]:
+ 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)
@@ -168,6 +232,20 @@ 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]:
+ all_section_dependencies = self.when_rules_section_dependencies_by_section.get(
+ section_id, set()
+ )
+
+ if progress_dependencies := self.when_rules_section_dependencies_for_progress.get(
+ section_id
+ ):
+ all_section_dependencies.update(progress_dependencies)
+
+ return all_section_dependencies
+
def _get_sections_by_id(self) -> dict[str, ImmutableDict]:
return {
section["id"]: section
@@ -419,6 +497,9 @@ def get_summary_options(self) -> ImmutableDict[str, bool]:
def get_sections(self) -> Iterable[ImmutableDict]:
return self._sections_by_id.values()
+ def get_section_ids(self) -> Iterable[str]:
+ return self._sections_by_id.keys()
+
def get_section(self, section_id: str) -> ImmutableDict | None:
return self._sections_by_id.get(section_id)
@@ -887,21 +968,115 @@ def _get_error_messages(self) -> dict:
return messages
def _populate_when_rules_section_dependencies(self) -> None:
+ """
+ Populates section dependencies for when rules, including when rules containing
+ progress value sources.
+ Progress section dependencies by section are directly populated in this method.
+ Progress section dependencies by block are populated in the
+ `self._populate_block_dependencies_for_progress_value_source` called here.
+ """
+ progress_section_dependencies = (
+ self._when_rules_section_dependencies_by_section_for_progress_value_source
+ )
+
for section in self.get_sections():
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
+ for (
+ key,
+ values,
+ ) in rule_section_dependencies_for_progress_value_source.items():
+ progress_section_dependencies[key].update(values)
+
+ 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, DependencyDictType
+ ],
+ ) -> None:
+ """
+ Populates section dependencies for progress value sources at the block level
+ """
+ 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():
+ for block_id, section_ids in section_dependencies_by_block.items():
+ dependencies[dependent_section][block_id].update(section_ids)
+
+ 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, OrderedSet[str] | DependencyDictType]]]:
+ """
+ For a given rule, returns a set of dependent answer ids and any dependent sections for progress value sources.
+ Progress dependencies are keyed both by section and by block e.g.
+ sections: {"section-1": {"section-2"}}
+ blocks: {"section-1": {"block-1": {"section-2"}}}
+ """
+ answer_id_list: set[str] = set()
+ dependencies_ids_for_progress_value_source: dict[
+ str, dict[str, OrderedSet[str] | DependencyDictType]
+ ] = {
+ "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:
+ 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
+ )
+ answer_id_list.update(calculated_summary_answer_ids)
+ elif source == "progress" and identifier:
+ if selector == "section" and identifier != current_section_id:
+ # Type ignore: Added as this will be a set rather than a dict at this point
+ dependencies_ids_for_progress_value_source["sections"][identifier] = OrderedSet([current_section_id]) # type: ignore
+ 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: OrderedSet()
+ }
+ dependencies_ids_for_progress_value_source["blocks"][section_id][identifier].add(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: Mapping | Sequence
- ) -> set[str]:
- rules_section_dependencies: set[str] = set()
+ ) -> tuple[set[str], DependencyDictType, dict[str, DependencyDictType]]:
+ """
+ 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)
@@ -910,18 +1085,21 @@ def _get_rules_section_dependencies(
if not isinstance(rule, Mapping):
continue
- answer_id_list: list = []
- identifier: str | None = rule.get("identifier")
- source: str | None = 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.update(
+ dependencies_for_progress_value_source["sections"]
+ )
+ block_dependencies_for_progress_value_source.update(
+ 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
@@ -930,14 +1108,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
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():
diff --git a/app/questionnaire/questionnaire_store_updater.py b/app/questionnaire/questionnaire_store_updater.py
index 72d748cbff..2e9b80fa41 100644
--- a/app/questionnaire/questionnaire_store_updater.py
+++ b/app/questionnaire/questionnaire_store_updater.py
@@ -2,6 +2,7 @@
from itertools import combinations
from typing import Any, Iterable, Mapping
+from ordered_set import OrderedSet
from werkzeug.datastructures import ImmutableDict
from app.data_models import AnswerValueTypes, QuestionnaireStore
@@ -48,13 +49,13 @@ def save(self) -> None:
self._questionnaire_store.save()
def is_dirty(self) -> bool:
- if (
- self._answer_store.is_dirty
- or self._list_store.is_dirty
- or self._progress_store.is_dirty
- ):
- return True
- return False
+ return bool(
+ (
+ self._answer_store.is_dirty
+ or self._list_store.is_dirty
+ or self._progress_store.is_dirty
+ )
+ )
def update_relationships_answer(
self,
@@ -69,16 +70,15 @@ def update_relationships_answer(
def remove_completed_relationship_locations_for_list_name(
self, list_name: str
) -> None:
- target_relationship_collectors = self._get_relationship_collectors_by_list_name(
+ if target_relationship_collectors := self._get_relationship_collectors_by_list_name(
list_name
- )
- if target_relationship_collectors:
+ ):
for target in target_relationship_collectors:
block_id = target["id"]
section_id = self._schema.get_section_for_block_id(block_id)["id"] # type: ignore
self.remove_completed_location(Location(section_id, block_id))
- def update_relationship_question_completeness(self, list_name: str) -> None:
+ def _update_relationship_question_completeness(self, list_name: str) -> None:
relationship_collectors = self._get_relationship_collectors_by_list_name(
list_name
)
@@ -92,11 +92,9 @@ def update_relationship_question_completeness(self, list_name: str) -> None:
relationship_answer_id = self._schema.get_first_answer_id_for_block(
collector["id"]
)
- relationship_answers = self._get_relationships_in_answer_store(
+ if relationship_answers := self._get_relationships_in_answer_store(
relationship_answer_id
- )
-
- if relationship_answers:
+ ):
pairs = {
(answer["list_item_id"], answer["to_list_item_id"])
for answer in relationship_answers
@@ -147,8 +145,7 @@ def remove_primary_person(self, list_name: str) -> None:
"""Remove the primary person and all of their answers.
Any context for the primary person will be removed
"""
- list_item_id = self._list_store[list_name].primary_person
- if list_item_id:
+ if list_item_id := self._list_store[list_name].primary_person:
self.remove_list_item_and_answers(list_name, list_item_id)
def remove_list_item_and_answers(self, list_name: str, list_item_id: str) -> None:
@@ -161,10 +158,9 @@ def remove_list_item_and_answers(self, list_name: str, list_item_id: str) -> Non
list_item_id=list_item_id
)
- answers = self.get_relationship_answers_for_list_name(list_name)
- if answers:
- self.remove_relationship_answers_for_list_item_id(list_item_id, answers)
- self.update_relationship_question_completeness(list_name)
+ if answers := self.get_relationship_answers_for_list_name(list_name):
+ self._remove_relationship_answers_for_list_item_id(list_item_id, answers)
+ self._update_relationship_question_completeness(list_name)
self._progress_store.remove_progress_for_list_item_id(list_item_id=list_item_id)
@@ -220,7 +216,7 @@ def update_same_name_items(
list_model.same_name_items = list(same_name_items) # type: ignore
- def remove_relationship_answers_for_list_item_id(
+ def _remove_relationship_answers_for_list_item_id(
self, list_item_id: str, answers: list
) -> None:
for answer in answers:
@@ -243,11 +239,13 @@ def remove_completed_location(self, location: Location | None = None) -> bool:
def update_section_status(
self, is_complete: bool, section_id: str, list_item_id: str | None = None
- ) -> None:
+ ) -> bool:
status = (
CompletionStatus.COMPLETED if is_complete else CompletionStatus.IN_PROGRESS
)
- self._progress_store.update_section_status(status, section_id, list_item_id)
+ return self._progress_store.update_section_status(
+ status, section_id, list_item_id
+ )
def _update_answer(
self,
@@ -342,6 +340,44 @@ def _capture_section_dependencies_for_answer(self, answer_id: str) -> None:
else:
self.dependent_sections.add(DependentSection(section_id, None, None))
+ def _capture_section_dependencies_progress_value_source_for_current_section(
+ self,
+ ) -> None:
+ """
+ Captures a unique list of section ids that are dependents of the current section, for progress value sources.
+ """
+ dependent_sections: Iterable = self._schema.when_rules_section_dependencies_by_section_for_progress_value_source.get(
+ self._current_location.section_id, set()
+ )
+ self._update_section_dependencies(dependent_sections)
+
+ def _capture_section_dependencies_progress_value_source_for_current_block(
+ self,
+ ) -> None:
+ """
+ Captures a unique list of section ids that are dependents of the current block, for progress value sources.
+ """
+ # Type ignore: Added as block_id will exist at this point
+ dependent_sections: Iterable = self._schema.when_rules_block_dependencies_by_section_for_progress_value_source.get(
+ self._current_location.section_id, {}
+ ).get(
+ self._current_location.block_id, set() # type: ignore
+ )
+
+ self._update_section_dependencies(dependent_sections)
+
+ def _update_section_dependencies(self, dependent_sections: Iterable) -> None:
+ for section_id in dependent_sections:
+ if repeating_list := self._schema.get_repeating_list_for_section(
+ section_id
+ ):
+ for list_item_id in self._list_store[repeating_list].items:
+ self.dependent_sections.add(
+ DependentSection(section_id, list_item_id, None)
+ )
+ else:
+ self.dependent_sections.add(DependentSection(section_id, None, None))
+
def update_answers(
self, form_data: Mapping[str, Any], list_item_id: str | None = None
) -> None:
@@ -358,41 +394,103 @@ def update_answers(
answer_id_to_use = resolved_answer.get("original_answer_id") or answer_id
list_item_id_to_use = resolved_answer.get("list_item_id") or list_item_id
- answer_updated = self._update_answer(
- answer_id_to_use, list_item_id_to_use, answer_value
- )
- if answer_updated:
+ if self._update_answer(answer_id_to_use, list_item_id_to_use, answer_value):
self._capture_section_dependencies_for_answer(answer_id_to_use)
self._capture_block_dependencies_for_answer(answer_id_to_use)
+ self.capture_progress_section_dependencies()
+
+ def capture_progress_section_dependencies(self) -> None:
+ self._capture_section_dependencies_progress_value_source_for_current_block()
+ self._capture_section_dependencies_progress_value_source_for_current_section()
def update_progress_for_dependent_sections(self) -> None:
- """Updates the progress to IN_PROGRESS. Section progress is not updated for the current location as it is handled by `handle_post` on block handlers."""
+ """Removes dependent blocks from the progress store and updates the progress to IN_PROGRESS.
+ Section progress is not updated for the current location as it is handled by `handle_post` on block handlers.
+ """
+ evaluated_dependents: list[tuple] = []
+
+ chronological_dependents = self.get_chronological_section_dependents()
- for section in self.dependent_sections:
+ for section in chronological_dependents:
if (
section.section_id,
section.list_item_id,
) not in self.started_section_keys():
continue
- is_path_complete = section.is_complete
- if is_path_complete is None:
- is_path_complete = self._router.is_path_complete(
- self._router.routing_path(
- section.section_id, list_item_id=section.list_item_id
- )
+ if (
+ section.section_id,
+ section.list_item_id,
+ ) not in evaluated_dependents:
+ self._evaluate_dependents(
+ dependent_section=section, evaluated_dependents=evaluated_dependents
+ )
+ evaluated_dependents.append((section.section_id, section.list_item_id))
+
+ def _evaluate_dependents(
+ self,
+ *,
+ dependent_section: DependentSection,
+ evaluated_dependents: list[tuple],
+ ) -> None:
+ is_path_complete = dependent_section.is_complete
+ if is_path_complete is None:
+ is_path_complete = self._router.is_path_complete(
+ self._router.routing_path(
+ dependent_section.section_id,
+ list_item_id=dependent_section.list_item_id,
)
+ )
- self.update_section_status(
- is_complete=is_path_complete,
- section_id=section.section_id,
- list_item_id=section.list_item_id,
+ if self.update_section_status(
+ is_complete=is_path_complete,
+ section_id=dependent_section.section_id,
+ list_item_id=dependent_section.list_item_id,
+ ):
+ dependents_of_dependent: OrderedSet = self._schema.when_rules_section_dependencies_by_section_for_progress_value_source.get(
+ dependent_section.section_id, OrderedSet()
)
+ for dependent_section_id in dependents_of_dependent:
+ if repeating_list := self._schema.get_repeating_list_for_section(
+ dependent_section_id
+ ):
+ for item_id in self._list_store[repeating_list].items:
+ if (
+ dependent_section_id,
+ item_id,
+ ) not in evaluated_dependents:
+ self._evaluate_dependent_of_dependents(
+ dependent_section_id=dependent_section_id,
+ list_item_id=item_id,
+ evaluated_dependents=evaluated_dependents,
+ )
+ elif (
+ dependent_section_id,
+ dependent_section.list_item_id,
+ ) not in evaluated_dependents:
+ self._evaluate_dependent_of_dependents(
+ dependent_section_id=dependent_section_id,
+ evaluated_dependents=evaluated_dependents,
+ )
+
+ def _evaluate_dependent_of_dependents(
+ self,
+ dependent_section_id: str,
+ evaluated_dependents: list[tuple],
+ list_item_id: str | None = None,
+ ) -> None:
+ self._evaluate_dependents(
+ dependent_section=DependentSection(
+ section_id=dependent_section_id,
+ list_item_id=list_item_id,
+ is_complete=None,
+ ),
+ evaluated_dependents=evaluated_dependents,
+ )
+ evaluated_dependents.append((dependent_section_id, list_item_id))
def remove_dependent_blocks_and_capture_dependent_sections(self) -> None:
- """Removes dependent blocks from the progress store.
- This must be called before updating section progress (update_progress_for_dependent_sections) and section dependencies (_update_section_completeness)
- """
+ """Removes dependent blocks from the progress store."""
for (
section_key,
@@ -430,3 +528,9 @@ def started_section_keys(
self, section_ids: Iterable[str] | None = None
) -> list[SectionKeyType]:
return self._progress_store.started_section_keys(section_ids)
+
+ def get_chronological_section_dependents(self) -> list:
+ sections = list(self._schema.get_section_ids())
+ return sorted(
+ self.dependent_sections, key=lambda x: sections.index(x.section_id)
+ )
diff --git a/app/questionnaire/router.py b/app/questionnaire/router.py
index 8fa1692329..b5ab65e7d5 100644
--- a/app/questionnaire/router.py
+++ b/app/questionnaire/router.py
@@ -320,7 +320,7 @@ def is_block_complete(
self, *, block_id: str, section_id: str, list_item_id: str | None
) -> bool:
return block_id in self._progress_store.get_completed_block_ids(
- section_id, list_item_id
+ section_id=section_id, list_item_id=list_item_id
)
def _get_first_incomplete_location_in_section(
@@ -387,9 +387,10 @@ def _is_section_enabled(self, section: Mapping) -> bool:
return True
enabled = section["enabled"]
+ section_id = section["id"]
routing_path_block_ids = self._path_finder.get_when_rules_block_dependencies(
- section["id"]
+ section_id
)
when_rule_evaluator = RuleEvaluator(
@@ -399,7 +400,7 @@ def _is_section_enabled(self, section: Mapping) -> bool:
metadata=self._metadata,
response_metadata=self._response_metadata,
progress_store=self._progress_store,
- location=None,
+ location=Location(section_id=section_id),
routing_path_block_ids=routing_path_block_ids,
)
diff --git a/app/questionnaire/rules/rule_evaluator.py b/app/questionnaire/rules/rule_evaluator.py
index 8173521a82..241d3fa6bf 100644
--- a/app/questionnaire/rules/rule_evaluator.py
+++ b/app/questionnaire/rules/rule_evaluator.py
@@ -45,8 +45,8 @@ def __post_init__(self) -> None:
location=self.location,
list_item_id=list_item_id,
routing_path_block_ids=self.routing_path_block_ids,
- use_default_answer=True,
progress_store=self.progress_store,
+ use_default_answer=True,
)
renderer: PlaceholderRenderer = PlaceholderRenderer(
diff --git a/app/questionnaire/value_source_resolver.py b/app/questionnaire/value_source_resolver.py
index 819d9f3ce8..ed2be5bfd8 100644
--- a/app/questionnaire/value_source_resolver.py
+++ b/app/questionnaire/value_source_resolver.py
@@ -37,10 +37,15 @@ class ValueSourceResolver:
def _is_answer_on_path(self, answer_id: str) -> bool:
if self.routing_path_block_ids:
block = self.schema.get_block_for_answer_id(answer_id)
- return block is not None and block["id"] in self.routing_path_block_ids
-
+ return block is not None and self._is_block_on_path(block["id"])
return True
+ def _is_block_on_path(self, block_id: str) -> bool:
+ return (
+ self.routing_path_block_ids is not None
+ and block_id in self.routing_path_block_ids
+ )
+
def _get_answer_value(
self,
answer_id: str,
@@ -116,6 +121,36 @@ def _resolve_answer_value_source(
return answer_value
+ def _resolve_progress_value_source(
+ self, value_source: Mapping
+ ) -> ValueSourceEscapedTypes | ValueSourceTypes | None:
+ identifier = value_source["identifier"]
+ selector = value_source["selector"]
+ if selector == "section":
+ # List item id is set to None here as we do not support checking progress value sources for
+ # repeating sections
+ return self.progress_store.get_section_status(
+ section_id=identifier, list_item_id=None
+ )
+
+ if selector == "block":
+ if not self.location:
+ raise ValueError("location is required to resolve block progress")
+
+ if not self._is_block_on_path(identifier):
+ return None
+
+ # Type ignore: Section id will exist at this point
+ section_id_for_block: str = self.schema.get_section_id_for_block_id(identifier) # type: ignore
+
+ return self.progress_store.get_block_status(
+ block_id=identifier,
+ section_id=section_id_for_block,
+ list_item_id=self.location.list_item_id
+ if self.location.section_id == section_id_for_block
+ else None,
+ )
+
def _resolve_list_value_source(self, value_source: Mapping) -> int | str | list:
identifier = value_source["identifier"]
list_model: ListModel = self.list_store[identifier]
@@ -154,13 +189,26 @@ def _resolve_calculated_summary_value_source(
self.list_store,
self.metadata,
self.response_metadata,
+ progress_store=self.progress_store,
location=self.location,
routing_path_block_ids=self.routing_path_block_ids,
- progress_store=self.progress_store,
)
return evaluator.evaluate(calculation["operation"]) # type: ignore
+ def _resolve_metadata_source(self, value_source: Mapping) -> str | None:
+ if not self.metadata:
+ raise NoMetadataException
+ identifier = value_source["identifier"]
+ return self.metadata[identifier]
+
+ def _resolve_location_source(self, value_source: Mapping) -> str | None:
+ if value_source.get("identifier") == "list_item_id":
+ return self.list_item_id
+
+ def _resolve_response_metadata_source(self, value_source: Mapping) -> str | None:
+ return self.response_metadata.get(value_source.get("identifier"))
+
@staticmethod
def get_calculation_operator(
calculation_type: str,
@@ -175,29 +223,18 @@ def resolve(
) -> ValueSourceEscapedTypes | ValueSourceTypes:
source = value_source["source"]
- # We always need to assess the routing path for calculated summary value sources
- # as they may contain answers that are not on the path
- if source == "answers":
- return self._resolve_answer_value_source(value_source)
-
- if source == "list":
- return self._resolve_list_value_source(value_source)
-
- if source == "metadata":
- if not self.metadata:
- raise NoMetadataException
- identifier = value_source["identifier"]
- return self.metadata[identifier]
-
- if source == "location" and value_source.get("identifier") == "list_item_id":
- # This does not use the location object because
- # routes such as individual response does not have the concept of location.
- return self.list_item_id
-
- if source == "response_metadata":
- return self.response_metadata.get(value_source["identifier"])
-
if source == "calculated_summary":
return self._resolve_calculated_summary_value_source(
- value_source, assess_routing_path=True
+ value_source=value_source, assess_routing_path=True
)
+
+ resolve_method_mapping = {
+ "answers": self._resolve_answer_value_source,
+ "list": self._resolve_list_value_source,
+ "metadata": self._resolve_metadata_source,
+ "location": self._resolve_location_source,
+ "response_metadata": self._resolve_response_metadata_source,
+ "progress": self._resolve_progress_value_source,
+ }
+
+ return resolve_method_mapping[source](value_source)
diff --git a/app/survey_config/business_config.py b/app/survey_config/business_config.py
index 74e75b68b8..c9803a33f8 100644
--- a/app/survey_config/business_config.py
+++ b/app/survey_config/business_config.py
@@ -16,7 +16,7 @@ class BusinessSurveyConfig(SurveyConfig):
footer_links: Iterable[MutableMapping] = field(default_factory=list)
footer_legal_links: Iterable[Mapping] = field(default_factory=list)
- def __post_init__(self):
+ def __post_init__(self) -> None:
self.base_url = self._stripped_base_url
super().__post_init__()
diff --git a/app/survey_config/social_survey_config.py b/app/survey_config/social_survey_config.py
index 4b21e7cb97..30bd716b82 100644
--- a/app/survey_config/social_survey_config.py
+++ b/app/survey_config/social_survey_config.py
@@ -17,7 +17,7 @@ class SocialSurveyConfig(
footer_links: Iterable[MutableMapping] = field(default_factory=list)
footer_legal_links: Iterable[Mapping] = field(default_factory=list)
- def __post_init__(self):
+ def __post_init__(self) -> None:
super().__post_init__()
upstream_base_url = f"{self.base_url}/{self.language_code}"
diff --git a/app/survey_config/survey_config.py b/app/survey_config/survey_config.py
index b945911400..c9cd640d84 100644
--- a/app/survey_config/survey_config.py
+++ b/app/survey_config/survey_config.py
@@ -40,7 +40,7 @@ class SurveyConfig:
privacy_and_data_protection_url: str = field(init=False)
language_code: Optional[str] = None
- def __post_init__(self):
+ def __post_init__(self) -> None:
self.contact_us_url: str = f"{self.base_url}/contact-us/"
self.cookie_settings_url: str = f"{self.base_url}/cookies/"
self.cookie_domain: str = self.cookie_settings_url.split("://")[-1].split("/")[
diff --git a/app/translations/messages.pot b/app/translations/messages.pot
index 9fcab41dd0..e53387de23 100644
--- a/app/translations/messages.pot
+++ b/app/translations/messages.pot
@@ -15,28 +15,28 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.11.0\n"
+"Generated-By: Babel 2.12.1\n"
-#: app/forms/validators.py:377 app/jinja_filters.py:115
+#: app/forms/validators.py:377 app/jinja_filters.py:119
#, python-format
msgid "%(num)s year"
msgid_plural "%(num)s years"
msgstr[0] ""
msgstr[1] ""
-#: app/forms/validators.py:381 app/jinja_filters.py:123
+#: app/forms/validators.py:381 app/jinja_filters.py:127
#, python-format
msgid "%(num)s month"
msgid_plural "%(num)s months"
msgstr[0] ""
msgstr[1] ""
-#: app/jinja_filters.py:165
+#: app/jinja_filters.py:169
#, python-format
msgid "%(date)s at %(time)s"
msgstr ""
-#: app/jinja_filters.py:179
+#: app/jinja_filters.py:183
#, python-format
msgid "%(from_date)s to %(to_date)s"
msgstr ""
@@ -231,19 +231,19 @@ msgid ""
"browser if using a shared device"
msgstr ""
-#: app/questionnaire/placeholder_transforms.py:160
+#: app/questionnaire/placeholder_transforms.py:158
msgid "{number_of_years} year"
msgid_plural "{number_of_years} years"
msgstr[0] ""
msgstr[1] ""
-#: app/questionnaire/placeholder_transforms.py:166
+#: app/questionnaire/placeholder_transforms.py:164
msgid "{number_of_months} month"
msgid_plural "{number_of_months} months"
msgstr[0] ""
msgstr[1] ""
-#: app/questionnaire/placeholder_transforms.py:171
+#: app/questionnaire/placeholder_transforms.py:169
msgid "{number_of_days} day"
msgid_plural "{number_of_days} days"
msgstr[0] ""
diff --git a/app/utilities/mappings.py b/app/utilities/mappings.py
index 63dc6eba28..7069a25155 100644
--- a/app/utilities/mappings.py
+++ b/app/utilities/mappings.py
@@ -1,10 +1,12 @@
from typing import Generator, Iterable, Mapping, Sequence
+from ordered_set import OrderedSet
+
def get_flattened_mapping_values(
map_to_flatten: Mapping[tuple, Iterable[str]] | Mapping[str, Iterable[str]]
-) -> set[str]:
- return {x for v in map_to_flatten.values() for x in v}
+) -> OrderedSet[str]:
+ return OrderedSet([x for v in map_to_flatten.values() for x in v])
def get_mappings_with_key( # noqa: C901 pylint: disable=too-complex
diff --git a/app/views/contexts/calculated_summary_context.py b/app/views/contexts/calculated_summary_context.py
index 50fc424435..bbe379ed59 100644
--- a/app/views/contexts/calculated_summary_context.py
+++ b/app/views/contexts/calculated_summary_context.py
@@ -4,6 +4,7 @@
Any,
Callable,
Iterable,
+ Literal,
Mapping,
MutableMapping,
Optional,
@@ -254,7 +255,8 @@ def _get_answer_format(self, groups: list) -> Tuple[dict[str, Any], list]:
@staticmethod
def _format_total(
- answer_format: Mapping[str, str], total: Union[int, float, Decimal]
+ answer_format: Mapping[str, Literal["short", "long", "narrow"]],
+ total: int | float | Decimal,
) -> str:
if answer_format["type"] == "currency":
return get_formatted_currency(total, answer_format["currency"])
diff --git a/app/views/contexts/summary/answer.py b/app/views/contexts/summary/answer.py
index 86fe3e0e21..2c275f3434 100644
--- a/app/views/contexts/summary/answer.py
+++ b/app/views/contexts/summary/answer.py
@@ -61,7 +61,7 @@ def _build_link(
return_to_block_id: Optional[str],
) -> str:
return url_for(
- "questionnaire.block",
+ endpoint="questionnaire.block",
list_name=list_name,
block_id=block_id,
list_item_id=list_item_id,
diff --git a/app/views/handlers/calculated_summary.py b/app/views/handlers/calculated_summary.py
index 37b5cbb2a0..325ada18e1 100644
--- a/app/views/handlers/calculated_summary.py
+++ b/app/views/handlers/calculated_summary.py
@@ -21,3 +21,10 @@ def get_context(self):
self.page_title = context["summary"]["calculated_question"]["title"]
return context
+
+ def handle_post(self):
+ # We prematurely set the current as complete, so that dependent sections can be updated accordingly
+ self.questionnaire_store_updater.add_completed_location()
+ # Then we update dependent sections
+ self.questionnaire_store_updater.capture_progress_section_dependencies()
+ return super().handle_post()
diff --git a/app/views/handlers/individual_response.py b/app/views/handlers/individual_response.py
index c4872aaef2..69aa7ce2e4 100644
--- a/app/views/handlers/individual_response.py
+++ b/app/views/handlers/individual_response.py
@@ -577,7 +577,7 @@ def handle_post(self) -> Response | None:
def _update_section_completeness(self):
if not self._questionnaire_store.progress_store.get_completed_block_ids(
- self.individual_section_id, self._list_item_id
+ section_id=self.individual_section_id, list_item_id=self._list_item_id
):
status = CompletionStatus.NOT_STARTED
else:
diff --git a/app/views/handlers/list_action.py b/app/views/handlers/list_action.py
index 010defc49e..5083de11c4 100644
--- a/app/views/handlers/list_action.py
+++ b/app/views/handlers/list_action.py
@@ -91,7 +91,6 @@ def handle_post(self):
self._routing_path = self.router.routing_path(
self.current_location.section_id, self.current_location.list_item_id
)
-
self.questionnaire_store_updater.remove_dependent_blocks_and_capture_dependent_sections()
self.questionnaire_store_updater.update_progress_for_dependent_sections()
self.questionnaire_store_updater.save()
diff --git a/app/views/handlers/question.py b/app/views/handlers/question.py
index 4299047179..483da976f7 100644
--- a/app/views/handlers/question.py
+++ b/app/views/handlers/question.py
@@ -211,6 +211,9 @@ def handle_post(self):
# wtforms Form parents are not discoverable in the 2.3.3 implementation
self.questionnaire_store_updater.update_answers(self.form.data)
if self.questionnaire_store_updater.is_dirty():
+ # We prematurely complete the block, as we need it completed to build the routing path
+ # In order to support progress value source references of the previous block
+ self.questionnaire_store_updater.add_completed_location()
self._routing_path = self.router.routing_path(
section_id=self._current_location.section_id,
list_item_id=self._current_location.list_item_id,
diff --git a/schemas/test/en/test_progress_block_value_source_repeating_sections.json b/schemas/test/en/test_progress_block_value_source_repeating_sections.json
new file mode 100644
index 0000000000..605565a303
--- /dev/null
+++ b/schemas/test/en/test_progress_block_value_source_repeating_sections.json
@@ -0,0 +1,392 @@
+{
+ "mime_type": "application/json/ons/eq",
+ "language": "en",
+ "schema_version": "0.0.1",
+ "survey_id": "139",
+ "theme": "default",
+ "title": "Progress Value Source Repeating Sections Test",
+ "data_version": "0.0.3",
+ "description": "Progress Value Source Repeating Sections Test",
+ "navigation": {
+ "visible": true
+ },
+ "metadata": [
+ {
+ "name": "user_id",
+ "type": "string"
+ },
+ {
+ "name": "period_id",
+ "type": "string"
+ },
+ {
+ "name": "ru_name",
+ "type": "string"
+ },
+ {
+ "name": "trad_as",
+ "type": "string",
+ "optional": true
+ }
+ ],
+ "questionnaire_flow": {
+ "type": "Hub",
+ "options": {}
+ },
+ "sections": [
+ {
+ "id": "section-1",
+ "title": "List collector + random question",
+ "groups": [
+ {
+ "id": "group",
+ "title": "List",
+ "blocks": [
+ {
+ "id": "list-collector",
+ "type": "ListCollector",
+ "for_list": "people",
+ "question": {
+ "id": "confirmation-question",
+ "type": "General",
+ "title": "Does anyone else live here?",
+ "answers": [
+ {
+ "id": "anyone-else",
+ "mandatory": true,
+ "type": "Radio",
+ "options": [
+ {
+ "label": "Yes",
+ "value": "Yes",
+ "action": {
+ "type": "RedirectToListAddBlock"
+ }
+ },
+ {
+ "label": "No",
+ "value": "No"
+ }
+ ]
+ }
+ ]
+ },
+ "add_block": {
+ "id": "add-person",
+ "type": "ListAddQuestion",
+ "question": {
+ "id": "add-question",
+ "type": "General",
+ "title": "What is the name of the person?",
+ "answers": [
+ {
+ "id": "first-name",
+ "label": "First name",
+ "mandatory": true,
+ "type": "TextField"
+ },
+ {
+ "id": "last-name",
+ "label": "Last name",
+ "mandatory": true,
+ "type": "TextField"
+ }
+ ]
+ }
+ },
+ "edit_block": {
+ "id": "edit-person",
+ "type": "ListEditQuestion",
+ "question": {
+ "id": "edit-question",
+ "type": "General",
+ "title": "What is the name of the person?",
+ "answers": [
+ {
+ "id": "first-name",
+ "label": "First name",
+ "mandatory": true,
+ "type": "TextField"
+ },
+ {
+ "id": "last-name",
+ "label": "Last name",
+ "mandatory": true,
+ "type": "TextField"
+ }
+ ]
+ }
+ },
+ "remove_block": {
+ "id": "remove-person",
+ "type": "ListRemoveQuestion",
+ "question": {
+ "id": "remove-question",
+ "type": "General",
+ "title": "Are you sure you want to remove this person?",
+ "answers": [
+ {
+ "id": "remove-confirmation",
+ "mandatory": true,
+ "type": "Radio",
+ "options": [
+ {
+ "label": "Yes",
+ "value": "Yes",
+ "action": {
+ "type": "RemoveListItemAndAnswers"
+ }
+ },
+ {
+ "label": "No",
+ "value": "No"
+ }
+ ]
+ }
+ ]
+ }
+ },
+ "summary": {
+ "title": "Household members",
+ "item_title": {
+ "text": "{person_name}",
+ "placeholders": [
+ {
+ "placeholder": "person_name",
+ "transforms": [
+ {
+ "arguments": {
+ "delimiter": " ",
+ "list_to_concatenate": [
+ {
+ "source": "answers",
+ "identifier": "first-name"
+ },
+ {
+ "source": "answers",
+ "identifier": "last-name"
+ }
+ ]
+ },
+ "transform": "concatenate_list"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ {
+ "type": "Question",
+ "id": "question-block",
+ "question": {
+ "id": "question",
+ "title": "Question",
+ "description": ["The next question is used as a dependency in the repeating sections."],
+ "type": "General",
+ "answers": [
+ {
+ "id": "answer",
+ "mandatory": false,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ },
+ {
+ "type": "Question",
+ "id": "random-question-enabler-block",
+ "question": {
+ "id": "random-question-enabler-question",
+ "title": "Random question enabler",
+ "description": [
+ "Answering this question will enable the random question in the repeated section coming after the list collector."
+ ],
+ "type": "General",
+ "answers": [
+ {
+ "id": "random-question-enabler-answer",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "section-2",
+ "title": "Questions",
+ "summary": { "show_on_completion": true },
+ "repeat": {
+ "for_list": "people",
+ "title": {
+ "text": "{person_name}",
+ "placeholders": [
+ {
+ "placeholder": "person_name",
+ "transforms": [
+ {
+ "transform": "concatenate_list",
+ "arguments": {
+ "list_to_concatenate": [
+ {
+ "source": "answers",
+ "identifier": "first-name"
+ },
+ {
+ "source": "answers",
+ "identifier": "last-name"
+ }
+ ],
+ "delimiter": " "
+ }
+ }
+ ]
+ }
+ ]
+ }
+ },
+ "groups": [
+ {
+ "id": "dob-group",
+ "title": "Date of birth",
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "dob-block",
+ "question": {
+ "answers": [
+ {
+ "id": "date-of-birth-answer",
+ "mandatory": false,
+ "maximum": {
+ "value": "now"
+ },
+ "minimum": {
+ "offset_by": {
+ "years": -115
+ },
+ "value": "2019-10-13"
+ },
+ "type": "Date"
+ }
+ ],
+ "guidance": {
+ "contents": [
+ {
+ "description": "For example 31 12 1970"
+ }
+ ]
+ },
+ "id": "date-of-birth-question",
+ "title": {
+ "placeholders": [
+ {
+ "placeholder": "person_name_possessive",
+ "transforms": [
+ {
+ "arguments": {
+ "delimiter": " ",
+ "list_to_concatenate": [
+ {
+ "source": "answers",
+ "identifier": "first-name"
+ },
+ {
+ "source": "answers",
+ "identifier": "last-name"
+ }
+ ]
+ },
+ "transform": "concatenate_list"
+ },
+ {
+ "arguments": {
+ "string_to_format": {
+ "source": "previous_transform"
+ }
+ },
+ "transform": "format_possessive"
+ }
+ ]
+ }
+ ],
+ "text": "What is {person_name_possessive} date of birth?"
+ },
+ "type": "General"
+ }
+ },
+ {
+ "type": "Question",
+ "id": "other-question-block",
+ "question": {
+ "id": "other-question",
+ "answers": [
+ {
+ "id": "other-answer",
+ "mandatory": true,
+ "label": "Anything",
+ "type": "Number"
+ }
+ ],
+ "title": {
+ "placeholders": [
+ {
+ "placeholder": "person_name_possessive",
+ "transforms": [
+ {
+ "arguments": {
+ "delimiter": " ",
+ "list_to_concatenate": [
+ {
+ "source": "answers",
+ "identifier": "first-name"
+ },
+ {
+ "source": "answers",
+ "identifier": "last-name"
+ }
+ ]
+ },
+ "transform": "concatenate_list"
+ },
+ {
+ "arguments": {
+ "string_to_format": {
+ "source": "previous_transform"
+ }
+ },
+ "transform": "format_possessive"
+ }
+ ]
+ }
+ ],
+ "text": "Random question about {person_name_possessive}"
+ },
+ "description": ["Shows because the random question was completed in section 1"],
+ "type": "General"
+ },
+ "skip_conditions": {
+ "when": {
+ "!=": [
+ {
+ "source": "progress",
+ "selector": "block",
+ "identifier": "random-question-enabler-block"
+ },
+ "COMPLETED"
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/schemas/test/en/test_progress_section_value_source_repeating_sections.json b/schemas/test/en/test_progress_section_value_source_repeating_sections.json
new file mode 100644
index 0000000000..4f7c41b55c
--- /dev/null
+++ b/schemas/test/en/test_progress_section_value_source_repeating_sections.json
@@ -0,0 +1,392 @@
+{
+ "mime_type": "application/json/ons/eq",
+ "language": "en",
+ "schema_version": "0.0.1",
+ "survey_id": "139",
+ "theme": "default",
+ "title": "Progress Value Source Repeating Sections Test",
+ "data_version": "0.0.3",
+ "description": "Progress Value Source Repeating Sections Test",
+ "navigation": {
+ "visible": true
+ },
+ "metadata": [
+ {
+ "name": "user_id",
+ "type": "string"
+ },
+ {
+ "name": "period_id",
+ "type": "string"
+ },
+ {
+ "name": "ru_name",
+ "type": "string"
+ },
+ {
+ "name": "trad_as",
+ "type": "string",
+ "optional": true
+ }
+ ],
+ "questionnaire_flow": {
+ "type": "Hub",
+ "options": {}
+ },
+ "sections": [
+ {
+ "id": "section-1",
+ "title": "List collector + random question",
+ "groups": [
+ {
+ "id": "group",
+ "title": "List",
+ "blocks": [
+ {
+ "id": "list-collector",
+ "type": "ListCollector",
+ "for_list": "people",
+ "question": {
+ "id": "confirmation-question",
+ "type": "General",
+ "title": "Does anyone else live here?",
+ "answers": [
+ {
+ "id": "anyone-else",
+ "mandatory": true,
+ "type": "Radio",
+ "options": [
+ {
+ "label": "Yes",
+ "value": "Yes",
+ "action": {
+ "type": "RedirectToListAddBlock"
+ }
+ },
+ {
+ "label": "No",
+ "value": "No"
+ }
+ ]
+ }
+ ]
+ },
+ "add_block": {
+ "id": "add-person",
+ "type": "ListAddQuestion",
+ "question": {
+ "id": "add-question",
+ "type": "General",
+ "title": "What is the name of the person?",
+ "answers": [
+ {
+ "id": "first-name",
+ "label": "First name",
+ "mandatory": true,
+ "type": "TextField"
+ },
+ {
+ "id": "last-name",
+ "label": "Last name",
+ "mandatory": true,
+ "type": "TextField"
+ }
+ ]
+ }
+ },
+ "edit_block": {
+ "id": "edit-person",
+ "type": "ListEditQuestion",
+ "question": {
+ "id": "edit-question",
+ "type": "General",
+ "title": "What is the name of the person?",
+ "answers": [
+ {
+ "id": "first-name",
+ "label": "First name",
+ "mandatory": true,
+ "type": "TextField"
+ },
+ {
+ "id": "last-name",
+ "label": "Last name",
+ "mandatory": true,
+ "type": "TextField"
+ }
+ ]
+ }
+ },
+ "remove_block": {
+ "id": "remove-person",
+ "type": "ListRemoveQuestion",
+ "question": {
+ "id": "remove-question",
+ "type": "General",
+ "title": "Are you sure you want to remove this person?",
+ "answers": [
+ {
+ "id": "remove-confirmation",
+ "mandatory": true,
+ "type": "Radio",
+ "options": [
+ {
+ "label": "Yes",
+ "value": "Yes",
+ "action": {
+ "type": "RemoveListItemAndAnswers"
+ }
+ },
+ {
+ "label": "No",
+ "value": "No"
+ }
+ ]
+ }
+ ]
+ }
+ },
+ "summary": {
+ "title": "Household members",
+ "item_title": {
+ "text": "{person_name}",
+ "placeholders": [
+ {
+ "placeholder": "person_name",
+ "transforms": [
+ {
+ "arguments": {
+ "delimiter": " ",
+ "list_to_concatenate": [
+ {
+ "source": "answers",
+ "identifier": "first-name"
+ },
+ {
+ "source": "answers",
+ "identifier": "last-name"
+ }
+ ]
+ },
+ "transform": "concatenate_list"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ {
+ "type": "Question",
+ "id": "question-block",
+ "question": {
+ "id": "question",
+ "title": "Question",
+ "description": ["The next question is used as a dependency in the repeating sections."],
+ "type": "General",
+ "answers": [
+ {
+ "id": "answer",
+ "mandatory": false,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ },
+ {
+ "type": "Question",
+ "id": "random-question-enabler-block",
+ "question": {
+ "id": "random-question-enabler-question",
+ "title": "Random question enabler",
+ "description": [
+ "Answering this question will enable the random question in the repeated section coming after the list collector."
+ ],
+ "type": "General",
+ "answers": [
+ {
+ "id": "random-question-enabler-answer",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "section-2",
+ "title": "Questions",
+ "summary": { "show_on_completion": true },
+ "repeat": {
+ "for_list": "people",
+ "title": {
+ "text": "{person_name}",
+ "placeholders": [
+ {
+ "placeholder": "person_name",
+ "transforms": [
+ {
+ "transform": "concatenate_list",
+ "arguments": {
+ "list_to_concatenate": [
+ {
+ "source": "answers",
+ "identifier": "first-name"
+ },
+ {
+ "source": "answers",
+ "identifier": "last-name"
+ }
+ ],
+ "delimiter": " "
+ }
+ }
+ ]
+ }
+ ]
+ }
+ },
+ "groups": [
+ {
+ "id": "dob-group",
+ "title": "Date of birth",
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "dob-block",
+ "question": {
+ "answers": [
+ {
+ "id": "date-of-birth-answer",
+ "mandatory": false,
+ "maximum": {
+ "value": "now"
+ },
+ "minimum": {
+ "offset_by": {
+ "years": -115
+ },
+ "value": "2019-10-13"
+ },
+ "type": "Date"
+ }
+ ],
+ "guidance": {
+ "contents": [
+ {
+ "description": "For example 31 12 1970"
+ }
+ ]
+ },
+ "id": "date-of-birth-question",
+ "title": {
+ "placeholders": [
+ {
+ "placeholder": "person_name_possessive",
+ "transforms": [
+ {
+ "arguments": {
+ "delimiter": " ",
+ "list_to_concatenate": [
+ {
+ "source": "answers",
+ "identifier": "first-name"
+ },
+ {
+ "source": "answers",
+ "identifier": "last-name"
+ }
+ ]
+ },
+ "transform": "concatenate_list"
+ },
+ {
+ "arguments": {
+ "string_to_format": {
+ "source": "previous_transform"
+ }
+ },
+ "transform": "format_possessive"
+ }
+ ]
+ }
+ ],
+ "text": "What is {person_name_possessive} date of birth?"
+ },
+ "type": "General"
+ }
+ },
+ {
+ "type": "Question",
+ "id": "other-question-block",
+ "question": {
+ "id": "other-question",
+ "answers": [
+ {
+ "id": "other-answer",
+ "mandatory": true,
+ "label": "Anything",
+ "type": "Number"
+ }
+ ],
+ "title": {
+ "placeholders": [
+ {
+ "placeholder": "person_name_possessive",
+ "transforms": [
+ {
+ "arguments": {
+ "delimiter": " ",
+ "list_to_concatenate": [
+ {
+ "source": "answers",
+ "identifier": "first-name"
+ },
+ {
+ "source": "answers",
+ "identifier": "last-name"
+ }
+ ]
+ },
+ "transform": "concatenate_list"
+ },
+ {
+ "arguments": {
+ "string_to_format": {
+ "source": "previous_transform"
+ }
+ },
+ "transform": "format_possessive"
+ }
+ ]
+ }
+ ],
+ "text": "Random question about {person_name_possessive}"
+ },
+ "description": ["Shows because the random question was completed in section 1"],
+ "type": "General"
+ },
+ "skip_conditions": {
+ "when": {
+ "!=": [
+ {
+ "source": "progress",
+ "selector": "section",
+ "identifier": "section-1"
+ },
+ "COMPLETED"
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/schemas/test/en/test_progress_value_source_blocks.json b/schemas/test/en/test_progress_value_source_blocks.json
new file mode 100644
index 0000000000..3a10d2a382
--- /dev/null
+++ b/schemas/test/en/test_progress_value_source_blocks.json
@@ -0,0 +1,210 @@
+{
+ "mime_type": "application/json/ons/eq",
+ "language": "en",
+ "schema_version": "0.0.1",
+ "data_version": "0.0.3",
+ "survey_id": "001",
+ "title": "Test progress value source",
+ "theme": "default",
+ "description": "A test survey for testing progres value source referencing blocks",
+ "metadata": [
+ {
+ "name": "user_id",
+ "type": "string"
+ },
+ {
+ "name": "period_id",
+ "type": "string"
+ },
+ {
+ "name": "ru_name",
+ "type": "string"
+ }
+ ],
+ "questionnaire_flow": {
+ "type": "Linear",
+ "options": {
+ "summary": {
+ "collapsible": false
+ }
+ }
+ },
+ "sections": [
+ {
+ "id": "section-1",
+ "groups": [
+ {
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "s1-b1",
+ "question": {
+ "id": "s1-b1-q1",
+ "title": "Section 1 Question 1",
+ "description": [
+ "If you answer 0, then the second question will be skipped because of a routing rule, as well as the fourth question because of a skip condition referencing the progress of question 2, as well as the 6th question because of a routing rule referencing the progress of question 4.",
+ "So only questions 3, 5, 7 will be displayed.",
+ "Otherwise, questions 2, 4 and 6 can also display."
+ ],
+ "type": "General",
+ "answers": [
+ {
+ "id": "s1-b1-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ },
+ "routing_rules": [
+ {
+ "when": {
+ "==": [
+ {
+ "source": "answers",
+ "identifier": "s1-b1-q1-a1"
+ },
+ 0
+ ]
+ },
+ "block": "s1-b3"
+ },
+ {
+ "block": "s1-b2"
+ }
+ ]
+ },
+ {
+ "type": "Question",
+ "id": "s1-b2",
+ "question": {
+ "id": "s1-b2-q1",
+ "title": "Section 1 Question 2",
+ "description": ["Showing this question because question 1 value is not 0"],
+ "type": "General",
+ "answers": [
+ {
+ "id": "s1-b2-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ },
+ {
+ "type": "Question",
+ "id": "s1-b3",
+ "question": {
+ "id": "s1-b3-q1",
+ "title": "Section 1 Question 3",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s1-b3-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ },
+ {
+ "type": "Question",
+ "id": "s1-b4",
+ "question": {
+ "id": "s1-b4-q1",
+ "title": "Section 1 Question 4",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s1-b4-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ },
+ "skip_conditions": {
+ "when": {
+ "!=": [{ "source": "progress", "selector": "block", "identifier": "s1-b2" }, "COMPLETED"]
+ }
+ }
+ },
+ {
+ "type": "Question",
+ "id": "s1-b5",
+ "question": {
+ "id": "s1-b5-q1",
+ "title": "Section 1 Question 5",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s1-b5-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ },
+ "routing_rules": [
+ {
+ "when": {
+ "==": [
+ {
+ "source": "progress",
+ "selector": "block",
+ "identifier": "s1-b4"
+ },
+ "COMPLETED"
+ ]
+ },
+ "block": "s1-b6"
+ },
+ {
+ "block": "s1-b7"
+ }
+ ]
+ },
+ {
+ "type": "Question",
+ "id": "s1-b6",
+ "question": {
+ "id": "s1-b6-q1",
+ "title": "Section 1 Question 6",
+ "description": ["Showing this question because question 4 was completed"],
+ "type": "General",
+ "answers": [
+ {
+ "id": "s1-b6-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ },
+ {
+ "type": "Question",
+ "id": "s1-b7",
+ "question": {
+ "id": "s1-b7-q1",
+ "title": "Section 1 Question 7",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s1-b7-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ }
+ ],
+ "id": "group-1"
+ }
+ ]
+ }
+ ]
+}
diff --git a/schemas/test/en/test_progress_value_source_blocks_cross_section.json b/schemas/test/en/test_progress_value_source_blocks_cross_section.json
new file mode 100644
index 0000000000..d8cb40da44
--- /dev/null
+++ b/schemas/test/en/test_progress_value_source_blocks_cross_section.json
@@ -0,0 +1,224 @@
+{
+ "mime_type": "application/json/ons/eq",
+ "language": "en",
+ "schema_version": "0.0.1",
+ "data_version": "0.0.3",
+ "survey_id": "001",
+ "title": "Test progress value source",
+ "theme": "default",
+ "description": "A test survey for testing progres value source referencing blocks",
+ "metadata": [
+ {
+ "name": "user_id",
+ "type": "string"
+ },
+ {
+ "name": "period_id",
+ "type": "string"
+ },
+ {
+ "name": "ru_name",
+ "type": "string"
+ }
+ ],
+ "questionnaire_flow": {
+ "type": "Hub",
+ "options": {}
+ },
+ "sections": [
+ {
+ "id": "section-1",
+ "title": "Section One",
+ "summary": {
+ "show_on_completion": true
+ },
+ "groups": [
+ {
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "s1-b1",
+ "question": {
+ "id": "s1-b1-q1",
+ "title": "Section 1 Question 1",
+ "description": [
+ "If you answer 0, then the second question will be skipped because of a routing rule, as well as the fourth question because of a skip condition referencing the progress of question 2, as well as the 6th question in the Second Section because of a routing rule referencing the progress of question 4.",
+ "So only question 3 in Section One, and questions 5 and 7 in Section Two will be displayed.",
+ "Otherwise, questions 2 and 4 in Section Ona and question 6 in Section Two can also display."
+ ],
+ "type": "General",
+ "answers": [
+ {
+ "id": "s1-b1-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ },
+ "routing_rules": [
+ {
+ "when": {
+ "==": [
+ {
+ "source": "answers",
+ "identifier": "s1-b1-q1-a1"
+ },
+ 0
+ ]
+ },
+ "block": "s1-b3"
+ },
+ {
+ "block": "s1-b2"
+ }
+ ]
+ },
+ {
+ "type": "Question",
+ "id": "s1-b2",
+ "question": {
+ "id": "s1-b2-q1",
+ "title": "Section 1 Question 2",
+ "description": ["Showing this question because question 1 value is not 0"],
+ "type": "General",
+ "answers": [
+ {
+ "id": "s1-b2-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ },
+ {
+ "type": "Question",
+ "id": "s1-b3",
+ "question": {
+ "id": "s1-b3-q1",
+ "title": "Section 1 Question 3",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s1-b3-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ },
+ {
+ "type": "Question",
+ "id": "s1-b4",
+ "question": {
+ "id": "s1-b4-q1",
+ "title": "Section 1 Question 4",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s1-b4-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ },
+ "skip_conditions": {
+ "when": {
+ "!=": [{ "source": "progress", "selector": "block", "identifier": "s1-b2" }, "COMPLETED"]
+ }
+ }
+ }
+ ],
+ "id": "group-1"
+ }
+ ]
+ },
+ {
+ "id": "section-2",
+ "title": "Section Two",
+ "summary": {
+ "show_on_completion": true
+ },
+ "groups": [
+ {
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "s2-b5",
+ "question": {
+ "id": "s2-b5-q1",
+ "title": "Section 2 Question 5",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s2-b5-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ },
+ "routing_rules": [
+ {
+ "when": {
+ "==": [
+ {
+ "source": "progress",
+ "selector": "block",
+ "identifier": "s1-b4"
+ },
+ "COMPLETED"
+ ]
+ },
+ "block": "s2-b6"
+ },
+ {
+ "block": "s2-b7"
+ }
+ ]
+ },
+ {
+ "type": "Question",
+ "id": "s2-b6",
+ "question": {
+ "id": "s2-b6-q1",
+ "title": "Section 2 Question 6",
+ "description": ["Showing this question because question 4 in Section One was completed"],
+ "type": "General",
+ "answers": [
+ {
+ "id": "s2-b6-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ },
+ {
+ "type": "Question",
+ "id": "s2-b7",
+ "question": {
+ "id": "s2-b7-q1",
+ "title": "Section 2 Question 7",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s2-b7-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ }
+ ],
+ "id": "group-2"
+ }
+ ]
+ }
+ ]
+}
diff --git a/schemas/test/en/test_progress_value_source_calculated_summary.json b/schemas/test/en/test_progress_value_source_calculated_summary.json
new file mode 100644
index 0000000000..b8e3ff32ba
--- /dev/null
+++ b/schemas/test/en/test_progress_value_source_calculated_summary.json
@@ -0,0 +1,517 @@
+{
+ "mime_type": "application/json/ons/eq",
+ "language": "en",
+ "schema_version": "0.0.1",
+ "survey_id": "139",
+ "theme": "default",
+ "title": "Progress Value Source Repeating Sections Test",
+ "data_version": "0.0.3",
+ "description": "Progress Value Source Repeating Sections Test",
+ "navigation": {
+ "visible": true
+ },
+ "metadata": [
+ {
+ "name": "user_id",
+ "type": "string"
+ },
+ {
+ "name": "period_id",
+ "type": "string"
+ },
+ {
+ "name": "ru_name",
+ "type": "string"
+ },
+ {
+ "name": "trad_as",
+ "type": "string",
+ "optional": true
+ }
+ ],
+ "questionnaire_flow": {
+ "type": "Hub",
+ "options": {}
+ },
+ "sections": [
+ {
+ "id": "section-1",
+ "title": "Calculated Summary",
+ "summary": { "show_on_completion": true },
+ "groups": [
+ {
+ "id": "group-1",
+ "title": "Calculated Summary group",
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "first-number-block",
+ "question": {
+ "id": "first-number-question",
+ "title": "First Number Question Title",
+ "type": "General",
+ "answers": [
+ {
+ "id": "first-number-answer",
+ "label": "First answer label",
+ "mandatory": true,
+ "type": "Currency",
+ "currency": "GBP",
+ "decimal_places": 2
+ }
+ ]
+ }
+ },
+ {
+ "type": "Question",
+ "id": "second-number-block",
+ "question": {
+ "id": "second-number-question",
+ "title": "Second Number Question Title",
+ "type": "General",
+ "answers": [
+ {
+ "id": "second-number-answer",
+ "label": "Second answer label",
+ "mandatory": true,
+ "type": "Currency",
+ "currency": "GBP",
+ "decimal_places": 2
+ }
+ ]
+ }
+ },
+ {
+ "type": "CalculatedSummary",
+ "id": "calculated-summary-block",
+ "title": "We calculate the total of currency values entered to be %(total)s. Is this correct?",
+ "calculation": {
+ "operation": {
+ "+": [
+ {
+ "source": "answers",
+ "identifier": "first-number-answer"
+ },
+ {
+ "source": "answers",
+ "identifier": "second-number-answer"
+ }
+ ]
+ },
+ "title": "Grand total of previous values"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "section-2",
+ "title": "Skippable random question + List collector",
+ "groups": [
+ {
+ "id": "group",
+ "title": "List",
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "s2-b1",
+ "question": {
+ "id": "s2-b1-q1",
+ "title": "Skippable random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s2-b1-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ },
+ "skip_conditions": {
+ "when": {
+ "!=": [
+ {
+ "source": "progress",
+ "selector": "block",
+ "identifier": "calculated-summary-block"
+ },
+ "COMPLETED"
+ ]
+ }
+ }
+ },
+ {
+ "id": "list-collector",
+ "type": "ListCollector",
+ "for_list": "people",
+ "question": {
+ "id": "confirmation-question",
+ "type": "General",
+ "title": "Does anyone else live here?",
+ "answers": [
+ {
+ "id": "anyone-else",
+ "mandatory": true,
+ "type": "Radio",
+ "options": [
+ {
+ "label": "Yes",
+ "value": "Yes",
+ "action": {
+ "type": "RedirectToListAddBlock"
+ }
+ },
+ {
+ "label": "No",
+ "value": "No"
+ }
+ ]
+ }
+ ]
+ },
+ "add_block": {
+ "id": "add-person",
+ "type": "ListAddQuestion",
+ "question": {
+ "id": "add-question",
+ "type": "General",
+ "title": "What is the name of the person?",
+ "answers": [
+ {
+ "id": "first-name",
+ "label": "First name",
+ "mandatory": true,
+ "type": "TextField"
+ },
+ {
+ "id": "last-name",
+ "label": "Last name",
+ "mandatory": true,
+ "type": "TextField"
+ }
+ ]
+ }
+ },
+ "edit_block": {
+ "id": "edit-person",
+ "type": "ListEditQuestion",
+ "question": {
+ "id": "edit-question",
+ "type": "General",
+ "title": "What is the name of the person?",
+ "answers": [
+ {
+ "id": "first-name",
+ "label": "First name",
+ "mandatory": true,
+ "type": "TextField"
+ },
+ {
+ "id": "last-name",
+ "label": "Last name",
+ "mandatory": true,
+ "type": "TextField"
+ }
+ ]
+ }
+ },
+ "remove_block": {
+ "id": "remove-person",
+ "type": "ListRemoveQuestion",
+ "question": {
+ "id": "remove-question",
+ "type": "General",
+ "title": "Are you sure you want to remove this person?",
+ "answers": [
+ {
+ "id": "remove-confirmation",
+ "mandatory": true,
+ "type": "Radio",
+ "options": [
+ {
+ "label": "Yes",
+ "value": "Yes",
+ "action": {
+ "type": "RemoveListItemAndAnswers"
+ }
+ },
+ {
+ "label": "No",
+ "value": "No"
+ }
+ ]
+ }
+ ]
+ }
+ },
+ "summary": {
+ "title": "Household members",
+ "item_title": {
+ "text": "{person_name}",
+ "placeholders": [
+ {
+ "placeholder": "person_name",
+ "transforms": [
+ {
+ "arguments": {
+ "delimiter": " ",
+ "list_to_concatenate": [
+ {
+ "source": "answers",
+ "identifier": "first-name"
+ },
+ {
+ "source": "answers",
+ "identifier": "last-name"
+ }
+ ]
+ },
+ "transform": "concatenate_list"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "section-3",
+ "title": "Repeating section",
+ "summary": { "show_on_completion": true },
+ "repeat": {
+ "for_list": "people",
+ "title": {
+ "text": "{person_name}",
+ "placeholders": [
+ {
+ "placeholder": "person_name",
+ "transforms": [
+ {
+ "transform": "concatenate_list",
+ "arguments": {
+ "list_to_concatenate": [
+ {
+ "source": "answers",
+ "identifier": "first-name"
+ },
+ {
+ "source": "answers",
+ "identifier": "last-name"
+ }
+ ],
+ "delimiter": " "
+ }
+ }
+ ]
+ }
+ ]
+ }
+ },
+ "groups": [
+ {
+ "id": "dob-group",
+ "title": "Date of birth",
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "dob-block",
+ "question": {
+ "answers": [
+ {
+ "id": "date-of-birth-answer",
+ "mandatory": false,
+ "maximum": {
+ "value": "now"
+ },
+ "minimum": {
+ "offset_by": {
+ "years": -115
+ },
+ "value": "2019-10-13"
+ },
+ "type": "Date"
+ }
+ ],
+ "guidance": {
+ "contents": [
+ {
+ "description": "For example 31 12 1970"
+ }
+ ]
+ },
+ "id": "date-of-birth-question",
+ "title": {
+ "placeholders": [
+ {
+ "placeholder": "person_name_possessive",
+ "transforms": [
+ {
+ "arguments": {
+ "delimiter": " ",
+ "list_to_concatenate": [
+ {
+ "source": "answers",
+ "identifier": "first-name"
+ },
+ {
+ "source": "answers",
+ "identifier": "last-name"
+ }
+ ]
+ },
+ "transform": "concatenate_list"
+ },
+ {
+ "arguments": {
+ "string_to_format": {
+ "source": "previous_transform"
+ }
+ },
+ "transform": "format_possessive"
+ }
+ ]
+ }
+ ],
+ "text": "What is {person_name_possessive} date of birth?"
+ },
+ "type": "General"
+ }
+ },
+ {
+ "type": "Question",
+ "id": "other-question-block",
+ "question": {
+ "id": "other-question",
+ "answers": [
+ {
+ "id": "other-answer",
+ "mandatory": true,
+ "label": "Anything",
+ "type": "Number"
+ }
+ ],
+ "title": {
+ "placeholders": [
+ {
+ "placeholder": "person_name_possessive",
+ "transforms": [
+ {
+ "arguments": {
+ "delimiter": " ",
+ "list_to_concatenate": [
+ {
+ "source": "answers",
+ "identifier": "first-name"
+ },
+ {
+ "source": "answers",
+ "identifier": "last-name"
+ }
+ ]
+ },
+ "transform": "concatenate_list"
+ },
+ {
+ "arguments": {
+ "string_to_format": {
+ "source": "previous_transform"
+ }
+ },
+ "transform": "format_possessive"
+ }
+ ]
+ }
+ ],
+ "text": "Random question about {person_name_possessive}"
+ },
+ "description": ["Shows because the calculated summary was completed in section 1"],
+ "type": "General"
+ },
+ "skip_conditions": {
+ "when": {
+ "!=": [
+ {
+ "source": "progress",
+ "selector": "block",
+ "identifier": "calculated-summary-block"
+ },
+ "COMPLETED"
+ ]
+ }
+ }
+ },
+ {
+ "type": "Question",
+ "id": "other-question-block-2",
+ "question": {
+ "id": "other-question-2",
+ "answers": [
+ {
+ "id": "other-answer-2",
+ "mandatory": true,
+ "label": "Anything",
+ "type": "Number"
+ }
+ ],
+ "title": {
+ "placeholders": [
+ {
+ "placeholder": "person_name_possessive",
+ "transforms": [
+ {
+ "arguments": {
+ "delimiter": " ",
+ "list_to_concatenate": [
+ {
+ "source": "answers",
+ "identifier": "first-name"
+ },
+ {
+ "source": "answers",
+ "identifier": "last-name"
+ }
+ ]
+ },
+ "transform": "concatenate_list"
+ },
+ {
+ "arguments": {
+ "string_to_format": {
+ "source": "previous_transform"
+ }
+ },
+ "transform": "format_possessive"
+ }
+ ]
+ }
+ ],
+ "text": "Another random question about {person_name_possessive}"
+ },
+ "description": ["Shows because block 2 of this repeating section was completed."],
+ "type": "General"
+ },
+ "skip_conditions": {
+ "when": {
+ "!=": [
+ {
+ "source": "progress",
+ "selector": "block",
+ "identifier": "other-question-block"
+ },
+ "COMPLETED"
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/schemas/test/en/test_progress_value_source_calculated_summary_extended.json b/schemas/test/en/test_progress_value_source_calculated_summary_extended.json
new file mode 100644
index 0000000000..9a11b9b5f9
--- /dev/null
+++ b/schemas/test/en/test_progress_value_source_calculated_summary_extended.json
@@ -0,0 +1,1129 @@
+{
+ "mime_type": "application/json/ons/eq",
+ "language": "en",
+ "schema_version": "0.0.1",
+ "survey_id": "139",
+ "theme": "default",
+ "title": "Progress Value Source Caluclated Summary (Extended)",
+ "data_version": "0.0.3",
+ "description": "An extended version of the Progress Value Source Calculated Summary schema intended to test chained dependency evaluation for progress value sources where multiple sections have progress value source dependencies on one another",
+ "navigation": {
+ "visible": true
+ },
+ "metadata": [
+ {
+ "name": "user_id",
+ "type": "string"
+ },
+ {
+ "name": "period_id",
+ "type": "string"
+ },
+ {
+ "name": "ru_name",
+ "type": "string"
+ },
+ {
+ "name": "trad_as",
+ "type": "string",
+ "optional": true
+ }
+ ],
+ "questionnaire_flow": {
+ "type": "Hub",
+ "options": {
+ "required_completed_sections": ["introduction-section"]
+ }
+ },
+ "sections": [
+ {
+ "id": "introduction-section",
+ "title": "Guidance",
+ "show_on_hub": false,
+ "groups": [
+ {
+ "blocks": [
+ {
+ "id": "interstitial",
+ "content": {
+ "title": "Guidance for completing this test schema",
+ "contents": [
+ {
+ "description": "This schema was created in order to ensure that dependencies based on a progress value sources captured in order."
+ },
+ {
+ "description": "It is also being used to test progress value sources with chained dependents. For example, In this schema, Sections 7, 8, 9 and 10 are all dependent on Section 2, and Sections 11 and 12 are dependent on Section 9 and 10."
+ },
+ {
+ "description": "So we can use this schema to test journeys and ensure that all dependent sections are updated. For example if we had not started Section 2 yet, but Sections 8, 9 and 10 are all Complete, and sections 11 and 12 are Partially Completed. Given the dependencies in this schema, completing Section 2 would mean that the status of Sections 8, 9 and 10 would change to Partially Complete and Sections 11 and 12 to Complete."
+ }
+ ]
+ },
+ "type": "Interstitial"
+ }
+ ],
+ "id": "introduction-group",
+ "title": "Test Schema Guidance"
+ }
+ ]
+ },
+ {
+ "id": "section-1",
+ "title": "Calculated Summary",
+ "groups": [
+ {
+ "id": "group-1",
+ "title": "Calculated Summary group",
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "first-number-block",
+ "question": {
+ "id": "first-number-question",
+ "title": "First Number Question Title",
+ "type": "General",
+ "answers": [
+ {
+ "id": "first-number-answer",
+ "label": "First answer label",
+ "mandatory": true,
+ "type": "Currency",
+ "currency": "GBP",
+ "decimal_places": 2
+ }
+ ]
+ }
+ },
+ {
+ "type": "Question",
+ "id": "second-number-block",
+ "question": {
+ "id": "second-number-question",
+ "title": "Second Number Question Title",
+ "type": "General",
+ "answers": [
+ {
+ "id": "second-number-answer",
+ "label": "Second answer label",
+ "mandatory": true,
+ "type": "Currency",
+ "currency": "GBP",
+ "decimal_places": 2
+ }
+ ]
+ }
+ },
+ {
+ "type": "CalculatedSummary",
+ "id": "calculated-summary-block",
+ "title": "We calculate the total of currency values entered to be %(total)s. Is this correct?",
+ "calculation": {
+ "operation": {
+ "+": [
+ {
+ "source": "answers",
+ "identifier": "first-number-answer"
+ },
+ {
+ "source": "answers",
+ "identifier": "second-number-answer"
+ }
+ ]
+ },
+ "title": "Grand total of previous values"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "section-2",
+ "title": "Skippable random question + List collector",
+ "groups": [
+ {
+ "id": "group",
+ "title": "List",
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "s2-b1",
+ "question": {
+ "id": "s2-b1-q1",
+ "title": "Skippable random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s2-b1-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ },
+ "skip_conditions": {
+ "when": {
+ "!=": [
+ {
+ "source": "progress",
+ "selector": "block",
+ "identifier": "calculated-summary-block"
+ },
+ "COMPLETED"
+ ]
+ }
+ }
+ },
+ {
+ "id": "list-collector",
+ "type": "ListCollector",
+ "for_list": "people",
+ "question": {
+ "id": "confirmation-question",
+ "type": "General",
+ "title": "Does anyone else live here?",
+ "answers": [
+ {
+ "id": "anyone-else",
+ "mandatory": true,
+ "type": "Radio",
+ "options": [
+ {
+ "label": "Yes",
+ "value": "Yes",
+ "action": {
+ "type": "RedirectToListAddBlock"
+ }
+ },
+ {
+ "label": "No",
+ "value": "No"
+ }
+ ]
+ }
+ ]
+ },
+ "add_block": {
+ "id": "add-person",
+ "type": "ListAddQuestion",
+ "question": {
+ "id": "add-question",
+ "type": "General",
+ "title": "What is the name of the person?",
+ "answers": [
+ {
+ "id": "first-name",
+ "label": "First name",
+ "mandatory": true,
+ "type": "TextField"
+ },
+ {
+ "id": "last-name",
+ "label": "Last name",
+ "mandatory": true,
+ "type": "TextField"
+ }
+ ]
+ }
+ },
+ "edit_block": {
+ "id": "edit-person",
+ "type": "ListEditQuestion",
+ "question": {
+ "id": "edit-question",
+ "type": "General",
+ "title": "What is the name of the person?",
+ "answers": [
+ {
+ "id": "first-name",
+ "label": "First name",
+ "mandatory": true,
+ "type": "TextField"
+ },
+ {
+ "id": "last-name",
+ "label": "Last name",
+ "mandatory": true,
+ "type": "TextField"
+ }
+ ]
+ }
+ },
+ "remove_block": {
+ "id": "remove-person",
+ "type": "ListRemoveQuestion",
+ "question": {
+ "id": "remove-question",
+ "type": "General",
+ "title": "Are you sure you want to remove this person?",
+ "answers": [
+ {
+ "id": "remove-confirmation",
+ "mandatory": true,
+ "type": "Radio",
+ "options": [
+ {
+ "label": "Yes",
+ "value": "Yes",
+ "action": {
+ "type": "RemoveListItemAndAnswers"
+ }
+ },
+ {
+ "label": "No",
+ "value": "No"
+ }
+ ]
+ }
+ ]
+ }
+ },
+ "summary": {
+ "title": "Household members",
+ "item_title": {
+ "text": "{person_name}",
+ "placeholders": [
+ {
+ "placeholder": "person_name",
+ "transforms": [
+ {
+ "arguments": {
+ "delimiter": " ",
+ "list_to_concatenate": [
+ {
+ "source": "answers",
+ "identifier": "first-name"
+ },
+ {
+ "source": "answers",
+ "identifier": "last-name"
+ }
+ ]
+ },
+ "transform": "concatenate_list"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "section-3",
+ "title": "Repeating section",
+ "summary": { "show_on_completion": true },
+ "repeat": {
+ "for_list": "people",
+ "title": {
+ "text": "{person_name}",
+ "placeholders": [
+ {
+ "placeholder": "person_name",
+ "transforms": [
+ {
+ "transform": "concatenate_list",
+ "arguments": {
+ "list_to_concatenate": [
+ {
+ "source": "answers",
+ "identifier": "first-name"
+ },
+ {
+ "source": "answers",
+ "identifier": "last-name"
+ }
+ ],
+ "delimiter": " "
+ }
+ }
+ ]
+ }
+ ]
+ }
+ },
+ "groups": [
+ {
+ "id": "dob-group",
+ "title": "Date of birth",
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "dob-block",
+ "question": {
+ "answers": [
+ {
+ "id": "date-of-birth-answer",
+ "mandatory": false,
+ "maximum": {
+ "value": "now"
+ },
+ "minimum": {
+ "offset_by": {
+ "years": -115
+ },
+ "value": "2019-10-13"
+ },
+ "type": "Date"
+ }
+ ],
+ "guidance": {
+ "contents": [
+ {
+ "description": "For example 31 12 1970"
+ }
+ ]
+ },
+ "id": "date-of-birth-question",
+ "title": {
+ "placeholders": [
+ {
+ "placeholder": "person_name_possessive",
+ "transforms": [
+ {
+ "arguments": {
+ "delimiter": " ",
+ "list_to_concatenate": [
+ {
+ "source": "answers",
+ "identifier": "first-name"
+ },
+ {
+ "source": "answers",
+ "identifier": "last-name"
+ }
+ ]
+ },
+ "transform": "concatenate_list"
+ },
+ {
+ "arguments": {
+ "string_to_format": {
+ "source": "previous_transform"
+ }
+ },
+ "transform": "format_possessive"
+ }
+ ]
+ }
+ ],
+ "text": "What is {person_name_possessive} date of birth?"
+ },
+ "type": "General"
+ }
+ },
+ {
+ "type": "Question",
+ "id": "other-question-block",
+ "question": {
+ "id": "other-question",
+ "answers": [
+ {
+ "id": "other-answer",
+ "mandatory": true,
+ "label": "Anything",
+ "type": "Number"
+ }
+ ],
+ "title": {
+ "placeholders": [
+ {
+ "placeholder": "person_name_possessive",
+ "transforms": [
+ {
+ "arguments": {
+ "delimiter": " ",
+ "list_to_concatenate": [
+ {
+ "source": "answers",
+ "identifier": "first-name"
+ },
+ {
+ "source": "answers",
+ "identifier": "last-name"
+ }
+ ]
+ },
+ "transform": "concatenate_list"
+ },
+ {
+ "arguments": {
+ "string_to_format": {
+ "source": "previous_transform"
+ }
+ },
+ "transform": "format_possessive"
+ }
+ ]
+ }
+ ],
+ "text": "Random question about {person_name_possessive}"
+ },
+ "description": ["Shows because the calculated summary was completed in section 1"],
+ "type": "General"
+ },
+ "skip_conditions": {
+ "when": {
+ "!=": [
+ {
+ "source": "progress",
+ "selector": "block",
+ "identifier": "calculated-summary-block"
+ },
+ "COMPLETED"
+ ]
+ }
+ }
+ },
+ {
+ "type": "Question",
+ "id": "other-question-block-2",
+ "question": {
+ "id": "other-question-2",
+ "answers": [
+ {
+ "id": "other-answer-2",
+ "mandatory": true,
+ "label": "Anything",
+ "type": "Number"
+ }
+ ],
+ "title": {
+ "placeholders": [
+ {
+ "placeholder": "person_name_possessive",
+ "transforms": [
+ {
+ "arguments": {
+ "delimiter": " ",
+ "list_to_concatenate": [
+ {
+ "source": "answers",
+ "identifier": "first-name"
+ },
+ {
+ "source": "answers",
+ "identifier": "last-name"
+ }
+ ]
+ },
+ "transform": "concatenate_list"
+ },
+ {
+ "arguments": {
+ "string_to_format": {
+ "source": "previous_transform"
+ }
+ },
+ "transform": "format_possessive"
+ }
+ ]
+ }
+ ],
+ "text": "Another random question about {person_name_possessive}"
+ },
+ "description": ["Shows because block 2 of this repeating section was completed."],
+ "type": "General"
+ },
+ "skip_conditions": {
+ "when": {
+ "!=": [
+ {
+ "source": "progress",
+ "selector": "block",
+ "identifier": "other-question-block"
+ },
+ "COMPLETED"
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "section-4",
+ "title": "Section 4 (Dependent on Section 1)",
+ "groups": [
+ {
+ "id": "group-4",
+ "title": "List",
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "s4-b1",
+ "question": {
+ "id": "s4-b1-q1",
+ "title": "Skippable random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s4-b1-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ },
+ "skip_conditions": {
+ "when": {
+ "!=": [
+ {
+ "source": "progress",
+ "selector": "section",
+ "identifier": "section-1"
+ },
+ "COMPLETED"
+ ]
+ }
+ }
+ },
+ {
+ "type": "Question",
+ "id": "s4-b2",
+ "question": {
+ "id": "s4-b2-q1",
+ "title": "Random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s4-b2-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "section-5",
+ "title": "Section 5 (Dependent on Calc Summary Block Section 1)",
+ "groups": [
+ {
+ "id": "group-5",
+ "title": "List",
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "s5-b1",
+ "question": {
+ "id": "s5-b1-q1",
+ "title": "Skippable random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s5-b1-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ },
+ "skip_conditions": {
+ "when": {
+ "!=": [
+ {
+ "source": "progress",
+ "selector": "block",
+ "identifier": "calculated-summary-block"
+ },
+ "COMPLETED"
+ ]
+ }
+ }
+ },
+ {
+ "type": "Question",
+ "id": "s5-b2",
+ "question": {
+ "id": "s5-b2-q1",
+ "title": "Random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s5-b2-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "section-6",
+ "title": "Section 6 (Dependent on Section 4)",
+ "groups": [
+ {
+ "id": "group-6",
+ "title": "List",
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "s6-b1",
+ "question": {
+ "id": "s6-b1-q1",
+ "title": "Skippable random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s6-b1-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ },
+ "skip_conditions": {
+ "when": {
+ "!=": [
+ {
+ "source": "progress",
+ "selector": "section",
+ "identifier": "section-4"
+ },
+ "COMPLETED"
+ ]
+ }
+ }
+ },
+ {
+ "type": "Question",
+ "id": "s6-b2",
+ "question": {
+ "id": "s6-b2-q1",
+ "title": "Random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s6-b2-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "section-7",
+ "title": "Section 7 (Dependent on Section 5)",
+ "groups": [
+ {
+ "id": "group-7",
+ "title": "List",
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "s7-b1",
+ "question": {
+ "id": "s7-b1-q1",
+ "title": "Skippable random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s7-b1-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ },
+ "skip_conditions": {
+ "when": {
+ "!=": [
+ {
+ "source": "progress",
+ "selector": "section",
+ "identifier": "section-5"
+ },
+ "COMPLETED"
+ ]
+ }
+ }
+ },
+ {
+ "type": "Question",
+ "id": "s7-b2",
+ "question": {
+ "id": "s7-b2-q1",
+ "title": "Skippable random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s7-b2-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ },
+ "skip_conditions": {
+ "when": {
+ "!=": [
+ {
+ "source": "progress",
+ "selector": "section",
+ "identifier": "section-2"
+ },
+ "COMPLETED"
+ ]
+ }
+ }
+ },
+ {
+ "type": "Question",
+ "id": "s7-b3",
+ "question": {
+ "id": "s7-b3-q1",
+ "title": "Random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s7-b3-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "section-8",
+ "title": "Section 8 (Dependent on Section 7 and Section 2)",
+ "groups": [
+ {
+ "id": "group-8",
+ "title": "List",
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "s8-b1",
+ "question": {
+ "id": "s8-b1-q1",
+ "title": "Skippable random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s8-b1-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ },
+ "skip_conditions": {
+ "when": {
+ "!=": [
+ {
+ "source": "progress",
+ "selector": "section",
+ "identifier": "section-7"
+ },
+ "COMPLETED"
+ ]
+ }
+ }
+ },
+ {
+ "type": "Question",
+ "id": "s8-b2",
+ "question": {
+ "id": "s8-b2-q1",
+ "title": "Skippable random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s8-b2-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ },
+ "skip_conditions": {
+ "when": {
+ "!=": [
+ {
+ "source": "progress",
+ "selector": "section",
+ "identifier": "section-2"
+ },
+ "COMPLETED"
+ ]
+ }
+ }
+ },
+ {
+ "type": "Question",
+ "id": "s8-b3",
+ "question": {
+ "id": "s8-b3-q1",
+ "title": "Random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s8-b3-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "section-9",
+ "title": "Section 9 (Dependent on Section 2)",
+ "groups": [
+ {
+ "id": "group-9",
+ "title": "List",
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "s9-b1",
+ "question": {
+ "id": "s9-b1-q1",
+ "title": "Skippable random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s9-b1-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ },
+ "skip_conditions": {
+ "when": {
+ "!=": [
+ {
+ "source": "progress",
+ "selector": "section",
+ "identifier": "section-2"
+ },
+ "COMPLETED"
+ ]
+ }
+ }
+ },
+ {
+ "type": "Question",
+ "id": "s9-b2",
+ "question": {
+ "id": "s9-b2-q1",
+ "title": "Random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s9-b2-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "section-10",
+ "title": "Section 10 (Dependent on Section 2)",
+ "groups": [
+ {
+ "id": "group-10",
+ "title": "List",
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "s10-b1",
+ "question": {
+ "id": "s10-b1-q1",
+ "title": "Skippable random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s10-b1-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ },
+ "skip_conditions": {
+ "when": {
+ "!=": [
+ {
+ "source": "progress",
+ "selector": "section",
+ "identifier": "section-2"
+ },
+ "COMPLETED"
+ ]
+ }
+ }
+ },
+ {
+ "type": "Question",
+ "id": "s10-b2",
+ "question": {
+ "id": "s10-b2-q1",
+ "title": "Random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s10-b2-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "section-11",
+ "title": "Section 11 (Dependent on Section 10)",
+ "groups": [
+ {
+ "id": "group-11",
+ "title": "List",
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "s11-b1",
+ "question": {
+ "id": "s11-b1-q1",
+ "title": "Skippable random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s11-b1-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ },
+ "skip_conditions": {
+ "when": {
+ "!=": [
+ {
+ "source": "progress",
+ "selector": "section",
+ "identifier": "section-10"
+ },
+ "COMPLETED"
+ ]
+ }
+ }
+ },
+ {
+ "type": "Question",
+ "id": "s11-b2",
+ "question": {
+ "id": "s11-b2-q1",
+ "title": "Random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s11-b2-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "section-12",
+ "title": "Section 12 (Dependent on Section 9)",
+ "groups": [
+ {
+ "id": "group-12",
+ "title": "List",
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "s12-b1",
+ "question": {
+ "id": "s12-b1-q1",
+ "title": "Skippable random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s12-b1-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ },
+ "skip_conditions": {
+ "when": {
+ "!=": [
+ {
+ "source": "progress",
+ "selector": "section",
+ "identifier": "section-9"
+ },
+ "COMPLETED"
+ ]
+ }
+ }
+ },
+ {
+ "type": "Question",
+ "id": "s12-b2",
+ "question": {
+ "id": "s12-b2-q1",
+ "title": "Random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s12-b2-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/schemas/test/en/test_progress_value_source_repeating_sections_chained_dependencies.json b/schemas/test/en/test_progress_value_source_repeating_sections_chained_dependencies.json
new file mode 100644
index 0000000000..5e53c28755
--- /dev/null
+++ b/schemas/test/en/test_progress_value_source_repeating_sections_chained_dependencies.json
@@ -0,0 +1,490 @@
+{
+ "mime_type": "application/json/ons/eq",
+ "language": "en",
+ "schema_version": "0.0.1",
+ "survey_id": "139",
+ "theme": "default",
+ "title": "Progress Value Source Repeating Sections With Chained Dependencies Test",
+ "data_version": "0.0.3",
+ "description": "Progress Value Source Repeating Sections Test",
+ "navigation": {
+ "visible": true
+ },
+ "metadata": [
+ {
+ "name": "user_id",
+ "type": "string"
+ },
+ {
+ "name": "period_id",
+ "type": "string"
+ },
+ {
+ "name": "ru_name",
+ "type": "string"
+ },
+ {
+ "name": "trad_as",
+ "type": "string",
+ "optional": true
+ }
+ ],
+ "questionnaire_flow": {
+ "type": "Hub",
+ "options": {}
+ },
+ "sections": [
+ {
+ "id": "section-1",
+ "title": "Section 1",
+ "groups": [
+ {
+ "id": "group-1",
+ "title": "List",
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "s1-b2",
+ "question": {
+ "id": "s1-b1-q1",
+ "title": "Random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s1-b1-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "section-2",
+ "title": "Section 2 (Dependent on Section 1)",
+ "groups": [
+ {
+ "id": "group-2",
+ "title": "List",
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "s2-b1",
+ "question": {
+ "id": "s2-b1-q1",
+ "title": "Skippable random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s2-b1-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ },
+ "skip_conditions": {
+ "when": {
+ "!=": [
+ {
+ "source": "progress",
+ "selector": "section",
+ "identifier": "section-1"
+ },
+ "COMPLETED"
+ ]
+ }
+ }
+ },
+ {
+ "type": "Question",
+ "id": "s2-b2",
+ "question": {
+ "id": "s2-b2-q1",
+ "title": "Random question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s2-b2-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "section-3",
+ "title": "Section 3 (Depends on Section 2)",
+ "groups": [
+ {
+ "id": "group",
+ "title": "Second List Collector",
+ "blocks": [
+ {
+ "id": "second-list-collector",
+ "type": "ListCollector",
+ "for_list": "second-people",
+ "question": {
+ "id": "second-confirmation-question",
+ "type": "General",
+ "title": "Does anyone else live here?",
+ "answers": [
+ {
+ "id": "second-anyone-else",
+ "mandatory": true,
+ "type": "Radio",
+ "options": [
+ {
+ "label": "Yes",
+ "value": "Yes",
+ "action": {
+ "type": "RedirectToListAddBlock"
+ }
+ },
+ {
+ "label": "No",
+ "value": "No"
+ }
+ ]
+ }
+ ]
+ },
+ "add_block": {
+ "id": "second-add-person",
+ "type": "ListAddQuestion",
+ "question": {
+ "id": "second-add-question",
+ "type": "General",
+ "title": "What is the name of the person?",
+ "answers": [
+ {
+ "id": "second-first-name",
+ "label": "First name",
+ "mandatory": true,
+ "type": "TextField"
+ },
+ {
+ "id": "second-last-name",
+ "label": "Last name",
+ "mandatory": true,
+ "type": "TextField"
+ }
+ ]
+ }
+ },
+ "edit_block": {
+ "id": "second-edit-person",
+ "type": "ListEditQuestion",
+ "question": {
+ "id": "second-edit-question",
+ "type": "General",
+ "title": "What is the name of the person?",
+ "answers": [
+ {
+ "id": "second-first-name",
+ "label": "First name",
+ "mandatory": true,
+ "type": "TextField"
+ },
+ {
+ "id": "second-last-name",
+ "label": "Last name",
+ "mandatory": true,
+ "type": "TextField"
+ }
+ ]
+ }
+ },
+ "remove_block": {
+ "id": "second-remove-person",
+ "type": "ListRemoveQuestion",
+ "question": {
+ "id": "second-remove-question",
+ "type": "General",
+ "title": "Are you sure you want to remove this person?",
+ "answers": [
+ {
+ "id": "second-remove-confirmation",
+ "mandatory": true,
+ "type": "Radio",
+ "options": [
+ {
+ "label": "Yes",
+ "value": "Yes",
+ "action": {
+ "type": "RemoveListItemAndAnswers"
+ }
+ },
+ {
+ "label": "No",
+ "value": "No"
+ }
+ ]
+ }
+ ]
+ }
+ },
+ "summary": {
+ "title": "Household members",
+ "item_title": {
+ "text": "{person_name}",
+ "placeholders": [
+ {
+ "placeholder": "person_name",
+ "transforms": [
+ {
+ "arguments": {
+ "delimiter": " ",
+ "list_to_concatenate": [
+ {
+ "source": "answers",
+ "identifier": "second-first-name"
+ },
+ {
+ "source": "answers",
+ "identifier": "second-last-name"
+ }
+ ]
+ },
+ "transform": "concatenate_list"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ {
+ "type": "Question",
+ "id": "second-question-block",
+ "question": {
+ "id": "second-question",
+ "title": "Question",
+ "type": "General",
+ "answers": [
+ {
+ "id": "second-answer",
+ "mandatory": false,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ },
+ "skip_conditions": {
+ "when": {
+ "!=": [
+ {
+ "source": "progress",
+ "selector": "section",
+ "identifier": "section-2"
+ },
+ "COMPLETED"
+ ]
+ }
+ }
+ },
+ {
+ "type": "Question",
+ "id": "second-random-question-enabler-block",
+ "question": {
+ "id": "second-random-question-enabler-question",
+ "title": "Random question enabler",
+ "description": [
+ "Answering this question will enable the random question in the repeated section coming after the list collector."
+ ],
+ "type": "General",
+ "answers": [
+ {
+ "id": "second-random-question-enabler-answer",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "section-4",
+ "title": "Section 4 - Repeat (Depends on section 3)",
+ "summary": { "show_on_completion": true },
+ "repeat": {
+ "for_list": "second-people",
+ "title": {
+ "text": "{person_name}",
+ "placeholders": [
+ {
+ "placeholder": "person_name",
+ "transforms": [
+ {
+ "transform": "concatenate_list",
+ "arguments": {
+ "list_to_concatenate": [
+ {
+ "source": "answers",
+ "identifier": "second-first-name"
+ },
+ {
+ "source": "answers",
+ "identifier": "second-last-name"
+ }
+ ],
+ "delimiter": " "
+ }
+ }
+ ]
+ }
+ ]
+ }
+ },
+ "groups": [
+ {
+ "id": "second-dob-group",
+ "title": "Date of birth",
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "second-dob-block",
+ "question": {
+ "answers": [
+ {
+ "id": "second-date-of-birth-answer",
+ "mandatory": false,
+ "maximum": {
+ "value": "now"
+ },
+ "minimum": {
+ "offset_by": {
+ "years": -115
+ },
+ "value": "2019-10-13"
+ },
+ "type": "Date"
+ }
+ ],
+ "guidance": {
+ "contents": [
+ {
+ "description": "For example 31 12 1970"
+ }
+ ]
+ },
+ "id": "second-date-of-birth-question",
+ "title": {
+ "placeholders": [
+ {
+ "placeholder": "person_name_possessive",
+ "transforms": [
+ {
+ "arguments": {
+ "delimiter": " ",
+ "list_to_concatenate": [
+ {
+ "source": "answers",
+ "identifier": "second-first-name"
+ },
+ {
+ "source": "answers",
+ "identifier": "second-last-name"
+ }
+ ]
+ },
+ "transform": "concatenate_list"
+ },
+ {
+ "arguments": {
+ "string_to_format": {
+ "source": "previous_transform"
+ }
+ },
+ "transform": "format_possessive"
+ }
+ ]
+ }
+ ],
+ "text": "What is {person_name_possessive} date of birth?"
+ },
+ "type": "General"
+ }
+ },
+ {
+ "type": "Question",
+ "id": "second-other-question-block",
+ "question": {
+ "id": "second-other-question",
+ "answers": [
+ {
+ "id": "second-other-answer",
+ "mandatory": true,
+ "label": "Anything",
+ "type": "Number"
+ }
+ ],
+ "title": {
+ "placeholders": [
+ {
+ "placeholder": "person_name_possessive",
+ "transforms": [
+ {
+ "arguments": {
+ "delimiter": " ",
+ "list_to_concatenate": [
+ {
+ "source": "answers",
+ "identifier": "second-first-name"
+ },
+ {
+ "source": "answers",
+ "identifier": "second-last-name"
+ }
+ ]
+ },
+ "transform": "concatenate_list"
+ },
+ {
+ "arguments": {
+ "string_to_format": {
+ "source": "previous_transform"
+ }
+ },
+ "transform": "format_possessive"
+ }
+ ]
+ }
+ ],
+ "text": "Random question about {person_name_possessive}"
+ },
+ "description": ["Shows because section 2 was completed"],
+ "type": "General"
+ },
+ "skip_conditions": {
+ "when": {
+ "!=": [
+ {
+ "source": "progress",
+ "selector": "section",
+ "identifier": "section-2"
+ },
+ "COMPLETED"
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/schemas/test/en/test_progress_value_source_section_enabled_hub.json b/schemas/test/en/test_progress_value_source_section_enabled_hub.json
new file mode 100644
index 0000000000..e3babd5cb2
--- /dev/null
+++ b/schemas/test/en/test_progress_value_source_section_enabled_hub.json
@@ -0,0 +1,110 @@
+{
+ "mime_type": "application/json/ons/eq",
+ "language": "en",
+ "schema_version": "0.0.1",
+ "data_version": "0.0.3",
+ "survey_id": "001",
+ "title": "Test progress value source",
+ "theme": "default",
+ "description": "A test survey for testing progress value source section enabled in a hub flow",
+ "metadata": [
+ {
+ "name": "user_id",
+ "type": "string"
+ },
+ {
+ "name": "period_id",
+ "type": "string"
+ },
+ {
+ "name": "ru_name",
+ "type": "string"
+ }
+ ],
+ "questionnaire_flow": {
+ "type": "Hub",
+ "options": {}
+ },
+ "sections": [
+ {
+ "id": "section-1",
+ "title": "Section 1",
+ "groups": [
+ {
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "s1-b1",
+ "question": {
+ "id": "s1-b1-q1",
+ "title": "Section 1 Question 1",
+ "description": ["Always shows"],
+ "type": "General",
+ "answers": [
+ {
+ "id": "s1-b1-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ },
+ {
+ "type": "Question",
+ "id": "s1-b2",
+ "question": {
+ "id": "s1-b2-q1",
+ "title": "Section 1 Question 2",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s1-b2-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ }
+ ],
+ "id": "group-1"
+ }
+ ]
+ },
+ {
+ "id": "section-2",
+ "title": "Section 2",
+ "enabled": {
+ "when": {
+ "==": [{ "source": "progress", "selector": "section", "identifier": "section-1" }, "COMPLETED"]
+ }
+ },
+ "groups": [
+ {
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "s2-b1",
+ "question": {
+ "id": "s2-b1-q1",
+ "title": "Section 2 Question 1",
+ "description": ["This question always shows"],
+ "type": "General",
+ "answers": [
+ {
+ "id": "s2-b1-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ }
+ ],
+ "id": "group-2"
+ }
+ ]
+ }
+ ]
+}
diff --git a/schemas/test/en/test_progress_value_source_section_enabled_hub_complex.json b/schemas/test/en/test_progress_value_source_section_enabled_hub_complex.json
new file mode 100644
index 0000000000..4d647dbdb6
--- /dev/null
+++ b/schemas/test/en/test_progress_value_source_section_enabled_hub_complex.json
@@ -0,0 +1,251 @@
+{
+ "mime_type": "application/json/ons/eq",
+ "language": "en",
+ "schema_version": "0.0.1",
+ "data_version": "0.0.3",
+ "survey_id": "001",
+ "title": "Test progress value source",
+ "theme": "default",
+ "description": "A test survey for testing progress value source section enabled in a hub flow, with a mixture of skip conditions and section enabled conditions, and a mix of block and section references",
+ "metadata": [
+ {
+ "name": "user_id",
+ "type": "string"
+ },
+ {
+ "name": "period_id",
+ "type": "string"
+ },
+ {
+ "name": "ru_name",
+ "type": "string"
+ }
+ ],
+ "questionnaire_flow": {
+ "type": "Hub",
+ "options": {}
+ },
+ "sections": [
+ {
+ "id": "section-1",
+ "title": "Section 1",
+ "groups": [
+ {
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "s1-b1",
+ "question": {
+ "id": "s1-b1-q1",
+ "title": "Section 1 Question 1",
+ "description": ["Always shows. The next question in the section also shows when the answer is not 0"],
+ "type": "General",
+ "answers": [
+ {
+ "id": "s1-b1-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ },
+ "routing_rules": [
+ {
+ "when": {
+ "!=": [
+ {
+ "identifier": "s1-b1-q1-a1",
+ "source": "answers"
+ },
+ 0
+ ]
+ },
+ "block": "s1-b2"
+ },
+ {
+ "section": "End"
+ }
+ ]
+ },
+ {
+ "type": "Question",
+ "id": "s1-b2",
+ "question": {
+ "id": "s1-b2-q1",
+ "title": "Section 1 Question 2",
+ "type": "General",
+ "description": ["Shows if the answer to the previous question is not 0"],
+ "answers": [
+ {
+ "id": "s1-b2-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ }
+ ],
+ "id": "group-1"
+ }
+ ]
+ },
+ {
+ "id": "section-2",
+ "title": "Section 2",
+ "enabled": {
+ "when": {
+ "==": [{ "source": "progress", "selector": "section", "identifier": "section-1" }, "COMPLETED"]
+ }
+ },
+ "groups": [
+ {
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "s2-b1",
+ "question": {
+ "id": "s2-b1-q1",
+ "title": "Section 2 Question 1",
+ "description": ["This question always shows. The next question in the section also shows when the answer is not 0"],
+ "type": "General",
+ "answers": [
+ {
+ "id": "s2-b1-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ },
+ {
+ "type": "Question",
+ "id": "s2-b2",
+ "question": {
+ "id": "s2-b2-q1",
+ "title": "Section 2 Question 2",
+ "type": "General",
+ "description": ["Always shows"],
+ "answers": [
+ {
+ "id": "s2-b2-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ },
+ "routing_rules": [
+ {
+ "when": {
+ "!=": [
+ {
+ "identifier": "s2-b1-q1-a1",
+ "source": "answers"
+ },
+ 0
+ ]
+ },
+ "block": "s2-b3"
+ },
+ {
+ "section": "End"
+ }
+ ]
+ },
+ {
+ "type": "Question",
+ "id": "s2-b3",
+ "question": {
+ "id": "s2-b3-q1",
+ "title": "Section 2 Question 3",
+ "type": "General",
+ "description": ["Shows if the answer to the Section 2 Question 1 is not 0"],
+ "answers": [
+ {
+ "id": "s2-b3-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ }
+ ],
+ "id": "group-2"
+ }
+ ]
+ },
+ {
+ "id": "section-3",
+ "title": "Section 3",
+ "groups": [
+ {
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "s3-b1",
+ "question": {
+ "id": "s3-b1-q1",
+ "title": "Section 3 Question 1",
+ "description": ["Always shows"],
+ "type": "General",
+ "answers": [
+ {
+ "id": "s3-b1-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ }
+ ],
+ "id": "group-3"
+ }
+ ]
+ },
+ {
+ "id": "section-4",
+ "title": "Section 4",
+ "enabled": {
+ "when": {
+ "and": [
+ {
+ "==": [{ "source": "progress", "selector": "block", "identifier": "s2-b2" }, "COMPLETED"]
+ },
+ {
+ "==": [{ "source": "progress", "selector": "section", "identifier": "section-2" }, "COMPLETED"]
+ }
+ ]
+ }
+ },
+ "groups": [
+ {
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "s4-b1",
+ "question": {
+ "id": "s4-b1-q1",
+ "title": "Section 4 Question 1",
+ "description": ["This section shows if section 2 block 2 is completed, as well as section 2"],
+ "type": "General",
+ "answers": [
+ {
+ "id": "s4-b1-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ }
+ ],
+ "id": "group-4"
+ }
+ ]
+ }
+ ]
+}
diff --git a/schemas/test/en/test_progress_value_source_section_enabled_no_hub.json b/schemas/test/en/test_progress_value_source_section_enabled_no_hub.json
new file mode 100644
index 0000000000..a9f88839d2
--- /dev/null
+++ b/schemas/test/en/test_progress_value_source_section_enabled_no_hub.json
@@ -0,0 +1,114 @@
+{
+ "mime_type": "application/json/ons/eq",
+ "language": "en",
+ "schema_version": "0.0.1",
+ "data_version": "0.0.3",
+ "survey_id": "001",
+ "title": "Test progress value source",
+ "theme": "default",
+ "description": "A test survey for testing progress value source section enabled in a linear flow",
+ "metadata": [
+ {
+ "name": "user_id",
+ "type": "string"
+ },
+ {
+ "name": "period_id",
+ "type": "string"
+ },
+ {
+ "name": "ru_name",
+ "type": "string"
+ }
+ ],
+ "questionnaire_flow": {
+ "type": "Linear",
+ "options": {
+ "summary": {
+ "collapsible": false
+ }
+ }
+ },
+ "sections": [
+ {
+ "id": "section-1",
+ "title": "Section 1",
+ "groups": [
+ {
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "s1-b1",
+ "question": {
+ "id": "s1-b1-q1",
+ "title": "Section 1 Question 1",
+ "description": ["Always shows"],
+ "type": "General",
+ "answers": [
+ {
+ "id": "s1-b1-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ },
+ {
+ "type": "Question",
+ "id": "s1-b2",
+ "question": {
+ "id": "s1-b2-q1",
+ "title": "Section 1 Question 2",
+ "type": "General",
+ "answers": [
+ {
+ "id": "s1-b2-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ }
+ ],
+ "id": "group-1"
+ }
+ ]
+ },
+ {
+ "id": "section-2",
+ "title": "Section 2",
+ "enabled": {
+ "when": {
+ "==": [{ "source": "progress", "selector": "section", "identifier": "section-1" }, "COMPLETED"]
+ }
+ },
+ "groups": [
+ {
+ "blocks": [
+ {
+ "type": "Question",
+ "id": "s2-b1",
+ "question": {
+ "id": "s2-b1-q1",
+ "title": "Section 2 Question 1",
+ "description": ["This question always shows"],
+ "type": "General",
+ "answers": [
+ {
+ "id": "s2-b1-q1-a1",
+ "mandatory": true,
+ "label": "Enter any number",
+ "type": "Number"
+ }
+ ]
+ }
+ }
+ ],
+ "id": "group-2"
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/app/data_model/test_progress_store.py b/tests/app/data_model/test_progress_store.py
index 020bd38ffa..72b218d61f 100644
--- a/tests/app/data_model/test_progress_store.py
+++ b/tests/app/data_model/test_progress_store.py
@@ -68,7 +68,7 @@ def test_deserialisation():
store = ProgressStore(in_progress_sections)
assert store.get_section_status(section_id="s1") == CompletionStatus.IN_PROGRESS
- assert store.get_completed_block_ids("s1") == ["one", "two"]
+ assert store.get_completed_block_ids(section_id="s1") == ["one", "two"]
assert (
store.get_section_status(section_id="s2", list_item_id="abc123")
diff --git a/tests/app/data_model/test_questionnaire_store.py b/tests/app/data_model/test_questionnaire_store.py
index e416ac9dbb..f9d686770b 100644
--- a/tests/app/data_model/test_questionnaire_store.py
+++ b/tests/app/data_model/test_questionnaire_store.py
@@ -29,11 +29,17 @@ def test_questionnaire_store_json_loads(
expected_completed_block_ids = basic_input["PROGRESS"][0]["block_ids"][0]
assert (
- len(store.progress_store.get_completed_block_ids("a-test-section", "abc123"))
+ len(
+ store.progress_store.get_completed_block_ids(
+ section_id="a-test-section", list_item_id="abc123"
+ )
+ )
== 1
)
assert (
- store.progress_store.get_completed_block_ids("a-test-section", "abc123")[0]
+ store.progress_store.get_completed_block_ids(
+ section_id="a-test-section", list_item_id="abc123"
+ )[0]
== expected_completed_block_ids
)
diff --git a/tests/app/questionnaire/conftest.py b/tests/app/questionnaire/conftest.py
index 58ed5d4bae..876b236b79 100644
--- a/tests/app/questionnaire/conftest.py
+++ b/tests/app/questionnaire/conftest.py
@@ -1292,6 +1292,25 @@ def section_dependencies_new_calculated_summary_schema():
)
+@pytest.fixture
+def progress_block_dependencies_schema():
+ return load_schema_from_name("test_progress_value_source_calculated_summary")
+
+
+@pytest.fixture
+def progress_section_dependencies_schema():
+ return load_schema_from_name(
+ "test_progress_value_source_section_enabled_hub_complex"
+ )
+
+
+@pytest.fixture
+def progress_dependencies_schema():
+ return load_schema_from_name(
+ "test_progress_value_source_calculated_summary_extended"
+ )
+
+
@pytest.fixture
@pytest.mark.usefixtures("app", "gb_locale")
def placeholder_transform_question_dynamic_answers_json():
diff --git a/tests/app/questionnaire/rules/test_rule_evaluator.py b/tests/app/questionnaire/rules/test_rule_evaluator.py
index b2875a4686..981d788c30 100644
--- a/tests/app/questionnaire/rules/test_rule_evaluator.py
+++ b/tests/app/questionnaire/rules/test_rule_evaluator.py
@@ -12,6 +12,7 @@
from app.questionnaire.relationship_location import RelationshipLocation
from app.questionnaire.rules.operator import Operator
from app.questionnaire.rules.rule_evaluator import RuleEvaluator
+from app.utilities.schema import load_schema_from_name
from tests.app.questionnaire.conftest import get_metadata
from tests.app.questionnaire.test_value_source_resolver import get_list_items
@@ -45,6 +46,7 @@ def get_rule_evaluator(
section_id="test-section", block_id="test-block"
),
routing_path_block_ids: Optional[list] = None,
+ progress: ProgressStore = ProgressStore(),
):
if not schema:
schema = get_mock_schema()
@@ -60,7 +62,7 @@ def get_rule_evaluator(
list_store=list_store,
location=location,
routing_path_block_ids=routing_path_block_ids,
- progress_store=ProgressStore(),
+ progress_store=progress,
)
@@ -255,6 +257,45 @@ def test_metadata_source(metadata_value, expected_result):
)
+@pytest.mark.parametrize(
+ "identifier, selector, expected_result",
+ [("s1-b1", "block", True), ("s1-b2", "block", True), ("s1-b3", "block", False)],
+)
+def test_progress_source(identifier, selector, expected_result):
+ schema = load_schema_from_name("test_progress_value_source_blocks")
+ in_progress_sections = [
+ {
+ "section_id": "section-1",
+ "list_item_id": None,
+ "status": "COMPLETED",
+ "block_ids": ["s1-b1", "s1-b2"],
+ },
+ ]
+
+ rule_evaluator = get_rule_evaluator(
+ schema=schema,
+ location=Location(section_id="section-1", block_id="s1-b3", list_item_id=None),
+ progress=ProgressStore(in_progress_sections),
+ routing_path_block_ids=["s1-b1", "s1-b2", "s1-b3"],
+ )
+
+ assert (
+ rule_evaluator.evaluate(
+ rule={
+ Operator.EQUAL: [
+ {
+ "source": "progress",
+ "selector": selector,
+ "identifier": identifier,
+ },
+ "COMPLETED",
+ ]
+ },
+ )
+ is expected_result
+ )
+
+
@pytest.mark.parametrize(
"response_metadata_value, expected_result",
[
diff --git a/tests/app/questionnaire/test_placeholder_parser.py b/tests/app/questionnaire/test_placeholder_parser.py
index c57f6be309..6974b969c1 100644
--- a/tests/app/questionnaire/test_placeholder_parser.py
+++ b/tests/app/questionnaire/test_placeholder_parser.py
@@ -1,4 +1,3 @@
-# pylint: disable=too-many-lines
from mock import Mock
from app.data_models import ProgressStore
@@ -9,6 +8,8 @@
from app.utilities.schema import load_schema_from_name
from tests.app.questionnaire.conftest import get_metadata
+# pylint: disable=too-many-lines
+
def test_parse_placeholders(placeholder_list, parser):
placeholders = parser(placeholder_list)
diff --git a/tests/app/questionnaire/test_questionnaire_schema.py b/tests/app/questionnaire/test_questionnaire_schema.py
index 98b923bc09..43b78f273f 100644
--- a/tests/app/questionnaire/test_questionnaire_schema.py
+++ b/tests/app/questionnaire/test_questionnaire_schema.py
@@ -1,6 +1,7 @@
from collections import abc
import pytest
+from ordered_set import OrderedSet
from werkzeug.datastructures import ImmutableDict
from app.questionnaire.questionnaire_schema import AnswerDependent, QuestionnaireSchema
@@ -776,6 +777,67 @@ def test_when_rules_section_dependencies_new_calculated_summary(
} == schema.when_rules_section_dependencies_by_answer
+def test_progress_block_dependencies(
+ progress_block_dependencies_schema,
+):
+ schema = progress_block_dependencies_schema
+
+ assert {
+ "section-1": {"calculated-summary-block": {"section-2", "section-3"}}
+ } == schema.when_rules_block_dependencies_by_section_for_progress_value_source
+
+
+def test_progress_section_dependencies(
+ progress_section_dependencies_schema,
+):
+ schema = progress_section_dependencies_schema
+
+ assert {
+ "section-1": {"section-2"},
+ "section-2": {"section-4"},
+ } == schema.when_rules_section_dependencies_by_section_for_progress_value_source
+
+
+def test_progress_block_and_section_dependencies_are_ordered(
+ progress_dependencies_schema,
+):
+ schema = progress_dependencies_schema
+
+ assert (
+ ImmutableDict(
+ {
+ "section-1": OrderedSet(["section-4"]),
+ "section-2": OrderedSet(
+ ["section-7", "section-8", "section-9", "section-10"]
+ ),
+ "section-4": OrderedSet(["section-6"]),
+ "section-5": OrderedSet(["section-7"]),
+ "section-7": OrderedSet(["section-8"]),
+ "section-9": OrderedSet(["section-12"]),
+ "section-10": OrderedSet(["section-11"]),
+ }
+ )
+ == schema.when_rules_section_dependencies_by_section_for_progress_value_source
+ )
+
+ assert (
+ ImmutableDict(
+ {
+ "section-1": {
+ "calculated-summary-block": OrderedSet(
+ [
+ "section-2",
+ "section-3",
+ "section-5",
+ ]
+ )
+ }
+ }
+ )
+ == schema.when_rules_block_dependencies_by_section_for_progress_value_source
+ )
+
+
@pytest.mark.parametrize(
"rule, expected_result",
(
@@ -802,3 +864,29 @@ def test_when_rules_section_dependencies_new_calculated_summary(
def test_has_operator_returns_correct_value(rule, expected_result):
result = QuestionnaireSchema.has_operator(rule)
assert result == expected_result
+
+
+def test_progress_dependencies_for_when_rules(
+ progress_dependencies_schema,
+):
+ """
+ Asserts that the dependencies captured by
+ schema.when_rules_section_dependencies_by_section_for_progress_value_source and
+ schema.when_rules_section_dependencies_by_block_for_progress_value_source are flipped
+ correctly so that progress dependencies can be evaluated with our normal when rules
+ """
+ schema = progress_dependencies_schema
+
+ assert {
+ "section-10": {"section-2"},
+ "section-11": {"section-10"},
+ "section-12": {"section-9"},
+ "section-2": {"section-1"},
+ "section-3": {"section-1"},
+ "section-4": {"section-1"},
+ "section-5": {"section-1"},
+ "section-6": {"section-4"},
+ "section-7": {"section-5", "section-2"},
+ "section-8": {"section-7", "section-2"},
+ "section-9": {"section-2"},
+ } == schema.when_rules_section_dependencies_for_progress
diff --git a/tests/app/questionnaire/test_questionnaire_store_updater.py b/tests/app/questionnaire/test_questionnaire_store_updater.py
index 03b09a3b77..4b900f9e4e 100644
--- a/tests/app/questionnaire/test_questionnaire_store_updater.py
+++ b/tests/app/questionnaire/test_questionnaire_store_updater.py
@@ -1,5 +1,9 @@
+from collections import defaultdict
+
import pytest
from mock import MagicMock, Mock
+from mock.mock import call
+from ordered_set import OrderedSet
from werkzeug.datastructures import MultiDict
from app.data_models import QuestionnaireStore
@@ -446,7 +450,7 @@ def test_update_relationship_question_completeness_no_relationship_collectors(
)
assert (
- questionnaire_store_updater.update_relationship_question_completeness(
+ questionnaire_store_updater._update_relationship_question_completeness( # pylint: disable=protected-access
"test-relationship-collector"
)
is None
@@ -861,6 +865,7 @@ def test_answer_id_section_dependents(
mock_schema.when_rules_section_dependencies_by_answer = {
"first-answer": {"section-2"}
}
+ mock_schema.get_section_ids.return_value = ["section-1", "section-2"]
mock_router.is_path_complete.return_value = is_path_complete
mock_schema.get_answers_for_question_by_id.return_value = {
"first-answer": {},
@@ -960,6 +965,7 @@ def test_answer_id_section_dependents_repeating(
mock_schema.when_rules_section_dependencies_by_answer = {
"first-answer": {"section-2"}
}
+ mock_schema.get_section_ids.return_value = ["section-1", "section-2"]
mock_schema.get_answers_for_question_by_id.return_value = {
"first-answer": {},
"second-answer": {},
@@ -1088,11 +1094,11 @@ def get_questionnaire_store_updater(
[CompletionStatus.IN_PROGRESS, CompletionStatus.COMPLETED],
)
def test_dependent_sections_completed_dependant_blocks_removed_and_status_updated(
- dependent_section_status, mock_router
+ mocker, dependent_section_status, mock_router
):
# Given
current_location = Location(
- section_id="company-summary-section", block_id="total-turnover-block"
+ section_id="company-summary-section", block_id="breakdown-section"
)
progress_store = ProgressStore(
[
@@ -1110,6 +1116,7 @@ def test_dependent_sections_completed_dependant_blocks_removed_and_status_update
},
],
)
+
questionnaire_store_updater = get_questionnaire_store_updater(
current_location=current_location,
progress_store=progress_store,
@@ -1123,19 +1130,29 @@ def test_dependent_sections_completed_dependant_blocks_removed_and_status_update
}
assert dependent_block_id in progress_store.get_completed_block_ids(
- *dependent_section_key
+ section_id=dependent_section_key[0], list_item_id=dependent_section_key[1]
)
+ mocker.patch(
+ "app.questionnaire.questionnaire_store_updater.QuestionnaireStoreUpdater.get_chronological_section_dependents",
+ return_value=[
+ DependentSection(
+ section_id="breakdown-section", list_item_id=None, is_complete=False
+ )
+ ],
+ )
# When
questionnaire_store_updater.remove_dependent_blocks_and_capture_dependent_sections()
questionnaire_store_updater.update_progress_for_dependent_sections()
# Then
assert dependent_block_id not in progress_store.get_completed_block_ids(
- *dependent_section_key
+ section_id=dependent_section_key[0], list_item_id=dependent_section_key[1]
)
assert (
- progress_store.get_section_status(*dependent_section_key)
+ progress_store.get_section_status(
+ section_id=dependent_section_key[0], list_item_id=dependent_section_key[1]
+ )
== CompletionStatus.IN_PROGRESS
)
@@ -1169,7 +1186,7 @@ def test_dependent_sections_current_section_status_not_updated(mocker):
questionnaire_store_updater.update_section_status = mocker.Mock()
assert dependent_block_id in progress_store.get_completed_block_ids(
- *dependent_section_key
+ section_id=dependent_section_key[0], list_item_id=dependent_section_key[1]
)
# When
@@ -1178,7 +1195,7 @@ def test_dependent_sections_current_section_status_not_updated(mocker):
# Then
assert dependent_block_id not in progress_store.get_completed_block_ids(
- *dependent_section_key
+ section_id=dependent_section_key[0], list_item_id=dependent_section_key[1]
)
# Status for current section is handled separately by handle post.
assert questionnaire_store_updater.update_section_status.call_count == 0
@@ -1259,7 +1276,7 @@ def test_dependent_sections_started_but_blocks_incomplete(mock_router, mocker):
questionnaire_store_updater.update_section_status = mocker.Mock()
assert dependent_block_id not in progress_store.get_completed_block_ids(
- *dependent_section_key
+ section_id=dependent_section_key[0], list_item_id=dependent_section_key[1]
)
# When
@@ -1275,7 +1292,7 @@ def test_dependent_sections_started_but_blocks_incomplete(mock_router, mocker):
[CompletionStatus.IN_PROGRESS, CompletionStatus.COMPLETED],
)
def test_repeating_dependent_sections_completed_dependant_blocks_removed_and_status_updated(
- dependent_section_status, mock_router
+ mocker, dependent_section_status, mock_router
):
# Given
current_location = Location(
@@ -1331,6 +1348,14 @@ def test_repeating_dependent_sections_completed_dependant_blocks_removed_and_sta
)
)
+ mocker.patch(
+ "app.questionnaire.questionnaire_store_updater.QuestionnaireStoreUpdater.get_chronological_section_dependents",
+ return_value=[
+ DependentSection(
+ section_id="breakdown-section", list_item_id=None, is_complete=None
+ )
+ ],
+ )
# When
questionnaire_store_updater.remove_dependent_blocks_and_capture_dependent_sections()
questionnaire_store_updater.update_progress_for_dependent_sections()
@@ -1339,7 +1364,7 @@ def test_repeating_dependent_sections_completed_dependant_blocks_removed_and_sta
for list_item in list_store["some-list"]:
section_id, list_item_id = "breakdown-section", list_item
assert "turnover-breakdown-block" not in progress_store.get_completed_block_ids(
- section_id, list_item_id
+ section_id=section_id, list_item_id=list_item_id
)
assert (
progress_store.get_section_status(section_id, list_item_id)
@@ -1395,7 +1420,7 @@ def test_dependent_sections_added_dependant_block_removed(
}
assert dependent_block_id in progress_store.get_completed_block_ids(
- *dependent_section_key
+ section_id=dependent_section_key[0], list_item_id=dependent_section_key[1]
)
assert questionnaire_store_updater.dependent_sections == set()
@@ -1404,10 +1429,110 @@ def test_dependent_sections_added_dependant_block_removed(
# Then
assert dependent_block_id not in progress_store.get_completed_block_ids(
- *dependent_section_key
+ section_id=dependent_section_key[0], list_item_id=dependent_section_key[1]
)
assert questionnaire_store_updater.dependent_sections == {
DependentSection(
section_id="breakdown-section", list_item_id=None, is_complete=False
)
}
+
+
+@pytest.mark.parametrize(
+ "status_unchanged_section_ids, expected_routing_path_calls",
+ [
+ (
+ # s1.s1 -> s1.s2 -> s2.s3 -> s3.s4 -> s3.s5 -> s3.s7 -> s2.s6
+ [],
+ [
+ call("section-1", list_item_id=None),
+ call("section-2", list_item_id=None),
+ call("section-3", list_item_id=None),
+ call("section-4", list_item_id=None),
+ call("section-5", list_item_id=None),
+ call("section-7", list_item_id=None),
+ call("section-6", list_item_id=None),
+ ],
+ ),
+ (
+ # s1 -> s1.s2 -> s1.s3 -> s3.s4 -> s3.s5 -> s3.s7
+ ["section-2"],
+ [
+ call("section-1", list_item_id=None),
+ call("section-2", list_item_id=None),
+ call("section-3", list_item_id=None),
+ call("section-4", list_item_id=None),
+ call("section-5", list_item_id=None),
+ call("section-7", list_item_id=None),
+ ],
+ ),
+ (
+ # s1 -> s1.s2 -> s2.s3 -> s2.s4 -> s2.s5 -> s2.s6
+ ["section-3"],
+ [
+ call("section-1", list_item_id=None),
+ call("section-2", list_item_id=None),
+ call("section-3", list_item_id=None),
+ call("section-4", list_item_id=None),
+ call("section-5", list_item_id=None),
+ call("section-6", list_item_id=None),
+ ],
+ ),
+ ],
+)
+def test_questionnaire_store_updater_dependency_capture(
+ mocker,
+ mock_router,
+ mock_schema,
+ status_unchanged_section_ids,
+ expected_routing_path_calls,
+):
+ """
+ This test is intended to ensure that the order in which dependencies are captured and evaluated is correct.
+ We should only call the routing path for a given section once and need to ensure that only the necessary paths are evaluated
+ i.e. only sections in which the status has changed should be evaluated.
+ """
+ current_location = Location(section_id="section-1", block_id="block-2")
+ mock_dependencies = defaultdict(OrderedSet) | {
+ "section-1": OrderedSet(["section-2", "section-3"]),
+ "section-2": OrderedSet(["section-3", "section-4", "section-5", "section-6"]),
+ "section-3": OrderedSet(["section-4", "section-5", "section-7"]),
+ }
+ mock_schema.when_rules_section_dependencies_by_section_for_progress_value_source = (
+ mock_dependencies
+ )
+ mock_schema.get_repeating_list_for_section.return_value = False
+ mock_router.is_path_complete.return_value = (
+ False # This will result in new status being IN=PROGRESS
+ )
+ progress_store = ProgressStore(
+ [
+ {
+ "section_id": f"section-{idx}",
+ "block_ids": ["block-1", "block-2"],
+ "status": CompletionStatus.IN_PROGRESS
+ if f"section-{idx}" in status_unchanged_section_ids
+ else CompletionStatus.COMPLETED,
+ }
+ for idx in range(1, 8)
+ ],
+ )
+ questionnaire_store_updater = get_questionnaire_store_updater(
+ current_location=current_location,
+ progress_store=progress_store,
+ router=mock_router,
+ schema=mock_schema,
+ )
+ mocker.patch(
+ "app.questionnaire.questionnaire_store_updater.QuestionnaireStoreUpdater.get_chronological_section_dependents",
+ return_value=[
+ DependentSection(
+ section_id="section-1", list_item_id=None, is_complete=None
+ ),
+ DependentSection(
+ section_id="section-2", list_item_id=None, is_complete=None
+ ),
+ ],
+ )
+ questionnaire_store_updater.update_progress_for_dependent_sections()
+ assert mock_router.routing_path.call_args_list == expected_routing_path_calls
diff --git a/tests/app/questionnaire/test_value_source_resolver.py b/tests/app/questionnaire/test_value_source_resolver.py
index 38ff197d53..57957aa7dd 100644
--- a/tests/app/questionnaire/test_value_source_resolver.py
+++ b/tests/app/questionnaire/test_value_source_resolver.py
@@ -46,6 +46,7 @@ def get_value_source_resolver(
routing_path_block_ids: Optional[list] = None,
use_default_answer=False,
escape_answer_values=False,
+ progress_store: ProgressStore | None = None,
):
if not schema:
schema = get_mock_schema()
@@ -65,7 +66,7 @@ def get_value_source_resolver(
routing_path_block_ids=routing_path_block_ids,
use_default_answer=use_default_answer,
escape_answer_values=escape_answer_values,
- progress_store=ProgressStore(),
+ progress_store=progress_store,
)
@@ -685,3 +686,13 @@ def test_answer_value_with_selector_can_be_escaped():
)
== ESCAPED_CONTENT
)
+
+
+def test_progress_values_source_throws_if_no_location_given():
+ value_source_resolver = get_value_source_resolver(
+ progress_store=ProgressStore(), location=None
+ )
+ with pytest.raises(ValueError):
+ value_source_resolver.resolve(
+ {"source": "progress", "selector": "block", "identifier": "a-block"}
+ )
diff --git a/tests/app/views/contexts/summary/test_question.py b/tests/app/views/contexts/summary/test_question.py
index 364f28262c..a5389a70e2 100644
--- a/tests/app/views/contexts/summary/test_question.py
+++ b/tests/app/views/contexts/summary/test_question.py
@@ -184,7 +184,7 @@ def test_create_question(
# When
question = Question(
- question_schema,
+ question_schema=question_schema,
answer_store=answer_store,
list_store=list_store,
progress_store=progress_store,
diff --git a/tests/functional/spec/features/progress/progress_value_source_blocks.js b/tests/functional/spec/features/progress/progress_value_source_blocks.js
new file mode 100644
index 0000000000..c7219c932c
--- /dev/null
+++ b/tests/functional/spec/features/progress/progress_value_source_blocks.js
@@ -0,0 +1,193 @@
+import FirstQuestionPage from "../../../generated_pages/progress_value_source_blocks/s1-b1.page";
+import SecondQuestionPage from "../../../generated_pages/progress_value_source_blocks/s1-b2.page";
+import ThirdQuestionPage from "../../../generated_pages/progress_value_source_blocks/s1-b3.page";
+import ThirdQuestionSectionTwoPage from "../../../generated_pages/progress_value_source_section_enabled_no_hub/s2-b1.page";
+import FourthQuestionPage from "../../../generated_pages/progress_value_source_blocks/s1-b4.page";
+import FifthQuestionPage from "../../../generated_pages/progress_value_source_blocks/s1-b5.page";
+import SixthQuestionPage from "../../../generated_pages/progress_value_source_blocks/s1-b6.page";
+import SeventhQuestionPage from "../../../generated_pages/progress_value_source_blocks/s1-b7.page";
+import SubmitPage from "../../../generated_pages/progress_value_source_blocks/submit.page";
+import HubPage from "../../../base_pages/hub.page";
+
+describe("Feature: Routing based on progress value sources using block identifiers", () => {
+ beforeEach(async () => {
+ await browser.openQuestionnaire("test_progress_value_source_blocks.json");
+ });
+
+ describe("Given I have routing based on the completeness of a block", () => {
+ it("When the block being evaluated is incomplete (Q2), Then the dependent question (Q4) should not be on the path or displayed on the summary", async () => {
+ await $(FirstQuestionPage.q1A1()).setValue("0");
+ await $(FirstQuestionPage.submit()).click();
+
+ await expect(await browser.getUrl()).to.contain(ThirdQuestionPage.pageName);
+ await $(ThirdQuestionPage.q1A1()).setValue("1");
+ await $(ThirdQuestionPage.submit()).click();
+
+ await $(FifthQuestionPage.q1A1()).setValue("2");
+ await $(FifthQuestionPage.submit()).click();
+
+ await $(SeventhQuestionPage.q1A1()).setValue("3");
+ await $(SeventhQuestionPage.submit()).click();
+
+ await expect(await browser.getUrl()).to.contain(SubmitPage.pageName);
+ await expect(await $("body").getText()).to.not.have.string("Section 1 Question 2");
+ await expect(await $("body").getText()).to.not.have.string("Section 1 Question 4");
+ });
+ });
+
+ describe("Given I have routing based on the completeness of a block", () => {
+ it("When the blocks being evaluated are complete (Q2 + Q5), Then the dependent questions (Q4 + Q6) should be on the path and displayed on the summary", async () => {
+ await $(FirstQuestionPage.q1A1()).setValue("1");
+ await $(FirstQuestionPage.submit()).click();
+
+ await expect(await browser.getUrl()).to.contain(SecondQuestionPage.pageName);
+ await $(SecondQuestionPage.q1A1()).setValue("1");
+ await $(SecondQuestionPage.submit()).click();
+
+ await $(ThirdQuestionPage.q1A1()).setValue("2");
+ await $(ThirdQuestionPage.submit()).click();
+
+ await expect(await browser.getUrl()).to.contain(FourthQuestionPage.pageName);
+ await $(FourthQuestionPage.q1A1()).setValue("3");
+ await $(FourthQuestionPage.submit()).click();
+
+ await $(FifthQuestionPage.q1A1()).setValue("4");
+ await $(FifthQuestionPage.submit()).click();
+
+ await expect(await browser.getUrl()).to.contain(SixthQuestionPage.pageName);
+ await $(SixthQuestionPage.q1A1()).setValue("5");
+ await $(SixthQuestionPage.submit()).click();
+
+ await $(SeventhQuestionPage.q1A1()).setValue("6");
+ await $(SeventhQuestionPage.submit()).click();
+
+ await expect(await browser.getUrl()).to.contain(SubmitPage.pageName);
+ await expect(await $("body").getText()).to.have.string("Section 1 Question 4");
+ await expect(await $("body").getText()).to.have.string("Section 1 Question 6");
+ });
+ });
+
+ describe("Given I have routing based on the completeness of a block", () => {
+ it("When an answer is changed so that the block being evaluated is completed, Then the dependent questions (Q4 + Q6) should be on the path and displayed on the summary", async () => {
+ await $(FirstQuestionPage.q1A1()).setValue("0");
+ await $(FirstQuestionPage.submit()).click();
+
+ await expect(await browser.getUrl()).to.contain(ThirdQuestionPage.pageName);
+ await $(ThirdQuestionPage.q1A1()).setValue("1");
+ await $(ThirdQuestionPage.submit()).click();
+
+ await $(FifthQuestionPage.q1A1()).setValue("2");
+ await $(FifthQuestionPage.submit()).click();
+
+ await $(SeventhQuestionPage.q1A1()).setValue("3");
+ await $(SeventhQuestionPage.submit()).click();
+
+ await $(SubmitPage.s1B1Q1A1Edit()).click();
+ await expect(await browser.getUrl()).to.contain(FirstQuestionPage.pageName);
+ await $(FirstQuestionPage.q1A1()).setValue("1");
+ await $(FirstQuestionPage.submit()).click();
+
+ await expect(await browser.getUrl()).to.contain(SecondQuestionPage.pageName);
+ await $(SecondQuestionPage.q1A1()).setValue("1");
+ await $(SecondQuestionPage.submit()).click();
+
+ await $(ThirdQuestionPage.submit()).click();
+
+ await expect(await browser.getUrl()).to.contain(FourthQuestionPage.pageName);
+ await $(FourthQuestionPage.q1A1()).setValue("3");
+ await $(FourthQuestionPage.submit()).click();
+
+ await $(FifthQuestionPage.submit()).click();
+
+ await expect(await browser.getUrl()).to.contain(SixthQuestionPage.pageName);
+ await $(SixthQuestionPage.q1A1()).setValue("3");
+ await $(SixthQuestionPage.submit()).click();
+
+ await expect(await browser.getUrl()).to.contain(SubmitPage.pageName);
+ await expect(await $("body").getText()).to.have.string("Section 1 Question 4");
+ await expect(await $("body").getText()).to.have.string("Section 1 Question 6");
+ });
+ });
+
+ describe("Given I have routing based on the completeness of a block", () => {
+ it("When an answer is removed form the path block being evaluated is no longer completed, Then the dependent questions (Q4 + Q6) should not be on the path and not be displayed on the summary", async () => {
+ await $(FirstQuestionPage.q1A1()).setValue("1");
+ await $(FirstQuestionPage.submit()).click();
+
+ await expect(await browser.getUrl()).to.contain(SecondQuestionPage.pageName);
+ await $(SecondQuestionPage.q1A1()).setValue("1");
+ await $(SecondQuestionPage.submit()).click();
+
+ await $(ThirdQuestionPage.q1A1()).setValue("2");
+ await $(ThirdQuestionPage.submit()).click();
+
+ await expect(await browser.getUrl()).to.contain(FourthQuestionPage.pageName);
+ await $(FourthQuestionPage.q1A1()).setValue("3");
+ await $(FourthQuestionPage.submit()).click();
+
+ await $(FifthQuestionPage.q1A1()).setValue("4");
+ await $(FifthQuestionPage.submit()).click();
+
+ await expect(await browser.getUrl()).to.contain(SixthQuestionPage.pageName);
+ await $(SixthQuestionPage.q1A1()).setValue("5");
+ await $(SixthQuestionPage.submit()).click();
+
+ await $(SeventhQuestionPage.q1A1()).setValue("6");
+ await $(SeventhQuestionPage.submit()).click();
+
+ await expect(await browser.getUrl()).to.contain(SubmitPage.pageName);
+ await $(SubmitPage.s1B1Q1A1Edit()).click();
+ await expect(await browser.getUrl()).to.contain(FirstQuestionPage.pageName);
+ await $(FirstQuestionPage.q1A1()).setValue("0");
+ await $(FirstQuestionPage.submit()).click();
+
+ await expect(await $("body").getText()).to.not.have.string("Section 1 Question 4");
+ await expect(await $("body").getText()).to.not.have.string("Section 1 Question 6");
+ });
+ });
+});
+
+describe("Feature: Section enabled based on progress value sources using block identifiers (no hub)", () => {
+ beforeEach(async () => {
+ await browser.openQuestionnaire("test_progress_value_source_section_enabled_no_hub.json");
+ });
+
+ describe("Given I have a section enabled based on the completeness of a block", () => {
+ it("When the block being evaluated is complete, Then the dependent section should be enabled", async () => {
+ await $(FirstQuestionPage.q1A1()).setValue("0");
+ await $(FirstQuestionPage.submit()).click();
+ await $(SecondQuestionPage.q1A1()).setValue("1");
+ await $(SecondQuestionPage.submit()).click();
+ await expect(await browser.getUrl()).to.contain(ThirdQuestionSectionTwoPage.pageName);
+ });
+ });
+});
+
+describe("Feature: Section enabled based on progress value sources using section identifiers", () => {
+ beforeEach(async () => {
+ await browser.openQuestionnaire("test_progress_value_source_section_enabled_hub.json");
+ });
+
+ describe("Given I have a section enabled based on the completeness of another section", () => {
+ it("When the section being evaluated is complete, Then the dependent section should be enabled", async () => {
+ await $(HubPage.submit()).click();
+ await $(FirstQuestionPage.q1A1()).setValue("0");
+ await $(FirstQuestionPage.submit()).click();
+ await $(SecondQuestionPage.q1A1()).setValue("1");
+ await $(SecondQuestionPage.submit()).click();
+ await expect(await $(HubPage.summaryRowState("section-2")).getText()).to.equal("Not started");
+ });
+ });
+
+ describe("Given I have a section enabled based on the completeness of another section", () => {
+ it("When the section being evaluated is incomplete, Then the dependent section should not be enabled", async () => {
+ await $(HubPage.submit()).click();
+ await $(FirstQuestionPage.q1A1()).setValue("0");
+ await $(FirstQuestionPage.submit()).click();
+ await browser.url(HubPage.url());
+
+ await expect(await $(HubPage.summaryRowState("section-1")).getText()).to.equal("Partially completed");
+ await expect(await $("body").getText()).to.not.have.string("Section 2");
+ });
+ });
+});
diff --git a/tests/functional/spec/features/progress/progress_value_source_repeating.js b/tests/functional/spec/features/progress/progress_value_source_repeating.js
new file mode 100644
index 0000000000..51b544c022
--- /dev/null
+++ b/tests/functional/spec/features/progress/progress_value_source_repeating.js
@@ -0,0 +1,199 @@
+import HubPage from "../../../base_pages/hub.page";
+import ListCollectorPage from "../../../generated_pages/new_calculated_summary_repeating_section/list-collector.page";
+import ListCollectorAddPage from "../../../generated_pages/new_calculated_summary_repeating_section/list-collector-add.page";
+import QuestionBlockPage from "../../../generated_pages/progress_block_value_source_repeating_sections/question-block.page";
+import DOBQuestionBlockPage from "../../../generated_pages/progress_block_value_source_repeating_sections/dob-block.page";
+import RandomQuestionEnablerBlockPage from "../../../generated_pages/progress_block_value_source_repeating_sections/random-question-enabler-block.page";
+import SectionTwoSummaryPage from "../../../generated_pages/progress_block_value_source_repeating_sections/section-2-summary.page";
+import SectionThreeSummaryPage from "../../../generated_pages/progress_value_source_calculated_summary/section-3-summary.page";
+import OtherQuestionBlockPage from "../../../generated_pages/progress_block_value_source_repeating_sections/other-question-block.page";
+import FirstNumberBlockPage from "../../../generated_pages/progress_value_source_calculated_summary/first-number-block.page";
+import SecondNumberBlockPage from "../../../generated_pages/progress_value_source_calculated_summary/second-number-block.page";
+import SectionTwoQuestionBlockPage from "../../../generated_pages/progress_value_source_calculated_summary/s2-b1.page";
+import CalculatedSummaryBlockPage from "../../../generated_pages/progress_value_source_calculated_summary/calculated-summary-block.page";
+
+describe("Feature: Routing rules based on progress value sources in repeating sections", () => {
+ beforeEach(async () => {
+ await browser.openQuestionnaire("test_progress_block_value_source_repeating_sections.json");
+ });
+
+ describe("Given I have routing in a repeating section based on the completeness of a block", () => {
+ it("When the block is incomplete, then I should not see the dependent question in the repeating section", async () => {
+ await $(HubPage.submit()).click();
+ await $(ListCollectorPage.yes()).click();
+ await $(ListCollectorPage.submit()).click();
+ await $(ListCollectorAddPage.firstName()).setValue("John");
+ await $(ListCollectorAddPage.lastName()).setValue("Doe");
+ await $(ListCollectorAddPage.submit()).click();
+ await $(ListCollectorPage.no()).click();
+ await $(ListCollectorPage.submit()).click();
+ await expect(await browser.getUrl()).to.contain(QuestionBlockPage.pageName);
+
+ await browser.url(HubPage.url());
+ await expect(await $(HubPage.summaryRowState("section-1")).getText()).to.equal("Partially completed");
+
+ await $(HubPage.summaryRowLink("section-2-1")).click();
+ await $(DOBQuestionBlockPage.submit()).click();
+ await expect(await browser.getUrl()).to.contain(SectionTwoSummaryPage.pageName);
+ });
+ });
+
+ describe("Given I have routing in a repeating section based on the completeness of a block", () => {
+ it("When the block is complete, then I should see the dependent question in the repeating section", async () => {
+ await $(HubPage.submit()).click();
+ await $(ListCollectorPage.yes()).click();
+ await $(ListCollectorPage.submit()).click();
+ await $(ListCollectorAddPage.firstName()).setValue("John");
+ await $(ListCollectorAddPage.lastName()).setValue("Doe");
+ await $(ListCollectorAddPage.submit()).click();
+ await $(ListCollectorPage.no()).click();
+ await $(ListCollectorPage.submit()).click();
+ await $(QuestionBlockPage.submit()).click();
+ await $(RandomQuestionEnablerBlockPage.randomQuestionEnabler()).setValue(1);
+ await $(RandomQuestionEnablerBlockPage.submit()).click();
+
+ await browser.url(HubPage.url());
+ await expect(await $(HubPage.summaryRowState("section-1")).getText()).to.equal("Completed");
+
+ await $(HubPage.summaryRowLink("section-2-1")).click();
+ await $(DOBQuestionBlockPage.submit()).click();
+ await expect(await browser.getUrl()).to.contain(OtherQuestionBlockPage.pageName);
+ });
+ });
+
+ describe("Given I have routing in a repeating section based on the completeness of a block", () => {
+ it("When the status of the block changes from incomplete to complete, then the dependent question should be on the path in the repeating sections", async () => {
+ await $(HubPage.submit()).click();
+ await $(ListCollectorPage.yes()).click();
+ await $(ListCollectorPage.submit()).click();
+ await $(ListCollectorAddPage.firstName()).setValue("John");
+ await $(ListCollectorAddPage.lastName()).setValue("Doe");
+ await $(ListCollectorAddPage.submit()).click();
+ await $(ListCollectorPage.yes()).click();
+ await $(ListCollectorPage.submit()).click();
+ await $(ListCollectorAddPage.firstName()).setValue("Joe");
+ await $(ListCollectorAddPage.lastName()).setValue("Bloggs");
+ await $(ListCollectorAddPage.submit()).click();
+ await $(ListCollectorPage.no()).click();
+ await $(ListCollectorPage.submit()).click();
+ await browser.url(HubPage.url());
+ await expect(await $(HubPage.summaryRowState("section-2-1")).getText()).to.equal("Not started");
+ await expect(await $(HubPage.summaryRowState("section-2-2")).getText()).to.equal("Not started");
+
+ await $(HubPage.summaryRowLink("section-2-1")).click();
+ await $(DOBQuestionBlockPage.submit()).click();
+ await $(SectionTwoSummaryPage.submit()).click();
+ await expect(await $(HubPage.summaryRowState("section-2-1")).getText()).to.equal("Completed");
+ await expect(await $(HubPage.summaryRowState("section-2-2")).getText()).to.equal("Not started");
+
+ await $(HubPage.submit()).click();
+ await $(QuestionBlockPage.submit()).click();
+ await $(RandomQuestionEnablerBlockPage.randomQuestionEnabler()).setValue(1);
+ await $(RandomQuestionEnablerBlockPage.submit()).click();
+
+ await expect(await $(HubPage.summaryRowState("section-2-1")).getText()).to.equal("Partially completed");
+ await expect(await $(HubPage.summaryRowState("section-2-2")).getText()).to.equal("Not started");
+ });
+ });
+});
+
+describe("Feature: Routing rules based on progress value sources in repeating sections", () => {
+ beforeEach(async () => {
+ await browser.openQuestionnaire("test_progress_value_source_calculated_summary.json");
+ });
+
+ describe("Given I have routing in a repeating section based on the completeness of a calculated summary", () => {
+ it("When the calculated summary block is incomplete, then I should not see the dependent question in the repeating section", async () => {
+ await $(HubPage.submit()).click();
+ await $(FirstNumberBlockPage.firstNumber()).setValue(1);
+ await $(FirstNumberBlockPage.submit()).click();
+ await browser.url(HubPage.url());
+
+ await $(HubPage.summaryRowLink("section-2")).click();
+
+ await $(ListCollectorPage.yes()).click();
+ await $(ListCollectorPage.submit()).click();
+ await $(ListCollectorAddPage.firstName()).setValue("John");
+ await $(ListCollectorAddPage.lastName()).setValue("Doe");
+ await $(ListCollectorAddPage.submit()).click();
+ await $(ListCollectorPage.no()).click();
+ await $(ListCollectorPage.submit()).click();
+ await expect(await browser.getUrl()).to.contain(HubPage.pageName);
+
+ await $(HubPage.summaryRowLink("section-3-1")).click();
+ await $(DOBQuestionBlockPage.submit()).click();
+ await $(SectionThreeSummaryPage.submit()).click();
+
+ await expect(await $(HubPage.summaryRowState("section-1")).getText()).to.equal("Partially completed");
+ await expect(await $(HubPage.summaryRowState("section-2")).getText()).to.equal("Completed");
+ await expect(await $(HubPage.summaryRowState("section-3-1")).getText()).to.equal("Completed");
+ });
+ });
+
+ describe("Given I have routing in a repeating section based on the completeness of a calculated summary", () => {
+ it("When the calculated summary block is incomplete but is updated so that it is completed, then I should see the dependency should be updated in the repeating section", async () => {
+ await $(HubPage.submit()).click();
+ await $(FirstNumberBlockPage.firstNumber()).setValue(1);
+ await $(FirstNumberBlockPage.submit()).click();
+ await browser.url(HubPage.url());
+
+ await $(HubPage.summaryRowLink("section-2")).click();
+
+ await $(ListCollectorPage.yes()).click();
+ await $(ListCollectorPage.submit()).click();
+ await $(ListCollectorAddPage.firstName()).setValue("John");
+ await $(ListCollectorAddPage.lastName()).setValue("Doe");
+ await $(ListCollectorAddPage.submit()).click();
+ await $(ListCollectorPage.no()).click();
+ await $(ListCollectorPage.submit()).click();
+ await expect(await browser.getUrl()).to.contain(HubPage.pageName);
+
+ await $(HubPage.summaryRowLink("section-3-1")).click();
+ await $(DOBQuestionBlockPage.submit()).click();
+ await $(SectionThreeSummaryPage.submit()).click();
+
+ await expect(await $(HubPage.summaryRowState("section-1")).getText()).to.equal("Partially completed");
+ await expect(await $(HubPage.summaryRowState("section-2")).getText()).to.equal("Completed");
+ await expect(await $(HubPage.summaryRowState("section-3-1")).getText()).to.equal("Completed");
+
+ await $(HubPage.summaryRowLink("section-1")).click();
+ await $(SecondNumberBlockPage.secondNumber()).setValue(2);
+ await $(SecondNumberBlockPage.submit()).click();
+ await $(CalculatedSummaryBlockPage.submit()).click();
+ await browser.url(HubPage.url());
+
+ await expect(await $(HubPage.summaryRowState("section-1")).getText()).to.equal("Completed");
+ await expect(await $(HubPage.summaryRowState("section-2")).getText()).to.equal("Partially completed");
+ await expect(await $(HubPage.summaryRowState("section-3-1")).getText()).to.equal("Partially completed");
+ });
+ });
+
+ describe("Given I have routing in a repeating section based on the completeness of a calculated summary", () => {
+ it("When the calculated summary block is complete, then I should see the dependent question in the repeating section", async () => {
+ await $(HubPage.submit()).click();
+ await $(FirstNumberBlockPage.firstNumber()).setValue(1);
+ await $(FirstNumberBlockPage.submit()).click();
+ await $(SecondNumberBlockPage.secondNumber()).setValue(2);
+ await $(SecondNumberBlockPage.submit()).click();
+ await $(CalculatedSummaryBlockPage.submit()).click();
+ await browser.url(HubPage.url());
+
+ await $(HubPage.summaryRowLink("section-2")).click();
+
+ await $(SectionTwoQuestionBlockPage.q1A1()).setValue(1);
+ await $(SectionTwoQuestionBlockPage.submit()).click();
+ await $(ListCollectorPage.yes()).click();
+ await $(ListCollectorPage.submit()).click();
+ await $(ListCollectorAddPage.firstName()).setValue("John");
+ await $(ListCollectorAddPage.lastName()).setValue("Doe");
+ await $(ListCollectorAddPage.submit()).click();
+ await $(ListCollectorPage.no()).click();
+ await $(ListCollectorPage.submit()).click();
+ await expect(await browser.getUrl()).to.contain(HubPage.pageName);
+
+ await $(HubPage.summaryRowLink("section-3-1")).click();
+ await $(DOBQuestionBlockPage.submit()).click();
+ await expect(await browser.getUrl()).to.contain(OtherQuestionBlockPage.pageName);
+ });
+ });
+});
diff --git a/tests/integration/integration_test_case.py b/tests/integration/integration_test_case.py
index 44f0f08709..3f1b6a7aa6 100644
--- a/tests/integration/integration_test_case.py
+++ b/tests/integration/integration_test_case.py
@@ -329,6 +329,10 @@ def getHtmlSoup(self):
"""
return BeautifulSoup(self.getResponseData(), "html.parser")
+ @staticmethod
+ def row_selector(row_number):
+ return f".ons-summary__item:nth-of-type({row_number})"
+
# Extra Helper Assertions
def assertInHead(self, content):
self.assertInSelector(content, "head")
diff --git a/tests/integration/questionnaire/test_questionnaire_progress_value_source_blocks.py b/tests/integration/questionnaire/test_questionnaire_progress_value_source_blocks.py
new file mode 100644
index 0000000000..b167e50887
--- /dev/null
+++ b/tests/integration/questionnaire/test_questionnaire_progress_value_source_blocks.py
@@ -0,0 +1,213 @@
+from tests.integration.integration_test_case import IntegrationTestCase
+from tests.integration.questionnaire import SUBMIT_URL_PATH
+
+
+class TestQuestionnaireProgressValueSourceBlocks(IntegrationTestCase):
+ def go_to_section(self, section_id):
+ self.get(f"/questionnaire/sections/{section_id}/")
+
+ def test_skip_condition_block_not_complete(self):
+ """
+ Test that a block is skipped if the progress value source is not complete
+ """
+
+ self.launchSurvey("test_progress_value_source_blocks")
+
+ self.assertInBody("Section 1 Question 1")
+ self.post({"s1-b1-q1-a1": 0})
+
+ # Routes to block 3 because answer to block 1 is 0
+ self.assertInBody("Section 1 Question 3")
+ self.post({"s1-b3-q1-a1": 1})
+
+ # Block 4 is skipped because block 2 is not complete
+ self.assertInBody("Section 1 Question 5")
+
+ def test_routing_condition_block_not_complete(self):
+ """
+ Test that routes to proper block if the progress value source is not complete
+ """
+
+ self.launchSurvey("test_progress_value_source_blocks")
+
+ self.assertInBody("Section 1 Question 1")
+ self.post({"s1-b1-q1-a1": 0})
+
+ # Routes to block 3 because answer to block 1 is 0
+ self.assertInBody("Section 1 Question 3")
+ self.post({"s1-b3-q1-a1": 1})
+
+ # Block 4 is skipped because block 2 is not complete
+ self.assertInBody("Section 1 Question 5")
+ self.post({"s1-b5-q1-a1": 1})
+
+ # Routes to block 7 because answer to block 2 is not complete
+ self.assertInBody("Section 1 Question 7")
+ self.post({"s1-b7-q1-a1": 1})
+
+ def test_block_value_source_dependencies_updated(self):
+ """
+ Test that the block value source dependencies are updated when a dependent block progress changes
+ """
+
+ self.launchSurvey("test_progress_value_source_blocks")
+
+ self.assertInBody("Section 1 Question 1")
+ self.post({"s1-b1-q1-a1": 0})
+
+ # Routes to block 3 because answer to block 1 is 0
+ self.assertInBody("Section 1 Question 3")
+ self.post({"s1-b3-q1-a1": 1})
+
+ # Block 4 is skipped because block 2 is not complete
+ self.assertInBody("Section 1 Question 5")
+ self.post({"s1-b5-q1-a1": 1})
+
+ # Routes to block 7 because answer to block 2 is not complete
+ self.assertInBody("Section 1 Question 7")
+ self.post({"s1-b7-q1-a1": 1})
+
+ self.assertInBody("Check your answers and submit")
+
+ # Change block 1 answer to 1
+ self.get(
+ "/questionnaire/s1-b1/?return_to=final-summary&return_to_answer_id=s1-b1-q1-a1#s1-b1-q1-a1"
+ )
+ self.assertInBody("Section 1 Question 1")
+ self.post({"s1-b1-q1-a1": 1})
+
+ # Routes to block 2 because answer to block 1 is now 1
+ self.assertInBody("Section 1 Question 2")
+ self.post({"s1-b2-q1-a1": 1})
+
+ self.assertInBody("Section 1 Question 3")
+ self.post({"s1-b3-q1-a1": 1})
+
+ # bBock 4 is not skipped because answer to block 1 is now 1
+ self.assertInBody("Section 1 Question 4")
+ self.post({"s1-b4-q1-a1": 1})
+
+ # Routes to block 5 because answer to block 1 is now 1
+ self.assertInBody("Section 1 Question 5")
+ self.post({"s1-b5-q1-a1": 1})
+
+ # Routes to block 6 because answer to block 1 is now 1
+ self.assertInBody("Section 1 Question 6")
+ self.post({"s1-b6-q1-a1": 1})
+
+ # Redirects to the hub
+ self.assertInUrl(SUBMIT_URL_PATH)
+
+ # Question 2, 4 and 6 are now visible because block 2 is complete (dependencies updated)
+
+ self.assertInSelector("Section 1 Question 2", self.row_selector(2))
+ self.assertInSelector("1", self.row_selector(2))
+ self.assertInSelector("Change", self.row_selector(2))
+
+ self.assertInSelector("Section 1 Question 4", self.row_selector(4))
+ self.assertInSelector("1", self.row_selector(4))
+ self.assertInSelector("Change", self.row_selector(4))
+
+ self.assertInSelector("Section 1 Question 6", self.row_selector(6))
+ self.assertInSelector("1", self.row_selector(6))
+ self.assertInSelector("Change", self.row_selector(6))
+
+ def test_block_value_source_dependencies_removed_from_path(self):
+ """
+ Test that the block value source dependencies are updated when a dependent block progress changes and gets removed from path
+ """
+
+ self.launchSurvey("test_progress_value_source_blocks")
+
+ self.assertInBody("Section 1 Question 1")
+ self.post({"s1-b1-q1-a1": 1})
+
+ self.assertInBody("Section 1 Question 2")
+ self.post({"s1-b2-q1-a1": 1})
+
+ self.assertInBody("Section 1 Question 3")
+ self.post({"s1-b3-q1-a1": 1})
+
+ self.assertInBody("Section 1 Question 4")
+ self.post({"s1-b4-q1-a1": 1})
+
+ self.assertInBody("Section 1 Question 5")
+ self.post({"s1-b5-q1-a1": 1})
+
+ self.assertInBody("Section 1 Question 6")
+ self.post({"s1-b6-q1-a1": 1})
+
+ self.assertInBody("Section 1 Question 7")
+ self.post({"s1-b7-q1-a1": 1})
+
+ self.assertInBody("Check your answers and submit")
+
+ # Change block 1 answer to 0
+ self.get(
+ "/questionnaire/s1-b1/?return_to=final-summary&return_to_answer_id=s1-b1-q1-a1#s1-b1-q1-a1"
+ )
+ self.assertInBody("Section 1 Question 1")
+ self.post({"s1-b1-q1-a1": 0})
+
+ # Redirects to the hub
+ self.assertInUrl(SUBMIT_URL_PATH)
+
+ # Questions 2, 4 and 6 are not visible because they aren't on the path anymore although they've been answered earlier
+ self.assertNotInBody("Section 1 Question 2")
+ self.assertNotInBody("Section 1 Question 4")
+ self.assertNotInBody("Section 1 Question 6")
+
+ def test_block_value_source_cross_section_dependencies_removed_from_path(self):
+ """
+ Test that the block value source dependencies are updated when a dependent block progress changes and gets removed from path
+ """
+
+ self.launchSurvey("test_progress_value_source_blocks_cross_section")
+
+ self.post()
+
+ self.assertInBody("Section 1 Question 1")
+ self.post({"s1-b1-q1-a1": 1})
+
+ self.assertInBody("Section 1 Question 2")
+ self.post({"s1-b2-q1-a1": 1})
+
+ self.assertInBody("Section 1 Question 3")
+ self.post({"s1-b3-q1-a1": 1})
+
+ self.assertInBody("Section 1 Question 4")
+ self.post({"s1-b4-q1-a1": 1})
+
+ self.post()
+ self.post()
+
+ self.assertInBody("Section 2 Question 5")
+ self.post({"s2-b5-q1-a1": 1})
+
+ self.assertInBody("Section 2 Question 6")
+ self.post({"s2-b6-q1-a1": 1})
+
+ self.assertInBody("Section 2 Question 7")
+ self.post({"s2-b7-q1-a1": 1})
+
+ self.post()
+ self.assertEqualUrl("/questionnaire/")
+
+ # Change block 1 answer to 0
+ self.get("/questionnaire/s1-b1/")
+ self.assertInBody("Section 1 Question 1")
+ self.post({"s1-b1-q1-a1": 0})
+
+ # Questions 2, 4 in Section 1 are not visible because they aren't on the path anymore
+ self.assertEqualUrl("/questionnaire/sections/section-1/")
+ self.assertNotInBody("Section 1 Question 2")
+ self.assertNotInBody("Section 1 Question 4")
+ self.post()
+
+ # Redirects to the hub
+ self.assertEqualUrl("/questionnaire/")
+
+ # Question 6 in Section 2 is not visible because it is not on the path anymore
+ self.go_to_section("section-2")
+ self.assertEqualUrl("/questionnaire/sections/section-2/")
+ self.assertNotInBody("Section 2 Question 6")
diff --git a/tests/integration/questionnaire/test_questionnaire_progress_value_source_calculated_summary.py b/tests/integration/questionnaire/test_questionnaire_progress_value_source_calculated_summary.py
new file mode 100644
index 0000000000..3027aea765
--- /dev/null
+++ b/tests/integration/questionnaire/test_questionnaire_progress_value_source_calculated_summary.py
@@ -0,0 +1,417 @@
+from tests.integration.integration_test_case import IntegrationTestCase
+
+
+class TestQuestionnaireProgressValueSource(IntegrationTestCase):
+ def john_doe_link(self):
+ return self.getHtmlSoup().find("a", {"data-qa": "hub-row-section-3-1-link"})[
+ "href"
+ ]
+
+ def james_bond_link(self):
+ return self.getHtmlSoup().find("a", {"data-qa": "hub-row-section-3-2-link"})[
+ "href"
+ ]
+
+ def section_one_link(self):
+ return self.getHtmlSoup().find("a", {"data-qa": "hub-row-section-1-link"})[
+ "href"
+ ]
+
+ def add_person(self, first_name, last_name):
+ self.assertEqualUrl("/questionnaire/people/add-person/")
+ self.post({"first-name": first_name, "last-name": last_name})
+
+ def assert_section_status(self, section_index, status, other_labels=None):
+ self.assertInSelector(status, self.row_selector(section_index))
+ if other_labels and len(other_labels) > 0:
+ for other_label in other_labels:
+ self.assertInSelector(other_label, self.row_selector(section_index))
+
+ def answer_dob(self, payload=None):
+ if not payload:
+ payload = {
+ "date-of-birth-answer-year": "1998",
+ "date-of-birth-answer-month": "12",
+ "date-of-birth-answer-day": 12,
+ }
+ self.post(payload)
+
+ def go_to_section(self, section_id):
+ self.get(f"/questionnaire/sections/{section_id}/")
+
+ def go_to_hub(self):
+ self.get("/questionnaire/")
+
+ # pylint: disable=locally-disabled, too-many-statements
+ def test_happy_path(self):
+ self.launchSurvey("test_progress_value_source_calculated_summary")
+
+ self.assertInBody("Choose another section to complete")
+ self.assertInBody("Calculated Summary")
+ self.assertInBody("Skippable random question + List collector")
+
+ # 1. Complete the first section
+ self.go_to_section("section-1")
+ self.assertInUrl("/questionnaire/first-number-block/")
+ self.assertInBody("First Number Question Title")
+ self.post({"first-number-answer": 1})
+
+ self.assertInUrl("/questionnaire/second-number-block")
+ self.assertInBody("Second Number Question Title")
+ self.post({"second-number-answer": 1})
+
+ # 2. Should be on the calculated summary page
+ self.assertEqualUrl("/questionnaire/calculated-summary-block/")
+
+ self.assertInSelector("First answer label", self.row_selector(1))
+ self.assertInSelector("£1.00", self.row_selector(1))
+ self.assertInSelector("Change", self.row_selector(1))
+
+ self.assertInSelector("Second answer label", self.row_selector(2))
+ self.assertInSelector("£1.00", self.row_selector(2))
+ self.assertInSelector("Change", self.row_selector(2))
+
+ self.post()
+ self.post()
+
+ # 3. Should be on the hub page
+ self.assertEqualUrl("/questionnaire/")
+ self.assertInBody("Choose another section to complete")
+
+ # 4. 1st section should be marked as complete
+ self.assert_section_status(1, "Completed", ["View answers"])
+
+ # 5. Complete the second section
+ # 6. Random question shows
+ self.go_to_section("section-2")
+ self.assertInUrl("/questionnaire/s2-b1/")
+ self.assertInBody("Skippable random question")
+ self.post({"s2-b1-q1-a1": 1})
+
+ # 7. Add two people
+ self.assertEqualUrl("/questionnaire/list-collector/")
+ self.post({"anyone-else": "Yes"})
+ self.add_person("John", "Doe")
+
+ self.assertEqualUrl("/questionnaire/list-collector/")
+ self.post({"anyone-else": "Yes"})
+ self.add_person("James", "Bond")
+
+ self.post({"anyone-else": "No"})
+
+ # 8. Two new sections should be available on the hub
+ self.assert_section_status(3, "Not started", ["John Doe"])
+ self.assert_section_status(4, "Not started", ["James Bond"])
+
+ # 9. Complete the John Doe section, random question shows
+ self.get(self.john_doe_link())
+
+ self.assertInBody("John Doe")
+
+ self.answer_dob()
+
+ self.assertInBody("Random question about")
+ self.post({"other-answer": 1})
+
+ self.assertInBody("Another random question about")
+ self.post({"other-answer-2": 1})
+
+ self.assertInUrl("/questionnaire/sections/section-3/")
+
+ self.post()
+
+ # 10. John Doe section should be marked as complete
+ self.assert_section_status(3, "Completed", ["John Doe"])
+ self.assert_section_status(4, "Not started", ["James Bond"])
+
+ # 11. Complete the James Bond section, random question shows
+ self.get(self.james_bond_link())
+
+ self.assertInBody("James Bond")
+
+ self.answer_dob()
+
+ self.assertInBody("Random question about")
+ self.post({"other-answer": 1})
+
+ self.assertInBody("Another random question about")
+ self.post({"other-answer-2": 1})
+
+ self.assertInUrl("/questionnaire/sections/section-3/")
+
+ self.post()
+
+ # 12. James Bond section should be marked as complete
+ self.assert_section_status(4, "Completed", ["James Bond"])
+
+ # pylint: disable=locally-disabled, too-many-statements
+ def test_calculated_summary_first_incomplete_then_complete(self):
+ self.launchSurvey("test_progress_value_source_calculated_summary")
+
+ # 1. Start completing the first section
+ self.go_to_section("section-1")
+ self.post({"first-number-answer": 1})
+
+ self.post({"second-number-answer": 1})
+
+ self.assertEqualUrl("/questionnaire/calculated-summary-block/")
+
+ # 2. Go back to the hub BEFORE completing the section
+ self.go_to_hub()
+
+ # 3. Section 1 should show as partially complete
+ self.assert_section_status(1, "Partially completed", ["Continue with section"])
+
+ # 4. Complete the second section
+ self.go_to_section("section-2")
+
+ self.assertNotInBody("Skippable random question")
+ self.assertEqualUrl("/questionnaire/list-collector/")
+
+ self.post({"anyone-else": "Yes"})
+ self.add_person("John", "Doe")
+ self.post({"anyone-else": "Yes"})
+ self.add_person("James", "Bond")
+ self.post({"anyone-else": "No"})
+
+ # 5. Section 2 should show as complete
+ self.assert_section_status(2, "Completed")
+
+ # 6. Complete the John Doe section. Random question DOES NOT show because section 1 is not complete
+ self.get(self.john_doe_link())
+
+ self.answer_dob()
+
+ self.assertNotInBody("Random question about")
+ self.post()
+
+ self.assertEqualUrl("/questionnaire/")
+
+ # 7. On the hub, John Doe section shows as completted
+ self.assert_section_status(3, "Completed", ["John Doe"])
+
+ # 8. Complete the James Bond section. Random question DOES NOT show because section 1 is not complete
+ self.get(self.james_bond_link())
+
+ self.assertInBody("James Bond")
+
+ self.answer_dob()
+
+ self.assertNotInBody("Random question about")
+ self.post()
+
+ self.assertEqualUrl("/questionnaire/")
+
+ # 9. On the hub, James Bond section shows as completed
+ self.assert_section_status(4, "Completed", ["James Bond"])
+
+ # 10. Go back to calculated summary (section 1) and complete it
+ self.get("/questionnaire/calculated-summary-block/?resume=True")
+ self.assertInBody("We calculate the total of currency values entered to be")
+ self.post()
+ self.post()
+
+ # 11. Dependent sections should have been updated to partially completed
+ self.assert_section_status(1, "Completed")
+ self.assert_section_status(
+ 2, "Partially completed", ["Skippable random question + List collector"]
+ )
+ self.assert_section_status(3, "Partially completed", ["John Doe"])
+ self.assert_section_status(4, "Partially completed", ["James Bond"])
+
+ # 12. Go back to section 2 and complete it
+ # Random question SHOWS because section 1 is now completed
+ self.go_to_section("section-2")
+ self.assertInBody("Skippable random question")
+ self.post({"s2-b1-q1-a1": 1})
+
+ # 13. Section 2 shows as completed on the hub
+ self.assertEqualUrl("/questionnaire/")
+ self.assertInBody("Choose another section to complete")
+
+ self.assert_section_status(2, "Completed")
+
+ # 14. Go back to John Doe section and complete it
+ # Random questions show because section 1 is now complete
+ self.get(self.john_doe_link())
+ self.assertInBody("Random question about")
+ self.post({"other-answer": 1})
+ self.assertInBody("Another random question about")
+ self.post({"other-answer-2": 1})
+ self.post()
+
+ self.assertEqualUrl("/questionnaire/")
+
+ # 15. Go back to James Bond section and complete it
+ # Random questions show because section 1 is now complete
+ self.get(self.james_bond_link())
+ self.assertInBody("Random question about")
+ self.post({"other-answer": 1})
+ self.assertInBody("Another random question about")
+ self.post({"other-answer-2": 1})
+ self.post()
+
+ self.assertEqualUrl("/questionnaire/")
+
+ # 16. All sections should show as completed on the hub
+ self.assert_section_status(3, "Completed", ["John Doe"])
+ self.assert_section_status(4, "Completed", ["James Bond"])
+
+ def test_happy_path_then_make_calculated_summary_incomplete(self):
+ self.launchSurvey("test_progress_value_source_calculated_summary")
+
+ # 1. Complete section 1
+ self.go_to_section("section-1")
+ self.post({"first-number-answer": 1})
+
+ self.post({"second-number-answer": 1})
+
+ self.post()
+ self.post()
+
+ # 2. Complete section 2 and add two people
+ self.go_to_section("section-2")
+ self.post({"s2-b1-q1-a1": 1})
+
+ self.post({"anyone-else": "Yes"})
+ self.add_person("John", "Doe")
+
+ self.post({"anyone-else": "Yes"})
+ self.add_person("James", "Bond")
+
+ self.post({"anyone-else": "No"})
+
+ # 3. Complete John Doe section
+ self.get(self.john_doe_link())
+
+ self.answer_dob()
+
+ self.post({"other-answer": 1})
+
+ self.post({"other-answer-2": 1})
+
+ self.post()
+
+ # 4. Complete James Bond section
+ self.get(self.james_bond_link())
+
+ self.answer_dob()
+
+ self.post({"other-answer": 1})
+
+ self.post({"other-answer-2": 1})
+
+ self.post()
+
+ # END OF HAPPY PATH
+
+ # 5. Go back to calculated summary and make it incomplete
+ self.get("/questionnaire/calculated-summary-block/")
+ # Edit first answer
+ first_answer_link = self.getHtmlSoup().find(
+ "a", {"data-qa": "first-number-answer-edit"}
+ )["href"]
+ self.get(first_answer_link)
+ self.post({"first-number-answer": 2})
+
+ # Don't complete the calculated summary, go back to the hub
+ self.go_to_hub()
+
+ # 6. Section 1 should show as partially completed on the hub
+ # Other sections should show as completed
+ self.assert_section_status(1, "Partially completed")
+ self.assert_section_status(2, "Completed")
+ self.assert_section_status(3, "Completed")
+ self.assert_section_status(4, "Completed")
+
+ self.get("/questionnaire/sections/section-2/")
+ # No random question
+ self.assertInBody("Does anyone else live here?")
+
+ self.go_to_hub()
+
+ self.get(self.john_doe_link())
+ self.assertNotInBody("Random question about")
+
+ self.go_to_hub()
+
+ self.get(self.james_bond_link())
+ self.assertNotInBody("Random question about")
+
+ def test_progress_value_source_with_backward_chained_dependencies(self):
+ self.launchSurvey("test_progress_value_source_calculated_summary_extended")
+ self.post()
+
+ # 1. Complete section 7
+ self.go_to_section("section-7")
+ self.post({"s7-b3-q1-a1": 1})
+
+ # Check the section is complete
+ self.assertEqualUrl("/questionnaire/")
+ self.assert_section_status(6, "Completed")
+
+ # 2. Complete section 5, will change the status of section 7 to partially completed
+ self.go_to_section("section-5")
+ self.post({"s5-b2-q1-a1": 1})
+
+ # Check the section is complete
+ self.assertEqualUrl("/questionnaire/")
+ self.assert_section_status(4, "Completed")
+ self.assert_section_status(6, "Partially completed")
+
+ # 2. Complete section 1, this will change the status of section 5 to partially completed
+ # and section 7 should once again be complete
+ self.go_to_section("section-1")
+ self.post({"first-number-answer": 1})
+
+ self.post({"second-number-answer": 1})
+
+ self.post()
+
+ # Check the section is complete
+ self.assertEqualUrl("/questionnaire/")
+ self.assert_section_status(1, "Completed")
+ self.assert_section_status(4, "Partially completed")
+ self.assert_section_status(6, "Completed")
+
+ def test_progress_value_source_with_chained_dependencies(self):
+ self.launchSurvey("test_progress_value_source_calculated_summary_extended")
+ self.post()
+
+ # 1. Complete section 8, 9, 10, 11 and 12
+ self.go_to_section("section-12")
+ self.post({"s12-b2-q1-a1": 1})
+
+ self.go_to_section("section-11")
+ self.post({"s11-b2-q1-a1": 1})
+
+ self.go_to_section("section-8")
+ self.post({"s8-b3-q1-a1": 1})
+
+ self.go_to_section("section-9")
+ self.post({"s9-b2-q1-a1": 1})
+
+ self.go_to_section("section-10")
+ self.post({"s10-b2-q1-a1": 1})
+
+ # Check that sections 8, 9 and 10 are complete, and 11 and 12 are partially complete
+ self.assertEqualUrl("/questionnaire/")
+ self.assert_section_status(7, "Completed")
+ self.assert_section_status(8, "Completed")
+ self.assert_section_status(9, "Completed")
+ self.assert_section_status(10, "Partially completed")
+ self.assert_section_status(11, "Partially completed")
+
+ # 2. Update the second section, this should make sections
+ self.go_to_section("section-2")
+ self.post({"s2-b1-q1-a1": 1})
+ self.post({"anyone-else": "No"})
+
+ # Check that section 11 and 12 are complete, and 8, 9 and 10 are partially complete
+ self.assertEqualUrl("/questionnaire/")
+ self.assert_section_status(2, "Completed")
+ self.assert_section_status(7, "Partially completed")
+ self.assert_section_status(8, "Partially completed")
+ self.assert_section_status(9, "Partially completed")
+ self.assert_section_status(10, "Completed")
+ self.assert_section_status(11, "Completed")
diff --git a/tests/integration/questionnaire/test_questionnaire_progress_value_source_in_repeating_sections.py b/tests/integration/questionnaire/test_questionnaire_progress_value_source_in_repeating_sections.py
new file mode 100644
index 0000000000..db12b2c901
--- /dev/null
+++ b/tests/integration/questionnaire/test_questionnaire_progress_value_source_in_repeating_sections.py
@@ -0,0 +1,533 @@
+from tests.integration.integration_test_case import IntegrationTestCase
+
+
+class TestQuestionnaireProgressValueSourceInRepeatingSections(IntegrationTestCase):
+ def answer_dob(self, payload=None):
+ if not payload:
+ payload = {
+ "date-of-birth-answer-year": "1998",
+ "date-of-birth-answer-month": "12",
+ "date-of-birth-answer-day": 12,
+ }
+ self.post(payload)
+
+ def answer_dob_second_repeat(self, payload=None):
+ if not payload:
+ payload = {
+ "second-date-of-birth-answer-year": "1998",
+ "second-date-of-birth-answer-month": "12",
+ "second-date-of-birth-answer-day": 12,
+ }
+ self.post(payload)
+
+ def john_doe_link(self):
+ return self.getHtmlSoup().find("a", {"data-qa": "hub-row-section-2-1-link"})[
+ "href"
+ ]
+
+ def james_bond_link(self):
+ return self.getHtmlSoup().find("a", {"data-qa": "hub-row-section-2-2-link"})[
+ "href"
+ ]
+
+ def jane_doe_link(self):
+ return self.getHtmlSoup().find("a", {"data-qa": "hub-row-section-4-1-link"})[
+ "href"
+ ]
+
+ def add_person(self, first_name, last_name):
+ self.assertInBody("What is the name of the person?")
+ self.post({"first-name": first_name, "last-name": last_name})
+
+ def add_person_second_list_collector(self, first_name, last_name):
+ self.assertInBody("What is the name of the person?")
+ self.post({"second-first-name": first_name, "second-last-name": last_name})
+
+ def assert_section_status(self, section_index, status, other_labels=None):
+ self.assertInSelector(status, self.row_selector(section_index))
+ if other_labels and len(other_labels):
+ for other_label in other_labels:
+ self.assertInSelector(other_label, self.row_selector(section_index))
+
+ def go_to_section(self, section_id):
+ self.get(f"/questionnaire/sections/{section_id}/")
+
+ def go_to_hub(self):
+ self.get("/questionnaire/")
+
+ def test_disable_block_in_repeating_section_if_block_source_progress_not_completed(
+ self,
+ ):
+ """
+ Test that a block inside a repeating section is disabled if the progress value source
+ from a block in another section is not completed
+ """
+
+ self.launchSurvey("test_progress_block_value_source_repeating_sections")
+
+ self.assertInBody("Choose another section to complete")
+
+ # 1. First section shows as not started
+ self.assertInSelector("List collector + random question", self.row_selector(1))
+ self.assert_section_status(1, "Not started")
+
+ # 2. Start completing section 1 and add 2 people
+ # Don't answer the random question enabler
+ self.go_to_section("section-1")
+ self.assertInBody("Does anyone else live here?")
+ self.post({"anyone-else": "Yes"})
+
+ self.add_person("John", "Doe")
+
+ self.assertInBody("Does anyone else live here?")
+ self.assertInSelector("John Doe", self.row_selector(1))
+ self.post({"anyone-else": "Yes"})
+
+ self.add_person("James", "Bond")
+
+ self.assertInBody("Does anyone else live here?")
+ self.assertInSelector("James Bond", self.row_selector(2))
+ self.post({"anyone-else": "No"})
+
+ self.post()
+
+ # 3. Assert random question is there
+ self.assertInBody("Random question enabler")
+
+ # 4. Go back to the hub and leave random question block incomplete
+ self.go_to_hub()
+
+ # 5. The two repeating sections should show as "not started"
+ self.assertInBody("Choose another section to complete")
+ self.assert_section_status(2, "Not started", ["John Doe"])
+ self.assert_section_status(3, "Not started", ["James Bond"])
+
+ # 5. Complete John Doe section
+ # Random question doesn't show
+ self.get(self.john_doe_link())
+ self.assertInBody("John Doe")
+ self.answer_dob()
+
+ # Assert random question not there
+ self.assertNotInBody("Random question about")
+
+ def test_disable_block_in_repeating_section_if_section_source_progress_not_completed(
+ self,
+ ):
+ """
+ Test that a block inside a repeating section is disabled if the progress value source
+ from a block in another section is not completed
+ """
+
+ self.launchSurvey("test_progress_section_value_source_repeating_sections")
+
+ self.assertInBody("Choose another section to complete")
+
+ # 1. First section shows as not started
+ self.assertInSelector("List collector + random question", self.row_selector(1))
+ self.assert_section_status(1, "Not started")
+
+ # 2. Start completing section 1 and add 2 people
+ # Don't answer the random question enabler
+ self.go_to_section("section-1")
+ self.assertInBody("Does anyone else live here?")
+ self.post({"anyone-else": "Yes"})
+
+ self.add_person("John", "Doe")
+
+ self.assertInBody("Does anyone else live here?")
+ self.assertInSelector("John Doe", self.row_selector(1))
+ self.post({"anyone-else": "Yes"})
+
+ self.add_person("James", "Bond")
+
+ self.assertInBody("Does anyone else live here?")
+ self.assertInSelector("James Bond", self.row_selector(2))
+ self.post({"anyone-else": "No"})
+
+ self.post()
+
+ # 3. Assert random question is there
+ self.assertInBody("Random question enabler")
+
+ # 4. Go back to the hub and leave random question block incomplete
+ self.go_to_hub()
+
+ # 5. The two repeating sections should show as "not started"
+ self.assertInBody("Choose another section to complete")
+ self.assert_section_status(2, "Not started", ["John Doe"])
+ self.assert_section_status(3, "Not started", ["James Bond"])
+
+ # 5. Complete John Doe section
+ # Random question doesn't show
+ self.get(self.john_doe_link())
+ self.assertInBody("John Doe")
+ self.answer_dob()
+
+ # Assert random question not there
+ self.assertNotInBody("Random question about")
+
+ def test_enable_block_in_repeating_section_if_block_source_progress_is_completed(
+ self,
+ ):
+ """
+ Test that a block inside a repeating section is enabled if the progress value source
+ from a block in another section is completeted
+ """
+
+ self.launchSurvey("test_progress_block_value_source_repeating_sections")
+
+ self.assertInBody("Choose another section to complete")
+
+ # 1. Complete 1st section and add 2 people
+ # And answer the random question enabler
+ self.go_to_section("section-1")
+ self.assertInBody("Does anyone else live here?")
+ self.post({"anyone-else": "Yes"})
+
+ self.add_person("John", "Doe")
+
+ self.assertInBody("Does anyone else live here?")
+ self.assertInSelector("John Doe", self.row_selector(1))
+ self.post({"anyone-else": "Yes"})
+
+ self.add_person("James", "Bond")
+
+ self.assertInBody("Does anyone else live here?")
+ self.assertInSelector("James Bond", self.row_selector(2))
+ self.post({"anyone-else": "No"})
+
+ self.post()
+
+ self.assertInBody("Random question enabler")
+ self.post({"random-question-enabler-answer": 1})
+
+ # Go back to the hub
+ self.go_to_hub()
+ self.assertInBody("Choose another section to complete")
+
+ # Assert first section completed
+ self.assert_section_status(1, "Completed", ["List collector + random question"])
+
+ # 2. Complete John Doe section
+ self.get(self.john_doe_link())
+ self.assertInBody("John Doe")
+ self.answer_dob()
+
+ # 3. Assert random question shows up
+ self.assertInBody("Random question")
+
+ def test_enable_block_in_repeating_section_if_section_source_progress_is_completed(
+ self,
+ ):
+ """
+ Test that a block inside a repeating section is enabled if the progress value source
+ from a block in another section is completeted
+ """
+ self.launchSurvey("test_progress_section_value_source_repeating_sections")
+
+ self.assertInBody("Choose another section to complete")
+
+ # 1. Complete 1st section and add 2 people
+ # And answer the random question enabler
+ self.go_to_section("section-1")
+ self.assertInBody("Does anyone else live here?")
+ self.post({"anyone-else": "Yes"})
+
+ self.add_person("John", "Doe")
+
+ self.assertInBody("Does anyone else live here?")
+ self.assertInSelector("John Doe", self.row_selector(1))
+ self.post({"anyone-else": "Yes"})
+
+ self.add_person("James", "Bond")
+
+ self.assertInBody("Does anyone else live here?")
+ self.assertInSelector("James Bond", self.row_selector(2))
+ self.post({"anyone-else": "No"})
+
+ self.post()
+
+ self.assertInBody("Random question enabler")
+ self.post({"random-question-enabler-answer": 1})
+
+ self.post()
+
+ # Go back to the hub
+ self.go_to_hub()
+ self.assertInBody("Choose another section to complete")
+
+ # Assert first section completed
+ self.assert_section_status(1, "Completed", ["List collector + random question"])
+
+ # 2. Complete John Doe section
+ self.get(self.john_doe_link())
+ self.assertInBody("John Doe")
+ self.answer_dob()
+
+ # 3. Assert random question shows up
+ self.assertInBody("Random question")
+
+ # pylint: disable=locally-disabled, too-many-statements
+ def test_block_progress_dependencies_updated_in_repeating_sections(self):
+ """
+ Test that dependency blocks inside repeating sections are updated properly
+ """
+
+ self.launchSurvey("test_progress_block_value_source_repeating_sections")
+
+ self.assertInBody("Choose another section to complete")
+
+ # 1. Complete 1st section and add 2 people
+ # Don't answer the random question enabler
+ self.go_to_section("section-1")
+ self.assertInBody("Does anyone else live here?")
+ self.post({"anyone-else": "Yes"})
+
+ self.add_person("John", "Doe")
+
+ self.assertInBody("Does anyone else live here?")
+ self.assertInSelector("John Doe", self.row_selector(1))
+ self.post({"anyone-else": "Yes"})
+
+ self.add_person("James", "Bond")
+
+ self.assertInBody("Does anyone else live here?")
+ self.assertInSelector("James Bond", self.row_selector(2))
+ self.post({"anyone-else": "No"})
+
+ self.post()
+
+ self.assertInBody("Random question enabler")
+
+ # 2. Go back to the hub and leave random question block incomplete
+ self.go_to_hub()
+
+ # 3. Repeating sections show as "Not started"
+ self.assertInBody("Choose another section to complete")
+ self.assert_section_status(2, "Not started", ["John Doe"])
+ self.assert_section_status(3, "Not started", ["James Bond"])
+
+ # 4. Complete John Doe section
+ self.get(self.john_doe_link())
+ self.assertInBody("John Doe")
+ self.answer_dob()
+
+ # 5. Assert random question not there
+ self.assertNotInBody("Random question about")
+
+ # 6. Go back to section 1 and complete random question
+ self.go_to_section("section-1")
+ self.assertInBody("Random question enabler")
+ self.post({"random-question-enabler-answer": 1})
+
+ # 7. Go back to the hub
+ self.go_to_hub()
+
+ # 8. Assert sections 1 is completed and repeating sections are partially completed
+ self.assertInBody("Choose another section to complete")
+ self.assert_section_status(1, "Completed", ["List collector + random question"])
+ self.assert_section_status(
+ 2, "Partially completed", ["John Doe", "Continue with section"]
+ )
+ self.assert_section_status(3, "Not started", ["James Bond"])
+
+ # 9. Go back to John Doe section
+ self.get(self.john_doe_link())
+
+ # 10. Assert prompts for random question
+ self.assertInBody("Random question about")
+ self.post({"other-answer": 1})
+
+ # 12. Assert it goes to the section summary
+ self.assertInUrl("questionnaire/sections/section-2")
+
+ # 13. Assert answer was provided
+ self.assertInSelector("Random question about", self.row_selector(2))
+ self.assertNotInSelector("No answer provided", self.row_selector(2))
+
+ # 14. Go back to summary
+ self.go_to_hub()
+
+ # 15. Assert John Doe section is completed
+ self.assert_section_status(2, "Completed", ["John Doe"])
+
+ # 15. Edit James Bond section
+ self.get(self.james_bond_link())
+ self.assertInBody("James Bond")
+ self.answer_dob()
+
+ # 16. Assert random question shows up
+ self.assertInBody("Random question about")
+
+ def test_section_progress_dependencies_updated_in_repeating_sections(self):
+ """
+ Test that dependency blocks inside repeating sections are updated properly
+ """
+
+ self.launchSurvey("test_progress_section_value_source_repeating_sections")
+
+ self.assertInBody("Choose another section to complete")
+
+ # 1. Complete 1st section and add 2 people
+ # Don't answer the random question enabler
+ self.go_to_section("section-1")
+ self.assertInBody("Does anyone else live here?")
+ self.post({"anyone-else": "Yes"})
+
+ self.add_person("John", "Doe")
+
+ self.assertInBody("Does anyone else live here?")
+ self.assertInSelector("John Doe", self.row_selector(1))
+ self.post({"anyone-else": "Yes"})
+
+ self.add_person("James", "Bond")
+
+ self.assertInBody("Does anyone else live here?")
+ self.assertInSelector("James Bond", self.row_selector(2))
+ self.post({"anyone-else": "No"})
+
+ self.post()
+
+ self.assertInBody("Random question enabler")
+
+ # 2. Go back to the hub and leave random question block incomplete
+ self.go_to_hub()
+
+ # 3. Repeating sections show as "Not started"
+ self.assertInBody("Choose another section to complete")
+ self.assert_section_status(2, "Not started", ["John Doe"])
+ self.assert_section_status(3, "Not started", ["James Bond"])
+
+ # 4. Complete John Doe section
+ self.get(self.john_doe_link())
+ self.assertInBody("John Doe")
+ self.answer_dob()
+
+ # 5. Assert random question not there
+ self.assertNotInBody("Random question about")
+
+ # 6. Go back to section 1 and complete random question
+ self.go_to_section("section-1")
+ self.assertInBody("Random question enabler")
+ self.post({"random-question-enabler-answer": 1})
+
+ # 7. Go back to the hub
+ self.go_to_hub()
+
+ # 8. Assert sections 1 is completed and repeating sections are partially completed
+ self.assertInBody("Choose another section to complete")
+ self.assert_section_status(1, "Completed", ["List collector + random question"])
+ self.assert_section_status(
+ 2, "Partially completed", ["John Doe", "Continue with section"]
+ )
+ self.assert_section_status(3, "Not started", ["James Bond"])
+
+ # 9. Go back to John Doe section
+ self.get(self.john_doe_link())
+
+ # 10. Assert prompts for random question
+ self.assertInBody("Random question about")
+ self.post({"other-answer": 1})
+
+ # 12. Assert it goes to the section summary
+ self.assertInUrl("questionnaire/sections/section-2")
+
+ # 13. Assert answer was provided
+ self.assertInSelector("Random question about", self.row_selector(2))
+ self.assertNotInSelector("No answer provided", self.row_selector(2))
+
+ # 14. Go back to summary
+ self.go_to_hub()
+
+ # 15. Assert John Doe section is completed
+ self.assert_section_status(2, "Completed", ["John Doe"])
+
+ # 15. Edit James Bond section
+ self.get(self.james_bond_link())
+ self.assertInBody("James Bond")
+ self.answer_dob()
+
+ # 16. Assert random question shows up
+ self.assertInBody("Random question about")
+
+ def test_section_progress_dependencies_updated_in_repeating_sections_with_chained_dependencies(
+ self,
+ ):
+ """
+ Test that dependency blocks inside repeating sections are updated properly when there are chained dependencies
+ """
+ self.launchSurvey(
+ "test_progress_value_source_repeating_sections_chained_dependencies"
+ )
+
+ self.assertInBody("Choose another section to complete")
+
+ # 1. Complete 3rd section and add 2 people
+ # And answer the random question enabler
+ self.go_to_section("section-3")
+ self.assertInBody("Does anyone else live here?")
+ self.post({"second-anyone-else": "Yes"})
+
+ self.add_person_second_list_collector("Jane", "Doe")
+
+ self.assertInBody("Does anyone else live here?")
+ self.assertInSelector("Jane Doe", self.row_selector(1))
+ self.post({"second-anyone-else": "Yes"})
+
+ self.add_person_second_list_collector("Marie", "Clare")
+
+ self.assertInBody("Does anyone else live here?")
+ self.assertInSelector("Marie Clare", self.row_selector(2))
+ self.post({"second-anyone-else": "No"})
+
+ self.post()
+
+ self.assertInBody("Random question enabler")
+ self.post({"second-random-question-enabler-answer": 1})
+
+ self.post()
+
+ # Go back to the hub
+ self.go_to_hub()
+
+ # 2. The two repeating sections should show as "not started"
+ self.assertInBody("Choose another section to complete")
+ self.assert_section_status(4, "Not started", ["Jane Doe"])
+ self.assert_section_status(5, "Not started", ["Marie Clare"])
+
+ # 3. Complete Jane Doe section
+ self.get(self.jane_doe_link())
+ self.assertInBody("Jane Doe")
+ self.answer_dob_second_repeat()
+ self.post({"second-other-answer": 1})
+
+ # Go back to the hub
+ self.go_to_hub()
+
+ # 4. The Jane Doe section should now be Complete
+ self.assertInBody("Choose another section to complete")
+ self.assert_section_status(4, "Completed", ["Jane Doe"])
+ self.assert_section_status(5, "Not started", ["Marie Clare"])
+
+ # 5. Complete section 2
+ self.go_to_section("section-2")
+ self.post({"s2-b2-q1-a1": 1})
+
+ # 6. Section-2 and should be complete but the Jane Doe section should be partially completed
+ self.assertInBody("Choose another section to complete")
+ self.assert_section_status(2, "Completed")
+ self.assert_section_status(4, "Partially completed", ["Jane Doe"])
+ self.assert_section_status(5, "Not started", ["Marie Clare"])
+
+ # 5. Complete section 1
+ self.go_to_section("section-1")
+ self.post({"s1-b1-q1-a1": 1})
+
+ # 6. Section 1 and the Jane Doe section should now be but complete
+ # but Section-2 should be partially completed
+ self.assertInBody("Choose another section to complete")
+ self.assert_section_status(1, "Completed")
+ self.assert_section_status(2, "Partially completed")
+ self.assert_section_status(4, "Completed", ["Jane Doe"])
+ self.assert_section_status(5, "Not started", ["Marie Clare"])
diff --git a/tests/integration/questionnaire/test_questionnaire_progress_value_source_section_enabled.py b/tests/integration/questionnaire/test_questionnaire_progress_value_source_section_enabled.py
new file mode 100644
index 0000000000..d528cfc9a7
--- /dev/null
+++ b/tests/integration/questionnaire/test_questionnaire_progress_value_source_section_enabled.py
@@ -0,0 +1,176 @@
+from tests.integration.integration_test_case import IntegrationTestCase
+
+
+class TestQuestionnaireProgressValueSource(IntegrationTestCase):
+ def assert_section_status(self, section_index, status, other_labels=None):
+ self.assertInSelector(status, self.row_selector(section_index))
+ if other_labels and len(other_labels) > 0:
+ for other_label in other_labels:
+ self.assertInSelector(other_label, self.row_selector(section_index))
+
+ def go_to_section(self, section_id):
+ self.get(f"/questionnaire/sections/{section_id}/")
+
+ def go_to_hub(self):
+ self.get("/questionnaire/")
+
+ def test_enable_section_by_progress_linear_flow(self):
+ """
+ Test that a section is enabled by progress value source
+ In a linear flow with no hub
+ """
+
+ self.launchSurvey("test_progress_value_source_section_enabled_no_hub")
+
+ self.assertInBody("Section 1 Question 1")
+ self.post({"s1-b1-q1-a1": 1})
+
+ self.assertInBody("Section 1 Question 2")
+ self.post({"s1-b2-q1-a1": 1})
+
+ self.assertInBody("Section 2 Question 1")
+ self.post({"s2-b1-q1-a1": 1})
+
+ def test_enable_section_by_progress_hub_flow(self):
+ """
+ Test that a section is enabled by progress value source
+ In a hub flow
+ """
+
+ self.launchSurvey("test_progress_value_source_section_enabled_hub")
+
+ # 1. Only section 1 shows on the hub
+ self.assertInBody("Choose another section to complete")
+ self.assertInBody("Section 1")
+ self.assertNotInBody("Section 2")
+
+ # 2. Complete section 1
+ self.go_to_section("section-1")
+ self.assertInBody("Section 1 Question 1")
+ self.post({"s1-b1-q1-a1": 1})
+
+ self.assertInUrl("/questionnaire/s1-b2")
+ self.post({"s1-b2-q1-a1": 1})
+
+ # 3. Assert section 1 completed on the hub
+ self.assertEqualUrl("/questionnaire/")
+
+ self.assert_section_status(1, "Completed", ["View answers"])
+
+ # 4. Assert section 2 is now available on the hub
+ self.assert_section_status(2, "Not started", ["Start section"])
+
+ # 5. Complete section 2
+ self.go_to_section("section-2")
+ self.assertInUrl("/questionnaire/s2-b1")
+ self.post({"s2-b1-q1-a1": 1})
+
+ self.assertEqualUrl("/questionnaire/")
+
+ # 6. Assert section 2 completed on the hub
+ self.assert_section_status(1, "Completed", ["View answers"])
+ self.assert_section_status(2, "Completed", ["View answers"])
+
+ self.assertInBody("Submit survey")
+
+ def test_value_source_dependency_enable_section_by_progress_hub_flow(self):
+ """
+ Test that dependencies that rely on a section's progress
+ are updated when the section progress changes
+ """
+
+ self.launchSurvey("test_progress_value_source_section_enabled_hub")
+
+ self.assertInBody("Choose another section to complete")
+ self.assertInBody("Section 1")
+ self.assertNotInBody("Section 2")
+
+ # 1. Start section 1
+ self.go_to_section("section-1")
+ self.assertInBody("Section 1 Question 1")
+ self.post({"s1-b1-q1-a1": 1})
+
+ self.assertInBody("Section 1 Question 2")
+
+ # 2. Leave section 1 incomplete
+ self.get("/questionnaire/")
+
+ # 3. Assert that section 2 is not enabled
+ self.assertNotInBody("Section 2")
+
+ # 4. Assert section 1 is in progress
+ self.assert_section_status(1, "Partially completed", ["Continue with section"])
+
+ # 5. Go back to section 1 and complete it
+ self.get("/questionnaire/sections/section-1/?resume=True")
+
+ self.assertInBody("Section 1 Question 2")
+ self.post({"s1-b2-q1-a1": 1})
+
+ self.assertEqualUrl("/questionnaire/")
+
+ # 6. Assert section 1 completed on the hub
+ self.assert_section_status(1, "Completed", ["View answers"])
+
+ # 7. Assert that section 2 is now enabled
+ self.assert_section_status(2, "Not started", ["Start section"])
+
+ def test_enable_section_by_progress_hub_complex_happy_path(self):
+ self.launchSurvey("test_progress_value_source_section_enabled_hub_complex")
+
+ self.assertInBody("Choose another section to complete")
+ self.assertInBody("Section 1")
+ self.assertInBody("Section 3")
+ self.assertNotInBody("Section 2")
+
+ # 1. Complete section 1
+ self.go_to_section("section-1")
+ self.assertInBody("Section 1 Question 1")
+ self.post({"s1-b1-q1-a1": 1})
+
+ self.assertInBody("Section 1 Question 2")
+ self.post({"s1-b2-q1-a1": 1})
+
+ # 2. Complete section 2 with all the questions
+ self.assertInBody("Choose another section to complete")
+ self.assertInBody("Section 2")
+ self.go_to_section("section-2")
+
+ self.assertInBody("Section 2 Question 1")
+ self.post({"s2-b1-q1-a1": 1})
+
+ self.assertInBody("Section 2 Question 2")
+ self.post({"s2-b2-q1-a1": 0})
+
+ self.assertInBody("Section 2 Question 3")
+ self.post({"s2-b3-q1-a1": 0})
+
+ # 3. Assert section 3 is on the path
+ self.assertInBody("Choose another section to complete")
+ self.assertInBody("Section 3")
+ self.go_to_section("section-3")
+ self.assertInUrl("/questionnaire/s3-b1")
+
+ # 4. Complete section 3
+ self.assertInBody("Section 3 Question 1")
+ self.post({"s3-b1-q1-a1": 0})
+
+ # 5. Complete section 4
+ self.assertInBody("Choose another section to complete")
+ self.assertInBody("Section 4")
+ self.go_to_section("section-4")
+ self.assertInUrl("/questionnaire/s4-b1")
+
+ self.assertInBody("Section 4 Question 1")
+ self.post({"s4-b1-q1-a1": 0})
+
+ # 6. Assert all sections have been completed
+
+ for sel in (
+ self.row_selector(1),
+ self.row_selector(2),
+ self.row_selector(3),
+ self.row_selector(4),
+ ):
+ self.assertInSelector("Completed", sel)
+ self.assertInSelector("View answers", sel)
diff --git a/tests/integration/test_application_variables.py b/tests/integration/test_application_variables.py
index 0acc0ec6f7..8c8a228ef7 100644
--- a/tests/integration/test_application_variables.py
+++ b/tests/integration/test_application_variables.py
@@ -21,7 +21,7 @@ def test_google_analytics_code_and_credentials_are_present(self):
self.get("/dump/debug")
actual = json_loads(self.getResponseData())
self._client.set_cookie(
- "localhost", key="ons_cookie_policy", value="'usage':true"
+ domain="localhost", key="ons_cookie_policy", value="'usage':true"
)
self.get("/questionnaire/feedback/")
self.assertStatusOK()
@@ -38,7 +38,7 @@ def test_google_analytics_data_layer_has_no_null_fields(self):
self.get("/dump/debug")
actual = json_loads(self.getResponseData())
self._client.set_cookie(
- "localhost", key="ons_cookie_policy", value="'usage':true"
+ domain="localhost", key="ons_cookie_policy", value="'usage':true"
)
self.get("/questionnaire/name-block/")
self.assertStatusOK()
@@ -53,7 +53,7 @@ def test_google_analytics_data_layer_is_set_to_nisra_false(self):
self.get("/dump/debug")
actual = json_loads(self.getResponseData())
self._client.set_cookie(
- "localhost", key="ons_cookie_policy", value="'usage':true"
+ domain="localhost", key="ons_cookie_policy", value="'usage':true"
)
self.get("/questionnaire/individual-confirmation/")
self.assertStatusOK()
From 82b2abee888216147784d6ae287b530fa9b2094d Mon Sep 17 00:00:00 2001
From: petechd <53475968+petechd@users.noreply.github.com>
Date: Tue, 30 May 2023 13:51:40 +0100
Subject: [PATCH 8/9] Fix handling of invalid values in form numerical inputs
(#1111)
---
.../fields/decimal_field_with_separator.py | 7 +--
.../fields/integer_field_with_separator.py | 7 +--
app/forms/validators.py | 44 +++++--------------
app/helpers/form_helpers.py | 33 ++++++++++++++
tests/app/forms/test_custom_fields.py | 41 +++++++++++++++++
.../forms/validation/test_number_validator.py | 18 ++++----
tests/functional/spec/error_messages.spec.js | 18 ++++++++
7 files changed, 116 insertions(+), 52 deletions(-)
create mode 100644 app/helpers/form_helpers.py
diff --git a/app/forms/fields/decimal_field_with_separator.py b/app/forms/fields/decimal_field_with_separator.py
index 666540a56a..ea202f0b50 100644
--- a/app/forms/fields/decimal_field_with_separator.py
+++ b/app/forms/fields/decimal_field_with_separator.py
@@ -1,9 +1,8 @@
from decimal import Decimal, InvalidOperation
-from babel import numbers
from wtforms import DecimalField
-from app.settings import DEFAULT_LOCALE
+from app.helpers.form_helpers import sanitise_number
class DecimalFieldWithSeparator(DecimalField):
@@ -23,8 +22,6 @@ def __init__(self, **kwargs):
def process_formdata(self, valuelist):
if valuelist:
try:
- self.data = Decimal(
- valuelist[0].replace(numbers.get_group_symbol(DEFAULT_LOCALE), "")
- )
+ self.data = Decimal(sanitise_number(valuelist[0]))
except (ValueError, TypeError, InvalidOperation):
pass
diff --git a/app/forms/fields/integer_field_with_separator.py b/app/forms/fields/integer_field_with_separator.py
index 7a3098a8c7..d218ab29b3 100644
--- a/app/forms/fields/integer_field_with_separator.py
+++ b/app/forms/fields/integer_field_with_separator.py
@@ -1,7 +1,6 @@
-from babel import numbers
from wtforms import IntegerField
-from app.settings import DEFAULT_LOCALE
+from app.helpers.form_helpers import sanitise_number
class IntegerFieldWithSeparator(IntegerField):
@@ -21,8 +20,6 @@ def __init__(self, **kwargs):
def process_formdata(self, valuelist):
if valuelist:
try:
- self.data = int(
- valuelist[0].replace(numbers.get_group_symbol(DEFAULT_LOCALE), "")
- )
+ self.data = int(sanitise_number(valuelist[0]))
except ValueError:
pass
diff --git a/app/forms/validators.py b/app/forms/validators.py
index 9366b54126..1739808770 100644
--- a/app/forms/validators.py
+++ b/app/forms/validators.py
@@ -1,5 +1,6 @@
from __future__ import annotations
+import math
import re
from datetime import datetime, timezone
from decimal import Decimal, InvalidOperation
@@ -19,10 +20,14 @@
DecimalFieldWithSeparator,
IntegerFieldWithSeparator,
)
-from app.jinja_filters import format_number, get_formatted_currency
+from app.helpers.form_helpers import (
+ format_message_with_title,
+ format_playback_value,
+ sanitise_mobile_number,
+ sanitise_number,
+)
from app.questionnaire.questionnaire_store_updater import QuestionnaireStoreUpdater
from app.questionnaire.rules.utils import parse_datetime
-from app.utilities import safe_content
if TYPE_CHECKING:
from app.forms.questionnaire_form import QuestionnaireForm # pragma: no cover
@@ -49,15 +54,12 @@ def __call__(
field: Union[DecimalFieldWithSeparator, IntegerFieldWithSeparator],
) -> None:
try:
- Decimal(
- field.raw_data[0].replace(
- numbers.get_group_symbol(flask_babel.get_locale()), ""
- )
- )
+ # number is sanitised to guard against inputs like `,NaN_` etc
+ number = Decimal(sanitise_number(number=field.raw_data[0]))
except (ValueError, TypeError, InvalidOperation, AttributeError) as exc:
raise validators.StopValidation(self.message) from exc
- if "e" in field.raw_data[0].lower():
+ if "e" in field.raw_data[0].lower() or math.isnan(number):
raise validators.StopValidation(self.message)
@@ -126,6 +128,7 @@ def __call__(
field: Union[DecimalFieldWithSeparator, IntegerFieldWithSeparator],
) -> None:
value: Union[int, Decimal] = field.data
+
if value is not None:
error_message = self.validate_minimum(value) or self.validate_maximum(value)
if error_message:
@@ -179,11 +182,7 @@ def __init__(self, max_decimals: int = 0, messages: OptionalMessage = None):
def __call__(
self, form: "QuestionnaireForm", field: DecimalFieldWithSeparator
) -> None:
- data = (
- field.raw_data[0]
- .replace(numbers.get_group_symbol(flask_babel.get_locale()), "")
- .replace(" ", "")
- )
+ data = sanitise_number(field.raw_data[0])
decimal_symbol = numbers.get_decimal_symbol(flask_babel.get_locale())
if data and decimal_symbol in data:
if self.max_decimals == 0:
@@ -450,20 +449,6 @@ def _is_valid(
raise NotImplementedError(f"Condition '{condition}' is not implemented")
-def format_playback_value(
- value: Union[float, Decimal], currency: Optional[str] = None
-) -> str:
- if currency:
- return get_formatted_currency(value, currency)
-
- formatted_number: str = format_number(value)
- return formatted_number
-
-
-def format_message_with_title(error_message: str, question_title: str) -> str:
- return error_message % {"question_title": safe_content(question_title)}
-
-
class MutuallyExclusiveCheck:
def __init__(self, question_title: str, messages: OptionalMessage = None):
self.messages = {**error_messages, **(messages or {})}
@@ -492,11 +477,6 @@ def __call__(
raise validators.ValidationError(message)
-def sanitise_mobile_number(data: str) -> str:
- data = re.sub(r"[\s.,\t\-{}\[\]()/]", "", data)
- return re.sub(r"^(0{1,2}44|\+44|0)", "", data)
-
-
class MobileNumberCheck:
def __init__(self, message: OptionalMessage = None):
self.message = message or error_messages["INVALID_MOBILE_NUMBER"]
diff --git a/app/helpers/form_helpers.py b/app/helpers/form_helpers.py
new file mode 100644
index 0000000000..52d773c879
--- /dev/null
+++ b/app/helpers/form_helpers.py
@@ -0,0 +1,33 @@
+import re
+from decimal import Decimal
+
+import flask_babel
+from babel import numbers
+
+from app.jinja_filters import format_number, get_formatted_currency
+from app.utilities import safe_content
+
+
+def sanitise_number(number: str) -> str:
+ return (
+ number.replace(numbers.get_group_symbol(flask_babel.get_locale()), "")
+ .replace("_", "")
+ .replace(" ", "")
+ )
+
+
+def sanitise_mobile_number(data: str) -> str:
+ data = re.sub(r"[\s.,\t\-{}\[\]()/]", "", data)
+ return re.sub(r"^(0{1,2}44|\+44|0)", "", data)
+
+
+def format_playback_value(value: float | Decimal, currency: str | None = None) -> str:
+ if currency:
+ return get_formatted_currency(value, currency)
+
+ formatted_number: str = format_number(value)
+ return formatted_number
+
+
+def format_message_with_title(error_message: str, question_title: str) -> str:
+ return error_message % {"question_title": safe_content(question_title)}
diff --git a/tests/app/forms/test_custom_fields.py b/tests/app/forms/test_custom_fields.py
index a1d5454371..ef79081356 100644
--- a/tests/app/forms/test_custom_fields.py
+++ b/tests/app/forms/test_custom_fields.py
@@ -1,3 +1,5 @@
+from decimal import Decimal
+
import pytest
from wtforms.fields import Field
@@ -21,6 +23,7 @@ def test_text_area_supports_maxlength_property(mock_form):
assert text_area.maxlength == 20
+@pytest.mark.usefixtures("gb_locale")
def test_integer_field(mock_form):
integer_field = IntegerFieldWithSeparator(_form=mock_form, name="aName")
assert isinstance(integer_field, Field)
@@ -31,6 +34,44 @@ def test_integer_field(mock_form):
pytest.fail("Exceptions should not thrown by CustomIntegerField")
+@pytest.mark.usefixtures("gb_locale")
+@pytest.mark.parametrize(
+ "number_input, result",
+ [
+ ("_110", 110),
+ ("1_10", 110),
+ ("1__10", 110),
+ ("1_1,0", 110),
+ ("_1_1,0,0", 1100),
+ ("1.10", None),
+ ],
+)
+def test_integer_field_inputs(mock_form, number_input, result):
+ integer_field = IntegerFieldWithSeparator(_form=mock_form, name="aName")
+ integer_field.process_formdata([number_input])
+
+ assert integer_field.data == result
+
+
+@pytest.mark.usefixtures("gb_locale")
+@pytest.mark.parametrize(
+ "number_input, result",
+ [
+ ("1_1,0", Decimal("110")),
+ ("1.10", Decimal("1.1")),
+ ("_1.1_0", Decimal("1.1")),
+ ("_1.1_0,0", Decimal("1.1")),
+ ("_1._1,0,0", Decimal("1.1")),
+ ],
+)
+def test_decimal_field_inputs(mock_form, number_input, result):
+ decimal_field = DecimalFieldWithSeparator(_form=mock_form, name="aName")
+ decimal_field.process_formdata([number_input])
+
+ assert decimal_field.data == result
+
+
+@pytest.mark.usefixtures("gb_locale")
def test_decimal_field(mock_form):
decimal_field = DecimalFieldWithSeparator(_form=mock_form, name="aName")
assert isinstance(decimal_field, Field)
diff --git a/tests/app/forms/validation/test_number_validator.py b/tests/app/forms/validation/test_number_validator.py
index 81d45fddde..ddc61ecf14 100644
--- a/tests/app/forms/validation/test_number_validator.py
+++ b/tests/app/forms/validation/test_number_validator.py
@@ -1,3 +1,5 @@
+from decimal import Decimal
+
import pytest
from wtforms.validators import StopValidation, ValidationError
@@ -7,12 +9,7 @@
@pytest.mark.parametrize(
"value",
- (
- [None],
- [""],
- ["a"],
- ["2E2"],
- ),
+ ([None], [""], ["a"], ["2E2"], ["NaN"], [",NaN_"]),
)
@pytest.mark.usefixtures("gb_locale")
def test_number_validator_raises_StopValidation(
@@ -49,12 +46,13 @@ def test_decimal_validator_raises_StopValidation(
@pytest.mark.parametrize(
"value",
(
- ["0"],
- ["10"],
- ["-10"],
+ "0",
+ "10",
+ "-10",
),
)
@pytest.mark.usefixtures("gb_locale")
def test_number_validator(number_check, value, mock_form, mock_field):
- mock_field.raw_data = value
+ mock_field.raw_data = [value]
+ mock_field.data = Decimal(value)
number_check(mock_form, mock_field)
diff --git a/tests/functional/spec/error_messages.spec.js b/tests/functional/spec/error_messages.spec.js
index c363f9433e..ac1d4a9fde 100644
--- a/tests/functional/spec/error_messages.spec.js
+++ b/tests/functional/spec/error_messages.spec.js
@@ -1,4 +1,5 @@
import AboutYou from "../generated_pages/multiple_answers/about-you-block.page";
+import BlockPage from "../generated_pages/percentage/block.page";
async function answerAllButOne() {
await $(AboutYou.textfield()).setValue("John Doe");
@@ -135,3 +136,20 @@ describe("Error Messages", () => {
await expect(await $(AboutYou.textarea()).isFocused()).to.be.true;
});
});
+describe("Error Message NaN value", () => {
+ beforeEach(async () => {
+ await browser.openQuestionnaire("test_percentage.json");
+ });
+ it("Given a NaN value was entered on percentage question, When the error is displayed, Then the error message is correct", async () => {
+ await $(BlockPage.answer()).setValue("NaN");
+ await $(BlockPage.submit()).click();
+ await expect(await $(BlockPage.errorHeader()).getText()).to.equal("There is a problem with your answer");
+ await expect(await $(BlockPage.answerErrorItem()).getText()).to.equal("Enter a number");
+ });
+ it("Given a NaN value with separators was entered on percentage question, When the error is displayed, Then the error message is correct", async () => {
+ await $(BlockPage.answer()).setValue(",NaN_");
+ await $(BlockPage.submit()).click();
+ await expect(await $(BlockPage.errorHeader()).getText()).to.equal("There is a problem with your answer");
+ await expect(await $(BlockPage.answerErrorItem()).getText()).to.equal("Enter a number");
+ });
+});
From f4046195902e80654f6eed5e20782bb78a697f31 Mon Sep 17 00:00:00 2001
From: Katie Gardner_ONS <114991656+katie-gardner@users.noreply.github.com>
Date: Thu, 1 Jun 2023 10:34:57 +0100
Subject: [PATCH 9/9] Update Chromedriver to version 113 (#1118)
* update chromedriver
* temporarily comment out line, to be fixed separately
---
package.json | 2 +-
tests/functional/spec/textfield_suggestions.spec.js | 3 ++-
yarn.lock | 8 ++++----
3 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/package.json b/package.json
index f4e26d4f46..5e7eb261de 100644
--- a/package.json
+++ b/package.json
@@ -33,7 +33,7 @@
"@wdio/mocha-framework": "^8.3.0",
"@wdio/spec-reporter": "^8.3.0",
"chai": "^4.3.6",
- "chromedriver": "^112.0.0",
+ "chromedriver": "^113.0.0",
"eslint": "^8.10.0",
"eslint-cli": "^1.1.1",
"eslint-config-standard": "^14.1.1",
diff --git a/tests/functional/spec/textfield_suggestions.spec.js b/tests/functional/spec/textfield_suggestions.spec.js
index 24807ef3a9..80cd8fd70a 100644
--- a/tests/functional/spec/textfield_suggestions.spec.js
+++ b/tests/functional/spec/textfield_suggestions.spec.js
@@ -31,7 +31,8 @@ describe("Suggestions", () => {
await browser.keys(" United");
await suggestionsList.waitForExist();
await expect(await $$(".ons-js-autosuggest-listbox li").length).to.not.equal(0);
- await suggestionsOption.click();
+ // TODO there is an issue with the load-time of the auto-suggest dropdown causing this test to fail. Uncomment when this has been resolved.
+ // await suggestionsOption.click();
await $(MultipleSuggestionsPage.submit()).click();
await expect(await browser.getUrl()).to.contain(SubmitPage.url());
});
diff --git a/yarn.lock b/yarn.lock
index e07600b0ed..724845cce5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1786,10 +1786,10 @@ chrome-launcher@^0.15.0:
is-wsl "^2.2.0"
lighthouse-logger "^1.0.0"
-chromedriver@^112.0.0:
- version "112.0.1"
- resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-112.0.1.tgz#b1d23720613d7afa17cde6d5057e765109e07e69"
- integrity sha512-ieQzvellbtPY4MUrFzzayC1bZa/HoBsGXejUQJhAPWcYALxtkjUZNUYWbojMjIzf8iIhVda9VvdXiRKqdlN7ow==
+chromedriver@^113.0.0:
+ version "113.0.0"
+ resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-113.0.0.tgz#d4855f156ee51cea4282e04aadd29fa154e44dbb"
+ integrity sha512-UnQlt2kPicYXVNHPzy9HfcWvEbKJjjKAEaatdcnP/lCIRwuSoZFVLH0HVDAGdbraXp3dNVhfE2Qx7gw8TnHnPw==
dependencies:
"@testim/chrome-version" "^1.1.3"
axios "^1.2.1"