Commit f2d3a4fc authored by Peter Hegman's avatar Peter Hegman Committed by Matthias Käppler

Convert project members page to new Vue layout

Convert HAML project members view to new Vue layout that matches group
members layout
parent 15b9bc05
......@@ -3,4 +3,6 @@ export default {
merge_requests: 'merge-request-recent-searches',
group_members: 'group-members-recent-searches',
group_invited_members: 'group-invited-members-recent-searches',
project_members: 'project-members-recent-searches',
project_group_links: 'project-group-links-recent-searches',
};
......@@ -6,6 +6,8 @@ import groupsSelect from '~/groups_select';
import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue';
import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger';
import { __ } from '~/locale';
import { deprecatedCreateFlash as flash } from '~/flash';
function mountRemoveMemberModal() {
const el = document.querySelector('.js-remove-member-modal');
......@@ -32,3 +34,65 @@ document.addEventListener('DOMContentLoaded', () => {
new Members(); // eslint-disable-line no-new
new UsersSelect(); // eslint-disable-line no-new
});
if (window.gon.features.vueProjectMembersList) {
const SHARED_FIELDS = ['account', 'expires', 'maxRole', 'expiration', 'actions'];
Promise.all([
import('~/members/index'),
import('~/members/utils'),
import('~/projects/members/utils'),
import('~/locale'),
])
.then(
([
{ initMembersApp },
{ groupLinkRequestFormatter },
{ projectMemberRequestFormatter },
{ s__ },
]) => {
initMembersApp(document.querySelector('.js-project-members-list'), {
tableFields: SHARED_FIELDS.concat(['source', 'granted']),
tableAttrs: { tr: { 'data-qa-selector': 'member_row' } },
tableSortableFields: ['account', 'granted', 'maxRole', 'lastSignIn'],
requestFormatter: projectMemberRequestFormatter,
filteredSearchBar: {
show: true,
tokens: ['with_inherited_permissions'],
searchParam: 'search',
placeholder: s__('Members|Filter members'),
recentSearchesStorageKey: 'project_members',
},
});
initMembersApp(document.querySelector('.js-project-group-links-list'), {
tableFields: SHARED_FIELDS.concat('granted'),
tableAttrs: {
table: { 'data-qa-selector': 'groups_list' },
tr: { 'data-qa-selector': 'group_row' },
},
requestFormatter: groupLinkRequestFormatter,
filteredSearchBar: {
show: true,
tokens: [],
searchParam: 'search_groups',
placeholder: s__('Members|Search groups'),
recentSearchesStorageKey: 'project_group_links',
},
});
initMembersApp(document.querySelector('.js-project-invited-members-list'), {
tableFields: SHARED_FIELDS.concat('invited'),
requestFormatter: projectMemberRequestFormatter,
});
initMembersApp(document.querySelector('.js-project-access-requests-list'), {
tableFields: SHARED_FIELDS.concat('requested'),
requestFormatter: projectMemberRequestFormatter,
});
},
)
.catch(() => {
flash(__('An error occurred while loading the members, please try again.'));
});
}
export const PROJECT_MEMBER_BASE_PROPERTY_NAME = 'project_member';
import { baseRequestFormatter } from '~/members/utils';
import { PROJECT_MEMBER_BASE_PROPERTY_NAME } from './constants';
import { MEMBER_ACCESS_LEVEL_PROPERTY_NAME } from '~/members/constants';
export const projectMemberRequestFormatter = baseRequestFormatter(
PROJECT_MEMBER_BASE_PROPERTY_NAME,
MEMBER_ACCESS_LEVEL_PROPERTY_NAME,
);
......@@ -8,6 +8,10 @@ class Projects::ProjectMembersController < Projects::ApplicationController
# Authorize
before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access]
before_action do
push_frontend_feature_flag(:vue_project_members_list, @project)
end
feature_category :authentication_and_authorization
def index
......
- page_title _("Members")
- group = @project.group
- vue_project_members_list_enabled = Feature.enabled?(:vue_project_members_list, @project)
.js-remove-member-modal
.row.gl-mt-3
......@@ -74,24 +75,44 @@
%span.badge.badge-pill= @requesters.count
.tab-content
#tab-members.tab-pane{ class: ('active' unless groups_tab_active?) }
= render 'projects/project_members/team', project: @project, group: group, members: @project_members, current_user_is_group_owner: current_user_is_group_owner?(@project)
- if vue_project_members_list_enabled
.js-project-members-list{ data: project_members_list_data_attributes(@project, @project_members) }
.loading
.spinner.spinner-md
- else
= render 'projects/project_members/team', project: @project, group: group, members: @project_members, current_user_is_group_owner: current_user_is_group_owner?(@project)
= paginate @project_members, theme: "gitlab", params: { search_groups: nil }
- if show_groups?(@group_links)
#tab-groups.tab-pane{ class: ('active' if groups_tab_active?) }
= render 'projects/project_members/groups', group_links: @group_links
- if vue_project_members_list_enabled
.js-project-group-links-list{ data: project_group_links_list_data_attributes(@project, @group_links) }
.loading
.spinner.spinner-md
- else
= render 'projects/project_members/groups', group_links: @group_links
- if show_invited_members?(@project, @invited_members)
#tab-invited-members.tab-pane
.card.card-without-border
= render 'shared/members/tab_pane/header' do
= render 'shared/members/tab_pane/title' do
= html_escape(_('Members invited to %{strong_start}%{project_name}%{strong_end}')) % { project_name: @project.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
%ul.content-list.members-list
= render partial: 'shared/members/member', collection: @invited_members, as: :member, locals: { membership_source: @project, group: group, current_user_is_group_owner: current_user_is_group_owner?(@project) }
- if vue_project_members_list_enabled
.js-project-invited-members-list{ data: project_members_list_data_attributes(@project, @invited_members) }
.loading
.spinner.spinner-md
- else
.card.card-without-border
= render 'shared/members/tab_pane/header' do
= render 'shared/members/tab_pane/title' do
= html_escape(_('Members invited to %{strong_start}%{project_name}%{strong_end}')) % { project_name: @project.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
%ul.content-list.members-list
= render partial: 'shared/members/member', collection: @invited_members, as: :member, locals: { membership_source: @project, group: group, current_user_is_group_owner: current_user_is_group_owner?(@project) }
- if show_access_requests?(@project, @requesters)
#tab-access-requests.tab-pane
.card.card-without-border
= render 'shared/members/tab_pane/header' do
= render 'shared/members/tab_pane/title' do
= html_escape(_('Users requesting access to %{strong_start}%{project_name}%{strong_end}')) % { project_name: @project.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
%ul.content-list.members-list
= render partial: 'shared/members/member', collection: @requesters, as: :member, locals: { membership_source: @project, group: group }
- if vue_project_members_list_enabled
.js-project-access-requests-list{ data: project_members_list_data_attributes(@project, @requesters) }
.loading
.spinner.spinner-md
- else
.card.card-without-border
= render 'shared/members/tab_pane/header' do
= render 'shared/members/tab_pane/title' do
= html_escape(_('Users requesting access to %{strong_start}%{project_name}%{strong_end}')) % { project_name: @project.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
%ul.content-list.members-list
= render partial: 'shared/members/member', collection: @requesters, as: :member, locals: { membership_source: @project, group: group }
---
name: vue_project_members_list
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52148
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/299954
milestone: '13.9'
type: development
group: group::access
default_enabled: false
......@@ -17,6 +17,7 @@ RSpec.describe 'Groups > Members > Maintainer/Owner can override LDAP access lev
let!(:regular_member) { create(:group_member, :guest, group: group, user: maryjane, ldap: false) }
before do
stub_feature_flags(vue_project_members_list: false)
# We need to actually activate the LDAP config otherwise `Group#ldap_synced?` will always be false!
allow(Gitlab.config.ldap).to receive_messages(enabled: true)
......
......@@ -8,6 +8,8 @@ RSpec.describe 'Projects > Audit Events', :js do
let(:project) { create(:project, :repository, namespace: user.namespace) }
before do
stub_feature_flags(vue_project_members_list: false)
project.add_maintainer(user)
sign_in(user)
end
......
......@@ -10,6 +10,7 @@ RSpec.describe 'Project > Members > Invite group and members', :js do
before do
stub_feature_flags(invite_members_group_modal: false)
stub_feature_flags(vue_project_members_list: false)
end
describe 'Share group lock' do
......
......@@ -8,6 +8,8 @@ RSpec.describe 'Projects > Members > Member is removed from project' do
let(:other_user) { create(:user) }
before do
stub_feature_flags(vue_project_members_list: false)
project.add_maintainer(user)
project.add_maintainer(other_user)
sign_in(user)
......
......@@ -3267,6 +3267,9 @@ msgstr ""
msgid "An error occurred while loading the file. Please try again later."
msgstr ""
msgid "An error occurred while loading the members, please try again."
msgstr ""
msgid "An error occurred while loading the merge request changes."
msgstr ""
......@@ -17688,6 +17691,9 @@ msgstr ""
msgid "Members|Role updated successfully."
msgstr ""
msgid "Members|Search groups"
msgstr ""
msgid "Members|Search invited"
msgstr ""
......
......@@ -10,6 +10,8 @@ RSpec.describe "Admin::Projects" do
let(:current_user) { create(:admin) }
before do
stub_feature_flags(vue_project_members_list: false)
sign_in(current_user)
gitlab_enable_admin_mode_sign_in(current_user)
end
......
......@@ -8,6 +8,8 @@ RSpec.describe 'Projects > Members > Anonymous user sees members' do
let(:project) { create(:project, :public) }
before do
stub_feature_flags(vue_project_members_list: false)
project.add_maintainer(user)
create(:project_group_link, project: project, group: group)
end
......
......@@ -13,6 +13,8 @@ RSpec.describe 'Projects members', :js do
let(:group_requester) { create(:user) }
before do
stub_feature_flags(vue_project_members_list: false)
project.add_developer(developer)
group.add_owner(user)
sign_in(user)
......
......@@ -11,6 +11,8 @@ RSpec.describe 'Projects > Members > Groups with access list', :js do
let!(:group_link) { create(:project_group_link, project: project, group: group, **additional_link_attrs) }
before do
stub_feature_flags(vue_project_members_list: false)
travel_to Time.now.utc.beginning_of_day
project.add_maintainer(user)
......
......@@ -10,6 +10,7 @@ RSpec.describe 'Project > Members > Invite group', :js do
before do
stub_feature_flags(invite_members_group_modal: false)
stub_feature_flags(vue_project_members_list: false)
end
describe 'Share with group lock' do
......
......@@ -13,10 +13,18 @@ RSpec.describe 'Project members list' do
before do
stub_feature_flags(invite_members_group_modal: false)
stub_feature_flags(vue_project_members_list: false)
sign_in(user1)
group.add_owner(user1)
end
it 'pushes `vue_project_members_list` feature flag to the frontend' do
visit_members_page
expect(page).to have_pushed_frontend_feature_flags(vueProjectMembersList: false)
end
it 'show members from project and group' do
project.add_developer(user2)
......
......@@ -11,6 +11,8 @@ RSpec.describe 'Projects > Members > Maintainer adds member with expiration date
let(:new_member) { create(:user) }
before do
stub_feature_flags(vue_project_members_list: false)
travel_to Time.now.utc.beginning_of_day
project.add_maintainer(maintainer)
......
......@@ -8,6 +8,8 @@ RSpec.describe 'Projects > Members > Sorting' do
let(:project) { create(:project, namespace: maintainer.namespace, creator: maintainer) }
before do
stub_feature_flags(vue_project_members_list: false)
create(:project_member, :developer, user: developer, project: project, created_at: 3.days.ago)
sign_in(maintainer)
......
......@@ -20,6 +20,7 @@ RSpec.describe 'Projects > Members > Tabs' do
end
before do
stub_feature_flags(vue_project_members_list: false)
allow(Kaminari.config).to receive(:default_per_page).and_return(1)
sign_in(user)
......
......@@ -11,6 +11,8 @@ RSpec.describe 'Projects > Settings > User manages project members' do
let(:user_mike) { create(:user, name: 'Mike') }
before do
stub_feature_flags(vue_project_members_list: false)
project.add_maintainer(user)
project.add_developer(user_dmitriy)
sign_in(user)
......
import { projectMemberRequestFormatter } from '~/projects/members/utils';
describe('project member utils', () => {
describe('projectMemberRequestFormatter', () => {
it('returns expected format', () => {
expect(
projectMemberRequestFormatter({
accessLevel: 50,
expires_at: '2020-10-16',
}),
).toEqual({ project_member: { access_level: 50, expires_at: '2020-10-16' } });
});
});
});
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