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

Send the whole user preferences object to the backend when saving #28817

Merged
merged 3 commits into from
Sep 15, 2023

Conversation

ryanclark
Copy link
Contributor

@ryanclark ryanclark commented Jul 7, 2023

This changes the UI to always send the whole user preferences object when the user changes a preference. This is so we can support bool and int values in the future.

@ryanclark ryanclark force-pushed the ryan/userpreferences-add-fieldmask branch 2 times, most recently from a19330d to 4252ce0 Compare July 7, 2023 16:44
@ryanclark ryanclark requested a review from jakule July 7, 2023 16:46
Copy link
Contributor

@ibeckermayer ibeckermayer left a comment

Choose a reason for hiding this comment

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

Confused as to exactly why this is necessary. IIUC we're planning to add boolean fields to this UserPreferences object: why can we not make these fields optional/nullable and use that to represent whether or not we wish to update them? (I'm presuming you've thought of that and have a good answer, just asking for my own understanding).

lib/fieldmask/fieldmask.go Outdated Show resolved Hide resolved
Copy link
Contributor

@codingllama codingllama left a comment

Choose a reason for hiding this comment

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

Drive-by per Ryan's request.

Comment on lines 55 to 56
// fieldmask is the field mask of the user preferences to update.
google.protobuf.FieldMask fieldmask = 3;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// fieldmask is the field mask of the user preferences to update.
google.protobuf.FieldMask fieldmask = 3;
// User preferences update mask.
// Field names are specified using the proto names, for example $EXAMPLES_HERE.
// If absent a full update is performed, in accordance to legacy semantics.
google.protobuf.FieldMask update_mask = 3;

Are full updates of entire fields allowed, like "assist", or does one need to specify particular fields, like "assist.preferred_logins"?

Is the comment above correct in regards to full updates/legacy?

Reference for the update_mask name: https://cloud.google.com/apis/design/standard_methods#update

Copy link
Contributor Author

Choose a reason for hiding this comment

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

At the moment it's the full path, i.e. assist.preferred_logins. This is because the frontend code doesn't check if you're updating all of the object's values to simplify assist.view_mode and assist.preferred_logins into assist.

Do you think I should add support for specifying assist?

Also, I updated all the names to update mask.

Copy link
Contributor

Choose a reason for hiding this comment

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

There are no hard-and-fast rules for this, I think. Just document your decision and it's alright.

