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

Migrate ::FeatureFlag and ::FeatureFlagsUser to ActiveRecord #15841

Conversation

amiedes
Copy link
Contributor

@amiedes amiedes commented Sep 17, 2020

Story details: https://app.clubhouse.io/cartoteam/story/104742

What does this PR do?

  • Migrates the ::FeatureFlag and ::FeatureFlagsUsere Sequel models to the ActiveRecord equivalents: Carto::FeatureFlag and Carto::FeatureFlagsUser
  • Unifies the API for reading user feature flags. Before the behavior was not coherent between ::User and Carto::User, and some method names were misleading.
  • Writes more specs for the modified methods

@amiedes amiedes changed the title Migrate ::FeatureFlat and ::FeatureFlagsUser to ActiveRecord Migrate ::FeatureFlag and ::FeatureFlagsUser to ActiveRecord Sep 17, 2020
@amiedes amiedes force-pushed the chore/ch104742/migrate-and-delete-old-featureflag-and-featureflaguser branch from 07499b4 to a67da0b Compare September 21, 2020 10:46
it { should be_false }
end
end
end
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was moved to user_commons_spec.rb

SequelRails.connection["select count(*) from feature_flags_users where user_id = '#{user_id}'"].first[:count].should eq 0
SequelRails.connection["select count(*) from feature_flags where id = '#{ff.id}'"].first[:count].should eq 1
end

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was moved to user_commons_spec.rb as describe '#update_feature_flags' do ...

@feature_flag.id = feature_flag_params[:id]
end
@feature_flag.name = feature_flag_params[:name]
@feature_flag.restricted = feature_flag_params[:restricted]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I refactored this code because it was a bit counterintuitive. We were assigning new values to the feature flag attributes even when the request only aimed to do a #destroy.


def create
@feature_flag.save
render json: @feature_flag, status: 204
Carto::FeatureFlag.create!(feature_flag_params)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I switched things like #save and #destroy for the ! equivalents so we are able to spot an error. Otherwise we were always returning a 204 no matter if the action succeeded or not.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that indeed keeps the behavior consistent

Oh! I did not notice that it's indeed the Sequel equivalent. So the consistency was "by accident" 🤣 🤣

end

def feature_flag_params
params.require(:feature_flag).permit(:id, :name, :restricted)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the standard Rails way to define which parameters are allowed to be updated from a controller

@@ -54,7 +54,7 @@ def create
if @user.save
@user.reload
CartoDB::Visualization::CommonDataService.load_common_data(@user, self) if @user.should_load_common_data?
@user.set_relationships_from_central(user_param)
@user.update_feature_flags(user_param[:feature_flags])
Copy link
Contributor Author

Choose a reason for hiding this comment

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

For simplicity, I removed the additional level of indirection added by set_relationships_from_central, since calling it is equivalent to calling update_feature_flags directly.

Copy link
Contributor

Choose a reason for hiding this comment

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

Is it ok to do @user.update_feature_flags(nil)? that's the check you may be missing, right? What's the semantics? Hope it is not to reset feature flags to empty. EDIT: to be honest, I don't know if that situation is possible, but I think it is as Central only sends "attributes" (or pseudo-attributes) that have changed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The way of calling it should be:

user.update_feature_flags(["123", "456"])

Hope it is not to reset feature flags to empty

This is taken care of here. On the other hand you made me realize that this is an important-enough case to have a specific spec to check that flags are not reset by accident, so I'm gonna add it 👍


def self.find_by_user(user)
FeatureFlag.where(restricted: false) + user.feature_flags.select(&:restricted)
end
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not used

Copy link
Contributor

Choose a reason for hiding this comment

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

🔥 !

@amiedes amiedes marked this pull request as ready for review September 22, 2020 09:40
@@ -160,7 +160,7 @@ def non_owner_users
end

def inheritable_feature_flags
inherit_owner_ffs ? owner.feature_flags_user : []
inherit_owner_ffs ? owner.self_feature_flags : Carto::FeatureFlag.none
end
Copy link
Contributor Author

@amiedes amiedes Sep 22, 2020

