Export user preferences on project export#3968
Export user preferences on project export#3968kleintom wants to merge 8 commits intoSpeciesFileGroup:developmentfrom
Conversation
Otherwise you get exceptions such as when tasks like New CE try to use Preferences.layout
Codecov ReportAll modified and coverable lines are covered by tests ✅
❗ Your organization needs to install the Codecov GitHub app to enable full functionality. Additional details and impacted files@@ Coverage Diff @@
## development #3968 +/- ##
================================================
+ Coverage 58.17% 72.78% +14.60%
================================================
Files 1655 1935 +280
Lines 52473 66524 +14051
================================================
+ Hits 30527 48419 +17892
+ Misses 21946 18105 -3841 ☔ View full report in Codecov by Sentry. |
| <h3> Export SQL </h3> | ||
| <p><em>Generate a downloadable copy of the database (PostgreSQL dump) with all data referenced in this project. Includes Community data like Sources, People, and Repositories.</em></p> | ||
|
|
||
| <p><em>All exported user passwords are set to</em> 'taxonworks'.</p> |
There was a problem hiding this comment.
Because of this (that I totally forgot about) then would be nice to add a warning that it is not a good idea at all to build a public TW instance out of the generated dump.
lib/export/project_data/sql.rb
Outdated
| is_administrator: user.is_administrator || 'NULL', | ||
| hub_tab_order: "'{#{conn.escape_string(user.hub_tab_order.join(','))}}'" | ||
| hub_tab_order: "'{#{conn.escape_string(user.hub_tab_order.join(','))}}'", | ||
| preferences: %['"#{conn.escape_string(JSON.generate(user.preferences)).gsub('"', '\"')}"'] |
There was a problem hiding this comment.
Have you tried preferences: "'#{conn.escape_string(user.preferences.to_json)}'"? I think ' is the char that needs to be escaped, not ".
3.3.1 :017 > puts ActiveRecord::Base.connection.raw_connection.escape_string(%["hello" 'bye'])
"hello" ''bye''|
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? [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. |
Yes, that was intended already. Might just be that we have to re-initialize a legal JSON base, not sure, '{}' |
This reverts commit f994a57. Decided to go the route of making sure preferences have a default value instead - see the discussion at SpeciesFileGroup#3968
Because all passwords are set to 'taxonworks'.
Defaults are merged into any preferences being saved on taxonworks/app/models/user/preferences.rb Lines 23 to 32 in aa82f29 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: 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? |
Based on what I'm seeing online I expected that to work, but it didn't. With the current release code, if I do If I import using 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 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 If you update preferences to be I haven't been able to make sense of all of that yet; thoughts? |
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)
endWhat do you think? (cc @mjy)
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.) |
No, never :). |
|
Would be nice to figure out exactly how is ActiveRecord serializing the preferences attribute to make sure |
|
I remember battling this. I just realized this is a |
It's included with all the other property accessors at https://github.com/SpeciesFileGroup/taxonworks/blob/aa82f2998430e64b97965195f8854d0e397bc1d4/app/models/user/preferences.rb#L7
Thanks to Hernán for the suggestion and code. For when preferences are empty, in this case because the project was imported via psql and so avoided the before_save check that normally would fill any unset preferences. Empty prefs lead to exceptions on things like preferences.layout reads, which assume default preferences exist. Note that in general the preferences returned here won't be saved to the database.
`change_column` is always irreversible, so we provide down here.
|
The issue with user preferences showing up in the database as double-quoted strings: 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 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: Note the default set to Most of the failing tests from my previous push are at Line 397 in 1263c7e 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 stored 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 The sqed_depiction migration I think might be in error. taxonworks/app/models/sqed_depiction.rb Line 218 in 1263c7e 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:
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. |
|
Coming back to this again since it's still a pain and becoming moreso now that we store more user prefs. Reminder of the basic issue:
These two json interpretations are ADDITIVE, not complementary :( PG does its conversion, AR does its own additional conversion. (Project may have the same issue with its preferences.) # IMPORTANT: Double-encoding behavior with `store` and `json` column type
#
# The preferences column is a PostgreSQL `json` type (see db/schema.rb), but
# we also use `store` with `coder: JSON` here (below). This creates a
# double-encoding/decoding flow:
#
# When WRITING to the database:
# 1. User model has a preferences Hash: { disable_chime: false }
# 2. ActiveRecord::Type::Serialized (from `store`) encodes it:
# '{"disable_chime":false}'
# 3. PostgreSQL json type stores this JSON string AS a json value:
# '"{"disable_chime":false}"' (Note: The database stores a JSON string
# containing another JSON string)
#
# When READING from the database:
# 1. PostgreSQL returns the json column value: '{"disable_chime":false}' (a
# Ruby String)
# 2. ActiveRecord::Type::Serialized (from `store`) decodes it: {
# disable_chime: false }
# 3. User model receives the preferences Hash
#
# This means:
# - In PostgreSQL, preferences are stored as:
# '"{\\"disable_chime\\":false}"' (a JSON string value, not a JSON object)
# - Project exports must use JSON.generate(JSON.generate(user.preferences))
# to match this double-encoding behavior (see
# lib/export/project_data/sql.rb)
# - Raw SQL queries will return a JSON string, not a parsed object
#
# Historical note: This pattern works but is unconventional. Typically with a
# `json` column, you would NOT use `coder: JSON` since PostgreSQL's adapter
# already handles JSON encoding. Changing this would require a migration and
# careful data conversion.
store :preferences, accessors: [:disable_chime], coder: JSONPossible actions regarding the double encodinga) Just deal with it. It's possible now that we understand it (see new commit comment below).
(We could add backup columns as we have in other similar cases, if we do change things.) Discussion regarding exporting prefs with Project export
|
|
(This whole branch will need to be cleaned up of course, I'm not looking to push anything to dev right away.) |
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...)