Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RAS-939 Survey Status Change #914

Merged
merged 19 commits into from
Feb 15, 2024
Merged

RAS-939 Survey Status Change #914

merged 19 commits into from
Feb 15, 2024

Conversation

arroyoAle
Copy link
Contributor

@arroyoAle arroyoAle commented Feb 2, 2024

What and why?

Adds a new case group transition from complete to not started to allow business users a new status change in rOps

How to test?

Jira

https://jira.ons.gov.uk/browse/RAS-939

{% set survey_link = '&nbsp; <a href="' + url_for("case_bp.get_response_statuses", ru_ref=ru.sampleUnitRef, survey=survey.shortName, period=ce.exerciseRef) + '">Change</a>' %}
{% elif ce.responseStatus in ['Completed', 'Completed by phone', 'No longer required'] and isReportingUnitPermission %}
{% elif isReportingUnitPermission %}
Copy link
Contributor Author

@arroyoAle arroyoAle Feb 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure about this since I added the permission check in the next page as well so this might be worth just being an else and then view link

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to just make this an if else and show the view to all users as that is how it is used across the rest of the site. It just made more sense to me

@arroyoAle arroyoAle changed the title changes to enable status change from complete to not started RAS-939 Survey Status Change Feb 5, 2024
@arroyoAle arroyoAle marked this pull request as ready for review February 5, 2024 14:03
@@ -67,12 +67,10 @@ <h3 id="COLLECTION_EXERCISES" class="ons-u-fs-m">Collection exercises</h3>
{% set status_class = 'ons-status--error' %}
{% endif %}

{% if ce.responseStatus in ['Not started', 'In progress'] and isReportingUnitPermission %}
{% if ce.responseStatus in ['Not started', 'In progress','Completed', 'Completed by phone', 'No longer required'] and isReportingUnitPermission %}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't going to work in conjunction with eQ, QuestionnaireState which holds the answers for eQ will still be present if Completed by phone or No longer required has been used and the respondent started the survey. eQ only deletes QuestionnaireState if it's completed by them

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to take the opportunity to move to this to the backend, removing the logic from the template. It only needs the check and link generation, not everything.

Copy link
Contributor

@pricem14pc pricem14pc Feb 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't going to work in conjunction with eQ, QuestionnaireState which holds the answers for eQ will still be present if Completed by phone or No longer required has been used and the respondent started the survey. eQ only deletes QuestionnaireState if it's completed by them

The issue of EQ post submission state has also been raised when changing from COMPLETED to NOT STARTED. Depending on the time-bound post submission state, and the post-submission cookie state the respondent will not necessarily be presented with a new 'blank' questionnaire as the response_id may resolve to an existing state for a period of time after submission.

Copy link
Contributor Author

@arroyoAle arroyoAle Feb 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't changed the functionality of the Completed by Phone and No longer required rOps users were already able to change those to Not started just that before they were clicking a link that said View and were then presented with options to change the status - I've also moved the logic to the backend

self.assertIn(b"Not started", data)

@requests_mock.mock()
def test_not_started_status_is_not_present_for_not_started_without_permission(self, mock_request):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this name difficult to understand, specially with 2 not_started in it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to be more clear