Choose a reason for hiding this comment

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

By returning Carto::FeatureFlag.none instead of [], we return an ActiveRecord association proxy, so we allow chaining more conditions to the query afterwards without running the query yet.

@amiedes
Copy link
Contributor Author

amiedes commented Sep 22, 2020

The acceptance in staging looked ✅ 👍

  • I tested updated feature flags from superadmin and if the flag was restricted it was preserved in CartoDB and if it was not restricted it was removed (in production it works like that)
  • Tested creating new feature flags from superadmin (both restricted and unrestricted) and the sync with CartoDB was ok

Copy link
Contributor

@rafatower rafatower left a comment

Choose a reason for hiding this comment

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

This is a lot of work and a lot of small conflicts solved nicely! 💪

The only thing that I think you need to review (or smoke test in staging) is syncing FF's from Central. EDIT: I mean, syncing something other than feature flags and checking it does not break with a nil usage (or delete flags)


def create
@feature_flag.save
render json: @feature_flag, status: 204
Carto::FeatureFlag.create!(feature_flag_params)
Copy link
Contributor

Choose a reason for hiding this comment

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

@@ -54,7 +54,7 @@ def create
if @user.save
@user.reload
CartoDB::Visualization::CommonDataService.load_common_data(@user, self) if @user.should_load_common_data?
@user.set_relationships_from_central(user_param)
@user.update_feature_flags(user_param[:feature_flags])
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it ok to do @user.update_feature_flags(nil)? that's the check you may be missing, right? What's the semantics? Hope it is not to reset feature flags to empty. EDIT: to be honest, I don't know if that situation is possible, but I think it is as Central only sends "attributes" (or pseudo-attributes) that have changed.


def self.find_by_user(user)
FeatureFlag.where(restricted: false) + user.feature_flags.select(&:restricted)
end
Copy link
Contributor

Choose a reason for hiding this comment

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

🔥 !

@@ -306,4 +306,36 @@ def has_access_to_coverband?
organization&.name == 'team'
end

def feature_flags
feature_flags_ids = self_feature_flags.pluck(:id) + Carto::FeatureFlag.not_restricted.pluck(:id)
feature_flags_ids += organization.inheritable_feature_flags.pluck(:id) if organization
Copy link
Contributor

Choose a reason for hiding this comment

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

@@ -39,8 +39,8 @@ class Carto::User < ActiveRecord::Base
has_one :owned_organization, class_name: Carto::Organization, inverse_of: :owner, foreign_key: :owner_id
has_one :static_notifications, class_name: Carto::UserNotification, inverse_of: :user

has_many :feature_flags_user, dependent: :destroy, foreign_key: :user_id, inverse_of: :user
has_many :feature_flags, through: :feature_flags_user
has_many :self_feature_flags_user, dependent: :destroy, foreign_key: :user_id, inverse_of: :user, class_name: Carto::FeatureFlagsUser
Copy link
Contributor

Choose a reason for hiding this comment

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

(maybe I'm picking this out of context, but why the rename?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is because the feature flags of a user are not only the ones he/she has, but also the ones inherited from the organization owner. I had manly two ways to address this to avoid ambiguity:

Option 1

Carto::User.self_feature_flags # => An association which only returns the user feature flags
Carto::User.feature_flags # => A method which returns self_feature_flags + organization feature flags

Option 2

Carto::User.feature_flags # => An association which only returns the user feature flags
Carto::User.effective_feature_flags # => A method which returns feature_flags + organization feature flags

Even though 2 would be more consistent with the naming we used to address the similar issue with the user quotas, finally I did 1 because I thought it was more natural for some other developer to try to retrieve the feature flags doing user.feature_flags rather than knowing that the real method he/she has to use is user.effective_feature_flags

Copy link
Contributor

@thedae thedae left a comment

Choose a reason for hiding this comment

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

Nice! 👍

@amiedes amiedes merged commit b451213 into master Sep 24, 2020
@amiedes amiedes deleted the chore/ch104742/migrate-and-delete-old-featureflag-and-featureflaguser branch September 24, 2020 08:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants