Open kleintom opened 5 months ago
All modified and coverable lines are covered by tests :white_check_mark:
Project coverage is 72.78%. Comparing base (
de5c9f0
) to head (b764c70
). Report is 7 commits behind head on development.:exclamation: Current head b764c70 differs from pull request most recent head f994a57
Please upload reports for the commit f994a57 to get more accurate results.
:exclamation: Your organization needs to install the Codecov GitHub app to enable full functionality.
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
As general comment, my concern with this would be if there a potential to leak semi-sensitive (or private) information from other project members. If not a problem, after doing some revisions I think it is good to merge, otherwise I'd seek to make the user model tolerant to empty preferences and initialize them properly if so.
Correction, it would export preferences for all users, not just project members (although non-members have their email and name fields redacted).
I think we can review preferences to ensure nothing sensitive is there. There shouldn't be, if there is we need to strike it from there and handle it elsewhere. We should never store credentials, or any personal information beyond things like "I want the form layed out this way".
We don't save any sensitive information there, just layout preferences and copy paste shortcuts. Maybe we could add a checkbox option for this? Like:
[x] Export user preferences
If it is not checked, initialize the users preferences. I'm not sure if export projects will always need export user preferences or not
The initial motivation for this I guess was that @kleintom experienced software crashes on some tasks because preferences were not initialized. If that is the main goal here, then I think would be best to instead fix the model so User's preferences attribute returns a default set of preferences if none are stored in the database or have an invalid structure.
. If that is the main goal here, then I think would be best to instead fix the model so User's preferences attribute returns a default set of preferences if none are stored in the databa
Yes, that was intended already. Might just be that we have to re-initialize a legal JSON base, not sure, '{}'
The initial motivation for this I guess was that @kleintom experienced software crashes on some tasks because preferences were not initialized. If that is the main goal here, then I think would be best to instead fix the model so User's preferences attribute returns a default set of preferences if none are stored in the database or have an invalid structure.
Defaults are merged into any preferences being saved on before_save
here:
https://github.com/SpeciesFileGroup/taxonworks/blob/aa82f2998430e64b97965195f8854d0e397bc1d4/app/models/user/preferences.rb#L8
https://github.com/SpeciesFileGroup/taxonworks/blob/aa82f2998430e64b97965195f8854d0e397bc1d4/app/models/user/preferences.rb#L23-L32
The issue is that in the export/import case, users are created at the pg level directly by psql, and the default preferences in that case are {}
.
With those empty preferences we get exceptions like here: https://github.com/SpeciesFileGroup/taxonworks/blob/aa82f2998430e64b97965195f8854d0e397bc1d4/app/javascript/vue/tasks/collecting_events/new_collecting_event/components/CollectingEventForm.vue#L85 where we try to do preferences.layout
.
I don't think we can make the default for User preferences be BASE_PREFERENCES (can we?); if preferences are non-sensitive/will stay non-sensitive, then always importing preferences, for everyone, seems to me like the easiest solution here. (I'm not clear on why non-project users are exported, maybe so you're guaranteed to have an existing administrator account?) Other thoughts?
Have you tried preferences: "'#{conn.escape_string(user.preferences.to_json)}'"?
Based on what I'm seeing online I expected that to work, but it didn't.
With the current release code, if I do select preferences from users
(where I only have one user), it shows
"{\"layout\":{},\"disable_chime\":false,\"items_per_list_page\":20,\"taxon_name_autocomplete_redirects_to_valid\":false,\"default_hub_tab_order\":[\"tasks\",\"data\",\"favorite\"]}"
.
If I import using "'#{conn.escape_string(user.preferences.to_json)}'"
and then do the select it shows {"layout":{},"disable_chime":false,"items_per_list_page":20,"taxon_name_autocomplete_redirects_to_valid":false,"default_hub_tab_order":["tasks","data","favorite"]}
. (Which is what you would expect to see in a json field, no?)
But with that second imported preferences value, if I try to load a page that uses those preferences, or from the rails console I do User.first.preferences
, I get an error no implicit conversion of Hash into String (TypeError)
- that error hangs the app when you try to load a page that uses preferences.
It seems to expect the db value of preferences to be serialized json (which is what it appears to be with release code), not a json hash (though it's not clear to me in what sense the db value is a hash in this case). Schema.rb says t.json "preferences", default: {}
for preferences.
If you update preferences to be {}
(no quotes) it's fine with that, even though it's a "hash", not a double-quoted string...
I haven't been able to make sense of all of that yet; thoughts?
The initial motivation for this I guess was that @kleintom experienced software crashes on some tasks because preferences were not initialized. If that is the main goal here, then I think would be best to instead fix the model so User's preferences attribute returns a default set of preferences if none are stored in the database or have an invalid structure.
Defaults are merged into any preferences being saved on
before_save
here:The issue is that in the export/import case, users are created at the pg level directly by psql, and the default preferences in that case are
{}
.
In this case I would add this in user/preferences.rb
# Assuming this method will be instance method of User, if not the case then needs to be relocated (if possible)
def preferences
prefs = read_attribute(:preferences)
return prefs unless prefs.empty?
reset_preferences
read_attribute(:preferences)
end
What do you think? (cc @mjy)
With those empty preferences we get exceptions like here:
https://github.com/SpeciesFileGroup/taxonworks/blob/aa82f2998430e64b97965195f8854d0e397bc1d4/app/javascript/vue/tasks/collecting_events/new_collecting_event/components/CollectingEventForm.vue#L85 where we try to do
preferences.layout
.I don't think we can make the default for User preferences be BASE_PREFERENCES (can we?); if preferences are non-sensitive/will stay non-sensitive, then always importing preferences, for everyone, seems to me like the easiest solution here. (I'm not clear on why non-project users are exported, maybe so you're guaranteed to have an existing administrator account?) Other thoughts?
Non project users are exported because housekeeping of community data might reference them.
That fixes the exception issue, though it doesn't persist the new prefs to the database. I don't know, would you want to call save in the accessor in that case?
Do we want to not export preferences then? (Could still export plus add the reader for future-proofing in other export cases.)
would you want to call save in the accessor in that case?
No, never :).
Would be nice to figure out exactly how is ActiveRecord serializing the preferences attribute to make sure gsub
is the correct solution. If we can be certain the export is done properly, then I guess it is fine to start exporting preferences. Would also include the custom getter even if the export includes preferences.
I remember battling this. I just realized this is a json
not jsonb
field, IIRC the latter was my preference. So first we should probalby unify all our json
to one type. Can check migrations for pattern/comments.
The issue with user preferences showing up in the database as double-quoted strings: "{\"layout\":{},\"disable_chime\":false,\"items_per_list_page\":20,\"taxon_name_autocomplete_redirects_to_valid\":false,\"default_hub_tab_order\":[\"tasks\",\"data\",\"favorite\"]}"
seems to be because there's a store
property defined on that field: https://github.com/SpeciesFileGroup/taxonworks/blob/1263c7eb278000501643a182d80eeab0604a2f6f/app/models/user/preferences.rb#L7 From https://api.rubyonrails.org/classes/ActiveRecord/Store.html "Store gives you a thin wrapper around serialize", and the coder: JSON
allows the serialization to be stored on a JSON field.
Seems strange that you can't insert actual json into your json column anymore (and how does it work if you only have a store accessor on one of your many json keys?). In any case, the link suggests that if your underlying field is already json, then you should use store_accessor
instead of store
. I confirmed that does work: you can insert "normal" json such as '{"a": true}' and still read it as User.a. But if you make the switch to store_accessor
without migrating your old serialized data then you get errors lik a 500 in user/preferences.json.jbuilder when you try to do json.merge! @user.preferences
on a string.
So I'm skipping exporting user preferences for now on project export, but let me know if there's more you'd like me to work on here.
Oops, I'll check out the errors.
I modeled the json -> jsonb migration that I wrote on previous migrations doing the same thing: https://github.com/SpeciesFileGroup/taxonworks/blob/1263c7eb278000501643a182d80eeab0604a2f6f/db/migrate/20180322031114_sqed_depiction_hstore_to_jsonb.rb#L3-L4 https://github.com/SpeciesFileGroup/taxonworks/blob/1263c7eb278000501643a182d80eeab0604a2f6f/db/migrate/20180529170201_update_project_workbench_settings_to_preferences_jsonb.rb#L3
Note the default set to '{}'
and not {}
in all cases.
Most of the failing tests from my previous push are at https://github.com/SpeciesFileGroup/taxonworks/blob/1263c7eb278000501643a182d80eeab0604a2f6f/app/models/user.rb#L397
The issue seems to be that we're trying to read the value of the footprints
hash with key recently_visited
when the current value of the hash is "{}"
(not {}
). This would work if footprints
was a store
d field, but it's not (as determined by User.stored_attributes
). So in this case the default value of footprints should be {}
and not '{}'
. (Yuk.) Doing that fixes those tests. (Thank goodness for the tests!)
The Project migration linked above (which also renames workbench_settings
to preferences
) should be fine/correct because preferences
in Project is also store
d.
The sqed_depiction migration I think might be in error. specimen_coordinates
is currently unused and unaccessed, so no errors there, but if you create two sqed depictions and then visit /tasks/accessions/breakdown/buffered_data/thumb_navigator/2.html
you get a 500 on undefined method 'inject' for an instance of String
here: https://github.com/SpeciesFileGroup/taxonworks/blob/1263c7eb278000501643a182d80eeab0604a2f6f/app/models/sqed_depiction.rb#L218 because metadata_map
is the string "{}"
(the default value) and you're trying to do an inject
on it. If I change those default metadata_map
to '{}' instead of "{}"
then the page loads without error.
So I think the way forward is to do another migration to:
{}
(not '{}'
)metadata_map
and specimen_coordinates
defaults to {}
(not '{}'
)metadata_map
and specimen_coordinates
default values already in the database to {}
(not '{}'
).I can keep working on this - in which case let me know if everything sounds right - but it's a little hinky with the quotes vs unquotes and now the migrations are changing more, so feel free to take it if that works better.
I tested this locally with 1) default (reset) preferences, and 2)
Two caveats, please send back to me if there's any concern:
"
in the preferences (since I'm expecting that never happens...)