// }
//
// ```
func FromStruct(s any) *fieldmaskpb.FieldMask {
Copy link
Contributor

Choose a reason for hiding this comment

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

This defeats the purpose of the field mask, I think. You are supposed to write explicitly what fields you want to touch in the updates.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed - field masks work best when the client sets them. Inferring field masks just moves the issues around presence detection to where you infer the field mask.

lib/services/local/userpreferences.go Outdated Show resolved Hide resolved
lib/services/local/userpreferences.go Outdated Show resolved Hide resolved
@ryanclark ryanclark force-pushed the ryan/userpreferences-add-fieldmask branch from 4252ce0 to 0bcad8d Compare July 12, 2023 13:16
@ryanclark ryanclark requested a review from jakule July 12, 2023 13:18
@ryanclark ryanclark force-pushed the ryan/userpreferences-add-fieldmask branch from 0bcad8d to ec7228d Compare July 12, 2023 13:23
@tigrato
Copy link
Contributor

tigrato commented Jul 12, 2023

To leave my two cents here:
Field masks make sense when they represent what we want to update. In this case, as we use an intermediate structure to make the JSON marshal/unmarshal, there is no 1:1 guarantee between that structure and the protobuf representation.
This means that either we do a fieldmask translation between what users send via JSON and the fieldmask values in protobuf (these are the fields name and not the json name) or else the backend has to use the representations in JSON which it does little sense since it prevents any use of the API just for protobuf.

If everyone wants to follow this path I won't block it but for me, it doesn't make much sense to ask for fieldmasks from the client without there being a 1:1 guarantee between the object they send and the protobuf object.

@codingllama
Copy link
Contributor

To leave my two cents here (...)

Agree with @tigrato - the mask paths are supposed to come from the originator of the update, otherwise we are mostly shifting the problem around.

@ryanclark ryanclark force-pushed the ryan/userpreferences-add-fieldmask branch 2 times, most recently from cfcb8c8 to 8de25d1 Compare July 13, 2023 16:08
@codingllama
Copy link
Contributor

There are lots of eyes here already, some with fuller reviews than myself, so I'll un-assign me. Feel free to call me in again for design discussions, anytime. :)

@codingllama codingllama removed their request for review July 13, 2023 16:41
@jakule
Copy link
Contributor

jakule commented Jul 14, 2023

This PR has a lot of conversations, so let me summarize.
The problem that we're trying to solve is to update User Preferences in the backend. The main issue is that Go has default types, and it is impossible to distinguish between a default value and a value not being set. FieldMaks implemented in this PR, IMO add a lot of complexity.

Currently, on login WebUI already fetches the user preferences. If any changes are made, WebUI can apply those changes to the local copy and send the full version to the backend. This will keep the logic simple.
I know that we don't have very strict rules, but HTTP PUT should that the whole object by definition. It will be less surprising for new users to use our API if we decide to stick to the standard.
Last, we're not using field masks in any other place in our REST API, and I don't think that user preferences are the place where we should deviate from it. I think we should be consistent in our APIs as much as possible.

Let's change the API to take the full user preferences. This solves the default/not set problem. It also removes the merge/patch logic in the backend.

@ryanclark ryanclark force-pushed the ryan/userpreferences-add-fieldmask branch from 8de25d1 to dd5c658 Compare July 14, 2023 16:27
@ryanclark ryanclark changed the title Use a field mask for updating user preferences Send the whole user preferences object to the backend when saving Jul 14, 2023
@ibeckermayer
Copy link
Contributor

This PR has a lot of conversations, so let me summarize. The problem that we're trying to solve is to update User Preferences in the backend. The main issue is that Go has default types, and it is impossible to distinguish between a default value and a value not being set. FieldMaks implemented in this PR, IMO add a lot of complexity.

Currently, on login WebUI already fetches the user preferences. If any changes are made, WebUI can apply those changes to the local copy and send the full version to the backend. This will keep the logic simple. I know that we don't have very strict rules, but HTTP PUT should that the whole object by definition. It will be less surprising for new users to use our API if we decide to stick to the standard. Last, we're not using field masks in any other place in our REST API, and I don't think that user preferences are the place where we should deviate from it. I think we should be consistent in our APIs as much as possible.

Let's change the API to take the full user preferences. This solves the default/not set problem. It also removes the merge/patch logic in the backend.

relates to #29234

Copy link
Contributor

@jakule jakule left a comment

Choose a reason for hiding this comment

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

LGTM

@public-teleport-github-review-bot public-teleport-github-review-bot bot removed the request for review from ravicious July 18, 2023 18:27
@ryanclark ryanclark force-pushed the ryan/userpreferences-add-fieldmask branch from dd5c658 to 81b4d5a Compare September 12, 2023 21:24
@ryanclark ryanclark enabled auto-merge September 12, 2023 21:39
@ryanclark ryanclark force-pushed the ryan/userpreferences-add-fieldmask branch from 6fa51bc to 4a7c902 Compare September 13, 2023 13:25
@ryanclark ryanclark added this pull request to the merge queue Sep 14, 2023
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Sep 14, 2023
@ryanclark ryanclark added this pull request to the merge queue Sep 15, 2023
Merged via the queue into master with commit ae0afd5 Sep 15, 2023
@ryanclark ryanclark deleted the ryan/userpreferences-add-fieldmask branch September 15, 2023 14:54
@public-teleport-github-review-bot

@ryanclark See the table below for backport results.

Branch Result
branch/v14 Create PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants