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 { ...@@ -3,4 +3,6 @@ export default {
merge_requests: 'merge-request-recent-searches', merge_requests: 'merge-request-recent-searches',
group_members: 'group-members-recent-searches', group_members: 'group-members-recent-searches',
group_invited_members: 'group-invited-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'; ...@@ -6,6 +6,8 @@ import groupsSelect from '~/groups_select';
import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue'; import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue';
import initInviteMembersModal from '~/invite_members/init_invite_members_modal'; import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger'; import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger';
import { __ } from '~/locale';
import { deprecatedCreateFlash as flash } from '~/flash';
function mountRemoveMemberModal() { function mountRemoveMemberModal() {
const el = document.querySelector('.js-remove-member-modal'); const el = document.querySelector('.js-remove-member-modal');
...@@ -32,3 +34,65 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -32,3 +34,65 @@ document.addEventListener('DOMContentLoaded', () => {
new Members(); // eslint-disable-line no-new new Members(); // eslint-disable-line no-new
new UsersSelect(); // 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 ...@@ -8,6 +8,10 @@ class Projects::ProjectMembersController < Projects::ApplicationController
# Authorize # Authorize
before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access] 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 feature_category :authentication_and_authorization
def index def index
......
- page_title _("Members") - page_title _("Members")
- group = @project.group - group = @project.group
- vue_project_members_list_enabled = Feature.enabled?(:vue_project_members_list, @project)
.js-remove-member-modal .js-remove-member-modal
.row.gl-mt-3 .row.gl-mt-3
...@@ -74,24 +75,44 @@ ...@@ -74,24 +75,44 @@
%span.badge.badge-pill= @requesters.count %span.badge.badge-pill= @requesters.count
.tab-content .tab-content
#tab-members.tab-pane{ class: ('active' unless groups_tab_active?) } #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 } = paginate @project_members, theme: "gitlab", params: { search_groups: nil }
- if show_groups?(@group_links) - if show_groups?(@group_links)
#tab-groups.tab-pane{ class: ('active' if groups_tab_active?) } #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) - if show_invited_members?(@project, @invited_members)
#tab-invited-members.tab-pane #tab-invited-members.tab-pane
.card.card-without-border - if vue_project_members_list_enabled
= render 'shared/members/tab_pane/header' do .js-project-invited-members-list{ data: project_members_list_data_attributes(@project, @invited_members) }
= render 'shared/members/tab_pane/title' do .loading
= 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 } .spinner.spinner-md
%ul.content-list.members-list - else
= 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) } .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) - if show_access_requests?(@project, @requesters)
#tab-access-requests.tab-pane #tab-access-requests.tab-pane
.card.card-without-border - if vue_project_members_list_enabled
= render 'shared/members/tab_pane/header' do .js-project-access-requests-list{ data: project_members_list_data_attributes(@project, @requesters) }
= render 'shared/members/tab_pane/title' do .loading
= 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 } .spinner.spinner-md
%ul.content-list.members-list - else
= render partial: 'shared/members/member', collection: @requesters, as: :member, locals: { membership_source: @project, group: group } .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 ...@@ -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) } let!(:regular_member) { create(:group_member, :guest, group: group, user: maryjane, ldap: false) }
before do 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! # 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) allow(Gitlab.config.ldap).to receive_messages(enabled: true)
......
...@@ -8,6 +8,8 @@ RSpec.describe 'Projects > Audit Events', :js do ...@@ -8,6 +8,8 @@ RSpec.describe 'Projects > Audit Events', :js do
let(:project) { create(:project, :repository, namespace: user.namespace) } let(:project) { create(:project, :repository, namespace: user.namespace) }
before do before do
stub_feature_flags(vue_project_members_list: false)
project.add_maintainer(user) project.add_maintainer(user)
sign_in(user) sign_in(user)
end end
......
...@@ -10,6 +10,7 @@ RSpec.describe 'Project > Members > Invite group and members', :js do ...@@ -10,6 +10,7 @@ RSpec.describe 'Project > Members > Invite group and members', :js do
before do before do
stub_feature_flags(invite_members_group_modal: false) stub_feature_flags(invite_members_group_modal: false)
stub_feature_flags(vue_project_members_list: false)
end end
describe 'Share group lock' do describe 'Share group lock' do
......
...@@ -8,6 +8,8 @@ RSpec.describe 'Projects > Members > Member is removed from project' do ...@@ -8,6 +8,8 @@ RSpec.describe 'Projects > Members > Member is removed from project' do
let(:other_user) { create(:user) } let(:other_user) { create(:user) }
before do before do
stub_feature_flags(vue_project_members_list: false)
project.add_maintainer(user) project.add_maintainer(user)
project.add_maintainer(other_user) project.add_maintainer(other_user)
sign_in(user) sign_in(user)
......
...@@ -3267,6 +3267,9 @@ msgstr "" ...@@ -3267,6 +3267,9 @@ msgstr ""
msgid "An error occurred while loading the file. Please try again later." msgid "An error occurred while loading the file. Please try again later."
msgstr "" msgstr ""
msgid "An error occurred while loading the members, please try again."
msgstr ""
msgid "An error occurred while loading the merge request changes." msgid "An error occurred while loading the merge request changes."
msgstr "" msgstr ""
...@@ -17688,6 +17691,9 @@ msgstr "" ...@@ -17688,6 +17691,9 @@ msgstr ""
msgid "Members|Role updated successfully." msgid "Members|Role updated successfully."
msgstr "" msgstr ""
msgid "Members|Search groups"
msgstr ""
msgid "Members|Search invited" msgid "Members|Search invited"
msgstr "" msgstr ""
......
...@@ -10,6 +10,8 @@ RSpec.describe "Admin::Projects" do ...@@ -10,6 +10,8 @@ RSpec.describe "Admin::Projects" do
let(:current_user) { create(:admin) } let(:current_user) { create(:admin) }
before do before do
stub_feature_flags(vue_project_members_list: false)
sign_in(current_user) sign_in(current_user)
gitlab_enable_admin_mode_sign_in(current_user) gitlab_enable_admin_mode_sign_in(current_user)
end end
......
...@@ -8,6 +8,8 @@ RSpec.describe 'Projects > Members > Anonymous user sees members' do ...@@ -8,6 +8,8 @@ RSpec.describe 'Projects > Members > Anonymous user sees members' do
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
before do before do
stub_feature_flags(vue_project_members_list: false)
project.add_maintainer(user) project.add_maintainer(user)
create(:project_group_link, project: project, group: group) create(:project_group_link, project: project, group: group)
end end
......
...@@ -13,6 +13,8 @@ RSpec.describe 'Projects members', :js do ...@@ -13,6 +13,8 @@ RSpec.describe 'Projects members', :js do
let(:group_requester) { create(:user) } let(:group_requester) { create(:user) }
before do before do
stub_feature_flags(vue_project_members_list: false)
project.add_developer(developer) project.add_developer(developer)
group.add_owner(user) group.add_owner(user)
sign_in(user) sign_in(user)
......
...@@ -11,6 +11,8 @@ RSpec.describe 'Projects > Members > Groups with access list', :js do ...@@ -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) } let!(:group_link) { create(:project_group_link, project: project, group: group, **additional_link_attrs) }
before do before do
stub_feature_flags(vue_project_members_list: false)
travel_to Time.now.utc.beginning_of_day travel_to Time.now.utc.beginning_of_day
project.add_maintainer(user) project.add_maintainer(user)
......
...@@ -10,6 +10,7 @@ RSpec.describe 'Project > Members > Invite group', :js do ...@@ -10,6 +10,7 @@ RSpec.describe 'Project > Members > Invite group', :js do
before do before do
stub_feature_flags(invite_members_group_modal: false) stub_feature_flags(invite_members_group_modal: false)
stub_feature_flags(vue_project_members_list: false)
end end
describe 'Share with group lock' do describe 'Share with group lock' do
......
...@@ -13,10 +13,18 @@ RSpec.describe 'Project members list' do ...@@ -13,10 +13,18 @@ RSpec.describe 'Project members list' do
before do before do
stub_feature_flags(invite_members_group_modal: false) stub_feature_flags(invite_members_group_modal: false)
stub_feature_flags(vue_project_members_list: false)
sign_in(user1) sign_in(user1)
group.add_owner(user1) group.add_owner(user1)
end 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 it 'show members from project and group' do
project.add_developer(user2) project.add_developer(user2)
......
...@@ -11,6 +11,8 @@ RSpec.describe 'Projects > Members > Maintainer adds member with expiration date ...@@ -11,6 +11,8 @@ RSpec.describe 'Projects > Members > Maintainer adds member with expiration date
let(:new_member) { create(:user) } let(:new_member) { create(:user) }
before do before do
stub_feature_flags(vue_project_members_list: false)
travel_to Time.now.utc.beginning_of_day travel_to Time.now.utc.beginning_of_day
project.add_maintainer(maintainer) project.add_maintainer(maintainer)
......
...@@ -8,6 +8,8 @@ RSpec.describe 'Projects > Members > Sorting' do ...@@ -8,6 +8,8 @@ RSpec.describe 'Projects > Members > Sorting' do
let(:project) { create(:project, namespace: maintainer.namespace, creator: maintainer) } let(:project) { create(:project, namespace: maintainer.namespace, creator: maintainer) }
before do before do
stub_feature_flags(vue_project_members_list: false)
create(:project_member, :developer, user: developer, project: project, created_at: 3.days.ago) create(:project_member, :developer, user: developer, project: project, created_at: 3.days.ago)
sign_in(maintainer) sign_in(maintainer)
......
...@@ -20,6 +20,7 @@ RSpec.describe 'Projects > Members > Tabs' do ...@@ -20,6 +20,7 @@ RSpec.describe 'Projects > Members > Tabs' do
end end
before do before do
stub_feature_flags(vue_project_members_list: false)
allow(Kaminari.config).to receive(:default_per_page).and_return(1) allow(Kaminari.config).to receive(:default_per_page).and_return(1)
sign_in(user) sign_in(user)
......
...@@ -11,6 +11,8 @@ RSpec.describe 'Projects > Settings > User manages project members' do ...@@ -11,6 +11,8 @@ RSpec.describe 'Projects > Settings > User manages project members' do
let(:user_mike) { create(:user, name: 'Mike') } let(:user_mike) { create(:user, name: 'Mike') }
before do before do
stub_feature_flags(vue_project_members_list: false)
project.add_maintainer(user) project.add_maintainer(user)
project.add_developer(user_dmitriy) project.add_developer(user_dmitriy)
sign_in(user) 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