Commit 0b9f213e authored by Corinna Wiesner's avatar Corinna Wiesner

Expose is_using_seat attribute for Member in API

Add association :max_access_level_membership to User to avoid 1+n
queries in User#highest_role
parent fdd4a846
...@@ -101,6 +101,7 @@ class User < ApplicationRecord ...@@ -101,6 +101,7 @@ class User < ApplicationRecord
# Groups # Groups
has_many :members has_many :members
has_one :max_access_level_membership, -> { select(:id, :user_id, :access_level).order(access_level: :desc).readonly }, class_name: 'Member'
has_many :group_members, -> { where(requested_at: nil) }, source: 'GroupMember' has_many :group_members, -> { where(requested_at: nil) }, source: 'GroupMember'
has_many :groups, through: :group_members has_many :groups, through: :group_members
has_many :owned_groups, -> { where(members: { access_level: Gitlab::Access::OWNER }) }, through: :group_members, source: :group has_many :owned_groups, -> { where(members: { access_level: Gitlab::Access::OWNER }) }, through: :group_members, source: :group
...@@ -1022,7 +1023,7 @@ class User < ApplicationRecord ...@@ -1022,7 +1023,7 @@ class User < ApplicationRecord
end end
def highest_role def highest_role
members.maximum(:access_level) || Gitlab::Access::NO_ACCESS max_access_level_membership&.access_level || Gitlab::Access::NO_ACCESS
end end
def accessible_deploy_keys def accessible_deploy_keys
......
---
title: Expose is_using_seat attribute for Member in API
merge_request: 21496
author:
type: added
...@@ -93,6 +93,9 @@ module EE ...@@ -93,6 +93,9 @@ module EE
expose :group_saml_identity, expose :group_saml_identity,
using: ::API::Entities::Identity, using: ::API::Entities::Identity,
if: -> (member, options) { Ability.allowed?(options[:current_user], :read_group_saml_identity, member.source) } if: -> (member, options) { Ability.allowed?(options[:current_user], :read_group_saml_identity, member.source) }
expose :is_using_seat, if: -> (member, options) { options[:show_seat_info] } do |member, _options|
!!member.user&.using_license_seat?
end
end end
end end
......
...@@ -17,6 +17,7 @@ module EE ...@@ -17,6 +17,7 @@ module EE
override :retrieve_members override :retrieve_members
def retrieve_members(source, params:, deep: false) def retrieve_members(source, params:, deep: false)
members = super members = super
members = members.includes(user: :max_access_level_membership)
if can_view_group_identity?(source) if can_view_group_identity?(source)
members = members.includes(user: :group_saml_identities) members = members.includes(user: :group_saml_identities)
......
...@@ -29,6 +29,13 @@ describe API::Members do ...@@ -29,6 +29,13 @@ describe API::Members do
end end
describe 'GET /groups/:id/members' do describe 'GET /groups/:id/members' do
it 'matches json schema' do
get api("/groups/#{group.to_param}/members", owner)
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/members')
end
context 'when a group has SAML provider configured' do context 'when a group has SAML provider configured' do
let(:maintainer) { create(:user) } let(:maintainer) { create(:user) }
...@@ -74,6 +81,40 @@ describe API::Members do ...@@ -74,6 +81,40 @@ describe API::Members do
end end
end end
end end
context 'with is_using_seat' do
shared_examples 'seat information not included' do
it 'returns a list of users that does not contain the is_using_seat attribute' do
get api(api_url, owner)
expect(response).to have_gitlab_http_status(200)
expect(json_response.size).to eq(1)
expect(json_response.first.keys).not_to include('is_using_seat')
end
end
context 'with show_seat_info set to true' do
it 'returns a list of users that contains the is_using_seat attribute' do
get api("/groups/#{group.to_param}/members?show_seat_info=true", owner)
expect(response).to have_gitlab_http_status(200)
expect(json_response.size).to eq(1)
expect(json_response.first['is_using_seat']).to be_truthy
end
end
context 'with show_seat_info set to false' do
let(:api_url) { "/groups/#{group.to_param}/members?show_seat_info=false" }
it_behaves_like 'seat information not included'
end
context 'with no show_seat_info set' do
let(:api_url) { "/groups/#{group.to_param}/members" }
it_behaves_like 'seat information not included'
end
end
end end
shared_examples 'POST /:source_type/:id/members' do |source_type| shared_examples 'POST /:source_type/:id/members' do |source_type|
......
...@@ -46,7 +46,7 @@ module API ...@@ -46,7 +46,7 @@ module API
end end
def present_members(members) def present_members(members)
present members, with: Entities::Member, current_user: current_user present members, with: Entities::Member, current_user: current_user, show_seat_info: params[:show_seat_info]
end end
end end
end end
......
...@@ -19,6 +19,7 @@ module API ...@@ -19,6 +19,7 @@ module API
params do params do
optional :query, type: String, desc: 'A query string to search for members' optional :query, type: String, desc: 'A query string to search for members'
optional :user_ids, type: Array[Integer], desc: 'Array of user ids to look up for membership' optional :user_ids, type: Array[Integer], desc: 'Array of user ids to look up for membership'
optional :show_seat_info, type: Boolean, desc: 'Show seat information for members'
use :optional_filter_params_ee use :optional_filter_params_ee
use :pagination use :pagination
end end
...@@ -37,6 +38,7 @@ module API ...@@ -37,6 +38,7 @@ module API
params do params do
optional :query, type: String, desc: 'A query string to search for members' optional :query, type: String, desc: 'A query string to search for members'
optional :user_ids, type: Array[Integer], desc: 'Array of user ids to look up for membership' optional :user_ids, type: Array[Integer], desc: 'Array of user ids to look up for membership'
optional :show_seat_info, type: Boolean, desc: 'Show seat information for members'
use :pagination use :pagination
end end
......
{
"type": "array",
"items": {
"type": "object",
"properties" : {
"id": { "type": "integer" },
"name": { "type": "string" },
"username": { "type": "string" },
"state": { "type": "string" },
"avatar_url": { "type": ["string", "null"] },
"web_url": { "type": ["string", "null"] },
"access_level": { "type": "integer" },
"expires_at": { "type": ["date", "null"] },
"is_using_seat": { "type": "boolean" }
},
"required": [
"id", "name", "username", "state",
"web_url", "access_level", "expires_at"
],
"additionalProperties": false
}
}
...@@ -25,6 +25,7 @@ describe User, :do_not_mock_admin_mode do ...@@ -25,6 +25,7 @@ describe User, :do_not_mock_admin_mode do
describe 'associations' do describe 'associations' do
it { is_expected.to have_one(:namespace) } it { is_expected.to have_one(:namespace) }
it { is_expected.to have_one(:status) } it { is_expected.to have_one(:status) }
it { is_expected.to have_one(:max_access_level_membership) }
it { is_expected.to have_many(:snippets).dependent(:destroy) } it { is_expected.to have_many(:snippets).dependent(:destroy) }
it { is_expected.to have_many(:members) } it { is_expected.to have_many(:members) }
it { is_expected.to have_many(:project_members) } it { is_expected.to have_many(:project_members) }
...@@ -825,9 +826,36 @@ describe User, :do_not_mock_admin_mode do ...@@ -825,9 +826,36 @@ describe User, :do_not_mock_admin_mode do
describe '#highest_role' do describe '#highest_role' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:group) { create(:group) } let(:group) { create(:group) }
context 'with association :max_access_level_membership' do
let(:another_user) { create(:user) }
before do
create(:project, group: group) do |project|
group.add_user(user, GroupMember::GUEST)
group.add_user(another_user, GroupMember::DEVELOPER)
end
create(:project, group: create(:group)) do |project|
project.add_guest(another_user)
end
create(:project, group: create(:group)) do |project|
project.add_maintainer(user)
end
end
it 'returns the correct highest role' do
users = User.includes(:max_access_level_membership).where(id: [user.id, another_user.id])
expect(users.collect { |u| [u.id, u.highest_role] }).to contain_exactly(
[user.id, Gitlab::Access::MAINTAINER],
[another_user.id, Gitlab::Access::DEVELOPER]
)
end
end
it 'returns NO_ACCESS if none has been set' do it 'returns NO_ACCESS if none has been set' do
expect(user.highest_role).to eq(Gitlab::Access::NO_ACCESS) expect(user.highest_role).to eq(Gitlab::Access::NO_ACCESS)
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