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';
import { groupMemberRequestFormatter } from '~/groups/members/utils';
import groupsSelect from '~/groups_select';
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 initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger';
import { s__ } from '~/locale';
import memberExpirationDate from '~/member_expiration_date';
import { initMembersApp } from '~/members/index';
import { initMembersApp } from '~/members';
import { groupLinkRequestFormatter } from '~/members/utils';
import UsersSelect from '~/users_select';
import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue';
......@@ -73,4 +74,8 @@ initInviteMembersModal();
initInviteMembersTrigger();
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
import Vue from 'vue';
import { deprecatedCreateFlash as flash } from '~/flash';
import groupsSelect from '~/groups_select';
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 initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger';
import { __ } from '~/locale';
import { s__ } from '~/locale';
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 RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue';
......@@ -32,26 +34,14 @@ initInviteMembersModal();
initInviteMembersTrigger();
initInviteGroupTrigger();
new Members(); // eslint-disable-line no-new
new UsersSelect(); // eslint-disable-line no-new
// 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();
if (window.gon.features.vueProjectMembersList) {
const SHARED_FIELDS = ['account', 'expires', 'maxRole', 'expiration', 'actions'];
new UsersSelect(); // eslint-disable-line no-new
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'), {
const SHARED_FIELDS = ['account', 'expires', 'maxRole', 'expiration', 'actions'];
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'],
......@@ -63,9 +53,9 @@ if (window.gon.features.vueProjectMembersList) {
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'),
tableAttrs: {
table: { 'data-qa-selector': 'groups_list' },
......@@ -79,20 +69,14 @@ if (window.gon.features.vueProjectMembersList) {
placeholder: s__('Members|Search groups'),
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'),
requestFormatter: projectMemberRequestFormatter,
});
});
initMembersApp(document.querySelector('.js-project-access-requests-list'), {
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.'));
});
}
});
......@@ -8,10 +8,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController
# Authorize
before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access]
before_action do
push_frontend_feature_flag(:vue_project_members_list, @project, default_enabled: :yaml)
end
feature_category :authentication_and_authorization
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")
- group = @project.group
- vue_project_members_list_enabled = Feature.enabled?(:vue_project_members_list, @project, default_enabled: :yaml)
.js-remove-member-modal
.row.gl-mt-3
......@@ -76,44 +74,22 @@
%span.badge.badge-pill= @requesters.count
.tab-content
#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) }
.loading
.spinner.spinner-md
- else
= render 'projects/project_members/team', project: @project, group: group, members: @project_members, current_user_is_group_owner: current_user_is_group_owner?(@project)
= paginate @project_members, theme: "gitlab", params: { search_groups: nil }
- if show_groups?(@group_links)
#tab-groups.tab-pane{ class: ('active' if groups_tab_active?) }
- if vue_project_members_list_enabled
.js-project-group-links-list{ data: project_group_links_list_data_attributes(@project, @group_links) }
.loading
.spinner.spinner-md
- else
= render 'projects/project_members/groups', group_links: @group_links
- if show_invited_members?(@project, @invited_members)
#tab-invited-members.tab-pane
- if vue_project_members_list_enabled
.js-project-invited-members-list{ data: project_members_list_data_attributes(@project, @invited_members) }
.loading
.spinner.spinner-md
- else
.card.card-without-border
= render 'shared/members/tab_pane/header' do
= render 'shared/members/tab_pane/title' do
= html_escape(_('Members invited to %{strong_start}%{project_name}%{strong_end}')) % { project_name: @project.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
%ul.content-list.members-list
= render partial: 'shared/members/member', collection: @invited_members, as: :member, locals: { membership_source: @project, group: group, current_user_is_group_owner: current_user_is_group_owner?(@project) }
- if show_access_requests?(@project, @requesters)
#tab-access-requests.tab-pane
- if vue_project_members_list_enabled
.js-project-access-requests-list{ data: project_members_list_data_attributes(@project, @requesters) }
.loading
.spinner.spinner-md
- else
.card.card-without-border
= render 'shared/members/tab_pane/header' do
= render 'shared/members/tab_pane/title' do
= html_escape(_('Users requesting access to %{strong_start}%{project_name}%{strong_end}')) % { project_name: @project.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
%ul.content-list.members-list
= render partial: 'shared/members/member', collection: @requesters, as: :member, locals: { membership_source: @project, group: group }
---
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:
> - [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.
> - Improvements are [deployed behind a feature flag](../../feature_flags.md), enabled by default.
> - 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)**
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/299954) in GitLab 13.10.
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.
......@@ -199,27 +196,3 @@ To remove a member from a project:
A **Remove member** modal appears.
1. (Optional) Select the **Also unassign this user from related issues and merge requests** checkbox.
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
let!(:regular_member) { create(:group_member, :guest, group: group, user: maryjane, ldap: false) }
before do
stub_feature_flags(vue_project_members_list: false)
# We need to actually activate the LDAP config otherwise `Group#ldap_synced?` will always be false!
allow(Gitlab.config.ldap).to receive_messages(enabled: true)
......
......@@ -113,7 +113,6 @@ RSpec.describe 'Projects > Audit Events', :js do
project.add_developer(pete)
end
context 'when `vue_project_members_list` feature flag is enabled' do
it "appears in the project's audit events" do
visit project_project_members_path(project)
......@@ -135,35 +134,6 @@ RSpec.describe 'Projects > Audit Events', :js do
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 "appears in the project's audit events" do
visit project_project_members_path(project)
project_member = project.project_member(pete)
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
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
end
describe 'changing merge request approval permission for authors and reviewers' do
before do
project.add_developer(pete)
......
......@@ -71,7 +71,6 @@ RSpec.describe 'Project > Members > Invite group and members', :js 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'
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
stub_feature_flags(invite_members_group_modal: false)
......@@ -111,29 +110,6 @@ RSpec.describe 'Project > Members > Invite group and members', :js do
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 'allows the project to be shared with another group' do
visit project_project_members_path(project)
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
context 'when the group has "Share with group lock" enabled' do
before do
project.namespace.update_column(:share_with_group_lock, true)
......
......@@ -2,7 +2,7 @@
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(:project) { create(:project) }
let(:other_user) { create(:user) }
......@@ -10,10 +10,7 @@ RSpec.describe 'Projects > Members > Member is removed from project' do
before do
project.add_maintainer(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
......@@ -46,35 +43,4 @@ RSpec.describe 'Projects > Members > Member is removed from project' do
expect(non_matching_protected_branch.merge_access_levels.where(user: other_user)).to exist
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)
visit project_project_members_path(project)
end
it 'user is removed from project' do
click_link 'Leave'
expect(project.users.exists?(user.id)).to be_falsey
end
context 'when the user has been specifically allowed to access a protected branch' do
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_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
......@@ -3451,9 +3451,6 @@ msgstr ""
msgid "An error occurred while loading the file. Please try again later."
msgstr ""
msgid "An error occurred while loading the members, please try again."
msgstr ""
msgid "An error occurred while loading the merge request changes."
msgstr ""
......@@ -14925,9 +14922,6 @@ msgstr ""
msgid "Groups to synchronize"
msgstr ""
msgid "Groups with access to %{strong_open}%{project_name}%{strong_close}"
msgstr ""
msgid "GroupsDropdown|Frequently visited"
msgstr ""
......@@ -18766,9 +18760,6 @@ msgstr ""
msgid "Members can be added by project %{i_open}Maintainers%{i_close} or %{i_open}Owners%{i_close}"
msgstr ""
msgid "Members invited to %{strong_start}%{project_name}%{strong_end}"
msgstr ""
msgid "Members listed as CODEOWNERS of affected files."
msgstr ""
......@@ -18778,9 +18769,6 @@ msgstr ""
msgid "Members of %{group} can also push to this branch: %{branch}"
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"
msgstr ""
......@@ -32946,9 +32934,6 @@ msgstr ""
msgid "Users requesting access to"
msgstr ""
msgid "Users requesting access to %{strong_start}%{project_name}%{strong_end}"
msgstr ""
msgid "Users were successfully added."
msgstr ""
......
......@@ -4,12 +4,8 @@ module QA
RSpec.describe 'Manage', :requires_admin do
describe 'Add project member' do
before do
Runtime::Feature.enable('vue_project_members_list')
Runtime::Feature.enable(:invite_members_group_modal)
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
Flow::Login.sign_in
......
......@@ -16,15 +16,10 @@ module QA
end
before do
Runtime::Feature.enable('vue_project_members_list', project: project)
Runtime::Feature.enable(:invite_members_group_modal)
Flow::Login.sign_in
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
project.visit!
......
......@@ -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
before do
Runtime::Feature.enable('vue_project_members_list', project: project)
Runtime::Feature.enable(:invite_members_group_modal)
sign_in
project.visit!
......@@ -121,10 +120,6 @@ module QA
group.visit!
end
after do
Runtime::Feature.disable('vue_project_members_list', project: project)
end
it_behaves_like 'audit event', ['Added project access', 'Removed project access']
end
end
......
......@@ -92,14 +92,13 @@ RSpec.describe "Admin::Projects" do
end
end
context 'when `vue_project_members_list` feature flag is enabled', :js do
describe 'admin adds themselves to the project' do
describe 'admin adds themselves to the project', :js do
before do
project.add_maintainer(user)
stub_feature_flags(invite_members_group_modal: false)
end
it 'adds admin to the project as developer', :js do
it 'adds admin to the project as developer' do
visit project_project_members_path(project)
page.within '.invite-users-form' do
......@@ -113,7 +112,7 @@ RSpec.describe "Admin::Projects" do
end
end
describe 'admin removes themselves from the project' do
describe 'admin removes themselves from the project', :js do
before do
project.add_maintainer(user)
project.add_developer(current_user)
......@@ -135,54 +134,4 @@ RSpec.describe "Admin::Projects" do
expect(current_path).to match dashboard_projects_path
end
end
end
context 'when `vue_project_members_list` feature flag is disabled' do
before do
stub_feature_flags(vue_project_members_list: false)
end
describe 'admin adds themselves to the project' do
before do
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'
page.within '.content-list' do
expect(page).to have_content(current_user.name)
expect(page).to have_content('Developer')
end
end
end
describe 'admin removes themselves from the project' do
before do
project.add_maintainer(user)
project.add_developer(current_user)
end
it 'removes admin from the project' do
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
......@@ -14,25 +14,9 @@ RSpec.describe 'Projects > Members > Anonymous user sees members' do
create(:project_group_link, project: project, group: group)
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" do
it "anonymous user visits the project's members page and sees the list of members", :js do
visit project_project_members_path(project)
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
......@@ -20,7 +20,6 @@ RSpec.describe 'Projects members', :js do
sign_in(user)
end
context 'when `vue_project_members_list` feature flag is enabled' do
context 'with a group invitee' do
before do
group_invitee
......@@ -113,125 +112,4 @@ RSpec.describe 'Projects members', :js do
expect(first_row).to have_selector('gl-emoji[data-name="smirk"]')
end
end
end
context 'when `vue_project_members_list` feature flag is disabled' do
before do
stub_feature_flags(vue_project_members_list: false)
end
context 'with a group invitee' do
before do
group_invitee
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
context 'with a group' do
it 'shows group and project members by default' do
visit project_project_members_path(project)
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)
expect(page).not_to have_content(group.name)
end
end
it 'shows group members only if requested' do
visit project_project_members_path(project, with_inherited_permissions: 'only')
page.within first('.content-list') do
expect(page).not_to have_content(developer.name)
expect(page).to have_content(user.name)
expect(page).to have_content(group.name)
end
end
end
context 'with a group, a project invitee, and a project requester' do
before do
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
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
context 'with a group requester' do
before do
stub_feature_flags(invite_members_group_modal: false)
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
context 'showing status of members' do
it_behaves_like 'showing user status' do
let(:user_with_status) { developer }
subject { visit project_project_members_path(project) }
end
end
end
end
......@@ -17,10 +17,7 @@ RSpec.describe 'Projects > Members > Groups with access list', :js do
project.add_maintainer(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
......@@ -96,95 +93,6 @@ RSpec.describe 'Projects > Members > Groups with access list', :js do
expect(members_table).to have_content(group.full_name)
end
end
end
context 'when `vue_project_members_list` feature flag is disabled' do
before do
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')
end
it 'updates expiry date' do
expires_at_field = "member_expires_at_#{group.id}"
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
context 'when link has expiry date set' do
let(:additional_link_attrs) { { expires_at: 3.days.from_now.to_date } }
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')
end
end
end
it 'deletes group link' do
page.within(first('.group_member')) do
accept_confirm { find('.btn-danger').click }
end
wait_for_requests
expect(page).not_to have_selector('.group_member')
end
context 'search in existing members' do
it 'finds no results' do
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
expect(page).to have_selector('.group_member', count: 1)
end
end
end
def click_groups_tab
click_link 'Groups'
......
......@@ -41,7 +41,6 @@ RSpec.describe 'Project > Members > Invite group', :js do
context 'when the group has "Share with group lock" disabled' do
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
visit project_project_members_path(project)
......@@ -59,31 +58,6 @@ RSpec.describe 'Project > Members > Invite group', :js do
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
context 'when the group has "Share with group lock" enabled' do
before do
project.namespace.update_column(:share_with_group_lock, true)
......@@ -162,7 +136,6 @@ RSpec.describe 'Project > Members > Invite group', :js do
find('.btn-confirm').click
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
setup
click_link 'Groups'
......@@ -172,26 +145,6 @@ RSpec.describe 'Project > Members > Invite group', :js do
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
# Using distance_of_time_in_words_to_now because it is not the same as
# 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
describe 'the groups dropdown' do
context 'with multiple groups to choose from' do
let(:project) { create(:project) }
......
......@@ -2,8 +2,9 @@
require 'spec_helper'
RSpec.describe 'Project members list' do
RSpec.describe 'Project members list', :js do
include Select2Helper
include Spec::Support::Helpers::Features::MembersHelpers
let(:user1) { create(:user, name: 'John Doe') }
let(:user2) { create(:user, name: 'Mary Jane') }
......@@ -17,15 +18,6 @@ RSpec.describe 'Project members list' do
group.add_owner(user1)
end
context 'when `vue_project_members_list` feature flag is enabled', :js do
include Spec::Support::Helpers::Features::MembersHelpers
it 'pushes `vue_project_members_list` feature flag to the frontend' do
visit_members_page
expect(page).to have_pushed_frontend_feature_flags(vueProjectMembersList: true)
end
it 'show members from project and group' do
project.add_developer(user2)
......@@ -44,7 +36,7 @@ RSpec.describe 'Project members list' do
expect(second_row).to be_blank
end
it 'update user access level', :js do
it 'update user access level' do
project.add_developer(user2)
visit_members_page
......@@ -57,7 +49,7 @@ RSpec.describe 'Project members list' do
end
end
it 'add user to project', :js do
it 'add user to project' do
visit_members_page
add_user(user2.name, 'Reporter')
......@@ -67,7 +59,7 @@ RSpec.describe 'Project members list' do
end
end
it 'uses ProjectMember access_level_roles for the invite members modal access option', :js do
it 'uses ProjectMember access_level_roles for the invite members modal access option' do
visit_members_page
click_on 'Invite members'
......@@ -84,7 +76,7 @@ RSpec.describe 'Project members list' do
end
end
it 'remove user from project', :js do
it 'remove user from project' do
other_user = create(:user)
project.add_developer(other_user)
......@@ -105,7 +97,7 @@ RSpec.describe 'Project members list' do
expect(members_table).not_to have_content(other_user.name)
end
it 'invite user to project', :js do
it 'invite user to project' do
visit_members_page
add_user('test@example.com', 'Reporter')
......@@ -176,107 +168,6 @@ RSpec.describe 'Project members list' do
expect(find_member_row(user_with_2fa)).to have_content('2FA')
end
end
end
context 'when `vue_project_members_list` feature flag is disabled' do
include Spec::Support::Helpers::Features::ListRowsHelpers
before do
stub_feature_flags(vue_project_members_list: false)
end
it 'show members from project and group' do
project.add_developer(user2)
visit_members_page
expect(first_row.text).to include(user1.name)
expect(second_row.text).to include(user2.name)
end
it 'show user once if member of both group and project' do
project.add_developer(user1)
visit_members_page
expect(first_row.text).to include(user1.name)
expect(second_row).to be_blank
end
it 'update user access level', :js do
project.add_developer(user2)
visit_members_page
page.within(second_row) do
click_button('Developer')
click_link('Reporter')
expect(page).to have_button('Reporter')
end
end
it 'add user to project', :js do
visit_members_page
add_user(user2.name, 'Reporter')
page.within(second_row) do
expect(page).to have_content(user2.name)
expect(page).to have_button('Reporter')
end
end
it 'remove user from project', :js do
other_user = create(:user)
project.add_developer(other_user)
visit_members_page
# Open modal
find(:css, 'li.project_member', text: other_user.name).find(:css, 'button.btn-danger').click
expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests'
click_on('Remove member')
wait_for_requests
expect(page).not_to have_content(other_user.name)
expect(project.users).not_to include(other_user)
end
it 'invite user to project', :js do
visit_members_page
add_user('test@example.com', 'Reporter')
click_link 'Invited'
page.within(first_row) do
expect(page).to have_content('test@example.com')
expect(page).to have_content('Invited')
expect(page).to have_button('Reporter')
end
end
context 'project bots' do
let(:project_bot) { create(:user, :project_bot, name: 'project_bot') }
before do
project.add_maintainer(project_bot)
end
it 'does not show form used to change roles and "Expiration date" or the remove user button' do
project_member = project.project_members.find_by(user_id: project_bot.id)
visit_members_page
expect(page).not_to have_selector("#edit_project_member_#{project_member.id}")
expect(page).to have_no_selector("#project_member_#{project_member.id} .btn-danger")
end
end
end
private
......
......@@ -18,7 +18,6 @@ RSpec.describe 'Projects > Members > Maintainer adds member with expiration date
sign_in(maintainer)
end
context 'when `vue_project_members_list` feature flag is enabled' do
it 'expiration date is displayed in the members list' do
stub_feature_flags(invite_members_group_modal: false)
......@@ -66,61 +65,6 @@ RSpec.describe 'Projects > Members > Maintainer adds member with expiration date
expect(page).to have_content('No expiration set')
end
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 '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'
end
page.within "#project_member_#{project_member_id}" do
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('Expires in 3 days')
end
end
it 'clears 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 "#project_member_#{project_member_id}" do
expect(page).to have_content('Expires in 3 days')
find('.js-clear-input').click
wait_for_requests
expect(page).not_to have_content('Expires in')
end
end
end
def project_member_id
project.members.find_by(user_id: new_member).id
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'Projects > Members > Sorting' do
RSpec.describe 'Projects > Members > Sorting', :js do
include Spec::Support::Helpers::Features::MembersHelpers
let(:maintainer) { create(:user, name: 'John Doe') }
......@@ -15,7 +15,6 @@ RSpec.describe 'Projects > Members > Sorting' do
sign_in(maintainer)
end
context 'when `vue_project_members_list` feature flag is enabled', :js do
it 'sorts by account by default' do
visit_members_list(sort: nil)
......@@ -96,85 +95,6 @@ RSpec.describe 'Projects > Members > Sorting' do
expect_sort_by('Last sign-in', :desc)
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 'sorts alphabetically by default' do
visit_members_list(sort: nil)
expect(first_member).to include(maintainer.name)
expect(second_member).to include(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
visit_members_list(sort: :access_level_asc)
expect(first_member).to include(developer.name)
expect(second_member).to include(maintainer.name)
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending')
end
it 'sorts by access level descending' do
visit_members_list(sort: :access_level_desc)
expect(first_member).to include(maintainer.name)
expect(second_member).to include(developer.name)
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending')
end
it 'sorts by last joined' do
visit_members_list(sort: :last_joined)
expect(first_member).to include(maintainer.name)
expect(second_member).to include(developer.name)
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Last joined')
end
it 'sorts by oldest joined' do
visit_members_list(sort: :oldest_joined)
expect(first_member).to include(developer.name)
expect(second_member).to include(maintainer.name)
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined')
end
it 'sorts by name ascending' do
visit_members_list(sort: :name_asc)
expect(first_member).to include(maintainer.name)
expect(second_member).to include(developer.name)
expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
end
it 'sorts by name descending' do
visit_members_list(sort: :name_desc)
expect(first_member).to include(developer.name)
expect(second_member).to include(maintainer.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
visit_members_list(sort: :recent_sign_in)
expect(first_member).to include(maintainer.name)
expect(second_member).to include(developer.name)
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
visit_members_list(sort: :oldest_sign_in)
expect(first_member).to include(developer.name)
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
private
......
......@@ -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(: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
it 'displays "Members" tab' do
expect(page).to have_selector('.nav-link.active', text: 'Members')
......@@ -21,11 +26,6 @@ RSpec.describe 'Projects > Members > Tabs' do
end
context 'tabs' do
before do
sign_in(user)
visit project_project_members_path(project)
end
where(:tab, :count) do
'Members' | 3
'Invited' | 2
......@@ -44,12 +44,6 @@ RSpec.describe 'Projects > Members > Tabs' do
end
end
context 'when `vue_project_members_list` feature flag is enabled' do
before do
sign_in(user)
visit project_project_members_path(project)
end
context 'when searching "Groups"', :js do
before do
click_link 'Groups'
......@@ -71,42 +65,4 @@ RSpec.describe 'Projects > Members > Tabs' do
it_behaves_like 'active "Members" tab'
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)
visit project_project_members_path(project)
end
context 'when searching "Groups"', :js do
before do
click_link 'Groups'
page.within '[data-testid="group-link-search-form"]' do
fill_in 'search_groups', with: 'group'
find('button[type="submit"]').click
end
end
it 'displays "Groups" tab' do
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
......@@ -19,7 +19,6 @@ RSpec.describe 'Projects > Settings > User manages project members' do
sign_in(user)
end
context 'when `vue_project_members_list` feature flag is enabled' do
it 'cancels a team member', :js do
visit(project_project_members_path(project))
......@@ -70,72 +69,4 @@ RSpec.describe 'Projects > Settings > User manages project members' do
expect(find_group_row(group)).to have_content('Maintainer')
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 'cancels a team member', :js do
visit(project_project_members_path(project))
project_member = project.project_members.find_by(user_id: user_dmitriy.id)
page.within("#project_member_#{project_member.id}") do
# Open modal
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
it 'imports a team from another project' do
stub_feature_flags(invite_members_group_modal: false)
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
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
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'
page.within('[data-testid="project-member-groups"]') do
expect(page).to have_content('OpenSource')
expect(first('.group_member')).to have_content('Maintainer')
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