-
-
Notifications
You must be signed in to change notification settings - Fork 218
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
Conversation
be2ad1f
to
5fbb769
Compare
5fbb769
to
21957e1
Compare
There was a problem hiding this 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?
current = 0 | ||
for row in self.user_specs: |
There was a problem hiding this comment.
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
current = 0 | |
for row in self.user_specs: | |
for i, row in enumerate(self.user_specs): |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 @property
s in this class should probably be @cached_property
s, right? Otherwise they'll be called for every row in the import.
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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.
except UserUploadError as e: | ||
self.status_row['flag'] = str(e) |
There was a problem hiding this comment.
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 UserUploadError
s for web users, but I see in the old version except UserUploadError as e:
is also in create_or_update_commcare_users_and_groups
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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_property
s? Then they'd be lazily initialized
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
self.status_row['row']['password'] = password | ||
if self.column_values['user_id'] and is_password(password): | ||
self.status_row['row']['password'] = 'REDACTED' |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
a816022
to
b19e19a
Compare
There was a problem hiding this 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
self.status_row['row']['password'] = password | ||
if self.column_values['user_id'] and is_password(password): | ||
self.status_row['row']['password'] = 'REDACTED' |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⭐
Oh whoops, one note - this line in the description is no longer accurate:
This PR now fixes that user data profile bug |
Support web user data update via bulk upload
@sravfeyn is it fair to say you can now remove the |
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
Labels & Review