@@ -331,3 +356,43 @@ def test_not_started_status_is_present_for_no_longer_required(self, mock_request
self.assertEqual(response.status_code, 200)
self.assertIn(b"No longer required", data)
self.assertIn(b"Not started", data)

@requests_mock.mock()
def test_not_started_status_is_present_for_completed(self, mock_request):
Copy link
Contributor

@LJBabbage LJBabbage Feb 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We really should try to move away from this pattern or mass mocking at the highest level as it gets messy. The functionality should in the backend which then means we can unit test the link creation combinations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll leave this in but I have moved the link creation to the backend with additional tests

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can, but now you have done the work to test the dict being passed, you don't need to. The idea is you should only need to test the template logic

@SteveScorfield
Copy link
Contributor

/deploy scorfs

@ras-rm-pr-bot
Copy link
Collaborator

Deploying to dev cluster with following parameters:

  • namespace: scorfs

  • tag: pr-914

  • configBranch: main

  • paramKey: ``

  • paramValue: ``

Copy link
Contributor

@SteveScorfield SteveScorfield left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tested these changes and reviewed the code. All looks good to me.

Copy link
Contributor

@LJBabbage LJBabbage left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking better and nice to see some pytest fixtures use. It needs a bit or refinement with function names, structure and reuse.

from flask import url_for


def build_case_context(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function name and return, don't really align

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated this



def build_case_context(
ru_ref,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not fundamental, but I have been trying to do typing for anything new

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added typing where i can

return {"change_response_status": change_response_status}


def _build_change_response_status(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite sure why this is split as it is single use, bar the dict assignment I can't see what else the parent function is doing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right I feel like I was on autopilot when writing this - now fixed

):
if has_reporting_unit_permission:
change_response_status = {
"form_action": url_for(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very minor, but I would probably go with url as the key, just because it aligns with the flask function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated this


def _generate_radios(allowed_transitions_for_case):
radios = []
for index, (event, status) in enumerate(allowed_transitions_for_case.items()):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

list comprehension instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Swapped list comprehension in

def multiple_collection_exercises_with_details():
return [
{
"id": "57e06b27-a62b-4d8e-8d57-7b6b80067e4f",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels to me this chunk can be extracted

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

},
],
"attributes": {
"birthdate": "01/09/1993",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This again feels like it can be extracted and used as a fixture import

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made into a fixture import

@pytest.fixture
def multiple_cases():
return {
"state": "ACTIONABLE",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again this looks like the same chunk bar an odd change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated this

SURVEY_ID = "02b9c366-7397-42f7-942a-76dc5876d86d"


def test_case_context_with_permissions(app, transitions_for_complete_case, expected_case_context_with_all_permissions):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might be able to use fixture params here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Swapped for fixture params

@@ -331,3 +356,43 @@ def test_not_started_status_is_present_for_no_longer_required(self, mock_request
self.assertEqual(response.status_code, 200)
self.assertIn(b"No longer required", data)
self.assertIn(b"Not started", data)

@requests_mock.mock()
def test_not_started_status_is_present_for_completed(self, mock_request):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can, but now you have done the work to test the dict being passed, you don't need to. The idea is you should only need to test the template logic

@SteveScorfield
Copy link
Contributor

/deploy scorfs

@ras-rm-pr-bot
Copy link
Collaborator

Deploying to dev cluster with following parameters:

  • namespace: scorfs

  • tag: pr-914

  • configBranch: main

  • paramKey: ``

  • paramValue: ``

Copy link
Contributor

@SteveScorfield SteveScorfield left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tested this again and all works as expected. I'm also happy with the recommended code changes.

Copy link
Contributor

@LJBabbage LJBabbage left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looking good, a few minor things, tests need a bit more work regarding naming and fixture use

survey_id: str,
has_reporting_unit_permission: bool,
) -> dict:
if has_reporting_unit_permission:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have kind of doubled up here with the frontend and checked permissions twice ({% if isReportingUnitPermission %} in the template), so else is never reachable. However we shouldn't need to do isReportingUnitPermission in the template, as it's been done in the back end. Depends on what else uses it on how to pass it through, if it's just this then checking change_response_status is not null would work

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed this permissions check from the frontend and made it just check for it existing

Comment on lines 29 to 31
survey["surveyRef"],
survey["shortName"],
survey["id"],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry my fault, I wasn't clear. If it's 1 or 2 elements in a list we should pass the value, more than that pass the list. It's a balance act between passing what it needs and how many vars for the function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh yeah that makes more sense - updated

return {"change_response_status": change_response_status}


def _generate_radios(allowed_transitions_for_case: dict):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing the return type

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added return type

),
"hyperlink_text": "Change" if ru_permission else "View",
"status_class": (
CE_STATUS_CLASS[response_status] if response_status in CE_STATUS_CLASS.keys() else "ons-status--error"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A get with default might be better here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to get with a default

for respondent in respondents:
row = {}
if permissions["reporting_unit_edit"]:
if unused_iac:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see it, the logic is a bit unusual as the frontend is also checking off the iac key being passed to it and diverting it around. We should try not to do that as it kind of invalidates any unit tests in that area as there is the same logic both in the frontend and back.



@pytest.fixture()
def collection_exercises_with_details():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has a lot of stuff we don't need. Notice up the top there is a slim line ce_details. The idea being that is the starting point, each fixture below extents depending on what it needs to test. It doesn't need anything that it is not testing, it's not going to the frontend so dates, events, samples etc aren't needed for everything. So if it's build_reporting_units_context your testing, call your fixture appropriately extent the CE base and just make sure the values are populated to test, everything else could be None.

It will reduce the code massively and make it easier to maintain

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated and slimmed down

Comment on lines 1003 to 1016
@pytest.fixture
def transitions_for_complete_case():
return {"COMPLETED_TO_NOTSTARTED": "Not started"}


@pytest.fixture
def transitions_for_completed_by_phone_case():
return {"COMPLETED_TO_NOTSTARTED": "Not started"}


@pytest.fixture
def transitions_for_no_longer_required_case():
return {"COMPLETED_TO_NOTSTARTED": "Not started"}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why these are all the same

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed the repeated ones

Comment on lines 1018 to 1031
@pytest.fixture
def transitions_for_in_progress_case():
return {
"COMPLETED_BY_PHONE": "Completed by phone",
"NO_LONGER_REQUIRED": "No longer required",
}


@pytest.fixture
def transitions_for_not_started_case():
return {
"COMPLETED_BY_PHONE": "Completed by phone",
"NO_LONGER_REQUIRED": "No longer required",
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same again

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above

from response_operations_ui.contexts.case import build_response_status_context


def test_case_context_with_permissions(
Copy link
Contributor

@LJBabbage LJBabbage Feb 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A number of test names needs a tweak, they need to reflect what we are testing here its not case but the response status

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Copy link
Contributor

@LJBabbage LJBabbage left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good and works as expected, seen a small issue not connected to the card, respondent seems to be missing, but it's the same in latest. Bar that one recommendation for future tests
image

from response_operations_ui.contexts.case import build_response_status_context


def test_response_status_context_with_permissions(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are fine, and one for another time, but pytest.mark.parametrize would of worked well here and saved a lot of repetition, https://docs.pytest.org/en/7.1.x/how-to/parametrize.html#parametrize. You would of had to use something like getfixturevalue in the function for the compare

@arroyoAle arroyoAle merged commit bb502c3 into main Feb 15, 2024
3 checks passed
@arroyoAle arroyoAle deleted the change-status-to-not-started branch February 15, 2024 12:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants