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

Refactor user import, support user data in import and fix profile data bug #33890

Merged
merged 10 commits into from
Jan 24, 2024

Conversation

sravfeyn
Copy link
Member

@sravfeyn sravfeyn commented Dec 18, 2023

Product Description

This is a Refactor https://dimagi-dev.atlassian.net/browse/USH-3613 and a bug fix https://dimagi-dev.atlassian.net/browse/USH-2762

Technical Summary

https://dimagi-dev.atlassian.net/browse/USH-3613

Broken up the long function methods into classes to improve reusability and (hopefully) better readability.

Feature Flag

NA

Safety Assurance

Safety story

I have tested the import locally and there is a good amount of test cases for the code that's refactored.

Automated test coverage

corehq.apps.user_importer.tests

QA Plan

Perhaps not required since the basic import works and there is enough test coverage

Rollback instructions

  • This PR can be reverted after deploy with no further considerations

Labels & Review

  • Risk label is set correctly
  • The set of people pinged as reviewers is appropriate for the level of risk of the change

@sravfeyn sravfeyn requested a review from esoergel December 18, 2023 12:19
@dimagimon dimagimon added the Risk: Medium Change affects files that have been flagged as medium risk. label Dec 18, 2023
@sravfeyn sravfeyn force-pushed the sr/refusers branch 4 times, most recently from be2ad1f to 5fbb769 Compare December 19, 2023 12:51
@sravfeyn sravfeyn changed the title [WIP] Refactor user import code Refactor user import code Dec 21, 2023
@sravfeyn
Copy link
Member Author

@esoergel

@sravfeyn sravfeyn marked this pull request as ready for review December 21, 2023 06:29
Copy link
Contributor

@esoergel esoergel left a comment

Choose a reason for hiding this comment

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

Things are looking much better overall - I had difficulty figuring out what changed since it's so much code all at once, but I had some hopefully-useful comments. Any idea how good test coverage is on this?

Comment on lines 778 to 779
current = 0
for row in self.user_specs:
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: you could use enumerate here instead of incrementing it manually

Suggested change
current = 0
for row in self.user_specs:
for i, row in enumerate(self.user_specs):

Copy link
Member Author

Choose a reason for hiding this comment

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

Of course!

self.domain = domain
self.is_web_upload = is_web_upload

@property
Copy link
Contributor

Choose a reason for hiding this comment

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

I think all of the @propertys in this class should probably be @cached_propertys, right? Otherwise they'll be called for every row in the import.

Copy link
Member Author

Choose a reason for hiding this comment

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

That's right, updated it.

).run()


class UserRowMixin:
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: this isn't really a mixin, but a shared base class - there's not multiple inheritance.

Comment on lines +678 to +679
except UserUploadError as e:
self.status_row['flag'] = str(e)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this go in WebImporter.run instead? Looks like this only catches UserUploadErrors for web users, but I see in the old version except UserUploadError as e: is also in create_or_update_commcare_users_and_groups

Copy link
Member Author

@sravfeyn sravfeyn Dec 26, 2023

Choose a reason for hiding this comment

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

(It's still here in commcare_user version as well).

Yes, we can move it, though we still have to have a similar line for CouchUser.Inconsistent for CommCareUser, so it's not a super-worthy change

self._parse_password()
return True

def _set_user_importer(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

This method sets properties outside of __init__ which introduces unneeded temporal coupling (this method must be called before you can use those vars). Instead, would you consider making user and commcare_user_importer into @cached_propertys? Then they'd be lazily initialized

Copy link
Member Author

Choose a reason for hiding this comment

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

That's a good point, updated.

deactivate_after = row.get('deactivate_after', None) if update_deactivate_after_date else None
if isinstance(deactivate_after, datetime):
deactivate_after = deactivate_after.strftime("%m-%Y")
row['deactivate_after'] = deactivate_after
Copy link
Contributor

Choose a reason for hiding this comment

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

I like how you moved this logic to where it's actually needed

Comment on lines 507 to 509
self.status_row['row']['password'] = password
if self.column_values['user_id'] and is_password(password):
self.status_row['row']['password'] = 'REDACTED'
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does this check if user_id? Shouldn't it always redact passwords, also for new users? It looks like the old version of the code doesn't set password at all if the user ID isn't present.

Copy link
Member Author

Choose a reason for hiding this comment

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

Why does this check if user_id? Shouldn't it always redact passwords, also for new users?

I was using the older logic. I guess, we could redact. I refactored without changing the logic, I will update.

It looks like the old version of the code doesn't set password at all if the user ID isn't present.

Right, we are still doing the same, no?

Copy link
Member Author

Choose a reason for hiding this comment

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

There is this test that's failing corehq.apps.user_importer.tests.test_importer:TestMobileUserBulkUpload.test_password_is_not_string when I skip the user_id check. I am not sure why that's the way it is. I will rather leave the logic as is.

Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like the old version of the code doesn't set password at all if the user ID isn't present.

Right, we are still doing the same, no?

I meant line 505 here, where password is added to the status row - just want to be sure there's no situation where we might store the password in plaintext.

        self.status_row['row']['password'] = password

Would it be possible to just delete that line or initialize it to "" or something?

Copy link
Member Author

Choose a reason for hiding this comment

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

It looks like the status_row data is used only for communicating back to the uploading user when the password is not valid. If it is a valid password and it's set then it gets REDACTED. That's my understanding of the old logic. But of course there should be no problem to set that to empty regardless of whether the password is valid or not. I have updated it as such.

Copy link
Contributor

@esoergel esoergel left a comment

Choose a reason for hiding this comment

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

apologies for the delay in getting back to the review - looks like this is about done

Comment on lines 507 to 509
self.status_row['row']['password'] = password
if self.column_values['user_id'] and is_password(password):
self.status_row['row']['password'] = 'REDACTED'
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like the old version of the code doesn't set password at all if the user ID isn't present.

Right, we are still doing the same, no?

I meant line 505 here, where password is added to the status row - just want to be sure there's no situation where we might store the password in plaintext.

        self.status_row['row']['password'] = password

Would it be possible to just delete that line or initialize it to "" or something?

@@ -695,6 +695,28 @@ def test_user_data_profile_blank(self):
PROFILE_SLUG: self.profile.id,
})

def test_required_field_optional_if_profile_set(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

@esoergel
Copy link
Contributor

esoergel commented Jan 9, 2024

Oh whoops, one note - this line in the description is no longer accurate:

This is a Refactor without any product changes https://dimagi-dev.atlassian.net/browse/USH-3613

This PR now fixes that user data profile bug

@sravfeyn sravfeyn changed the title Refactor user import code Refactor user import code and fix profile data bug Jan 22, 2024
@sravfeyn sravfeyn changed the title Refactor user import code and fix profile data bug Refactor user import, support user data in import and fix profile data bug Jan 22, 2024
@sravfeyn
Copy link
Member Author

sravfeyn commented Jan 22, 2024

@esoergel I have updated the description and also merged in the other PR into this #33930

@sravfeyn sravfeyn merged commit bbe7d35 into master Jan 24, 2024
11 of 13 checks passed
@gherceg
Copy link
Contributor

gherceg commented Feb 2, 2024

@sravfeyn is it fair to say you can now remove the HELPME comment in the create_or_update_commcare_users_and_groups method? I imagine it no longer triggers an 'E' or 'F' classification in static analysis.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Risk: Medium Change affects files that have been flagged as medium risk.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants