Commit 371024f5 authored by Jason Goodman's avatar Jason Goodman Committed by Etienne Baqué

Use ReactiveCaching for Namespace User Cap Banner

parent df155fcb
......@@ -10,7 +10,7 @@ module EE
return false if alert_has_been_dismissed?(root_namespace)
can?(current_user, :admin_namespace, root_namespace) && user_cap_reached?(root_namespace)
can?(current_user, :admin_namespace, root_namespace) && root_namespace.user_cap_reached?(use_cache: true)
end
def hide_user_cap_alert_cookie_id(root_namespace)
......@@ -22,11 +22,5 @@ module EE
def alert_has_been_dismissed?(root_namespace)
cookies[hide_user_cap_alert_cookie_id(root_namespace)] == 'true'
end
def user_cap_reached?(root_namespace)
Rails.cache.fetch(root_namespace.namespace_user_cap_reached_cache_key, expires_in: 2.hours) do
root_namespace.user_cap_reached?
end
end
end
end
......@@ -14,6 +14,11 @@ module EE
include InsightsFeature
include HasWiki
include CanMoveRepositoryStorage
include ReactiveCaching
self.reactive_cache_work_type = :no_dependency
self.reactive_cache_refresh_interval = 10.minutes
self.reactive_cache_lifetime = 1.hour
add_authentication_token_field :saml_discovery_token, unique: false, token_generator: -> { Devise.friendly_token(8) }
......@@ -305,6 +310,16 @@ module EE
project
end
def calculate_reactive_cache
billable_members_count
end
def billable_members_count_with_reactive_cache
with_reactive_cache do |return_value|
return_value
end
end
override :billable_members_count
def billable_members_count(requested_hosted_plan = nil)
billable_ids = billed_user_ids(requested_hosted_plan)
......@@ -481,17 +496,16 @@ module EE
::Feature.enabled?(:iteration_cadences, self, default_enabled: :yaml)
end
def user_cap_reached?
def user_cap_reached?(use_cache: false)
return false unless ::Feature.enabled?(:saas_user_caps, root_ancestor, default_enabled: :yaml)
user_cap = root_ancestor.namespace_settings&.new_user_signups_cap
return false unless user_cap
user_cap <= root_ancestor.billable_members_count
end
members_count = use_cache ? root_ancestor.billable_members_count_with_reactive_cache : root_ancestor.billable_members_count
return false unless members_count
def namespace_user_cap_reached_cache_key
"namespace_user_cap_reached:#{root_ancestor.id}"
user_cap <= members_count
end
private
......
......@@ -30,8 +30,6 @@ module EE
before_create :set_membership_activation
after_commit :invalidate_namespace_user_cap_cache
scope :with_csv_entity_associations, -> do
includes(:user, source: [:route, :parent])
end
......@@ -151,11 +149,5 @@ module EE
self.state = group.user_cap_reached? ? STATE_AWAITING : STATE_ACTIVE
end
def invalidate_namespace_user_cap_cache
return unless group && ::Feature.enabled?(:saas_user_caps, group.root_ancestor, default_enabled: :yaml)
Rails.cache.delete(group.namespace_user_cap_reached_cache_key)
end
end
end
......@@ -2,7 +2,9 @@
require 'spec_helper'
RSpec.describe 'Namespace user cap reached alert', :feature, :js do
RSpec.describe 'Namespace user cap reached alert', :feature, :js, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
let_it_be(:group, refind: true) do
create(:group, :public,
namespace_settings: create(:namespace_settings, new_user_signups_cap: 2))
......@@ -21,6 +23,10 @@ RSpec.describe 'Namespace user cap reached alert', :feature, :js do
end
context 'with an exceeded user cap' do
before do
stub_cache(group)
end
it 'displays the banner to a group owner' do
sign_in(owner)
visit group_path(group)
......@@ -101,6 +107,7 @@ RSpec.describe 'Namespace user cap reached alert', :feature, :js do
other_group = create(:group, :public,
namespace_settings: create(:namespace_settings, new_user_signups_cap: 1))
other_group.add_owner(owner)
stub_cache(other_group)
sign_in(owner)
visit group_path(group)
dismiss_button.click
......@@ -121,11 +128,21 @@ RSpec.describe 'Namespace user cap reached alert', :feature, :js do
expect_group_page_for(group)
expect_banner_to_be_absent
end
it 'does not display the banner to a group owner on a reactive cache miss' do
stub_reactive_cache(group, nil)
sign_in(owner)
visit group_path(group)
expect_group_page_for(group)
expect_banner_to_be_absent
end
end
context 'with a user cap that has not been exceeded' do
before do
group.namespace_settings.update!(new_user_signups_cap: 4)
stub_cache(group)
end
it 'does not display the banner to a group owner' do
......@@ -140,6 +157,7 @@ RSpec.describe 'Namespace user cap reached alert', :feature, :js do
context 'without a user cap set' do
before do
group.namespace_settings.update!(new_user_signups_cap: nil)
stub_cache(group)
end
it 'does not display the banner to a group owner' do
......@@ -184,4 +202,10 @@ RSpec.describe 'Namespace user cap reached alert', :feature, :js do
def expect_banner_to_be_absent
expect(page).not_to have_text 'Your group has reached its billable member limit'
end
def stub_cache(group)
group_with_fresh_memoization = Group.find(group.id)
result = group_with_fresh_memoization.calculate_reactive_cache
stub_reactive_cache(group, result)
end
end
......@@ -2,7 +2,9 @@
require 'spec_helper'
RSpec.describe EE::NamespaceUserCapReachedAlertHelper do
RSpec.describe EE::NamespaceUserCapReachedAlertHelper, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
describe '#display_namespace_user_cap_reached_alert?' do
let_it_be(:group, refind: true) do
create(:group, :public,
......@@ -21,6 +23,7 @@ RSpec.describe EE::NamespaceUserCapReachedAlertHelper do
before do
allow(helper).to receive(:can?).with(owner, :admin_namespace, group).and_return(true)
allow(helper).to receive(:can?).with(developer, :admin_namespace, group).and_return(false)
stub_cache(group)
end
it 'returns true when the user cap is reached for a user who can admin the namespace' do
......@@ -35,24 +38,23 @@ RSpec.describe EE::NamespaceUserCapReachedAlertHelper do
expect(helper.display_namespace_user_cap_reached_alert?(group)).to be false
end
it 'caches the result' do
sign_in(owner)
expect(Rails.cache).to receive(:fetch).with("namespace_user_cap_reached:#{group.id}", expires_in: 2.hours)
helper.display_namespace_user_cap_reached_alert?(group)
end
it 'does not trigger reactive caching if there is no user cap set' do
group.namespace_settings.update!(new_user_signups_cap: nil)
it 'caches the result for a subgroup' do
sign_in(owner)
expect(Rails.cache).to receive(:fetch).with("namespace_user_cap_reached:#{group.id}", expires_in: 2.hours)
helper.display_namespace_user_cap_reached_alert?(subgroup)
expect(group).not_to receive(:with_reactive_cache)
expect(helper.display_namespace_user_cap_reached_alert?(group)).to be false
end
def sign_in(user)
allow(helper).to receive(:current_user).and_return(user)
end
def stub_cache(group)
group_with_fresh_memoization = Group.find(group.id)
result = group_with_fresh_memoization.calculate_reactive_cache
stub_reactive_cache(group, result)
end
end
end
......@@ -245,80 +245,4 @@ RSpec.describe Member, type: :model do
end
end
end
describe '#invalidate_namespace_user_cap_cache' do
let_it_be(:other_user) { create(:user) }
context 'when the :saas_user_caps feature flag is enabled for the root group' do
before do
stub_feature_flags(saas_user_caps: group)
end
it 'invalidates the namespace user cap reached cache when adding a member to a group' do
expect(Rails.cache).to receive(:delete).with("namespace_user_cap_reached:#{group.id}")
group.add_developer(other_user)
end
it 'invalidates the cache when adding a member to a subgroup' do
expect(Rails.cache).to receive(:delete).with("namespace_user_cap_reached:#{group.id}")
sub_group.add_developer(other_user)
end
it 'invalidates the cache when adding a member to a project' do
expect(Rails.cache).to receive(:delete).with("namespace_user_cap_reached:#{group.id}")
project.add_developer(other_user)
end
it 'invalidates the cache when removing a member from a group' do
expect(Rails.cache).to receive(:delete).with("namespace_user_cap_reached:#{group.id}")
member.destroy!
end
it 'invalidates the cache when removing a member from a project' do
project_member = project.add_developer(other_user)
expect(Rails.cache).to receive(:delete).with("namespace_user_cap_reached:#{group.id}")
project_member.destroy!
end
it 'invalidates the cache when changing the access level' do
guest_member = create(:group_member, :guest, group: group, user: other_user)
expect(Rails.cache).to receive(:delete).with("namespace_user_cap_reached:#{group.id}")
guest_member.update!(access_level: GroupMember::DEVELOPER)
end
end
context 'when the :saas_user_caps feature flag is globally enabled' do
before do
stub_feature_flags(saas_user_caps: true)
end
it 'does not try to invalidate the cache for a project with a user namespace' do
project_owner = create(:user)
personal_project = create(:project, namespace: project_owner.namespace)
expect(Rails.cache).not_to receive(:delete)
personal_project.add_developer(other_user)
end
end
context 'when the :saas_user_caps feature flag is disabled' do
before do
stub_feature_flags(saas_user_caps: false)
end
it 'does not invalidate the namespace user cap reached cache' do
expect(Rails.cache).not_to receive(:delete)
group.add_developer(other_user)
end
end
end
end
......@@ -2,7 +2,9 @@
require 'spec_helper'
RSpec.describe 'shared/namespace_user_cap_reached_alert' do
RSpec.describe 'shared/namespace_user_cap_reached_alert', :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
let_it_be(:group, refind: true) { create(:group, namespace_settings: create(:namespace_settings, new_user_signups_cap: 1)) }
let_it_be(:subgroup) { create(:group, parent: group) }
let_it_be(:other_group) { create(:group, namespace_settings: create(:namespace_settings, new_user_signups_cap: 1)) }
......@@ -18,6 +20,8 @@ RSpec.describe 'shared/namespace_user_cap_reached_alert' do
before do
allow(view).to receive(:current_user).and_return(owner)
stub_cache(group)
stub_cache(other_group)
end
it 'renders a link to pending user approvals' do
......@@ -44,4 +48,10 @@ RSpec.describe 'shared/namespace_user_cap_reached_alert' do
expect(rendered).to have_link('View pending user approvals', href: usage_quotas_path(project.namespace, anchor: 'seats-quota-tab'))
end
def stub_cache(group)
group_with_fresh_memoization = Group.find(group.id)
result = group_with_fresh_memoization.calculate_reactive_cache
stub_reactive_cache(group, result)
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment