Commit 9a0212d1 authored by Dylan Griffith's avatar Dylan Griffith

Merge branch 'peterhegman/remove-vue_project_members_list-feature-flag' into 'master'

Remove `vue_project_members_list` feature flag [RUN ALL RSPEC] [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!55902
parents 7e6c8784 e842c800
import { disableButtonIfEmptyField } from '~/lib/utils/common_utils';
// This is only used when `invite_members_group_modal` feature flag is disabled.
// This file can be removed when `invite_members_group_modal` feature flag is removed
export default () => {
disableButtonIfEmptyField('#user_ids', 'input[name=commit]', 'change');
};
import $ from 'jquery';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
import { disableButtonIfEmptyField } from '~/lib/utils/common_utils';
import { Rails } from '~/lib/utils/rails_ujs';
import { __, sprintf } from '~/locale';
export default class Members {
constructor() {
this.addListeners();
this.initGLDropdown();
}
addListeners() {
// eslint-disable-next-line @gitlab/no-global-event-off
$('.js-member-update-control').off('change').on('change', this.formSubmit.bind(this));
// eslint-disable-next-line @gitlab/no-global-event-off
$('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess.bind(this));
disableButtonIfEmptyField('#user_ids', 'input[name=commit]', 'change');
}
dropdownClicked(options) {
options.e.preventDefault();
this.formSubmit(null, options.$el);
}
// eslint-disable-next-line class-methods-use-this
dropdownToggleLabel(selected, $el) {
return $el.text();
}
// eslint-disable-next-line class-methods-use-this
dropdownIsSelectable(selected, $el) {
return !$el.hasClass('is-active');
}
initGLDropdown() {
$('.js-member-permissions-dropdown').each((i, btn) => {
const $btn = $(btn);
initDeprecatedJQueryDropdown($btn, {
selectable: true,
isSelectable: (selected, $el) => this.dropdownIsSelectable(selected, $el),
fieldName: $btn.data('fieldName'),
id(selected, $el) {
return $el.data('id');
},
toggleLabel: (selected, $el) => this.dropdownToggleLabel(selected, $el, $btn),
clicked: (options) => this.dropdownClicked(options),
});
});
}
formSubmit(e, $el = null) {
const $this = e ? $(e.currentTarget) : $el;
const { $toggle, $dateInput } = this.getMemberListItems($this);
const formEl = $this.closest('form').get(0);
Rails.fire(formEl, 'submit');
$toggle.disable();
$dateInput.disable();
}
formSuccess(e) {
const { $toggle, $dateInput, $expiresIn, $expiresInText } = this.getMemberListItems(
$(e.currentTarget).closest('.js-member'),
);
const [data] = e.detail;
const expiresIn = data?.expires_in;
if (expiresIn) {
$expiresIn.removeClass('gl-display-none');
$expiresInText.text(sprintf(__('Expires in %{expires_at}'), { expires_at: expiresIn }));
const { expires_soon: expiresSoon, expires_at_formatted: expiresAtFormatted } = data;
if (expiresSoon) {
$expiresInText.addClass('text-warning');
} else {
$expiresInText.removeClass('text-warning');
}
// Update tooltip
if (expiresAtFormatted) {
$expiresInText.attr('title', expiresAtFormatted);
$expiresInText.attr('data-original-title', expiresAtFormatted);
}
} else {
$expiresIn.addClass('gl-display-none');
}
$toggle.enable();
$dateInput.enable();
}
// eslint-disable-next-line class-methods-use-this
getMemberListItems($el) {
const $memberListItem = $el.is('.js-member') ? $el : $(`#${$el.data('elId')}`);
return {
$memberListItem,
$expiresIn: $memberListItem.find('.js-expires-in'),
$expiresInText: $memberListItem.find('.js-expires-in-text'),
$toggle: $memberListItem.find('.dropdown-menu-toggle'),
$dateInput: $memberListItem.find('.js-access-expiration-date'),
};
}
}
...@@ -2,11 +2,12 @@ import Vue from 'vue'; ...@@ -2,11 +2,12 @@ import Vue from 'vue';
import { groupMemberRequestFormatter } from '~/groups/members/utils'; import { groupMemberRequestFormatter } from '~/groups/members/utils';
import groupsSelect from '~/groups_select'; import groupsSelect from '~/groups_select';
import initInviteGroupTrigger from '~/invite_members/init_invite_group_trigger'; import initInviteGroupTrigger from '~/invite_members/init_invite_group_trigger';
import initInviteMembersForm from '~/invite_members/init_invite_members_form';
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 { s__ } from '~/locale'; import { s__ } from '~/locale';
import memberExpirationDate from '~/member_expiration_date'; import memberExpirationDate from '~/member_expiration_date';
import { initMembersApp } from '~/members/index'; import { initMembersApp } from '~/members';
import { groupLinkRequestFormatter } from '~/members/utils'; import { groupLinkRequestFormatter } from '~/members/utils';
import UsersSelect from '~/users_select'; import UsersSelect from '~/users_select';
import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue'; import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue';
...@@ -73,4 +74,8 @@ initInviteMembersModal(); ...@@ -73,4 +74,8 @@ initInviteMembersModal();
initInviteMembersTrigger(); initInviteMembersTrigger();
initInviteGroupTrigger(); initInviteGroupTrigger();
// This is only used when `invite_members_group_modal` feature flag is disabled.
// This can be removed when `invite_members_group_modal` feature flag is removed.
initInviteMembersForm();
new UsersSelect(); // eslint-disable-line no-new new UsersSelect(); // eslint-disable-line no-new
import Vue from 'vue'; import Vue from 'vue';
import { deprecatedCreateFlash as flash } from '~/flash';
import groupsSelect from '~/groups_select'; import groupsSelect from '~/groups_select';
import initInviteGroupTrigger from '~/invite_members/init_invite_group_trigger'; import initInviteGroupTrigger from '~/invite_members/init_invite_group_trigger';
import initInviteMembersForm from '~/invite_members/init_invite_members_form';
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 { s__ } from '~/locale';
import memberExpirationDate from '~/member_expiration_date'; import memberExpirationDate from '~/member_expiration_date';
import Members from '~/members'; import { initMembersApp } from '~/members';
import { groupLinkRequestFormatter } from '~/members/utils';
import { projectMemberRequestFormatter } from '~/projects/members/utils';
import UsersSelect from '~/users_select'; import UsersSelect from '~/users_select';
import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue'; import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue';
...@@ -32,67 +34,49 @@ initInviteMembersModal(); ...@@ -32,67 +34,49 @@ initInviteMembersModal();
initInviteMembersTrigger(); initInviteMembersTrigger();
initInviteGroupTrigger(); initInviteGroupTrigger();
new Members(); // eslint-disable-line no-new // This is only used when `invite_members_group_modal` feature flag is disabled.
new UsersSelect(); // eslint-disable-line no-new // This can be removed when `invite_members_group_modal` feature flag is removed.
initInviteMembersForm();
if (window.gon.features.vueProjectMembersList) { new UsersSelect(); // eslint-disable-line no-new
const SHARED_FIELDS = ['account', 'expires', 'maxRole', 'expiration', 'actions'];
Promise.all([ const SHARED_FIELDS = ['account', 'expires', 'maxRole', 'expiration', 'actions'];
import('~/members/index'), initMembersApp(document.querySelector('.js-project-members-list'), {
import('~/members/utils'), tableFields: SHARED_FIELDS.concat(['source', 'granted']),
import('~/projects/members/utils'), tableAttrs: { tr: { 'data-qa-selector': 'member_row' } },
import('~/locale'), tableSortableFields: ['account', 'granted', 'maxRole', 'lastSignIn'],
]) requestFormatter: projectMemberRequestFormatter,
.then( filteredSearchBar: {
([ show: true,
{ initMembersApp }, tokens: ['with_inherited_permissions'],
{ groupLinkRequestFormatter }, searchParam: 'search',
{ projectMemberRequestFormatter }, placeholder: s__('Members|Filter members'),
{ s__ }, recentSearchesStorageKey: 'project_members',
]) => { },
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'), { initMembersApp(document.querySelector('.js-project-group-links-list'), {
tableFields: SHARED_FIELDS.concat('granted'), tableFields: SHARED_FIELDS.concat('granted'),
tableAttrs: { tableAttrs: {
table: { 'data-qa-selector': 'groups_list' }, table: { 'data-qa-selector': 'groups_list' },
tr: { 'data-qa-selector': 'group_row' }, tr: { 'data-qa-selector': 'group_row' },
}, },
requestFormatter: groupLinkRequestFormatter, requestFormatter: groupLinkRequestFormatter,
filteredSearchBar: { filteredSearchBar: {
show: true, show: true,
tokens: [], tokens: [],
searchParam: 'search_groups', searchParam: 'search_groups',
placeholder: s__('Members|Search groups'), placeholder: s__('Members|Search groups'),
recentSearchesStorageKey: 'project_group_links', recentSearchesStorageKey: 'project_group_links',
}, },
}); });
initMembersApp(document.querySelector('.js-project-invited-members-list'), { initMembersApp(document.querySelector('.js-project-invited-members-list'), {
tableFields: SHARED_FIELDS.concat('invited'), tableFields: SHARED_FIELDS.concat('invited'),
requestFormatter: projectMemberRequestFormatter, requestFormatter: projectMemberRequestFormatter,
}); });
initMembersApp(document.querySelector('.js-project-access-requests-list'), { initMembersApp(document.querySelector('.js-project-access-requests-list'), {
tableFields: SHARED_FIELDS.concat('requested'), tableFields: SHARED_FIELDS.concat('requested'),
requestFormatter: projectMemberRequestFormatter, requestFormatter: projectMemberRequestFormatter,
}); });
},
)
.catch(() => {
flash(__('An error occurred while loading the members, please try again.'));
});
}
...@@ -8,10 +8,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -8,10 +8,6 @@ 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, default_enabled: :yaml)
end
feature_category :authentication_and_authorization feature_category :authentication_and_authorization
def index def index
......
.card.card-without-border
= render 'shared/members/tab_pane/header' do
= render 'shared/members/tab_pane/title' do
= html_escape(_("Groups with access to %{strong_open}%{project_name}%{strong_close}")) % { project_name: sanitize(@project.name, tags: []), strong_open: '<strong>'.html_safe, strong_close: '</strong>'.html_safe }
= form_tag project_project_members_path(@project), method: :get, class: 'user-search-form gl-mx-n3 gl-my-n3', data: { testid: 'group-link-search-form' } do
.gl-px-3.gl-py-2
.search-control-wrap.gl-relative
= render 'shared/members/search_field', name: 'search_groups'
%ul.content-list.members-list{ data: { testid: 'project-member-groups' } }
- @group_links.each do |group_link|
= render 'shared/members/group', group_link: group_link, can_admin_member: can_manage_project_members?(@project), group_link_path: project_group_link_path(@project, group_link)
- project = local_assigns.fetch(:project)
- members = local_assigns.fetch(:members)
- group = local_assigns.fetch(:group)
- current_user_is_group_owner = local_assigns.fetch(:current_user_is_group_owner)
.card.card-without-border
= render 'shared/members/tab_pane/header' do
= render 'shared/members/tab_pane/title' do
= html_escape(_("Members of %{strong_open}%{project_name}%{strong_close}")) % { project_name: sanitize(project.name, tags: []), strong_open: '<strong>'.html_safe, strong_close: '</strong>'.html_safe }
= form_tag project_project_members_path(project), method: :get, class: 'user-search-form gl-display-flex gl-md-align-items-center gl-flex-wrap gl-flex-direction-column gl-md-flex-direction-row gl-mx-n3 gl-my-n3', data: { testid: 'user-search-form' } do
.gl-px-3.gl-py-2
.search-control-wrap.gl-relative
= render 'shared/members/search_field'
= render 'shared/members/tab_pane/form_item' do
= label_tag :sort_by, _('Sort by'), class: 'label-bold gl-mr-2 gl-mb-0 gl-py-2 align-self-md-center'
= render 'shared/members/sort_dropdown'
%ul.content-list.members-list{ data: { qa_selector: 'members_list', testid: 'members-table' } }
= render partial: 'shared/members/member',
collection: members, as: :member,
locals: { membership_source: project,
group: group,
current_user_is_group_owner: current_user_is_group_owner }
- page_title _("Members") - page_title _("Members")
- group = @project.group
- vue_project_members_list_enabled = Feature.enabled?(:vue_project_members_list, @project, default_enabled: :yaml)
.js-remove-member-modal .js-remove-member-modal
.row.gl-mt-3 .row.gl-mt-3
...@@ -76,44 +74,22 @@ ...@@ -76,44 +74,22 @@
%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?) }
- if vue_project_members_list_enabled .js-project-members-list{ data: project_members_list_data_attributes(@project, @project_members) }
.js-project-members-list{ data: project_members_list_data_attributes(@project, @project_members) } .loading
.loading .spinner.spinner-md
.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?) }
- if vue_project_members_list_enabled .js-project-group-links-list{ data: project_group_links_list_data_attributes(@project, @group_links) }
.js-project-group-links-list{ data: project_group_links_list_data_attributes(@project, @group_links) } .loading
.loading .spinner.spinner-md
.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
- if vue_project_members_list_enabled .js-project-invited-members-list{ data: project_members_list_data_attributes(@project, @invited_members) }
.js-project-invited-members-list{ data: project_members_list_data_attributes(@project, @invited_members) } .loading
.loading .spinner.spinner-md
.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) - if show_access_requests?(@project, @requesters)
#tab-access-requests.tab-pane #tab-access-requests.tab-pane
- if vue_project_members_list_enabled .js-project-access-requests-list{ data: project_members_list_data_attributes(@project, @requesters) }
.js-project-access-requests-list{ data: project_members_list_data_attributes(@project, @requesters) } .loading
.loading .spinner.spinner-md
.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 }
---
title: Remove `vue_project_members_list` feature flag
merge_request: 55902
author:
type: changed
---
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: true
...@@ -37,10 +37,7 @@ From the image above, we can deduce the following things: ...@@ -37,10 +37,7 @@ From the image above, we can deduce the following things:
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/21727) in GitLab 12.6. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/21727) in GitLab 12.6.
> - [Improved](https://gitlab.com/groups/gitlab-org/-/epics/4901) in GitLab 13.9. > - [Improved](https://gitlab.com/groups/gitlab-org/-/epics/4901) in GitLab 13.9.
> - Improvements are [deployed behind a feature flag](../../feature_flags.md), enabled by default. > - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/299954) in GitLab 13.10.
> - Improvements are enabled on GitLab.com.
> - Improvements are recommended for production use.
> - For GitLab self-managed instances, GitLab administrators can opt to [disable improvements](#enable-or-disable-improvements-to-project-member-management). **(FREE SELF)**
The following sections illustrate how you can filter and sort members in a project. To view these options, The following sections illustrate how you can filter and sort members in a project. To view these options,
navigate to your desired project, go to **Members**, and include the noted search terms. navigate to your desired project, go to **Members**, and include the noted search terms.
...@@ -199,27 +196,3 @@ To remove a member from a project: ...@@ -199,27 +196,3 @@ To remove a member from a project:
A **Remove member** modal appears. A **Remove member** modal appears.
1. (Optional) Select the **Also unassign this user from related issues and merge requests** checkbox. 1. (Optional) Select the **Also unassign this user from related issues and merge requests** checkbox.
1. Click **Remove member**. 1. Click **Remove member**.
## Enable or disable improvements to project member management **(FREE SELF)**
Project member management improvements are deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
can opt to disable the improvements.
To disable them:
```ruby
# For the instance
Feature.disable(:vue_project_members_list)
# For a single project
Feature.disable(:vue_project_members_list, Project.find(<project id>))
```
To enable them:
```ruby
# For the instance
Feature.enable(:vue_project_members_list)
# For a single project
Feature.enable(:vue_project_members_list, Project.find(<project id>))
```
...@@ -18,7 +18,6 @@ RSpec.describe 'Groups > Members > Maintainer/Owner can override LDAP access lev ...@@ -18,7 +18,6 @@ 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)
......
...@@ -113,53 +113,23 @@ RSpec.describe 'Projects > Audit Events', :js do ...@@ -113,53 +113,23 @@ RSpec.describe 'Projects > Audit Events', :js do
project.add_developer(pete) project.add_developer(pete)
end end
context 'when `vue_project_members_list` feature flag is enabled' do it "appears in the project's audit events" do
it "appears in the project's audit events" do visit project_project_members_path(project)
visit project_project_members_path(project)
page.within find_member_row(pete) do
click_button 'Developer'
click_button 'Maintainer'
end
page.within('.qa-project-sidebar') do
find(:link, text: 'Security & Compliance').click
click_link 'Audit Events'
end
page.within('.audit-log-table') do
expect(page).to have_content 'Changed access level from Developer to Maintainer'
expect(page).to have_content(project.owner.name)
expect(page).to have_content('Pete')
end
end
end
context 'when `vue_project_members_list` feature flag is disabled' do page.within find_member_row(pete) do
before do click_button 'Developer'
stub_feature_flags(vue_project_members_list: false) click_button 'Maintainer'
end end
it "appears in the project's audit events" do page.within('.qa-project-sidebar') do
visit project_project_members_path(project) find(:link, text: 'Security & Compliance').click
click_link 'Audit Events'
project_member = project.project_member(pete) end
page.within "#project_member_#{project_member.id}" do
click_button 'Developer'
click_link 'Maintainer'
end
page.within('.qa-project-sidebar') do
find(:link, text: 'Security & Compliance').click
click_link 'Audit Events'
end
page.within('.audit-log-table') do page.within('.audit-log-table') do
expect(page).to have_content 'Changed access level from Developer to Maintainer' expect(page).to have_content 'Changed access level from Developer to Maintainer'
expect(page).to have_content(project.owner.name) expect(page).to have_content(project.owner.name)
expect(page).to have_content('Pete') expect(page).to have_content('Pete')
end
end end
end end
end end
......
...@@ -71,65 +71,41 @@ RSpec.describe 'Project > Members > Invite group and members', :js do ...@@ -71,65 +71,41 @@ RSpec.describe 'Project > Members > Invite group and members', :js do
context 'when the group has "Share with group lock" and "Member lock" disabled' do context 'when the group has "Share with group lock" and "Member lock" disabled' do
it_behaves_like 'the project can be shared with groups and members' it_behaves_like 'the project can be shared with groups and members'
context 'when `vue_project_members_list` feature flag is enabled' do it 'allows the project to be shared with another group using the invite form' do
it 'allows the project to be shared with another group using the invite form' do stub_feature_flags(invite_members_group_modal: false)
stub_feature_flags(invite_members_group_modal: false)
visit project_project_members_path(project) visit project_project_members_path(project)
click_on 'invite-group-tab' click_on 'invite-group-tab'
select2 group_to_share_with.id, from: '#link_group_id' select2 group_to_share_with.id, from: '#link_group_id'
page.find('body').click page.find('body').click
find('.btn-confirm').click find('.btn-confirm').click
click_link 'Groups' click_link 'Groups'
page.within(members_table) do page.within(members_table) do
expect(page).to have_content(group_to_share_with.name) expect(page).to have_content(group_to_share_with.name)
end
end
it 'allows the project to be shared with another group using the invite modal' do
stub_feature_flags(invite_members_group_modal: true)
visit project_project_members_path(project)
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
click_button group_to_share_with.name
click_button 'Invite'
visit project_project_members_path(project)
click_link 'Groups'
page.within(members_table) do
expect(page).to have_content(group_to_share_with.name)
end
end end
end end
context 'when `vue_project_members_list` feature flag is disabled' do it 'allows the project to be shared with another group using the invite modal' do
before do stub_feature_flags(invite_members_group_modal: true)
stub_feature_flags(vue_project_members_list: false)
end
it 'allows the project to be shared with another group' do visit project_project_members_path(project)
visit project_project_members_path(project)
click_on 'invite-group-tab' click_on 'Invite a group'
select2 group_to_share_with.id, from: '#link_group_id' click_on 'Select a group'
page.find('body').click wait_for_requests
find('.btn-confirm').click click_button group_to_share_with.name
click_button 'Invite'
click_link 'Groups' visit project_project_members_path(project)
click_link 'Groups'
page.within('[data-testid="project-member-groups"]') do page.within(members_table) do
expect(page).to have_content(group_to_share_with.name) expect(page).to have_content(group_to_share_with.name)
end
end end
end end
end end
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'Projects > Members > Member is removed from project' do RSpec.describe 'Projects > Members > Member is removed from project', :js do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project) } let(:project) { create(:project) }
let(:other_user) { create(:user) } let(:other_user) { create(:user) }
...@@ -10,71 +10,37 @@ RSpec.describe 'Projects > Members > Member is removed from project' do ...@@ -10,71 +10,37 @@ RSpec.describe 'Projects > Members > Member is removed from project' do
before do before do
project.add_maintainer(user) project.add_maintainer(user)
project.add_maintainer(other_user) project.add_maintainer(other_user)
end
context 'when `vue_project_members_list` feature flag is enabled', :js do
before do
sign_in(user)
visit project_project_members_path(project)
end
it 'user is removed from project' do sign_in(user)
click_button 'Leave' visit project_project_members_path(project)
end
page.within('[role="dialog"]') do it 'user is removed from project' do
click_button('Leave') click_button 'Leave'
end
expect(project.users.exists?(user.id)).to be_falsey page.within('[role="dialog"]') do
click_button('Leave')
end end
context 'when the user has been specifically allowed to access a protected branch' do expect(project.users.exists?(user.id)).to be_falsey
let!(:matching_protected_branch) { create(:protected_branch, authorize_user_to_push: user, authorize_user_to_merge: user, project: project) }
let!(:non_matching_protected_branch) { create(:protected_branch, authorize_user_to_push: other_user, authorize_user_to_merge: other_user, project: project) }
it 'user leaves project' do
click_button 'Leave'
page.within('[role="dialog"]') do
click_button('Leave')
end
expect(project.users.exists?(user.id)).to be_falsey
expect(matching_protected_branch.push_access_levels.where(user: user)).not_to exist
expect(matching_protected_branch.merge_access_levels.where(user: user)).not_to exist
expect(non_matching_protected_branch.push_access_levels.where(user: other_user)).to exist
expect(non_matching_protected_branch.merge_access_levels.where(user: other_user)).to exist
end
end
end end
context 'when `vue_project_members_list` feature flag is disabled' do context 'when the user has been specifically allowed to access a protected branch' do
before do let!(:matching_protected_branch) { create(:protected_branch, authorize_user_to_push: user, authorize_user_to_merge: user, project: project) }
stub_feature_flags(vue_project_members_list: false) let!(:non_matching_protected_branch) { create(:protected_branch, authorize_user_to_push: other_user, authorize_user_to_merge: other_user, project: project) }
sign_in(user) it 'user leaves project' do
visit project_project_members_path(project) click_button 'Leave'
end
it 'user is removed from project' do page.within('[role="dialog"]') do
click_link 'Leave' click_button('Leave')
end
expect(project.users.exists?(user.id)).to be_falsey expect(project.users.exists?(user.id)).to be_falsey
end expect(matching_protected_branch.push_access_levels.where(user: user)).not_to exist
expect(matching_protected_branch.merge_access_levels.where(user: user)).not_to exist
context 'when the user has been specifically allowed to access a protected branch' do expect(non_matching_protected_branch.push_access_levels.where(user: other_user)).to exist
let!(:matching_protected_branch) { create(:protected_branch, authorize_user_to_push: user, authorize_user_to_merge: user, project: project) } expect(non_matching_protected_branch.merge_access_levels.where(user: other_user)).to exist
let!(:non_matching_protected_branch) { create(:protected_branch, authorize_user_to_push: other_user, authorize_user_to_merge: other_user, project: project) }
it 'user leaves project' do
click_link 'Leave'
expect(project.users.exists?(user.id)).to be_falsey
expect(matching_protected_branch.push_access_levels.where(user: user)).not_to exist
expect(matching_protected_branch.merge_access_levels.where(user: user)).not_to exist
expect(non_matching_protected_branch.push_access_levels.where(user: other_user)).to exist
expect(non_matching_protected_branch.merge_access_levels.where(user: other_user)).to exist
end
end end
end end
end end
...@@ -3451,9 +3451,6 @@ msgstr "" ...@@ -3451,9 +3451,6 @@ 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 ""
...@@ -14925,9 +14922,6 @@ msgstr "" ...@@ -14925,9 +14922,6 @@ msgstr ""
msgid "Groups to synchronize" msgid "Groups to synchronize"
msgstr "" msgstr ""
msgid "Groups with access to %{strong_open}%{project_name}%{strong_close}"
msgstr ""
msgid "GroupsDropdown|Frequently visited" msgid "GroupsDropdown|Frequently visited"
msgstr "" msgstr ""
...@@ -18766,9 +18760,6 @@ msgstr "" ...@@ -18766,9 +18760,6 @@ msgstr ""
msgid "Members can be added by project %{i_open}Maintainers%{i_close} or %{i_open}Owners%{i_close}" msgid "Members can be added by project %{i_open}Maintainers%{i_close} or %{i_open}Owners%{i_close}"
msgstr "" msgstr ""
msgid "Members invited to %{strong_start}%{project_name}%{strong_end}"
msgstr ""
msgid "Members listed as CODEOWNERS of affected files." msgid "Members listed as CODEOWNERS of affected files."
msgstr "" msgstr ""
...@@ -18778,9 +18769,6 @@ msgstr "" ...@@ -18778,9 +18769,6 @@ msgstr ""
msgid "Members of %{group} can also push to this branch: %{branch}" msgid "Members of %{group} can also push to this branch: %{branch}"
msgstr "" msgstr ""
msgid "Members of %{strong_open}%{project_name}%{strong_close}"
msgstr ""
msgid "Members of a group may only view projects they have permission to access" msgid "Members of a group may only view projects they have permission to access"
msgstr "" msgstr ""
...@@ -32946,9 +32934,6 @@ msgstr "" ...@@ -32946,9 +32934,6 @@ msgstr ""
msgid "Users requesting access to" msgid "Users requesting access to"
msgstr "" msgstr ""
msgid "Users requesting access to %{strong_start}%{project_name}%{strong_end}"
msgstr ""
msgid "Users were successfully added." msgid "Users were successfully added."
msgstr "" msgstr ""
......
...@@ -4,12 +4,8 @@ module QA ...@@ -4,12 +4,8 @@ module QA
RSpec.describe 'Manage', :requires_admin do RSpec.describe 'Manage', :requires_admin do
describe 'Add project member' do describe 'Add project member' do
before do before do
Runtime::Feature.enable('vue_project_members_list')
Runtime::Feature.enable(:invite_members_group_modal) Runtime::Feature.enable(:invite_members_group_modal)
end end
after do
Runtime::Feature.disable('vue_project_members_list')
end
it 'user adds project member', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/482' do it 'user adds project member', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/482' do
Flow::Login.sign_in Flow::Login.sign_in
......
...@@ -16,15 +16,10 @@ module QA ...@@ -16,15 +16,10 @@ module QA
end end
before do before do
Runtime::Feature.enable('vue_project_members_list', project: project)
Runtime::Feature.enable(:invite_members_group_modal) Runtime::Feature.enable(:invite_members_group_modal)
Flow::Login.sign_in Flow::Login.sign_in
end end
after do
Runtime::Feature.disable('vue_project_members_list', project: project)
end
it 'is received by a user for project invitation', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/676' do it 'is received by a user for project invitation', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/676' do
project.visit! project.visit!
......
...@@ -103,7 +103,6 @@ module QA ...@@ -103,7 +103,6 @@ module QA
context 'Add and remove project access', :requires_admin, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/735' do context 'Add and remove project access', :requires_admin, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/735' do
before do before do
Runtime::Feature.enable('vue_project_members_list', project: project)
Runtime::Feature.enable(:invite_members_group_modal) Runtime::Feature.enable(:invite_members_group_modal)
sign_in sign_in
project.visit! project.visit!
...@@ -121,10 +120,6 @@ module QA ...@@ -121,10 +120,6 @@ module QA
group.visit! group.visit!
end end
after do
Runtime::Feature.disable('vue_project_members_list', project: project)
end
it_behaves_like 'audit event', ['Added project access', 'Removed project access'] it_behaves_like 'audit event', ['Added project access', 'Removed project access']
end end
end end
......
...@@ -92,97 +92,46 @@ RSpec.describe "Admin::Projects" do ...@@ -92,97 +92,46 @@ RSpec.describe "Admin::Projects" do
end end
end end
context 'when `vue_project_members_list` feature flag is enabled', :js do describe 'admin adds themselves to the project', :js do
describe 'admin adds themselves to the project' do before do
before do project.add_maintainer(user)
project.add_maintainer(user) stub_feature_flags(invite_members_group_modal: false)
stub_feature_flags(invite_members_group_modal: false)
end
it 'adds admin to the project as developer', :js do
visit project_project_members_path(project)
page.within '.invite-users-form' do
select2(current_user.id, from: '#user_ids', multiple: true)
select 'Developer', from: 'access_level'
end
click_button 'Invite'
expect(find_member_row(current_user)).to have_content('Developer')
end
end end
describe 'admin removes themselves from the project' do it 'adds admin to the project as developer' do
before do visit project_project_members_path(project)
project.add_maintainer(user)
project.add_developer(current_user)
end
it 'removes admin from the project' do
visit project_project_members_path(project)
expect(find_member_row(current_user)).to have_content('Developer')
page.within find_member_row(current_user) do page.within '.invite-users-form' do
click_button 'Leave' select2(current_user.id, from: '#user_ids', multiple: true)
end select 'Developer', from: 'access_level'
end
page.within('[role="dialog"]') do click_button 'Invite'
click_button('Leave')
end
expect(current_path).to match dashboard_projects_path expect(find_member_row(current_user)).to have_content('Developer')
end
end end
end end
context 'when `vue_project_members_list` feature flag is disabled' do describe 'admin removes themselves from the project', :js do
before do before do
stub_feature_flags(vue_project_members_list: false) project.add_maintainer(user)
project.add_developer(current_user)
end end
describe 'admin adds themselves to the project' do it 'removes admin from the project' do
before do visit project_project_members_path(project)
project.add_maintainer(user)
stub_feature_flags(invite_members_group_modal: false)
end
it 'adds admin to the project as developer', :js do
visit project_project_members_path(project)
page.within '.invite-users-form' do
select2(current_user.id, from: '#user_ids', multiple: true)
select 'Developer', from: 'access_level'
end
click_button 'Invite' expect(find_member_row(current_user)).to have_content('Developer')
page.within '.content-list' do page.within find_member_row(current_user) do
expect(page).to have_content(current_user.name) click_button 'Leave'
expect(page).to have_content('Developer')
end
end end
end
describe 'admin removes themselves from the project' do page.within('[role="dialog"]') do
before do click_button('Leave')
project.add_maintainer(user)
project.add_developer(current_user)
end end
it 'removes admin from the project' do expect(current_path).to match dashboard_projects_path
visit project_project_members_path(project)
page.within '.content-list' do
expect(page).to have_content(current_user.name)
expect(page).to have_content('Developer')
end
find(:css, '.content-list li', text: current_user.name).find(:css, 'a.btn-danger').click
expect(page).not_to have_selector(:css, '.content-list')
end
end end
end end
end end
...@@ -14,25 +14,9 @@ RSpec.describe 'Projects > Members > Anonymous user sees members' do ...@@ -14,25 +14,9 @@ RSpec.describe 'Projects > Members > Anonymous user sees members' do
create(:project_group_link, project: project, group: group) create(:project_group_link, project: project, group: group)
end end
context 'when `vue_project_members_list` feature flag is enabled', :js do it "anonymous user visits the project's members page and sees the list of members", :js do
it "anonymous user visits the project's members page and sees the list of members" do visit project_project_members_path(project)
visit project_project_members_path(project)
expect(find_member_row(user)).to have_content(user.name) expect(find_member_row(user)).to have_content(user.name)
end
end
context 'when `vue_project_members_list` feature flag is disabled' do
before do
stub_feature_flags(vue_project_members_list: false)
end
it "anonymous user visits the project's members page and sees the list of members" do
visit project_project_members_path(project)
expect(current_path).to eq(
project_project_members_path(project))
expect(page).to have_content(user.name)
end
end end
end end
...@@ -20,218 +20,96 @@ RSpec.describe 'Projects members', :js do ...@@ -20,218 +20,96 @@ RSpec.describe 'Projects members', :js do
sign_in(user) sign_in(user)
end end
context 'when `vue_project_members_list` feature flag is enabled' do context 'with a group invitee' do
context 'with a group invitee' do before do
before do group_invitee
group_invitee visit project_project_members_path(project)
visit project_project_members_path(project)
end
it 'does not appear in the project members page' do
expect(members_table).not_to have_content('test2@abc.com')
end
end end
context 'with a group' do it 'does not appear in the project members page' do
it 'shows group and project members by default' do expect(members_table).not_to have_content('test2@abc.com')
visit project_project_members_path(project) end
end
expect(members_table).to have_content(developer.name)
expect(members_table).to have_content(user.name)
expect(members_table).to have_content(group.name)
end
it 'shows project members only if requested' do
visit project_project_members_path(project, with_inherited_permissions: 'exclude')
expect(members_table).to have_content(developer.name)
expect(members_table).not_to have_content(user.name)
expect(members_table).not_to have_content(group.name)
end
it 'shows group members only if requested' do context 'with a group' do
visit project_project_members_path(project, with_inherited_permissions: 'only') it 'shows group and project members by default' do
visit project_project_members_path(project)
expect(members_table).not_to have_content(developer.name) expect(members_table).to have_content(developer.name)
expect(members_table).to have_content(user.name) expect(members_table).to have_content(user.name)
expect(members_table).to have_content(group.name) expect(members_table).to have_content(group.name)
end
end end
context 'with a group, a project invitee, and a project requester' do it 'shows project members only if requested' do
before do visit project_project_members_path(project, with_inherited_permissions: 'exclude')
group.request_access(group_requester)
project.request_access(project_requester)
group_invitee
project_invitee
visit project_project_members_path(project)
end
it 'shows the group owner' do
expect(members_table).to have_content(user.name)
expect(members_table).to have_content(group.name)
end
it 'shows the project developer' do
expect(members_table).to have_content(developer.name)
end
it 'shows the project invitee' do
click_link 'Invited'
expect(members_table).to have_content('test1@abc.com')
expect(members_table).not_to have_content('test2@abc.com')
end
it 'shows the project requester' do
click_link 'Access requests'
expect(members_table).to have_content(project_requester.name)
expect(members_table).not_to have_content(group_requester.name)
end
end
context 'with a group requester' do expect(members_table).to have_content(developer.name)
before do expect(members_table).not_to have_content(user.name)
stub_feature_flags(invite_members_group_modal: false) expect(members_table).not_to have_content(group.name)
group.request_access(group_requester)
visit project_project_members_path(project)
end
it 'does not appear in the project members page' do
expect(page).not_to have_link('Access requests')
expect(members_table).not_to have_content(group_requester.name)
end
end end
context 'showing status of members' do it 'shows group members only if requested' do
it 'shows the status' do visit project_project_members_path(project, with_inherited_permissions: 'only')
create(:user_status, user: user, emoji: 'smirk', message: 'Authoring this object')
visit project_project_members_path(project) expect(members_table).not_to have_content(developer.name)
expect(members_table).to have_content(user.name)
expect(first_row).to have_selector('gl-emoji[data-name="smirk"]') expect(members_table).to have_content(group.name)
end
end end
end end
context 'when `vue_project_members_list` feature flag is disabled' do context 'with a group, a project invitee, and a project requester' do
before do before do
stub_feature_flags(vue_project_members_list: false) group.request_access(group_requester)
project.request_access(project_requester)
group_invitee
project_invitee
visit project_project_members_path(project)
end end
context 'with a group invitee' do it 'shows the group owner' do
before do expect(members_table).to have_content(user.name)
group_invitee expect(members_table).to have_content(group.name)
visit project_project_members_path(project)
end
it 'does not appear in the project members page' do
page.within first('.content-list') do
expect(page).not_to have_content('test2@abc.com')
end
end
end end
context 'with a group' do it 'shows the project developer' do
it 'shows group and project members by default' do expect(members_table).to have_content(developer.name)
visit project_project_members_path(project) end
page.within first('.content-list') do
expect(page).to have_content(developer.name)
expect(page).to have_content(user.name)
expect(page).to have_content(group.name)
end
end
it 'shows project members only if requested' do
visit project_project_members_path(project, with_inherited_permissions: 'exclude')
page.within first('.content-list') do
expect(page).to have_content(developer.name)
expect(page).not_to have_content(user.name) it 'shows the project invitee' do
expect(page).not_to have_content(group.name) click_link 'Invited'
end
end
it 'shows group members only if requested' do expect(members_table).to have_content('test1@abc.com')
visit project_project_members_path(project, with_inherited_permissions: 'only') expect(members_table).not_to have_content('test2@abc.com')
end
page.within first('.content-list') do it 'shows the project requester' do
expect(page).not_to have_content(developer.name) click_link 'Access requests'
expect(page).to have_content(user.name) expect(members_table).to have_content(project_requester.name)
expect(page).to have_content(group.name) expect(members_table).not_to have_content(group_requester.name)
end
end
end end
end
context 'with a group, a project invitee, and a project requester' do context 'with a group requester' do
before do before do
group.request_access(group_requester) stub_feature_flags(invite_members_group_modal: false)
project.request_access(project_requester) group.request_access(group_requester)
group_invitee visit project_project_members_path(project)
project_invitee
visit project_project_members_path(project)
end
it 'shows the group owner' do
page.within first('.content-list') do
# Group owner
expect(page).to have_content(user.name)
expect(page).to have_content(group.name)
end
end
it 'shows the project developer' do
page.within first('.content-list') do
# Project developer
expect(page).to have_content(developer.name)
end
end
it 'shows the project invitee' do
click_link 'Invited'
page.within first('.content-list') do
expect(page).to have_content('test1@abc.com')
expect(page).not_to have_content('test2@abc.com')
end
end
it 'shows the project requester' do
click_link 'Access requests'
page.within first('.content-list') do
expect(page).to have_content(project_requester.name)
expect(page).not_to have_content(group_requester.name)
end
end
end end
context 'with a group requester' do it 'does not appear in the project members page' do
before do expect(page).not_to have_link('Access requests')
stub_feature_flags(invite_members_group_modal: false) expect(members_table).not_to have_content(group_requester.name)
group.request_access(group_requester)
visit project_project_members_path(project)
end
it 'does not appear in the project members page' do
expect(page).not_to have_link('Access requests')
page.within first('.content-list') do
expect(page).not_to have_content(group_requester.name)
end
end
end end
end
context 'showing status of members' do
it 'shows the status' do
create(:user_status, user: user, emoji: 'smirk', message: 'Authoring this object')
context 'showing status of members' do visit project_project_members_path(project)
it_behaves_like 'showing user status' do
let(:user_with_status) { developer }
subject { visit project_project_members_path(project) } expect(first_row).to have_selector('gl-emoji[data-name="smirk"]')
end
end end
end end
end end
...@@ -17,172 +17,80 @@ RSpec.describe 'Projects > Members > Groups with access list', :js do ...@@ -17,172 +17,80 @@ RSpec.describe 'Projects > Members > Groups with access list', :js do
project.add_maintainer(user) project.add_maintainer(user)
sign_in(user) sign_in(user)
end
context 'when `vue_project_members_list` feature flag is enabled' do
before do
visit project_project_members_path(project)
click_groups_tab
end
it 'updates group access level' do
click_button group_link.human_access
click_button 'Guest'
wait_for_requests
visit project_project_members_path(project)
click_groups_tab visit project_project_members_path(project)
click_groups_tab
expect(find_group_row(group)).to have_content('Guest') end
end
it 'updates expiry date' do it 'updates group access level' do
page.within find_group_row(group) do click_button group_link.human_access
fill_in 'Expiration date', with: 5.days.from_now.to_date click_button 'Guest'
find_field('Expiration date').native.send_keys :enter
wait_for_requests wait_for_requests
expect(page).to have_content(/in \d days/) visit project_project_members_path(project)
end
end
context 'when link has expiry date set' do click_groups_tab
let(:additional_link_attrs) { { expires_at: 5.days.from_now.to_date } }
it 'clears expiry date' do expect(find_group_row(group)).to have_content('Guest')
page.within find_group_row(group) do end
expect(page).to have_content(/in \d days/)
find('[data-testid="clear-button"]').click it 'updates expiry date' do
page.within find_group_row(group) do
fill_in 'Expiration date', with: 5.days.from_now.to_date
find_field('Expiration date').native.send_keys :enter
wait_for_requests wait_for_requests
expect(page).to have_content('No expiration set') expect(page).to have_content(/in \d days/)
end
end
end end
end
it 'deletes group link' do context 'when link has expiry date set' do
expect(page).to have_content(group.full_name) let(:additional_link_attrs) { { expires_at: 5.days.from_now.to_date } }
it 'clears expiry date' do
page.within find_group_row(group) do page.within find_group_row(group) do
click_button 'Remove group' expect(page).to have_content(/in \d days/)
end
page.within('[role="dialog"]') do
click_button('Remove group')
end
expect(page).not_to have_content(group.full_name)
end
context 'search in existing members' do
it 'finds no results' do
fill_in_filtered_search 'Search groups', with: 'testing 123'
click_groups_tab
expect(page).not_to have_content(group.full_name)
end
it 'finds results' do find('[data-testid="clear-button"]').click
fill_in_filtered_search 'Search groups', with: group.full_name
click_groups_tab wait_for_requests
expect(members_table).to have_content(group.full_name) expect(page).to have_content('No expiration set')
end end
end end
end end
context 'when `vue_project_members_list` feature flag is disabled' do it 'deletes group link' do
before do expect(page).to have_content(group.full_name)
stub_feature_flags(vue_project_members_list: false)
visit project_project_members_path(project)
click_groups_tab
end
it 'updates group access level' do
click_button group_link.human_access
page.within '.dropdown-menu' do
click_link 'Guest'
end
wait_for_requests
visit project_project_members_path(project)
click_groups_tab
expect(first('.group_member')).to have_content('Guest') page.within find_group_row(group) do
click_button 'Remove group'
end end
it 'updates expiry date' do page.within('[role="dialog"]') do
expires_at_field = "member_expires_at_#{group.id}" click_button('Remove group')
fill_in expires_at_field, with: 3.days.from_now.to_date
find_field(expires_at_field).native.send_keys :enter
wait_for_requests
page.within(find('li.group_member')) do
expect(page).to have_content('Expires in 3 days')
end
end end
context 'when link has expiry date set' do expect(page).not_to have_content(group.full_name)
let(:additional_link_attrs) { { expires_at: 3.days.from_now.to_date } } end
it 'clears expiry date' do
page.within(find('li.group_member')) do
expect(page).to have_content('Expires in 3 days')
page.within(find('.js-edit-member-form')) do
find('.js-clear-input').click
end
wait_for_requests
expect(page).not_to have_content('Expires in') context 'search in existing members' do
end it 'finds no results' do
end fill_in_filtered_search 'Search groups', with: 'testing 123'
end
it 'deletes group link' do click_groups_tab
page.within(first('.group_member')) do
accept_confirm { find('.btn-danger').click }
end
wait_for_requests
expect(page).not_to have_selector('.group_member') expect(page).not_to have_content(group.full_name)
end end
context 'search in existing members' do it 'finds results' do
it 'finds no results' do fill_in_filtered_search 'Search groups', with: group.full_name
page.within '.user-search-form' do
fill_in 'search_groups', with: 'testing 123'
find('.user-search-btn').click
end
click_groups_tab
expect(page).not_to have_selector('.group_member')
end
it 'finds results' do
page.within '.user-search-form' do
fill_in 'search_groups', with: group.name
find('.user-search-btn').click
end
click_groups_tab click_groups_tab
expect(page).to have_selector('.group_member', count: 1) expect(members_table).to have_content(group.full_name)
end
end end
end end
......
...@@ -41,46 +41,20 @@ RSpec.describe 'Project > Members > Invite group', :js do ...@@ -41,46 +41,20 @@ RSpec.describe 'Project > Members > Invite group', :js do
context 'when the group has "Share with group lock" disabled' do context 'when the group has "Share with group lock" disabled' do
it_behaves_like 'the project can be shared with groups' it_behaves_like 'the project can be shared with groups'
context 'when `vue_project_members_list` feature flag is enabled' do it 'the project can be shared with another group' do
it 'the project can be shared with another group' do visit project_project_members_path(project)
visit project_project_members_path(project)
expect(page).not_to have_link 'Groups' expect(page).not_to have_link 'Groups'
click_on 'invite-group-tab' click_on 'invite-group-tab'
select2 group_to_share_with.id, from: '#link_group_id' select2 group_to_share_with.id, from: '#link_group_id'
page.find('body').click page.find('body').click
find('.btn-confirm').click find('.btn-confirm').click
click_link 'Groups' click_link 'Groups'
expect(members_table).to have_content(group_to_share_with.name) expect(members_table).to have_content(group_to_share_with.name)
end
end
context 'when `vue_project_members_list` feature flag is disabled' do
before do
stub_feature_flags(vue_project_members_list: false)
end
it 'the project can be shared with another group' do
visit project_project_members_path(project)
expect(page).not_to have_link 'Groups'
click_on 'invite-group-tab'
select2 group_to_share_with.id, from: '#link_group_id'
page.find('body').click
find('.btn-confirm').click
click_link 'Groups'
page.within('[data-testid="project-member-groups"]') do
expect(page).to have_content(group_to_share_with.name)
end
end
end end
end end
...@@ -162,33 +136,12 @@ RSpec.describe 'Project > Members > Invite group', :js do ...@@ -162,33 +136,12 @@ RSpec.describe 'Project > Members > Invite group', :js do
find('.btn-confirm').click find('.btn-confirm').click
end end
context 'when `vue_project_members_list` feature flag is enabled' do it 'the group link shows the expiration time with a warning class' do
it 'the group link shows the expiration time with a warning class' do setup
setup click_link 'Groups'
click_link 'Groups'
expect(find_group_row(group)).to have_content(/in \d days/)
expect(find_group_row(group)).to have_selector('.gl-text-orange-500')
end
end
context 'when `vue_project_members_list` feature flag is disabled' do
before do
stub_feature_flags(vue_project_members_list: false)
end
it 'the group link shows the expiration time with a warning class' do
setup
click_link 'Groups'
page.within('[data-testid="project-member-groups"]') do expect(find_group_row(group)).to have_content(/in \d days/)
# Using distance_of_time_in_words_to_now because it is not the same as expect(find_group_row(group)).to have_selector('.gl-text-orange-500')
# subtraction, and this way avoids time zone issues as well
expires_in_text = distance_of_time_in_words_to_now(project.project_group_links.first.expires_at)
expect(page).to have_content(expires_in_text)
expect(page).to have_selector('.text-warning')
end
end
end end
end end
......
This diff is collapsed.
...@@ -18,107 +18,51 @@ RSpec.describe 'Projects > Members > Maintainer adds member with expiration date ...@@ -18,107 +18,51 @@ RSpec.describe 'Projects > Members > Maintainer adds member with expiration date
sign_in(maintainer) sign_in(maintainer)
end end
context 'when `vue_project_members_list` feature flag is enabled' do it 'expiration date is displayed in the members list' do
it 'expiration date is displayed in the members list' do stub_feature_flags(invite_members_group_modal: false)
stub_feature_flags(invite_members_group_modal: false)
visit project_project_members_path(project) visit project_project_members_path(project)
page.within '.invite-users-form' do page.within '.invite-users-form' do
select2(new_member.id, from: '#user_ids', multiple: true) select2(new_member.id, from: '#user_ids', multiple: true)
fill_in 'expires_at', with: 5.days.from_now.to_date fill_in 'expires_at', with: 5.days.from_now.to_date
find_field('expires_at').native.send_keys :enter find_field('expires_at').native.send_keys :enter
click_on 'Invite' click_on 'Invite'
end
page.within find_member_row(new_member) do
expect(page).to have_content(/in \d days/)
end
end
it 'changes expiration date' do
project.team.add_users([new_member.id], :developer, expires_at: 3.days.from_now.to_date)
visit project_project_members_path(project)
page.within find_member_row(new_member) do
fill_in 'Expiration date', with: 5.days.from_now.to_date
find_field('Expiration date').native.send_keys :enter
wait_for_requests
expect(page).to have_content(/in \d days/)
end
end end
it 'clears expiration date' do page.within find_member_row(new_member) do
project.team.add_users([new_member.id], :developer, expires_at: 5.days.from_now.to_date) expect(page).to have_content(/in \d days/)
visit project_project_members_path(project)
page.within find_member_row(new_member) do
expect(page).to have_content(/in \d days/)
find('[data-testid="clear-button"]').click
wait_for_requests
expect(page).to have_content('No expiration set')
end
end end
end end
context 'when `vue_project_members_list` feature flag is disabled' do it 'changes expiration date' do
before do project.team.add_users([new_member.id], :developer, expires_at: 3.days.from_now.to_date)
stub_feature_flags(vue_project_members_list: false) visit project_project_members_path(project)
end
it 'expiration date is displayed in the members list' do
stub_feature_flags(invite_members_group_modal: false)
visit project_project_members_path(project)
page.within '.invite-users-form' do
select2(new_member.id, from: '#user_ids', multiple: true)
fill_in 'expires_at', with: 3.days.from_now.to_date
find_field('expires_at').native.send_keys :enter
click_on 'Invite' page.within find_member_row(new_member) do
end fill_in 'Expiration date', with: 5.days.from_now.to_date
find_field('Expiration date').native.send_keys :enter
page.within "#project_member_#{project_member_id}" do wait_for_requests
expect(page).to have_content('Expires in 3 days')
end
end
it 'changes expiration date' do
project.team.add_users([new_member.id], :developer, expires_at: 1.day.from_now.to_date)
visit project_project_members_path(project)
page.within "#project_member_#{project_member_id}" do
fill_in 'Expiration date', with: 3.days.from_now.to_date
find_field('Expiration date').native.send_keys :enter
wait_for_requests expect(page).to have_content(/in \d days/)
expect(page).to have_content('Expires in 3 days')
end
end end
end
it 'clears expiration date' do it 'clears expiration date' do
project.team.add_users([new_member.id], :developer, expires_at: 3.days.from_now.to_date) project.team.add_users([new_member.id], :developer, expires_at: 5.days.from_now.to_date)
visit project_project_members_path(project) visit project_project_members_path(project)
page.within "#project_member_#{project_member_id}" do page.within find_member_row(new_member) do
expect(page).to have_content('Expires in 3 days') expect(page).to have_content(/in \d days/)
find('.js-clear-input').click find('[data-testid="clear-button"]').click
wait_for_requests wait_for_requests
expect(page).not_to have_content('Expires in') expect(page).to have_content('No expiration set')
end
end end
end end
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'Projects > Members > Sorting' do RSpec.describe 'Projects > Members > Sorting', :js do
include Spec::Support::Helpers::Features::MembersHelpers include Spec::Support::Helpers::Features::MembersHelpers
let(:maintainer) { create(:user, name: 'John Doe') } let(:maintainer) { create(:user, name: 'John Doe') }
...@@ -15,165 +15,85 @@ RSpec.describe 'Projects > Members > Sorting' do ...@@ -15,165 +15,85 @@ RSpec.describe 'Projects > Members > Sorting' do
sign_in(maintainer) sign_in(maintainer)
end end
context 'when `vue_project_members_list` feature flag is enabled', :js do it 'sorts by account by default' do
it 'sorts by account by default' do visit_members_list(sort: nil)
visit_members_list(sort: nil)
expect(first_row).to have_content(maintainer.name) expect(first_row).to have_content(maintainer.name)
expect(second_row).to have_content(developer.name) expect(second_row).to have_content(developer.name)
expect_sort_by('Account', :asc) expect_sort_by('Account', :asc)
end end
it 'sorts by max role ascending' do
visit_members_list(sort: :access_level_asc)
expect(first_row).to have_content(developer.name)
expect(second_row).to have_content(maintainer.name)
expect_sort_by('Max role', :asc)
end
it 'sorts by max role descending' do
visit_members_list(sort: :access_level_desc)
expect(first_row).to have_content(maintainer.name)
expect(second_row).to have_content(developer.name)
expect_sort_by('Max role', :desc)
end
it 'sorts by access granted ascending' do
visit_members_list(sort: :last_joined)
expect(first_row).to have_content(maintainer.name)
expect(second_row).to have_content(developer.name)
expect_sort_by('Access granted', :asc)
end
it 'sorts by access granted descending' do
visit_members_list(sort: :oldest_joined)
expect(first_row).to have_content(developer.name)
expect(second_row).to have_content(maintainer.name)
expect_sort_by('Access granted', :desc)
end
it 'sorts by account ascending' do
visit_members_list(sort: :name_asc)
expect(first_row).to have_content(maintainer.name)
expect(second_row).to have_content(developer.name)
expect_sort_by('Account', :asc)
end
it 'sorts by account descending' do
visit_members_list(sort: :name_desc)
expect(first_row).to have_content(developer.name)
expect(second_row).to have_content(maintainer.name)
expect_sort_by('Account', :desc)
end
it 'sorts by last sign-in ascending', :clean_gitlab_redis_shared_state do it 'sorts by max role ascending' do
visit_members_list(sort: :recent_sign_in) visit_members_list(sort: :access_level_asc)
expect(first_row).to have_content(maintainer.name) expect(first_row).to have_content(developer.name)
expect(second_row).to have_content(developer.name) expect(second_row).to have_content(maintainer.name)
expect_sort_by('Last sign-in', :asc) expect_sort_by('Max role', :asc)
end end
it 'sorts by last sign-in descending', :clean_gitlab_redis_shared_state do it 'sorts by max role descending' do
visit_members_list(sort: :oldest_sign_in) visit_members_list(sort: :access_level_desc)
expect(first_row).to have_content(developer.name) expect(first_row).to have_content(maintainer.name)
expect(second_row).to have_content(maintainer.name) expect(second_row).to have_content(developer.name)
expect_sort_by('Last sign-in', :desc) expect_sort_by('Max role', :desc)
end
end end
context 'when `vue_project_members_list` feature flag is disabled' do it 'sorts by access granted ascending' do
before do visit_members_list(sort: :last_joined)
stub_feature_flags(vue_project_members_list: false)
end
it 'sorts alphabetically by default' do
visit_members_list(sort: nil)
expect(first_member).to include(maintainer.name) expect(first_row).to have_content(maintainer.name)
expect(second_member).to include(developer.name) expect(second_row).to have_content(developer.name)
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
end
it 'sorts by access level ascending' do expect_sort_by('Access granted', :asc)
visit_members_list(sort: :access_level_asc) end
expect(first_member).to include(developer.name) it 'sorts by access granted descending' do
expect(second_member).to include(maintainer.name) visit_members_list(sort: :oldest_joined)
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending')
end
it 'sorts by access level descending' do expect(first_row).to have_content(developer.name)
visit_members_list(sort: :access_level_desc) expect(second_row).to have_content(maintainer.name)
expect(first_member).to include(maintainer.name) expect_sort_by('Access granted', :desc)
expect(second_member).to include(developer.name) end
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending')
end
it 'sorts by last joined' do it 'sorts by account ascending' do
visit_members_list(sort: :last_joined) visit_members_list(sort: :name_asc)
expect(first_member).to include(maintainer.name) expect(first_row).to have_content(maintainer.name)
expect(second_member).to include(developer.name) expect(second_row).to have_content(developer.name)
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Last joined')
end
it 'sorts by oldest joined' do expect_sort_by('Account', :asc)
visit_members_list(sort: :oldest_joined) end
expect(first_member).to include(developer.name) it 'sorts by account descending' do
expect(second_member).to include(maintainer.name) visit_members_list(sort: :name_desc)
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined')
end
it 'sorts by name ascending' do expect(first_row).to have_content(developer.name)
visit_members_list(sort: :name_asc) expect(second_row).to have_content(maintainer.name)
expect(first_member).to include(maintainer.name) expect_sort_by('Account', :desc)
expect(second_member).to include(developer.name) end
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
end
it 'sorts by name descending' do it 'sorts by last sign-in ascending', :clean_gitlab_redis_shared_state do
visit_members_list(sort: :name_desc) visit_members_list(sort: :recent_sign_in)
expect(first_member).to include(developer.name) expect(first_row).to have_content(maintainer.name)
expect(second_member).to include(maintainer.name) expect(second_row).to have_content(developer.name)
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, descending')
end
it 'sorts by recent sign in', :clean_gitlab_redis_shared_state do expect_sort_by('Last sign-in', :asc)
visit_members_list(sort: :recent_sign_in) end
expect(first_member).to include(maintainer.name) it 'sorts by last sign-in descending', :clean_gitlab_redis_shared_state do
expect(second_member).to include(developer.name) visit_members_list(sort: :oldest_sign_in)
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in')
end
it 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do expect(first_row).to have_content(developer.name)
visit_members_list(sort: :oldest_sign_in) expect(second_row).to have_content(maintainer.name)
expect(first_member).to include(developer.name) expect_sort_by('Last sign-in', :desc)
expect(second_member).to include(maintainer.name)
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in')
end
end end
private private
......
...@@ -14,6 +14,11 @@ RSpec.describe 'Projects > Members > Tabs' do ...@@ -14,6 +14,11 @@ RSpec.describe 'Projects > Members > Tabs' do
let_it_be(:invites) { create_list(:project_member, 2, :invited, project: project) } let_it_be(:invites) { create_list(:project_member, 2, :invited, project: project) }
let_it_be(:project_group_links) { create_list(:project_group_link, 2, project: project) } let_it_be(:project_group_links) { create_list(:project_group_link, 2, project: project) }
before do
sign_in(user)
visit project_project_members_path(project)
end
shared_examples 'active "Members" tab' do shared_examples 'active "Members" tab' do
it 'displays "Members" tab' do it 'displays "Members" tab' do
expect(page).to have_selector('.nav-link.active', text: 'Members') expect(page).to have_selector('.nav-link.active', text: 'Members')
...@@ -21,11 +26,6 @@ RSpec.describe 'Projects > Members > Tabs' do ...@@ -21,11 +26,6 @@ RSpec.describe 'Projects > Members > Tabs' do
end end
context 'tabs' do context 'tabs' do
before do
sign_in(user)
visit project_project_members_path(project)
end
where(:tab, :count) do where(:tab, :count) do
'Members' | 3 'Members' | 3
'Invited' | 2 'Invited' | 2
...@@ -44,69 +44,25 @@ RSpec.describe 'Projects > Members > Tabs' do ...@@ -44,69 +44,25 @@ RSpec.describe 'Projects > Members > Tabs' do
end end
end end
context 'when `vue_project_members_list` feature flag is enabled' do context 'when searching "Groups"', :js do
before do before do
sign_in(user) click_link 'Groups'
visit project_project_members_path(project)
end
context 'when searching "Groups"', :js do
before do
click_link 'Groups'
fill_in_filtered_search 'Search groups', with: 'group'
end
it 'displays "Groups" tab' do
expect(page).to have_selector('.nav-link.active', text: 'Groups')
end
context 'and then searching "Members"' do fill_in_filtered_search 'Search groups', with: 'group'
before do
click_link 'Members 3'
fill_in_filtered_search 'Filter members', with: 'user'
end
it_behaves_like 'active "Members" tab'
end
end end
end
context 'when `vue_project_members_list` feature flag is disabled' do
before do
stub_feature_flags(vue_project_members_list: false)
sign_in(user) it 'displays "Groups" tab' do
visit project_project_members_path(project) expect(page).to have_selector('.nav-link.active', text: 'Groups')
end end
context 'when searching "Groups"', :js do context 'and then searching "Members"' do
before do before do
click_link 'Groups' click_link 'Members 3'
page.within '[data-testid="group-link-search-form"]' do fill_in_filtered_search 'Filter members', with: 'user'
fill_in 'search_groups', with: 'group'
find('button[type="submit"]').click
end
end end
it 'displays "Groups" tab' do it_behaves_like 'active "Members" tab'
expect(page).to have_selector('.nav-link.active', text: 'Groups')
end
context 'and then searching "Members"' do
before do
click_link 'Members 3'
page.within '[data-testid="user-search-form"]' do
fill_in 'search', with: 'user'
find('button[type="submit"]').click
end
end
it_behaves_like 'active "Members" tab'
end
end end
end end
end end
...@@ -19,123 +19,54 @@ RSpec.describe 'Projects > Settings > User manages project members' do ...@@ -19,123 +19,54 @@ RSpec.describe 'Projects > Settings > User manages project members' do
sign_in(user) sign_in(user)
end end
context 'when `vue_project_members_list` feature flag is enabled' do it 'cancels a team member', :js do
it 'cancels a team member', :js do visit(project_project_members_path(project))
visit(project_project_members_path(project))
page.within find_member_row(user_dmitriy) do page.within find_member_row(user_dmitriy) do
click_button 'Remove member' click_button 'Remove member'
end
page.within('[role="dialog"]') do
expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests'
click_button('Remove member')
end
visit(project_project_members_path(project))
expect(members_table).not_to have_content(user_dmitriy.name)
expect(members_table).not_to have_content(user_dmitriy.username)
end end
it 'imports a team from another project', :js do page.within('[role="dialog"]') do
stub_feature_flags(invite_members_group_modal: false) expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests'
click_button('Remove member')
project2.add_maintainer(user)
project2.add_reporter(user_mike)
visit(project_project_members_path(project))
page.within('.invite-users-form') do
click_link('Import')
end
select2(project2.id, from: '#source_project_id')
click_button('Import project members')
expect(find_member_row(user_mike)).to have_content('Reporter')
end end
it 'shows all members of project shared group', :js do visit(project_project_members_path(project))
group.add_owner(user)
group.add_developer(user_dmitriy)
share_link = project.project_group_links.new(group_access: Gitlab::Access::MAINTAINER)
share_link.group_id = group.id
share_link.save!
visit(project_project_members_path(project))
click_link 'Groups' expect(members_table).not_to have_content(user_dmitriy.name)
expect(members_table).not_to have_content(user_dmitriy.username)
expect(find_group_row(group)).to have_content('Maintainer')
end
end end
context 'when `vue_project_members_list` feature flag is disabled' do it 'imports a team from another project', :js do
before do stub_feature_flags(invite_members_group_modal: false)
stub_feature_flags(vue_project_members_list: false)
end
it 'cancels a team member', :js do project2.add_maintainer(user)
visit(project_project_members_path(project)) project2.add_reporter(user_mike)
project_member = project.project_members.find_by(user_id: user_dmitriy.id) visit(project_project_members_path(project))
page.within("#project_member_#{project_member.id}") do page.within('.invite-users-form') do
# Open modal click_link('Import')
click_on('Remove user from project')
end
expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests'
click_on('Remove member')
visit(project_project_members_path(project))
expect(page).not_to have_content(user_dmitriy.name)
expect(page).not_to have_content(user_dmitriy.username)
end end
it 'imports a team from another project' do select2(project2.id, from: '#source_project_id')
stub_feature_flags(invite_members_group_modal: false) click_button('Import project members')
project2.add_maintainer(user)
project2.add_reporter(user_mike)
visit(project_project_members_path(project))
page.within('.invite-users-form') do expect(find_member_row(user_mike)).to have_content('Reporter')
click_link('Import') end
end
select(project2.full_name, from: 'source_project_id')
click_button('Import')
project_member = project.project_members.find_by(user_id: user_mike.id)
page.within("#project_member_#{project_member.id}") do
expect(page).to have_content('Mike')
expect(page).to have_content('Reporter')
end
end
it 'shows all members of project shared group', :js do it 'shows all members of project shared group', :js do
group.add_owner(user) group.add_owner(user)
group.add_developer(user_dmitriy) group.add_developer(user_dmitriy)
share_link = project.project_group_links.new(group_access: Gitlab::Access::MAINTAINER) share_link = project.project_group_links.new(group_access: Gitlab::Access::MAINTAINER)
share_link.group_id = group.id share_link.group_id = group.id
share_link.save! share_link.save!
visit(project_project_members_path(project)) visit(project_project_members_path(project))
click_link 'Groups' click_link 'Groups'
page.within('[data-testid="project-member-groups"]') do expect(find_group_row(group)).to have_content('Maintainer')
expect(page).to have_content('OpenSource')
expect(first('.group_member')).to have_content('Maintainer')
end
end
end 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