Commit 376d7c15 authored by Peter Hegman's avatar Peter Hegman Committed by Sean McGivern

Refactor helper method to serializer

In preparation to also use for project members
parent 1c0f1851
......@@ -28,13 +28,13 @@ export default {
if (this.isCurrentUser) {
return sprintf(
s__('Members|Are you sure you want to withdraw your access request for "%{source}"'),
{ source: source.name },
{ source: source.fullName },
);
}
return sprintf(
s__('Members|Are you sure you want to deny %{usersName}\'s request to join "%{source}"'),
{ usersName: user.name, source: source.name },
{ usersName: user.name, source: source.fullName },
);
},
},
......
......@@ -25,7 +25,7 @@ export default {
s__(
'Members|Are you sure you want to revoke the invitation for %{inviteEmail} to join "%{source}"',
),
{ inviteEmail: invite.email, source: source.name },
{ inviteEmail: invite.email, source: source.fullName },
);
},
},
......
......@@ -36,7 +36,7 @@ export default {
s__('Members|Are you sure you want to remove %{usersName} from "%{source}"'),
{
usersName: user.name,
source: source.name,
source: source.fullName,
},
);
}
......@@ -44,7 +44,7 @@ export default {
return sprintf(
s__('Members|Are you sure you want to remove this orphaned member from "%{source}"'),
{
source: source.name,
source: source.fullName,
},
);
},
......
......@@ -35,7 +35,7 @@ export default {
return this.memberPath.replace(/:id$/, 'leave');
},
modalTitle() {
return sprintf(s__('Members|Leave "%{source}"'), { source: this.member.source.name });
return sprintf(s__('Members|Leave "%{source}"'), { source: this.member.source.fullName });
},
},
methods: {
......@@ -59,7 +59,7 @@ export default {
<gl-form ref="form" :action="leavePath" method="post">
<p>
<gl-sprintf :message="$options.modalContent">
<template #source>{{ member.source.name }}</template>
<template #source>{{ member.source.fullName }}</template>
</gl-sprintf>
</p>
......
......@@ -22,6 +22,6 @@ export default {
<template>
<span v-if="isDirectMember">{{ __('Direct member') }}</span>
<a v-else v-gl-tooltip.hover :title="__('Inherited')" :href="memberSource.webUrl">{{
memberSource.name
memberSource.fullName
}}</a>
</template>
......@@ -18,7 +18,7 @@ module Groups::GroupMembersHelper
end
def members_data_json(group, members)
members_data(group, members).to_json
MemberSerializer.new.represent(members, { current_user: current_user, group: group }).to_json
end
# Overridden in `ee/app/helpers/ee/groups/group_members_helper.rb`
......@@ -38,84 +38,6 @@ module Groups::GroupMembersHelper
group_id: group.id
}
end
private
def members_data(group, members)
members.map do |member|
user = member.user
source = member.source
data = {
id: member.id,
created_at: member.created_at,
expires_at: member.expires_at&.to_time,
requested_at: member.requested_at,
can_update: member.can_update?,
can_remove: member.can_remove?,
access_level: {
string_value: member.human_access,
integer_value: member.access_level
},
source: {
id: source.id,
name: source.full_name,
web_url: Gitlab::UrlBuilder.build(source)
},
valid_roles: member.valid_level_roles
}.merge(member_created_by_data(member.created_by))
if member.invite?
data[:invite] = member_invite_data(member)
elsif user.present?
data[:user] = member_user_data(user)
end
data
end
end
def member_created_by_data(created_by)
return {} unless created_by.present?
{
created_by: {
name: created_by.name,
web_url: Gitlab::UrlBuilder.build(created_by)
}
}
end
def member_user_data(user)
{
id: user.id,
name: user.name,
username: user.username,
web_url: Gitlab::UrlBuilder.build(user),
avatar_url: avatar_icon_for_user(user, AVATAR_SIZE),
blocked: user.blocked?,
two_factor_enabled: user.two_factor_enabled?
}.merge(member_user_status_data(user.status))
end
def member_user_status_data(status)
return {} unless status.present?
{
status: {
emoji: status.emoji,
message_html: status.message_html
}
}
end
def member_invite_data(member)
{
email: member.invite_email,
avatar_url: avatar_icon_for_email(member.invite_email, AVATAR_SIZE),
can_resend: member.can_resend_invite?
}
end
end
Groups::GroupMembersHelper.prepend_if_ee('EE::Groups::GroupMembersHelper')
......@@ -13,6 +13,8 @@ class Member < ApplicationRecord
include FromUnion
include UpdateHighestRole
AVATAR_SIZE = 40
attr_accessor :raw_invite_token
belongs_to :created_by, class_name: "User"
......
# frozen_string_literal: true
class MemberEntity < Grape::Entity
include RequestAwareEntity
include AvatarsHelper
expose :id
expose :created_at
expose :expires_at do |member|
member.expires_at&.to_time
end
expose :requested_at
expose :created_by, if: -> (member) { member.created_by.present? } do |member|
UserEntity.represent(member.created_by, only: [:name, :web_url])
end
expose :can_update do |member|
member.can_update?
end
expose :can_remove do |member|
member.can_remove?
end
expose :access_level do
expose :human_access, as: :string_value
expose :access_level, as: :integer_value
end
expose :source do |member|
GroupEntity.represent(member.source, only: [:id, :full_name, :web_url])
end
expose :valid_level_roles, as: :valid_roles
expose :user, if: -> (member) { member.user.present? }, using: MemberUserEntity
expose :invite, if: -> (member) { member.invite? } do
expose :email do |member|
member.invite_email
end
expose :avatar_url do |member|
avatar_icon_for_email(member.invite_email, Member::AVATAR_SIZE)
end
expose :can_resend do |member|
member.can_resend_invite?
end
end
end
MemberEntity.prepend_if_ee('EE::MemberEntity')
# frozen_string_literal: true
class MemberSerializer < BaseSerializer
entity MemberEntity
end
# frozen_string_literal: true
class MemberUserEntity < UserEntity
unexpose :show_status
unexpose :path
unexpose :state
unexpose :status_tooltip_html
expose :avatar_url do |user|
user.avatar_url(size: Member::AVATAR_SIZE, only_path: false)
end
expose :blocked do |user|
user.blocked?
end
expose :two_factor_enabled do |user|
user.two_factor_enabled?
end
expose :status, if: -> (user) { user.status.present? } do
expose :emoji do |user|
user.status.emoji
end
end
end
MemberUserEntity.prepend_if_ee('EE::MemberUserEntity')
......@@ -14,21 +14,4 @@ module EE::Groups::GroupMembersHelper
ldap_override_path: override_group_group_member_path(group, ':id')
})
end
private
override :members_data
def members_data(group, members)
ce_members = super(group, members)
members.map.with_index do |member, index|
ce_members[index].merge({
using_license: can?(current_user, :owner_access, group) && member.user&.using_gitlab_com_seat?(group),
group_sso: member.user&.group_sso?(group),
group_managed_account: member.user&.group_managed_account?,
can_override: member.can_override?,
is_overridden: member.override
})
end
end
end
# frozen_string_literal: true
module EE
module MemberEntity
extend ActiveSupport::Concern
prepended do
expose :using_license do |member|
can?(current_user, :owner_access, group) && member.user&.using_gitlab_com_seat?(group)
end
expose :group_sso do |member|
member.user&.group_sso?(group)
end
expose :group_managed_account do |member|
member.user&.group_managed_account?
end
expose :can_override do |member|
member.can_override?
end
expose :override, as: :is_overridden
end
private
def current_user
options[:current_user]
end
def group
options[:group]
end
end
end
# frozen_string_literal: true
module EE
module MemberUserEntity
extend ActiveSupport::Concern
prepended do
unexpose :gitlab_employee
unexpose :email
end
end
end
{
"type": "object",
"allOf": [
{ "$ref": "../../../../../../spec/fixtures/api/schemas/entities/member.json" },
{
"required": [
"using_license",
"group_sso",
"group_managed_account",
"can_override",
"is_overridden"
],
"properties": {
"using_license": { "type": ["boolean", "null"] },
"group_sso": { "type": ["boolean", "null"] },
"group_managed_account": { "type": ["boolean", "null"] },
"can_override": { "type": ["boolean"] },
"is_overridden": { "type": ["boolean"] }
}
}
]
}
{
"type": "array",
"items": {
"allOf": [
{ "$ref": "../../../../../spec/fixtures/api/schemas/group_member.json" },
{
"required": ["using_license", "group_sso", "group_managed_account", "can_override", "is_overridden"],
"properties": {
"using_license": { "type": ["boolean", "null"] },
"group_sso": { "type": ["boolean", "null"] },
"group_managed_account": { "type": ["boolean", "null"] },
"can_override": { "type": ["boolean"] },
"is_overridden": { "type": ["boolean"] }
}
}
]
}
}
{
"type": "array",
"items": {
"allOf": [
{ "$ref": "entities/member.json" }
]
}
}
......@@ -22,34 +22,6 @@ RSpec.describe Groups::GroupMembersHelper do
end
end
describe '#members_data' do
let(:group_member) { create(:group_member, group: group, created_by: current_user) }
subject { helper.send('members_data', group, present_members([group_member])) }
before do
allow(helper).to receive(:can?).with(current_user, :owner_access, group).and_return(true)
end
it 'adds `using_license` property to hash' do
allow(group_member.user).to receive(:using_gitlab_com_seat?).with(group).and_return(true)
expect(subject.first).to include(using_license: true)
end
it 'adds `group_sso` property to hash' do
allow(group_member.user).to receive(:group_sso?).with(group).and_return(true)
expect(subject.first).to include(group_sso: true)
end
it 'adds `group_managed_account` property to hash' do
allow(group_member.user).to receive(:group_managed_account?).and_return(true)
expect(subject.first).to include(group_managed_account: true)
end
end
describe '#group_members_list_data_attributes' do
before do
allow(helper).to receive(:override_group_group_member_path).with(group, ':id').and_return('/groups/foo-bar/-/group_members/:id/override')
......@@ -58,7 +30,7 @@ RSpec.describe Groups::GroupMembersHelper do
end
it 'adds `ldap_override_path` to returned hash' do
expect(helper.group_members_list_data_attributes(group, {})).to include(ldap_override_path: '/groups/foo-bar/-/group_members/:id/override')
expect(helper.group_members_list_data_attributes(group, [])).to include(ldap_override_path: '/groups/foo-bar/-/group_members/:id/override')
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe MemberEntity do
let_it_be(:current_user) { create(:user) }
let(:entity) { described_class.new(member, { current_user: current_user, group: group }) }
let(:entity_hash) { entity.as_json }
shared_examples 'member.json' do
it 'matches json schema' do
expect(entity.to_json).to match_schema('entities/member', dir: 'ee')
end
it 'correctly exposes `using_license`' do
allow(entity).to receive(:can?).with(current_user, :owner_access, group).and_return(true)
allow(member.user).to receive(:using_gitlab_com_seat?).with(group).and_return(true)
expect(entity_hash[:using_license]).to be(true)
end
it 'correctly exposes `group_sso`' do
allow(member.user).to receive(:group_sso?).with(group).and_return(true)
expect(entity_hash[:group_sso]).to be(true)
end
it 'correctly exposes `group_managed_account`' do
allow(member.user).to receive(:group_managed_account?).and_return(true)
expect(entity_hash[:group_managed_account]).to be(true)
end
it 'correctly exposes `can_override`' do
allow(member).to receive(:can_override?).and_return(true)
expect(entity_hash[:can_override]).to be(true)
end
end
context 'group member' do
let(:group) { create(:group) }
let(:member) { GroupMemberPresenter.new(create(:group_member, group: group, created_by: current_user), current_user: current_user) }
it_behaves_like 'member.json'
end
context 'project member' do
let(:project) { create(:project) }
let(:group) { project.group }
let(:member) { ProjectMemberPresenter.new(create(:project_member, project: project), current_user: current_user) }
it_behaves_like 'member.json'
end
end
......@@ -24,16 +24,18 @@
"properties": {
"integer_value": { "type": "integer" },
"string_value": { "type": "string" }
}
},
"additionalProperties": false
},
"source": {
"type": "object",
"required": ["id", "name", "web_url"],
"required": ["id", "full_name", "web_url"],
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"full_name": { "type": "string" },
"web_url": { "type": "string" }
}
},
"additionalProperties": false
},
"valid_roles": { "type": "object" },
"created_by": {
......@@ -42,39 +44,13 @@
"properties": {
"name": { "type": "string" },
"web_url": { "type": "string" }
}
},
"additionalProperties": false
},
"user": {
"type": "object",
"required": [
"id",
"name",
"username",
"avatar_url",
"web_url",
"blocked",
"two_factor_enabled"
],
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"username": { "type": "string" },
"avatar_url": { "type": ["string", "null"] },
"web_url": { "type": "string" },
"blocked": { "type": "boolean" },
"two_factor_enabled": { "type": "boolean" },
"status": {
"type": "object",
"required": [
"emoji",
"message_html"
],
"properties": {
"emoji": { "type": "string" },
"message_html": { "type": "string" }
}
}
}
"allOf": [
{ "$ref": "member_user.json" }
]
},
"invite": {
"type": "object",
......@@ -83,7 +59,8 @@
"email": { "type": "string" },
"avatar_url": { "type": "string" },
"can_resend": { "type": "boolean" }
}
},
"additionalProperties": false
}
}
}
{
"type": "object",
"required": ["id", "name", "username", "avatar_url", "web_url", "blocked", "two_factor_enabled"],
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"username": { "type": "string" },
"avatar_url": { "type": ["string", "null"] },
"web_url": { "type": "string" },
"blocked": { "type": "boolean" },
"two_factor_enabled": { "type": "boolean" },
"status": {
"type": "object",
"required": ["emoji"],
"properties": {
"emoji": { "type": "string" }
},
"additionalProperties": false
}
},
"additionalProperties": false
}
{
"type": "array",
"items": {
"$ref": "group_member.json"
"$ref": "entities/member.json"
}
}
......@@ -49,7 +49,7 @@ describe('AccessRequestActionButtons', () => {
describe('when member is the current user', () => {
it('sets `message` prop correctly', () => {
expect(findRemoveMemberButton().props('message')).toBe(
`Are you sure you want to withdraw your access request for "${member.source.name}"`,
`Are you sure you want to withdraw your access request for "${member.source.fullName}"`,
);
});
});
......@@ -64,7 +64,7 @@ describe('AccessRequestActionButtons', () => {
});
expect(findRemoveMemberButton().props('message')).toBe(
`Are you sure you want to deny ${member.user.name}'s request to join "${member.source.name}"`,
`Are you sure you want to deny ${member.user.name}'s request to join "${member.source.fullName}"`,
);
});
});
......
......@@ -39,7 +39,7 @@ describe('InviteActionButtons', () => {
it('sets props correctly', () => {
expect(findRemoveMemberButton().props()).toEqual({
memberId: member.id,
message: `Are you sure you want to revoke the invitation for ${member.invite.email} to join "${member.source.name}"`,
message: `Are you sure you want to revoke the invitation for ${member.invite.email} to join "${member.source.fullName}"`,
title: 'Revoke invite',
isAccessRequest: false,
icon: 'remove',
......
......@@ -39,7 +39,7 @@ describe('UserActionButtons', () => {
it('sets props correctly', () => {
expect(findRemoveMemberButton().props()).toEqual({
memberId: member.id,
message: `Are you sure you want to remove ${member.user.name} from "${member.source.name}"`,
message: `Are you sure you want to remove ${member.user.name} from "${member.source.fullName}"`,
title: 'Remove member',
isAccessRequest: false,
icon: 'remove',
......@@ -56,7 +56,7 @@ describe('UserActionButtons', () => {
});
expect(findRemoveMemberButton().props('message')).toBe(
`Are you sure you want to remove this orphaned member from "${orphanedMember.source.name}"`,
`Are you sure you want to remove this orphaned member from "${orphanedMember.source.fullName}"`,
);
});
});
......
......@@ -60,11 +60,11 @@ describe('LeaveModal', () => {
});
it('displays modal title', () => {
expect(getByText(`Leave "${member.source.name}"`).exists()).toBe(true);
expect(getByText(`Leave "${member.source.fullName}"`).exists()).toBe(true);
});
it('displays modal body', () => {
expect(getByText(`Are you sure you want to leave "${member.source.name}"?`).exists()).toBe(
expect(getByText(`Are you sure you want to leave "${member.source.fullName}"?`).exists()).toBe(
true,
);
});
......
......@@ -11,7 +11,7 @@ describe('MemberSource', () => {
propsData: {
memberSource: {
id: 102,
name: 'Foo bar',
fullName: 'Foo bar',
webUrl: 'https://gitlab.com/groups/foo-bar',
},
...propsData,
......
......@@ -7,7 +7,7 @@ export const member = {
accessLevel: { integerValue: 50, stringValue: 'Owner' },
source: {
id: 178,
name: 'Foo Bar',
fullName: 'Foo Bar',
webUrl: 'https://gitlab.com/groups/foo-bar',
},
user: {
......
......@@ -34,38 +34,38 @@ RSpec.describe Groups::GroupMembersHelper do
end
describe '#members_data_json' do
shared_examples 'group_members.json' do
shared_examples 'members.json' do
it 'matches json schema' do
json = helper.members_data_json(group, present_members([group_member]))
expect(json).to match_schema('group_members')
expect(json).to match_schema('members')
end
end
context 'for a group member' do
let(:group_member) { create(:group_member, group: group, created_by: current_user) }
it_behaves_like 'group_members.json'
it_behaves_like 'members.json'
context 'with user status set' do
let(:user) { create(:user) }
let!(:status) { create(:user_status, user: user) }
let(:group_member) { create(:group_member, group: group, user: user, created_by: current_user) }
it_behaves_like 'group_members.json'
it_behaves_like 'members.json'
end
end
context 'for an invited group member' do
let(:group_member) { create(:group_member, :invited, group: group, created_by: current_user) }
it_behaves_like 'group_members.json'
it_behaves_like 'members.json'
end
context 'for an access request' do
let(:group_member) { create(:group_member, :access_request, group: group, created_by: current_user) }
it_behaves_like 'group_members.json'
it_behaves_like 'members.json'
end
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe MemberEntity do
let_it_be(:current_user) { create(:user) }
let(:entity) { described_class.new(member, { current_user: current_user, group: group }) }
let(:entity_hash) { entity.as_json }
shared_examples 'member.json' do
it 'matches json schema' do
expect(entity.to_json).to match_schema('entities/member')
end
it 'correctly exposes `can_update`' do
allow(member).to receive(:can_update?).and_return(true)
expect(entity_hash[:can_update]).to be(true)
end
it 'correctly exposes `can_remove`' do
allow(member).to receive(:can_remove?).and_return(true)
expect(entity_hash[:can_remove]).to be(true)
end
end
shared_examples 'invite' do
it 'correctly exposes `invite.avatar_url`' do
avatar_url = 'https://www.gravatar.com/avatar/c4637cb869d5f94c3193bde4f23d4cdc?s=80&d=identicon'
allow(entity).to receive(:avatar_icon_for_email).with(member.invite_email, Member::AVATAR_SIZE).and_return(avatar_url)
expect(entity_hash[:invite][:avatar_url]).to match(avatar_url)
end
it 'correctly exposes `invite.can_resend`' do
allow(member).to receive(:can_resend_invite?).and_return(true)
expect(entity_hash[:invite][:can_resend]).to be(true)
end
end
context 'group member' do
let(:group) { create(:group) }
let(:member) { GroupMemberPresenter.new(create(:group_member, group: group), current_user: current_user) }
it_behaves_like 'member.json'
context 'invite' do
let(:member) { GroupMemberPresenter.new(create(:group_member, :invited, group: group), current_user: current_user) }
it_behaves_like 'member.json'
it_behaves_like 'invite'
end
end
context 'project member' do
let(:project) { create(:project) }
let(:group) { project.group }
let(:member) { ProjectMemberPresenter.new(create(:project_member, project: project), current_user: current_user) }
it_behaves_like 'member.json'
context 'invite' do
let(:member) { ProjectMemberPresenter.new(create(:project_member, :invited, project: project), current_user: current_user) }
it_behaves_like 'member.json'
it_behaves_like 'invite'
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe MemberSerializer do
include MembersPresentation
let_it_be(:current_user) { create(:user) }
subject { described_class.new.represent(members, { current_user: current_user, group: group }) }
shared_examples 'members.json' do
it 'matches json schema' do
expect(subject.to_json).to match_schema('members')
end
end
context 'group member' do
let(:group) { create(:group) }
let(:members) { present_members(create_list(:group_member, 1, group: group)) }
it_behaves_like 'members.json'
end
context 'project member' do
let(:project) { create(:project) }
let(:group) { project.group }
let(:members) { present_members(create_list(:project_member, 1, project: project)) }
it_behaves_like 'members.json'
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe MemberUserEntity do
let_it_be(:user) { create(:user) }
let_it_be(:emoji) { 'slight_smile' }
let_it_be(:user_status) { create(:user_status, user: user, emoji: emoji) }
let(:entity) { described_class.new(user) }
let(:entity_hash) { entity.as_json }
it 'matches json schema' do
expect(entity.to_json).to match_schema('entities/member_user')
end
it 'correctly exposes `avatar_url`' do
avatar_url = 'https://www.gravatar.com/avatar/c4637cb869d5f94c3193bde4f23d4cdc?s=80&d=identicon'
allow(user).to receive(:avatar_url).and_return(avatar_url)
expect(entity_hash[:avatar_url]).to match(avatar_url)
end
it 'correctly exposes `blocked`' do
allow(user).to receive(:blocked?).and_return(true)
expect(entity_hash[:blocked]).to be(true)
end
it 'correctly exposes `two_factor_enabled`' do
allow(user).to receive(:two_factor_enabled?).and_return(true)
expect(entity_hash[:two_factor_enabled]).to be(true)
end
it 'correctly exposes `status.emoji`' do
expect(entity_hash[:status][:emoji]).to match(emoji)
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