Add latest changes from gitlab-org/gitlab@master
|
@ -17,17 +17,18 @@ class Projects::ProjectMembersController < Projects::ApplicationController
|
|||
@skip_groups += @project.group.self_and_ancestors_ids if @project.group
|
||||
|
||||
@group_links = @project.project_group_links
|
||||
@group_links = @group_links.search(params[:search]) if params[:search].present?
|
||||
@group_links = @group_links.search(params[:search_groups]) if params[:search_groups].present?
|
||||
|
||||
@project_members = MembersFinder
|
||||
project_members = MembersFinder
|
||||
.new(@project, current_user, params: filter_params)
|
||||
.execute(include_relations: requested_relations)
|
||||
|
||||
@project_members = present_members(@project_members.page(params[:page]))
|
||||
if helpers.can_manage_project_members?(@project)
|
||||
@invited_members = present_members(project_members.invite)
|
||||
@requesters = present_members(AccessRequestsFinder.new(@project).execute(current_user))
|
||||
end
|
||||
|
||||
@requesters = present_members(
|
||||
AccessRequestsFinder.new(@project).execute(current_user)
|
||||
)
|
||||
@project_members = present_members(project_members.non_invite.page(params[:page]))
|
||||
|
||||
@project_member = @project.project_members.new
|
||||
end
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Projects::ProjectMembersHelper
|
||||
def can_manage_project_members?(project)
|
||||
can?(current_user, :admin_project_member, project)
|
||||
end
|
||||
|
||||
def show_groups?(group_links)
|
||||
group_links.exists? || groups_tab_active?
|
||||
end
|
||||
|
||||
def show_invited_members?(project, invited_members)
|
||||
can_manage_project_members?(project) && invited_members.exists?
|
||||
end
|
||||
|
||||
def show_access_requests?(project, requesters)
|
||||
can_manage_project_members?(project) && requesters.exists?
|
||||
end
|
||||
|
||||
def groups_tab_active?
|
||||
params[:search_groups].present?
|
||||
end
|
||||
|
||||
def current_user_is_group_owner?(project)
|
||||
return false if project.group.nil?
|
||||
|
||||
project.group.has_owner?(current_user)
|
||||
end
|
||||
end
|
|
@ -53,18 +53,18 @@
|
|||
#tab-members.tab-pane{ class: ('active' unless invited_active) }
|
||||
.card.card-without-border
|
||||
- unless filtered_search_enabled
|
||||
= render 'groups/group_members/tab_pane/header' do
|
||||
= render 'groups/group_members/tab_pane/title' do
|
||||
= render 'shared/members/tab_pane/header' do
|
||||
= render 'shared/members/tab_pane/title' do
|
||||
= html_escape(_('Members with access to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
|
||||
= form_tag group_group_members_path(@group), 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'
|
||||
- if can_manage_members
|
||||
= render 'groups/group_members/tab_pane/form_item' do
|
||||
= render 'shared/members/tab_pane/form_item' do
|
||||
= label_tag '2fa', _('2FA'), class: form_item_label_css_class
|
||||
= render 'shared/members/filter_2fa_dropdown'
|
||||
= render 'groups/group_members/tab_pane/form_item' do
|
||||
= render 'shared/members/tab_pane/form_item' do
|
||||
= label_tag :sort_by, _('Sort by'), class: form_item_label_css_class
|
||||
= render 'shared/members/sort_dropdown'
|
||||
.js-group-members-list{ data: group_members_list_data_attributes(@group, @members) }
|
||||
|
@ -75,8 +75,8 @@
|
|||
#tab-groups.tab-pane
|
||||
.card.card-without-border
|
||||
- unless filtered_search_enabled
|
||||
= render 'groups/group_members/tab_pane/header' do
|
||||
= render 'groups/group_members/tab_pane/title' do
|
||||
= render 'shared/members/tab_pane/header' do
|
||||
= render 'shared/members/tab_pane/title' do
|
||||
= html_escape(_('Groups with access to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
|
||||
.js-group-linked-list{ data: linked_groups_list_data_attributes(@group) }
|
||||
.loading
|
||||
|
@ -85,8 +85,8 @@
|
|||
#tab-invited-members.tab-pane{ class: ('active' if invited_active) }
|
||||
.card.card-without-border
|
||||
- unless filtered_search_enabled
|
||||
= render 'groups/group_members/tab_pane/header' do
|
||||
= render 'groups/group_members/tab_pane/title' do
|
||||
= render 'shared/members/tab_pane/header' do
|
||||
= render 'shared/members/tab_pane/title' do
|
||||
= html_escape(_('Members invited to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
|
||||
= form_tag group_group_members_path(@group), method: :get, class: 'user-search-form', data: { testid: 'user-search-form' } do
|
||||
= render 'shared/members/search_field', name: 'search_invited'
|
||||
|
@ -98,8 +98,8 @@
|
|||
#tab-access-requests.tab-pane
|
||||
.card.card-without-border
|
||||
- unless filtered_search_enabled
|
||||
= render 'groups/group_members/tab_pane/header' do
|
||||
= render 'groups/group_members/tab_pane/title' do
|
||||
= render 'shared/members/tab_pane/header' do
|
||||
= render 'shared/members/tab_pane/title' do
|
||||
= html_escape(_('Users requesting access to %{strong_start}%{group_name}%{strong_end}')) % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
|
||||
.js-group-access-requests-list{ data: group_members_list_data_attributes(@group, @requesters) }
|
||||
.loading
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
.card.project-members-groups
|
||||
.card-header
|
||||
.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 }
|
||||
%span.badge.badge-pill= group_links.size
|
||||
%ul.content-list.members-list
|
||||
- can_admin_member = can?(current_user, :admin_project_member, @project)
|
||||
= 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_admin_member, group_link_path: project_group_link_path(@project, 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)
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
- project = local_assigns.fetch(:project)
|
||||
- members = local_assigns.fetch(:members)
|
||||
- group = local_assigns.fetch(:group)
|
||||
- current_user_is_group_owner = group && group.has_owner?(current_user)
|
||||
- current_user_is_group_owner = local_assigns.fetch(:current_user_is_group_owner)
|
||||
|
||||
.card
|
||||
.card-header.flex-project-members-panel
|
||||
%span.flex-project-title
|
||||
.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 }
|
||||
%span.badge.badge-pill= members.total_count
|
||||
= form_tag project_project_members_path(project), method: :get, class: 'form-inline user-search-form flex-users-form' do
|
||||
.form-group
|
||||
.position-relative
|
||||
= search_field_tag :search, params[:search], { placeholder: _('Find existing members by name'), class: 'form-control', spellcheck: false }
|
||||
%button.user-search-btn{ type: "submit", "aria-label" => _("Submit search") }
|
||||
= sprite_icon('search', css_class: 'gl-vertical-align-middle!')
|
||||
= label_tag :sort_by, _('Sort by'), class: 'col-form-label label-bold px-2'
|
||||
= 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',
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
- page_title _("Members")
|
||||
- can_admin_project_members = can?(current_user, :admin_project_member, @project)
|
||||
- group = @project.group
|
||||
|
||||
.js-remove-member-modal
|
||||
|
@ -8,14 +7,13 @@
|
|||
- if project_can_be_shared?
|
||||
%h4
|
||||
= _("Project members")
|
||||
- if can_admin_project_members
|
||||
- if can_manage_project_members?(@project)
|
||||
%p= share_project_description(@project)
|
||||
- else
|
||||
%p
|
||||
= html_escape(_("Members can be added by project %{i_open}Maintainers%{i_close} or %{i_open}Owners%{i_close}")) % { i_open: '<i>'.html_safe, i_close: '</i>'.html_safe }
|
||||
|
||||
.light
|
||||
- if can_admin_project_members && project_can_be_shared?
|
||||
- if can_manage_project_members?(@project) && project_can_be_shared?
|
||||
- if !membership_locked? && @project.allowed_to_share_with_group?
|
||||
%ul.nav-links.nav.nav-tabs.gitlab-tabs{ role: 'tablist' }
|
||||
%li.nav-tab{ role: 'presentation' }
|
||||
|
@ -32,13 +30,50 @@
|
|||
.invite-member= render 'shared/members/invite_member', submit_url: project_project_members_path(@project), access_levels: ProjectMember.access_level_roles, default_access_level: @project_member.access_level, can_import_members?: can_import_members?, import_path: import_project_project_members_path(@project)
|
||||
- elsif @project.allowed_to_share_with_group?
|
||||
.invite-group= render 'shared/members/invite_group', access_levels: ProjectGroupLink.access_options, default_access_level: ProjectGroupLink.default_access, submit_url: project_group_links_path(@project), group_link_field: 'link_group_id', group_access_field: 'link_group_access'
|
||||
|
||||
= render 'shared/members/requests', membership_source: @project, group: group, requesters: @requesters
|
||||
.clearfix
|
||||
%h5.member.existing-title
|
||||
= _("Existing members and groups")
|
||||
- if @group_links.any?
|
||||
%ul.nav-links.mobile-separator.nav.nav-tabs
|
||||
%li.nav-item
|
||||
= link_to '#tab-members', class: ['nav-link', ('active' unless groups_tab_active?)], data: { toggle: 'tab' } do
|
||||
%span
|
||||
= _('Members')
|
||||
%span.badge.badge-pill= @project_members.total_count
|
||||
- if show_groups?(@group_links)
|
||||
%li.nav-item
|
||||
= link_to '#tab-groups', class: ['nav-link', ('active' if groups_tab_active?)] , data: { toggle: 'tab', qa_selector: 'groups_list_tab' } do
|
||||
%span
|
||||
= _('Groups')
|
||||
%span.badge.badge-pill= @group_links.count
|
||||
- if show_invited_members?(@project, @invited_members)
|
||||
%li.nav-item
|
||||
= link_to '#tab-invited-members', class: 'nav-link', data: { toggle: 'tab' } do
|
||||
%span
|
||||
= _('Invited')
|
||||
%span.badge.badge-pill= @invited_members.count
|
||||
- if show_access_requests?(@project, @requesters)
|
||||
%li.nav-item
|
||||
= link_to '#tab-access-requests', class: 'nav-link', data: { toggle: 'tab' } do
|
||||
%span
|
||||
= _('Access requests')
|
||||
%span.badge.badge-pill= @requesters.count
|
||||
.tab-content
|
||||
#tab-members.tab-pane{ class: ('active' unless groups_tab_active?) }
|
||||
= render 'projects/project_members/team', project: @project, group: group, members: @project_members, current_user_is_group_owner: current_user_is_group_owner?(@project)
|
||||
= paginate @project_members, theme: "gitlab", params: { search_groups: nil }
|
||||
- if show_groups?(@group_links)
|
||||
#tab-groups.tab-pane{ class: ('active' if groups_tab_active?) }
|
||||
= render 'projects/project_members/groups', group_links: @group_links
|
||||
|
||||
= render 'projects/project_members/team', project: @project, group: group, members: @project_members
|
||||
= paginate @project_members, theme: "gitlab"
|
||||
- if show_invited_members?(@project, @invited_members)
|
||||
#tab-invited-members.tab-pane
|
||||
.card.card-without-border
|
||||
= render 'shared/members/tab_pane/header' do
|
||||
= render 'shared/members/tab_pane/title' do
|
||||
= html_escape(_('Members invited to %{strong_start}%{project_name}%{strong_end}')) % { project_name: @project.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
|
||||
%ul.content-list.members-list
|
||||
= render partial: 'shared/members/member', collection: @invited_members, as: :member, locals: { membership_source: @project, group: group, current_user_is_group_owner: current_user_is_group_owner?(@project) }
|
||||
- if show_access_requests?(@project, @requesters)
|
||||
#tab-access-requests.tab-pane
|
||||
.card.card-without-border
|
||||
= render 'shared/members/tab_pane/header' do
|
||||
= render 'shared/members/tab_pane/title' do
|
||||
= html_escape(_('Users requesting access to %{strong_start}%{project_name}%{strong_end}')) % { project_name: @project.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
|
||||
%ul.content-list.members-list
|
||||
= render partial: 'shared/members/member', collection: @requesters, as: :member, locals: { membership_source: @project, group: group }
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Reorganize project member management into tabs
|
||||
merge_request: 49764
|
||||
author:
|
||||
type: changed
|
|
@ -516,12 +516,14 @@ class MyMigration < ActiveRecord::Migration[6.0]
|
|||
|
||||
disable_ddl_transaction!
|
||||
|
||||
INDEX_NAME = 'index_name'
|
||||
|
||||
def up
|
||||
add_concurrent_index :table, :column
|
||||
add_concurrent_index :table, :column, name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index :table, :column, name: index_name
|
||||
remove_concurrent_index :table, :column, name: INDEX_NAME
|
||||
end
|
||||
end
|
||||
```
|
||||
|
|
Before Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 285 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 369 KiB |
After Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 387 KiB |
|
@ -21,7 +21,7 @@ project's **Members**.
|
|||
When your project belongs to the group, group members inherit the membership and permission
|
||||
level for the project from the group.
|
||||
|
||||
![Project members page](img/project_members.png)
|
||||
![Project members page](img/project_members_13_8.png)
|
||||
|
||||
From the image above, we can deduce the following things:
|
||||
|
||||
|
@ -46,17 +46,17 @@ using the dropdown on the right side:
|
|||
Right next to **People**, start typing the name or username of the user you
|
||||
want to add.
|
||||
|
||||
![Search for people](img/add_user_search_people.png)
|
||||
![Search for people](img/add_user_search_people_13_8.png)
|
||||
|
||||
Select the user and the [permission level](../../permissions.md)
|
||||
that you'd like to give the user. Note that you can select more than one user.
|
||||
|
||||
![Give user permissions](img/add_user_give_permissions.png)
|
||||
![Give user permissions](img/add_user_give_permissions_13_8.png)
|
||||
|
||||
Once done, select **Add users to project** and they are immediately added to
|
||||
your project with the permissions you gave them above.
|
||||
|
||||
![List members](img/add_user_list_members.png)
|
||||
![List members](img/add_user_list_members_13_8.png)
|
||||
|
||||
From there on, you can either remove an existing user or change their access
|
||||
level to the project.
|
||||
|
@ -68,14 +68,14 @@ You can import another project's users in your own project by hitting the
|
|||
|
||||
In the dropdown menu, you can see only the projects you are Maintainer on.
|
||||
|
||||
![Import members from another project](img/add_user_import_members_from_another_project.png)
|
||||
![Import members from another project](img/add_user_import_members_from_another_project_13_8.png)
|
||||
|
||||
Select the one you want and hit **Import project members**. A flash message
|
||||
displays, notifying you that the import was successful, and the new members
|
||||
are now in the project's members list. Notice that the permissions that they
|
||||
had on the project you imported from are retained.
|
||||
|
||||
![Members list of new members](img/add_user_imported_members.png)
|
||||
![Members list of new members](img/add_user_imported_members_13_8.png)
|
||||
|
||||
## Invite people using their e-mail address
|
||||
|
||||
|
@ -83,18 +83,18 @@ If a user you want to give access to doesn't have an account on your GitLab
|
|||
instance, you can invite them just by typing their e-mail address in the
|
||||
user search field.
|
||||
|
||||
![Invite user by mail](img/add_user_email_search.png)
|
||||
![Invite user by mail](img/add_user_email_search_13_8.png)
|
||||
|
||||
As you can imagine, you can mix inviting multiple people and adding existing
|
||||
GitLab users to the project.
|
||||
|
||||
![Invite user by mail ready to submit](img/add_user_email_ready.png)
|
||||
![Invite user by mail ready to submit](img/add_user_email_ready_13_8.png)
|
||||
|
||||
Once done, hit **Add users to project** and watch that there is a new member
|
||||
with the e-mail address we used above. From there on, you can resend the
|
||||
invitation, change their access level, or even delete them.
|
||||
|
||||
![Invite user members list](img/add_user_email_accept.png)
|
||||
![Invite user members list](img/add_user_email_accept_13_8.png)
|
||||
|
||||
While unaccepted, the system automatically sends reminder emails on the second, fifth,
|
||||
and tenth day after the invitation was initially sent.
|
||||
|
@ -130,7 +130,7 @@ NOTE:
|
|||
If a project does not have any maintainers, the notification is sent to the
|
||||
most recently active owners of the project's group.
|
||||
|
||||
![Manage access requests](img/access_requests_management.png)
|
||||
![Manage access requests](img/access_requests_management_13_8.png)
|
||||
|
||||
If you change your mind before your request is approved, just click the
|
||||
**Withdraw Access Request** button.
|
||||
|
|
|
@ -26,19 +26,20 @@ To share 'Project Acme' with the 'Engineering' group:
|
|||
|
||||
1. For 'Project Acme' use the left navigation menu to go to **Members**.
|
||||
|
||||
![share project with groups](img/share_project_with_groups_tab_v13_6.png)
|
||||
![share project with groups](img/share_project_with_groups_tab_v13_8.png)
|
||||
|
||||
1. Select the **Invite group** tab.
|
||||
1. Add the 'Engineering' group with the maximum access level of your choice.
|
||||
1. Optionally, select an expiring date.
|
||||
1. Click **Invite**.
|
||||
1. After sharing 'Project Acme' with 'Engineering':
|
||||
- The group is listed in the **Groups** tab.
|
||||
|
||||
![share project with groups tab](img/share_project_with_groups_tab_v13_6.png)
|
||||
!['Engineering' group is listed in Groups tab](img/project_groups_tab_13_8.png)
|
||||
|
||||
1. After sharing 'Project Acme' with 'Engineering', the project is listed
|
||||
on the group dashboard
|
||||
- The project is listed on the group dashboard.
|
||||
|
||||
!['Project Acme' is listed as a shared project for 'Engineering'](img/other_group_sees_shared_project_v13_6.png)
|
||||
!['Project Acme' is listed as a shared project for 'Engineering'](img/other_group_sees_shared_project_v13_8.png)
|
||||
|
||||
Note that you can only share a project with:
|
||||
|
||||
|
|
|
@ -111,8 +111,8 @@ module Gitlab
|
|||
private
|
||||
|
||||
def before_send(event, hint)
|
||||
event = add_context_from_exception_type(event, hint)
|
||||
event = custom_fingerprinting(event, hint)
|
||||
inject_context_for_exception(event, hint[:exception])
|
||||
custom_fingerprinting(event, hint[:exception])
|
||||
|
||||
event
|
||||
end
|
||||
|
@ -123,7 +123,6 @@ module Gitlab
|
|||
end
|
||||
|
||||
extra = sanitize_request_parameters(extra)
|
||||
inject_sql_query_into_extra(exception, extra)
|
||||
|
||||
if sentry && Raven.configuration.server
|
||||
Raven.capture_exception(exception, tags: default_tags, extra: extra)
|
||||
|
@ -150,12 +149,6 @@ module Gitlab
|
|||
filter.filter(parameters)
|
||||
end
|
||||
|
||||
def inject_sql_query_into_extra(exception, extra)
|
||||
return unless exception.is_a?(ActiveRecord::StatementInvalid)
|
||||
|
||||
extra[:sql] = PgQuery.normalize(exception.sql.to_s)
|
||||
end
|
||||
|
||||
def sentry_dsn
|
||||
return unless Rails.env.production? || Rails.env.development?
|
||||
return unless Gitlab.config.sentry.enabled
|
||||
|
@ -183,9 +176,17 @@ module Gitlab
|
|||
{}
|
||||
end
|
||||
|
||||
# Debugging for https://gitlab.com/gitlab-org/gitlab-foss/issues/57727
|
||||
def add_context_from_exception_type(event, hint)
|
||||
if ActiveModel::MissingAttributeError === hint[:exception]
|
||||
# Group common, mostly non-actionable exceptions by type and message,
|
||||
# rather than cause
|
||||
def custom_fingerprinting(event, ex)
|
||||
return event unless CUSTOM_FINGERPRINTING.include?(ex.class.name)
|
||||
|
||||
event.fingerprint = [ex.class.name, ex.message]
|
||||
end
|
||||
|
||||
def inject_context_for_exception(event, ex)
|
||||
case ex
|
||||
when ActiveModel::MissingAttributeError # Debugging for https://gitlab.com/gitlab-org/gitlab/-/issues/26751
|
||||
columns_hash = ActiveRecord::Base
|
||||
.connection
|
||||
.schema_cache
|
||||
|
@ -193,21 +194,11 @@ module Gitlab
|
|||
.transform_values { |v| v.map(&:first) }
|
||||
|
||||
event.extra.merge!(columns_hash)
|
||||
when ActiveRecord::StatementInvalid
|
||||
event.extra[:sql] = PgQuery.normalize(ex.sql.to_s)
|
||||
else
|
||||
inject_context_for_exception(event, ex.cause) if ex.cause.present?
|
||||
end
|
||||
|
||||
event
|
||||
end
|
||||
|
||||
# Group common, mostly non-actionable exceptions by type and message,
|
||||
# rather than cause
|
||||
def custom_fingerprinting(event, hint)
|
||||
ex = hint[:exception]
|
||||
|
||||
return event unless CUSTOM_FINGERPRINTING.include?(ex.class.name)
|
||||
|
||||
event.fingerprint = [ex.class.name, ex.message]
|
||||
|
||||
event
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11559,9 +11559,6 @@ msgstr ""
|
|||
msgid "Existing branch name, tag, or commit SHA"
|
||||
msgstr ""
|
||||
|
||||
msgid "Existing members and groups"
|
||||
msgstr ""
|
||||
|
||||
msgid "Existing projects may be moved into a group"
|
||||
msgstr ""
|
||||
|
||||
|
@ -12428,9 +12425,6 @@ msgstr ""
|
|||
msgid "Find by path"
|
||||
msgstr ""
|
||||
|
||||
msgid "Find existing members by name"
|
||||
msgstr ""
|
||||
|
||||
msgid "Find file"
|
||||
msgstr ""
|
||||
|
||||
|
@ -17272,6 +17266,9 @@ msgstr ""
|
|||
msgid "Members invited to %{strong_start}%{group_name}%{strong_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Members invited to %{strong_start}%{project_name}%{strong_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Members listed as CODEOWNERS of affected files."
|
||||
msgstr ""
|
||||
|
||||
|
@ -30733,6 +30730,9 @@ msgstr ""
|
|||
msgid "Users requesting access to %{strong_start}%{group_name}%{strong_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Users requesting access to %{strong_start}%{project_name}%{strong_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Users were successfully added."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -42,9 +42,9 @@
|
|||
"@babel/plugin-syntax-import-meta": "^7.10.1",
|
||||
"@babel/preset-env": "^7.10.1",
|
||||
"@gitlab/at.js": "1.5.5",
|
||||
"@gitlab/svgs": "1.177.0",
|
||||
"@gitlab/svgs": "1.178.0",
|
||||
"@gitlab/tributejs": "1.0.0",
|
||||
"@gitlab/ui": "25.2.1",
|
||||
"@gitlab/ui": "25.3.1",
|
||||
"@gitlab/visual-review-tools": "1.6.1",
|
||||
"@rails/actioncable": "^6.0.3-4",
|
||||
"@rails/ujs": "^6.0.3-4",
|
||||
|
|
|
@ -17,6 +17,7 @@ module QA
|
|||
|
||||
view 'app/views/projects/project_members/index.html.haml' do
|
||||
element :invite_group_tab
|
||||
element :groups_list_tab
|
||||
end
|
||||
|
||||
view 'app/views/shared/members/_invite_group.html.haml' do
|
||||
|
@ -48,6 +49,7 @@ module QA
|
|||
|
||||
def remove_group(group_name)
|
||||
click_element :invite_group_tab
|
||||
click_element :groups_list_tab
|
||||
page.accept_alert do
|
||||
within_element(:group_row, text: group_name) do
|
||||
click_element :delete_group_access_link
|
||||
|
|
|
@ -14,6 +14,7 @@ RSpec.describe Projects::ProjectMembersController do
|
|||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
|
||||
context 'project members' do
|
||||
context 'when project belongs to group' do
|
||||
let(:user_in_group) { create(:user) }
|
||||
let(:project_in_group) { create(:project, :public, group: group) }
|
||||
|
@ -42,6 +43,110 @@ RSpec.describe Projects::ProjectMembersController do
|
|||
expect(assigns(:project_members).map(&:user_id)).to contain_exactly(user_in_group.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invited members are present' do
|
||||
let!(:invited_member) { create(:project_member, :invited, project: project) }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'excludes the invited members from project members list' do
|
||||
get :index, params: { namespace_id: project.namespace, project_id: project }
|
||||
|
||||
expect(assigns(:project_members).map(&:invite_email)).not_to contain_exactly(invited_member.invite_email)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'group links' do
|
||||
let!(:project_group_link) { create(:project_group_link, project: project, group: group) }
|
||||
|
||||
it 'lists group links' do
|
||||
get :index, params: { namespace_id: project.namespace, project_id: project }
|
||||
|
||||
expect(assigns(:group_links).map(&:id)).to contain_exactly(project_group_link.id)
|
||||
end
|
||||
|
||||
context 'when `search_groups` param is present' do
|
||||
let(:group_2) { create(:group, :public, name: 'group_2') }
|
||||
let!(:project_group_link_2) { create(:project_group_link, project: project, group: group_2) }
|
||||
|
||||
it 'lists group links that match search' do
|
||||
get :index, params: { namespace_id: project.namespace, project_id: project, search_groups: 'group_2' }
|
||||
|
||||
expect(assigns(:group_links).map(&:id)).to contain_exactly(project_group_link_2.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'invited members' do
|
||||
let!(:invited_member) { create(:project_member, :invited, project: project) }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'when user has `admin_project_member` permissions' do
|
||||
before do
|
||||
allow(controller.helpers).to receive(:can_manage_project_members?).with(project).and_return(true)
|
||||
end
|
||||
|
||||
it 'lists invited members' do
|
||||
get :index, params: { namespace_id: project.namespace, project_id: project }
|
||||
|
||||
expect(assigns(:invited_members).map(&:invite_email)).to contain_exactly(invited_member.invite_email)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user does not have `admin_project_member` permissions' do
|
||||
before do
|
||||
allow(controller.helpers).to receive(:can_manage_project_members?).with(project).and_return(false)
|
||||
end
|
||||
|
||||
it 'does not list invited members' do
|
||||
get :index, params: { namespace_id: project.namespace, project_id: project }
|
||||
|
||||
expect(assigns(:invited_members)).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'access requests' do
|
||||
let(:access_requester_user) { create(:user) }
|
||||
|
||||
before do
|
||||
project.request_access(access_requester_user)
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'when user has `admin_project_member` permissions' do
|
||||
before do
|
||||
allow(controller.helpers).to receive(:can_manage_project_members?).with(project).and_return(true)
|
||||
end
|
||||
|
||||
it 'lists access requests' do
|
||||
get :index, params: { namespace_id: project.namespace, project_id: project }
|
||||
|
||||
expect(assigns(:requesters).map(&:user_id)).to contain_exactly(access_requester_user.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user does not have `admin_project_member` permissions' do
|
||||
before do
|
||||
allow(controller.helpers).to receive(:can_manage_project_members?).with(project).and_return(false)
|
||||
end
|
||||
|
||||
it 'does not list access requests' do
|
||||
get :index, params: { namespace_id: project.namespace, project_id: project }
|
||||
|
||||
expect(assigns(:requesters)).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST create' do
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
FactoryBot.define do
|
||||
factory :project_group_link do
|
||||
project
|
||||
group
|
||||
group { association(:group) }
|
||||
expires_at { nil }
|
||||
group_access { Gitlab::Access::DEVELOPER }
|
||||
|
||||
|
|
|
@ -15,7 +15,9 @@ FactoryBot.define do
|
|||
trait(:invited) do
|
||||
user_id { nil }
|
||||
invite_token { 'xxx' }
|
||||
invite_email { 'email@email.com' }
|
||||
sequence :invite_email do |n|
|
||||
"email#{n}@email.com"
|
||||
end
|
||||
end
|
||||
|
||||
trait :blocked do
|
||||
|
|
|
@ -4,7 +4,6 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe 'Groups > Members > Maintainer manages access requests' do
|
||||
it_behaves_like 'Maintainer manages access requests' do
|
||||
let(:has_tabs) { true }
|
||||
let(:entity) { create(:group, :public) }
|
||||
let(:members_page_path) { group_group_members_path(entity) }
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Projects members' do
|
||||
RSpec.describe 'Projects members', :js do
|
||||
let(:user) { create(:user) }
|
||||
let(:developer) { create(:user) }
|
||||
let(:group) { create(:group, :public) }
|
||||
|
@ -66,26 +66,47 @@ RSpec.describe 'Projects members' do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with a group and a project invitee' do
|
||||
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 project invitee, the project developer, and the group owner' do
|
||||
it 'shows the group owner' do
|
||||
page.within first('.content-list') do
|
||||
expect(page).to have_content('test1@abc.com')
|
||||
expect(page).not_to have_content('test2@abc.com')
|
||||
|
||||
# Project developer
|
||||
expect(page).to have_content(developer.name)
|
||||
|
||||
# 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
|
||||
|
@ -95,36 +116,13 @@ RSpec.describe 'Projects members' do
|
|||
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 'with a group and a project requesters' do
|
||||
before do
|
||||
group.request_access(group_requester)
|
||||
project.request_access(project_requester)
|
||||
visit project_project_members_path(project)
|
||||
end
|
||||
|
||||
it 'shows the project requester, the project developer, and the group owner' do
|
||||
page.within first('.content-list') do
|
||||
expect(page).to have_content(project_requester.name)
|
||||
expect(page).not_to have_content(group_requester.name)
|
||||
end
|
||||
|
||||
page.within all('.content-list').last do
|
||||
# Project developer
|
||||
expect(page).to have_content(developer.name)
|
||||
|
||||
# Group owner
|
||||
expect(page).to have_content(user.name)
|
||||
expect(page).to have_content(group.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'showing status of members' do
|
||||
it_behaves_like 'showing user status' do
|
||||
let(:user_with_status) { developer }
|
||||
|
|
|
@ -16,6 +16,7 @@ RSpec.describe 'Projects > Members > Groups with access list', :js do
|
|||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
visit project_project_members_path(project)
|
||||
click_groups_tab
|
||||
end
|
||||
|
||||
it 'updates group access level' do
|
||||
|
@ -29,6 +30,8 @@ RSpec.describe 'Projects > Members > Groups with access list', :js do
|
|||
|
||||
visit project_project_members_path(project)
|
||||
|
||||
click_groups_tab
|
||||
|
||||
expect(first('.group_member')).to have_content('Guest')
|
||||
end
|
||||
|
||||
|
@ -71,23 +74,31 @@ RSpec.describe 'Projects > Members > Groups with access list', :js do
|
|||
expect(page).not_to have_selector('.group_member')
|
||||
end
|
||||
|
||||
context 'search in existing members (yes, this filters the groups list as well)' do
|
||||
context 'search in existing members' do
|
||||
it 'finds no results' do
|
||||
page.within '.user-search-form' do
|
||||
fill_in 'search', with: 'testing 123'
|
||||
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', with: group.name
|
||||
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
|
||||
|
||||
def click_groups_tab
|
||||
click_link 'Groups'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -39,7 +39,7 @@ RSpec.describe 'Project > Members > Invite group', :js do
|
|||
it 'the project can be shared with another group' do
|
||||
visit project_project_members_path(project)
|
||||
|
||||
expect(page).not_to have_css('.project-members-groups')
|
||||
expect(page).not_to have_link 'Groups'
|
||||
|
||||
click_on 'invite-group-tab'
|
||||
|
||||
|
@ -47,7 +47,9 @@ RSpec.describe 'Project > Members > Invite group', :js do
|
|||
page.find('body').click
|
||||
find('.btn-success').click
|
||||
|
||||
page.within('.project-members-groups') do
|
||||
click_link 'Groups'
|
||||
|
||||
page.within('[data-testid="project-member-groups"]') do
|
||||
expect(page).to have_content(group_to_share_with.name)
|
||||
end
|
||||
end
|
||||
|
@ -132,7 +134,9 @@ RSpec.describe 'Project > Members > Invite group', :js do
|
|||
end
|
||||
|
||||
it 'the group link shows the expiration time with a warning class' do
|
||||
page.within('.project-members-groups') do
|
||||
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)
|
||||
|
|
|
@ -82,7 +82,9 @@ RSpec.describe 'Project members list' do
|
|||
|
||||
add_user('test@example.com', 'Reporter')
|
||||
|
||||
page.within(second_row) do
|
||||
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')
|
||||
|
|
|
@ -4,7 +4,6 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe 'Projects > Members > Maintainer manages access requests' do
|
||||
it_behaves_like 'Maintainer manages access requests' do
|
||||
let(:has_tabs) { false }
|
||||
let(:entity) { create(:project, :public) }
|
||||
let(:members_page_path) { project_project_members_path(entity) }
|
||||
end
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Projects > Members > Tabs' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, creator: user, namespace: user.namespace) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project_members) { create_list(:project_member, 2, project: project) }
|
||||
let_it_be(:access_requests) { create_list(:project_member, 2, :access_request, 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) }
|
||||
|
||||
shared_examples 'active "Members" tab' do
|
||||
it 'displays "Members" tab' do
|
||||
expect(page).to have_selector('.nav-link.active', text: 'Members')
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Kaminari.config).to receive(:default_per_page).and_return(1)
|
||||
|
||||
sign_in(user)
|
||||
visit project_project_members_path(project)
|
||||
end
|
||||
|
||||
where(:tab, :count) do
|
||||
'Members' | 3
|
||||
'Invited' | 2
|
||||
'Groups' | 2
|
||||
'Access requests' | 2
|
||||
end
|
||||
|
||||
with_them do
|
||||
it "renders #{params[:tab]} tab" do
|
||||
expect(page).to have_selector('.nav-link', text: "#{tab} #{count}")
|
||||
end
|
||||
end
|
||||
|
||||
context 'displays "Members" tab by default' do
|
||||
it_behaves_like 'active "Members" tab'
|
||||
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
|
|
@ -57,7 +57,7 @@ RSpec.describe 'Projects > Settings > User manages project members' do
|
|||
end
|
||||
end
|
||||
|
||||
it 'shows all members of project shared group' do
|
||||
it 'shows all members of project shared group', :js do
|
||||
group.add_owner(user)
|
||||
group.add_developer(user_dmitriy)
|
||||
|
||||
|
@ -67,7 +67,9 @@ RSpec.describe 'Projects > Settings > User manages project members' do
|
|||
|
||||
visit(project_project_members_path(project))
|
||||
|
||||
page.within('.project-members-groups') do
|
||||
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
|
||||
|
|
|
@ -19,7 +19,7 @@ describe('Submit Changes Error', () => {
|
|||
});
|
||||
};
|
||||
|
||||
const findRetryButton = () => wrapper.findAll(GlButton).at(1);
|
||||
const findRetryButton = () => wrapper.find(GlButton);
|
||||
const findAlert = () => wrapper.find(GlAlert);
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Projects::ProjectMembersHelper do
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
let(:allow_admin_project) { nil }
|
||||
|
||||
before do
|
||||
allow(helper).to receive(:current_user).and_return(current_user)
|
||||
allow(helper).to receive(:can?).with(current_user, :admin_project_member, project).and_return(allow_admin_project)
|
||||
end
|
||||
|
||||
shared_examples 'when `current_user` does not have `admin_project_member` permissions' do
|
||||
let(:allow_admin_project) { false }
|
||||
|
||||
it { is_expected.to be(false) }
|
||||
end
|
||||
|
||||
describe '#can_manage_project_members?' do
|
||||
subject { helper.can_manage_project_members?(project) }
|
||||
|
||||
context 'when `current_user` has `admin_project_member` permissions' do
|
||||
let(:allow_admin_project) { true }
|
||||
|
||||
it { is_expected.to be(true) }
|
||||
end
|
||||
|
||||
include_examples 'when `current_user` does not have `admin_project_member` permissions'
|
||||
end
|
||||
|
||||
describe '#show_groups?' do
|
||||
subject { helper.show_groups?(project.project_group_links) }
|
||||
|
||||
context 'when group links exist' do
|
||||
let!(:project_group_link) { create(:project_group_link, project: project) }
|
||||
|
||||
it { is_expected.to be(true) }
|
||||
end
|
||||
|
||||
context 'when `search_groups` param is set' do
|
||||
before do
|
||||
allow(helper).to receive(:params).and_return({ search_groups: 'foo' })
|
||||
end
|
||||
|
||||
it { is_expected.to be(true) }
|
||||
end
|
||||
|
||||
context 'when `search_groups` param is not set and group links do not exist' do
|
||||
it { is_expected.to be(false) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#show_invited_members?' do
|
||||
subject { helper.show_invited_members?(project, project.project_members.invite) }
|
||||
|
||||
context 'when `current_user` has `admin_project_member` permissions' do
|
||||
let(:allow_admin_project) { true }
|
||||
|
||||
context 'when invited members exist' do
|
||||
let!(:invite) { create(:project_member, :invited, project: project) }
|
||||
|
||||
it { is_expected.to be(true) }
|
||||
end
|
||||
|
||||
context 'when invited members do not exist' do
|
||||
it { is_expected.to be(false) }
|
||||
end
|
||||
end
|
||||
|
||||
include_examples 'when `current_user` does not have `admin_project_member` permissions'
|
||||
end
|
||||
|
||||
describe '#show_access_requests?' do
|
||||
subject { helper.show_access_requests?(project, project.requesters) }
|
||||
|
||||
context 'when `current_user` has `admin_project_member` permissions' do
|
||||
let(:allow_admin_project) { true }
|
||||
|
||||
context 'when access requests exist' do
|
||||
let!(:access_request) { create(:project_member, :access_request, project: project) }
|
||||
|
||||
it { is_expected.to be(true) }
|
||||
end
|
||||
|
||||
context 'when access requests do not exist' do
|
||||
it { is_expected.to be(false) }
|
||||
end
|
||||
end
|
||||
|
||||
include_examples 'when `current_user` does not have `admin_project_member` permissions'
|
||||
end
|
||||
|
||||
describe '#groups_tab_active?' do
|
||||
subject { helper.groups_tab_active? }
|
||||
|
||||
context 'when `search_groups` param is set' do
|
||||
before do
|
||||
allow(helper).to receive(:params).and_return({ search_groups: 'foo' })
|
||||
end
|
||||
|
||||
it { is_expected.to be(true) }
|
||||
end
|
||||
|
||||
context 'when `search_groups` param is not set' do
|
||||
it { is_expected.to be(false) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#current_user_is_group_owner?' do
|
||||
let(:group) { create(:group) }
|
||||
|
||||
subject { helper.current_user_is_group_owner?(project2) }
|
||||
|
||||
describe "when current user is the owner of the project's parent group" do
|
||||
let(:project2) { create(:project, namespace: group) }
|
||||
|
||||
before do
|
||||
group.add_owner(current_user)
|
||||
end
|
||||
|
||||
it { is_expected.to be(true) }
|
||||
end
|
||||
|
||||
describe "when current user is not the owner of the project's parent group" do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let(:project2) { create(:project, namespace: group) }
|
||||
|
||||
before do
|
||||
group.add_owner(user)
|
||||
end
|
||||
|
||||
it { is_expected.to be(false) }
|
||||
end
|
||||
|
||||
describe "when project does not have a parent group" do
|
||||
let(:user) { create(:user) }
|
||||
let(:project2) { create(:project, namespace: user.namespace) }
|
||||
|
||||
it { is_expected.to be(false) }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -236,7 +236,7 @@ RSpec.describe Gitlab::ErrorTracking do
|
|||
|
||||
context 'the exception implements :sentry_extra_data' do
|
||||
let(:extra_info) { { event: 'explosion', size: :massive } }
|
||||
let(:exception) { double(message: 'bang!', sentry_extra_data: extra_info, backtrace: caller) }
|
||||
let(:exception) { double(message: 'bang!', sentry_extra_data: extra_info, backtrace: caller, cause: nil) }
|
||||
|
||||
it 'includes the extra data from the exception in the tracking information' do
|
||||
track_exception
|
||||
|
@ -247,7 +247,7 @@ RSpec.describe Gitlab::ErrorTracking do
|
|||
end
|
||||
|
||||
context 'the exception implements :sentry_extra_data, which returns nil' do
|
||||
let(:exception) { double(message: 'bang!', sentry_extra_data: nil, backtrace: caller) }
|
||||
let(:exception) { double(message: 'bang!', sentry_extra_data: nil, backtrace: caller, cause: nil) }
|
||||
let(:extra) { { issue_url: issue_url } }
|
||||
|
||||
it 'just includes the other extra info' do
|
||||
|
@ -287,10 +287,23 @@ RSpec.describe Gitlab::ErrorTracking do
|
|||
let(:exception) { ActiveRecord::StatementInvalid.new(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = 1 AND "users"."foo" = $1') }
|
||||
|
||||
it 'injects the normalized sql query into extra' do
|
||||
track_exception
|
||||
allow(Raven.client.transport).to receive(:send_event) do |event|
|
||||
expect(event.extra).to include(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = $2 AND "users"."foo" = $1')
|
||||
end
|
||||
|
||||
expect(Raven).to have_received(:capture_exception)
|
||||
.with(exception, a_hash_including(extra: a_hash_including(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = $2 AND "users"."foo" = $1')))
|
||||
track_exception
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the `ActiveRecord::StatementInvalid` is wrapped in another exception' do
|
||||
let(:exception) { RuntimeError.new(cause: ActiveRecord::StatementInvalid.new(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = 1 AND "users"."foo" = $1')) }
|
||||
|
||||
it 'injects the normalized sql query into extra' do
|
||||
allow(Raven.client.transport).to receive(:send_event) do |event|
|
||||
expect(event.extra).to include(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = $2 AND "users"."foo" = $1')
|
||||
end
|
||||
|
||||
track_exception
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,10 +12,8 @@ RSpec.shared_examples 'Maintainer manages access requests' do
|
|||
sign_in(maintainer)
|
||||
visit members_page_path
|
||||
|
||||
if has_tabs
|
||||
click_on 'Access requests'
|
||||
end
|
||||
end
|
||||
|
||||
it 'maintainer can see access requests', :js do
|
||||
expect_visible_access_request(entity, user)
|
||||
|
@ -48,11 +46,7 @@ RSpec.shared_examples 'Maintainer manages access requests' do
|
|||
end
|
||||
|
||||
def expect_visible_access_request(entity, user)
|
||||
if has_tabs
|
||||
expect(page).to have_content "Access requests 1"
|
||||
else
|
||||
expect(page).to have_content "Users requesting access to #{entity.name} 1"
|
||||
end
|
||||
|
||||
expect(page).to have_content user.name
|
||||
end
|
||||
|
|
16
yarn.lock
|
@ -861,20 +861,20 @@
|
|||
eslint-plugin-vue "^6.2.1"
|
||||
vue-eslint-parser "^7.0.0"
|
||||
|
||||
"@gitlab/svgs@1.177.0":
|
||||
version "1.177.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.177.0.tgz#e481ed327a11d3834c8b1668d7485b9eefef97f5"
|
||||
integrity sha512-L7DggusgkbubNFCRIYtCuYiLx+t5Hp8y/XIxJ3RM5mqAfxkTR1KxALNLDP9CT7xWieHDhNvgcXAdamGoi0ofDQ==
|
||||
"@gitlab/svgs@1.178.0":
|
||||
version "1.178.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.178.0.tgz#069edb8abb4c7137d48f527592476655f066538b"
|
||||
integrity sha512-m1xe5SPgpi9lSFCHHTkkGeScxkqhi7aD8qApL5F4MqCGeKF9IhELIVoMD1R6vkfjzFJh0BwFREPkuwjnAOMKfA==
|
||||
|
||||
"@gitlab/tributejs@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
|
||||
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
|
||||
|
||||
"@gitlab/ui@25.2.1":
|
||||
version "25.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-25.2.1.tgz#2c332134bbc82a6c40ff5fdb73aacccf730629d8"
|
||||
integrity sha512-bOkL2sfkovCV6MO/N70Xfe+vTdyi2Vp2efgDvOx4tHzqJllM6Y379wculi0VmdGw3X4TpmPI+zLWAAZ9vkhDAQ==
|
||||
"@gitlab/ui@25.3.1":
|
||||
version "25.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-25.3.1.tgz#7557c810397f8c4b81c5360e4642afc3f8274dfc"
|
||||
integrity sha512-vCl74UZgQ5m1caJk8O067KKYa+DP40ES2XDnM/wAc9mZAMynP0GPpePc3cmTLY8vpfzxx2A2iJr04SLgI2pxjA==
|
||||
dependencies:
|
||||
"@babel/standalone" "^7.0.0"
|
||||
"@gitlab/vue-toasted" "^1.3.0"
|
||||
|
|