Commit 49c0ec6f authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch 'gl-badge-poc' into 'master'

Derive GitLab Team member badge from gitlab-com group

See merge request gitlab-org/gitlab!30884
parents 6a4264e1 7067c86d
...@@ -348,7 +348,7 @@ module EE ...@@ -348,7 +348,7 @@ module EE
def gitlab_employee? def gitlab_employee?
strong_memoize(:gitlab_employee) do strong_memoize(:gitlab_employee) do
if ::Gitlab.com? && ::Feature.enabled?(:gitlab_employee_badge) if ::Gitlab.com? && ::Feature.enabled?(:gitlab_employee_badge)
confirmed? && human? && Mail::Address.new(email).domain == "gitlab.com" human? && ::Gitlab::Com.gitlab_com_group_member_id?(id)
else else
false false
end end
......
# frozen_string_literal: true
module Gitlab
module Com
ALLOWED_USER_IDS_KEY = 'gitlab_com_group_allowed_user_ids'
EXPIRY_TIME_L1_CACHE = 1.minute
EXPIRY_TIME_L2_CACHE = 5.minutes
GITLAB_COM_GROUP = 'gitlab-com'
def self.gitlab_com_group_member_id?(user_id = nil)
return false unless Gitlab.com? && user_id && ::Feature.enabled?(:gitlab_employee_badge)
gitlab_com_user_ids.include?(user_id)
end
# rubocop: disable CodeReuse/ActiveRecord
def self.gitlab_com_user_ids
l1_cache_backend.fetch(ALLOWED_USER_IDS_KEY, expires_in: EXPIRY_TIME_L1_CACHE) do
l2_cache_backend.fetch(ALLOWED_USER_IDS_KEY, expires_in: EXPIRY_TIME_L2_CACHE) do
group = Group.find_by_name(GITLAB_COM_GROUP)
if group
GroupMembersFinder.new(group).execute.pluck(:user_id)
else
[]
end
end
end
end
# rubocop: enable CodeReuse/ActiveRecord
private_class_method :gitlab_com_user_ids
def self.expire_allowed_user_ids_cache
l1_cache_backend.delete(ALLOWED_USER_IDS_KEY)
l2_cache_backend.delete(ALLOWED_USER_IDS_KEY)
end
def self.l1_cache_backend
Gitlab::ProcessMemoryCache.cache_backend
end
def self.l2_cache_backend
Rails.cache
end
end
end
...@@ -238,23 +238,21 @@ describe Projects::IssuesController do ...@@ -238,23 +238,21 @@ describe Projects::IssuesController do
before do before do
sign_in(user) sign_in(user)
allow(Gitlab).to receive(:com?).and_return(true) allow(Gitlab).to receive(:com?).and_return(true)
note_user = discussion.author discussion.update(author: user)
note_user.update(email: email)
note_user.confirm
end end
shared_examples 'non inclusion of gitlab employee badge' do shared_context 'non inclusion of gitlab team member badge' do |result|
it 'does not render the is_gitlab_employee attribute' do it 'does not render the is_gitlab_employee attribute' do
subject subject
note_json = json_response.first['notes'].first note_json = json_response.first['notes'].first
expect(note_json['author'].has_key?('is_gitlab_employee')).to be false expect(note_json['author']['is_gitlab_employee']).to be result
end end
end end
context 'when user is a gitlab employee' do context 'when user is a gitlab team member' do
let(:email) { 'test@gitlab.com' } include_context 'gitlab team member'
it 'renders the is_gitlab_employee attribute' do it 'renders the is_gitlab_employee attribute' do
subject subject
...@@ -269,27 +267,19 @@ describe Projects::IssuesController do ...@@ -269,27 +267,19 @@ describe Projects::IssuesController do
stub_feature_flags(gitlab_employee_badge: false) stub_feature_flags(gitlab_employee_badge: false)
end end
it_behaves_like 'non inclusion of gitlab employee badge' it_behaves_like 'non inclusion of gitlab team member badge', nil
end end
end end
context 'when user is not a gitlab employee' do context 'when user is not a gitlab team member' do
let(:email) { 'test@example.com' } it_behaves_like 'non inclusion of gitlab team member badge', false
it 'shows is_gitlab_employee attribute as false' do
subject
note_json = json_response.first['notes'].first
expect(note_json['author']['is_gitlab_employee']).to be false
end
context 'when feature flag is disabled' do context 'when feature flag is disabled' do
before do before do
stub_feature_flags(gitlab_employee_badge: false) stub_feature_flags(gitlab_employee_badge: false)
end end
it_behaves_like 'non inclusion of gitlab employee badge' it_behaves_like 'non inclusion of gitlab team member badge', nil
end end
end end
end end
......
...@@ -53,6 +53,7 @@ describe IssuablesHelper do ...@@ -53,6 +53,7 @@ describe IssuablesHelper do
end end
describe '#gitlab_team_member_badge' do describe '#gitlab_team_member_badge' do
let(:user) { create(:user) }
let(:issue) { build(:issue, author: user) } let(:issue) { build(:issue, author: user) }
before do before do
...@@ -60,7 +61,7 @@ describe IssuablesHelper do ...@@ -60,7 +61,7 @@ describe IssuablesHelper do
end end
context 'when `:gitlab_employee_badge` feature flag is disabled' do context 'when `:gitlab_employee_badge` feature flag is disabled' do
let(:user) { build(:user, email: 'test@gitlab.com') } include_context 'gitlab team member'
before do before do
stub_feature_flags(gitlab_employee_badge: false) stub_feature_flags(gitlab_employee_badge: false)
...@@ -72,15 +73,13 @@ describe IssuablesHelper do ...@@ -72,15 +73,13 @@ describe IssuablesHelper do
end end
context 'when issue author is not a GitLab team member' do context 'when issue author is not a GitLab team member' do
let(:user) { build(:user, email: 'test@example.com') }
it 'returns nil' do it 'returns nil' do
expect(helper.gitlab_team_member_badge(issue.author)).to be_nil expect(helper.gitlab_team_member_badge(issue.author)).to be_nil
end end
end end
context 'when issue author is a GitLab team member' do context 'when issue author is a GitLab team member' do
let(:user) { build(:user, email: 'test@gitlab.com') } include_context 'gitlab team member'
it 'returns span with svg icon' do it 'returns span with svg icon' do
expect(helper.gitlab_team_member_badge(issue.author)).to have_selector('span > svg') expect(helper.gitlab_team_member_badge(issue.author)).to have_selector('span > svg')
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Com do
it { expect(described_class.l1_cache_backend).to eq(Gitlab::ProcessMemoryCache.cache_backend) }
it { expect(described_class.l2_cache_backend).to eq(Rails.cache) }
describe '.gitlab_team_member?' do
subject { described_class.gitlab_com_group_member_id?(user&.id) }
let(:user) { create(:user) }
before do
allow(Gitlab).to receive(:com?).and_return(true)
end
context 'when user is a gitlab team member' do
include_context 'gitlab team member'
it { is_expected.to be true }
context 'caching of allowed user IDs' do
before do
described_class.gitlab_com_group_member_id?(user&.id)
end
it_behaves_like 'allowed user IDs are cached'
end
end
context 'when user is not a gitlab team member' do
it { is_expected.to be false }
context 'caching of allowed user IDs' do
before do
described_class.gitlab_com_group_member_id?(user&.id)
end
it_behaves_like 'allowed user IDs are cached'
end
end
context 'when user is nil' do
let(:user) { nil }
it { is_expected.to be false }
end
context 'when gitlab-com group does not exist' do
before do
allow(Group).to receive(:find_by_name).and_return(nil)
end
it { is_expected.to be false }
end
context 'when feature flag is turned off' do
before do
stub_feature_flags(gitlab_employee_badge: false)
end
it { is_expected.to be false }
end
end
end
...@@ -1101,46 +1101,73 @@ describe User do ...@@ -1101,46 +1101,73 @@ describe User do
subject { user.gitlab_employee? } subject { user.gitlab_employee? }
where(:email, :is_com, :expected_result) do let_it_be(:gitlab_group) { create(:group, name: 'gitlab-com') }
'test@gitlab.com' | true | true let_it_be(:random_group) { create(:group, name: 'random-group') }
'test@example.com' | true | false
'test@gitlab.com' | false | false context 'based on group membership' do
'test@example.com' | false | false before do
allow(Gitlab).to receive(:com?).and_return(is_com)
end
context 'when user belongs to gitlab-com group' do
where(:is_com, :expected_result) do
true | true
false | false
end end
with_them do with_them do
let(:user) { build(:user, email: email) } let(:user) { create(:user) }
before do before do
allow(Gitlab).to receive(:com?).and_return(is_com) gitlab_group.add_user(user, Gitlab::Access::DEVELOPER)
end end
it { is_expected.to be expected_result } it { is_expected.to be expected_result }
end end
end
context 'when email is of Gitlab and is not confirmed' do context 'when user does not belongs to gitlab-com group' do
let(:user) { build(:user, email: 'test@gitlab.com', confirmed_at: nil) } where(:is_com, :expected_result) do
true | false
false | false
end
it { is_expected.to be false } with_them do
let(:user) { create(:user) }
before do
random_group.add_user(user, Gitlab::Access::DEVELOPER)
end
it { is_expected.to be expected_result }
end
end
end
context 'based on user type' do
before do
gitlab_group.add_user(user, Gitlab::Access::DEVELOPER)
end end
context 'when user is a bot' do context 'when user is a bot' do
let(:user) { build(:user, email: 'test@gitlab.com', user_type: :alert_bot) } let(:user) { build(:user, user_type: :alert_bot) }
it { is_expected.to be false } it { is_expected.to be false }
end end
context 'when user is ghost' do context 'when user is ghost' do
let(:user) { build(:user, :ghost, email: 'test@gitlab.com') } let(:user) { build(:user, :ghost) }
it { is_expected.to be false } it { is_expected.to be false }
end end
end
context 'when `:gitlab_employee_badge` feature flag is disabled' do context 'when `:gitlab_employee_badge` feature flag is disabled' do
let(:user) { build(:user, email: 'test@gitlab.com') } let(:user) { build(:user) }
before do before do
stub_feature_flags(gitlab_employee_badge: false) stub_feature_flags(gitlab_employee_badge: false)
gitlab_group.add_user(user, Gitlab::Access::DEVELOPER)
end end
it { is_expected.to be false } it { is_expected.to be false }
......
# frozen_string_literal: true
shared_context 'gitlab team member' do
let_it_be(:namespace) { create(:group, name: 'gitlab-com') }
before do
namespace.add_developer(user)
end
end
...@@ -4,6 +4,8 @@ require 'spec_helper' ...@@ -4,6 +4,8 @@ require 'spec_helper'
describe 'profiles/show' do describe 'profiles/show' do
context 'gitlab.com organization field' do context 'gitlab.com organization field' do
let(:user) { create(:user) }
before do before do
assign(:user, user) assign(:user, user)
allow(controller).to receive(:current_user).and_return(user) allow(controller).to receive(:current_user).and_return(user)
...@@ -12,8 +14,8 @@ describe 'profiles/show' do ...@@ -12,8 +14,8 @@ describe 'profiles/show' do
end end
context 'when `:gitlab_employee_badge` feature flag is enabled' do context 'when `:gitlab_employee_badge` feature flag is enabled' do
context 'and when user has an `@gitlab.com` email address' do context 'and when user is a gitlab team member' do
let(:user) { create(:user, email: 'test@gitlab.com') } include_context 'gitlab team member'
it 'displays the organization field as `readonly` with a `value` of `GitLab`' do it 'displays the organization field as `readonly` with a `value` of `GitLab`' do
render render
...@@ -22,9 +24,7 @@ describe 'profiles/show' do ...@@ -22,9 +24,7 @@ describe 'profiles/show' do
end end
end end
context 'and when a user does not have an `@gitlab.com` email' do context 'and when a user is not a gitlab team member' do
let(:user) { create(:user, email: 'test@example.com') }
it 'displays an editable organization field' do it 'displays an editable organization field' do
render render
...@@ -38,8 +38,8 @@ describe 'profiles/show' do ...@@ -38,8 +38,8 @@ describe 'profiles/show' do
stub_feature_flags(gitlab_employee_badge: false) stub_feature_flags(gitlab_employee_badge: false)
end end
context 'and when a user has an `@gitlab.com` email' do context 'and when a user is a gitlab team member' do
let(:user) { create(:user, email: 'test@gitlab.com') } include_context 'gitlab team member'
it 'displays an editable organization field' do it 'displays an editable organization field' do
render render
......
...@@ -6,7 +6,9 @@ describe 'projects/issues/show' do ...@@ -6,7 +6,9 @@ describe 'projects/issues/show' do
include_context 'project show action' include_context 'project show action'
context 'when issue is created by a GitLab team member' do context 'when issue is created by a GitLab team member' do
let(:user) { create(:user, email: 'test@gitlab.com') } let(:user) { create(:user) }
include_context 'gitlab team member'
before do before do
allow(Gitlab).to receive(:com?).and_return(true) allow(Gitlab).to receive(:com?).and_return(true)
......
...@@ -6,7 +6,9 @@ describe 'projects/merge_requests/show.html.haml' do ...@@ -6,7 +6,9 @@ describe 'projects/merge_requests/show.html.haml' do
include_context 'merge request show action' include_context 'merge request show action'
context 'when merge request is created by a GitLab team member' do context 'when merge request is created by a GitLab team member' do
let(:user) { create(:user, email: 'test@gitlab.com') } let(:user) { create(:user) }
include_context 'gitlab team member'
before do before do
allow(Gitlab).to receive(:com?).and_return(true) allow(Gitlab).to receive(:com?).and_return(true)
......
...@@ -3,41 +3,6 @@ ...@@ -3,41 +3,6 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::PerformanceBar do describe Gitlab::PerformanceBar do
shared_examples 'allowed user IDs are cached' do
before do
# Warm the caches
described_class.enabled_for_user?(user)
end
it 'caches the allowed user IDs in cache', :use_clean_rails_memory_store_caching do
expect do
expect(described_class.l1_cache_backend).to receive(:fetch).and_call_original
expect(described_class.l2_cache_backend).not_to receive(:fetch)
expect(described_class.enabled_for_user?(user)).to be_truthy
end.not_to exceed_query_limit(0)
end
it 'caches the allowed user IDs in L1 cache for 1 minute', :use_clean_rails_memory_store_caching do
Timecop.travel 2.minutes do
expect do
expect(described_class.l1_cache_backend).to receive(:fetch).and_call_original
expect(described_class.l2_cache_backend).to receive(:fetch).and_call_original
expect(described_class.enabled_for_user?(user)).to be_truthy
end.not_to exceed_query_limit(0)
end
end
it 'caches the allowed user IDs in L2 cache for 5 minutes', :use_clean_rails_memory_store_caching do
Timecop.travel 6.minutes do
expect do
expect(described_class.l1_cache_backend).to receive(:fetch).and_call_original
expect(described_class.l2_cache_backend).to receive(:fetch).and_call_original
expect(described_class.enabled_for_user?(user)).to be_truthy
end.not_to exceed_query_limit(2)
end
end
end
it { expect(described_class.l1_cache_backend).to eq(Gitlab::ProcessMemoryCache.cache_backend) } it { expect(described_class.l1_cache_backend).to eq(Gitlab::ProcessMemoryCache.cache_backend) }
it { expect(described_class.l2_cache_backend).to eq(Rails.cache) } it { expect(described_class.l2_cache_backend).to eq(Rails.cache) }
...@@ -82,8 +47,17 @@ describe Gitlab::PerformanceBar do ...@@ -82,8 +47,17 @@ describe Gitlab::PerformanceBar do
expect(described_class.enabled_for_user?(user)).to be_falsy expect(described_class.enabled_for_user?(user)).to be_falsy
end end
context 'caching of allowed user IDs' do
subject { described_class.enabled_for_user?(user) }
before do
# Warm the caches
described_class.enabled_for_user?(user)
end
it_behaves_like 'allowed user IDs are cached' it_behaves_like 'allowed user IDs are cached'
end end
end
context 'when user is a member of the allowed group' do context 'when user is a member of the allowed group' do
before do before do
...@@ -94,9 +68,18 @@ describe Gitlab::PerformanceBar do ...@@ -94,9 +68,18 @@ describe Gitlab::PerformanceBar do
expect(described_class.enabled_for_user?(user)).to be_truthy expect(described_class.enabled_for_user?(user)).to be_truthy
end end
context 'caching of allowed user IDs' do
subject { described_class.enabled_for_user?(user) }
before do
# Warm the caches
described_class.enabled_for_user?(user)
end
it_behaves_like 'allowed user IDs are cached' it_behaves_like 'allowed user IDs are cached'
end end
end end
end
context 'when allowed group is nested' do context 'when allowed group is nested' do
let!(:nested_my_group) { create(:group, parent: create(:group, path: 'my-org'), path: 'my-group') } let!(:nested_my_group) { create(:group, parent: create(:group, path: 'my-org'), path: 'my-group') }
......
# frozen_string_literal: true
shared_examples 'allowed user IDs are cached' do
it 'caches the allowed user IDs in cache', :use_clean_rails_memory_store_caching do
expect do
expect(described_class.l1_cache_backend).to receive(:fetch).and_call_original
expect(described_class.l2_cache_backend).not_to receive(:fetch)
expect(subject).to be_truthy
end.not_to exceed_query_limit(0)
end
it 'caches the allowed user IDs in L1 cache for 1 minute', :use_clean_rails_memory_store_caching do
Timecop.travel 2.minutes do
expect do
expect(described_class.l1_cache_backend).to receive(:fetch).and_call_original
expect(described_class.l2_cache_backend).to receive(:fetch).and_call_original
expect(subject).to be_truthy
end.not_to exceed_query_limit(0)
end
end
it 'caches the allowed user IDs in L2 cache for 5 minutes', :use_clean_rails_memory_store_caching do
Timecop.travel 6.minutes do
expect do
expect(described_class.l1_cache_backend).to receive(:fetch).and_call_original
expect(described_class.l2_cache_backend).to receive(:fetch).and_call_original
expect(subject).to be_truthy
end.not_to exceed_query_limit(2)
end
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