From 3edaffa0c5f017fffa7c72fa8519483e702a4d62 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Tue, 20 May 2025 08:39:01 -0500 Subject: [PATCH 001/148] - add manual deployment option to circi docs --- docs/Technical-Documentation/circle-ci.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/Technical-Documentation/circle-ci.md b/docs/Technical-Documentation/circle-ci.md index be8b5edd4..d7d00d86f 100644 --- a/docs/Technical-Documentation/circle-ci.md +++ b/docs/Technical-Documentation/circle-ci.md @@ -11,12 +11,19 @@ * Executors: build environments used for jobs * Jobs: a collection of steps run on an executor * Orbs: reusable code that can be imported in to circle config - similar to pip packages, etc -* We currently have 5 workflows: +* We currently have 5 automated workflows: * `build-and-test`: Runs jobs `secrets-check`, `test-frontend` and `test-backend` on every commit * `dev-deployment`: Deploys a PR to the dev space. Triggered by a GitHub action whenever one of the relevant deployment labels is assigned via an API call to Circle CI with the pipeline parameter `run_dev_deployment`. * `nightly`: Runs every night at UTC midnight and performs an OWASP scan against the staging site for both backend and frontend then stores the results in Django using a Cloud Foundry task. * `owasp-scan`: Runs an OWASP scan against the backend and frontend for a given PR. Triggered by a GitHub action whenever the `QASP Review` label is assigned via an API call to Circle CI with the pipeline parameter `run_owasp_scan`. - * `staging-deployment`: Deploys the main branch to the staging space in Cloud.gov. Triggered via merges to the branch `develop`. + * `staging-deployment`: Deploys the main branch to the staging space in Cloud.gov. Triggered via merges to the branch `main`. +* Manual deployment + * Select the desired branch from the branch dropdown on the CircleCI project page + * Click the "Trigger Pipeline" button + * Enter the `target_env`: e.g. `qasp`, `raft`, `a11y` + * Set `triggered` to true + * Set `run_dev_deployment` to true + * Click the `Run Pipeline` button ## How are environment variables supplied in CI? We manually set some environment variables in the project settings for Circle CI. From there, they are used in several places: From b6678c2c939bda0ac32e3c5e169b3b34cacf8e31 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Tue, 20 May 2025 08:41:31 -0500 Subject: [PATCH 002/148] - grammar change --- docs/Technical-Documentation/circle-ci.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Technical-Documentation/circle-ci.md b/docs/Technical-Documentation/circle-ci.md index d7d00d86f..19c6460bf 100644 --- a/docs/Technical-Documentation/circle-ci.md +++ b/docs/Technical-Documentation/circle-ci.md @@ -11,7 +11,7 @@ * Executors: build environments used for jobs * Jobs: a collection of steps run on an executor * Orbs: reusable code that can be imported in to circle config - similar to pip packages, etc -* We currently have 5 automated workflows: +* Automated workflows: * `build-and-test`: Runs jobs `secrets-check`, `test-frontend` and `test-backend` on every commit * `dev-deployment`: Deploys a PR to the dev space. Triggered by a GitHub action whenever one of the relevant deployment labels is assigned via an API call to Circle CI with the pipeline parameter `run_dev_deployment`. * `nightly`: Runs every night at UTC midnight and performs an OWASP scan against the staging site for both backend and frontend then stores the results in Django using a Cloud Foundry task. From 72fc26b923d03a518334a38ea6ac4d39afc2acd2 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Tue, 27 Jan 2026 12:17:26 -0600 Subject: [PATCH 003/148] - Move reparse "finished_at" logic to the finally clause to encapsulate the entire parsing process - Allow celery to run 100 tasks before recreating itself --- tdrs-backend/celery_start.sh | 4 +-- .../tdpservice/scheduling/parser_task.py | 29 ++++++++++--------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/tdrs-backend/celery_start.sh b/tdrs-backend/celery_start.sh index ce7a99ea6..ade6b5915 100755 --- a/tdrs-backend/celery_start.sh +++ b/tdrs-backend/celery_start.sh @@ -20,9 +20,9 @@ if [[ $1 == "cloud" ]]; then fi # Celery worker config can be found here: https://docs.celeryq.dev/en/stable/userguide/workers.html#:~:text=The-,hostname,-argument%20can%20expand -celery -A tdpservice.settings worker --loglevel=INFO --concurrency=1 --max-tasks-per-child=1 -n worker1@%h & # tune -c ? +celery -A tdpservice.settings worker --loglevel=INFO --concurrency=1 --max-tasks-per-child=100 -n worker1@%h & # tune -c ? sleep 5 # TODO: Uncomment the following line to add flower service when memory limitation is resolved celery -A tdpservice.settings --broker=$REDIS_URI flower --port=8080 & -celery -A tdpservice.settings beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler \ No newline at end of file +celery -A tdpservice.settings beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler diff --git a/tdrs-backend/tdpservice/scheduling/parser_task.py b/tdrs-backend/tdpservice/scheduling/parser_task.py index 28cf127ec..46e79ad43 100644 --- a/tdrs-backend/tdpservice/scheduling/parser_task.py +++ b/tdrs-backend/tdpservice/scheduling/parser_task.py @@ -106,20 +106,7 @@ def parse(data_file_id, reparse_id=None): f"{dfs.status}." ) - if reparse_id is not None: - file_meta.num_records_created = dfs.total_number_of_records_created - file_meta.cat_4_errors_generated = ParserError.objects.filter( - file_id=data_file_id, - error_type=ParserErrorCategoryChoices.CASE_CONSISTENCY, - ).count() - file_meta.finished = True - file_meta.success = True - file_meta.finished_at = timezone.now() - file_meta.save() - ReparseMeta.set_total_num_records_post( - ReparseMeta.objects.get(pk=reparse_id) - ) - else: + if reparse_id is None: qs = User.objects.filter( stt=data_file.stt, account_approval_status=AccountApprovalStatusChoices.APPROVED, @@ -179,3 +166,17 @@ def parse(data_file_id, reparse_id=None): set_error_report(dfs, error_report) logger.handlers[2].doRollover(data_file) update_dfs(dfs, data_file) + + if reparse_id is not None: + file_meta.num_records_created = dfs.total_number_of_records_created + file_meta.cat_4_errors_generated = ParserError.objects.filter( + file_id=data_file_id, + error_type=ParserErrorCategoryChoices.CASE_CONSISTENCY, + ).count() + file_meta.finished = True + file_meta.success = True + file_meta.finished_at = timezone.now() + file_meta.save() + ReparseMeta.set_total_num_records_post( + ReparseMeta.objects.get(pk=reparse_id) + ) From 445a48f703c9a2e542a5256c066e09ca45355350 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Tue, 27 Jan 2026 12:35:59 -0600 Subject: [PATCH 004/148] - badcommit --- tdrs-backend/tdpservice/parsers/error_generator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tdrs-backend/tdpservice/parsers/error_generator.py b/tdrs-backend/tdpservice/parsers/error_generator.py index 6e8d23c7a..33b5e4c50 100644 --- a/tdrs-backend/tdpservice/parsers/error_generator.py +++ b/tdrs-backend/tdpservice/parsers/error_generator.py @@ -24,6 +24,7 @@ class ErrorGeneratorType(Enum): MSG_ONLY_PRECHECK = "message_only_precheck" MSG_ONLY_RECORD_PRECHECK = "message_only_record_precheck" DYNAMIC_ROW_CASE_CONSISTENCY = "dynamic_row_case_consistency" + TEST = "test" class ErrorGeneratorFactory: From 7532cb4b82e1fcc54afa72f37a2b556299aa3d93 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Tue, 27 Jan 2026 12:36:23 -0600 Subject: [PATCH 005/148] Revert "- badcommit" This reverts commit 445a48f703c9a2e542a5256c066e09ca45355350. --- tdrs-backend/tdpservice/parsers/error_generator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tdrs-backend/tdpservice/parsers/error_generator.py b/tdrs-backend/tdpservice/parsers/error_generator.py index 33b5e4c50..6e8d23c7a 100644 --- a/tdrs-backend/tdpservice/parsers/error_generator.py +++ b/tdrs-backend/tdpservice/parsers/error_generator.py @@ -24,7 +24,6 @@ class ErrorGeneratorType(Enum): MSG_ONLY_PRECHECK = "message_only_precheck" MSG_ONLY_RECORD_PRECHECK = "message_only_record_precheck" DYNAMIC_ROW_CASE_CONSISTENCY = "dynamic_row_case_consistency" - TEST = "test" class ErrorGeneratorFactory: From 0e4b980d036d2aa19152084f17bff293044c5c94 Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Wed, 28 Jan 2026 10:27:07 -0600 Subject: [PATCH 006/148] add test fixture --- tdrs-backend/tdpservice/conftest.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tdrs-backend/tdpservice/conftest.py b/tdrs-backend/tdpservice/conftest.py index affe81273..29850dcf3 100644 --- a/tdrs-backend/tdpservice/conftest.py +++ b/tdrs-backend/tdpservice/conftest.py @@ -198,6 +198,18 @@ def stt(region): return stt +@pytest.fixture +def tribe_stt(region): + """Return a Tribal STT.""" + stt, _ = STT.objects.get_or_create( + name="Blackfeet Nation", + region=region, + stt_code="020", + type=STT.EntityType.TRIBE, + ) + return stt + + @pytest.fixture def region(): """Return a region.""" From 4833e6057d1fd06f1f5731a3d091f6f80c292c5a Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Wed, 28 Jan 2026 10:27:17 -0600 Subject: [PATCH 007/148] add new datafiles get endpoint filtering tests --- .../tdpservice/data_files/test/test_api.py | 240 +++++++++++------- 1 file changed, 144 insertions(+), 96 deletions(-) diff --git a/tdrs-backend/tdpservice/data_files/test/test_api.py b/tdrs-backend/tdpservice/data_files/test/test_api.py index 2b216914a..4b740d39e 100644 --- a/tdrs-backend/tdpservice/data_files/test/test_api.py +++ b/tdrs-backend/tdpservice/data_files/test/test_api.py @@ -714,122 +714,170 @@ def test_list_ofa_admin_data_file_years_no_self_stt( class TestDataFileQuerysetFiltering: - """Tests for the get_queryset filtering logic with program_type and is_program_audit combinations. + """Tests for the get_queryset filtering logic with program_type, stt, section, year, quarter, and is_program_audit combinations. Note: Program integrity audit (is_program_audit=True) only applies to TANF files. Tribal, SSP, and FRA files do not have audit variants. + + FRA files also query differently than TANF files, the `file_type` url param corresponds to the section for FRA files, but to the program type for TANF/SSP files. Tribal TANF files use `file_type=tanf` but from a tribal STT. """ root_url = "/v1/data_files/" - @pytest.fixture - def tanf_non_audit_file(self, data_analyst, stt): - """Create a TANF file with is_program_audit=False.""" - return DataFile.create_new_version( - { - "original_filename": "tanf_non_audit.txt", - "user": data_analyst, - "stt": stt, - "year": 2024, - "quarter": "Q1", - "section": "Active Case Data", - "program_type": DataFile.ProgramType.TANF, - "is_program_audit": False, - } - ) + program_type_options = [i[0] for i in DataFile.ProgramType.choices] + year_options = [2021, 2022] + quarter_options = [i[0] for i in DataFile.Quarter.choices] - @pytest.fixture - def tanf_audit_file(self, data_analyst, stt): - """Create a TANF file with is_program_audit=True.""" - return DataFile.create_new_version( - { - "original_filename": "tanf_audit.txt", - "user": data_analyst, - "stt": stt, - "year": 2024, - "quarter": "Q1", - "section": "Active Case Data", - "program_type": DataFile.ProgramType.TANF, - "is_program_audit": True, - } - ) + fra_section_options = DataFile.get_fra_section_list() + tanf_section_options = [ + i[0] + for i in DataFile.Section.choices + if not i[0] in DataFile.get_fra_section_list() + ] - @pytest.fixture - def tribal_file(self, data_analyst, stt): - """Create a TRIBAL file (is_program_audit is always False for Tribal).""" - return DataFile.create_new_version( - { - "original_filename": "tribal.txt", - "user": data_analyst, - "stt": stt, - "year": 2024, - "quarter": "Q1", - "section": "Active Case Data", - "program_type": DataFile.ProgramType.TRIBAL, - "is_program_audit": False, - } - ) + non_pia_files = {} + pia_files = {} - @pytest.mark.django_db - def test_tanf_non_audit_appears_in_regular_list( - self, api_client, data_analyst, stt, tanf_non_audit_file, tanf_audit_file - ): - """Test TANF file with is_program_audit=False appears in regular file list.""" - api_client.login(username=data_analyst.username, password="test_password") + def gen_key(self, program_type, year, quarter, init_array=False): + k = f"{program_type}{year}{quarter}" - response = api_client.get(f"{self.root_url}?stt={stt.id}&year=2024&quarter=Q1") + if init_array: + if k not in self.non_pia_files: + self.non_pia_files[k] = [] + if k not in self.pia_files: + self.pia_files[k] = [] + return k - assert response.status_code == status.HTTP_200_OK - file_ids = [f["id"] for f in response.data] - assert len(file_ids) == 1 - assert tanf_non_audit_file.id in file_ids - assert tanf_audit_file.id not in file_ids + def test_pia(self, program_type): + return program_type is DataFile.ProgramType.TANF.value - @pytest.mark.django_db - def test_tanf_audit_appears_in_program_integrity_audit_list( - self, api_client, data_analyst, stt, tanf_non_audit_file, tanf_audit_file + def get_section_options(self, program_type): + if program_type is DataFile.ProgramType.FRA.value: + return self.fra_section_options + return self.tanf_section_options + + def get_location(self, program_type, stt, tribe_stt): + if program_type is DataFile.ProgramType.TRIBAL.value: + return tribe_stt + return stt + + def create_file( + self, + program_type, + section, + year, + quarter, + location, + user, + pia=False, ): - """Test TANF file with is_program_audit=True appears in program-integrity-audit list.""" - api_client.login(username=data_analyst.username, password="test_password") + pia_part = "pia" if pia else "spl" - response = api_client.get( - f"{self.root_url}?stt={stt.id}&year=2024&quarter=Q1&file_type=program-integrity-audit" + return DataFile.create_new_version( + { + "original_filename": f"{program_type}-{section}-{year}-{quarter}-{pia_part}.txt", + "user": user, + "stt": location, + "year": year, + "quarter": quarter, + "section": section, + "program_type": program_type, + "is_program_audit": pia, + } ) + def setup_test_data(self, stt, tribe_stt, ofa_system_admin): + for program_type in self.program_type_options: + section_options = self.get_section_options(program_type) + location = self.get_location(program_type, stt, tribe_stt) + + for section in section_options: + for year in self.year_options: + for quarter in self.quarter_options: + k = self.gen_key(program_type, year, quarter, init_array=True) + self.non_pia_files[k].append( + self.create_file( + program_type, + section, + year, + quarter, + location, + ofa_system_admin, + ) + ) + if self.test_pia(program_type): + self.pia_files[k].append( + self.create_file( + program_type, + section, + year, + quarter, + location, + ofa_system_admin, + pia=True, + ) + ) + + def get_file_types(self, program_type): + if program_type is DataFile.ProgramType.FRA.value: + return self.fra_section_options + elif program_type is DataFile.ProgramType.SSP.value: + return ["ssp-moe"] + return ["tanf"] + + def make_get_request(self, api_client, url_param_str): + response = api_client.get(f"{self.root_url}?{url_param_str}") assert response.status_code == status.HTTP_200_OK - file_ids = [f["id"] for f in response.data] - assert len(file_ids) == 1 - assert tanf_audit_file.id in file_ids - assert tanf_non_audit_file.id not in file_ids - @pytest.mark.django_db - def test_tribal_appears_in_regular_list( - self, api_client, data_analyst, stt, tribal_file, tanf_audit_file - ): - """Test TRIBAL file appears in regular file list alongside TANF non-audit files.""" - api_client.login(username=data_analyst.username, password="test_password") + response_file_ids = [f["id"] for f in response.data] + return response_file_ids - response = api_client.get(f"{self.root_url}?stt={stt.id}&year=2024&quarter=Q1") + def assert_tanf(self, k, response_file_ids, section_options): + assert len(response_file_ids) == len(section_options) + for f in self.non_pia_files[k]: + assert f.id in response_file_ids - assert response.status_code == status.HTTP_200_OK - file_ids = [f["id"] for f in response.data] - assert len(file_ids) == 1 - assert tribal_file.id in file_ids - assert tanf_audit_file.id not in file_ids + def assert_fra(self, k, response_file_ids): + assert len(response_file_ids) == 1 + assert response_file_ids[0] in [f.id for f in self.non_pia_files[k]] + for f in self.non_pia_files[k]: + assert f.program_type is DataFile.ProgramType.FRA.value - @pytest.mark.django_db - def test_tribal_excluded_from_program_integrity_audit_list( - self, api_client, data_analyst, stt, tribal_file, tanf_audit_file - ): - """Test TRIBAL files are excluded from program-integrity-audit list (only TANF audit files appear).""" - api_client.login(username=data_analyst.username, password="test_password") + def assert_pia(self, k, response_file_ids, section_options): + assert len(response_file_ids) == len(section_options) + for f in self.pia_files[k]: + assert f.id in response_file_ids - response = api_client.get( - f"{self.root_url}?stt={stt.id}&year=2024&quarter=Q1&file_type=program-integrity-audit" - ) - - assert response.status_code == status.HTTP_200_OK - file_ids = [f["id"] for f in response.data] - assert len(file_ids) == 1 - assert tanf_audit_file.id in file_ids - assert tribal_file.id not in file_ids + @pytest.mark.django_db + def test_get_api_filtering(self, api_client, stt, tribe_stt, ofa_system_admin): + self.setup_test_data(stt, tribe_stt, ofa_system_admin) + + # check the endpoint filtering + api_client.login(username=ofa_system_admin.username, password="test_password") + + for program_type in self.program_type_options: + section_options = self.get_section_options(program_type) + location = self.get_location(program_type, stt, tribe_stt) + file_types = self.get_file_types(program_type) + + for file_type in file_types: + for year in self.year_options: + for quarter in self.quarter_options: + k = self.gen_key(program_type, year, quarter) + + non_pia_file_ids = self.make_get_request( + api_client, + f"stt={location.id}&year={year}&quarter={quarter}&file_type={file_type}", + ) + + if program_type is DataFile.ProgramType.FRA.value: + self.assert_fra(k, non_pia_file_ids) + else: + self.assert_tanf(k, non_pia_file_ids, section_options) + + if self.test_pia(program_type): + pia_file_ids = self.make_get_request( + api_client, + f"stt={location.id}&year={year}&quarter={quarter}&file_type=program-integrity-audit", + ) + self.assert_pia(k, pia_file_ids, section_options) From bf4bb5a25816879bb68b030bedc6f668e8108625 Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Wed, 28 Jan 2026 11:18:24 -0600 Subject: [PATCH 008/148] lint --- .../tdpservice/data_files/test/test_api.py | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/tdrs-backend/tdpservice/data_files/test/test_api.py b/tdrs-backend/tdpservice/data_files/test/test_api.py index 4b740d39e..db8464be9 100644 --- a/tdrs-backend/tdpservice/data_files/test/test_api.py +++ b/tdrs-backend/tdpservice/data_files/test/test_api.py @@ -739,6 +739,7 @@ class TestDataFileQuerysetFiltering: pia_files = {} def gen_key(self, program_type, year, quarter, init_array=False): + """Generate a key and optionally initialize the array for a given file.""" k = f"{program_type}{year}{quarter}" if init_array: @@ -749,14 +750,17 @@ def gen_key(self, program_type, year, quarter, init_array=False): return k def test_pia(self, program_type): + """Return true if a file should be tested for program integrity audit.""" return program_type is DataFile.ProgramType.TANF.value def get_section_options(self, program_type): + """Return the allowed sections for a given program type.""" if program_type is DataFile.ProgramType.FRA.value: return self.fra_section_options return self.tanf_section_options def get_location(self, program_type, stt, tribe_stt): + """Return the submitting location for a given program type.""" if program_type is DataFile.ProgramType.TRIBAL.value: return tribe_stt return stt @@ -771,8 +775,8 @@ def create_file( user, pia=False, ): + """Create a DataFile object in the db for the test.""" pia_part = "pia" if pia else "spl" - return DataFile.create_new_version( { "original_filename": f"{program_type}-{section}-{year}-{quarter}-{pia_part}.txt", @@ -787,6 +791,7 @@ def create_file( ) def setup_test_data(self, stt, tribe_stt, ofa_system_admin): + """Create a file for each program, section, year, quarter, pia combo.""" for program_type in self.program_type_options: section_options = self.get_section_options(program_type) location = self.get_location(program_type, stt, tribe_stt) @@ -819,37 +824,39 @@ def setup_test_data(self, stt, tribe_stt, ofa_system_admin): ) def get_file_types(self, program_type): + """Return the search api's `file_type`s for a given program.""" if program_type is DataFile.ProgramType.FRA.value: return self.fra_section_options elif program_type is DataFile.ProgramType.SSP.value: return ["ssp-moe"] return ["tanf"] - def make_get_request(self, api_client, url_param_str): + def _make_get_request(self, api_client, url_param_str): response = api_client.get(f"{self.root_url}?{url_param_str}") assert response.status_code == status.HTTP_200_OK response_file_ids = [f["id"] for f in response.data] return response_file_ids - def assert_tanf(self, k, response_file_ids, section_options): + def _assert_tanf(self, k, response_file_ids, section_options): assert len(response_file_ids) == len(section_options) for f in self.non_pia_files[k]: assert f.id in response_file_ids - def assert_fra(self, k, response_file_ids): + def _assert_fra(self, k, response_file_ids): assert len(response_file_ids) == 1 assert response_file_ids[0] in [f.id for f in self.non_pia_files[k]] for f in self.non_pia_files[k]: assert f.program_type is DataFile.ProgramType.FRA.value - def assert_pia(self, k, response_file_ids, section_options): + def _assert_pia(self, k, response_file_ids, section_options): assert len(response_file_ids) == len(section_options) for f in self.pia_files[k]: assert f.id in response_file_ids @pytest.mark.django_db def test_get_api_filtering(self, api_client, stt, tribe_stt, ofa_system_admin): + """Check that requests made to the filter endpoint contain every expected file and only files for the requested combination.""" self.setup_test_data(stt, tribe_stt, ofa_system_admin) # check the endpoint filtering @@ -865,19 +872,19 @@ def test_get_api_filtering(self, api_client, stt, tribe_stt, ofa_system_admin): for quarter in self.quarter_options: k = self.gen_key(program_type, year, quarter) - non_pia_file_ids = self.make_get_request( + non_pia_file_ids = self._make_get_request( api_client, f"stt={location.id}&year={year}&quarter={quarter}&file_type={file_type}", ) if program_type is DataFile.ProgramType.FRA.value: - self.assert_fra(k, non_pia_file_ids) + self._assert_fra(k, non_pia_file_ids) else: - self.assert_tanf(k, non_pia_file_ids, section_options) + self._assert_tanf(k, non_pia_file_ids, section_options) if self.test_pia(program_type): - pia_file_ids = self.make_get_request( + pia_file_ids = self._make_get_request( api_client, f"stt={location.id}&year={year}&quarter={quarter}&file_type=program-integrity-audit", ) - self.assert_pia(k, pia_file_ids, section_options) + self._assert_pia(k, pia_file_ids, section_options) From 6d705fe033b4e5c7b307b9206e55b371b1008f9d Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Wed, 28 Jan 2026 11:53:33 -0600 Subject: [PATCH 009/148] fix issue --- tdrs-backend/tdpservice/data_files/test/test_api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tdrs-backend/tdpservice/data_files/test/test_api.py b/tdrs-backend/tdpservice/data_files/test/test_api.py index db8464be9..669bd9aee 100644 --- a/tdrs-backend/tdpservice/data_files/test/test_api.py +++ b/tdrs-backend/tdpservice/data_files/test/test_api.py @@ -749,7 +749,7 @@ def gen_key(self, program_type, year, quarter, init_array=False): self.pia_files[k] = [] return k - def test_pia(self, program_type): + def should_test_pia(self, program_type): """Return true if a file should be tested for program integrity audit.""" return program_type is DataFile.ProgramType.TANF.value @@ -810,7 +810,7 @@ def setup_test_data(self, stt, tribe_stt, ofa_system_admin): ofa_system_admin, ) ) - if self.test_pia(program_type): + if self.should_test_pia(program_type): self.pia_files[k].append( self.create_file( program_type, @@ -882,7 +882,7 @@ def test_get_api_filtering(self, api_client, stt, tribe_stt, ofa_system_admin): else: self._assert_tanf(k, non_pia_file_ids, section_options) - if self.test_pia(program_type): + if self.should_test_pia(program_type): pia_file_ids = self._make_get_request( api_client, f"stt={location.id}&year={year}&quarter={quarter}&file_type=program-integrity-audit", From 9e2bfa2c9ab3178c879564e02eb50cdafca961b5 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Thu, 29 Jan 2026 10:50:14 -0600 Subject: [PATCH 010/148] - Increase connection age setting to allow persistent connections between requests --- tdrs-backend/tdpservice/settings/common.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tdrs-backend/tdpservice/settings/common.py b/tdrs-backend/tdpservice/settings/common.py index 392b4a7c9..acb2d2766 100644 --- a/tdrs-backend/tdpservice/settings/common.py +++ b/tdrs-backend/tdpservice/settings/common.py @@ -12,12 +12,12 @@ from django.core.exceptions import ImproperlyConfigured import sentry_sdk -from sentry_sdk.types import SamplingContext from celery.schedules import crontab from configurations import Configuration from corsheaders.defaults import default_headers from sentry_sdk.integrations.django import DjangoIntegration from sentry_sdk.integrations.logging import LoggingIntegration +from sentry_sdk.types import SamplingContext BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -35,8 +35,10 @@ def get_required_env_var_setting( return env_var + SAMPLER_FILTER_URLS = ["/prometheus/metrics"] + def traces_sampler(sampling_context: SamplingContext) -> float: # Examine provided sampling context along with anything in the # global namespace to compute the sample rate for this transaction @@ -185,6 +187,8 @@ class Common(Configuration): "PORT": os.getenv("DB_PORT"), } } + # Allow DB connections to persist for 10 min + CONN_MAX_AGE = 600 # General APPEND_SLASH = True From 140debae61515da0a950e152aeb45da5097f7c71 Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Fri, 30 Jan 2026 10:45:14 -0600 Subject: [PATCH 011/148] is to == --- tdrs-backend/tdpservice/data_files/test/test_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tdrs-backend/tdpservice/data_files/test/test_api.py b/tdrs-backend/tdpservice/data_files/test/test_api.py index 669bd9aee..bce10d38c 100644 --- a/tdrs-backend/tdpservice/data_files/test/test_api.py +++ b/tdrs-backend/tdpservice/data_files/test/test_api.py @@ -847,7 +847,7 @@ def _assert_fra(self, k, response_file_ids): assert len(response_file_ids) == 1 assert response_file_ids[0] in [f.id for f in self.non_pia_files[k]] for f in self.non_pia_files[k]: - assert f.program_type is DataFile.ProgramType.FRA.value + assert f.program_type == DataFile.ProgramType.FRA.value def _assert_pia(self, k, response_file_ids, section_options): assert len(response_file_ids) == len(section_options) From de775fe63f8c78617adc8321f6cdcdd2d88cd21c Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Fri, 6 Feb 2026 10:31:50 -0600 Subject: [PATCH 012/148] - remove all refs to axios and axios instance - remove axios from dependencies - created fetch instance that handles headers, cookies, faro, etc... - updated code throughout to use the new fetch instance and the new error handling methods - updated tests --- tdrs-frontend/package.json | 3 - tdrs-frontend/src/__mocks__/fetch-instance.js | 35 +++++ tdrs-frontend/src/actions/auth.js | 37 +++-- tdrs-frontend/src/actions/auth.test.js | 28 +++- tdrs-frontend/src/actions/fraReports.js | 92 +++++------ tdrs-frontend/src/actions/fraReports.test.js | 147 +++++++++--------- tdrs-frontend/src/actions/reports.js | 75 +++++---- tdrs-frontend/src/actions/reports.test.js | 34 ++-- tdrs-frontend/src/actions/requestAccess.js | 45 +++--- .../src/actions/requestAccess.test.js | 49 ++++-- tdrs-frontend/src/actions/sttList.js | 37 +++-- tdrs-frontend/src/actions/sttList.test.js | 35 ++++- .../src/actions/updateUserRequest.js | 51 +++--- .../src/actions/updateUserRequest.test.js | 34 +++- tdrs-frontend/src/axios-instance.js | 52 ------- .../src/components/Feedback/FeedbackForm.jsx | 63 +++----- .../components/Feedback/FeedbackForm.test.js | 112 +++++++------ .../FeedbackReports/FeedbackReports.jsx | 45 +++--- .../FeedbackReports/FeedbackReports.test.js | 86 +++++----- .../src/components/Reports/FRAReports.test.js | 62 ++++---- .../src/components/Reports/Reports.test.js | 48 ++++-- .../RequestAccessForm.test.js | 9 +- .../components/SubmissionHistory/helpers.jsx | 12 +- tdrs-frontend/src/fetch-instance.js | 123 +++++++++++++++ tdrs-frontend/src/hooks/usePollingTimer.js | 13 +- .../src/hooks/usePollingTimer.test.js | 72 +++++---- tdrs-frontend/src/index.js | 4 - tdrs-frontend/src/utils/eventLogger.js | 6 +- 28 files changed, 813 insertions(+), 596 deletions(-) create mode 100644 tdrs-frontend/src/__mocks__/fetch-instance.js delete mode 100644 tdrs-frontend/src/axios-instance.js create mode 100644 tdrs-frontend/src/fetch-instance.js diff --git a/tdrs-frontend/package.json b/tdrs-frontend/package.json index a49ae3884..c69385268 100644 --- a/tdrs-frontend/package.json +++ b/tdrs-frontend/package.json @@ -12,7 +12,6 @@ "@grafana/faro-web-tracing": "^1.18.1", "@lagunovsky/redux-react-router": "^4.3.2", "@uswds/uswds": "3.10.0", - "axios": "^1.7.7", "classnames": "^2.5.1", "detect-file-encoding-and-language": "^2.4.0", "@faker-js/faker": "^9.2.0", @@ -97,7 +96,6 @@ "collectCoverageFrom": [ "src/**/*.{js,jsx}", "!src/**/index.js", - "!src/axios-instance.js", "!src/serviceWorker.js", "!src/configureStore.js", "!src/mirage.js", @@ -117,7 +115,6 @@ "node_modules/(?!(@grafana|web-vitals)/)" ], "moduleNameMapper": { - "^axios$": "axios/dist/node/axios.cjs", "@uswds/uswds/src/js/components": "@uswds/uswds/packages/uswds-core/src/js/index.js", "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "jest-transform-stub" } diff --git a/tdrs-frontend/src/__mocks__/fetch-instance.js b/tdrs-frontend/src/__mocks__/fetch-instance.js new file mode 100644 index 000000000..b959f1ab1 --- /dev/null +++ b/tdrs-frontend/src/__mocks__/fetch-instance.js @@ -0,0 +1,35 @@ +/** + * Mocks fetch-instance HTTP requests. + */ + +export const get = jest.fn(() => + Promise.resolve({ + data: {}, + error: null, + status: 200, + ok: true, + }) +) + +export const post = jest.fn(() => + Promise.resolve({ + data: {}, + error: null, + status: 200, + ok: true, + }) +) + +export const patch = jest.fn(() => + Promise.resolve({ + data: {}, + error: null, + status: 200, + ok: true, + }) +) + +export const setCSRFToken = jest.fn() +export const getCSRFToken = jest.fn(() => null) + +export default { get, post, patch, setCSRFToken, getCSRFToken } diff --git a/tdrs-frontend/src/actions/auth.js b/tdrs-frontend/src/actions/auth.js index 6b0147c6b..a7a038907 100644 --- a/tdrs-frontend/src/actions/auth.js +++ b/tdrs-frontend/src/actions/auth.js @@ -1,4 +1,4 @@ -import axiosInstance from '../axios-instance' +import { get, setCSRFToken } from '../fetch-instance' import { logErrorToServer } from '../utils/eventLogger' export const FETCH_AUTH = 'FETCH_AUTH' @@ -41,27 +41,26 @@ export const SET_MOCK_LOGIN_STATE = 'SET_MOCK_LOGIN_STATE' export const fetchAuth = () => async (dispatch) => { dispatch({ type: FETCH_AUTH }) - try { - const URL = `${process.env.REACT_APP_BACKEND_URL}/auth_check` - const { data } = await axiosInstance.get(URL, { - withCredentials: true, - }) + const URL = `${process.env.REACT_APP_BACKEND_URL}/auth_check` + const { data, ok, error } = await get(URL) - if (data?.inactive) { - dispatch({ type: SET_DEACTIVATED }) - } else if (data?.user) { - const { user, csrf } = data - - // Work around for csrf cookie issue we encountered in production. - axiosInstance.defaults.headers.common['X-CSRFToken'] = csrf - - dispatch({ type: SET_AUTH, payload: { user } }) - } else { - dispatch({ type: CLEAR_AUTH }) - } - } catch (error) { + if (!ok) { logErrorToServer(SET_AUTH_ERROR) dispatch({ type: SET_AUTH_ERROR, payload: { error } }) + return + } + + if (data?.inactive) { + dispatch({ type: SET_DEACTIVATED }) + } else if (data?.user) { + const { user, csrf } = data + + // Work around for csrf cookie issue we encountered in production. + setCSRFToken(csrf) + + dispatch({ type: SET_AUTH, payload: { user } }) + } else { + dispatch({ type: CLEAR_AUTH }) } } diff --git a/tdrs-frontend/src/actions/auth.test.js b/tdrs-frontend/src/actions/auth.test.js index fca4f8638..257da4a77 100644 --- a/tdrs-frontend/src/actions/auth.test.js +++ b/tdrs-frontend/src/actions/auth.test.js @@ -1,4 +1,4 @@ -import axios from 'axios' +import { get } from '../fetch-instance' import { thunk } from 'redux-thunk' import configureStore from 'redux-mock-store' import { v4 as uuidv4 } from 'uuid' @@ -11,16 +11,21 @@ import { SET_DEACTIVATED, } from './auth' +jest.mock('../fetch-instance') + describe('actions/auth.js', () => { const mockStore = configureStore([thunk]) it('fetches a user and sets user info, when the user is authenticated', async () => { const mockUser = { id: uuidv4(), email: 'hi@bye.com' } - axios.get.mockImplementationOnce(() => + get.mockImplementationOnce(() => Promise.resolve({ data: { user: mockUser, }, + ok: true, + status: 200, + error: null, }) ) const store = mockStore() @@ -34,11 +39,14 @@ describe('actions/auth.js', () => { }) it('clears the auth state, if user is not authenticated', async () => { - axios.get.mockImplementationOnce(() => + get.mockImplementationOnce(() => Promise.resolve({ data: { authenticated: false, }, + ok: true, + status: 200, + error: null, }) ) const store = mockStore() @@ -51,8 +59,13 @@ describe('actions/auth.js', () => { }) it('dispatches an error to the store if the API errors', async () => { - axios.get.mockImplementationOnce(() => - Promise.reject(Error({ message: 'something went wrong' })) + get.mockImplementationOnce(() => + Promise.resolve({ + data: null, + ok: false, + status: 500, + error: new Error('something went wrong'), + }) ) const store = mockStore() @@ -64,12 +77,15 @@ describe('actions/auth.js', () => { }) it('clears the auth state and triggers dispatches if the API returns `inactive`', async () => { - axios.get.mockImplementationOnce(() => + get.mockImplementationOnce(() => Promise.resolve({ data: { authenticated: false, inactive: true, }, + ok: true, + status: 200, + error: null, }) ) const store = mockStore() diff --git a/tdrs-frontend/src/actions/fraReports.js b/tdrs-frontend/src/actions/fraReports.js index b3b7ddc64..26aabb38c 100644 --- a/tdrs-frontend/src/actions/fraReports.js +++ b/tdrs-frontend/src/actions/fraReports.js @@ -1,6 +1,5 @@ import { v4 as uuidv4 } from 'uuid' -import axios from 'axios' -import axiosInstance from '../axios-instance' +import { get, post } from '../fetch-instance' import { objectToUrlParams } from '../utils/stringUtils' const BACKEND_URL = process.env.REACT_APP_BACKEND_URL @@ -23,35 +22,31 @@ export const getFraSubmissionHistory = payload: { isLoadingSubmissionHistory: true }, }) - try { - const requestParams = { - stt: stt.id, - file_type: reportType, - year: fiscalYear, - quarter: fiscalQuarter, - } - - const response = await axios.get( - `${BACKEND_URL}/data_files/?${objectToUrlParams(requestParams)}`, - { - responseType: 'json', - } - ) + const requestParams = { + stt: stt.id, + file_type: reportType, + year: fiscalYear, + quarter: fiscalQuarter, + } + const { data, ok, error } = await get( + `${BACKEND_URL}/data_files/?${objectToUrlParams(requestParams)}` + ) + + if (ok) { dispatch({ type: SET_FRA_SUBMISSION_HISTORY, - payload: { submissionHistory: response?.data }, + payload: { submissionHistory: data }, }) - onSuccess() - } catch (error) { + } else { onError(error) - } finally { - dispatch({ - type: SET_IS_LOADING_SUBMISSION_HISTORY, - payload: { isLoadingSubmissionHistory: false }, - }) } + + dispatch({ + type: SET_IS_LOADING_SUBMISSION_HISTORY, + payload: { isLoadingSubmissionHistory: false }, + }) } export const uploadFraReport = @@ -82,25 +77,24 @@ export const uploadFraReport = formData.append(key, value) } - try { - const response = await axiosInstance.post( - `${process.env.REACT_APP_BACKEND_URL}/data_files/`, - formData, - { - headers: { 'Content-Type': 'multipart/form-data' }, - withCredentials: true, - } - ) + const { data, ok, error } = await post( + `${process.env.REACT_APP_BACKEND_URL}/data_files/`, + formData + ) - onSuccess(response?.data) - } catch (error) { - onError(error) - } finally { - dispatch({ - type: SET_IS_UPLOADING_FRA_REPORT, - payload: { isUploadingFraReport: false }, + if (ok) { + onSuccess(data) + } else { + onError({ + message: error?.message || 'Error', + response: { data }, }) } + + dispatch({ + type: SET_IS_UPLOADING_FRA_REPORT, + payload: { isUploadingFraReport: false }, + }) } export const downloadOriginalSubmission = @@ -109,12 +103,13 @@ export const downloadOriginalSubmission = try { if (!id) throw new Error('No id provided to download action') - const response = await axios.get( + const { data, ok, error } = await get( `${BACKEND_URL}/data_files/${id}/download/`, { responseType: 'blob' } ) - const data = response.data + if (!ok) throw error + const url = window.URL.createObjectURL(new Blob([data])) const link = document.createElement('a') @@ -137,12 +132,11 @@ export const downloadOriginalSubmission = } export const getFraSubmissionStatus = async (datafile_id) => { - try { - const response = await axios.get( - `${BACKEND_URL}/data_files/${datafile_id}/` - ) - return response - } catch (axiosError) { - throw axiosError + const { data, ok, error } = await get( + `${BACKEND_URL}/data_files/${datafile_id}/` + ) + if (!ok) { + throw error } + return { data, ok: true } } diff --git a/tdrs-frontend/src/actions/fraReports.test.js b/tdrs-frontend/src/actions/fraReports.test.js index b5f3f95dd..91aeceffd 100644 --- a/tdrs-frontend/src/actions/fraReports.test.js +++ b/tdrs-frontend/src/actions/fraReports.test.js @@ -1,4 +1,4 @@ -import axios from 'axios' +import { get, post } from '../fetch-instance' import { thunk } from 'redux-thunk' import configureStore from 'redux-mock-store' @@ -11,22 +11,30 @@ import { downloadOriginalSubmission, } from './fraReports' +jest.mock('../fetch-instance') + describe('actions/fraReports', () => { - jest.mock('axios') - const mockAxios = axios const mockStore = configureStore([thunk]) + beforeEach(() => { + get.mockClear() + post.mockClear() + }) + describe('getFraSubmissionHistory', () => { it('should handle success without callbacks', async () => { const store = mockStore() - mockAxios.get.mockResolvedValue({ + get.mockResolvedValue({ data: { yay: 'we did it' }, + ok: true, + status: 200, + error: null, }) await store.dispatch( getFraSubmissionHistory({ - stt: 'stt', + stt: { id: 'stt' }, reportType: 'something', fiscalQuarter: '1', fiscalYear: '2', @@ -52,23 +60,22 @@ describe('actions/fraReports', () => { isLoadingSubmissionHistory: false, }) - expect(axios.get).toHaveBeenCalledTimes(1) + expect(get).toHaveBeenCalledTimes(1) }) it('should handle fail without callbacks', async () => { const store = mockStore() - mockAxios.get.mockRejectedValue({ - message: 'Error', - response: { - status: 400, - data: { detail: 'Mock fail response' }, - }, + get.mockResolvedValue({ + data: null, + ok: false, + status: 400, + error: new Error('Mock fail response'), }) await store.dispatch( getFraSubmissionHistory({ - stt: 'stt', + stt: { id: 'stt' }, reportType: 'something', fiscalQuarter: '1', fiscalYear: '2', @@ -89,14 +96,17 @@ describe('actions/fraReports', () => { isLoadingSubmissionHistory: false, }) - expect(axios.get).toHaveBeenCalledTimes(1) + expect(get).toHaveBeenCalledTimes(1) }) it('should call onSuccess', async () => { const store = mockStore() - mockAxios.get.mockResolvedValue({ + get.mockResolvedValue({ data: { yay: 'we did it' }, + ok: true, + status: 200, + error: null, }) const onSuccess = jest.fn() @@ -105,7 +115,7 @@ describe('actions/fraReports', () => { await store.dispatch( getFraSubmissionHistory( { - stt: 'stt', + stt: { id: 'stt' }, reportType: 'something', fiscalQuarter: '1', fiscalYear: '2', @@ -134,7 +144,7 @@ describe('actions/fraReports', () => { isLoadingSubmissionHistory: false, }) - expect(axios.get).toHaveBeenCalledTimes(1) + expect(get).toHaveBeenCalledTimes(1) expect(onSuccess).toHaveBeenCalledTimes(1) expect(onError).toHaveBeenCalledTimes(0) @@ -143,12 +153,11 @@ describe('actions/fraReports', () => { it('should call onError', async () => { const store = mockStore() - mockAxios.get.mockRejectedValue({ - message: 'Error', - response: { - status: 400, - data: { detail: 'Mock fail response' }, - }, + get.mockResolvedValue({ + data: null, + ok: false, + status: 400, + error: new Error('Mock fail response'), }) const onSuccess = jest.fn() @@ -157,7 +166,7 @@ describe('actions/fraReports', () => { await store.dispatch( getFraSubmissionHistory( { - stt: 'stt', + stt: { id: 'stt' }, reportType: 'something', fiscalQuarter: '1', fiscalYear: '2', @@ -181,7 +190,7 @@ describe('actions/fraReports', () => { isLoadingSubmissionHistory: false, }) - expect(axios.get).toHaveBeenCalledTimes(1) + expect(get).toHaveBeenCalledTimes(1) expect(onSuccess).toHaveBeenCalledTimes(0) expect(onError).toHaveBeenCalledTimes(1) @@ -192,22 +201,21 @@ describe('actions/fraReports', () => { it('should handle success without callbacks', async () => { const store = mockStore() - mockAxios.post.mockResolvedValue({ + post.mockResolvedValue({ data: { yay: 'success' }, - }) - - mockAxios.get.mockResolvedValue({ - data: { yay: 'we did it' }, + ok: true, + status: 200, + error: null, }) await store.dispatch( uploadFraReport({ - stt: 'stt', + stt: { id: 'stt' }, reportType: 'something', fiscalQuarter: '1', fiscalYear: '2', - file: 'bytes', - user: 'me', + file: { name: 'bytes' }, + user: { id: 'me' }, }) ) @@ -225,33 +233,28 @@ describe('actions/fraReports', () => { isUploadingFraReport: false, }) - expect(axios.post).toHaveBeenCalledTimes(1) - expect(axios.get).toHaveBeenCalledTimes(0) + expect(post).toHaveBeenCalledTimes(1) + expect(get).toHaveBeenCalledTimes(0) }) it('should handle fail without callbacks', async () => { const store = mockStore() - mockAxios.post.mockRejectedValue({ - message: 'Error', - response: { - status: 400, - data: { detail: 'Mock fail response' }, - }, - }) - - mockAxios.get.mockResolvedValue({ - data: { yay: 'we did it' }, + post.mockResolvedValue({ + data: null, + ok: false, + status: 400, + error: new Error('Mock fail response'), }) await store.dispatch( uploadFraReport({ - stt: 'stt', + stt: { id: 'stt' }, reportType: 'something', fiscalQuarter: '1', fiscalYear: '2', - file: 'bytes', - user: 'me', + file: { name: 'bytes' }, + user: { id: 'me' }, }) ) @@ -269,19 +272,18 @@ describe('actions/fraReports', () => { isUploadingFraReport: false, }) - expect(axios.post).toHaveBeenCalledTimes(1) - expect(axios.get).toHaveBeenCalledTimes(0) + expect(post).toHaveBeenCalledTimes(1) + expect(get).toHaveBeenCalledTimes(0) }) it('should call onSuccess', async () => { const store = mockStore() - mockAxios.post.mockResolvedValue({ + post.mockResolvedValue({ data: { yay: 'success' }, - }) - - mockAxios.get.mockResolvedValue({ - data: { yay: 'we did it' }, + ok: true, + status: 200, + error: null, }) const onSuccess = jest.fn() @@ -290,12 +292,12 @@ describe('actions/fraReports', () => { await store.dispatch( uploadFraReport( { - stt: 'stt', + stt: { id: 'stt' }, reportType: 'something', fiscalQuarter: '1', fiscalYear: '2', - file: 'bytes', - user: 'me', + file: { name: 'bytes' }, + user: { id: 'me' }, }, onSuccess, onError @@ -316,8 +318,8 @@ describe('actions/fraReports', () => { isUploadingFraReport: false, }) - expect(axios.post).toHaveBeenCalledTimes(1) - expect(axios.get).toHaveBeenCalledTimes(0) + expect(post).toHaveBeenCalledTimes(1) + expect(get).toHaveBeenCalledTimes(0) expect(onSuccess).toHaveBeenCalledTimes(1) expect(onError).toHaveBeenCalledTimes(0) @@ -326,16 +328,11 @@ describe('actions/fraReports', () => { it('should call onError', async () => { const store = mockStore() - mockAxios.post.mockRejectedValue({ - message: 'Error', - response: { - status: 400, - data: { detail: 'Mock fail response' }, - }, - }) - - mockAxios.get.mockResolvedValue({ - data: { yay: 'we did it' }, + post.mockResolvedValue({ + data: null, + ok: false, + status: 400, + error: new Error('Mock fail response'), }) const onSuccess = jest.fn() @@ -344,12 +341,12 @@ describe('actions/fraReports', () => { await store.dispatch( uploadFraReport( { - stt: 'stt', + stt: { id: 'stt' }, reportType: 'something', fiscalQuarter: '1', fiscalYear: '2', - file: 'bytes', - user: 'me', + file: { name: 'bytes' }, + user: { id: 'me' }, }, onSuccess, onError @@ -370,8 +367,8 @@ describe('actions/fraReports', () => { isUploadingFraReport: false, }) - expect(axios.post).toHaveBeenCalledTimes(1) - expect(axios.get).toHaveBeenCalledTimes(0) + expect(post).toHaveBeenCalledTimes(1) + expect(get).toHaveBeenCalledTimes(0) expect(onSuccess).toHaveBeenCalledTimes(0) expect(onError).toHaveBeenCalledTimes(1) diff --git a/tdrs-frontend/src/actions/reports.js b/tdrs-frontend/src/actions/reports.js index ca4c388c4..1767b178d 100644 --- a/tdrs-frontend/src/actions/reports.js +++ b/tdrs-frontend/src/actions/reports.js @@ -1,7 +1,6 @@ import { v4 as uuidv4 } from 'uuid' -import axios from 'axios' -import axiosInstance from '../axios-instance' +import { get, post } from '../fetch-instance' import { logErrorToServer } from '../utils/eventLogger' import removeFileInputErrorState from '../utils/removeFileInputErrorState' import { quarters } from '../components/Reports/utils' @@ -58,22 +57,13 @@ export const getAvailableFileList = dispatch({ type: FETCH_FILE_LIST, }) - try { - let url = `${BACKEND_URL}/data_files/?year=${year}&stt=${stt.id}&file_type=${file_type}` - if (quarter) { - url += `&quarter=${quarter}` - } - const response = await axios.get(url, { - responseType: 'json', - }) - dispatch({ - type: SET_FILE_LIST, - payload: { - data: response?.data, - }, - }) - onSuccess() - } catch (error) { + let url = `${BACKEND_URL}/data_files/?year=${year}&stt=${stt.id}&file_type=${file_type}` + if (quarter) { + url += `&quarter=${quarter}` + } + const { data, ok, error } = await get(url) + + if (!ok) { dispatch({ type: FETCH_FILE_LIST_ERROR, payload: { @@ -84,7 +74,16 @@ export const getAvailableFileList = section, }, }) + return } + + dispatch({ + type: SET_FILE_LIST, + payload: { + data, + }, + }) + onSuccess() } export const download = @@ -93,13 +92,12 @@ export const download = try { if (!id) throw new Error('No id was provided to download action.') dispatch({ type: START_FILE_DOWNLOAD }) - const response = await axios.get( + const { data, ok, error } = await get( `${BACKEND_URL}/data_files/${id}/download/`, - { - responseType: 'blob', - } + { responseType: 'blob' } ) - const data = response.data + + if (!ok) throw error // Create a link and associate it with the blob returned from the file // download - this allows us to trigger the file download dialog without @@ -203,18 +201,20 @@ export const submit = for (const [key, value] of Object.entries(dataFile)) { formData.append(key, value) } - return axiosInstance.post( - `${process.env.REACT_APP_BACKEND_URL}/data_files/`, - formData, - { - headers: { 'Content-Type': 'multipart/form-data' }, - withCredentials: true, - } - ) + return post(`${process.env.REACT_APP_BACKEND_URL}/data_files/`, formData) }) return Promise.all(submissionRequests) .then((responses) => { + // Check if any responses have errors + const failedResponse = responses.find((r) => !r.ok) + if (failedResponse) { + throw { + message: failedResponse.error?.message || 'Error', + response: { data: failedResponse.data }, + } + } + setLocalAlertState({ active: true, type: 'success', @@ -279,12 +279,11 @@ export const setStt = (stt) => (dispatch) => { } export const getTanfSubmissionStatus = async (datafile_id) => { - try { - const response = await axios.get( - `${BACKEND_URL}/data_files/${datafile_id}/` - ) - return response - } catch (axiosError) { - throw axiosError + const { data, ok, error } = await get( + `${BACKEND_URL}/data_files/${datafile_id}/` + ) + if (!ok) { + throw error } + return { data, ok: true } } diff --git a/tdrs-frontend/src/actions/reports.test.js b/tdrs-frontend/src/actions/reports.test.js index fdf7527ff..f8438f8ed 100644 --- a/tdrs-frontend/src/actions/reports.test.js +++ b/tdrs-frontend/src/actions/reports.test.js @@ -1,4 +1,4 @@ -import axios from 'axios' +import { get, post } from '../fetch-instance' import { thunk } from 'redux-thunk' import configureStore from 'redux-mock-store' import { v4 as uuidv4 } from 'uuid' @@ -19,6 +19,8 @@ import { SET_FILE_SUBMITTED, } from './reports' +jest.mock('../fetch-instance') + describe('actions/reports', () => { const mockStore = configureStore([thunk]) @@ -61,9 +63,12 @@ describe('actions/reports', () => { it('should dispatch OPEN_FILE_DIALOG when a file has been successfully downloaded', async () => { window.URL.createObjectURL = jest.fn(() => null) - axios.get.mockImplementationOnce(() => + get.mockImplementationOnce(() => Promise.resolve({ data: 'Some text', + ok: true, + status: 200, + error: null, }) ) const store = mockStore() @@ -86,7 +91,7 @@ describe('actions/reports', () => { }) it('should dispatch SET_FILE_LIST', async () => { - axios.get.mockImplementationOnce(() => + get.mockImplementationOnce(() => Promise.resolve({ data: [ { @@ -100,6 +105,9 @@ describe('actions/reports', () => { uuid: uuidv4(), }, ], + ok: true, + status: 200, + error: null, }) ) const store = mockStore() @@ -120,7 +128,7 @@ describe('actions/reports', () => { it('should dispatch SET_FILE_SUBMITTED', async () => { const uuid = uuidv4() - axios.post.mockImplementationOnce(() => + post.mockImplementationOnce(() => Promise.resolve({ data: { extension: 'txt', @@ -131,6 +139,9 @@ describe('actions/reports', () => { slug: uuid, year: 2021, }, + ok: true, + status: 200, + error: null, }) ) const store = mockStore() @@ -157,7 +168,7 @@ describe('actions/reports', () => { const actions = store.getActions() try { - expect(axios.post).toHaveBeenCalledTimes(1) + expect(post).toHaveBeenCalledTimes(1) expect(actions[0].type).toBe(SET_FILE_SUBMITTED) } catch (err) { throw actions[0].payload.error @@ -183,13 +194,12 @@ describe('actions/reports', () => { 'should set local alert state on submission failure', async (data, msg) => { const uuid = uuidv4() - axios.post.mockImplementationOnce(() => - Promise.reject({ + post.mockImplementationOnce(() => + Promise.resolve({ + data, + ok: false, status: 400, - message: 'Error', - response: { - data, - }, + error: new Error('Error'), }) ) const store = mockStore() @@ -216,7 +226,7 @@ describe('actions/reports', () => { }) ) - expect(axios.post).toHaveBeenCalledTimes(1) + expect(post).toHaveBeenCalledTimes(1) expect(setLocalAlertState).toHaveBeenCalledWith({ active: true, message: msg || 'Error: Something went wrong', diff --git a/tdrs-frontend/src/actions/requestAccess.js b/tdrs-frontend/src/actions/requestAccess.js index 01a4b86ab..9f0eac5fd 100644 --- a/tdrs-frontend/src/actions/requestAccess.js +++ b/tdrs-frontend/src/actions/requestAccess.js @@ -1,5 +1,5 @@ import { SET_AUTH } from './auth' -import axios from 'axios' +import { patch } from '../fetch-instance' import { logErrorToServer } from '../utils/eventLogger' export const PATCH_REQUEST_ACCESS = 'PATCH_REQUEST_ACCESS' @@ -11,30 +11,29 @@ export const requestAccess = ({ firstName, lastName, stt, regions, hasFRAAccess }) => async (dispatch) => { dispatch({ type: PATCH_REQUEST_ACCESS }) - try { - const URL = `${process.env.REACT_APP_BACKEND_URL}/users/request_access/` - const user = { - first_name: firstName, - last_name: lastName, - stt: stt?.id, - regions: regions ? [...regions] : [], - has_fra_access: hasFRAAccess, - } - const { data } = await axios.patch(URL, user, { - withCredentials: true, - }) + const URL = `${process.env.REACT_APP_BACKEND_URL}/users/request_access/` + const user = { + first_name: firstName, + last_name: lastName, + stt: stt?.id, + regions: regions ? [...regions] : [], + has_fra_access: hasFRAAccess, + } + const { data, ok, error } = await patch(URL, user) - if (data) { - dispatch({ type: SET_REQUEST_ACCESS }) - dispatch({ - type: SET_AUTH, - payload: { user: data }, - }) - } else { - dispatch({ type: CLEAR_REQUEST_ACCESS }) - } - } catch (error) { + if (!ok) { logErrorToServer(SET_REQUEST_ACCESS_ERROR) dispatch({ type: SET_REQUEST_ACCESS_ERROR, payload: { error } }) + return + } + + if (data) { + dispatch({ type: SET_REQUEST_ACCESS }) + dispatch({ + type: SET_AUTH, + payload: { user: data }, + }) + } else { + dispatch({ type: CLEAR_REQUEST_ACCESS }) } } diff --git a/tdrs-frontend/src/actions/requestAccess.test.js b/tdrs-frontend/src/actions/requestAccess.test.js index 137e442a5..0be8450a1 100644 --- a/tdrs-frontend/src/actions/requestAccess.test.js +++ b/tdrs-frontend/src/actions/requestAccess.test.js @@ -1,4 +1,4 @@ -import axios from 'axios' +import { patch } from '../fetch-instance' import { thunk } from 'redux-thunk' import configureStore from 'redux-mock-store' @@ -10,22 +10,29 @@ import { CLEAR_REQUEST_ACCESS, } from './requestAccess' +jest.mock('../fetch-instance') + describe('actions/requestAccess.js', () => { const mockStore = configureStore([thunk]) it('sends a PATCH request when requestAccess is called', async () => { - axios.patch = jest.fn().mockResolvedValue({ - data: { - first_name: 'harry', - last_name: 'potter', - stt: { - code: 'AK', - id: 2, - name: 'Alaska', - type: 'state', + patch.mockImplementationOnce(() => + Promise.resolve({ + data: { + first_name: 'harry', + last_name: 'potter', + stt: { + code: 'AK', + id: 2, + name: 'Alaska', + type: 'state', + }, }, - }, - }) + ok: true, + status: 200, + error: null, + }) + ) const profileInfo = { firstName: 'harry', lastName: 'potter', @@ -42,7 +49,14 @@ describe('actions/requestAccess.js', () => { }) it('clears the request access state if there is no data returned from the API', async () => { - axios.patch = jest.fn().mockResolvedValue({}) + patch.mockImplementationOnce(() => + Promise.resolve({ + data: null, + ok: true, + status: 200, + error: null, + }) + ) const profileInfo = { firstName: 'harry', lastName: 'potter', @@ -59,7 +73,14 @@ describe('actions/requestAccess.js', () => { }) it('dispatches an error to the store if the API errors', async () => { - axios.patch = jest.fn().mockRejectedValue(new Error('threw an error')) + patch.mockImplementationOnce(() => + Promise.resolve({ + data: null, + ok: false, + status: 500, + error: new Error('threw an error'), + }) + ) const profileInfo = { firstName: 'harry', lastName: 'potter', diff --git a/tdrs-frontend/src/actions/sttList.js b/tdrs-frontend/src/actions/sttList.js index dbf74aa99..84be2a850 100644 --- a/tdrs-frontend/src/actions/sttList.js +++ b/tdrs-frontend/src/actions/sttList.js @@ -1,4 +1,4 @@ -import axios from 'axios' +import { get } from '../fetch-instance' import { logErrorToServer } from '../utils/eventLogger' export const FETCH_STTS = 'FETCH_STTS' @@ -32,26 +32,25 @@ export const CLEAR_STTS = 'CLEAR_STTS' */ export const fetchSttList = () => async (dispatch) => { dispatch({ type: FETCH_STTS }) - try { - const URL = `${process.env.REACT_APP_BACKEND_URL}/stts/alpha` - const { data } = await axios.get(URL, { - withCredentials: true, - }) + const URL = `${process.env.REACT_APP_BACKEND_URL}/stts/alpha` + const { data, ok, error } = await get(URL) - if (data) { - // shouldn't this logic be done by the backend serializer? - data.forEach((item, i) => { - if (item.name === 'Federal Government') { - data.splice(i, 1) - data.unshift(item) - } - }) - dispatch({ type: SET_STTS, payload: { data } }) - } else { - dispatch({ type: CLEAR_STTS }) - } - } catch (error) { + if (!ok) { logErrorToServer(SET_STTS_ERROR) dispatch({ type: SET_STTS_ERROR, payload: { error } }) + return + } + + if (data) { + // shouldn't this logic be done by the backend serializer? + data.forEach((item, i) => { + if (item.name === 'Federal Government') { + data.splice(i, 1) + data.unshift(item) + } + }) + dispatch({ type: SET_STTS, payload: { data } }) + } else { + dispatch({ type: CLEAR_STTS }) } } diff --git a/tdrs-frontend/src/actions/sttList.test.js b/tdrs-frontend/src/actions/sttList.test.js index 3d053917f..7eb5aa54b 100644 --- a/tdrs-frontend/src/actions/sttList.test.js +++ b/tdrs-frontend/src/actions/sttList.test.js @@ -1,4 +1,4 @@ -import axios from 'axios' +import { get } from '../fetch-instance' import { thunk } from 'redux-thunk' import configureStore from 'redux-mock-store' @@ -10,13 +10,18 @@ import { CLEAR_STTS, } from './sttList' +jest.mock('../fetch-instance') + describe('actions/stts.js', () => { const mockStore = configureStore([thunk]) it('fetches a list of stts, when the user is authenticated', async () => { - axios.get.mockImplementationOnce(() => + get.mockImplementationOnce(() => Promise.resolve({ data: [{ id: 1, type: 'state', code: 'AL', name: 'Alabama' }], + ok: true, + status: 200, + error: null, }) ) const store = mockStore() @@ -32,7 +37,7 @@ describe('actions/stts.js', () => { }) it('fetches a list of stts and puts "Federal Government" as the first option if it exists, when the user is authenticated', async () => { - axios.get.mockImplementationOnce(() => + get.mockImplementationOnce(() => Promise.resolve({ data: [ { id: 1, type: 'state', code: 'AL', name: 'Alabama' }, @@ -43,6 +48,9 @@ describe('actions/stts.js', () => { name: 'Federal Government', }, ], + ok: true, + status: 200, + error: null, }) ) const store = mockStore() @@ -64,7 +72,14 @@ describe('actions/stts.js', () => { }) it('clears the stt state, if user is not authenticated', async () => { - axios.get.mockImplementationOnce(() => Promise.resolve({ test: {} })) + get.mockImplementationOnce(() => + Promise.resolve({ + data: null, + ok: true, + status: 200, + error: null, + }) + ) const store = mockStore() await store.dispatch(fetchSttList()) @@ -75,8 +90,14 @@ describe('actions/stts.js', () => { }) it('dispatches an error to the store if the API errors', async () => { - axios.get.mockImplementationOnce(() => - Promise.reject(Error({ message: 'something went wrong' })) + const mockError = new Error('something went wrong') + get.mockImplementationOnce(() => + Promise.resolve({ + data: null, + ok: false, + status: 500, + error: mockError, + }) ) const store = mockStore() @@ -86,7 +107,7 @@ describe('actions/stts.js', () => { expect(actions[0].type).toBe(FETCH_STTS) expect(actions[1].type).toBe(SET_STTS_ERROR) expect(actions[1].payload).toStrictEqual({ - error: Error({ message: 'something went wrong' }), + error: mockError, }) }) }) diff --git a/tdrs-frontend/src/actions/updateUserRequest.js b/tdrs-frontend/src/actions/updateUserRequest.js index d36710450..dc85a46a0 100644 --- a/tdrs-frontend/src/actions/updateUserRequest.js +++ b/tdrs-frontend/src/actions/updateUserRequest.js @@ -1,4 +1,4 @@ -import axios from 'axios' +import { patch } from '../fetch-instance' import { logErrorToServer } from '../utils/eventLogger' import { SET_AUTH } from './auth' @@ -11,33 +11,32 @@ export const updateUserRequest = ({ firstName, lastName, stt, regions, hasFRAAccess }) => async (dispatch) => { dispatch({ type: PATCH_REQUEST_USER_UPDATE }) - try { - const URL = `${process.env.REACT_APP_BACKEND_URL}/users/update_profile/` - const user = { - first_name: firstName, - last_name: lastName, - stt: stt?.id, - has_fra_access: hasFRAAccess, - create_change_requests: true, - // backend requires region value if region key is present. - // this guards it so the key isn't present if value isn't - ...(Array.isArray(regions) && regions.length > 0 ? { regions } : {}), - } - const { data } = await axios.patch(URL, user, { - withCredentials: true, - }) + const URL = `${process.env.REACT_APP_BACKEND_URL}/users/update_profile/` + const user = { + first_name: firstName, + last_name: lastName, + stt: stt?.id, + has_fra_access: hasFRAAccess, + create_change_requests: true, + // backend requires region value if region key is present. + // this guards it so the key isn't present if value isn't + ...(Array.isArray(regions) && regions.length > 0 ? { regions } : {}), + } + const { data, ok, error } = await patch(URL, user) - if (data) { - dispatch({ type: SET_REQUEST_USER_UPDATE }) - dispatch({ - type: SET_AUTH, - payload: { user: data }, - }) - } else { - dispatch({ type: CLEAR_REQUEST_USER_UPDATE }) - } - } catch (error) { + if (!ok) { logErrorToServer(SET_REQUEST_USER_UPDATE_ERROR) dispatch({ type: SET_REQUEST_USER_UPDATE_ERROR, payload: { error } }) + return + } + + if (data) { + dispatch({ type: SET_REQUEST_USER_UPDATE }) + dispatch({ + type: SET_AUTH, + payload: { user: data }, + }) + } else { + dispatch({ type: CLEAR_REQUEST_USER_UPDATE }) } } diff --git a/tdrs-frontend/src/actions/updateUserRequest.test.js b/tdrs-frontend/src/actions/updateUserRequest.test.js index a5f017be5..41381335a 100644 --- a/tdrs-frontend/src/actions/updateUserRequest.test.js +++ b/tdrs-frontend/src/actions/updateUserRequest.test.js @@ -1,4 +1,4 @@ -import axios from 'axios' +import { patch } from '../fetch-instance' import configureStore from 'redux-mock-store' import { thunk } from 'redux-thunk' import { SET_AUTH } from './auth' @@ -10,6 +10,8 @@ import { updateUserRequest, } from './updateUserRequest' +jest.mock('../fetch-instance') + const middlewares = [thunk] const mockStore = configureStore(middlewares) @@ -32,7 +34,12 @@ describe('updateUserRequest', () => { has_fra_access: true, pending_requests: 1, } - axios.patch.mockResolvedValue({ data: apiUserResponse }) + patch.mockResolvedValue({ + data: apiUserResponse, + ok: true, + status: 200, + error: null, + }) await store.dispatch(updateUserRequest(mockInput)) @@ -61,7 +68,12 @@ describe('updateUserRequest', () => { has_fra_access: false, pending_requests: 0, } - axios.patch.mockResolvedValue({ data: apiUserResponse }) + patch.mockResolvedValue({ + data: apiUserResponse, + ok: true, + status: 200, + error: null, + }) await store.dispatch(updateUserRequest(mockInput)) @@ -83,7 +95,12 @@ describe('updateUserRequest', () => { hasFRAAccess: false, } - axios.patch.mockRejectedValue(new Error('threw and error')) + patch.mockResolvedValue({ + data: null, + ok: false, + status: 500, + error: new Error('threw an error'), + }) await store.dispatch(updateUserRequest(mockInput)) @@ -92,7 +109,7 @@ describe('updateUserRequest', () => { expect(actions[1].type).toBe(SET_REQUEST_USER_UPDATE_ERROR) }) - it('dispatches an error to the store if the API errors', async () => { + it('clears the state if the API returns no data', async () => { const store = mockStore() const mockInput = { @@ -102,7 +119,12 @@ describe('updateUserRequest', () => { hasFRAAccess: false, } - axios.patch.mockResolvedValue({}) + patch.mockResolvedValue({ + data: null, + ok: true, + status: 200, + error: null, + }) await store.dispatch(updateUserRequest(mockInput)) diff --git a/tdrs-frontend/src/axios-instance.js b/tdrs-frontend/src/axios-instance.js deleted file mode 100644 index 3a7dfdac8..000000000 --- a/tdrs-frontend/src/axios-instance.js +++ /dev/null @@ -1,52 +0,0 @@ -import axios from 'axios' -import { faro } from '@grafana/faro-react' - -// Need a custom instance of axios so we can set the csrf keys on auth_check -// Work around for csrf cookie issue we encountered in production. -// It may still be possible to do this with a cookie, and something on the -// frontend (most likely) is misconfigured. the configuration has alluded -// us thus far, and this implementation is functionally equivalent to -// using cookies. - -const axiosInstance = axios.create() - -// Add an interceptor to include trace context in outgoing requests for custom instance and default instance -axios.interceptors.request.use((config) => { - try { - // Add service name to the request - config.headers = config.headers || {} - config.headers['x-service-name'] = 'tdp-frontend' - // Add trace context if Faro is initialized - if (faro && faro.api) { - const traceContext = faro.api.getTraceContext() - if (traceContext) { - // Add W3C trace context headers - Object.assign(config.headers, traceContext) - } - } - } catch (e) { - console.error('Failed to add trace context', e) - } - return config -}) - -axiosInstance.interceptors.request.use((config) => { - try { - // Add service name to the request - config.headers = config.headers || {} - config.headers['x-service-name'] = 'tdp-frontend' - // Add trace context if Faro is initialized - if (faro && faro.api) { - const traceContext = faro.api.getTraceContext() - if (traceContext) { - // Add W3C trace context headers - Object.assign(config.headers, traceContext) - } - } - } catch (e) { - console.error('Failed to add trace context', e) - } - return config -}) - -export default axiosInstance diff --git a/tdrs-frontend/src/components/Feedback/FeedbackForm.jsx b/tdrs-frontend/src/components/Feedback/FeedbackForm.jsx index f4d129aef..5aea1c4dd 100644 --- a/tdrs-frontend/src/components/Feedback/FeedbackForm.jsx +++ b/tdrs-frontend/src/components/Feedback/FeedbackForm.jsx @@ -1,6 +1,6 @@ /* eslint-disable no-unused-vars */ import React, { useCallback, useEffect, useRef, useState } from 'react' -import axiosInstance from '../../axios-instance' +import { post, patch } from '../../fetch-instance' import classNames from 'classnames' import FeedbackRadioSelectGroup from './FeedbackRadioSelectGroup' import { useSelector } from 'react-redux' @@ -50,22 +50,12 @@ const FeedbackForm = ({ } const postFeedback = useCallback(async (payload) => { - return axiosInstance.post(`${BACKEND_URL}/feedback/`, payload, { - headers: { 'Content-Type': 'application/json' }, - withCredentials: true, - }) + return post(`${BACKEND_URL}/feedback/`, payload) }, []) const updateFeedback = useCallback( async (payload) => { - return axiosInstance.patch( - `${BACKEND_URL}/feedback/${feedbackID}/`, - payload, - { - headers: { 'Content-Type': 'application/json' }, - withCredentials: true, - } - ) + return patch(`${BACKEND_URL}/feedback/${feedbackID}/`, payload) }, [feedbackID] ) @@ -118,19 +108,17 @@ const FeedbackForm = ({ const submitFeedback = useCallback( async (payload) => { - try { - const response = feedbackID - ? await updateFeedback(payload) - : await postFeedback(payload) + const response = feedbackID + ? await updateFeedback(payload) + : await postFeedback(payload) - if (response.status === 200 || response.status === 201) { - setFeedbackID(response.data.id) - } - return response - } catch (error) { - console.error('Error submitting feedback:', error) + if (response.ok) { + setFeedbackID(response.data.id) + } else { + console.error('Error submitting feedback:', response.error) onRequestError?.() } + return response }, [ feedbackID, @@ -151,28 +139,15 @@ const FeedbackForm = ({ return } - try { - const payload = constructPayload() - const response = await submitFeedback(payload) + const payload = constructPayload() + const response = await submitFeedback(payload) - if (response.status === 200 || response.status === 201) { - onFeedbackSubmit() - onRequestSuccess?.() - resetStatesOnceSubmitted() - } else { - console.error('Unexpected response: ', response) - onRequestError?.() - } - } catch (error) { - const status = error?.response?.status - if (status === 400) { - console.error('Error submitting feedback: ', error.response) - } else { - console.error( - 'An unexpected error occurred. Please try again later.', - error - ) - } + if (response.ok) { + onFeedbackSubmit() + onRequestSuccess?.() + resetStatesOnceSubmitted() + } else { + console.error('Unexpected response: ', response) onRequestError?.() } }, [ diff --git a/tdrs-frontend/src/components/Feedback/FeedbackForm.test.js b/tdrs-frontend/src/components/Feedback/FeedbackForm.test.js index 2b9fb066b..ce7879900 100644 --- a/tdrs-frontend/src/components/Feedback/FeedbackForm.test.js +++ b/tdrs-frontend/src/components/Feedback/FeedbackForm.test.js @@ -1,10 +1,10 @@ import React from 'react' import * as reactRedux from 'react-redux' -import { render, screen, fireEvent, waitFor } from '@testing-library/react' +import { render, screen, fireEvent, waitFor, act } from '@testing-library/react' import '@testing-library/jest-dom' import FeedbackForm from './FeedbackForm' import { useSelector } from 'react-redux' -import axiosInstance from '../../axios-instance' +import { post, patch } from '../../fetch-instance' // Mock the Redux selector jest.mock('react-redux', () => ({ @@ -12,7 +12,7 @@ jest.mock('react-redux', () => ({ useSelector: jest.fn(), })) -jest.mock('../../axios-instance') +jest.mock('../../fetch-instance') jest.mock('../../assets/feedback/very-dissatisfied-feedback.svg', () => { const React = require('react') @@ -78,8 +78,8 @@ describe('Feedback Form tests', () => { const mockOnFeedbackSubmit = jest.fn() beforeEach(() => { - axiosInstance.post.mockClear() - axiosInstance.patch.mockClear() + post.mockClear() + patch.mockClear() mockOnFeedbackSubmit.mockClear() // Default to authenticated for most tests reactRedux.useSelector.mockImplementation(() => true) @@ -197,7 +197,7 @@ describe('Feedback Form tests', () => { ).toBeInTheDocument() }) - expect(axiosInstance.post).not.toHaveBeenCalledWith( + expect(post).not.toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ rating: expect.any(Number) }) ) @@ -221,10 +221,12 @@ describe('Feedback Form tests', () => { ).toBeInTheDocument() }) - expect(axiosInstance.post).not.toHaveBeenCalled() + expect(post).not.toHaveBeenCalled() }) it('clears error message after rating is selected', async () => { + post.mockResolvedValue({ ok: true, status: 201, data: { id: 1 }, error: null }) + render( { }) it('submits feedback with rating, message, and anonymous flag', async () => { - axiosInstance.post.mockResolvedValue({ status: 201, data: { id: 1 } }) - axiosInstance.patch.mockResolvedValue({ status: 200, data: { id: 1 } }) + post.mockResolvedValue({ ok: true, status: 201, data: { id: 1 }, error: null }) + patch.mockResolvedValue({ ok: true, status: 200, data: { id: 1 }, error: null }) render( { const ratingInput = screen.getByTestId('feedback-radio-input-4') fireEvent.click(ratingInput) + // Wait for rating click's async post to complete and feedbackID to be set + await waitFor(() => expect(post).toHaveBeenCalled()) + // Simulate entering feedback message fireEvent.change(screen.getByTestId('feedback-message-input'), { target: { value: 'Great!! test feedback' }, @@ -274,7 +279,7 @@ describe('Feedback Form tests', () => { fireEvent.click(screen.getByRole('button', { name: /send feedback/i })) await waitFor(() => { - expect(axiosInstance.post).toHaveBeenCalledWith( + expect(patch).toHaveBeenCalledWith( expect.stringContaining('/feedback/'), { rating: 4, @@ -283,10 +288,6 @@ describe('Feedback Form tests', () => { feedback_type: 'general_feedback', page_url: 'http://localhost/', anonymous: true, - }, - { - headers: { 'Content-Type': 'application/json' }, - withCredentials: true, } ) }) @@ -294,8 +295,8 @@ describe('Feedback Form tests', () => { }) it('submits with rating and no feedback message', async () => { - axiosInstance.post.mockResolvedValue({ status: 201, data: { id: 1 } }) - axiosInstance.patch.mockResolvedValue({ status: 200, data: { id: 1 } }) + post.mockResolvedValue({ ok: true, status: 201, data: { id: 1 }, error: null }) + patch.mockResolvedValue({ ok: true, status: 200, data: { id: 1 }, error: null }) render( { /> ) - fireEvent.click(screen.getByTestId('feedback-radio-input-3')) + await act(async () => { + fireEvent.click(screen.getByTestId('feedback-radio-input-3')) + }) + expect(post).toHaveBeenCalled() + fireEvent.click(screen.getByRole('button', { name: /send feedback/i })) await waitFor(() => - expect(axiosInstance.post).toHaveBeenCalledWith( + expect(patch).toHaveBeenCalledWith( expect.stringContaining('/feedback/'), { component: 'general-website', @@ -317,18 +322,14 @@ describe('Feedback Form tests', () => { rating: 3, feedback: '', anonymous: false, - }, - { - headers: { 'Content-Type': 'application/json' }, - withCredentials: true, } ) ) }) it('submits form using Enter key on submit button', async () => { - axiosInstance.post.mockResolvedValue({ status: 201, data: { id: 1 } }) - axiosInstance.patch.mockResolvedValue({ status: 200, data: { id: 1 } }) + post.mockResolvedValue({ ok: true, status: 201, data: { id: 1 }, error: null }) + patch.mockResolvedValue({ ok: true, status: 200, data: { id: 1 }, error: null }) render( { fireEvent.keyDown(button, { key: 'Enter', code: 'Enter' }) await waitFor(() => { - expect(axiosInstance.post).toHaveBeenCalled() + expect(post).toHaveBeenCalled() expect(mockOnFeedbackSubmit).toHaveBeenCalled() }) }) it('submits form with Cmd/Ctrl + Enter from inside textarea', async () => { - axiosInstance.post.mockResolvedValue({ status: 201, data: { id: 1 } }) - axiosInstance.patch.mockResolvedValue({ status: 200, data: { id: 1 } }) + post.mockResolvedValue({ ok: true, status: 201, data: { id: 1 }, error: null }) + patch.mockResolvedValue({ ok: true, status: 200, data: { id: 1 }, error: null }) render( { const textarea = screen.getByTestId('feedback-message-input') textarea.focus() fireEvent.change(textarea, { target: { value: 'Quick feedback' } }) - fireEvent.click(screen.getByTestId('feedback-radio-input-3')) + + await act(async () => { + fireEvent.click(screen.getByTestId('feedback-radio-input-3')) + }) + expect(post).toHaveBeenCalled() fireEvent.keyDown(window, { key: 'Enter', metaKey: true }) // Mac - // OR use ctrlKey: true for Windows await waitFor(() => { - expect(axiosInstance.post).toHaveBeenCalled() + expect(patch).toHaveBeenCalled() expect(mockOnFeedbackSubmit).toHaveBeenCalled() }) }) it('resets form fields after successful submission', async () => { - axiosInstance.post.mockResolvedValue({ status: 201, data: { id: 1 } }) - axiosInstance.patch.mockResolvedValue({ status: 200, data: { id: 1 } }) + post.mockResolvedValue({ ok: true, status: 201, data: { id: 1 }, error: null }) + patch.mockResolvedValue({ ok: true, status: 200, data: { id: 1 }, error: null }) render( { }) it('does not reset form on failed feedback submission', async () => { - axiosInstance.post.mockResolvedValueOnce({ status: 500 }) + const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}) + post.mockResolvedValue({ ok: false, status: 500, data: null, error: new Error('Server error') }) render( { ) fireEvent.click(screen.getByTestId('feedback-radio-input-2')) + + // Wait for rating click's async post to complete + await waitFor(() => expect(post).toHaveBeenCalled()) + fireEvent.change(screen.getByTestId('feedback-message-input'), { target: { value: 'Should not reset' }, }) @@ -424,12 +433,16 @@ describe('Feedback Form tests', () => { 'Should not reset' ) ) + consoleSpy.mockRestore() }) - it('logs error message when API returns 400', async () => { + it('logs error message when API returns error', async () => { const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}) - axiosInstance.post.mockRejectedValueOnce({ - response: { status: 400 }, + post.mockResolvedValue({ + ok: false, + status: 400, + data: null, + error: new Error('Bad request'), }) render( @@ -448,9 +461,9 @@ describe('Feedback Form tests', () => { consoleSpy.mockRestore() }) - it('logs an error if feedbackPost returns non-200 status', async () => { + it('logs an error if feedbackPost returns non-ok status', async () => { const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}) - axiosInstance.post.mockResolvedValueOnce({ status: 500 }) + post.mockResolvedValue({ ok: false, status: 500, data: null, error: new Error('Server error') }) render( @@ -468,9 +481,9 @@ describe('Feedback Form tests', () => { consoleSpy.mockRestore() }) - it('logs fallback error if API throws unexpected error', async () => { + it('logs error if API returns error', async () => { const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}) - axiosInstance.post.mockRejectedValueOnce(new Error('Network down')) + post.mockResolvedValue({ ok: false, status: 500, data: null, error: new Error('Network down') }) render( @@ -481,7 +494,7 @@ describe('Feedback Form tests', () => { await waitFor(() => expect(consoleSpy).toHaveBeenCalledWith( - 'An unexpected error occurred. Please try again later.', + 'Error submitting feedback:', expect.any(Object) ) ) @@ -511,8 +524,8 @@ describe('Feedback Form tests', () => { }) it('submits minimal fields when isGeneralFeedback is false', async () => { - axiosInstance.post.mockResolvedValue({ status: 201, data: { id: 1 } }) - axiosInstance.patch.mockResolvedValue({ status: 200, data: { id: 1 } }) + post.mockResolvedValue({ ok: true, status: 201, data: { id: 1 }, error: null }) + patch.mockResolvedValue({ ok: true, status: 200, data: { id: 1 }, error: null }) render( { /> ) - // Provide required rating - fireEvent.click(screen.getByTestId('feedback-radio-input-5')) + // Provide required rating and wait for async post to complete + await act(async () => { + fireEvent.click(screen.getByTestId('feedback-radio-input-5')) + }) + expect(post).toHaveBeenCalled() // Skip comment input (allowed) fireEvent.click(screen.getByRole('button', { name: /send feedback/i })) await waitFor(() => { - expect(axiosInstance.post).toHaveBeenCalledWith( + expect(patch).toHaveBeenCalledWith( expect.any(String), { attachments: [], @@ -540,10 +556,6 @@ describe('Feedback Form tests', () => { rating: 5, feedback: '', // comment left blank anonymous: false, // anonymous checkbox hidden - }, - { - headers: { 'Content-Type': 'application/json' }, - withCredentials: true, } ) expect(mockOnFeedbackSubmit).toHaveBeenCalled() diff --git a/tdrs-frontend/src/components/FeedbackReports/FeedbackReports.jsx b/tdrs-frontend/src/components/FeedbackReports/FeedbackReports.jsx index 17baaa1bf..58be52bcd 100644 --- a/tdrs-frontend/src/components/FeedbackReports/FeedbackReports.jsx +++ b/tdrs-frontend/src/components/FeedbackReports/FeedbackReports.jsx @@ -1,5 +1,5 @@ import { useState, useEffect, useRef, useCallback } from 'react' -import axiosInstance from '../../axios-instance' +import { get, post } from '../../fetch-instance' import createFileInputErrorState from '../../utils/createFileInputErrorState' import { fileInput } from '@uswds/uswds/src/js/components' import FeedbackReportsUpload from './FeedbackReportsUpload' @@ -33,22 +33,21 @@ function FeedbackReports() { */ const fetchUploadHistory = useCallback(async () => { setHistoryLoading(true) - try { - const response = await axiosInstance.get( - `${process.env.REACT_APP_BACKEND_URL}/reports/report-sources/`, - { withCredentials: true } - ) - setUploadHistory(response.data.results) - } catch (error) { + const { data, ok, error } = await get( + `${process.env.REACT_APP_BACKEND_URL}/reports/report-sources/` + ) + + if (ok) { + setUploadHistory(data.results) + } else { console.error('Failed to fetch upload history:', error) setAlert({ active: true, type: 'error', message: 'Failed to load upload history. Please refresh the page.', }) - } finally { - setHistoryLoading(false) } + setHistoryLoading(false) }, []) // Initialize USWDS file input component and fetch upload history on mount @@ -120,16 +119,12 @@ function FeedbackReports() { const formData = new FormData() formData.append('file', selectedFile) - try { - await axiosInstance.post( - `${process.env.REACT_APP_BACKEND_URL}/reports/report-sources/`, - formData, - { - headers: { 'Content-Type': 'multipart/form-data' }, - withCredentials: true, - } - ) + const { data, ok, error } = await post( + `${process.env.REACT_APP_BACKEND_URL}/reports/report-sources/`, + formData + ) + if (ok) { setAlert({ active: true, type: 'success', @@ -143,11 +138,11 @@ function FeedbackReports() { // Refresh upload history fetchUploadHistory() - } catch (error) { + } else { const errorMessage = - error.response?.data?.file?.[0] || - error.response?.data?.detail || - error.response?.data?.message || + data?.file?.[0] || + data?.detail || + data?.message || 'Upload failed. Please try again.' setAlert({ @@ -155,9 +150,9 @@ function FeedbackReports() { type: 'error', message: errorMessage, }) - } finally { - setLoading(false) } + + setLoading(false) } /** diff --git a/tdrs-frontend/src/components/FeedbackReports/FeedbackReports.test.js b/tdrs-frontend/src/components/FeedbackReports/FeedbackReports.test.js index 4dd4cbdd6..bdff421bd 100644 --- a/tdrs-frontend/src/components/FeedbackReports/FeedbackReports.test.js +++ b/tdrs-frontend/src/components/FeedbackReports/FeedbackReports.test.js @@ -5,9 +5,9 @@ import { MemoryRouter } from 'react-router-dom' import configureStore from 'redux-mock-store' import { thunk } from 'redux-thunk' import FeedbackReports from './FeedbackReports' -import axiosInstance from '../../axios-instance' +import { get, post } from '../../fetch-instance' -jest.mock('../../axios-instance') +jest.mock('../../fetch-instance') jest.mock('../../utils/createFileInputErrorState') jest.mock('@uswds/uswds/src/js/components', () => ({ fileInput: { @@ -36,7 +36,7 @@ describe('FeedbackReports', () => { jest.clearAllMocks() // Mock successful history fetch by default - axiosInstance.get.mockResolvedValue({ data: { results: [] } }) + get.mockResolvedValue({ data: { results: [] }, ok: true, status: 200, error: null }) // Mock FileReader for async file handling global.FileReader = jest.fn().mockImplementation(() => ({ @@ -155,15 +155,18 @@ describe('FeedbackReports', () => { }) it('successfully uploads a file and shows success message', async () => { - axiosInstance.post.mockResolvedValue({ + post.mockResolvedValue({ data: { id: 1, status: 'PENDING', original_filename: 'feedback.zip', }, + ok: true, + status: 200, + error: null, }) - axiosInstance.get.mockResolvedValue({ data: { results: [] } }) + get.mockResolvedValue({ data: { results: [] }, ok: true, status: 200, error: null }) renderComponent() @@ -197,23 +200,20 @@ describe('FeedbackReports', () => { ).toBeInTheDocument() }) - expect(axiosInstance.post).toHaveBeenCalledWith( + expect(post).toHaveBeenCalledWith( expect.stringContaining('/reports/report-sources/'), - expect.any(FormData), - expect.objectContaining({ - headers: { 'Content-Type': 'multipart/form-data' }, - withCredentials: true, - }) + expect.any(FormData) ) }) it('shows error message when upload fails', async () => { - axiosInstance.post.mockRejectedValue({ - response: { - data: { - file: ['Invalid zip file structure'], - }, + post.mockResolvedValue({ + data: { + file: ['Invalid zip file structure'], }, + ok: false, + status: 400, + error: new Error('HTTP 400'), }) renderComponent() @@ -248,7 +248,12 @@ describe('FeedbackReports', () => { }) it('shows generic error message when upload fails without specific error', async () => { - axiosInstance.post.mockRejectedValue(new Error('Network error')) + post.mockResolvedValue({ + data: null, + ok: false, + status: 0, + error: new Error('Network error'), + }) renderComponent() @@ -282,8 +287,8 @@ describe('FeedbackReports', () => { }) it('shows loading state during upload', async () => { - axiosInstance.post.mockImplementation( - () => new Promise((resolve) => setTimeout(resolve, 100)) + post.mockImplementation( + () => new Promise((resolve) => setTimeout(() => resolve({ data: {}, ok: true, status: 200, error: null }), 100)) ) renderComponent() @@ -331,7 +336,7 @@ describe('FeedbackReports', () => { }, ] - axiosInstance.get.mockResolvedValue({ data: { results: mockHistory } }) + get.mockResolvedValue({ data: { results: mockHistory }, ok: true, status: 200, error: null }) renderComponent() @@ -340,14 +345,13 @@ describe('FeedbackReports', () => { expect(screen.getByText('FY2025.zip')).toBeInTheDocument() }) - expect(axiosInstance.get).toHaveBeenCalledWith( - expect.stringContaining('/reports/report-sources/'), - expect.objectContaining({ withCredentials: true }) + expect(get).toHaveBeenCalledWith( + expect.stringContaining('/reports/report-sources/') ) }) it('displays empty state when no history exists', async () => { - axiosInstance.get.mockResolvedValue({ data: { results: [] } }) + get.mockResolvedValue({ data: { results: [] }, ok: true, status: 200, error: null }) renderComponent() @@ -357,8 +361,8 @@ describe('FeedbackReports', () => { }) it('displays loading state while fetching history', () => { - axiosInstance.get.mockImplementation( - () => new Promise((resolve) => setTimeout(resolve, 100)) + get.mockImplementation( + () => new Promise((resolve) => setTimeout(() => resolve({ data: { results: [] }, ok: true, status: 200, error: null }), 100)) ) renderComponent() @@ -367,7 +371,7 @@ describe('FeedbackReports', () => { }) it('displays error alert when history fetch fails', async () => { - axiosInstance.get.mockRejectedValue(new Error('Failed to fetch')) + get.mockResolvedValue({ data: null, ok: false, status: 500, error: new Error('Failed to fetch') }) renderComponent() @@ -400,7 +404,7 @@ describe('FeedbackReports', () => { }, ] - axiosInstance.get.mockResolvedValue({ data: { results: mockHistory } }) + get.mockResolvedValue({ data: { results: mockHistory }, ok: true, status: 200, error: null }) renderComponent() @@ -411,8 +415,11 @@ describe('FeedbackReports', () => { }) it('refreshes history after successful upload', async () => { - axiosInstance.post.mockResolvedValue({ + post.mockResolvedValue({ data: { id: 1, status: 'PENDING' }, + ok: true, + status: 200, + error: null, }) const mockHistory = [ @@ -427,9 +434,9 @@ describe('FeedbackReports', () => { ] // Initial fetch returns empty, all subsequent calls return mockHistory - axiosInstance.get - .mockResolvedValueOnce({ data: { results: [] } }) // Initial fetch - .mockResolvedValue({ data: { results: mockHistory } }) // All subsequent calls (including after upload) + get + .mockResolvedValueOnce({ data: { results: [] }, ok: true, status: 200, error: null }) // Initial fetch + .mockResolvedValue({ data: { results: mockHistory }, ok: true, status: 200, error: null }) // All subsequent calls (including after upload) renderComponent() @@ -460,7 +467,7 @@ describe('FeedbackReports', () => { }) // Should have called GET at least twice: once on mount, once after upload - expect(axiosInstance.get.mock.calls.length).toBeGreaterThanOrEqual(2) + expect(get.mock.calls.length).toBeGreaterThanOrEqual(2) }) }) @@ -478,7 +485,7 @@ describe('FeedbackReports', () => { }, ] - axiosInstance.get.mockResolvedValue({ data: { results: mockHistory } }) + get.mockResolvedValue({ data: { results: mockHistory }, ok: true, status: 200, error: null }) renderComponent() @@ -502,7 +509,7 @@ describe('FeedbackReports', () => { }, ] - axiosInstance.get.mockResolvedValue({ data: { results: mockHistory } }) + get.mockResolvedValue({ data: { results: mockHistory }, ok: true, status: 200, error: null }) renderComponent() @@ -515,8 +522,11 @@ describe('FeedbackReports', () => { describe('File Input Interaction', () => { it('clears file selection after successful upload', async () => { - axiosInstance.post.mockResolvedValue({ + post.mockResolvedValue({ data: { id: 1, status: 'PENDING' }, + ok: true, + status: 200, + error: null, }) renderComponent() @@ -572,7 +582,7 @@ describe('FeedbackReports', () => { }, ] - axiosInstance.get.mockResolvedValue({ data: { results: mockHistory } }) + get.mockResolvedValue({ data: { results: mockHistory }, ok: true, status: 200, error: null }) renderComponent() @@ -596,7 +606,7 @@ describe('FeedbackReports', () => { }, ] - axiosInstance.get.mockResolvedValue({ data: { results: mockHistory } }) + get.mockResolvedValue({ data: { results: mockHistory }, ok: true, status: 200, error: null }) renderComponent() diff --git a/tdrs-frontend/src/components/Reports/FRAReports.test.js b/tdrs-frontend/src/components/Reports/FRAReports.test.js index 4e7dd4e26..5a498e589 100644 --- a/tdrs-frontend/src/components/Reports/FRAReports.test.js +++ b/tdrs-frontend/src/components/Reports/FRAReports.test.js @@ -1,11 +1,13 @@ import React from 'react' import { fireEvent, waitFor, render, within } from '@testing-library/react' -import axios from 'axios' +import { get, post } from '../../fetch-instance' import { Provider } from 'react-redux' import { MemoryRouter } from 'react-router-dom' import { FRAReports } from '.' import configureStore from '../../configureStore' +jest.mock('../../fetch-instance') + const initialState = { auth: { authenticated: false, @@ -39,6 +41,8 @@ const makeTestFile = (name, contents = ['test'], type = 'text/plain') => describe('FRA Reports Page', () => { beforeEach(() => { jest.useFakeTimers() + get.mockResolvedValue({ data: [], ok: true, status: 200, error: null }) + post.mockResolvedValue({ data: { id: 1, original_filename: 'test.txt', extension: '.txt', section: 'Active Case Data', quarter: 'Q1' }, ok: true, status: 200, error: null }) }) afterEach(() => { jest.runOnlyPendingTimers() @@ -159,15 +163,12 @@ describe('FRA Reports Page', () => { }) it('Shows upload form once search has been clicked', async () => { - jest.mock('axios') - const mockAxios = axios - let searchUrl = null - mockAxios.get.mockImplementation((url) => { + get.mockImplementation((url) => { if (url.includes('/data_files/')) { searchUrl = url } - return Promise.resolve({ data: [] }) + return Promise.resolve({ data: [], ok: true, status: 200, error: null }) }) const state = { @@ -233,9 +234,6 @@ describe('FRA Reports Page', () => { describe('Upload form', () => { const setup = async () => { - jest.mock('axios') - const mockAxios = axios - window.HTMLElement.prototype.scrollIntoView = () => {} const state = { ...initialState, @@ -259,8 +257,11 @@ describe('FRA Reports Page', () => { const origDispatch = store.dispatch store.dispatch = jest.fn(origDispatch) - mockAxios.post.mockResolvedValue({ + post.mockResolvedValue({ data: { id: 1 }, + ok: true, + status: 200, + error: null, }) const component = render( @@ -294,7 +295,7 @@ describe('FRA Reports Page', () => { expect(getByText('Submit Report')).toBeInTheDocument() }) - return { ...component, ...store, mockAxios } + return { ...component, ...store } } it('Allows csv files to be selected and submitted', async () => { @@ -364,11 +365,10 @@ describe('FRA Reports Page', () => { queryAllByTestId, queryAllByText, dispatch, - mockAxios, container, } = await setup() - mockAxios.post.mockResolvedValue({ + post.mockResolvedValue({ data: { id: 1, original_filename: 'testFile.txt', @@ -384,10 +384,13 @@ describe('FRA Reports Page', () => { summary: null, latest_reparse_file_meta: '', }, + ok: true, + status: 200, + error: null, }) let times = 0 - mockAxios.get.mockImplementation((url) => { + get.mockImplementation((url) => { if (url.includes('/data_files/1/')) { // status times += 1 @@ -396,6 +399,9 @@ describe('FRA Reports Page', () => { id: 1, summary: { status: times > 1 ? 'Approved' : 'Pending' }, }, + ok: true, + status: 200, + error: null, }) } else { // submission history @@ -417,6 +423,9 @@ describe('FRA Reports Page', () => { latest_reparse_file_meta: '', }, ], + ok: true, + status: 200, + error: null, }) } }) @@ -454,7 +463,7 @@ describe('FRA Reports Page', () => { jest.runOnlyPendingTimers() - expect(mockAxios.get).toHaveBeenCalledTimes(4) + expect(get).toHaveBeenCalledTimes(4) expect(times).toBe(2) await waitFor(() => { @@ -467,16 +476,13 @@ describe('FRA Reports Page', () => { }) it('Shows an error if file submission failed', async () => { - jest.mock('axios') - const mockAxios = axios const { getByText, dispatch, container } = await setup() - mockAxios.post.mockRejectedValue({ - message: 'Error', - response: { - status: 400, - data: { detail: 'Mock fail response' }, - }, + post.mockResolvedValue({ + data: { detail: 'Mock fail response' }, + ok: false, + status: 400, + error: new Error('HTTP 400'), }) const uploadForm = container.querySelector('#fra-file-upload') @@ -495,7 +501,7 @@ describe('FRA Reports Page', () => { fireEvent.click(submitButton) await waitFor(() => - expect(getByText('Error: Mock fail response')).toBeInTheDocument() + expect(getByText('HTTP 400: Mock fail response')).toBeInTheDocument() ) await waitFor(() => expect(dispatch).toHaveBeenCalledTimes(4)) }) @@ -755,9 +761,6 @@ describe('FRA Reports Page', () => { describe('Submission History', () => { const setup = async (submissionHistoryApiResponse = []) => { - jest.mock('axios') - const mockAxios = axios - window.HTMLElement.prototype.scrollIntoView = () => {} const state = { ...initialState, @@ -796,8 +799,11 @@ describe('FRA Reports Page', () => { const { getByLabelText, getByText } = component - mockAxios.get.mockResolvedValue({ + get.mockResolvedValue({ data: submissionHistoryApiResponse, + ok: true, + status: 200, + error: null, }) // fill out the form values before clicking search diff --git a/tdrs-frontend/src/components/Reports/Reports.test.js b/tdrs-frontend/src/components/Reports/Reports.test.js index a9f4dd678..cfedf7788 100644 --- a/tdrs-frontend/src/components/Reports/Reports.test.js +++ b/tdrs-frontend/src/components/Reports/Reports.test.js @@ -4,12 +4,14 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react' import { Provider } from 'react-redux' import { MemoryRouter } from 'react-router-dom' import { thunk } from 'redux-thunk' -import axios from 'axios' +import { get, post } from '../../fetch-instance' import configureStore from 'redux-mock-store' import appConfigureStore from '../../configureStore' import Reports from './Reports' import { SET_FILE, upload } from '../../actions/reports' +jest.mock('../../fetch-instance') + describe('Reports', () => { let originalScrollIntoView @@ -19,6 +21,10 @@ describe('Reports', () => { // Mock it for all tests window.HTMLElement.prototype.scrollIntoView = jest.fn() jest.useFakeTimers() + + // Set default mock return values for fetch-instance functions + get.mockResolvedValue({ data: [], ok: true, status: 200, error: null }) + post.mockResolvedValue({ data: { id: 1, original_filename: 'test.txt', extension: '.txt', section: 'Active Case Data', quarter: 'Q3' }, ok: true, status: 200, error: null }) }) afterEach(() => { @@ -1036,10 +1042,8 @@ describe('Reports', () => { it('should show spinners while the upload is parsing', async () => { jest.useFakeTimers() - jest.mock('axios') - const mockAxios = axios - mockAxios.post.mockResolvedValue({ + post.mockResolvedValue({ data: { id: 1, original_filename: 'testFile.txt', @@ -1055,10 +1059,13 @@ describe('Reports', () => { summary: null, latest_reparse_file_meta: '', }, + ok: true, + status: 200, + error: null, }) let times = 0 - mockAxios.get.mockImplementation((url) => { + get.mockImplementation((url) => { if (url.includes('/data_files/1/')) { // status times += 1 @@ -1080,6 +1087,9 @@ describe('Reports', () => { has_error: false, latest_reparse_file_meta: '', }, + ok: true, + status: 200, + error: null, }) } else { // submission history @@ -1101,6 +1111,9 @@ describe('Reports', () => { latest_reparse_file_meta: '', }, ], + ok: true, + status: 200, + error: null, }) } }) @@ -1193,7 +1206,7 @@ describe('Reports', () => { // act(() => jest.advanceTimersByTime(2000)) - expect(mockAxios.get).toHaveBeenCalledTimes(2) + expect(get).toHaveBeenCalledTimes(2) expect(times).toBe(1) fireEvent.click(getByText('Submission History')) @@ -1222,11 +1235,9 @@ describe('Reports', () => { it('should show spinners while multiple uploads are parsing', async () => { jest.useFakeTimers() - jest.mock('axios') - const mockAxios = axios let postTimes = 0 - mockAxios.post.mockImplementation((url) => { + post.mockImplementation((url) => { postTimes += 1 if (postTimes === 1) { @@ -1246,6 +1257,9 @@ describe('Reports', () => { summary: null, latest_reparse_file_meta: '', }, + ok: true, + status: 200, + error: null, }) } @@ -1265,12 +1279,15 @@ describe('Reports', () => { summary: null, latest_reparse_file_meta: '', }, + ok: true, + status: 200, + error: null, }) }) let times1 = 0 let times2 = 0 - mockAxios.get.mockImplementation((url) => { + get.mockImplementation((url) => { if (url.includes('/data_files/1/')) { // status times1 += 1 @@ -1292,6 +1309,9 @@ describe('Reports', () => { has_error: false, latest_reparse_file_meta: '', }, + ok: true, + status: 200, + error: null, }) } else if (url.includes('/data_files/2/')) { // status @@ -1314,6 +1334,9 @@ describe('Reports', () => { has_error: false, latest_reparse_file_meta: '', }, + ok: true, + status: 200, + error: null, }) } else { // submission history @@ -1350,6 +1373,9 @@ describe('Reports', () => { latest_reparse_file_meta: '', }, ], + ok: true, + status: 200, + error: null, }) } }) @@ -1448,7 +1474,7 @@ describe('Reports', () => { // act(() => jest.advanceTimersByTime(2000)) - expect(mockAxios.get).toHaveBeenCalledTimes(3) + expect(get).toHaveBeenCalledTimes(3) expect(times1).toBe(1) expect(times2).toBe(1) diff --git a/tdrs-frontend/src/components/RequestAccessForm/RequestAccessForm.test.js b/tdrs-frontend/src/components/RequestAccessForm/RequestAccessForm.test.js index bbbfe1929..8427297ff 100644 --- a/tdrs-frontend/src/components/RequestAccessForm/RequestAccessForm.test.js +++ b/tdrs-frontend/src/components/RequestAccessForm/RequestAccessForm.test.js @@ -9,7 +9,9 @@ import { SET_REQUEST_USER_UPDATE_ERROR, } from '../../actions/updateUserRequest' import { SET_AUTH } from '../../actions/auth' -import axios from 'axios' +import { patch } from '../../fetch-instance' + +jest.mock('../../fetch-instance') jest.mock('../STTComboBox', () => (props) => { return ( @@ -124,6 +126,7 @@ describe('RequestAccessForm', () => { }) it('dispatches requestAccess when form is valid', async () => { + patch.mockResolvedValue({ data: { first_name: 'Jane', last_name: 'Doe' }, ok: true, status: 200, error: null }) const { store } = setup() // Spy on dispatch @@ -282,6 +285,8 @@ describe('RequestAccessForm', () => { }) it('dispatches updateUserRequest in editMode when data changes', async () => { + patch.mockResolvedValue({ data: { first_name: 'John', last_name: 'Smith' }, ok: true, status: 200, error: null }) + const initialValues = { firstName: 'John', lastName: 'Doe', @@ -361,7 +366,7 @@ describe('RequestAccessForm', () => { has_fra_access: false, pending_requests: 1, } - axios.patch.mockResolvedValue({ data: apiUserResponse }) + patch.mockResolvedValue({ data: apiUserResponse, ok: true, status: 200, error: null }) const { store } = setup(props, storeOverrides) // Spy on store.dispatch to monitor calls diff --git a/tdrs-frontend/src/components/SubmissionHistory/helpers.jsx b/tdrs-frontend/src/components/SubmissionHistory/helpers.jsx index 899f343c5..044ac3fca 100644 --- a/tdrs-frontend/src/components/SubmissionHistory/helpers.jsx +++ b/tdrs-frontend/src/components/SubmissionHistory/helpers.jsx @@ -1,5 +1,5 @@ import React from 'react' -import axios from 'axios' +import { get } from '../../fetch-instance' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faCheckCircle, @@ -14,14 +14,12 @@ export const formatDate = (dateStr) => new Date(dateStr).toLocaleString() export const downloadFile = (dispatch, file) => dispatch(download(file)) export const downloadErrorReport = async (file, reportName) => { try { - const promise = axios.get( + const { data, ok, error } = await get( `${process.env.REACT_APP_BACKEND_URL}/data_files/${file.id}/download_error_report/`, - { - responseType: 'blob', - } + { responseType: 'blob' } ) - const dataPromise = await promise.then((response) => response.data) - getParseErrors(dataPromise, reportName) + if (!ok) throw error + getParseErrors(data, reportName) } catch (error) { console.log(error) } diff --git a/tdrs-frontend/src/fetch-instance.js b/tdrs-frontend/src/fetch-instance.js new file mode 100644 index 000000000..ff3aeab51 --- /dev/null +++ b/tdrs-frontend/src/fetch-instance.js @@ -0,0 +1,123 @@ +import { faro } from '@grafana/faro-react' + +let csrfToken = null + +export function setCSRFToken(token) { + csrfToken = token +} + +export function getCSRFToken() { + return csrfToken +} + +function buildHeaders(customHeaders = {}, includeCSRF = true) { + const headers = { 'x-service-name': 'tdp-frontend', ...customHeaders } + + if (includeCSRF && csrfToken) { + headers['X-CSRFToken'] = csrfToken + } + + if (faro?.api) { + try { + const traceContext = faro.api.getTraceContext() + if (traceContext) Object.assign(headers, traceContext) + } catch (e) { + console.error('Failed to add trace context', e) + } + } + + return headers +} + +async function handleResponse(response, responseType) { + if (responseType === 'blob') { + return { + data: await response.blob(), + error: response.ok ? null : new Error(`HTTP ${response.status}`), + status: response.status, + ok: response.ok, + } + } + + const contentType = response.headers.get('content-type') || '' + let data = null + + if (contentType.includes('application/json')) { + data = await response.json().catch(() => null) + } else { + data = await response.text() + } + + return { + data, + error: response.ok ? null : new Error(`HTTP ${response.status}`), + status: response.status, + ok: response.ok, + } +} + +export async function get(url, options = {}) { + const { headers: customHeaders, responseType, ...rest } = options + + try { + const response = await fetch(url, { + method: 'GET', + credentials: 'include', + headers: buildHeaders(customHeaders, false), + ...rest, + }) + return handleResponse(response, responseType) + } catch (error) { + return { data: null, error, status: 0, ok: false } + } +} + +export async function post(url, body, options = {}) { + const { headers: customHeaders, ...rest } = options + const isFormData = body instanceof FormData + const headers = buildHeaders( + isFormData + ? customHeaders + : { 'Content-Type': 'application/json', ...customHeaders }, + true + ) + + if (isFormData) { + delete headers['Content-Type'] + } + + try { + const response = await fetch(url, { + method: 'POST', + credentials: 'include', + headers, + body: isFormData ? body : JSON.stringify(body), + ...rest, + }) + return handleResponse(response) + } catch (error) { + return { data: null, error, status: 0, ok: false } + } +} + +export async function patch(url, body, options = {}) { + const { headers: customHeaders, ...rest } = options + + try { + const response = await fetch(url, { + method: 'PATCH', + credentials: 'include', + headers: buildHeaders( + { 'Content-Type': 'application/json', ...customHeaders }, + true + ), + body: JSON.stringify(body), + ...rest, + }) + return handleResponse(response) + } catch (error) { + return { data: null, error, status: 0, ok: false } + } +} + +export default { get, post, patch, setCSRFToken, getCSRFToken } diff --git a/tdrs-frontend/src/hooks/usePollingTimer.js b/tdrs-frontend/src/hooks/usePollingTimer.js index 7749f4b9b..0836b377f 100644 --- a/tdrs-frontend/src/hooks/usePollingTimer.js +++ b/tdrs-frontend/src/hooks/usePollingTimer.js @@ -105,8 +105,13 @@ export const usePollingTimer = () => { try { response = await request() - } catch (axiosError) { - const statusCode = axiosError?.response?.status + } catch (networkError) { + retry(requestId, tryNumber) + return + } + + if (!response.ok) { + const statusCode = response.status const shouldStopPolling = statusCode === 400 || statusCode === 401 || statusCode === 403 @@ -114,7 +119,9 @@ export const usePollingTimer = () => { retry(requestId, tryNumber) return } else { - finish(requestId, () => onError(axiosError)) + finish(requestId, () => + onError(response.error || new Error(`HTTP ${statusCode}`)) + ) return } } diff --git a/tdrs-frontend/src/hooks/usePollingTimer.test.js b/tdrs-frontend/src/hooks/usePollingTimer.test.js index 4636c996a..527847214 100644 --- a/tdrs-frontend/src/hooks/usePollingTimer.test.js +++ b/tdrs-frontend/src/hooks/usePollingTimer.test.js @@ -1,14 +1,15 @@ import { render, fireEvent, waitFor, act } from '@testing-library/react' -import axios from 'axios' +import { get } from '../fetch-instance' import { usePollingTimer } from './usePollingTimer' +jest.mock('../fetch-instance') + describe('usePollingTimer', () => { const setupMockFuncs = () => { jest.useFakeTimers() return { - mockAxios: axios, requestFunc: jest.fn(() => { - return axios.get('/fake_status_endpoint/') + return get('/fake_status_endpoint/') }), testFunc: jest.fn((response) => { return response?.data?.summary?.status !== 'Pending' @@ -108,9 +109,11 @@ describe('usePollingTimer', () => { it('should start polling when startPolling called', async () => { const mocks = setupMockFuncs() - const { mockAxios } = mocks - mockAxios.get.mockResolvedValue({ + get.mockResolvedValue({ data: { id: 1, hasErrors: false, summary: { status: 'Pending' } }, + ok: true, + status: 200, + error: null, }) const { queryByText, getByTitle } = setupSingleTimerComponent( @@ -134,9 +137,11 @@ describe('usePollingTimer', () => { it('should call retry until the test completes then call onSuccess', async () => { const mocks = setupMockFuncs() - const { mockAxios } = mocks - mockAxios.get.mockResolvedValue({ + get.mockResolvedValue({ data: { id: 1, hasErrors: false, summary: { status: 'Pending' } }, + ok: true, + status: 200, + error: null, }) const { queryByText, getByTitle } = setupSingleTimerComponent( @@ -176,8 +181,11 @@ describe('usePollingTimer', () => { expect(mocks.onTimeoutFunc).toHaveBeenCalledTimes(0) // now update the mock and run a third time - mockAxios.get.mockResolvedValue({ + get.mockResolvedValue({ data: { id: 1, hasErrors: false, summary: { status: 'Approved' } }, + ok: true, + status: 200, + error: null, }) act(() => jest.advanceTimersByTime(1000)) @@ -195,15 +203,13 @@ describe('usePollingTimer', () => { it.each([404, 500])( 'should call retry when the backend is down', - async (status) => { + async (statusCode) => { const mocks = setupMockFuncs() - const { mockAxios } = mocks - mockAxios.get.mockRejectedValue({ - message: 'Error', - response: { - status, - data: { detail: 'Mock fail response' }, - }, + get.mockResolvedValue({ + data: { detail: 'Mock fail response' }, + ok: false, + status: statusCode, + error: new Error(`HTTP ${statusCode}`), }) const { queryByText, getByTitle } = setupSingleTimerComponent( @@ -246,16 +252,14 @@ describe('usePollingTimer', () => { it.each([400, 401, 403])( 'should stop polling and call onError if the request fails with a %s error', - async (status) => { + async (statusCode) => { const mocks = setupMockFuncs() - const { mockAxios } = mocks - - mockAxios.get.mockRejectedValue({ - message: 'Error', - response: { - status, - data: { detail: 'Mock fail response' }, - }, + + get.mockResolvedValue({ + data: { detail: 'Mock fail response' }, + ok: false, + status: statusCode, + error: new Error(`HTTP ${statusCode}`), }) const { queryByText, getByTitle } = setupSingleTimerComponent( @@ -285,9 +289,11 @@ describe('usePollingTimer', () => { it('should stop polling and call onTimeout when max tries reached', async () => { const mocks = setupMockFuncs() - const { mockAxios } = mocks - mockAxios.get.mockResolvedValue({ + get.mockResolvedValue({ data: { id: 1, hasErrors: false, summary: { status: 'Pending' } }, + ok: true, + status: 200, + error: null, }) const { queryByText, getByTitle } = setupSingleTimerComponent( @@ -333,9 +339,11 @@ describe('usePollingTimer', () => { it('should allow multiple parallel timers with different requestIds', async () => { const mocks = setupMockFuncs() - const { mockAxios } = mocks - mockAxios.get.mockResolvedValue({ + get.mockResolvedValue({ data: { id: 1, hasErrors: false, summary: { status: 'Pending' } }, + ok: true, + status: 200, + error: null, }) const { queryByText, getByTitle } = setupMultiTimerComponent( @@ -407,9 +415,11 @@ describe('usePollingTimer', () => { jest.spyOn(global, 'setTimeout') jest.spyOn(global, 'clearTimeout') const mocks = setupMockFuncs() - const { mockAxios } = mocks - mockAxios.get.mockResolvedValue({ + get.mockResolvedValue({ data: { id: 1, hasErrors: false, summary: { status: 'Pending' } }, + ok: true, + status: 200, + error: null, }) const { queryByText, getByTitle, unmount } = setupMultiTimerComponent( diff --git a/tdrs-frontend/src/index.js b/tdrs-frontend/src/index.js index f0fe71d03..cdfa7cd50 100644 --- a/tdrs-frontend/src/index.js +++ b/tdrs-frontend/src/index.js @@ -1,6 +1,5 @@ import React from 'react' import { createRoot } from 'react-dom/client' -import axios from 'axios' import { ReduxRouter as Router } from '@lagunovsky/redux-react-router' import { Provider } from 'react-redux' @@ -37,9 +36,6 @@ if ( // needs to be called before auth_check startMirage() } -axios.defaults.xsrfCookieName = 'csrftoken' -axios.defaults.xsrfHeaderName = 'X-CSRFToken' -axios.defaults.withCredentials = true // Initialize FaroSDK if (process.env.REACT_APP_ENABLE_RUM === 'true') { diff --git a/tdrs-frontend/src/utils/eventLogger.js b/tdrs-frontend/src/utils/eventLogger.js index 6afc02ce6..36a14b7c4 100644 --- a/tdrs-frontend/src/utils/eventLogger.js +++ b/tdrs-frontend/src/utils/eventLogger.js @@ -1,10 +1,8 @@ import { useSelector } from 'react-redux' -import axiosInstance from '../axios-instance' +import { post } from '../fetch-instance' function sendDataToServer(data) { - axiosInstance.post(`${process.env.REACT_APP_BACKEND_URL}/logs/`, data, { - withCredentials: true, - }) + post(`${process.env.REACT_APP_BACKEND_URL}/logs/`, data) } function logEvents(initialContext) { From 608e66f68b389e3bd73ed8b33af5a2b574416378 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Fri, 6 Feb 2026 10:35:00 -0600 Subject: [PATCH 013/148] - fix warning about throwing object literal and not an error --- tdrs-frontend/src/actions/reports.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tdrs-frontend/src/actions/reports.js b/tdrs-frontend/src/actions/reports.js index 1767b178d..4662ba132 100644 --- a/tdrs-frontend/src/actions/reports.js +++ b/tdrs-frontend/src/actions/reports.js @@ -209,10 +209,9 @@ export const submit = // Check if any responses have errors const failedResponse = responses.find((r) => !r.ok) if (failedResponse) { - throw { - message: failedResponse.error?.message || 'Error', - response: { data: failedResponse.data }, - } + const err = new Error(failedResponse.error?.message || 'Error') + err.response = { data: failedResponse.data } + throw err } setLocalAlertState({ From ad1adc369b6a2ccc4bac33e396201c0afab03185 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Fri, 6 Feb 2026 10:54:47 -0600 Subject: [PATCH 014/148] - Boosting coverage --- .../src/__mocks__/@grafana/faro-react.js | 6 + tdrs-frontend/src/actions/fraReports.test.js | 106 ++++++++ .../SubmissionHistory/helpers.test.js | 130 +++++++++- tdrs-frontend/src/fetch-instance.test.js | 242 ++++++++++++++++++ 4 files changed, 483 insertions(+), 1 deletion(-) create mode 100644 tdrs-frontend/src/fetch-instance.test.js diff --git a/tdrs-frontend/src/__mocks__/@grafana/faro-react.js b/tdrs-frontend/src/__mocks__/@grafana/faro-react.js index 501442b3b..0c7d93d74 100644 --- a/tdrs-frontend/src/__mocks__/@grafana/faro-react.js +++ b/tdrs-frontend/src/__mocks__/@grafana/faro-react.js @@ -4,3 +4,9 @@ import { Routes } from 'react-router-dom' export const FaroRoutes = ({ children }) => { return {children} } + +export const faro = { + api: { + getTraceContext: jest.fn(() => null), + }, +} diff --git a/tdrs-frontend/src/actions/fraReports.test.js b/tdrs-frontend/src/actions/fraReports.test.js index 91aeceffd..369d1c317 100644 --- a/tdrs-frontend/src/actions/fraReports.test.js +++ b/tdrs-frontend/src/actions/fraReports.test.js @@ -9,6 +9,7 @@ import { SET_IS_UPLOADING_FRA_REPORT, uploadFraReport, downloadOriginalSubmission, + getFraSubmissionStatus, } from './fraReports' jest.mock('../fetch-instance') @@ -382,5 +383,110 @@ describe('actions/fraReports', () => { const actions = store.getActions() expect(actions.length).toEqual(0) }) + + it('downloads file on success', async () => { + const store = mockStore() + const blob = new Blob(['file-content']) + + get.mockResolvedValue({ + data: blob, + ok: true, + status: 200, + error: null, + }) + + const mockLink = { + href: '', + setAttribute: jest.fn(), + click: jest.fn(), + } + jest.spyOn(document, 'createElement').mockReturnValue(mockLink) + jest.spyOn(document.body, 'appendChild').mockImplementation(() => {}) + jest.spyOn(document.body, 'removeChild').mockImplementation(() => {}) + window.URL.createObjectURL = jest.fn(() => 'blob:test-url') + + await store.dispatch( + downloadOriginalSubmission({ + id: 42, + fileName: 'report.txt', + year: '2025', + quarter: 'Q1', + section: 'Active Case Data', + }) + ) + + expect(get).toHaveBeenCalledWith( + expect.stringContaining('/data_files/42/download/'), + { responseType: 'blob' } + ) + expect(mockLink.setAttribute).toHaveBeenCalledWith( + 'download', + 'report (2025-Q1-Active Case Data).txt' + ) + expect(mockLink.click).toHaveBeenCalled() + expect(document.body.removeChild).toHaveBeenCalledWith(mockLink) + + document.createElement.mockRestore() + document.body.appendChild.mockRestore() + document.body.removeChild.mockRestore() + }) + + it('logs error when API returns non-ok response', async () => { + const store = mockStore() + const consoleSpy = jest.spyOn(console, 'error').mockImplementation() + + get.mockResolvedValue({ + data: null, + ok: false, + status: 500, + error: new Error('Server error'), + }) + + await store.dispatch( + downloadOriginalSubmission({ + id: 42, + fileName: 'report.txt', + year: '2025', + quarter: 'Q1', + section: 'Active Case Data', + }) + ) + + expect(consoleSpy).toHaveBeenCalledWith( + 'error downloading file', + expect.any(Error) + ) + consoleSpy.mockRestore() + }) + }) + + describe('getFraSubmissionStatus', () => { + it('returns data on success', async () => { + get.mockResolvedValue({ + data: { status: 'complete' }, + ok: true, + status: 200, + error: null, + }) + + const result = await getFraSubmissionStatus(99) + + expect(get).toHaveBeenCalledWith( + expect.stringContaining('/data_files/99/') + ) + expect(result).toEqual({ data: { status: 'complete' }, ok: true }) + }) + + it('throws error on non-ok response', async () => { + const mockError = new Error('Not found') + get.mockResolvedValue({ + data: null, + ok: false, + status: 404, + error: mockError, + }) + + await expect(getFraSubmissionStatus(99)).rejects.toThrow('Not found') + }) }) }) diff --git a/tdrs-frontend/src/components/SubmissionHistory/helpers.test.js b/tdrs-frontend/src/components/SubmissionHistory/helpers.test.js index 8ad7a25cc..b21d1bd59 100644 --- a/tdrs-frontend/src/components/SubmissionHistory/helpers.test.js +++ b/tdrs-frontend/src/components/SubmissionHistory/helpers.test.js @@ -1,4 +1,16 @@ -import { formatProgramType } from './helpers' +import React from 'react' +import { render, screen, fireEvent } from '@testing-library/react' +import { get } from '../../fetch-instance' +import { getParseErrors } from '../../actions/createXLSReport' +import { + formatProgramType, + downloadErrorReport, + getErrorReportStatus, + SubmissionSummaryStatusIcon, +} from './helpers' + +jest.mock('../../fetch-instance') +jest.mock('../../actions/createXLSReport') describe('formatProgramType', () => { it('returns a label for SSP', () => { @@ -17,3 +29,119 @@ describe('formatProgramType', () => { expect(formatProgramType('UNKNOWN')).toEqual('') }) }) + +describe('downloadErrorReport', () => { + beforeEach(() => { + get.mockClear() + getParseErrors.mockClear() + }) + + it('downloads and parses the error report on success', async () => { + const blob = new Blob(['error-data']) + get.mockResolvedValue({ data: blob, ok: true, error: null }) + + await downloadErrorReport({ id: 5 }, 'My Error Report') + + expect(get).toHaveBeenCalledWith( + expect.stringContaining('/data_files/5/download_error_report/'), + { responseType: 'blob' } + ) + expect(getParseErrors).toHaveBeenCalledWith(blob, 'My Error Report') + }) + + it('logs error when API returns non-ok response', async () => { + const consoleSpy = jest.spyOn(console, 'log').mockImplementation() + get.mockResolvedValue({ + data: null, + ok: false, + error: new Error('Server error'), + }) + + await downloadErrorReport({ id: 5 }, 'Report') + + expect(consoleSpy).toHaveBeenCalledWith(expect.any(Error)) + expect(getParseErrors).not.toHaveBeenCalled() + consoleSpy.mockRestore() + }) +}) + +describe('getErrorReportStatus', () => { + it('returns download button when file has errors', () => { + const file = { + summary: { status: 'Accepted with Errors' }, + program_type: 'TAN', + year: '2025', + quarter: 'Q1', + section: 'Active Case Data', + hasError: true, + id: 10, + } + + const result = getErrorReportStatus(file) + render(result) + + expect( + screen.getByText('2025-Q1-TANF Active Case Data Error Report.xlsx') + ).toBeInTheDocument() + }) + + it('returns No Errors when file has no errors', () => { + const file = { + summary: { status: 'Accepted' }, + program_type: 'TAN', + year: '2025', + quarter: 'Q1', + section: 'Active Case Data', + hasError: false, + } + + expect(getErrorReportStatus(file)).toBe('No Errors') + }) + + it('returns Pending when summary status is Pending', () => { + const file = { summary: { status: 'Pending' } } + expect(getErrorReportStatus(file)).toBe('Pending') + }) + + it('calls downloadErrorReport when button is clicked', () => { + get.mockResolvedValue({ data: new Blob(), ok: true, error: null }) + + const file = { + summary: { status: 'Rejected' }, + program_type: 'SSP', + year: '2025', + quarter: 'Q2', + section: 'Closed Case Data', + hasError: true, + id: 42, + } + + const result = getErrorReportStatus(file) + render(result) + + fireEvent.click(screen.getByRole('button')) + expect(get).toHaveBeenCalled() + }) +}) + +describe('SubmissionSummaryStatusIcon', () => { + it.each([ + ['Pending'], + ['Accepted'], + ['Partially Accepted with Errors'], + ['Accepted with Errors'], + ['Rejected'], + ])('renders icon for status "%s"', (status) => { + const { container } = render( + + ) + expect(container.querySelector('svg')).toBeInTheDocument() + }) + + it('renders without icon for unknown status', () => { + const { container } = render( + + ) + expect(container.querySelector('svg')).toBeNull() + }) +}) diff --git a/tdrs-frontend/src/fetch-instance.test.js b/tdrs-frontend/src/fetch-instance.test.js new file mode 100644 index 000000000..5261947a7 --- /dev/null +++ b/tdrs-frontend/src/fetch-instance.test.js @@ -0,0 +1,242 @@ +import { get, post, patch, setCSRFToken, getCSRFToken } from './fetch-instance' +import { faro } from '@grafana/faro-react' + +jest.mock('@grafana/faro-react') + +const mockResponse = (body, options = {}) => ({ + ok: options.ok !== undefined ? options.ok : true, + status: options.status || 200, + headers: { + get: (key) => options.contentType || 'application/json', + }, + json: () => Promise.resolve(body), + text: () => Promise.resolve(typeof body === 'string' ? body : JSON.stringify(body)), + blob: () => Promise.resolve(new Blob([JSON.stringify(body)])), +}) + +describe('fetch-instance', () => { + beforeEach(() => { + jest.clearAllMocks() + global.fetch = jest.fn() + setCSRFToken(null) + }) + + describe('setCSRFToken / getCSRFToken', () => { + it('stores and retrieves the CSRF token', () => { + expect(getCSRFToken()).toBeNull() + setCSRFToken('test-token') + expect(getCSRFToken()).toBe('test-token') + }) + }) + + describe('get', () => { + it('makes a GET request and returns JSON data', async () => { + const data = { message: 'hello' } + global.fetch.mockResolvedValue(mockResponse(data)) + + const result = await get('/api/test') + + expect(fetch).toHaveBeenCalledWith('/api/test', { + method: 'GET', + credentials: 'include', + headers: expect.objectContaining({ + 'x-service-name': 'tdp-frontend', + }), + }) + expect(result).toEqual({ data, error: null, status: 200, ok: true }) + }) + + it('returns error info for non-ok responses', async () => { + global.fetch.mockResolvedValue( + mockResponse({ detail: 'Not found' }, { ok: false, status: 404 }) + ) + + const result = await get('/api/missing') + + expect(result.ok).toBe(false) + expect(result.status).toBe(404) + expect(result.error).toBeInstanceOf(Error) + expect(result.error.message).toBe('HTTP 404') + }) + + it('handles network errors', async () => { + const networkError = new Error('Network failure') + global.fetch.mockRejectedValue(networkError) + + const result = await get('/api/down') + + expect(result).toEqual({ + data: null, + error: networkError, + status: 0, + ok: false, + }) + }) + + it('handles blob response type', async () => { + const blob = new Blob(['file-data']) + global.fetch.mockResolvedValue({ + ok: true, + status: 200, + headers: { get: () => 'application/octet-stream' }, + blob: () => Promise.resolve(blob), + }) + + const result = await get('/api/download', { responseType: 'blob' }) + + expect(result.data).toBe(blob) + expect(result.ok).toBe(true) + expect(result.error).toBeNull() + }) + + it('handles non-JSON text responses', async () => { + global.fetch.mockResolvedValue( + mockResponse('plain text', { contentType: 'text/plain' }) + ) + + const result = await get('/api/text') + + expect(result.data).toBe('plain text') + expect(result.ok).toBe(true) + }) + }) + + describe('post', () => { + it('makes a POST request with JSON body', async () => { + const responseData = { id: 1 } + global.fetch.mockResolvedValue(mockResponse(responseData)) + + const result = await post('/api/create', { name: 'test' }) + + expect(fetch).toHaveBeenCalledWith('/api/create', { + method: 'POST', + credentials: 'include', + headers: expect.objectContaining({ + 'Content-Type': 'application/json', + 'x-service-name': 'tdp-frontend', + }), + body: JSON.stringify({ name: 'test' }), + }) + expect(result).toEqual({ data: responseData, error: null, status: 200, ok: true }) + }) + + it('makes a POST request with FormData', async () => { + global.fetch.mockResolvedValue(mockResponse({ id: 2 })) + const formData = new FormData() + formData.append('file', 'test-file') + + await post('/api/upload', formData) + + const callArgs = fetch.mock.calls[0][1] + expect(callArgs.body).toBe(formData) + expect(callArgs.headers['Content-Type']).toBeUndefined() + }) + + it('includes CSRF token in POST headers', async () => { + setCSRFToken('my-csrf-token') + global.fetch.mockResolvedValue(mockResponse({})) + + await post('/api/create', {}) + + expect(fetch).toHaveBeenCalledWith('/api/create', expect.objectContaining({ + headers: expect.objectContaining({ + 'X-CSRFToken': 'my-csrf-token', + }), + })) + }) + + it('handles network errors', async () => { + const networkError = new Error('Connection refused') + global.fetch.mockRejectedValue(networkError) + + const result = await post('/api/create', {}) + + expect(result).toEqual({ + data: null, + error: networkError, + status: 0, + ok: false, + }) + }) + }) + + describe('patch', () => { + it('makes a PATCH request with JSON body', async () => { + const responseData = { updated: true } + global.fetch.mockResolvedValue(mockResponse(responseData)) + + const result = await patch('/api/update/1', { name: 'updated' }) + + expect(fetch).toHaveBeenCalledWith('/api/update/1', { + method: 'PATCH', + credentials: 'include', + headers: expect.objectContaining({ + 'Content-Type': 'application/json', + 'x-service-name': 'tdp-frontend', + }), + body: JSON.stringify({ name: 'updated' }), + }) + expect(result).toEqual({ data: responseData, error: null, status: 200, ok: true }) + }) + + it('includes CSRF token in PATCH headers', async () => { + setCSRFToken('patch-csrf-token') + global.fetch.mockResolvedValue(mockResponse({})) + + await patch('/api/update/1', {}) + + expect(fetch).toHaveBeenCalledWith('/api/update/1', expect.objectContaining({ + headers: expect.objectContaining({ + 'X-CSRFToken': 'patch-csrf-token', + }), + })) + }) + + it('handles network errors', async () => { + const networkError = new Error('Timeout') + global.fetch.mockRejectedValue(networkError) + + const result = await patch('/api/update/1', {}) + + expect(result).toEqual({ + data: null, + error: networkError, + status: 0, + ok: false, + }) + }) + }) + + describe('faro trace context', () => { + it('includes trace context headers when faro is available', async () => { + faro.api.getTraceContext.mockReturnValue({ traceparent: 'test-trace-id' }) + global.fetch.mockResolvedValue(mockResponse({})) + + await get('/api/test') + + expect(faro.api.getTraceContext).toHaveBeenCalled() + expect(fetch).toHaveBeenCalledWith('/api/test', expect.objectContaining({ + headers: expect.objectContaining({ + traceparent: 'test-trace-id', + }), + })) + }) + + it('handles faro getTraceContext throwing an error', async () => { + faro.api.getTraceContext.mockImplementation(() => { + throw new Error('faro error') + }) + global.fetch.mockResolvedValue(mockResponse({})) + const consoleSpy = jest.spyOn(console, 'error').mockImplementation() + + await get('/api/test') + + expect(consoleSpy).toHaveBeenCalledWith( + 'Failed to add trace context', + expect.any(Error) + ) + expect(fetch).toHaveBeenCalled() + consoleSpy.mockRestore() + }) + }) +}) From 0035f381ed800298d3b26b055074a1f4a751aaa1 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Mon, 9 Feb 2026 10:14:43 -0600 Subject: [PATCH 015/148] - linting --- .../components/Feedback/FeedbackForm.test.js | 135 ++++++++++++++---- .../FeedbackReports/FeedbackReports.test.js | 106 ++++++++++++-- .../src/components/Reports/FRAReports.test.js | 13 +- .../src/components/Reports/Reports.test.js | 13 +- .../RequestAccessForm.test.js | 21 ++- tdrs-frontend/src/fetch-instance.test.js | 56 +++++--- 6 files changed, 278 insertions(+), 66 deletions(-) diff --git a/tdrs-frontend/src/components/Feedback/FeedbackForm.test.js b/tdrs-frontend/src/components/Feedback/FeedbackForm.test.js index ce7879900..713eb2236 100644 --- a/tdrs-frontend/src/components/Feedback/FeedbackForm.test.js +++ b/tdrs-frontend/src/components/Feedback/FeedbackForm.test.js @@ -225,7 +225,12 @@ describe('Feedback Form tests', () => { }) it('clears error message after rating is selected', async () => { - post.mockResolvedValue({ ok: true, status: 201, data: { id: 1 }, error: null }) + post.mockResolvedValue({ + ok: true, + status: 201, + data: { id: 1 }, + error: null, + }) render( { }) it('submits feedback with rating, message, and anonymous flag', async () => { - post.mockResolvedValue({ ok: true, status: 201, data: { id: 1 }, error: null }) - patch.mockResolvedValue({ ok: true, status: 200, data: { id: 1 }, error: null }) + post.mockResolvedValue({ + ok: true, + status: 201, + data: { id: 1 }, + error: null, + }) + patch.mockResolvedValue({ + ok: true, + status: 200, + data: { id: 1 }, + error: null, + }) render( { }) it('submits with rating and no feedback message', async () => { - post.mockResolvedValue({ ok: true, status: 201, data: { id: 1 }, error: null }) - patch.mockResolvedValue({ ok: true, status: 200, data: { id: 1 }, error: null }) + post.mockResolvedValue({ + ok: true, + status: 201, + data: { id: 1 }, + error: null, + }) + patch.mockResolvedValue({ + ok: true, + status: 200, + data: { id: 1 }, + error: null, + }) render( { }) it('submits form using Enter key on submit button', async () => { - post.mockResolvedValue({ ok: true, status: 201, data: { id: 1 }, error: null }) - patch.mockResolvedValue({ ok: true, status: 200, data: { id: 1 }, error: null }) + post.mockResolvedValue({ + ok: true, + status: 201, + data: { id: 1 }, + error: null, + }) + patch.mockResolvedValue({ + ok: true, + status: 200, + data: { id: 1 }, + error: null, + }) render( { }) it('submits form with Cmd/Ctrl + Enter from inside textarea', async () => { - post.mockResolvedValue({ ok: true, status: 201, data: { id: 1 }, error: null }) - patch.mockResolvedValue({ ok: true, status: 200, data: { id: 1 }, error: null }) + post.mockResolvedValue({ + ok: true, + status: 201, + data: { id: 1 }, + error: null, + }) + patch.mockResolvedValue({ + ok: true, + status: 200, + data: { id: 1 }, + error: null, + }) render( { }) it('resets form fields after successful submission', async () => { - post.mockResolvedValue({ ok: true, status: 201, data: { id: 1 }, error: null }) - patch.mockResolvedValue({ ok: true, status: 200, data: { id: 1 }, error: null }) + post.mockResolvedValue({ + ok: true, + status: 201, + data: { id: 1 }, + error: null, + }) + patch.mockResolvedValue({ + ok: true, + status: 200, + data: { id: 1 }, + error: null, + }) render( { it('does not reset form on failed feedback submission', async () => { const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}) - post.mockResolvedValue({ ok: false, status: 500, data: null, error: new Error('Server error') }) + post.mockResolvedValue({ + ok: false, + status: 500, + data: null, + error: new Error('Server error'), + }) render( { it('logs an error if feedbackPost returns non-ok status', async () => { const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}) - post.mockResolvedValue({ ok: false, status: 500, data: null, error: new Error('Server error') }) + post.mockResolvedValue({ + ok: false, + status: 500, + data: null, + error: new Error('Server error'), + }) render( @@ -483,7 +548,12 @@ describe('Feedback Form tests', () => { it('logs error if API returns error', async () => { const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}) - post.mockResolvedValue({ ok: false, status: 500, data: null, error: new Error('Network down') }) + post.mockResolvedValue({ + ok: false, + status: 500, + data: null, + error: new Error('Network down'), + }) render( @@ -524,8 +594,18 @@ describe('Feedback Form tests', () => { }) it('submits minimal fields when isGeneralFeedback is false', async () => { - post.mockResolvedValue({ ok: true, status: 201, data: { id: 1 }, error: null }) - patch.mockResolvedValue({ ok: true, status: 200, data: { id: 1 }, error: null }) + post.mockResolvedValue({ + ok: true, + status: 201, + data: { id: 1 }, + error: null, + }) + patch.mockResolvedValue({ + ok: true, + status: 200, + data: { id: 1 }, + error: null, + }) render( { fireEvent.click(screen.getByRole('button', { name: /send feedback/i })) await waitFor(() => { - expect(patch).toHaveBeenCalledWith( - expect.any(String), - { - attachments: [], - component: 'data-file-submission', - feedback_type: 'fra_submission_feedback', - page_url: 'http://localhost/', - widget_id: 'unknown-submission-feedback', - rating: 5, - feedback: '', // comment left blank - anonymous: false, // anonymous checkbox hidden - } - ) + expect(patch).toHaveBeenCalledWith(expect.any(String), { + attachments: [], + component: 'data-file-submission', + feedback_type: 'fra_submission_feedback', + page_url: 'http://localhost/', + widget_id: 'unknown-submission-feedback', + rating: 5, + feedback: '', // comment left blank + anonymous: false, // anonymous checkbox hidden + }) expect(mockOnFeedbackSubmit).toHaveBeenCalled() }) }) diff --git a/tdrs-frontend/src/components/FeedbackReports/FeedbackReports.test.js b/tdrs-frontend/src/components/FeedbackReports/FeedbackReports.test.js index bdff421bd..1584b3ac8 100644 --- a/tdrs-frontend/src/components/FeedbackReports/FeedbackReports.test.js +++ b/tdrs-frontend/src/components/FeedbackReports/FeedbackReports.test.js @@ -36,7 +36,12 @@ describe('FeedbackReports', () => { jest.clearAllMocks() // Mock successful history fetch by default - get.mockResolvedValue({ data: { results: [] }, ok: true, status: 200, error: null }) + get.mockResolvedValue({ + data: { results: [] }, + ok: true, + status: 200, + error: null, + }) // Mock FileReader for async file handling global.FileReader = jest.fn().mockImplementation(() => ({ @@ -166,7 +171,12 @@ describe('FeedbackReports', () => { error: null, }) - get.mockResolvedValue({ data: { results: [] }, ok: true, status: 200, error: null }) + get.mockResolvedValue({ + data: { results: [] }, + ok: true, + status: 200, + error: null, + }) renderComponent() @@ -288,7 +298,13 @@ describe('FeedbackReports', () => { it('shows loading state during upload', async () => { post.mockImplementation( - () => new Promise((resolve) => setTimeout(() => resolve({ data: {}, ok: true, status: 200, error: null }), 100)) + () => + new Promise((resolve) => + setTimeout( + () => resolve({ data: {}, ok: true, status: 200, error: null }), + 100 + ) + ) ) renderComponent() @@ -336,7 +352,12 @@ describe('FeedbackReports', () => { }, ] - get.mockResolvedValue({ data: { results: mockHistory }, ok: true, status: 200, error: null }) + get.mockResolvedValue({ + data: { results: mockHistory }, + ok: true, + status: 200, + error: null, + }) renderComponent() @@ -351,7 +372,12 @@ describe('FeedbackReports', () => { }) it('displays empty state when no history exists', async () => { - get.mockResolvedValue({ data: { results: [] }, ok: true, status: 200, error: null }) + get.mockResolvedValue({ + data: { results: [] }, + ok: true, + status: 200, + error: null, + }) renderComponent() @@ -362,7 +388,19 @@ describe('FeedbackReports', () => { it('displays loading state while fetching history', () => { get.mockImplementation( - () => new Promise((resolve) => setTimeout(() => resolve({ data: { results: [] }, ok: true, status: 200, error: null }), 100)) + () => + new Promise((resolve) => + setTimeout( + () => + resolve({ + data: { results: [] }, + ok: true, + status: 200, + error: null, + }), + 100 + ) + ) ) renderComponent() @@ -371,7 +409,12 @@ describe('FeedbackReports', () => { }) it('displays error alert when history fetch fails', async () => { - get.mockResolvedValue({ data: null, ok: false, status: 500, error: new Error('Failed to fetch') }) + get.mockResolvedValue({ + data: null, + ok: false, + status: 500, + error: new Error('Failed to fetch'), + }) renderComponent() @@ -404,7 +447,12 @@ describe('FeedbackReports', () => { }, ] - get.mockResolvedValue({ data: { results: mockHistory }, ok: true, status: 200, error: null }) + get.mockResolvedValue({ + data: { results: mockHistory }, + ok: true, + status: 200, + error: null, + }) renderComponent() @@ -435,8 +483,18 @@ describe('FeedbackReports', () => { // Initial fetch returns empty, all subsequent calls return mockHistory get - .mockResolvedValueOnce({ data: { results: [] }, ok: true, status: 200, error: null }) // Initial fetch - .mockResolvedValue({ data: { results: mockHistory }, ok: true, status: 200, error: null }) // All subsequent calls (including after upload) + .mockResolvedValueOnce({ + data: { results: [] }, + ok: true, + status: 200, + error: null, + }) // Initial fetch + .mockResolvedValue({ + data: { results: mockHistory }, + ok: true, + status: 200, + error: null, + }) // All subsequent calls (including after upload) renderComponent() @@ -485,7 +543,12 @@ describe('FeedbackReports', () => { }, ] - get.mockResolvedValue({ data: { results: mockHistory }, ok: true, status: 200, error: null }) + get.mockResolvedValue({ + data: { results: mockHistory }, + ok: true, + status: 200, + error: null, + }) renderComponent() @@ -509,7 +572,12 @@ describe('FeedbackReports', () => { }, ] - get.mockResolvedValue({ data: { results: mockHistory }, ok: true, status: 200, error: null }) + get.mockResolvedValue({ + data: { results: mockHistory }, + ok: true, + status: 200, + error: null, + }) renderComponent() @@ -582,7 +650,12 @@ describe('FeedbackReports', () => { }, ] - get.mockResolvedValue({ data: { results: mockHistory }, ok: true, status: 200, error: null }) + get.mockResolvedValue({ + data: { results: mockHistory }, + ok: true, + status: 200, + error: null, + }) renderComponent() @@ -606,7 +679,12 @@ describe('FeedbackReports', () => { }, ] - get.mockResolvedValue({ data: { results: mockHistory }, ok: true, status: 200, error: null }) + get.mockResolvedValue({ + data: { results: mockHistory }, + ok: true, + status: 200, + error: null, + }) renderComponent() diff --git a/tdrs-frontend/src/components/Reports/FRAReports.test.js b/tdrs-frontend/src/components/Reports/FRAReports.test.js index 5a498e589..27ef63e2d 100644 --- a/tdrs-frontend/src/components/Reports/FRAReports.test.js +++ b/tdrs-frontend/src/components/Reports/FRAReports.test.js @@ -42,7 +42,18 @@ describe('FRA Reports Page', () => { beforeEach(() => { jest.useFakeTimers() get.mockResolvedValue({ data: [], ok: true, status: 200, error: null }) - post.mockResolvedValue({ data: { id: 1, original_filename: 'test.txt', extension: '.txt', section: 'Active Case Data', quarter: 'Q1' }, ok: true, status: 200, error: null }) + post.mockResolvedValue({ + data: { + id: 1, + original_filename: 'test.txt', + extension: '.txt', + section: 'Active Case Data', + quarter: 'Q1', + }, + ok: true, + status: 200, + error: null, + }) }) afterEach(() => { jest.runOnlyPendingTimers() diff --git a/tdrs-frontend/src/components/Reports/Reports.test.js b/tdrs-frontend/src/components/Reports/Reports.test.js index cfedf7788..84f62b203 100644 --- a/tdrs-frontend/src/components/Reports/Reports.test.js +++ b/tdrs-frontend/src/components/Reports/Reports.test.js @@ -24,7 +24,18 @@ describe('Reports', () => { // Set default mock return values for fetch-instance functions get.mockResolvedValue({ data: [], ok: true, status: 200, error: null }) - post.mockResolvedValue({ data: { id: 1, original_filename: 'test.txt', extension: '.txt', section: 'Active Case Data', quarter: 'Q3' }, ok: true, status: 200, error: null }) + post.mockResolvedValue({ + data: { + id: 1, + original_filename: 'test.txt', + extension: '.txt', + section: 'Active Case Data', + quarter: 'Q3', + }, + ok: true, + status: 200, + error: null, + }) }) afterEach(() => { diff --git a/tdrs-frontend/src/components/RequestAccessForm/RequestAccessForm.test.js b/tdrs-frontend/src/components/RequestAccessForm/RequestAccessForm.test.js index 8427297ff..5dc016243 100644 --- a/tdrs-frontend/src/components/RequestAccessForm/RequestAccessForm.test.js +++ b/tdrs-frontend/src/components/RequestAccessForm/RequestAccessForm.test.js @@ -126,7 +126,12 @@ describe('RequestAccessForm', () => { }) it('dispatches requestAccess when form is valid', async () => { - patch.mockResolvedValue({ data: { first_name: 'Jane', last_name: 'Doe' }, ok: true, status: 200, error: null }) + patch.mockResolvedValue({ + data: { first_name: 'Jane', last_name: 'Doe' }, + ok: true, + status: 200, + error: null, + }) const { store } = setup() // Spy on dispatch @@ -285,7 +290,12 @@ describe('RequestAccessForm', () => { }) it('dispatches updateUserRequest in editMode when data changes', async () => { - patch.mockResolvedValue({ data: { first_name: 'John', last_name: 'Smith' }, ok: true, status: 200, error: null }) + patch.mockResolvedValue({ + data: { first_name: 'John', last_name: 'Smith' }, + ok: true, + status: 200, + error: null, + }) const initialValues = { firstName: 'John', @@ -366,7 +376,12 @@ describe('RequestAccessForm', () => { has_fra_access: false, pending_requests: 1, } - patch.mockResolvedValue({ data: apiUserResponse, ok: true, status: 200, error: null }) + patch.mockResolvedValue({ + data: apiUserResponse, + ok: true, + status: 200, + error: null, + }) const { store } = setup(props, storeOverrides) // Spy on store.dispatch to monitor calls diff --git a/tdrs-frontend/src/fetch-instance.test.js b/tdrs-frontend/src/fetch-instance.test.js index 5261947a7..22331ebfb 100644 --- a/tdrs-frontend/src/fetch-instance.test.js +++ b/tdrs-frontend/src/fetch-instance.test.js @@ -10,7 +10,8 @@ const mockResponse = (body, options = {}) => ({ get: (key) => options.contentType || 'application/json', }, json: () => Promise.resolve(body), - text: () => Promise.resolve(typeof body === 'string' ? body : JSON.stringify(body)), + text: () => + Promise.resolve(typeof body === 'string' ? body : JSON.stringify(body)), blob: () => Promise.resolve(new Blob([JSON.stringify(body)])), }) @@ -117,7 +118,12 @@ describe('fetch-instance', () => { }), body: JSON.stringify({ name: 'test' }), }) - expect(result).toEqual({ data: responseData, error: null, status: 200, ok: true }) + expect(result).toEqual({ + data: responseData, + error: null, + status: 200, + ok: true, + }) }) it('makes a POST request with FormData', async () => { @@ -138,11 +144,14 @@ describe('fetch-instance', () => { await post('/api/create', {}) - expect(fetch).toHaveBeenCalledWith('/api/create', expect.objectContaining({ - headers: expect.objectContaining({ - 'X-CSRFToken': 'my-csrf-token', - }), - })) + expect(fetch).toHaveBeenCalledWith( + '/api/create', + expect.objectContaining({ + headers: expect.objectContaining({ + 'X-CSRFToken': 'my-csrf-token', + }), + }) + ) }) it('handles network errors', async () => { @@ -176,7 +185,12 @@ describe('fetch-instance', () => { }), body: JSON.stringify({ name: 'updated' }), }) - expect(result).toEqual({ data: responseData, error: null, status: 200, ok: true }) + expect(result).toEqual({ + data: responseData, + error: null, + status: 200, + ok: true, + }) }) it('includes CSRF token in PATCH headers', async () => { @@ -185,11 +199,14 @@ describe('fetch-instance', () => { await patch('/api/update/1', {}) - expect(fetch).toHaveBeenCalledWith('/api/update/1', expect.objectContaining({ - headers: expect.objectContaining({ - 'X-CSRFToken': 'patch-csrf-token', - }), - })) + expect(fetch).toHaveBeenCalledWith( + '/api/update/1', + expect.objectContaining({ + headers: expect.objectContaining({ + 'X-CSRFToken': 'patch-csrf-token', + }), + }) + ) }) it('handles network errors', async () => { @@ -215,11 +232,14 @@ describe('fetch-instance', () => { await get('/api/test') expect(faro.api.getTraceContext).toHaveBeenCalled() - expect(fetch).toHaveBeenCalledWith('/api/test', expect.objectContaining({ - headers: expect.objectContaining({ - traceparent: 'test-trace-id', - }), - })) + expect(fetch).toHaveBeenCalledWith( + '/api/test', + expect.objectContaining({ + headers: expect.objectContaining({ + traceparent: 'test-trace-id', + }), + }) + ) }) it('handles faro getTraceContext throwing an error', async () => { From e7e1905cbc85a62f735410ef714427440de2d766 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Mon, 26 Jan 2026 14:44:36 -0600 Subject: [PATCH 016/148] - Update to use s3 storage --- tdrs-backend/tdpservice/settings/cloudgov.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tdrs-backend/tdpservice/settings/cloudgov.py b/tdrs-backend/tdpservice/settings/cloudgov.py index bcb9d2446..52f8f2897 100644 --- a/tdrs-backend/tdpservice/settings/cloudgov.py +++ b/tdrs-backend/tdpservice/settings/cloudgov.py @@ -124,7 +124,7 @@ class CloudGov(Common): "BACKEND": "tdpservice.backends.DataFilesS3Storage", }, "staticfiles": { - "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", + "BACKEND": "tdpservice.backends.StaticFilesS3Storage", }, } AWS_S3_DATAFILES_ACCESS_KEY = s3_datafiles_creds["access_key_id"] From 9b879bfee77e2fc8bdb68af3082f08de08540a15 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Thu, 8 Jan 2026 16:18:22 -0600 Subject: [PATCH 017/148] - Updated NavItem to conditionally use anchor tags or react internal links when appropriate - UPdated header tests to include memory router - Removed usage of fetchstts through components - Only fetch stts on app mount or via stt combobox if they dont exist due to client refresh --- tdrs-frontend/src/App.js | 7 +- .../src/components/Header/Header.jsx | 3 + .../src/components/Header/Header.test.js | 97 ++++++++++++------- .../src/components/NavItem/NavItem.jsx | 16 ++- .../src/components/Profile/Profile.jsx | 13 +-- .../src/components/Reports/FRAReports.jsx | 7 -- .../src/components/Reports/Reports.jsx | 10 +- 7 files changed, 84 insertions(+), 69 deletions(-) diff --git a/tdrs-frontend/src/App.js b/tdrs-frontend/src/App.js index 1c871ac69..0a845acb6 100644 --- a/tdrs-frontend/src/App.js +++ b/tdrs-frontend/src/App.js @@ -5,7 +5,8 @@ import { Alert } from './components/Alert' import Header from './components/Header' import Footer from './components/Footer' import Feedback from './components/Feedback/Feedback' -import { useSelector } from 'react-redux' +import { useDispatch, useSelector } from 'react-redux' +import { fetchSttList } from './actions/sttList' import { useRUM } from './hooks/useRUM' /** @@ -23,6 +24,7 @@ import { useRUM } from './hooks/useRUM' */ function App() { + const dispatch = useDispatch() const user = useSelector((state) => state.auth.user) const { setUserInfo } = useRUM() @@ -30,8 +32,9 @@ function App() { useEffect(() => { if (user) { setUserInfo(user) + dispatch(fetchSttList()) } - }, [user, setUserInfo]) + }, [user, setUserInfo, dispatch]) return ( <> diff --git a/tdrs-frontend/src/components/Header/Header.jsx b/tdrs-frontend/src/components/Header/Header.jsx index b093cce75..03dc934b9 100644 --- a/tdrs-frontend/src/components/Header/Header.jsx +++ b/tdrs-frontend/src/components/Header/Header.jsx @@ -160,6 +160,7 @@ function Header() { pathname={pathname} tabTitle="Admin" href={`${process.env.REACT_APP_BACKEND_HOST}/admin/`} + target="_blank" /> )} {userViewGrafana && ( @@ -167,6 +168,7 @@ function Header() { pathname={pathname} tabTitle="Grafana" href={`${process.env.REACT_APP_BACKEND_HOST}/grafana/`} + target="_blank" /> )} {userViewAlerts && ( @@ -174,6 +176,7 @@ function Header() { pathname={pathname} tabTitle="Alerts" href={`${process.env.REACT_APP_BACKEND_HOST}/alerts/`} + target="_blank" /> )} { it('should have a title link', () => { const store = mockStore(initialState) render( - -
- + + +
+ + ) const title = screen.getByText('TANF Data Portal') @@ -39,9 +42,11 @@ describe('Header', () => { it('should have a navigation link for Welcome', () => { const store = mockStore(initialState) render( - -
- + + +
+ + ) const welcomeLink = screen.getByText('Home') expect(welcomeLink).toBeInTheDocument() @@ -66,9 +71,11 @@ describe('Header', () => { }) render( - -
- + + +
+ + ) const adminLink = screen.queryByText('Admin') expect(adminLink).not.toBeInTheDocument() @@ -81,9 +88,11 @@ describe('Header', () => { }) render( - -
- + + +
+ + ) const welcomeTab = screen.getByText('Home') @@ -97,9 +106,11 @@ describe('Header', () => { }) render( - -
- + + +
+ + ) const dataFilesTab = screen.getByText('TANF Data Files') @@ -110,9 +121,11 @@ describe('Header', () => { it("should add usa-current class to Profile tab when on '/profile'", () => { const store = mockStore(initialState) const { container } = render( - -
- + + +
+ + ) const profileTab = container.querySelector('#profile') @@ -124,9 +137,11 @@ describe('Header', () => { const state = { ...initialState, router: { location: { pathname: '/' } } } const store = mockStore(state) render( - -
- + + +
+ + ) const welcomeTab = screen.getByText('Home') @@ -136,9 +151,11 @@ describe('Header', () => { it('should have secondaryItems when user is logged in', () => { const store = mockStore(initialState) const { container } = render( - -
- + + +
+ + ) const secondaryLinks = container.querySelectorAll( @@ -158,9 +175,11 @@ describe('Header', () => { const store = mockStore(state) const { queryByText } = render( - -
- + + +
+ + ) expect(queryByText('Welcome')).not.toBeInTheDocument() @@ -186,9 +205,11 @@ describe('Header', () => { const store = mockStore(state) const { queryByText } = render( - -
- + + +
+ + ) expect(queryByText('TANF Data Files')).not.toBeInTheDocument() @@ -218,9 +239,11 @@ describe('Header', () => { const store = mockStore(state) const { queryByText } = render( - -
- + + +
+ + ) expect(queryByText('TANF Data Files')).not.toBeInTheDocument() @@ -232,9 +255,11 @@ describe('Header', () => { const store = mockStore(initialState) const { queryByText } = render( - -
- + + +
+ + ) expect(queryByText('TANF Data Files')).toBeInTheDocument() diff --git a/tdrs-frontend/src/components/NavItem/NavItem.jsx b/tdrs-frontend/src/components/NavItem/NavItem.jsx index e4a444fc8..6202d7529 100644 --- a/tdrs-frontend/src/components/NavItem/NavItem.jsx +++ b/tdrs-frontend/src/components/NavItem/NavItem.jsx @@ -1,5 +1,14 @@ import React from 'react' import PropTypes from 'prop-types' +import { Link } from 'react-router-dom' + +function Anchor({ to, children, ...props }) { + return ( + + {children} + + ) +} /** * @@ -11,10 +20,11 @@ import PropTypes from 'prop-types' * @param {string} target - the target for which to open the link (default: "_self") */ function NavItem({ pathname, tabTitle, href, target }) { + const LinkComponent = href.startsWith('http') ? Anchor : Link return (
  • - data-files) id={tabTitle.replace(/ /g, '-').toLowerCase()} @@ -23,7 +33,7 @@ function NavItem({ pathname, tabTitle, href, target }) { target={target ? target : '_self'} > {tabTitle} - +
  • ) } diff --git a/tdrs-frontend/src/components/Profile/Profile.jsx b/tdrs-frontend/src/components/Profile/Profile.jsx index d8bb22b5a..d4b7270d2 100644 --- a/tdrs-frontend/src/components/Profile/Profile.jsx +++ b/tdrs-frontend/src/components/Profile/Profile.jsx @@ -1,5 +1,5 @@ import React, { useEffect } from 'react' -import { useDispatch, useSelector } from 'react-redux' +import { useSelector } from 'react-redux' import { Navigate } from 'react-router-dom' import RequestAccessForm from '../RequestAccessForm/RequestAccessForm' @@ -10,7 +10,6 @@ import { accountIsMissingAccessRequest, } from '../../selectors/auth' import { JURISDICTION_TYPES } from './JurisdictionLocationInfo' -import { fetchSttList } from '../../actions/sttList' function Profile({ isEditing = false, @@ -21,7 +20,6 @@ function Profile({ onCancel, setInEditMode, }) { - const dispatch = useDispatch() const isAMSUser = user?.email?.includes('@acf.hhs.gov') const userPermissions = user?.permissions?.map((p) => p.codename) || [] const hasFRAAccess = userPermissions.includes('has_fra_access') @@ -30,15 +28,6 @@ function Profile({ const isAccessRequestPending = useSelector(accountIsInReview) const isProfileChangePending = useSelector(accountHasPendingProfileChange) - // Fetch STT list if not already loaded (needed for profile editing) - // TODO: Remove this useEffect when the NavItem component no longer uses anchor tags and instead uses React Link - // components. - useEffect(() => { - if (sttList?.length === 0) { - dispatch(fetchSttList()) - } - }, [dispatch, sttList]) - useEffect(() => { if (setInEditMode) { setInEditMode(isEditing, type) diff --git a/tdrs-frontend/src/components/Reports/FRAReports.jsx b/tdrs-frontend/src/components/Reports/FRAReports.jsx index d4255e17d..b28cda564 100644 --- a/tdrs-frontend/src/components/Reports/FRAReports.jsx +++ b/tdrs-frontend/src/components/Reports/FRAReports.jsx @@ -32,7 +32,6 @@ import { getFraSubmissionStatus, SET_FRA_SUBMISSION_STATUS, } from '../../actions/fraReports' -import { fetchSttList } from '../../actions/sttList' import { RadioSelect } from '../Form' import { PaginatedComponent } from '../Paginator/Paginator' import { Spinner } from '../Spinner' @@ -777,12 +776,6 @@ const FRAReportsContent = () => { } }, [allFieldsFilled, headerRef]) - useEffect(() => { - if (sttList && sttList.length === 0) { - dispatch(fetchSttList()) - } - }, [dispatch, sttList]) - useEffect(() => { if (localAlert.active && alertRef && alertRef.current) { alertRef.current.scrollIntoView({ behavior: 'smooth' }) diff --git a/tdrs-frontend/src/components/Reports/Reports.jsx b/tdrs-frontend/src/components/Reports/Reports.jsx index e82577238..c64c40083 100644 --- a/tdrs-frontend/src/components/Reports/Reports.jsx +++ b/tdrs-frontend/src/components/Reports/Reports.jsx @@ -1,9 +1,8 @@ import React, { useEffect, useRef } from 'react' -import { useDispatch, useSelector } from 'react-redux' +import { useSelector } from 'react-redux' import classNames from 'classnames' import STTComboBox from '../STTComboBox' -import { fetchSttList } from '../../actions/sttList' import Modal from '../Modal' import ReprocessedModal from '../SubmissionHistory/ReprocessedModal' import { @@ -34,7 +33,6 @@ function ReportsContent() { getFileTypeError, } = useReportsContext() - const dispatch = useDispatch() const user = useSelector((state) => state.auth.user) const isOFAAdmin = useSelector(selectPrimaryUserRole)?.name === 'OFA Admin' const isDIGITTeam = useSelector(selectPrimaryUserRole)?.name === 'DIGIT Team' @@ -55,12 +53,6 @@ function ReportsContent() { } }, []) - useEffect(() => { - if (sttList.length === 0) { - dispatch(fetchSttList()) - } - }, [dispatch, sttList]) - const redux_stt = useSelector((state) => state.reports.stt) const currentStt = From 1028f1a8a4dc640e8c1bd79594941ab928e5a34b Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Thu, 8 Jan 2026 16:32:28 -0600 Subject: [PATCH 018/148] - Update other extraneous anchor tags to be react links where possible --- tdrs-frontend/src/components/Header/Header.jsx | 9 +++++---- tdrs-frontend/src/components/NoMatch/NoMatch.jsx | 5 +++-- tdrs-frontend/src/components/NoMatch/NoMatch.test.js | 9 ++++++--- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/tdrs-frontend/src/components/Header/Header.jsx b/tdrs-frontend/src/components/Header/Header.jsx index 03dc934b9..e36b476af 100644 --- a/tdrs-frontend/src/components/Header/Header.jsx +++ b/tdrs-frontend/src/components/Header/Header.jsx @@ -1,5 +1,6 @@ import React, { useEffect, useMemo, useRef } from 'react' import { useSelector } from 'react-redux' +import { Link } from 'react-router-dom' import closeIcon from '@uswds/uswds/img/usa-icons/close.svg' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faSignOutAlt, faUserCircle } from '@fortawesome/free-solid-svg-icons' @@ -92,9 +93,9 @@ function Header() {
    {authenticated && ( @@ -194,13 +195,13 @@ function Header() { className={`${user && user.email ? 'display-block' : 'display-none'} usa-nav__secondary-item`} > {user && user.email && ( - + {user && user.email} - + )} {authenticated && ( diff --git a/tdrs-frontend/src/components/NoMatch/NoMatch.jsx b/tdrs-frontend/src/components/NoMatch/NoMatch.jsx index 640a56f2e..262b3555f 100644 --- a/tdrs-frontend/src/components/NoMatch/NoMatch.jsx +++ b/tdrs-frontend/src/components/NoMatch/NoMatch.jsx @@ -1,4 +1,5 @@ import React, { useEffect, useRef } from 'react' +import { Link } from 'react-router-dom' /** This component renders a message signaling to the user that this * page does not exist. @@ -40,9 +41,9 @@ export default function NoMatch() {
    diff --git a/tdrs-frontend/src/components/ResourceCards/ResourceCards.jsx b/tdrs-frontend/src/components/ResourceCards/ResourceCards.jsx index 1a2fa3508..3b389dd00 100644 --- a/tdrs-frontend/src/components/ResourceCards/ResourceCards.jsx +++ b/tdrs-frontend/src/components/ResourceCards/ResourceCards.jsx @@ -1,4 +1,5 @@ import Button from '../Button' +import LinkComponent from '../Link' function Card({ title, body, buttonId, link, linkText }) { return ( @@ -30,9 +31,12 @@ function ResourceCards() {

    Questions about TANF data?

    Email:{' '} - + tanfdata@acf.hhs.gov - +

    diff --git a/tdrs-frontend/src/components/SplashPage/SplashPage.jsx b/tdrs-frontend/src/components/SplashPage/SplashPage.jsx index 647f6e762..82a5deb71 100644 --- a/tdrs-frontend/src/components/SplashPage/SplashPage.jsx +++ b/tdrs-frontend/src/components/SplashPage/SplashPage.jsx @@ -7,6 +7,7 @@ import { setMockLoginState } from '../../actions/auth' import loginLogo from '../../assets/login-gov-logo.svg' import Button from '../Button' import ResourceCards from '../ResourceCards' +import LinkComponent from '../Link' /** * SplashPage renders the Welcome page for the TANF Data Portal @@ -82,9 +83,12 @@ function SplashPage() {

    Please email{' '} - + tanfdata@acf.hhs.gov - {' '} + {' '} to reactivate your account.

    From eb2797eb0bcc251d7ca27496e564e817fc19edc6 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Tue, 21 Oct 2025 19:03:25 -0600 Subject: [PATCH 020/148] Add sprint summary report for 2025-10-21 --- sprint-summary-2025-10-21.md | 250 +++++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 sprint-summary-2025-10-21.md diff --git a/sprint-summary-2025-10-21.md b/sprint-summary-2025-10-21.md new file mode 100644 index 000000000..73fd2799b --- /dev/null +++ b/sprint-summary-2025-10-21.md @@ -0,0 +1,250 @@ +# Sprint Summary: Oct 08, 2025 - Oct 21, 2025 + +⚪️ **Total Issues:** 53 +✅ **Closed:** 8 +➡️ **Moved:** 20 +⬛️ **Unchanged:** 17 +🛑 **Blocked:** 8 + +--- + +## [KC NavBar Item](https://github.com/raft-tech/TANF-app/issues/5428) + +- ➡️ [KC NavBar Item (#5428)](https://github.com/raft-tech/TANF-app/issues/5428) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + + +## [Update Research Protocol for In App Error Reporting Prototype](https://github.com/raft-tech/TANF-app/issues/5423) + +- ➡️ [Update Research Protocol for In App Error Reporting Prototype (#5423)](https://github.com/raft-tech/TANF-app/issues/5423) +_Moved from **Next Sprint Backlog** to **In Progress**_ + + +## [Refresh Tolerant Data Files Pages](https://github.com/raft-tech/TANF-app/issues/5418) + +- ➡️ [Refresh Tolerant Data Files Pages (#5418)](https://github.com/raft-tech/TANF-app/issues/5418) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + + +## [Reorganize the parser](https://github.com/raft-tech/TANF-app/issues/5414) + +- ➡️ [Reorganize the parser (#5414)](https://github.com/raft-tech/TANF-app/issues/5414) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + + +## [Prod Celery App Memory Bump](https://github.com/raft-tech/TANF-app/issues/5413) + +- ➡️ [Prod Celery App Memory Bump (#5413)](https://github.com/raft-tech/TANF-app/issues/5413) +_Moved from **In Progress** to **Raft (Dev) Review**_ + + +## [Enable and fix a11y e2e tests](https://github.com/raft-tech/TANF-app/issues/5407) + +- ➡️ [Enable and fix a11y e2e tests (#5407)](https://github.com/raft-tech/TANF-app/issues/5407) +_Moved from **Raft (Dev) Review** to **Next Sprint Backlog**_ + + +## [Write KC Content for PI Audit and Centralized Feedback Reports](https://github.com/raft-tech/TANF-app/issues/5406) + +- ⬛️ [Write KC Content for PI Audit and Centralized Feedback Reports (#5406)](https://github.com/raft-tech/TANF-app/issues/5406) +_Remained in **Next Sprint Backlog**_ + + +## [(Spike) Knowledge Center Enhancements: Dynamic Navigation and Easier Information Access](https://github.com/raft-tech/TANF-app/issues/5403) + +- ➡️ [(Spike) Knowledge Center Enhancements: Dynamic Navigation and Easier Information Access (#5403)](https://github.com/raft-tech/TANF-app/issues/5403) +_Moved from **In Progress** to **UX Review**_ + + +## [Add alert banner to knowledge center regarding government shutdown](https://github.com/raft-tech/TANF-app/issues/5398) + +- 🛑 [Add alert banner to knowledge center regarding government shutdown (#5398)](https://github.com/raft-tech/TANF-app/issues/5398) +_Remained in **Blocked**_ + + +## [Feedback Reports Backend](https://github.com/raft-tech/TANF-app/issues/5397) + +- ➡️ [Feedback Reports Backend (#5397)](https://github.com/raft-tech/TANF-app/issues/5397) +_Moved from **Current Sprint Backlog** to **In Progress**_ + + +## [Update Knowledge Center with Program Integrity Audit and Centralized Feedback Reports](https://github.com/raft-tech/TANF-app/issues/5391) + +- ⬛️ [Update Knowledge Center with Program Integrity Audit and Centralized Feedback Reports (#5391)](https://github.com/raft-tech/TANF-app/issues/5391) +_Remained in **Next Sprint Backlog**_ + + +## [Admin Feedback Reports UI](https://github.com/raft-tech/TANF-app/issues/5390) + +- ⬛️ [Admin Feedback Reports UI (#5390)](https://github.com/raft-tech/TANF-app/issues/5390) +_Remained in **Next Sprint Backlog**_ + + +## [Read Only Datasources](https://github.com/raft-tech/TANF-app/issues/5388) + +- ➡️ [Read Only Datasources (#5388)](https://github.com/raft-tech/TANF-app/issues/5388) +_Moved from **In Progress** to **Raft (Dev) Review**_ + + +## [Migrate from In-Memory to Database-Based Duplicate Detection for Program Integrity Audit Requirements](https://github.com/raft-tech/TANF-app/issues/5387) + +- ✅ [Migrate from In-Memory to Database-Based Duplicate Detection for Program Integrity Audit Requirements (#5387)](https://github.com/raft-tech/TANF-app/issues/5387) +_**Closed**_ - _Moved from **Raft (Dev) Review**_ + + +## [Program Integrity Audit](https://github.com/raft-tech/TANF-app/issues/5356) + +- ➡️ [Frontend Program Integrity Audit Submissions (#5345)](https://github.com/raft-tech/TANF-app/issues/5345) +_Moved from **In Progress** to **Raft (Dev) Review**_ + +- ➡️ [Backend Program Integrity Audit Schemas, Models, & Parser (#5346)](https://github.com/raft-tech/TANF-app/issues/5346) +_Moved from **In Progress** to **Raft (Dev) Review**_ + +- ⬛️ [[Design Deliverable] Email notification for confirming program integrity audit submissions (#5363)](https://github.com/raft-tech/TANF-app/issues/5363) +_Remained in **QASP Review**_ + + +## [Add functionality for users to update STT when in Access Request state](https://github.com/raft-tech/TANF-app/issues/5317) + +- ✅ [Add functionality for users to update STT when in Access Request state (#5317)](https://github.com/raft-tech/TANF-app/issues/5317) +_**Closed**_ - _Moved from **Current Sprint Backlog**_ + + +## [Develop E2E tests for Editing Profile/Access Request](https://github.com/raft-tech/TANF-app/issues/5316) + +- ➡️ [Develop E2E tests for Editing Profile/Access Request (#5316)](https://github.com/raft-tech/TANF-app/issues/5316) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + + +## [In-App Error Reporting - Foundational Design & Concept Validation](https://github.com/raft-tech/TANF-app/issues/4629) + +- ⬛️ [Plan research for in-app error reporting interface (#4721)](https://github.com/raft-tech/TANF-app/issues/4721) +_Remained in **QASP Review**_ + +- ➡️ [Complete In App Error Reports HTML Prototypes (#5300)](https://github.com/raft-tech/TANF-app/issues/5300) +_Moved from **UX Review** to **QASP Review**_ + + +## [fTANF Replacement - Foundational Research & Concept Validation](https://github.com/raft-tech/TANF-app/issues/4628) + +- ➡️ [Plan initial research for FTANF replacement (#4989)](https://github.com/raft-tech/TANF-app/issues/4989) +_Moved from **UX Review** to **QASP Review**_ + + +## [Continuous User Feedback & Research Loops (CFL)](https://github.com/raft-tech/TANF-app/issues/4614) + +- ➡️ [Add metadata to feedback submissions to support contextual analysis + a11y fixes (#5197)](https://github.com/raft-tech/TANF-app/issues/5197) +_Moved from **Raft (Dev) Review** to **UX Review**_ + + +## [Operations & Maintenance](https://github.com/raft-tech/TANF-app/issues/4445) + +- ➡️ [Switch to React Router links for internal navigation (#2008)](https://github.com/raft-tech/TANF-app/issues/2008) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + +- ➡️ [Implement user deletion support in DAC while retaining associated objects (#3089)](https://github.com/raft-tech/TANF-app/issues/3089) +_Moved from **Next Sprint Backlog** to **In Progress**_ + +- ⬛️ [Implement Login.gov SET Tech Memo (#5268)](https://github.com/raft-tech/TANF-app/issues/5268) +_Remained in **Raft (Dev) Review**_ + +- ✅ [Improve Cypress E2E reporting and artifacts in CircleCI (#5308)](https://github.com/raft-tech/TANF-app/issues/5308) +_**Closed**_ - _Moved from **Raft (Dev) Review**_ + +- ✅ [Implement additional SSN validation for federally-funded TANF recipients (#5333)](https://github.com/raft-tech/TANF-app/issues/5333) +_**Closed**_ - _Moved from **Raft (Dev) Review**_ + + +## [User Experience Enhancements](https://github.com/raft-tech/TANF-app/issues/4444) + +- ⬛️ [Implement automated past due submission email notifications (#2984)](https://github.com/raft-tech/TANF-app/issues/2984) +_Remained in **Next Sprint Backlog**_ + +- ⬛️ [Update email templates for STT data submissions re: status and error guidance (#3251)](https://github.com/raft-tech/TANF-app/issues/3251) +_Remained in **QASP Review**_ + +- ➡️ [Create email templates for reparsing (new error report & error resolution) (#3263)](https://github.com/raft-tech/TANF-app/issues/3263) +_Moved from **UX Review** to **QASP Review**_ + +- ⬛️ [Implement spinner for TANF / SSP data file submissions (#3564)](https://github.com/raft-tech/TANF-app/issues/3564) +_Remained in **Current Sprint Backlog**_ + +- ⬛️ [Create research plan template for UX Playbook (#5369)](https://github.com/raft-tech/TANF-app/issues/5369) +_Remained in **QASP Review**_ + +- ⬛️ [Create a Journey Mapping template & document best practices (#5371)](https://github.com/raft-tech/TANF-app/issues/5371) +_Remained in **QASP Review**_ + +- ⬛️ [Create a QA Checklist for Completing UX & Dev Reviews (#5374)](https://github.com/raft-tech/TANF-app/issues/5374) +_Remained in **QASP Review**_ + +- ⬛️ [Create a guide for Conducting a Heuristic Evaluation (Checklist + Best Practices) (#5375)](https://github.com/raft-tech/TANF-app/issues/5375) +_Remained in **Next Sprint Backlog**_ + + +## [FRA Post-MVP Enhancements](https://github.com/raft-tech/TANF-app/issues/4443) + +- ⬛️ [Design FRA successful processing email template (#3485)](https://github.com/raft-tech/TANF-app/issues/3485) +_Remained in **Ready to merge into staging branch**_ + +- ✅ [Implement FRA submission email template (#3486)](https://github.com/raft-tech/TANF-app/issues/3486) +_**Closed**_ - _Moved from **Raft (Dev) Review**_ + +- ✅ [Implement Edit Profile page with shared Request Access logic (#4973)](https://github.com/raft-tech/TANF-app/issues/4973) +_**Closed**_ - _Moved from **UX Review**_ + + +## [Bug Reports](https://github.com/raft-tech/TANF-app/issues/4441) + +- ➡️ [Bug: Uncaught exception when sorting Data Files by ID, STT, or version in DAC (#3356)](https://github.com/raft-tech/TANF-app/issues/3356) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + +- ⬛️ [Bug: OFA system admin users get stuck if region is assigned (#3515)](https://github.com/raft-tech/TANF-app/issues/3515) +_Remained in **Raft (Dev) Review**_ + +- ⬛️ [[BUG] User STT and Region assignment validation on user group change (#5368)](https://github.com/raft-tech/TANF-app/issues/5368) +_Remained in **In Progress**_ + + +## [[BLOCKED] Regional Staff TDP Access & Onboarding (RSAO)](https://github.com/raft-tech/TANF-app/issues/4395) + +- 🛑 [Create and facilitate Project Updates meeting for regional staff (#3461)](https://github.com/raft-tech/TANF-app/issues/3461) +_Remained in **Blocked**_ + +- 🛑 [Create and facilitate optional training session for regional staff (#3462)](https://github.com/raft-tech/TANF-app/issues/3462) +_Remained in **Blocked**_ + +- ✅ [Update Profile component to display regional user's assigned regions (#3475)](https://github.com/raft-tech/TANF-app/issues/3475) +_**Closed**_ - _Moved from **Next Sprint Backlog**_ + +- 🛑 [Refine research plan for regional staff MVP onboarding experience (#3523)](https://github.com/raft-tech/TANF-app/issues/3523) +_Remained in **Blocked**_ + +- ➡️ [Restrict regional staff FRA submission history to participating STTs (#3571)](https://github.com/raft-tech/TANF-app/issues/3571) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + +- 🛑 [Gather and iterate on OFA feedback (#3995)](https://github.com/raft-tech/TANF-app/issues/3995) +_Remained in **Blocked**_ + +- 🛑 [Gather final OFA feedback and iterate (#4045)](https://github.com/raft-tech/TANF-app/issues/4045) +_Remained in **Blocked**_ + +- 🛑 [Develop training materials, including slides, written instructions, and screenshots or screen recordings for visual guidance (#4047)](https://github.com/raft-tech/TANF-app/issues/4047) +_Remained in **Blocked**_ + +- ⬛️ [Iterate on training materials internally (#4052)](https://github.com/raft-tech/TANF-app/issues/4052) +_Remained in **In Progress**_ + + +## [Enabling Secure Data Access for DIGIT in Grafana (SDA)](https://github.com/raft-tech/TANF-app/issues/4339) + +- 🛑 [Bump Grafana memory in prod by 4GB to support 1.5M record visualizations (#5269)](https://github.com/raft-tech/TANF-app/issues/5269) +_Remained in **Blocked**_ + + +## [Application Health Monitoring (AHM)](https://github.com/raft-tech/TANF-app/issues/3587) + +- ✅ [Implement Sentry (#5381)](https://github.com/raft-tech/TANF-app/issues/5381) +_**Closed**_ - _Moved from **In Progress**_ + + From e2a7473f0cbb488623f5149aea3d662c977c313c Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 7 Nov 2025 16:00:47 -0700 Subject: [PATCH 021/148] Add sprint summary for 2025-11-04 --- sprint-summary-2025-11-04.md | 199 +++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 sprint-summary-2025-11-04.md diff --git a/sprint-summary-2025-11-04.md b/sprint-summary-2025-11-04.md new file mode 100644 index 000000000..651810bcb --- /dev/null +++ b/sprint-summary-2025-11-04.md @@ -0,0 +1,199 @@ +# Sprint Summary: Oct 22, 2025 - Nov 04, 2025 + +⚪️ **Total Issues:** 56 +✅ **Closed:** 10 +➡️ **Moved:** 19 +⬛️ **Unchanged:** 19 +🛑 **Blocked:** 8 + +--- + +## [Goal 1: Grantees can easily submit data](https://github.com/raft-tech/TANF-app/issues/5447) + +- ⬛️ [Design FRA successful processing email template (#3485)](https://github.com/raft-tech/TANF-app/issues/3485) +_Remained in **Ready to merge into staging branch**_ + +- ➡️ [Update minimum year in FRA reporting dropdown (#3500)](https://github.com/raft-tech/TANF-app/issues/3500) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + +- ⬛️ [Plan initial research for FTANF replacement (#4989)](https://github.com/raft-tech/TANF-app/issues/4989) +_Remained in **QASP Review**_ + +- ➡️ [Design ideation for post-MVP centralized feedback reports (#5223)](https://github.com/raft-tech/TANF-app/issues/5223) +_Moved from **Product Backlog** to **In Progress**_ + +- ✅ [Frontend Program Integrity Audit Submissions (#5345)](https://github.com/raft-tech/TANF-app/issues/5345) +_**Closed**_ - _Moved from **Raft (Dev) Review**_ + +- ✅ [Backend Program Integrity Audit Schemas, Models, & Parser (#5346)](https://github.com/raft-tech/TANF-app/issues/5346) +_**Closed**_ - _Moved from **Raft (Dev) Review**_ + +- ⬛️ [[Design Deliverable] Email notification for confirming program integrity audit submissions (#5363)](https://github.com/raft-tech/TANF-app/issues/5363) +_Remained in **QASP Review**_ + +- ➡️ [Enable and fix a11y e2e tests (#5407)](https://github.com/raft-tech/TANF-app/issues/5407) +_Moved from **Next Sprint Backlog** to **In Progress**_ + +- ✅ [Prod Celery App Memory Bump (#5413)](https://github.com/raft-tech/TANF-app/issues/5413) +_**Closed**_ - _Moved from **Raft (Dev) Review**_ + +- ➡️ [Requirements Gathering and User Stories for Post-MVP Centralized Feedback Reports (#5443)](https://github.com/raft-tech/TANF-app/issues/5443) +_Moved from **Product Backlog** to **In Progress**_ + + +## [Goal 2: Grantees are confident about reporting compliance](https://github.com/raft-tech/TANF-app/issues/5448) + +- ⬛️ [Plan research for in-app error reporting interface (#4721)](https://github.com/raft-tech/TANF-app/issues/4721) +_Remained in **QASP Review**_ + +- ⬛️ [Complete In App Error Reports HTML Prototypes (#5300)](https://github.com/raft-tech/TANF-app/issues/5300) +_Remained in **QASP Review**_ + +- ➡️ [Update Research Protocol for In App Error Reporting Prototype (#5423)](https://github.com/raft-tech/TANF-app/issues/5423) +_Moved from **In Progress** to **UX Review**_ + + +## [Goal 3: Reduce the burden on users](https://github.com/raft-tech/TANF-app/issues/5449) + +- ✅ [Add metadata to feedback submissions to support contextual analysis + a11y fixes (#5197)](https://github.com/raft-tech/TANF-app/issues/5197) +_**Closed**_ - _Moved from **UX Review**_ + +- 🛑 [Bump Grafana memory in prod by 4GB to support 1.5M record visualizations (#5269)](https://github.com/raft-tech/TANF-app/issues/5269) +_Remained in **Blocked**_ + +- ✅ [Read Only Datasources (#5388)](https://github.com/raft-tech/TANF-app/issues/5388) +_**Closed**_ - _Moved from **Raft (Dev) Review**_ + +- ➡️ [Feedback module - submit when score is selected (rather than when "submit" is clicked) (#5431)](https://github.com/raft-tech/TANF-app/issues/5431) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + + +## [Goal 4: Free up staff time](https://github.com/raft-tech/TANF-app/issues/5450) + +- 🛑 [Create and facilitate Project Updates meeting for regional staff (#3461)](https://github.com/raft-tech/TANF-app/issues/3461) +_Remained in **Blocked**_ + +- 🛑 [Create and facilitate optional training session for regional staff (#3462)](https://github.com/raft-tech/TANF-app/issues/3462) +_Remained in **Blocked**_ + +- 🛑 [Refine research plan for regional staff MVP onboarding experience (#3523)](https://github.com/raft-tech/TANF-app/issues/3523) +_Remained in **Blocked**_ + +- ✅ [Restrict regional staff FRA submission history to participating STTs (#3571)](https://github.com/raft-tech/TANF-app/issues/3571) +_**Closed**_ - _Moved from **Next Sprint Backlog**_ + +- 🛑 [Gather and iterate on OFA feedback (#3995)](https://github.com/raft-tech/TANF-app/issues/3995) +_Remained in **Blocked**_ + +- 🛑 [Gather final OFA feedback and iterate (#4045)](https://github.com/raft-tech/TANF-app/issues/4045) +_Remained in **Blocked**_ + +- 🛑 [Develop training materials, including slides, written instructions, and screenshots or screen recordings for visual guidance (#4047)](https://github.com/raft-tech/TANF-app/issues/4047) +_Remained in **Blocked**_ + +- ⬛️ [Iterate on training materials internally (#4052)](https://github.com/raft-tech/TANF-app/issues/4052) +_Remained in **In Progress**_ + +- ⬛️ [Develop E2E tests for Editing Profile/Access Request (#5316)](https://github.com/raft-tech/TANF-app/issues/5316) +_Remained in **Next Sprint Backlog**_ + +- ➡️ [Admin Feedback Reports UI (#5390)](https://github.com/raft-tech/TANF-app/issues/5390) +_Moved from **Next Sprint Backlog** to **In Progress**_ + +- ⬛️ [Update Knowledge Center with Program Integrity Audit and Centralized Feedback Reports (#5391)](https://github.com/raft-tech/TANF-app/issues/5391) +_Remained in **Next Sprint Backlog**_ + +- ➡️ [Feedback Reports Backend (#5397)](https://github.com/raft-tech/TANF-app/issues/5397) +_Moved from **In Progress** to **Raft (Dev) Review**_ + +- 🛑 [Add alert banner to knowledge center regarding government shutdown (#5398)](https://github.com/raft-tech/TANF-app/issues/5398) +_Remained in **Blocked**_ + +- ✅ [(Spike) Knowledge Center Enhancements: Dynamic Navigation and Easier Information Access (#5403)](https://github.com/raft-tech/TANF-app/issues/5403) +_**Closed**_ - _Moved from **UX Review**_ + +- ➡️ [Write KC Content for PI Audit and Centralized Feedback Reports (#5406)](https://github.com/raft-tech/TANF-app/issues/5406) +_Moved from **Next Sprint Backlog** to **In Progress**_ + +- ✅ [Refresh Tolerant Data Files Pages (#5418)](https://github.com/raft-tech/TANF-app/issues/5418) +_**Closed**_ - _Moved from **Next Sprint Backlog**_ + +- ➡️ [KC NavBar Item (#5428)](https://github.com/raft-tech/TANF-app/issues/5428) +_Moved from **Next Sprint Backlog** to **In Progress**_ + +- ➡️ [Fully implement Knowledge Center component navigation & search (#5429)](https://github.com/raft-tech/TANF-app/issues/5429) +_Moved from **Product Backlog** to **In Progress**_ + + +## [Bug Reports](https://github.com/raft-tech/TANF-app/issues/4441) + +- ✅ [Bug: Uncaught exception when sorting Data Files by ID, STT, or version in DAC (#3356)](https://github.com/raft-tech/TANF-app/issues/3356) +_**Closed**_ - _Moved from **Next Sprint Backlog**_ + +- ⬛️ [Bug: OFA system admin users get stuck if region is assigned (#3515)](https://github.com/raft-tech/TANF-app/issues/3515) +_Remained in **Raft (Dev) Review**_ + +- ➡️ [Bug: Regional Access Request – Pre-selected option & typo (#4924)](https://github.com/raft-tech/TANF-app/issues/4924) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + +- ➡️ [[BUG] User STT and Region assignment validation on user group change (#5368)](https://github.com/raft-tech/TANF-app/issues/5368) +_Moved from **In Progress** to **Raft (Dev) Review**_ + + +## [Operations & Maintenance](https://github.com/raft-tech/TANF-app/issues/4445) + +- ⬛️ [Switch to React Router links for internal navigation (#2008)](https://github.com/raft-tech/TANF-app/issues/2008) +_Remained in **Next Sprint Backlog**_ + +- ⬛️ [Implement user deletion support in DAC while retaining associated objects (#3089)](https://github.com/raft-tech/TANF-app/issues/3089) +_Remained in **In Progress**_ + +- ➡️ [Add bulk user deactivation and inactivity filter for system admins (#3090)](https://github.com/raft-tech/TANF-app/issues/3090) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + +- ➡️ [Investigate adding Change Message Filter (#3092)](https://github.com/raft-tech/TANF-app/issues/3092) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + +- ➡️ [Fix health dashboard to respect environment filters (#3611)](https://github.com/raft-tech/TANF-app/issues/3611) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + +- ✅ [Implement Login.gov SET Tech Memo (#5268)](https://github.com/raft-tech/TANF-app/issues/5268) +_**Closed**_ - _Moved from **Raft (Dev) Review**_ + +- ⬛️ [Reorganize the parser (#5414)](https://github.com/raft-tech/TANF-app/issues/5414) +_Remained in **Next Sprint Backlog**_ + +- ➡️ [Delete vault integration from backend (#5416)](https://github.com/raft-tech/TANF-app/issues/5416) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + +- ➡️ [Change admin "acked" column to "read" (#5432)](https://github.com/raft-tech/TANF-app/issues/5432) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + + +## [User Experience Enhancements](https://github.com/raft-tech/TANF-app/issues/4444) + +- ⬛️ [Implement automated past due submission email notifications (#2984)](https://github.com/raft-tech/TANF-app/issues/2984) +_Remained in **Next Sprint Backlog**_ + +- ⬛️ [Update email templates for STT data submissions re: status and error guidance (#3251)](https://github.com/raft-tech/TANF-app/issues/3251) +_Remained in **QASP Review**_ + +- ⬛️ [Create email templates for reparsing (new error report & error resolution) (#3263)](https://github.com/raft-tech/TANF-app/issues/3263) +_Remained in **QASP Review**_ + +- ➡️ [Implement spinner for TANF / SSP data file submissions (#3564)](https://github.com/raft-tech/TANF-app/issues/3564) +_Moved from **Current Sprint Backlog** to **In Progress**_ + +- ⬛️ [Create research plan template for UX Playbook (#5369)](https://github.com/raft-tech/TANF-app/issues/5369) +_Remained in **QASP Review**_ + +- ⬛️ [Create a Journey Mapping template & document best practices (#5371)](https://github.com/raft-tech/TANF-app/issues/5371) +_Remained in **QASP Review**_ + +- ⬛️ [Create a QA Checklist for Completing UX & Dev Reviews (#5374)](https://github.com/raft-tech/TANF-app/issues/5374) +_Remained in **QASP Review**_ + +- ⬛️ [Create a guide for Conducting a Heuristic Evaluation (Checklist + Best Practices) (#5375)](https://github.com/raft-tech/TANF-app/issues/5375) +_Remained in **Next Sprint Backlog**_ + + From 3cc76770e9f77b840c5fecc9e532caf2425d989e Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 17 Oct 2025 15:24:12 -0600 Subject: [PATCH 022/148] Sprint summary 2025-10-07 --- .../sprint-summary-2025-10-07.md | 202 ++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 docs/Sprint-Review/sprint-summary-2025-10-07.md diff --git a/docs/Sprint-Review/sprint-summary-2025-10-07.md b/docs/Sprint-Review/sprint-summary-2025-10-07.md new file mode 100644 index 000000000..eb131bb5f --- /dev/null +++ b/docs/Sprint-Review/sprint-summary-2025-10-07.md @@ -0,0 +1,202 @@ +# Sprint Summary: Sep 24, 2025 - Oct 07, 2025 + +⚪️ **Total Issues:** 44 +✅ **Closed:** 8 +➡️ **Moved:** 20 +⬛️ **Unchanged:** 8 +🛑 **Blocked:** 8 + +--- + +## [Write KC Content for PI Audit and Centralized Feedback Reports](https://github.com/raft-tech/TANF-app/issues/5406) + +- ➡️ [#5406](https://github.com/raft-tech/TANF-app/issues/5406) - _Moved from **Product Backlog** to **Next Sprint Backlog**_ +Write KC Content for PI Audit and Centralized Feedback Reports + + +## [(Spike) Knowledge Center Enhancements: Dynamic Navigation and Easier Information Access](https://github.com/raft-tech/TANF-app/issues/5403) + +- ➡️ [#5403](https://github.com/raft-tech/TANF-app/issues/5403) - _Moved from **Next Sprint Backlog** to **Current Sprint Backlog**_ +(Spike) Knowledge Center Enhancements: Dynamic Navigation and Easier Information Access + + +## [Add alert banner to knowledge center regarding government shutdown](https://github.com/raft-tech/TANF-app/issues/5398) + +- 🛑 [#5398](https://github.com/raft-tech/TANF-app/issues/5398) - _Moved from **Current Sprint Backlog** to **Blocked**_ +Add alert banner to knowledge center regarding government shutdown + + +## [Feedback Reports Backend](https://github.com/raft-tech/TANF-app/issues/5397) + +- ➡️ [#5397](https://github.com/raft-tech/TANF-app/issues/5397) - _Moved from **Next Sprint Backlog** to **Current Sprint Backlog**_ +Feedback Reports Backend + + +## [Update Knowledge Center with Program Integrity Audit and Centralized Feedback Reports](https://github.com/raft-tech/TANF-app/issues/5391) + +- ➡️ [#5391](https://github.com/raft-tech/TANF-app/issues/5391) - _Moved from **Product Backlog** to **Next Sprint Backlog**_ +Update Knowledge Center with Program Integrity Audit and Centralized Feedback Reports + + +## [Admin Feedback Reports UI](https://github.com/raft-tech/TANF-app/issues/5390) + +- ➡️ [#5390](https://github.com/raft-tech/TANF-app/issues/5390) - _Moved from **Product Backlog** to **Next Sprint Backlog**_ +Admin Feedback Reports UI + + +## [Read Only Datasources](https://github.com/raft-tech/TANF-app/issues/5388) + +- ➡️ [#5388](https://github.com/raft-tech/TANF-app/issues/5388) - _Moved from **Next Sprint Backlog** to **Current Sprint Backlog**_ +Read Only Datasources + + +## [Migrate from In-Memory to Database-Based Duplicate Detection for Program Integrity Audit Requirements](https://github.com/raft-tech/TANF-app/issues/5387) + +- ✅ [#5387](https://github.com/raft-tech/TANF-app/issues/5387) - _**Closed**_ - _Moved from **Product Backlog**_ +Migrate from In-Memory to Database-Based Duplicate Detection for Program Integrity Audit Requirements + + +## [Program Integrity Audit](https://github.com/raft-tech/TANF-app/issues/5356) + +- ➡️ [#5345](https://github.com/raft-tech/TANF-app/issues/5345) - _Moved from **Product Backlog** to **In Progress**_ +Frontend Program Integrity Audit Submissions + +- ⬛️ [#5346](https://github.com/raft-tech/TANF-app/issues/5346) - _Remained in **In Progress**_ +Backend Program Integrity Audit Schemas, Models, & Parser + +- ✅ [#5347](https://github.com/raft-tech/TANF-app/issues/5347) - _**Closed**_ - _Moved from **In Progress**_ +Spike: Identify Memory Overhead of Duplicate Detection WRT Universe/Sample Datafile Submissions + +- ➡️ [#5363](https://github.com/raft-tech/TANF-app/issues/5363) - _Moved from **UX Review** to **QASP Review**_ +[Design Deliverable] Email notification for confirming program integrity audit submissions + + +## [Add functionality for users to update STT when in Access Request state](https://github.com/raft-tech/TANF-app/issues/5317) + +- ✅ [#5317](https://github.com/raft-tech/TANF-app/issues/5317) - _**Closed**_ - _Moved from **Next Sprint Backlog**_ +Add functionality for users to update STT when in Access Request state + + +## [In-App Error Reporting - Foundational Design & Concept Validation](https://github.com/raft-tech/TANF-app/issues/4629) + +- ➡️ [#4721](https://github.com/raft-tech/TANF-app/issues/4721) - _Moved from **UX Review** to **QASP Review**_ +Plan research for in-app error reporting interface + +- ⬛️ [#5300](https://github.com/raft-tech/TANF-app/issues/5300) - _Remained in **UX Review**_ +Complete In App Error Reports HTML Prototypes + + +## [fTANF Replacement - Foundational Research & Concept Validation](https://github.com/raft-tech/TANF-app/issues/4628) + +- ➡️ [#4989](https://github.com/raft-tech/TANF-app/issues/4989) - _Moved from **In Progress** to **UX Review**_ +Plan initial research for FTANF replacement + + +## [Continuous User Feedback & Research Loops (CFL)](https://github.com/raft-tech/TANF-app/issues/4614) + +- ⬛️ [#5197](https://github.com/raft-tech/TANF-app/issues/5197) - _Remained in **Raft (Dev) Review**_ +Add metadata to feedback submissions to support contextual analysis + a11y fixes + + +## [Operations & Maintenance](https://github.com/raft-tech/TANF-app/issues/4445) + +- ➡️ [#3089](https://github.com/raft-tech/TANF-app/issues/3089) - _Moved from **Product Backlog** to **Next Sprint Backlog**_ +Implement user deletion support in DAC while retaining associated objects + +- ⬛️ [#5268](https://github.com/raft-tech/TANF-app/issues/5268) - _Remained in **Raft (Dev) Review**_ +Implement Login.gov SET Tech Memo + +- ✅ [#5308](https://github.com/raft-tech/TANF-app/issues/5308) - _**Closed**_ - _Moved from **UX Review**_ +Improve Cypress E2E reporting and artifacts in CircleCI + +- ✅ [#5333](https://github.com/raft-tech/TANF-app/issues/5333) - _**Closed**_ - _Moved from **In Progress**_ +Implement additional SSN validation for federally-funded TANF recipients + + +## [User Experience Enhancements](https://github.com/raft-tech/TANF-app/issues/4444) + +- ➡️ [#2984](https://github.com/raft-tech/TANF-app/issues/2984) - _Moved from **Product Backlog** to **Next Sprint Backlog**_ +Implement automated past due submission email notifications + +- ⬛️ [#3251](https://github.com/raft-tech/TANF-app/issues/3251) - _Remained in **QASP Review**_ +Update email templates for STT data submissions re: status and error guidance + +- ➡️ [#3263](https://github.com/raft-tech/TANF-app/issues/3263) - _Moved from **In Progress** to **UX Review**_ +Create email templates for reparsing (new error report & error resolution) + +- ➡️ [#3564](https://github.com/raft-tech/TANF-app/issues/3564) - _Moved from **Next Sprint Backlog** to **Current Sprint Backlog**_ +Implement spinner for TANF / SSP data file submissions + +- ➡️ [#5369](https://github.com/raft-tech/TANF-app/issues/5369) - _Moved from **UX Review** to **QASP Review**_ +Create research plan template for UX Playbook + +- ➡️ [#5371](https://github.com/raft-tech/TANF-app/issues/5371) - _Moved from **UX Review** to **QASP Review**_ +Create a Journey Mapping template & document best practices + +- ➡️ [#5374](https://github.com/raft-tech/TANF-app/issues/5374) - _Moved from **UX Review** to **QASP Review**_ +Create a QA Checklist for Completing UX & Dev Reviews + +- ➡️ [#5375](https://github.com/raft-tech/TANF-app/issues/5375) - _Moved from **Product Backlog** to **Next Sprint Backlog**_ +Create a guide for Conducting a Heuristic Evaluation (Checklist + Best Practices) + + +## [FRA Post-MVP Enhancements](https://github.com/raft-tech/TANF-app/issues/4443) + +- ⬛️ [#3485](https://github.com/raft-tech/TANF-app/issues/3485) - _Remained in **Ready to merge into staging branch**_ +Design FRA successful processing email template + +- ✅ [#3486](https://github.com/raft-tech/TANF-app/issues/3486) - _**Closed**_ - _Moved from **UX Review**_ +Implement FRA submission email template + +- ✅ [#4973](https://github.com/raft-tech/TANF-app/issues/4973) - _**Closed**_ - _Moved from **Raft (Dev) Review**_ +Implement Edit Profile page with shared Request Access logic + + +## [Bug Reports](https://github.com/raft-tech/TANF-app/issues/4441) + +- ⬛️ [#3515](https://github.com/raft-tech/TANF-app/issues/3515) - _Remained in **Raft (Dev) Review**_ +Bug: OFA system admin users get stuck if region is assigned + +- ➡️ [#5368](https://github.com/raft-tech/TANF-app/issues/5368) - _Moved from **Current Sprint Backlog** to **In Progress**_ +[BUG] User STT and Region assignment validation on user group change + + +## [[BLOCKED] Regional Staff TDP Access & Onboarding (RSAO)](https://github.com/raft-tech/TANF-app/issues/4395) + +- 🛑 [#3461](https://github.com/raft-tech/TANF-app/issues/3461) - _Remained in **Blocked**_ +Create and facilitate Project Updates meeting for regional staff + +- 🛑 [#3462](https://github.com/raft-tech/TANF-app/issues/3462) - _Remained in **Blocked**_ +Create and facilitate optional training session for regional staff + +- ➡️ [#3475](https://github.com/raft-tech/TANF-app/issues/3475) - _Moved from **Product Backlog** to **Next Sprint Backlog**_ +Update Profile component to display regional user's assigned regions + +- 🛑 [#3523](https://github.com/raft-tech/TANF-app/issues/3523) - _Remained in **Blocked**_ +Refine research plan for regional staff MVP onboarding experience + +- 🛑 [#3995](https://github.com/raft-tech/TANF-app/issues/3995) - _Remained in **Blocked**_ +Gather and iterate on OFA feedback + +- 🛑 [#4045](https://github.com/raft-tech/TANF-app/issues/4045) - _Remained in **Blocked**_ +Gather final OFA feedback and iterate + +- 🛑 [#4047](https://github.com/raft-tech/TANF-app/issues/4047) - _Remained in **Blocked**_ +Develop training materials, including slides, written instructions, and screenshots or screen recordings for visual guidance + +- ⬛️ [#4052](https://github.com/raft-tech/TANF-app/issues/4052) - _Remained in **In Progress**_ +Iterate on training materials internally + + +## [Enabling Secure Data Access for DIGIT in Grafana (SDA)](https://github.com/raft-tech/TANF-app/issues/4339) + +- 🛑 [#5269](https://github.com/raft-tech/TANF-app/issues/5269) - _Remained in **Blocked**_ +Bump Grafana memory in prod by 4GB to support 1.5M record visualizations + + +## [Application Health Monitoring (AHM)](https://github.com/raft-tech/TANF-app/issues/3587) + +- ✅ [#5381](https://github.com/raft-tech/TANF-app/issues/5381) - _**Closed**_ - _Moved from **Current Sprint Backlog**_ +Implement Sentry + + From d6ab96206dc445ee8571b8f964bbd21009fefdb6 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 17 Oct 2025 15:57:10 -0600 Subject: [PATCH 023/148] Revise sprint summary with updated issue links Updated issue links and statuses in sprint summary. --- .../sprint-summary-2025-10-07.md | 178 +++++++++--------- 1 file changed, 88 insertions(+), 90 deletions(-) diff --git a/docs/Sprint-Review/sprint-summary-2025-10-07.md b/docs/Sprint-Review/sprint-summary-2025-10-07.md index eb131bb5f..df85b8ba2 100644 --- a/docs/Sprint-Review/sprint-summary-2025-10-07.md +++ b/docs/Sprint-Review/sprint-summary-2025-10-07.md @@ -10,193 +10,191 @@ ## [Write KC Content for PI Audit and Centralized Feedback Reports](https://github.com/raft-tech/TANF-app/issues/5406) -- ➡️ [#5406](https://github.com/raft-tech/TANF-app/issues/5406) - _Moved from **Product Backlog** to **Next Sprint Backlog**_ -Write KC Content for PI Audit and Centralized Feedback Reports +- ➡️ [Write KC Content for PI Audit and Centralized Feedback Reports (#5406)](https://github.com/raft-tech/TANF-app/issues/5406) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ ## [(Spike) Knowledge Center Enhancements: Dynamic Navigation and Easier Information Access](https://github.com/raft-tech/TANF-app/issues/5403) -- ➡️ [#5403](https://github.com/raft-tech/TANF-app/issues/5403) - _Moved from **Next Sprint Backlog** to **Current Sprint Backlog**_ -(Spike) Knowledge Center Enhancements: Dynamic Navigation and Easier Information Access +- ➡️ [(Spike) Knowledge Center Enhancements: Dynamic Navigation and Easier Information Access (#5403)](https://github.com/raft-tech/TANF-app/issues/5403) +_Moved from **Next Sprint Backlog** to **Current Sprint Backlog**_ ## [Add alert banner to knowledge center regarding government shutdown](https://github.com/raft-tech/TANF-app/issues/5398) -- 🛑 [#5398](https://github.com/raft-tech/TANF-app/issues/5398) - _Moved from **Current Sprint Backlog** to **Blocked**_ -Add alert banner to knowledge center regarding government shutdown +- 🛑 [Add alert banner to knowledge center regarding government shutdown (#5398)](https://github.com/raft-tech/TANF-app/issues/5398) +_Moved from **Current Sprint Backlog** to **Blocked**_ ## [Feedback Reports Backend](https://github.com/raft-tech/TANF-app/issues/5397) -- ➡️ [#5397](https://github.com/raft-tech/TANF-app/issues/5397) - _Moved from **Next Sprint Backlog** to **Current Sprint Backlog**_ -Feedback Reports Backend +- ➡️ [Feedback Reports Backend (#5397)](https://github.com/raft-tech/TANF-app/issues/5397) +_Moved from **Next Sprint Backlog** to **Current Sprint Backlog**_ ## [Update Knowledge Center with Program Integrity Audit and Centralized Feedback Reports](https://github.com/raft-tech/TANF-app/issues/5391) -- ➡️ [#5391](https://github.com/raft-tech/TANF-app/issues/5391) - _Moved from **Product Backlog** to **Next Sprint Backlog**_ -Update Knowledge Center with Program Integrity Audit and Centralized Feedback Reports +- ➡️ [Update Knowledge Center with Program Integrity Audit and Centralized Feedback Reports (#5391)](https://github.com/raft-tech/TANF-app/issues/5391) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ ## [Admin Feedback Reports UI](https://github.com/raft-tech/TANF-app/issues/5390) -- ➡️ [#5390](https://github.com/raft-tech/TANF-app/issues/5390) - _Moved from **Product Backlog** to **Next Sprint Backlog**_ -Admin Feedback Reports UI +- ➡️ [Admin Feedback Reports UI (#5390)](https://github.com/raft-tech/TANF-app/issues/5390) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ ## [Read Only Datasources](https://github.com/raft-tech/TANF-app/issues/5388) -- ➡️ [#5388](https://github.com/raft-tech/TANF-app/issues/5388) - _Moved from **Next Sprint Backlog** to **Current Sprint Backlog**_ -Read Only Datasources +- ➡️ [Read Only Datasources (#5388)](https://github.com/raft-tech/TANF-app/issues/5388) +_Moved from **Next Sprint Backlog** to **Current Sprint Backlog**_ ## [Migrate from In-Memory to Database-Based Duplicate Detection for Program Integrity Audit Requirements](https://github.com/raft-tech/TANF-app/issues/5387) -- ✅ [#5387](https://github.com/raft-tech/TANF-app/issues/5387) - _**Closed**_ - _Moved from **Product Backlog**_ -Migrate from In-Memory to Database-Based Duplicate Detection for Program Integrity Audit Requirements +- ✅ [Migrate from In-Memory to Database-Based Duplicate Detection for Program Integrity Audit Requirements (#5387)](https://github.com/raft-tech/TANF-app/issues/5387) +_**Closed**_ - _Moved from **Product Backlog**_ ## [Program Integrity Audit](https://github.com/raft-tech/TANF-app/issues/5356) -- ➡️ [#5345](https://github.com/raft-tech/TANF-app/issues/5345) - _Moved from **Product Backlog** to **In Progress**_ -Frontend Program Integrity Audit Submissions +- ➡️ [Frontend Program Integrity Audit Submissions (#5345)](https://github.com/raft-tech/TANF-app/issues/5345) +_Moved from **Product Backlog** to **In Progress**_ -- ⬛️ [#5346](https://github.com/raft-tech/TANF-app/issues/5346) - _Remained in **In Progress**_ -Backend Program Integrity Audit Schemas, Models, & Parser +- ⬛️ [Backend Program Integrity Audit Schemas, Models, & Parser (#5346)](https://github.com/raft-tech/TANF-app/issues/5346) +_Remained in **In Progress**_ -- ✅ [#5347](https://github.com/raft-tech/TANF-app/issues/5347) - _**Closed**_ - _Moved from **In Progress**_ -Spike: Identify Memory Overhead of Duplicate Detection WRT Universe/Sample Datafile Submissions +- ✅ [Spike: Identify Memory Overhead of Duplicate Detection WRT Universe/Sample Datafile Submissions (#5347)](https://github.com/raft-tech/TANF-app/issues/5347) +_**Closed**_ - _Moved from **In Progress**_ -- ➡️ [#5363](https://github.com/raft-tech/TANF-app/issues/5363) - _Moved from **UX Review** to **QASP Review**_ -[Design Deliverable] Email notification for confirming program integrity audit submissions +- ➡️ [[Design Deliverable] Email notification for confirming program integrity audit submissions (#5363)](https://github.com/raft-tech/TANF-app/issues/5363) +_Moved from **UX Review** to **QASP Review**_ ## [Add functionality for users to update STT when in Access Request state](https://github.com/raft-tech/TANF-app/issues/5317) -- ✅ [#5317](https://github.com/raft-tech/TANF-app/issues/5317) - _**Closed**_ - _Moved from **Next Sprint Backlog**_ -Add functionality for users to update STT when in Access Request state +- ✅ [Add functionality for users to update STT when in Access Request state (#5317)](https://github.com/raft-tech/TANF-app/issues/5317) +_**Closed**_ - _Moved from **Next Sprint Backlog**_ ## [In-App Error Reporting - Foundational Design & Concept Validation](https://github.com/raft-tech/TANF-app/issues/4629) -- ➡️ [#4721](https://github.com/raft-tech/TANF-app/issues/4721) - _Moved from **UX Review** to **QASP Review**_ -Plan research for in-app error reporting interface +- ➡️ [Plan research for in-app error reporting interface (#4721)](https://github.com/raft-tech/TANF-app/issues/4721) +_Moved from **UX Review** to **QASP Review**_ -- ⬛️ [#5300](https://github.com/raft-tech/TANF-app/issues/5300) - _Remained in **UX Review**_ -Complete In App Error Reports HTML Prototypes +- ⬛️ [Complete In App Error Reports HTML Prototypes (#5300)](https://github.com/raft-tech/TANF-app/issues/5300) +_Remained in **UX Review**_ ## [fTANF Replacement - Foundational Research & Concept Validation](https://github.com/raft-tech/TANF-app/issues/4628) -- ➡️ [#4989](https://github.com/raft-tech/TANF-app/issues/4989) - _Moved from **In Progress** to **UX Review**_ -Plan initial research for FTANF replacement +- ➡️ [Plan initial research for FTANF replacement (#4989)](https://github.com/raft-tech/TANF-app/issues/4989) +_Moved from **In Progress** to **UX Review**_ ## [Continuous User Feedback & Research Loops (CFL)](https://github.com/raft-tech/TANF-app/issues/4614) -- ⬛️ [#5197](https://github.com/raft-tech/TANF-app/issues/5197) - _Remained in **Raft (Dev) Review**_ -Add metadata to feedback submissions to support contextual analysis + a11y fixes +- ⬛️ [Add metadata to feedback submissions to support contextual analysis + a11y fixes (#5197)](https://github.com/raft-tech/TANF-app/issues/5197) +_Remained in **Raft (Dev) Review**_ ## [Operations & Maintenance](https://github.com/raft-tech/TANF-app/issues/4445) -- ➡️ [#3089](https://github.com/raft-tech/TANF-app/issues/3089) - _Moved from **Product Backlog** to **Next Sprint Backlog**_ -Implement user deletion support in DAC while retaining associated objects +- ➡️ [Implement user deletion support in DAC while retaining associated objects (#3089)](https://github.com/raft-tech/TANF-app/issues/3089) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ -- ⬛️ [#5268](https://github.com/raft-tech/TANF-app/issues/5268) - _Remained in **Raft (Dev) Review**_ -Implement Login.gov SET Tech Memo +- ⬛️ [Implement Login.gov SET Tech Memo (#5268)](https://github.com/raft-tech/TANF-app/issues/5268) +_Remained in **Raft (Dev) Review**_ -- ✅ [#5308](https://github.com/raft-tech/TANF-app/issues/5308) - _**Closed**_ - _Moved from **UX Review**_ -Improve Cypress E2E reporting and artifacts in CircleCI +- ✅ [Improve Cypress E2E reporting and artifacts in CircleCI (#5308)](https://github.com/raft-tech/TANF-app/issues/5308) +_**Closed**_ - _Moved from **UX Review**_ -- ✅ [#5333](https://github.com/raft-tech/TANF-app/issues/5333) - _**Closed**_ - _Moved from **In Progress**_ -Implement additional SSN validation for federally-funded TANF recipients +- ✅ [Implement additional SSN validation for federally-funded TANF recipients (#5333)](https://github.com/raft-tech/TANF-app/issues/5333) +_**Closed**_ - _Moved from **In Progress**_ ## [User Experience Enhancements](https://github.com/raft-tech/TANF-app/issues/4444) -- ➡️ [#2984](https://github.com/raft-tech/TANF-app/issues/2984) - _Moved from **Product Backlog** to **Next Sprint Backlog**_ -Implement automated past due submission email notifications +- ➡️ [Implement automated past due submission email notifications (#2984)](https://github.com/raft-tech/TANF-app/issues/2984) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ -- ⬛️ [#3251](https://github.com/raft-tech/TANF-app/issues/3251) - _Remained in **QASP Review**_ -Update email templates for STT data submissions re: status and error guidance +- ⬛️ [Update email templates for STT data submissions re: status and error guidance (#3251)](https://github.com/raft-tech/TANF-app/issues/3251) +_Remained in **QASP Review**_ -- ➡️ [#3263](https://github.com/raft-tech/TANF-app/issues/3263) - _Moved from **In Progress** to **UX Review**_ -Create email templates for reparsing (new error report & error resolution) +- ➡️ [Create email templates for reparsing (new error report & error resolution) (#3263)](https://github.com/raft-tech/TANF-app/issues/3263) +_Moved from **In Progress** to **UX Review**_ -- ➡️ [#3564](https://github.com/raft-tech/TANF-app/issues/3564) - _Moved from **Next Sprint Backlog** to **Current Sprint Backlog**_ -Implement spinner for TANF / SSP data file submissions +- ➡️ [Implement spinner for TANF / SSP data file submissions (#3564)](https://github.com/raft-tech/TANF-app/issues/3564) +_Moved from **Next Sprint Backlog** to **Current Sprint Backlog**_ -- ➡️ [#5369](https://github.com/raft-tech/TANF-app/issues/5369) - _Moved from **UX Review** to **QASP Review**_ -Create research plan template for UX Playbook +- ➡️ [Create research plan template for UX Playbook (#5369)](https://github.com/raft-tech/TANF-app/issues/5369) +_Moved from **UX Review** to **QASP Review**_ -- ➡️ [#5371](https://github.com/raft-tech/TANF-app/issues/5371) - _Moved from **UX Review** to **QASP Review**_ -Create a Journey Mapping template & document best practices +- ➡️ [Create a Journey Mapping template & document best practices (#5371)](https://github.com/raft-tech/TANF-app/issues/5371) +_Moved from **UX Review** to **QASP Review**_ -- ➡️ [#5374](https://github.com/raft-tech/TANF-app/issues/5374) - _Moved from **UX Review** to **QASP Review**_ -Create a QA Checklist for Completing UX & Dev Reviews +- ➡️ [Create a QA Checklist for Completing UX & Dev Reviews (#5374)](https://github.com/raft-tech/TANF-app/issues/5374) +_Moved from **UX Review** to **QASP Review**_ -- ➡️ [#5375](https://github.com/raft-tech/TANF-app/issues/5375) - _Moved from **Product Backlog** to **Next Sprint Backlog**_ -Create a guide for Conducting a Heuristic Evaluation (Checklist + Best Practices) +- ➡️ [Create a guide for Conducting a Heuristic Evaluation (Checklist + Best Practices) (#5375)](https://github.com/raft-tech/TANF-app/issues/5375) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ ## [FRA Post-MVP Enhancements](https://github.com/raft-tech/TANF-app/issues/4443) -- ⬛️ [#3485](https://github.com/raft-tech/TANF-app/issues/3485) - _Remained in **Ready to merge into staging branch**_ -Design FRA successful processing email template +- ⬛️ [Design FRA successful processing email template (#3485)](https://github.com/raft-tech/TANF-app/issues/3485) +_Remained in **Ready to merge into staging branch**_ -- ✅ [#3486](https://github.com/raft-tech/TANF-app/issues/3486) - _**Closed**_ - _Moved from **UX Review**_ -Implement FRA submission email template +- ✅ [Implement FRA submission email template (#3486)](https://github.com/raft-tech/TANF-app/issues/3486) +_**Closed**_ - _Moved from **UX Review**_ -- ✅ [#4973](https://github.com/raft-tech/TANF-app/issues/4973) - _**Closed**_ - _Moved from **Raft (Dev) Review**_ -Implement Edit Profile page with shared Request Access logic +- ✅ [Implement Edit Profile page with shared Request Access logic (#4973)](https://github.com/raft-tech/TANF-app/issues/4973) +_**Closed**_ - _Moved from **Raft (Dev) Review**_ ## [Bug Reports](https://github.com/raft-tech/TANF-app/issues/4441) -- ⬛️ [#3515](https://github.com/raft-tech/TANF-app/issues/3515) - _Remained in **Raft (Dev) Review**_ -Bug: OFA system admin users get stuck if region is assigned +- ⬛️ [Bug: OFA system admin users get stuck if region is assigned (#3515)](https://github.com/raft-tech/TANF-app/issues/3515) +_Remained in **Raft (Dev) Review**_ -- ➡️ [#5368](https://github.com/raft-tech/TANF-app/issues/5368) - _Moved from **Current Sprint Backlog** to **In Progress**_ -[BUG] User STT and Region assignment validation on user group change +- ➡️ [[BUG] User STT and Region assignment validation on user group change (#5368)](https://github.com/raft-tech/TANF-app/issues/5368) +_Moved from **Current Sprint Backlog** to **In Progress**_ ## [[BLOCKED] Regional Staff TDP Access & Onboarding (RSAO)](https://github.com/raft-tech/TANF-app/issues/4395) -- 🛑 [#3461](https://github.com/raft-tech/TANF-app/issues/3461) - _Remained in **Blocked**_ -Create and facilitate Project Updates meeting for regional staff +- 🛑 [Create and facilitate Project Updates meeting for regional staff (#3461)](https://github.com/raft-tech/TANF-app/issues/3461) +_Remained in **Blocked**_ -- 🛑 [#3462](https://github.com/raft-tech/TANF-app/issues/3462) - _Remained in **Blocked**_ -Create and facilitate optional training session for regional staff +- 🛑 [Create and facilitate optional training session for regional staff (#3462)](https://github.com/raft-tech/TANF-app/issues/3462) +_Remained in **Blocked**_ -- ➡️ [#3475](https://github.com/raft-tech/TANF-app/issues/3475) - _Moved from **Product Backlog** to **Next Sprint Backlog**_ -Update Profile component to display regional user's assigned regions +- ➡️ [Update Profile component to display regional user's assigned regions (#3475)](https://github.com/raft-tech/TANF-app/issues/3475) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ -- 🛑 [#3523](https://github.com/raft-tech/TANF-app/issues/3523) - _Remained in **Blocked**_ -Refine research plan for regional staff MVP onboarding experience +- 🛑 [Refine research plan for regional staff MVP onboarding experience (#3523)](https://github.com/raft-tech/TANF-app/issues/3523) +_Remained in **Blocked**_ -- 🛑 [#3995](https://github.com/raft-tech/TANF-app/issues/3995) - _Remained in **Blocked**_ -Gather and iterate on OFA feedback +- 🛑 [Gather and iterate on OFA feedback (#3995)](https://github.com/raft-tech/TANF-app/issues/3995) +_Remained in **Blocked**_ -- 🛑 [#4045](https://github.com/raft-tech/TANF-app/issues/4045) - _Remained in **Blocked**_ -Gather final OFA feedback and iterate +- 🛑 [Gather final OFA feedback and iterate (#4045)](https://github.com/raft-tech/TANF-app/issues/4045) +_Remained in **Blocked**_ -- 🛑 [#4047](https://github.com/raft-tech/TANF-app/issues/4047) - _Remained in **Blocked**_ -Develop training materials, including slides, written instructions, and screenshots or screen recordings for visual guidance +- 🛑 [Develop training materials, including slides, written instructions, and screenshots or screen recordings for visual guidance (#4047)](https://github.com/raft-tech/TANF-app/issues/4047) +_Remained in **Blocked**_ -- ⬛️ [#4052](https://github.com/raft-tech/TANF-app/issues/4052) - _Remained in **In Progress**_ -Iterate on training materials internally +- ⬛️ [Iterate on training materials internally (#4052)](https://github.com/raft-tech/TANF-app/issues/4052) +_Remained in **In Progress**_ ## [Enabling Secure Data Access for DIGIT in Grafana (SDA)](https://github.com/raft-tech/TANF-app/issues/4339) -- 🛑 [#5269](https://github.com/raft-tech/TANF-app/issues/5269) - _Remained in **Blocked**_ -Bump Grafana memory in prod by 4GB to support 1.5M record visualizations +- 🛑 [Bump Grafana memory in prod by 4GB to support 1.5M record visualizations (#5269)](https://github.com/raft-tech/TANF-app/issues/5269) +_Remained in **Blocked**_ ## [Application Health Monitoring (AHM)](https://github.com/raft-tech/TANF-app/issues/3587) -- ✅ [#5381](https://github.com/raft-tech/TANF-app/issues/5381) - _**Closed**_ - _Moved from **Current Sprint Backlog**_ -Implement Sentry - - +- ✅ [Implement Sentry (#5381)](https://github.com/raft-tech/TANF-app/issues/5381) +_**Closed**_ - _Moved from **Current Sprint Backlog**_ From fcf86a72c1313377bd502ebe014af77e563d3fd6 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Thu, 2 Oct 2025 10:40:39 -0600 Subject: [PATCH 024/148] Add files via upload --- docs/Sprint-Review/sprint-133-summary.md | 38 ++++++++ docs/Sprint-Review/sprint-134-summary.md | 116 +++++++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 docs/Sprint-Review/sprint-133-summary.md create mode 100644 docs/Sprint-Review/sprint-134-summary.md diff --git a/docs/Sprint-Review/sprint-133-summary.md b/docs/Sprint-Review/sprint-133-summary.md new file mode 100644 index 000000000..7e8013f47 --- /dev/null +++ b/docs/Sprint-Review/sprint-133-summary.md @@ -0,0 +1,38 @@ +--- +description: August 27, 2025 - September 9, 2025 +--- + +# Sprint 133 Summary + +## Summary + +This sprint focused on enhancing system functionality and compliance through various critical updates. We investigated the decoupling of the SSP flag from filenames to ensure historical data integrity, implemented an admin review workflow for user change requests, and developed end-to-end tests for TANF/SSP data submissions. Additionally, we prepared for Sentry integration to improve error tracking and monitoring, and addressed several bugs to enhance overall system reliability. These efforts not only streamline processes but also ensure that our system remains robust and compliant with evolving requirements. + +This sprint introduced several key enhancements that significantly improve our platform's capabilities and user experience. Here are the top 5 highlights: + +* Decoupled SSP flag from filenames to maintain historical data integrity. +* Launched an admin review workflow for user change requests, enhancing governance and transparency. +* Developed comprehensive end-to-end tests for TANF/SSP data submissions, ensuring compliance and security. +* Prepared for Sentry integration, enabling faster issue resolution and improved monitoring. +* Addressed critical bugs, enhancing system reliability and user satisfaction. + +### **Here's a list of the issues the team completed in this Sprint...** + +* [Investigate decoupling SSP flag from filenames and retain historical data for NY post-FY24](https://github.com/raft-tech/TANF-app/issues/3323) TANF-app #3323 Completed after sprint ended +* [Investigate missing DataFileSummary records for submitted files in production](https://github.com/raft-tech/TANF-app/issues/5264) TANF-app #5264 Completed after sprint ended +* [Design MVP for sample STT universe data submission flow](https://github.com/raft-tech/TANF-app/issues/5349) TANF-app #5349Completed after sprint ended +* [Develop E2E tests for non-data analyst submissions (TANF/SSP Data Files)](https://github.com/raft-tech/TANF-app/issues/5226) TANF-app #5226 +* [Implement admin review workflow for user change requests](https://github.com/raft-tech/TANF-app/issues/4974) TANF-app #4974Completed after sprint ended +* [4974 implement admin review workflow user change requests](https://github.com/raft-tech/TANF-app/issues/5165) TANF-app #5165Completed after sprint ended +* [Prepare for Sentry](https://github.com/raft-tech/TANF-app/issues/5272) TANF-app #5272 +* [Create sprint-131-summary.md](https://github.com/raft-tech/TANF-app/issues/5311) TANF-app #5311 +* [5226 e2e tests for non data analyst submissions](https://github.com/raft-tech/TANF-app/issues/5312) TANF-app #5312 +* [5272 Prepare for Sentry](https://github.com/raft-tech/TANF-app/issues/5313) TANF-app #5313 +* [relax t7 section indicator exclusions](https://github.com/raft-tech/TANF-app/issues/5357) TANF-app #5357 +* [Adds grafana guide & images](https://github.com/raft-tech/TANF-app/issues/5358) TANF-app #5358 +* [Hotfix: Header Field Validators Should NOT Reject File](https://github.com/raft-tech/TANF-app/issues/5361) TANF-app #5361 +* [Add referer to cypress admin api requests](https://github.com/raft-tech/TANF-app/issues/5364) TANF-app #5364 +* [Vault Findings Doc](https://github.com/raft-tech/TANF-app/issues/5274) TANF-app #5274 +* [Create digit-grafana-training-materials.md](https://github.com/raft-tech/TANF-app/issues/5307) TANF-app #5307 +* [Hotfix: Remove View Drop](https://github.com/raft-tech/TANF-app/issues/5362) TANF-app #5362 + diff --git a/docs/Sprint-Review/sprint-134-summary.md b/docs/Sprint-Review/sprint-134-summary.md new file mode 100644 index 000000000..b0d4ffda3 --- /dev/null +++ b/docs/Sprint-Review/sprint-134-summary.md @@ -0,0 +1,116 @@ +--- +description: September 10, 2025 - September 23, 2025 +--- + +# Sprint 134 Summary + +## Summary + +This sprint focused on enhancing the system's capabilities and addressing critical issues to ensure compliance and improve user experience. Key achievements include the decoupling of the SSP flag from filenames to retain historical data, the implementation of an admin review workflow for user change requests, and the investigation of missing DataFileSummary records. These efforts not only streamline processes but also enhance data integrity and accessibility, which are crucial for maintaining operational efficiency and compliance with federal reporting requirements. + +**Here's a list of the issues the team completed in this Sprint...** + +* [Investigate decoupling SSP flag from filenames and retain historical data for NY post-FY24](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/3323) TANF-app #3323 +* [Investigate missing DataFileSummary records for submitted files in production](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/5264) TANF-app #5264 +* [Implement admin review workflow for user change requests](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/4974) TANF-app #4974 +* [4974 implement admin review workflow user change requests](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/5165) TANF-app #5165 +* [3515 bug ofa system admin users get stuck region assigned](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/5348) TANF-app #5348 +* [Design MVP interface for centralized feedback reports](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/5222) TANF-app #5222 +* [Design MVP for sample STT universe data submission flow](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/5349) TANF-app #5349 +* [Universe memory exploration](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/issues/gh/raft-tech/tanf-app/5378) TANF-app #5378 + +## Statuses of all issues in this sprint + +#### \[BLOCKED] Regional Staff TDP Access & Onboarding (RSAO) (#4395) + +| Issue | Title | Status | +| ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | --------------- | +| [#3461](https://github.com/raft-tech/TANF-app/issues/3461) | Create and facilitate Project Updates meeting for regional staff | Blocked | +| [#3462](https://github.com/raft-tech/TANF-app/issues/3462) | Create and facilitate optional training session for regional staff | Blocked | +| [#3475](https://github.com/raft-tech/TANF-app/issues/3475) | Update Profile component to display regional user's assigned regions | Product Backlog | +| [#3523](https://github.com/raft-tech/TANF-app/issues/3523) | Refine research plan for regional staff MVP onboarding experience | Blocked | +| [#3995](https://github.com/raft-tech/TANF-app/issues/3995) | Gather and iterate on OFA feedback | Blocked | +| [#4045](https://github.com/raft-tech/TANF-app/issues/4045) | Gather final OFA feedback and iterate | Blocked | +| [#4047](https://github.com/raft-tech/TANF-app/issues/4047) | Develop training materials, including slides, written instructions, and screenshots or screen recordings for visual guidance | Blocked | +| [#4052](https://github.com/raft-tech/TANF-app/issues/4052) | Iterate on training materials internally | In Progress | + +#### Operations & Maintenance (#4445) + +| Issue | Title | Status | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ---------------------- | +| [#3323](https://github.com/raft-tech/TANF-app/issues/3323) | Investigate decoupling SSP flag from filenames and retain historical data for NY post-FY24 | In Progress | +| [#5268](https://github.com/raft-tech/TANF-app/issues/5268) | Implement Login.gov SET Tech Memo | Raft (Dev) Review | +| [#5302](https://github.com/raft-tech/TANF-app/issues/5302) | Investigate alignment of application configurations across environments | Current Sprint Backlog | +| [#5308](https://github.com/raft-tech/TANF-app/issues/5308) | Improve Cypress E2E reporting and artifacts in CircleCI | UX Review | +| [#5333](https://github.com/raft-tech/TANF-app/issues/5333) | Implement additional SSN validation for federally-funded TANF recipients | Current Sprint Backlog | + +#### FRA Post-MVP Enhancements (#4443) + +| Issue | Title | Status | +| ---------------------------------------------------------- | ------------------------------------------------------------ | ---------------------------------- | +| [#3485](https://github.com/raft-tech/TANF-app/issues/3485) | Design FRA successful processing email template | Ready to merge into staging branch | +| [#3486](https://github.com/raft-tech/TANF-app/issues/3486) | Implement FRA submission email template | Current Sprint Backlog | +| [#4973](https://github.com/raft-tech/TANF-app/issues/4973) | Implement Edit Profile page with shared Request Access logic | In Progress | +| [#4974](https://github.com/raft-tech/TANF-app/issues/4974) | Implement admin review workflow for user change requests | Raft (Dev) Review | + +#### Program Integrity Audit (#5356) + +| Issue | Title | Status | +| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------- | +| [#5346](https://github.com/raft-tech/TANF-app/issues/5346) | Backend Program Integrity Audit Schemas, Models, & Parser | In Progress | +| [#5347](https://github.com/raft-tech/TANF-app/issues/5347) | Spike: Identify Memory Overhead of Duplicate Detection WRT Universe/Sample Datafile Submissions | In Progress | +| [#5349](https://github.com/raft-tech/TANF-app/issues/5349) | Design MVP for sample STT universe data submission flow | UX Review | +| [#5363](https://github.com/raft-tech/TANF-app/issues/5363) | \[Design Deliverable] Email notification for confirming program integrity audit submissions | UX Review | + +#### Bug Reports (#4441) + +| Issue | Title | Status | +| ---------------------------------------------------------- | ----------------------------------------------------------------------------- | ---------------------- | +| [#3515](https://github.com/raft-tech/TANF-app/issues/3515) | Bug: OFA system admin users get stuck if region is assigned | Raft (Dev) Review | +| [#5264](https://github.com/raft-tech/TANF-app/issues/5264) | Investigate missing DataFileSummary records for submitted files in production | In Progress | +| [#5368](https://github.com/raft-tech/TANF-app/issues/5368) | \[BUG] User STT and Region assignment validation on user group change | Current Sprint Backlog | + +#### User Experience Enhancements (#4444) + +| Issue | Title | Status | +| ---------------------------------------------------------- | ----------------------------------------------------------------------------- | ----------- | +| [#3251](https://github.com/raft-tech/TANF-app/issues/3251) | Update email templates for STT data submissions re: status and error guidance | QASP Review | +| [#5369](https://github.com/raft-tech/TANF-app/issues/5369) | Create research plan template for UX Playbook | In Progress | + +#### In-App Error Reporting - Foundational Design & Concept Validation (#4629) + +| Issue | Title | Status | +| ---------------------------------------------------------- | -------------------------------------------------- | --------- | +| [#4721](https://github.com/raft-tech/TANF-app/issues/4721) | Plan research for in-app error reporting interface | UX Review | +| [#5300](https://github.com/raft-tech/TANF-app/issues/5300) | Complete In App Error Reports HTML Prototypes | UX Review | + +#### fTANF Replacement - Foundational Research & Concept Validation (#4628) + +| Issue | Title | Status | +| ---------------------------------------------------------- | --------------------------------------------------------------- | ---------------------- | +| [#4989](https://github.com/raft-tech/TANF-app/issues/4989) | Define success metrics and goals for FTANF replacement research | Current Sprint Backlog | + +#### Continuous User Feedback & Research Loops (CFL) (#4614) + +| Issue | Title | Status | +| ---------------------------------------------------------- | -------------------------------------------------------------------------------- | ----------------- | +| [#5197](https://github.com/raft-tech/TANF-app/issues/5197) | Add metadata to feedback submissions to support contextual analysis + a11y fixes | Raft (Dev) Review | + +#### Centralizing Feedback Reports in TDP - Foundational Discovery (#5276) + +| Issue | Title | Status | +| ---------------------------------------------------------- | ----------------------------------------------------- | --------- | +| [#5222](https://github.com/raft-tech/TANF-app/issues/5222) | Design MVP interface for centralized feedback reports | UX Review | + +#### Enabling Secure Data Access for DIGIT in Grafana (SDA) (#4339) + +| Issue | Title | Status | +| ---------------------------------------------------------- | ------------------------------------------------------------------------ | ------- | +| [#5269](https://github.com/raft-tech/TANF-app/issues/5269) | Bump Grafana memory in prod by 4GB to support 1.5M record visualizations | Blocked | + +#### Application Health Monitoring (AHM) (#3587) + +| Issue | Title | Status | +| ---------------------------------------------------------- | ---------------- | ---------------------- | +| [#5381](https://github.com/raft-tech/TANF-app/issues/5381) | Implement Sentry | Current Sprint Backlog | + From 10727f7ba08464074c88c28b123deda383b24df1 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Thu, 18 Sep 2025 17:05:05 -0600 Subject: [PATCH 025/148] Create sprint-132-summary.md --- docs/Sprint-Review/sprint-132-summary.md | 82 ++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 docs/Sprint-Review/sprint-132-summary.md diff --git a/docs/Sprint-Review/sprint-132-summary.md b/docs/Sprint-Review/sprint-132-summary.md new file mode 100644 index 000000000..ff44920bc --- /dev/null +++ b/docs/Sprint-Review/sprint-132-summary.md @@ -0,0 +1,82 @@ +--- +description: August 13, 2025 - August 26, 2025 +--- + +# Sprint 132 Summary + +## Summary + +### Highlights + +This sprint focused on enhancing the reliability and usability of our application through various improvements and bug fixes. Key achievements include the implementation of updated Alertmanager inhibition rules to reduce alert fatigue, the development of end-to-end tests for critical user flows, and the creation of user training materials for Grafana access. These efforts not only streamline our backend processes but also ensure a smoother experience for our users, ultimately leading to improved operational efficiency and user satisfaction. The team also began work onenabling universe data submission for STTs + + + +**Alert Management Improvements:** + +* Improved alert logic and frequency by updating Alertmanager inhibition rules. + +**Enabling Secure Data Access for DIGIT via Grafana:** + +* Removed `NULL` values from filtered query results to improve the accuracy and reliability of Grafana dashboards. + +**In-App Error Reporting:** + +* Developed concepts for testing to ensure that usability testing sessions are grounded in realistic scenarios and interactions. + +**Operations & Maintenance:** + +* Developed end-to-end tests for login and account management and for data analyst and non-data analyst submissions for TANF and SSP data files. +* Re-evaluated the initial local implementation of Sentry to confirm that configurations, integrations, and functionality remain accurate, secure, and ready for deployment to a cloud environment. +* Fixed a bug that caused file input to display multiple files after error state. +* Hotfixes for missing fields in views, Cat 4 missing fields, and an FRA file extension bug. +* Set up Django fixture data. + + + +## Product Roadmap Progress + +{% hint style="info" %} +For more detailed information on task progress, please visit the [overall roadmap](https://app.zenhub.com/workspaces/sprint-board-5f18ab06dfd91c000f7e682e/timeline) for these epics. +{% endhint %} + +* Application Health Monitoring | **ON TRACK** | Progress: 94% **(+0%)** | Estimated Completion Date: September 23, 2025 +* Centralizing Feedback Reports in TDP - Foundational Discovery | **ON TRACK** | Progress: 86% **(+27%)** | Estimated Completion Date: September 23, 2025 +* Continuous User Research & Feedback Loops | **ON TRACK** | Progress: 93% **(+2%)** | Estimated Completion Date: September 23, 2025 +* Enabling Secure Data Access for DIGIT in Grafana | **AT RISK** | Progress: 94% **(+0%)** | Estimated Completion Date: August 26, 2025 + * **Note:** The timeline has been slightly extended while we work to bump resources in Grafana. +* FRA Post-MVP Enhancements | **ON TRACK** | Progress: 79% **(+7%)** | Estimated Completion Date: September 23, 2025 +* fTANF Replacement - Foundational Research & Concept Validation | **ON TRACK** | Progress: 38% **(+0%)** | Estimated Completion Date: January 27, 2026 +* In-App Error Reporting - Foundational Research & Concept Validation | **ON TRACK** | Progress: 47% **(+5%)** | Estimated Completion Date: February 10, 2026 +* Regional Staff TDP Access & Onboarding | **ON HOLD** | Progress: 38% **(+0%)** | Estimated Completion Date: TBD + * **Note:** Due to some of the unforeseen changes in HHS staffing, this epic is on pause until further notice. + +## Tasks + +### Centralizing Feedback Reports in TDP - Foundational Discovery + +
    TaskValue PropositionStatus
    #5224 - Conduct research session with Yun on centralizing feedback reports in TDPEnsures we capture critical insights from Yun to inform how feedback report workflows are centralized in TDP, enabling a more streamlined and well-scoped MVP versus post-MVP roadmap.COMPLETE
    #5222 - Design MVP interface for centralized feedback reportsDelivers dev-ready designs for in-app and email delivery of feedback reports, enabling a shift from manual email distribution to a streamlined, accessible, and integrated experience within TDP.UX REVIEW
    + +### Continuous User Feedback & Research Loops + +
    TaskValue PropositionStatus
    #5197 - Add metadata to feedback submissions to support contextual analysis + a11y fixesAdds rich, structured metadata to every feedback submission (page, type, widget, file refs), enabling precise triage, better filtering and analysis, and faster, data-driven UX improvements.IN PROGRES & MOVED TO NEXT SPRINT
    + +### Enabling Secure Data Access for DIGIT in Grafana + +
    TaskValue PropositionStatus
    #5267 - Handle Null Values in Custom Where ClausesThe WHERE clauses in specific PostgreSQL views used by Grafana will be updated to exclude records with NULL values in key fields. COMPLETE
    #5269 - Bump Grafana memory in prod by 4GB to support 1.5M record visualizationsEnables reliable visualization of datasets up to 1.5M records, eliminating OOM failures and accelerating troubleshooting for large TANF/SSP submissions.BLOCKED
    + +### FRA Post-MVP Enhancements + +
    TaskValue PropositionStatus
    #4974 - Implement admin review workflow for user change requestsIntroduces an auditable workflow for user-initiated profile and access change requests, improving self-service, administrative oversight, and compliance as the platform scales.IN PROGRESS & MOVED TO NEXT SPRINT
    #4973 - Implement Edit Profile page with shared Request Access logicDelivers a redesigned, self-service “Edit Profile” experience that aligns with the latest design system, reuses proven Request Access components, and enables users to easily request accurate profile updates while reducing support workload and improving data quality.UX REVIEW
    #3485 - Design FRA successful processing email templateDelivers an accessible, USWDS-compliant FRA success notification email that gives users clear, trustworthy confirmation of processed submissions, reduces uncertainty, and aligns communications with our established design system.COMPLETE
    + +### In-App Error Reporting + +
    TaskValue PropositionStatus
    #4753 - Develop concepts for testingDelivers structured, testable design concepts for in-app error reporting, enabling usability testing that validates workflows, improves user understanding, and guides more effective error resolution experiences.COMPLETE
    + +### Operations & Maintenance + +
    TaskValue PropositionStatus
    #3432 - Update Alertmanager Inhibition RulesStreamlines alerting by suppressing redundant UpTime notifications during AppDown events and reducing their frequency, minimizing alert fatigue and ensuring focus stays on actionable issues.COMPLETE
    #4908 - Bug: File input displays multiple files after error stateEnsures the file input resets to show only the most recently selected file after an upload error, preventing confusion and giving users clear confirmation of what will be submitted.COMPLETE
    #5167 - Develop E2E tests for data analyst submissions (TANF/SSP Data Files)Delivers automated Cypress end-to-end test coverage for TANF, SSP, and Tribal data file submissions, ensuring analysts can successfully upload, validate, and track files while catching errors early and safeguarding compliance with federal reporting requirements.COMPLETE
    #5168 - Develop E2E tests for FRA data file submissionsProvides automated, role-aware Cypress tests for FRA data file submissions that verify permissions and workflows end-to-end, reduce regressions, and strengthen compliance and confidence in the platform.COMPLETE
    #5226 - Develop E2E tests for non-data analyst submissions (TANF/SSP Data Files)Delivers automated, role-based Cypress tests that verify TANF/SSP access controls end-to-end, reducing regressions, safeguarding compliance and data security, and increasing confidence in the platform’s permissions model.RAFT REVIEW
    #5166 - Develop E2E tests for login and account managementDelivers automated end-to-end coverage of login and account approval flows, ensuring secure, role-correct onboarding, preventing access misconfigurations, and increasing confidence that users only see features appropriate to their permissions.COMPLETE
    + +### User Experience Enhancements + +
    TaskValue PropositionStatus
    #3251 - Update email templates for STT data submissions re: status and error guidanceEnsures STTs receive clear, action-oriented submission emails that highlight errors early, reduce confusion, and drive timely corrections, ultimately improving data quality and compliance.QASP REVIEW
    From a34f8b8ba45063c17954e0f29487710e93b97eb4 Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Fri, 16 Jan 2026 11:31:10 -0600 Subject: [PATCH 026/148] don't include all a11y tests for every spec run --- tdrs-frontend/cypress/e2e/a11y/a11y.js | 141 ------------------ .../cypress/e2e/a11y/login-error-state.js | 48 ------ .../cypress/e2e/a11y/login-helpers.js | 14 -- .../cypress/e2e/a11y/profile-flow.js | 65 -------- tdrs-frontend/cypress/e2e/accounts/a11y.js | 70 +++++++++ .../cypress/e2e/common-steps/a11y.js | 57 +++++++ tdrs-frontend/cypress/e2e/profile/a11y.js | 17 +++ tdrs-frontend/package.json | 6 +- 8 files changed, 146 insertions(+), 272 deletions(-) delete mode 100644 tdrs-frontend/cypress/e2e/a11y/a11y.js delete mode 100644 tdrs-frontend/cypress/e2e/a11y/login-error-state.js delete mode 100644 tdrs-frontend/cypress/e2e/a11y/login-helpers.js delete mode 100644 tdrs-frontend/cypress/e2e/a11y/profile-flow.js create mode 100644 tdrs-frontend/cypress/e2e/accounts/a11y.js create mode 100644 tdrs-frontend/cypress/e2e/common-steps/a11y.js create mode 100644 tdrs-frontend/cypress/e2e/profile/a11y.js diff --git a/tdrs-frontend/cypress/e2e/a11y/a11y.js b/tdrs-frontend/cypress/e2e/a11y/a11y.js deleted file mode 100644 index c1ca38c15..000000000 --- a/tdrs-frontend/cypress/e2e/a11y/a11y.js +++ /dev/null @@ -1,141 +0,0 @@ -/// - -import { ACTORS, clearCookies } from '../common-steps/common-steps' -import { loginAsDataAnalystStefani } from './login-helpers' - -function terminalLog(violations) { - // existing summary - cy.task( - 'log', - `${violations.length} accessibility violation${ - violations.length === 1 ? '' : 's' - } ${violations.length === 1 ? 'was' : 'were'} detected` - ) - - const violationData = violations.map( - ({ id, impact, description, nodes }) => ({ - id, - impact, - description, - nodes: nodes.length, - }) - ) - - cy.task('table', violationData) - - // 🔍 NEW: log selectors + snippets for debugging - violations.forEach((violation) => { - cy.task( - 'log', - `Violation: ${violation.id} (${violation.impact}) – ${violation.description}` - ) - - violation.nodes.forEach((node, index) => { - cy.task( - 'log', - ` Node ${index + 1} targets: ${node.target.join(', ')}` - ) - cy.task('log', ` HTML: ${node.html}`) - }) - }) -} - - -const loginAsFRADataAnalystFred = () => { - clearCookies() - - cy.visit('/') - cy.adminLogin('cypress-admin-alex@teamraft.com') - - const username = ACTORS['FRA Data Analyst Fred'].username - cy.login(username) -} - -/* ───────────── PUBLIC PAGES ───────────── */ - -describe('Public pages accessibility', () => { - const publicPages = [ - { name: 'Landing', path: '/' }, - { name: 'Login', path: '/login' }, - ] - - publicPages.forEach((page) => { - it(`has no serious accessibility violations on ${page.name}`, () => { - cy.visit(page.path) - // Sanity checks - cy.url().should('include', page.path) - cy.get('h1').first().should('exist') - - cy.injectAxe() - - cy.checkA11y( - null, - { - includedImpacts: ['critical', 'serious'], - }, - terminalLog - ) - }) - }) -}) - -describe('FRA pages accessibility', () => { - const fraPages = [{ name: 'FRA Data Files', path: '/fra-data-files' }] - - before(() => { - loginAsFRADataAnalystFred() - }) - - fraPages.forEach((page) => { - it(`has no serious accessibility violations on ${page.name}`, () => { - cy.visit(page.path) - cy.url().should('include', page.path) - cy.get('h1').first().should('exist') - - cy.injectAxe() - - cy.checkA11y( - null, - { - includedImpacts: ['critical', 'serious'], - }, - terminalLog - ) - }) - }) -}) - -/* ───────────── AUTHENTICATED PAGES ───────────── */ - -describe('Authenticated pages accessibility', () => { - const authedPages = [ - { name: 'Home', path: '/home' }, - { name: 'Data files', path: '/data-files' }, - // add profile or others here if you like: - { name: 'Profile', path: '/profile' }, - ] - - // Log in ONCE for this suite - before(() => { - loginAsDataAnalystStefani() - }) - - authedPages.forEach((page) => { - it(`has no serious accessibility violations on ${page.name}`, () => { - cy.visit(page.path) - // Sanity checks - cy.url().should('include', page.path) - cy.get('h1').first().should('exist') - - cy.injectAxe() - - cy.checkA11y( - null, - { - includedImpacts: ['critical', 'serious'], - }, - terminalLog - ) - }) - }) -}) diff --git a/tdrs-frontend/cypress/e2e/a11y/login-error-state.js b/tdrs-frontend/cypress/e2e/a11y/login-error-state.js deleted file mode 100644 index 0fa8540ff..000000000 --- a/tdrs-frontend/cypress/e2e/a11y/login-error-state.js +++ /dev/null @@ -1,48 +0,0 @@ -/// - -import { clearCookies } from '../common-steps/common-steps' - -function terminalLog(violations) { - cy.task( - 'log', - `${violations.length} accessibility violation${ - violations.length === 1 ? '' : 's' - } ${violations.length === 1 ? 'was' : 'were'} detected` - ) - - const violationData = violations.map( - ({ id, impact, description, nodes }) => ({ - id, - impact, - description, - nodes: nodes.length, - }) - ) - - cy.task('table', violationData) -} - -describe('Login page accessibility', () => { - it('has no serious accessibility violations on the splash/login page', () => { - // ensure we see the unauthenticated splash, not a redirect to /profile - clearCookies() - - cy.visit('/') - - // wait for splash content - cy.contains('Sign into TANF Data Portal', { timeout: 30000 }) - - // the hero heading is an

    on SplashPage - cy.get('h1').should('exist') - - cy.injectAxe() - - cy.checkA11y( - 'main', - { - includedImpacts: ['critical', 'serious'], - }, - terminalLog - ) - }) -}) diff --git a/tdrs-frontend/cypress/e2e/a11y/login-helpers.js b/tdrs-frontend/cypress/e2e/a11y/login-helpers.js deleted file mode 100644 index 9e25afb8c..000000000 --- a/tdrs-frontend/cypress/e2e/a11y/login-helpers.js +++ /dev/null @@ -1,14 +0,0 @@ -/// - -import { ACTORS, clearCookies } from '../common-steps/common-steps' - -export const loginAsDataAnalystStefani = () => { - clearCookies() - - cy.visit('/') - cy.adminLogin('cypress-admin-alex@teamraft.com') - - const username = ACTORS['Data Analyst Stefani'].username - cy.contains('Sign into TANF Data Portal', { timeout: 30000 }) - cy.login(username) -} diff --git a/tdrs-frontend/cypress/e2e/a11y/profile-flow.js b/tdrs-frontend/cypress/e2e/a11y/profile-flow.js deleted file mode 100644 index eaaf20af7..000000000 --- a/tdrs-frontend/cypress/e2e/a11y/profile-flow.js +++ /dev/null @@ -1,65 +0,0 @@ -/// - -import { loginAsDataAnalystStefani } from './login-helpers' - -function terminalLog(violations) { - cy.task( - 'log', - `${violations.length} accessibility violation${ - violations.length === 1 ? '' : 's' - } ${violations.length === 1 ? 'was' : 'were'} detected` - ) - - const violationData = violations.map( - ({ id, impact, description, nodes }) => ({ - id, - impact, - description, - nodes: nodes.length, - }) - ) - - cy.task('table', violationData) - - // Log selectors + snippets for debugging when failures occur - violations.forEach((violation) => { - cy.task( - 'log', - `Violation: ${violation.id} (${violation.impact}) – ${violation.description}` - ) - - violation.nodes.forEach((node, index) => { - cy.task( - 'log', - ` Node ${index + 1} targets: ${node.target.join(', ')}` - ) - cy.task('log', ` HTML: ${node.html}`) - }) - }) -} - -/* ───────────── Profile flow accessibility ───────────── */ - -describe('Profile flow accessibility', () => { - before(() => { - loginAsDataAnalystStefani() - }) - - it('is accessible when viewing profile', () => { - cy.visit('/profile') - - // make sure we are on the profile page - cy.url().should('include', '/profile') - cy.get('main').should('exist') - - cy.injectAxe() - - cy.checkA11y( - 'main', - { - includedImpacts: ['critical', 'serious'], - }, - terminalLog - ) - }) -}) diff --git a/tdrs-frontend/cypress/e2e/accounts/a11y.js b/tdrs-frontend/cypress/e2e/accounts/a11y.js new file mode 100644 index 000000000..8cb642458 --- /dev/null +++ b/tdrs-frontend/cypress/e2e/accounts/a11y.js @@ -0,0 +1,70 @@ +import { + loginAsDataAnalystStefani, + loginAsFRADataAnalystFred, + checkA11y, +} from '../common-steps/a11y' + +/* ───────────── PUBLIC PAGES ───────────── */ + +describe('Public pages accessibility', () => { + const publicPages = [ + { name: 'Landing', path: '/' }, + { name: 'Login', path: '/login' }, + ] + + publicPages.forEach((page) => { + it(`has no serious accessibility violations on ${page.name}`, () => { + cy.visit(page.path) + // Sanity checks + cy.url().should('include', page.path) + cy.get('h1').first().should('exist') + + checkA11y() + }) + }) +}) + +describe('FRA pages accessibility', () => { + const fraPages = [{ name: 'FRA Data Files', path: '/fra-data-files' }] + + before(() => { + loginAsFRADataAnalystFred() + }) + + fraPages.forEach((page) => { + it(`has no serious accessibility violations on ${page.name}`, () => { + cy.visit(page.path) + cy.url().should('include', page.path) + cy.get('h1').first().should('exist') + + checkA11y() + }) + }) +}) + +/* ───────────── AUTHENTICATED PAGES ───────────── */ + +describe('Authenticated pages accessibility', () => { + const authedPages = [ + { name: 'Home', path: '/home' }, + { name: 'Data files', path: '/data-files' }, + // add profile or others here if you like: + { name: 'Profile', path: '/profile' }, + ] + + // Log in ONCE for this suite + before(() => { + loginAsDataAnalystStefani() + }) + + authedPages.forEach((page) => { + it(`has no serious accessibility violations on ${page.name}`, () => { + cy.visit(page.path) + // Sanity checks + cy.url().should('include', page.path) + cy.get('h1').first().should('exist') + + checkA11y() + }) + }) +}) diff --git a/tdrs-frontend/cypress/e2e/common-steps/a11y.js b/tdrs-frontend/cypress/e2e/common-steps/a11y.js new file mode 100644 index 000000000..a6fdd31b5 --- /dev/null +++ b/tdrs-frontend/cypress/e2e/common-steps/a11y.js @@ -0,0 +1,57 @@ +/// + +import { loginAsActor } from './common-steps' + +export function terminalLog(violations) { + // existing summary + cy.task( + 'log', + `${violations.length} accessibility violation${ + violations.length === 1 ? '' : 's' + } ${violations.length === 1 ? 'was' : 'were'} detected` + ) + + const violationData = violations.map( + ({ id, impact, description, nodes }) => ({ + id, + impact, + description, + nodes: nodes.length, + }) + ) + + cy.task('table', violationData) + + // 🔍 NEW: log selectors + snippets for debugging + violations.forEach((violation) => { + cy.task( + 'log', + `Violation: ${violation.id} (${violation.impact}) – ${violation.description}` + ) + + violation.nodes.forEach((node, index) => { + cy.task('log', ` Node ${index + 1} targets: ${node.target.join(', ')}`) + cy.task('log', ` HTML: ${node.html}`) + }) + }) +} + +export const loginAsDataAnalystStefani = () => + loginAsActor('Data Analyst Stefani') + +export const loginAsFRADataAnalystFred = () => + loginAsActor('FRA Data Analyst Fred') + +export const checkA11y = ( + context = null, + options = {}, + violationCallback = terminalLog +) => { + cy.injectAxe() + + let opt = { + includedImpacts: ['critical', 'serious'], + ...options, + } + cy.checkA11y(context, opt, violationCallback) +} diff --git a/tdrs-frontend/cypress/e2e/profile/a11y.js b/tdrs-frontend/cypress/e2e/profile/a11y.js new file mode 100644 index 000000000..43e479f5f --- /dev/null +++ b/tdrs-frontend/cypress/e2e/profile/a11y.js @@ -0,0 +1,17 @@ +import { loginAsDataAnalystStefani, checkA11y } from '../common-steps/a11y' + +describe('Profile flow accessibility', () => { + before(() => { + loginAsDataAnalystStefani() + }) + + it('is accessible when viewing profile', () => { + cy.visit('/profile') + + // make sure we are on the profile page + cy.url().should('include', '/profile') + cy.get('main').should('exist') + + checkA11y() + }) +}) diff --git a/tdrs-frontend/package.json b/tdrs-frontend/package.json index 656cc742a..a49ae3884 100644 --- a/tdrs-frontend/package.json +++ b/tdrs-frontend/package.json @@ -124,10 +124,8 @@ }, "cypress-cucumber-preprocessor": { "stepDefinitions": [ - "cypress/e2e/*/[filepath].js", - "cypress/e2e/*/*.js", - "cypress/e2e/common-steps/*.js", - "cypress/e2e/accounts/*.js" + "cypress/e2e/[filepart]/*.js", + "cypress/e2e/common-steps/*.js" ] }, "overrides": { From cffcd105fd74fe4bf32d98c632074bb6880c8f88 Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Fri, 16 Jan 2026 11:31:28 -0600 Subject: [PATCH 027/148] improve datafiles page timing --- .../cypress/e2e/data-files/file_upload.js | 15 +++++++++------ .../cypress/e2e/data-files/submission_history.js | 4 ++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/tdrs-frontend/cypress/e2e/data-files/file_upload.js b/tdrs-frontend/cypress/e2e/data-files/file_upload.js index f517eb911..d939b3cf4 100644 --- a/tdrs-frontend/cypress/e2e/data-files/file_upload.js +++ b/tdrs-frontend/cypress/e2e/data-files/file_upload.js @@ -45,6 +45,7 @@ Then('{string} can download the {string} error report', (actor, program) => { When('{string} submits the Work Outcomes Report', (actor) => { cy.visit('/fra-data-files') + cy.get('h1').contains('FRA Data Files').should('exist') if (actor.includes('Admin')) { df.fillSttFyQ('New York', '2024', 'Q2', false, false) } else { @@ -63,6 +64,7 @@ When('{string} submits the Work Outcomes Report', (actor) => { When('{string} submits the TANF Report', (actor) => { cy.visit('/data-files') + cy.get('h1').contains('TANF Data Files').should('exist') if (actor.includes('Admin')) { df.fillSttFyQ('New York', '2021', 'Q1', true, false) } else if (actor.includes('FRA')) { @@ -84,6 +86,7 @@ When('{string} submits the TANF Report', (actor) => { When('{string} submits the SSP Report', (actor) => { cy.visit('/data-files') + cy.get('h1').contains('TANF Data Files').should('exist') if (actor.includes('Admin')) { df.fillSttFyQ('Iowa', '2024', 'Q1', false, false) } else if (actor.includes('FRA')) { @@ -171,12 +174,12 @@ Then( program === 'TANF' ? 'TANF' : program === 'FRA' - ? 'FRA' - : program === 'SSP' - ? 'SSP' - : program === 'Tribal' || program === 'TRIBAL' - ? 'Tribal' - : '' + ? 'FRA' + : program === 'SSP' + ? 'SSP' + : program === 'Tribal' || program === 'TRIBAL' + ? 'Tribal' + : '' df.downloadErrorReportAndAssert(section, year, quarter, programPrefix) }) } diff --git a/tdrs-frontend/cypress/e2e/data-files/submission_history.js b/tdrs-frontend/cypress/e2e/data-files/submission_history.js index 103e539ff..8f7bdd6cb 100644 --- a/tdrs-frontend/cypress/e2e/data-files/submission_history.js +++ b/tdrs-frontend/cypress/e2e/data-files/submission_history.js @@ -7,6 +7,7 @@ import * as df from '../common-steps/data_files.js' // TANF steps Then('Admin Alex can view the Illinois TANF Submission History', () => { cy.visit('/data-files') + cy.get('h1').contains('TANF Data Files').should('exist') df.fillSttFyQNoProgramSelector('Illinois', '2023', 'Q1') cy.get('button').contains('Submission History').click() }) @@ -19,6 +20,7 @@ Then('Admin Alex can verify the Illinois TANF submission', () => { // SSP steps Then('Admin Alex can view the Missouri SSP Submission History', () => { cy.visit('/data-files') + cy.get('h1').contains('TANF Data Files').should('exist') df.fillSttFyQ('Missouri', '2024', 'Q1', false, false) cy.get('button').contains('Submission History').click() }) @@ -31,6 +33,7 @@ Then('Admin Alex can verify the Missouri SSP submission', () => { // FRA steps Then('Admin Alex can view the Arizona FRA Submission History', () => { cy.visit('/fra-data-files') + cy.get('h1').contains('FRA Data Files').should('exist') df.fillSttFyQNoProgramSelector('Arizona', '2024', 'Q2') }) @@ -44,6 +47,7 @@ Then('Admin Alex can verify the Arizona FRA submission', () => { When('Regional Randy searches TANF Data Files', () => { cy.visit('/data-files') + cy.get('h1').contains('TANF Data Files').should('exist') df.fillSttFyQ('California', '2021', 'Q1', true, true) }) From 08f6f586c4f20e1909b325c070f8144fe36d122e Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Fri, 16 Jan 2026 11:31:44 -0600 Subject: [PATCH 028/148] cache auth state between specs --- .../cypress/e2e/common-steps/common-steps.js | 31 ++-- tdrs-frontend/cypress/support/commands.js | 151 +++++++----------- 2 files changed, 69 insertions(+), 113 deletions(-) diff --git a/tdrs-frontend/cypress/e2e/common-steps/common-steps.js b/tdrs-frontend/cypress/e2e/common-steps/common-steps.js index 32d762f57..86ad0f22d 100644 --- a/tdrs-frontend/cypress/e2e/common-steps/common-steps.js +++ b/tdrs-frontend/cypress/e2e/common-steps/common-steps.js @@ -90,6 +90,15 @@ export const ACTORS = { }, } +export const loginAsActor = (actor) => { + cy.login(ACTORS[actor].username) + cy.visit('/') + + cy.contains(new RegExp('Welcome to TDP|Request Submitted'), { + timeout: 30000, + }) +} + const setAccountStatus = (actor, status) => { let endpoint = null switch (status) { @@ -106,34 +115,24 @@ const setAccountStatus = (actor, status) => { break } - cy.get('@cypressUsers').then((cypressUsers) => { - cy.log(cypressUsers) + cy.adminLogin('cypress-admin-alex@teamraft.com') + cy.visit('/') + + cy.window().then((win) => { + let cypressUsers = JSON.parse(win.localStorage.getItem('cypressUsers')) const username = ACTORS[actor].username - cy.log(username) const user = Cypress._.find(cypressUsers, (u) => u.username === username) - - cy.log(user) cy.adminApiRequest('PATCH', `/cypress-users/${user.id}/${endpoint}/`) }) } -export const clearCookies = () => { - cy.clearCookie('sessionid') - cy.clearCookie('csrftoken') -} - Given('{string} logs in', (actor) => { - clearCookies() - cy.visit('/') - cy.adminLogin('cypress-admin-alex@teamraft.com') // if unapproved, reset if (Cypress._.startsWith(actor, 'Unapproved')) { setAccountStatus(actor, 'Initial') } - cy.contains('Sign into TANF Data Portal', { timeout: 30000 }) - - cy.login(ACTORS[actor].username) + loginAsActor(actor) }) When('Admin Alex approves {string}', (actor) => { diff --git a/tdrs-frontend/cypress/support/commands.js b/tdrs-frontend/cypress/support/commands.js index 7f8551e31..582863ade 100644 --- a/tdrs-frontend/cypress/support/commands.js +++ b/tdrs-frontend/cypress/support/commands.js @@ -26,49 +26,56 @@ // -- This will overwrite an existing command -- // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) -Cypress.Commands.add('login', (username) => { - cy.request({ - method: 'GET', - url: `${Cypress.env('apiUrl')}/login/cypress`, - qs: { username }, - headers: { - 'X-Cypress-Token': Cypress.env('cypressToken'), - }, - }).then((response) => { - cy.visit('/') - cy.window() - .its('store') - .invoke('dispatch', { - type: 'SET_AUTH', - payload: { - user: response?.body?.user, +Cypress.Commands.add('login', (username) => + cy.session( + username, + () => { + cy.request({ + method: 'GET', + url: `${Cypress.env('apiUrl')}/login/cypress`, + qs: { username }, + headers: { + 'X-Cypress-Token': Cypress.env('cypressToken'), }, + }).then((response) => { + cy.visit('/') + cy.window() + .its('store') + .invoke('dispatch', { + type: 'SET_AUTH', + payload: { + user: response?.body?.user, + }, + }) }) - - cy.getCookie('sessionid').its('value').as('userSessionId') - cy.getCookie('csrftoken').its('value').as('userCsrfToken') - }) -}) - -Cypress.Commands.add('adminLogin', () => { - cy.request({ - method: 'GET', - url: `${Cypress.env('apiUrl')}/login/cypress`, - qs: { username: 'cypress-admin@teamraft.com' }, - headers: { - 'X-Cypress-Token': Cypress.env('cypressToken'), }, - }).then((response) => { - cy.getCookie('sessionid').its('value').as('adminSessionId') - cy.getCookie('csrftoken').its('value').as('adminCsrfToken') - - // handle response, list of user emails/ids for use in adminConsoleFormRequest - cy.wrap(response.body.users).as('cypressUsers') - }) + { cacheAcrossSpecs: true } + ) +) - cy.clearCookie('sessionid') - cy.clearCookie('csrftoken') -}) +Cypress.Commands.add('adminLogin', (username) => + cy.session( + username, + () => { + cy.visit('/') + cy.request({ + method: 'GET', + url: `${Cypress.env('apiUrl')}/login/cypress`, + qs: { username }, + headers: { + 'X-Cypress-Token': Cypress.env('cypressToken'), + }, + }).then((response) => { + // handle response, list of user emails/ids for use in adminConsoleFormRequest + window.localStorage.setItem( + 'cypressUsers', + JSON.stringify(response.body.users) + ) + }) + }, + { cacheAcrossSpecs: true } + ) +) Cypress.Commands.add( 'adminConsoleFormRequest', @@ -83,34 +90,7 @@ Cypress.Commands.add( }, } - cy.get('@adminSessionId').then((sessionId) => - cy.setCookie('sessionid', sessionId) - ) - cy.clearCookie('csrftoken') - cy.get('@adminCsrfToken').then((csrfToken) => { - cy.setCookie('csrftoken', csrfToken) - options.headers['X-CSRFToken'] = csrfToken - }) - - cy.request(options) - - cy.clearCookie('sessionid') - cy.clearCookie('csrftoken') - - const userSessionId = cy.state('aliases').userSessionId - const userCsrfToken = cy.state('aliases').userCsrfToken - - if (userSessionId) { - cy.get('@userSessionId').then((sessionId) => - cy.setCookie('sessionid', sessionId) - ) - } - - if (userCsrfToken) { - cy.get('@userCsrfToken').then((csrfToken) => - cy.setCookie('csrftoken', csrfToken) - ) - } + return cy.request(options) } ) @@ -127,37 +107,14 @@ Cypress.Commands.add( }, } - cy.get('@adminSessionId').then((sessionId) => - cy.setCookie('sessionid', sessionId) - ) - cy.get('@adminCsrfToken').then((csrfToken) => { - cy.setCookie('csrftoken', csrfToken) - options.headers['X-CSRFToken'] = csrfToken - }) - - cy.request(options).then((r) => { - cy.wrap(r).as('response') - }) - - cy.clearCookie('sessionid') - cy.clearCookie('csrftoken') - - const userSessionId = cy.state('aliases').userSessionId - const userCsrfToken = cy.state('aliases').userCsrfToken - - if (userSessionId) { - cy.get('@userSessionId').then((sessionId) => - cy.setCookie('sessionid', sessionId) - ) - } - - if (userCsrfToken) { - cy.get('@userCsrfToken').then((csrfToken) => - cy.setCookie('csrftoken', csrfToken) - ) - } + return cy + .getCookie('csrftoken') + .its('value') + .then((csrfToken) => { + options.headers['X-CSRFToken'] = csrfToken - return cy.get('@response') + return cy.request(options) + }) } ) From c09493f68a68c6f1cd0d3f75899c17c1988b5b7d Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Tue, 20 Jan 2026 07:51:07 -0600 Subject: [PATCH 029/148] rm missing ref --- tdrs-frontend/cypress/e2e/common-steps/data_files.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tdrs-frontend/cypress/e2e/common-steps/data_files.js b/tdrs-frontend/cypress/e2e/common-steps/data_files.js index b9cca4684..9bc7094b8 100644 --- a/tdrs-frontend/cypress/e2e/common-steps/data_files.js +++ b/tdrs-frontend/cypress/e2e/common-steps/data_files.js @@ -1,9 +1,7 @@ /* eslint-disable no-undef */ import { When, Then } from '@badeball/cypress-cucumber-preprocessor' -import { clearCookies } from './common-steps' export const restartAtHomePage = () => { - clearCookies() cy.intercept('/v1/stts/alpha').as('getSttSearchList') cy.visit('/') cy.contains('Sign into TANF Data Portal', { timeout: 30000 }) @@ -65,7 +63,10 @@ export const validateFraCsv = () => { } export const downloadErrorReport = (error_report_name) => { - cy.get('button').contains(error_report_name).should('exist').click({ force: true }) + cy.get('button') + .contains(error_report_name) + .should('exist') + .click({ force: true }) cy.readFile(`${Cypress.config('downloadsFolder')}/${error_report_name}`) } @@ -254,9 +255,7 @@ export const downloadErrorReportAndAssert = ( cy.intercept('GET', '/v1/data_files/*/download_error_report/').as( 'downloadErrorReport' ) - cy.contains('button', fileName) - .should('exist') - .click({ force: true }) + cy.contains('button', fileName).should('exist').click({ force: true }) cy.wait('@downloadErrorReport').its('response.statusCode').should('eq', 200) // Assert Error Report successfully downloaded From ba6dc0ec54d23860412ea69c7a37453a371f976e Mon Sep 17 00:00:00 2001 From: Matt Cole Anderson Date: Thu, 8 Jan 2026 15:23:17 -0600 Subject: [PATCH 030/148] health grafana dashboard not using proper job name for celery prometheus metrics --- .../plg/grafana/dashboards/health.json | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tdrs-backend/plg/grafana/dashboards/health.json b/tdrs-backend/plg/grafana/dashboards/health.json index b0344033d..182e54487 100644 --- a/tdrs-backend/plg/grafana/dashboards/health.json +++ b/tdrs-backend/plg/grafana/dashboards/health.json @@ -555,7 +555,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "100 * avg_over_time(up{job=~\"celery-$app_env|celery-exporter-$app_env\"}[$__range])", + "expr": "100 * avg_over_time(up{job=~\"tdp-celery-$app_env|tdp-celery-exporter-$app_env\"}[$__range])", "instant": false, "legendFormat": "__auto", "range": true, @@ -629,7 +629,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(ALERTS{job=~\"celery-$app_env|celery-exporter-$app_env\", alertstate=\"firing\"})", + "expr": "sum(ALERTS{job=~\"tdp-celery-$app_env|tdp-celery-exporter-$app_env\", alertstate=\"firing\"})", "instant": false, "legendFormat": "__auto", "range": true, @@ -719,7 +719,7 @@ "uid": "$datasource" }, "editorMode": "code", - "expr": "sum (\n round(\n increase(\n celery_task_succeeded_total{\n job=\"celery-$app_env\",\n queue_name=~\"$queue_name\"\n }[$__range]\n )\n )\n) by (name)\n/(sum (\n round(\n increase(\n celery_task_succeeded_total{\n job=\"celery-$app_env\",\n queue_name=~\"$queue_name\"\n }[$__range]\n )\n )\n) by (name)\n+sum (\n round(\n increase(\n celery_task_failed_total{\n job=\"celery-$app_env\",\n queue_name=~\"$queue_name\"\n }[$__range]\n )\n )\n) by (name)\n) > -1\n", + "expr": "sum (\n round(\n increase(\n celery_task_succeeded_total{\n job=\"tdp-celery-$app_env\",\n queue_name=~\"$queue_name\"\n }[$__range]\n )\n )\n) by (name)\n/(sum (\n round(\n increase(\n celery_task_succeeded_total{\n job=\"tdp-celery-$app_env\",\n queue_name=~\"$queue_name\"\n }[$__range]\n )\n )\n) by (name)\n+sum (\n round(\n increase(\n celery_task_failed_total{\n job=\"tdp-celery-$app_env\",\n queue_name=~\"$queue_name\"\n }[$__range]\n )\n )\n) by (name)\n) > -1\n", "format": "table", "instant": true, "refId": "A" @@ -730,7 +730,7 @@ "uid": "$datasource" }, "editorMode": "code", - "expr": "sum (\n round(\n increase(\n celery_task_succeeded_total{\n job=\"celery-$app_env\",\n queue_name=~\"$queue_name\"\n }[$__range]\n )\n )\n) by (name) > 0\n", + "expr": "sum (\n round(\n increase(\n celery_task_succeeded_total{\n job=\"tdp-celery-$app_env\",\n queue_name=~\"$queue_name\"\n }[$__range]\n )\n )\n) by (name) > 0\n", "format": "table", "instant": true, "refId": "B" @@ -741,7 +741,7 @@ "uid": "$datasource" }, "editorMode": "code", - "expr": "sum (\n round(\n increase(\n celery_task_failed_total{\n job=\"celery-$app_env\",\n queue_name=~\"$queue_name\"\n }[$__range]\n )\n )\n) by (name) > 0\n", + "expr": "sum (\n round(\n increase(\n celery_task_failed_total{\n job=\"tdp-celery-$app_env\",\n queue_name=~\"$queue_name\"\n }[$__range]\n )\n )\n) by (name) > 0\n", "format": "table", "instant": true, "refId": "C" @@ -752,7 +752,7 @@ "uid": "$datasource" }, "editorMode": "code", - "expr": "sum (\n round(\n increase(\n celery_task_sent_total{\n job=\"celery-$app_env\",\n queue_name=~\"$queue_name\"\n }[$__range]\n )\n )\n) by (name) > 0\n", + "expr": "sum (\n round(\n increase(\n celery_task_sent_total{\n job=\"tdp-celery-$app_env\",\n queue_name=~\"$queue_name\"\n }[$__range]\n )\n )\n) by (name) > 0\n", "format": "table", "instant": true, "refId": "D" @@ -763,7 +763,7 @@ "uid": "$datasource" }, "editorMode": "code", - "expr": "sum (\n round(\n increase(\n celery_task_received_total{\n job=\"celery-$app_env\",\n queue_name=~\"$queue_name\"\n }[$__range]\n )\n )\n) by (name) > 0\n", + "expr": "sum (\n round(\n increase(\n celery_task_received_total{\n job=\"tdp-celery-$app_env\",\n queue_name=~\"$queue_name\"\n }[$__range]\n )\n )\n) by (name) > 0\n", "format": "table", "instant": true, "refId": "E" @@ -774,7 +774,7 @@ "uid": "$datasource" }, "editorMode": "code", - "expr": "sum (\n round(\n increase(\n celery_task_rejected_total{\n job=\"celery-$app_env\",\n queue_name=~\"$queue_name\"\n }[$__range]\n )\n )\n) by (name) > 0\n", + "expr": "sum (\n round(\n increase(\n celery_task_rejected_total{\n job=\"tdp-celery-$app_env\",\n queue_name=~\"$queue_name\"\n }[$__range]\n )\n )\n) by (name) > 0\n", "format": "table", "instant": true, "refId": "F" @@ -785,7 +785,7 @@ "uid": "$datasource" }, "editorMode": "code", - "expr": "sum (\n round(\n increase(\n celery_task_retried_total{\n job=\"celery-$app_env\",\n queue_name=~\"$queue_name\"\n }[$__range]\n )\n )\n) by (name) > 0\n", + "expr": "sum (\n round(\n increase(\n celery_task_retried_total{\n job=\"tdp-celery-$app_env\",\n queue_name=~\"$queue_name\"\n }[$__range]\n )\n )\n) by (name) > 0\n", "format": "table", "instant": true, "refId": "G" @@ -796,7 +796,7 @@ "uid": "$datasource" }, "editorMode": "code", - "expr": "sum (\n round(\n increase(\n celery_task_revoked_total{\n job=\"celery-$app_env\",\n queue_name=~\"$queue_name\"\n }[$__range]\n )\n )\n) by (name) > 0\n", + "expr": "sum (\n round(\n increase(\n celery_task_revoked_total{\n job=\"tdp-celery-$app_env\",\n queue_name=~\"$queue_name\"\n }[$__range]\n )\n )\n) by (name) > 0\n", "format": "table", "instant": true, "refId": "H" @@ -907,7 +907,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "sum(\n celery_task_runtime_sum{\n job=\"celery-$app_env\",\n queue_name=~\"$queue_name\"\n }\n) by (name) / sum(\n celery_task_runtime_count{\n job=\"celery-$app_env\",\n queue_name=~\"$queue_name\"\n }\n) by (name)\n\n", + "expr": "sum(\n celery_task_runtime_sum{\n job=\"tdp-celery-$app_env\",\n queue_name=~\"$queue_name\"\n }\n) by (name) / sum(\n celery_task_runtime_count{\n job=\"tdp-celery-$app_env\",\n queue_name=~\"$queue_name\"\n }\n) by (name)\n\n", "format": "table", "instant": true, "legendFormat": "", @@ -1023,7 +1023,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(celery_active_worker_count{job=\"celery-$app_env\", queue_name=\"$queue_name\"}) by (queue_name)", + "expr": "sum(celery_active_worker_count{job=\"tdp-celery-$app_env\", queue_name=\"$queue_name\"}) by (queue_name)", "instant": false, "legendFormat": "__auto", "range": true, @@ -1588,7 +1588,7 @@ "label": "Queue Name", "name": "queue_name", "options": [], - "query": "label_values(celery_task_received_total{namespace=\"$namespace\", job=\"celery-$app_env\", name!~\"None\"}, queue_name)", + "query": "label_values(celery_task_received_total{namespace=\"$namespace\", job=\"tdp-celery-$app_env\", name!~\"None\"}, queue_name)", "refresh": 2, "regex": "", "sort": 1, @@ -1605,4 +1605,4 @@ "title": "Uptime/Health", "uid": "aeh7ymwdwpvk0e", "version": 1 -} \ No newline at end of file +} From 42ac75e70e3ac56687d0057f1e7ea59455cca7bb Mon Sep 17 00:00:00 2001 From: Matt Cole Anderson Date: Thu, 8 Jan 2026 15:23:57 -0600 Subject: [PATCH 031/148] prepend 'tdp-' to local celery prometheus job to match nonlocal env naming convention --- tdrs-backend/plg/prometheus/prometheus.local.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tdrs-backend/plg/prometheus/prometheus.local.yml b/tdrs-backend/plg/prometheus/prometheus.local.yml index 08549574b..46cec4f05 100644 --- a/tdrs-backend/plg/prometheus/prometheus.local.yml +++ b/tdrs-backend/plg/prometheus/prometheus.local.yml @@ -33,7 +33,7 @@ scrape_configs: service: "tdp-backend" env: "local" - - job_name: "celery-local" + - job_name: "tdp-celery-local" static_configs: - targets: ["celery-exporter:9808"] labels: From 588e6433fff8025c0c398c34337256473ce18cc2 Mon Sep 17 00:00:00 2001 From: Matt Cole Anderson Date: Thu, 8 Jan 2026 15:26:48 -0600 Subject: [PATCH 032/148] export REDIS_URI using django settings so the celery_exporter can access it --- tdrs-backend/celery_start.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tdrs-backend/celery_start.sh b/tdrs-backend/celery_start.sh index 79cb1629f..ce7a99ea6 100755 --- a/tdrs-backend/celery_start.sh +++ b/tdrs-backend/celery_start.sh @@ -3,6 +3,9 @@ echo "Starting celery" if [[ $1 == "cloud" ]]; then + # Get the computed URI from django settings + REDIS_URI=$(python manage.py shell -c "from django.conf import settings; print(settings.CELERY_BROKER_URL)" 2>/dev/null) + echo "Starting Alloy" mkdir /home/vcap/app/alloy-data wget https://github.com/grafana/alloy/releases/download/v1.9.1/alloy-boringcrypto-linux-amd64.zip From 70fbf34f93ccc2313eaa643499618d8bd26fe911 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Wed, 28 Jan 2026 16:28:14 -0600 Subject: [PATCH 033/148] - Test using jq to get uri and quote uri when passing to executanles --- tdrs-backend/celery_start.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tdrs-backend/celery_start.sh b/tdrs-backend/celery_start.sh index ce7a99ea6..d9da2e5b6 100755 --- a/tdrs-backend/celery_start.sh +++ b/tdrs-backend/celery_start.sh @@ -3,8 +3,8 @@ echo "Starting celery" if [[ $1 == "cloud" ]]; then - # Get the computed URI from django settings - REDIS_URI=$(python manage.py shell -c "from django.conf import settings; print(settings.CELERY_BROKER_URL)" 2>/dev/null) + # Get the computed URI from VCAP_SERVICES + REDIS_URI=$(echo $VCAP_SERVICES | jq -r '."aws-elasticache-redis"[0].credentials.uri') echo "Starting Alloy" mkdir /home/vcap/app/alloy-data @@ -16,7 +16,7 @@ if [[ $1 == "cloud" ]]; then echo "Starting the Celery Exporter" curl -L https://github.com/danihodovic/celery-exporter/releases/download/latest/celery-exporter -o ./celery-exporter chmod +x ./celery-exporter - ./celery-exporter --broker-url=$REDIS_URI --port 9808 & + ./celery-exporter --broker-url="$REDIS_URI" --port 9808 & fi # Celery worker config can be found here: https://docs.celeryq.dev/en/stable/userguide/workers.html#:~:text=The-,hostname,-argument%20can%20expand @@ -24,5 +24,5 @@ celery -A tdpservice.settings worker --loglevel=INFO --concurrency=1 --max-tasks sleep 5 # TODO: Uncomment the following line to add flower service when memory limitation is resolved -celery -A tdpservice.settings --broker=$REDIS_URI flower --port=8080 & -celery -A tdpservice.settings beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler \ No newline at end of file +celery -A tdpservice.settings --broker="$REDIS_URI" flower --port=8080 & +celery -A tdpservice.settings beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler From 544605329729378b23b91c011037b67f393a25c8 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Wed, 21 Jan 2026 12:35:58 -0600 Subject: [PATCH 034/148] -updated so that open feedback widget accepts the submitted files as a parameter - Update submit to return the submitted files --- tdrs-frontend/src/actions/reports.js | 11 +++++++--- .../src/components/Reports/ReportsContext.jsx | 21 +++++++++++-------- tdrs-frontend/src/hooks/useFileUploadForm.js | 4 ++-- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/tdrs-frontend/src/actions/reports.js b/tdrs-frontend/src/actions/reports.js index c95b91fe2..ca4c388c4 100644 --- a/tdrs-frontend/src/actions/reports.js +++ b/tdrs-frontend/src/actions/reports.js @@ -222,7 +222,8 @@ export const submit = }) removeFileInputErrorState() - const submittedFiles = responses.reduce((result, response) => { + const submittedFileObjects = [] + const submittedFileNames = responses.reduce((result, response) => { const submittedFile = map_section(fileType, response?.data) dispatch({ @@ -230,6 +231,7 @@ export const submit = payload: { submittedFile }, }) + submittedFileObjects.push(submittedFile) result.push( `${submittedFile?.original_filename} (${submittedFile?.extension})` ) @@ -240,8 +242,8 @@ export const submit = const fileIds = responses.map((response) => response?.data?.id) logger.alert( `Submitted ${ - submittedFiles.length - } data file(s): ${submittedFiles.join(', ')}`, + submittedFileNames.length + } data file(s): ${submittedFileNames.join(', ')}`, { files: fileIds, activity: 'upload', @@ -249,6 +251,9 @@ export const submit = ) onComplete(fileIds) + + // Return the submitted file objects for feedback widget + return submittedFileObjects }) .catch((error) => { const error_response = error.response?.data diff --git a/tdrs-frontend/src/components/Reports/ReportsContext.jsx b/tdrs-frontend/src/components/Reports/ReportsContext.jsx index 1a0446ba5..6ab9842b8 100644 --- a/tdrs-frontend/src/components/Reports/ReportsContext.jsx +++ b/tdrs-frontend/src/components/Reports/ReportsContext.jsx @@ -313,15 +313,18 @@ export const ReportsProvider = ({ isFra = false, children }) => { setPendingChange({ type: null, value: null }) } - const handleOpenFeedbackWidget = useCallback(() => { - dispatch( - openFeedbackWidget({ - dataType: fileTypeInputValue, - dataFiles: submittedFiles, - widgetId: `${fileTypeInputValue}-report-submission-feedback`, - }) - ) - }, [dispatch, fileTypeInputValue, submittedFiles]) + const handleOpenFeedbackWidget = useCallback( + (files = null) => { + dispatch( + openFeedbackWidget({ + dataType: fileTypeInputValue, + dataFiles: files || submittedFiles, + widgetId: `${fileTypeInputValue}-report-submission-feedback`, + }) + ) + }, + [dispatch, fileTypeInputValue, submittedFiles] + ) const selectFileType = (value) => { setFileTypeTouched(true) diff --git a/tdrs-frontend/src/hooks/useFileUploadForm.js b/tdrs-frontend/src/hooks/useFileUploadForm.js index 1538afa46..3fae4e9be 100644 --- a/tdrs-frontend/src/hooks/useFileUploadForm.js +++ b/tdrs-frontend/src/hooks/useFileUploadForm.js @@ -129,10 +129,10 @@ export const useFileUploadForm = ({ fileType: fileTypeInputValue, }) - await executeSubmission(() => + const submittedFiles = await executeSubmission(() => dispatch(submit(payload, onFileUploadSuccess)) ) - handleOpenFeedbackWidget() + handleOpenFeedbackWidget(submittedFiles) } catch (error) { console.error('Error during form submission:', error) setLocalAlertState({ From f8e4076b02af49215f2c4131eb6e9fce97dd6017 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Thu, 15 Jan 2026 17:15:40 -0600 Subject: [PATCH 035/148] - Created helper functions to validate textual dates - Updated view generator to leverage new functions to validate both dob and rpt_month_year --- .../plg/grafana_views/generate_views.py | 98 ++++++++----------- .../plg/grafana_views/helper_functions.sql | 45 +++++++++ 2 files changed, 84 insertions(+), 59 deletions(-) create mode 100644 tdrs-backend/plg/grafana_views/helper_functions.sql diff --git a/tdrs-backend/plg/grafana_views/generate_views.py b/tdrs-backend/plg/grafana_views/generate_views.py index ce5c00e5a..781e9f346 100644 --- a/tdrs-backend/plg/grafana_views/generate_views.py +++ b/tdrs-backend/plg/grafana_views/generate_views.py @@ -111,33 +111,18 @@ def handle_field(field, formatted_fields, is_admin): formatted_fields.append( f''' -- CASE -- Calculate AGE_FIRST: Age as of the first day of the reporting month - WHEN "{field}" ~ '^[0-9]{{8}}$' AND - -- Validate year (reasonable range) + WHEN is_valid_date_yyyymmdd("{field}") AND + is_valid_yyyymm("RPT_MONTH_YEAR"::TEXT) AND + -- Validate year (reasonable range for birth year) CAST(SUBSTRING("{field}" FROM 1 FOR 4) AS INTEGER) BETWEEN 1900 AND - EXTRACT(YEAR FROM CURRENT_DATE) AND - -- Validate month (01-12) - CAST(SUBSTRING("{field}" FROM 5 FOR 2) AS INTEGER) BETWEEN 1 AND 12 AND - -- Validate day (01-31) - CAST(SUBSTRING("{field}" FROM 7 FOR 2) AS INTEGER) BETWEEN 1 AND 31 AND - -- Validate RPT_MONTH_YEAR format (YYYYMM) - "RPT_MONTH_YEAR"::TEXT ~ '^[0-9]{{6}}$' + EXTRACT(YEAR FROM CURRENT_DATE) THEN - -- Simple calculation: (end_date - start_date) / 365.25 ROUND( EXTRACT(EPOCH FROM ( - -- Calculate the difference in days between last day of reporting month and birth date - (DATE_TRUNC('MONTH', TO_DATE( - SUBSTRING("RPT_MONTH_YEAR"::TEXT FROM 1 FOR 4) || '-' || - SUBSTRING("RPT_MONTH_YEAR"::TEXT FROM 5 FOR 2) || '-01', - 'YYYY-MM-DD' - ))) - - TO_DATE( - SUBSTRING("{field}" FROM 1 FOR 4) || '-' || - SUBSTRING("{field}" FROM 5 FOR 2) || '-' || - SUBSTRING("{field}" FROM 7 FOR 2), - 'YYYY-MM-DD' - ) - )) / (365.25 * 86400), -- Convert seconds to years (86400 seconds per day) + -- First day of reporting month minus birth date + DATE_TRUNC('MONTH', TO_DATE("RPT_MONTH_YEAR"::TEXT || '01', 'YYYYMMDD')) - + TO_DATE("{field}", 'YYYYMMDD') + )) / (365.25 * 86400), -- Convert seconds to years 1 -- Round to 1 decimal place ) ELSE NULL @@ -147,33 +132,18 @@ def handle_field(field, formatted_fields, is_admin): formatted_fields.append( f''' -- CASE -- Calculate AGE_LAST: Age as of the last day of the reporting month - WHEN "{field}" ~ '^[0-9]{{8}}$' AND - -- Validate year (reasonable range) + WHEN is_valid_date_yyyymmdd("{field}") AND + is_valid_yyyymm("RPT_MONTH_YEAR"::TEXT) AND + -- Validate year (reasonable range for birth year) CAST(SUBSTRING("{field}" FROM 1 FOR 4) AS INTEGER) BETWEEN 1900 AND - EXTRACT(YEAR FROM CURRENT_DATE) AND - -- Validate month (01-12) - CAST(SUBSTRING("{field}" FROM 5 FOR 2) AS INTEGER) BETWEEN 1 AND 12 AND - -- Validate day (01-31) - CAST(SUBSTRING("{field}" FROM 7 FOR 2) AS INTEGER) BETWEEN 1 AND 31 AND - -- Validate RPT_MONTH_YEAR format (YYYYMM) - "RPT_MONTH_YEAR"::TEXT ~ '^[0-9]{{6}}$' + EXTRACT(YEAR FROM CURRENT_DATE) THEN - -- Simple calculation: (end_date - start_date) / 365.25 ROUND( EXTRACT(EPOCH FROM ( - -- Calculate the difference in days between last day of reporting month and birth date - (DATE_TRUNC('MONTH', TO_DATE( - SUBSTRING("RPT_MONTH_YEAR"::TEXT FROM 1 FOR 4) || '-' || - SUBSTRING("RPT_MONTH_YEAR"::TEXT FROM 5 FOR 2) || '-01', - 'YYYY-MM-DD' - )) + INTERVAL '1 MONTH - 1 day') - - TO_DATE( - SUBSTRING("{field}" FROM 1 FOR 4) || '-' || - SUBSTRING("{field}" FROM 5 FOR 2) || '-' || - SUBSTRING("{field}" FROM 7 FOR 2), - 'YYYY-MM-DD' - ) - )) / (365.25 * 86400), -- Convert seconds to years (86400 seconds per day) + -- Last day of reporting month minus birth date + (DATE_TRUNC('MONTH', TO_DATE("RPT_MONTH_YEAR"::TEXT || '01', 'YYYYMMDD')) + INTERVAL '1 MONTH - 1 day') - + TO_DATE("{field}", 'YYYYMMDD') + )) / (365.25 * 86400), -- Convert seconds to years 1 -- Round to 1 decimal place ) ELSE NULL @@ -182,20 +152,13 @@ def handle_field(field, formatted_fields, is_admin): formatted_fields.append( f''' -- - -- Determine AGE_VALID + -- Determine AGE_VALID (1 if DATE_OF_BIRTH is a valid date with reasonable birth year, 0 otherwise) CASE - WHEN "{field}" !~ '^[0-9]{{8}}$' OR - -- Perform null check - "{field}" IS NULL OR "{field}" = '' OR - -- Validate year (reasonable range) - CAST(SUBSTRING("{field}" FROM 1 FOR 4) AS INTEGER) NOT BETWEEN 1900 AND - EXTRACT(YEAR FROM CURRENT_DATE) OR - -- Validate month (01-12) - CAST(SUBSTRING("{field}" FROM 5 FOR 2) AS INTEGER) NOT BETWEEN 1 AND 12 OR - -- Validate day (01-31) - CAST(SUBSTRING("{field}" FROM 7 FOR 2) AS INTEGER) NOT BETWEEN 1 AND 31 - THEN 0 - ELSE 1 + WHEN is_valid_date_yyyymmdd("{field}") AND + CAST(SUBSTRING("{field}" FROM 1 FOR 4) AS INTEGER) BETWEEN 1900 AND + EXTRACT(YEAR FROM CURRENT_DATE) + THEN 1 + ELSE 0 END AS "AGE_VALID"'''.strip() ) else: @@ -222,6 +185,20 @@ def handle_table_name(schema_type, schema_name): return table_name, record_type +def write_helper_functions(output_dir: str) -> None: + """Copy helper functions SQL file to output directory.""" + source_file = os.path.join(CWD, "helper_functions.sql") + output_file = os.path.join(output_dir, "00_helper_functions.sql") + + with open(source_file, "r") as src: + helper_sql = src.read() + + with open(output_file, "w") as dest: + dest.write(helper_sql) + + logger.info(f"Copied helper functions SQL to {output_dir}") + + def handle_where_clause(record_type): """Add custom where clause based on record type.""" if "3" in record_type: @@ -252,6 +229,9 @@ def main(is_admin): output_dir = os.path.join(CWD, output_dir_name) os.makedirs(output_dir, exist_ok=True) + # Write helper functions SQL (required for date validation in views) + write_helper_functions(output_dir) + # Process each schema type and its schemas for schema_type, schemas in schema_data.items(): for schema_name, fields in schemas.items(): diff --git a/tdrs-backend/plg/grafana_views/helper_functions.sql b/tdrs-backend/plg/grafana_views/helper_functions.sql new file mode 100644 index 000000000..03177996a --- /dev/null +++ b/tdrs-backend/plg/grafana_views/helper_functions.sql @@ -0,0 +1,45 @@ +-- Helper functions for Grafana view date validation +-- Run this script before creating the views +-- +-- These functions safely validate date strings without throwing errors, +-- which is necessary because TO_DATE() throws an error on invalid dates +-- like '20230931' (September 31st doesn't exist). + +-- Validates an 8-digit date string in YYYYMMDD format (e.g., '20230915') +-- Returns TRUE if the string represents a valid calendar date, FALSE otherwise +CREATE OR REPLACE FUNCTION is_valid_date_yyyymmdd(date_str TEXT) +RETURNS BOOLEAN AS $$ +BEGIN + -- Check format first (8 digits) + IF date_str IS NULL OR date_str = '' OR date_str !~ '^[0-9]{8}$' THEN + RETURN FALSE; + END IF; + + -- Attempt to parse - will throw exception if invalid date + PERFORM TO_DATE(date_str, 'YYYYMMDD'); + RETURN TRUE; + +EXCEPTION WHEN OTHERS THEN + RETURN FALSE; +END; +$$ LANGUAGE plpgsql IMMUTABLE; + + +-- Validates a 6-digit year/month string in YYYYMM format (e.g., '202309') +-- Returns TRUE if the string represents a valid year/month, FALSE otherwise +CREATE OR REPLACE FUNCTION is_valid_yyyymm(yyyymm TEXT) +RETURNS BOOLEAN AS $$ +BEGIN + -- Check format first (6 digits) + IF yyyymm IS NULL OR yyyymm = '' OR yyyymm !~ '^[0-9]{6}$' THEN + RETURN FALSE; + END IF; + + -- Attempt to parse with day 01 - will throw exception if invalid month + PERFORM TO_DATE(yyyymm || '01', 'YYYYMMDD'); + RETURN TRUE; + +EXCEPTION WHEN OTHERS THEN + RETURN FALSE; +END; +$$ LANGUAGE plpgsql IMMUTABLE; From 59dcef0209e26e7b7ed7d89305c4d844a0b04241 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Thu, 15 Jan 2026 17:33:40 -0600 Subject: [PATCH 036/148] - update description --- tdrs-backend/plg/grafana_views/helper_functions.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tdrs-backend/plg/grafana_views/helper_functions.sql b/tdrs-backend/plg/grafana_views/helper_functions.sql index 03177996a..2154991ca 100644 --- a/tdrs-backend/plg/grafana_views/helper_functions.sql +++ b/tdrs-backend/plg/grafana_views/helper_functions.sql @@ -1,5 +1,5 @@ -- Helper functions for Grafana view date validation --- Run this script before creating the views +-- Must be run before creating the views (handled by generate_views.py) -- -- These functions safely validate date strings without throwing errors, -- which is necessary because TO_DATE() throws an error on invalid dates From 441acac51b7a44cc60cd5dd06e4da915312a9e79 Mon Sep 17 00:00:00 2001 From: Mo S <97037188+raftmsohani@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:01:43 -0500 Subject: [PATCH 037/148] quick lint fix (#5607) --- tdrs-frontend/src/components/Link/Link.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tdrs-frontend/src/components/Link/Link.jsx b/tdrs-frontend/src/components/Link/Link.jsx index 9d0eb1d1e..3ce23bb58 100644 --- a/tdrs-frontend/src/components/Link/Link.jsx +++ b/tdrs-frontend/src/components/Link/Link.jsx @@ -8,7 +8,7 @@ function Anchor({ to, children, ...props }) { ) } -function LinkComponent({to, key, target, children, ...props}) { +function LinkComponent({ to, key, target, children, ...props }) { const LinkComponent = !to.startsWith('/') ? Anchor : Link return ( From 6734d733d6bcd0028758600cd8c840848ef15390 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Tue, 3 Feb 2026 16:37:20 -0600 Subject: [PATCH 038/148] - Add django admin interface as a dependency to get better 508 compliance - Update pipfile and lock file - Add data migration to enable uswds by default - Add to installed apps --- tdrs-backend/Pipfile | 1 + tdrs-backend/Pipfile.lock | 654 +++++++++++------- .../migrations/0003_load_uswds_admin_theme.py | 40 ++ tdrs-backend/tdpservice/settings/common.py | 5 + 4 files changed, 435 insertions(+), 265 deletions(-) create mode 100644 tdrs-backend/tdpservice/core/migrations/0003_load_uswds_admin_theme.py diff --git a/tdrs-backend/Pipfile b/tdrs-backend/Pipfile index 2bea1847f..2238f56ab 100644 --- a/tdrs-backend/Pipfile +++ b/tdrs-backend/Pipfile @@ -27,6 +27,7 @@ boto3 = "==1.42.23" cryptography = "==46.0.3" dj-database-url = "==3.1.0" django = "==5.2.10" +django-admin-interface = "==0.32.0" django-admin-logs = "==1.5.0" django-configurations = "==2.5.1" django-cors-headers = "==4.9.0" diff --git a/tdrs-backend/Pipfile.lock b/tdrs-backend/Pipfile.lock index 3a9cb46fd..c08dfeeaa 100644 --- a/tdrs-backend/Pipfile.lock +++ b/tdrs-backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "774ae74e8d0c88e42550312865347296b71d5eccd28c8fb02d3c3c5c9f9b3dc7" + "sha256": "397cf0a72bfbdef2ec7fbf5859ab3531988ce18c650bbae57d592022b0cd5642" }, "pipfile-spec": 6, "requires": { @@ -26,11 +26,11 @@ }, "asgiref": { "hashes": [ - "sha256:13acff32519542a1736223fb79a715acdebe24286d98e8b164a73085f40da2c4", - "sha256:1db9021efadb0d9512ce8ffaf72fcef601c7b73a8807a1bb2ef143dc6b14846d" + "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce", + "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133" ], "markers": "python_version >= '3.9'", - "version": "==3.11.0" + "version": "==3.11.1" }, "async-timeout": { "hashes": [ @@ -428,6 +428,14 @@ "markers": "python_version >= '3.10'", "version": "==5.2.10" }, + "django-admin-interface": { + "hashes": [ + "sha256:d0e80e382a7084ff59d0ba06934d6690fb6c04bb5327574f4c03b602fe41219c", + "sha256:f3684cfb857c12dd0befc95d5d45ac8a04840d54ad7d5920a9a10f3606e1bdcf" + ], + "index": "pypi", + "version": "==0.32.0" + }, "django-admin-logs": { "hashes": [ "sha256:2b9a19ee59b60712a5005a27c0a060b5280ac2192607aa34757edfa5b3abc1c5", @@ -446,6 +454,13 @@ "markers": "python_version >= '3.8'", "version": "==2.8.1" }, + "django-colorfield": { + "hashes": [ + "sha256:478dbd3975a88f2ea2a9afc325faaca05c54ebf04ec985ce130f6dea39dfb899", + "sha256:f169c4e7ad8f336e51d4ea81f866346e7d4f336f3766e54e144cd16ea7d84a0e" + ], + "version": "==0.14.0" + }, "django-configurations": { "hashes": [ "sha256:6e5083757e2bbdf9bb7850567536b96a93515f6b17503d74928ff628db2e0e94", @@ -601,70 +616,70 @@ }, "grpcio": { "hashes": [ - "sha256:035d90bc79eaa4bed83f524331d55e35820725c9fbb00ffa1904d5550ed7ede3", - "sha256:04bbe1bfe3a68bbfd4e52402ab7d4eb59d72d02647ae2042204326cf4bbad280", - "sha256:063065249d9e7e0782d03d2bca50787f53bd0fb89a67de9a7b521c4a01f1989b", - "sha256:06c3d6b076e7b593905d04fdba6a0525711b3466f43b3400266f04ff735de0cd", - "sha256:08caea849a9d3c71a542827d6df9d5a69067b0a1efbea8a855633ff5d9571465", - "sha256:0aaa82d0813fd4c8e589fac9b65d7dd88702555f702fb10417f96e2a2a6d4c0f", - "sha256:0b7604868b38c1bfd5cf72d768aedd7db41d78cb6a4a18585e33fb0f9f2363fd", - "sha256:0c37db8606c258e2ee0c56b78c62fc9dee0e901b5dbdcf816c2dd4ad652b8b0c", - "sha256:1c9b93f79f48b03ada57ea24725d83a30284a012ec27eab2cf7e50a550cbbbcc", - "sha256:2107b0c024d1b35f4083f11245c0e23846ae64d02f40b2b226684840260ed054", - "sha256:2229ae655ec4e8999599469559e97630185fdd53ae1e8997d147b7c9b2b72cba", - "sha256:25a18e9810fbc7e7f03ec2516addc116a957f8cbb8cbc95ccc80faa072743d03", - "sha256:26ef06c73eb53267c2b319f43e6634c7556ea37672029241a056629af27c10e2", - "sha256:2e1743fbd7f5fa713a1b0a8ac8ebabf0ec980b5d8809ec358d488e273b9cf02a", - "sha256:32483fe2aab2c3794101c2a159070584e5db11d0aa091b2c0ea9c4fc43d0d749", - "sha256:3bf0f392c0b806905ed174dcd8bdd5e418a40d5567a05615a030a5aeddea692d", - "sha256:3e2a27c89eb9ac3d81ec8835e12414d73536c6e620355d65102503064a4ed6eb", - "sha256:40ad3afe81676fd9ec6d9d406eda00933f218038433980aa19d401490e46ecde", - "sha256:4215d3a102bd95e2e11b5395c78562967959824156af11fa93d18fdd18050990", - "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958", - "sha256:45e0111e73f43f735d70786557dc38141185072d7ff8dc1829d6a77ac1471468", - "sha256:479496325ce554792dba6548fae3df31a72cef7bad71ca2e12b0e58f9b336bfc", - "sha256:490fa6d203992c47c7b9e4a9d39003a0c2bcc1c9aa3c058730884bbbb0ee9f09", - "sha256:49ce47231818806067aea3324d4bf13825b658ad662d3b25fada0bdad9b8a6af", - "sha256:4baf3cbe2f0be3289eb68ac8ae771156971848bb8aaff60bad42005539431980", - "sha256:522175aba7af9113c48ec10cc471b9b9bd4f6ceb36aeb4544a8e2c80ed9d252d", - "sha256:5e8571632780e08526f118f74170ad8d50fb0a48c23a746bef2a6ebade3abd6f", - "sha256:615ba64c208aaceb5ec83bfdce7728b80bfeb8be97562944836a7a0a9647d882", - "sha256:61f69297cba3950a524f61c7c8ee12e55c486cb5f7db47ff9dcee33da6f0d3ae", - "sha256:65a20de41e85648e00305c1bb09a3598f840422e522277641145a32d42dcefcc", - "sha256:6a15c17af8839b6801d554263c546c69c4d7718ad4321e3166175b37eaacca77", - "sha256:747fa73efa9b8b1488a95d0ba1039c8e2dca0f741612d80415b1e1c560febf4e", - "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73", - "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8", - "sha256:83d57312a58dcfe2a3a0f9d1389b299438909a02db60e2f2ea2ae2d8034909d3", - "sha256:8843114c0cfce61b40ad48df65abcfc00d4dba82eae8718fab5352390848c5da", - "sha256:8cc3309d8e08fd79089e13ed4819d0af72aa935dd8f435a195fd152796752ff2", - "sha256:8ebe63ee5f8fa4296b1b8cfc743f870d10e902ca18afc65c68cf46fd39bb0783", - "sha256:8eddfb4d203a237da6f3cc8a540dad0517d274b5a1e9e636fd8d2c79b5c1d397", - "sha256:922fa70ba549fce362d2e2871ab542082d66e2aaf0c19480ea453905b01f384e", - "sha256:931091142fd8cc14edccc0845a79248bc155425eee9a98b2db2ea4f00a235a42", - "sha256:971fd5a1d6e62e00d945423a567e42eb1fa678ba89072832185ca836a94daaa6", - "sha256:980a846182ce88c4f2f7e2c22c56aefd515daeb36149d1c897f83cf57999e0b6", - "sha256:9d9adda641db7207e800a7f089068f6f645959f2df27e870ee81d44701dd9db3", - "sha256:9f8f757bebaaea112c00dba718fc0d3260052ce714e25804a03f93f5d1c6cc11", - "sha256:a6ae758eb08088d36812dd5d9af7a9859c05b1e0f714470ea243694b49278e7b", - "sha256:a8c2cf1209497cf659a667d7dea88985e834c24b7c3b605e6254cbb5076d985c", - "sha256:acab0277c40eff7143c2323190ea57b9ee5fd353d8190ee9652369fae735668a", - "sha256:b331680e46239e090f5b3cead313cc772f6caa7d0fc8de349337563125361a4a", - "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347", - "sha256:d099566accf23d21037f18a2a63d323075bebace807742e4b0ac210971d4dd70", - "sha256:d388087771c837cdb6515539f43b9d4bf0b0f23593a24054ac16f7a960be16f4", - "sha256:dcfe41187da8992c5f40aa8c5ec086fa3672834d2be57a32384c08d5a05b4c00", - "sha256:e6d1db20594d9daba22f90da738b1a0441a7427552cc6e2e3d1297aeddc00378", - "sha256:ebea5cc3aa8ea72e04df9913492f9a96d9348db876f9dda3ad729cfedf7ac416", - "sha256:ebebf83299b0cb1721a8859ea98f3a77811e35dce7609c5c963b9ad90728f886", - "sha256:f0e34c2079d47ae9f6188211db9e777c619a21d4faba6977774e8fa43b085e48", - "sha256:f92f88e6c033db65a5ae3d97905c8fea9c725b63e28d5a75cb73b49bda5024d8", - "sha256:f9f7bd5faab55f47231ad8dba7787866b69f5e93bc306e3915606779bbfb4ba8", - "sha256:fd5ef5932f6475c436c4a55e4336ebbe47bd3272be04964a03d316bbf4afbcbc", - "sha256:ff8a59ea85a1f2191a0ffcc61298c571bc566332f82e5f5be1b83c9d8e668a62" - ], - "markers": "python_version >= '3.9'", - "version": "==1.76.0" + "sha256:008602fb5bfab98ef9146da9009933d13042c00b219ba79f1e179e83cf10c85c", + "sha256:06881eb8fde0550e0d7c1c7251d11b6b07e2dee502d4241538b8dd152168a233", + "sha256:0ef4693037bbafb5f51f74815dd9e5f0e019c14ba477dbfe5b6eabe1b3560cd1", + "sha256:103df608ce317c590fba50fde69cad502f5fba24e746a22917ca5691b300187c", + "sha256:10e24511de26810a723b54517f5abe3cb14dee7c2ae3c121e3e08f5c6104807b", + "sha256:2b0ef0c6cb638e51ac037f851b755a16088b4004e5eb688f729ffa060eb216ca", + "sha256:2c1cfbf2a172e9249d236ee004d1c51906b4fc8cb43d6dac6bff6eb3a0fbb9e8", + "sha256:2f4b15f132f6b14487c0410066489f775f559db3baef64cc8b0d4a9f1dd166ec", + "sha256:2fa60476242efbfbfefb620371f9e935e033e748b359a7959c16bb3f78333c7e", + "sha256:335e902286649cba6f3937cb39343c99959e5acc31e893ab5e9f700d0d8defdd", + "sha256:3bbf866c7be1095167c62470e1fdc317059b42db97aff1ff71d9237eef0f239e", + "sha256:408a4302e220a39dccfadb41b7b65977518f8953c1ca3ad524ff4ac5de867339", + "sha256:43ce95f92e6f89c5c6cafa7a299e47b35477cf027337b1a1d1e0c71c111b6761", + "sha256:455b16d30abd5f6e364120b297b2b4cb396f93463450d93930d5a5e049194d92", + "sha256:47b5bf9d431b95f627c20f66d573b571f10513022465588c1c0a3913a540f4b7", + "sha256:4e99bbce4f509eb6af4b523109152258043b92bbb945950456a9318eca71ef2e", + "sha256:4fb8b0df1c14dee78f076467c4f581539087f022725a66cbc3970ec91658ea49", + "sha256:54dd38f8edb3d1b693ff29f7ae1c45bb6999f836d0e4c12211bff057529683ac", + "sha256:58bd130450275a87b55d60c7a9185762d6d83a3b1a4401d976e9c6083c7280e0", + "sha256:5ccf4496425b5f5a7a9b801d79fe5e8bfbdf2408b2ab976f291f3e1536d4a3f7", + "sha256:5d39633a0ead39c07d45e00ffb8b262426c5e88ab97bdbc82b2e89ebb6d536cd", + "sha256:5d87f71ce840eb4c13a43d74e59f2d2f83163a71b2c59430598ed983df715a02", + "sha256:6266ce303159899e7f0d545dd9c8edf978f28b79babd3e6aeedec66bb845fb8f", + "sha256:63e69c529121ae6c62a566bde31828dbdd85edf6438610170506dd8b5da6366d", + "sha256:6ba646159dfbd00074e6679103b069d4ef5dc66098cad557e8550feded049b4a", + "sha256:797ec8d482ad7580c29f7dbcc54eebd44d0c1d074c606603aef7eedad3eb61c5", + "sha256:7a3ef091f2082b4ae17463874a6531c01b42e963f164df8ef0c6304f35d9be47", + "sha256:7ab0a68f513620fa34e2dd5428429e0757aac7b3daa9861e5a5a761851ad5767", + "sha256:7fe343a2ccaa3ca48a933e81f4c0a9de37057cf5bc5567864a98775cce570456", + "sha256:86ae01b963762badb8474f0cbf3701cfebaf0cc2cfc860eddd954e974050360b", + "sha256:9944a4cad60e1bf076b025e62157de91aec13216614994038930505a718bae3f", + "sha256:99df1ab7048c18ad08bd8d0e4f81bf69dd1e47b108555aa7afc8befba3f8e62e", + "sha256:a5e2b4a355c92a2263e0e5c8bab97a5516f5e283586e7f83bd9c7579ec2dc9e0", + "sha256:af0b2125bcc19f8ff4274186b48ef9c09d37112e157d2afca4c4dc9ee08eff67", + "sha256:b0c9439c4fc05567ad5e3ae44afb449c0676d8eef5df3ff074e70399883ac7be", + "sha256:b47f176881d6b848f25bdc5b2bcb2c54aa478069ca8339c408015f17f1538f60", + "sha256:b77ee0d0c7abf861fa0b8be9b19a859318dddbf9e6c17437fea781d5205a011b", + "sha256:bf2cf9c2d3919ad9545539c7609e2a7cad48ffddb0b87d58730fec24704057cb", + "sha256:bf84e8dd589bfba77387b4e2ef69380135d3d73d2d322e78ec0f49d388b3ec70", + "sha256:bfb22fefd5cb4a6ac2687d8b314d43f8d3312ed619913270b28524cc4cbbe1dd", + "sha256:c4c3ee365435e6df07ad399ffaabd07f5a0ee8c85a95419a2b658bb0c29e02f0", + "sha256:c670d8f52f4bbddff85cddcf1327255143ded8f6eaf2bdfe25fa58243791f899", + "sha256:c76eab67c341623d52064cf4ef1259184abfba6db85883e481256e40cbbe6b1a", + "sha256:caf8808325a5fdd30cf40d85f3efac965d14b95f92dd8e7d1a7c14ca5e24e67b", + "sha256:ccb40af8fe22e50e8ae9bef0c53840d2297af6c70b2c331b0f61d847082854d8", + "sha256:cf393affd32de39266e2b85b613b5a8420057e55b115774d9adb6546477a8b76", + "sha256:d085ac0245c778bbb32306b3ae477dbe0fc6b58b226d0e54ec934522e336f71e", + "sha256:d0c073d1a6b5de0f550766873e4393f3a0f3b6e1bbb10300735fef4046cbda24", + "sha256:d624592c82a19a5898c5576fbda43c28d7062bac04ea6f33bbd8871bc0639e64", + "sha256:e3364a06f3395853ee926e7cff6c5c2dd1d444f10e071aa4cdbed9db3c59192c", + "sha256:e7bde54ad7bee2d4dbc6d4a351d5b62cc2bfa87c58e9db911ed8a0489192ca9a", + "sha256:ea66e360e5ea032a1f6dde926915ba683edca811ed6f0e0620c52e264ba364e4", + "sha256:f05f444577a7b6b1a8dd83fce602d41e89c00c3524687f947e1f424a547e497c", + "sha256:f16bf178a72dadc61cba3a8b905352076751ee4f5e2cad502091798ce210b4d8", + "sha256:f1999217dd1586236940e3c008f07b76ea25abde4990e2bafb7dbb16da5bbf33", + "sha256:f3890e28a9b9544b03a0c6493b366806ef04296b253fc0d4b4bfee97ecf04f1d", + "sha256:f683813c36e738a5c48661d6945845ee256ea064257161a5ef67d8bd1a23f3e1", + "sha256:f701f14da42d59ee06c993a4e24cc8594fbfdfb373c277dd4580d1de26c98887", + "sha256:f76cf0474eee6cf47cb5ef392e7f15a37941b26100a51adba8d99b3027ac218a", + "sha256:f84ab791751ad5936e0f7f9dce8b29e8ac3efc25a81c8c3780b238726a7face2", + "sha256:fcc6a08f37151bb66785161dc8817b247b85924ef90580d9ee08c72c54598125" + ], + "markers": "python_version >= '3.9'", + "version": "==1.78.0rc2" }, "gunicorn": { "hashes": [ @@ -709,11 +724,11 @@ }, "jmespath": { "hashes": [ - "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", - "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe" + "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", + "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64" ], - "markers": "python_version >= '3.7'", - "version": "==1.0.1" + "markers": "python_version >= '3.9'", + "version": "==1.1.0" }, "jwcrypto": { "hashes": [ @@ -1009,19 +1024,116 @@ }, "packaging": { "hashes": [ - "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", - "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", + "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529" ], "markers": "python_version >= '3.8'", - "version": "==25.0" + "version": "==26.0" + }, + "pillow": { + "hashes": [ + "sha256:00162e9ca6d22b7c3ee8e61faa3c3253cd19b6a37f126cad04f2f88b306f557d", + "sha256:079af2fb0c599c2ec144ba2c02766d1b55498e373b3ac64687e43849fbbef5bc", + "sha256:0b022eaaf709541b391ee069f0022ee5b36c709df71986e3f7be312e46f42c84", + "sha256:0c27407a2d1b96774cbc4a7594129cc027339fd800cd081e44497722ea1179de", + "sha256:0ddedfaa8b5f0b4ffbc2fa87b556dc59f6bb4ecb14a53b33f9189713ae8053c0", + "sha256:0deedf2ea233722476b3a81e8cdfbad786f7adbed5d848469fa59fe52396e4ef", + "sha256:0ed07dca4a8464bada6139ab38f5382f83e5f111698caf3191cb8dbf27d908b4", + "sha256:0fde7ec5538ab5095cc02df38ee99b0443ff0e1c847a045554cf5f9af1f4aa82", + "sha256:15c794d74303828eaa957ff8070846d0efe8c630901a1c753fdc63850e19ecd9", + "sha256:1a949604f73eb07a8adab38c4fe50791f9919344398bdc8ac6b307f755fc7030", + "sha256:1f345e7bc9d7f368887c712aa5054558bad44d2a301ddf9248599f4161abc7c0", + "sha256:1fcc52d86ce7a34fd17cb04e87cfdb164648a3662a6f20565910a99653d66c18", + "sha256:21e686a21078b0f9cb8c8a961d99e6a4ddb88e0fc5ea6e130172ddddc2e5221a", + "sha256:2415373395a831f53933c23ce051021e79c8cd7979822d8cc478547a3f4da8ef", + "sha256:277518bf4fe74aa91489e1b20577473b19ee70fb97c374aa50830b279f25841b", + "sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6", + "sha256:29a4cef9cb672363926f0470afc516dbf7305a14d8c54f7abbb5c199cd8f8179", + "sha256:3413c2ae377550f5487991d444428f1a8ae92784aac79caa8b1e3b89b175f77e", + "sha256:351889afef0f485b84078ea40fe33727a0492b9af3904661b0abbafee0355b72", + "sha256:3ffaa2f0659e2f740473bcf03c702c39a8d4b2b7ffc629052028764324842c64", + "sha256:40a8e3b9e8773876d6e30daed22f016509e3987bab61b3b7fe309d7019a87451", + "sha256:414b9a78e14ffeb98128863314e62c3f24b8a86081066625700b7985b3f529bd", + "sha256:43aca0a55ce1eefc0aefa6253661cb54571857b1a7b2964bd8a1e3ef4b729924", + "sha256:43b4899cfd091a9693a1278c4982f3e50f7fb7cff5153b05174b4afc9593b616", + "sha256:461f9dfdafa394c59cd6d818bdfdbab4028b83b02caadaff0ffd433faf4c9a7a", + "sha256:4f9f6a650743f0ddee5593ac9e954ba1bdbc5e150bc066586d4f26127853ab94", + "sha256:53d8b764726d3af1a138dd353116f774e3862ec7e3794e0c8781e30db0f35dfc", + "sha256:565c986f4b45c020f5421a4cea13ef294dde9509a8577f29b2fc5edc7587fff8", + "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", + "sha256:5cb7bc1966d031aec37ddb9dcf15c2da5b2e9f7cc3ca7c54473a20a927e1eb91", + "sha256:5da841d81b1a05ef940a8567da92decaa15bc4d7dedb540a8c219ad83d91808a", + "sha256:5fee4c04aad8932da9f8f710af2c1a15a83582cfb884152a9caa79d4efcdbf9c", + "sha256:609e89d9f90b581c8d16358c9087df76024cf058fa693dd3e1e1620823f39670", + "sha256:6258f3260986990ba2fa8a874f8b6e808cf5abb51a94015ca3dc3c68aa4f30ea", + "sha256:64efdf00c09e31efd754448a383ea241f55a994fd079866b92d2bbff598aad91", + "sha256:65b80c1ee7e14a87d6a068dd3b0aea268ffcabfe0498d38661b00c5b4b22e74c", + "sha256:6741e6f3074a35e47c77b23a4e4f2d90db3ed905cb1c5e6e0d49bff2045632bc", + "sha256:681088909d7e8fa9e31b9799aaa59ba5234c58e5e4f1951b4c4d1082a2e980e0", + "sha256:6b7a9d1db5dad90e2991645874f708e87d9a3c370c243c2d7684d28f7e133e6b", + "sha256:7315f9137087c4e0ee73a761b163fc9aa3b19f5f606a7fc08d83fd3e4379af65", + "sha256:742aea052cf5ab5034a53c3846165bc3ce88d7c38e954120db0ab867ca242661", + "sha256:75af0b4c229ac519b155028fa1be632d812a519abba9b46b20e50c6caa184f19", + "sha256:7b5dd7cbae20285cdb597b10eb5a2c13aa9de6cde9bb64a3c1317427b1db1ae1", + "sha256:7d6daa89a00b58c37cb1747ec9fb7ac3bc5ffd5949f5888657dfddde6d1312e0", + "sha256:800429ac32c9b72909c671aaf17ecd13110f823ddb7db4dfef412a5587c2c24e", + "sha256:806f3987ffe10e867bab0ddad45df1148a2b98221798457fa097ad85d6e8bc75", + "sha256:808b99604f7873c800c4840f55ff389936ef1948e4e87645eaf3fccbc8477ac4", + "sha256:80941e6d573197a0c28f394753de529bb436b1ca990ed6e765cf42426abc39f8", + "sha256:84cabc7095dd535ca934d57e9ce2a72ffd216e435a84acb06b2277b1de2689bd", + "sha256:8637e29d13f478bc4f153d8daa9ffb16455f0a6cb287da1b432fdad2bfbd66c7", + "sha256:896866d2d436563fa2a43a9d72f417874f16b5545955c54a64941e87c1376c61", + "sha256:8e178e3e99d3c0ea8fc64b88447f7cac8ccf058af422a6cedc690d0eadd98c51", + "sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551", + "sha256:9212d6b86917a2300669511ed094a9406888362e085f2431a7da985a6b124f45", + "sha256:92a7fe4225365c5e3a8e598982269c6d6698d3e783b3b1ae979e7819f9cd55c1", + "sha256:935b9d1aed48fcfb3f838caac506f38e29621b44ccc4f8a64d575cb1b2a88644", + "sha256:97e9993d5ed946aba26baf9c1e8cf18adbab584b99f452ee72f7ee8acb882796", + "sha256:983976c2ab753166dc66d36af6e8ec15bb511e4a25856e2227e5f7e00a160587", + "sha256:9f5fefaca968e700ad1a4a9de98bf0869a94e397fe3524c4c9450c1445252304", + "sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b", + "sha256:a40905599d8079e09f25027423aed94f2823adaf2868940de991e53a449e14a8", + "sha256:a6dfc2af5b082b635af6e08e0d1f9f1c4e04d17d4e2ca0ef96131e85eda6eb17", + "sha256:a786bf667724d84aa29b5db1c61b7bfdde380202aaca12c3461afd6b71743171", + "sha256:a83e0850cb8f5ac975291ebfc4170ba481f41a28065277f7f735c202cd8e0af3", + "sha256:aa0c9cc0b82b14766a99fbe6084409972266e82f459821cd26997a488a7261a7", + "sha256:b17fbdbe01c196e7e159aacb889e091f28e61020a8abeac07b68079b6e626988", + "sha256:b63e13dd27da389ed9475b3d28510f0f954bca0041e8e551b2a4eb1eab56a39a", + "sha256:b6e53e82ec2db0717eabb276aa56cf4e500c9a7cec2c2e189b55c24f65a3e8c0", + "sha256:bb0984b30e973f7e2884362b7d23d0a348c7143ee559f38ef3eaab640144204c", + "sha256:bc11908616c8a283cf7d664f77411a5ed2a02009b0097ff8abbba5e79128ccf2", + "sha256:bdec5e43377761c5dbca620efb69a77f6855c5a379e32ac5b158f54c84212b14", + "sha256:bef9768cab184e7ae6e559c032e95ba8d07b3023c289f79a2bd36e8bf85605a5", + "sha256:c990547452ee2800d8506c4150280757f88532f3de2a58e3022e9b179107862a", + "sha256:ca94b6aac0d7af2a10ba08c0f888b3d5114439b6b3ef39968378723622fed377", + "sha256:cad302dc10fac357d3467a74a9561c90609768a6f73a1923b0fd851b6486f8b0", + "sha256:d0a7735df32ccbcc98b98a1ac785cc4b19b580be1bdf0aeb5c03223220ea09d5", + "sha256:d70347c8a5b7ccd803ec0c85c8709f036e6348f1e6a5bf048ecd9c64d3550b8b", + "sha256:d70534cea9e7966169ad29a903b99fc507e932069a881d0965a1a84bb57f6c6d", + "sha256:db44d5c160a90df2d24a24760bbd37607d53da0b34fb546c4c232af7192298ac", + "sha256:e115c15e3bc727b1ca3e641a909f77f8ca72a64fff150f666fcc85e57701c26c", + "sha256:e2479c7f02f9d505682dc47df8c0ea1fc5e264c4d1629a5d63fe3e2334b89554", + "sha256:e5dcbe95016e88437ecf33544ba5db21ef1b8dd6e1b434a2cb2a3d605299e643", + "sha256:e6bdb408f7c9dd2a5ff2b14a3b0bb6d4deb29fb9961e6eb3ae2031ae9a5cec13", + "sha256:e75d3dba8fc1ddfec0cd752108f93b83b4f8d6ab40e524a95d35f016b9683b09", + "sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208", + "sha256:f10c98f49227ed8383d28174ee95155a675c4ed7f85e2e573b04414f7e371bda", + "sha256:f188028b5af6b8fb2e9a76ac0f841a575bd1bd396e46ef0840d9b88a48fdbcea", + "sha256:f188d580bd870cda1e15183790d1cc2fa78f666e76077d103edf048eed9c356e", + "sha256:f45bd71d1fa5e5749587613037b172e0b3b23159d1c00ef2fc920da6f470e6f0", + "sha256:f61333d817698bdcdd0f9d7793e365ac3d2a21c1f1eb02b32ad6aefb8d8ea831", + "sha256:fb125d860738a09d363a88daa0f59c4533529a90e564785e20fe875b200b6dbd" + ], + "markers": "python_version >= '3.10'", + "version": "==12.1.0" }, "prometheus-client": { "hashes": [ - "sha256:6ae8f9081eaaaf153a2e959d2e6c4f4fb57b12ef76c8c7980202f1e57b48b2ce", - "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99" + "sha256:150db128af71a5c2482b36e588fc8a6b95e498750da4b17065947c16070f4055", + "sha256:7e0ced7fbbd40f7b84962d5d2ab6f17ef88a72504dcf7c0b40737b43b2a461f9" ], "markers": "python_version >= '3.9'", - "version": "==0.23.1" + "version": "==0.24.1" }, "prompt-toolkit": { "hashes": [ @@ -1033,19 +1145,19 @@ }, "protobuf": { "hashes": [ - "sha256:1f8017c48c07ec5859106533b682260ba3d7c5567b1ca1f24297ce03384d1b4f", - "sha256:2981c58f582f44b6b13173e12bb8656711189c2a70250845f264b877f00b1913", - "sha256:56dc370c91fbb8ac85bc13582c9e373569668a290aa2e66a590c2a0d35ddb9e4", - "sha256:7109dcc38a680d033ffb8bf896727423528db9163be1b6a02d6a49606dcadbfe", - "sha256:7636aad9bb01768870266de5dc009de2d1b936771b38a793f73cbbf279c91c5c", - "sha256:87eb388bd2d0f78febd8f4c8779c79247b26a5befad525008e49a6955787ff3d", - "sha256:8cd7640aee0b7828b6d03ae518b5b4806fdfc1afe8de82f79c3454f8aef29872", - "sha256:b5d3b5625192214066d99b2b605f5783483575656784de223f00a8d00754fc0e", - "sha256:d9b19771ca75935b3a4422957bc518b0cecb978b31d1dd12037b088f6bcc0e43", - "sha256:fc2a0e8b05b180e5fc0dd1559fe8ebdae21a27e81ac77728fb6c42b12c7419b4" + "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", + "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", + "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", + "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", + "sha256:8f04fa32763dcdb4973d537d6b54e615cc61108c7cb38fe59310c3192d29510a", + "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", + "sha256:a3157e62729aafb8df6da2c03aa5c0937c7266c626ce11a278b6eb7963c4e37c", + "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", + "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", + "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b" ], "markers": "python_version >= '3.9'", - "version": "==6.33.2" + "version": "==6.33.5" }, "psycopg2": { "hashes": [ @@ -1071,11 +1183,11 @@ }, "pycparser": { "hashes": [ - "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", - "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934" + "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", + "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992" ], - "markers": "python_version >= '3.8'", - "version": "==2.23" + "markers": "python_version >= '3.10'", + "version": "==3.0" }, "pyjwt": { "hashes": [ @@ -1109,6 +1221,14 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==3.3.7" }, + "python-slugify": { + "hashes": [ + "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", + "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856" + ], + "markers": "python_version >= '3.7'", + "version": "==8.0.4" + }, "pytz": { "hashes": [ "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", @@ -1274,6 +1394,13 @@ "markers": "python_version >= '3.8'", "version": "==0.5.5" }, + "text-unidecode": { + "hashes": [ + "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", + "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93" + ], + "version": "==1.3" + }, "tornado": { "hashes": [ "sha256:053e6e16701eb6cbe641f308f4c1a9541f91b6261991160391bfc342e8a551a1", @@ -1351,11 +1478,11 @@ }, "wcwidth": { "hashes": [ - "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", - "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1" + "sha256:53123b7af053c74e9fe2e92ac810301f6139e64379031f7124574212fb3b4091", + "sha256:d584eff31cd4753e1e5ff6c12e1edfdb324c995713f75d26c29807bb84bf649e" ], - "markers": "python_version >= '3.6'", - "version": "==0.2.14" + "markers": "python_version >= '3.8'", + "version": "==0.5.3" }, "werkzeug": { "hashes": [ @@ -1473,11 +1600,11 @@ "develop": { "asgiref": { "hashes": [ - "sha256:13acff32519542a1736223fb79a715acdebe24286d98e8b164a73085f40da2c4", - "sha256:1db9021efadb0d9512ce8ffaf72fcef601c7b73a8807a1bb2ef143dc6b14846d" + "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce", + "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133" ], "markers": "python_version >= '3.9'", - "version": "==3.11.0" + "version": "==3.11.1" }, "awscli": { "hashes": [ @@ -1535,101 +1662,101 @@ "toml" ], "hashes": [ - "sha256:0403f647055de2609be776965108447deb8e384fe4a553c119e3ff6bfbab4784", - "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e", - "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3", - "sha256:0d2c11f3ea4db66b5cbded23b20185c35066892c67d80ec4be4bab257b9ad1e0", - "sha256:0e42e0ec0cd3e0d851cb3c91f770c9301f48647cb2877cb78f74bdaa07639a79", - "sha256:132718176cc723026d201e347f800cd1a9e4b62ccd3f82476950834dad501c75", - "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6", - "sha256:18be793c4c87de2965e1c0f060f03d9e5aff66cfeae8e1dbe6e5b88056ec153f", - "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88", - "sha256:1dcb645d7e34dcbcc96cd7c132b1fc55c39263ca62eb961c064eb3928997363b", - "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573", - "sha256:228b90f613b25ba0019361e4ab81520b343b622fc657daf7e501c4ed6a2366c0", - "sha256:309ef5706e95e62578cda256b97f5e097916a2c26247c287bbe74794e7150df2", - "sha256:339dc63b3eba969067b00f41f15ad161bf2946613156fb131266d8debc8e44d0", - "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba", - "sha256:3d42df8201e00384736f0df9be2ced39324c3907607d17d50d50116c989d84cd", - "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c", - "sha256:3f2f725aa3e909b3c5fdb8192490bdd8e1495e85906af74fe6e34a2a77ba0673", - "sha256:3fc6a169517ca0d7ca6846c3c5392ef2b9e38896f61d615cb75b9e7134d4ee1e", - "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee", - "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3", - "sha256:4de84e71173d4dada2897e5a0e1b7877e5eefbfe0d6a44edee6ce31d9b8ec09e", - "sha256:549d195116a1ba1e1ae2f5ca143f9777800f6636eab917d4f02b5310d6d73461", - "sha256:562ec27dfa3f311e0db1ba243ec6e5f6ab96b1edfcfc6cf86f28038bc4961ce6", - "sha256:57dfc8048c72ba48a8c45e188d811e5efd7e49b387effc8fb17e97936dde5bf6", - "sha256:5899d28b5276f536fcf840b18b61a9fce23cc3aec1d114c44c07fe94ebeaa500", - "sha256:60cfb538fe9ef86e5b2ab0ca8fc8d62524777f6c611dcaf76dc16fbe9b8e698a", - "sha256:623dcc6d7a7ba450bbdbeedbaa0c42b329bdae16491af2282f12a7e809be7eb9", - "sha256:67170979de0dacac3f3097d02b0ad188d8edcea44ccc44aaa0550af49150c7dc", - "sha256:6e73ebb44dca5f708dc871fe0b90cf4cff1a13f9956f747cc87b535a840386f5", - "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3", - "sha256:724b1b270cb13ea2e6503476e34541a0b1f62280bc997eab443f87790202033d", - "sha256:75a6f4aa904301dab8022397a22c0039edc1f51e90b83dbd4464b8a38dc87842", - "sha256:77545b5dcda13b70f872c3b5974ac64c21d05e65b1590b441c8560115dc3a0d1", - "sha256:776483fd35b58d8afe3acbd9988d5de592ab6da2d2a865edfdbc9fdb43e7c486", - "sha256:77cc258aeb29a3417062758975521eae60af6f79e930d6993555eeac6a8eac29", - "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968", - "sha256:868a2fae76dfb06e87291bcbd4dcbcc778a8500510b618d50496e520bd94d9b9", - "sha256:8842af7f175078456b8b17f1b73a0d16a65dcbdc653ecefeb00a56b3c8c298c4", - "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c", - "sha256:8f572d989142e0908e6acf57ad1b9b86989ff057c006d13b76c146ec6a20216a", - "sha256:90480b2134999301eea795b3a9dbf606c6fbab1b489150c501da84a959442465", - "sha256:916abf1ac5cf7eb16bc540a5bf75c71c43a676f5c52fcb9fe75a2bd75fb944e8", - "sha256:92f980729e79b5d16d221038dbf2e8f9a9136afa072f9d5d6ed4cb984b126a09", - "sha256:933082f161bbb3e9f90d00990dc956120f608cdbcaeea15c4d897f56ef4fe416", - "sha256:97ab3647280d458a1f9adb85244e81587505a43c0c7cff851f5116cd2814b894", - "sha256:985b7836931d033570b94c94713c6dba5f9d3ff26045f72c3e5dbc5fe3361e5a", - "sha256:9e549d642426e3579b3f4b92d0431543b012dcb6e825c91619d4e93b7363c3f9", - "sha256:9edd0e01a343766add6817bc448408858ba6b489039eaaa2018474e4001651a4", - "sha256:9ee68b21909686eeb21dfcba2c3b81fee70dcf38b140dcd5aa70680995fa3aa5", - "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f", - "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb", - "sha256:a4d240d260a1aed814790bbe1f10a5ff31ce6c21bc78f0da4a1e8268d6c80dbd", - "sha256:a5a68357f686f8c4d527a2dc04f52e669c2fc1cbde38f6f7eb6a0e58cbd17cae", - "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a", - "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e", - "sha256:b6f3b96617e9852703f5b633ea01315ca45c77e879584f283c44127f0f1ec564", - "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd", - "sha256:bb3f6562e89bad0110afbe64e485aac2462efdce6232cdec7862a095dc3412f6", - "sha256:bb4f8c3c9a9f34423dba193f241f617b08ffc63e27f67159f60ae6baf2dcfe0f", - "sha256:bd63e7b74661fed317212fab774e2a648bc4bb09b35f25474f8e3325d2945cd7", - "sha256:be753b225d159feb397bd0bf91ae86f689bad0da09d3b301478cd39b878ab31a", - "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee", - "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62", - "sha256:c3d8c679607220979434f494b139dfb00131ebf70bb406553d69c1ff01a5c33d", - "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef", - "sha256:c832ec92c4499ac463186af72f9ed4d8daec15499b16f0a879b0d1c8e5cf4a3b", - "sha256:c8e2706ceb622bc63bac98ebb10ef5da80ed70fbd8a7999a5076de3afaef0fb1", - "sha256:cb237bfd0ef4d5eb6a19e29f9e528ac67ac3be932ea6b44fb6cc09b9f3ecff78", - "sha256:ccd7a6fca48ca9c131d9b0a2972a581e28b13416fc313fb98b6d24a03ce9a398", - "sha256:d10a2ed46386e850bb3de503a54f9fe8192e5917fcbb143bfef653a9355e9a53", - "sha256:d1443ba9acbb593fa7c1c29e011d7c9761545fe35e7652e85ce7f51a16f7e08d", - "sha256:d2287ac9360dec3837bfdad969963a5d073a09a85d898bd86bea82aa8876ef3c", - "sha256:d3c9f051b028810f5a87c88e5d6e9af3c0ff32ef62763bf15d29f740453ca909", - "sha256:d72140ccf8a147e94274024ff6fd8fb7811354cf7ef88b1f0a988ebaa5bc774f", - "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9", - "sha256:db622b999ffe49cb891f2fff3b340cdc2f9797d01a0a202a0973ba2562501d90", - "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851", - "sha256:e1fa280b3ad78eea5be86f94f461c04943d942697e0dac889fa18fff8f5f9147", - "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf", - "sha256:e825dbb7f84dfa24663dd75835e7257f8882629fc11f03ecf77d84a75134b864", - "sha256:eaecf47ef10c72ece9a2a92118257da87e460e113b83cc0d2905cbbe931792b4", - "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf", - "sha256:f398ba4df52d30b1763f62eed9de5620dcde96e6f491f4c62686736b155aa6e4", - "sha256:f80e2bb21bfab56ed7405c2d79d34b5dc0bc96c2c1d2a067b643a09fb756c43a", - "sha256:f83351e0f7dcdb14d7326c3d8d8c4e915fa685cbfdc6281f9470d97a04e9dfe4", - "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c", - "sha256:fa3edde1aa8807de1d05934982416cb3ec46d1d4d91e280bcce7cca01c507992", - "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c", - "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19", - "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7", - "sha256:ffed1e4980889765c84a5d1a566159e363b71d6b6fbaf0bebc9d3c30bc016766" + "sha256:00d34b29a59d2076e6f318b30a00a69bf63687e30cd882984ed444e753990cc1", + "sha256:00dd3f02de6d5f5c9c3d95e3e036c3c2e2a669f8bf2d3ceb92505c4ce7838f67", + "sha256:01119735c690786b6966a1e9f098da4cd7ca9174c4cfe076d04e653105488395", + "sha256:03a6e5e1e50819d6d7436f5bc40c92ded7e484e400716886ac921e35c133149d", + "sha256:05dd25b21afffe545e808265897c35f32d3e4437663923e0d256d9ab5031fb14", + "sha256:06d316dbb3d9fd44cca05b2dbcfbef22948493d63a1f28e828d43e6cc505fed8", + "sha256:06e49c5897cb12e3f7ecdc111d44e97c4f6d0557b81a7a0204ed70a8b038f86f", + "sha256:0b4f345f7265cdbdb5ec2521ffff15fa49de6d6c39abf89fc7ad68aa9e3a55f0", + "sha256:0c2be202a83dde768937a61cdc5d06bf9fb204048ca199d93479488e6247656c", + "sha256:0f45e32ef383ce56e0ca099b2e02fcdf7950be4b1b56afaab27b4ad790befe5b", + "sha256:123ceaf2b9d8c614f01110f908a341e05b1b305d6b2ada98763b9a5a59756051", + "sha256:16d23d6579cf80a474ad160ca14d8b319abaa6db62759d6eef53b2fc979b58c8", + "sha256:2213a8d88ed35459bda71597599d4eec7c2ebad201c88f0bfc2c26fd9b0dd2ea", + "sha256:24db3959de8ee394eeeca89ccb8ba25305c2da9a668dd44173394cbd5aa0777f", + "sha256:284e06eadfe15ddfee2f4ee56631f164ef897a7d7d5a15bca5f0bb88889fc5ba", + "sha256:299d66e9218193f9dc6e4880629ed7c4cd23486005166247c283fb98531656c3", + "sha256:2d098709621d0819039f3f1e471ee554f55a0b2ac0d816883c765b14129b5627", + "sha256:2f5e731627a3d5ef11a2a35aa0c6f7c435867c7ccbc391268eb4f2ca5dbdcc10", + "sha256:303d38b19626c1981e1bb067a9928236d88eb0e4479b18a74812f05a82071508", + "sha256:318002f1fd819bdc1651c619268aa5bc853c35fa5cc6d1e8c96bd9cd6c828b75", + "sha256:318b2e4753cbf611061e01b6cc81477e1cdfeb69c36c4a14e6595e674caadb56", + "sha256:31b6e889c53d4e6687ca63706148049494aace140cffece1c4dc6acadb70a7b3", + "sha256:343aaeb5f8bb7bcd38620fd7bc56e6ee8207847d8c6103a1e7b72322d381ba4a", + "sha256:3d1aed4f4e837a832df2f3b4f68a690eede0de4560a2dbc214ea0bc55aabcdb4", + "sha256:3f379b02c18a64de78c4ccdddf1c81c2c5ae1956c72dacb9133d7dd7809794ab", + "sha256:44f14a62f5da2e9aedf9080e01d2cda61df39197d48e323538ec037336d68da8", + "sha256:46d29926349b5c4f1ea4fca95e8c892835515f3600995a383fa9a923b5739ea4", + "sha256:51c4c42c0e7d09a822b08b6cf79b3c4db8333fffde7450da946719ba0d45730f", + "sha256:53be4aab8ddef18beb6188f3a3fdbf4d1af2277d098d4e618be3a8e6c88e74be", + "sha256:562136b0d401992118d9b49fbee5454e16f95f85b120a4226a04d816e33fe024", + "sha256:5907605ee20e126eeee2abe14aae137043c2c8af2fa9b38d2ab3b7a6b8137f73", + "sha256:59224bfb2e9b37c1335ae35d00daa3a5b4e0b1a20f530be208fff1ecfa436f43", + "sha256:5b1ad2e0dc672625c44bc4fe34514602a9fd8b10d52ddc414dc585f74453516c", + "sha256:5badd7e596e6b0c89aa8ec6d37f4473e4357f982ce57f9a2942b0221cd9cf60c", + "sha256:5d67b9ed6f7b5527b209b24b3df9f2e5bf0198c1bbf99c6971b0e2dcb7e2a107", + "sha256:65436cde5ecabe26fb2f0bf598962f0a054d3f23ad529361326ac002c61a2a1e", + "sha256:6ed2e787249b922a93cd95c671cc9f4c9797a106e81b455c83a9ddb9d34590c0", + "sha256:71295f2d1d170b9977dc386d46a7a1b7cbb30e5405492529b4c930113a33f895", + "sha256:75b3c0300f3fa15809bd62d9ca8b170eb21fcf0100eb4b4154d6dc8b3a5bbd43", + "sha256:79f2670c7e772f4917895c3d89aad59e01f3dbe68a4ed2d0373b431fad1dcfba", + "sha256:7a482f2da9086971efb12daca1d6547007ede3674ea06e16d7663414445c683e", + "sha256:7bbb5aa9016c4c29e3432e087aa29ebee3f8fda089cfbfb4e6d64bd292dcd1c2", + "sha256:7df8759ee57b9f3f7b66799b7660c282f4375bef620ade1686d6a7b03699e75f", + "sha256:824bb95cd71604031ae9a48edb91fd6effde669522f960375668ed21b36e3ec4", + "sha256:853c3d3c79ff0db65797aad79dee6be020efd218ac4510f15a205f1e8d13ce25", + "sha256:87ff33b652b3556b05e204ae20793d1f872161b0fa5ec8a9ac76f8430e152ed6", + "sha256:8bb09e83c603f152d855f666d70a71765ca8e67332e5829e62cb9466c176af23", + "sha256:8f1010029a5b52dc427c8e2a8dbddb2303ddd180b806687d1acd1bb1d06649e7", + "sha256:8f2adf4bcffbbec41f366f2e6dffb9d24e8172d16e91da5799c9b7ed6b5716e6", + "sha256:90a8af9dba6429b2573199622d72e0ebf024d6276f16abce394ad4d181bb0910", + "sha256:94d2ac94bd0cc57c5626f52f8c2fffed1444b5ae8c9fc68320306cc2b255e155", + "sha256:96c3be8bae9d0333e403cc1a8eb078a7f928b5650bae94a18fb4820cc993fb9b", + "sha256:989aa158c0eb19d83c76c26f4ba00dbb272485c56e452010a3450bdbc9daafd9", + "sha256:99fee45adbb1caeb914da16f70e557fb7ff6ddc9e4b14de665bd41af631367ef", + "sha256:9db3a3285d91c0b70fab9f39f0a4aa37d375873677efe4e71e58d8321e8c5d39", + "sha256:9f9efbbaf79f935d5fbe3ad814825cbce4f6cdb3054384cb49f0c0f496125fa0", + "sha256:a2f7589c6132c44c53f6e705e1a6677e2b7821378c22f7703b2cf5388d0d4587", + "sha256:a88705500988c8acad8b8fd86c2a933d3aa96bec1ddc4bc5cb256360db7bbd00", + "sha256:ab6d72bffac9deb6e6cb0f61042e748de3f9f8e98afb0375a8e64b0b6e11746b", + "sha256:ae9306b5299e31e31e0d3b908c66bcb6e7e3ddca143dea0266e9ce6c667346d3", + "sha256:b2182129f4c101272ff5f2f18038d7b698db1bf8e7aa9e615cb48440899ad32e", + "sha256:b2beb64c145593a50d90db5c7178f55daeae129123b0d265bdb3cbec83e5194a", + "sha256:b607a40cba795cfac6d130220d25962931ce101f2f478a29822b19755377fb34", + "sha256:be14d0622125edef21b3a4d8cd2d138c4872bf6e38adc90fd92385e3312f406a", + "sha256:bfeee64ad8b4aae3233abb77eb6b52b51b05fa89da9645518671b9939a78732b", + "sha256:c5e9787cec750793a19a28df7edd85ac4e49d3fb91721afcdc3b86f6c08d9aa8", + "sha256:c672d4e2f0575a4ca2bf2aa0c5ced5188220ab806c1bb6d7179f70a11a017222", + "sha256:c6f6169bbdbdb85aab8ac0392d776948907267fcc91deeacf6f9d55f7a83ae3b", + "sha256:ca46e5c3be3b195098dd88711890b8011a9fa4feca942292bb84714ce5eab5d3", + "sha256:cc7fd0f726795420f3678ac82ff882c7fc33770bd0074463b5aef7293285ace9", + "sha256:cd5dee4fd7659d8306ffa79eeaaafd91fa30a302dac3af723b9b469e549247e0", + "sha256:d1a049b5c51b3b679928dd35e47c4a2235e0b6128b479a7596d0ef5b42fa6301", + "sha256:d358dc408edc28730aed5477a69338e444e62fba0b7e9e4a131c505fadad691e", + "sha256:d3a16d6398666510a6886f67f43d9537bfd0e13aca299688a19daa84f543122f", + "sha256:d401f0864a1d3198422816878e4e84ca89ec1c1bf166ecc0ae01380a39b888cd", + "sha256:d6f4a21328ea49d38565b55599e1c02834e76583a6953e5586d65cb1efebd8f8", + "sha256:db83b77f97129813dbd463a67e5335adc6a6a91db652cc085d60c2d512746f96", + "sha256:debf29e0b157769843dff0981cc76f79e0ed04e36bb773c6cac5f6029054bd8a", + "sha256:dfb428e41377e6b9ba1b0a32df6db5409cb089a0ed1d0a672dc4953ec110d84f", + "sha256:e129328ad1258e49cae0123a3b5fcb93d6c2fa90d540f0b4c7cdcdc019aaa3dc", + "sha256:e5b86db331c682fd0e4be7098e6acee5e8a293f824d41487c667a93705d415ca", + "sha256:ed48b4170caa2c4420e0cd27dc977caaffc7eecc317355751df8373dddcef595", + "sha256:edc7754932682d52cf6e7a71806e529ecd5ce660e630e8bd1d37109a2e5f63ba", + "sha256:f45c9bcb16bee25a798ccba8a2f6a1251b19de6a0d617bb365d7d2f386c4e20e", + "sha256:f75695e157c83d374f88dcc646a60cb94173304a9258b2e74ba5a66b7614a51a", + "sha256:f7f153d0184d45f3873b3ad3ad22694fd73aadcb8cdbc4337ab4b41ea6b4dff1", + "sha256:f7f6182d3dfb8802c1747eacbfe611b669455b69b7c037484bb1efbbb56711ac", + "sha256:f9bada7bc660d20b23d7d312ebe29e927b655cf414dadcdb6335a2075695bd86", + "sha256:fae6a21537519c2af00245e834e5bf2884699cc7c1055738fd0f9dc37a3644ad", + "sha256:fb25061a66802df9fc13a9ba1967d25faa4dae0418db469264fd9860a921dde4", + "sha256:fc970575799a9d17d5c3fafd83a0f6ccf5d5117cdc9ad6fbd791e9ead82418b0", + "sha256:fcda51c918c7a13ad93b5f89a58d56e3a072c9e0ba5c231b0ed81404bf2648fb" ], "markers": "python_version >= '3.10'", - "version": "==7.13.1" + "version": "==7.13.3" }, "django": { "hashes": [ @@ -1651,11 +1778,11 @@ }, "django-stubs-ext": { "hashes": [ - "sha256:1dd5470c9675591362c78a157a3cf8aec45d0e7a7f0cf32f227a1363e54e0652", - "sha256:b39938c46d7a547cd84e4a6378dbe51a3dd64d70300459087229e5fee27e5c6b" + "sha256:230c51575551b0165be40177f0f6805f1e3ebf799b835c85f5d64c371ca6cf71", + "sha256:6db4054d1580657b979b7d391474719f1a978773e66c7070a5e246cd445a25a9" ], "markers": "python_version >= '3.10'", - "version": "==5.2.8" + "version": "==5.2.9" }, "docutils": { "hashes": [ @@ -1684,11 +1811,11 @@ }, "faker": { "hashes": [ - "sha256:a616d35818e2a2387c297de80e2288083bc915e24b7e39d2fb5bc66cce3a929f", - "sha256:c402212a981a8a28615fea9120d789e3f6062c0c259a82bfb8dff5d273e539d2" + "sha256:93503165c165d330260e4379fd6dc07c94da90c611ed3191a0174d2ab9966a42", + "sha256:b76a68163aa5f171d260fc24827a8349bc1db672f6a665359e8d0095e8135d30" ], "markers": "python_version >= '3.10'", - "version": "==40.1.0" + "version": "==40.1.2" }, "flake8": { "hashes": [ @@ -1750,17 +1877,17 @@ }, "jmespath": { "hashes": [ - "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", - "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe" + "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", + "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64" ], - "markers": "python_version >= '3.7'", - "version": "==1.0.1" + "markers": "python_version >= '3.9'", + "version": "==1.1.0" }, "localstack-client": { "hashes": [ - "sha256:732a07e23fffd6a581af2714bbe006ad6f884ac4f8ac955211a8a63321cdc409" + "sha256:1cbd7bf1f03b9b553ffe7ea10fe137f44e8d690a37af9c6515eba61a2379fc46" ], - "version": "==2.10" + "version": "==2.11" }, "markdown": { "hashes": [ @@ -1901,19 +2028,19 @@ }, "packaging": { "hashes": [ - "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", - "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", + "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529" ], "markers": "python_version >= '3.8'", - "version": "==25.0" + "version": "==26.0" }, "pathspec": { "hashes": [ - "sha256:62f8558917908d237d399b9b338ef455a814801a4688bc41074b25feefd93472", - "sha256:fa32b1eb775ed9ba8d599b22c5f906dc098113989da2c00bf8b210078ca7fb92" + "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", + "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723" ], "markers": "python_version >= '3.9'", - "version": "==1.0.2" + "version": "==1.0.4" }, "platformdirs": { "hashes": [ @@ -1933,11 +2060,11 @@ }, "pyasn1": { "hashes": [ - "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", - "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034" + "sha256:1eb26d860996a18e9b6ed05e7aae0e9fc21619fcee6af91cca9bad4fbea224bf", + "sha256:9b59a2b25ba7e4f8197db7686c09fb33e658b98339fadb826e9512629017833b" ], "markers": "python_version >= '3.8'", - "version": "==0.6.1" + "version": "==0.6.2" }, "pycodestyle": { "hashes": [ @@ -2170,51 +2297,56 @@ }, "tomli": { "hashes": [ - "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", - "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", - "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", - "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", - "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", - "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", - "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", - "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", - "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", - "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", - "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", - "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", - "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", - "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", - "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", - "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", - "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", - "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", - "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", - "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", - "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", - "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", - "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", - "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", - "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", - "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", - "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", - "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", - "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", - "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", - "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", - "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", - "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", - "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", - "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", - "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", - "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", - "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", - "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", - "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", - "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", - "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876" + "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", + "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", + "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", + "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", + "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", + "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", + "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", + "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", + "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", + "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", + "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", + "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", + "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", + "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", + "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", + "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", + "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", + "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", + "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", + "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", + "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", + "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", + "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", + "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", + "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", + "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", + "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", + "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", + "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", + "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", + "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", + "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", + "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", + "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", + "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", + "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", + "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", + "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", + "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", + "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", + "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", + "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", + "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", + "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", + "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", + "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", + "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087" ], "markers": "python_version >= '3.8'", - "version": "==2.3.0" + "version": "==2.4.0" }, "types-pyyaml": { "hashes": [ @@ -2232,14 +2364,6 @@ "markers": "python_version >= '3.9'", "version": "==4.15.0" }, - "tzdata": { - "hashes": [ - "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", - "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7" - ], - "markers": "python_version >= '2'", - "version": "==2025.3" - }, "urllib3": { "hashes": [ "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", diff --git a/tdrs-backend/tdpservice/core/migrations/0003_load_uswds_admin_theme.py b/tdrs-backend/tdpservice/core/migrations/0003_load_uswds_admin_theme.py new file mode 100644 index 000000000..89638cd99 --- /dev/null +++ b/tdrs-backend/tdpservice/core/migrations/0003_load_uswds_admin_theme.py @@ -0,0 +1,40 @@ +"""Load the U.S. Web Design Standards theme for django-admin-interface.""" + +from django.core.management import call_command +from django.db import migrations + + +def load_uswds_theme(apps, schema_editor): + """Load the USWDS theme fixture and set it as the active theme.""" + Theme = apps.get_model("admin_interface", "Theme") + + # Deactivate any existing themes + Theme.objects.all().update(active=False) + + # Load the USWDS theme fixture shipped with django-admin-interface + call_command("loaddata", "admin_interface_theme_uswds.json", verbosity=0) + + # Ensure the USWDS theme is the active one + Theme.objects.filter(name="U.S. Web Design Standards").update(active=True) + + +def revert_to_default(apps, schema_editor): + """Revert to the default Django theme.""" + Theme = apps.get_model("admin_interface", "Theme") + Theme.objects.filter(name="U.S. Web Design Standards").update(active=False) + # Re-activate the default Django theme if it exists + default = Theme.objects.filter(name="Django").first() + if default: + default.active = True + default.save() + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0002_auto_20201013_0630"), + ("admin_interface", "0001_initial"), + ] + + operations = [ + migrations.RunPython(load_uswds_theme, revert_to_default), + ] diff --git a/tdrs-backend/tdpservice/settings/common.py b/tdrs-backend/tdpservice/settings/common.py index a04834f01..4695d567d 100644 --- a/tdrs-backend/tdpservice/settings/common.py +++ b/tdrs-backend/tdpservice/settings/common.py @@ -78,6 +78,8 @@ class Common(Configuration): """Define configuration class.""" INSTALLED_APPS = ( + "admin_interface", + "colorfield", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", @@ -160,6 +162,9 @@ class Common(Configuration): SECRET_KEY = os.getenv("DJANGO_SECRET_KEY") WSGI_APPLICATION = "tdpservice.wsgi.application" + # Required by django-admin-interface for related-modal popups + X_FRAME_OPTIONS = "SAMEORIGIN" + # Application URLs BASE_URL = os.getenv("BASE_URL", "http://localhost:8080/v1") FRONTEND_BASE_URL = os.getenv("FRONTEND_BASE_URL", "http://localhost:3000") From 0556a719957da6d213e37a18ce562f4cb5d8a224 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Thu, 5 Feb 2026 14:15:30 -0600 Subject: [PATCH 039/148] - add missing perms to tst --- tdrs-backend/tdpservice/users/test/test_permissions.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tdrs-backend/tdpservice/users/test/test_permissions.py b/tdrs-backend/tdpservice/users/test/test_permissions.py index 0cd98328a..2f8d7ab99 100644 --- a/tdrs-backend/tdpservice/users/test/test_permissions.py +++ b/tdrs-backend/tdpservice/users/test/test_permissions.py @@ -35,6 +35,9 @@ def test_ofa_system_admin_permissions(ofa_system_admin): "admin.add_logentry", "admin.change_logentry", "admin.view_logentry", + "admin_interface.add_theme", + "admin_interface.change_theme", + "admin_interface.view_theme", "auth.add_group", "auth.add_permission", "auth.change_group", From 514b5301b154a02be544ab070f2b81f2b718cd8d Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Wed, 21 Jan 2026 11:07:02 -0600 Subject: [PATCH 040/148] - Updated storage backend to store the currently used s3 version id so that the view can access it later. - Remove old function and update view --- tdrs-backend/tdpservice/backends.py | 15 +++++++++++++++ tdrs-backend/tdpservice/data_files/views.py | 21 ++------------------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/tdrs-backend/tdpservice/backends.py b/tdrs-backend/tdpservice/backends.py index 1cdbc519d..f45061d9a 100644 --- a/tdrs-backend/tdpservice/backends.py +++ b/tdrs-backend/tdpservice/backends.py @@ -43,6 +43,21 @@ class DataFilesS3Storage(OverriddenCredentialsS3Storage): # Use distinct region for the tdp-datafiles service region_name = settings.AWS_S3_DATAFILES_REGION_NAME + def _save(self, name, content): + """Save file and capture version ID from S3 response.""" + name = super()._save(name, content) + obj = self.bucket.Object(self._normalize_name(name)) + version_id = obj.version_id + if version_id and version_id != "null": + self._last_version_id = version_id + else: + self._last_version_id = None + return name + + def get_version_id(self) -> str | None: + """Get the version ID captured from the most recent upload.""" + return getattr(self, "_last_version_id", None) + class StaticFilesS3Storage(OverriddenCredentialsS3Storage): """An S3 backed storage provider for Django Admin staticfiles.""" diff --git a/tdrs-backend/tdpservice/data_files/views.py b/tdrs-backend/tdpservice/data_files/views.py index bd40dcf86..7f494330c 100644 --- a/tdrs-backend/tdpservice/data_files/views.py +++ b/tdrs-backend/tdpservice/data_files/views.py @@ -18,7 +18,7 @@ from rest_framework.viewsets import ModelViewSet from tdpservice.data_files.error_reports import ErrorReportFactory -from tdpservice.data_files.models import DataFile, get_s3_upload_path +from tdpservice.data_files.models import DataFile from tdpservice.data_files.s3_client import S3Client from tdpservice.data_files.serializers import DataFileSerializer from tdpservice.log_handler import S3FileHandler @@ -95,12 +95,7 @@ def create(self, request, *args, **kwargs): + f"quarter {data_file.quarter}, year {data_file.year}." ) - app_name = settings.APP_NAME + "/" - key = app_name + get_s3_upload_path(data_file, "") - version_id = self.get_s3_versioning_id( - response.data.get("original_filename"), key - ) - + version_id = data_file.file.storage.get_version_id() data_file.s3_versioning_id = version_id data_file.save(update_fields=["s3_versioning_id"]) @@ -110,18 +105,6 @@ def create(self, request, *args, **kwargs): logger.debug(f"{self.__class__.__name__}: return val: {response}") return response - def get_s3_versioning_id(self, file_name, prefix): - """Get the version id of the file uploaded to S3.""" - s3 = S3Client() - bucket_name = settings.AWS_S3_DATAFILES_BUCKET_NAME - versions = s3.client.list_object_versions(Bucket=bucket_name, Prefix=prefix) - for version in versions["Versions"]: - file_path = version["Key"] - if file_name in file_path: - if version["IsLatest"] and version["VersionId"] != "null": - return version["VersionId"] - return None - def get_queryset(self): """Apply custom queryset filters.""" queryset = super().get_queryset().order_by("-created_at") From 6590d449d30d073d79a131da639f761af2f71832 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Wed, 21 Jan 2026 11:18:15 -0600 Subject: [PATCH 041/148] - Update to make retrieving the version from the storage object thread safe --- tdrs-backend/tdpservice/backends.py | 19 ++++++++++++------- tdrs-backend/tdpservice/data_files/views.py | 2 +- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/tdrs-backend/tdpservice/backends.py b/tdrs-backend/tdpservice/backends.py index f45061d9a..14e48ed2c 100644 --- a/tdrs-backend/tdpservice/backends.py +++ b/tdrs-backend/tdpservice/backends.py @@ -43,20 +43,25 @@ class DataFilesS3Storage(OverriddenCredentialsS3Storage): # Use distinct region for the tdp-datafiles service region_name = settings.AWS_S3_DATAFILES_REGION_NAME + # Thread-safe cache for version IDs, keyed by file name + # This is strictly a percaution. Gunicorn isn't setup with multiple threads currently. But if that + # changes in the future, this will prevent any issues with thread safety. + _version_id_cache: dict[str, str] = {} + def _save(self, name, content): """Save file and capture version ID from S3 response.""" name = super()._save(name, content) - obj = self.bucket.Object(self._normalize_name(name)) + normalized_name = self._normalize_name(name) + obj = self.bucket.Object(normalized_name) version_id = obj.version_id if version_id and version_id != "null": - self._last_version_id = version_id - else: - self._last_version_id = None + self._version_id_cache[normalized_name] = version_id return name - def get_version_id(self) -> str | None: - """Get the version ID captured from the most recent upload.""" - return getattr(self, "_last_version_id", None) + def get_version_id(self, name: str) -> str | None: + """Get and remove the version ID for a specific file from cache.""" + normalized_name = self._normalize_name(name) + return self._version_id_cache.pop(normalized_name, None) class StaticFilesS3Storage(OverriddenCredentialsS3Storage): diff --git a/tdrs-backend/tdpservice/data_files/views.py b/tdrs-backend/tdpservice/data_files/views.py index 7f494330c..8b46f563c 100644 --- a/tdrs-backend/tdpservice/data_files/views.py +++ b/tdrs-backend/tdpservice/data_files/views.py @@ -95,7 +95,7 @@ def create(self, request, *args, **kwargs): + f"quarter {data_file.quarter}, year {data_file.year}." ) - version_id = data_file.file.storage.get_version_id() + version_id = data_file.file.storage.get_version_id(data_file.file.name) data_file.s3_versioning_id = version_id data_file.save(update_fields=["s3_versioning_id"]) From 78ac2ec583fde42190fcd4842d20753c648a5496 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Wed, 21 Jan 2026 12:44:24 -0600 Subject: [PATCH 042/148] - linting --- tdrs-backend/tdpservice/data_files/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tdrs-backend/tdpservice/data_files/views.py b/tdrs-backend/tdpservice/data_files/views.py index 8b46f563c..c16e95c2e 100644 --- a/tdrs-backend/tdpservice/data_files/views.py +++ b/tdrs-backend/tdpservice/data_files/views.py @@ -3,7 +3,6 @@ import logging from wsgiref.util import FileWrapper -from django.conf import settings from django.http import FileResponse, Http404, HttpResponse from django_filters import rest_framework as filters From 7e07c75ac238256b0204bd75ef9c6848938ec3f0 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Thu, 29 Jan 2026 14:44:33 -0600 Subject: [PATCH 043/148] - Move version logic into custom field outside of the backends - Added migration for new field - Added tests --- tdrs-backend/tdpservice/backends.py | 20 ----- tdrs-backend/tdpservice/common/fields.py | 50 +++++++++++ .../tdpservice/core/test/test_fields.py | 85 +++++++++++++++++++ .../migrations/0024_alter_datafile_file.py | 26 ++++++ tdrs-backend/tdpservice/data_files/models.py | 9 +- tdrs-backend/tdpservice/data_files/views.py | 4 - 6 files changed, 168 insertions(+), 26 deletions(-) create mode 100644 tdrs-backend/tdpservice/common/fields.py create mode 100644 tdrs-backend/tdpservice/core/test/test_fields.py create mode 100644 tdrs-backend/tdpservice/data_files/migrations/0024_alter_datafile_file.py diff --git a/tdrs-backend/tdpservice/backends.py b/tdrs-backend/tdpservice/backends.py index 14e48ed2c..1cdbc519d 100644 --- a/tdrs-backend/tdpservice/backends.py +++ b/tdrs-backend/tdpservice/backends.py @@ -43,26 +43,6 @@ class DataFilesS3Storage(OverriddenCredentialsS3Storage): # Use distinct region for the tdp-datafiles service region_name = settings.AWS_S3_DATAFILES_REGION_NAME - # Thread-safe cache for version IDs, keyed by file name - # This is strictly a percaution. Gunicorn isn't setup with multiple threads currently. But if that - # changes in the future, this will prevent any issues with thread safety. - _version_id_cache: dict[str, str] = {} - - def _save(self, name, content): - """Save file and capture version ID from S3 response.""" - name = super()._save(name, content) - normalized_name = self._normalize_name(name) - obj = self.bucket.Object(normalized_name) - version_id = obj.version_id - if version_id and version_id != "null": - self._version_id_cache[normalized_name] = version_id - return name - - def get_version_id(self, name: str) -> str | None: - """Get and remove the version ID for a specific file from cache.""" - normalized_name = self._normalize_name(name) - return self._version_id_cache.pop(normalized_name, None) - class StaticFilesS3Storage(OverriddenCredentialsS3Storage): """An S3 backed storage provider for Django Admin staticfiles.""" diff --git a/tdrs-backend/tdpservice/common/fields.py b/tdrs-backend/tdpservice/common/fields.py new file mode 100644 index 000000000..a15a43fcc --- /dev/null +++ b/tdrs-backend/tdpservice/common/fields.py @@ -0,0 +1,50 @@ +"""Custom Django model fields for tdpservice.""" +import logging + +from django.db import models +from django.db.models.fields.files import FieldFile + +logger = logging.getLogger(__name__) + + +class S3VersionedFieldFile(FieldFile): + """A FieldFile that captures the S3 version ID after upload and sets it on the model.""" + + def save(self, name, content, save=True): + """Save file to storage, then capture the S3 version ID onto the model instance.""" + super().save(name, content, save=False) + + version_id_field = getattr(self.field, "version_id_field", None) + if version_id_field and hasattr(self.storage, "bucket"): + try: + normalized_name = self.storage._normalize_name(self.name) + obj = self.storage.bucket.Object(normalized_name) + version_id = obj.version_id + if version_id and version_id != "null": + setattr(self.instance, version_id_field, version_id) + except Exception: + logger.exception("Failed to capture S3 version ID for %s", self.name) + + if save: + self.instance.save() + + +class S3VersionedFileField(models.FileField): + """A FileField that automatically captures S3 version IDs on upload. + + The `version_id_field` argument names the model field where the S3 + version ID will be stored after a successful upload. + """ + + attr_class = S3VersionedFieldFile + + def __init__(self, *args, version_id_field: str | None = None, **kwargs): + self.version_id_field = version_id_field + super().__init__(*args, **kwargs) + + def deconstruct(self): + """Include version_id_field in migration serialization.""" + name, path, args, kwargs = super().deconstruct() + if self.version_id_field: + kwargs["version_id_field"] = self.version_id_field + return name, path, args, kwargs diff --git a/tdrs-backend/tdpservice/core/test/test_fields.py b/tdrs-backend/tdpservice/core/test/test_fields.py new file mode 100644 index 000000000..8f59ff60a --- /dev/null +++ b/tdrs-backend/tdpservice/core/test/test_fields.py @@ -0,0 +1,85 @@ +"""Tests for custom fields.""" +from unittest.mock import MagicMock + +from tdpservice.common.fields import S3VersionedFieldFile, S3VersionedFileField + + +class TestS3VersionedFileField: + """Tests for S3VersionedFileField and S3VersionedFieldFile.""" + + def test_versioned_file_field_attr_class(self): + """Test that S3VersionedFileField uses S3VersionedFieldFile as its attr_class.""" + field = S3VersionedFileField(version_id_field="s3_versioning_id") + assert field.attr_class is S3VersionedFieldFile + + def test_versioned_file_field_stores_version_id_field(self): + """Test that the version_id_field kwarg is stored on the field.""" + field = S3VersionedFileField(version_id_field="s3_versioning_id") + assert field.version_id_field == "s3_versioning_id" + + def test_versioned_file_field_deconstruct_includes_version_id_field(self): + """Test that deconstruct includes version_id_field for migrations.""" + field = S3VersionedFileField(version_id_field="s3_versioning_id") + _, _, _, kwargs = field.deconstruct() + assert kwargs["version_id_field"] == "s3_versioning_id" + + def test_versioned_file_field_deconstruct_omits_none_version_id_field(self): + """Test that deconstruct omits version_id_field when not set.""" + field = S3VersionedFileField() + _, _, _, kwargs = field.deconstruct() + assert "version_id_field" not in kwargs + + def _make_field_file( + self, version_id_field="s3_versioning_id", s3_version_id="abc123" + ): + """Create an S3VersionedFieldFile with mocked storage, field, and instance.""" + mock_storage = MagicMock() + mock_storage.save.return_value = "data_files/test.txt" + mock_storage._normalize_name.return_value = "data_files/test.txt" + + mock_s3_obj = MagicMock() + mock_s3_obj.version_id = s3_version_id + mock_storage.bucket.Object.return_value = mock_s3_obj + + mock_field = MagicMock() + mock_field.version_id_field = version_id_field + mock_field.generate_filename.return_value = "data_files/test.txt" + mock_field.max_length = 100 + mock_field.attname = "file" + + mock_instance = MagicMock() + mock_instance.s3_versioning_id = None + + field_file = S3VersionedFieldFile( + instance=mock_instance, field=mock_field, name="test.txt" + ) + field_file.storage = mock_storage + + return field_file, mock_instance, mock_storage + + def test_versioned_field_file_save_sets_version_id(self): + """Test that saving a file captures the S3 version ID on the model instance.""" + field_file, mock_instance, _ = self._make_field_file(s3_version_id="abc123") + + field_file.save("test.txt", MagicMock(), save=False) + + assert mock_instance.s3_versioning_id == "abc123" + + def test_versioned_field_file_save_skips_null_version_id(self): + """Test that 'null' version IDs from S3 are not set on the model.""" + field_file, mock_instance, _ = self._make_field_file(s3_version_id="null") + + field_file.save("test.txt", MagicMock(), save=False) + + assert mock_instance.s3_versioning_id is None + + def test_versioned_field_file_save_without_version_id_field(self): + """Test that save works normally when version_id_field is not configured.""" + field_file, mock_instance, mock_storage = self._make_field_file( + version_id_field=None, s3_version_id="abc123" + ) + + field_file.save("test.txt", MagicMock(), save=False) + + # Should not attempt to access storage.bucket at all + mock_storage.bucket.Object.assert_not_called() diff --git a/tdrs-backend/tdpservice/data_files/migrations/0024_alter_datafile_file.py b/tdrs-backend/tdpservice/data_files/migrations/0024_alter_datafile_file.py new file mode 100644 index 000000000..6de0a1463 --- /dev/null +++ b/tdrs-backend/tdpservice/data_files/migrations/0024_alter_datafile_file.py @@ -0,0 +1,26 @@ +# Generated by Django 3.2.15 on 2026-01-29 19:24 + +from django.db import migrations +import tdpservice.backends +import tdpservice.common.fields +import tdpservice.data_files.models + + +class Migration(migrations.Migration): + dependencies = [ + ("data_files", "0023_rename_indexes"), + ] + + operations = [ + migrations.AlterField( + model_name="datafile", + name="file", + field=tdpservice.common.fields.S3VersionedFileField( + blank=True, + null=True, + storage=tdpservice.backends.DataFilesS3Storage, + upload_to=tdpservice.data_files.models.get_s3_upload_path, + version_id_field="s3_versioning_id", + ), + ), + ] diff --git a/tdrs-backend/tdpservice/data_files/models.py b/tdrs-backend/tdpservice/data_files/models.py index f1b149114..98834487c 100644 --- a/tdrs-backend/tdpservice/data_files/models.py +++ b/tdrs-backend/tdpservice/data_files/models.py @@ -15,6 +15,7 @@ from django.utils.html import format_html from tdpservice.backends import DataFilesS3Storage +from tdpservice.common.fields import S3VersionedFileField from tdpservice.common.models import FileRecord from tdpservice.data_files.util import create_s3_log_file_path from tdpservice.stts.models import STT @@ -177,8 +178,12 @@ class Meta: # NOTE: `file` is only temporarily nullable until we complete the issue: # https://github.com/raft-tech/TANF-app/issues/755 - file = models.FileField( - storage=DataFilesS3Storage, upload_to=get_s3_upload_path, null=True, blank=True + file = S3VersionedFileField( + storage=DataFilesS3Storage, + upload_to=get_s3_upload_path, + version_id_field="s3_versioning_id", + null=True, + blank=True, ) s3_versioning_id = models.CharField(max_length=1024, blank=False, null=True) diff --git a/tdrs-backend/tdpservice/data_files/views.py b/tdrs-backend/tdpservice/data_files/views.py index c16e95c2e..123fa8fdc 100644 --- a/tdrs-backend/tdpservice/data_files/views.py +++ b/tdrs-backend/tdpservice/data_files/views.py @@ -94,10 +94,6 @@ def create(self, request, *args, **kwargs): + f"quarter {data_file.quarter}, year {data_file.year}." ) - version_id = data_file.file.storage.get_version_id(data_file.file.name) - data_file.s3_versioning_id = version_id - data_file.save(update_fields=["s3_versioning_id"]) - parser_task.parse.delay(data_file_id) logger.info("Submitted parse task to queue for datafile %s.", data_file_id) From 63d8b172ccf778ae68b386f30270fa5484d30b1f Mon Sep 17 00:00:00 2001 From: Mo S <97037188+raftmsohani@users.noreply.github.com> Date: Tue, 3 Feb 2026 11:48:27 -0500 Subject: [PATCH 044/148] 5597 Fix showing tribal STT (#5606) * 5597 Fix showing tribal STT * Tech debt Linting --- .../src/components/Reports/Reports.test.js | 9 +--- tdrs-frontend/src/hooks/useFocusTrap.test.js | 4 -- .../src/hooks/usePollingTimer.test.js | 12 ++--- tdrs-frontend/src/selectors/stts.js | 31 +++++++----- tdrs-frontend/src/selectors/stts.test.js | 48 +++++++++++++++++++ 5 files changed, 74 insertions(+), 30 deletions(-) create mode 100644 tdrs-frontend/src/selectors/stts.test.js diff --git a/tdrs-frontend/src/components/Reports/Reports.test.js b/tdrs-frontend/src/components/Reports/Reports.test.js index 9cf9b7d55..a9f4dd678 100644 --- a/tdrs-frontend/src/components/Reports/Reports.test.js +++ b/tdrs-frontend/src/components/Reports/Reports.test.js @@ -1,12 +1,5 @@ import React from 'react' -import { - render, - screen, - fireEvent, - waitFor, - act, - queryAllByText, -} from '@testing-library/react' +import { render, screen, fireEvent, waitFor } from '@testing-library/react' import { Provider } from 'react-redux' import { MemoryRouter } from 'react-router-dom' diff --git a/tdrs-frontend/src/hooks/useFocusTrap.test.js b/tdrs-frontend/src/hooks/useFocusTrap.test.js index d11b80a19..6c31bcc96 100644 --- a/tdrs-frontend/src/hooks/useFocusTrap.test.js +++ b/tdrs-frontend/src/hooks/useFocusTrap.test.js @@ -23,15 +23,12 @@ function TestComponent({ isActive }) { ) } -let activeElement = null - beforeAll(() => { jest .spyOn(window, 'requestAnimationFrame') .mockImplementation((cb) => setTimeout(cb, 0)) HTMLElement.prototype.focus = jest.fn(function () { - activeElement = this Object.defineProperty(document, 'activeElement', { configurable: true, get: () => this, @@ -46,7 +43,6 @@ afterAll(() => { describe('useFocusTrap', () => { beforeEach(() => { jest.useFakeTimers() - activeElement = null jest.clearAllMocks() }) diff --git a/tdrs-frontend/src/hooks/usePollingTimer.test.js b/tdrs-frontend/src/hooks/usePollingTimer.test.js index ce60af872..4636c996a 100644 --- a/tdrs-frontend/src/hooks/usePollingTimer.test.js +++ b/tdrs-frontend/src/hooks/usePollingTimer.test.js @@ -113,7 +113,7 @@ describe('usePollingTimer', () => { data: { id: 1, hasErrors: false, summary: { status: 'Pending' } }, }) - const { queryByText, getByTitle, getByText } = setupSingleTimerComponent( + const { queryByText, getByTitle } = setupSingleTimerComponent( mocks, 1000, 10 @@ -139,7 +139,7 @@ describe('usePollingTimer', () => { data: { id: 1, hasErrors: false, summary: { status: 'Pending' } }, }) - const { queryByText, getByTitle, getByText } = setupSingleTimerComponent( + const { queryByText, getByTitle } = setupSingleTimerComponent( mocks, 1000, 10 @@ -206,7 +206,7 @@ describe('usePollingTimer', () => { }, }) - const { queryByText, getByTitle, getByText } = setupSingleTimerComponent( + const { queryByText, getByTitle } = setupSingleTimerComponent( mocks, 1000, 10 @@ -258,7 +258,7 @@ describe('usePollingTimer', () => { }, }) - const { queryByText, getByTitle, getByText } = setupSingleTimerComponent( + const { queryByText, getByTitle } = setupSingleTimerComponent( mocks, 1000, 10 @@ -290,7 +290,7 @@ describe('usePollingTimer', () => { data: { id: 1, hasErrors: false, summary: { status: 'Pending' } }, }) - const { queryByText, getByTitle, getByText } = setupSingleTimerComponent( + const { queryByText, getByTitle } = setupSingleTimerComponent( mocks, 1000, 2 // time out after 2 tries @@ -338,7 +338,7 @@ describe('usePollingTimer', () => { data: { id: 1, hasErrors: false, summary: { status: 'Pending' } }, }) - const { queryByText, getByTitle, getByText } = setupMultiTimerComponent( + const { queryByText, getByTitle } = setupMultiTimerComponent( mocks, 1000, 10 diff --git a/tdrs-frontend/src/selectors/stts.js b/tdrs-frontend/src/selectors/stts.js index a8462d17e..27e056b07 100644 --- a/tdrs-frontend/src/selectors/stts.js +++ b/tdrs-frontend/src/selectors/stts.js @@ -1,17 +1,24 @@ import { selectUser, accountIsRegionalStaff } from './auth' export const availableStts = (path) => { - return (state) => - accountIsRegionalStaff(state) - ? selectUser(state) + return (state) => { + const filterTribes = (stts = []) => { + if (path.includes('fra')) { + return stts.filter((stt) => stt.type !== 'tribe') + } + return stts + } + + if (accountIsRegionalStaff(state)) { + const regionalStts = + selectUser(state) .regions?.map((region) => region.stts) - .flat() - .filter((stt) => { - if (path.includes('fra')) { - return stt.type !== 'tribe' - } - return true - }) - .sort((a, b) => a.name.localeCompare(b.name)) - : state?.stts?.sttList + .flat() || [] + return filterTribes(regionalStts).sort((a, b) => + a.name.localeCompare(b.name) + ) + } + + return filterTribes(state?.stts?.sttList || []) + } } diff --git a/tdrs-frontend/src/selectors/stts.test.js b/tdrs-frontend/src/selectors/stts.test.js new file mode 100644 index 000000000..5fc4e49a9 --- /dev/null +++ b/tdrs-frontend/src/selectors/stts.test.js @@ -0,0 +1,48 @@ +import { availableStts } from './stts' + +const baseStts = [ + { id: 1, name: 'Alabama', type: 'state' }, + { id: 2, name: 'Tribe A', type: 'tribe' }, + { id: 3, name: 'Guam', type: 'territory' }, +] + +const makeState = ({ roleName, sttList = baseStts, regions } = {}) => ({ + auth: { + user: { + account_approval_status: 'Approved', + roles: [{ name: roleName }], + regions, + }, + }, + stts: { sttList }, +}) + +describe('availableStts', () => { + it('filters tribal STTs for non-regional staff on FRA pages', () => { + const state = makeState({ roleName: 'OFA System Admin' }) + const result = availableStts('/reports/fra')(state) + + expect(result.map((stt) => stt.type)).toEqual(['state', 'territory']) + }) + + it('keeps tribal STTs for non-regional staff on non-FRA pages', () => { + const state = makeState({ roleName: 'OFA System Admin' }) + const result = availableStts('/data-files')(state) + + expect(result.map((stt) => stt.type)).toEqual([ + 'state', + 'tribe', + 'territory', + ]) + }) + + it('filters tribal STTs for regional staff on FRA pages', () => { + const state = makeState({ + roleName: 'OFA Regional Staff', + regions: [{ stts: baseStts }], + }) + const result = availableStts('/reports/fra')(state) + + expect(result.map((stt) => stt.type)).toEqual(['state', 'territory']) + }) +}) From cba048307f8be078967106bc53dd6aaaf8b19ac5 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Tue, 13 Jan 2026 13:54:29 -0700 Subject: [PATCH 045/148] Add sprint summary for 2026-01-06 --- sprint-summary-2026-01-06.md | 201 +++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 sprint-summary-2026-01-06.md diff --git a/sprint-summary-2026-01-06.md b/sprint-summary-2026-01-06.md new file mode 100644 index 000000000..d8420b74b --- /dev/null +++ b/sprint-summary-2026-01-06.md @@ -0,0 +1,201 @@ +# Sprint Summary: Dec 17, 2025 - Jan 06, 2026 + +## Overview + +- Closed a batch of high-impact deliverables accelerating production readiness and user-facing improvements, including FRA emails and related UI/content updates. ([#3485](https://github.com/raft-tech/TANF-app/issues/3485), [#5359](https://github.com/raft-tech/TANF-app/issues/5359), [#5390](https://github.com/raft-tech/TANF-app/issues/5390), [#5391](https://github.com/raft-tech/TANF-app/issues/5391), [#5406](https://github.com/raft-tech/TANF-app/issues/5406), [#5363](https://github.com/raft-tech/TANF-app/issues/5363)) + +- Finalized data quality and error-handling work: in-app error report prototypes plus SSN_VALID and validation fixes. ([#5300](https://github.com/raft-tech/TANF-app/issues/5300), [#5475](https://github.com/raft-tech/TANF-app/issues/5475), [#5557](https://github.com/raft-tech/TANF-app/issues/5557), [#5473](https://github.com/raft-tech/TANF-app/issues/5473)) + +- Advanced testing and navigation changes, moving key items into the current sprint and setting up E2E coverage for admin feedback and error reporting. ([#5421](https://github.com/raft-tech/TANF-app/issues/5421), [#5423](https://github.com/raft-tech/TANF-app/issues/5423), [#5497](https://github.com/raft-tech/TANF-app/issues/5497), [#5316](https://github.com/raft-tech/TANF-app/issues/5316)) + +- Blockers on regional staff onboarding and training tasks persisted, constraining momentum but with a clear path to unblock in the next sprint. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462), [#3523](https://github.com/raft-tech/TANF-app/issues/3523), [#4047](https://github.com/raft-tech/TANF-app/issues/4047)) + +- Sprint progress snapshot: 19 completed (38.8%), 8 progressed, 15 unchanged, 7 blocked, with several high-priority items now closed. ([#3485](https://github.com/raft-tech/TANF-app/issues/3485), [#5316](https://github.com/raft-tech/TANF-app/issues/5316), [#5390](https://github.com/raft-tech/TANF-app/issues/5390), [#5391](https://github.com/raft-tech/TANF-app/issues/5391), [#5406](https://github.com/raft-tech/TANF-app/issues/5406), [#5300](https://github.com/raft-tech/TANF-app/issues/5300)) + +--- + +⚪️ **Total Issues:** 49 +✅ **Closed:** 19 +➡️ **Moved:** 8 +⬛️ **Unchanged:** 15 +🛑 **Blocked:** 7 + +--- + +## [Goal 1: Grantees can easily submit data](https://github.com/raft-tech/TANF-app/issues/5447) + +- ✅ [Design FRA successful processing email template (#3485)](https://github.com/raft-tech/TANF-app/issues/3485) +_**Closed**_ - _Moved from **Ready to merge into staging branch**_ + +- ⬛️ [Plan initial research for FTANF replacement (#4989)](https://github.com/raft-tech/TANF-app/issues/4989) +_Remained in **QASP Review**_ + +- ⬛️ [Design ideation for post-MVP centralized feedback reports: Plain Language and Interpretability (#5223)](https://github.com/raft-tech/TANF-app/issues/5223) +_Remained in **Current Sprint Backlog**_ + +- ✅ [Implement FRA Submission Emails (#5359)](https://github.com/raft-tech/TANF-app/issues/5359) +_**Closed**_ - _Moved from **Raft (Dev) Review**_ + +- ✅ [[Design Deliverable] Email notification for confirming program integrity audit submissions (#5363)](https://github.com/raft-tech/TANF-app/issues/5363) +_**Closed**_ - _Moved from **UX Review**_ + +- ✅ [Requirements Gathering and User Stories for Post-MVP Centralized Feedback Reports (#5443)](https://github.com/raft-tech/TANF-app/issues/5443) +_**Closed**_ - _Moved from **UX Review**_ + + +## [Goal 2: Grantees are confident about reporting compliance](https://github.com/raft-tech/TANF-app/issues/5448) + +- ⬛️ [Plan research for in-app error reporting interface (#4721)](https://github.com/raft-tech/TANF-app/issues/4721) +_Remained in **QASP Review**_ + +- ⬛️ [Complete In App Error Reports HTML Prototypes (#5300)](https://github.com/raft-tech/TANF-app/issues/5300) +_Remained in **QASP Review**_ + +- ⬛️ [Update Research Protocol for In App Error Reporting Prototype (#5423)](https://github.com/raft-tech/TANF-app/issues/5423) +_Remained in **QASP Review**_ + +- ⬛️ [Update Figma with Feedback & Error Report Examples (#5425)](https://github.com/raft-tech/TANF-app/issues/5425) +_Remained in **In Progress**_ + +- ➡️ [E2E Test for Same FY/Q/STT Across Program Types (#5477)](https://github.com/raft-tech/TANF-app/issues/5477) +_Moved from **Next Sprint Backlog** to **Current Sprint Backlog**_ + +- ⬛️ [Implement Updated email templates for STT data submissions re: status and error guidance (#5478)](https://github.com/raft-tech/TANF-app/issues/5478) +_Remained in **Raft (Dev) Review**_ + +- ✅ [[Bug] Error report file name missing program type (#5505)](https://github.com/raft-tech/TANF-app/issues/5505) +_**Closed**_ - _Moved from **Next Sprint Backlog**_ + +- ✅ [Update error message to new text (#5561)](https://github.com/raft-tech/TANF-app/issues/5561) +_**Closed**_ - _Moved from **Product Backlog**_ + + +## [Goal 3: Reduce the burden on users](https://github.com/raft-tech/TANF-app/issues/5449) + +- 🛑 [Bump Grafana memory in prod by 4GB to support 1.5M record visualizations (#5269)](https://github.com/raft-tech/TANF-app/issues/5269) +_Remained in **Blocked**_ + + +## [Goal 4: Free up staff time](https://github.com/raft-tech/TANF-app/issues/5450) + +- 🛑 [Create and facilitate Project Updates meeting for regional staff (#3461)](https://github.com/raft-tech/TANF-app/issues/3461) +_Remained in **Blocked**_ + +- 🛑 [Create and facilitate optional training session for regional staff (#3462)](https://github.com/raft-tech/TANF-app/issues/3462) +_Remained in **Blocked**_ + +- 🛑 [Refine research plan for regional staff MVP onboarding experience (#3523)](https://github.com/raft-tech/TANF-app/issues/3523) +_Remained in **Blocked**_ + +- 🛑 [Gather and iterate on OFA feedback (#3995)](https://github.com/raft-tech/TANF-app/issues/3995) +_Remained in **Blocked**_ + +- 🛑 [Gather final OFA feedback and iterate (#4045)](https://github.com/raft-tech/TANF-app/issues/4045) +_Remained in **Blocked**_ + +- 🛑 [Develop training materials, including slides, written instructions, and screenshots or screen recordings for visual guidance (#4047)](https://github.com/raft-tech/TANF-app/issues/4047) +_Remained in **Blocked**_ + +- ⬛️ [Iterate on training materials internally (#4052)](https://github.com/raft-tech/TANF-app/issues/4052) +_Remained in **In Progress**_ + +- ✅ [Develop E2E tests for Editing Profile/Access Request (#5316)](https://github.com/raft-tech/TANF-app/issues/5316) +_**Closed**_ - _Moved from **Raft (Dev) Review**_ + +- ✅ [Admin Feedback Reports UI (#5390)](https://github.com/raft-tech/TANF-app/issues/5390) +_**Closed**_ - _Moved from **UX Review**_ + +- ✅ [Update Knowledge Center with Program Integrity Audit and Centralized Feedback Reports (#5391)](https://github.com/raft-tech/TANF-app/issues/5391) +_**Closed**_ - _Moved from **Current Sprint Backlog**_ + +- ✅ [Write KC Content for PI Audit and Centralized Feedback Reports (#5406)](https://github.com/raft-tech/TANF-app/issues/5406) +_**Closed**_ - _Moved from **UX Review**_ + +- ➡️ [STT Feedback Report UI (#5417)](https://github.com/raft-tech/TANF-app/issues/5417) +_Moved from **Blocked** to **Raft (Dev) Review**_ + +- ⬛️ [Fully implement Knowledge Center component navigation & search (#5429)](https://github.com/raft-tech/TANF-app/issues/5429) +_Remained in **QASP Review**_ + +- ➡️ [Upgrade Backend Dependencies (#5535)](https://github.com/raft-tech/TANF-app/issues/5535) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + + +## [Goal 5: Improve data quality](https://github.com/raft-tech/TANF-app/issues/5451) + +- ⬛️ [[Tech Memo]: Refactor and cleanup parsing logic (#5434)](https://github.com/raft-tech/TANF-app/issues/5434) +_Remained in **Current Sprint Backlog**_ + +- ✅ [Fix cat 2 validation on SSP Item # 41 (WEI) (#5473)](https://github.com/raft-tech/TANF-app/issues/5473) +_**Closed**_ - _Moved from **In Progress**_ + +- ✅ [Update SSN_VALID logic in SQL Views (#5475)](https://github.com/raft-tech/TANF-app/issues/5475) +_**Closed**_ - _Moved from **Raft (Dev) Review**_ + + +## [Goal 6: Documentation is current and helpful](https://github.com/raft-tech/TANF-app/issues/5435) + +- ⬛️ [Create research plan template for UX Playbook (#5369)](https://github.com/raft-tech/TANF-app/issues/5369) +_Remained in **QASP Review**_ + +- ⬛️ [Create a Journey Mapping template & document best practices (#5371)](https://github.com/raft-tech/TANF-app/issues/5371) +_Remained in **QASP Review**_ + +- ⬛️ [Create a QA Checklist for Completing UX & Dev Reviews (#5374)](https://github.com/raft-tech/TANF-app/issues/5374) +_Remained in **QASP Review**_ + + +## [Bug Reports](https://github.com/raft-tech/TANF-app/issues/4441) + +- ✅ [Bug: OFA system admin users get stuck if region is assigned (#3515)](https://github.com/raft-tech/TANF-app/issues/3515) +_**Closed**_ - _Moved from **Raft (Dev) Review**_ + +- ✅ [[Bug]: Postgres View Does Not Exist (#5483)](https://github.com/raft-tech/TANF-app/issues/5483) +_**Closed**_ - _Moved from **In Progress**_ + +- ⬛️ [[Bug]: Celery Apps not being Monitored (#5494)](https://github.com/raft-tech/TANF-app/issues/5494) +_Remained in **In Progress**_ + + +## [Operations & Maintenance](https://github.com/raft-tech/TANF-app/issues/4445) + +- ✅ [Optimize Docker base images to reduce size (#5512)](https://github.com/raft-tech/TANF-app/issues/5512) +_**Closed**_ - _Moved from **In Progress**_ + + +## [User Experience Enhancements](https://github.com/raft-tech/TANF-app/issues/4444) + +- ⬛️ [Create email templates for reparsing (new error report & error resolution) (#3263)](https://github.com/raft-tech/TANF-app/issues/3263) +_Remained in **QASP Review**_ + + +## Issues without Parent + +- ➡️ [Implement E2E Tests for Admin Feedback Reports (#5421)](https://github.com/raft-tech/TANF-app/issues/5421) +_Moved from **Next Sprint Backlog** to **Current Sprint Backlog**_ + +- ➡️ [Transition NavItem component to React Router navigation (#5497)](https://github.com/raft-tech/TANF-app/issues/5497) +_Moved from **Next Sprint Backlog** to **Current Sprint Backlog**_ + +- ✅ [Integrate Alertmanager with Mattermost (#5498)](https://github.com/raft-tech/TANF-app/issues/5498) +_**Closed**_ - _Moved from **Product Backlog**_ + +- ✅ [PIA Submission History Displays in TANF Submission History (#5506)](https://github.com/raft-tech/TANF-app/issues/5506) +_**Closed**_ - _Moved from **Next Sprint Backlog**_ + +- ➡️ [Rhode Island should be able to submit SSP data via TDP starting in FY2026 (#5521)](https://github.com/raft-tech/TANF-app/issues/5521) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + +- ✅ [Add comprehensive tests for `remove_all_old_versions` datafile retention task (#5522)](https://github.com/raft-tech/TANF-app/issues/5522) +_**Closed**_ - _Moved from **Next Sprint Backlog**_ + +- ➡️ [Upload Panel State Reset on Accepted Files (#5527)](https://github.com/raft-tech/TANF-app/issues/5527) +_Moved from **Product Backlog** to **Current Sprint Backlog**_ + +- ✅ [SSN_VALID Not Handling Non-Numeric Values (#5557)](https://github.com/raft-tech/TANF-app/issues/5557) +_**Closed**_ - _Moved from **Product Backlog**_ + +- ➡️ [Investigate and resolve transient E2E test failures on develop (#5577)](https://github.com/raft-tech/TANF-app/issues/5577) +_Moved from **Product Backlog** to **Current Sprint Backlog**_ + + From 91504c43ae6f51c790807893bf0996b10310ca94 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Wed, 17 Dec 2025 11:13:31 -0700 Subject: [PATCH 046/148] Add sprint summary for 2025-12-16 --- sprint-summary-2025-12-16.md | 151 +++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 sprint-summary-2025-12-16.md diff --git a/sprint-summary-2025-12-16.md b/sprint-summary-2025-12-16.md new file mode 100644 index 000000000..7608ac903 --- /dev/null +++ b/sprint-summary-2025-12-16.md @@ -0,0 +1,151 @@ +# Sprint Summary: Dec 03, 2025 - Dec 16, 2025 + +## Overview + +- Completed 8 tasks (18.6%), moving 8 issues to Done and signaling solid sprint momentum. ([#3564](https://github.com/raft-tech/TANF-app/issues/3564), [#5316](https://github.com/raft-tech/TANF-app/issues/5316), [#5407](https://github.com/raft-tech/TANF-app/issues/5407), [#5416](https://github.com/raft-tech/TANF-app/issues/5416), [#5461](https://github.com/raft-tech/TANF-app/issues/5461), [#5476](https://github.com/raft-tech/TANF-app/issues/5476), [#5495](https://github.com/raft-tech/TANF-app/issues/5495), [#5507](https://github.com/raft-tech/TANF-app/issues/5507)) +- All core end-to-end/test fixes closed this sprint, including spinner UI, E2E editing/profile tests, a11y tests, and vault integration removal. ([#3564](https://github.com/raft-tech/TANF-app/issues/3564), [#5316](https://github.com/raft-tech/TANF-app/issues/5316), [#5407](https://github.com/raft-tech/TANF-app/issues/5407), [#5416](https://github.com/raft-tech/TANF-app/issues/5416)) +- Validation and security updates progressed: education level validation for adults (family affiliation 2 or 3) updated; NPM package vulnerabilities investigated and resolved. ([#5461](https://github.com/raft-tech/TANF-app/issues/5461), [#5507](https://github.com/raft-tech/TANF-app/issues/5507)) +- Historical comparison shows progress improving versus prior sprints; no blockers surfaced this sprint, enabling a smoother delivery flow. ([#3564](https://github.com/raft-tech/TANF-app/issues/3564), [#5316](https://github.com/raft-tech/TANF-app/issues/5316), [#5407](https://github.com/raft-tech/TANF-app/issues/5407), [#5416](https://github.com/raft-tech/TANF-app/issues/5416), [#5461](https://github.com/raft-tech/TANF-app/issues/5461), [#5507](https://github.com/raft-tech/TANF-app/issues/5507)) + +--- + +⚪️ **Total Issues:** 43 +✅ **Closed:** 8 +➡️ **Moved:** 0 +⬛️ **Unchanged:** 35 +🛑 **Blocked:** 0 + +--- + +## Issues without Parent + +- ⬛️ [Create email templates for reparsing (new error report & error resolution) (#3263)](https://github.com/raft-tech/TANF-app/issues/3263) +_Remained in **GraphQL Error**_ + +- ⬛️ [Create and facilitate Project Updates meeting for regional staff (#3461)](https://github.com/raft-tech/TANF-app/issues/3461) +_Remained in **GraphQL Error**_ + +- ⬛️ [Create and facilitate optional training session for regional staff (#3462)](https://github.com/raft-tech/TANF-app/issues/3462) +_Remained in **GraphQL Error**_ + +- ⬛️ [Design FRA successful processing email template (#3485)](https://github.com/raft-tech/TANF-app/issues/3485) +_Remained in **GraphQL Error**_ + +- ⬛️ [Bug: OFA system admin users get stuck if region is assigned (#3515)](https://github.com/raft-tech/TANF-app/issues/3515) +_Remained in **GraphQL Error**_ + +- ⬛️ [Refine research plan for regional staff MVP onboarding experience (#3523)](https://github.com/raft-tech/TANF-app/issues/3523) +_Remained in **GraphQL Error**_ + +- ✅ [Implement spinner for TANF / SSP data file submissions (#3564)](https://github.com/raft-tech/TANF-app/issues/3564) +_**Closed**_ - _Moved from **GraphQL Error**_ + +- ⬛️ [Gather and iterate on OFA feedback (#3995)](https://github.com/raft-tech/TANF-app/issues/3995) +_Remained in **GraphQL Error**_ + +- ⬛️ [Gather final OFA feedback and iterate (#4045)](https://github.com/raft-tech/TANF-app/issues/4045) +_Remained in **GraphQL Error**_ + +- ⬛️ [Develop training materials, including slides, written instructions, and screenshots or screen recordings for visual guidance (#4047)](https://github.com/raft-tech/TANF-app/issues/4047) +_Remained in **GraphQL Error**_ + +- ⬛️ [Iterate on training materials internally (#4052)](https://github.com/raft-tech/TANF-app/issues/4052) +_Remained in **GraphQL Error**_ + +- ⬛️ [Plan research for in-app error reporting interface (#4721)](https://github.com/raft-tech/TANF-app/issues/4721) +_Remained in **GraphQL Error**_ + +- ⬛️ [Plan initial research for FTANF replacement (#4989)](https://github.com/raft-tech/TANF-app/issues/4989) +_Remained in **GraphQL Error**_ + +- ⬛️ [Design ideation for post-MVP centralized feedback reports: Plain Language and Interpretability (#5223)](https://github.com/raft-tech/TANF-app/issues/5223) +_Remained in **GraphQL Error**_ + +- ⬛️ [Bump Grafana memory in prod by 4GB to support 1.5M record visualizations (#5269)](https://github.com/raft-tech/TANF-app/issues/5269) +_Remained in **GraphQL Error**_ + +- ⬛️ [Complete In App Error Reports HTML Prototypes (#5300)](https://github.com/raft-tech/TANF-app/issues/5300) +_Remained in **GraphQL Error**_ + +- ✅ [Develop E2E tests for Editing Profile/Access Request (#5316)](https://github.com/raft-tech/TANF-app/issues/5316) +_**Closed**_ - _Moved from **GraphQL Error**_ + +- ⬛️ [Implement FRA Submission Emails (#5359)](https://github.com/raft-tech/TANF-app/issues/5359) +_Remained in **GraphQL Error**_ + +- ⬛️ [[Design Deliverable] Email notification for confirming program integrity audit submissions (#5363)](https://github.com/raft-tech/TANF-app/issues/5363) +_Remained in **GraphQL Error**_ + +- ⬛️ [Create research plan template for UX Playbook (#5369)](https://github.com/raft-tech/TANF-app/issues/5369) +_Remained in **GraphQL Error**_ + +- ⬛️ [Create a Journey Mapping template & document best practices (#5371)](https://github.com/raft-tech/TANF-app/issues/5371) +_Remained in **GraphQL Error**_ + +- ⬛️ [Create a QA Checklist for Completing UX & Dev Reviews (#5374)](https://github.com/raft-tech/TANF-app/issues/5374) +_Remained in **GraphQL Error**_ + +- ⬛️ [Admin Feedback Reports UI (#5390)](https://github.com/raft-tech/TANF-app/issues/5390) +_Remained in **GraphQL Error**_ + +- ⬛️ [Update Knowledge Center with Program Integrity Audit and Centralized Feedback Reports (#5391)](https://github.com/raft-tech/TANF-app/issues/5391) +_Remained in **GraphQL Error**_ + +- ⬛️ [Write KC Content for PI Audit and Centralized Feedback Reports (#5406)](https://github.com/raft-tech/TANF-app/issues/5406) +_Remained in **GraphQL Error**_ + +- ✅ [Enable and fix a11y e2e tests (#5407)](https://github.com/raft-tech/TANF-app/issues/5407) +_**Closed**_ - _Moved from **GraphQL Error**_ + +- ✅ [Delete vault integration from backend (#5416)](https://github.com/raft-tech/TANF-app/issues/5416) +_**Closed**_ - _Moved from **GraphQL Error**_ + +- ⬛️ [STT Feedback Report UI (#5417)](https://github.com/raft-tech/TANF-app/issues/5417) +_Remained in **GraphQL Error**_ + +- ⬛️ [Update Research Protocol for In App Error Reporting Prototype (#5423)](https://github.com/raft-tech/TANF-app/issues/5423) +_Remained in **GraphQL Error**_ + +- ⬛️ [Update Figma with Feedback & Error Report Examples (#5425)](https://github.com/raft-tech/TANF-app/issues/5425) +_Remained in **GraphQL Error**_ + +- ⬛️ [Fully implement Knowledge Center component navigation & search (#5429)](https://github.com/raft-tech/TANF-app/issues/5429) +_Remained in **GraphQL Error**_ + +- ⬛️ [[Tech Memo]: Refactor and cleanup parsing logic (#5434)](https://github.com/raft-tech/TANF-app/issues/5434) +_Remained in **GraphQL Error**_ + +- ⬛️ [Requirements Gathering and User Stories for Post-MVP Centralized Feedback Reports (#5443)](https://github.com/raft-tech/TANF-app/issues/5443) +_Remained in **GraphQL Error**_ + +- ✅ [Update education level validation for adults when family affiliation is 2 or 3 (#5461)](https://github.com/raft-tech/TANF-app/issues/5461) +_**Closed**_ - _Moved from **GraphQL Error**_ + +- ⬛️ [Fix cat 2 validation on SSP Item # 41 (WEI) (#5473)](https://github.com/raft-tech/TANF-app/issues/5473) +_Remained in **GraphQL Error**_ + +- ⬛️ [Update SSN_VALID logic in SQL Views (#5475)](https://github.com/raft-tech/TANF-app/issues/5475) +_Remained in **GraphQL Error**_ + +- ✅ [[Tech Memo]: Feature Toggle (#5476)](https://github.com/raft-tech/TANF-app/issues/5476) +_**Closed**_ - _Moved from **GraphQL Error**_ + +- ⬛️ [Implement Updated email templates for STT data submissions re: status and error guidance (#5478)](https://github.com/raft-tech/TANF-app/issues/5478) +_Remained in **GraphQL Error**_ + +- ⬛️ [[Bug]: Postgres View Does Not Exist (#5483)](https://github.com/raft-tech/TANF-app/issues/5483) +_Remained in **GraphQL Error**_ + +- ⬛️ [[Bug]: Celery Apps not being Monitored (#5494)](https://github.com/raft-tech/TANF-app/issues/5494) +_Remained in **GraphQL Error**_ + +- ✅ [[Bug]: Investigate e2e test failures in `develop` (#5495)](https://github.com/raft-tech/TANF-app/issues/5495) +_**Closed**_ - _Moved from **GraphQL Error**_ + +- ✅ [Investigate and Resolve NPM Package Vulnerabilities (#5507)](https://github.com/raft-tech/TANF-app/issues/5507) +_**Closed**_ - _Moved from **GraphQL Error**_ + +- ⬛️ [Optimize Docker base images to reduce size (#5512)](https://github.com/raft-tech/TANF-app/issues/5512) +_Remained in **GraphQL Error**_ + + From 53db85b227e5a7665f4bde19b5f231cb99ba6f98 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Tue, 23 Dec 2025 13:02:27 -0700 Subject: [PATCH 047/148] Revise sprint summary for Dec 03 - Dec 16, 2025 Updated to correctly organize uncategorized issues --- sprint-summary-2025-12-16.md | 207 ++++++++++++++++++++--------------- 1 file changed, 117 insertions(+), 90 deletions(-) diff --git a/sprint-summary-2025-12-16.md b/sprint-summary-2025-12-16.md index 7608ac903..369f45a5c 100644 --- a/sprint-summary-2025-12-16.md +++ b/sprint-summary-2025-12-16.md @@ -2,150 +2,177 @@ ## Overview -- Completed 8 tasks (18.6%), moving 8 issues to Done and signaling solid sprint momentum. ([#3564](https://github.com/raft-tech/TANF-app/issues/3564), [#5316](https://github.com/raft-tech/TANF-app/issues/5316), [#5407](https://github.com/raft-tech/TANF-app/issues/5407), [#5416](https://github.com/raft-tech/TANF-app/issues/5416), [#5461](https://github.com/raft-tech/TANF-app/issues/5461), [#5476](https://github.com/raft-tech/TANF-app/issues/5476), [#5495](https://github.com/raft-tech/TANF-app/issues/5495), [#5507](https://github.com/raft-tech/TANF-app/issues/5507)) -- All core end-to-end/test fixes closed this sprint, including spinner UI, E2E editing/profile tests, a11y tests, and vault integration removal. ([#3564](https://github.com/raft-tech/TANF-app/issues/3564), [#5316](https://github.com/raft-tech/TANF-app/issues/5316), [#5407](https://github.com/raft-tech/TANF-app/issues/5407), [#5416](https://github.com/raft-tech/TANF-app/issues/5416)) -- Validation and security updates progressed: education level validation for adults (family affiliation 2 or 3) updated; NPM package vulnerabilities investigated and resolved. ([#5461](https://github.com/raft-tech/TANF-app/issues/5461), [#5507](https://github.com/raft-tech/TANF-app/issues/5507)) -- Historical comparison shows progress improving versus prior sprints; no blockers surfaced this sprint, enabling a smoother delivery flow. ([#3564](https://github.com/raft-tech/TANF-app/issues/3564), [#5316](https://github.com/raft-tech/TANF-app/issues/5316), [#5407](https://github.com/raft-tech/TANF-app/issues/5407), [#5416](https://github.com/raft-tech/TANF-app/issues/5416), [#5461](https://github.com/raft-tech/TANF-app/issues/5461), [#5507](https://github.com/raft-tech/TANF-app/issues/5507)) +- Significant closures across core delivery: E2E tests, FRA emails, and PI audit/internal UX outputs completed, moving several items from In Progress to Closed. ([#5316](https://github.com/raft-tech/TANF-app/issues/5316), [#5359](https://github.com/raft-tech/TANF-app/issues/5359), [#5363](https://github.com/raft-tech/TANF-app/issues/5363), [#5406](https://github.com/raft-tech/TANF-app/issues/5406), [#5407](https://github.com/raft-tech/TANF-app/issues/5407), [#5416](https://github.com/raft-tech/TANF-app/issues/5416)) + +- Stability and data integrity improvements advanced with backend fixes and vulnerability remediation: SSN_VALID logic updated, Postgres view issue resolved, NPM vulnerabilities addressed. ([#5475](https://github.com/raft-tech/TANF-app/issues/5475), [#5483](https://github.com/raft-tech/TANF-app/issues/5483), [#5507](https://github.com/raft-tech/TANF-app/issues/5507)) + +- Planning and prep tasks advanced to future sprint boundaries; design deliverables and parsing refactor moved from Current Sprint Backlog to Next Sprint Backlog. ([#5223](https://github.com/raft-tech/TANF-app/issues/5223), [#5434](https://github.com/raft-tech/TANF-app/issues/5434)) + +- Ongoing training materials progress is mixed; some iteration continues, while regional staff onboarding work remains blocked. ([#4047](https://github.com/raft-tech/TANF-app/issues/4047), [#4052](https://github.com/raft-tech/TANF-app/issues/4052), [#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462), [#3523](https://github.com/raft-tech/TANF-app/issues/3523)) --- ⚪️ **Total Issues:** 43 -✅ **Closed:** 8 -➡️ **Moved:** 0 -⬛️ **Unchanged:** 35 -🛑 **Blocked:** 0 +✅ **Closed:** 15 +➡️ **Moved:** 8 +⬛️ **Unchanged:** 12 +🛑 **Blocked:** 8 --- -## Issues without Parent +## [Goal 1: Grantees can easily submit data](https://github.com/raft-tech/TANF-app/issues/5447) -- ⬛️ [Create email templates for reparsing (new error report & error resolution) (#3263)](https://github.com/raft-tech/TANF-app/issues/3263) -_Remained in **GraphQL Error**_ +- ⬛️ [Design FRA successful processing email template (#3485)](https://github.com/raft-tech/TANF-app/issues/3485) +_Remained in **Ready to merge into staging branch**_ -- ⬛️ [Create and facilitate Project Updates meeting for regional staff (#3461)](https://github.com/raft-tech/TANF-app/issues/3461) -_Remained in **GraphQL Error**_ +- ⬛️ [Plan initial research for FTANF replacement (#4989)](https://github.com/raft-tech/TANF-app/issues/4989) +_Remained in **QASP Review**_ -- ⬛️ [Create and facilitate optional training session for regional staff (#3462)](https://github.com/raft-tech/TANF-app/issues/3462) -_Remained in **GraphQL Error**_ +- ➡️ [Design ideation for post-MVP centralized feedback reports: Plain Language and Interpretability (#5223)](https://github.com/raft-tech/TANF-app/issues/5223) +_Moved from **Current Sprint Backlog** to **Next Sprint Backlog**_ -- ⬛️ [Design FRA successful processing email template (#3485)](https://github.com/raft-tech/TANF-app/issues/3485) -_Remained in **GraphQL Error**_ +- ✅ [Implement FRA Submission Emails (#5359)](https://github.com/raft-tech/TANF-app/issues/5359) +_**Closed**_ - _Moved from **In Progress**_ -- ⬛️ [Bug: OFA system admin users get stuck if region is assigned (#3515)](https://github.com/raft-tech/TANF-app/issues/3515) -_Remained in **GraphQL Error**_ +- ✅ [[Design Deliverable] Email notification for confirming program integrity audit submissions (#5363)](https://github.com/raft-tech/TANF-app/issues/5363) +_**Closed**_ - _Moved from **In Progress**_ -- ⬛️ [Refine research plan for regional staff MVP onboarding experience (#3523)](https://github.com/raft-tech/TANF-app/issues/3523) -_Remained in **GraphQL Error**_ +- ✅ [Enable and fix a11y e2e tests (#5407)](https://github.com/raft-tech/TANF-app/issues/5407) +_**Closed**_ - _Moved from **In Progress**_ -- ✅ [Implement spinner for TANF / SSP data file submissions (#3564)](https://github.com/raft-tech/TANF-app/issues/3564) -_**Closed**_ - _Moved from **GraphQL Error**_ +- ✅ [Requirements Gathering and User Stories for Post-MVP Centralized Feedback Reports (#5443)](https://github.com/raft-tech/TANF-app/issues/5443) +_**Closed**_ - _Moved from **QASP Review**_ -- ⬛️ [Gather and iterate on OFA feedback (#3995)](https://github.com/raft-tech/TANF-app/issues/3995) -_Remained in **GraphQL Error**_ -- ⬛️ [Gather final OFA feedback and iterate (#4045)](https://github.com/raft-tech/TANF-app/issues/4045) -_Remained in **GraphQL Error**_ +## [Goal 2: Grantees are confident about reporting compliance](https://github.com/raft-tech/TANF-app/issues/5448) -- ⬛️ [Develop training materials, including slides, written instructions, and screenshots or screen recordings for visual guidance (#4047)](https://github.com/raft-tech/TANF-app/issues/4047) -_Remained in **GraphQL Error**_ +- ⬛️ [Plan research for in-app error reporting interface (#4721)](https://github.com/raft-tech/TANF-app/issues/4721) +_Remained in **QASP Review**_ -- ⬛️ [Iterate on training materials internally (#4052)](https://github.com/raft-tech/TANF-app/issues/4052) -_Remained in **GraphQL Error**_ +- ⬛️ [Complete In App Error Reports HTML Prototypes (#5300)](https://github.com/raft-tech/TANF-app/issues/5300) +_Remained in **QASP Review**_ -- ⬛️ [Plan research for in-app error reporting interface (#4721)](https://github.com/raft-tech/TANF-app/issues/4721) -_Remained in **GraphQL Error**_ +- ⬛️ [Update Research Protocol for In App Error Reporting Prototype (#5423)](https://github.com/raft-tech/TANF-app/issues/5423) +_Remained in **QASP Review**_ -- ⬛️ [Plan initial research for FTANF replacement (#4989)](https://github.com/raft-tech/TANF-app/issues/4989) -_Remained in **GraphQL Error**_ +- ➡️ [Update Figma with Feedback & Error Report Examples (#5425)](https://github.com/raft-tech/TANF-app/issues/5425) +_Moved from **Product Backlog** to **In Progress**_ -- ⬛️ [Design ideation for post-MVP centralized feedback reports: Plain Language and Interpretability (#5223)](https://github.com/raft-tech/TANF-app/issues/5223) -_Remained in **GraphQL Error**_ +- ➡️ [Implement Updated email templates for STT data submissions re: status and error guidance (#5478)](https://github.com/raft-tech/TANF-app/issues/5478) +_Moved from **Product Backlog** to **Raft (Dev) Review**_ -- ⬛️ [Bump Grafana memory in prod by 4GB to support 1.5M record visualizations (#5269)](https://github.com/raft-tech/TANF-app/issues/5269) -_Remained in **GraphQL Error**_ -- ⬛️ [Complete In App Error Reports HTML Prototypes (#5300)](https://github.com/raft-tech/TANF-app/issues/5300) -_Remained in **GraphQL Error**_ +## [Goal 3: Reduce the burden on users](https://github.com/raft-tech/TANF-app/issues/5449) -- ✅ [Develop E2E tests for Editing Profile/Access Request (#5316)](https://github.com/raft-tech/TANF-app/issues/5316) -_**Closed**_ - _Moved from **GraphQL Error**_ +- 🛑 [Bump Grafana memory in prod by 4GB to support 1.5M record visualizations (#5269)](https://github.com/raft-tech/TANF-app/issues/5269) +_Remained in **Blocked**_ -- ⬛️ [Implement FRA Submission Emails (#5359)](https://github.com/raft-tech/TANF-app/issues/5359) -_Remained in **GraphQL Error**_ -- ⬛️ [[Design Deliverable] Email notification for confirming program integrity audit submissions (#5363)](https://github.com/raft-tech/TANF-app/issues/5363) -_Remained in **GraphQL Error**_ +## [Goal 4: Free up staff time](https://github.com/raft-tech/TANF-app/issues/5450) -- ⬛️ [Create research plan template for UX Playbook (#5369)](https://github.com/raft-tech/TANF-app/issues/5369) -_Remained in **GraphQL Error**_ +- 🛑 [Create and facilitate Project Updates meeting for regional staff (#3461)](https://github.com/raft-tech/TANF-app/issues/3461) +_Remained in **Blocked**_ -- ⬛️ [Create a Journey Mapping template & document best practices (#5371)](https://github.com/raft-tech/TANF-app/issues/5371) -_Remained in **GraphQL Error**_ +- 🛑 [Create and facilitate optional training session for regional staff (#3462)](https://github.com/raft-tech/TANF-app/issues/3462) +_Remained in **Blocked**_ -- ⬛️ [Create a QA Checklist for Completing UX & Dev Reviews (#5374)](https://github.com/raft-tech/TANF-app/issues/5374) -_Remained in **GraphQL Error**_ +- 🛑 [Refine research plan for regional staff MVP onboarding experience (#3523)](https://github.com/raft-tech/TANF-app/issues/3523) +_Remained in **Blocked**_ -- ⬛️ [Admin Feedback Reports UI (#5390)](https://github.com/raft-tech/TANF-app/issues/5390) -_Remained in **GraphQL Error**_ +- 🛑 [Gather and iterate on OFA feedback (#3995)](https://github.com/raft-tech/TANF-app/issues/3995) +_Remained in **Blocked**_ -- ⬛️ [Update Knowledge Center with Program Integrity Audit and Centralized Feedback Reports (#5391)](https://github.com/raft-tech/TANF-app/issues/5391) -_Remained in **GraphQL Error**_ +- 🛑 [Gather final OFA feedback and iterate (#4045)](https://github.com/raft-tech/TANF-app/issues/4045) +_Remained in **Blocked**_ -- ⬛️ [Write KC Content for PI Audit and Centralized Feedback Reports (#5406)](https://github.com/raft-tech/TANF-app/issues/5406) -_Remained in **GraphQL Error**_ +- 🛑 [Develop training materials, including slides, written instructions, and screenshots or screen recordings for visual guidance (#4047)](https://github.com/raft-tech/TANF-app/issues/4047) +_Remained in **Blocked**_ -- ✅ [Enable and fix a11y e2e tests (#5407)](https://github.com/raft-tech/TANF-app/issues/5407) -_**Closed**_ - _Moved from **GraphQL Error**_ +- ⬛️ [Iterate on training materials internally (#4052)](https://github.com/raft-tech/TANF-app/issues/4052) +_Remained in **In Progress**_ -- ✅ [Delete vault integration from backend (#5416)](https://github.com/raft-tech/TANF-app/issues/5416) -_**Closed**_ - _Moved from **GraphQL Error**_ +- ✅ [Develop E2E tests for Editing Profile/Access Request (#5316)](https://github.com/raft-tech/TANF-app/issues/5316) +_**Closed**_ - _Moved from **In Progress**_ -- ⬛️ [STT Feedback Report UI (#5417)](https://github.com/raft-tech/TANF-app/issues/5417) -_Remained in **GraphQL Error**_ +- ➡️ [Admin Feedback Reports UI (#5390)](https://github.com/raft-tech/TANF-app/issues/5390) +_Moved from **Raft (Dev) Review** to **UX Review**_ -- ⬛️ [Update Research Protocol for In App Error Reporting Prototype (#5423)](https://github.com/raft-tech/TANF-app/issues/5423) -_Remained in **GraphQL Error**_ +- ➡️ [Update Knowledge Center with Program Integrity Audit and Centralized Feedback Reports (#5391)](https://github.com/raft-tech/TANF-app/issues/5391) +_Moved from **Current Sprint Backlog** to **Next Sprint Backlog**_ -- ⬛️ [Update Figma with Feedback & Error Report Examples (#5425)](https://github.com/raft-tech/TANF-app/issues/5425) -_Remained in **GraphQL Error**_ +- ✅ [Write KC Content for PI Audit and Centralized Feedback Reports (#5406)](https://github.com/raft-tech/TANF-app/issues/5406) +_**Closed**_ - _Moved from **In Progress**_ + +- 🛑 [STT Feedback Report UI (#5417)](https://github.com/raft-tech/TANF-app/issues/5417) +_Moved from **In Progress** to **Blocked**_ - ⬛️ [Fully implement Knowledge Center component navigation & search (#5429)](https://github.com/raft-tech/TANF-app/issues/5429) -_Remained in **GraphQL Error**_ +_Remained in **QASP Review**_ + +- ✅ [[Tech Memo]: Feature Toggle (#5476)](https://github.com/raft-tech/TANF-app/issues/5476) +_**Closed**_ - _Moved from **In Progress**_ -- ⬛️ [[Tech Memo]: Refactor and cleanup parsing logic (#5434)](https://github.com/raft-tech/TANF-app/issues/5434) -_Remained in **GraphQL Error**_ -- ⬛️ [Requirements Gathering and User Stories for Post-MVP Centralized Feedback Reports (#5443)](https://github.com/raft-tech/TANF-app/issues/5443) -_Remained in **GraphQL Error**_ +## [Goal 5: Improve data quality](https://github.com/raft-tech/TANF-app/issues/5451) + +- ➡️ [[Tech Memo]: Refactor and cleanup parsing logic (#5434)](https://github.com/raft-tech/TANF-app/issues/5434) +_Moved from **Current Sprint Backlog** to **Next Sprint Backlog**_ - ✅ [Update education level validation for adults when family affiliation is 2 or 3 (#5461)](https://github.com/raft-tech/TANF-app/issues/5461) -_**Closed**_ - _Moved from **GraphQL Error**_ +_**Closed**_ - _Moved from **Raft (Dev) Review**_ - ⬛️ [Fix cat 2 validation on SSP Item # 41 (WEI) (#5473)](https://github.com/raft-tech/TANF-app/issues/5473) -_Remained in **GraphQL Error**_ +_Remained in **In Progress**_ -- ⬛️ [Update SSN_VALID logic in SQL Views (#5475)](https://github.com/raft-tech/TANF-app/issues/5475) -_Remained in **GraphQL Error**_ +- ✅ [Update SSN_VALID logic in SQL Views (#5475)](https://github.com/raft-tech/TANF-app/issues/5475) +_**Closed**_ - _Moved from **Current Sprint Backlog**_ -- ✅ [[Tech Memo]: Feature Toggle (#5476)](https://github.com/raft-tech/TANF-app/issues/5476) -_**Closed**_ - _Moved from **GraphQL Error**_ -- ⬛️ [Implement Updated email templates for STT data submissions re: status and error guidance (#5478)](https://github.com/raft-tech/TANF-app/issues/5478) -_Remained in **GraphQL Error**_ +## [Goal 6: Documentation is current and helpful](https://github.com/raft-tech/TANF-app/issues/5435) + +- ⬛️ [Create research plan template for UX Playbook (#5369)](https://github.com/raft-tech/TANF-app/issues/5369) +_Remained in **QASP Review**_ + +- ⬛️ [Create a Journey Mapping template & document best practices (#5371)](https://github.com/raft-tech/TANF-app/issues/5371) +_Remained in **QASP Review**_ + +- ⬛️ [Create a QA Checklist for Completing UX & Dev Reviews (#5374)](https://github.com/raft-tech/TANF-app/issues/5374) +_Remained in **QASP Review**_ + -- ⬛️ [[Bug]: Postgres View Does Not Exist (#5483)](https://github.com/raft-tech/TANF-app/issues/5483) -_Remained in **GraphQL Error**_ +## [Bug Reports](https://github.com/raft-tech/TANF-app/issues/4441) -- ⬛️ [[Bug]: Celery Apps not being Monitored (#5494)](https://github.com/raft-tech/TANF-app/issues/5494) -_Remained in **GraphQL Error**_ +- ✅ [Bug: OFA system admin users get stuck if region is assigned (#3515)](https://github.com/raft-tech/TANF-app/issues/3515) +_**Closed**_ - _Moved from **Raft (Dev) Review**_ + +- ✅ [[Bug]: Postgres View Does Not Exist (#5483)](https://github.com/raft-tech/TANF-app/issues/5483) +_**Closed**_ - _Moved from **Current Sprint Backlog**_ + +- ➡️ [[Bug]: Celery Apps not being Monitored (#5494)](https://github.com/raft-tech/TANF-app/issues/5494) +_Moved from **Current Sprint Backlog** to **Next Sprint Backlog**_ - ✅ [[Bug]: Investigate e2e test failures in `develop` (#5495)](https://github.com/raft-tech/TANF-app/issues/5495) -_**Closed**_ - _Moved from **GraphQL Error**_ +_**Closed**_ - _Moved from **Product Backlog**_ + + +## [Operations & Maintenance](https://github.com/raft-tech/TANF-app/issues/4445) + +- ✅ [Delete vault integration from backend (#5416)](https://github.com/raft-tech/TANF-app/issues/5416) +_**Closed**_ - _Moved from **Raft (Dev) Review**_ - ✅ [Investigate and Resolve NPM Package Vulnerabilities (#5507)](https://github.com/raft-tech/TANF-app/issues/5507) -_**Closed**_ - _Moved from **GraphQL Error**_ +_**Closed**_ - _Moved from **Product Backlog**_ -- ⬛️ [Optimize Docker base images to reduce size (#5512)](https://github.com/raft-tech/TANF-app/issues/5512) -_Remained in **GraphQL Error**_ +- ➡️ [Optimize Docker base images to reduce size (#5512)](https://github.com/raft-tech/TANF-app/issues/5512) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + + +## [User Experience Enhancements](https://github.com/raft-tech/TANF-app/issues/4444) + +- ⬛️ [Create email templates for reparsing (new error report & error resolution) (#3263)](https://github.com/raft-tech/TANF-app/issues/3263) +_Remained in **QASP Review**_ + +- ✅ [Implement spinner for TANF / SSP data file submissions (#3564)](https://github.com/raft-tech/TANF-app/issues/3564) +_**Closed**_ - _Moved from **Raft (Dev) Review**_ From fc92b876302db5412f62c716afdbcf69331c0d11 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Wed, 21 Jan 2026 11:20:44 -0700 Subject: [PATCH 048/148] Add sprint summary for 2026-01-20 --- sprint-summary-2026-01-20.md | 181 +++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 sprint-summary-2026-01-20.md diff --git a/sprint-summary-2026-01-20.md b/sprint-summary-2026-01-20.md new file mode 100644 index 000000000..7b6a997ee --- /dev/null +++ b/sprint-summary-2026-01-20.md @@ -0,0 +1,181 @@ +# Sprint Summary: Jan 07, 2026 - Jan 20, 2026 + +## Overview + +- Completed a set of high-impact fixes and templates, moving them to Closed: Design FRA successful processing email template, Admin Feedback Reports UI, and reliability/quality fixes. ([#3485](https://github.com/raft-tech/TANF-app/issues/3485), [#5390](https://github.com/raft-tech/TANF-app/issues/5390), [#5494](https://github.com/raft-tech/TANF-app/issues/5494), [#5512](https://github.com/raft-tech/TANF-app/issues/5512), [#5557](https://github.com/raft-tech/TANF-app/issues/5557), [#5561](https://github.com/raft-tech/TANF-app/issues/5561), [#5577](https://github.com/raft-tech/TANF-app/issues/5577)) + +- Progressed several work items from Backlog/Current Sprint Backlog into In Progress, including E2E tests for Admin Feedback Reports, navigation refactor to React Router, and parsing/refactor tasks. ([#5421](https://github.com/raft-tech/TANF-app/issues/5421), [#5497](https://github.com/raft-tech/TANF-app/issues/5497), [#5477](https://github.com/raft-tech/TANF-app/issues/5477), [#5434](https://github.com/raft-tech/TANF-app/issues/5434), [#5527](https://github.com/raft-tech/TANF-app/issues/5527), [#5525](https://github.com/raft-tech/TANF-app/issues/5525), [#5535](https://github.com/raft-tech/TANF-app/issues/5535)) + +- Blockers remained a risk with 7 items stalled (regional staff training/onboarding, OFA feedback loops, and research planning) to be resolved. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462), [#3523](https://github.com/raft-tech/TANF-app/issues/3523), [#3995](https://github.com/raft-tech/TANF-app/issues/3995), [#4045](https://github.com/raft-tech/TANF-app/issues/4045), [#4047](https://github.com/raft-tech/TANF-app/issues/4047), [#4052](https://github.com/raft-tech/TANF-app/issues/4052)) + +- Sprint momentum: 8 completed, 18 progressed, 7 blocked, reflecting steady throughput despite blockers and strong movement on key initiatives. ([#3485](https://github.com/raft-tech/TANF-app/issues/3485), [#5390](https://github.com/raft-tech/TANF-app/issues/5390), [#5494](https://github.com/raft-tech/TANF-app/issues/5494), [#5512](https://github.com/raft-tech/TANF-app/issues/5512), [#5557](https://github.com/raft-tech/TANF-app/issues/5557), [#5561](https://github.com/raft-tech/TANF-app/issues/5561), [#5577](https://github.com/raft-tech/TANF-app/issues/5577), [#5421](https://github.com/raft-tech/TANF-app/issues/5421), [#5497](https://github.com/raft-tech/TANF-app/issues/5497), [#5477](https://github.com/raft-tech/TANF-app/issues/5477), [#5434](https://github.com/raft-tech/TANF-app/issues/5434)) + +--- + +⚪️ **Total Issues:** 43 +✅ **Closed:** 8 +➡️ **Moved:** 18 +⬛️ **Unchanged:** 10 +🛑 **Blocked:** 7 + +--- + +## [Goal 1: Grantees can easily submit data](https://github.com/raft-tech/TANF-app/issues/5447) + +- ✅ [Design FRA successful processing email template (#3485)](https://github.com/raft-tech/TANF-app/issues/3485) +_**Closed**_ - _Moved from **Ready to merge into staging branch**_ + +- ⬛️ [Plan initial research for FTANF replacement (#4989)](https://github.com/raft-tech/TANF-app/issues/4989) +_Remained in **QASP Review**_ + +- ⬛️ [Design ideation for post-MVP centralized feedback reports: Plain Language and Interpretability (#5223)](https://github.com/raft-tech/TANF-app/issues/5223) +_Remained in **Current Sprint Backlog**_ + + +## [Goal 2: Grantees are confident about reporting compliance](https://github.com/raft-tech/TANF-app/issues/5448) + +- ✅ [Plan research for in-app error reporting interface (#4721)](https://github.com/raft-tech/TANF-app/issues/4721) +_**Closed**_ - _Moved from **QASP Review**_ + +- ⬛️ [Complete In App Error Reports HTML Prototypes (#5300)](https://github.com/raft-tech/TANF-app/issues/5300) +_Remained in **QASP Review**_ + +- ➡️ [Update Research Protocol for In App Error Reporting Prototype (#5423)](https://github.com/raft-tech/TANF-app/issues/5423) +_Moved from **QASP Review** to **In Progress**_ + +- ➡️ [Update Figma with Feedback & Error Report Examples (#5425)](https://github.com/raft-tech/TANF-app/issues/5425) +_Moved from **In Progress** to **Current Sprint Backlog**_ + +- ➡️ [E2E Test for Same FY/Q/STT Across Program Types (#5477)](https://github.com/raft-tech/TANF-app/issues/5477) +_Moved from **Current Sprint Backlog** to **In Progress**_ + +- ⬛️ [Implement Updated email templates for STT data submissions re: status and error guidance (#5478)](https://github.com/raft-tech/TANF-app/issues/5478) +_Remained in **Raft (Dev) Review**_ + +- ✅ [Update error message to new text (#5561)](https://github.com/raft-tech/TANF-app/issues/5561) +_**Closed**_ - _Moved from **In Progress**_ + + +## [Goal 3: Reduce the burden on users](https://github.com/raft-tech/TANF-app/issues/5449) + +- 🛑 [Bump Grafana memory in prod by 4GB to support 1.5M record visualizations (#5269)](https://github.com/raft-tech/TANF-app/issues/5269) +_Remained in **Blocked**_ + + +## [Goal 4: Free up staff time](https://github.com/raft-tech/TANF-app/issues/5450) + +- 🛑 [Create and facilitate Project Updates meeting for regional staff (#3461)](https://github.com/raft-tech/TANF-app/issues/3461) +_Remained in **Blocked**_ + +- 🛑 [Create and facilitate optional training session for regional staff (#3462)](https://github.com/raft-tech/TANF-app/issues/3462) +_Remained in **Blocked**_ + +- 🛑 [Refine research plan for regional staff MVP onboarding experience (#3523)](https://github.com/raft-tech/TANF-app/issues/3523) +_Remained in **Blocked**_ + +- 🛑 [Gather and iterate on OFA feedback (#3995)](https://github.com/raft-tech/TANF-app/issues/3995) +_Remained in **Blocked**_ + +- 🛑 [Gather final OFA feedback and iterate (#4045)](https://github.com/raft-tech/TANF-app/issues/4045) +_Remained in **Blocked**_ + +- 🛑 [Develop training materials, including slides, written instructions, and screenshots or screen recordings for visual guidance (#4047)](https://github.com/raft-tech/TANF-app/issues/4047) +_Remained in **Blocked**_ + +- ⬛️ [Iterate on training materials internally (#4052)](https://github.com/raft-tech/TANF-app/issues/4052) +_Remained in **In Progress**_ + +- ✅ [Admin Feedback Reports UI (#5390)](https://github.com/raft-tech/TANF-app/issues/5390) +_**Closed**_ - _Moved from **UX Review**_ + +- ➡️ [STT Feedback Report UI (#5417)](https://github.com/raft-tech/TANF-app/issues/5417) +_Moved from **Raft (Dev) Review** to **UX Review**_ + +- ⬛️ [Fully implement Knowledge Center component navigation & search (#5429)](https://github.com/raft-tech/TANF-app/issues/5429) +_Remained in **QASP Review**_ + +- ➡️ [FeatureFlag Model and Admin Interface (#5531)](https://github.com/raft-tech/TANF-app/issues/5531) +_Moved from **Next Sprint Backlog** to **In Progress**_ + +- ➡️ [Cache Service Implementation (#5532)](https://github.com/raft-tech/TANF-app/issues/5532) +_Moved from **Next Sprint Backlog** to **Current Sprint Backlog**_ + +- ➡️ [Upgrade Backend Dependencies (#5535)](https://github.com/raft-tech/TANF-app/issues/5535) +_Moved from **Current Sprint Backlog** to **In Progress**_ + + +## [Goal 5: Improve data quality](https://github.com/raft-tech/TANF-app/issues/5451) + +- ➡️ [[Tech Memo]: Refactor and cleanup parsing logic (#5434)](https://github.com/raft-tech/TANF-app/issues/5434) +_Moved from **Current Sprint Backlog** to **In Progress**_ + + +## [Goal 6: Documentation is current and helpful](https://github.com/raft-tech/TANF-app/issues/5435) + +- ⬛️ [Create research plan template for UX Playbook (#5369)](https://github.com/raft-tech/TANF-app/issues/5369) +_Remained in **QASP Review**_ + +- ⬛️ [Create a Journey Mapping template & document best practices (#5371)](https://github.com/raft-tech/TANF-app/issues/5371) +_Remained in **QASP Review**_ + +- ⬛️ [Create a QA Checklist for Completing UX & Dev Reviews (#5374)](https://github.com/raft-tech/TANF-app/issues/5374) +_Remained in **QASP Review**_ + + +## [Bug Reports](https://github.com/raft-tech/TANF-app/issues/4441) + +- ✅ [[Bug]: Celery Apps not being Monitored (#5494)](https://github.com/raft-tech/TANF-app/issues/5494) +_**Closed**_ - _Moved from **In Progress**_ + + +## [Operations & Maintenance](https://github.com/raft-tech/TANF-app/issues/4445) + +- ✅ [Optimize Docker base images to reduce size (#5512)](https://github.com/raft-tech/TANF-app/issues/5512) +_**Closed**_ - _Moved from **In Progress**_ + + +## [User Experience Enhancements](https://github.com/raft-tech/TANF-app/issues/4444) + +- ⬛️ [Create email templates for reparsing (new error report & error resolution) (#3263)](https://github.com/raft-tech/TANF-app/issues/3263) +_Remained in **QASP Review**_ + + +## Issues without Parent + +- ➡️ [Implement E2E Tests for Admin Feedback Reports (#5421)](https://github.com/raft-tech/TANF-app/issues/5421) +_Moved from **Current Sprint Backlog** to **In Progress**_ + +- ➡️ [Transition NavItem component to React Router navigation (#5497)](https://github.com/raft-tech/TANF-app/issues/5497) +_Moved from **Current Sprint Backlog** to **In Progress**_ + +- ➡️ [Rhode Island should be able to submit SSP data via TDP starting in FY2026 (#5521)](https://github.com/raft-tech/TANF-app/issues/5521) +_Moved from **Current Sprint Backlog** to **In Progress**_ + +- ➡️ [Re-start submission status polling on page navigation/refresh (#5525)](https://github.com/raft-tech/TANF-app/issues/5525) +_Moved from **Next Sprint Backlog** to **In Progress**_ + +- ➡️ [Upload Panel State Reset on Accepted Files (#5527)](https://github.com/raft-tech/TANF-app/issues/5527) +_Moved from **Current Sprint Backlog** to **In Progress**_ + +- ✅ [SSN_VALID Not Handling Non-Numeric Values (#5557)](https://github.com/raft-tech/TANF-app/issues/5557) +_**Closed**_ - _Moved from **In Progress**_ + +- ✅ [Investigate and resolve transient E2E test failures on develop (#5577)](https://github.com/raft-tech/TANF-app/issues/5577) +_**Closed**_ - _Moved from **Current Sprint Backlog**_ + +- ➡️ [Bug: TANF datafile feedback attachments not included due to stale Redux state (#5582)](https://github.com/raft-tech/TANF-app/issues/5582) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + +- ➡️ ["Parsing Complete" banner interrupts screenreader reading of "Successfully Submitted" (#5584)](https://github.com/raft-tech/TANF-app/issues/5584) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + +- ➡️ [Spike: Explore Rapid Research Methodologies to Capture Ongoing User Feedback (#5588)](https://github.com/raft-tech/TANF-app/issues/5588) +_Moved from **Product Backlog** to **In Progress**_ + +- ➡️ [Inconsistent File Status (#5591)](https://github.com/raft-tech/TANF-app/issues/5591) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + +- ➡️ [File Download Links Missing in DAC (#5592)](https://github.com/raft-tech/TANF-app/issues/5592) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + + From e80b8217d52cc959a4ac4cec2509ee739d99b8e5 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Wed, 3 Dec 2025 11:12:24 -0700 Subject: [PATCH 049/148] Add sprint summary for 2025-12-02 --- sprint-summary-2025-12-02.md | 212 +++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 sprint-summary-2025-12-02.md diff --git a/sprint-summary-2025-12-02.md b/sprint-summary-2025-12-02.md new file mode 100644 index 000000000..6c28d6ee3 --- /dev/null +++ b/sprint-summary-2025-12-02.md @@ -0,0 +1,212 @@ +# Sprint Summary: Nov 19, 2025 - Dec 02, 2025 + +## Overview + +- Implemented user deletion support in DAC with retention of associated objects. ([#3089](https://github.com/raft-tech/TANF-app/issues/3089)) + +- Fixed and closed validation on User STT and Region changes in user groups, removing a key blocker. ([#5368](https://github.com/raft-tech/TANF-app/issues/5368)) + +- Completed Feedback module change to submit when a score is selected, improving the submission UX. ([#5431](https://github.com/raft-tech/TANF-app/issues/5431)) + +- Updated STT email templates to provide clearer status and error guidance. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251)) + +- Added alert banner to Knowledge Center regarding government shutdown, ensuring awareness. ([#5398](https://github.com/raft-tech/TANF-app/issues/5398)) + +- Merged fix for a merge conflict that re-merged deleted code, stabilizing the codebase. ([#5489](https://github.com/raft-tech/TANF-app/issues/5489)) + +--- + +⚪️ **Total Issues:** 53 +✅ **Closed:** 6 +➡️ **Moved:** 15 +⬛️ **Unchanged:** 25 +🛑 **Blocked:** 7 + +--- + +## [Goal 1: Grantees can easily submit data](https://github.com/raft-tech/TANF-app/issues/5447) + +- ⬛️ [Design FRA successful processing email template (#3485)](https://github.com/raft-tech/TANF-app/issues/3485) +_Remained in **Ready to merge into staging branch**_ + +- ⬛️ [Update minimum year in FRA reporting dropdown (#3500)](https://github.com/raft-tech/TANF-app/issues/3500) +_Remained in **Next Sprint Backlog**_ + +- ⬛️ [Plan initial research for FTANF replacement (#4989)](https://github.com/raft-tech/TANF-app/issues/4989) +_Remained in **QASP Review**_ + +- ⬛️ [Design ideation for post-MVP centralized feedback reports: Plain Language and Interpretability (#5223)](https://github.com/raft-tech/TANF-app/issues/5223) +_Remained in **Current Sprint Backlog**_ + +- ➡️ [Implement FRA Submission Emails (#5359)](https://github.com/raft-tech/TANF-app/issues/5359) +_Moved from **Current Sprint Backlog** to **In Progress**_ + +- ➡️ [[Design Deliverable] Email notification for confirming program integrity audit submissions (#5363)](https://github.com/raft-tech/TANF-app/issues/5363) +_Moved from **QASP Review** to **In Progress**_ + +- ⬛️ [Enable and fix a11y e2e tests (#5407)](https://github.com/raft-tech/TANF-app/issues/5407) +_Remained in **In Progress**_ + +- ⬛️ [Implement E2E Tests for Program Integrity Audit Frontend (#5420)](https://github.com/raft-tech/TANF-app/issues/5420) +_Remained in **Next Sprint Backlog**_ + +- ➡️ [Requirements Gathering and User Stories for Post-MVP Centralized Feedback Reports (#5443)](https://github.com/raft-tech/TANF-app/issues/5443) +_Moved from **UX Review** to **QASP Review**_ + + +## [Goal 2: Grantees are confident about reporting compliance](https://github.com/raft-tech/TANF-app/issues/5448) + +- ⬛️ [Plan research for in-app error reporting interface (#4721)](https://github.com/raft-tech/TANF-app/issues/4721) +_Remained in **QASP Review**_ + +- ⬛️ [Complete In App Error Reports HTML Prototypes (#5300)](https://github.com/raft-tech/TANF-app/issues/5300) +_Remained in **QASP Review**_ + +- ⬛️ [Update Research Protocol for In App Error Reporting Prototype (#5423)](https://github.com/raft-tech/TANF-app/issues/5423) +_Remained in **QASP Review**_ + + +## [Goal 3: Reduce the burden on users](https://github.com/raft-tech/TANF-app/issues/5449) + +- 🛑 [Bump Grafana memory in prod by 4GB to support 1.5M record visualizations (#5269)](https://github.com/raft-tech/TANF-app/issues/5269) +_Remained in **Blocked**_ + +- ✅ [Feedback module - submit when score is selected (rather than when "submit" is clicked) (#5431)](https://github.com/raft-tech/TANF-app/issues/5431) +_**Closed**_ - _Moved from **In Progress**_ + + +## [Goal 4: Free up staff time](https://github.com/raft-tech/TANF-app/issues/5450) + +- 🛑 [Create and facilitate Project Updates meeting for regional staff (#3461)](https://github.com/raft-tech/TANF-app/issues/3461) +_Remained in **Blocked**_ + +- 🛑 [Create and facilitate optional training session for regional staff (#3462)](https://github.com/raft-tech/TANF-app/issues/3462) +_Remained in **Blocked**_ + +- 🛑 [Refine research plan for regional staff MVP onboarding experience (#3523)](https://github.com/raft-tech/TANF-app/issues/3523) +_Remained in **Blocked**_ + +- 🛑 [Gather and iterate on OFA feedback (#3995)](https://github.com/raft-tech/TANF-app/issues/3995) +_Remained in **Blocked**_ + +- 🛑 [Gather final OFA feedback and iterate (#4045)](https://github.com/raft-tech/TANF-app/issues/4045) +_Remained in **Blocked**_ + +- 🛑 [Develop training materials, including slides, written instructions, and screenshots or screen recordings for visual guidance (#4047)](https://github.com/raft-tech/TANF-app/issues/4047) +_Remained in **Blocked**_ + +- ⬛️ [Iterate on training materials internally (#4052)](https://github.com/raft-tech/TANF-app/issues/4052) +_Remained in **In Progress**_ + +- ➡️ [Develop E2E tests for Editing Profile/Access Request (#5316)](https://github.com/raft-tech/TANF-app/issues/5316) +_Moved from **Current Sprint Backlog** to **In Progress**_ + +- ➡️ [Admin Feedback Reports UI (#5390)](https://github.com/raft-tech/TANF-app/issues/5390) +_Moved from **In Progress** to **Raft (Dev) Review**_ + +- ⬛️ [Update Knowledge Center with Program Integrity Audit and Centralized Feedback Reports (#5391)](https://github.com/raft-tech/TANF-app/issues/5391) +_Remained in **Current Sprint Backlog**_ + +- ✅ [Add alert banner to knowledge center regarding government shutdown (#5398)](https://github.com/raft-tech/TANF-app/issues/5398) +_**Closed**_ - _Moved from **Blocked**_ + +- ➡️ [Write KC Content for PI Audit and Centralized Feedback Reports (#5406)](https://github.com/raft-tech/TANF-app/issues/5406) +_Moved from **QASP Review** to **UX Review**_ + +- ➡️ [STT Feedback Report UI (#5417)](https://github.com/raft-tech/TANF-app/issues/5417) +_Moved from **Current Sprint Backlog** to **In Progress**_ + +- ➡️ [Fully implement Knowledge Center component navigation & search (#5429)](https://github.com/raft-tech/TANF-app/issues/5429) +_Moved from **UX Review** to **QASP Review**_ + + +## [Goal 6: Documentation is current and helpful](https://github.com/raft-tech/TANF-app/issues/5435) + +- ⬛️ [Create research plan template for UX Playbook (#5369)](https://github.com/raft-tech/TANF-app/issues/5369) +_Remained in **QASP Review**_ + +- ⬛️ [Create a Journey Mapping template & document best practices (#5371)](https://github.com/raft-tech/TANF-app/issues/5371) +_Remained in **QASP Review**_ + +- ⬛️ [Create a QA Checklist for Completing UX & Dev Reviews (#5374)](https://github.com/raft-tech/TANF-app/issues/5374) +_Remained in **QASP Review**_ + +- ⬛️ [Create a guide for Conducting a Heuristic Evaluation (Checklist + Best Practices) (#5375)](https://github.com/raft-tech/TANF-app/issues/5375) +_Remained in **Next Sprint Backlog**_ + + +## [Bug Reports](https://github.com/raft-tech/TANF-app/issues/4441) + +- ⬛️ [Bug: OFA system admin users get stuck if region is assigned (#3515)](https://github.com/raft-tech/TANF-app/issues/3515) +_Remained in **Raft (Dev) Review**_ + +- ✅ [[BUG] User STT and Region assignment validation on user group change (#5368)](https://github.com/raft-tech/TANF-app/issues/5368) +_**Closed**_ - _Moved from **Raft (Dev) Review**_ + + +## [Operations & Maintenance](https://github.com/raft-tech/TANF-app/issues/4445) + +- ⬛️ [Switch to React Router links for internal navigation (#2008)](https://github.com/raft-tech/TANF-app/issues/2008) +_Remained in **Next Sprint Backlog**_ + +- ✅ [Implement user deletion support in DAC while retaining associated objects (#3089)](https://github.com/raft-tech/TANF-app/issues/3089) +_**Closed**_ - _Moved from **In Progress**_ + +- ⬛️ [Add bulk user deactivation and inactivity filter for system admins (#3090)](https://github.com/raft-tech/TANF-app/issues/3090) +_Remained in **Next Sprint Backlog**_ + +- ⬛️ [Fix health dashboard to respect environment filters (#3611)](https://github.com/raft-tech/TANF-app/issues/3611) +_Remained in **Next Sprint Backlog**_ + +- ⬛️ [Reorganize the parser (#5414)](https://github.com/raft-tech/TANF-app/issues/5414) +_Remained in **Next Sprint Backlog**_ + +- ➡️ [Delete vault integration from backend (#5416)](https://github.com/raft-tech/TANF-app/issues/5416) +_Moved from **Current Sprint Backlog** to **In Progress**_ + + +## [User Experience Enhancements](https://github.com/raft-tech/TANF-app/issues/4444) + +- ⬛️ [Implement automated past due submission email notifications (#2984)](https://github.com/raft-tech/TANF-app/issues/2984) +_Remained in **Next Sprint Backlog**_ + +- ✅ [Update email templates for STT data submissions re: status and error guidance (#3251)](https://github.com/raft-tech/TANF-app/issues/3251) +_**Closed**_ - _Moved from **QASP Review**_ + +- ⬛️ [Create email templates for reparsing (new error report & error resolution) (#3263)](https://github.com/raft-tech/TANF-app/issues/3263) +_Remained in **QASP Review**_ + +- ➡️ [Implement spinner for TANF / SSP data file submissions (#3564)](https://github.com/raft-tech/TANF-app/issues/3564) +_Moved from **In Progress** to **Raft (Dev) Review**_ + + +## Issues without Parent + +- ⬛️ [Implement E2E Tests for Admin Feedback Reports (#5421)](https://github.com/raft-tech/TANF-app/issues/5421) +_Remained in **Next Sprint Backlog**_ + +- ⬛️ [Refactor and cleanup parsing logic tech memo (#5434)](https://github.com/raft-tech/TANF-app/issues/5434) +_Remained in **Current Sprint Backlog**_ + +- ➡️ [Update education level validation for adults when family affiliation is 2 or 3 (#5461)](https://github.com/raft-tech/TANF-app/issues/5461) +_Moved from **Current Sprint Backlog** to **Raft (Dev) Review**_ + +- ➡️ [Fix cat 2 validation on SSP Item # 41 (WEI) (#5473)](https://github.com/raft-tech/TANF-app/issues/5473) +_Moved from **Current Sprint Backlog** to **In Progress**_ + +- ⬛️ [Update SSN_VALID logic in SQL Views (#5475)](https://github.com/raft-tech/TANF-app/issues/5475) +_Remained in **Current Sprint Backlog**_ + +- ➡️ [Feature Toggle Tech Memo (#5476)](https://github.com/raft-tech/TANF-app/issues/5476) +_Moved from **Current Sprint Backlog** to **In Progress**_ + +- ➡️ [Postgres View Does Not Exist (#5483)](https://github.com/raft-tech/TANF-app/issues/5483) +_Moved from **Product Backlog** to **Current Sprint Backlog**_ + +- ✅ [BUG : merge conflict caused some deleted code to be re-merged (#5489)](https://github.com/raft-tech/TANF-app/issues/5489) +_**Closed**_ - _Moved from **Product Backlog**_ + +- ➡️ [[Bug]: Celery Apps not being Monitored (#5494)](https://github.com/raft-tech/TANF-app/issues/5494) +_Moved from **Product Backlog** to **Current Sprint Backlog**_ + + From d1c89203c13c2ad3b5c56f6dd755de230df72f11 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Thu, 4 Dec 2025 14:28:24 -0700 Subject: [PATCH 050/148] Edit to sprint overview about the alert banner module --- sprint-summary-2025-12-02.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sprint-summary-2025-12-02.md b/sprint-summary-2025-12-02.md index 6c28d6ee3..126268a98 100644 --- a/sprint-summary-2025-12-02.md +++ b/sprint-summary-2025-12-02.md @@ -10,7 +10,7 @@ - Updated STT email templates to provide clearer status and error guidance. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251)) -- Added alert banner to Knowledge Center regarding government shutdown, ensuring awareness. ([#5398](https://github.com/raft-tech/TANF-app/issues/5398)) +- Added alert banner module to Knowledge Center codebase and saved for use in potential future scenarios. It was originally intended to notify users about the effects of the government shutdown, but it wasn't used for that after all. ([#5398](https://github.com/raft-tech/TANF-app/issues/5398)) - Merged fix for a merge conflict that re-merged deleted code, stabilizing the codebase. ([#5489](https://github.com/raft-tech/TANF-app/issues/5489)) From cfedf54b485758bb0cd7795fafa33aef1ab9bea0 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 21 Nov 2025 14:45:46 -0700 Subject: [PATCH 051/148] Add sprint summary for 2025-11-18 --- sprint-summary-2025-11-18.md | 225 +++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 sprint-summary-2025-11-18.md diff --git a/sprint-summary-2025-11-18.md b/sprint-summary-2025-11-18.md new file mode 100644 index 000000000..274639ed0 --- /dev/null +++ b/sprint-summary-2025-11-18.md @@ -0,0 +1,225 @@ +# Sprint Summary: Nov 05, 2025 - Nov 18, 2025 + +## Overview + +- Overall sprint momentum: completed 12 issues, progressed 19, blocked 7; 20.3% done, showing steady forward movement despite blockers. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#4924](https://github.com/raft-tech/TANF-app/issues/4924), [#5368](https://github.com/raft-tech/TANF-app/issues/5368), [#5397](https://github.com/raft-tech/TANF-app/issues/5397), [#5398](https://github.com/raft-tech/TANF-app/issues/5398)) + +- Backlog and sequencing adjusted: FRA tasks moved toward Next Sprint Backlog and related design advanced toward staging. ([#5359](https://github.com/raft-tech/TANF-app/issues/5359), [#3485](https://github.com/raft-tech/TANF-app/issues/3485)) + +- Regional staff blockers identified as risk: several items remained Blocked, delaying regional onboarding and training efforts. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462), [#3523](https://github.com/raft-tech/TANF-app/issues/3523), [#3995](https://github.com/raft-tech/TANF-app/issues/3995), [#4047](https://github.com/raft-tech/TANF-app/issues/4047)) + +- QA/E2E progress: accessibility tests enabled; E2E test work for Admin Feedback Reports and Program Integrity audits moved forward. ([#5407](https://github.com/raft-tech/TANF-app/issues/5407), [#5420](https://github.com/raft-tech/TANF-app/issues/5420), [#5421](https://github.com/raft-tech/TANF-app/issues/5421)) + +- Knowledge Center/Admin UI progress: closures and readiness for release advanced, including KC NavBar, Refresh Tolerant Data Pages, FRA email, and read-status changes. ([#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5418](https://github.com/raft-tech/TANF-app/issues/5418), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469), [#5432](https://github.com/raft-tech/TANF-app/issues/5432)) + +--- + +⚪️ **Total Issues:** 59 +✅ **Closed:** 12 +➡️ **Moved:** 19 +⬛️ **Unchanged:** 21 +🛑 **Blocked:** 7 + +--- + +## [Goal 1: Grantees can easily submit data](https://github.com/raft-tech/TANF-app/issues/5447) + +- ⬛️ [Design FRA successful processing email template (#3485)](https://github.com/raft-tech/TANF-app/issues/3485) +_Remained in **Ready to merge into staging branch**_ + +- ⬛️ [Update minimum year in FRA reporting dropdown (#3500)](https://github.com/raft-tech/TANF-app/issues/3500) +_Remained in **Next Sprint Backlog**_ + +- ⬛️ [Plan initial research for FTANF replacement (#4989)](https://github.com/raft-tech/TANF-app/issues/4989) +_Remained in **QASP Review**_ + +- ➡️ [Design ideation for post-MVP centralized feedback reports: Plain Language and Interpretability (#5223)](https://github.com/raft-tech/TANF-app/issues/5223) +_Moved from **In Progress** to **Current Sprint Backlog**_ + +- ➡️ [Implement FRA Submission Emails (#5359)](https://github.com/raft-tech/TANF-app/issues/5359) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + +- ⬛️ [[Design Deliverable] Email notification for confirming program integrity audit submissions (#5363)](https://github.com/raft-tech/TANF-app/issues/5363) +_Remained in **QASP Review**_ + +- ⬛️ [Enable and fix a11y e2e tests (#5407)](https://github.com/raft-tech/TANF-app/issues/5407) +_Remained in **In Progress**_ + +- ➡️ [Implement E2E Tests for Program Integrity Audit Frontend (#5420)](https://github.com/raft-tech/TANF-app/issues/5420) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + +- ➡️ [Requirements Gathering and User Stories for Post-MVP Centralized Feedback Reports (#5443)](https://github.com/raft-tech/TANF-app/issues/5443) +_Moved from **In Progress** to **UX Review**_ + + +## [Goal 2: Grantees are confident about reporting compliance](https://github.com/raft-tech/TANF-app/issues/5448) + +- ⬛️ [Plan research for in-app error reporting interface (#4721)](https://github.com/raft-tech/TANF-app/issues/4721) +_Remained in **QASP Review**_ + +- ⬛️ [Complete In App Error Reports HTML Prototypes (#5300)](https://github.com/raft-tech/TANF-app/issues/5300) +_Remained in **QASP Review**_ + +- ➡️ [Update Research Protocol for In App Error Reporting Prototype (#5423)](https://github.com/raft-tech/TANF-app/issues/5423) +_Moved from **UX Review** to **QASP Review**_ + + +## [Goal 3: Reduce the burden on users](https://github.com/raft-tech/TANF-app/issues/5449) + +- 🛑 [Bump Grafana memory in prod by 4GB to support 1.5M record visualizations (#5269)](https://github.com/raft-tech/TANF-app/issues/5269) +_Remained in **Blocked**_ + +- ➡️ [Feedback module - submit when score is selected (rather than when "submit" is clicked) (#5431)](https://github.com/raft-tech/TANF-app/issues/5431) +_Moved from **Current Sprint Backlog** to **In Progress**_ + + +## [Goal 4: Free up staff time](https://github.com/raft-tech/TANF-app/issues/5450) + +- ✅ [Migrate Promtail to Alloy (#3258)](https://github.com/raft-tech/TANF-app/issues/3258) +_**Closed**_ - _Moved from **Product Backlog**_ + +- 🛑 [Create and facilitate Project Updates meeting for regional staff (#3461)](https://github.com/raft-tech/TANF-app/issues/3461) +_Remained in **Blocked**_ + +- 🛑 [Create and facilitate optional training session for regional staff (#3462)](https://github.com/raft-tech/TANF-app/issues/3462) +_Remained in **Blocked**_ + +- 🛑 [Refine research plan for regional staff MVP onboarding experience (#3523)](https://github.com/raft-tech/TANF-app/issues/3523) +_Remained in **Blocked**_ + +- ✅ [Restrict regional staff FRA submission history to participating STTs (#3571)](https://github.com/raft-tech/TANF-app/issues/3571) +_**Closed**_ - _Moved from **Raft (Dev) Review**_ + +- 🛑 [Gather and iterate on OFA feedback (#3995)](https://github.com/raft-tech/TANF-app/issues/3995) +_Remained in **Blocked**_ + +- 🛑 [Gather final OFA feedback and iterate (#4045)](https://github.com/raft-tech/TANF-app/issues/4045) +_Remained in **Blocked**_ + +- 🛑 [Develop training materials, including slides, written instructions, and screenshots or screen recordings for visual guidance (#4047)](https://github.com/raft-tech/TANF-app/issues/4047) +_Remained in **Blocked**_ + +- ⬛️ [Iterate on training materials internally (#4052)](https://github.com/raft-tech/TANF-app/issues/4052) +_Remained in **In Progress**_ + +- ➡️ [Develop E2E tests for Editing Profile/Access Request (#5316)](https://github.com/raft-tech/TANF-app/issues/5316) +_Moved from **Next Sprint Backlog** to **Current Sprint Backlog**_ + +- ⬛️ [Admin Feedback Reports UI (#5390)](https://github.com/raft-tech/TANF-app/issues/5390) +_Remained in **In Progress**_ + +- ➡️ [Update Knowledge Center with Program Integrity Audit and Centralized Feedback Reports (#5391)](https://github.com/raft-tech/TANF-app/issues/5391) +_Moved from **Next Sprint Backlog** to **Current Sprint Backlog**_ + +- ✅ [Feedback Reports Backend (#5397)](https://github.com/raft-tech/TANF-app/issues/5397) +_**Closed**_ - _Moved from **Raft (Dev) Review**_ + +- ✅ [Add alert banner to knowledge center regarding government shutdown (#5398)](https://github.com/raft-tech/TANF-app/issues/5398) +_**Closed**_ - _Moved from **Blocked**_ + +- ➡️ [Write KC Content for PI Audit and Centralized Feedback Reports (#5406)](https://github.com/raft-tech/TANF-app/issues/5406) +_Moved from **UX Review** to **QASP Review**_ + +- ➡️ [STT Feedback Report UI (#5417)](https://github.com/raft-tech/TANF-app/issues/5417) +_Moved from **Product Backlog** to **Current Sprint Backlog**_ + +- ✅ [Refresh Tolerant Data Files Pages (#5418)](https://github.com/raft-tech/TANF-app/issues/5418) +_**Closed**_ - _Moved from **Raft (Dev) Review**_ + +- ✅ [KC NavBar Item (#5428)](https://github.com/raft-tech/TANF-app/issues/5428) +_**Closed**_ - _Moved from **In Progress**_ + +- ➡️ [Fully implement Knowledge Center component navigation & search (#5429)](https://github.com/raft-tech/TANF-app/issues/5429) +_Moved from **In Progress** to **UX Review**_ + + +## [Bug Reports](https://github.com/raft-tech/TANF-app/issues/4441) + +- ⬛️ [Bug: OFA system admin users get stuck if region is assigned (#3515)](https://github.com/raft-tech/TANF-app/issues/3515) +_Remained in **Raft (Dev) Review**_ + +- ✅ [Bug: Regional Access Request – Pre-selected option & typo (#4924)](https://github.com/raft-tech/TANF-app/issues/4924) +_**Closed**_ - _Moved from **Current Sprint Backlog**_ + +- ✅ [[BUG] User STT and Region assignment validation on user group change (#5368)](https://github.com/raft-tech/TANF-app/issues/5368) +_**Closed**_ - _Moved from **Raft (Dev) Review**_ + + +## [Operations & Maintenance](https://github.com/raft-tech/TANF-app/issues/4445) + +- ⬛️ [Switch to React Router links for internal navigation (#2008)](https://github.com/raft-tech/TANF-app/issues/2008) +_Remained in **Next Sprint Backlog**_ + +- ⬛️ [Implement user deletion support in DAC while retaining associated objects (#3089)](https://github.com/raft-tech/TANF-app/issues/3089) +_Remained in **In Progress**_ + +- ➡️ [Add bulk user deactivation and inactivity filter for system admins (#3090)](https://github.com/raft-tech/TANF-app/issues/3090) +_Moved from **Current Sprint Backlog** to **Next Sprint Backlog**_ + +- ⬛️ [Fix health dashboard to respect environment filters (#3611)](https://github.com/raft-tech/TANF-app/issues/3611) +_Remained in **Next Sprint Backlog**_ + +- ⬛️ [Reorganize the parser (#5414)](https://github.com/raft-tech/TANF-app/issues/5414) +_Remained in **Next Sprint Backlog**_ + +- ➡️ [Delete vault integration from backend (#5416)](https://github.com/raft-tech/TANF-app/issues/5416) +_Moved from **Next Sprint Backlog** to **Current Sprint Backlog**_ + +- ✅ [Change admin "acked" column to "read" (#5432)](https://github.com/raft-tech/TANF-app/issues/5432) +_**Closed**_ - _Moved from **Current Sprint Backlog**_ + + +## [User Experience Enhancements](https://github.com/raft-tech/TANF-app/issues/4444) + +- ⬛️ [Implement automated past due submission email notifications (#2984)](https://github.com/raft-tech/TANF-app/issues/2984) +_Remained in **Next Sprint Backlog**_ + +- ✅ [Update email templates for STT data submissions re: status and error guidance (#3251)](https://github.com/raft-tech/TANF-app/issues/3251) +_**Closed**_ - _Moved from **QASP Review**_ + +- ⬛️ [Create email templates for reparsing (new error report & error resolution) (#3263)](https://github.com/raft-tech/TANF-app/issues/3263) +_Remained in **QASP Review**_ + +- ⬛️ [Implement spinner for TANF / SSP data file submissions (#3564)](https://github.com/raft-tech/TANF-app/issues/3564) +_Remained in **In Progress**_ + +- ⬛️ [Create research plan template for UX Playbook (#5369)](https://github.com/raft-tech/TANF-app/issues/5369) +_Remained in **QASP Review**_ + +- ⬛️ [Create a Journey Mapping template & document best practices (#5371)](https://github.com/raft-tech/TANF-app/issues/5371) +_Remained in **QASP Review**_ + +- ⬛️ [Create a QA Checklist for Completing UX & Dev Reviews (#5374)](https://github.com/raft-tech/TANF-app/issues/5374) +_Remained in **QASP Review**_ + +- ⬛️ [Create a guide for Conducting a Heuristic Evaluation (Checklist + Best Practices) (#5375)](https://github.com/raft-tech/TANF-app/issues/5375) +_Remained in **Next Sprint Backlog**_ + + +## Issues without Parent + +- ➡️ [Implement E2E Tests for Admin Feedback Reports (#5421)](https://github.com/raft-tech/TANF-app/issues/5421) +_Moved from **Product Backlog** to **Next Sprint Backlog**_ + +- ➡️ [Refactor and cleanup parsing logic tech memo (#5434)](https://github.com/raft-tech/TANF-app/issues/5434) +_Moved from **Product Backlog** to **Current Sprint Backlog**_ + +- ➡️ [Update education level validation for adults when family affiliation is 2 or 3 (#5461)](https://github.com/raft-tech/TANF-app/issues/5461) +_Moved from **Product Backlog** to **Current Sprint Backlog**_ + +- ✅ [Datetime Field Type for EXIT_MONTH in Xlsx Files Causing Parsing Failure (#5463)](https://github.com/raft-tech/TANF-app/issues/5463) +_**Closed**_ - _Moved from **Product Backlog**_ + +- ✅ [PIA Submission History Contains Non-PIA Submissions (#5469)](https://github.com/raft-tech/TANF-app/issues/5469) +_**Closed**_ - _Moved from **Product Backlog**_ + +- ➡️ [Fix cat 2 validation on SSP Item # 41 (WEI) (#5473)](https://github.com/raft-tech/TANF-app/issues/5473) +_Moved from **Product Backlog** to **Current Sprint Backlog**_ + +- ➡️ [Update SSN_VALID logic in SQL Views (#5475)](https://github.com/raft-tech/TANF-app/issues/5475) +_Moved from **Product Backlog** to **Current Sprint Backlog**_ + +- ➡️ [Feature Toggle Tech Memo (#5476)](https://github.com/raft-tech/TANF-app/issues/5476) +_Moved from **Product Backlog** to **Current Sprint Backlog**_ + + From 26424082422da4c385b146acdcf7b1b7a1bc0868 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 21 Nov 2025 14:52:00 -0700 Subject: [PATCH 052/148] Add sprint summary for 2025-11-18 --- sprint-summary-2025-11-18.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sprint-summary-2025-11-18.md b/sprint-summary-2025-11-18.md index 274639ed0..59a4030e1 100644 --- a/sprint-summary-2025-11-18.md +++ b/sprint-summary-2025-11-18.md @@ -2,15 +2,13 @@ ## Overview -- Overall sprint momentum: completed 12 issues, progressed 19, blocked 7; 20.3% done, showing steady forward movement despite blockers. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#4924](https://github.com/raft-tech/TANF-app/issues/4924), [#5368](https://github.com/raft-tech/TANF-app/issues/5368), [#5397](https://github.com/raft-tech/TANF-app/issues/5397), [#5398](https://github.com/raft-tech/TANF-app/issues/5398)) +- Completed 12 issues this sprint, reflecting a solid delivery across admin UI, FRA improvements, and Knowledge Center/Reporting enhancements. (3251, 3258, 5368, 3571, 4924, 5397, 5398, 5418, 5428, 5463, 5469) -- Backlog and sequencing adjusted: FRA tasks moved toward Next Sprint Backlog and related design advanced toward staging. ([#5359](https://github.com/raft-tech/TANF-app/issues/5359), [#3485](https://github.com/raft-tech/TANF-app/issues/3485)) +- Progressed 19 issues, with several high-priority items moved into Current Sprint Backlog to firm up the scope. (5434, 5475, 5461) -- Regional staff blockers identified as risk: several items remained Blocked, delaying regional onboarding and training efforts. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462), [#3523](https://github.com/raft-tech/TANF-app/issues/3523), [#3995](https://github.com/raft-tech/TANF-app/issues/3995), [#4047](https://github.com/raft-tech/TANF-app/issues/4047)) +- Blockers remained a risk, with dependencies in regional staff workstreams keeping multiple items blocked. (3461, 3462) -- QA/E2E progress: accessibility tests enabled; E2E test work for Admin Feedback Reports and Program Integrity audits moved forward. ([#5407](https://github.com/raft-tech/TANF-app/issues/5407), [#5420](https://github.com/raft-tech/TANF-app/issues/5420), [#5421](https://github.com/raft-tech/TANF-app/issues/5421)) - -- Knowledge Center/Admin UI progress: closures and readiness for release advanced, including KC NavBar, Refresh Tolerant Data Pages, FRA email, and read-status changes. ([#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5418](https://github.com/raft-tech/TANF-app/issues/5418), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469), [#5432](https://github.com/raft-tech/TANF-app/issues/5432)) +- Momentum on centralized feedback and reporting initiatives, with closures advancing program integrity and Knowledge Center readiness. (5469, 5397, 5398) --- From c2e5eb56ce64f6b1757aadfecb57016c473765c2 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 21 Nov 2025 14:58:20 -0700 Subject: [PATCH 053/148] Add sprint summary for 2025-11-18 --- sprint-summary-2025-11-18.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sprint-summary-2025-11-18.md b/sprint-summary-2025-11-18.md index 59a4030e1..0124ee3c1 100644 --- a/sprint-summary-2025-11-18.md +++ b/sprint-summary-2025-11-18.md @@ -2,13 +2,15 @@ ## Overview -- Completed 12 issues this sprint, reflecting a solid delivery across admin UI, FRA improvements, and Knowledge Center/Reporting enhancements. (3251, 3258, 5368, 3571, 4924, 5397, 5398, 5418, 5428, 5463, 5469) +- Cleanup and knowledge-center work closed this sprint, including Promtail migration to Alloy, STT email templates, admin 'read' change, and KC navigation/refresh tasks. ([#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#5432](https://github.com/raft-tech/TANF-app/issues/5432), [#5429](https://github.com/raft-tech/TANF-app/issues/5429), [#5398](https://github.com/raft-tech/TANF-app/issues/5398), [#5418](https://github.com/raft-tech/TANF-app/issues/5418), [#5469](https://github.com/raft-tech/TANF-app/issues/5469)) -- Progressed 19 issues, with several high-priority items moved into Current Sprint Backlog to firm up the scope. (5434, 5475, 5461) +- E2E testing and UI progress advanced: admin feedback UI ongoing, Program Integrity Audit frontend tests kicked off, and accessibility tests enabled. ([#5407](https://github.com/raft-tech/TANF-app/issues/5407), [#5420](https://github.com/raft-tech/TANF-app/issues/5420), [#5421](https://github.com/raft-tech/TANF-app/issues/5421), [#5390](https://github.com/raft-tech/TANF-app/issues/5390)) -- Blockers remained a risk, with dependencies in regional staff workstreams keeping multiple items blocked. (3461, 3462) +- Parsing and validation work moved into scope: refactor/parsing logic tech memo progressed and education-level validation updates added to current sprint backlog. ([#5434](https://github.com/raft-tech/TANF-app/issues/5434), [#5461](https://github.com/raft-tech/TANF-app/issues/5461)) -- Momentum on centralized feedback and reporting initiatives, with closures advancing program integrity and Knowledge Center readiness. (5469, 5397, 5398) +- Backlog transitions pushed active work forward: vault backend cleanup, knowledge center updates, and STT feedback UI moved into current sprint backlog. ([#5416](https://github.com/raft-tech/TANF-app/issues/5416), [#5391](https://github.com/raft-tech/TANF-app/issues/5391), [#5417](https://github.com/raft-tech/TANF-app/issues/5417)) + +- Design and planning progressed: post-MVP centralized feedback reports requirements moved to UX Review and design ideation advanced to current sprint backlog. ([#5443](https://github.com/raft-tech/TANF-app/issues/5443), [#5223](https://github.com/raft-tech/TANF-app/issues/5223)) --- From 51a89cb21129666824c9279a7e49304bac2d12a2 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 21 Nov 2025 15:06:32 -0700 Subject: [PATCH 054/148] Add sprint summary for 2025-11-18 --- sprint-summary-2025-11-18.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sprint-summary-2025-11-18.md b/sprint-summary-2025-11-18.md index 0124ee3c1..08a171140 100644 --- a/sprint-summary-2025-11-18.md +++ b/sprint-summary-2025-11-18.md @@ -2,15 +2,13 @@ ## Overview -- Cleanup and knowledge-center work closed this sprint, including Promtail migration to Alloy, STT email templates, admin 'read' change, and KC navigation/refresh tasks. ([#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#5432](https://github.com/raft-tech/TANF-app/issues/5432), [#5429](https://github.com/raft-tech/TANF-app/issues/5429), [#5398](https://github.com/raft-tech/TANF-app/issues/5398), [#5418](https://github.com/raft-tech/TANF-app/issues/5418), [#5469](https://github.com/raft-tech/TANF-app/issues/5469)) +- Completed 12 issues this sprint, including closure of several high-impact items like email templates, Promtail migration, FRA submission updates, and knowledge center changes. ( #3251, #3258, #3571, #5368, #5418, #5463, #5469, #4924, #5428, #5397, #5398 ) -- E2E testing and UI progress advanced: admin feedback UI ongoing, Program Integrity Audit frontend tests kicked off, and accessibility tests enabled. ([#5407](https://github.com/raft-tech/TANF-app/issues/5407), [#5420](https://github.com/raft-tech/TANF-app/issues/5420), [#5421](https://github.com/raft-tech/TANF-app/issues/5421), [#5390](https://github.com/raft-tech/TANF-app/issues/5390)) +- Progressed 19 issues by moving several from Backlog/Next Sprint Backlog into Current Sprint Backlog and advancing key UX/admin work, e.g., STT Feedback UI, parser refactor, and admin data flows. ( #5316, #5416, #5417, #5434 ) -- Parsing and validation work moved into scope: refactor/parsing logic tech memo progressed and education-level validation updates added to current sprint backlog. ([#5434](https://github.com/raft-tech/TANF-app/issues/5434), [#5461](https://github.com/raft-tech/TANF-app/issues/5461)) +- Blockers slowed progress on major regional staff initiatives and design work, with items remaining Blocked. ( #3461, #3462, #3523, #5269 ) -- Backlog transitions pushed active work forward: vault backend cleanup, knowledge center updates, and STT feedback UI moved into current sprint backlog. ([#5416](https://github.com/raft-tech/TANF-app/issues/5416), [#5391](https://github.com/raft-tech/TANF-app/issues/5391), [#5417](https://github.com/raft-tech/TANF-app/issues/5417)) - -- Design and planning progressed: post-MVP centralized feedback reports requirements moved to UX Review and design ideation advanced to current sprint backlog. ([#5443](https://github.com/raft-tech/TANF-app/issues/5443), [#5223](https://github.com/raft-tech/TANF-app/issues/5223)) +- Major initiatives gained momentum in UX and operations, with related tasks advancing into active states and related backend work closing, signaling near-term deliverables. ( #5390, #5397, #5418, #5428 ) --- From 40299fe4e82b4eec0288f670ced4f668be89d02a Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 21 Nov 2025 15:15:55 -0700 Subject: [PATCH 055/148] Add sprint summary for 2025-11-18 --- sprint-summary-2025-11-18.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sprint-summary-2025-11-18.md b/sprint-summary-2025-11-18.md index 08a171140..cd75884d9 100644 --- a/sprint-summary-2025-11-18.md +++ b/sprint-summary-2025-11-18.md @@ -2,13 +2,13 @@ ## Overview -- Completed 12 issues this sprint, including closure of several high-impact items like email templates, Promtail migration, FRA submission updates, and knowledge center changes. ( #3251, #3258, #3571, #5368, #5418, #5463, #5469, #4924, #5428, #5397, #5398 ) +- Closed a batch of backend, data validation, and admin work, reducing risk and backlog. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3571](https://github.com/raft-tech/TANF-app/issues/3571), [#4924](https://github.com/raft-tech/TANF-app/issues/4924), [#5397](https://github.com/raft-tech/TANF-app/issues/5397), [#5398](https://github.com/raft-tech/TANF-app/issues/5398), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469), [#5432](https://github.com/raft-tech/TANF-app/issues/5432)) -- Progressed 19 issues by moving several from Backlog/Next Sprint Backlog into Current Sprint Backlog and advancing key UX/admin work, e.g., STT Feedback UI, parser refactor, and admin data flows. ( #5316, #5416, #5417, #5434 ) +- Progressed knowledge center and UI work: KC NavBar item closed, data pages refreshed, and related UI/content moved toward production. ([#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5418](https://github.com/raft-tech/TANF-app/issues/5418), [#5390](https://github.com/raft-tech/TANF-app/issues/5390), [#5417](https://github.com/raft-tech/TANF-app/issues/5417), [#5429](https://github.com/raft-tech/TANF-app/issues/5429)) -- Blockers slowed progress on major regional staff initiatives and design work, with items remaining Blocked. ( #3461, #3462, #3523, #5269 ) +- Moved parsing and data-quality improvements forward, including refactor of parsing logic and EXIT_MONTH/SSN validation fixes toward production. ([#5434](https://github.com/raft-tech/TANF-app/issues/5434), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5475](https://github.com/raft-tech/TANF-app/issues/5475)) -- Major initiatives gained momentum in UX and operations, with related tasks advancing into active states and related backend work closing, signaling near-term deliverables. ( #5390, #5397, #5418, #5428 ) +- Blockers and risk areas identified, with several regional staff and OFA-related tracks still blocked, signaling risk to final sprint delivery. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462), [#3523](https://github.com/raft-tech/TANF-app/issues/3523), [#3995](https://github.com/raft-tech/TANF-app/issues/3995), [#4045](https://github.com/raft-tech/TANF-app/issues/4045), [#4047](https://github.com/raft-tech/TANF-app/issues/4047)) --- From 4edf8be0933e4e9b7945b9fa77bb08ad564eb720 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 21 Nov 2025 15:27:47 -0700 Subject: [PATCH 056/148] Add sprint summary for 2025-11-18 --- sprint-summary-2025-11-18.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/sprint-summary-2025-11-18.md b/sprint-summary-2025-11-18.md index cd75884d9..b36be2028 100644 --- a/sprint-summary-2025-11-18.md +++ b/sprint-summary-2025-11-18.md @@ -2,13 +2,11 @@ ## Overview -- Closed a batch of backend, data validation, and admin work, reducing risk and backlog. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3571](https://github.com/raft-tech/TANF-app/issues/3571), [#4924](https://github.com/raft-tech/TANF-app/issues/4924), [#5397](https://github.com/raft-tech/TANF-app/issues/5397), [#5398](https://github.com/raft-tech/TANF-app/issues/5398), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469), [#5432](https://github.com/raft-tech/TANF-app/issues/5432)) - -- Progressed knowledge center and UI work: KC NavBar item closed, data pages refreshed, and related UI/content moved toward production. ([#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5418](https://github.com/raft-tech/TANF-app/issues/5418), [#5390](https://github.com/raft-tech/TANF-app/issues/5390), [#5417](https://github.com/raft-tech/TANF-app/issues/5417), [#5429](https://github.com/raft-tech/TANF-app/issues/5429)) - -- Moved parsing and data-quality improvements forward, including refactor of parsing logic and EXIT_MONTH/SSN validation fixes toward production. ([#5434](https://github.com/raft-tech/TANF-app/issues/5434), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5475](https://github.com/raft-tech/TANF-app/issues/5475)) - -- Blockers and risk areas identified, with several regional staff and OFA-related tracks still blocked, signaling risk to final sprint delivery. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462), [#3523](https://github.com/raft-tech/TANF-app/issues/3523), [#3995](https://github.com/raft-tech/TANF-app/issues/3995), [#4045](https://github.com/raft-tech/TANF-app/issues/4045), [#4047](https://github.com/raft-tech/TANF-app/issues/4047)) +- Completed a set of high-impact items: STT submission templates updated, Promtail migrated to Alloy, FRA submission history restricted to participating STTs, and admin 'acked' column renamed to 'read'. (3251, 3258, 3571, 5432) +- Knowledge Center progress: KC NavBar item closed and broader navigation/search work advanced to UX Review. (5428, 5429) +- Data parsing/tech debt moved forward: refactor and cleanup parsing logic tech memo moved from Product Backlog to Current Sprint Backlog. (5434) +- Critical data fixes completed: EXIT_MONTH datetime parsing issue resolved and PIA submission history cleaned/closed. (5463, 5469) +- Bug fix delivered: Regional Access Request pre-selected option corrected. (4924) --- From 51c6c8532abaa31bf12cc21c4138b447d87378a0 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 21 Nov 2025 15:37:30 -0700 Subject: [PATCH 057/148] Add sprint summary for 2025-11-18 --- sprint-summary-2025-11-18.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/sprint-summary-2025-11-18.md b/sprint-summary-2025-11-18.md index b36be2028..28a3c5116 100644 --- a/sprint-summary-2025-11-18.md +++ b/sprint-summary-2025-11-18.md @@ -2,11 +2,13 @@ ## Overview -- Completed a set of high-impact items: STT submission templates updated, Promtail migrated to Alloy, FRA submission history restricted to participating STTs, and admin 'acked' column renamed to 'read'. (3251, 3258, 3571, 5432) -- Knowledge Center progress: KC NavBar item closed and broader navigation/search work advanced to UX Review. (5428, 5429) -- Data parsing/tech debt moved forward: refactor and cleanup parsing logic tech memo moved from Product Backlog to Current Sprint Backlog. (5434) -- Critical data fixes completed: EXIT_MONTH datetime parsing issue resolved and PIA submission history cleaned/closed. (5463, 5469) -- Bug fix delivered: Regional Access Request pre-selected option corrected. (4924) +- Closed a sizable set of items, clearing backlog and delivering completions this sprint. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3571](https://github.com/raft-tech/TANF-app/issues/3571), [#4924](https://github.com/raft-tech/TANF-app/issues/4924), [#5397](https://github.com/raft-tech/TANF-app/issues/5397), [#5398](https://github.com/raft-tech/TANF-app/issues/5398), [#5418](https://github.com/raft-tech/TANF-app/issues/5418), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469)) + +- UI/Knowledge Center momentum: KC NavBar Item closed; Knowledge Center navigation and search advanced to UX Review. ([#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5429](https://github.com/raft-tech/TANF-app/issues/5429)) + +- Data processing and validation progress: refactor/cleanup parsing logic tech memo moved to Current Sprint Backlog; Update SSN_VALID logic in SQL Views moved to Current Sprint Backlog. ([#5434](https://github.com/raft-tech/TANF-app/issues/5434), [#5475](https://github.com/raft-tech/TANF-app/issues/5475)) + +- Testing and admin/reporting pipeline advanced: E2E tests for Admin Feedback Reports and Program Integrity Audit Frontend moved from Product Backlog to Next Sprint Backlog. ([#5421](https://github.com/raft-tech/TANF-app/issues/5421), [#5420](https://github.com/raft-tech/TANF-app/issues/5420)) --- From 3f2033e78f9e2b735511af89165a310925a286c8 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 21 Nov 2025 15:45:32 -0700 Subject: [PATCH 058/148] Add sprint summary for 2025-11-18 --- sprint-summary-2025-11-18.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sprint-summary-2025-11-18.md b/sprint-summary-2025-11-18.md index 28a3c5116..2fdeff96b 100644 --- a/sprint-summary-2025-11-18.md +++ b/sprint-summary-2025-11-18.md @@ -2,13 +2,15 @@ ## Overview -- Closed a sizable set of items, clearing backlog and delivering completions this sprint. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3571](https://github.com/raft-tech/TANF-app/issues/3571), [#4924](https://github.com/raft-tech/TANF-app/issues/4924), [#5397](https://github.com/raft-tech/TANF-app/issues/5397), [#5398](https://github.com/raft-tech/TANF-app/issues/5398), [#5418](https://github.com/raft-tech/TANF-app/issues/5418), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469)) +- 12 issues were completed this sprint, closing core items and stabilizing the baseline. ([#3571](https://github.com/raft-tech/TANF-app/issues/3571), [#4924](https://github.com/raft-tech/TANF-app/issues/4924), [#5368](https://github.com/raft-tech/TANF-app/issues/5368), [#5397](https://github.com/raft-tech/TANF-app/issues/5397), [#5463](https://github.com/raft-tech/TANF-app/issues/5463)) -- UI/Knowledge Center momentum: KC NavBar Item closed; Knowledge Center navigation and search advanced to UX Review. ([#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5429](https://github.com/raft-tech/TANF-app/issues/5429)) +- Moved multiple high-priority items from Backlog into active workstreams, advancing E2E test work and knowledge-center tasks. ([#5316](https://github.com/raft-tech/TANF-app/issues/5316), [#5416](https://github.com/raft-tech/TANF-app/issues/5416), [#5390](https://github.com/raft-tech/TANF-app/issues/5390), [#5429](https://github.com/raft-tech/TANF-app/issues/5429)) -- Data processing and validation progress: refactor/cleanup parsing logic tech memo moved to Current Sprint Backlog; Update SSN_VALID logic in SQL Views moved to Current Sprint Backlog. ([#5434](https://github.com/raft-tech/TANF-app/issues/5434), [#5475](https://github.com/raft-tech/TANF-app/issues/5475)) +- Blockers persisted for several regional staff initiatives, with 6 issues still Blocked after intake and triage. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462), [#3995](https://github.com/raft-tech/TANF-app/issues/3995), [#4045](https://github.com/raft-tech/TANF-app/issues/4045), [#4047](https://github.com/raft-tech/TANF-app/issues/4047), [#4721](https://github.com/raft-tech/TANF-app/issues/4721)) -- Testing and admin/reporting pipeline advanced: E2E tests for Admin Feedback Reports and Program Integrity Audit Frontend moved from Product Backlog to Next Sprint Backlog. ([#5421](https://github.com/raft-tech/TANF-app/issues/5421), [#5420](https://github.com/raft-tech/TANF-app/issues/5420)) +- Refinements to parsing and validation surfaced in the sprint backlog, setting up for stability gains post-release. ([#5434](https://github.com/raft-tech/TANF-app/issues/5434), [#5461](https://github.com/raft-tech/TANF-app/issues/5461)) + +- UX and Knowledge Center improvements progressed to UX Review, signaling readiness for QA and staging. ([#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5406](https://github.com/raft-tech/TANF-app/issues/5406)) --- From 52d3ad9ac9912074f30b64b9bb0c82a979e9efeb Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 21 Nov 2025 15:52:02 -0700 Subject: [PATCH 059/148] Add sprint summary for 2025-11-18 --- sprint-summary-2025-11-18.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sprint-summary-2025-11-18.md b/sprint-summary-2025-11-18.md index 2fdeff96b..b97f33313 100644 --- a/sprint-summary-2025-11-18.md +++ b/sprint-summary-2025-11-18.md @@ -2,15 +2,15 @@ ## Overview -- 12 issues were completed this sprint, closing core items and stabilizing the baseline. ([#3571](https://github.com/raft-tech/TANF-app/issues/3571), [#4924](https://github.com/raft-tech/TANF-app/issues/4924), [#5368](https://github.com/raft-tech/TANF-app/issues/5368), [#5397](https://github.com/raft-tech/TANF-app/issues/5397), [#5463](https://github.com/raft-tech/TANF-app/issues/5463)) +- Closed a wave of high-impact items, accelerating production readiness across admin UI, data parsing, and reporting—email templates, Promtail migration, FRA submission history, admin read state, knowledge center UI, and key data fixes. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3571](https://github.com/raft-tech/TANF-app/issues/3571), [#4924](https://github.com/raft-tech/TANF-app/issues/4924), [#5432](https://github.com/raft-tech/TANF-app/issues/5432), [#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5418](https://github.com/raft-tech/TANF-app/issues/5418), [#5397](https://github.com/raft-tech/TANF-app/issues/5397), [#5398](https://github.com/raft-tech/TANF-app/issues/5398), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469)) -- Moved multiple high-priority items from Backlog into active workstreams, advancing E2E test work and knowledge-center tasks. ([#5316](https://github.com/raft-tech/TANF-app/issues/5316), [#5416](https://github.com/raft-tech/TANF-app/issues/5416), [#5390](https://github.com/raft-tech/TANF-app/issues/5390), [#5429](https://github.com/raft-tech/TANF-app/issues/5429)) +- Progress on testing and accessibility: ongoing E2E and a11y work with several tests moving forward and some test-related items entering Next Sprint Backlog. ([#5407](https://github.com/raft-tech/TANF-app/issues/5407), [#5420](https://github.com/raft-tech/TANF-app/issues/5420), [#5421](https://github.com/raft-tech/TANF-app/issues/5421), [#5390](https://github.com/raft-tech/TANF-app/issues/5390)) -- Blockers persisted for several regional staff initiatives, with 6 issues still Blocked after intake and triage. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462), [#3995](https://github.com/raft-tech/TANF-app/issues/3995), [#4045](https://github.com/raft-tech/TANF-app/issues/4045), [#4047](https://github.com/raft-tech/TANF-app/issues/4047), [#4721](https://github.com/raft-tech/TANF-app/issues/4721)) +- Parser and centralized feedback groundwork advanced: refactoring the parsing logic moved to current sprint backlog; planning for post-MVP centralized feedback started (requirements gathering moved to UX Review). ([#5434](https://github.com/raft-tech/TANF-app/issues/5434), [#5443](https://github.com/raft-tech/TANF-app/issues/5443)) -- Refinements to parsing and validation surfaced in the sprint backlog, setting up for stability gains post-release. ([#5434](https://github.com/raft-tech/TANF-app/issues/5434), [#5461](https://github.com/raft-tech/TANF-app/issues/5461)) +- Blockers remained a constraint; 7 items blocked, with critical regional staff and research work paused, including examples. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462), [#3523](https://github.com/raft-tech/TANF-app/issues/3523), [#4721](https://github.com/raft-tech/TANF-app/issues/4721)) -- UX and Knowledge Center improvements progressed to UX Review, signaling readiness for QA and staging. ([#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5406](https://github.com/raft-tech/TANF-app/issues/5406)) +- Momentum and reprioritization visible as several tasks shifted between backlogs and sprint planning for next cycle (e.g., E2E tests and knowledge center readiness in motion): move of tasks. ([#5316](https://github.com/raft-tech/TANF-app/issues/5316), [#5391](https://github.com/raft-tech/TANF-app/issues/5391), [#5420](https://github.com/raft-tech/TANF-app/issues/5420), [#5421](https://github.com/raft-tech/TANF-app/issues/5421)) --- From 3273e668e5e43986254f04527584249d3484b39b Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 21 Nov 2025 15:57:43 -0700 Subject: [PATCH 060/148] Add sprint summary for 2025-11-18 --- sprint-summary-2025-11-18.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sprint-summary-2025-11-18.md b/sprint-summary-2025-11-18.md index b97f33313..147a2c740 100644 --- a/sprint-summary-2025-11-18.md +++ b/sprint-summary-2025-11-18.md @@ -2,15 +2,15 @@ ## Overview -- Closed a wave of high-impact items, accelerating production readiness across admin UI, data parsing, and reporting—email templates, Promtail migration, FRA submission history, admin read state, knowledge center UI, and key data fixes. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3571](https://github.com/raft-tech/TANF-app/issues/3571), [#4924](https://github.com/raft-tech/TANF-app/issues/4924), [#5432](https://github.com/raft-tech/TANF-app/issues/5432), [#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5418](https://github.com/raft-tech/TANF-app/issues/5418), [#5397](https://github.com/raft-tech/TANF-app/issues/5397), [#5398](https://github.com/raft-tech/TANF-app/issues/5398), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469)) +- Closed 12 issues this sprint, moving core work from backlog or in-progress stages to done across email templates, FRA parsing, data validation, and Knowledge Center improvements. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3571](https://github.com/raft-tech/TANF-app/issues/3571), [#4924](https://github.com/raft-tech/TANF-app/issues/4924), [#5418](https://github.com/raft-tech/TANF-app/issues/5418), [#5432](https://github.com/raft-tech/TANF-app/issues/5432), [#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469), [#5398](https://github.com/raft-tech/TANF-app/issues/5398), [#5368](https://github.com/raft-tech/TANF-app/issues/5368), [#5397](https://github.com/raft-tech/TANF-app/issues/5397)) -- Progress on testing and accessibility: ongoing E2E and a11y work with several tests moving forward and some test-related items entering Next Sprint Backlog. ([#5407](https://github.com/raft-tech/TANF-app/issues/5407), [#5420](https://github.com/raft-tech/TANF-app/issues/5420), [#5421](https://github.com/raft-tech/TANF-app/issues/5421), [#5390](https://github.com/raft-tech/TANF-app/issues/5390)) +- UX and reporting infrastructure advanced, with KC NavBar item finalized and backend support for Feedback Reports completed, enabling smoother navigation and reporting workflows. ([#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5397](https://github.com/raft-tech/TANF-app/issues/5397)) -- Parser and centralized feedback groundwork advanced: refactoring the parsing logic moved to current sprint backlog; planning for post-MVP centralized feedback started (requirements gathering moved to UX Review). ([#5434](https://github.com/raft-tech/TANF-app/issues/5434), [#5443](https://github.com/raft-tech/TANF-app/issues/5443)) +- Data quality fixes progressed, including the EXIT_MONTH datetime parsing fix and PIA submission history clean-up. ([#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469)) -- Blockers remained a constraint; 7 items blocked, with critical regional staff and research work paused, including examples. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462), [#3523](https://github.com/raft-tech/TANF-app/issues/3523), [#4721](https://github.com/raft-tech/TANF-app/issues/4721)) +- Blockers persist as risk; 7 items remain blocked, with notable blockers on regional staff initiatives that require cross-team alignment. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462)) -- Momentum and reprioritization visible as several tasks shifted between backlogs and sprint planning for next cycle (e.g., E2E tests and knowledge center readiness in motion): move of tasks. ([#5316](https://github.com/raft-tech/TANF-app/issues/5316), [#5391](https://github.com/raft-tech/TANF-app/issues/5391), [#5420](https://github.com/raft-tech/TANF-app/issues/5420), [#5421](https://github.com/raft-tech/TANF-app/issues/5421)) +- E2E testing work shifted to Next Sprint Backlog for Admin and Program Integrity Frontend, reflecting reprioritization of testing in response to dependencies. ([#5420](https://github.com/raft-tech/TANF-app/issues/5420), [#5421](https://github.com/raft-tech/TANF-app/issues/5421)) --- From cef950c32c8fcc94f576e56985d688ffb84ca4e9 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 21 Nov 2025 16:04:42 -0700 Subject: [PATCH 061/148] Add sprint summary for 2025-11-18 --- sprint-summary-2025-11-18.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sprint-summary-2025-11-18.md b/sprint-summary-2025-11-18.md index 147a2c740..df668f62c 100644 --- a/sprint-summary-2025-11-18.md +++ b/sprint-summary-2025-11-18.md @@ -2,15 +2,15 @@ ## Overview -- Closed 12 issues this sprint, moving core work from backlog or in-progress stages to done across email templates, FRA parsing, data validation, and Knowledge Center improvements. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3571](https://github.com/raft-tech/TANF-app/issues/3571), [#4924](https://github.com/raft-tech/TANF-app/issues/4924), [#5418](https://github.com/raft-tech/TANF-app/issues/5418), [#5432](https://github.com/raft-tech/TANF-app/issues/5432), [#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469), [#5398](https://github.com/raft-tech/TANF-app/issues/5398), [#5368](https://github.com/raft-tech/TANF-app/issues/5368), [#5397](https://github.com/raft-tech/TANF-app/issues/5397)) +- Closed several high-priority back-end/UX items, including email templates for STT submissions, migration of Promtail to Alloy, and restricting FRA submission history to participating STTs. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3571](https://github.com/raft-tech/TANF-app/issues/3571)) -- UX and reporting infrastructure advanced, with KC NavBar item finalized and backend support for Feedback Reports completed, enabling smoother navigation and reporting workflows. ([#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5397](https://github.com/raft-tech/TANF-app/issues/5397)) +- Improved navigation and readability with KC NavBar item completion and admin 'acked' column renamed to 'read'. ([#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5432](https://github.com/raft-tech/TANF-app/issues/5432)) -- Data quality fixes progressed, including the EXIT_MONTH datetime parsing fix and PIA submission history clean-up. ([#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469)) +- Boosted data reliability by fixing EXIT_MONTH parsing in XLSX and cleaning PIA submission history of non-PIA entries. ([#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469)) -- Blockers persist as risk; 7 items remain blocked, with notable blockers on regional staff initiatives that require cross-team alignment. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462)) +- Progress on data/knowledge center workflows with Refresh Tolerant Data Files Pages completed for faster documentation access. ([#5418](https://github.com/raft-tech/TANF-app/issues/5418)) -- E2E testing work shifted to Next Sprint Backlog for Admin and Program Integrity Frontend, reflecting reprioritization of testing in response to dependencies. ([#5420](https://github.com/raft-tech/TANF-app/issues/5420), [#5421](https://github.com/raft-tech/TANF-app/issues/5421)) +- Advancement in testing and regional onboarding planning: E2E tests for Admin Feedback Reports and Program Integrity Frontend moved toward Next Sprint Backlog, while regional onboarding items were blocked. ([#5420](https://github.com/raft-tech/TANF-app/issues/5420), [#5421](https://github.com/raft-tech/TANF-app/issues/5421), [#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462), [#3523](https://github.com/raft-tech/TANF-app/issues/3523)) --- From 73f0d3a9c34d853f1026a5020266e5881b738dcd Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 21 Nov 2025 16:14:48 -0700 Subject: [PATCH 062/148] Add sprint summary for 2025-11-18 --- sprint-summary-2025-11-18.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/sprint-summary-2025-11-18.md b/sprint-summary-2025-11-18.md index df668f62c..9d4f177ff 100644 --- a/sprint-summary-2025-11-18.md +++ b/sprint-summary-2025-11-18.md @@ -2,15 +2,10 @@ ## Overview -- Closed several high-priority back-end/UX items, including email templates for STT submissions, migration of Promtail to Alloy, and restricting FRA submission history to participating STTs. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3571](https://github.com/raft-tech/TANF-app/issues/3571)) - -- Improved navigation and readability with KC NavBar item completion and admin 'acked' column renamed to 'read'. ([#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5432](https://github.com/raft-tech/TANF-app/issues/5432)) - -- Boosted data reliability by fixing EXIT_MONTH parsing in XLSX and cleaning PIA submission history of non-PIA entries. ([#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469)) - -- Progress on data/knowledge center workflows with Refresh Tolerant Data Files Pages completed for faster documentation access. ([#5418](https://github.com/raft-tech/TANF-app/issues/5418)) - -- Advancement in testing and regional onboarding planning: E2E tests for Admin Feedback Reports and Program Integrity Frontend moved toward Next Sprint Backlog, while regional onboarding items were blocked. ([#5420](https://github.com/raft-tech/TANF-app/issues/5420), [#5421](https://github.com/raft-tech/TANF-app/issues/5421), [#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462), [#3523](https://github.com/raft-tech/TANF-app/issues/3523)) +- Closed 12 items this sprint across admin, data, and reporting, delivering tangible momentum and reduced backlog (3251, 3258, 3571, 4924, 5368, 5397, 5398, 5418, 5428, 5432, 5463, 5469) +- UX/Knowledge Center momentum: KC NavBar Item closed; Knowledge Center navigation moved to UX Review; E2E tests for Admin Feedback and Program Integrity shifted to Next Sprint Backlog; E2E tests for Editing Profile moved to Current Sprint Backlog (5316, 5428, 5429, 5420, 5421) +- Data quality and parsing improvements advanced into the current sprint: SSN_VALID logic update, SSP item validation fix, and related feature toggle work moved to Current Sprint Backlog (5475, 5473, 5476) +- Blockers persist with key items blocked: regional staff training/onboarding and related work (3461, 3462, 3995, 4047) --- From bdf4e5c179bde7f4350ea562b7acc78f928baf15 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 21 Nov 2025 16:25:53 -0700 Subject: [PATCH 063/148] Add sprint summary for 2025-11-18 --- sprint-summary-2025-11-18.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/sprint-summary-2025-11-18.md b/sprint-summary-2025-11-18.md index 9d4f177ff..8371ad178 100644 --- a/sprint-summary-2025-11-18.md +++ b/sprint-summary-2025-11-18.md @@ -2,10 +2,15 @@ ## Overview -- Closed 12 items this sprint across admin, data, and reporting, delivering tangible momentum and reduced backlog (3251, 3258, 3571, 4924, 5368, 5397, 5398, 5418, 5428, 5432, 5463, 5469) -- UX/Knowledge Center momentum: KC NavBar Item closed; Knowledge Center navigation moved to UX Review; E2E tests for Admin Feedback and Program Integrity shifted to Next Sprint Backlog; E2E tests for Editing Profile moved to Current Sprint Backlog (5316, 5428, 5429, 5420, 5421) -- Data quality and parsing improvements advanced into the current sprint: SSN_VALID logic update, SSP item validation fix, and related feature toggle work moved to Current Sprint Backlog (5475, 5473, 5476) -- Blockers persist with key items blocked: regional staff training/onboarding and related work (3461, 3462, 3995, 4047) +- 12 issues completed and several high-impact closures (email templates, FRA history restriction, PIA/audit reports, admin UX tweaks) reducing risk and clearing blockers. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3571](https://github.com/raft-tech/TANF-app/issues/3571), [#4924](https://github.com/raft-tech/TANF-app/issues/4924), [#5397](https://github.com/raft-tech/TANF-app/issues/5397), [#5398](https://github.com/raft-tech/TANF-app/issues/5398), [#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469)) + +- Advanced work items into active sprint phases: API/UI tests, parsing refactor, admin feedback/frontend testing moving from Next Sprint Backlog to Current Sprint/In Progress. ([#5316](https://github.com/raft-tech/TANF-app/issues/5316), [#5420](https://github.com/raft-tech/TANF-app/issues/5420), [#5421](https://github.com/raft-tech/TANF-app/issues/5421), [#5434](https://github.com/raft-tech/TANF-app/issues/5434), [#5416](https://github.com/raft-tech/TANF-app/issues/5416), [#5407](https://github.com/raft-tech/TANF-app/issues/5407)) + +- Blockers for regional staff onboarding and training remained, delaying several related initiatives and keeping work in Blocked status ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462), [#3523](https://github.com/raft-tech/TANF-app/issues/3523), [#3995](https://github.com/raft-tech/TANF-app/issues/3995), [#4045](https://github.com/raft-tech/TANF-app/issues/4045)) + +- Progress on major initiatives to reduce staff time and enhance UX continued, with knowledge center updates and improved submission workflows advancing toward completion ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#5391](https://github.com/raft-tech/TANF-app/issues/5391), [#5429](https://github.com/raft-tech/TANF-app/issues/5429), [#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5397](https://github.com/raft-tech/TANF-app/issues/5397)) + +- Testing and reliability gains through E2E test enablement and error-reporting enhancements began to bear fruit, setting up for next sprint deliverables ([#5420](https://github.com/raft-tech/TANF-app/issues/5420), [#5421](https://github.com/raft-tech/TANF-app/issues/5421), [#5407](https://github.com/raft-tech/TANF-app/issues/5407), [#5300](https://github.com/raft-tech/TANF-app/issues/5300)) --- From 8c1128a53aad3b4236ee9ed3a11a5bc5e9422d36 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 21 Nov 2025 16:33:48 -0700 Subject: [PATCH 064/148] Add sprint summary for 2025-11-18 --- sprint-summary-2025-11-18.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/sprint-summary-2025-11-18.md b/sprint-summary-2025-11-18.md index 8371ad178..4bf710e56 100644 --- a/sprint-summary-2025-11-18.md +++ b/sprint-summary-2025-11-18.md @@ -2,15 +2,10 @@ ## Overview -- 12 issues completed and several high-impact closures (email templates, FRA history restriction, PIA/audit reports, admin UX tweaks) reducing risk and clearing blockers. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3571](https://github.com/raft-tech/TANF-app/issues/3571), [#4924](https://github.com/raft-tech/TANF-app/issues/4924), [#5397](https://github.com/raft-tech/TANF-app/issues/5397), [#5398](https://github.com/raft-tech/TANF-app/issues/5398), [#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469)) - -- Advanced work items into active sprint phases: API/UI tests, parsing refactor, admin feedback/frontend testing moving from Next Sprint Backlog to Current Sprint/In Progress. ([#5316](https://github.com/raft-tech/TANF-app/issues/5316), [#5420](https://github.com/raft-tech/TANF-app/issues/5420), [#5421](https://github.com/raft-tech/TANF-app/issues/5421), [#5434](https://github.com/raft-tech/TANF-app/issues/5434), [#5416](https://github.com/raft-tech/TANF-app/issues/5416), [#5407](https://github.com/raft-tech/TANF-app/issues/5407)) - -- Blockers for regional staff onboarding and training remained, delaying several related initiatives and keeping work in Blocked status ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462), [#3523](https://github.com/raft-tech/TANF-app/issues/3523), [#3995](https://github.com/raft-tech/TANF-app/issues/3995), [#4045](https://github.com/raft-tech/TANF-app/issues/4045)) - -- Progress on major initiatives to reduce staff time and enhance UX continued, with knowledge center updates and improved submission workflows advancing toward completion ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#5391](https://github.com/raft-tech/TANF-app/issues/5391), [#5429](https://github.com/raft-tech/TANF-app/issues/5429), [#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5397](https://github.com/raft-tech/TANF-app/issues/5397)) - -- Testing and reliability gains through E2E test enablement and error-reporting enhancements began to bear fruit, setting up for next sprint deliverables ([#5420](https://github.com/raft-tech/TANF-app/issues/5420), [#5421](https://github.com/raft-tech/TANF-app/issues/5421), [#5407](https://github.com/raft-tech/TANF-app/issues/5407), [#5300](https://github.com/raft-tech/TANF-app/issues/5300)) +- Closed several high-impact items, delivering user-facing updates and admin/reporting improvements that advance submission workflows. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#5397](https://github.com/raft-tech/TANF-app/issues/5397), [#5398](https://github.com/raft-tech/TANF-app/issues/5398), [#5432](https://github.com/raft-tech/TANF-app/issues/5432)) +- Resolved data quality and parsing issues, including the EXIT_MONTH parsing fix and PIA history validation; completed supporting data-file resilience work. ([#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469), [#5418](https://github.com/raft-tech/TANF-app/issues/5418)) +- Progressed Knowledge Center UX and navigation work, moving key UI efforts toward UX Review and finalizing navigation components. ([#5417](https://github.com/raft-tech/TANF-app/issues/5417), [#5429](https://github.com/raft-tech/TANF-app/issues/5429)) +- Acknowledged blockers slowing pace; several items remain blocked or at risk and need resolution to maintain momentum. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462), [#3523](https://github.com/raft-tech/TANF-app/issues/3523), [#3995](https://github.com/raft-tech/TANF-app/issues/3995), [#4045](https://github.com/raft-tech/TANF-app/issues/4045), [#4721](https://github.com/raft-tech/TANF-app/issues/4721)) --- From 133bc8a3c1f4ef1e65e0c324bd084d5fb934a42c Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 21 Nov 2025 16:41:26 -0700 Subject: [PATCH 065/148] Add sprint summary for 2025-11-18 --- sprint-summary-2025-11-18.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sprint-summary-2025-11-18.md b/sprint-summary-2025-11-18.md index 4bf710e56..407a56002 100644 --- a/sprint-summary-2025-11-18.md +++ b/sprint-summary-2025-11-18.md @@ -2,10 +2,10 @@ ## Overview -- Closed several high-impact items, delivering user-facing updates and admin/reporting improvements that advance submission workflows. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#5397](https://github.com/raft-tech/TANF-app/issues/5397), [#5398](https://github.com/raft-tech/TANF-app/issues/5398), [#5432](https://github.com/raft-tech/TANF-app/issues/5432)) -- Resolved data quality and parsing issues, including the EXIT_MONTH parsing fix and PIA history validation; completed supporting data-file resilience work. ([#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469), [#5418](https://github.com/raft-tech/TANF-app/issues/5418)) -- Progressed Knowledge Center UX and navigation work, moving key UI efforts toward UX Review and finalizing navigation components. ([#5417](https://github.com/raft-tech/TANF-app/issues/5417), [#5429](https://github.com/raft-tech/TANF-app/issues/5429)) -- Acknowledged blockers slowing pace; several items remain blocked or at risk and need resolution to maintain momentum. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462), [#3523](https://github.com/raft-tech/TANF-app/issues/3523), [#3995](https://github.com/raft-tech/TANF-app/issues/3995), [#4045](https://github.com/raft-tech/TANF-app/issues/4045), [#4721](https://github.com/raft-tech/TANF-app/issues/4721)) +- Completed 12 issues this sprint across admin, data validation, and UX work, signaling solid momentum and several domain closures ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3571](https://github.com/raft-tech/TANF-app/issues/3571), [#4924](https://github.com/raft-tech/TANF-app/issues/4924), [#5398](https://github.com/raft-tech/TANF-app/issues/5398), [#5397](https://github.com/raft-tech/TANF-app/issues/5397), [#5418](https://github.com/raft-tech/TANF-app/issues/5418), [#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5432](https://github.com/raft-tech/TANF-app/issues/5432), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469)) +- Progressed 19 issues into active scope by moving key work into Current Sprint Backlog and advancing refactor/training items: parsing logic tech memo, training materials, and STT Feedback UI ([#5434](https://github.com/raft-tech/TANF-app/issues/5434), [#4052](https://github.com/raft-tech/TANF-app/issues/4052), [#5417](https://github.com/raft-tech/TANF-app/issues/5417)) +- QA momentum via E2E work: two items migrated to Next Sprint Backlog to accelerate testing on Admin Feedback Reports and Program Integrity Audit Frontend ([#5421](https://github.com/raft-tech/TANF-app/issues/5421), [#5420](https://github.com/raft-tech/TANF-app/issues/5420)) +- Blockers remain a risk: 7 issues blocked, with notable blockers on regional onboarding and regional training potentially delaying milestones ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462)) --- From 8aede328fb710fd618d0a97eae9e075c6edd2990 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 21 Nov 2025 16:46:43 -0700 Subject: [PATCH 066/148] Add sprint summary for 2025-11-18 --- sprint-summary-2025-11-18.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/sprint-summary-2025-11-18.md b/sprint-summary-2025-11-18.md index 407a56002..4ba4a6555 100644 --- a/sprint-summary-2025-11-18.md +++ b/sprint-summary-2025-11-18.md @@ -2,10 +2,13 @@ ## Overview -- Completed 12 issues this sprint across admin, data validation, and UX work, signaling solid momentum and several domain closures ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3571](https://github.com/raft-tech/TANF-app/issues/3571), [#4924](https://github.com/raft-tech/TANF-app/issues/4924), [#5398](https://github.com/raft-tech/TANF-app/issues/5398), [#5397](https://github.com/raft-tech/TANF-app/issues/5397), [#5418](https://github.com/raft-tech/TANF-app/issues/5418), [#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5432](https://github.com/raft-tech/TANF-app/issues/5432), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469)) -- Progressed 19 issues into active scope by moving key work into Current Sprint Backlog and advancing refactor/training items: parsing logic tech memo, training materials, and STT Feedback UI ([#5434](https://github.com/raft-tech/TANF-app/issues/5434), [#4052](https://github.com/raft-tech/TANF-app/issues/4052), [#5417](https://github.com/raft-tech/TANF-app/issues/5417)) -- QA momentum via E2E work: two items migrated to Next Sprint Backlog to accelerate testing on Admin Feedback Reports and Program Integrity Audit Frontend ([#5421](https://github.com/raft-tech/TANF-app/issues/5421), [#5420](https://github.com/raft-tech/TANF-app/issues/5420)) -- Blockers remain a risk: 7 issues blocked, with notable blockers on regional onboarding and regional training potentially delaying milestones ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462)) +- Completed 12 issues this sprint (20.3%), despite 7 blockers, delivering closures across STT communications, knowledge center, and reporting/history improvements. (3251, 3571, 5428, 5397, 5398, 5463, 5469) + +- Moved a key technical milestone forward by placing the parsing logic refactor tech memo into Current Sprint Backlog, setting a cleaner foundation for future iterations. (5434) + +- Advanced automated testing and accessibility planning, with E2E tests for Admin Feedback Reports and Program Integrity Audit frontend and one-time accessibility work moving into Next Sprint Backlog. (5407, 5420, 5421) + +- Progressed backlog hygiene for data validation and UI guardrails, including education-level validation and SSN logic updates slated for current sprint work. (5461, 5475) --- From fd926c95f077233d5bde2d4e4ff63db26cbe8755 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 21 Nov 2025 16:53:04 -0700 Subject: [PATCH 067/148] Add sprint summary for 2025-11-18 --- sprint-summary-2025-11-18.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/sprint-summary-2025-11-18.md b/sprint-summary-2025-11-18.md index 4ba4a6555..2f6c990f4 100644 --- a/sprint-summary-2025-11-18.md +++ b/sprint-summary-2025-11-18.md @@ -2,13 +2,11 @@ ## Overview -- Completed 12 issues this sprint (20.3%), despite 7 blockers, delivering closures across STT communications, knowledge center, and reporting/history improvements. (3251, 3571, 5428, 5397, 5398, 5463, 5469) - -- Moved a key technical milestone forward by placing the parsing logic refactor tech memo into Current Sprint Backlog, setting a cleaner foundation for future iterations. (5434) - -- Advanced automated testing and accessibility planning, with E2E tests for Admin Feedback Reports and Program Integrity Audit frontend and one-time accessibility work moving into Next Sprint Backlog. (5407, 5420, 5421) - -- Progressed backlog hygiene for data validation and UI guardrails, including education-level validation and SSN logic updates slated for current sprint work. (5461, 5475) +- Significant closure momentum across critical admin/data work; multiple items moved to Done, including STT email templates, Promtail migration, FRA submission history restriction, admin 'acked'→'read', knowledge center alert banner, PIA submission history, and related data parsing fixes. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3571](https://github.com/raft-tech/TANF-app/issues/3571), [#4924](https://github.com/raft-tech/TANF-app/issues/4924), [#5397](https://github.com/raft-tech/TANF-app/issues/5397), [#5398](https://github.com/raft-tech/TANF-app/issues/5398), [#5469](https://github.com/raft-tech/TANF-app/issues/5469), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5432](https://github.com/raft-tech/TANF-app/issues/5432), [#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5418](https://github.com/raft-tech/TANF-app/issues/5418)) +- Knowledge center/UI enhancements advanced: NavBar closed; navigation work progressed to UX Review for the Knowledge Center components. ([#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5429](https://github.com/raft-tech/TANF-app/issues/5429)) +- Parsing and data handling improvements advanced: refactor parsing logic tech memo moved into Current Sprint Backlog; EXIT_MONTH parsing fix closed. ([#5434](https://github.com/raft-tech/TANF-app/issues/5434), [#5463](https://github.com/raft-tech/TANF-app/issues/5463)) +- E2E testing planning progressed: new E2E test initiatives moved from Product Backlog to Next Sprint Backlog (Program Integrity Frontend and Admin Feedback Reports). ([#5420](https://github.com/raft-tech/TANF-app/issues/5420), [#5421](https://github.com/raft-tech/TANF-app/issues/5421)) +- Post-MVP planning progressed: requirements gathering for centralized feedback reports advanced into UX Review. ([#5443](https://github.com/raft-tech/TANF-app/issues/5443)) --- From fd08c73120b791c64cc500c4a9f4dff5e47824d6 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 21 Nov 2025 16:58:55 -0700 Subject: [PATCH 068/148] Add sprint summary for 2025-11-18 --- sprint-summary-2025-11-18.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/sprint-summary-2025-11-18.md b/sprint-summary-2025-11-18.md index 2f6c990f4..af1494f5d 100644 --- a/sprint-summary-2025-11-18.md +++ b/sprint-summary-2025-11-18.md @@ -2,11 +2,13 @@ ## Overview -- Significant closure momentum across critical admin/data work; multiple items moved to Done, including STT email templates, Promtail migration, FRA submission history restriction, admin 'acked'→'read', knowledge center alert banner, PIA submission history, and related data parsing fixes. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3571](https://github.com/raft-tech/TANF-app/issues/3571), [#4924](https://github.com/raft-tech/TANF-app/issues/4924), [#5397](https://github.com/raft-tech/TANF-app/issues/5397), [#5398](https://github.com/raft-tech/TANF-app/issues/5398), [#5469](https://github.com/raft-tech/TANF-app/issues/5469), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5432](https://github.com/raft-tech/TANF-app/issues/5432), [#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5418](https://github.com/raft-tech/TANF-app/issues/5418)) -- Knowledge center/UI enhancements advanced: NavBar closed; navigation work progressed to UX Review for the Knowledge Center components. ([#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5429](https://github.com/raft-tech/TANF-app/issues/5429)) -- Parsing and data handling improvements advanced: refactor parsing logic tech memo moved into Current Sprint Backlog; EXIT_MONTH parsing fix closed. ([#5434](https://github.com/raft-tech/TANF-app/issues/5434), [#5463](https://github.com/raft-tech/TANF-app/issues/5463)) -- E2E testing planning progressed: new E2E test initiatives moved from Product Backlog to Next Sprint Backlog (Program Integrity Frontend and Admin Feedback Reports). ([#5420](https://github.com/raft-tech/TANF-app/issues/5420), [#5421](https://github.com/raft-tech/TANF-app/issues/5421)) -- Post-MVP planning progressed: requirements gathering for centralized feedback reports advanced into UX Review. ([#5443](https://github.com/raft-tech/TANF-app/issues/5443)) +- Closed a batch of admin/backend and data-validation fixes, accelerating reporting readiness. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3571](https://github.com/raft-tech/TANF-app/issues/3571), [#5368](https://github.com/raft-tech/TANF-app/issues/5368), [#5397](https://github.com/raft-tech/TANF-app/issues/5397), [#5398](https://github.com/raft-tech/TANF-app/issues/5398), [#5418](https://github.com/raft-tech/TANF-app/issues/5418), [#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469), [#4924](https://github.com/raft-tech/TANF-app/issues/4924), [#5432](https://github.com/raft-tech/TANF-app/issues/5432)) + +- Progressed Knowledge Center and UI architecture: KC NavBar item closed; refactor parsing logic moved into the current sprint backlog; Knowledge Center navigation/search advanced toward UX Review. ([#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5434](https://github.com/raft-tech/TANF-app/issues/5434), [#5429](https://github.com/raft-tech/TANF-app/issues/5429)) + +- Advanced E2E testing readiness: E2E tests for Admin Feedback Reports and Program Integrity Audit Frontend moved from Product Backlog to Next Sprint Backlog; accessibility tests enabled. ([#5420](https://github.com/raft-tech/TANF-app/issues/5420), [#5421](https://github.com/raft-tech/TANF-app/issues/5421), [#5407](https://github.com/raft-tech/TANF-app/issues/5407)) + +- 7 blockers remain, potentially impacting next sprint milestones. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462), [#3523](https://github.com/raft-tech/TANF-app/issues/3523), [#3995](https://github.com/raft-tech/TANF-app/issues/3995), [#4045](https://github.com/raft-tech/TANF-app/issues/4045), [#4047](https://github.com/raft-tech/TANF-app/issues/4047), [#5269](https://github.com/raft-tech/TANF-app/issues/5269)) --- From eba3ecad76ab1d48df6941162a9e9859cea50f8e Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 21 Nov 2025 17:21:51 -0700 Subject: [PATCH 069/148] Add sprint summary for 2025-11-18 --- sprint-summary-2025-11-18.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sprint-summary-2025-11-18.md b/sprint-summary-2025-11-18.md index af1494f5d..77b239ed9 100644 --- a/sprint-summary-2025-11-18.md +++ b/sprint-summary-2025-11-18.md @@ -2,13 +2,15 @@ ## Overview -- Closed a batch of admin/backend and data-validation fixes, accelerating reporting readiness. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3571](https://github.com/raft-tech/TANF-app/issues/3571), [#5368](https://github.com/raft-tech/TANF-app/issues/5368), [#5397](https://github.com/raft-tech/TANF-app/issues/5397), [#5398](https://github.com/raft-tech/TANF-app/issues/5398), [#5418](https://github.com/raft-tech/TANF-app/issues/5418), [#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469), [#4924](https://github.com/raft-tech/TANF-app/issues/4924), [#5432](https://github.com/raft-tech/TANF-app/issues/5432)) +- This sprint: 12 completed, 19 progressed toward done, 21 unchanged, and 7 blocked, reflecting steady throughput amid blockers. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3571](https://github.com/raft-tech/TANF-app/issues/3571), [#4924](https://github.com/raft-tech/TANF-app/issues/4924), [#5418](https://github.com/raft-tech/TANF-app/issues/5418), [#5428](https://github.com/raft-tech/TANF-app/issues/5428)) -- Progressed Knowledge Center and UI architecture: KC NavBar item closed; refactor parsing logic moved into the current sprint backlog; Knowledge Center navigation/search advanced toward UX Review. ([#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5434](https://github.com/raft-tech/TANF-app/issues/5434), [#5429](https://github.com/raft-tech/TANF-app/issues/5429)) +- Backlog movement forward: Items moved to Current Sprint Backlog, including Delete vault integration and STT Feedback UI, improving plan-to-work flow. ([#5416](https://github.com/raft-tech/TANF-app/issues/5416), [#5417](https://github.com/raft-tech/TANF-app/issues/5417)) -- Advanced E2E testing readiness: E2E tests for Admin Feedback Reports and Program Integrity Audit Frontend moved from Product Backlog to Next Sprint Backlog; accessibility tests enabled. ([#5420](https://github.com/raft-tech/TANF-app/issues/5420), [#5421](https://github.com/raft-tech/TANF-app/issues/5421), [#5407](https://github.com/raft-tech/TANF-app/issues/5407)) +- Key blockers persist: 7 items blocked; notable blockers affecting regional staff and training work. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462)) -- 7 blockers remain, potentially impacting next sprint milestones. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462), [#3523](https://github.com/raft-tech/TANF-app/issues/3523), [#3995](https://github.com/raft-tech/TANF-app/issues/3995), [#4045](https://github.com/raft-tech/TANF-app/issues/4045), [#4047](https://github.com/raft-tech/TANF-app/issues/4047), [#5269](https://github.com/raft-tech/TANF-app/issues/5269)) +- Data validation and education updates advanced into current sprint backlog, signaling progress on data integrity goals. ([#5475](https://github.com/raft-tech/TANF-app/issues/5475), [#5461](https://github.com/raft-tech/TANF-app/issues/5461)) + +- Knowledge Center UI progress: critical navigation/search work moved toward UX Review, and alert banners were completed. ([#5429](https://github.com/raft-tech/TANF-app/issues/5429), [#5398](https://github.com/raft-tech/TANF-app/issues/5398)) --- From f72449882738811f443f855a4fd2a6424d95fc33 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 21 Nov 2025 17:41:05 -0700 Subject: [PATCH 070/148] Add sprint summary for 2025-11-18 --- sprint-summary-2025-11-18.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sprint-summary-2025-11-18.md b/sprint-summary-2025-11-18.md index 77b239ed9..8756bff29 100644 --- a/sprint-summary-2025-11-18.md +++ b/sprint-summary-2025-11-18.md @@ -2,15 +2,13 @@ ## Overview -- This sprint: 12 completed, 19 progressed toward done, 21 unchanged, and 7 blocked, reflecting steady throughput amid blockers. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3571](https://github.com/raft-tech/TANF-app/issues/3571), [#4924](https://github.com/raft-tech/TANF-app/issues/4924), [#5418](https://github.com/raft-tech/TANF-app/issues/5418), [#5428](https://github.com/raft-tech/TANF-app/issues/5428)) +- Completed 12 issues this sprint, boosting momentum and delivering a batch of key closures across governance, knowledge center, and data submission workflows. ( #3251, #3258, #3571, #4924, #5368, #5428, #5432, #5463, #5469, #5398 ) -- Backlog movement forward: Items moved to Current Sprint Backlog, including Delete vault integration and STT Feedback UI, improving plan-to-work flow. ([#5416](https://github.com/raft-tech/TANF-app/issues/5416), [#5417](https://github.com/raft-tech/TANF-app/issues/5417)) +- Progressed 19 issues, with several items moving from plan/backlog into Current Sprint Backlog or In Progress, sharpening focus on critical flows. ( #5316, #5416, #5417, #5391 ) -- Key blockers persist: 7 items blocked; notable blockers affecting regional staff and training work. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462)) +- Blockers remain 7, constraining progress in regional staff onboarding, training, error reporting, and parsing tasks. ( #3461, #3462, #3523, #3995, #4045, #4047, #5269 ) -- Data validation and education updates advanced into current sprint backlog, signaling progress on data integrity goals. ([#5475](https://github.com/raft-tech/TANF-app/issues/5475), [#5461](https://github.com/raft-tech/TANF-app/issues/5461)) - -- Knowledge Center UI progress: critical navigation/search work moved toward UX Review, and alert banners were completed. ([#5429](https://github.com/raft-tech/TANF-app/issues/5429), [#5398](https://github.com/raft-tech/TANF-app/issues/5398)) +- Major initiatives gained momentum, with Knowledge Center navigation improvements closed and refactorings pushed into the current sprint backlog, signaling closer delivery. ( #5428, #5434 ) --- From 233d69f9238ee0bee68338bb970e57b122395ffb Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 21 Nov 2025 18:08:09 -0700 Subject: [PATCH 071/148] Add sprint summary for 2025-11-18 --- sprint-summary-2025-11-18.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sprint-summary-2025-11-18.md b/sprint-summary-2025-11-18.md index 8756bff29..5a4097676 100644 --- a/sprint-summary-2025-11-18.md +++ b/sprint-summary-2025-11-18.md @@ -2,13 +2,15 @@ ## Overview -- Completed 12 issues this sprint, boosting momentum and delivering a batch of key closures across governance, knowledge center, and data submission workflows. ( #3251, #3258, #3571, #4924, #5368, #5428, #5432, #5463, #5469, #5398 ) +- Delivered steady throughput: 12 issues completed this sprint, with major closures across admin feedback, knowledge center, and data parsing cleanup. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3571](https://github.com/raft-tech/TANF-app/issues/3571), [#5397](https://github.com/raft-tech/TANF-app/issues/5397), [#5398](https://github.com/raft-tech/TANF-app/issues/5398), [#5418](https://github.com/raft-tech/TANF-app/issues/5418), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469), [#5432](https://github.com/raft-tech/TANF-app/issues/5432), [#5428](https://github.com/raft-tech/TANF-app/issues/5428)) -- Progressed 19 issues, with several items moving from plan/backlog into Current Sprint Backlog or In Progress, sharpening focus on critical flows. ( #5316, #5416, #5417, #5391 ) +- Moved key initiatives from backlog into current/future sprint planning to lock in upcoming deliverables. ([#5476](https://github.com/raft-tech/TANF-app/issues/5476), [#5461](https://github.com/raft-tech/TANF-app/issues/5461), [#5443](https://github.com/raft-tech/TANF-app/issues/5443)) -- Blockers remain 7, constraining progress in regional staff onboarding, training, error reporting, and parsing tasks. ( #3461, #3462, #3523, #3995, #4045, #4047, #5269 ) +- Persistent blockers slowed progress on regional staff initiatives and infrastructure upgrades, with several items staying Blocked across transitions. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462), [#3523](https://github.com/raft-tech/TANF-app/issues/3523), [#5269](https://github.com/raft-tech/TANF-app/issues/5269)) -- Major initiatives gained momentum, with Knowledge Center navigation improvements closed and refactorings pushed into the current sprint backlog, signaling closer delivery. ( #5428, #5434 ) +- Advanced testing and training work: e2e tests planning for Admin Feedback Reports and accessibility tests progressed; training materials planning advanced toward Next Sprint Backlog. ([#5407](https://github.com/raft-tech/TANF-app/issues/5407), [#5420](https://github.com/raft-tech/TANF-app/issues/5420), [#5421](https://github.com/raft-tech/TANF-app/issues/5421)) + +- Knowledge Center and reporting improvements moved forward with UI cleanups and closeouts, including a closed KC NavBar item and government shutdown alert. ([#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5398](https://github.com/raft-tech/TANF-app/issues/5398)) --- From 557e33e001fa9a4e118da36cfec61d36adc9d78f Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 21 Nov 2025 18:12:23 -0700 Subject: [PATCH 072/148] Add sprint summary for 2025-11-18 --- sprint-summary-2025-11-18.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sprint-summary-2025-11-18.md b/sprint-summary-2025-11-18.md index 5a4097676..7cb4db15d 100644 --- a/sprint-summary-2025-11-18.md +++ b/sprint-summary-2025-11-18.md @@ -2,15 +2,13 @@ ## Overview -- Delivered steady throughput: 12 issues completed this sprint, with major closures across admin feedback, knowledge center, and data parsing cleanup. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3571](https://github.com/raft-tech/TANF-app/issues/3571), [#5397](https://github.com/raft-tech/TANF-app/issues/5397), [#5398](https://github.com/raft-tech/TANF-app/issues/5398), [#5418](https://github.com/raft-tech/TANF-app/issues/5418), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469), [#5432](https://github.com/raft-tech/TANF-app/issues/5432), [#5428](https://github.com/raft-tech/TANF-app/issues/5428)) +- Several high-impact items reached Closed this sprint, accelerating admin, data, and Knowledge Center work: Feedback Reports Backend, KC shutdown alert banner, FRA submission history tightening, EXIT_MONTH parsing fix, admin "acked"→"read", KC/Nav cleanup, and related data-file refresh work. ([#5397](https://github.com/raft-tech/TANF-app/issues/5397), [#5398](https://github.com/raft-tech/TANF-app/issues/5398), [#5469](https://github.com/raft-tech/TANF-app/issues/5469), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5432](https://github.com/raft-tech/TANF-app/issues/5432), [#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5418](https://github.com/raft-tech/TANF-app/issues/5418), [#4924](https://github.com/raft-tech/TANF-app/issues/4924), [#3258](https://github.com/raft-tech/TANF-app/issues/3258)) -- Moved key initiatives from backlog into current/future sprint planning to lock in upcoming deliverables. ([#5476](https://github.com/raft-tech/TANF-app/issues/5476), [#5461](https://github.com/raft-tech/TANF-app/issues/5461), [#5443](https://github.com/raft-tech/TANF-app/issues/5443)) +- E2E testing scope expanded into this sprint by moving two test-focused items into the sprint backlog and kicking off scaffolding for the Program Integrity Audit frontend and Admin Feedback Reports tests. ([#5420](https://github.com/raft-tech/TANF-app/issues/5420), [#5421](https://github.com/raft-tech/TANF-app/issues/5421)) -- Persistent blockers slowed progress on regional staff initiatives and infrastructure upgrades, with several items staying Blocked across transitions. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462), [#3523](https://github.com/raft-tech/TANF-app/issues/3523), [#5269](https://github.com/raft-tech/TANF-app/issues/5269)) +- Knowledge Center UX progress: the full navigation and search implementation advanced to UX Review, and KC NavBar item work reached completion. ([#5429](https://github.com/raft-tech/TANF-app/issues/5429), [#5428](https://github.com/raft-tech/TANF-app/issues/5428)) -- Advanced testing and training work: e2e tests planning for Admin Feedback Reports and accessibility tests progressed; training materials planning advanced toward Next Sprint Backlog. ([#5407](https://github.com/raft-tech/TANF-app/issues/5407), [#5420](https://github.com/raft-tech/TANF-app/issues/5420), [#5421](https://github.com/raft-tech/TANF-app/issues/5421)) - -- Knowledge Center and reporting improvements moved forward with UI cleanups and closeouts, including a closed KC NavBar item and government shutdown alert. ([#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5398](https://github.com/raft-tech/TANF-app/issues/5398)) +- Blockers persist for regional staff onboarding work (Project Updates meeting and optional training sessions), posing risk to timing and related deliverables. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462)) --- From efdf495bf44cc3fd87ab1866131ad202d2b09403 Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 21 Nov 2025 18:16:54 -0700 Subject: [PATCH 073/148] Add sprint summary for 2025-11-18 --- sprint-summary-2025-11-18.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/sprint-summary-2025-11-18.md b/sprint-summary-2025-11-18.md index 7cb4db15d..004e1f31f 100644 --- a/sprint-summary-2025-11-18.md +++ b/sprint-summary-2025-11-18.md @@ -2,13 +2,11 @@ ## Overview -- Several high-impact items reached Closed this sprint, accelerating admin, data, and Knowledge Center work: Feedback Reports Backend, KC shutdown alert banner, FRA submission history tightening, EXIT_MONTH parsing fix, admin "acked"→"read", KC/Nav cleanup, and related data-file refresh work. ([#5397](https://github.com/raft-tech/TANF-app/issues/5397), [#5398](https://github.com/raft-tech/TANF-app/issues/5398), [#5469](https://github.com/raft-tech/TANF-app/issues/5469), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5432](https://github.com/raft-tech/TANF-app/issues/5432), [#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5418](https://github.com/raft-tech/TANF-app/issues/5418), [#4924](https://github.com/raft-tech/TANF-app/issues/4924), [#3258](https://github.com/raft-tech/TANF-app/issues/3258)) - -- E2E testing scope expanded into this sprint by moving two test-focused items into the sprint backlog and kicking off scaffolding for the Program Integrity Audit frontend and Admin Feedback Reports tests. ([#5420](https://github.com/raft-tech/TANF-app/issues/5420), [#5421](https://github.com/raft-tech/TANF-app/issues/5421)) - -- Knowledge Center UX progress: the full navigation and search implementation advanced to UX Review, and KC NavBar item work reached completion. ([#5429](https://github.com/raft-tech/TANF-app/issues/5429), [#5428](https://github.com/raft-tech/TANF-app/issues/5428)) - -- Blockers persist for regional staff onboarding work (Project Updates meeting and optional training sessions), posing risk to timing and related deliverables. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462)) +- Closed key compliance/QA items: updated STT data submission emails to reflect status and errors; migrated Promtail to Alloy; FRA submission history restricted to participating STTs. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3571](https://github.com/raft-tech/TANF-app/issues/3571)) +- UX/Knowledge Center momentum: KC NavBar item closed and Knowledge Center navigation & search advanced toward UX Review. ([#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5429](https://github.com/raft-tech/TANF-app/issues/5429)) +- Moved four items from Product Backlog to Current Sprint Backlog to prep for active sprint: parsing cleanup, SSN_VALID logic, education validation, SSP item 41 validation. ([#5434](https://github.com/raft-tech/TANF-app/issues/5434), [#5475](https://github.com/raft-tech/TANF-app/issues/5475), [#5461](https://github.com/raft-tech/TANF-app/issues/5461), [#5473](https://github.com/raft-tech/TANF-app/issues/5473)) +- Blockers slowed progress; several regional initiatives remain blocked, including two regional staff training items. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462)) +- E2E and admin testing progressed: a11y e2e tests in progress and admin feedback tests moved to Next Sprint Backlog. ([#5407](https://github.com/raft-tech/TANF-app/issues/5407), [#5421](https://github.com/raft-tech/TANF-app/issues/5421)) --- From 0d0a61e23b413d4de3628c2455b7107104fdbace Mon Sep 17 00:00:00 2001 From: Kenny McNett Date: Fri, 21 Nov 2025 18:21:23 -0700 Subject: [PATCH 074/148] Add sprint summary for 2025-11-18 --- sprint-summary-2025-11-18.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/sprint-summary-2025-11-18.md b/sprint-summary-2025-11-18.md index 004e1f31f..d6e7a8a52 100644 --- a/sprint-summary-2025-11-18.md +++ b/sprint-summary-2025-11-18.md @@ -2,11 +2,15 @@ ## Overview -- Closed key compliance/QA items: updated STT data submission emails to reflect status and errors; migrated Promtail to Alloy; FRA submission history restricted to participating STTs. ([#3251](https://github.com/raft-tech/TANF-app/issues/3251), [#3258](https://github.com/raft-tech/TANF-app/issues/3258), [#3571](https://github.com/raft-tech/TANF-app/issues/3571)) -- UX/Knowledge Center momentum: KC NavBar item closed and Knowledge Center navigation & search advanced toward UX Review. ([#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5429](https://github.com/raft-tech/TANF-app/issues/5429)) -- Moved four items from Product Backlog to Current Sprint Backlog to prep for active sprint: parsing cleanup, SSN_VALID logic, education validation, SSP item 41 validation. ([#5434](https://github.com/raft-tech/TANF-app/issues/5434), [#5475](https://github.com/raft-tech/TANF-app/issues/5475), [#5461](https://github.com/raft-tech/TANF-app/issues/5461), [#5473](https://github.com/raft-tech/TANF-app/issues/5473)) -- Blockers slowed progress; several regional initiatives remain blocked, including two regional staff training items. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462)) -- E2E and admin testing progressed: a11y e2e tests in progress and admin feedback tests moved to Next Sprint Backlog. ([#5407](https://github.com/raft-tech/TANF-app/issues/5407), [#5421](https://github.com/raft-tech/TANF-app/issues/5421)) +- Moved multiple planning items into active work and began implementation: #5316 into Current Sprint Backlog; #5417 STT Feedback Report UI into Current Sprint Backlog; #5434 parsing logic refactor into Current Sprint Backlog. ([#5316](https://github.com/raft-tech/TANF-app/issues/5316), [#5417](https://github.com/raft-tech/TANF-app/issues/5417), [#5434](https://github.com/raft-tech/TANF-app/issues/5434)) + +- Closed a number of backlog items this sprint, advancing cleanup and reducing risk: #3571, #5463, #5469, #5428, #5432, #5418. ([#3571](https://github.com/raft-tech/TANF-app/issues/3571), [#5463](https://github.com/raft-tech/TANF-app/issues/5463), [#5469](https://github.com/raft-tech/TANF-app/issues/5469), [#5428](https://github.com/raft-tech/TANF-app/issues/5428), [#5432](https://github.com/raft-tech/TANF-app/issues/5432), [#5418](https://github.com/raft-tech/TANF-app/issues/5418)) + +- Blockers remain for regional staff initiatives, signaling velocity risk on high-priority work: #3461, #3462. ([#3461](https://github.com/raft-tech/TANF-app/issues/3461), [#3462](https://github.com/raft-tech/TANF-app/issues/3462)) + +- UX/Knowledge Center progress: Knowledge Center navigation advanced toward UX Review and related UI work ongoing: #5429 to UX Review; #5390 ongoing. ([#5429](https://github.com/raft-tech/TANF-app/issues/5429), [#5390](https://github.com/raft-tech/TANF-app/issues/5390)) + +- Data validation, parsing, and education-level validation items moved into Current Sprint Backlog to accelerate fixes: #5473, #5475, #5461. ([#5473](https://github.com/raft-tech/TANF-app/issues/5473), [#5475](https://github.com/raft-tech/TANF-app/issues/5475), [#5461](https://github.com/raft-tech/TANF-app/issues/5461)) --- From 90f5b027c3658c276fd6dfb4ac5061d25db627f7 Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Tue, 10 Feb 2026 07:18:39 -0600 Subject: [PATCH 075/148] add local cache config --- tdrs-backend/tdpservice/settings/common.py | 20 ++++++++++++++++++-- tdrs-backend/tdpservice/settings/local.py | 5 +++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/tdrs-backend/tdpservice/settings/common.py b/tdrs-backend/tdpservice/settings/common.py index 4695d567d..b3a5cb813 100644 --- a/tdrs-backend/tdpservice/settings/common.py +++ b/tdrs-backend/tdpservice/settings/common.py @@ -518,8 +518,8 @@ class Common(Configuration): REDIS_URI = os.getenv("REDIS_URI", "redis://redis-server:6379") logger.debug("REDIS_URI: " + REDIS_URI) - CELERY_BROKER_URL = REDIS_URI - CELERY_RESULT_BACKEND = REDIS_URI + CELERY_BROKER_URL = REDIS_URI + "/0" + CELERY_RESULT_BACKEND = REDIS_URI + "/0" ## should they be the same? CELERY_ACCEPT_CONTENT = ["application/json"] CELERY_TASK_SERIALIZER = "json" CELERY_RESULT_SERIALIZER = "json" @@ -622,6 +622,22 @@ class Common(Configuration): }, } + DEFAULT_CACHE_TIMEOUT = 300 + CACHES = { + "default": { + "BACKEND": "django.core.cache.backends.redis.RedisCache", + "LOCATION": f"{REDIS_URI}/1", + }, + "stts": { + "BACKEND": "django.core.cache.backends.redis.RedisCache", + "LOCATION": f"{REDIS_URI}/2", + }, + "feature-flags": { + "BACKEND": "django.core.cache.backends.redis.RedisCache", + "LOCATION": f"{REDIS_URI}/3", + }, + } + CYPRESS_TOKEN = os.getenv("CYPRESS_TOKEN", None) FIXTURE_DIRS = [os.path.join(BASE_DIR, "fixtures")] diff --git a/tdrs-backend/tdpservice/settings/local.py b/tdrs-backend/tdpservice/settings/local.py index f8312f08f..3378c50ec 100644 --- a/tdrs-backend/tdpservice/settings/local.py +++ b/tdrs-backend/tdpservice/settings/local.py @@ -1,4 +1,5 @@ """Define configuration settings for local environment.""" + import os from distutils.util import strtobool @@ -55,3 +56,7 @@ class Local(Common): ) SENTRY_DSN = None + + Common.CACHES["default"]["KEY_PREFIX"] = "local" + Common.CACHES["stts"]["KEY_PREFIX"] = "local" + Common.CACHES["feature-flags"]["KEY_PREFIX"] = "local" From 91890ea5129ff337eb53bb1f35d162f4840d8da1 Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Tue, 10 Feb 2026 07:19:01 -0600 Subject: [PATCH 076/148] configure stts/alpha endpoint caching --- tdrs-backend/tdpservice/stts/test/test_api.py | 131 +++++++++++++++--- tdrs-backend/tdpservice/stts/views.py | 11 ++ 2 files changed, 120 insertions(+), 22 deletions(-) diff --git a/tdrs-backend/tdpservice/stts/test/test_api.py b/tdrs-backend/tdpservice/stts/test/test_api.py index 87f17ae91..16eeb9334 100644 --- a/tdrs-backend/tdpservice/stts/test/test_api.py +++ b/tdrs-backend/tdpservice/stts/test/test_api.py @@ -1,11 +1,19 @@ """API STT Tests.""" + +from unittest.mock import MagicMock, patch + from django.contrib.auth import get_user_model +from django.core.cache import caches +from django.test import TestCase, override_settings from django.urls import reverse import pytest from rest_framework import status +from rest_framework.test import APIClient +from tdpservice.conftest import UserFactory from tdpservice.stts.models import STT, Region +from tdpservice.stts.views import STTApiAlphaView User = get_user_model() @@ -67,28 +75,6 @@ def test_can_get_stts(api_client, stt_user, stts): assert STT.objects.filter(name=state_name).exists() -@pytest.mark.django_db -def test_can_get_alpha_stts(api_client, stt_user, stts): - """Test endpoint returns the alphabetized listing of STTs.""" - api_client.login(username=stt_user.username, password="test_password") - response = api_client.get(reverse("stts-alpha")) - assert response.status_code == status.HTTP_200_OK - assert len(response.data) == STT.objects.count() - - state_name = response.data[0]["name"] - assert STT.objects.filter(name=state_name).exists() - - -@pytest.mark.django_db -def test_alpha_stts_is_sorted(api_client, stt_user, stts): - """Test alphabetized endpoint is alphabetized.""" - api_client.login(username=stt_user.username, password="test_password") - response = api_client.get(reverse("stts-alpha")) - response_names = [datum["name"] for datum in response.data] - database_names = STT.objects.values_list("name", flat=True).order_by("name") - assert response_names == list(database_names) - - @pytest.mark.django_db def test_can_get_by_region_stts(api_client, stt_user, stts): """Test endpoint returns the alphabetized listing of STTs.""" @@ -115,9 +101,110 @@ def test_can_get_by_region_stts(api_client, stt_user, stts): @pytest.mark.django_db +@override_settings( + CACHES={ + "stts": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "LOCATION": "unique-test-cache-location", # Unique location to avoid conflicts + "KEY_PREFIX": "test", + }, + } +) def test_stts_and_stts_alpha_are_dissimilar(api_client, stt_user, stts): """The default STTs endpoint is not sorted the same as the alpha.""" api_client.login(username=stt_user.username, password="test_password") alpha_response = api_client.get(reverse("stts-alpha")) default_response = api_client.get(reverse("stts")) assert not alpha_response.data == default_response.data + + +@pytest.mark.django_db +@override_settings( + CACHES={ + "stts": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "LOCATION": "unique-test-cache-location", # Unique location to avoid conflicts + "KEY_PREFIX": "test", + }, + } +) +def test_can_get_alpha_stts(api_client, stt_user, stts): + """Test endpoint returns the alphabetized listing of STTs.""" + api_client.login(username=stt_user.username, password="test_password") + response = api_client.get(reverse("stts-alpha")) + assert response.status_code == status.HTTP_200_OK + assert len(response.data) == STT.objects.count() + + state_name = response.data[0]["name"] + assert STT.objects.filter(name=state_name).exists() + + +@pytest.mark.django_db +@override_settings( + CACHES={ + "stts": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "LOCATION": "unique-test-cache-location", # Unique location to avoid conflicts + "KEY_PREFIX": "test", + }, + } +) +def test_alpha_stts_is_sorted(api_client, stt_user, stts): + """Test alphabetized endpoint is alphabetized.""" + api_client.login(username=stt_user.username, password="test_password") + response = api_client.get(reverse("stts-alpha")) + response_names = [datum["name"] for datum in response.data] + database_names = STT.objects.values_list("name", flat=True).order_by("name") + assert response_names == list(database_names) + + +@override_settings( + CACHES={ + "stts": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "LOCATION": "unique-test-cache-location", # Unique location to avoid conflicts + "KEY_PREFIX": "test", + }, + } +) +class TestSTTApiAlphaViewCache(TestCase): + """Tests for the STTApiAlphaView class.""" + + api_client = APIClient() + + def setUp(self): + super().setUp() + cache = caches["stts"] + cache.clear() + + user = UserFactory.create() + self.api_client.login(username=user.username, password="test_password") + + def test_existing_cache_avoids_lookup(self): + """Test that no lookup is performed if flags exist in the cache.""" + mock_queryset = MagicMock() + with patch.object( + STTApiAlphaView, "get_queryset", return_value=mock_queryset + ) as mock_method: + # request and check the cache was cold + response = self.api_client.get(reverse("stts-alpha")) + assert response.status_code == status.HTTP_200_OK + assert mock_method.called + + mock_method.reset_mock() + + # the cache should be warm now, request again + response = self.api_client.get(reverse("stts-alpha")) + assert response.status_code == status.HTTP_200_OK + assert not mock_method.called + + def test_no_cache_forces_lookup(self): + """Test that a lookup is performed if there are no flags in the cache.""" + mock_queryset = MagicMock() + with patch.object( + STTApiAlphaView, "get_queryset", return_value=mock_queryset + ) as mock_method: + # request and check the cache was cold + response = self.api_client.get(reverse("stts-alpha")) + assert response.status_code == status.HTTP_200_OK + assert mock_method.called diff --git a/tdrs-backend/tdpservice/stts/views.py b/tdrs-backend/tdpservice/stts/views.py index c58efc5d0..dc9decb4d 100644 --- a/tdrs-backend/tdpservice/stts/views.py +++ b/tdrs-backend/tdpservice/stts/views.py @@ -1,7 +1,11 @@ """Define API views for user class.""" + import logging +from django.conf import settings from django.db.models import Prefetch +from django.utils.decorators import method_decorator +from django.views.decorators.cache import cache_page from rest_framework import generics from rest_framework.permissions import IsAuthenticated @@ -32,6 +36,13 @@ class STTApiAlphaView(generics.ListAPIView): queryset = STT.objects.order_by("name") serializer_class = STTSerializer + @method_decorator( + cache_page(settings.DEFAULT_CACHE_TIMEOUT, cache="stts", key_prefix="alpha") + ) + def list(self, request): + """Get the stt list from the cache if available, else fetch the queryset.""" + return super().list(request) + class STTApiView(generics.ListAPIView): """Simple view to get all STTs.""" From eb4ca0cb98a06bfd6992f00083806b4354d01443 Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Tue, 10 Feb 2026 07:19:36 -0600 Subject: [PATCH 077/148] add cached feature flag endpoint --- tdrs-backend/tdpservice/core/serializers.py | 22 +++++ tdrs-backend/tdpservice/core/test/test_api.py | 89 +++++++++++++++++++ tdrs-backend/tdpservice/core/views.py | 29 ++++++ tdrs-backend/tdpservice/urls.py | 3 +- 4 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 tdrs-backend/tdpservice/core/serializers.py diff --git a/tdrs-backend/tdpservice/core/serializers.py b/tdrs-backend/tdpservice/core/serializers.py new file mode 100644 index 000000000..48764e959 --- /dev/null +++ b/tdrs-backend/tdpservice/core/serializers.py @@ -0,0 +1,22 @@ +"""Serialize core model data.""" + +from rest_framework import serializers + +from tdpservice.core.models import FeatureFlag + + +class FeatureFlagSerializer(serializers.ModelSerializer): + """FeatureFlag serializer.""" + + class Meta: + """Metadata.""" + + model = FeatureFlag + fields = [ + "feature_name", + "enabled", + "config", + "description", + "created_at", + "updated_at", + ] diff --git a/tdrs-backend/tdpservice/core/test/test_api.py b/tdrs-backend/tdpservice/core/test/test_api.py index 7896da5db..148af698b 100644 --- a/tdrs-backend/tdpservice/core/test/test_api.py +++ b/tdrs-backend/tdpservice/core/test/test_api.py @@ -1,12 +1,21 @@ """Core API tests.""" + import uuid +from unittest.mock import MagicMock, patch from django.contrib.admin.models import LogEntry from django.contrib.contenttypes.models import ContentType +from django.core.cache import caches +from django.test import TestCase, override_settings +from django.urls import reverse import pytest from rest_framework import status +from rest_framework.test import APIClient +from tdpservice.conftest import UserFactory +from tdpservice.core.models import FeatureFlag +from tdpservice.core.views import FeatureFlagView from tdpservice.data_files.models import DataFile @@ -78,3 +87,83 @@ def test_log_entry_creation(api_client, data_file_instance): content_type_id=ContentType.objects.get_for_model(DataFile).pk, object_id=data_file_instance.pk, ).exists() + + +@override_settings( + CACHES={ + "feature-flags": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "LOCATION": "unique-test-cache-location", # Unique location to avoid conflicts + "KEY_PREFIX": "test", + }, + } +) +class TestFeatureFlagView(TestCase): + """Tests for the FeatureFlagView class.""" + + api_client = APIClient() + + def setUp(self): + super().setUp() + cache = caches["feature-flags"] + cache.clear() + + user = UserFactory.create() + self.api_client.login(username=user.username, password="test_password") + + def test_existing_cache_avoids_lookup(self): + """Test that no lookup is performed if flags exist in the cache.""" + mock_queryset = MagicMock() + with patch.object( + FeatureFlagView, "get_queryset", return_value=mock_queryset + ) as mock_method: + # request and check the cache was cold + response = self.api_client.get(reverse("feature-flags")) + assert response.status_code == status.HTTP_200_OK + assert mock_method.called + + mock_method.reset_mock() + + # the cache should be warm now, request again + response = self.api_client.get(reverse("feature-flags")) + assert response.status_code == status.HTTP_200_OK + assert not mock_method.called + + def test_no_cache_forces_lookup(self): + """Test that a lookup is performed if there are no flags in the cache.""" + mock_queryset = MagicMock() + with patch.object( + FeatureFlagView, "get_queryset", return_value=mock_queryset + ) as mock_method: + # request and check the cache was cold + response = self.api_client.get(reverse("feature-flags")) + assert response.status_code == status.HTTP_200_OK + assert mock_method.called + + def test_saving_flag_invalidates_cache(self): + """Test saving a feature flag invalidates existing cache.""" + mock_queryset = MagicMock() + with patch.object( + FeatureFlagView, "get_queryset", return_value=mock_queryset + ) as mock_method: + # request and check the cache was cold + response = self.api_client.get(reverse("feature-flags")) + assert response.status_code == status.HTTP_200_OK + assert mock_method.called + + mock_method.reset_mock() + + # the cache should be warm now, request again + response = self.api_client.get(reverse("feature-flags")) + assert response.status_code == status.HTTP_200_OK + assert not mock_method.called + + mock_method.reset_mock() + + # create a new feature flag + FeatureFlag.objects.create(feature_name="unit-test") + + # check that the cache was invalidated + response = self.api_client.get(reverse("feature-flags")) + assert response.status_code == status.HTTP_200_OK + assert mock_method.called diff --git a/tdrs-backend/tdpservice/core/views.py b/tdrs-backend/tdpservice/core/views.py index fb1e2f323..c9a4ea511 100644 --- a/tdrs-backend/tdpservice/core/views.py +++ b/tdrs-backend/tdpservice/core/views.py @@ -1,13 +1,20 @@ """Define core, generic views of the app.""" + import logging +from django.conf import settings from django.contrib.admin.models import ADDITION, LogEntry from django.contrib.contenttypes.models import ContentType +from django.utils.decorators import method_decorator +from django.views.decorators.cache import cache_page +from rest_framework import generics from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response +from tdpservice.core.models import FeatureFlag +from tdpservice.core.serializers import FeatureFlagSerializer from tdpservice.data_files.models import DataFile logger = logging.getLogger() @@ -56,3 +63,25 @@ def write_logs(request): ) return Response("Success") + + +class FeatureFlagView(generics.ListAPIView): + """Simple view to get all STTs alphabetized.""" + + pagination_class = None + permission_classes = [] # [IsAuthenticated] + queryset = FeatureFlag.objects.all() + serializer_class = FeatureFlagSerializer + + @method_decorator( + [ + cache_page( + settings.DEFAULT_CACHE_TIMEOUT, + cache="feature-flags", + key_prefix="list", + ), + ] + ) + def list(self, request): + """Get the feature flag list from the cache if available, else fetch the queryset.""" + return super().list(request) diff --git a/tdrs-backend/tdpservice/urls.py b/tdrs-backend/tdpservice/urls.py index fe46f8c08..4867b2abe 100755 --- a/tdrs-backend/tdpservice/urls.py +++ b/tdrs-backend/tdpservice/urls.py @@ -12,7 +12,7 @@ from drf_yasg.views import get_schema_view from rest_framework.permissions import AllowAny -from .core.views import write_logs +from .core.views import FeatureFlagView, write_logs from .users.api.authorization_check import AuthorizationCheck, PlgAuthorizationCheck from .users.api.login import ( CypressLoginDotGovAuthenticationOverride, @@ -43,6 +43,7 @@ path("data_files/", include("tdpservice.data_files.urls")), path("reports/", include("tdpservice.reports.urls")), path("logs/", write_logs), + path("feature-flags/", FeatureFlagView.as_view(), name="feature-flags"), path("security/", include("tdpservice.security.urls")), ] From 72dc8bf5c980c567ec76d7bbab362a32be7ec2dc Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Tue, 10 Feb 2026 07:19:52 -0600 Subject: [PATCH 078/148] clear feature flag cache on model updates --- tdrs-backend/tdpservice/core/models.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/tdrs-backend/tdpservice/core/models.py b/tdrs-backend/tdpservice/core/models.py index 254c8e4c7..a2ceca953 100644 --- a/tdrs-backend/tdpservice/core/models.py +++ b/tdrs-backend/tdpservice/core/models.py @@ -2,7 +2,10 @@ from django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType +from django.core.cache import caches from django.db import models +from django.db.models.signals import post_delete, post_migrate, post_save +from django.dispatch import receiver class FeatureFlag(models.Model): @@ -11,9 +14,9 @@ class FeatureFlag(models.Model): class Meta: """Metadata.""" - ordering = ['feature_name'] - verbose_name = 'Feature Flag' - verbose_name_plural = 'Feature Flags' + ordering = ["feature_name"] + verbose_name = "Feature Flag" + verbose_name_plural = "Feature Flags" feature_name = models.CharField(max_length=100, unique=True, db_index=True) enabled = models.BooleanField(default=False) @@ -28,6 +31,19 @@ def __str__(self) -> str: return f"{self.feature_name} ({status})" +@receiver([post_delete, post_migrate, post_save], sender=FeatureFlag) +def clear_feature_flag_cache(sender, instance, **kwargs): + """ + FeatureFlag post-save signal invalidates the cache after any changes to feature flags. + + This depends on the cache being separated by feature, so the entire cache can be deleted. + There are too many options for headers/cookies to determine the key programatically, + so we segment the different featuers into separate caches to be able to invalidate efficiently + """ + cache = caches["feature-flags"] + cache.clear() + + """Global permissions Allows for the creation of permissions that are From ee636f5aa183a2eeedad147e26d193acd90f0d9a Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Tue, 10 Feb 2026 07:24:29 -0600 Subject: [PATCH 079/148] cloudgov cache settings --- tdrs-backend/tdpservice/settings/cloudgov.py | 49 +++++++++++++------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/tdrs-backend/tdpservice/settings/cloudgov.py b/tdrs-backend/tdpservice/settings/cloudgov.py index 52f8f2897..7cc6bb377 100644 --- a/tdrs-backend/tdpservice/settings/cloudgov.py +++ b/tdrs-backend/tdpservice/settings/cloudgov.py @@ -27,26 +27,31 @@ def get_cloudgov_service_creds_by_instance_name(services, instance_name): ) +cache_options = ["default", "stts", "feature-flags"] + + def get_cloudgov_broker_db_numbers(cloudgov_name): """ Get the appropriate redis broker db numbers for an environment. - Returns a tuple of (broker_db_number, results_db_number) + Returns a tuple of (celery_broker_db_number: int, cache_db_numbers: Iterable[int]) """ - match cloudgov_name: - case "raft": - return ("0", "1") - case "qasp": - return ("2", "3") - case "a11y": - return ("4", "5") - case "develop": - return ("0", "1") - case "staging": - return ("2", "3") - case "prod": - return ("0", "1") - return ("0", "1") + incr = 0 + envs = ["raft", "qasp", "a11y", "develop", "staging", "prod"] + + broker_nums = {} + + for env in envs: + celery = incr + caches = {} + for c in cache_options: + incr += 1 + caches[c] = incr + + broker_nums[env] = (celery, caches) + incr += 1 + + return broker_nums[cloudgov_name] class CloudGov(Common): @@ -166,12 +171,20 @@ class CloudGov(Common): redis_settings = cloudgov_services["aws-elasticache-redis"][0]["credentials"] REDIS_URI = f"rediss://:{redis_settings['password']}@{redis_settings['host']}:{redis_settings['port']}" - (broker_db_number, results_db_number) = get_cloudgov_broker_db_numbers( + (celery_broker_db_number, cache_db_numbers) = get_cloudgov_broker_db_numbers( cloudgov_name ) - CELERY_BROKER_URL = REDIS_URI + "/" + broker_db_number - CELERY_RESULT_BACKEND = REDIS_URI + "/" + broker_db_number + CELERY_BROKER_URL = REDIS_URI + "/" + celery_broker_db_number + CELERY_RESULT_BACKEND = REDIS_URI + "/" + celery_broker_db_number # ?? + CACHES = {} + + for c, n in cache_db_numbers: + CACHES[c] = { + "BACKEND": "django.core.cache.backends.redis.RedisCache", + "LOCATION": f"{REDIS_URI}/{n}", + "KEY_PREFIX": cloudgov_name, # does include "prod" for prod, can specify per env in classes below + } OTEL_EXPORTER_OTLP_ENDPOINT = os.getenv( "OTEL_EXPORTER_OTLP_ENDPOINT", "http://tempo.apps.internal:4317" From 48acde3c15fde832edbfc9fc2e7645a4a5913087 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Tue, 10 Feb 2026 11:31:28 -0600 Subject: [PATCH 080/148] - Update task to use fiscal year and quarter since thats what datafiles store - Added logic to include program type in the search for submitted file types --- tdrs-backend/tdpservice/email/tasks.py | 28 +++++++++++++++----------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/tdrs-backend/tdpservice/email/tasks.py b/tdrs-backend/tdpservice/email/tasks.py index 94337e6ea..8b339f807 100644 --- a/tdrs-backend/tdpservice/email/tasks.py +++ b/tdrs-backend/tdpservice/email/tasks.py @@ -173,8 +173,7 @@ def email_admin_num_access_requests(): @shared_task def send_data_submission_reminder(due_date, reporting_period, fiscal_quarter): """Send all Data Analysts a reminder to submit if they have not already.""" - now = datetime.now() - fiscal_year = calendar_to_fiscal(now.year, fiscal_quarter) + fiscal_year = datetime.now().year all_locations = STT.objects.all() @@ -185,19 +184,24 @@ def send_data_submission_reminder(due_date, reporting_period, fiscal_quarter): ) for loc in all_locations: - submitted_sections = ( + submitted_programs_sections = ( year_quarter_files.filter(stt=loc) - .values_list("section", flat=True) + .values_list("program_type", "section") .distinct() ) - required_sections = loc.filenames.keys() - - submitted_all_sections = True - for s in required_sections: - if s not in submitted_sections: - submitted_all_sections = False - - if not submitted_all_sections: + + submitted_programs_sections = [f"{ps[0]} {ps[1]}".upper() for ps in submitted_programs_sections] + + required_program_sections = loc.filenames.keys() + required_program_sections = [ps.upper() for ps in required_program_sections] + + submitted_all_programs_sections = True + for ps in required_program_sections: + if ps not in submitted_programs_sections: + submitted_all_programs_sections = False + break + + if not submitted_all_programs_sections: reminder_locations.append(loc) template_path = DataFileEmail.UPCOMING_SUBMISSION_DEADLINE.value From 379fcdaa2b6a9c09b6f4024029ef6855d53e5bfb Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Tue, 10 Feb 2026 11:31:48 -0600 Subject: [PATCH 081/148] - remove unused import --- tdrs-backend/tdpservice/email/tasks.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tdrs-backend/tdpservice/email/tasks.py b/tdrs-backend/tdpservice/email/tasks.py index 8b339f807..7e57b76c4 100644 --- a/tdrs-backend/tdpservice/email/tasks.py +++ b/tdrs-backend/tdpservice/email/tasks.py @@ -22,7 +22,6 @@ send_deactivation_warning_email, ) from tdpservice.email.helpers.admin_notifications import email_admin_deactivated_user -from tdpservice.parsers.util import calendar_to_fiscal from tdpservice.stts.models import STT from tdpservice.users.models import ( AccountApprovalStatusChoices, From e53a3f10651bbc1f97e0746845064213a89b7611 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Tue, 10 Feb 2026 11:32:43 -0600 Subject: [PATCH 082/148] - linting --- tdrs-backend/tdpservice/email/tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tdrs-backend/tdpservice/email/tasks.py b/tdrs-backend/tdpservice/email/tasks.py index 7e57b76c4..5eb676bec 100644 --- a/tdrs-backend/tdpservice/email/tasks.py +++ b/tdrs-backend/tdpservice/email/tasks.py @@ -188,9 +188,9 @@ def send_data_submission_reminder(due_date, reporting_period, fiscal_quarter): .values_list("program_type", "section") .distinct() ) - + submitted_programs_sections = [f"{ps[0]} {ps[1]}".upper() for ps in submitted_programs_sections] - + required_program_sections = loc.filenames.keys() required_program_sections = [ps.upper() for ps in required_program_sections] From 471eae6d4c410aa74171c3cd7b436c50cf74bcaa Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Tue, 10 Feb 2026 12:03:37 -0600 Subject: [PATCH 083/148] - Update and add tests for email reminder logic --- .../test/test_upcoming_deadline_email.py | 316 +++++++++++++----- 1 file changed, 235 insertions(+), 81 deletions(-) diff --git a/tdrs-backend/tdpservice/email/test/test_upcoming_deadline_email.py b/tdrs-backend/tdpservice/email/test/test_upcoming_deadline_email.py index fb91c4c7b..64aade5e3 100644 --- a/tdrs-backend/tdpservice/email/test/test_upcoming_deadline_email.py +++ b/tdrs-backend/tdpservice/email/test/test_upcoming_deadline_email.py @@ -13,7 +13,7 @@ from tdpservice.users.models import User -@pytest.mark.parametrize( +QUARTERLY_PARAMS = pytest.mark.parametrize( "due_date, reporting_period, fiscal_quarter", [ ("February 14", "Oct - Dec", "Q1"), @@ -22,139 +22,293 @@ ("November 14th", "Jul - Sep", "Q4"), ], ) -@pytest.mark.django_db -def test_upcoming_deadline_sends_no_sections_submitted( - due_date, reporting_period, fiscal_quarter -): - """Test that the send_deactivation_warning_email function runs when no sections have been submitted.""" - stt = STT.objects.create( - name="Arkansas", - filenames={ - "Active Case Data": "test-filename.txt", - "Closed Case Data": "test-filename-closed.txt", - }, - ) + +def _create_stt_with_analyst(name, filenames, ssp=False): + """Create an STT and an approved Data Analyst assigned to it.""" + stt = STT.objects.create(name=name, filenames=filenames, ssp=ssp) data_analyst = User.objects.create( - username="test@email.com", stt=stt, account_approval_status="Approved" + username=f"{name.lower().replace(' ', '')}@test.com", + stt=stt, + account_approval_status="Approved", ) data_analyst.groups.add(Group.objects.get(name="Data Analyst")) data_analyst.save() + return stt, data_analyst - send_data_submission_reminder(due_date, reporting_period, fiscal_quarter) - assert len(mail.outbox) == 1 - assert ( - mail.outbox[0].subject == "Action Requested: Please submit your TANF data files" +def _submit_file(stt, user, program_type, section, fiscal_quarter): + """Create a DataFile for the current fiscal year and given quarter.""" + return DataFile.create_new_version( + { + "section": section, + "program_type": program_type, + "quarter": fiscal_quarter, + "year": datetime.now().year, + "stt": stt, + "user": user, + "is_program_audit": False, + } ) -@pytest.mark.parametrize( - "due_date, reporting_period, fiscal_quarter", - [ - ("February 14", "Oct - Dec", "Q1"), - ("May 15th", "Jan - Mar", "Q2"), - ("August 14th", "Apr - Jun", "Q3"), - ("November 14th", "Jul - Sep", "Q4"), - ], -) +@QUARTERLY_PARAMS @pytest.mark.django_db -def test_upcoming_deadline_sends_some_sections_submitted( +def test_upcoming_deadline_sends_no_sections_submitted( due_date, reporting_period, fiscal_quarter ): - """Test that the send_deactivation_warning_email function runs when some sections have been submitted.""" - stt = STT.objects.create( - name="Arkansas", - filenames={ - "Active Case Data": "test-filename.txt", - "Closed Case Data": "test-filename-closed.txt", + """Reminder is sent when no sections have been submitted.""" + stt, _ = _create_stt_with_analyst( + "Arkansas", + { + "TAN Active Case Data": "test-filename.txt", + "TAN Closed Case Data": "test-filename-closed.txt", }, ) - data_analyst = User.objects.create( - username="test@email.com", stt=stt, account_approval_status="Approved" + send_data_submission_reminder(due_date, reporting_period, fiscal_quarter) + + assert len(mail.outbox) == 1 + assert ( + mail.outbox[0].subject + == "Action Requested: Please submit your TANF data files" ) - data_analyst.groups.add(Group.objects.get(name="Data Analyst")) - data_analyst.save() - now = datetime.now() - fiscal_year = now.year - 1 if fiscal_quarter == "Q1" else now.year - _ = DataFile.create_new_version( +@QUARTERLY_PARAMS +@pytest.mark.django_db +def test_upcoming_deadline_sends_some_sections_submitted( + due_date, reporting_period, fiscal_quarter +): + """Reminder is sent when only some sections have been submitted.""" + stt, analyst = _create_stt_with_analyst( + "Arkansas", { - "section": "Active Case Data", - "program_type": "TAN", - "quarter": fiscal_quarter, - "year": fiscal_year, - "stt": stt, - "user": data_analyst, - "is_program_audit": False, - } + "TAN Active Case Data": "test-filename.txt", + "TAN Closed Case Data": "test-filename-closed.txt", + }, ) + _submit_file(stt, analyst, "TAN", "Active Case Data", fiscal_quarter) + send_data_submission_reminder(due_date, reporting_period, fiscal_quarter) assert len(mail.outbox) == 1 assert ( - mail.outbox[0].subject == "Action Requested: Please submit your TANF data files" + mail.outbox[0].subject + == "Action Requested: Please submit your TANF data files" ) -@pytest.mark.parametrize( - "due_date, reporting_period, fiscal_quarter", - [ - ("February 14", "Oct - Dec", "Q1"), - ("May 15th", "Jan - Mar", "Q2"), - ("August 14th", "Apr - Jun", "Q3"), - ("November 14th", "Jul - Sep", "Q4"), - ], -) +@QUARTERLY_PARAMS @pytest.mark.django_db def test_upcoming_deadline_no_send_when_all_sections_complete( due_date, reporting_period, fiscal_quarter ): - """Test that the send_deactivation_warning_email function does not run when all sections have been submitted.""" - stt = STT.objects.create( - name="Arkansas", - filenames={ - "Active Case Data": "test-filename.txt", - "Closed Case Data": "test-filename-closed.txt", + """No reminder is sent when all required sections have been submitted.""" + stt, analyst = _create_stt_with_analyst( + "Arkansas", + { + "TAN Active Case Data": "test-filename.txt", + "TAN Closed Case Data": "test-filename-closed.txt", }, ) - data_analyst = User.objects.create( - username="test@email.com", stt=stt, account_approval_status="Approved" + _submit_file(stt, analyst, "TAN", "Active Case Data", fiscal_quarter) + _submit_file(stt, analyst, "TAN", "Closed Case Data", fiscal_quarter) + + send_data_submission_reminder(due_date, reporting_period, fiscal_quarter) + + assert len(mail.outbox) == 0 + + +@pytest.mark.django_db +def test_q1_files_found_using_current_year(): + """Q1 DataFiles stored with the current year are found by the task.""" + stt, analyst = _create_stt_with_analyst( + "TestState", + { + "TAN Active Case Data": "file1.txt", + "TAN Closed Case Data": "file2.txt", + }, ) - data_analyst.groups.add(Group.objects.get(name="Data Analyst")) - data_analyst.save() - now = datetime.now() - fiscal_year = now.year - 1 if fiscal_quarter == "Q1" else now.year + # Submit all required files for Q1 using the current year + _submit_file(stt, analyst, "TAN", "Active Case Data", "Q1") + _submit_file(stt, analyst, "TAN", "Closed Case Data", "Q1") - _ = DataFile.create_new_version( + send_data_submission_reminder("February 14", "Oct - Dec", "Q1") + + # All files submitted → no reminder should be sent + assert len(mail.outbox) == 0 + + +@pytest.mark.django_db +def test_q1_files_with_previous_year_are_not_matched(): + """DataFiles stored with the previous year should not satisfy the current period.""" + stt, analyst = _create_stt_with_analyst( + "TestState", + { + "TAN Active Case Data": "file1.txt", + "TAN Closed Case Data": "file2.txt", + }, + ) + + # Submit files with LAST year — these should not count + last_year = datetime.now().year - 1 + DataFile.create_new_version( { "section": "Active Case Data", "program_type": "TAN", - "quarter": fiscal_quarter, - "year": fiscal_year, + "quarter": "Q1", + "year": last_year, "stt": stt, - "user": data_analyst, + "user": analyst, "is_program_audit": False, } ) - - _ = DataFile.create_new_version( + DataFile.create_new_version( { "section": "Closed Case Data", "program_type": "TAN", - "quarter": fiscal_quarter, - "year": fiscal_year, + "quarter": "Q1", + "year": last_year, "stt": stt, - "user": data_analyst, + "user": analyst, "is_program_audit": False, } ) - send_data_submission_reminder(due_date, reporting_period, fiscal_quarter) + send_data_submission_reminder("February 14", "Oct - Dec", "Q1") + + # Files are for the wrong year → reminder should still be sent + assert len(mail.outbox) == 1 + + +@pytest.mark.django_db +def test_tribal_submission_does_not_satisfy_tanf_requirement(): + """A Tribal DataFile should not count toward TANF filing requirements. + + Regression: the old code only checked section name, so a Tribal 'Active + Case Data' submission would satisfy a TANF 'Active Case Data' requirement. + """ + stt, analyst = _create_stt_with_analyst( + "TestState", + { + "TAN Active Case Data": "file1.txt", + "TAN Closed Case Data": "file2.txt", + }, + ) + + # Submit Tribal files — wrong program type for this STT + _submit_file(stt, analyst, "TRIBAL", "Active Case Data", "Q1") + _submit_file(stt, analyst, "TRIBAL", "Closed Case Data", "Q1") + + send_data_submission_reminder("February 14", "Oct - Dec", "Q1") + + # Tribal files don't satisfy TANF requirement → reminder sent + assert len(mail.outbox) == 1 + + +@pytest.mark.django_db +def test_tanf_submission_does_not_satisfy_tribal_requirement(): + """A TANF DataFile should not count toward Tribal filing requirements.""" + stt, analyst = _create_stt_with_analyst( + "TestTribe", + { + "Tribal Active Case Data": "file1.txt", + "Tribal Closed Case Data": "file2.txt", + "Tribal Aggregate Data": "file3.txt", + }, + ) + + # Submit TANF files — wrong program type for a tribal STT + _submit_file(stt, analyst, "TAN", "Active Case Data", "Q1") + _submit_file(stt, analyst, "TAN", "Closed Case Data", "Q1") + _submit_file(stt, analyst, "TAN", "Aggregate Data", "Q1") + + send_data_submission_reminder("February 14", "Oct - Dec", "Q1") + + # TANF files don't satisfy Tribal requirement → reminder sent + assert len(mail.outbox) == 1 + + +@pytest.mark.django_db +def test_tribal_stt_no_reminder_when_all_tribal_sections_submitted(): + """A Tribal STT that has submitted all Tribal files should not get a reminder.""" + stt, analyst = _create_stt_with_analyst( + "TestTribe", + { + "Tribal Active Case Data": "file1.txt", + "Tribal Closed Case Data": "file2.txt", + "Tribal Aggregate Data": "file3.txt", + }, + ) + + _submit_file(stt, analyst, "TRIBAL", "Active Case Data", "Q1") + _submit_file(stt, analyst, "TRIBAL", "Closed Case Data", "Q1") + _submit_file(stt, analyst, "TRIBAL", "Aggregate Data", "Q1") + + send_data_submission_reminder("February 14", "Oct - Dec", "Q1") + + assert len(mail.outbox) == 0 + + +@pytest.mark.django_db +def test_ssp_stt_requires_both_tanf_and_ssp_submissions(): + """An SSP state must submit both TANF and SSP files to avoid a reminder. + + Regression: the old code ignored program type, so submitting only TANF + files could falsely satisfy SSP requirements (since both have the same + section names). + """ + stt, analyst = _create_stt_with_analyst( + "RhodeIsland", + { + "TAN Active Case Data": "tanf1.txt", + "TAN Closed Case Data": "tanf2.txt", + "TAN Aggregate Data": "tanf3.txt", + "SSP Active Case Data": "ssp1.txt", + "SSP Closed Case Data": "ssp2.txt", + "SSP Aggregate Data": "ssp3.txt", + }, + ssp=True, + ) + + # Only submit TANF files — SSP requirements are not satisfied + _submit_file(stt, analyst, "TAN", "Active Case Data", "Q2") + _submit_file(stt, analyst, "TAN", "Closed Case Data", "Q2") + _submit_file(stt, analyst, "TAN", "Aggregate Data", "Q2") + + send_data_submission_reminder("May 15th", "Jan - Mar", "Q2") + + # Missing SSP files → reminder sent + assert len(mail.outbox) == 1 + assert "SSP" in mail.outbox[0].subject + + +@pytest.mark.django_db +def test_ssp_stt_no_reminder_when_all_programs_submitted(): + """An SSP state that has submitted all TANF and SSP files should not get a reminder.""" + stt, analyst = _create_stt_with_analyst( + "RhodeIsland", + { + "TAN Active Case Data": "tanf1.txt", + "TAN Closed Case Data": "tanf2.txt", + "TAN Aggregate Data": "tanf3.txt", + "SSP Active Case Data": "ssp1.txt", + "SSP Closed Case Data": "ssp2.txt", + "SSP Aggregate Data": "ssp3.txt", + }, + ssp=True, + ) + + # Submit both TANF and SSP files + for program_type in ("TAN", "SSP"): + _submit_file(stt, analyst, program_type, "Active Case Data", "Q2") + _submit_file(stt, analyst, program_type, "Closed Case Data", "Q2") + _submit_file(stt, analyst, program_type, "Aggregate Data", "Q2") + + send_data_submission_reminder("May 15th", "Jan - Mar", "Q2") assert len(mail.outbox) == 0 From 77fe74d87dd6dd8290350b4af1fc1126a19d0c59 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Tue, 10 Feb 2026 12:19:00 -0600 Subject: [PATCH 084/148] - linting --- .../tdpservice/email/test/test_upcoming_deadline_email.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tdrs-backend/tdpservice/email/test/test_upcoming_deadline_email.py b/tdrs-backend/tdpservice/email/test/test_upcoming_deadline_email.py index 64aade5e3..8a1bd08d0 100644 --- a/tdrs-backend/tdpservice/email/test/test_upcoming_deadline_email.py +++ b/tdrs-backend/tdpservice/email/test/test_upcoming_deadline_email.py @@ -145,7 +145,7 @@ def test_q1_files_found_using_current_year(): @pytest.mark.django_db def test_q1_files_with_previous_year_are_not_matched(): - """DataFiles stored with the previous year should not satisfy the current period.""" + """Data files stored with the previous year should not satisfy the current period.""" stt, analyst = _create_stt_with_analyst( "TestState", { From d17c43b5ec8aca486647906f113f9c9fffddbe09 Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Tue, 10 Feb 2026 13:06:16 -0600 Subject: [PATCH 085/148] - Removed logic to disable submit button if there are no uploaded files - Re-added the "no changes made" error alert when clicking submit with no files - Updated upload form tests to verify alert - Updated cypress to not depend on button state --- .../cypress/e2e/common-steps/data_files.js | 8 +------- .../FileUploadForms/QuarterFileUploadForm.jsx | 2 +- .../FileUploadForms/QuarterFileUploadForm.test.js | 14 +++++++++++--- .../FileUploadForms/SectionFileUploadForm.jsx | 2 +- .../FileUploadForms/SectionFileUploadForm.test.js | 14 +++++++++++--- .../src/components/Reports/FRAReports.jsx | 11 ++++++++++- .../src/components/Reports/FRAReports.test.js | 8 +++++++- .../src/components/Reports/Reports.test.js | 5 +++-- tdrs-frontend/src/hooks/useFileUploadForm.js | 9 +++++++++ 9 files changed, 54 insertions(+), 19 deletions(-) diff --git a/tdrs-frontend/cypress/e2e/common-steps/data_files.js b/tdrs-frontend/cypress/e2e/common-steps/data_files.js index 9bc7094b8..eb3101369 100644 --- a/tdrs-frontend/cypress/e2e/common-steps/data_files.js +++ b/tdrs-frontend/cypress/e2e/common-steps/data_files.js @@ -24,9 +24,6 @@ export const uploadFile = (file_input, file_path, willError = false) => { 'exist' ) cy.get('.usa-alert__text').should('not.exist') - cy.get('button') - .contains('Submit') - .should('not.be.disabled', { timeout: 5000 }) } } @@ -201,10 +198,7 @@ export const uploadSectionFile = ( 'is-loading' ) cy.get('.usa-alert__text').should('not.exist') - cy.get('button') - .contains('Submit') - .should('not.be.disabled', { timeout: 5000 }) - cy.contains('button', 'Submit').should('be.enabled').click() + cy.contains('button', 'Submit').click() cy.wait('@dataFileSubmit', { timeout: 60000 }).then(({ response }) => { const id = response?.body?.id if (!id) throw new Error('Missing data_file id in response') diff --git a/tdrs-frontend/src/components/FileUploadForms/QuarterFileUploadForm.jsx b/tdrs-frontend/src/components/FileUploadForms/QuarterFileUploadForm.jsx index 3398c9594..2bf579cf2 100644 --- a/tdrs-frontend/src/components/FileUploadForms/QuarterFileUploadForm.jsx +++ b/tdrs-frontend/src/components/FileUploadForms/QuarterFileUploadForm.jsx @@ -113,7 +113,7 @@ const QuarterFileUploadForm = ({ stt }) => { diff --git a/tdrs-frontend/src/components/FileUploadForms/QuarterFileUploadForm.test.js b/tdrs-frontend/src/components/FileUploadForms/QuarterFileUploadForm.test.js index b641f4aaa..d28741321 100644 --- a/tdrs-frontend/src/components/FileUploadForms/QuarterFileUploadForm.test.js +++ b/tdrs-frontend/src/components/FileUploadForms/QuarterFileUploadForm.test.js @@ -139,7 +139,7 @@ describe('QuarterFileUploadForm', () => { }) describe('Form Submission', () => { - it('does not allow submission with no uploaded files', async () => { + it('shows error alert when submitting with no uploaded files', async () => { const storeState = { ...initialState, reports: { @@ -147,10 +147,18 @@ describe('QuarterFileUploadForm', () => { }, } - const { getByText } = renderComponent(storeState) + const { getByText, getByRole } = renderComponent(storeState) const submitButton = getByText('Submit Data Files') - expect(submitButton).not.toBeEnabled() + fireEvent.click(submitButton) + + await waitFor(() => { + const alert = getByRole('alert') + expect(alert).toBeInTheDocument() + expect(alert).toHaveTextContent( + 'No changes have been made to data files' + ) + }) expect(mockExecuteSubmission).not.toHaveBeenCalled() }) diff --git a/tdrs-frontend/src/components/FileUploadForms/SectionFileUploadForm.jsx b/tdrs-frontend/src/components/FileUploadForms/SectionFileUploadForm.jsx index 18daa8673..c8d49bce1 100644 --- a/tdrs-frontend/src/components/FileUploadForms/SectionFileUploadForm.jsx +++ b/tdrs-frontend/src/components/FileUploadForms/SectionFileUploadForm.jsx @@ -100,7 +100,7 @@ const SectionFileUploadForm = ({ stt }) => { diff --git a/tdrs-frontend/src/components/FileUploadForms/SectionFileUploadForm.test.js b/tdrs-frontend/src/components/FileUploadForms/SectionFileUploadForm.test.js index 138f36220..557be1147 100644 --- a/tdrs-frontend/src/components/FileUploadForms/SectionFileUploadForm.test.js +++ b/tdrs-frontend/src/components/FileUploadForms/SectionFileUploadForm.test.js @@ -176,7 +176,7 @@ describe('SectionFileUploadForm', () => { }) describe('Form Submission', () => { - it('does not allow submission with no uploaded files', async () => { + it('shows error alert when submitting with no uploaded files', async () => { const storeState = { ...initialState, reports: { @@ -184,10 +184,18 @@ describe('SectionFileUploadForm', () => { }, } - const { getByText } = renderComponent(storeState) + const { getByText, getByRole } = renderComponent(storeState) const submitButton = getByText('Submit Data Files') - expect(submitButton).not.toBeEnabled() + fireEvent.click(submitButton) + + await waitFor(() => { + const alert = getByRole('alert') + expect(alert).toBeInTheDocument() + expect(alert).toHaveTextContent( + 'No changes have been made to data files' + ) + }) expect(mockExecuteSubmission).not.toHaveBeenCalled() }) diff --git a/tdrs-frontend/src/components/Reports/FRAReports.jsx b/tdrs-frontend/src/components/Reports/FRAReports.jsx index b28cda564..69e906186 100644 --- a/tdrs-frontend/src/components/Reports/FRAReports.jsx +++ b/tdrs-frontend/src/components/Reports/FRAReports.jsx @@ -271,6 +271,15 @@ const UploadForm = ({ return } + if (!file || (file && file.id)) { + setLocalAlertState({ + active: true, + type: 'error', + message: 'No changes have been made to data files', + }) + return + } + handleUpload({ file }) } @@ -324,7 +333,7 @@ const UploadForm = ({ diff --git a/tdrs-frontend/src/components/Reports/FRAReports.test.js b/tdrs-frontend/src/components/Reports/FRAReports.test.js index 4e7dd4e26..39183ca85 100644 --- a/tdrs-frontend/src/components/Reports/FRAReports.test.js +++ b/tdrs-frontend/src/components/Reports/FRAReports.test.js @@ -504,7 +504,13 @@ describe('FRA Reports Page', () => { const { getByText } = await setup() const submitButton = getByText('Submit Report', { selector: 'button' }) - expect(submitButton).not.toBeEnabled() + fireEvent.click(submitButton) + + await waitFor(() => + expect( + getByText('No changes have been made to data files') + ).toBeInTheDocument() + ) }) it('Shows an error if a non-allowed file type is selected', async () => { diff --git a/tdrs-frontend/src/components/Reports/Reports.test.js b/tdrs-frontend/src/components/Reports/Reports.test.js index a9f4dd678..968289c46 100644 --- a/tdrs-frontend/src/components/Reports/Reports.test.js +++ b/tdrs-frontend/src/components/Reports/Reports.test.js @@ -407,8 +407,9 @@ describe('Reports', () => { await waitFor(() => expect(getByText('section2.txt')).toBeInTheDocument()) await waitFor(() => expect(getByText('section3.txt')).toBeInTheDocument()) await waitFor(() => expect(getByText('section4.txt')).toBeInTheDocument()) - await waitFor(() => expect(getByText('Submit Data Files')).toBeEnabled()) - expect(store.dispatch).toHaveBeenCalledTimes(14) + await waitFor(() => + expect(store.dispatch).toHaveBeenCalledTimes(14) + ) fireEvent.click(getByText('Submit Data Files')) await waitFor(() => getByRole('alert')) diff --git a/tdrs-frontend/src/hooks/useFileUploadForm.js b/tdrs-frontend/src/hooks/useFileUploadForm.js index 3fae4e9be..396210518 100644 --- a/tdrs-frontend/src/hooks/useFileUploadForm.js +++ b/tdrs-frontend/src/hooks/useFileUploadForm.js @@ -110,6 +110,15 @@ export const useFileUploadForm = ({ const onSubmit = async (event) => { event.preventDefault() + if (uploadedFiles.length === 0) { + setLocalAlertState({ + active: true, + type: 'error', + message: 'No changes have been made to data files', + }) + return + } + try { // Transform files if needed (e.g., for Program Audit) const filesToSubmit = transformFiles From d8a04198a79507a1a258fa07c8f6653a3a91a5ab Mon Sep 17 00:00:00 2001 From: Eric Lipe Date: Tue, 10 Feb 2026 14:10:13 -0600 Subject: [PATCH 086/148] - Add data tag for cypress tests to avoid the redux race issue - Update cypress to look for data tag on submit button - Add data tag to all submit buttons --- tdrs-frontend/cypress/e2e/common-steps/data_files.js | 10 ++++++++++ tdrs-frontend/src/components/Button/Button.jsx | 3 +++ .../FileUploadForms/QuarterFileUploadForm.jsx | 1 + .../FileUploadForms/SectionFileUploadForm.jsx | 1 + tdrs-frontend/src/components/Reports/FRAReports.jsx | 1 + 5 files changed, 16 insertions(+) diff --git a/tdrs-frontend/cypress/e2e/common-steps/data_files.js b/tdrs-frontend/cypress/e2e/common-steps/data_files.js index eb3101369..7c6cb2fac 100644 --- a/tdrs-frontend/cypress/e2e/common-steps/data_files.js +++ b/tdrs-frontend/cypress/e2e/common-steps/data_files.js @@ -24,6 +24,11 @@ export const uploadFile = (file_input, file_path, willError = false) => { 'exist' ) cy.get('.usa-alert__text').should('not.exist') + cy.contains('button', 'Submit', { timeout: 5000 }).should( + 'have.attr', + 'data-has-uploaded-files', + 'true' + ) } } @@ -198,6 +203,11 @@ export const uploadSectionFile = ( 'is-loading' ) cy.get('.usa-alert__text').should('not.exist') + cy.contains('button', 'Submit', { timeout: 5000 }).should( + 'have.attr', + 'data-has-uploaded-files', + 'true' + ) cy.contains('button', 'Submit').click() cy.wait('@dataFileSubmit', { timeout: 60000 }).then(({ response }) => { const id = response?.body?.id diff --git a/tdrs-frontend/src/components/Button/Button.jsx b/tdrs-frontend/src/components/Button/Button.jsx index f4581223c..5471799ca 100644 --- a/tdrs-frontend/src/components/Button/Button.jsx +++ b/tdrs-frontend/src/components/Button/Button.jsx @@ -18,6 +18,7 @@ function Button({ target = '_blank', href, buttonKey = null, + ...rest }) { const isBig = size ? size === 'big' : false const isSmall = size ? size === 'small' : false @@ -61,6 +62,7 @@ function Button({ disabled={disabled} aria-disabled={disabled} buttonkey={buttonKey} + {...rest} > {children} @@ -78,6 +80,7 @@ function Button({ aria-disabled={disabled || undefined} onClick={handleClick} buttonkey={buttonKey} + {...rest} > {children} diff --git a/tdrs-frontend/src/components/FileUploadForms/QuarterFileUploadForm.jsx b/tdrs-frontend/src/components/FileUploadForms/QuarterFileUploadForm.jsx index 2bf579cf2..3c0550864 100644 --- a/tdrs-frontend/src/components/FileUploadForms/QuarterFileUploadForm.jsx +++ b/tdrs-frontend/src/components/FileUploadForms/QuarterFileUploadForm.jsx @@ -114,6 +114,7 @@ const QuarterFileUploadForm = ({ stt }) => { className="card:margin-y-1" type="submit" disabled={isSubmitting} + data-has-uploaded-files={uploadedFiles.length > 0} > {isSubmitting ? 'Submitting...' : 'Submit Data Files'} diff --git a/tdrs-frontend/src/components/FileUploadForms/SectionFileUploadForm.jsx b/tdrs-frontend/src/components/FileUploadForms/SectionFileUploadForm.jsx index c8d49bce1..a99142e7c 100644 --- a/tdrs-frontend/src/components/FileUploadForms/SectionFileUploadForm.jsx +++ b/tdrs-frontend/src/components/FileUploadForms/SectionFileUploadForm.jsx @@ -101,6 +101,7 @@ const SectionFileUploadForm = ({ stt }) => { className="card:margin-y-1" type="submit" disabled={isSubmitting} + data-has-uploaded-files={uploadedFiles.length > 0} > {isSubmitting ? 'Submitting...' : 'Submit Data Files'} diff --git a/tdrs-frontend/src/components/Reports/FRAReports.jsx b/tdrs-frontend/src/components/Reports/FRAReports.jsx index 69e906186..b89a62cf9 100644 --- a/tdrs-frontend/src/components/Reports/FRAReports.jsx +++ b/tdrs-frontend/src/components/Reports/FRAReports.jsx @@ -334,6 +334,7 @@ const UploadForm = ({ className="card:margin-y-1" type="submit" disabled={isSubmitting} + data-has-uploaded-files={fraHasUploadedFile} > {isSubmitting ? 'Submitting...' : 'Submit Report'} From 30852996c98fade88a51891fad0974e768b60ca3 Mon Sep 17 00:00:00 2001 From: Matt Cole Anderson Date: Tue, 10 Feb 2026 14:39:19 -0600 Subject: [PATCH 087/148] add new processingAlert --- .../FileUploadForms/QuarterFileUploadForm.jsx | 14 +++++++++ .../FileUploadForms/SectionFileUploadForm.jsx | 14 +++++++++ .../src/components/Reports/FRAReports.jsx | 30 +++++++++++++++++-- .../src/components/Reports/ReportsContext.jsx | 19 ++++++++++++ tdrs-frontend/src/hooks/useFileUploadForm.js | 20 +++++++++++-- 5 files changed, 92 insertions(+), 5 deletions(-) diff --git a/tdrs-frontend/src/components/FileUploadForms/QuarterFileUploadForm.jsx b/tdrs-frontend/src/components/FileUploadForms/QuarterFileUploadForm.jsx index 3398c9594..41e060a45 100644 --- a/tdrs-frontend/src/components/FileUploadForms/QuarterFileUploadForm.jsx +++ b/tdrs-frontend/src/components/FileUploadForms/QuarterFileUploadForm.jsx @@ -69,9 +69,11 @@ const QuarterFileUploadForm = ({ stt }) => { yearInputValue, fileTypeInputValue, localAlert, + processingAlert, uploadedFiles, isSubmitting, alertRef, + processingAlertRef, onSubmit, handleCancel, setLocalAlertState, @@ -96,6 +98,18 @@ const QuarterFileUploadForm = ({ stt }) => { )} + {processingAlert.active && ( +
    +
    +

    {processingAlert.message}

    +
    +
    + )}
    {programIntegrityAuditLabels.map((quarterLabel, index) => ( { fileTypeInputValue, uploadedFiles, localAlert, + processingAlert, isSubmitting, alertRef, + processingAlertRef, onSubmit, handleCancel, setLocalAlertState, @@ -83,6 +85,18 @@ const SectionFileUploadForm = ({ stt }) => { )} + {processingAlert.active && ( +
    +
    +

    {processingAlert.message}

    +
    +
    + )} {fileUploadSections.slice(0, num_sections).map((section, index) => ( { setModalTriggerSource, localAlert, setLocalAlertState, + processingAlert, + setProcessingAlertState, + processingAlertRef, fraSelectedFile, setFraSelectedFile, fraUploadError, @@ -659,10 +662,10 @@ const FRAReportsContent = () => { datafile: response?.data, }, }) - setLocalAlertState({ + setProcessingAlertState({ active: true, type: 'success', - message: 'Parsing complete.', + message: 'Processing complete.', }) }, (error) => { @@ -782,6 +785,16 @@ const FRAReportsContent = () => { } }, [localAlert, alertRef]) + useEffect(() => { + if ( + processingAlert.active && + processingAlertRef && + processingAlertRef.current + ) { + processingAlertRef.current.scrollIntoView({ behavior: 'smooth' }) + } + }, [processingAlert, processingAlertRef]) + return (
    @@ -824,7 +837,6 @@ const FRAReportsContent = () => { {localAlert.active && (
    {
    )} + {processingAlert.active && ( +
    +
    +

    {processingAlert.message}

    +
    +
    + )} { message: null, }) + // Processing alert state (separate from localAlert for accessibility) + const [processingAlert, setProcessingAlertState] = useState({ + active: false, + type: null, + message: null, + }) + // Refs const headerRef = useRef(null) const alertRef = useRef(null) + const processingAlertRef = useRef(null) // Redux selectors const files = useSelector((state) => state.reports.submittedFiles) @@ -337,6 +345,7 @@ export const ReportsProvider = ({ isFra = false, children }) => { } else { setFileTypeInputValue(value) setLocalAlertState({ active: false, type: null, message: null }) + setProcessingAlertState({ active: false, type: null, message: null }) dispatch(clearFileList({ fileType: value })) dispatch(reinitializeSubmittedFiles(value)) setFraSelectedFile(null) @@ -361,6 +370,7 @@ export const ReportsProvider = ({ isFra = false, children }) => { } else { setYearInputValue(value) setLocalAlertState({ active: false, type: null, message: null }) + setProcessingAlertState({ active: false, type: null, message: null }) dispatch(clearFileList({ fileType: fileTypeInputValue })) setFraSelectedFile(null) } @@ -377,6 +387,7 @@ export const ReportsProvider = ({ isFra = false, children }) => { } else { setQuarterInputValue(value) setLocalAlertState({ active: false, type: null, message: null }) + setProcessingAlertState({ active: false, type: null, message: null }) dispatch(clearFileList({ fileType: fileTypeInputValue })) setFraSelectedFile(null) } @@ -398,6 +409,11 @@ export const ReportsProvider = ({ isFra = false, children }) => { type: null, message: null, }) + setProcessingAlertState({ + active: false, + type: null, + message: null, + }) // Check if current file type is valid for the new STT // If SSP is selected but new STT doesn't support SSP, reset to TANF @@ -529,6 +545,9 @@ export const ReportsProvider = ({ isFra = false, children }) => { setReprocessedDate, localAlert, setLocalAlertState, + processingAlert, + setProcessingAlertState, + processingAlertRef, selectedSubmissionTab, setSelectedSubmissionTab, headerRef, diff --git a/tdrs-frontend/src/hooks/useFileUploadForm.js b/tdrs-frontend/src/hooks/useFileUploadForm.js index 3fae4e9be..d69214384 100644 --- a/tdrs-frontend/src/hooks/useFileUploadForm.js +++ b/tdrs-frontend/src/hooks/useFileUploadForm.js @@ -38,6 +38,9 @@ export const useFileUploadForm = ({ fileTypeInputValue, localAlert, setLocalAlertState, + processingAlert, + setProcessingAlertState, + processingAlertRef, uploadedFiles, setErrorModalVisible, setModalTriggerSource, @@ -70,10 +73,10 @@ export const useFileUploadForm = ({ datafile: response?.data, }, }) - setLocalAlertState({ + setProcessingAlertState({ active: true, type: 'success', - message: 'Parsing complete.', + message: 'Processing complete.', }) }, (error) => { @@ -165,15 +168,28 @@ export const useFileUploadForm = ({ } }, [localAlert, alertRef]) + // Scroll to processing alert when it becomes active (uses aria-live="polite" for sequential reading) + useEffect(() => { + if ( + processingAlert.active && + processingAlertRef && + processingAlertRef.current + ) { + processingAlertRef.current.scrollIntoView({ behavior: 'smooth' }) + } + }, [processingAlert, processingAlertRef]) + return { // Form state yearInputValue, quarterInputValue, fileTypeInputValue, localAlert, + processingAlert, uploadedFiles, isSubmitting, alertRef, + processingAlertRef, formattedSections, // Form handlers From f56d9d359da00b57441f0cadaa6e82e637343621 Mon Sep 17 00:00:00 2001 From: Matt Cole Anderson Date: Tue, 10 Feb 2026 15:58:37 -0600 Subject: [PATCH 088/148] stop screen reader from interrupting alert message --- .../FileUploadForms/QuarterFileUploadForm.jsx | 22 +++++++++++++++---- .../FileUploadForms/SectionFileUploadForm.jsx | 22 +++++++++++++++---- .../src/components/Reports/FRAReports.jsx | 22 +++++++++++++++---- 3 files changed, 54 insertions(+), 12 deletions(-) diff --git a/tdrs-frontend/src/components/FileUploadForms/QuarterFileUploadForm.jsx b/tdrs-frontend/src/components/FileUploadForms/QuarterFileUploadForm.jsx index 41e060a45..79878f9db 100644 --- a/tdrs-frontend/src/components/FileUploadForms/QuarterFileUploadForm.jsx +++ b/tdrs-frontend/src/components/FileUploadForms/QuarterFileUploadForm.jsx @@ -86,30 +86,44 @@ const QuarterFileUploadForm = ({ stt }) => { return ( <> + {/* Screen-reader announcer */} +
    +
    + {localAlert.active ? localAlert.message : ''} +
    + +
    + {processingAlert.active ? processingAlert.message : ''} +
    +
    + + {/* Visible alerts (not in accessibility tree, prevents duplicate screen reads */} {localAlert.active && (